Quantx Factoryで基本的なアルゴ実装をするときによく使うメソッドをまとめてみた

#自己紹介
[株式会社SmartTrade](https://smarttrade.co.jp/)でインターンをしている、M大学理工学部2年の[益川](https://www.facebook.com/ryozodesert)と申します。機械学習勉強中です。

#QuantX Factoryとは？
株式会社Smart Tradeが
>金融の民主化

を理念に、トレードアルゴリズムを誰もが簡単に開発することのできるプラットフォーム"QuantX Factory"を開発しました。

###強み
- 初心者の挫折しがちな環境構築が不要
- 各銘柄の終値や高値、出来高などの各種データが用意されていて,<font color="blue">データセットの用意が不要</font> 
- 開発したトレードアルゴリズムは審査を通過すればQuantX Storeで販売可能
- 上級者の方にはQuantX Labといった開発環境も (引用元：[公式ドキュメント](https://determined-feynman-fb5a05.netlify.com/ja/dataset.jp.stock.html))

#本題
当社インターン一ヶ月目ということで、MACDや移動平均線、RSIといった基本的なテクニカル指標を実装するためのアルゴリズム作成において頻繁に使うpandasやtalibのメソッドの使い方を解説していこうと思います。

<font color = "red">(注：このコードの内容を今完全に理解しなくて大丈夫です) </font>

```python 
# ライブラリーのimport
# 必要ライブラリー
# バックテストエンジン「maron」をインポート
import maron
import maron.signalfunc as sf
import maron.execfunc as ef
# 追加ライブラリー
# 追加で必要なライブラリーがある場合は以下の例に倣って追加して下さい
# データ分析ツール「pandas」をインポートし「pd」という名前で使用
import pandas as pd
# 金融系関数セット「talib」をインポートし「ta」という名前で使用
import talib as ta
import numpy as np
# 「」をインポートし「」という名前で使用
# import  as 

# オーダ方法(目的の注文方法に合わせて以下の3つの中から一つだけコメントアウトを外して下さい)
# ot = maron.OrderType.MARKET_CLOSE  # シグナルが出たた翌日の終値のタイミングでオーダー
ot = maron.OrderType.MARKET_OPEN   # シグナルが出たた翌日の始値のタイミングでオーダー
# ot = maron.OrderType.LIMIT         # 指値によるオーダー

def initialize(ctx):  # 初期化部分
  ctx.logger.debug("initialize() called")  # ログ出力
  ctx.target = 0.1
  ctx.loss_cut = -0.02
  ctx.plofit = 0.05
  
  ctx.configure(
      channels={
        "jp.stock": {
          "symbols": [
            "jp.stock.2914", #JT(日本たばこ産業)
            "jp.stock.8766", #東京海上ホールディングス
            "jp.stock.8031", #三井物産
            "jp.stock.8316", #三井住友フィナンシャルグループ
            "jp.stock.8411", #みずほフィナンシャルグループ
            "jp.stock.9437", #NTTドコモ
            "jp.stock.4502", #武田薬品工業
            "jp.stock.8058", #三菱商事
            "jp.stock.9433", #KDDI
            "jp.stock.9432", #日本電信電話
            "jp.stock.7267", #ホンダ（本田技研工業）
            "jp.stock.8306", #三菱ＵＦＪフィナンシャル・グループ
            "jp.stock.4503", #アステラス製薬
            "jp.stock.4063", #信越化学工業
            "jp.stock.7974", #任天堂
            "jp.stock.6981", #村田製作所
            "jp.stock.3382", #セブン＆アイ・ホールディングス
            "jp.stock.9020", #東日本旅客鉄道
            "jp.stock.8802", #三菱地所
            "jp.stock.9022", #東海旅客鉄道
            "jp.stock.9984", #ソフトバンクグループ
            "jp.stock.6861", #キーエンス
            "jp.stock.6501", #日立製作所
            "jp.stock.6752", #パナソニック
            "jp.stock.6758", #ソニー
            "jp.stock.6954", #ファナック
            "jp.stock.7203", #トヨタ自動車
            "jp.stock.7751", #キヤノン
            "jp.stock.4452", #花王
            "jp.stock.6098", #リクルートホールディングス
          ],
          #⑥
          "columns": ["close_price_adj",    # 終値(株式分割調整後) 
          ]}})
  
  def _my_signal(data):  # 売買シグナル生成部分
    # dataの中身を確認したい場合は以下のコメントアウトを外してください
    # ctx.logger.debug(data)

    syms = data.minor_axis   # 銘柄リストの作成
    dates = data.major_axis  # 日付リストの作成
    
    '''↓ロジックの計算に必要なデータを3次元構造のdataから取得するコードを書いて下さい↓'''
    cp = data["close_price_adj"].fillna("ffill")
    '''↑ロジックの計算に必要なデータを3次元構造のdataから取得するコードを書いて下さい↑'''
    
    '''↓売買条件を定義するために必要なロジックの計算をするコードを書いて下さい↓'''
    
    
    movave5 = cp.rolling(window = 5, center = False).mean()
    movave25 = cp.rolling(window = 25, center = False).mean()
    
    '''↑売買条件を定義するために必要なロジックの計算をするコードを書いて下さい↑'''
    
    # 売買シグナルを定義(bool値で返す)
    buy_sig = (movave5 > movave25) & (movave5.shift(1) < movave25.shift(1))
    sell_sig =  (movave5 < movave25) & (movave5.shift(1) > movave25.shift(1))

    # market_sigという全て0.0が格納されている「横：銘柄名、縦：日付」のデータフレームを作成
    market_sig = pd.DataFrame(data=0.0, columns=syms, index=dates)
    
    # buy_sigがTrueのとき1.0、sell_sigがTrueのとき-1.0、どちらもTrueのとき0.0とおく
    market_sig[buy_sig == True] = 1.0
    market_sig[sell_sig == True] = -1.0
    market_sig[(buy_sig == True) & (sell_sig == True)] = 0.0

    # market_sigの中身を確認したい場合は以下のコメントアウトを外してください
    # ctx.logger.debug(market_sig)

    return {
      "buy:sig":buy_sig,
      "sell:sig": sell_sig,
      "market:sig": market_sig,
      # 以下にバックテスト結果のチャートで表示したいデータを追加して下さい
      
    }
      
  ctx.regist_signal("my_signal", _my_signal)  # シグナル登録

def handle_signals(ctx, date, current):  # 日ごとの処理部分
  '''
  current: pd.DataFrame
  '''
  # initializeの_my_signalで生成したシグナルをmarket_sigに格納
  market_sig = current["market:sig"]
  done_syms = set([])  # 利益確定及び損切りが行われた銘柄を格納するset型
  none_syms = set([])  # portfolio.positionsに存在しない銘柄を格納するset型
  # portfolio.positions(保有している銘柄)に対象銘柄(sym)が存在するかのチェック
  for (sym, val) in market_sig.items():
    if sym not in ctx.portfolio.positions:
      none_syms.add(sym)
  # portfolio.positions(保有している銘柄)のそれぞれの銘柄(sym)の保有株数が0ではないかのチェック
  for (sym, val) in ctx.portfolio.positions.items():
    if val["amount"] == 0:
      none_syms.add(sym)
  # 損切り、利益確定(利確)の設定
  # 所有している銘柄を1つずつ確認する繰り返し処理
  for (sym, val) in ctx.portfolio.positions.items():
    # 損益率の取得
    returns = val["returns"]
    if returns < -0.03:  # 損益率が-3%未満(絶対値で3%より大きい損)の場合
      # 損切りのための売り注文
      sec = ctx.getSecurity(sym)
      sec.order(-val["amount"], comment="損切り(%f)" % returns)
      # 利益確定及び損切りが行われた銘柄を格納するset型にsymに代入されている銘柄を追加
      done_syms.add(sym)
    elif returns > 0.05:  # 損益率が+5%より大きい場合
      # 利益確定(利確)のための売り注文
      sec = ctx.getSecurity(sym)
      sec.order(-val["amount"], comment="利益確定売(%f)" % returns)
      # 利益確定及び損切りが行われた銘柄を格納するset型にsymに代入されている銘柄を追加
      done_syms.add(sym)
  buy = market_sig[market_sig > 0.0]  # 買いシグナル
  for (sym, val) in buy.items():  # 買いシグナルが出ている銘柄を1つずつ処理
    # done_symsまたはnone_symsにsymがある場合
    if sym in done_syms:
      continue  # 処理をスキップ
    # 買い注文
    sec = ctx.getSecurity(sym)
    sec.order(sec.unit() * 1, orderType=ot, comment="SIGNAL BUY")
    # 買い注文のログを下に出力したい場合は以下のコメントアウトを外してください(長期間注意)
    #ctx.logger.debug("BUY: %s,  %f" % (sec.code(), val))
  sell = market_sig[market_sig < 0.0]  # 売りシグナル
  for (sym, val) in sell.items():  # 売りシグナルが出ている銘柄を1つずつ処理
    # done_symsまたはnone_symsにsymがある場合
    if (sym in done_syms) | (sym in none_syms):
      continue  # 処理をスキップ
    # 売り注文
    sec = ctx.getSecurity(sym)
    sec.order(sec.unit() * -1,orderType=ot, comment="SIGNAL SELL")
```
図１：移動平均線のゴールデンクロス、デッドクロスを用いたアルゴリズム（参照：[https://factory.quantx.io/developer/149d79a8cf744f059af0e96918913a9f/coding](https://factory.quantx.io/developer/149d79a8cf744f059af0e96918913a9f/coding)）

#pandas系

##**rollingメソッド**
`pd.DataFrame.rolling(window)`

- DataFrame、Seriesなどのpandasの基本的なデータ構造に窓関数をに適用するときに用いるメソッドで、rollingオブジェクトを返す。
- 主に用いる引数は窓数を設定するwindow
- 窓関数：<font color = "blue">ある有限区間以外で0となる関数</font>。 
        - *ある関数や信号（データ）に窓関数が掛け合わせられると、区間外は0になり、有限区間内だけが残るので、数値解析が容易になる。*(参照：[Weblio 「窓関数とは」](https://www.weblio.jp/content/%E7%AA%93%E9%96%A2%E6%95%B0))
- 使い道：[移動平均線](https://qiita.com/katakyo/items/d786599f6638d53a8602)、[出来高移動平均線](https://qiita.com/tyunibyokanja/items/011122a255cce90c39f9)等のテクニカル指標を実装するときに用いる。
 
例：25日移動平均線を実装する

図１の82行目において
`movave25 = cp.rolling(window = 25).mean()`
とありますが、これが25日移動平均線を定義しているものです。

1. 各銘柄の各日付の終値を格納したDataFrameである`cp`に`cp.rolling(window = 25)`で窓数25の窓関数を適用し、rollingオブジェクトが返されます。
2. 返されたrollingオブジェクトにmeanメソッドを適用して、25日分の平均値の格納されている新たなDataFrame型オブジェクトが返されます。

これで、25日分の移動平均を格納したDataFrameである、movave25を得ることができました。

##shiftメソッド
`pandas.DataFrame.shift(periods)`

- データフレームに格納されているデータを<font color = "blue">periods分だけ移動(シフト)させる</font>メソッド、DataFrameオブジェクトを返す
- 使い道：短期線と長期線の二つの移動平均線の大小関係の前日からの変化を検知する。

例： ゴールデンクロス、デッドクロスの判定
図１の87行目において
`buy_sig = (movave5 > movave25) & (movave5.shift(1) < movave25.shift(1))`
とありますが、このコードは「当日の5日移動平均の方が25日移動平均よりも大きく、前日5日移動平均の方が25日移動平均よりも小さい」こと、つまりゴールデンクロスしたことを示しています。88行目はその逆です。

shiftメソッドはこのような形で利用します。

#talib系
##そもそもtalibとは
テクニカル指標などを簡単に実装できるライブラリ。
実装できる指標については下記URLを参考にしてください。
[https://mrjbq7.github.io/ta-lib/funcs.html](https://mrjbq7.github.io/ta-lib/funcs.html)

##talibを用いた例
```python
# Sample Algorithm
# ライブラリーのimport
# 必要ライブラリー
import maron
import maron.signalfunc as sf
import maron.execfunc as ef
# 追加ライブラリー
# 使用可能なライブラリに関しましては右画面のノートをご覧ください①



import pandas as pd
import talib as ta
import numpy as np

# オーダ方法(目的の注文方法に合わせて以下の2つの中から一つだけコメントアウトを外してください)
# オーダー方法に関しましては右画面のノートをご覧ください②
#ot = maron.OrderType.MARKET_CLOSE # シグナルがでた翌日の終値のタイミングでオーダー
ot = maron.OrderType.MARKET_OPEN   # シグナルがでた翌日の始値のタイミングでオーダー
#ot = maron.OrderType.LIMIT        # 指値によるオーダー

# 銘柄、columnsの取得
# 銘柄の指定に関しては右画面のノートをご覧ください③
# columnsの取得に関しては右画面のノートをご覧ください④
def initialize(ctx):
  # 設定
  ctx.logger.debug("initialize() called")
  ctx.configure(
    channels={               # 利用チャンネル
      "jp.stock": {
        "symbols": [
            "jp.stock.2914", #JT(日本たばこ産業)
            "jp.stock.8766", #東京海上ホールディングス
            "jp.stock.8031", #三井物産
            "jp.stock.8316", #三井住友フィナンシャルグループ
            "jp.stock.8411", #みずほフィナンシャルグループ
            "jp.stock.9437", #NTTドコモ
            "jp.stock.4502", #武田薬品工業
            "jp.stock.8058", #三菱商事
            "jp.stock.9433", #KDDI
            "jp.stock.9432", #日本電信電話
            "jp.stock.7267", #ホンダ（本田技研工業）
            "jp.stock.8306", #三菱ＵＦＪフィナンシャル・グループ
            "jp.stock.4503", #アステラス製薬
            "jp.stock.4063", #信越化学工業
            "jp.stock.7974", #任天堂
            "jp.stock.6981", #村田製作所
            "jp.stock.3382", #セブン＆アイ・ホールディングス
            "jp.stock.9020", #東日本旅客鉄道
            "jp.stock.8802", #三菱地所
            "jp.stock.9022", #東海旅客鉄道
            "jp.stock.9984", #ソフトバンクグループ
            "jp.stock.6861", #キーエンス
            "jp.stock.6501", #日立製作所
            "jp.stock.6752", #パナソニック
            "jp.stock.6758", #ソニー
            "jp.stock.6954", #ファナック
            "jp.stock.7203", #トヨタ自動車
            "jp.stock.7751", #キヤノン
            "jp.stock.4452", #花王
            "jp.stock.6098", #リクルートホールディングス     
        ],
        "columns": [
          "close_price",     # 終値
          "close_price_adj", # 終値(株式分割調整後)
          #"volume_adj",     # 出来高
          #"txn_volume",     # 売買代金
        ]
      }
    }
  )
  
  # シグナル定義
  def _my_signal(data):
    #終値のデータを取得
    cp=data["close_price_adj"].fillna(method="ffill")
    
    syms = data.minor_axis   # 銘柄リストの作成
    dates = data.major_axis  # 日付リストの作成
   
    #データを格納する場所
    macd = pd.DataFrame(data=0.0, columns=syms, index=dates)
    macdsignal = pd.DataFrame(data=0.0, columns=syms, index=dates)
    macdhist = pd.DataFrame(data=0.0, columns=syms, index=dates)
    rsi = pd.DataFrame(data = 0.0, columns = syms, index = dates)

    #TA-LibによるMACDの計算
    for (sym,val) in cp.items():
      macd[sym],macdsignal[sym],macdhist[sym] = ta.MACD(cp[sym])
      rsi[sym] = ta.RSI(cp[sym].values.astype(np.double), timeperiod = 10)

    
    macd_golden = (macd > macdsignal) & (macd.shift(1) < macdsignal.shift(1))
    macd_dead = (macd < macdsignal) & (macd.shift(1) > macdsignal.shift(1))
    
    #売買シグナル生成部分
    
    buy_sig = macd_golden | (rsi < 30)
    sell_sig = macd_dead | (rsi > 70)

    #market_sigという全て0が格納されているデータフレームを作成
    market_sig = pd.DataFrame(data=0.0, columns=syms, index=dates)
    
    #buy_sigがTrueのとき1.0、sell_sigがTrueのとき-1.0とおく
    market_sig[buy_sig == True] = 1.0
    market_sig[sell_sig == True] = -1.0
    market_sig[(buy_sig == True) & (sell_sig == True)] = 0.0
    # ctx.logger.debug(market_sig)

    return {
      "MACD:g2": macd, 
      "MACDSignal:g2": macdsignal, 
      "MACDHist": macdhist, 
      "market:sig": market_sig,
    }

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

def handle_signals(ctx, date, current):  # 日ごとの処理部分
  '''
  current: pd.DataFrame
  '''
  # initializeの_my_signalで生成したシグナルをmarket_sigに格納
  market_sig = current["market:sig"]
  done_syms = set([])  # 利益確定及び損切りが行われた銘柄を格納するset型
  none_syms = set([])  # portfolio.positionsに存在しない銘柄を格納するset型
  # portfolio.positions(保有している銘柄)に対象銘柄(sym)が存在するかのチェック
  for (sym, val) in market_sig.items():
    if sym not in ctx.portfolio.positions:
      none_syms.add(sym)
  # portfolio.positions(保有している銘柄)のそれぞれの銘柄(sym)の保有株数が0ではないかのチェック
  for (sym, val) in ctx.portfolio.positions.items():
    if val["amount"] == 0:
      none_syms.add(sym)
  # 損切り、利益確定(利確)の設定
  # 所有している銘柄を1つずつ確認する繰り返し処理
  for (sym, val) in ctx.portfolio.positions.items():
    # 損益率の取得
    returns = val["returns"]
    if returns < -0.03:  # 損益率が-3%未満(絶対値で3%より大きい損)の場合
      # 損切りのための売り注文
      sec = ctx.getSecurity(sym)
      sec.order(-val["amount"], comment="損切り(%f)" % returns)
      # 利益確定及び損切りが行われた銘柄を格納するset型にsymに代入されている銘柄を追加
      done_syms.add(sym)
    elif returns > 0.05:  # 損益率が+5%より大きい場合
      # 利益確定(利確)のための売り注文
      sec = ctx.getSecurity(sym)
      sec.order(-val["amount"], comment="利益確定売(%f)" % returns)
      # 利益確定及び損切りが行われた銘柄を格納するset型にsymに代入されている銘柄を追加
      done_syms.add(sym)
  buy = market_sig[market_sig > 0.0]  # 買いシグナル
  for (sym, val) in buy.items():  # 買いシグナルが出ている銘柄を1つずつ処理
    # done_symsまたはnone_symsにsymがある場合
    if sym in done_syms:
      continue  # 処理をスキップ
    # 買い注文
    sec = ctx.getSecurity(sym)
    sec.order(sec.unit() * 1, orderType=ot, comment="SIGNAL BUY")
    # 買い注文のログを下に出力したい場合は以下のコメントアウトを外してください(長期間注意)
    #ctx.logger.debug("BUY: %s,  %f" % (sec.code(), val))
  sell = market_sig[market_sig < 0.0]  # 売りシグナル
  for (sym, val) in sell.items():  # 売りシグナルが出ている銘柄を1つずつ処理
    # done_symsまたはnone_symsにsymがある場合
    if (sym in done_syms) | (sym in none_syms):
      continue  # 処理をスキップ
    # 売り注文
    sec = ctx.getSecurity(sym)
    sec.order(sec.unit() * -1,orderType=ot, comment="SIGNAL SELL")
```
図2：RSIとMACDのゴールデンクロス、デッドクロスを用いたアルゴリズム（参照：[https://factory.quantx.io/developer/6ba1eb1b748d46a18ce128fea3156282/coding](https://factory.quantx.io/developer/6ba1eb1b748d46a18ce128fea3156282/coding)）

##RSI
`talib.RSI(close, timeperiod = 14)`

- RSIとは、「買われすぎ」、「売られすぎ」を判別するテクニカル指標です
- 終値を示すnp.double型のnp.arraysオブジェクトcloseと、期間を示すtimeperiodを引数にとります。

例：図２のコードの82行目において

```python
rsi = pd.DataFrame(data = 0.0, columns = syms, index = dates)
```

各銘柄のRSIを格納するDataFrameを定義して

85行目のfor文

```python
for (sym,val) in cp.items():
      rsi[sym] = ta.RSI(cp[sym].values.astype(np.double), timeperiod = 10)
```
において各銘柄のRSIを格納しています。

- `cp[sym]`はDataFrame型のオブジェクトですので、`cp[sym].values`でarray型のオブジェクトに変換します。
- そして`cp[sym].values.astype(np.double)`でarrayの中身をnp.double型に変換しています。
- 今回は10日分のRSIを取るのでtimeperiod = 10にします
- 「売られすぎ」か「買われすぎ」かを参考に売買を判断します

##MACD
`ta.MACD(close)`

- 移動平均線を応用したテクニカル指標で、MACDラインとMACDシグナルラインの日本の線の組み合わせて売買のタイミングを測ります。
- 銘柄の終値を格納したSeriesオブジェクトcloseを引数にとります。
- 三つのDataFrame型のオブジェクトを返します

例：図２のコード79〜81行目において、

```python
macd = pd.DataFrame(data=0.0, columns=syms, index=dates)
macdsignal = pd.DataFrame(data=0.0, columns=syms, index=dates)
macdhist = pd.DataFrame(data=0.0, columns=syms, index=dates)
```
返り値を格納するDataFrameを定義します。
85行目のfor文

```python
for (sym,val) in cp.items():
      macd[sym],macdsignal[sym],macdhist[sym] = ta.MACD(cp[sym])
```
において各銘柄のmacd, macdsignal, macdhistをとります。

90,91行目でMACDラインとMACDシグナルラインのゴールデンクロス、デッドクロスを判断しています。

```python
macd_golden = (macd > macdsignal) & (macd.shift(1) < macdsignal.shift(1))
macd_dead = (macd < macdsignal) & (macd.shift(1) > macdsignal.shift(1))
```

##MACD、RSIから、買いシグナル、売りシグナルを定める
```python
buy_sig = macd_golden | (rsi < 30) #ゴールデンクロスかつ売られすぎ
sell_sig = macd_dead | (rsi > 70) #デッドクロスかつ買われすぎ
```
このように、talibを用いることでMACD、RSIなどの有名なテクニカル指標を簡単に実装することができます。


#まとめ
今回はtalibやpandasのアルゴ作成において頻繁に使うメソッドをピックアップしてみました。これを読んで少しでQuantX Factoryのアルゴリズムへの理解が深まったら幸いです。最後まで拙い文章をお読みいただきありがとうございました。

#免責注意事項
このコード・知識を使った実際の取引で生じた損益に関しては一切の責任を負いかねますので御了承下さい。