배당률이라는 "대중의 집단 예측"을 데이터 모델이 넘어설 수 있는가
KHUDA 10기 토이프로젝트 3조입니다. 한국마사회 서울 경주 데이터로 승률 예측 모델을 만들고, 배당률에서 환산한 시장 확률과 정면으로 비교했습니다.
경마는 대중의 예측이 실시간으로 집계되어 숫자로 공개되는 드문 분야입니다. 보통의 예측 문제는 "정답을 얼마나 맞혔나"만 재지만, 여기에는 수만 명이 자기 돈을 걸어 만든 확률 추정치가 매 경주마다 이미 존재합니다. 그래서 질문이 하나 더 생깁니다 — 그 기준선을 넘을 수 있는가.
마지막 갱신 2026-08-23 · 7단계까지 코드와 보고서 정리 완료
시장을 정면으로 이기는 데는 실패했습니다. 대신 시장이 구조적으로 약한 구간만 골라내는 필터로 방향을 틀어 의미 있는 결과를 얻었습니다.
폴더 번호가 아래 단계를 그대로 따릅니다. 같은 번호의 src/와 reports/가 짝을 이룹니다.
| 단계 | 결과 | 코드 | 보고서 | |
|---|---|---|---|---|
| 1 | 승률 예측 모델 설계 | RandomForest ROC-AUC 0.7495 | src/2_승률모델 |
reports/2_승률모델 |
| 2 | 배당률과 대조 | 시장 0.817 > 모델 0.75 — 넘지 못함 | 02_market_gap.py |
최종보고서 |
| 3 | 시장을 따라잡으려는 시도 | 확률 정확도만 근소하게 앞섬. 적중률·수익은 아님 | src/3_시장대조 |
reports/3_시장대조 |
| 4 | 선행 연구 조사 | 시장을 이긴 사례가 사실상 없음 — 조건을 규명 | — | 문헌조사 |
| 5 | 방향 전환 — 이변 분석 | Lift 1.81배. 유의미한 신호 확인 | 03_upset.py · notebooks |
reports/4_이변모델 |
| 6 | 이변에 영향을 주는 피처 | 최근 조교량 · 직전 성적 · 휴식일 | src/4_이변모델 |
배당률 포함 모델 |
| 7 | 이변 탐지 모델과 베팅 전략 | 배당 10~50배 구간 ROI +32.0% | src/5_이변모델_선정 |
reports/5_이변모델_선정 |
4단계에서 얻은 인사이트가 5단계로 넘어가는 근거입니다. 시장 전체를 이기려 하지 말고, 시장이 약한 구간만 노려라.
배당률만으로 ROC-AUC 0.816이 나옵니다. 우리가 만든 어떤 모델도 이 선을 넘지 못했습니다.
| ROC-AUC | |
|---|---|
| 시장 (배당률) | 0.816 |
| RandomForest | 0.7507 |
| 2단계 결합 모델 | 0.7081 |
피처를 20개로 줄여도, 100개로 늘려도 마찬가지였습니다. 피처 개수의 문제가 아니었습니다.
시장 확률에 모델을 소량 섞고 온도 보정을 하면 확률 예측 정확도가 미세하게 개선됩니다. 2025-12-28 ~ 2026-08-09의 635경주를 최초 1회만 평가했습니다.
| Race Log Loss ↓ | Top-1 적중률 | |
|---|---|---|
| 시장 (M0) | 1.817348 | 37.80% |
| M2 XGBoost 후보 | 1.813446 | 37.48% |
개선폭 +0.003902, 부트스트랩 95% 신뢰구간 [+0.001085, +0.006721]로 0을 포함하지 않습니다. 다만 Top-1 적중률은 오히려 시장이 높고, 기대수익이 양수인 후보가 없어 베팅 정책은 no_bet입니다. 개선폭 0.0039는 공제율 약 20%를 넘기에 턱없이 작습니다.
"이 말이 이긴다"를 맞히는 대신 "시장이 과소평가한 복병"을 골라내는 문제로 바꿨습니다.
| 선별력 | Lift@10% 1.72배 [1.41, 2.02] — 상위 10%의 적중률이 전체 평균의 1.7배 |
| 전략 | winOdds 10~50배 구간에서 모델 상위 10%만 단승 |
| 수익 | ROI +32.0% · 부트스트랩 1,000회 중 87.9%에서 수익 |
| 이변마 유형 | 데뷔형 · 베테랑 복귀형 · 신예 깜짝형 |
모델의 가치는 예측이 아니라 필터에 있습니다. 다만 개선 실험 6가지 중 성공한 건 배당 구간 필터 하나뿐이었고 나머지는 ROI −7~−15%였습니다. 실전 판단의 근거로 쓰기엔 아직 표본이 부족합니다.
10th-toy-team3/
├── data/ 원천 데이터와 전처리 8버전
│ ├── race_entries.csv.gz API 수집 원천 56,648행 × 156열
│ └── 전처리_데이터셋/ v1~v8 × train/valid/test (gz)
│ ├── 분할안내.md 왜 시간순으로 잘랐는지 · 어떻게 불러 쓰는지
│ └── 사용정책.md v5~v8을 신규 학습에 쓰면 안 되는 이유
│
├── src/ 파이프라인 코드 — 폴더마다 01부터 실행 순서
│ ├── 0_데이터수집/ KRA API 수집 → 용도별 분리 01~02
│ ├── 1_전처리/ 결측·이상치·다중공선성 → 8버전 01~07
│ │ └── 버전비교/ v1~v8 중 무엇을 쓸지 실험 01
│ ├── 2_승률모델/ [1·2단계] 1차 모델링 · 시장 괴리 01~05
│ ├── 3_시장대조/ [3단계] 시장을 따라잡으려는 시도
│ │ ├── 방향실험/ 확률보정 · 시장결합 · 이변예측 01~04
│ │ ├── 비시장모델/ 배당률 없이 피처만으로 01~06
│ │ └── 시장확률보정/ ★ 시장에 가장 가까이 간 독립 패키지
│ ├── 4_이변모델/ [6단계] 배당률을 넣은 이변 예측 01~10
│ └── 5_이변모델_선정/ [7단계] 개선·배당필터·배분 전략 01~05
│
├── notebooks/ 탐색 분석 (노트북은 GitHub에서 바로 렌더링됩니다)
├── reports/ 단계별 보고서 — 번호가 읽는 순서입니다
│ docx 마다 같은 이름의 pdf 가 있어 클릭하면 바로 보입니다
└── docs/ 데이터 명세와 사용 지침
src/ 스크립트 |
01_ 부터 폴더 안에서의 실행 순서. 폴더가 바뀌면 다시 01부터 |
config.py · kra_client.py |
실행하는 파일이 아니라 불러 쓰는 모듈이라 번호를 붙이지 않았습니다 |
reports/ 문서 |
01_ 부터 읽는 순서. .docx와 .pdf가 같은 이름으로 짝을 이룹니다 |
결과 .csv · .png |
코드가 이 이름 그대로 읽고 써서 영문 원본을 유지했습니다. 뜻은 각 폴더 README.md의 표에 있습니다 |
config.py는 7개 폴더에 같은 내용으로 복사돼 있습니다. 각 파이프라인이 다른 폴더에 의존하지 않고
혼자 돌아가도록 한 것이라 중복이 아니라 의도입니다.
각 src/·reports/ 하위 폴더에 무엇이 들어 있고 어떤 순서로 실행하는지 적은 README.md가 있습니다.
git clone https://github.com/khuda-data/10th-toy-team3.git
cd 10th-toy-team3
pip install -r requirements.txtimport pandas as pd
VERSION = "v1_base" # 트리 모델 (RF · XGBoost · LightGBM)
# VERSION = "v2_standard" # 로지스틱 회귀 · SVM
BASE = f"data/전처리_데이터셋/{VERSION}"
dtypes = {"hrNo": str, "jkNo": str, "trNo": str, "owNo": str}
train = pd.read_csv(f"{BASE}/train.csv.gz", encoding="utf-8-sig", dtype=dtypes)
valid = pd.read_csv(f"{BASE}/valid.csv.gz", encoding="utf-8-sig", dtype=dtypes)
test = pd.read_csv(f"{BASE}/test.csv.gz", encoding="utf-8-sig", dtype=dtypes)docs/데이터.md의 「쓸 때 지킬 것」 절입니다. 아래 다섯 가지를 놓치면 결과가 조용히 틀립니다.
| 1 | hrNo 등 식별자는 문자열로 읽기. 숫자로 읽으면 앞자리 0이 날아가 조인이 깨집니다 |
| 2 | 경주 결과·사후 정보를 피처에 넣지 않기 |
| 3 | 현재 경주의 배당·베팅 정보를 피처에 넣지 않기. 이 프로젝트의 핵심 제약입니다 |
| 4 | 시간순 분할 지키기. train_test_split으로 섞으면 안 됩니다 |
| 5 | class_weight·오버샘플링 쓰지 않기. 확률 자체가 분석 대상이라 보정이 무너집니다 |
| 출처 | 한국마사회 공공데이터 API |
| 기간 | 2023-08 ~ 2026-08 |
| 원천 | 56,648 출전행 × 156열 (서울 + 부경) |
| 모델링 | 서울 32,888행 × 111열 — 경주일 순 60 : 20 : 20 분할 |
| 예측 대상 | 1착 여부 win (양성 9.6%) · 이변 upset_A upset_B |
파리뮤추얼 방식이라 배당률은 주최측이 매긴 값이 아니라 사람들이 어디에 얼마를 걸었는지의 결과입니다. 경주 내 1/배당률 합이 약 1.25이고, 이 초과분(오버라운드)이 주최측 몫입니다. 환급률 약 80%.
열 156개 설명과 분할 기준은 docs/데이터.md, data/전처리_데이터셋/분할안내.md에 있습니다.
| 이름 | GitHub | 담당 |
|---|---|---|
| 김찬진 | cj001212 |
저장소 개설 · 시장확률 보정 파이프라인 |
| 윤우진 | PLAUROS |
데이터 수집 · 전처리 · 문헌 조사 · 저장소 정리 |
| 박준석 | junseok |
EDA · 전처리 · 승률 모델링 · 이변 예측 모델과 베팅 전략 |
| 홍정의 | a01063950463-svg |
비시장 모델 피처 수 실험 |
| 이종원 | happy97908a-cmyk |
3차 모델링 6종 비교 · LightGBM 5-fold 검증 |
| 전유림 | yoolim-jeon |
이변 유형별 특징 분석 |
본 자료는 연구 결과이며 실제 베팅이나 금융 조언이 아닙니다.