日本株システムトレードプラットフォーム QuantX でn日足（終値）を扱うアルゴリズムを実装する


# 日本株システムトレードプラットフォーム QuantX でn日足（終値）を扱うアルゴリズムを実装する

## 背景

* Python による日本株システムトレードプラットフォームである [QuantX](https://quantx.io/top) ではデフォルトでアルゴリズムの実装をする際に、1日足の株価しか取得することができません
* なので、n日分の株価を扱えるようにしてみようと思い、実装してみました

## 実装したアルゴリズム

* https://factory.quantx.io/developer/bb3c6f7b6b7342d5b0c907dc90e2a762 にて公開

下記がその内容のプログラムとなります。
ちなみに、BETWEEN_DAY の値を n にすることで n日足の終値を取得することができます。

ボリンジャーバンドの上端、下端を跨いだときに売買をするアルゴリズムです。
株価が下がりすぎた時を検出した場合は買い、逆の場合は売り、というのを行っております。

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

BETWEEN_DAY = 2

def initialize(ctx):

    # 設定
    ctx.configure(
        target="jp.stock.daily",
        channels={          # 利用チャンネル
            "jp.stock": {
              "symbols": [
                'jp.stock.1332',	#	日本水産(株)
                'jp.stock.1605',	#	国際石油開発帝石(株)
              ],
              "columns": [ "close_price_adj",]
            }
        }
    )
    
    def _TALIB_CALL(data):
      cp = data["close_price_adj"].fillna(method="ffill")
      cp_between_n = pd.DataFrame(data=0,columns=[], index=cp.index)
      buy_sig = pd.DataFrame(data=0,columns=[], index=cp.index)
      sell_sig = pd.DataFrame(data=0,columns=[], index=cp.index)
      
      tmp = 0
      for (sym,val) in cp.items():
        count = 0
        for [day,val_day] in val.items():
          if count % BETWEEN_DAY == 0:
            cp_between_n.loc[day,sym] = val_day
          else:
            cp_between_n.loc[day,sym] = np.nan
          count += 1
          
      cp_between_n.dropna(inplace=True)
      
      upperband = {}
      middleband = {}
      lowerband = {}
      
      rsi = pd.DataFrame(data=0,columns=[], index=cp_between_n.index)
      uband = pd.DataFrame(data=0,columns=[], index=cp_between_n.index)
      lband = pd.DataFrame(data=0,columns=[], index=cp_between_n.index)
      
      for (sym,val) in cp_between_n.items(): 
        upperband[sym], middleband[sym], lowerband[sym] = ta.BBANDS(
          cp_between_n[sym].values.astype(np.double),
          timeperiod=10,
          nbdevup=1,
          nbdevdn=1,
          matype=0)
        
        lband[sym] = lowerband[sym]
        uband[sym] = upperband[sym]

      lband2 = cp.copy()
      lband2[:] = 0
      uband2 = cp.copy()
      uband2[:] = 0
      uband2 = uband + uband2
      lband2 = lband + lband2
  
      for (sym,val) in cp.items(): 
        buy_sig[sym] = lband2[sym] > cp[sym]
        sell_sig[sym] = cp[sym] > uband2[sym]
      
      buy_sig = (buy_sig == True)
      sell_sig = ((sell_sig == True) & (buy_sig == False))
      
      return {
        "upperband:price": uband2,
        "lowerband:price": lband2,
        "buy:sig": buy_sig,
        "sell:sig": sell_sig,
      }

    # シグナル登録
    ctx.regist_signal("TALIB", _TALIB_CALL)

def handle_signals(ctx, date, current):

    done_syms = set([])
    for (sym,val) in ctx.portfolio.positions.items():
      amount = val["amount"]
      if amount > 0:
        done_syms.add(sym)
            
    ratio = current["buy:sig"]
    df = ratio[ratio == 1.0]
    for (sym,val) in df.items():
      if sym in done_syms:
        continue
        
      sec = ctx.getSecurity(sym)
      sec.order_target_percent(0.4, comment="シグナル買(%f)" % val)
      pass

    ratio = current["sell:sig"]
    df = ratio[ratio == 1.0]
    for (sym,val) in df.items():
        
      sec = ctx.getSecurity(sym)
      sec.order_target_percent(0, comment="シグナル売(%f)" % val)
      pass

    pass
```

うん、長い。

##  解説

* 上記コードに至るまで、Jupyter 上で模擬データを使って検証したのでそれに沿って解説していきます。

### 模擬データを準備する

まずは、QuantX で扱われている時系列データを模擬したデータを定義します。
これは、下記のように定義できます。

実際には `jp.stock.XXXX` の `XXXX` が銘柄コードとなります。


```python
import pandas as pd
import numpy as np

# indexに名前を指定しています
cp = pd.DataFrame([
    [2000, 2015],
    [2096, 2018],
    [2066, 2034],
    [2076, 2054],
    [2026, 2074],
    [2076, 2044]],
    index = ['2015-01-05 ', '2015-01-06', '2015-01-07', '2015-01-08', '2015-01-09', '2015-01-12', '2015-01-13'],
    columns=["jp.stock.XXXX",'jp.stock.YYYY']
)
#cp
```




<div>

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>jp.stock.XXXX</th>
      <th>jp.stock.YYYY</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>2015-01-05</th>
      <td>2000</td>
      <td>2015</td>
    </tr>
    <tr>
      <th>2015-01-06</th>
      <td>2024</td>
      <td>2025</td>
    </tr>
    <tr>
      <th>2015-01-07</th>
      <td>2096</td>
      <td>2018</td>
    </tr>
    <tr>
      <th>2015-01-08</th>
      <td>2066</td>
      <td>2034</td>
    </tr>
    <tr>
      <th>2015-01-09</th>
      <td>2076</td>
      <td>2054</td>
    </tr>
    <tr>
      <th>2015-01-12</th>
      <td>2026</td>
      <td>2074</td>
    </tr>
    <tr>
      <th>2015-01-13</th>
      <td>2076</td>
      <td>2044</td>
    </tr>
  </tbody>
</table>
</div>



### n日毎のデータとして整形する

次に、このデータセットを n 日ごとに整形するようにします。

ここでは、２日として、処理します。


```python
BETWEEN_DAY = 2

cp_ndays = pd.DataFrame(data=0,columns=[], index=cp.index)

tmp = 0
for (sym,val) in cp.items():
  count = 0
  for [day,val_day] in val.items():
    if count % BETWEEN_DAY == 0:
      cp_ndays.loc[day,sym] = val_day
      tmp = val_day
    else:
      cp_ndays.loc[day,sym] = np.nan
    
    count += 1
    
cp_ndays.dropna(inplace=True)
cp_ndays
```




<div>

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>jp.stock.XXXX</th>
      <th>jp.stock.YYYY</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>2015-01-05</th>
      <td>2000.0</td>
      <td>2015.0</td>
    </tr>
    <tr>
      <th>2015-01-07</th>
      <td>2096.0</td>
      <td>2018.0</td>
    </tr>
    <tr>
      <th>2015-01-09</th>
      <td>2076.0</td>
      <td>2054.0</td>
    </tr>
    <tr>
      <th>2015-01-13</th>
      <td>2076.0</td>
      <td>2044.0</td>
    </tr>
  </tbody>
</table>
</div>



### n日毎のデータからボリンジャーバンドの下端、上端を算出する

次に、TA-lib を利用して3日ボリンジャーバンドを実装します。

`lband` には ボリンジャーバンドの下端値

`uband` には ボリンジャーバンドの上端値

が代入されます。


```python
import talib as ta

upperband = {}
middleband = {}
lowerband = {}

uband = pd.DataFrame(data=0,columns=[], index=cp_ndays.index)
lband = pd.DataFrame(data=0,columns=[], index=cp_ndays.index)

for (sym,val) in cp_ndays.items(): 
  upperband[sym], middleband[sym], lowerband[sym] = ta.BBANDS(
    cp_ndays[sym].values.astype(np.double),
    timeperiod=3,
    nbdevup=1,
    nbdevdn=1,
    matype=0)

  lband[sym] = lowerband[sym]
  uband[sym] = upperband[sym]
```


```python
lband
```




<div>
<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>jp.stock.XXXX</th>
      <th>jp.stock.YYYY</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>2015-01-05</th>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>2015-01-07</th>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>2015-01-09</th>
      <td>2015.978500</td>
      <td>2011.279955</td>
    </tr>
    <tr>
      <th>2015-01-13</th>
      <td>2073.238576</td>
      <td>2023.493591</td>
    </tr>
  </tbody>
</table>
</div>




```python
uband
```




<div>

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>jp.stock.XXXX</th>
      <th>jp.stock.YYYY</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>2015-01-05</th>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>2015-01-07</th>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>2015-01-09</th>
      <td>2098.688166</td>
      <td>2046.720045</td>
    </tr>
    <tr>
      <th>2015-01-13</th>
      <td>2092.094757</td>
      <td>2053.839742</td>
    </tr>
  </tbody>
</table>
</div>



### シグナル出力用のデータフレームに整形する

この状態のままでは、シグナルとして出力できないため
整形元のデータフレームと同じ形に当てはめる必要がある。

値が全て 0 のデータフレームとして `lband2`, `uband2` を定義し、
`lband`, `uband` と同じ日付にて結合させている。


```python
lband2 = cp.copy()
lband2[:] =0
uband2 = cp.copy()
uband2[:] = 0
```


```python
uband2 = uband + uband2
uband2
```




<div>

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>jp.stock.XXXX</th>
      <th>jp.stock.YYYY</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>2015-01-05</th>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>2015-01-06</th>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>2015-01-07</th>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>2015-01-08</th>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>2015-01-09</th>
      <td>2098.688166</td>
      <td>2046.720045</td>
    </tr>
    <tr>
      <th>2015-01-12</th>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>2015-01-13</th>
      <td>2092.094757</td>
      <td>2053.839742</td>
    </tr>
  </tbody>
</table>
</div>




```python
lband2 = lband + lband2
lband2
```




<div>

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>jp.stock.XXXX</th>
      <th>jp.stock.YYYY</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>2015-01-05</th>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>2015-01-06</th>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>2015-01-07</th>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>2015-01-08</th>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>2015-01-09</th>
      <td>2015.978500</td>
      <td>2011.279955</td>
    </tr>
    <tr>
      <th>2015-01-12</th>
      <td>NaN</td>
      <td>NaN</td>
    </tr>
    <tr>
      <th>2015-01-13</th>
      <td>2073.238576</td>
      <td>2023.493591</td>
    </tr>
  </tbody>
</table>
</div>



### 終値との比較を行い、バンドを跨いだかどうか判定する


```python
buy_sig = uband2 > cp
sell_sig = lband2 < cp
```


```python
buy_sig
```




<div>

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>jp.stock.XXXX</th>
      <th>jp.stock.YYYY</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>2015-01-05</th>
      <td>False</td>
      <td>False</td>
    </tr>
    <tr>
      <th>2015-01-06</th>
      <td>False</td>
      <td>False</td>
    </tr>
    <tr>
      <th>2015-01-07</th>
      <td>False</td>
      <td>False</td>
    </tr>
    <tr>
      <th>2015-01-08</th>
      <td>False</td>
      <td>False</td>
    </tr>
    <tr>
      <th>2015-01-09</th>
      <td>True</td>
      <td>True</td>
    </tr>
    <tr>
      <th>2015-01-12</th>
      <td>False</td>
      <td>False</td>
    </tr>
    <tr>
      <th>2015-01-13</th>
      <td>True</td>
      <td>True</td>
    </tr>
  </tbody>
</table>
</div>




```python
sell_sig
```




<div>

<table border="1" class="dataframe">
  <thead>
    <tr style="text-align: right;">
      <th></th>
      <th>jp.stock.XXXX</th>
      <th>jp.stock.YYYY</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>2015-01-05</th>
      <td>False</td>
      <td>False</td>
    </tr>
    <tr>
      <th>2015-01-06</th>
      <td>False</td>
      <td>False</td>
    </tr>
    <tr>
      <th>2015-01-07</th>
      <td>False</td>
      <td>False</td>
    </tr>
    <tr>
      <th>2015-01-08</th>
      <td>False</td>
      <td>False</td>
    </tr>
    <tr>
      <th>2015-01-09</th>
      <td>True</td>
      <td>True</td>
    </tr>
    <tr>
      <th>2015-01-12</th>
      <td>False</td>
      <td>False</td>
    </tr>
    <tr>
      <th>2015-01-13</th>
      <td>True</td>
      <td>True</td>
    </tr>
  </tbody>
</table>
</div>



このデータフレームのデータがシグナルとなって、
売買タイミングの通知をユーザにしてくれる感じですね。

この検証を基に QuantX 上に組み込んだものが最初のコードとなります。

このやり方だと終値しか取得できないから、高値、安値を取得するには min とか max とかを合わせて使う必要がありそう。

今回はそこまでできませんでした。。