Help us understand the problem. What is going on with this article?

日本株システムトレードプラットフォーム QuantX でn日足(終値)を扱うアルゴリズムを実装する

More than 1 year has passed since last update.

日本株システムトレードプラットフォーム QuantX でn日足(終値)を扱うアルゴリズムを実装する

背景

  • Python による日本株システムトレードプラットフォームである QuantX ではデフォルトでアルゴリズムの実装をする際に、1日足の株価しか取得することができません
  • なので、n日分の株価を扱えるようにしてみようと思い、実装してみました

実装したアルゴリズム

下記がその内容のプログラムとなります。
ちなみに、BETWEEN_DAY の値を n にすることで n日足の終値を取得することができます。

ボリンジャーバンドの上端、下端を跨いだときに売買をするアルゴリズムです。
株価が下がりすぎた時を検出した場合は買い、逆の場合は売り、というのを行っております。

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

BETWEEN_DAY = 2

def initialize(ctx):

    # 設定
    ctx.configure(
        target="jp.stock.daily",
        channels={          # 利用チャンネル
            "jp.stock": {
              "symbols": [
                'jp.stock.1332',    #   日本水産(株)
                'jp.stock.1605',    #   国際石油開発帝石(株)
              ],
              "columns": [ "close_price_adj",]
            }
        }
    )

    def _TALIB_CALL(data):
      cp = data["close_price_adj"].fillna(method="ffill")
      cp_between_n = pd.DataFrame(data=0,columns=[], index=cp.index)
      buy_sig = pd.DataFrame(data=0,columns=[], index=cp.index)
      sell_sig = pd.DataFrame(data=0,columns=[], index=cp.index)

      tmp = 0
      for (sym,val) in cp.items():
        count = 0
        for [day,val_day] in val.items():
          if count % BETWEEN_DAY == 0:
            cp_between_n.loc[day,sym] = val_day
          else:
            cp_between_n.loc[day,sym] = np.nan
          count += 1

      cp_between_n.dropna(inplace=True)

      upperband = {}
      middleband = {}
      lowerband = {}

      rsi = pd.DataFrame(data=0,columns=[], index=cp_between_n.index)
      uband = pd.DataFrame(data=0,columns=[], index=cp_between_n.index)
      lband = pd.DataFrame(data=0,columns=[], index=cp_between_n.index)

      for (sym,val) in cp_between_n.items(): 
        upperband[sym], middleband[sym], lowerband[sym] = ta.BBANDS(
          cp_between_n[sym].values.astype(np.double),
          timeperiod=10,
          nbdevup=1,
          nbdevdn=1,
          matype=0)

        lband[sym] = lowerband[sym]
        uband[sym] = upperband[sym]

      lband2 = cp.copy()
      lband2[:] = 0
      uband2 = cp.copy()
      uband2[:] = 0
      uband2 = uband + uband2
      lband2 = lband + lband2

      for (sym,val) in cp.items(): 
        buy_sig[sym] = lband2[sym] > cp[sym]
        sell_sig[sym] = cp[sym] > uband2[sym]

      buy_sig = (buy_sig == True)
      sell_sig = ((sell_sig == True) & (buy_sig == False))

      return {
        "upperband:price": uband2,
        "lowerband:price": lband2,
        "buy:sig": buy_sig,
        "sell:sig": sell_sig,
      }

    # シグナル登録
    ctx.regist_signal("TALIB", _TALIB_CALL)

def handle_signals(ctx, date, current):

    done_syms = set([])
    for (sym,val) in ctx.portfolio.positions.items():
      amount = val["amount"]
      if amount > 0:
        done_syms.add(sym)

    ratio = current["buy:sig"]
    df = ratio[ratio == 1.0]
    for (sym,val) in df.items():
      if sym in done_syms:
        continue

      sec = ctx.getSecurity(sym)
      sec.order_target_percent(0.4, comment="シグナル買(%f)" % val)
      pass

    ratio = current["sell:sig"]
    df = ratio[ratio == 1.0]
    for (sym,val) in df.items():

      sec = ctx.getSecurity(sym)
      sec.order_target_percent(0, comment="シグナル売(%f)" % val)
      pass

    pass

うん、長い。

解説

  • 上記コードに至るまで、Jupyter 上で模擬データを使って検証したのでそれに沿って解説していきます。

模擬データを準備する

まずは、QuantX で扱われている時系列データを模擬したデータを定義します。
これは、下記のように定義できます。

実際には jp.stock.XXXXXXXX が銘柄コードとなります。

import pandas as pd
import numpy as np

