트렌딩 딥다이브 · 2026-07-22 · TrendShift Daily #5 · KACHENCE

kachence/polymm 딥다이브
— 실제 돈을 굴린 Polymarket 차익거래 봇, 코드째로 공개되다

polymm은 예측시장 Polymarket의 스포츠(주로 e스포츠) 마켓에서 몇 달간 실제 돈을 굴린 파이썬 마켓메이킹(market-making) 봇이다. 북메이커(스포츠 배당업체)의 배당에서 그들의 마진(vig)을 벗겨내 "진짜 확률"을 계산하고, Polymarket에 그보다 싸게 팔리는 값이 보이면 지정가 주문을 걸어 양쪽 결과를 다 사서 차익을 잠근다. 특별한 건 정직함이다. 대부분의 "나 트레이딩 봇 만들었다" 저장소가 README와 꿈뿐인데, 이건 실제로 거래했고 온체인 공개 지갑을 남겨 누구나 숫자를 검증할 수 있다. 저자는 "차익 부분은 +$8,293을 벌었지만, 억지로 떠안게 된 방향성 베팅이 -$3,184을 까먹어" 순수익 약 $5k에 그쳤다는 부검(post-mortem)까지 공개한다.

(저장소 kachence/polymm · Python 99.8% · 비동기 asyncio/aiohttp · Polymarket CLOB + the-odds-api + Supabase · MIT · src 62개 모듈 · 테스트 56개 파일 · "은퇴한 파이썬판"(현재는 Rust로 재작성, 이 저장소엔 없음) · 클론 소스 직접 분석)
목차
  1. 프로젝트 한 줄 요약
  2. 왜 주목받는가 — "지갑을 보여준다"는 정직함
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 — 차익거래 파이프라인과 반응형 두뇌
  5. 디렉토리 구조 해부
  6. 학습 포인트 — 여기서 훔쳐올 설계
  7. 하드웨어 / 시스템 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한 줄 요약

"진짜 거래했고, 지갑을 공개하며, 왜 실패했는지까지 적어둔" 스포츠 차익거래 봇
한 문장으로

polymm = 스포츠 배당업체의 "진짜 확률"과 Polymarket의 "파는 가격" 사이 틈을 실시간으로 찾아, 양쪽을 다 사서 차익을 잠그는 비동기 마켓메이킹 봇

같은 경기의 승패를 두고 스포츠 배당업체(북메이커)예측시장 Polymarket이 서로 다른 가격을 매긴다. 배당업체가 "A팀 승리 확률 63%"라 보는데 Polymarket에서 "A팀 승리"가 55센트($0.55, 즉 55% 확률로 값이 매겨짐)에 팔리고 있다면, 그 8%의 틈이 먹을 수 있는 마진(edge)이다.

봇은 이 틈을 자동으로 찾아 주문을 걸고, 한쪽이 체결되면 반대편 결과도 사서 "누가 이기든 이득"인 상태를 만든다. 핵심 재료는 딱 두 가지 — 더 신선한 배당더 빠른 속도다. 저자는 이 둘을 잃는 순간 봇이 돈을 못 벌게 됐다고 솔직하게 밝힌다.

먼저 무대를 이해하자. Polymarket은 "이 사건이 일어날까?"에 돈을 거는 예측시장(prediction market)이다. 각 결과는 0~1달러 사이의 "주식"처럼 거래된다. 예를 들어 "T1이 이번 경기를 이긴다" 토큰이 $0.60이면, 시장이 그 확률을 60%로 본다는 뜻이고, 실제로 T1이 이기면 그 토큰은 $1이 되고 지면 $0이 된다. polymm은 이 시장에서 사람 대신 24시간 지정가 주문을 걸고 관리하는 봇이다.

용어
예측시장 (prediction market) · Polymarket
미래 사건의 결과에 돈을 거는 거래소다. 각 결과가 0달러~1달러 사이 가격의 토큰으로 거래되고, 그 가격이 곧 시장이 믿는 확률이다($0.60 = 60%). 사건이 확정되면 이긴 결과는 $1, 진 결과는 $0으로 정산된다. Polymarket은 이더리움 계열 블록체인(Polygon) 위에서 돌아가는 대표적 예측시장으로, 모든 거래가 지갑 주소에 공개로 남는다.
용어
마켓메이킹 (market-making, 시장조성)
직접 "사자/팔자"를 급하게 체결하는 대신, 지정가 주문(limit order)을 미리 걸어두고 상대가 그 가격에 응해주길 기다리는 거래 방식이다. 늘 호가창에 주문을 얹어두어 "시장에 유동성을 대는" 역할을 한다. polymm은 자기가 계산한 공정 가격보다 유리한 값에 지정가를 걸어, 체결되면 곧바로 이득이 나도록 설계됐다.

