実写版ウマ娘攻略（pythonで競馬予測モデルを作成してバックテストする方法～３章バックテスト編～）

前回作成した予測モデルを使ってバックテストをしていきます。
前回記事はこちらです。
https://qiita.com/umaining/items/769fea8d5fb414d249ab
#全体の流れ
今回の記事では「３．バックテスト」を説明します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1186688/da1cfa06-52dd-851b-aada-5dedf195ff68.png)

#バックテスト
予測モデルでは各馬ごとに１着になる確率を計算します。
バックテストでは、予測モデルの確立を元にして馬券を購入するかどうかを決めます。
下記のソースコードでは予測モデルの確率が30％以上（１位になる確率が30％）の時に馬券を購入するということをしています。
バックテストを実行するときは前回作成したモデルと同じディレクトリで実行してください。
（もし別ディレクトリで実行したい場合はfilenameの箇所を変更してください）

```python:fit_ai_lgb.py
import pandas as pd
from sklearn import model_selection
from sklearn.linear_model import LogisticRegression
import pickle
import os
from pathlib2 import Path
import sqlite3
import pandas
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt
import sys
import itertools
import time
import numpy as np

def backtest(db_pass,buy_rate):
    start = time.time()
    # カレントディレクトリをスクリプト配置箇所に変更
    os.chdir(Path(__file__).parent)
    print("作業ディレクトリ:{0}".format(os.getcwd()))
    
    sql = """
    select  *    from    N_UMA_RACE WHERE  ninki NOT IN ("00")  and Odds not in ("0000")
    and year ="2019"
    """
    # DBファイル準備
    conn = sqlite3.connect(db_pass)
    cursor = conn.cursor()

    # SELECT結果をDataFrame
    df = pandas.read_sql_query(sql=sql, con=conn)
    #重複行の削除
    df = df.loc[:,~df.columns.duplicated()]

    df['Odds']=df['Odds'].astype(float)
    df['Ninki'].astype(int)
    df['1inai'].astype(int)
    df = df.fillna(0)
    X = df.loc[:,["Ninki","pre_Ninki","pre1_KakuteiJyuni"]].astype(float)
    df = df.replace('0', np.nan)
    y = df['1inai'].astype(int)


    # 保存したモデルをロードする
    filename = 'finalized_model.sav'
    loaded_model = pd.read_pickle(open(filename, 'rb'))
    y_pred = loaded_model.predict(X, num_iteration=loaded_model.best_iteration)
    print("---------------------")

    #predictの結果を元のdfに格納する(予測モデルの目的変数（１着になる）確率)
    df["answer_2"] = y_pred

    #'Year','MonthDay','JyoCD','RaceNum'でグループ分けして'Ninki'のソート
    df = df.sort_values(['Year','MonthDay','JyoCD','RaceNum','Ninki'])

    print("---------------------")
    print(df.head(30))
    df['rowIndex'] = df.apply(lambda row:row.name+1, axis=1)

    #dfのラムダ関数で使用：オッズ×100円を返す
    def func_haito(x):
        if x.KakuteiJyuni == "01":
            return x.Odds/10 * 100
        else:
            return 0

    def func_prieki(x,buy_rate):
        global rieki_sum
        if x.answer_2 > buy_rate:
            #rieki_sum = rieki_sum + round((rieki_sum*0.01/x.Odds/10))*x.get_haito - 100*round((rieki_sum*0.01/x.Odds/10))
            rieki_sum = rieki_sum + x.get_haito-100
        return rieki_sum

    df['get_haito'] = df.apply(lambda row:(func_haito(row)),axis=1)
    #単勝のバックテスト
    df['get_prieki'] = df.apply(lambda row:(func_prieki(row,buy_rate)),axis=1)

    elapsed_time = time.time() - start
    print ("elapsed_time:{0}".format(elapsed_time) + "[sec]")
    plt.figure()
    plt.plot(df['get_prieki'].values.tolist())
    plt.show()
    plt.close('all')

    print(df.head(10))



if __name__ == '__main__':
    #rieki_sum dfのラムダ関数で使用：予測結果の馬券を100円で買った時の純利益を返す（1件ずつの結果を累積していく)
    rieki_sum = 0
    db_pass = r'C:\Users\XXXXXX\EveryDB2_1_0_1_0\ecore.db'
    #buy_rate １着の確率が30％以上の馬券を購入する
    buy_rate = 0.3

    backtest(db_pass,buy_rate)
```

#実行結果
縦軸は損益、横軸は購入数を示しています。
下記のグラフをみると予測モデルに従って単勝馬券を100円買っていった場合は-６万円になることがわかります。
見事に負けていますね。
このままでは使い物にならないので予測モデルを改善する必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1186688/a0b3467e-95b8-8d67-6303-34ca444e11a3.png)

#予測モデルの改善方法
一番簡単な改善方法は学習データを増やすことです。
今回は「人気」「前回人気」「前回着順」の３つしか使っていません。
競馬DBのデータをみるとわかりますが、多くの情報があります。
例えば、騎手の成績や開催場所やレース距離などいろいろとあります。
また、データを加工して今までの各馬の着順の平均値や過去３試合前までの平均データなどをいれることで改善できます。
いろいろ試して右肩あがりのグラフを作りましょう！

#おわり
学習モデルにデータを追加するときに気を付けてほしいことがあります。
それは未来のデータを使いわないことです。
未来のデータを使って学習させるとものすごく良いモデルができます。
あまりにも優秀なモデルが出来上がったときは未来のデータを使ってないか確認してみてください。