Help us understand the problem. What is going on with this article?

QuantX Factoryでセンチメントデータいじってみた

More than 1 year has passed since last update.

お前だれ

都内某T大学部4年、機械学習・統計手法でバイオインフォマ、ケモインフォマでケモケモしたりFinTechしようとしているSmartTrade社インターンのRic.です。

初心者がセンチメントデータ弄ってみた

「QuantX Factoryってなんだよ」って方はこちら

URL:QuantX Factoryホームページ

「センチメントデータってなんだよ」って方はこちら

URL:QuantX Cup 2018"日本株"を参照
(ざっくり言うと各会社に対する人々の感情的なコメント等のデータの集まりです)

ここでいう初心者(私の事)のレベルとは

・ Python大学の授業で触った事あるくらい(若しくは触った事ない)
・ 金融?経済?株?高い時に売って安い時に買えばええんやろ?
・ データサイエンス? 完全に理解した(わかってない)

これくらいの感じで実際に資金が無いと出来ない訳ではなく
あくまでもシミュレーションなのでガチガチの初心者の方もご安心下さい。

早速本題行きます

今回は先述したロイター社様から頂いているセンチメントデータを用いて
株のシステムトレードアルゴリズムをQuantX Factory上(以下の画面)で書いて
シミュレーションしてみました。(実際自分で書いたのは10行位なのでご安心下さい)
QuantX Factoryの使い方についてはコチラ(会員登録は済ませて下さい)
スクリーンショット 2018-11-21 18.03.30.png

新規プロジェクトはQuantX Cup 2018用のテンプレにしましょう。
スクリーンショット 2018-11-24 4.58.49.png

今回は

・移動平均線を用いたテクニカル手法(どこで売り買いするかの指標)のみのアルゴリズム
・以上にセンチメントデータの適用・条件に追加したアルゴリズム

以上の二つのアルゴリズムを書いてシミュレーション、比較してみました!

まずは移動平均線(平均乖離率分析)を用いたアルゴリズム

実際のコードはこちら(Ric.のGithubに飛びます)

workshop.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.1605',    #   国際石油開発帝石(株)
            'jp.stock.1802',    #   (株)大林組
            'jp.stock.1803',    #   清水建設(株)
            'jp.stock.9502',    #   中部電力(株)
            'jp.stock.9503',    #   関西電力(株)
            'jp.stock.9506',    #   東北電力(株)
            'jp.stock.9531',    #   東京ガス(株)
            'jp.stock.9532',    #   大阪ガス(株)
            'jp.stock.9613',    #   (株)NTTデータ
          ],
          "columns": [
            #"open_price_adj",    # 始値(株式分割調整後)
            #"high_price_adj",    # 高値(株式分割調整後)
            #"low_price_adj",     # 安値(株式分割調整後)
            "close_price",        # 終値
            "close_price_adj",    # 終値(株式分割調整後) 
            "volume_adj",         # 出来高
            "txn_volume",         # 売買代金
            #"ns_sentiment",      # ニュースとSNSのネガポジデータだよ。
                                  # 1〜-1で1に近いほどポジティブな発言が多いよ。
          ]
        }
      }
    )

    def _mavg_signal(data):
        #ns_sentiment = data["ns_sentiment"].fillna(method="ffill")
        # ctx.logger.debug(ns_sentiment)

        #ns_sentiment_m50 = ns_sentiment.rolling(window=50, center=False).mean()
        #ネガポジの50日移動平均出してるよ
        # ctx.logger.debug(ns_sentiment_m50)

        #ns_sentiment_m50_pct_change = ns_sentiment_m50.pct_change()
        #ネガポジの50日平均の1日の変化率出してるよ
        # ctx.logger.debug(ns_sentiment_m25_diff)
        m25 = data["close_price_adj"].fillna(method='ffill').ewm(span=25).mean()
        dfma25 = (data["close_price_adj"] -  m25) / m25 * 100

        buy_sig = dfma25[(dfma25 < -2)]
        sell_sig = dfma25[(dfma25 > 2)]
        return {
            "mavg_25:price": m25,
            #"mavg_ratio:ratio": ratio,
            "buy:sig": buy_sig,
            "sell:sig": sell_sig,
            #"ns_sentiment_m50": ns_sentiment_m50,
            #"ns_sentiment_m50_pct_change": ns_sentiment_m50_pct_change,
        }

    # シグナル登録
    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.02:
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="損切り(%f)" % returns)
          done_syms.add(sym)
        # elif returns > 0.08:
        #   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

実際に以上のコードを先ほどのアルゴリズムを書く所にペーストしていただければ、
とりあえずは動くかと思います。

平均乖離率分析とは

値動きは平均に収束(離れている程引っ張られる)するという法則を用いたテクニカル手法です。
ここで重要となるコードは買う時と、売る時の条件です!

平均より2%より価格が 回っている → 平均に引っ張られ上がる可能性が高い
買い!
平均より2%より価格が 回っている → 平均に引っ張られ下がる可能性が高い
売り!

こんな感じの判断をコードで書くと…(49行目辺りから)