왜 "차익거래(arbitrage)"인가? 봇은 한 시장에서 방향에 베팅해 "맞히기"를 노리는 게 아니다. 두 시장의 가격 차이만 먹는다. 이상적으로는 A팀 토큰과 B팀 토큰을 합쳐 $1 미만에 사두면, 경기 결과와 무관하게 둘 중 하나가 반드시 $1이 되므로 그 차액이 확정 이익이 된다. 이게 polymm이 노리는 구조다. 물론 현실은 이 이상이 항상 성립하지 않고, 바로 그 균열이 이 프로젝트의 가장 흥미로운 이야기(4장·2장)로 이어진다.

비유

polymm = 두 환전소의 시세 차이를 노리는 발 빠른 환전상. A 환전소는 달러를 1,300원에 팔고, B 환전소는 1,320원에 산다. 그 20원 틈을 아는 사람이 A에서 사서 B에 넘기면, 환율이 오르든 내리든 상관없이 20원을 남긴다.

polymm이 다른 점은 속도와 신선도가 전부라는 것. 남들도 같은 틈을 보고 달려들기 때문에, 배당이 조금만 늦거나 주문이 조금만 느리면 그 20원은 이미 사라진 뒤다. 저자의 봇이 "너무 느려져서" 돈을 못 벌게 된 게 정확히 이 지점이다.

먼저 짚고 갈 것
이건 "돈 버는 기계"가 아니다 — 저자가 가장 먼저 하는 경고

polymm은 은퇴한 파이썬 버전이다. 저자가 실제로 돌리는 봇은 Rust로 재작성됐고 이 저장소엔 없다. 결정적으로 스크레이퍼(신선한 배당을 긁어오는 파이프라인)가 빠져있다 — 그게 엣지의 절반이었다. 남은 건 유료 공개 API(the-odds-api) 경로뿐이라, 그대로 돌리면 "더 빠른 봇에게 기부"하게 된다.

이 문서는 돈벌이 안내가 아니라 소프트웨어 공부 자료다. 예측시장 거래에는 실제 금전 손실 위험이 있고, 지역에 따라 법적 제약이 다르다. 우리가 배울 것은 "돈 버는 법"이 아니라 비동기 오케스트레이션·반응형 이벤트 설계·정직한 엔지니어링이다.

2왜 주목받는가 — "지갑을 보여준다"는 정직함

README와 꿈뿐인 봇들 사이에서, 실거래 + 공개 지갑 + 실패 부검을 함께 내놓았다

GitHub에는 "Polymarket 봇 만들었다"는 저장소가 넘친다. 대부분은 돌려본 적 없는 코드와 장밋빛 README뿐이다. polymm이 눈에 띈 이유는 정반대의 태도 때문이다. 저자는 세 가지를 한꺼번에 공개했다.

실제 손익 (저자 블로그 공개 수치)

구분손익설명
차익(arb) 부분+$8,293양쪽을 다 잠근 "진짜 차익거래"에서 번 돈
방향성 잔여분(residual)-$3,184헤지가 안 붙어 억지로 떠안은 한쪽 베팅에서 잃은 돈
순수익≈ +$5,000몇 달간 실거래 결과. 그리고 점차 수익성이 사라짐

여기서 이 프로젝트의 가장 값진 교훈이 나온다. 봇이 건 모든 방향성 베팅은 개별적으로는 +7% 이상 엣지를 가진, 이론상 이겨야 할 베팅이었다. 그런데 그것들을 모아놓으니 돈을 잃었다. 범인은 역선택(adverse selection)이다.

용어
역선택 (adverse selection)
"내 주문이 체결되는 순간이, 하필 그 주문이 나쁜 주문일 때"인 현상이다. 마켓메이커가 지정가를 걸어두면, 정보가 더 빠른 상대는 가격이 유리하게 움직이기 직전에만 내 주문을 잡아먹는다. 즉 "체결됐다 = 남이 나보다 먼저 뭔가를 알았다"일 확률이 높다. 각 베팅의 기대값이 +여도, 체결되는 표본이 이렇게 편향되면 전체는 마이너스가 될 수 있다. polymm의 부검이 바로 이 이야기다.
비유

역선택 = 중고장터에서 "시세보다 싸게 삽니다" 팻말을 걸어둔 상황. 팻말을 보고 굳이 찾아와 파는 사람은, 대체로 그 물건에 내가 모르는 문제가 있다는 걸 아는 사람이다. 팻말 하나하나(=각 베팅)는 "시세보다 이득"처럼 보여도, 실제로 팔러 오는 표본이 죄다 하자품 쪽으로 쏠려 있으면 전체로는 손해다.

