Posted at

QuantXでゴールデンクロスストラテジーを書いてみた.

More than 1 year has passed since last update.


ゴールデンクロス

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.configurecolumns に入れた値や,シグナル登録に使った関数で 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)興味がある方はぜひ参加してみてはいかがでしょうか.