自己紹介
2019年3月4日現在SmartTradeでインターンをさせていただいている、芋大学の一年生K.Kです。
これだとほとんど何もわからないと思いますが、SmartTradeという会社のインターン生ということだけは知っていただきたいと思います。
プログラミングと株を独学で半年も学んでいますが、初心者の域は全く抜けきれていません。その辺を踏まえていただき、あたたかい目で見ていただけるとありがたいです。
初心者なのにどうやって書いたの!?
インターン始めるとすぐに「とりあえずアルゴリズム書いてみてください」と言われたので、いきなり楽しみだったアルゴリズム開発を出来ることになりました。まだ初心者とはいえ半年間大学の勉強や部活の合間を縫って一生懸命に勉強してきたので、アルゴリズムをかける自信はありました。しかし、QuantXは普通のpythonやnumpy、pandasを用いてはいるものの、QuantX特有の部分があり、出だしで思いっきり躓いてしまいました。そこで先輩のやってきたことを見よう見まねでやってみることにしました(=先輩の書いたコードをコピペでもらってくることにしました)。参考にした@Ric418というインターン先の先輩のQiitaはこちらです。とてもわかりやすくまとまられた記事なので、ぜひ皆さんも一読下さい。
僕が書いたQuantX
僕が書いたコードは以下のものです。
import numpy as np
import pandas as pd
import talib as ta
def get_rci(close_pr, term): # close_prに入るのはpandas形式
rank_term = np.arange(term, 0, -1) # (初項, この値の一個手前まで, 公差)
length = len(close_pr)
rci = np.zeros(length)
for i in range(length):
if i < term-1:
rci[i] = 0
else:
# ↓はvaluesを最後に付けるだけでDataFrameからnumpy行列に変換できる
rank_price = close_pr[i - term + 1: i + 1].rank(method='min', ascending = False).values
rci[i] = (1 - (6 * sum((rank_term - rank_price)**2)) / (term**3 - term)) * 100
return rci
def initialize(ctx):
# 設定
ctx.logger.debug("initialize() called")
ctx.configure(
target="jp.stock.daily",
channels={ # 利用チャンネル
"jp.stock": {
"symbols": [
"jp.stock.2914",
"jp.stock.3382",
"jp.stock.4063",
"jp.stock.4452",
"jp.stock.4502",
"jp.stock.4503",
"jp.stock.6098",
"jp.stock.6501",
"jp.stock.6752",
"jp.stock.6758",
"jp.stock.6861",
"jp.stock.6954",
"jp.stock.6981",
"jp.stock.7203",
"jp.stock.7267",
"jp.stock.7751",
"jp.stock.7974",
"jp.stock.8031",
"jp.stock.8058",
"jp.stock.8306",
"jp.stock.8316",
"jp.stock.8411",
"jp.stock.8766",
"jp.stock.8802",
"jp.stock.9020",
"jp.stock.9022",
"jp.stock.9432",
"jp.stock.9433",
"jp.stock.9437",
"jp.stock.9984",
],
"columns": [
"close_price", # 終値
"close_price_adj", # 終値(株式分割調整後)
"volume_adj", # 出来高
"txn_volume", # 売買代金
]
}
}
)
def _mavg_signal(data):
cp = data["close_price_adj"].fillna(method='ffill')
# ↑ データ"close_price_adj"の欠損を無くして使えるデータに整形
# ctx.logger.debug(cp) #データの中身を見れる
rci10 = pd.DataFrame(0, index=cp.index, columns=cp.columns)
# ↑ RCIの計算結果の容れ物、容れ物の形状はcpと同じ
rsi7 = pd.DataFrame(0,index=cp.index, columns=cp.columns)
# ↑ RSIの計算結果の容れ物、容れ物の形状はcpと同じ
for (sym,val) in cp.items():
rci10[sym]=get_rci(cp[sym], 10)
for (sym,val) in cp.items():
rsi7[sym] = ta.RSI(cp[sym].values.astype(np.double), timeperiod=7)
# ↑ RCI,RSIを計算する関数get_rci(),ta.RSI()を使い、各銘柄でfor文で計算
# ctx.logger.debug(rci10)
# ctx.logger.debug(rsi7) #RSI, RCIの値を確認できる
buy_sig= (rci10 < -80) | (rsi7 < 20)
# rci10 < -80、又は rsi7 < 20 の時買いシグナルを出す
sell_sig= (rci10 > 80) | (rsi7 > 80)
# rci10 > 80、又は rsi7 > 80 の時売りシグナルを出す
return {
"buy:sig": buy_sig,
"sell:sig": sell_sig,
"rci10:g2":rci10,
"rsi7:g2":rsi7,
}
# シグナル登録
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.05:
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))
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))
このコードに関する説明は僕より@Ric418さんの記事のほうが良いと思うので、そちらを参照してみてください。@Ric418さんのコードはブル・ベアの売買を行うもので、僕のはcore30という30の株式を扱っているもので少しだけ違います。また、数値もちょこちょこ違っていますが、核となる部分は大体同じです。
僕のコードの評価
先ほどのコードは実際に販売可能なのか、審査していただきました。その結果は、、、
、、、
、、、、
、、、、、
、、、、、、
、、、、、、、
そんなに引っ張る必要はないですね、予想通りダメでした。残念。
このアルゴリズムを実際に走らせてみると、毎日たくさん買ってたくさん売っていました。僕じゃ全然その理由がわからなかったので、@akihirosasakiさんに教えていただきました。
@akihirosasakiさん曰く、「シグナルをBool値で返すと、全部買い全部売りになってしまうので、falseをNullにするか、handle_signal関数をTrueのみ読み込むように変えるといいと思います。」とのことです。
つまり、Bool値で返すとTrue、Falseに関わらず全部売り買いしてしまい、今はその状態。falseの部分では売り買いしたくないのだから、「Trueを値、falseをNullにしてしまう(ドキュメントに載ってる)」か、「handle_signal関数をいじってTrueの時だけ売り買いするように変える」のどちらかをする必要があるということです。全然「つまり」になってませんね。@akihirosasakiさんの仰っていたことを、自分なりにかみ砕いてアウトプットしたかったので書いてしまいました。申し訳ありません。
@akihirosasakiさんが修正してくださったコードも載せておきます。「handle_signal関数をいじってTrueの時だけ売り買いするように変える」の修正方法だと思います。
def handle_signals(ctx, date, current):
'''
current: pd.DataFrame
'''
df = current.copy()
done_syms = set([])
for (sym,val) in ctx.portfolio.positions.items():
returns = val["returns"]
# ctx.logger.debug("%s %f" % (sym, returns))
if returns < -0.05:
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)
# 買いシグナル
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_percent(0.05, 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_percent(0, comment= msg)
感想
QuantXは難しいですね。QuantXを勉強し始めてから1、2週間経って結構理解できてきたのですが、完璧に理解できたとはとても言えません。でも、完全に理解して好きなコードを書いてシステムトレードを出来るようになった未来の自分を考えると、物凄く楽しみでワクワクします。早くマスターしたい!
この記事を読んでくださった他のインターン生の方々、社員さんたちへ
僕の拙い文章を読んでいただきありがとうございます。まだまだ力不足でみなさんの力を借りることも多々あると思いますが、僕も日々成長して、いつかその借りを返せるように頑張りたいと思います。よろしくお願いします。