배울 점은 "평균적으로 이득인 전략"과 "실제로 체결되는 표본이 이득인 전략"은 다르다는 것. 이건 트레이딩뿐 아니라 모든 종류의 "선택되는 데이터"(설문 응답자, 병원에 온 환자, 광고를 클릭한 사용자)에 똑같이 적용되는 통계적 함정이다. polymm은 이 함정을 코드로 겪고 글로 남겼다.

그래서 트렌딩
"코드는 어렵지 않았다. 엣지가 어려웠다."

저자는 봇을 공개하면서 "코드가 어려운 부분이었던 적이 없다"고 말한다. 진짜 어려움은 신선한 배당과 속도였고, 그 둘은 이제 읽는 사람의 몫이라고 넘긴다. 이 솔직함, 그리고 "AI로 거의 다 짰지만 그게 품질의 변명은 아니다"라는 태도가 개발자들의 신뢰를 샀다.

3기술 스택 전체 지도

GPU도 프레임워크도 없다 — 순수 파이썬 비동기 + 얇은 외부 클라이언트 몇 개

polymm의 기술 선택은 화려하지 않다. 오히려 그 절제가 배울 점이다. 무거운 프레임워크(Django·FastAPI 같은 것) 없이, 파이썬 표준 asyncio 하나로 열 개 남짓한 동시 루프를 돌려 실시간 트레이딩을 처리한다. 외부 의존성은 "꼭 필요한 것"만 골라 얇게 붙였다.

계층별 지도

계층핵심 기술역할
언어/동시성Python 3.10+ · asyncio~10개 코루틴 루프를 한 이벤트 루프에서 동시 실행
네트워크 I/Oaiohttp · requests비동기 HTTP + 웹소켓 연결(배당·주문·호가)
거래소 클라이언트py-clob-client · web3Polymarket CLOB(중앙지정가호가창) 주문·서명, 지갑 연동
데이터 저장supabase(Postgres)배당·체결·포지션을 읽고 기록하는 중앙 저장소
저지연 캐시upstash-redis봇 간 상태 공유·프리픽스 네임스페이스(SPORT_/LIVE_)
배당 소스the-odds-api · playwright(스크레이퍼)공개 유료 API 경로 + (제거된) 사설 스크레이핑 파이프라인
알림Telegram Bot API체결·오류를 텔레그램으로 실시간 통보
테스트/품질pytest · pytest-asyncio · mypy56개 테스트 파일, 비동기 테스트, 정적 타입 검사
분석 대시보드streamlit공개 지갑 손익을 뜯어보는 애널리틱스 스크립트
(차세대) 스캐너Rust 사이드카 바이너리속도 병목인 스캔 루프만 Rust로 재작성, NDJSON IPC로 연결
용어
CLOB · py-clob-client
CLOB(Central Limit Order Book, 중앙지정가호가창)는 매수·매도 지정가 주문을 가격순으로 쌓아 매칭하는 전통적 거래소 방식이다(우리가 아는 주식 호가창과 같다). Polymarket은 이 CLOB 위에서 돌아가고, py-clob-client는 그 호가창에 주문을 넣고·취소하고·서명하는 공식 파이썬 라이브러리다. polymm은 지갑 개인키에서 API 자격증명(key/secret/passphrase)을 자동으로 파생해 시작한다.
용어
asyncio 코루틴 / 이벤트 루프
파이썬에서 스레드 없이 여러 작업을 번갈아 처리하는 방식이다. 한 작업이 네트워크 응답을 기다리는 동안(await) 이벤트 루프가 다른 작업으로 넘어간다. polymm은 "배당 갱신", "기회 스캔", "주문 감시", "웹소켓 수신", "헬스 서버" 등을 각각 코루틴으로 만들어 한 스레드 안에서 동시에 돌린다. 트레이딩처럼 "대부분 기다리고, 가끔 폭발적으로 바쁜" 작업에 딱 맞는 모델이다.
비유

asyncio 이벤트 루프 = 혼자 여러 냄비를 보는 요리사. 스레드를 여러 개 쓰는 건 요리사를 여러 명 고용하는 것(비싸고 서로 부딪힘)이고, asyncio는 한 요리사가 냄비 열 개를 순회하며 끓는 것만 재빨리 손보는 방식이다. 트레이딩 봇은 대부분 "물 끓기(네트워크 응답)를 기다리는" 시간이라, 요리사 한 명으로도 충분히 냄비 열 개를 감당한다.

4아키텍처 심화 — 차익거래 파이프라인과 반응형 두뇌

데이터가 흐르는 5단계 파이프라인 + 상태 변화에 즉각 반응하는 이벤트 시스템

4-1. 큰 그림: 배당이 주문이 되기까지

