# 競馬プログラミング
- 複勝およびワイドを利用し、回収率100%越えを目指す。(単勝では回収率100%超え達成)
- できる限り、的中する馬券のみを選択。取りこぼさないようにするのではなく、(理想は)購入したものが全て的中するような選択をする。
- 満足のいく出力が出来次第、アプリ化も検討(最終目標)
## 現状タスク
- 進行中(2022.9.16)
- goal
- Outline
- X, yという訓練データを用いて、\_dateに開催される全レースの着順の予測を行う
- input:
- X: 説明変数
- y: 目的変数
- \_date: 日付
- output:
- recommended_places: 購入すべき(おすすめの)複勝馬券
- Excelへの自動書き込み or 表示
- 概略
- rank_predictor: ある日付に開催されたレースの予測
- prediction_extractor: 予測結果から必要なデータの取得
- prediction_handler: 予測結果の処理を行う
- タスク
- rank_predictor, prediction_extractorの作成(途中)
- 不備
- [x] predict_dfに星マークが付与されていない。(prediction_result_extracton.py l.235 \_\_attach_star2proba)
- [x] SettingWithCopyWarningの発生(prediction_result_extracton.py l.118 \_\_determine_fukusho_odds)
- ExcelWriterの作成(未着手)
- other refactoring
- [ ] Returnの引数をrace_id_dictからrace_id_lsに変更。
- [ ] horse_idならびにrace_idの取得をResultからRaceCardへ変更。
- [x] 一回の実行で同じtableに複数回SESSIONしてしまうことを解消。
- [ ] mergeの処理が遅いので、早くできる方法を模索する。(特に、jockeyは謎に遅くなってしまった。)
- [ ] \_\_init__.pyが雑に記入されているので、再度勉強。(Import Error: partially initialized module)
- [ ] rank_predictorにて予測する際のpbarの表示を綺麗に。
- [ ] ディレクトリ構想の見直し(特に、\_class内部を分割)
## ER図
(更新: 2022.8.20)
<img src="https://i.imgur.com/9AU5TFc.png" width="500">
## 開発予定技術
- Excel
- ~~Excelクラスの作成~~
- excelへの書き出しの際、1着馬が複数のレースにも対応できるようにする。
- 例) 1/9 中京12R
- 除外や中止、取消など、最終的に着順が決定しなかった馬を表から削除。
- 精度、回収率向上のために
- 血統データのprocessing(難)
- ensemble学習
- (コードによる)特徴量削減([参考文献](https://qiita.com/shimopino/items/5fee7504c7acf044a521#%E7%89%B9%E5%BE%B4%E9%87%8F%E9%81%B8%E6%8A%9E%E6%89%8B%E6%B3%95%E3%81%AE%E3%81%BE%E3%81%A8%E3%82%81))
- パラメータチューニング([参考文献](https://knknkn.hatenablog.com/entry/2021/06/29/125226#boostingboosting_type))
- フォーメーション自動作成(難)
- 複勝
- ~~1着予想の馬に対する複勝の回収率~~
- 買うべき馬を出力する関数
- 新特徴量候補
- 前レースと騎手が同じかどうか(diff_jockey)
- 前レースの斤量との差(diff_weights)
- 開催地が海外のレースを訓練データから削除
- trainer_table, owner_table, breeder_table, jockey_tableを作成(**最優先**)
- 以前はそれぞれのidを直接特徴量に入れたが種類が多く変に学習し断念。
- 理由はidは数値なので、数値が近しいid同士などに本来には関係ない関係性を見出されてしまうため。
- one-hot encodingの実装も考えたが、種類が多くふさわしくないと思いこちらも断念。
- 新たな提案として、idではなくそのレースに出走する馬に関わるjockeyなどの全体の1着率、2着率、3着率、圏外率、総レース数辺りを特徴量として追加。
- 例) ルメール騎手の過去戦績(1着率x_1%, 2着率x_2%...)
- テーブルが複数に増えるためdbなどを作成するのもあり。
- テストコード
- フォーメーション自動作成&美味しい複勝馬券に対応した回収率の計算を行う関数の作成。
- 現在は、閾値による簡易版のみ。
- Webapi
- フロントエンド
- React
- テンプレートの自動作成
- `npx create-react-app <name> `
- npxとは
- Node.jsのパッケージランナーツール
- 似たものにnpmがある。([参考文献](https://www.investor-daiki.com/it/npm-npx-difference#:~:text=%E4%B8%80%E6%96%B9%E3%81%A7npx%E3%81%AFNode,%E3%81%8C%E4%B8%BB%E3%81%AA%E8%B2%AC%E5%8B%99%E3%81%A7%E3%81%99%E3%80%82))
- バックエンド
- fast api
- ([参考文献](https://zenn.dev/sawao/articles/15a9cf0e3360a7))
- その他
- 見やすい変数名への変更([参考文献](https://qiita.com/Ted-HM/items/7dde25dcffae4cdc7923#%E7%9C%81%E7%95%A5%E3%81%97%E3%81%9F%E8%A8%80%E8%91%89))
- ~~予想の際、入力変数を日付のみにする(race_idの入力を省略)~~
- 自動化([参考文献](https://t.co/tvF5pHufc2))
- 最新情報のスクレイピング
- 結果の出力
- 馬券購入
## ブレスト
### フォーメーション自動作成
- ==自動で決定する馬券はワイドに絞る==
- 他は難しそう。作成するとしても、優先順位はかなり低め。
- 何を基準にして買い馬を決定するか。
- オッズxスコアから得られる指標
- 予測にオッズは用いない。購入するかどうかの意思決定のみにオッズを使用。
- スコアが高く硬い馬を軸に、穴馬をオッズが良い馬に限定
- トリガミはほとんどないようにする。どのように?
- トリガミが生じない程度の候補数にする。
- トリガミが生じる馬券に対しての賭け金を増やす。
- トリガミが生じる馬券を候補から除外する。
- **上位n件の馬の中から、不必要な馬(低オッズかつ低スコア)をフォーメーションから削除。**
- 不必要な馬の定義が必要
- **優位性のある馬(高スコア)は軸馬として考える。**
- すなわち、購入馬券に必ず軸馬を絡めるようにする。
- 軸馬の定義が必要
- ワイドの場合、硬い馬x穴馬の組み合わせが理想
### 買うべき馬を出力する関数
- tansho_binaryの結果の通り、当たりやすい馬ばかりを買うと、ほとんど回収が見込めない。
- 例) オッズ1.2の複勝馬券の場合、6回中5回以上の的中率(=約85%)が求められる。これは、リスキーな割に、利益に繋がりにくい。
- ここで言う買うべき馬は、当たりやすい馬ではなく、**回収率の良い馬**。
- ~~外した時のリスクが大きすぎるため、オッズが1.5未満は購入しない。~~ 計算結果次第では購入あり。(オッズが1.1の場合、的中率90%超えが必須であるため、自動的に省かれるはず。)
- 複勝オッズの最大値は優に100を超える。
- スコアは負の値も含む。
- (リークが起きていなければ、)現在のモデルは、スコアが約2.25以上の単勝馬券を買い続けて、回収率100%前後
- 大体1, 2頭/会場(=約5頭/日)ほどになるようにしたい。
- 但し、回収率100%超えは前提。回収率の条件が達成できそうでなければ、希望頭数の変更はやむなし
- ~~美味しい複勝馬券の基準候補
**i) スコア x オッズ >= 4.5**
**ii) オッズ >= 1.5**
(候補数が多すぎる or 少なすぎるで基準を変更の余地あり)~~
- 具体例
- オッズ1.5の場合、約67%の的中率が必須
- ~~スコア>=3?~~
- オッズ2.0の場合、50%以上の的中率が必須
- ~~スコア>=2.25?~~
- **==(採用)スコアに対しての複勝的中率の期待値を出力する関数が必須==**
- 上記の役割とはたす関数を`hit_fukusho(score)`とする。
- 購入すべき馬券の条件
- ==`1/hit_fukusho(predict(horse)) > fukusho_odds_df(horse, race)`==
(但し、`fukusho_odds_df(x)`とは、ある馬(horse)が出走するあるレース(race)に与えられている複勝オッズを引っ張ってくる関数)
### hit_fukusho関数
- スコア帯ごとの的中率を洗い出す。
- 最大値と最小値が必要
- 幅はいくつ?
- 0.1程度が妥当な気がする
- 幅を狭くしすぎると、一区画に対するデータ数が少なくなり、正しい的中率が得られない恐れがある。
- 幅を広くしすぎると、同じスコア帯の最大値と最小値で的中率の差が大きくなる。
- スコアでソートした全データをn分割し、それぞれグループA1, A2, ..., Anとする。すると、hit_fukushoは以下のように書ける。また、得られたスコアがどのスコア帯に属するかを出力する関数を`get_A(score)`とする。
==`hit_fukusho(get_A(predict(horse))) = (実データにより算出された的中率)`==
- `get_A(score)`の概要は、scoreと各グループの要素の平均値との差の絶対値`(|score-Avg(Ai)|)`が最も小さいものを採択
- 各グループ(Ai)に最低1000件ほど欲しい
### DDD
- ディレクトリは以下の4種
- adapters
- controllers
- usecaseが動作部分を示すのに対して、controllersはusecaseの結果を取得する。その際、利用しやすい形に変形する。
- パスオペレーション関数の内容と同値。
- usecaseから想定外の結果が得られた場合などの例外処理も行う。
- db_gateways
- dbのmodel定義及び、crudを記述。引数にengineを持つため、最もdbに近いディレクトリ
- applications
- usecase
- このappの必要なusecaseを網羅する。
- dbが既に存在する前提の記述となる。すなわち、db_gatewayを用いる。
- domains
- schemas
- dbの一要素分のdataを保持するclass
- 上記のclassをまとめて一括で処理するclass(=repository)
- builder.py
- 各primary_key一つに対応するデータの作成(?)
- repository.py
- db_gatewayを実際に使うもの?実際に、builder.pyで作成したものを複数所持しているもの。saveやselectができる。
- infrastructures
- db_drivers
- base.py
- declarative_baseを定義(Pythonで宣言したclassとdbをマッピングするためのベースとなるclass)
- servers
- route.py
- パスオペレーション関数をデコレータを用いて記述。作成したrouterを返り値とする。
### DB(postgresql)
- 便利ツール
- [SQLpad](https://itnews.org/news_resources/310204): databaseを可視化するWebUI
### 血統データのプロセッシング
- [fastText](https://qiita.com/KTaskn/items/e76551191214593c278e)の利用
- 利用したいとは思っているが、全く案が思い付かない。
- word2vec的なものもあるらしい。
## 未修正バグ
- ~~月ごとの集計を行う関数(make_BoP_sheet, make_hit_sheet)が、複勝回収率を加えたことにより、座標がずれ、意図していない集計がなされている。~~
- ~~列名指定で値の集計を行うように改善~~
- 5/8 修正完了
- ~~過去の実データに対しての予測を行う際も、現状スクレイピングしている全データを用いて予測をおこなっている。~~
- ~~未来のデータを用いていることになるので、訓練用データを一部削除する関数が必要。~~
- 5/9 修正完了
- 複勝回収率がランク毎になっている。
- 実際に購入する馬券は本命馬だけでないため、本命馬ランクを用いた分割はできない。
- 単勝も複勝同様のDataFrame(tansho_odds_df)を作成し、新たなランク帯を作成する
- fukusho_odds_dfのデータの分割をし忘れている。
- trainとtestに分割しなければならないが、現状は全データから全データを予測する状況になっており、正しい分布が得られていない。
- 現在の幅は0.05にしているが、データ数が約5分の1になるため、 再検討が必要。
- 三連複、三連単流し回収率がうまく機能していない
- 原因は未探索
- 予測不可能なレース(主に新馬戦)に星マーク(☆)がついている。
- 単純に見づらいし、今後悪影響が出そうなので削除したい。
## 現在のモデル
(更新: 2022.7.8)
- params([参考文献](https://knknkn.hatenablog.com/entry/2021/06/29/125226#bagging_fractio%E3%81%A8bagging_freq%E3%82%92%E3%81%A4%E3%81%8B%E3%81%A3%E3%81%A6bagging%E3%81%AE%E8%AA%BF%E6%95%B4%E3%82%92%E3%81%99%E3%82%8B))
| 変数名 | 値 | 説明 |
| ---- | ---- | ---- |
| objective | regression | 計算手法 |
| metric | rmse(二乗平均平方根誤差) | 誤差の計算方式 |
| feature_pre_filter | False | (検索中) |
| boosting_type | gdbt(勾配ブースティング) | boostingアルゴリズム |
| lambda_l1 | 6.6377046563499 | L1正則化項の係数 (重要じゃない特徴量を削ぐ役割) |
| lambda_l2 | 0.009493220785902975 | L2正則化項の係数(重要じゃない特徴量の影響力を小さくする) |
| num_leaves | 31 | 木の葉(=ノード)の数 |
| feature_fraction | 0.748 | 各機を作成する際、使用する特徴量の割合 |
| bagging_fraction | 1.0 | baggingを行う際、使用する特徴量の割合 |
| bagging_freq | 0 | baggingを行う頻度 |
| min_child_samples | 20 | 葉が有するデータの最小数 |
- bagging_fractionおよびbagging_freqから分かる通り、baggingは行っていない。
- 回収率のグラフ(縦軸:回収率、横軸:閾値)
- tansho_binary: 複勝馬の予想
- tansho_regression: オッズと着順両方を加味
- tansho_lambdarank: ランキング学習
