Python3
fintech
金融
SystemTrade
Quantx
FinTechDay 11

QuantX FactoryでMACDのアルゴリズムを作ってみよう。


まず自己紹介から

某理系大学で電気を専攻してる大学2年生のkatakyoです。

Pythonも金融の知識もガチ初心者です。

最近SmartTrade社の方でインターン始めました。


QuantX Factoryって何?

URL:QuantX Factory ホームページ

開いてみるとこんな画面が出てきます。

スクリーンショット 2018-11-28 18.14.48.png

QuantX Factoryとは

ブラウザ上で動かせる、株式や仮想通貨などの売買ルール(アルゴリズム)の作成、販売ができるシステムトレードプラットフォームです。pythonというプログラミング言語を用いて動かします。

開発デモを体験するというボタンをクリックするとログインしてくださいという画面がでます。

Facebook,Googleアカウントなどで簡単にログインできるのでログインしちゃいましょう!

ログインが終わると何やら難しそうなコードが出てきます。スクリーンショット 2018-11-29 9.22.17.png

これは移動平均線という金融指標を用いたアルゴリズムです。

デフォルトですでに完成されています!

移動平均線について知りたい方はコチラ

Pythonでコードをいじり、バックテストをすることで株式売買のシミュレーションができちゃいます!!


今回はMACDという金融指標を使ってみる


MACDとは

MACDとは、「Moving Average Convergence Divergence」を略したもので、「マクディー」もしくは「エムエーシーディー」と呼ばれています。(自分は「マクディー」派)

MACDは移動平均線を進化させて、より精度の高い分析を行うために開発されたテクニカル分析です。


MACDの計算方法


単純移動平均と指数平滑移動平均の違い

MACDは単純移動平均とは異なり、指数平滑移動平均というもの用います。

単純移動平均と指数平滑移動平均のそれぞれの計算式の例は以下のようになります。

単純移動平均=(1日目の終値+2日目の終値+3日目の終値)/3日間

指数平滑移動平均=(1日目の終値+2日目の終値+3日目の終値×2)/3日間+1日

式をみるとわかると思いますが、最終日の終値を2倍で計算するため、最新の株価の値が大きく反映されやすくなっています。そのため、単純移動平均では実際の相場と少し遅れて形状が変動しますが、指数平滑移動平均だと実際の値動きに近い形で平均値が算出できるというわけです。


MACDの計算式

MACDは、期間の短い指数平滑移動平均である短期平均と、期間の長い指数平滑移動平均である長期平均を用いて、短期平均から長期平均の値を引いた差で求められます。

MACD=短期平均-長期平均

シグナル=MACDの単純移動平均線

一般的には短期平均の期間が12日、長期平均の期間が26日で、シグナルの平均期間は9日を利用する場合が多いです。

今回はPythonのTa-Libというライブラリーを使うので初心者の方は計算方法ガン無視でも大丈夫です!!


MACDの売買サインは?

では、上記の数値がどのような値になった時に売り買いするといいのでしょうか?

ショーボンドホールディングス(株)の直近三ヶ月の値を例にとって見てみます。

スクリーンショット 2018-12-05 18.49.54.png

MACDを利用した売買ポイントの見極め方はとても単純です。

MACDがシグナルを下から上へクロスしたポイント(ゴールデンクロス)が買いサインとなり、反対にMACDがシグナルを上から下へクロスしたポイント(デッドクロス)は売りサインになります。

今回の例で行くと図の感じになります。

スクリーンショット 2018-12-06 15.17.16.png

このようなシグナルを出すアルゴリズムを今回は作っていきます!


MACDを使ったアルゴリズムを作ってみる

以下アルゴリズムを買いてみます!!

今回はコメントアウトをはずしながらコードの説明をしていきます。

以下のコードをコピーしてみて下さい。


macd.py

  #ライブラリーの設定

# import pandas as pd
# import talib as ta
# import numpy as np