봇의 핵심 데이터 흐름은 한 줄로 요약된다: 배당 소스 → Supabase → 기회 스캔 → 주문 실행 → Polymarket. 그리고 Polymarket에서 오는 두 개의 웹소켓(호가·체결)이 이 흐름에 실시간으로 피드백을 준다.

[ 배당 소스 ] the-odds-api / (제거된 사설 스크레이퍼) │ 배당 수집 ▼ ┌───────────┐ OddsService 가 Supabase에서 │ Supabase │◄────── 집계된 배당을 읽어온다 │ (Postgres)│ └─────┬─────┘ │ AggregatedMatch (경기별 집계 배당) ▼ ┌──────────────────┐ vig 제거 → 공정확률 계산 │ OpportunityScanner│ Polymarket 호가와 비교 │ "틈이 있나?" │ edge >= min_edge(7%) 인가? └─────┬────────────┘ │ 기회(opportunity) ▼ ┌──────────────┐ 지정가 주문을 최우선매수호가 │ OrderExecutor │ +1센트에 건다 └─────┬────────┘ │ 주문 전송 ▼ ┌──────────────┐ │ Polymarket │ │ CLOB │ └──┬────────┬──┘ │ │ book_ws user_ws ◄── 실시간 피드백 2줄 (호가변화) (체결알림) │ │ ▼ ▼ ┌────────────────────┐ 상태 변화를 이벤트로 발행 │ BotState │ EDGE_LOST / ORDER_FILLED ... │ (반응형 상태 저장소) │ └─────────┬──────────┘ │ StateEvent ▼ ┌────────────────────┐ "이 변화에 무엇을 할까?" │ ReactiveHandler │ 엣지 잃은 주문 취소, 헤지 걸기, │ (두뇌) │ 자연 차익 스킵, 라이브 취소 └────────────────────┘

4-2. de-vig: 배당에서 "진짜 확률" 뽑아내기

모든 것의 출발점은 de-vig(디빅, 마진 제거)다. 스포츠 배당업체가 내건 배당에는 그들의 수수료(vig)가 숨어있어서, 배당을 그냥 확률로 바꾸면 합이 100%를 넘는다(그 초과분이 업체 마진). 이걸 벗겨내야 진짜 확률이 나온다.

용어
vig(비그) / overround(오버라운드)
vig(vigorish의 준말)는 배당업체가 배당에 심어둔 마진이다. 배당을 확률로 바꿔(1÷배당) 모두 더하면 100%가 아니라 105% 같은 값이 나오는데, 이 합을 overround라 하고 초과분(5%)이 곧 vig다. 이 5%가 업체의 "집세"라서, 손님은 평균적으로 늘 진다. 차익거래를 하려면 이 마진을 걷어내 공정 확률(fair probability)을 복원해야 한다.

polymm의 vig_removal.py는 이걸 가장 단순한 비례(proportional) 방법으로 처리한다 — 각 확률을 전체 합으로 나눠 100%에 맞춘다. 실제 코드의 계산을 숫자로 따라가 보자(배당 1.50 / 2.60인 경우):

배당:      A팀 1.50   ·   B팀 2.60
암시확률:  1/1.50 = 66.7%   ·   1/2.60 = 38.5%
합(overround) = 105.1%   →   vig = 5.1%   # 이 5.1%가 업체 마진

공정확률 = 각 확률 ÷ 105.1%
  A팀 fair = 66.7% / 105.1% = 63.4%
  B팀 fair = 38.5% / 105.1% = 36.6%   # 합 = 100%, 마진 제거 완료

이제 Polymarket을 본다. 만약 "A팀 승리" 토큰이 $0.55(=시장이 55%로 봄)에 팔린다면, 봇의 계산으로는 진짜 확률이 63.4%다. 그 차이 63.4% − 55% = 8.4%가 엣지(edge)다. 이게 기준선 min_edge = 7%를 넘으니 봇은 매수 주문을 건다. 만약 $0.58이었다면 엣지가 5.4%뿐이라 그냥 넘긴다.

코드가 알려주는 교훈
"구현 안 된 옵션이 조용히 다른 걸로 대체되고 있었다"

vig_removal.py 주석에는 뼈아픈 고백이 있다. 원래 더 정교한 Shin 방법method="shin"으로 고를 수 있게 해놨는데, 사실 구현이 안 돼 있어서 말없이 비례 방법으로 넘어가고 있었다. "Shin을 쓴다"고 믿었던 모든 스크레이퍼가 실은 몇 달간 비례 방법을 쓰고 있었던 것. 저자는 이 죽은 파라미터를 제거했다.

교훈: "기본값으로 조용히 넘어가는(silent fallthrough)" 코드는 버그의 온상이다. 지원 안 하는 옵션은 조용히 무시하지 말고 큰 소리로 에러를 내야 한다.

