LoginSignup

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 3 years have passed since last update.

ゼロから始めるシステムトレード #QuantX

Last updated at Posted at 2019-08-21

はじめに

本記事ではQuantXでアルゴリズムを作るポイントについて抑えていきます。
versionはmaron0.0.5です。

QuantXとは?

ブラウザ上で動かせる、株式や仮想通貨などの売買ルール(アルゴリズム)の作成、販売ができるシステムトレードプラットフォームです。pythonというプログラミング言語を用いて動かします。
売買シグナルをプログラミングで設定することによって売り買いのタイミングをLINEまたはSlackで通知してくれます。

開発すれば無料でアルゴリズムを開発、販売(審査があります)できます!

アルゴリズムを作る前に

アルゴリズムを開発するには、QuantX Factoryというサイトで会員登録が必要です。
QuantX Factoryというサイトに飛びます。
右上の方に新規会員登録というボタンがあるので、会員登録をしましょう!!

アルゴリズムの作り方

今回作成するアルゴリズムになります。
https://factory.quantx.io/developer/1016c0e9deff4f35ad3a77afb0e6c715

使用するライブラリーの決定

まずはじめに、通常のPythonのコードのように必要なライブラリーimportします。
maronというライブラリーがありますが、これは注文方法、具体的には約定のタイミングを決めるときに使うので必ず入れてください。

#ライブラリーのimport
import maron
import pandas as pd
import numpy as np
import talib as ta

また、使用できるライブラリーは0.0.5系では以下のようになっています。

packages version
Python 3.6.7
numpy 1.16.3
pandas 0.23.4
statsmodels 0.9.0
scikit-learn 0.21.1
scipy 1.3.0
TA-lib 0.4.17
cvxopt 1.2.3
xgboost 0.90
Keras 2.2.4
chainer 6.0.0
tensorflow 1.13.1

使用銘柄と使用データの取得

ここでは今回アルゴリズムで使用する銘柄と使用データを決めます。
仮に銘柄を変えたい場合は"jp.stock.XXXX"のXXXX部分に証券コードを入れてください。
またColumnsのデータセットのリストはこちらから

今回の銘柄はバフェットコードの有力小型株を選びました。

# 銘柄、columnsの取得
def initialize(ctx):
  # 設定
  ctx.logger.debug("initialize() called")
  ctx.configure(
    channels={               # 利用チャンネル
      "jp.stock": {
        "symbols": [
              "jp.stock.6927",
              "jp.stock.1514",
              "jp.stock.2654",
              "jp.stock.1716",
              "jp.stock.2483",
              "jp.stock.6044",
              "jp.stock.6392",
              "jp.stock.2410",
              "jp.stock.6185",
              "jp.stock.2307",
              "jp.stock.6164",
              "jp.stock.2169",
              "jp.stock.6158",
              "jp.stock.9466",
              "jp.stock.2186",
              "jp.stock.7045",
              "jp.stock.3316",
              "jp.stock.6346",
              "jp.stock.3810",
              "jp.stock.7608",
              "jp.stock.2163",
              "jp.stock.3359",
              "jp.stock.1450",
              "jp.stock.3934",
              "jp.stock.5967",

        ],
        "columns": [
          "open_price_adj",     # 始値(株式分割調整後)
          "close_price_adj",    # 終値(株式分割調整後)
          "high_price_adj",     # 高値(株式分割調整後)
          "low_price_adj",      # 安値(株式分割調整後)
          "volume_adj",        # 出来高(株式分割調整後)
          #"txn_volume",        # 売買代金
        ]
      }
    }
  )

売買シグナルの作成

売買シグナルの構造は簡単に書くと以下のようになります。

  def _my_signal(data):

    #テクニカル指標の計算とか売買シグナルを各部分

    return {
      #バックテスト後のチャートに描画したいデータや以下handle_signals関数で使いたいデータを辞書で記述
      "market:sig": market_sig,
    }
  # シグナル登録
  ctx.regist_signal("my_signal", _my_signal)

以前の記事でも紹介しましたが、maron0.0.5系では市況シグナルというものが導入され少し書き方が変わっているので注意です。

今回は使用者の多いボリンジャーバンドと出来高が使われているMFIという指標を使ったアルゴリズムを作っていこうと思います。

各種データの定義

initialize関数で設定した銘柄、指定期間の全日付、全データの入った3次元の配列である「data」から計算するために変数で定義します。
.fillna(method='ffill')で欠損値の補完も行なっています。
今回、終値はcp、始値はop、安値はlp、高値はhp、出来高はvpとして定義しました。

      # シグナル定義
  def _my_signal(data):
    cp =  data["close_price_adj"].fillna(method='ffill')
    op =  data["open_price_adj"].fillna(method='ffill')
    lp =  data["low_price_adj"].fillna(method='ffill')
    hp =  data["high_price_adj"].fillna(method='ffill')
    vp =  data["volume_adj"].fillna(method='ffill')

