ゴールデンクロス
QuantX で, ゴールデンクロスのサンプルコードを書いてみました.
ゴールデンクロスとは, 25日移動平均線を5日移動平均線が上抜いたもの。上昇相場への突入を暗示し、買いシグナルとなるそうです.
シグナル
調整済み終値で,前日は,長期移動平均が短期移動平均より大きかったが,当日,短期が長期を上回ったタイミング.
手仕舞い
損切り-3%,益出し+5%
コード
def initialize(ctx):
ctx.logger.debug("initialize() called")
# 設定
# ゴールデンクロスの期間設定
ctx.long_term = 25
ctx.short_term = 5
# ポートフォリオの何%をオーダーするか
ctx.target = 0.05
# 損切りと益出しの水準
ctx.loss_cut = -0.03
ctx.plofit = 0.05
# 銘柄と使う株価データの設定
ctx.configure(
channels={ # 利用チャンネル
"jp.stock": {
"symbols": [
"jp.stock.1305", # ダイワ 上場投信-トピックス
"jp.stock.9984", # ソフトバンク
"jp.stock.9983", # ファーストリテリング
"jp.stock.7201", # 日産
"jp.stock.9201", # JAL
"jp.stock.9202", # ANA
"jp.stock.7203" # トヨタ
],
"columns": ["close_price_adj", # 終値(株式分割調整後)
]}})
def _GOLDEN_CROSS(data):
# 欠損値を埋める
# cp は pandasのDataFrame(memo 参照)
cp = data["close_price_adj"].fillna(method="ffill")
ma_long_term = cp.rolling(window=ctx.long_term, center=False).mean()
ma_short_term = cp.rolling(window=ctx.short_term, center=False).mean()
# 長期移動平均を短期移動平均が上抜く
# 前日は ma_long > ma_short で,
# 当日は ma_long < ma_short
buy_signal = (ma_long_term < ma_short_term) & (ma_long_term.shift(1) > ma_short_term.shift(1))
return {
"MA_long" : ma_long_term,
"MA_short" : ma_short_term,
"Ratio": ma_short_term / ma_long_term - 1,
"buy:sig": buy_signal,
}
# シグナル登録
ctx.regist_signal("GOLDEN_CROSS", _GOLDEN_CROSS)
def handle_signals(ctx, date, current):
# ポジションクローズ
# ctx.portfolio.positionsは,Keyにシンボル名,Valueに現在の自分のポジション情報を辞書型で持つ,辞書.(memo 参照)
for (sym,val) in ctx.portfolio.positions.items():
rtn = val["returns"]
if rtn < ctx.loss_cut:
sec = ctx.getSecurity(sym)
# order_target_percent (memo 参照)
sec.order_target_percent(0, comment="損切り {:.2%}".format(rtn))
elif rtn > ctx.plofit:
sec = ctx.getSecurity(sym)
sec.order_target_percent(0, comment="益出し {:.2%}".format(rtn))
# 買いシグナル
# currentは該当日の株価やシグナルを含むDataFrame (memo 参照)
# DataFrameだと言うことをハッキリさせたかったので df という変数に入れた.
df = current.copy()
# シグナル["buy:sig"]がTrueだけ取得
df = df[df["buy:sig"]]
if not df.empty:
for (sym, val) in df.iterrows():
sec = ctx.getSecurity(sym)
sec.order_target_percent(ctx.target, comment= "シグナル買 {:.2%}".format(val["Ratio"]))
memo
cp
data["close_price_adj"] (cp) は,全テスト期間の調整済み終値が入った pandas の DataFrame です.
ここから察するに,まず指定されたテスト期間のデータを取得し,シグナルを計算してしまった後に,handle_signals
で各日のシミュレーションを行っているようです.
date | jp.stock.1305 | jp.stock.7201 | jp.stock.7203 | jp.stock.9201 | jp.stock.9202 | jp.stock.9983 | jp.stock.9984 |
---|---|---|---|---|---|---|---|
2017-10-27 | 1847 | 1101.5 | 7050 | 3838 | 4310 | 37870 | 10295 |
2017-10-30 | 1849 | 1108 | 7077 | 3846 | 4333 | 37610 | 10430 |
2017-10-31 | 1842 | 1098.5 | 6990 | 3870 | 4346 | 37610 | 9947 |
2017-11-01 | 1865 | 1109 | 7038 | 4001 | 4458 | 37620 | 10130 |
2017-11-02 | 1872 | 1111.5 | 7150 | 4012 | 4428 | 37770 | 10210 |
ctx.portfolio.positions
ctx.portfolio.positionsには,毎日のポジションが,下記のような辞書型で格納されています. .items()
を使うことでforループ処理を行い,益出しと損切りを行っています.
{'jp.stock.7201':
{'position_ratio': 0.50577838205302517, 'amount': 400, 'pnl': -11200.0,
'total_buy_price': 457600.0, 'value': 446400.0, 'portfolio_ratio': 0.044627950453377585,
'returns': -0.024475524475524479, 'buy_price': 457600.0, 'total_sell_price': 0.0,
'sell_price': 0.0, 'max_returns': 0.0},
'jp.stock.9202':
{'position_ratio': 0.49422161794697483, 'amount': 100, 'pnl': 13900.0,
'total_buy_price': 422300.0, 'value': 436200.0, 'portfolio_ratio': 0.04360822577903966,
'returns': 0.032914989344068113, 'buy_price': 422300.0, 'total_sell_price': 0.0,
'sell_price': 0.0, 'max_returns': 0.032914989344068113}}
....
current
handle_signals(ctx, date, current)
で必ず引数に渡す current には,毎日の株価やシグナルが DataFrame の形で入っています.Index にはシンボル名(銘柄名),Columns には,ctx.configure
の columns
に入れた値や,シグナル登録に使った関数で return した値,それと split_ratio
が入っているように見えます.
17:05:43 | split_ratio | close_price_adj | close_price | MA_long | MA_short | buy:sig |
---|---|---|---|---|---|---|
jp.stock.1305 | 1 | 1869 | 1869 | 1833.84 | 1869.8 | FALSE |
jp.stock.7201 | 1 | 1126.5 | 1126.5 | 1126.22 | 1142.4 | FALSE |
jp.stock.7203 | 1 | 7201 | 7201 | 6936.16 | 7182.2 | FALSE |
jp.stock.9201 | 1 | 4134 | 4134 | 4271.12 | 4228.2 | FALSE |
jp.stock.9202 | 1 | 4193 | 4193 | 4208.48 | 4265 | FALSE |
jp.stock.9983 | 1 | 48850 | 48850 | 46458 | 48522 | FALSE |
jp.stock.9984 | 1 | 8461 | 8461 | 8039.12 | 8435.4 | FALSE |
コードではbuy:sig がTrueかどうかでフィルターをかけ,オーダーを行っています.
order_target_percent
order_target_percent(pct)
で,ポジションの枠のpct
%分をオーダーする注文方法です. pctは 0<pct<=1
の float です.
ただ,この注文方法のドキュメントを探せなかったので,(公式のサンプルコードから推察して使いました...)「ポジションの枠」というのが正確になにを表すのかは,分かりません.今度勉強会の時に確認したいと思います.
成績の見方
バックテストが終わると,このような形で全体と個別株の成績を確認できます.シグナルに登録しておいた MA_long や MA_short も確認できるので,コードが思った通りに書けているかどうかの確認もでき,便利です.
書いてみた感想
- なれたら,簡単にコードは書けると思います.が,
- python と pandas の知識は必須です.
- ドキュメントがあまり整備されていないのは,やはりツライです...
- そもそも,ゴールデンクロスは,これでいいのか知らないので間違っていたら教えて下さい.(今さらw)
QuantX とは
QuantX ならびに,運営会社の SmartTrade さんに関しては, @hiroshimodaさんの
Pythonで投資アルゴリズムを開発してみるや,公式アルゴリズム開発入門 を参照して下さい.
週一で勉強会
SmartTradeさんで週一で, Pythonアルゴリズム勉強会が開かれています.私もちょくちょくおじゃましてます(^^).
純粋にPythonを学びたい方から,アルゴリズムを学びたい方,Fintechに興味ある方,などなど,色々な人たちが集まって,毎回楽しいので(お菓子も出るしw)興味がある方はぜひ参加してみてはいかがでしょうか.