日本株システムトレードプラットフォーム 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.XXXX
の XXXX
が銘柄コードとなります。
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 とかを合わせて使う必要がありそう。
今回はそこまでできませんでした。。