QuantXでシンプルな機械学習を実装する

現状のQuantXってどこまでのライブラリがサポートされてるの？って思っていたところ、どうやらscikit-learnは使えるっぽいので、シンプルな機械学習を実装してみました。

#今回書いたコード

```python
from sklearn.ensemble import RandomForestClassifier
import numpy as np 
import pandas as pd

def initialize(ctx):
    # 設定
    ctx.logger.debug("initialize() called")
    ctx.configure(
      channels={          # 利用チャンネル
        "jp.stock": {
          "symbols": [
            "jp.stock.6942",
            "jp.stock.2721",
            "jp.stock.9263",
            "jp.stock.6556",
            "jp.stock.3092",
            "jp.stock.4380",
            "jp.stock.3856",
            "jp.stock.2930",
            "jp.stock.2146",
            "jp.stock.3445",
            "jp.stock.6065",
            "jp.stock.8927",
            "jp.stock.2371",
            "jp.stock.3765",
            "jp.stock.3932",
            "jp.stock.5542",
            "jp.stock.3779",
            "jp.stock.2127",
            "jp.stock.2211",
            "jp.stock.8167",
            "jp.stock.3064",
            "jp.stock.3276",
            "jp.stock.8918",
            "jp.stock.2928",
            "jp.stock.6071",
            "jp.stock.1434",
            "jp.stock.3676",
            "jp.stock.3825",
            "jp.stock.6067",
            "jp.stock.1333", 
            "jp.stock.3267",
            "jp.stock.1814", 
            "jp.stock.4521", 
            "jp.stock.4503", 
            "jp.stock.5194", 
            "jp.stock.5391", 
            "jp.stock.5940", 
            "jp.stock.7226", 
            "jp.stock.7956", 
            "jp.stock.3092", 
            "jp.stock.7148", 
            "jp.stock.9115", 
            "jp.stock.9508", 
            "jp.stock.2371", 
            "jp.stock.3765", 
            "jp.stock.6533", 
            "jp.stock.4587", 
            "jp.stock.4536", 
            "jp.stock.3064", 
            "jp.stock.1808", 
          ],
          "columns": [
            #"open_price_adj",    # 始値(株式分割調整後)
            #"high_price_adj",    # 高値(株式分割調整後)
            #"low_price_adj",     # 安値(株式分割調整後)
            #"volume_adj",         # 出来高
            #"txn_volume",         # 売買代金
            "close_price",        # 終値
            "close_price_adj",    # 終値(株式分割調整後) 
          ]
        }
      }
    )

    def _my_signal(data):
      cp=data["close_price_adj"].fillna(method='ffill')
      window_length = 10
      train_data = cp.ix[:,:int(len(cp.columns)/2)]
      test_data = cp.ix[:,int(len(cp.columns)/2):]
      classifier = RandomForestClassifier()
      X = []
      Y = []
      for (sym,val) in train_data.items():
        for i in range(len(train_data[sym]) - (window_length)):
          recent_prices_train = train_data[sym].iloc[i:(i + window_length +1)]
          recent_prices_train = recent_prices_train.values.astype(np.float)
          recent_prices_train = recent_prices_train.reshape(-1,)
          changes_train = (np.diff(recent_prices_train) > 0)
          X.append(changes_train[:-1])
          Y.append(changes_train[-1])
      X = np.array(X)
      Y = np.array(Y)
      Y = Y.reshape(-1,1)
      
      if len(Y) >= 100:
        model = classifier.fit(X, Y)
      
      
      tmp = pd.DataFrame(None, index=cp.index, columns=cp.columns)
      prediction = pd.Series(None, index=test_data.index)
      
      for (sym,val) in test_data.items():
        x = []
        for i in range(len(test_data[sym]) - (window_length)):
          recent_prices_test = test_data[sym].iloc[i:(i + window_length +1)]
          recent_prices_test = recent_prices_test.values.astype(np.float)
          recent_prices_test = recent_prices_test.reshape(-1,)
          changes_test = (np.diff(recent_prices_test) > 0)
          x.append(changes_test[:-1])
          
        prediction_tmp = model.predict(x)
        prediction[window_length:] = prediction_tmp
        tmp[sym] = prediction
      
      buy_sig = tmp[tmp.ix[:,int(len(cp.columns)/2):] == True]
      sell_sig = tmp[tmp.ix[:,int(len(cp.columns)/2):] == False]
      
      
      return {
        "buy:sig":buy_sig,
        "sell:sig":sell_sig
      }

    # シグナル登録
    ctx.regist_signal("my_signal", _my_signal)

def handle_signals(ctx, date, current):
    '''
    current: pd.DataFrame
    '''
    
    done_syms = set([])
    for (sym,val) in ctx.portfolio.positions.items():
        returns = val["returns"]
        if returns < -0.03:
          sec = ctx.getSecurity(sym)
          sec.order_target_percent(0, comment="損切り(%f)" % returns)
          done_syms.add(sym)
        elif returns > 0.05:
          sec = ctx.getSecurity(sym)
          sec.order_target_percent(0, comment="利益確定売(%f)" % returns)
          done_syms.add(sym)

    buy = current["buy:sig"].dropna()
    for (sym,val) in buy.items():
        if sym in done_syms:
          continue
        
        sec = ctx.getSecurity(sym)
        sec.order_target_percent(0.1, comment="SIGNAL BUY")
        pass

    sell = current["sell:sig"].dropna()
    for (sym,val) in sell.items():
        if sym in done_syms:
          continue
    
        sec = ctx.getSecurity(sym)
        sec.order_target_percent(0, comment="SIGNAL SELL")
        pass

```

#やったこと

![qiita用画像.png](https://qiita-image-store.s3.amazonaws.com/0/325611/4a30e2e4-087c-311b-aebd-8bc34feeff35.png)

まず、学習用の銘柄データと実際に売買する銘柄データに分けます。
次に、学習用のデータに対して、各銘柄の終値を算出し、前日と比べて株価が上昇したか下落したかの真偽値判定を行います。
そうして得られた真偽値データを一定期間ずつ切り出し（今回はwindow_length=10日間で検証）、9日間の入力データと10日目の正解ラベルを大量に用意します。
あとは、ランダムフォレストで分類器を作成し、実際に売買を実行するデータで予測値を算出した後、その予測値を元に売買シグナルを作成しました。

#バックテスト結果
![スクリーンショット 2019-01-24 15.01.28.png](https://qiita-image-store.s3.amazonaws.com/0/325611/5c7a8f2d-11af-6bf7-f760-060705b41782.png)

直近3年でこのくらいの値です。
いやシグナル回数多すぎな。

#まとめ
機械学習素人すぎてこれでうまくいってるか全く自信がないですが、一応QuantXでscikit-learnってどうやって使うんだ？ってところでサンプル例として作ってみました。
そろそろ金融指標をいじいじするのにも限界が見えてきたので、ここらへんで機械学習×システムトレードの領域の勉強を深めていきたいなあと思う次第です。
オススメの本やサイトなどあれば教えていただきたいです、、