Python으로 Polymarket 백테스팅 엔진 구축하기
요약
본 가이드는 Polymarket 거래 전략의 백테스팅 엔진을 Python으로 구축하는 아키텍처를 제시합니다. 단순히 과거 수익 계산을 넘어, 전략적 결정과 실행 가정을 분리하여 테스트 가능한 시뮬레이션을 만드는 것이 핵심입니다. CLOB API를 활용해 데이터를 수집하고 Pandas로 정규화하며, 신호와 실제 거래 실행 과정을 명확히 분리하는 구조가 필요합니다.
핵심 포인트
- 백테스팅은 봇이 알 수 있었던 정보만을 근사해야 합니다.
- CLOB API의 과거 가격 엔드포인트를 활용하여 데이터를 가져옵니다.
- 데이터는 Pandas를 이용해 표준화하고, 누락된 관측치를 정확히 처리해야 합니다.
- 전략적 신호와 실제 거래 실행 로직을 명확하게 분리하는 것이 중요합니다.
Polymarket 거래 봇의 백테스트는 해당 시점에 봇이 알 수 있었고 실행할 수 있었던 것을 근사하는 경우에만 유용합니다. 과거 가격은 시장 상황을 재구성하는 데 도움을 주지만, 주문이 체결되었는지, 얼마나 많은 슬리피지(slippage)가 발생했을지, 또는 전략이 미래의 정보에 의존했는지를 자동으로 알려주지는 않습니다.
개발자에게 주어진 과제는 단순히 과거 수익을 계산하는 것이 아닙니다. 그것은 전략적 결정과 실행 가정을 분리하고 그 가정을 테스트 가능하게 만드는 시뮬레이션을 구축하는 것입니다.
이 가이드는 과거 실적이 미래 수익의 증거로 취급되지 않으면서 Polymarket 거래 전략을 평가하기 위한 실제 Python 아키텍처를 다룹니다.
- 올바른 과거 데이터로 시작하기
Polymarket은 가격 및 오더북 정보에 대한 CLOB API를 포함하여 공개 시장 데이터 인터페이스를 제공합니다. 이의 과거 가격 엔드포인트는 "/prices-history"에서 문서화되어 있습니다.
이 엔드포인트는 시장 자산 식별자를 받아 과거 가격 관측치를 반환할 수 있습니다. "market" 매개변수에는 임의의 시장 제목이나 조건 식별자가 아닌, 관련 결과 토큰 ID가 포함되어야 합니다.
현재 엔드포인트 세부 정보는 "공식 Polymarket 문서(official Polymarket documentation)"(https://docs.polymarket.com/)에서 검토할 수 있습니다.
Python을 사용한 기본적인 요청은 다음과 같습니다:
import requests
BASE_URL = "https://clob.polymarket.com"
def fetch_price_history(token_id, start_ts, end_ts):
response = requests.get(
f"{BASE_URL}/prices-history",
params={
"market": token_id,
"startTs": start_ts,
"endTs": end_ts,
"fidelity": 1,
},
timeout=15,
)
response.raise_for_status()
payload = response.json()
...
이 함수는 과거 관측치를 검색합니다. 이는 완전한 실행 이력을 생성하지 않으며, 요청된 모든 간격에 데이터가 있다는 것을 보장하지도 않습니다.
출력물을 사용하기 전에 타임스탬프를 검사하고, 누락된 기간이 있는지 확인하며, 반환되는 가격이 예상 범위 내에 있는지 확인하세요.
누락된 관측치를 0으로 조용히 대체하지 마세요. 누락된 가격은 0 가격과 같지 않습니다.
- 테스트 전에 데이터를 정규화하기
타임스탬프, 가격 형식, 누락된 관측치 처리가 일관되지 않으면 백테스트를 신뢰하기 어렵습니다.
전략 로직을 적용하기 전에 일관된 내부 표현을 만드세요.
import pandas as pd
def normalize_history(history):
df = pd.DataFrame(history)
if df.empty:
return pd.DataFrame(columns=["timestamp", "price"])
...
이 예시는 분석을 위해 데이터를 표준화합니다. 프로덕션 파이프라인은 또한 예상 스키마를 검증하고, 의심스러운 가격 변동에 플래그를 지정하며, 누락된 부분을 숨기는 대신 기록해야 합니다.
예측 시장의 경우 특히 한 가지 세부 사항이 중요합니다. "0.62"와 같은 가격은 주당 62센트의 가격을 나타냅니다. 이는 62%의 수익률과 혼동되어서는 안 됩니다.
시뮬레이션 전반에 걸쳐 가격, 주식 수량, 수수료 및 달러 가치를 별도의 필드로 유지하세요.
- 신호와 실행 분리하기
깔끔한 백테스팅 엔진은 다음을 위한 명확하게 구분된 구성 요소를 가져야 합니다:
- 데이터 수집 및 검증
- 전략 신호
- 주문 시뮬레이션
- 포지션 회계 처리
- 위험 점검
- 성과 보고
이러한 분리는 신호 로직이 변경될 때마다 실행 모델을 바꿀 필요 없이 전략을 테스트할 수 있게 합니다.
예를 들어, 전략은 추정된 확률이 시장 가격과 충분히 다를 때 매수 신호를 생성할 수 있습니다.
하지만 그 신호가 봇이 무언가를 구매했다는 것을 의미하지는 않습니다.
실행 시뮬레이터는 주문이 어떤 가격으로, 얼마나 많은 수량으로 체결될 수 있었는지 결정해야 합니다. 포트폴리오 엔진은 시뮬레이션된 체결에 따라서만 보유량을 업데이트해야 합니다.
이러한 구별은 모든 의도된 거래를 완료된 거래로 기록하는 일반적인 회계 실수를 방지합니다.
- 미래 정보 사용 피하기
Look-ahead bias는 전략이 결정을 내릴 당시에는 이용할 수 없었을 정보를 사용하는 경우에 발생합니다.
예를 들어, 어떤 전략이 12:00의 가격 관측치를 평가한다고 가정해 봅시다. 이 전략은 12:05에 도착한 데이터를 기반으로 계산된 신호를 사용하여 12:00 거래가 매력적이었는지 결정해서는 안 됩니다.
간단한 과거 시계열의 경우, 지연(lagged)된 신호가 이 원칙을 보여주는 한 가지 방법입니다:
df["previous_price"] = df["price"].shift(1)
df["price_change"] = (
df["price"] - df["previous_price"]
)
df["signal"] = (
df["price_change"] > 0
).astype(int)
이는 신호를 지연시키는 것에 대한 단순한 시연일 뿐입니다. 완전한 거래 전략이 아니며, 실행 비용이나 포트폴리오 제약 조건은 고려하지 않습니다.
더 복잡한 전략의 경우, 타이밍을 명시적으로 처리해야 합니다. 관측치가 언제 게시되었는지, 시스템이 언제 수신했는지, 전략이 언제 평가했는지, 그리고 주문이 시장에 합리적으로 도달할 수 있었던 시점은 언제였는지를 기록해야 합니다.
백테스트는 단순히 완성된 데이터셋을 타임스탬프별로 정렬하는 것이 아니라, 결정 시점에 이용 가능했던 정보를 재현해야 합니다.
- 실행 가격 모델링하기
과거의 중간값(midpoint)이 반드시 실행 가능한 가격은 아닙니다.
매수 주문은 일반적으로 이용 가능한 매도 호가(asks)와 상호작용하는 반면, 매도 주문은 이용 가능한 매수 호가(bids)와 상호작용합니다. 더 큰 규모의 주문은 여러 가격 단계를 소모하여 다른 평균 체결 가격을 산출할 수 있습니다.
매수 주문의 경우, 단순화된 시뮬레이션은 다음과 같이 추정할 수 있습니다:
def estimate_buy_cost(ask_levels, requested_shares):
remaining = requested_shares
total_cost = 0.0
filled_shares = 0.0
for price, available_shares in ask_levels:
fill = min(remaining, available_shares)
...
여기서 "ask_levels"는 가장 낮은 매도 호가부터 높은 순으로 가격과 이용 가능한 주식 수를 포함한다고 가정합니다.
이 함수는 제공된 깊이(depth)를 소모하는 비용을 추정할 뿐입니다. 과거의 주문장 깊이를 재구성하거나, 미래 체결량을 예측하거나, 대기열 우선순위를 모델링하지는 않습니다.
만약 데이터셋이 샘플링된 과거 가격만을 포함한다면, 그러한 가격만으로는 역사적 오더북(order book)을 재현했다고 정직하게 주장할 수 없습니다.
가장 강력한 역사적 데이터를 사용하고, 누락된 부분을 문서화하며, 덜 유리한 체결 가정에 결과가 얼마나 민감한지 테스트해야 합니다.
- 수수료 및 부분 체결 포함하기
시뮬레이터가 체결 가격을 추정하더라도, 적용 가능한 수수료를 고려해야 합니다.
관련 수수료 규칙을 확인하지 않고 모든 시장과 기간에 걸쳐 하나의 수수료율을 하드코딩해서는 안 됩니다. Polymarket의 현재 문서는 모델링하는 시장 및 거래 조건에 대한 진실의 원천으로 간주되어야 합니다.
유용한 체결 기록(execution record)에는 다음이 포함될 수 있습니다:
trade = {
"token_id": token_id,
"side": "BUY",
"requested_shares": 50,
"filled_shares": 35,
"average_fill_price": 0.61,
"fees": None,
"timestamp": timestamp,
}
여기서 "fees" 필드는 의도적으로 설정되지 않은 상태로 남겨둡니다. 이는 임의로 만든 기본값 대신 적용 가능한 수수료 계산을 사용하여 채워져야 합니다.
예제는 또한 부분 체결(partial fill)을 기록합니다. 실제 체결 시뮬레이터는 요청된 수량과 실제로 체결된 수량을 구별해야 합니다.
이러한 차이는 현금, 노출도(exposure), 평균 진입 가격, 그리고 후속 청산 계산에 영향을 미칩니다.
- 개별 거래가 아닌 포트폴리오 추적하기
전략은 수익성 있는 개별 거래를 보여주더라도 포트폴리오 수준에서는 성능이 저조할 수 있습니다.
예를 들어, 여러 포지션이 동일한 기초 이벤트에 의존할 수 있습니다. 각 거래를 독립적으로 평가하는 것은 위험의 집중도를 숨길 수 있습니다.
포트폴리오 엔진은 현금, 오픈 포지션(open positions), 평균 진입 가격, 실현 손익(realized profit and loss), 그리고 해결되지 않은 보유 자산을 추적해야 합니다. 또한 전략의 포지션 크기 및 노출 한도를 강제해야 합니다.
실현 이익을 단순히 역사적 진입 가격에서 최신 관찰 가격을 빼서 계산하는 것을 피하십시오. 이는 완료된 청산(exit)이 아니라 미실현 평가액(unrealized mark)일 수 있습니다.
결정(resolution) 시점까지 보유된 포지션의 경우, 시장 규칙과 적용 가능한 지급액에 따라 정산(settlement)을 모델링해야 합니다. 모든 포지션을 최종 관측된 과거 가격으로 청산할 수 있다고 가정해서는 안 됩니다.
- 전략이 보지 못한 데이터로 테스트하기
전략을 개발한 후에는 별도의 평가 기간을 확보해야 합니다.
하나의 데이터셋은 전략 개발에 사용하고, 다른 하나는 평가에 사용하세요. 만약 평가 기간을 기반으로 전략을 반복적으로 조정한다면, 그 기간은 더 이상 독립적인 테스트가 아닙니다.
누적 수익(cumulative profit) 이상의 지표를 측정해야 합니다. 최대 낙폭(maximum drawdown), 거래 횟수(trade count), 평균 순이익(average net result), 노출도(exposure), 그리고 거래와 시장 전반에 걸쳐 수익이 얼마나 집중되어 있는지를 살펴봐야 합니다.
또한, 더 보수적인 실행 가정(execution assumptions) 하에서의 성과를 비교해야 합니다.
예상 슬리피지(slippage)가 조금만 증가해도 전체 우위(edge)가 사라진다면, 이는 전략의 취약성에 대한 중요한 증거입니다.
강력한 백테스트는 단순히 매력적인 성과 차트를 만들어내는 것이 아니라, 약점을 식별하기 쉽게 만들어야 합니다.
- 백테스팅에서 페이퍼 트레이딩으로 이동하기
실제 자본을 위험에 빠뜨리기 전에, 실제 주문을 제출하지 않고 라이브 시장 데이터에 대해 전략을 실행해 보세요.
모든 신호(signal), 예상 체결(expected fill), 시뮬레이션된 체결(simulated fill), 거부된 주문(rejected order), 그리고 리스크 제한 결정(risk-limit decision)을 기록하세요. 시뮬레이션된 가격과 신호가 발생했을 때 실제로 이용 가능했던 가격을 비교해야 합니다.
이는 역사적 테스트에서는 드러나지 않을 수 있는, 지연된 신호(delayed signals), 오래된 데이터(stale data), API 중단(API interruptions), 그리고 주문 실행에 대한 비현실적인 가정과 같은 문제를 식별하는 데 도움이 됩니다.
페이퍼 트레이딩 결과를 백테스트 결과와 분리하여 유지하세요. 이들은 서로 다른 것을 테스트합니다.
어느 것도 미래의 수익성을 보장하지는 않지만, 함께 사용하면 전략이 추가 테스트를 받을 자격이 있는지 결정하는 데 더 유용한 기반을 제공합니다.
- 재현성(reproducibility)을 중심으로 연구 프로세스 구축하기
모든 백테스트는 다른 개발자가 그 결과가 어떻게 도출되었는지 이해할 수 있을 만큼 충분한 정보를 보존해야 합니다.
데이터셋 버전, 시간 창(time window), 전략 매개변수, 수수료 가정, 실행 모델, 제외된 시장, 그리고 소프트웨어 버전을 기록해야 합니다.
만약 코드 업데이트 후 결과가 변경된다면, 어떤 것이 전략의 변화인지, 데이터의 변화인지, 아니면 시뮬레이터의 변화인지를 판별할 수 있어야 합니다.
이는 특히 자동화된 트레이딩 인프라를 개발할 때 중요합니다. Dexoryn Labs는 Polymarket 거래 및 카피 트레이딩 도구를 게시하며, 이러한 시스템을 평가하려면 전략 자체와 더불어 데이터 품질, 실행 가정, 포지션 추적, 그리고 위험 통제에 주의를 기울여야 합니다.
엔지니어링 목표는 모든 백테스트가 수익성이 좋아 보이게 만드는 것이 아닙니다. 그 결과가 설명 가능하고 재현 가능하도록 만드는 것입니다.
마지막 생각
유용한 Polymarket 백테스팅 엔진은 단순히 과거 가격으로부터 수익률을 계산하는 것 이상의 역할을 합니다.
정보의 타이밍을 보존하고, 신호(signal)와 체결(fill)을 구별하며, 현실적인 실행 비용을 모델링하고, 포지션을 정확하게 추적하며, 미접근 데이터(unseen data)에 대해 전략을 테스트합니다.
과거 가격은 시작점일 뿐이며, 실제 시장의 완전한 시뮬레이션이 아닙니다.
가정이 눈에 보이도록 테스트를 구축하세요. 그런 다음 그 가정을 의심해 본 후에 결과의 신뢰성을 판단하십시오.
그것이야말로 백테스팅을 성과 차트가 아닌 연구 도구로 만드는 방식입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기