workshop.py
m25 = data["close_price_adj"].fillna(method='ffill').ewm(span=25).mean()
'''
25日間指数平均(直近の値に重みをつけ過去の値を軽くとる平均)を.ewm(span=25).mean()で計算し
欠損値を.fillna(method='ffill')で補完しながらm25に格納していきます。
'''
dfma25 = (data["close_price_adj"] -  m25) / m25 * 100
#25日間指数平均と値の乖離率を%で出します。

buy_sig = dfma25[(dfma25 < -2)]
#乖離率が下に2%より大きい時→買いシグナルをだす。
sell_sig = dfma25[(dfma25 > 2)]
#乖離率が上に2%より大きい時→売りシグナルをだす。

こんな感じになります、一応データの生成の所から説明を入れました。

結果

平均乖離率分析のアルゴリズムのシミュレーション(command+B,cntrl+B)の結果は以下の様になりました。

スクリーンショット 2018-11-24 5.35.17.png

元手が300万で期間が8年、損益率が44.07%(132万位儲け)
そこそこと言った所でしょうか。完全に理解しましたね。(分かってない)

ついにセンチメントデータを使ってみる

実際のコードは先ほどの"普通の移動平均線"のプログラムをCloneして
スクリーンショット 2018-11-24 4.56.15.png
名前を"移動平均線にセンチメントデータ"として書いていきましょう。
スクリーンショット 2018-11-24 4.57.10.png

とりあえずデータを使える様にする