# indexに名前を指定しています
cp = pd.DataFrame([
    [2000, 2015],
    [2096, 2018],
    [2066, 2034],
    [2076, 2054],
    [2026, 2074],
    [2076, 2044]],
    index = ['2015-01-05 ', '2015-01-06', '2015-01-07', '2015-01-08', '2015-01-09', '2015-01-12', '2015-01-13'],
    columns=["jp.stock.XXXX",'jp.stock.YYYY']
)
#cp
jp.stock.XXXX jp.stock.YYYY
2015-01-05 2000 2015
2015-01-06 2024 2025
2015-01-07 2096 2018
2015-01-08 2066 2034
2015-01-09 2076 2054
2015-01-12 2026 2074
2015-01-13 2076 2044

n日毎のデータとして整形する

次に、このデータセットを n 日ごとに整形するようにします。

ここでは、2日として、処理します。

BETWEEN_DAY = 2

cp_ndays = pd.DataFrame(data=0,columns=[], index=cp.index)

tmp = 0
for (sym,val) in cp.items():
  count = 0
  for [day,val_day] in val.items():
    if count % BETWEEN_DAY == 0:
      cp_ndays.loc[day,sym] = val_day
      tmp = val_day
    else:
      cp_ndays.loc[day,sym] = np.nan

    count += 1

cp_ndays.dropna(inplace=True)
cp_ndays
jp.stock.XXXX jp.stock.YYYY
2015-01-05 2000.0 2015.0
2015-01-07 2096.0 2018.0
2015-01-09 2076.0 2054.0
2015-01-13 2076.0 2044.0

n日毎のデータからボリンジャーバンドの下端、上端を算出する

次に、TA-lib を利用して3日ボリンジャーバンドを実装します。

lband には ボリンジャーバンドの下端値

uband には ボリンジャーバンドの上端値

が代入されます。

import talib as ta

upperband = {}
middleband = {}
lowerband = {}

uband = pd.DataFrame(data=0,columns=[], index=cp_ndays.index)
lband = pd.DataFrame(data=0,columns=[], index=cp_ndays.index)

for (sym,val) in cp_ndays.items(): 
  upperband[sym], middleband[sym], lowerband[sym] = ta.BBANDS(
    cp_ndays[sym].values.astype(np.double),
    timeperiod=3,
    nbdevup=1,
    nbdevdn=1,
    matype=0)

  lband[sym] = lowerband[sym]
  uband[sym] = upperband[sym]
lband
jp.stock.XXXX jp.stock.YYYY
2015-01-05 NaN NaN
2015-01-07 NaN NaN
2015-01-09 2015.978500 2011.279955
2015-01-13 2073.238576 2023.493591
uband
jp.stock.XXXX jp.stock.YYYY
2015-01-05 NaN NaN
2015-01-07 NaN NaN
2015-01-09 2098.688166 2046.720045
2015-01-13 2092.094757 2053.839742

シグナル出力用のデータフレームに整形する

この状態のままでは、シグナルとして出力できないため
整形元のデータフレームと同じ形に当てはめる必要がある。

値が全て 0 のデータフレームとして lband2, uband2 を定義し、
lband, uband と同じ日付にて結合させている。

lband2 = cp.copy()
lband2[:] =0
uband2 = cp.copy()
uband2[:] = 0
uband2 = uband + uband2
uband2
jp.stock.XXXX jp.stock.YYYY
2015-01-05 NaN NaN
2015-01-06 NaN NaN
2015-01-07 NaN NaN
2015-01-08 NaN NaN
2015-01-09 2098.688166 2046.720045
2015-01-12 NaN NaN
2015-01-13 2092.094757 2053.839742
lband2 = lband + lband2
lband2
jp.stock.XXXX jp.stock.YYYY
2015-01-05 NaN NaN
2015-01-06 NaN NaN
2015-01-07 NaN NaN
2015-01-08 NaN NaN
2015-01-09 2015.978500 2011.279955
2015-01-12 NaN NaN
2015-01-13 2073.238576 2023.493591

終値との比較を行い、バンドを跨いだかどうか判定する

buy_sig = uband2 > cp
sell_sig = lband2 < cp
buy_sig
jp.stock.XXXX jp.stock.YYYY
2015-01-05 False False
2015-01-06 False False
2015-01-07 False False
2015-01-08 False False
2015-01-09 True True
2015-01-12 False False
2015-01-13 True True
sell_sig
jp.stock.XXXX jp.stock.YYYY
2015-01-05 False False
2015-01-06 False False
2015-01-07 False False
2015-01-08 False False
2015-01-09 True True
2015-01-12 False False
2015-01-13 True True

このデータフレームのデータがシグナルとなって、
売買タイミングの通知をユーザにしてくれる感じですね。

この検証を基に QuantX 上に組み込んだものが最初のコードとなります。

このやり方だと終値しか取得できないから、高値、安値を取得するには min とか max とかを合わせて使う必要がありそう。

今回はそこまでできませんでした。。

kozzy
Ruby だったり Python だったり Vue.js あたりと戯れているサーバーエンジニア。フロントエンド、データ可視化、分析の領域にも興味あり。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした