QuantX上でturnoverを算出する

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には関係ない？）想定資本に対して８０％〜１１０％の範囲で投資
-  turnover : 中程度のturnoverを維持。
- アルゴリズムに用いる株はある程度の流動性を持っていないといけない
- position concentration ：アルゴリズムは１つの銘柄に対して全資産の５％以上投資してはいけない
- Long and short positions ：買いポジションと売りポジションの差額がトータル資金に対して１０％以上になってはいけない。 

###以下補足
- Risk model は大きく分けて２つ。セクター要因とスタイル要因
- セクター要因とは株式会社を１１のセクターに業界で分別して（ex,技術、金融、不動産...）、各セクターに２０％以上の資金を投資していはいけない
- スタイル要因とは？？？momentum size volatility とかで計算していく
- turnover ＝（１日の投資資金の絶対値）/（全ポートフォリオの価値）
- コンテストに使用できる株が流動性のある株に限定するために、Quantopianは独自で流動性があるとみなした株式銘柄群（株の集合）を作成し、コンテストに使用する株はその銘柄群の中から選ぶ必要がある。


ざっくりとした説明になってしまったが今回計算に必要なのは
###「作成したトレードアルゴリズムのturnoverが、６３日営業日移動平均線をとった時、５〜６５％となる」
という情報のみである。




## turnoverの計算
turnoverの具体的な計算方法としては（１日の投資資金の絶対値）/（全ポートフォリオの価値）を毎日計算して、それの６３日営業日移動平均をとる。この値が５〜６５％の間に入っていることが条件。

```math
X日目のturnover = \sum_{銘柄} \frac{X日目の、ある銘柄の投資量}{全資産}
= \sum_{銘柄} {|(X日目の、ある銘柄の全資産に対する保有割合)-(X-1日目の、ある銘柄の全資産に対する保有割合)|}
```

で算出できる。

以下でQuantX上でturnoverを表示する

X日目の、ある銘柄の全資産に対する保有割合 current_portfolio_ratio は、symで銘柄を指定して

```python
current_portfolio_ratio = ctx.portfolio.positions[sym]["portfolio_ratio"]
```
で取得できる。
さらにあらかじめctx.localStorageを利用して保存しておいた、X-1日目のある銘柄の全資産に対する保有割合 previous_portfolio_ratio も用いて

X日目のturnoverは

```math
X日目のturnover = \sum_{銘柄} |current~portfolio~ratio - previous~portfolio~ratio|
```

で求まる。
ここでQuantopian上での制約はturnoverの63日移動平均を計算してそれが5~63%に入ることが条件である。
ゆえに、上で算出したX日目のturnoverを毎日localStorageに保管して、実際にはlocalStorageに保管されている過去63日分のturnoverの平均を数値評価し、これが５〜６３％となることが求める条件である。

以上を実際にQuantX上で実装してみる。
とりあえず、取り扱う通貨や損切り利確などの初期設定を行う

```python 
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に設定する。

``` python
     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%未満でキリのいいunit数売買することになる。
また、quantopianではturnoverの63日の移動平均をとって数値評価しているが、今回の試験的なQuantX上でのturnoverの実装なので、簡単にturnoverの5日移動平均をとって数値評価する。

```python 
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の５日移動平均をとった実際に数値評価するsma_turnoverが得られる。

ところで今回、二銘柄に対してsignalが発火するごとに5％づつ取引するように設定しているので、１日の取引量の全資産に対する割合はおおよそ８〜１０％であると予想される。

実際の結果は以下である。（以下はn日目のturnoverのarrayと）

```python
# 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の最新５日分の平均をとった値。
0.0888118524395898
```

となり予想して結果と一致している。

以上よりturnoverのQuantX上での計算が完了