Guide
Bot di market making
Bot di market making completo (TypeScript e Python): quota i due lati attorno al punto medio, aggiorna, gestisci le esecuzioni e ottieni ricompense di liquidità.
Il ciclo
Un market maker tiene nel libro ordini una proposta in acquisto e una in vendita e guadagna lo spread e il rimborso maker quando altri negoziano con lui. Su Seeryx bastano due proposte in acquisto: una proposta in acquisto di Giù a q è una proposta in vendita di Su a 100 − q. Il bot qui sotto quota il round BTC da 5 minuti in corso:
- Legge il round, il suo libro ordini di Su e il tuo conto (posizioni e ordini in attesa).
- Calcola il prezzo delle due quotazioni, qualche centesimo sopra e sotto il punto medio tra il miglior prezzo in acquisto e il miglior prezzo in vendita. Senza un libro ordini con entrambi i lati non c’è punto medio, quindi attende.
- Aggiorna le quotazioni solo quando il punto medio si è spostato o una quotazione è stata eseguita: annulla gli ordini del round in una sola richiesta e inserisce entrambe le quotazioni in un unico blocco, post only.
- Gestisce le esecuzioni. Segnala cosa è cambiato, smette di comprare un lato quando ne detiene abbastanza e riconverte le coppie Su e Giù in fondi disponibili.
- Si ferma prima della chiusura. 15 secondi prima della fine annulla tutto e attende il round successivo. Ciò che detiene ancora viene pagato automaticamente.
Il bot
Eseguilo accanto a seeryx, message e chain delle pagine precedenti: npx tsx bot.ts o python bot.py. Accede, apre una sessione di trading (una firma del wallet) e ripete il ciclo ogni 2 secondi.
// 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);
}Esecuzioni e inventario
- Un’esecuzione è una posizione. Una proposta in acquisto di Su eseguita ti lascia azioni Su: pagano 1,00 $ ciascuna se vince Su e nulla se perde.
MAX_HELDlimita quanto il bot accumula di un lato. - Le coppie sono fondi. Detenere entrambi i lati vale con certezza 1,00 $ per coppia, quindi il bot riconverte subito le coppie in USDC (gratis) e quota di nuovo con quei fondi.
- Post only mantiene ogni quotazione dal lato del maker: nessuna commissione taker e il rimborso maker (20%) su ogni esecuzione. Una quotazione che incrocerebbe il libro ordini viene rifiutata (
would_take) e semplicemente inserita di nuovo al passaggio successivo. - Esecuzioni più rapide. Il bot interroga periodicamente il server; per reagire più in fretta iscriviti al round e al conto tramite il WebSocket e aggiorna le quotazioni a ogni messaggio.
- Personalizzalo: ricava il prezzo da un segnale esterno (il movimento del prezzo del sottostante dall’apertura del round) invece che dal punto medio del libro ordini, sbilancia le quotazioni in base a ciò che detieni, quota i prossimi round e altri mercati.
Ricompense di liquidità
Gli ordini limite in attesa vicino al punto medio guadagnano punti Seeryx Rewards nei mercati in cui liquidityRewards di GET /api/markets è true (i mercati crypto da 5 minuti e da 15 minuti) e in tutti i mercati sugli eventi. Una volta al minuto ogni ordine riceve un punteggio rispetto al punto medio del libro ordini di Su di quel mercato:
| Regola | Valore |
|---|---|
| valore | La quantità in attesa dell’ordine al suo prezzo, in dollari (un acquisto di Giù a p conta come una proposta in vendita di Su a 100 − p) |
| distanza | Quanto dista l’ordine dal punto medio tra il miglior prezzo in acquisto e il miglior prezzo in vendita di Su; da 3¢ in su non guadagna nulla |
| Punti | 0,1 punti per dollaro di punteggio all’ora, accreditati minuto per minuto, per giorno UTC |
| Entrambi i lati | +50% in un mercato in cui i tuoi ordini ottengono punteggio sia in acquisto sia in vendita |
| Cosa conta | Ordini rimasti in attesa per almeno 60 secondi, pari almeno all’ordine minimo e a 5 $, in un round in corso con un libro ordini su entrambi i lati |
| Tetto | 500 $ di valore per mercato e per lato, a partire dagli ordini più vicini |
GET /api/rewards/liquidity restituisce i tuoi punti, il ritmo con cui i tuoi ordini in attesa guadagnano in questo momento e queste regole in forma numerica. I punti contano per i livelli di Ricompense.
Prima di avviarlo
- Rendi
wallet.jsonesession.jsonleggibili solo da te; la chiave di sessione può negoziare i tuoi fondi entro i suoi limiti. - Gestisci i 429 attendendo
Retry-After; il bot lo fa già. - Leggi
GET /api/feesall’avvio: le aliquote e l’ordine minimo possono cambiare.