以下のコードの#(31行目)を外してセンチメントデータを使える様にしましょう。
本来は#を外すのではなく実際に書いていきますが便宜上#を外していく形にしています。
(#があるとその行のコードは無効化されている状態です)

workshop.py
"columns": [
            #"open_price_adj",    # 始値(株式分割調整後)
            #"high_price_adj",    # 高値(株式分割調整後)
            #"low_price_adj",     # 安値(株式分割調整後)
            "close_price",        # 終値
            "close_price_adj",    # 終値(株式分割調整後) 
            "volume_adj",         # 出来高
            "txn_volume",         # 売買代金
            "ns_sentiment",      # ニュースとSNSのネガポジデータだよ。
                                  # 1〜-1で1に近いほどポジティブな発言が多いよ。
          ]

これでニュースとSNSにおいてのネガポジの発言の総和のデータを使うことができる様になりました。

次にデータとして成形していきましょう。(38行目辺りから)
39,42,46行目の#を外して有効なコードにします。

workshop.py
def _mavg_signal(data):
        ns_sentiment = data["ns_sentiment"].fillna(method="ffill")
        #データとして成形
        # ctx.logger.debug(ns_sentiment)

        ns_sentiment_m50 = ns_sentiment.rolling(window=50, center=False).mean()
        #ネガポジの50日移動平均出してるよ
        # ctx.logger.debug(ns_sentiment_m50)

        ns_sentiment_m50_pct_change = ns_sentiment_m50.pct_change()
        #ネガポジの50日平均の1日の変化率出してるよ
        # ctx.logger.debug(ns_sentiment_m25_diff)

そしてついにここで肝心な売買条件の追加です。
先ほどの条件は以下の単純な平均乖離率分析だけを用いた物でした。

workshop.py
buy_sig = dfma25[(dfma25 < -2)]
#乖離率が下に2%より大きい時→買いシグナルをだす。
sell_sig = dfma25[(dfma25 > 2)]
#乖離率が上に2%より大きい時→売りシグナルをだす。

今回はこれにセンチメントデータの条件を加えていきます。

workshop.py
buy_sig = dfma25[(dfma25 < -2) & (ns_sentiment_m50 > 0.16)]
#乖離率が下に2%より大きい時かつ
#ネガポジの50日平均が0.16より大きい(割とポジティブ)→買いシグナルをだす。
sell_sig = dfma25[(dfma25 > 2) & (ns_sentiment_m50_pct_change > 0.1)]
#乖離率が上に2%より大きい時かつ
#50日平均の変化率が0.1より大きい(何かしらの動きはあるはず?)→売りシグナルをだす。

この様に条件を加えていきます。
(&:かつ、|:又は)

そして
ns_sentiment_m50
ns_sentiment_m50_pct_change
この二つの動きも以下の様に見える様にしましょう。

スクリーンショット 2018-11-24 5.47.13.png

以下の様にコードを有効化(56,57行目)しましょう。
(この記事以降は実際にコードを書いていきます)

workshop.py
 return {
            "mavg_25:price": m25,
            #"mavg_ratio:ratio": ratio,
            "buy:sig": buy_sig,
            "sell:sig": sell_sig,
            "ns_sentiment_m50": ns_sentiment_m50,
            "ns_sentiment_m50_pct_change": ns_sentiment_m50_pct_change,
        }

因みに今回は
"mavg_ratio:ratio": ratio
以外は見える様になっています。

ここでコード自体は完成です。( 実際のコード(Ric.のGithubに飛びます) )
では実際にシミュレーションして

before:平均乖離率分析のみ
after:センチメントデータを加えたもの

でみてみましょう!
結果はコチラ!

before

スクリーンショット 2018-11-24 6.05.16.png

after

スクリーンショット 2018-11-24 5.35.34.png

before:損益率が8年で44.07%(+132万)
after:損益率が8年で92.18%(+276万)

損益率が2倍以上!!
(本当は損益率以外にも注目すべき所はあるのですが…それは又後日)
さらに特筆すべきは平均株価が下がっている時にbeforeは損出を出しているのですが
afterはなんとか耐えている所でしょうか!

githubのコード名がanswer?.pyとなっている様に
このコードはまだ最適な物かは分かりませんので皆さんで以下のコードの値や条件に使う変数(49行目辺り)、株の銘柄(13行目辺り)を選んでみると良いかと思います。

answer?.py
buy_sig = dfma25[(dfma25 < -2) & (ns_sentiment_m50 > 0.16)]
sell_sig = dfma25[(dfma25 > 2) & (ns_sentiment_m50_pct_change > 0.1)]
#0.16や0.1とかそこらへんの値とか
#ns_sentiment_m50とか使う変数を変えて微調整して見ましょう
answer?.py
def initialize(ctx):
    # 設定
    ctx.logger.debug("initialize() called")
    ctx.configure(
      target="jp.stock.daily",
      channels={          # 利用チャンネル
        "jp.stock": {
          "symbols": [
            'jp.stock.1605',    #   国際石油開発帝石(株)
            'jp.stock.1802',    #   (株)大林組
            'jp.stock.1803',    #   清水建設(株)
            'jp.stock.9502',    #   中部電力(株)
            'jp.stock.9503',    #   関西電力(株)
            'jp.stock.9506',    #   東北電力(株)
            'jp.stock.9531',    #   東京ガス(株)
            'jp.stock.9532',    #   大阪ガス(株)
            'jp.stock.9613',    #   (株)NTTデータ
            # 銘柄を変えるだけで大きく結果が変わることも
          ],

今回はこれで終わりですが、何行か書き換えるだけで損益が大きく変わるのでその快感
を皆さんも是非感じましょう(変態)

またセンチメントデータが使えるのは

QuantX Cup 2018 だけ!!

ぜひ皆さんも参加してみては?

おまけ

やらかし企業と様々な感情

今回はネガポジの総合のデータを適用しましたが、センチメントデータには

・怒り:ns_anger
・信頼:ns_trust
・恐怖:ns_fear
・期待:ns_marketrisk
・事実を重視するか、感情に流されているか:ns_emotionvsfact

などの細かいデータもあるので、
それらのデータのみでアルゴリズムを組んでみました!
(なんの最適化もしておらずガバガバアルゴリズムですがご容赦ください)

実際のコード(Ric.のGithubに飛びます)

少し追加したコードが長いので
売買の条件の出し方のコード()の詳細のみ説明します。

Yarakashi.py
SUM_emotion = (ns_trust_m25 + ns_joy_m25 + ns_optimism_m25 + ns_marketrisk_m25
+ ns_lovehate_m25 - ns_anger_m25 - ns_conflict_m25 - ns_gloom_m25 - ns_fear_m25)
        #以上感情に関するセンチメントデータの総計-8〜5(正規化した方がいいか?)

buy_sig = SUM_emotion[(SUM_emotion > 0.2) & (ns_emotionvsfact_m25 > 0.589)]
#感情に流されている(ns_emotionvsfact_m25が0.589より大きい)かつ感情の総計がプラス寄り
sell_sig = SUM_emotion[(SUM_emotion < -4.1) & (ns_emotionvsfact_m25 > 0.589)]
##感情に流されている(ns_emotionvsfact_m25が0.589より大きい)かつ感情の総計がマイナス寄り

こんな感じで書き換えて
やらかし企業のプラス意見をいち早く捉え値上がりすると考え買い、
やらかし企業のマイナス意見をいち早く捉え値下がりすると考え売り、
とするアルゴリズムとしました。

結果

スクリーンショット 2018-11-24 6.55.33.png
8年間で損益率53.21%(+159万)となりました。
(銘柄・値・変数全く最適化できていないのでまだ伸び代はある?)

結果として先ほどの平均乖離率にセンチメントデータよりかはいい結果ではないが
テクニカル分析単体よりもこちらの方がセンチメントデータのみでも損益率が良いのは注目すべき点でしょうか。(銘柄に中外製薬が入っているのも大きいですが)

まとめ

今回はセンチメントデータを用いたアルゴリズムを説明しましたが
個人的に開発して感じたのはあくまでもセンチメントデータはテクニカル分析などの補助として用いるのが今の所は良いのかと感じました。
(使いこなせればもっと良いものができそうですが)

個人的に面白そうだと思ったのはおまけでも出てきた

ns_emotionvsfact

というセンチメントデータを条件分岐として

・センチメントデータに判断基準の重きを置く時(市場全体は感情的に取引している)
・テクニカル分析・ファンダメンタル分析(これはまた後日)に判断基準の重きを置く時(市場全体は事実に基づいて取引している)

の二つで分岐すれば市場全体の投資家の不安定な考えを予測できるのでは?
と思いました(小並感)

免責注意事項

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

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away