Anleitungen
Market-Making-Bot
Ein vollständiger Market-Making-Bot für Seeryx in TypeScript und Python: beide Seiten um den Mittelpreis quotieren, neu quotieren, Ausführungen verarbeiten, Prämien sammeln.
Die Schleife
Ein Market-Maker hält ein Kaufgebot und ein Verkaufsangebot im Orderbuch und verdient den Spread und die Maker-Rückvergütung, wenn andere mit ihm handeln. Auf Seeryx genügen zwei Kaufgebote: Ein Fällt-Kaufgebot zu q ist ein Steigt-Verkaufsangebot zu 100 − q. Der folgende Bot quotiert die laufende 5-Minuten-Runde von BTC:
- Lies die Runde, ihr Steigt-Orderbuch und dein Konto (Positionen und Orders im Orderbuch).
- Setze beide Quotierungen einige Cent ober- und unterhalb des Mittelpreises aus bestem Kaufgebot und bestem Verkaufsangebot. Ohne zweiseitiges Orderbuch gibt es keinen Mittelpreis, dann wartet der Bot.
- Quotiere neu nur, wenn sich der Mittelpreis bewegt hat oder eine Quotierung ausgeführt wurde: Storniere die Orders der Runde in einer Anfrage und platziere beide Quotierungen in einer Sammelorder, Post-Only.
- Verarbeite Ausführungen. Melde, was sich geändert hat, hör auf, auf eine Seite zu bieten, sobald genug davon gehalten wird, und lege Paare aus Steigt und Fällt wieder zu Guthaben zusammen.
- Hör vor Rundenende auf. 15 Sekunden vor dem Ende storniert der Bot alles und wartet auf die nächste Runde. Was er noch hält, wird von selbst ausgezahlt.
Der Bot
Führe ihn neben seeryx, message und chain von den vorherigen Seiten aus: npx tsx bot.ts oder python bot.py. Er meldet sich an, öffnet eine Handelssitzung (eine Wallet-Signatur) und durchläuft die Schleife alle 2 Sekunden.
// bot.ts: quotes both sides of the live BTC 5-minute round with post-only limit orders. npx tsx bot.ts
import { api, loadWallet, signIn, type Account, type Book, type Round } from './seeryx';
import { signOrder, signPair, tradingSession, type Funds, type Order } from './chain';
import { logValue } from './api-url.mjs';
const MARKET = '/markets/BTC/5m/current';
const SIZE = 10; // shares per quote (at least minOrderShares of GET /api/fees)
const EDGE = 2; // cents on each side of the midpoint
const MAX_HELD = 100; // stop bidding a side once this many of its shares are held
const STOP_S = 15; // leave the round this many seconds before its close (new orders stop 5 s before)
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
type Results = { results: { status: number; body: { order?: number; error?: string } }[] };
const wallet = await loadWallet();
await signIn(wallet);
const funds = await api<Funds>('/funds');
const { minOrderShares } = await api<{ minOrderShares: number }>('/fees');
// every order is signed by a trading session (one wallet signature now, then none)
const session = funds.onChainTrading ? await tradingSession(funds, wallet) : null;
const sign = (order: Order) => (session ? signOrder(funds, session, wallet.address, order) : order);
let quoted = '';
let seen = new Map<string, number>(); // shares held per side, to report fills
for (;;) {
try {
const round = await api<Round>(MARKET);
if (round.end - Date.now() / 1000 < STOP_S) {
// step aside before the close: what is still held pays out by itself when the round resolves
await api(`/orders?slug=${round.slug}`, { method: 'DELETE' });
await sleep((round.end + 2) * 1000 - Date.now());
quoted = '';
continue;
}
const [book, account] = await Promise.all([
api<Book>(`/orderbook/${round.slug}?side=up`), // levels are [cents, shares], best first
api<Account>('/account'),
]);
const held = (side: string) =>
account.positions.find((p) => p.slug === round.slug && p.side === side)?.shares ?? 0;
for (const side of ['up', 'down']) {
const change = held(side) - (seen.get(side) ?? 0);
if (Math.abs(change) > 1e-9) console.log('filled', side, logValue(change));
}
seen = new Map([['up', held('up')], ['down', held('down')]]);
// an Up and a Down share together always pay $1: merge pairs back into cash to free it for new quotes
const pairs = Math.floor(Math.min(held('up'), held('down')));
if (pairs >= minOrderShares) {
const signed = session ? await signPair(funds, session, wallet.address, round.slug, true, pairs) : undefined;
await api('/merge', { method: 'POST', body: { slug: round.slug, shares: pairs, signed } });
continue;
}
const bid = book.bids[0]?.[0], ask = book.asks[0]?.[0];
if (bid === undefined || ask === undefined) { await sleep(3000); continue; } // no two-sided book: no midpoint
const mid = (bid + ask) / 2;
// a Down bid at q is an Up ask at 100 - q: two bids quote both sides without holding shares
const quotes = [
{ side: 'up' as const, limit: Math.floor(mid - EDGE) },
{ side: 'down' as const, limit: Math.floor(100 - mid - EDGE) },
].filter((q) => q.limit >= 1 && q.limit <= 99 && held(q.side) < MAX_HELD);
const resting = account.orders.filter((o) => o.slug === round.slug).length;
const key = `${round.slug} ${quotes.map((q) => q.side + q.limit).join(' ')}`;
if (key !== quoted || resting !== quotes.length) { // the midpoint moved or a quote was filled: requote
await api(`/orders?slug=${round.slug}`, { method: 'DELETE' });
const base = { slug: round.slug, action: 'buy', type: 'limit', shares: SIZE, postOnly: true } as const;
const orders: Order[] = quotes.map((q) => ({ ...base, ...q })); // post only: never takes, never pays the fee
const body = { orders: await Promise.all(orders.map(sign)) };
const { results } = await api<Results>('/orders/batch', { method: 'POST', body });
results.forEach((r, i) => console.log(quotes[i].side, quotes[i].limit, logValue(r.body.order ?? r.body.error)));
quoted = key;
}
} catch (error) {
const e = error as { message: string; status?: number; retryAfter?: number };
console.error(logValue(e.message));
if (e.status === 401 || e.status === 403) await signIn(wallet); // the 7-day sign-in token expired
if (e.retryAfter) await sleep(e.retryAfter * 1000); // 429 or 503: wait as told
}
await sleep(2000);
}Ausführungen und Bestand
- Eine Ausführung ist eine Position. Ein ausgeführtes Steigt-Kaufgebot hinterlässt dir Steigt-Anteile: Sie zahlen je 1,00 $ aus, wenn „Steigt“ gewinnt, und nichts, wenn es verliert.
MAX_HELDbegrenzt, wie viel einer Seite der Bot ansammelt. - Paare sind Guthaben. Beide Seiten zu halten ergibt sichere 1,00 $ pro Paar, daher legt der Bot Paare sofort (kostenlos) wieder zu USDC zusammen und quotiert mit diesem Guthaben erneut.
- Post-Only hält jede Quotierung auf der Maker-Seite: keine Taker-Gebühr und die Rückvergütung von 20 % bei jeder Ausführung. Eine Quotierung, die das Orderbuch kreuzen würde, wird abgelehnt (
would_take) und beim nächsten Durchlauf einfach erneut platziert. - Schnellere Ausführungen. Der Bot fragt regelmäßig ab; für schnellere Reaktionen abonniere die Runde und das Konto über den WebSocket und quotiere bei jeder Nachricht neu.
- Mach ihn zu deinem: Setze Preise nach einem externen Signal (der Kursbewegung des Basiswerts seit Rundenbeginn) statt nach dem Mittelpreis des Orderbuchs, verschiebe Quotierungen gegen deinen Bestand, quotiere kommende Runden und weitere Märkte.
Liquiditätsprämien
Limit-Orders nahe dem Mittelpreis sammeln Prämienpunkte in Märkten, in denen liquidityRewards von GET /api/markets true ist (die 5-Minuten- und 15-Minuten-Krypto-Märkte), und in jedem Ereignismarkt. Einmal pro Minute wird jede Order gegen den Mittelpreis des Steigt-Orderbuchs dieses Marktes bewertet:
| Regel | Wert |
|---|---|
| Wert | Die Größe der Order im Orderbuch zu ihrem eigenen Preis, in Dollar (ein Fällt-Kauf zu p zählt als Steigt-Verkaufsangebot zu 100 − p) |
| Abstand | Wie weit die Order vom Mittelpreis aus bestem Steigt-Kaufgebot und -Verkaufsangebot entfernt ist; ab 3¢ gibt es nichts |
| Punkte | 0,1 Punkte pro Dollar Wertung und Stunde, minutenweise gutgeschrieben, pro UTC-Tag |
| Beide Seiten | +50 % in einem Markt, in dem deine Orders auf der Kauf- und der Verkaufsseite gewertet werden |
| Was zählt | Orders, die mindestens 60 Sekunden im Orderbuch liegen und mindestens die Mindestorder sowie 5 $ groß sind, in einer Live-Runde mit zweiseitigem Orderbuch |
| Obergrenze | 500 $ Wert pro Markt und Seite, die nächstgelegenen Orders zuerst |
GET /api/rewards/liquidity liefert deine Punkte, die Rate, mit der deine Orders im Orderbuch gerade Punkte sammeln, und diese Regeln als Zahlen. Punkte zählen für die Stufen von Prämien.
Bevor du ihn startest
- Halte
wallet.jsonundsession.jsonnur für dich lesbar; der Sitzungsschlüssel kann innerhalb seiner Limits mit deinem Guthaben handeln. - Reagiere auf 429, indem du
Retry-Afterabwartest; der Bot tut das bereits. - Lies beim Start
GET /api/fees: Die Sätze und die Mindestorder können sich ändern.