艇脳 (Teinou) 運用フロー

モデル: v2026_27_lr02(AUC=0.9073 / Top-1=70.0% / FEATURE_COLS=111 / オッズ除外設計 / Isotonic Calibration付き)/ 最終更新: 2026-05-22 v8

🟢 ライブ稼働中(5/21〜) ✅ P0+P1 全解決 ✅ Pipeline v2 稼働中 ✅ Calibration 導入済み

① パイプライン全体像

✅ 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 — 当日番組表 + 初期オッズ取得
    Bファイル DL → 当日開催会場特定 / race_odds 初回 UPSERT
    ※ 展示データはまだない(発走40分前に predict stage が直接フェッチ)
  • 2
    23:30(Kファイル ETL 完了後)— backtest_live.py --margin 0.35
    fetch_feature_matrix → 0.0 を NaN 扱い ✅ 修正済
  • 3
    generate_pnl_report.pytrack_record_all.csv
    全 margin≥0.35 レースを対象(45R/日)
  • 4
    HTML 生成 → commit/push → teinou.pages.dev/pnl.html
    Telegram 日次サマリーも送信
リアルタイム通知

リアルタイム通知パイプライン

  • 1
    09:30 — morning_race_summary.py
    当日全レースを初期推論(オッズなし・展示なし)→ margin≥0.35 の注目レースを1通に集約して Telegram 通知
    ✅ 実装済み(2026-05-22)ランク別件数表示・買い目は直前通知に分離
  • 2
    07:45 — exhibition_scheduler.py
    各レースに 2 ジョブ登録: ① -10分 展示取得 ② -5分 vote(DateTrigger直接起動)
    ✅ 実装済み(2026-05-21)margin12-min=0.35 / min-proba=0.50
  • 3
    発走 -5分(1回のみ)— vote シングルショット
    bc_smt_od1 で直前オッズ → exhibition_runs 再フェッチ → LightGBM 再推論 → value_edge ≥ 0.10 → Telegram 通知(1R/1回 dedup)
    ✅ 実装済み — 児島R9: +2.88pp 実測
  • 4
    23:30 — 今日の結果レポート
    evening_race_results.py — 予測精度集計 → Telegram 配信
    pnl_daily — backtest → website 損益グラフ更新

② レース当日タイムライン

09:30 JST — morning_race_summary.py
朝注目レース一括通知 — 当日全レースを LightGBM 推論(オッズ・展示なし)→ margin≥0.35 の注目レースをランク別(S/A/B)に1通集約して Telegram 通知
✅ 実装済み(2026-05-22)
07:45 JST — exhibition_scheduler.py 起動
動的スケジューリングgetHoldingList2_{date}.json で全会場発走時刻取得 → 各レースに 2 ジョブ登録(-10分 / -5分)
✅ 実装済み(2026-05-21)launchd 07:45 起動 / APScheduler デーモン / 最終発走 +30分で自動終了
発走 -10分(全レース)— fetch_boatcast_prerace
展示3ファイル並列取得bc_j_tkz(展示タイム・チルト)/ bc_j_stt(コース・展示ST)/ bc_oriten(まわり足・直線)→ exhibition_runs MERGE
✅ 全会場・全レース対象(getHoldingList2 掲載分)/ 取得失敗は 23:15 バックフィルで補完
発走 -5分(1回のみ)— vote シングルショット
最終推論 + 投票判断 — bc_smt_od1 で直前オッズ取得 → exhibition_runs 再フェッチ → LightGBM 再推論(展示特徴量あり)→ value_edge ≥ 0.10 → Telegram 正式通知(1R/1回 dedup)
✅ 再推論ロジック実装済み(児島R9: +2.88pp 実測)
✅ exhibition_scheduler から DateTrigger で直接起動(2026-05-21 実装済み)
23:10 — Kファイル ETL
当日全レース着順・払戻取込payout_records / races 完成
全レース終了後。以降のジョブの前提条件
23:15 — backfill_sweep.py
欠損展示データ2段階補完
① 行欠損: 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 で除外
23:30 — 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)

③ モデル・ベット戦略・精度の読み方

稼働モデル
v2026_27_lr02
オッズ除外設計・FEATURE_COLS=111
AUC(OOS)
0.9073
v2026_27_lr02
Top-1 的中率(OOS)
70.0%
バックテスト基準値(参考値)
ROI(OOS backtest)
200.4%
⚠️ 参考値。本番と3倍程度乖離あり
確率上限(clip)
80%
MAX_WIN_PROB=0.80(5/22〜)
シャドーラン開始
5/21〜
100bet蓄積で実績ROI確定
ベット戦略
yoko(単勝 + 三連単1着軸流し3点) — 1レース4点 × ¥100〜¥400
単勝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差(期待値フィルタ)
信頼度ラベル
S(超本命): top_proba_cal ≥ 75% / A(本命): ≥ 65% / B(有力): ≥ 55% / C(妙味狙い): それ以下
※ 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件。

⑤ 精度監視 + サーキットブレーカー