4-3. 헤지: "누가 이기든 이득"을 잠그는 법

엣지를 보고 A팀을 샀다고 끝이 아니다. A팀이 지면 손해다. 그래서 한쪽이 체결되면 봇은 곧바로 반대편(B팀)도 사서 양쪽을 합쳐 $1 미만으로 만든다. 이게 hedge_finder.py의 일이다. 숫자로 보자:

# A팀 10주를 $0.55에 매수 체결
진입 비용 = 10주 × $0.55 = $5.50

# B팀 fair value($0.366)에 헤지 주문 → 체결되면
헤지 비용 = 10주 × $0.366 = $3.66

총비용   = $5.50 + $3.66 = $9.16   # 양쪽 10주씩 확보
정산금   = 누가 이기든 이긴 쪽 10주 × $1 = $10.00
확정이익 = $10.00 − $9.16 = $0.84  (약 +9.2%)   # 결과 무관

봇은 이 헤지가 최소 이익(min_profit, 기본 7%)을 못 넘기면 아예 걸지 않는다. 문제는 4-1에서 봤듯 헤지가 항상 체결되진 않는다는 것. 헤지가 안 붙으면 A팀 한쪽만 든 방향성 잔여분(residual)이 남고, 이 표본에 역선택이 작용해 -$3,184가 났다(2장).

4-4. 반응형 두뇌: 상태가 바뀌면 즉시 행동한다

polymm에서 가장 배울 만한 설계는 반응형(reactive) 이벤트 아키텍처다. 봇은 "15초마다 전부 다시 계산"하는 폴링에만 기대지 않는다. 대신 BotState가 상태 변화를 이벤트로 발행하고, ReactiveHandler가 그 이벤트를 듣고 즉시 결정한다.

state_events.py에 정의된 이벤트 종류를 보면 봇이 무엇에 반응하는지 한눈에 보인다:

이벤트의미 · 반응
ORDER_FILLED주문 체결됨 → 헤지를 걸거나 자연차익 확인
FAIR_PROBS_UPDATED배당이 갱신됨 → 걸어둔 주문들이 아직 엣지가 있나 재검토
BID_UPDATED호가 변동 → 누가 내 위를 덮었나(outbid) 확인
EDGE_LOST엣지가 기준 밑으로 → 주문이 잡아먹히기 전에 취소
STALE_ODDS배당이 너무 오래됨 → 믿을 수 없으니 취소
MATCH_GONE_LIVE경기가 시작됨 → 프리매치 주문 정리
NATURAL_ARB_DETECTED이미 양쪽이 싸다 → 헤지 없이도 차익, 스킵 처리
비유

폴링 = 5초마다 창밖을 내다보는 경비원, 반응형 = 문이 열리면 울리는 센서. 폴링은 아무 일 없어도 계속 내다봐야 하고, 하필 두 번 내다보는 사이에 도둑이 들면 놓친다. 반응형은 변화(문 열림)가 생기는 그 순간 신호가 오므로 즉각 대응한다. 트레이딩에서 "즉각"은 곧 돈이라, 엣지가 사라지는 순간을 웹소켓으로 감지해 주문이 역선택당하기 전에 취소하는 게 이 설계의 핵심이다.

세 종류의 봇이 이 공통 뼈대(BaseBot 추상클래스)를 공유한다: SportsBot(프리매치 e스포츠·럭비), LiveBot(경기 진행 중, 더 빠르고 빡빡한 기준), SpreadBot(놀랍게도 날씨 기온 마켓!). 전략별로 다른 부분(_scan_and_execute 등)만 갈아끼우고, 웹소켓·상태·헬스서버 같은 공통 인프라는 부모가 다 제공한다.

5디렉토리 구조 해부

역할별로 칼같이 나뉜 62개 모듈 — 폴더 이름만 봐도 파이프라인이 읽힌다

polymm의 src/기능 계층별로 폴더를 나눈 교과서적 구조다. 4장의 데이터 흐름이 폴더 이름 순서와 거의 그대로 맞아떨어진다.

