Polymarket Bot Tutorial · บทที่ 29 จาก 32
สร้าง Polymarket paper trading engine ก่อนใช้งานจริง: จำลองออเดอร์เทียบกับราคาจริง ติดตาม P&L บังคับใช้ 30-trade gate (win rate >=55%, +PnL) ก่อนนำเงินจริงเข้าระบบ และโค้ด skeleton
บทนี้ครอบคลุมอะไรบ้าง
Paper trading คือขั้นตอนที่ข้ามไม่ได้ระหว่างไอเดียกลยุทธ์กับการ deploy จริง บทนี้คือ paper engine แบบง่ายที่ใช้เป็น gate สำหรับทุก live bot ที่เราเคยปล่อย-ใช้ Python ไม่ถึง 200 บรรทัด ติดตามทุกเทรดใน JSONL diary และใช้ fee/slippage เหมือนเส้นทาง live
- ทำไมต้อง paper ก่อน live (เสมอ)
- 30-trade gate (ยืนยันแล้วว่า +55% WR และ positive PnL)
- การสร้าง paper engine แบบง่าย
- ติดตาม paper diary ควบคู่กับ live diary
- เมื่อ paper แตกต่างจาก live (และทำไม)
- ก้าวสู่ live: ฝากเงินจริงก้อนแรกแบบเล็ก ๆ
- Code: minimal paper engine
ทำไมต้อง paper ก่อน live (เสมอ)
30-trade paper gate คือวินัยเดียวที่แยก Polymarket traders ที่ทำกำไรได้ 7.6% ออกจาก 84.1% ที่ขาดทุน คนสร้างบอทส่วนใหญ่ข้ามขั้นนี้แล้วต้องจ่ายค่าเรียนรู้เอง เหตุผลที่แท้จริงที่มันเวิร์กคือ paper trading เผย win rate จริงของกลยุทธ์เมื่อมีตัวอย่างมากพอให้แยกสัญญาณออกจากความบังเอิญ
การข้าม paper มีต้นทุนมากกว่าที่ประหยัดได้ กลยุทธ์ที่ดูเหมือนกำไรจาก backtest แต่จริง ๆ แล้วเป็นแค่ coin flip จะเผาผลาญเงิน live $200-500 ก่อนจะได้ข้อมูล live ครบ 30 ตัวอย่าง แต่ถ้า paper-trade เทรดเดียวกัน 30 ครั้ง ต้นทุนคือ $0
paper engine ไม่จำเป็นต้องซับซ้อน สิ่งที่ต้องมีคือความซื่อสัตย์-fee เดียวกัน slippage เดียวกัน และ fill latency เดียวกันกับเส้นทาง live ยิ่งเรียบง่ายยิ่งดี เพราะสิ่งใดที่ไม่จำเป็นมักถูกตัดทิ้ง และทำให้บอทถูกปล่อย live เร็วกว่าที่ควร
30-trade gate (ยืนยันแล้วว่า +55% WR และ positive PnL)
gate นี้เป็นแบบ binary: ต้องมี paper trades ที่ปิดแล้ว 30 ครั้ง, มีเกณฑ์ความสำเร็จที่กำหนดไว้ล่วงหน้า (โดยทั่วไป WR ≥ 55% สำหรับกลยุทธ์ที่มี positive EV) ไม่เช่นนั้นก็ห้าม deploy live
30 คือ sample size ขั้นต่ำที่ช่วงความเชื่อมั่น 95% ของ win rate จริงแคบพอจะบอกได้ว่านี่คือสัญญาณหรือ noise หากต่ำกว่า 30 อัตรา observed 60% อาจสอดคล้องกับอัตราจริง 45-75% ได้ แต่ที่ 30+ ช่วงจะหดลงเหลือราว 50-70%-ยังไม่แคบมาก แต่พอที่จะตัดข้ออ้างว่า "กลยุทธ์นี้ก็แค่ coin flip"
เกณฑ์ความสำเร็จต้องตั้งไว้ก่อนเริ่ม paper run เท่านั้น ถ้ามาตั้งทีหลังจะกลายเป็น post-hoc rationalization (คุณจะหาวิธีตีความเทรด 30 ครั้งว่า "พอใช้ได้" จนได้)
การสร้าง paper engine แบบง่าย
paper engine จริง ๆ ก็คือโค้ด live trading ที่เปลี่ยนฟังก์ชันส่งออเดอร์ให้เป็น simulated fill แทน การจำลองนี้:
- อ่าน live order book: ใช้ call เดียวกับที่ live bot จะใช้
- จำลอง fill: ถ้าซื้อแบบ FOK ด้วยราคา ≥ best ask จะ fill ตาม volume-weighted average ของ asks ที่ถูก consume และบันทึก fill ลง paper diary
- คำนวณ fee: หัก fee เดียวกับที่เส้นทาง live จะจ่าย
- ติดตาม inventory: เก็บ paper-balance และ paper-positions dictionary แยกต่างหาก
engine ทั้งหมดใส่ได้ใน Python 100-200 บรรทัด วินัยสำคัญคือ ทุก assumption ที่เส้นทาง live ใช้ (fill rate, latency, fee) ต้องถูกจำลองใน paper ด้วย แม้จะดูแย่กว่าความจริงเล็กน้อยก็ตาม-paper ควรเป็น floor ไม่ใช่ ceiling
ติดตาม paper diary ควบคู่กับ live diary
paper trading run จะสร้าง JSONL diary ที่โครงสร้างแยกไม่ออกจาก live diary ที่บอทจะเขียนในภายหลัง ฟิลด์เหมือนกัน: timestamp, action, market_slug, side, size, price, expected_fill_price, simulated_pnl_at_exit
มีสองเหตุผลที่ใช้ format เดียวกัน อย่างแรก เครื่องมือวิเคราะห์ที่อ่าน live trades อยู่แล้ว (PnL reports, win-rate calculators) จะใช้กับ paper ได้โดยไม่ต้องแก้ไข อย่างที่สอง การนำ paper มาเทียบกับ live ภายหลังจะช่วยจับความแตกต่างที่บ่งชี้บั๊ก
เคล็ดลับเชิง production: ให้ paper engine เขียนไปที่ per_trade_paper.jsonl ในไดเรกทอรีเดียวกับ live per_trade.jsonl คำสั่งเดียวเทียบทั้งสองไฟล์ได้: diff -y <(jq -r .market_slug per_trade.jsonl) <(jq -r .market_slug per_trade_paper.jsonl)
เมื่อ paper แตกต่างจาก live (และทำไม)
ความแตกต่างระหว่าง paper กับ live เป็นเรื่องที่หลีกเลี่ยงไม่ได้ มี 3 แบบที่พบบ่อย
- Slippage: paper fill ที่ snapshot ของ ask แต่ live จะไล่กิน book และอาจ fill แย่ลง 1-2c ในตลาดที่บาง วิธีแก้: จำลอง slippage ใน paper โดยเพิ่มค่าปรับต่อเทรดเท่ากับครึ่งหนึ่งของ spread
- Fill latency: paper fill ทันที แต่ live ใช้เวลา 200-500ms ซึ่งในช่วงนั้นราคาอาจขยับ วิธีแก้: จำลองโดยรอและอ่าน book ใหม่ก่อนจะ "fill" ใน paper
- Adverse selection: paper สมมติว่าคุณได้ best ask แต่ live ต้องแข่งกับบอทอื่นที่อาจ lift ask นั้นไปแล้ว วิธีแก้: จำลองได้ยากกว่า; ต้องยอมรับกับตัวเองอย่างตรงไปตรงมาว่า paper มักประเมินผลลัพธ์สูงกว่าความจริง
เมื่อ paper บอกว่า +5%/เดือน แต่ live กลับรันที่ -2%/เดือน ช่องว่างนี้มักมาจากหนึ่งในสามข้อด้านบน ให้ audit ทีละข้อ แทนที่จะสรุปว่ากลยุทธ์ผิดตั้งแต่แรก
ก้าวสู่ live: ฝากเงินจริงก้อนแรกแบบเล็ก ๆ
paper ผ่าน 30 เทรดแล้ว แผน deploy live คือ:
- ฝาก $25-50 เป็น smoke-test capital ให้มองว่าเป็นค่าเรียน ถ้าขาดทุน บทเรียนก็คุ้มค่า
- รันบอทใน live mode เป็น 5-10 เทรด โดยใช้ขนาด position ขั้นต่ำ (5 shares)
- ตรวจว่า fill แต่ละครั้งตรงกับความคาดหวังจาก paper ภายใน 2c หรือไม่ หากต่างมากกว่านั้นให้สืบสวนก่อนเดินต่อ
- ถ้า 5-10 live trades ตรงกับ paper ให้ฝากเพิ่ม $200-500 แล้วรัน position ขนาดปกติ
- ถ้าไม่ตรง ให้หยุด แก้บั๊ก แล้วเริ่มใหม่จากขั้นตอน 1
ช่องว่างระหว่าง live กับ paper ที่พบบ่อยที่สุดในการ deploy ครั้งแรกคือ fee ที่ลืมคำนวณหรือประเมิน slippage ผิด การแก้ปัญหาเหล่านี้ตรงไปตรงมา สิ่งสำคัญคือจับช่องว่างให้ได้ก่อนขยาย capital
Code: minimal paper engine
Reference: simple paper engine that reads live book + simulates FOK fill.
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: ฟังก์ชัน paper sell (mirror ของ buy), paper GTC simulation (post on book ที่ราคา แล้วจำลอง fill เมื่อ mid ขยับมาถึงราคา), และการ reconcile ระหว่าง paper diary กับ diary ของ "would-have-been" live





