LoginSignup
8
3

More than 5 years have passed since last update.

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

Posted at

ゴールデンクロス

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)興味がある方はぜひ参加してみてはいかがでしょうか.

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3