移動平均線の計算

今回はシグナル計算に利用しませんでしたが、チャートのトレンドが一目でわかる移動平均線も描画させます。
先ほど定義した終値cpに以下のようなメソッドを使っています。
https://note.nkmk.me/python-pandas-rolling/

    # 移動平均線(短期:25日 中期:75日 長期:200日)
    m25 = cp.rolling(window=25, center=False).mean()
    m75 = cp.rolling(window=75, center=False).mean()
    m200 = cp.rolling(window=200, center=False).mean()

ボリンジャーバンドの計算

次に有名なテクニカル指標の一つであるボリンジャーバンドを計算していきます。

ボリンジャーバンドとは?


ボリンジャーバンドは標準偏差を利用した指標です。
ある一定の確率で値動きが収まりやすいレンジを『σ(シグマ)』と呼び、平均値からみて上のレンジを+1σ、下のレンジを-1σと呼びます。これを2倍したものが+2σ・-2σになります。
正規分布の理論によれば、この+1σ、-1σに収まる確率は約68.2%、+2σから-2σに収まる確率は約95.4%です。
なので2σを売買シグナルの閾値として扱う人が多いようです。

TA-Libによる計算

これらの指標をTA-Libというライブラリーを用いて計算します。
*TA-Libはテクニカル分析を簡単に行うためのライブラリーです。計算に必要なデータ、期間を設定すると計算結果が出ます。
https://cryptotrader.org/talib

    # ボリンジャーバンド
    #データの入れ物の作成
    uband = pd.DataFrame(data=0,columns=cp.columns, index=cp.index)
    mband = pd.DataFrame(data=0,columns=cp.columns, index=cp.index)
    lband = pd.DataFrame(data=0,columns=cp.columns, index=cp.index)

まず計算したデータを格納するための入れ物(DataFrameを作ります。)

    #TA-Libによる計算
    for (sym,val) in cp.items():
        uband[sym], mband[sym], lband[sym] = ta.BBANDS(cp[sym],timeperiod=25,nbdevup=2.3,nbdevdn=2.3,matype=0)

次にTA-Libの必要なパラメータを入力してfor文でinitialize関数で設定した銘柄、指定期間の全日付文の計算処理をループさせます。

    #ボリンジャーバンドの売買シグナルの定義
    bband_buysig = (cp < lband) & (cp.shift(1) > lband.shift(1))
    bband_sellsig = (cp > uband) & (cp.shift(1) < lband.shift(1))

ボリンジャーバンドの売買シグナルを定義します。
今回は株価が-2.3σバンドを上から下に突き抜けたときに買い、株価が+2.3σバンドを下から上に突き抜けたときに売りとします。

MFIの計算

ボリンジャーバンドと同様にMFIも計算します。

MFIとは


MFIとは、相場の中でお金が買い、売りどちらの方向に流れているのかを株価と出来高から見極めるために作られた指標です。
RSIと似ていますがMFIは出来高も考慮しているという点で違いがあります。

$$TP=\frac{高値+安値+終値}{3}$$

$$MF=平均株価×出来高$$

$$PMF=前日比でTPが上昇したn日のMFの合計$$

$$NMF=前日比でTPが下落または変わらずだったn日のMFの合計$$

これらの式からMFIを出すことができます。
MFIの導出

$$MFI=\frac{PMF}{PMF+NMF}\times100$$

MFIは0〜100までの値を取ります。
RSIのように20〜30%を下回ったら買い、70〜80%を上回ったら売りのタイミングだと判断します。

今回は25%を買いの閾値、75%を売りの閾値とします。

    #MFI
    #データの入れ物の作成
    mfi = pd.DataFrame(data = 0, columns=[], index = cp.index)
    #TA-Libによる計算
    for (sym, val) in cp.items():
      mfi[sym] = ta.MFI(hp[sym],lp[sym],cp[sym],vp[sym],timeperiod = 14)
    #MFIの売買シグナルの定義
    mfi_buysig = (mfi < 25)
    mfi_sellsig = (mfi > 75)

売買シグナルの定義

先ほど、作成したボリンジャーバンドの指標とMFIの指標をORで繋いだものを売買シグナルとします。

    # 売買シグナルを定義
    buy_sig =  (bband_buysig) | (mfi_buysig) 
    sell_sig = (bband_sellsig) | (mfi_sellsig)

市況シグナルの定義

なぜ行うのか

以前は買いシグナルと売りシグナルの二つに分けてシグナルを出していました。
しかし、この場合買いシグナルと売りシグナルが同時に出る可能性があります。

これを防ぐために市況シグナル(market_sig)のデータフレームを用意し、買いを1.0 売りを-1.0 ニュートラル(何も取引しない状態)を0.0とするデータフレームを作っていきます。

