LoginSignup
4
6

More than 3 years have passed since last update.

Quantxで出来高移動平均線を実装してみた

Last updated at Posted at 2020-02-18

自己紹介

某M大学理工学部2年の株式会社SmartTradeでインターンをしている益川と申します。まだまだ初心者なので拙いところもたくさんあるかと思いますが、最後までお付き合いいただけたら幸いです。

この記事でやること

当社インターン3週間目ということで、Quantx Factoryで実装したアルゴリズムを紹介したいと思います。今回は有名なテクニカル指標の一つである、出来高移動平均線による株の売買アルゴリズム実装します。

QuantX Factoryとは?

株式会社Smart Tradeが

金融の民主化

を理念に、トレードアルゴリズムを誰もが簡単に開発することのできるプラットフォーム"QuantX Factory"を開発しました。

強み

  • 初心者の挫折しがちな環境構築が不要
  • 各銘柄の終値や高値、出来高などの各種データが用意されていて,データセットの用意が不要
  • 開発したトレードアルゴリズムは審査を通過すればQuantX Storeで販売可能
  • 上級者の方にはQuantX Labといった開発環境も (引用元:公式ドキュメント)

本題

出来高移動平均線の仕組みを理解する

出来高とは

  • その日売買の成立した株数のこと
  • 売買の活発具合を把握できる
  • 下画像下部のヒストグラムのこと

alt

「出来高は株価に先行する」

と言われ、株価の動きよりも先に出来高に変化がみられると言われています。つまり、出来高の移動平均線を用いたらより早く買いサインや売りサインが分かるかもしれません

移動平均線とは

alt

  • 数日分の終値を合計して日数で割ることで得られる平均値をプロットした折れ線グラフのこと
  • 「短期線」と「長期線」の二本を観察しながら、売買のサインを見極める
    • 短期線:参照する日数の短い移動平均線(一般的に5日)
    • 長期線:参照する日数の長い移動平均線(一般的に25日)
  • ゴールデンクロス時に買い、デッドクロス時に売りのシグナルをそれぞれ出す
  • 詳細はこちらを参考にしてください

ゴールデンクロス、デッドクロスとは

  • ゴールデンクロス:moneybag:
    alt

    • 短期線が長期線を上回る瞬間のことであり、下落トレンドが上昇トレンドに転換することを示す
    • 買いのサインとして使われる
  • デッドクロス:skull:
    alt

    • 短期線が長期線を下回る瞬間のことで、上昇トレンドが下落トレンドに転換することを示す。
    • 売りのサインとして使われる

出来高移動平均線とは

  • 数日分の終値の代わりに、数日分の出来高の移動平均をとったもの
  • 買いシグナル、売りシグナルの出し方は移動平均線と同様
短期線と長期線の状況 判断の目安
ゴールデンクロス発生 買いシグナル
短期線 > 長期線 上昇トレンド
短期線 < 長期線 下落トレンド
デッドクロス発生 売りサイン

コード


# 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):
  # 設定
  ctx.logger.debug("initialize() called")
  ctx.high_term = 10
  ctx.holding_days = 5
  ctx.configure(
    channels={               # 利用チャンネル
      "jp.stock": {
        "symbols": [
            "jp.stock.2914", #JT(日本たばこ産業)
            "jp.stock.8766", #東京海上ホールディングス
            "jp.stock.8031", #三井物産
            "jp.stock.8316", #三井住友フィナンシャルグループ
            "jp.stock.8411", #みずほフィナンシャルグループ
            "jp.stock.9437", #NTTドコモ
            "jp.stock.4502", #武田薬品工業
            "jp.stock.8058", #三菱商事
            "jp.stock.9433", #KDDI
            "jp.stock.9432", #日本電信電話
            "jp.stock.7267", #ホンダ(本田技研工業)
            "jp.stock.8306", #三菱UFJフィナンシャル・グループ
            "jp.stock.4503", #アステラス製薬
            "jp.stock.4063", #信越化学工業
            "jp.stock.7974", #任天堂
            "jp.stock.6981", #村田製作所
            "jp.stock.3382", #セブン&アイ・ホールディングス
            "jp.stock.9020", #東日本旅客鉄道
            "jp.stock.8802", #三菱地所
            "jp.stock.9022", #東海旅客鉄道
            "jp.stock.9984", #ソフトバンクグループ
            "jp.stock.6861", #キーエンス
            "jp.stock.6501", #日立製作所
            "jp.stock.6752", #パナソニック
            "jp.stock.6758", #ソニー
            "jp.stock.6954", #ファナック
            "jp.stock.7203", #トヨタ自動車
            "jp.stock.7751", #キヤノン
            "jp.stock.4452", #花王
            "jp.stock.6098", #リクルートホールディングス     
        ],
        "columns": [
          "close_price",     # 終値
          "close_price_adj", # 終値(株式分割調整後)
          "volume_adj",     # 出来高
          #"txn_volume",     # 売買代金
        ]
      }
    }
  )

  # シグナル定義
  def _my_signal(data):
    #出来高のデータを取得
    vol = data["volume_adj"].fillna(method = "ffill")


    syms = data.minor_axis   # 銘柄リストの作成
    dates = data.major_axis  # 日付リストの作成


    #出来高移動平均
    mav5 = vol.rolling(window = 5, center = False).mean()
    mav25 = vol.rolling(window = 25, center = False).mean()

    ratio = mav5 / mav25

    #出来高移動平均線のゴールデンクロス、デッドクロス
    vol_golden = (ratio >= 1.04) & (ratio.shift(1) < 1.0) #(mav5 > mav25) & (mav5.shift(1) < mav25.shift(1))
    vol_dead = (ratio <= 0.97)  & (ratio.shift(1) > 1.0)  #(mav5 < mav25) & (mav5.shift(1) > mav25.shift(1))
    #shiftメソッドによりDataFrameに入っているデータを一つずらし、前日の比率と比較してゴールデンクロス、デッドクロスを判断しています

    #売買シグナル生成部分

    buy_sig = vol_golden
    sell_sig =  vol_dead 

    #market_sigという全て0が格納されているデータフレームを作成
    market_sig = pd.DataFrame(data=0.0, columns=syms, index=dates)

    #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
    # ctx.logger.debug(market_sig)

    return {
      "mav5:g2": mav5,
      "mav25:g2": mav25,
      "market:sig": market_sig,
    }

  # シグナル登録
  ctx.regist_signal("my_signal", _my_signal)

