@kei-tenniacoさんと組んでアルゴリズムを作りました。
#トレーリングストップの説明
トレーリングストップとは、株価の上昇に合わせて損切りラインを更新する売買方法です。QuantX Factoryで現在使用されているアルゴリズムの多くでは、損切りと利確ラインを株価と独立に一定に設定しています。
トレーリングストップを使っていない例:
このように、株価が急増するときは利確ラインで売ってしまい、利益を失います。
トレーリングストップを使っている例:
トレーリングストップを使えば、損切りラインを株価とともに動かせて、急増している株価から最大の利益を得ることができます。
株価が下がったら損切りラインでSELLをして、株価が上がったらトレーリングストップを使って高い株価で売ります。
損切りラインが上下に動いているため、利確ラインはいりません。
#ゴンさんのトレーリングストップの間違え
2019年の3月にゴンさんがトレーリングストップのプログラムを作りました。それはこちら(1)とこちら(2)にあります。
彼の最初のコードには欠点が三つあります。
1. 利確がまだ残っている。
2. トレーリングストップポジションを保つため、30日間のmaxを使用している。これにより、実際にはポジションを保持していなかった時期のmax値を使用してしまったり、本来使用すべきmax値を逃してしまったりと、関係のないデータを使ってしまう可能性がある。一定の日にちを入力するよりもポジションを保持した時からのMAX値を使った方がいい。
3. 損切り時での使用でなく、SELL_SIGにトレーリングストップを導入してしまっている。これにより、ポジションを持っていない際もSELL_SIGがTrueとなり、0.0.5エンジンではBUYとSELLのシグナルが打ち消し合い、買いが入れれなくなってしまう。(0.0.1ではBUYのあと一瞬でSELLをしてしまう。)よって、これはどのバージョンでも効率的ではない。
また、ゴンさんの二つ目の記事は1番目の欠点しか直していません。これを解決してより良いトレーリングストップを作ります。
#完成したコード
一般的なWMAモデル(対象銘柄:アンジェス)のコードにトレーリングストップを実装しました。
(0.0.5エンジンを使っています。)
# 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):
# 設定, 6行目から20行目は変えないようにお願いします。
ctx.flag_profit = False #利益確定売りを用いるかTrueなら用いるFalseなら用いない
ctx.flag_loss = True #損切りを用いるかTrueなら用いるFalseなら用いない
ctx.flag_trailing_stop = True #トレーリングストップを用いるかTrueなら用いるFalseなら用いない
ctx.loss_cut = -0.03 #損切りのボーダーマイナス%
ctx.profit_taking = 0.05 #利益確定売りのボーダープラス%
ctx.trailing_stop = -0.03 #トレーリングストップのmax値比較ボーダーマイナス%
ctx.codes = [4563]
ctx.symbol_list = ["jp.stock.{}".format(code) for code in ctx.codes]
ctx.configure(
channels={ # 利用チャンネル
"jp.stock": {
"symbols":ctx.symbol_list,
"columns": [
#"open_price_adj", # 始値(株式分割調整後)
#"high_price_adj", # 高値(株式分割調整後)
#"low_price_adj", # 安値(株式分割調整後)
"close_price", # 終値
"close_price_adj", # 終値(株式分割調整後)
# "volume_adj", # 出来高
# "txn_volume", # 売買代金
]}})
# シグナル定義
def _my_signal(data):
# この部分に作成するアルゴの指標を書き込んで下さい。
#各銘柄の終値(株式分割調整後)を取得、欠損データの補完
cp = data["close_price_adj"].fillna(method="ffill")
for (sym, val) in cp.items():
# valがすべてNaNだったときはtalibで計算しない
isna = val.isnull().all()
if isna:
continue
#単純移動平均線(SMA)の設定
#データの入れ物を用意
s_wma = pd.DataFrame(data=0,columns=[], index=cp.index)
m_wma = pd.DataFrame(data=0,columns=[], index=cp.index)
l_wma = pd.DataFrame(data=0,columns=[], index=cp.index)
#TA-Libによる計算
for (sym,val) in cp.items():
s_wma[sym] = ta.WMA(cp[sym].values.astype(np.double), timeperiod=5)
m_wma[sym] = ta.WMA(cp[sym].values.astype(np.double), timeperiod=25)
l_wma[sym] = ta.WMA(cp[sym].values.astype(np.double), timeperiod=75)
#SMAの売買シグナルの定義
s_wma_ratio = s_wma/m_wma
l_wma_ratio = m_wma/l_wma
buy_sig = (s_wma_ratio > 1.00) & (s_wma_ratio < 1.10) & (l_wma_ratio > 1.00) & (l_wma_ratio < 1.10)
sell_sig = (((s_wma_ratio < 0.99) & (l_wma_ratio < 0.99)))
# market_sigという全て0が格納されているデータフレームを作成
# market_sigに関しては右画面のノートをご覧ください⑥
market_sig = pd.DataFrame(data=0.0, columns=cp.columns, index=cp.index)
# 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
# loggerに関しましては右画面のノートをご覧ください⑦
# ctx.logger.debug(market_sig)
return {
"wma5":s_wma,
"wma25":m_wma,
"wma75":l_wma,
"market:sig": market_sig,
}
# シグナル登録
ctx.regist_signal("my_signal", _my_signal)
def handle_signals(ctx, date, current):
'''
current: pd.DataFrame
'''
market_sig = current["market:sig"]
done_syms = set([])
for (sym,val) in ctx.portfolio.positions.items():
returns = val["returns"]
trailing = val["max_returns"] - returns
if (ctx.flag_loss) & (returns < ctx.loss_cut):
sec = ctx.getSecurity(sym)
sec.order(-val["amount"], comment="損切り(%f)" % returns)
done_syms.add(sym)
elif (ctx.flag_profit) & (returns > ctx.profit_taking):
sec = ctx.getSecurity(sym)
sec.order(-val["amount"], comment="利益確定売(%f)" % returns)
done_syms.add(sym)
elif (ctx.flag_trailing_stop) & (returns > 0.10) & (ctx.trailing_stop > 0.01):
sec = ctx.getSecurity(sym)
sec.order(-val["amount"], comment="トレーリング10%利確(%f)" % returns)
done_syms.add(sym)
elif (ctx.flag_trailing_stop) & (returns > 0.05) & (ctx.trailing_stop > 0.02):
sec = ctx.getSecurity(sym)
sec.order(-val["amount"], comment="トレーリング5%利確(%f)" % returns)
done_syms.add(sym)
elif (ctx.flag_trailing_stop) & (returns - val["max_returns"] < ctx.trailing_stop):
sec = ctx.getSecurity(sym)
sec.order(-val["amount"], comment="トレーリング利確(%f)" % returns)
done_syms.add(sym)
# 買いシグナル
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_percent(0.8, orderType=ot, comment="SIGNAL BUY")
#ctx.logger.debug("BUY: %s, %f" % (sec.code(), val))
pass
# 売りシグナル
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=ot, comment="SIGNAL SELL")
#ctx.logger.debug("SELL: %s, %f" % (sec.code(), val))
#コードの説明
###def initialize:
ctx.flag_profit = False #利益確定売りを用いるかTrueなら用いるFalseなら用いない
ctx.flag_loss = True #損切りを用いるかTrueなら用いるFalseなら用いない
ctx.flag_trailing_stop = True #トレーリングストップを用いるかをTrue/Falseで決める
ctx.loss_cut = -0.03 #損切りのボーダーマイナス%
ctx.profit_taking = 0.05 #利益確定売りのボーダープラス%
ctx.trailing_stop = -0.03 #トレーリングストップのmax値比較ボーダーマイナス%
ここでは損切りと利確ラインのボーダー%を決めます。トレーリングストップを使うので、利確はいりません。そのため、ctx.flag_profitをFalseになっています。
ctx.trailing_stop はトレーリングストップを株価の何パーセント下にするかを決めています。
トレーリングストップの弱点は変動が大きすぎて急増する前に売ってしまうことがあります。そのため、変動が激しいグラフでしたら、ctx.trailing_stopの設定幅を大きくした方が効果的です。
###def handle_signals:
elif (ctx.flag_trailing_stop) & (returns > 0.10) & (ctx.trailing_stop > 0.01):
sec = ctx.getSecurity(sym)
sec.order(-val["amount"], comment="トレーリング1%利確(%f)" % returns)
done_syms.add(sym)
elif (ctx.flag_trailing_stop) & (returns > 0.05) & (ctx.trailing_stop > 0.02):
sec = ctx.getSecurity(sym)
sec.order(-val["amount"], comment="トレーリング2%利確(%f)" % returns)
done_syms.add(sym)
elif (ctx.flag_trailing_stop) & (returns - val["max_returns"] < ctx.trailing_stop):
sec = ctx.getSecurity(sym)
sec.order(-val["amount"], comment="トレーリング利確(%f)" % returns)
done_syms.add(sym)
ここでは、トレーリングストップを実行しています。ゴンさんと同じく株価の利益率が10%を超えたらctx.trailing_stop を3%から1%に変えて。5%を超えたら2%に変えます。
#比べた結果
トレーリングストップを使っていないWMAモデル:
トレーリングストップを使ったWMAモデル:
トレーリングストップを使わずに利確ラインを使うと、急増で利益を失います。
(青はSELLです。)
ですが、利確ラインの代わりにトレーリングストップを使えば、効果的なSELLができます。