polymm/ ├─ src/ │ ├─ main.py # 진입점. --live / --spread 로 봇 선택 │ ├─ bots/ # 전략들 │ │ ├─ base_bot.py # ★ 공통 인프라(추상클래스) │ │ ├─ sports_bot.py # 프리매치 e스포츠·럭비 │ │ ├─ live_bot.py # 경기 진행 중 │ │ └─ spread_bot.py # 날씨 기온 마켓 │ ├─ services/ # 배당 수집·집계 │ │ ├─ odds_service.py odds_api_service.py │ │ ├─ odds_coverage_filter.py telegram_alerts.py │ ├─ core/ # 순수 계산 로직(무상태) │ │ ├─ vig_removal.py # ★ de-vig 수학 │ │ ├─ match_id.py # ★ 팀명 정규화의 단일 진실원 │ │ ├─ market_parser.py first_names.py config.py flags.py │ ├─ scanning/ # 기회 탐지 │ │ ├─ opportunity_scanner.py live_opportunity_scanner.py │ │ ├─ spread_scanner.py team_matcher.py │ │ ├─ shadow_diff.py sidecar_hydrate.py # Rust 병행검증 │ ├─ execution/ # 주문 실행·헤지 │ │ ├─ order_executor.py hedge_finder.py │ │ ├─ order_watcher.py order_adjuster.py position.py │ ├─ monitoring/ # 걸어둔 주문 감시 │ │ ├─ order_monitor.py hedge_seeker.py hedge_monitor.py │ ├─ polymarket/ # 거래소 연동 │ │ ├─ book_websocket.py # 호가 실시간 │ │ ├─ user_websocket.py # 내 체결 실시간 │ │ ├─ market_client.py weather_client.py │ ├─ state/ # 반응형 상태 시스템 │ │ ├─ bot_state.py reactive_handler.py state_events.py │ │ ├─ match_state.py order_state.py market_cache.py │ ├─ handlers/ # 웹소켓·체결 콜백 │ ├─ data/ # Supabase 기록(recorder, client) │ └─ infra/ # 헬스서버, redis, Rust 사이드카 ├─ analytics/ # 공개 지갑 손익 분석 5종 ├─ sql/ # Supabase 테이블 스키마 7개 └─ tests/ # 56개 테스트 파일(비동기 포함)
설계 포인트
core/match_id.py — "이 함수는 여기 말고 어디에도 구현하지 마"

match_id.py 맨 위엔 "팀명 정규화·매치ID 생성은 오직 이 파일에서만. 다른 데 구현하지 말고 import 해서 써라"는 규칙이 못 박혀 있다. e스포츠 팀명은 "OMG = Oh My God", "NAVI = Natus Vincere"처럼 표기가 제각각인데, 이걸 한 곳(단일 진실원, Single Source of Truth)에서만 처리하게 강제한 것. 같은 로직이 여기저기 흩어지면 반드시 어긋나기 때문이다.

6학습 포인트 — 여기서 훔쳐올 설계

트레이딩을 몰라도 가져갈 수 있는, 언어·도메인 무관한 엔지니어링 패턴들

① 반응형 이벤트 아키텍처 (상태 → 이벤트 → 반응)

"상태를 바꾸는 쪽(BotState)"과 "상태 변화에 반응하는 쪽(ReactiveHandler)"을 분리하고, 그 사이를 이벤트(StateEvent)로 잇는다. 이 패턴은 트레이딩 봇뿐 아니라 실시간 대시보드·게임·협업 도구·알림 시스템 어디에나 쓰인다. 배울 것: "무엇이 바뀌었나"를 값이 아니라 이벤트 타입으로 표현하면, 반응 로직을 한곳에 모아 테스트하기 쉬워진다.

② 비동기 오케스트레이션 — 한 루프에 열 개의 일

base_bot.pyasyncio로 스캔·감시·웹소켓·헬스체크를 동시에 돌리면서, 종료 시그널(SIGINT/SIGTERM)을 받으면 모든 태스크를 깔끔하게 취소한다. "Task was destroyed but it is pending!" 같은 흔한 async 잡음을 없애는 우아한 종료(graceful shutdown) 패턴은 그 자체로 좋은 교본이다.

③ 단일 진실원(Single Source of Truth)

match_id.py가 팀명 정규화를 독점하듯, "여러 곳에서 같은 판단을 하면 반드시 어긋난다 → 한 곳에서만"이라는 원칙. 설정값도 config.py 한 곳에 모아 세 봇이 공유한다. 규모가 커질수록 위력이 커지는 습관이다.

④ 정직한 관측 가능성(observability)과 부검

타임스탬프 찍힌 print 오버라이드, 텔레그램 실시간 알림, 그리고 "아무 공개 지갑에나 물려 손익을 뜯어보는" 분석 스크립트 5종. 특히 polymarket_attribution.py는 손익을 차익 vs 방향성 잔여분으로 쪼개, "어디서 벌고 어디서 잃었나"를 정확히 짚는다. 자기 시스템을 사후에 해부할 수 있게 만들어 두는 습관이 이 프로젝트의 백미다.

⑤ 점진적 재작성 — Rust를 "그림자"로 먼저 검증