def handle_signals(ctx, date, current):  # 日ごとの処理部分
  '''
  current: pd.DataFrame
  '''
  # initializeの_my_signalで生成したシグナルをmarket_sigに格納
  market_sig = current["market:sig"]
  done_syms = set([])  # 利益確定及び損切りが行われた銘柄を格納するset型
  none_syms = set([])  # portfolio.positionsに存在しない銘柄を格納するset型
  # portfolio.positions(保有している銘柄)に対象銘柄(sym)が存在するかのチェック
  for (sym, val) in market_sig.items():
    if sym not in ctx.portfolio.positions:
      none_syms.add(sym)
  # portfolio.positions(保有している銘柄)のそれぞれの銘柄(sym)の保有株数が0ではないかのチェック
  for (sym, val) in ctx.portfolio.positions.items():
    if val["amount"] == 0:
      none_syms.add(sym)
  # 損切り、利益確定(利確)の設定
  # 所有している銘柄を1つずつ確認する繰り返し処理
  for (sym, val) in ctx.portfolio.positions.items():
    # 損益率の取得
    returns = val["returns"]
    if returns < -0.03:  # 損益率が-3%未満(絶対値で3%より大きい損)の場合
      # 損切りのための売り注文
      sec = ctx.getSecurity(sym)
      sec.order(-val["amount"], comment="損切り(%f)" % returns)
      # 利益確定及び損切りが行われた銘柄を格納するset型にsymに代入されている銘柄を追加
      done_syms.add(sym)
    elif returns > 0.05:  # 損益率が+5%より大きい場合
      # 利益確定(利確)のための売り注文
      sec = ctx.getSecurity(sym)
      sec.order(-val["amount"], comment="利益確定売(%f)" % returns)
      # 利益確定及び損切りが行われた銘柄を格納するset型にsymに代入されている銘柄を追加
      done_syms.add(sym)
  buy = market_sig[market_sig > 0.0]  # 買いシグナル
  for (sym, val) in buy.items():  # 買いシグナルが出ている銘柄を1つずつ処理
    # done_symsまたはnone_symsにsymがある場合
    if sym in done_syms:
      continue  # 処理をスキップ
    # 買い注文
    sec = ctx.getSecurity(sym)
    sec.order(sec.unit() * 1, orderType=ot, comment="SIGNAL BUY")
    # 買い注文のログを下に出力したい場合は以下のコメントアウトを外してください(長期間注意)
    #ctx.logger.debug("BUY: %s,  %f" % (sec.code(), val))
  sell = market_sig[market_sig < 0.0]  # 売りシグナル
  for (sym, val) in sell.items():  # 売りシグナルが出ている銘柄を1つずつ処理
    # done_symsまたはnone_symsにsymがある場合
    if (sym in done_syms) | (sym in none_syms):
      continue  # 処理をスキップ
    # 売り注文
    sec = ctx.getSecurity(sym)
    sec.order(sec.unit() * -1,orderType=ot, comment="SIGNAL SELL")
  • 完成コードはこちら
  • 各行のコメントにアルゴリズムの詳細は記述してあります。

結果

alt

評価・考察

  • SharpRatio(投資効率)はそこそこ良い
  • 一年間の損益率も18%と上々
  • 個別の銘柄を見ると急落、急騰に弱いかも?
  • 例:武田薬品工業の2019年2月18日から2020年2月18日までの値動き alt

終わりに

この3週間でQuantX Factoryによる基本的なアルゴリズム開発の仕方が大分身についてきました!
今後は出来高移動平均線と他のテクニカル指標を組み合わせたり、機械学習を取り入れるなどして、アルゴリズムの品質をもっと向上させたく思います。以上でこの記事を終えようと思います。拙い文章を最後まで読んでいただきありがとうございました!

免責注意事項

このコード・知識を使った実際の取引で生じた損益に関しては一切の責任を負いかねますので御了承下さい。

materials

https://qiita.com/Miku_F/items/49c20fcbbbfae59cb9a6

https://www.kabutore.biz/idoheikinsen.html

https://ib-college.com/idou-heikin

4
6
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
4
6