LoginSignup
2
4

More than 3 years have passed since last update.

移動平均を使ったサンプル

Last updated at Posted at 2018-12-30

自己紹介

3ヶ月ほど前から趣味でPythonを触っている、金融の知識ゼロの超初心者です。
そんな超初心者の僕がSmartTrade社でインターンをさせていただくことになったので、プログラミングも投資も初心者でも理解出来るように、学んだ内容を随時更新していきたいと思います。

QuantXとは

QuantXとはSmart Trade社が提供しているプラットホームです。QuantXを用いることで、無料で株価データを分析し、アルゴリズムを開発することができます。この記事では、QuantXの公式サンプルコードを読んでいくだけなので、登録は必須ではありませんが、無料で登録することができるので登録してみてください。登録はQuantXに入門してみるを参考にするといいかもしれません。

この記事について

この記事は、QuantXの移動平均を使ったサンプルアルゴリズムの解説をしていきます。
以前僕が書いたQuantXの標準テンプレートを理解するとも繋がっているので、まだ読んでいないよ、という方は一度読んでみてください。

[DEMO] 移動平均を使った基本的なアルゴリズム

画面右上のヘルプから移動平均を使ったサンプルを選択すると、

def initialize(ctx):
    # 設定
    ctx.logger.debug("initialize() called")
    ctx.configure(
      target="jp.stock.daily",
      channels={          # 利用チャンネル
        "jp.stock": {
          "symbols": [
            "jp.stock.1305",
            "jp.stock.9984",
            "jp.stock.9983",
            "jp.stock.7201",
            "jp.stock.9201",
            "jp.stock.9202",
            "jp.stock.7203"
          ],
          "columns": [
            #"open_price_adj",    # 始値(株式分割調整後)
            #"high_price_adj",    # 高値(株式分割調整後)
            #"low_price_adj",     # 安値(株式分割調整後)
            "close_price",        # 終値
            "close_price_adj",    # 終値(株式分割調整後) 
            "volume_adj",         # 出来高
            "txn_volume",         # 売買代金
          ]
        }
      }
    )

    def _mavg_signal(data):
        m25 = data["close_price_adj"].fillna(method='ffill').rolling(window=25, center=False).mean()
        m75 = data["close_price_adj"].fillna(method='ffill').rolling(window=75, center=False).mean()
        ratio = m25 / m75

        buy_sig = ratio[(ratio > 1.02) & (ratio < 1.05)]
        sell_sig = ratio[ratio < 0.95]
        return {
            "mavg_25:price": m25,
            "mavg_75:price": m75,
            #"mavg_ratio:ratio": ratio,
            "buy:sig": buy_sig,
            "sell:sig": sell_sig,
        }

    # シグナル登録
    ctx.regist_signal("mavg_signal", _mavg_signal)

def handle_signals(ctx, date, current):
    '''
    current: pd.DataFrame
    '''

    done_syms = set([])
    for (sym,val) in ctx.portfolio.positions.items():
        returns = val["returns"]
        #ctx.logger.debug("%s %f" % (sym, returns))
        if returns < -0.03:
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="損切り(%f)" % returns)
          done_syms.add(sym)
        elif returns > 0.05:
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="利益確定売(%f)" % returns)
          done_syms.add(sym)


    buy = current["buy:sig"].dropna()
    for (sym,val) in buy.items():
        if sym in done_syms:
          continue

        sec = ctx.getSecurity(sym)
        sec.order(sec.unit() * 1, comment="SIGNAL BUY")
        #ctx.logger.debug("BUY: %s,  %f" % (sec.code(), val))
        pass

    sell = current["sell:sig"].dropna()
    for (sym,val) in sell.items():
        if sym in done_syms:
          continue

        sec = ctx.getSecurity(sym)
        sec.order(sec.unit() * -1, comment="SIGNAL SELL")
        #ctx.logger.debug("SELL: %s,  %f" % (sec.code(), val))
        pass

というコードが表示されると思います。

移動平均とは

移動平均線とは、一定期間の株価の終値の平均をつなぎ合わせた指標です。例えば、5日移動平均線の1月5日の値=「1/1〜1/5の終値の平均値」となっています。

コードの解説

基本的には以前書いたQuantXの標準テンプレートを理解するを読めば前半部分は理解できるかと思います。この記事では、QuantXの標準テンプレートでは出てこなかった箇所についての解説をしていこうと思います。

売買シグナル生成部分

def _mavg_signal(data):
        m25 = data["close_price_adj"].fillna(method='ffill').rolling(window=25, center=False).mean()
        m75 = data["close_price_adj"].fillna(method='ffill').rolling(window=75, center=False).mean()
        ratio = m25 / m75

        buy_sig = ratio[(ratio > 1.02) & (ratio < 1.05)]
        sell_sig = ratio[ratio < 0.95]
        return {
            "mavg_25:price": m25,
            "mavg_75:price": m75,
            #"mavg_ratio:ratio": ratio,
            "buy:sig": buy_sig,
            "sell:sig": sell_sig,
        }
