上放れタスキのアルゴリズム #QuantX

## 上放れタスキ

![image.png](https://qiita-image-store.s3.amazonaws.com/0/14019/75801ee7-30c4-f95f-be30-6a9741afc02d.png)


［出典］[上放れタスキ | 銘柄・株スクリーニング | 投資顧問比較ナビ](https://komonavi.com/stock/signal/%E4%B8%8A%E6%94%BE%E3%82%8C%E3%82%BF%E3%82%B9%E3%82%AD)


今回は，上放れタスキというシグナルを[QuantX](https://factory.quantx.io/)で書いてみたいと思います．

上放れタスキとは、上昇トレンドにおいて，窓を開けて上昇した陽線が出現し、翌日に窓を開けずに安寄りした陰線が現れた状態のチャートだそうです．
チャート画像を見ると，あ〜はん:smirk:とわかった気になるのですが，DataFrameではどう表現すればいいのでしょうか？

### 条件

自分なりに考えてみました．

#### トレンド
上昇トレンドであること．
具体的には，日足の変化率を過去5日間観察し，毎日ポジティブであること．

#### 直近3日
（以下，t-2,t-1,t0 日と表現する）

1. t-2の高値よりt-1の安値が高い
1. t-1の陽線が，過去100日の陽線の長さの2倍以上（必要？）
1. t0の始値がt-1の始値を上回っいて，かつt0は陰線
1. t0の安値がt-2の高値を上回っている

## シグナル

上記の条件が発生したt2の終値でシグナルを出すことにします．ただし，過去5日間（＝ホールド期間）に既にシグナルが発生していた場合は，不採用とします．

## 手仕舞い

シグナルが出てから５日間ホールドして、クローズすることにします。

##コード

```python
import talib as ta 
import numpy as np 


def initialize(ctx):
    # 設定
    ctx.up_trend_term = 5 
    ctx.yousen_term = 100
    ctx.yousen_threshold = 1.0 
    ctx.holding_days = 5 
    ctx.target = 0.50
    
    # 銘柄設定　memo参照
    ctx.codes = [6758, 8316, 2914, 7267, 7182, 8058, 8411, 4063, 7751, 5411,
                 6981, 6902, 3382, 8766, 6367, 6752, 4911, 8001, 6301, 6503, 
                 8031, 5108, 4519, 4578, 1925, 2502, 8801, 2503, 8750, 6273, 
                 7741, 7270, 6971, 4901, 8053, 6869, 6502, 8113, 8725, 8267, 
                 9843, 1605, 6723, 4612, 6702, 9021, 8308, 7309, 4188, 6762, 
                 4528, 9202, 2413, 7181, 3402, 6586, 3092, 1878, 6988, 1801, 
                 9502]

    ctx.symbol_list  = ["jp.stock.{}".format(code) for code in ctx.codes[:50]]

    ctx.configure(
      channels={          # 利用チャンネル
        "jp.stock": {
          "symbols": ctx.symbol_list, 
          "columns": ["open_price_adj",    # 始値(株式分割調整後)
                      "high_price_adj",    # 高値(株式分割調整後)
                      "low_price_adj",     # 安値(株式分割調整後)
                      "close_price",        # 終値
                      "close_price_adj",    # 終値(株式分割調整後) 
          ]}})

    def _TASUKI_GAP(data):
      
      df_open =  data["open_price_adj"].fillna(method='ffill')
      df_high =  data["high_price_adj"].fillna(method='ffill')
      df_low =  data["low_price_adj"].fillna(method='ffill')
      df_close =  data["close_price_adj"].fillna(method='ffill')
      
      # 日足の変化率を過去5日間観察し，毎日ポジティブであることを確認
      df_is_uptrend = df_close.pct_change().rolling(window=ctx.up_trend_term,).min() > 0
      # t-2の高値よりt-1の安値が高い
      df_open_window = df_high.shift(2) < df_low.shift(1)
      
      # t-1の陽線が，過去100日の陽線の長さの2倍以上（必要？）
      # memo 参照
      df_intraday = df_close / df_open - 1 
      df_MA_yousen = df_intraday.where(df_intraday>0).rolling(window=ctx.yousen_term, min_periods=1).apply(np.nanmean)
      df_is_yousen_long = df_intraday.shift(1) > df_intraday.shift(2) * ctx.yousen_threshold 

      # t0の始値がt-1の始値を上回っいて，かつt0は陰線
      df_tasuki = (df_open > df_open.shift(1)) & (df_open > df_close) 
      # t0の安値がt-2の高値を上回っている
      df_is_trend_continue = df_low > df_high.shift(2)
      
      df_uwabure_tasuki = df_is_uptrend & df_open_window & df_is_yousen_long & df_tasuki & df_is_trend_continue

      buy_signal = (df_uwabure_tasuki) & (df_uwabure_tasuki.rolling(window=ctx.holding_days, center=False).sum() == 1)
      close_signal = buy_signal.shift(ctx.holding_days)
      
      
      return {
        "buy:sig": buy_signal,
        "close:sig": close_signal, 
        }

    # シグナル登録
    ctx.regist_signal("TASUKI_GAP", _TASUKI_GAP)
    

def handle_signals(ctx, date, current):

    df = current.copy()

    # 買いシグナル
    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_target_percent(ctx.target, comment= msg)


    # # ポジションクローズ
    df_position_close = df[df["close:sig"]]
    if not df_position_close.empty:
      for (sym, val) in df_position_close.iterrows(): 
        sec = ctx.getSecurity(sym)
        sec.order_target_percent(0, comment= "position close")
        
```
## memo

### 銘柄設定

```python
ctx.codes = [6758, 8316, 2914, 7267, 7182, ...]
ctx.syms = ["jp.stock.{}".format(code) for code in ctx.codes[:50]]
```
`format`関数を使ってシンボルリストを作りました．
また，QuantXでは最大50銘柄をシミュレーションに組み込む事が出来るので，50を超えないように`ctx.codes[:50]`としています．

### t-1の陽線が，過去100日の陽線の長さの2倍以上
本当にこれが必要かどうかわかりませんが，びよーんと伸びた陽線は気分が高まるので入れてみました．

```python
df_intraday = df_close / df_open - 1 
df_MA_yousen = df_intraday.where(df_intraday>0).rolling(window=ctx.yousen_term, min_periods=1).apply(np.nanmean)
df_is_yousen_long = df_intraday.shift(1) > df_intraday.shift(2) * ctx.yousen_threshold 
```
`df_intraday` は始値から終値の変化率を出しています．
`df_intraday.where(df_intraday>0)`で，`df_intraday`の正の数だけを残し，負の数はNaNが入るDataFrameを作っています．
そのDataFrameに対して100日移動平均をとっているのですが，`np.nanmean` を渡してNaNを無視した平均値をとり，かつ min_periods=1を渡して，100日の間に一つでも数値が入っていれば平均値を返すように指示しています．つまり100日の間に99日はNaN，1日だけ0.05があれば，そこで返ってくる数値は0.05になります．

## 結果

![2018-07-29-15:00:48.jpg](https://qiita-image-store.s3.amazonaws.com/0/14019/96db3ff7-26c9-2eb5-61e0-f30a320fa787.jpeg)

## 感想
+ 銘柄は東証一部上場銘柄を適当に入れました．
+ めったに出現しないと書いてありましたが，確かに出現しませんね．
+ `shift` を使うとだいたいの事は出来る気がしてきました．
+ 間違ってるかもしれないので，その時はぜひおしえてください:bow_tone1:


## 免責注意事項
+ このコードに基づき投資した結果、損害が発生しても，一切責任を持ちません．
+ このコードが正しく機能する保証は一切致しません．
+ このアルゴリズムを勧めているわけではありません．あくまで python のサンプルコードとして掲載しているだけです．