稼働モデル
v2026_27_lr02 — AUC=0.9073 / Top-1=70.0% / ROI=200.4%(108特徴量・オッズ除外設計)
現在の状態
🟢 ライブ稼働中(5/21〜)— 実ベット + Telegram 通知
margin12=0.35(5/22変更: BT 5/2-5/20 p≥0.50/m≥0.35 → 85.5%的中/234%ROI/日45件)
除外会場
SKIP_VENUE_IDS = {39}(江戸川のみ)
江戸川: BoatCast oriten 配信なし(403 Forbidden)→ 24特徴量欠損で予測精度劣化
戸田(9)・多摩川(55): v2026_27_lr02切替によりリセット済み(1-2週間ライブデータ蓄積後に再評価)
継続条件
✅ 継続 — 7日MA Top-1 ≥ 65%(WARN ライン)
サーキットブレーカー
🔴 ALERT — 7日MA ≤ 58%.betting_paused フラグ作成 → 全通知停止
回復: 7日MA ≥ 65% で自動解除 / 手動解除は rm logs/.betting_paused
監視ツール
evening_race_results.py(23:30)— 7日MA Top-1 的中率を毎夜 Telegram 配信 + CB判定
月次リトレイン
毎月1日 02:00 JST — retrain_monthly_launchd.shtrain_monthly.py
自動バージョン採番(v{year}_{NN}_lr02)/ Telegram にメトリクス通知 / モデル自動切替なし(人間がレビューして手動切替)
次回: 6/1 → v2026_28_lr02 生成予定
月次フィルター最適化
毎月2日 06:00 JST — monthly_filter_optimize.sh
前月全レースでバックテスト → threshold_scan → prob×margin グリッドスキャン → Telegram レポート通知
パラメータ変更は人間判断(レポートのみ自動)

✅ 実装済み(2026-05-22)

⑥ 今後のロードマップ

完了済み(Phase 1-2)

P0: race_odds 0.0→NaN 修正 / Telegram 重複送信修正
P1: exhibition_scheduler + vote stage 再推論(+2.88pp 実測)
v2026_27_lr02 ライブ化(オッズ除外設計・AUC=0.9073)
backfill_sweep: 行欠損 + oriten欠損の2段階差分補完
サーキットブレーカー(.betting_paused / WARN65% / ALERT58%)
月次リトレイン自動化(launchd 毎月1日 02:00 / Telegram通知)

完了済み(Phase 3: 5/21-22)

5/21 ライブ化(シャドーモード終了)
exhibition_scheduler -5分 vote DateTrigger直接起動
morning_race_summary.py 朝注目レース一括通知(ランク別S/A/B)
margin12=0.35 最適化(BT: 85.5%的中/234%ROI)
月次フィルター最適化自動化(毎月2日 06:00 レポート通知)

進行中

backtest 再設計: vote 時点オッズスナップショット方式

長期(月次)

📅6/1 月次リトレイン → v2026_28_lr02 生成予定
📊バックテスト vs 実績の乖離監視ダッシュボード
🔬戸田(9)・多摩川(55) SKIP除外後の精度追跡(1-2週後に再評価)

⑦ 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
Step 1 — 朝

スケジュール構築

  • 1
    07:30 — scrape_today.py
    Bファイル → 当日開催会場 + 全レース発走時刻
    today_schedule.json 生成(既存)
  • 2
    07:45 — exhibition_scheduler.py 起動
    getHoldingList2_{date}.json で発走時刻確認
    各レースに 2 ジョブを APScheduler へ登録:
    ① 発走 -10分 → fetch_boatcast_prerace
    ② 発走 -5分 → vote シングルショット
Step 2 — レース毎

展示スクレイプ + vote トリガー(動的)

  • A
    発走 -10分 — fetch_boatcast_prerace.py
    3 ファイルを並列取得:
    bc_j_tkz → exhibition_runs (展示タイム・体重・チルト)
    bc_j_stt → exhibition_runs (コース・展示ST)
    bc_oriten → exhibition_runs (まわり足・直線・ラップ)
    ✅ 実装済み(2026-05-21)upserted=6/race 確認
  • B2
    発走 -5分 — vote シングルショット ⏳ 実装予定
    DateTrigger: deadline - timedelta(minutes=5)
    bc_smt_od1 で直前オッズ → exhibition_runs 再フェッチ → LightGBM 再推論
    value_edge ≥ 0.10 → run_vote_stage() → Telegram 正式通知
Step 3 — レース後

欠損リトライ・バックフィル

  • C
    23:15 — backfill_sweep.py ✅ 実装済み
    2段階差分: ① 行欠損(スケジュール - DB)② oriten欠損(行あり・turn_time全NULL)
    江戸川(39)は _ORITEN_UNAVAILABLE_VENUES で除外(403 Forbidden)
    今日の結果レポート(23:30)前に補完完了 → 月次リトレイン精度に寄与
新規ファイル
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 UPSERT
scripts/backfill_sweep.py — 23:15 展示データ2段階バックフィル(行欠損 + oriten欠損)
変更ファイル
scripts/race_notifier.py — vote stage に展示再フェッチ + ML再推論 追加 ✅ commit 93ab4a3
scripts/scrape_today.py — 展示スクレイプは exhibition_scheduler が一元管理
依存ライブラリ
apscheduler==3.11.2(インストール済み)
実装結果
✅ 2026-05-21 実装・動作確認済み — 児島 R9: predict prob 0.9392 → vote prob 0.9680(+2.88pp)。展示特徴量(展示タイム・コース・ST・まわり足)が初めてモデルに反映される。

変更履歴

バージョン 日付 変更内容
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)が未解決の状態