JRA中央競馬の予測プロジェクト。netkeiba からレース結果を収集し、特徴量を構築してLightGBM/CatBoostで複勝(3着以内)予測モデルを学習・評価・バックテストするパイプラインを提供します。2段階モデルにより、オッズ非依存の実力予測(Stage 1)と市場確率との乖離によるバリュー検出(Stage 2)を行います。複勝に加え、三連複(3着以内3頭の組み合わせ)のHarvilleモデルによる確率計算・バックテスト・予測にも対応。未来のレース(出馬表)をスクレイピングし、DBの過去戦績と組み合わせてリアルタイム予測も可能です。
# Python 3.14 が必要
uv python install 3.14
# 依存関係インストール(LightGBMにはlibompが必要)
brew install libomp # macOS
uv syncsrc/furlong/
db/
models.py # データモデル(Race, RaceResult, Enum定義)
store.py # SQLite永続化(RaceStore)
scraper/
client.py # HTTPクライアント(リトライ・間隔制御)
race_list.py # レースID収集
race_result.py # レース結果HTMLパーサー
upcoming.py # 出馬表パーサー + スクレイピング orchestration
odds.py # 単勝・複勝オッズJSON取得
horse.py # 馬ページパーサー(血統: sire_id/damsire_id抽出)
parsing_utils.py # 共通パースユーティリティ
features/
categories.py # 距離・頭数カテゴリ分類
race_class.py # レースクラス分類(新馬〜重賞・障害)
horse.py # 馬の実力系特徴量(27カラム: 成績・適性・Bayesian平滑化・欠損フラグ)
jockey.py # 騎手系特徴量(9カラム: 180d成績・距離別・Bayesian平滑化)
trainer.py # 調教師系特徴量(9カラム: 180d成績・馬場別・休養明け成績)
bloodline.py # 血統系特徴量(8カラム: 父・母父の馬場/距離/重馬場適性)
race.py # レース条件系特徴量(13カラム: 基本情報・コースID・クラス序数)
relative.py # レース内相対特徴量(6カラム: 能力順位・ギャップ・場力)
track_bias.py # 馬場バイアス特徴量(1カラム)
pace.py # ペース・脚質特徴量(7カラム: 脚質確率・意図・エントロピー)
pace_race.py # レース内ペース特徴量(4カラム: 前傾馬数・前圧力・適性スコア)
market.py # 市場残差特徴量(5カラム: 残差・サプライズ率・フラグ・期待複勝率)
builder.py # FeatureBuilder: 全特徴量の組み立て
model/
models.py # TrainedModel: LightGBM/CatBoostラッパー
train.py # TrainingPipeline: Binary + LambdaRank + Stage1学習・評価
evaluate.py # AUC-ROC, Top-N, キャリブレーション
walk_forward.py # ウォークフォワード検証(Stage 1対応)
sanity.py # 評価健全性テスト(ランダム予測ベースライン)
predict.py # 予測: 指定日のベット推奨出力(マルチ戦略対応)
predict_trio.py # 三連複予測: 確率計算・EV計算・DB保存
trio.py # 三連複確率計算(Harville + 独立法)
strategy.py # 戦略探索: フィルタ×VS閾値最適化パイプライン
review.py # ペーパートレード成績レビュー
backtest/
simulator.py # BacktestSimulator: 賭け戦略 + value_scoreスイープ
trio_simulator.py # TrioBacktestSimulator: 三連複バックテスト
roi_analysis.py # ROIAnalyzer: レース条件別ROI分析
condition_filter.py # ConditionFilter: 条件フィルタ付き戦略 + スイープ
pnl_simulation.py # P&Lシミュレーション(月別損益計算)
betting.py # payout計算共通関数
config.py # StrategyConfig + MultiStrategyConfig: TOML設定管理
main.py # CLIエントリポイント
experiments/ # 実験ログ(バックテスト結果・知見の記録)
# 2025年のレースデータを収集(デフォルト: 1月〜12月)
uv run furlong scrape
# 期間を指定して収集
uv run furlong scrape --start-date 2025-01-01 --end-date 2025-03-31
# リクエスト間隔を変更(デフォルト: 2秒、最低: 1秒)
uv run furlong scrape --interval 3.0
# DBファイルパスを指定
uv run furlong scrape --db data/furlong.db
# 一時的エラーで失敗したレースを再取得
uv run furlong scrape --retry-failures
# 後方互換: サブコマンド省略も可能
uv run furlong --start-date 2025-01-01 --end-date 2025-03-31途中で中断(Ctrl+C)しても、再実行すれば未取得分から自動的に再開します。
既存レースの複勝払戻(place_payout)と複勝オッズ(place_odds)をバックフィルします。
# 全レースをバックフィル
uv run furlong backfill-place
# 件数制限(テスト用)
uv run furlong backfill-place --limit 10
# 失敗レースを再試行
uv run furlong backfill-place --retry-failures
# リクエスト間隔を変更(デフォルト: 2秒)
uv run furlong backfill-place --interval 3.01レースあたり2リクエスト(結果ページHTML + オッズAPI/HTML)。処理済み判定はplace_oddsがNOT NULLの行の存在で行い、中断後の再開に対応しています。
既存レース結果に調教師ID(trainer_id)をバックフィルします。
# 全レースをバックフィル
uv run furlong backfill-trainer-id
# 件数制限(テスト用)
uv run furlong backfill-trainer-id --limit 10
# 失敗レースを再試行
uv run furlong backfill-trainer-id --retry-failures
# リクエスト間隔を変更(デフォルト: 1秒)
uv run furlong backfill-trainer-id --interval 2.0レース結果HTMLから調教師リンクのみを軽量抽出し、trainer_idカラムを更新します。100件ごとにバッチcommit、中断後の再開に対応。
馬ページから血統情報(父: sire_id、母父: damsire_id)を収集します。
# 全馬の血統を収集
uv run furlong scrape-bloodline
# 件数制限(テスト用)
uv run furlong scrape-bloodline --limit 10
# 失敗馬を再試行
uv run furlong scrape-bloodline --retry-failures
# リクエスト間隔を変更(デフォルト: 1秒)
uv run furlong scrape-bloodline --interval 2.0取得済みスキップ(idempotent)、失敗馬はfailed_horsesテーブルに記録(最大3回リトライ)。100件ごとにバッチcommit。
SQLiteに蓄積されたデータから、機械学習用の特徴量マトリクスを生成します。
from furlong.features.builder import FeatureBuilder
builder = FeatureBuilder("furlong.db")
df = builder.build() # 100列(モデル入力用)
df_meta = builder.build_with_metadata() # 114列(学習・バックテスト用)| # | カラム名 | 区分 | 説明 |
|---|---|---|---|
| 1 | race_id |
キー | レースID |
| 2 | horse_number |
キー/特徴量 | 馬番 |
| 3 | is_top3 |
目的変数 | 複勝圏内(3着以内)なら1。7頭以下は2着以内 |
| 基本情報 | |||
| 4 | age |
馬(基本) | 馬齢(sex_ageから抽出) |
| 5 | sex_ord |
馬(基本) | 性別序数(牡=0, 牝=1, セ=2) |
| 6 | weight_carried |
馬(基本) | 斤量 |
| 7 | race_no |
レース(基本) | レース番号 |
| 8 | race_class_ord |
レース(基本) | クラス序数(新馬=0〜重賞=6) |
| 9 | track_name_code |
レース(基本) | 競馬場コード(札幌=0〜小倉=9) |
| 10 | course_id_code |
レース(基本) | コースID(会場×馬場×距離の決定論的エンコーディング) |
| 馬: 直近成績 | |||
| 11-14 | avg_finish_last3/5, avg_last3f_last3/5 |
馬(直近成績) | 直近3/5走の平均着順・上がり3F |
| 馬: 正規化成績 | |||
| 15-16 | finish_pct_last3/5 |
馬(正規化) | 頭数正規化着順割合(直近3/5走平均) |
| 17 | finish_pct_best_last5 |
馬(正規化) | 直近5走のベスト着順割合 |
| 18 | finish_pct_std_last5 |
馬(正規化) | 直近5走の着順割合の標準偏差 |
| 19 | top3_rate_last5 |
馬(正規化) | 直近5走中の複勝率 |
| 20-21 | last3f_rank_pct_last3/5 |
馬(正規化) | 頭数正規化上がり順位割合(直近3/5走) |
| 22 | speed_slope_last3 |
馬(トレンド) | 直近3走のスピード指数線形回帰傾き |
| 馬: 適性 | |||
| 23 | avg_finish_same_dist_cat |
馬(距離適性) | 同距離カテゴリ平均着順 |
| 24-25 | win_rate_same_surface, place_rate_same_surface |
馬(馬場適性) | 同馬場勝率・複勝率 |
| 26-27 | win_rate_same_surface_smoothed/count |
馬(馬場適性) | Bayesian平滑化同馬場勝率・サンプル数 |
| 28-29 | place_rate_same_surface_smoothed/count |
馬(馬場適性) | Bayesian平滑化同馬場複勝率・サンプル数 |
| 馬: その他 | |||
| 30 | days_since_last_race |
馬(間隔) | 前走間隔日数 |
| 31 | horse_weight_diff |
馬(体重) | 馬体重前走比 |
| 32 | career_count |
馬(経験) | 過去出走回数 |
| 33-34 | avg_speed_index_last3/5 |
馬(スピード) | 直近3/5走平均スピード指数 |
| 35-36 | avg_last3f_rank_last3/5 |
馬(上がり順位) | 直近3/5走平均上がり3F順位 |
| 37 | distance_change |
馬(距離変化) | 前走との距離差(m) |
| 馬: 欠損フラグ | |||
| 38 | is_debut |
馬(フラグ) | デビュー戦フラグ(career_count=0) |
| 39 | is_few_starts |
馬(フラグ) | 少走馬フラグ(career_count≤2) |
| 40 | is_first_surface |
馬(フラグ) | 初馬場フラグ |
| 41 | is_first_distance_bucket |
馬(フラグ) | 初距離カテゴリフラグ |
| 騎手 | |||
| 42-43 | jockey_win_rate_180d, jockey_place_rate_180d |
騎手(直近) | 直近180日勝率・複勝率 |
| 44 | jockey_win_rate_dist_cat |
騎手(距離) | 距離カテゴリ別勝率 |
| 45-46 | jockey_win_rate_180d_smoothed/count |
騎手(Bayesian) | 180日Bayesian平滑化勝率・件数 |
| 47-48 | jockey_place_rate_180d_smoothed/count |
騎手(Bayesian) | 180日Bayesian平滑化複勝率・件数 |
| 49-50 | jockey_win_rate_dist_cat_smoothed/count |
騎手(Bayesian) | 距離別Bayesian平滑化勝率・件数 |
| レース条件 | |||
| 51 | bracket_number |
レース条件 | 枠番 |
| 52 | horse_count |
レース条件 | 出走頭数 |
| 53 | distance |
レース条件 | 距離(m) |
| 54 | surface_turf |
レース条件 | 芝=1, ダート=0 |
| 55-58 | track_good 〜 track_bad |
レース条件 | 馬場状態(one-hot) |
| 市場 | |||
| 59 | popularity |
市場 | 人気順(Float64) |
| 60 | implied_prob |
市場 | レース内正規化複勝オッズ確率(1/place_odds) |
| 馬場バイアス | |||
| 61 | track_bias_norm_bracket_top3 |
馬場バイアス | 同日先行レースの内外バイアス(※Stage 1では除外) |
| レース内相対 | |||
| 62 | ability_rank_in_field |
相対(能力) | レース内能力順位(降順rank) |
| 63 | ability_rank_pct |
相対(能力) | 能力順位の頭数正規化 |
| 64 | ability_gap_to_best |
相対(ギャップ) | 最強馬との能力差 |
| 65 | ability_gap_to_p3 |
相対(ギャップ) | 3番手との能力差 |
| 66 | field_strength_mean |
相対(場力) | 自馬除外の場力平均 |
| 67 | field_dispersion |
相対(場力) | 場力の分散(IQR) |
| ペース・脚質 (Phase 1) | |||
| 68 | style_p_nige |
脚質(確率) | 逃げ確率(直近5走EWMA, halflife=3) |
| 69 | style_p_senko |
脚質(確率) | 先行確率 |
| 70 | style_p_sashi |
脚質(確率) | 差し確率 |
| 71 | style_p_oikomi |
脚質(確率) | 追込確率 |
| 72 | front_intent |
脚質(意図) | 前へ行く意図(1.0×p_nige + 0.6×p_senko) |
| 73 | closing_intent |
脚質(意図) | 追込意図(0.7×p_sashi + 1.0×p_oikomi) |
| 74 | style_entropy |
脚質(自在性) | 脚質の自在性(-Σ p_i×ln(p_i)) |
| 75 | n_front_runners |
ペース(レース) | レース内前傾馬数(front_intent > 0.55の馬数) |
| 76 | sum_front_intent |
ペース(レース) | レース内front_intent合計 |
| 77 | race_front_pressure |
ペース(レース) | 前圧力(sum_front_intent / 出走頭数) |
| 78 | pace_fit_score |
ペース(適性) | ペース適性スコア(ペースシナリオ別複勝率の加重期待値) |
| 市場残差 (Phase 1) | |||
| 79 | market_resid_last5 |
市場残差 | EWMA市場残差(直近5走, halflife=3) |
| 80 | positive_surprise_rate |
市場残差 | 直近5走の期待上振れ率 |
| 81 | crowd_underbet_flag |
市場残差 | 過小評価フラグ(resid > 0.05, Int8) |
| 82 | crowd_overbet_flag |
市場残差 | 過大評価フラグ(resid < -0.05, Int8) |
| 83 | expected_top3_current |
市場残差 | 現レースオッズからの期待複勝率(※Stage 1では除外) |
| 調教師 (Phase 2) | |||
| 84-85 | trainer_win_rate_180d, trainer_place_rate_180d |
調教師(直近) | 直近180日勝率・複勝率 |
| 86 | trainer_rides_180d |
調教師(直近) | 直近180日出走数 |
| 87-88 | trainer_win_rate_180d_smoothed, trainer_place_rate_180d_smoothed |
調教師(Bayesian) | 180日Bayesian平滑化勝率・複勝率 |
| 89 | trainer_surface_place_rate |
調教師(馬場) | 同馬場複勝率(全期間) |
| 90 | trainer_surface_place_rate_smoothed |
調教師(Bayesian) | 同馬場Bayesian平滑化複勝率 |
| 91 | trainer_layoff_place_rate |
調教師(休養明) | 休養明け馬(60日超)の複勝率 |
| 92 | trainer_layoff_place_rate_smoothed |
調教師(Bayesian) | 休養明けBayesian平滑化複勝率 |
| 血統 (Phase 2) | |||
| 93-94 | sire_surface_place_rate, sire_surface_place_rate_smoothed |
血統(父) | 父の産駒の同馬場複勝率・Bayesian平滑化 |
| 95-96 | sire_distance_place_rate, sire_distance_place_rate_smoothed |
血統(父) | 父の産駒の同距離カテゴリ複勝率・Bayesian平滑化 |
| 97-98 | sire_wet_place_rate, sire_wet_place_rate_smoothed |
血統(父) | 父の産駒の重馬場(重/不良)複勝率・Bayesian平滑化 |
| 99-100 | damsire_surface_place_rate, damsire_surface_place_rate_smoothed |
血統(母父) | 母父の産駒の同馬場複勝率・Bayesian平滑化 |
メタデータ列(build_with_metadata() のみ): race_date, win_odds, place_odds, place_payout, finish_position, jockey_name, trainer_name, trainer_id, sire_id, damsire_id, race_class_category, distance_category, surface, horse_count_category
馬場適性率・騎手成績率にBayesian平滑化を適用: (n × 実績率 + 5 × 事前率) / (n + 5)。事前率は勝率=0.08、複勝率=0.25。サンプル数が少ない馬・騎手の過学習を抑制します。
ability_core = 0.4×speed_index + 0.3×finish_pct + 0.3×(1-last3f_rank_pct) を基に、レース内での順位・ギャップ・場の強さを計算。能力コアがnullの馬(デビュー馬等)は全相対特徴量がnull。
passing_positions(通過順位、例: "05-05-04-03")を解析し、各馬の脚質傾向とレース展開予測を特徴量化します。
- 脚質分類: 通過順位の正規化位置(
early_pct)と直線での順位変動(stretch_gain)から4分類(逃げ/先行/差し/追込) - 脚質確率: 直近5走のEWMA(halflife=3)で各脚質の確率を算出。
front_intent/closing_intentは線形結合 - ペースラベル: レース全体のペースを判定(high/mid/slow)。前半組の崩れ具合と後方組の追い上げで分類
- pace_fit_score: 予測されるペースシナリオ(
race_front_pressure由来)下での各馬の複勝率期待値。ハイペースで好走する追込馬が高スコアに - 入力フォールバック:
passing_positionsがDBに無い場合は全ペース特徴量がnull
オッズから計算される「市場の期待」に対する実績の乖離(残差)を特徴量化します。
- expected_top3: オッズ帯×頭数帯×クラス帯の累積日次集計から算出(当日データ除外でリーケージ防止)。階層フォールバック(primary → オッズ×頭数 → オッズのみ → global prior 0.25)
- market_resid:
is_top3 - expected_top3。正=期待以上の成績、負=期待以下 - crowd_underbet/overbet_flag: EWMA残差が±0.05を超える馬にフラグ
- expected_top3_current: 現レースのオッズから算出したリアルタイム期待複勝率(Stage 2のみ使用)
- 入力フォールバック:
win_oddsがDBに無い場合はhistory系4列がnull。target側にwin_oddsがあればexpected_top3_currentはglobal prior(0.25)を返却
trainer_idを使って調教師の成績傾向を特徴量化します。騎手特徴量と同パターン(180日窓・Bayesian平滑化)。
- 180日成績: 直近180日の勝率・複勝率・出走数(日次集約、過去日のみ)
- 馬場別成績: 同馬場(芝/ダート)での複勝率(全期間)
- 休養明け成績: 60日超の間隔が空いた馬の複勝率(全期間)
- Bayesian平滑化: 全率にN=5、prior=0.08(勝率)/0.25(複勝率)で適用
horse_bloodlineテーブルのsire_id・damsire_idを使い、産駒の過去成績から血統適性を特徴量化します。
- 父の馬場適性: 同馬場(芝/ダート)での産駒複勝率
- 父の距離適性: 同距離カテゴリでの産駒複勝率
- 父の重馬場適性: 重・不良馬場での産駒複勝率
- 母父の馬場適性: 同馬場での産駒複勝率
- 全率にBayesian平滑化(N=5, prior=0.25)を適用
speed_index = (base_time - finish_time) / std_time(正=速い=良い)。ベースラインは同距離・同馬場・同馬場状態の累積日別集計から計算(O(N log N))。サンプル不足時は馬場状態を除外した(距離, 馬場)フォールバックを使用。
同日同会場の先行レース(race_number < 自レース)の3着以内馬の正規化枠番平均。低値=内枠有利、高値=外枠有利。芝・ダートは独立して計算。
- 一次ソース:
place_payout > 0(バックフィル済み複勝払戻に基づく実データ) - フォールバック(
place_payoutがNULLの場合のみ):finish_position <= 3(horse_count <= 7なら<= 2) - フォールバック使用件数はログ出力で監視可能
- 馬・騎手特徴量:
past_date < target_date(厳密小なり)。同日レース不可視 - ペース・脚質: 過去レースのpassing_positionsのみ使用。当該レースの通過順位は不使用
- market_resid: 各past raceのis_top3とexpected_top3から算出。全て過去データのみ
- expected_top3 lookup: 累積日次集計 + shift(1)で当該日のデータを除外(speed index baselineと同パターン)
- 馬場バイアス: 同日の先行レース結果を使用(実運用で利用可能な情報)
| カテゴリ | 距離 |
|---|---|
| sprint | 〜1400m |
| mile | 1401〜1600m |
| middle | 1601〜2200m |
| long | 2201m〜 |
# 基本パイプライン(Binary分類のみ)
uv run furlong train
# LambdaRankモデルも学習
uv run furlong train --with-ranker
# ウォークフォワード検証も実行
uv run furlong train --with-walk-forward
# 2段階モデル(LightGBM vs CatBoost + value_score検証)
uv run furlong train --stage1
# 全機能有効化(Binary + LambdaRank + ウォークフォワード + Stage 1)
uv run furlong train --full
# DBやモデル出力先を指定
uv run furlong train --db furlong.db --output-dir models/- 特徴量生成: FeatureBuilderで100列のDataFrameを構築
- 時系列分割:
max(race_date) - 90日をカットオフとしてtrain/valに分割 - Binary LightGBM学習: 学習前にtrain末尾を校正用ホールドアウトとして分離(データ十分時)、binary classification(複勝予測、
is_unbalance=True)、early stopping 50 - 確率校正: 校正ホールドアウト上で
none / isotonic / sigmoidを比較し、Brier→LogLoss最小の方式を選択して検証確率へ適用 - 評価: AUC-ROC、Top-N的中率、キャリブレーション
- バックテスト: 7種の賭け戦略でROIをシミュレーション
- LambdaRank学習(
--with-ranker時): NDCG@1/3/5で最適化、Top-N固定ベット評価 - ウォークフォワード検証(
--with-walk-forward時): 4期間の時系列交差検証 - Stage 1: 2段階モデル(
--stage1時):- LightGBM/CatBoostでオッズ非依存の実力予測(
popularity,implied_prob,track_bias_norm_bracket_top3,expected_top3_currentを除外) - value_score(= pred_prob / implied_prob)スイープで最適閾値検出
- レース条件別(クラス/頭数/馬場/距離)ROI分析
- profitable条件の自動検出と条件フィルタ比較
- ウォークフォワードStage 1比較(
--with-walk-forward併用時)
- LightGBM/CatBoostでオッズ非依存の実力予測(
| 戦略 | ベット対象 | 的中条件 | 払戻 |
|---|---|---|---|
| 複勝 | 各レース予測確率1位に100円 | is_top3 == 1 | place_payout × bet_amount / 100 |
| 期待値 | pred_prob × place_odds > 閾値の馬に各100円 | is_top3 == 1 | place_payout × bet_amount / 100 |
| バリュー | モデル順位が市場順位より高い馬(rank_divergence ≥ 閾値) | is_top3 == 1 | place_payout × bet_amount / 100 |
| Top-N固定 | rankerスコア上位N頭に各100円 | is_top3 == 1 | place_payout × bet_amount / 100 |
| Value Score | value_score > 閾値 かつ pred_prob > min_prob | is_top3 == 1 | place_payout × bet_amount / 100 |
| モデル | 目的関数 | 評価指標 | ベット戦略 |
|---|---|---|---|
| Binary (LGBMClassifier) | binary_logloss | AUC-ROC, Top-N, EV sweep | 複勝/期待値/バリュー |
| LambdaRank (LGBMRanker) | lambdarank | NDCG@1/3/5, Top-N | Top-N固定ベット |
| Stage 1 LightGBM | binary_logloss | AUC-ROC (オッズ非依存) | Value Score(複勝) |
| Stage 1 CatBoost | Logloss | AUC (カテゴリ特徴量対応) | Value Score(複勝) |
| ファイル | 内容 |
|---|---|
models/model.txt |
Binary LightGBM native形式 |
models/model.pkl |
Binary pickle形式 |
models/model_calibration.pkl |
Binary確率校正アーティファクト(方式 + calibrator) |
models/training_meta.json |
特徴量順序・パラメータ・評価指標(auc_roc_raw/auc_roc_eval、校正方式とBrier/LogLoss比較を含む) |
models/model_ranker.txt |
LambdaRank native形式(--with-ranker時) |
models/model_ranker.pkl |
LambdaRank pickle形式 |
models/training_meta_ranker.json |
Rankerメタデータ |
reports/backtest_balance.csv |
各戦略の収支推移 |
models/model_stage1_lgb.pkl |
Stage 1 LightGBM(--stage1時) |
models/model_stage1_cb.pkl |
Stage 1 CatBoost(--stage1時) |
reports/stage1_comparison.csv |
LightGBM vs CatBoost比較(--stage1時) |
reports/value_score_sweep.csv |
Value Scoreスイープ結果(--stage1時) |
reports/roi_by_condition.csv |
レース条件別ROI(--stage1時) |
reports/walk_forward_stage1.csv |
WF Stage 1結果(--stage1 --with-walk-forward時) |
models/training_meta_stage1.json |
Stage 1学習メタデータ(--stage1時) |
config.toml |
最適戦略設定(strategyコマンドで生成) |
reports/filter_sweep.csv |
フィルタ×VS閾値スイープ結果(strategyコマンドで生成) |
# 条件フィルタ×VS閾値の最適化 → ウォークフォワード検証 → config.toml生成
uv run furlong strategy
# 特定プロファイルを更新(デフォルト: default)
uv run furlong strategy --profile aggressive
# ウォークフォワード検証をスキップ(高速)
uv run furlong strategy --skip-wf
# DB・モデル・出力先を指定
uv run furlong strategy --db furlong.db --model models/model_stage1_lgb.pkl --output config.toml処理フロー:
- 特徴量生成 → Stage 1モデルで検証データ予測
- 4つの候補戦略フィルタ(少頭数、少頭数×ダート、少頭数×OP、ダート×中距離)× VS閾値(1.5〜3.0)のスイープ
- 戦略選定:
bets/race 1〜3を満たす候補から、収縮ROI(shrinkage_factor=100)+ 最大DD制約(max_drawdown=5000)で選定(候補なし時は段階的に制約緩和) - Nestedウォークフォワード検証(outer=4期間、inner=4期間)で内側最適化/外側OOSを実施。Nestedで有効期間がない場合は単純WFにフォールバック
- P&Lシミュレーション(100円/1000円固定ベット)
config.tomlに最適戦略を保存(マルチ戦略フォーマット)
補足:
- 戦略コマンドのWFでは、初期フィルタ最適化に使った検証期間(
training_meta_stage1.jsonのval_date_start以降)を除外して評価し、in-sample化を回避します。
[model]
type = "lightgbm"
path = "models/model_stage1_lgb.pkl"
[database]
path = "furlong.db"
[strategy.conservative]
vs_threshold = 3.0
min_prob = 0.15
[strategy.aggressive]
vs_threshold = 7.0
min_prob = 0.15
[strategy.aggressive.conditions]
horse_count_category = ["~8頭", "9~12頭"]
surface = ["ダ"]複数の戦略プロファイルを定義し、predict コマンドで切り替えて使用できます。旧形式([strategy] に直接パラメータを記述)も自動検出して互換ロードされます。
# ランダム予測でバックテスト(VS>3.0、10回反復)
uv run furlong test-sanity
# VS閾値・反復回数を指定
uv run furlong test-sanity --vs-threshold 2.0 --iterations 20
# DB指定
uv run furlong test-sanity --db furlong.db各馬にランダムな確率(0〜1の一様乱数)を付与してValue Scoreバックテストを実行します。ランダム予測でROIがプラスであれば「⚠ 評価系にバイアスあり」と警告表示し、評価パイプラインの見直しが必要なことを示します。
毎週の予測→結果取込→振り返りを効率化するコマンド群です。
# 金曜夜: 今週末(土日)の予測を実行・保存
uv run furlong predict-weekend --save
# 月曜夜: 直近の土日のレース結果を取込
uv run furlong update-results --weekly
# 月曜夜: 直近の土日の成績レビュー
uv run furlong review --weekly今週の土曜・日曜の日付を自動算出し、両日分の予測を実行します。
# 表示のみ(保存しない)
uv run furlong predict-weekend
# 予測をDBに保存(既に保存済みの日はスキップ)
uv run furlong predict-weekend --save
# 保存済みでも強制再実行
uv run furlong predict-weekend --save --force
# 戦略・設定ファイル指定
uv run furlong predict-weekend --save --strategy all --config config.toml--save 指定時、既にDBに同日・同戦略の予測が保存されていればスキップして「スキップ: 2026-03-07(保存済み)」と表示します。--force でスキップを無効化できます。
指定日のレース結果をnetkeibaからスクレイピングしてDBに保存します。既にDB内にあるレースはスキップし、永続的なエラー(404等)のレースも除外します。取込後は予測の自動照合を実行します。
# 特定の日付の結果を取込
uv run furlong update-results --date 2026-02-28
# 当日の結果を取込
uv run furlong update-results --date today
# 直近の土日の結果を一括取込
uv run furlong update-results --weekly
# リクエスト間隔を変更(デフォルト: 2秒、最低: 1秒)
uv run furlong update-results --weekly --interval 3.0predict コマンドの --date に today を指定すると当日日付に自動変換されます。
uv run furlong predict --date today --strategy all# 過去の日付(DB内に結果がある場合)
uv run furlong predict --date 2025-12-01
# 未来の日付 → 出馬表を自動スクレイピングして予測
uv run furlong predict --date 2026-03-01 --strategy all
# 予測をDBに保存(ペーパートレード用)
uv run furlong predict --date 2026-03-01 --strategy all --save
# 特定の戦略プロファイルを指定
uv run furlong predict --date 2026-03-01 --strategy conservative
# config.tomlの戦略設定を使用
uv run furlong predict --date 2026-03-01 --config config.toml
# モデル・DBを明示指定
uv run furlong predict --date 2026-03-01 --model models/model_stage1_lgb.pkl --db furlong.db出力: 戦略ヘッダー付きで、レースごとにグループ化されたRichテーブル。会場名・レース番号・レース名・馬番・馬名・予測確率・複勝オッズ・Value Score・備考を表示します。VS値は10.0でキャップされます。min_career_count(デフォルト3)未満の出走歴の馬はベット推奨から除外されます。--strategy all時、同一設定の戦略は表示上統合されます(DB保存は元の戦略名ごと)。
自動フロー分岐: DBに対象日の完了済みレース結果があれば既存DBフロー、なければ出馬表をスクレイピングして予測します。出馬表フローでは:
- race.netkeiba.com から出馬表とオッズを取得
- DBの過去戦績を使って馬・騎手の特徴量を計算
- 障害レースは自動除外、取消・除外馬はエントリから除外
- オッズ未発売の馬は value_score=NaN となり戦略フィルタで除外
--save フラグを付けると予測結果をDBの predictions テーブルに保存し、レース結果との自動照合を行います。--strategy 未指定時は、default プロファイルまたは唯一のプロファイルが使用されます(複数プロファイル存在時はエラー)。
# 特定日の成績レビュー(ベット詳細表示付き)
uv run furlong review --date 2025-03-01
# 期間指定の成績レビュー
uv run furlong review --from 2025-01-01 --to 2025-03-31
# 全期間の成績レビュー
uv run furlong review
# 直近の土日の成績レビュー
uv run furlong review --weekly
# 強制再照合(結果データ更新後など)
uv run furlong review --date 2025-03-01 --force-reconcileレビューコマンドは保存済みの予測をレース結果と照合し、戦略別の成績を表示します:
- 戦略別集計: ベット数、的中数、複勝率、投資額、回収額、ROI、返金件数
- 未照合の予測件数(結果データ未スクレイピング or 複勝払戻未取得のケース)
- 単日指定時は個別ベットの詳細(着順、結果、複勝オッズ、払戻)も表示
is_top3モデルの確率出力を活用し、三連複(3着以内3頭の組み合わせ、順不同)の期待値ベッティングを行います。
# 既存レースの三連複払戻データをバックフィル
uv run furlong backfill-trio
# 件数制限
uv run furlong backfill-trio --limit 100
# リクエスト間隔を変更(デフォルト: 2秒)
uv run furlong backfill-trio --interval 3.0scrape / update-results コマンドでも三連複払戻は自動的にパース・保存されます。
# Top-N戦略でバックテスト
uv run furlong backtest-trio --top-n 5 10 20 50
# 確率計算手法を指定(harville / independent)
uv run furlong backtest-trio --method harville
# アンカー戦略も実行
uv run furlong backtest-trio --with-anchor
# モデル・DBを指定
uv run furlong backtest-trio --model models/model_stage1_lgb.pkl --db furlong.dbバックテスト戦略:
| 戦略 | ベット対象 | 説明 |
|---|---|---|
| top-N | 各レースでHarville確率上位N組み合わせ | N=5,10,20,50 |
| anchor-N | Top-1馬を軸に相手上位N頭からのC(N,2)組み合わせ | N=3,5,7 |
| random-N | ランダムN組み合わせ(ベースライン) | 100回反復平均 |
# 指定日の三連複予測
uv run furlong predict-trio --date 2026-03-08 --top-n 10
# オッズも取得してEV計算
uv run furlong predict-trio --date 2026-03-08 --with-odds
# 予測をDBに保存
uv run furlong predict-trio --date 2026-03-08 --save
# 確率計算手法を指定
uv run furlong predict-trio --date 2026-03-08 --method independent# 全期間の三連複成績レビュー
uv run furlong review-trio
# 期間指定
uv run furlong review-trio --from 2026-01-01 --to 2026-03-31
# 直近の土日
uv run furlong review-trio --weekly
# 強制再照合
uv run furlong review-trio --force-reconcile- Harville法: 各馬のstrength score (
s = p / (1-p)) を使い、条件付き確率の6順列の合計で三連複確率を算出。P(trio {a,b,c}) = Σ P(1st=x, 2nd=y, 3rd=z)(x,y,zはa,b,cの全順列) - 独立法:
P(i,j,k) = p_i × p_j × p_k / Z(Z = 全組み合わせの積の和で正規化) - 16頭立て: 16C3=560組み合わせ × 6順列 = 3,360回の計算
| カラム | 型 | 説明 |
|---|---|---|
| race_id | TEXT PK | 12桁のnetkeiba ID |
| race_date | TEXT | 開催日 |
| venue | TEXT | 競馬場名(中山、東京 等) |
| race_number | INTEGER | レース番号(1-12) |
| race_name | TEXT | レース名 |
| surface | TEXT | 芝 / ダ / 障 |
| distance | INTEGER | 距離(m) |
| weather | TEXT | 天候 |
| track_condition | TEXT | 馬場状態 |
| カラム | 型 | 説明 |
|---|---|---|
| race_id | TEXT | 外部キー |
| horse_number | INTEGER | 馬番 |
| finish_position | INTEGER | 着順(取消等はNULL) |
| horse_name | TEXT | 馬名 |
| horse_id | TEXT | netkeiba馬ID |
| jockey_name | TEXT | 騎手名 |
| finish_time_seconds | REAL | タイム(秒) |
| win_odds | REAL | 単勝オッズ |
| place_odds | REAL | 複勝オッズ(min/max平均) |
| place_payout | INTEGER | 複勝払戻(100円単位) |
| horse_weight | INTEGER | 馬体重 |
| prize_money | REAL | 賞金(万円) |
| trainer_id | TEXT | 調教師ID(netkeiba) |
※ 主要カラムのみ記載。全カラムは store.py を参照。
| カラム | 型 | 説明 |
|---|---|---|
| horse_id | TEXT PK | netkeiba馬ID |
| sire_id | TEXT | 父馬ID |
| damsire_id | TEXT | 母父馬ID |
| created_at | TEXT | 作成日時 |
| カラム | 型 | 説明 |
|---|---|---|
| strategy | TEXT | 戦略プロファイル名 |
| race_id | TEXT | レースID |
| race_date | TEXT | レース日 |
| horse_number | INTEGER | 馬番 |
| pred_prob | REAL | 予測確率 |
| value_score | REAL | バリュースコア |
| place_odds | REAL | 複勝オッズ(予測時) |
| bet_amount | INTEGER | ベット額(デフォルト: 100) |
| finish_position | INTEGER | 着順(照合後) |
| is_place | INTEGER | 複勝的中フラグ(照合後) |
| settled_place_odds | REAL | 確定複勝オッズ(照合後) |
| payout | INTEGER | 払戻額(照合後: place_payout × bet_amount / 100) |
| reconciled_at | TEXT | 照合日時 |
UNIQUE制約: (strategy, race_id, horse_number)。predict --save で保存、review で照合・集計。複勝圏内入着かつplace_payout未取得の場合は未照合(reconciled_at IS NULL)のまま保留されます。
| カラム | 型 | 説明 |
|---|---|---|
| race_id | TEXT | レースID |
| horse1 | INTEGER | 馬番1(常に horse1 < horse2 < horse3) |
| horse2 | INTEGER | 馬番2 |
| horse3 | INTEGER | 馬番3 |
| payout | INTEGER | 払戻金額(100円単位) |
PRIMARY KEY: (race_id, horse1, horse2, horse3)。同着時は複数行。
| カラム | 型 | 説明 |
|---|---|---|
| strategy | TEXT | 戦略名(例: trio-top10-harville) |
| race_id | TEXT | レースID |
| race_date | TEXT | レース日 |
| horse1-3 | INTEGER | 馬番(ソート済み) |
| prob_harville | REAL | Harville法確率 |
| prob_independent | REAL | 独立法確率 |
| trio_odds | REAL | 三連複オッズ |
| ev_harville | REAL | 期待値 |
| is_hit | INTEGER | 的中フラグ(照合後) |
| actual_payout | INTEGER | 払戻額(照合後) |
UNIQUE制約: (strategy, race_id, horse1, horse2, horse3)。
今後の実装タスク管理は TODO.md を参照してください。
# テスト実行
uv run pytest tests/ -v
# リント
uv run ruff check src/
# Phase 0 特徴量検証(65特徴量ベースライン)
uv run python scripts/verify_phase0.py
# Phase 1 特徴量検証(+16特徴量のアブレーション比較)
uv run python scripts/verify_phase1.py
# Phase 2 特徴量検証(+17特徴量: 調教師9 + 血統8、キャリブレーション比較)
uv run python scripts/verify_phase2.py| 対象 | リクエスト数 | 所要時間(デフォルト間隔) |
|---|---|---|
| レース結果 1ヶ月分 | 〜300 | 〜10分(2秒間隔) |
| レース結果 1年分 | 〜3,500 | 〜2時間(2秒間隔) |
| 調教師IDバックフィル | 〜10,900 | 〜3時間(1秒間隔) |
| 血統データ収集 | 〜22,500 | 〜6時間(1秒間隔) |