Když si někdo postaví backtest engine a paper-trading bota, většinou v nějaké podobě dělá tuhle chybu:
def execute_close(position):
fill_price = market.last_price
pnl = (fill_price - position.entry) * position.size
To je broken. last_price je cena posledního trade-u, ne cena za kterou bys ty teď prodal.
Na orderbooku existují dvě hodnoty:
Když chceš prodat okamžitě, dostaneš bid. Když chceš koupit okamžitě, platíš ask. last_price je jen cena posledního trade-u — může být kdekoliv mezi nimi, nebo dokonce úplně mimo current orderbook když market je tenký.
Rozdíl mezi bid a ask se jmenuje spread. Na velmi likvidním S&P 500 ETF je 0.01%. Na tenké Polymarket "longshot" market je extrémně velký.
Vezmi market "Will Toronto Raptors win the 2026 NBA Finals?" Pre-playoff je outcome NO na ceně ~99¢. Outcome YES je longshot — třeba 0.5¢.
Backtest engine vidí last_price = 0.005 a počítá s tím že prodáš za $0.005. Realita:
| Side | Price |
|---|---|
| Bid | $0.003 |
| Last | $0.005 |
| Ask | $0.008 |
To je 60% spread. Když máš 1000 shares co backtest hodnotí na $5.00 (1000 × $0.005), reálně dostaneš za market sell $3.00. Backtest přefoukl PnL o 67%.
Bid může být $0.003 ale jen na 200 shares. Zbylých 800 shares prodáš v hlubším orderbook layeru — třeba $0.001. Average fill = $1.40 za 1000 shares. Backtest pořád říká $5.00.
Tohle je důvod proč backtest křivky vypadají krásně, ale live PnL je proti.
V /opt/copybot/copybot/risk.py mám funkci realistic_fill_price():
def realistic_fill_price(price: float, side: str, size_usd: float) -> float:
"""Bid-ask spread model na základě polymarket data analysis."""
if price < 0.05:
spread_pct = 0.20 # 20% spread na pennies
elif price < 0.10:
spread_pct = 0.10
elif price < 0.20:
spread_pct = 0.05
elif price <= 0.80:
spread_pct = 0.013 # tightest band 1.3%
elif price < 0.95:
spread_pct = 0.03
else:
spread_pct = 0.05 # near-cert tail
half = price * spread_pct / 2
if side == "BUY":
fill = price + half # paying ask
else:
fill = price - half # receiving bid
# Size impact (linear penalty over $20)
if size_usd > 20:
impact = (size_usd - 20) / 1000 * 0.02
if side == "BUY":
fill += price * impact
else:
fill -= price * impact
return max(0.001, min(0.999, fill))
Empiricky kalibrováno na 30 dní polymarket orderbook snapshots. Mid-band ($0.20-$0.80) je nejlikvidnější — 1.3% spread reálně. Penny ranges mají blow-up spread protože market makers se tam nemají rádi.
Bot F je longshot-lottery strategie — kupuje predikční trhy s prices $0.01-$0.05 kde scanner detekoval mispriced upside. Před implementací realistic-fill, paper PnL ukazoval $2,359.
Po refactoru engine na realistic-fill: $1,376.91. 42% reduction.
Ten zbytek mezi nimi byl simulated alpha — number co bych live nikdy neviděl. Backtest mě klamal že strategie umí 5x to co umí.
"Paper PnL co počítá s last_price je placebo. Paper PnL s realistic-fill je nejlepší proxy live results bez nasazení reálných peněz."
Když ti někdo na YouTube ukazuje MT4 backtest s 90% win rate a Sharpe 3.0, zeptej se:
Pokud na žádnou z těch otázek neumí jasně odpovědět, ten backtest je fan fiction. Ne mock-up live tradingu.
Realistic-fill model na Polymarketu je relativně jednoduchý — orderbook je veřejný, lze kalibrovat. Pro Forex je to násobně složitější: každý broker má jiný spread, weekend gaps, news-event spread blow-ups, Asian session vs London session differences.
Můj forex-bot to teď dělá hrubě — fixed spread 0.5 pip pro EURUSD/GBPUSD, 1.0 pip pro USDJPY/CHF, 1.5 pip pro AUD/NZD/CAD. Adekvátní pro daily trades, hrubá pro M1 scalping. To bude další iterace.
Engine kód je veřejný na GitHubu. Když najdeš v fill modelu chybu, otevři PR.