ctx.regist_signal("mavg_signal", _mavg_signal)

def _mavg_signal(data):

ここはQuantXの標準テンプレートにもあった通り売買シグナルを指定する関数を定義しています。

m25 = data["close_price_adj"].fillna(method='ffill').rolling(window=25, center=False).mean()

ここは複雑なので1つずつ見て行きましょう

m25 = data["close_price_adj"]

jp.stock.1305 jp.stock.9984 jp.stock.9983 jp.stock.7201 jp.stock.9201 jp.stock.9202 jp.stock.7203
2017/5/1 終値 終値 終値 終値 終値 終値 終値
2017/5/2 終値 終値 終値 終値 終値 終値 終値
2017/5/3 終値 終値 終値 終値 終値 終値 終値
2017/5/4 終値 終値 終値 終値 終値 終値 終値
2017/5/5 終値 終値 終値 終値 終値 終値 終値
2017/5/6 終値 終値 終値 終値 終値 終値 終値
2017/5/7 終値 終値 終値 終値 終値 終値 終値

.fillna(method='ffill')

欠損値の補完を行なっています
引数は
method='ffill':前の値で置き換え
method='bfill':後ろの値で置き換え
という意味になります

.rolling(window=25, center=False).mean()

rollingメソッドはwindowsで指定した数の平均をとります。例えば

example

index
1 1
2 3
3 5
4 7
5 9
6 11

に対してexample.rolling(window=3, center=False).mean()を行うと、

index
1 NaN
2 NaN
3 3
4 5
5 7
6 9

となります。(例えばindex3にはexampleのindex1,2,3の3つの数字(1,3,5)の平均である3が、index4にはexampleのindex2,3,4の3つの数字(3,5,7)の平均である5が入ります)

従って、data["close_price_adj"].fillna(method='ffill').rolling(window=25, center=False).mean()では

jp.stock.1305 jp.stock.9984 jp.stock.9983 jp.stock.7201 jp.stock.9201 jp.stock.9202 jp.stock.7203
2017/5/1 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均
2017/5/2 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均
2017/5/3 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均
2017/5/4 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均
2017/5/5 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均
2017/5/6 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均
2017/5/7 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均 25日移動平均

と25日移動平均が出力されます。

m75 = data["close_price_adj"].fillna(method='ffill').rolling(window=75, center=False).mean()

同様に75日移動平均が出力されます。

ratio = m25 / m75

25日移動平均と75日移動平均の比率を計算しています。

buy_sig = ratio[(ratio > 1.02) & (ratio < 1.05)]

ratioが 1.02<ratio<1.05 である要素だけがbuy_sigに入り、それ以外は None になります。

e.g.)

jp.stock.1305 jp.stock.9984 jp.stock.9983 jp.stock.7201 jp.stock.9201 jp.stock.9202 jp.stock.7203
2017/5/1 None None None
2017/5/2 None None None
2017/5/3 None None None None
2017/5/4 None
2017/5/5 None None None None
2017/5/6 None None None
2017/5/7 None None None

sell_sig = ratio[ratio < 0.95]

同様にratioが ratio<0.95 である要素だけがsell_sigに入り、それ以外は None になります。

return {"mavg_25:price": m25,"mavg_75:price": m75,"buy:sig": buy_sig, "sell:sig": sell_sig,}

後ほど利用する配列を定義して残します。 また、チャートで表示する際の項目名も定義します。 例えば「buy_sig」という配列を"buy:sig"という名前でチャートの下に表示させます。

なお、suffixとしてsigが使用された場合(例えば今回では'buy:sig'と'sell:sig')は、特別扱いされ、 チャート上で矩形がfillされて範囲の確認が可能になります。 このとき、
keyにbuy,pos が含まれる場合には、ピンク
keyにsell,neg が含まれる場合には、水色
の色が固定的に利用されます。

ctx.regist_signal("mavg_signal", _mavg_signal)

標準テンプレートでもやったように、シグナルを登録します

日ごとの処理部分の記述

def handle_signals(ctx, date, current):
done_syms = set([])
    for (sym,val) in ctx.portfolio.positions.items():
        returns = val["returns"]
        #ctx.logger.debug("%s %f" % (sym, returns))
        if returns < -0.03:
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="損切り(%f)" % returns)
          done_syms.add(sym)
        elif returns > 0.05:
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="利益確定売(%f)" % returns)
          done_syms.add(sym)

def handle_signals(ctx, date, current):