以前のようにBool値で売買シグナルを出します

全てデータが0.0のデータフレームを用意

    # market_sigという全て0が格納されているデータフレームを作成
    market_sig = pd.DataFrame(data=0.0, columns=cp.columns, index=cp.index)

buy_sigがTrueの時に0.0を1.0に変換
sell_sigがTrueの時に0.0を-1.0に変換
両方Trueの時は0.0に変換していきます


    # 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

returnの入力

return内に後のhandle_signals関数下で使うデータまたはバックテスト時にチャートで描画したいものを入れます。

    return {
      "m25:price": m25,
      "m75:price": m75,
      "m200:price": m200,
      "uband":uband,
      "mband":mband,
      "lband":lband,
      "mfi:g2":mfi,
      "market:sig": market_sig,
    }

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

mfiは%で出力されるのでグラフ2の方に表示しました。
詳しくはこちらをご覧ください。
https://qiita.com/katakyo/items/db134688b17a4c418118

注文方法の決定

handle_signals関数の作成

_my_signal関数で定義した市況シグナルを元に買い注文、売り注文を行なっていきます。

def handle_signals(ctx, date, current):
  '''
  current: pd.DataFrame
  '''
  market_sig = current["market:sig"]
  done_syms = set([])

ここにあるcurrentはdateの当日のデータとシグナルを含んだ pandas.DataFrame オブジェクトになります。
また、後に使う利確損切りと売買注文が重複しないようにdone_symsというsetオブジェクトを作成します。
https://note.nkmk.me/python-set/

利確損切りの設定

  #利益確定売と損切り
  for (sym,val) in ctx.portfolio.positions.items():
    returns = val["returns"] 
    if returns < -0.03:
        sec = ctx.getSecurity(sym)
        sec.order(-val["amount"], comment="損切り(%f)" % returns)
        done_syms.add(sym)
    elif returns > 0.05:
        sec = ctx.getSecurity(sym)
        sec.order(-val["amount"], comment="利益確定売(%f)" % returns)
        done_syms.add(sym)

portfolio.positionsという辞書の中にあるreturnsというデータを用いて損益率を定義します。
損益率が-3%となったら損切りとして全部売り、+5%となったら利益確定売りとして全部売ります。

買い注文の設定

買い注文の設定を行います。
今回はorder_targetを用いて各銘柄の総保有数が1000株になるように注文します。

# 買いシグナル
  buy = market_sig[market_sig > 0.0]
  for (sym, val) in buy.items():
    if sym in done_syms:
      continue
    sec = ctx.getSecurity(sym)
    sec.order_target(1000, orderType=maron.OrderType.MARKET_OPEN, comment="SIGNAL BUY")
    #ctx.logger.debug("BUY: %s,  %f" % (sec.code(), val))
    pass

まずmarket_sigが0より大きいのものをbuyと定義します。
buyがTrueのもののうち、そしてdone_symsに銘柄が入っている(先に利確損切りがされている)場合何も注文しません。
もしdone_symsに銘柄が入っていない場合で購入できるcashがあれば注文します。
また約定執行のタイミングは翌日の始値、翌日の終値、また指値での執行が可能です。
今回だと約定は翌日の始値の価格で執行されます。

注文方法のリファレンスはコチラ
約定執行のリファレンスはコチラ

売り注文の設定

同様に売り注文も行います。今回売り注文を行なった銘柄は全て売るようにしています。

  # 売りシグナル
  sell = market_sig[market_sig < 0.0]
  for (sym, val) in sell.items():
    if sym in done_syms:
      continue
    sec = ctx.getSecurity(sym)
    sec.order_target_percent(0,orderType=maron.OrderType.MARKET_OPEN, comment="SIGNAL SELL")
    #ctx.logger.debug("SELL: %s,  %f" % (sec.code(), val))
    pass

まずmarket_sigが0より小さいのものをsellと定義します。
sellがTrueのもののうち、そしてdone_symsに銘柄が入っている(先に利確損切りがされている)場合何も注文しません。もしdone_symsに銘柄が入っていない場合は全部売ります(ポートフォリオのうち保有株の時価総額が0%となるようにします。)

バックテスト

今回は過去3年、初期資金量1000万円でバックテストします。
このようなグラフが描画されれば成功です!
スクリーンショット 2019-08-21 18.15.54.png

バックテストではグラフ描画も可能です。

スクリーンショット 2019-08-21 18.18.25.png

終わりに

QuantXはプラットフォーム以外でもSDKを用いて使うことができます。
SDKに関してはコチラ

また、Qiitaの方にたくさん記事があるので是非みなさんも試してオリジナルのアルゴリズムを販売もしくは運用して使ってみてください!
開発者の方へオススメの記事はコチラ

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up