① パイプライン全体像
✅ Pipeline v2 稼働中 — 展示データ完全パイプライン + oriten バックフィル
exhibition_scheduler.py が発走-10分に展示3ファイルを自動取得 → vote 時に再フェッチ + LightGBM 再推論。
23:15 backfill_sweep.py が行欠損 + oriten欠損(turn_time全NULL)の2段階差分でDB補完。
SKIP_VENUE_IDS: {39}(江戸川のみ) — oriten 配信なし(403 Forbidden)。他23会場は全て配信確認済み。
実精度は evening_race_results.py の 7日MA を正とする(バックテスト CSV は参考値)。
pnl_daily パイプライン
-
1a
07:30 —
scrape_today.py— 当日番組表 + 初期オッズ取得 -
2
23:30(Kファイル ETL 完了後)—
backtest_live.py --margin 0.35 -
3
generate_pnl_report.py→track_record_all.csv -
4
HTML 生成 → commit/push →
teinou.pages.dev/pnl.html
リアルタイム通知パイプライン
-
1
09:30 —
morning_race_summary.py -
2
07:45 —
exhibition_scheduler.py -
3
発走 -5分(1回のみ)— vote シングルショット
-
4
23:30 — 今日の結果レポート
② レース当日タイムライン
morning_race_summary.py✅ 実装済み(2026-05-22)
exhibition_scheduler.py 起動getHoldingList2_{date}.json で全会場発走時刻取得 → 各レースに 2 ジョブ登録(-10分 / -5分)✅ 実装済み(2026-05-21)launchd 07:45 起動 / APScheduler デーモン / 最終発走 +30分で自動終了
fetch_boatcast_preracebc_j_tkz(展示タイム・チルト)/ bc_j_stt(コース・展示ST)/ bc_oriten(まわり足・直線)→ exhibition_runs MERGE✅ 全会場・全レース対象(getHoldingList2 掲載分)/ 取得失敗は 23:15 バックフィルで補完
exhibition_runs 再フェッチ → LightGBM 再推論(展示特徴量あり)→ value_edge ≥ 0.10 → Telegram 正式通知(1R/1回 dedup)✅ 再推論ロジック実装済み(児島R9: +2.88pp 実測)
✅ exhibition_scheduler から DateTrigger で直接起動(2026-05-21 実装済み)
payout_records / races 完成全レース終了後。以降のジョブの前提条件
backfill_sweep.py① 行欠損:
getHoldingList2(当日全レース)vs exhibition_runs(取得済み)の差分② oriten欠損: 行はあるが
turn_time 全NULL(tkz/sttは取れたがoritenだけタイミングミスマッチ)→ 欠損レースを boatcast.jp から再フェッチ → UPSERT(NVL で既存値を上書きしない)
✅ 実装済み — 5/21: 122レース補完(行29 + oriten93)OK=122 NG=0
江戸川(39)は oriten 配信なし → _ORITEN_UNAVAILABLE_VENUES で除外
evening_race_results.py + pnl_daily①
evening_race_results.py — pending_vote_signals × 実結果 → 予測精度集計 → Telegram 配信(7日MA Top-1 的中率)②
pnl_daily — backtest → track_record_all.csv 追記 → teinou.pages.dev/pnl.html 更新 → push← ① が実際の予測精度の正。② は参考値(backtest)
③ モデル・ベット戦略・精度の読み方
単勝1点 + 三連単(top1-top2-top3 / top1-top2-top4 / top1-top3-top4)の3点流し
①
margin12 ≥ 0.35 — top1とtop2の予測確率差(自信度フィルタ)②
min-proba ≥ 0.50 — top1の生確率が50%以上③
value_edge ≥ 0.10 — EV-implied_prob差(期待値フィルタ)
※ Calibrated 確率ベース(raw確率の0.10下方シフト版。5/22〜)
v2026_27_lr02 の設計変更点
オッズ除外設計: win_odds / implied_prob をモデル特徴量から除外。train/serve skew の根本原因だったオッズ依存を排除。
SKIP_VENUE_IDS 縮小: {9, 39, 55} → {39}(江戸川のみ)。戸田・多摩川はモデル切替によりリセット、ライブデータ蓄積中。
oriten カバレッジ改善: backfill_sweep が行欠損 + oriten欠損(turn_time全NULL)の2段階で補完 → 23会場100%カバレッジ。
⚠️ バックテスト ROI と本番の乖離について(重要)
バックテスト件数(193件/5日)≠ 実通知件数(62件/5日):約3倍の差がある。
理由①「特徴量の計算方法が違う」:バックテストはDB全データから一括でfeature再計算するため、ローリング特徴量が微妙にズレる。本番はrolling_cache(当日リアルタイム)を使用。同じmargin閾値でも選ばれるレースが変わる。
理由②「完全なデータしか処理できない」:バックテストはデータが不完全なレースを自動除外 → 自然と「きれいなレース」だけになりROIが高く見える。
→ バックテストROIは「モデルの理論的な実力の目安」。本番ROIは3〜4割引で見るのが現実的。
本当の成績はシャドーラン(5/21〜)100bet蓄積後に確定する。
④ バグ一覧と対応状況
| 優先度 | バグ | 影響 | ステータス | 対処 |
|---|---|---|---|---|
| P0 | race_odds 0.0 格納 → モデル誤判定 | バックテスト的中率が実態と大幅乖離。精度低下の主因の可能性 | ✅ 修正済 | parse_odds.py / feature_engineering.py 修正。再学習で本来精度に回復見込み |
| P0 | vote stage Telegram 重複送信(1R に 2-4通) | 有料ユーザーに同一通知が複数届く→UX 最悪 | ✅ 修正済 | today_tg_voted_{date}.json で 1R/1回に制限 |
| P1 | predict stage "dead" 通知(vote stage を通過しない予告) | 「通知来たのに何も賭けない?」と混乱 | ✅ 修正済 | 「〔予告・30分前〕オッズ確認後に正式通知」ラベル付与 |
| P1 | live 推論で展示特徴量がゼロ(boatcast.jp は発走10-12分前に公開) | predict stage(20-40分前)はbeforeinfo未公開で空取得。vote stage も再フェッチなし → 展示タイム・コース取り・展示STが全て欠損のまま推論 | ✅ 修正済 | ① exhibition_scheduler.py — -10分に3ファイル並列取得 → exhibition_runs MERGE(launchd 07:45起動済み)② race_notifier.py vote stage — exhibition_runs 再フェッチ + ML再推論実装済み(+2.88pp 実測 2026-05-21)詳細 → ⑦ |
| P1 | backtest CSV が実態と乖離(race_odds の残存 0.0) | website の損益レポートが信頼できない | ⏳ 残タスク | pnl_daily_launchd.sh で backtest 前に odds 再取得(fetch_live_odds.py --date YESTERDAY の実装が必要) |
| P2 | 精度低下(7日MA 51.9% vs 78% baseline) | 三連単的中率が著しく低い | ✅ 解決済 | v2026_27_lr02(オッズ除外設計)で根本解決。AUC=0.9073 / Top-1=70.0% |
| P2 | oriten 欠損(行はあるが turn_time 全NULL) | 23会場中一部で oriten 配信タイミングミスマッチ → 展示特徴量欠損 | ✅ 解決済 | backfill_sweep.py が行欠損 + oriten欠損の2段階差分で23:15に一括補完。江戸川(39)のみ配信なし→SKIP |
| P0 | 予測確率 99%超の出力(統計的にあり得ない値) | EV計算が歪む / 信頼度ラベルS帯が偽陽性で乱発 / ユーザー信頼失墜 | ✅ 修正済 | MAX_WIN_PROB = 0.80 で clip(4ファイル適用: predict.py / predict_daily.py / race_notifier.py / backtest_live.py)。競艇の実勝率上限は〜72%のため80%は十分な余裕値。 |
| P1 | EV計算の歪み(モデルの生確率が実際の勝率より過大) | 期待値フィルタ(value_edge)が機能しない / 偽陽性推薦が増える | ✅ 修正済 | Isotonic Regression で calibration 導入。役割分離アーキテクチャ: margin12 = raw clip値で計算 / top_proba のみ calibration 適用(全艇適用だと margin12 が崩壊するため)。訓練データ: 2026-01-01〜05-18 / 10,683件。 |
⑤ 精度監視 + サーキットブレーカー
margin12=0.35(5/22変更: BT 5/2-5/20 p≥0.50/m≥0.35 → 85.5%的中/234%ROI/日45件)
江戸川: BoatCast oriten 配信なし(403 Forbidden)→ 24特徴量欠損で予測精度劣化
戸田(9)・多摩川(55): v2026_27_lr02切替によりリセット済み(1-2週間ライブデータ蓄積後に再評価)
.betting_paused フラグ作成 → 全通知停止回復: 7日MA ≥ 65% で自動解除 / 手動解除は
rm logs/.betting_paused
retrain_monthly_launchd.sh → train_monthly.py自動バージョン採番(v{year}_{NN}_lr02)/ Telegram にメトリクス通知 / モデル自動切替なし(人間がレビューして手動切替)
次回: 6/1 → v2026_28_lr02 生成予定
monthly_filter_optimize.sh前月全レースでバックテスト → threshold_scan → prob×margin グリッドスキャン → Telegram レポート通知
パラメータ変更は人間判断(レポートのみ自動)
✅ 実装済み(2026-05-22)
⑥ 今後のロードマップ
完了済み(Phase 1-2)
完了済み(Phase 3: 5/21-22)
morning_race_summary.py 朝注目レース一括通知(ランク別S/A/B)進行中
長期(月次)
⑦ exhibition_scheduler — 展示データ収集アーキテクチャ(✅ 実装済み 2026-05-21)
アーキテクチャ方針
朝の番組表(getHoldingList2_{date}.json)を元に各レースを動的スケジューリング。
直前データ取得ソースを race.boatcast.jp に一元化。exhibition_scheduler が -10分・-5分の 2 ジョブを管理し、run_vote_stage() を -5分に直接呼び出す。
📡 race.boatcast.jp — 利用可能ファイル一覧(調査済み 2026-05-21)
| パスパターン | コンテンツ | フォーマット(主要カラム) | HTTP | 利用タイミング |
|---|---|---|---|---|
| /api_txt/getHoldingList2_{YYYYMMDD}.json | 開催場一覧・発走時刻 | JSON: RaceStudiumNo, DeadlineTimeAll[] | 200 | 07:45 スケジュール構築 |
| /hp_txt/{jcd}/bc_j_tkz_{date}_{jcd}_{rno}.txt | 直前情報(展示タイム・体重・チルト) | TSV: name / exhibition_time / weight_adj_flag / weight_adj_g / weight / tilt_flag / tilt_amount | 200 | 発走 -10分 |
| /hp_txt/{jcd}/bc_j_stt_{date}_{jcd}_{rno}.txt | スタート展示(コース取り・展示ST) | TSV: position / lane / name / st_practice / st_actual / f_flag / distance | 200 | 発走 -10分 |
| /txt/{jcd}/bc_oriten_{date}_{jcd}_{rno}.txt | オリジナル展示(まわり足・直線・ラップ) | TSV: lane / name / lap_time / turn_time / straight_time | 200 | 発走 -10分(既存 fetch_oriten_to_adw.py) |
| /txt/{jcd}/bc_smt_od1_{date}_{jcd}_{rno}.txt | 単勝オッズ(リアルタイム) | TSV: 6枠分オッズグリッド + 名前行 | 200 | 30分毎 odds_load の代替候補 |
| /txt/{jcd}/bc_smt_od3_{date}_{jcd}_{rno}.txt | 3連単オッズ(リアルタイム) | TSV: 6行×25列(1着×2着3着組合せ) | 200 | 30分毎 odds_load の代替候補 |
| /txt/{jcd}/bc_smt_od2_{date}_{jcd}_{rno}.txt | 2連単オッズ(リアルタイム) | TSV: 6×6行列 | 200 | 将来活用候補 |
| /txt/{jcd}/bc_kakutei_od3_{date}_{jcd}_{rno}.txt | 3連単確定オッズ | レース確定後に200 | 403→200 | レース後結果検証 |
| /m_txt/{jcd}/bc_rs1_2_{date}_{jcd}_{rno}.txt | 競走成績(着順・ST・決まり手) | TSV: ST行 + 着順・選手名・タイム行 + 気象行 | 200 | レース後 結果検証 |
| /hp_txt/{jcd}/bc_j_str3_{date}_{jcd}_{rno}.txt | 出走表(選手情報・成績統計) | TSV: 登録番号 / 名前 / 期別 / 支部 / …統計列 | 200 | 07:30 scrape_today(出走表) |
⚠️ boatcast.jp 制約メモ
- 展示データ(bc_j_tkz / bc_j_stt / bc_oriten)は発走 10〜12分前 に公開。それ以前は
data=\n{rno}\nだけ(空) - 確定オッズ(bc_kakutei_*)はレース確定前 403、確定後 200 に切替わる
- bc_rs1_2(競走成績)は 完了済みレース のみ 200。未完了は 403
- User-Agent 必須:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36
スケジュール構築
-
1
07:30 —
scrape_today.py -
2
07:45 —
exhibition_scheduler.py起動
展示スクレイプ + vote トリガー(動的)
-
A
発走 -10分 —
fetch_boatcast_prerace.py -
B2
発走 -5分 — vote シングルショット ⏳ 実装予定
欠損リトライ・バックフィル
-
C
23:15 —
backfill_sweep.py✅ 実装済み
scripts/exhibition_scheduler.py — APScheduler デーモン(launchd で 07:45 起動)etl/fetch_boatcast_prerace.py — boatcast.jp 直前3ファイル並列取得 (bc_j_tkz + bc_j_stt + bc_oriten) → exhibition_runs UPSERTscripts/backfill_sweep.py — 23:15 展示データ2段階バックフィル(行欠損 + oriten欠損)
scripts/race_notifier.py — vote stage に展示再フェッチ + ML再推論 追加 ✅ commit 93ab4a3scripts/scrape_today.py — 展示スクレイプは exhibition_scheduler が一元管理
apscheduler==3.11.2(インストール済み)変更履歴
| バージョン | 日付 | 変更内容 |
|---|---|---|
| v6 | 2026-05-22 | モデル切替 + oriten完全カバレッジ — v2026_27_lr02(AUC=0.9073 / Top-1=70.0% / ROI=200.4%・オッズ除外設計)。SKIP_VENUE_IDS: {9,39,55}→{39}(江戸川のみ)。backfill_sweep: 行欠損+oriten欠損の2段階差分補完実装。サーキットブレーカー(.betting_paused / WARN65% / ALERT58%)+月次リトレイン自動化(launchd毎月1日02:00)。シャドーモード〜5/22 → 5/23ライブ化 |
| v5 | 2026-05-21 | パイプライン簡素化 — predict stage・30分毎 odds_load・vote ポーリング launchd を廃止。07:30 を注目レース初期推論通知に変更。exhibition_scheduler に -5分 vote シングルショットジョブ追加。+20分ジョブ廃止 + 23:15 バックフィルスイープ設計 |
| v4 | 2026-05-21 | P1完全解決 — exhibition_scheduler.py 発走 -10分スクレイプ実装 + vote stage 展示再フェッチ・LightGBM 再推論実装(児島R9: +2.88pp 実測)。commit 93ab4a3 |
| v3 | 2026-05-21 | race_odds 0.0→NaN 修正(P0)/ Telegram 重複送信修正(P0)/ モデル v2026_25_lr02(AUC=0.9139)ライブ化 |
| v1-v2 | 2026-05-19〜20 | 初版 / predict stage + vote stage ポーリング構成 / 展示特徴量ゼロ(P1)が未解決の状態 |