Polymarket Bot Tutorial · Chapter 29 of 32
실서비스에 배포하기 전에 Polymarket paper trading engine을 구축하세요: real price에 대해 order를 시뮬레이션하고, P&L을 추적하며, 실제 live capital 투입 전에 30-trade gate(>=55% win rate, +PnL)를 강제하고, code skeleton을 작성합니다.
이 장에서 다루는 내용
Paper trading은 strategy idea와 live deployment 사이에서 반드시 거쳐야 하는 단계입니다. 이 장은 우리가 배포한 모든 live bot에 대해 gate 역할을 해온 간단한 paper engine입니다. 200 lines가 되지 않는 Python으로 구현되며, 모든 trade를 JSONL diary에 기록하고, live path와 동일한 fees/slippage를 적용합니다.
- 왜 live 전에 paper를 하는가(항상)
- 30-trade gate(검증된 +55% WR + positive PnL)
- 간단한 paper engine 구축하기
- live diary와 함께 paper diary 추적하기
- paper가 live와 달라지는 경우(그리고 그 이유)
- live로 졸업하기: 작은 첫 deposit
- Code: minimal paper engine
왜 live 전에 paper를 해야 하는가(항상)
30-trade paper gate는 수익을 내는 Polymarket trader 7.6%와 손실을 보는 84.1%를 가르는 단 하나의 규율입니다. 대부분의 builder는 이를 건너뛰고 수업료를 냅니다. 이 방식이 효과적인 솔직한 이유는: paper trading이 충분한 sample에서 strategy의 진짜 win rate를 드러내어 signal과 luck을 구분해 주기 때문입니다.
paper를 생략하면 절약하는 것보다 더 많은 비용이 듭니다. backtest에서는 수익성 있어 보이지만 실제로는 coin flip인 strategy는 live capital $200-500을 소모한 뒤에야 30-sample size의 live data를 만들어 냅니다. 같은 30 trades를 paper-trading하는 데는 $0이 듭니다.
paper engine은 복잡할 필요가 없습니다. 정직해야 합니다. 즉, live path와 같은 fees, 같은 slippage, 같은 fill latency가 있어야 합니다. 더 단순할수록 좋습니다. 왜냐하면 선택 사항은 무엇이든 잘려나가고 bot이 예상보다 일찍 live로 배포되기 때문입니다.
30-trade gate(검증된 +55% WR + positive PnL)
gate는 이진적입니다: 30개의 closed paper trades, 사전에 정의된 성공 기준(일반적으로 positive-EV strategy에서 WR ≥ 55%), 아니면 live deployment 없음.
30은 true win rate에 대한 95% confidence interval이 signal과 noise를 구분할 만큼 충분히 좁아지는 최소 sample size입니다. 30 미만에서는 관측된 60% rate가 실제로는 45-75%의 true rate에 해당할 수 있습니다. 30 이상에서는 interval이 대략 50-70%로 좁아집니다. 여전히 넓지만, "이 strategy는 coin flip이다"를 배제하기에는 충분합니다.
성공 기준은 paper run이 시작되기 전에 반드시 설정해야 합니다. 나중에 설정하면 사후적 합리화(post-hoc rationalization)가 발생합니다(어떤 30 trades든 "충분히 좋다"고 해석할 방법을 찾게 됩니다).
간단한 paper engine 구축하기
paper engine은 본질적으로 order-placement function을 simulated fill로 바꾼 live trading code입니다. 시뮬레이션은 다음과 같습니다.
- Live order book 읽기: live bot이 호출할 것과 동일한 call을 사용합니다.
- Fill 시뮬레이션: FOK로 buy할 때 price가 best ask 이상이면, 소비된 asks의 volume-weighted average로 order를 fill하고, paper diary에 fill을 기록합니다.
- Fees 적용: live path가 지불할 동일한 fees를 차감합니다.
- Inventory 추적: parallel paper-balance와 paper-positions dictionary를 유지합니다.
전체 engine은 100-200 lines의 Python에 들어갑니다. 핵심 규율은 live path가 가정하는 모든 것(fill rate, latency, fee)을 paper에서도 재현해야 한다는 점입니다. 현실보다 약간 더 나쁘게 재현해도 괜찮습니다. paper는 ceiling이 아니라 floor여야 하기 때문입니다.
live diary와 함께 paper diary 추적하기
paper trading run은 bot이 나중에 작성할 live diary와 구조상 구별되지 않는 JSONL diary를 생성합니다. 동일한 fields: timestamp, action, market_slug, side, size, price, expected_fill_price, simulated_pnl_at_exit.
같은 format을 써야 하는 이유는 두 가지입니다. 첫째, live trades를 읽는 analysis tools(PnL reports, win-rate calculators)가 수정 없이 paper에도 작동합니다. 둘째, 나중에 paper와 live를 비교하면 bugs를 나타내는 divergences를 잡아낼 수 있습니다.
Production tip: paper engine이 live per_trade.jsonl과 같은 directory에 per_trade_paper.jsonl을 쓰게 하세요. 한 번의 command로 둘 다 비교할 수 있습니다: diff -y <(jq -r .market_slug per_trade.jsonl) <(jq -r .market_slug per_trade_paper.jsonl).
paper가 live와 달라지는 경우(그리고 그 이유)
paper와 live 사이의 divergence는 피할 수 없습니다. 흔한 경우는 세 가지입니다.
- Slippage: paper는 ask snapshot에서 fill되지만, live는 book을 따라가며 얇은 market에서는 1-2c 더 나쁘게 fill될 수 있습니다. 해결책: trade당 spread의 절반에 해당하는 penalty를 추가하여 paper에서 slippage를 시뮬레이션합니다.
- Fill latency: paper는 즉시 fill되지만, live는 200-500ms가 걸리고 그 사이 price가 움직일 수 있습니다. 해결책: paper에서 "fill"하기 전에 잠시 기다린 뒤 book을 다시 읽도록 시뮬레이션합니다.
- Adverse selection: paper는 최상의 ask를 얻는다고 가정하지만, live에서는 이미 그 ask를 들어 올린 다른 bot과 경쟁합니다. 해결책: 더 시뮬레이션하기 어렵습니다. paper가 과대평가한다는 점을 스스로에게 솔직히 인정해야 합니다.
paper에서는 +5%/month인데 live에서는 -2%/month라면, 그 차이는 대개 위 셋 중 하나입니다. strategy 자체가 틀렸다고 가정하기보다 하나씩 점검하세요.
live로 졸업하기: 작은 첫 deposit
Paper에서 30 trades를 통과했습니다. live deployment 계획은 다음과 같습니다.
- smoke-test capital로 $25-50를 deposit합니다. tuition이라고 생각하세요. 잃더라도 그 교훈의 가치는 충분합니다.
- minimum size(5 shares) position으로 bot을 live mode에서 5-10 trades 실행합니다.
- 각 fill이 paper expectation과 2c 이내로 일치하는지 확인합니다. 더 큰 gap은 계속하기 전에 조사합니다.
- 5-10 live trades가 paper와 일치하면 $200-500를 deposit하고 normal-size position을 실행합니다.
- 일치하지 않으면 중단하고, debug하고, 수정한 뒤 step 1부터 다시 시작합니다.
첫 deployment에서 가장 흔한 live-paper gap은 빠진 fee 또는 slippage misestimate입니다. 수정은 간단합니다. 중요한 것은 capital을 확장하기 전에 gap을 잡아내는 discipline입니다.
Code: minimal paper engine
Reference: live book을 읽고 FOK fill을 시뮬레이션하는 simple paper engine.
import json, time
PAPER_BAL = 10_000.0 # USD starting
positions = {} # token_id -> shares
def paper_fok_buy(token_id, max_price, size):
book = fetch_book(token_id)
# Walk asks, fill what we can within max_price
filled = 0; cost = 0
for level in book.asks:
px = float(level["price"])
if px > max_price: break
avail = float(level["size"])
take = min(avail, size - filled)
filled += take
cost += take * px
if filled >= size: break
if filled < size:
return {"status":"rejected","filled":0} # FOK semantics
global PAPER_BAL
PAPER_BAL -= cost
positions[token_id] = positions.get(token_id, 0) + filled
log_paper({"ts": int(time.time()), "action":"buy",
"token": token_id, "size": filled, "price": cost/filled})
return {"status":"matched","filled":filled,"cost":cost}
Production additions: paper sell function(buy의 mirror), paper GTC simulation(book 위에 price로 post한 뒤 mid가 price에 도달하면 fill을 시뮬레이션), paper diary와 "would-have-been" live diary 간 reconciliation.