#関数の初期化
def initialize(ctx):
#設定
ctx.logger.debug("initialize() called")
ctx.configure(
target="jp.stock.daily",
channels={ #銘柄選択
"jp.stock": {
"symbols": [
"jp.stock.1414", "jp.stock.2269",
"jp.stock.2292", "jp.stock.2371",
"jp.stock.2379", "jp.stock.2413",
"jp.stock.2427", "jp.stock.2702",
"jp.stock.2782", "jp.stock.2809",
"jp.stock.2930", "jp.stock.3003",
"jp.stock.3053", "jp.stock.3064",
"jp.stock.3092", "jp.stock.3141",
"jp.stock.3382", "jp.stock.3407",
"jp.stock.3665", "jp.stock.3676",
"jp.stock.4063", "jp.stock.4343",
"jp.stock.4452", "jp.stock.4519",
"jp.stock.4543", "jp.stock.4704",
"jp.stock.4751", "jp.stock.4911",
"jp.stock.4974", "jp.stock.5108",
"jp.stock.6028", "jp.stock.6098",
"jp.stock.6194", "jp.stock.6503",
"jp.stock.6645", "jp.stock.6758",
"jp.stock.6866", "jp.stock.7269",
"jp.stock.7272", "jp.stock.7606",
"jp.stock.7936", "jp.stock.8020",
"jp.stock.8252", "jp.stock.8591",
"jp.stock.8929", "jp.stock.9007",
"jp.stock.9086", "jp.stock.9433",
"jp.stock.9474", "jp.stock.9613"
],
"columns": [
#"open_price_adj", # 始値(株式分割調整後)
#"high_price_adj", # 高値(株式分割調整後)
#"low_price_adj", # 安値(株式分割調整後)
#"close_price", # 終値
"close_price_adj", # 終値(株式分割調整後)
]
}
}
)
#売買シグナル生成部分
def _my_signal(data):

#各銘柄の終値(株式分割調整後)を取得、欠損データの補完
# df_close = data["close_price_adj"].fillna(method='ffill')

#MACDの設定
# d_macd = dict()
# d_macdsignal = dict()
# d_macdhist = dict()

# for symbol in data.minor_axis:
# macd, macdsignal, macdhist = ta.MACD(df_close[symbol].values.astype(np.double), fastperiod=12 , slowperiod=26, signalperiod=9)

# d_macd[symbol] = macd
# d_macdsignal[symbol] = macdsignal
# d_macdhist[symbol] = macdhist

# df_macd = pd.DataFrame(d_macd, index=data.major_axis)
# df_macdsignal = pd.DataFrame(d_macdsignal, index=data.major_axis)
# df_macdhist = pd.DataFrame(d_macdhist, index=data.major_axis)

#macdの売り買いのサインを定義
# df_goldencross = (df_macd > df_macdsignal) & (df_macd.shift(1) < df_macdsignal.shift(1))
# df_deadcross = (df_macd < df_macdsignal) & (df_macd.shift(1) > df_macdsignal.shift(1))

#売買シグナルの設定
# buy_sig = df_goldencross
# sell_sig = df_deadcross

return{
# "MACD": df_macd,
# "MACDSignal": df_macdsignal,
# "MACDHist": df_macdhist,
# "buy:sig": buy_sig,
# "sell:sig": sell_sig,
}

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

def handle_signals(ctx, date, current):

df = current.copy()

# 買いシグナル
df_long = df[df["buy:sig"]]
if not df_long.empty:
for (sym, val) in df_long.iterrows():
#ctx.logger.info(val)
sec = ctx.getSecurity(sym)
msg = "買いシグナル"
sec.order_target_percent(0.10, comment= msg)

# 売りシグナル
df_sell = df[df["sell:sig"]]
if not df_sell.empty:
for (sym, val) in df_sell.iterrows():
sec = ctx.getSecurity(sym)
msg = "売りシグナル"
sec.order_target_percent(0, comment= msg)