currentは、dateの当日のデータとシグナルを含んだ pandas.DataFrame オブジェクトです。以下のようなpandas.DataFrame オブジェクトになっています。

mavg_25:price mavg_75:price buy:sig sell:sig
jp.stock.1305
jp.stock.9984
jp.stock.9983
jp.stock.7201
jp.stock.9201
jp.stock.9202
jp.stock.7203

done_syms = set([])

後に必要になる、空のsetオブジェクトを用意しておきます。

for (sym,val) in ctx.portfolio.positions.items():

ctx.portfolio.positionsには
毎日のポジションが,下記のような辞書型で格納されています。

{'jp.stock.1305': 
 {'position_ratio': , 'amount': , 'pnl': -, 
   'total_buy_price': , 'value': , 'portfolio_ratio': , 
    'returns': , 'buy_price': , 'total_sell_price': , 
    'sell_price': , 'max_returns': }, 
 'jp.stock.9984': 
 {'position_ratio': , 'amount': , 'pnl': , 
   'total_buy_price': , 'value': , 'portfolio_ratio': , 
   'returns': , 'buy_price': , 'total_sell_price': , 
   'sell_price': , 'max_returns': }}
   ....

for 文のsymは

'ctx.configureで指定した証券コード'

for文のvalは

{'position_ratio': , 'amount': , 'pnl': -, 
   'total_buy_price': , 'value': , 'portfolio_ratio': , 
    'returns': , 'buy_price': , 'total_sell_price': , 
    'sell_price': , 'max_returns': }

がctx.configureで指定した証券コードの数だけ入っています。

returns = val["returns"]

for文のvalにはctx.configureで指定した証券コードの'returns'、つまり損益率の値が入っています。

if returns < -0.03:

return=証券コードの損益率なので、ここでは損益率が-0.03未満になった場合に行う操作が入っています。つまり3%以上落下した場合に実行されます。

sec = ctx.getSecurity(sym)

Securityとは銘柄を表すオブジェクトであり、ctx.getSecurity() で取得できます。このオブジェクトを使用して注文を行ないます。symには前述した通りctx.configureで指定した証券コードが入っています。

sec.order(-val["amount"], comment="損切り(%f)" % returns)

order()メソッドで数量を指定して注文を行ないます。
第一引数=注文数。株式の場合は株数。
第二引数=コメント。バックテスト結果(個別銘柄詳細)に表示されます。
ここでは引数が(-val["amount"], comment="損切り(%f)" % returns)となっているので、-val["amount"]だけ注文をします。マイナスなので売っていますね。また、コメントに"損切り(値)"と表示されていることが確認できると思います。ちなみに、%returns(%f)returnsを代入するという意味のコードです。

done_syms.add(sym)

for文の前に作っておいたdone_syms = set([])にsym=損益率が-0.03未満の証券コードを追加します。

elif returns > 0.05:

同様にreturns が 0.05より大きい場合に利益確定売をするようなコードを書きます。

buy = current["buy:sig"].dropna()

buyシグナルの項目のpandas.Seriesオブジェクトを取得します。dropna()は欠損値のあるデータを除外するメソッドです。

for (sym,val) in buy.items():

buyシグナルの項目のpandas.Seriesオブジェクトの値を取得しています。

if sym in done_syms:

もしdone_symsにすでにsymが入っていたらシグナルによる売買と「損切り、利益確定売り」が被ってしまうのでdone_symsにすでにsymが入っていないかを確認します。

continue

もしdone_symsにすでにsymが入っていたら、それ以降の処理が実行されずにスキップされ、次のループに進みます。done_symsにすでにsymが入っていない場合、つまりシグナルによる売買と「損切り、利益確定売り」の被りがない場合のみ次の処理に進みます。

sec = ctx.getSecurity(sym)

Securityとは銘柄を表すオブジェクトであり、ctx.getSecurity() で取得できます。このオブジェクトを使用して注文を行ないます。symには前述した通りctx.configureで指定した証券コードが入っています。

sec.order(sec.unit() * 1, comment="SIGNAL BUY")

再びorder()メソッドです。sec.unit() * 1で「売買単位×1」の注文をするという量を指定し、comment="SIGNAL BUY"で"SIGNAL BUY"というコメントを出力します。

pass

pass文は、「何も行わない」という動作の命令です。

sell = current["sell:sig"].dropna() 以降

同様に売り注文も実行します。

宣伝

毎週、SmartTrade社のオフィス(神田千代田共同ビル4階)で勉強会(水曜日18時〜もくもく会、金曜日19時〜勉強会)を行なっています。気軽に参加してみてください!

Pythonアルゴリズム勉強会HP:https://python-algo.connpass.com/
(connpassって言うイベントサイトに飛びます)

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