속도 병목인 스캐너만 Rust로 다시 쓰되, 한 번에 갈아엎지 않는다. shadow_diff.py파이썬과 Rust를 동시에 돌려 결과를 비교(shadow mode)하고, 완전히 일치할 때만 Rust를 주력(primary)으로 승격한다. 리스크 큰 재작성을 안전하게 굴리는 실전 전략이다.

한 줄 정리
"도메인은 트레이딩, 배울 것은 소프트웨어 공학"

차익거래에 관심 없어도 좋다. 반응형 설계, async 종료 처리, 단일 진실원, 부검 가능한 로깅, 그림자 재작성 — 이 다섯은 어떤 실시간 백엔드에도 그대로 옮겨진다.

7하드웨어 / 시스템 요구사항

무거운 GPU는 필요 없다. 대신 "낮은 지연시간"과 세 개의 외부 계정이 필요하다

polymm은 계산이 무거운 프로젝트가 아니다. GPU도, 큰 메모리도 필요 없다. 진짜 요구사항은 속도(지연시간)외부 서비스 연결이다.

항목요구사항
런타임Python 3.10+ (비동기 asyncio). 평범한 VPS/노트북으로 충분
Polymarket 지갑개인키 + 펀더 주소. CLOB 자격증명은 개인키에서 자동 파생. 실제 자금 필요
배당 APIthe-odds-api 키(유료 공개 API). 봇은 배당 소스를 당신이 대야 돈다
SupabasePostgres 프로젝트 + sql/의 테이블 7개 생성. 배당·체결·포지션 저장소
Redis(선택)Upstash Redis — 봇 간 저지연 상태 공유
Telegram(선택)봇 토큰 + 챗 ID — 체결·오류 실시간 알림
지연시간가장 중요. "신선한 배당 + 빠른 실행"이 엣지 그 자체. 느리면 손해

실행은 세 모드로 갈린다: python src/main.py(프리매치 스포츠), --live(라이브), --spread(날씨). 기본 주문 크기는 10주로 아주 작게 시작하도록 설정돼 있다 — 저자가 "규모를 키우기 전에 한동안 지켜보고, 부검 글부터 읽으라"고 당부하는 이유다.

현실 점검
"그대로 돌리면 트레이딩이 안 된다" — 의도된 설계

저자가 못박는다: 이건 상자에서 꺼내 바로 돌아가지 않는다. 엣지의 절반이던 사설 스크레이퍼가 빠져 있고, 실제 봇은 Rust판이다. 여기 있는 파이썬판은 "작동하는 토대이자 학습 자료"이지 완제품 수익기가 아니다. 예측시장 거래는 실제 금전 손실과 지역별 법적 위험을 동반한다 — 공부용으로 읽고, 실거래는 신중히.

8직접 해볼 수 있는 실습 과제

돈을 걸지 않고도 이 코드에서 배울 수 있는 것들 — 난이도별
실습 1 · 입문 ★☆☆☆☆

de-vig 계산기를 직접 돌려보기

src/core/vig_removal.py는 파일 하나로 독립 실행된다(python src/core/vig_removal.py). 여러 배당 쌍에 대해 vig와 공정확률을 출력한다. 여기에 내가 아는 실제 배당(예: 좋아하는 팀 경기)을 넣어보고, "배당업체 마진이 몇 %인지", "Polymarket 가격과 비교하면 엣지가 있는지" 손으로 확인해 보라. 트레이딩의 출발점을 몸으로 익히는 과제.

실습 2 · 초급 ★★☆☆☆

공개 지갑 손익 분석 스크립트 읽고 돌리기

analytics/의 다섯 스크립트는 아무 공개 Polymarket 지갑 주소에나 물려 돌아간다(사설 데이터 불필요). capital_analysis.py(전체 손익), realized_pnl_by_month.py(월별)를 읽고 구조를 파악한 뒤, 유명한 공개 지갑에 물려 결과를 재현해 보라. "블록체인 공개 데이터로 남의 성적표를 읽는" 경험 자체가 배울 거리다.

실습 3 · 중급 ★★★☆☆

반응형 이벤트 시스템 미니 복제

state/state_events.pyreactive_handler.py의 구조만 떼어내, 트레이딩과 무관한 장난감 도메인(예: 재고 관리 — "재고 부족" 이벤트가 뜨면 발주)으로 옮겨 구현해 보라. @dataclass 이벤트 + 이벤트 타입 Enum + 콜백 등록 패턴을 손에 익히는 게 목표. polymm의 진짜 자산은 이 뼈대다.

실습 4 · 중상급 ★★★★☆

테스트 스위트로 안전하게 리팩터링하기

56개 테스트 파일이 있다. pytest를 돌려 초록불을 확인한 뒤, hedge_finder.py의 헤지 계산에 새 규칙(예: "수수료 2%를 뺀 뒤에도 이익이 나야 헤지")을 추가해 보라. test_hedge_finder.py가 잡아주는 범위 안에서 테스트가 있는 코드를 고치는 감각을 익히는 과제.