今回は説明の為にいくつかのコードをコメントアウト(行頭に#)をつけました。

コメントアウトとはその行のコードを無効にする効果があります。


コードの説明


ライブラリーと各種データの設定

まずライブラリーの設定を行います。


macd.py

  #ライブラリーの設定

import pandas as pd
import talib as ta
import numpy as np

コードの2〜4行目のコメントアウトをはずしてください。

pandasとnumpyはQuantXでは普段使いますが、今回MACDの計算を行うにあたってTa-Libというライブラリーが非常に便利なのでimportします。

Ta-Libは他の金融指標を扱う際にも非常に役に立つので、慣れていきましょう。

6~50行では今回使用する銘柄とデータ量を決めます。

データ量は終値(株式分割調整後)のみを使います。


売買シグナルの設定

次に売買のシグナル部分(売り買いするタイミングを決める部分)を設定していきます。


使うデータ量の設定


macd.py

  #売買シグナル生成部分

def _my_signal(data):

#各銘柄の終値(株式分割調整後)を取得、欠損データの補完
df_close = data["close_price_adj"].fillna(method='ffill')


コードの52行目と55行目とのコメントアウトをはずしてください。

52行目では売買シグナルを生成する関数の定義しています。

55行目では各銘柄の終値(株式分割調整後)のデータを取得します。データではたまに欠損値(値がNaNとなり、計算できない値)が含まれる場合があります。欠損値があると計算ができません。

そこで、fillna(method='ffill')を使うとNaNがあった場合に、さまざまな方法で自動的に補完をしてくれるようになります。


MACDの設定


macd.py

#MACDの設定

d_macd = dict()
d_macdsignal = dict()
d_macdhist = dict()

for symbol in data.minor_axis:
macd, macdsignal, macdhist = ta.MACD(df_close[symbol].values.astype(np.double), fastperiod=12 , slowperiod=26, signalperiod=9)

d_macd[symbol] = macd
d_macdsignal[symbol] = macdsignal
d_macdhist[symbol] = macdhist

df_macd = pd.DataFrame(d_macd, index=data.major_axis)
df_macdsignal = pd.DataFrame(d_macdsignal, index=data.major_axis)
df_macdhist = pd.DataFrame(d_macdhist, index=data.major_axis)


58~60行目のコメントアウトをはずします。これで空の辞書ができました。

62,63行目のコメントアウトをはずします。

これによってTa-LibのライブラリーからMACDの計算をしてくれます。

今回は、fastperiod=12 , slowperiod=26, signalperiod=9 と一般的なもの使っていますが、数値を変えるとシグナルも変化するので、バックテストの結果も変わってくると思います。

65~67行目のコメントアウトをはずします。これで各銘柄のMACDのの計算結果を辞書型オブジェクトに変えます。

69~71行目のコメントアウトをはずします。これでMACDのデータの容れ物が出来ました。


MACDの売り買いのサインを設定


macd.py

#macdの売り買いのサインを定義

df_goldencross = (df_macd > df_macdsignal) & (df_macd.shift(1) < df_macdsignal.shift(1))
df_deadcross = (df_macd < df_macdsignal) & (df_macd.shift(1) > df_macdsignal.shift(1))

#売買シグナルの設定
buy_sig = df_goldencross
sell_sig = df_deadcross


今回他の金融指標と組み合わせやすいようにMACDの買いサインをgoldencross,売りサインをdeadcrossと定義します。

74,75行目のコメントアウトをはずします。

これで、macdがmacdsignalより大きく、1日前のmacdがmacdsignalより小さくなった場合をgoldencross

macdがmacdsignalより小さく、1日前のmacdがmacdsignalより大きくなった場合をdeadcrossと定義します。

78,79行目のコメントアウトをはずして売買サインが出来ました!!


返り値を設定


macd.py

     return{

"MACD": df_macd,
"MACDSignal": df_macdsignal,
"MACDHist": df_macdhist,
"buy:sig": buy_sig,
"sell:sig": sell_sig,
}

return内のコメントアウトをはずしてください。

これで返り値が設定され各パラメータが可視化できるようになりました。

これでバックテストが実行できます!!

以下のコードは日ごとの処理の部分になります。

詳しくはコチラ

今回は損切りや利益確定売り等は入れずに

order_target_percent(amount, comment): void

というオブジェクトを用い買いの時は10%売りの時は0%と定義しました。

order_target_percent(amount, comment): voidとは

<この銘柄の総保有額が総資産評価額(現金+保有ポジション評価額)に対して指定の割合となるように注文を行ないます。 amountには割合(例:5%なら0.05)を指定します。>

この数値を変えても結果は変わります。


結果

スクリーンショット 2018-12-06 15.40.48.png


あれ?あんまり良くない

アルゴリズム自体は実装できましたが、3年で損益率27%程とあまり良くないですね。市場の平均株価に損益率が引っ張られて、利益があまり出せていない...?

シグナル数がかなり多いのも気になりますね。


MACDの欠点

MACDは、上昇トレンドや下降トレンドを読み、売買タイミングを見極める投資手法です。

そのため、上昇や下降のトレンドがない横ばいの相場では、うまく機能せずに間違ったサインを出してしまう場合があります。

トレンドについてはコチラ


MACDの欠点を簡単にまとめると

1.トレンドのない、横ばいの相場ではダマシのシグナルが出やすい

2.相場が緩やかに変化している場合は、ゴールデンクロスやデッドクロスが出てもサインとして弱く、信頼性に欠ける

3.移動平均を利用しているので、急激な変化に対応できない


[応用編] RSIを組み合わせてみよう

MACDはオシレータ系のテクニカル分析法ですが、トレンド系のテクニカル分析方でもあるので、レンジ相場に強いRSIと言う指標を使うと互いの弱点を補完し合えるのではと思い、組み合わせてみましょう。

df_goldencross,df_deadcrossのところからreturnまで以下のコードのように変えてみましょう。


macd&rsi.py

#macdの売り買いのサインを定義

df_goldencross = (df_macd > df_macdsignal) & (df_macd.shift(1) < df_macdsignal.shift(1))
df_deadcross = (df_macd < df_macdsignal) & (df_macd.shift(1) > df_macdsignal.shift(1))

#RSI設定
cp = data["close_price_adj"].fillna(method="ffill")
rsi = pd.DataFrame(data=0,columns=[], index=cp.index)
for (sym,val) in cp.items():
rsi[sym] = ta.RSI(cp[sym].values.astype(np.double), timeperiod=14)
df_rsi_buy=rsi > 75
df_rsi_sell=rsi < 25

# 売買シグナルの設定
buy_sig = df_goldencross & df_rsi_buy
sell_sig = df_deadcross & df_rsi_sell

return{
"MACD": df_macd,
"MACDSignal": df_macdsignal,
"MACDHist": df_macdhist,
"rsi": rsi,
"buy:sig": buy_sig,
"sell:sig": sell_sig,
}



完成したコードがコチラ


macd&rsi.py

  #ライブラリーの設定

import pandas as pd
import talib as ta
import numpy as np

#関数の初期化
def initialize(ctx):
#設定
ctx.logger.debug("initialize() called")
ctx.configure(
target="jp.stock.daily",
channels={ #銘柄選択
"jp.stock": {
"symbols": [
"jp.stock.1414", "jp.stock.2269",
"jp.stock.2292", "jp.stock.2371",
"jp.stock.2379", "jp.stock.2413",
"jp.stock.2427", "jp.stock.2702",
"jp.stock.2782", "jp.stock.2809",
"jp.stock.2930", "jp.stock.3003",
"jp.stock.3053", "jp.stock.3064",
"jp.stock.3092", "jp.stock.3141",
"jp.stock.3382", "jp.stock.3407",
"jp.stock.3665", "jp.stock.3676",
"jp.stock.4063", "jp.stock.4343",
"jp.stock.4452", "jp.stock.4519",
"jp.stock.4543", "jp.stock.4704",
"jp.stock.4751", "jp.stock.4911",
"jp.stock.4974", "jp.stock.5108",
"jp.stock.6028", "jp.stock.6098",
"jp.stock.6194", "jp.stock.6503",
"jp.stock.6645", "jp.stock.6758",
"jp.stock.6866", "jp.stock.7269",
"jp.stock.7272", "jp.stock.7606",
"jp.stock.7936", "jp.stock.8020",
"jp.stock.8252", "jp.stock.8591",
"jp.stock.8929", "jp.stock.9007",
"jp.stock.9086", "jp.stock.9433",
"jp.stock.9474", "jp.stock.9613"
],
"columns": [
#"open_price_adj", # 始値(株式分割調整後)
#"high_price_adj", # 高値(株式分割調整後)
#"low_price_adj", # 安値(株式分割調整後)
"close_price", # 終値
"close_price_adj", # 終値(株式分割調整後)
]
}
}
)

#売買シグナル生成部分
def _my_signal(data):

#各銘柄の終値(株式分割調整後)を取得、欠損データの補完
df_close = data["close_price_adj"].fillna(method='ffill')

#MACDの設定
d_macd = dict()
d_macdsignal = dict()
d_macdhist = dict()

for symbol in data.minor_axis:
macd, macdsignal, macdhist = ta.MACD(df_close[symbol].values.astype(np.double),
fastperiod=12 , slowperiod=26, signalperiod=9)
d_macd[symbol] = macd
d_macdsignal[symbol] = macdsignal
d_macdhist[symbol] = macdhist

df_macd = pd.DataFrame(d_macd, index=data.major_axis)
df_macdsignal = pd.DataFrame(d_macdsignal, index=data.major_axis)
df_macdhist = pd.DataFrame(d_macdhist, index=data.major_axis)

#macdの売り買いのサインを定義
df_goldencross = (df_macd > df_macdsignal) & (df_macd.shift(1) < df_macdsignal.shift(1))
df_deadcross = (df_macd < df_macdsignal) & (df_macd.shift(1) > df_macdsignal.shift(1))

#RSI設定
cp = data["close_price_adj"].fillna(method="ffill")
rsi = pd.DataFrame(data=0,columns=[], index=cp.index)
for (sym,val) in cp.items():
rsi[sym] = ta.RSI(cp[sym].values.astype(np.double), timeperiod=14)
df_rsi_buy=rsi > 75
df_rsi_sell=rsi < 25

# 売買シグナルの設定
buy_sig = df_goldencross & df_rsi_buy
sell_sig = df_deadcross & df_rsi_sell

return{
"MACD": df_macd,
"MACDSignal": df_macdsignal,
"MACDHist": df_macdhist,
"rsi": rsi,
"buy:sig": buy_sig,
"sell:sig": sell_sig,
}

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

def handle_signals(ctx, date, current):

df = current.copy()

# 買いシグナル
df_buy = df[df["buy:sig"]]
if not df_buy.empty:
for (sym, val) in df_buy.iterrows():
#ctx.logger.info(val)
sec = ctx.getSecurity(sym)
msg = "買いシグナル"
sec.order_target_percent(0.10, comment= msg)

# 売りシグナル
df_sell = df[df["sell:sig"]]
if not df_sell.empty:
for (sym, val) in df_sell.iterrows():
sec = ctx.getSecurity(sym)
msg = "売りシグナル"
sec.order_target_percent(0, comment= msg)


これでバックテストしてみましょう。


結果

スクリーンショット 2018-12-06 16.12.16.png

形が異形ですが、かなり改善されましたね!!

2年間くらいは利益が出てないのが気になりますが..

損益率も100%程度上昇!、MaxDrawdown(期間内での最大損失率)も改善されましたね!!

RSIを入れたことにより、条件が厳しくなり、シグナル回数も減っているのがわかると思います!!


終わりに

RSIの条件を変えてみたり、銘柄を変えてみるともっと良い結果が出ると思います!

今後はTa-libからRSIではなく他の金融指標やPBR等のファンダメンタル指標を用いたものをあげようと思います。

MACDの買いシグナル計算式に条件を追加するとよりだましに強いアルゴリズムも作れます!!

詳しくは同じインターン生のAkihiro Sasakiさんのコチラの記事を参考に!!


宣伝

勉強会やってます!

日時:毎週金曜日19時〜

場所:神田 千代田共同ビル4階 SmartTrade社オフィス

内容:初心者(プログラミングってものを知らなくてもOK)向けに初心者(私とか)がこんな内容をハンズオン(一緒にやる事)で解説しています

備考:猛者の方も是非御鞭撻にいらして下さい、そして開発・伝導者になりましょう!

もくもく会もやってます!

日時:毎週水曜日18時〜

場所:神田 千代田共同ビル4階 SmartTrade社オフィス

内容:基本黙々と自習しながら猛者の方に質問して強くなっていく会

備考:お菓子と終わりにお酒を飲みながら参加者と歓談できます!

詳細はこちらだよ

Pythonアルゴリズム勉強会HP:https://python-algo.connpass.com/

(connpassって言うイベントサイトに飛びます)


免責注意事項

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