QuantXとは、smart trade社が提供する、株式システムトレードアルゴリズムをブラウザ上で開発できるプラットフォームである。
今回はQuantopian(こちらも同じくブラウザ上でトレードアルゴリズムを開発できるプラットフォーム。アメリカの企業が提供)において、ユーザーが作成したトレードアルゴリズムを評価する指標を列挙し、その中から、turnoverをQuantX上で計算してみようと言う記事である。
Quantopian上での評価指標
Quantopian上で、トレードアルゴリズムを評価する際に以下のような指標が用いられる。
- positive returns:収益が正である
- Low exposure to sector risk and style risk:Quantopian独自の Risk model の条件を満たしている
- Beta-to-spy: β値に関する基準 (β値とは、株価指数に対する個別株の感応度) 毎日、-0.3~0.3となるのが条件
- Leverage:レバレッジ0.8-1.1倍(QuantXには関係ない?)想定資本に対して80%〜110%の範囲で投資
- turnover : 中程度のturnoverを維持。
- アルゴリズムに用いる株はある程度の流動性を持っていないといけない
- position concentration :アルゴリズムは1つの銘柄に対して全資産の5%以上投資してはいけない
- Long and short positions :買いポジションと売りポジションの差額がトータル資金に対して10%以上になってはいけない。
###以下補足
- Risk model は大きく分けて2つ。セクター要因とスタイル要因
- セクター要因とは株式会社を11のセクターに業界で分別して(ex,技術、金融、不動産...)、各セクターに20%以上の資金を投資していはいけない
- スタイル要因とは???momentum size volatility とかで計算していく
- turnover =(1日の投資資金の絶対値)/(全ポートフォリオの価値)
- コンテストに使用できる株が流動性のある株に限定するために、Quantopianは独自で流動性があるとみなした株式銘柄群(株の集合)を作成し、コンテストに使用する株はその銘柄群の中から選ぶ必要がある。
ざっくりとした説明になってしまったが今回計算に必要なのは
###「作成したトレードアルゴリズムのturnoverが、63日営業日移動平均線をとった時、5〜65%となる」
という情報のみである。
turnoverの計算
turnoverの具体的な計算方法としては(1日の投資資金の絶対値)/(全ポートフォリオの価値)を毎日計算して、それの63日営業日移動平均をとる。この値が5〜65%の間に入っていることが条件。
X日目のturnover = \sum_{銘柄} \frac{X日目の、ある銘柄の投資量}{全資産}
= \sum_{銘柄} {|(X日目の、ある銘柄の全資産に対する保有割合)-(X-1日目の、ある銘柄の全資産に対する保有割合)|}
で算出できる。
以下でQuantX上でturnoverを表示する
X日目の、ある銘柄の全資産に対する保有割合 current_portfolio_ratio は、symで銘柄を指定して
current_portfolio_ratio = ctx.portfolio.positions[sym]["portfolio_ratio"]
で取得できる。
さらにあらかじめctx.localStorageを利用して保存しておいた、X-1日目のある銘柄の全資産に対する保有割合 previous_portfolio_ratio も用いて
X日目のturnoverは
X日目のturnover = \sum_{銘柄} |current~portfolio~ratio - previous~portfolio~ratio|
で求まる。
ここでQuantopian上での制約はturnoverの63日移動平均を計算してそれが5~63%に入ることが条件である。
ゆえに、上で算出したX日目のturnoverを毎日localStorageに保管して、実際にはlocalStorageに保管されている過去63日分のturnoverの平均を数値評価し、これが5〜63%となることが求める条件である。
以上を実際にQuantX上で実装してみる。
とりあえず、取り扱う通貨や損切り利確などの初期設定を行う
import pandas as pd
import talib as ta
import numpy as np
def initialize(ctx):
ctx.flag_profit = False #利益確定売りを用いるかTrueなら用いるFalseなら用いない
ctx.flag_loss = False #損切りを用いるかTrueなら用いるFalseなら用いない
ctx.loss_cut = -0.03 #損切りのボーダーマイナス%
ctx.profit_taking = 0.05 #利益確定売りのボーダープラス%
ctx.codes = [
4502,
4506,
]
ctx.symbol_list = ["jp.stock.{}".format(code) for code in ctx.codes]
ctx.configure(
channels={ # 利用チャンネル
"jp.stock": {
"symbols":ctx.symbol_list,
"columns": [
"close_price", # 終値
"close_price_adj", # 終値(株式分割調整後)
]}})
次に_my_signal内でbuy_sigとsell_sigを設定する
しかし今回は試験的にわかりやすいようにするためにbuy_sigの値を全てTrueに、sell_sigの値を全てFalseに設定する。
def _my_signal(data):
# この部分に作成するアルゴの指標を書き込んで下さい。
#シグナル定義
#各銘柄の終値(株式分割調整後)を取得、欠損データの補完
cp = data["close_price_adj"].fillna(method="ffill")
#単純移動平均線(SMA)の設定
#データの入れ物を用意
s_sma = pd.DataFrame(data=0,columns=[], index=cp.index)
m_sma = pd.DataFrame(data=0,columns=[], index=cp.index)
l_sma = pd.DataFrame(data=0,columns=[], index=cp.index)
#TA-Libによる計算
for (sym,val) in cp.items():
s_sma[sym] = ta.SMA(cp[sym].values.astype(np.double), timeperiod=5)
m_sma[sym] = ta.SMA(cp[sym].values.astype(np.double), timeperiod=25)
l_sma[sym] = ta.SMA(cp[sym].values.astype(np.double), timeperiod=75)
#SMAの売買シグナルの定義
s_sma_ratio = s_sma/m_sma
l_sma_ratio = m_sma/l_sma
buy_sig = (s_sma_ratio > 1.00) & (s_sma_ratio < 1.10) & (l_sma_ratio > 1.00) & (l_sma_ratio < 1.10)
sell_sig = (s_sma_ratio < 0.99) & (l_sma_ratio < 0.99)
#実験的にbuy_sigの値を全てtrueに、sell_sigの値を全てFalseに設定。
buy_sig.iloc[:,:] = True
sell_sig.iloc[:,:] = False
return {
"sma5":s_sma,
"sma25":m_sma,
"sma75":l_sma,
"buy:sig": buy_sig,
"sell:sig": sell_sig,
}
# シグナル登録
ctx.regist_signal("my_signal", _my_signal)
さらにhandle_signal内で売買を行い、その売買結果を利用してturnoverの値を算出する。
なお設定としてbuy_sigにヒットすると、一回の取引で全資産の5%分の株を買うように設定している。
実際には全資産の5%ちょうど売買することは不可能で、5%未満でキリのいいunit数売買することになる。
また、quantopianではturnoverの63日の移動平均をとって数値評価しているが、今回の試験的なQuantX上でのturnoverの実装なので、簡単にturnoverの5日移動平均をとって数値評価する。
def handle_signals(ctx, date, current):
'''
current: pd.DataFrame
'''
df = current.copy()
done_syms = set([])
#X日目の各銘柄の全資産に対する保有割合の変化を足し合わせるためにsum_rate_changeを作成
sum_rate_change = 0
for (sym,val) in ctx.portfolio.positions.items():
current_portfolio_rate = ctx.portfolio.positions[sym]["portfolio_ratio"]
'''
localStorageにsymbolのkeyがあれば先に進み、なければsymbolをkey、
valueは空の辞書とする、辞書を作る
'''
if sym in ctx.localStorage:
pass
else:
ctx.localStorage[sym] = {}
'''
localStorage[sym]に"portfolio_ratio"のkeyがあれば、
前日と当日のportfolio_ratioの差の絶対値を計算しsum_rate_changeにたす
なければ、新しくlocalStorage[sym]にからの辞書を作る
'''
if "portfolio_ratio" in ctx.localStorage[sym]:
previous_portfolio_rate = ctx.localStorage[sym]["portfolio_ratio"]
abs_rate_change = abs(current_portfolio_rate - previous_portfolio_rate)
sum_rate_change += abs_rate_change
ctx.localStorage[sym]["portfolio_ratio"] = current_portfolio_rate
else:
ctx.localStorage[sym]["portfolio_ratio"] = current_portfolio_rate
returns = val["returns"]
if (ctx.flag_loss) & (returns < ctx.loss_cut):
sec = ctx.getSecurity(sym)
sec.order(-val["amount"], comment="損切り(%f)" % returns)
done_syms.add(sym)
elif (ctx.flag_profit) & (returns > ctx.profit_taking):
sec = ctx.getSecurity(sym)
sec.order(-val["amount"], comment="利益確定売(%f)" % returns)
done_syms.add(sym)
if "turnover" in ctx.localStorage.keys():
ctx.localStorage["turnover"] = np.append(ctx.localStorage["turnover"], float(sum_rate_change))
sma_turnover = ta.SMA(ctx.localStorage["turnover"], timeperiod=5)
ctx.logger.debug(sma_turnover[-1])
elif "turnover" not in ctx.localStorage.keys():
ctx.localStorage["turnover"] = np.zeros(1)
ctx.logger.debug(ctx.localStorage['turnover'])
# 買いシグナル
df_long = df[df["buy:sig"]]
# ctx.logger.debug(df_long)
if not df_long.empty:
for (sym, val) in df_long.iterrows():
if sym in done_syms:
continue
sec = ctx.getSecurity(sym)
sec.order_percent(0.05, comment="SIGNAL BUY")
# 売りシグナル
df_sell = df[df["sell:sig"]]
if not df_sell.empty:
for (sym, val) in df_sell.iterrows():
if sym in done_syms:
continue
sec = ctx.getSecurity(sym)
sec.order_percent(0, comment= "SIGNAL SELL")
これを実行するとdebug画面に
array型のturnoverと、そのarrayの5日移動平均をとった実際に数値評価するsma_turnoverが得られる。
ところで今回、二銘柄に対してsignalが発火するごとに5%づつ取引するように設定しているので、1日の取引量の全資産に対する割合はおおよそ8〜10%であると予想される。
実際の結果は以下である。(以下はn日目のturnoverのarrayと)
# turnoverのarray
[0. 0. 0. 0.08652656 0.08774072 0.08964686
0.08793647 0.08958893 0.08674776 0.08879918 0.09168621 0.08819629]
turnoverのarrayの最新5日分の平均をとった値。
0.0888118524395898
となり予想して結果と一致している。
以上よりturnoverのQuantX上での計算が完了