실습 5 · 고급 ★★★★★

"역선택"을 시뮬레이션으로 재현하기

2장의 교훈을 코드로 증명해 보라. 각 베팅이 개별적으로 +7% 엣지인데도, "가격이 나쁜 쪽으로 움직이기 직전에만 체결된다"는 편향을 넣으면 전체 손익이 마이너스로 갈 수 있음을 몬테카를로 시뮬레이션으로 보이는 것. de-vig로 공정확률을 만들고, 체결 확률을 "가격 움직임과 상관"시켜 표본을 편향시키면 된다. 통계적 함정을 뼈저리게 이해하는 최고의 방법.

9관련 기술 심화 학습 로드맵

"파이썬은 좀 아는데 실시간 시스템·트레이딩은 처음"인 사람을 위한 6주 코스
주차주제할 일
1주차비동기 파이썬 기초asyncio 코루틴·await·이벤트 루프 이해. aiohttp로 API 두 개를 동시에 호출하는 작은 스크립트 작성
2주차확률·배당·de-vig배당→암시확률→overround→vig→공정확률 흐름을 손으로 계산. vig_removal.py를 읽고 2-way/3-way 차이 파악
3주차예측시장·CLOBPolymarket이 어떻게 작동하는지(토큰=확률, 정산), 지정가 호가창(CLOB)의 매수·매도·체결 개념 학습
4주차이벤트 기반 설계state_events.py+reactive_handler.py 정독. 장난감 도메인으로 이벤트→반응 시스템 복제(실습 3)
5주차웹소켓 실시간 처리book_websocket.py/user_websocket.py로 실시간 스트림 수신·재연결·상태 갱신 패턴 학습
6주차역선택·시장미시구조2장 부검 글 3편 정독 → 실습 5(역선택 시뮬레이션). "왜 +EV가 지는가"를 코드로 증명
학습 팁

코드를 "위에서 아래로" 읽지 말고 "데이터 흐름"을 따라 읽어라. 4장의 ASCII 파이프라인을 옆에 띄워두고, 배당 한 건이 services/core/vig_removalscanning/execution/를 통과하는 여정을 파일을 오가며 따라가면, 62개 모듈이 하나의 이야기로 꿰어진다.

10핵심 키워드 사전

이 문서에 나온 용어를 한자리에
예측시장 (prediction market)
미래 사건 결과에 돈을 거는 거래소. 결과 토큰 가격($0~$1)이 곧 시장이 믿는 확률. Polymarket이 대표적.
마켓메이킹 (market-making)
지정가 주문을 미리 걸어두고 상대가 응하길 기다리는 거래 방식. 유동성을 대는 대가로 스프레드를 먹는다.
차익거래 (arbitrage)
방향에 베팅하지 않고 두 시장의 가격 차이만 먹는 거래. 양쪽 결과를 합쳐 $1 미만에 사면 결과 무관 확정이익.
vig · overround
배당업체가 배당에 심은 마진(vig). 암시확률의 합이 100%를 넘는 초과분(overround)이 그 마진.
de-vig (마진 제거)
배당에서 vig를 걷어내 공정확률(합=100%)을 복원하는 계산. polymm은 비례(proportional) 방법 사용.
edge (엣지)
공정확률과 시장 가격의 차이. polymm은 이 값이 min_edge(7%) 이상일 때만 주문한다.
hedge (헤지)
한쪽 체결 후 반대편도 사서 "누가 이겨도 이득"인 상태를 잠그는 것. min_profit(7%) 못 넘기면 안 건다.
역선택 (adverse selection)
"내 주문이 하필 나쁠 때만 체결되는" 편향. 개별 +EV여도 체결 표본이 편향되면 전체는 손해. polymm 손실의 주범.
CLOB
중앙지정가호가창. 매수·매도 지정가를 가격순으로 쌓아 매칭하는 거래소 방식. Polymarket이 사용.
asyncio / 코루틴
스레드 없이 한 이벤트 루프에서 여러 작업을 번갈아 처리하는 파이썬 비동기 모델. 대기가 많은 I/O에 최적.
반응형 아키텍처 (reactive)
상태 변화를 이벤트로 발행하고 핸들러가 즉시 반응하는 설계. 폴링보다 빠르게 변화에 대응.
shadow mode (그림자 모드)
신·구 구현(파이썬 vs Rust)을 동시에 돌려 결과를 비교·검증하고, 일치할 때만 신규를 승격하는 안전한 재작성 전략.

11참고 링크

원본부터 배경 지식까지

프로젝트

핵심 소스(먼저 읽을 파일)

기반 기술 / 배경