金融予測ラベリング応用編　投資戦略：タートルズ手法

参考:[タートルズ投資とは？そのルールと概要](https://www.ig.com/jp/trading-strategies/turtle-trading--what-is-it-and-what-are-the-rules--181207)

#　戦略に関する概略

タートルズ投資とは、持続的なモメンタムを重視したトレンドフォロー戦略です。これは上昇と下落の両トレンドでブレイクアウトを見極める戦略であり、多くの金融市場で用いられている手法です。

デニスのアイデアの根幹は、トレーダーが直感に頼るのではなく、ルールに従うことができるメカニカルな戦略を構築することにあります。初心者のトレーダー集団がルールに従うためのトレーニングを受け、成功した場合には、各々100万ドルの運用資金が与えられました。

# 本投資戦略のルール
- **取引市場**：タートルズは先物取引に投資し、大口注文なしで、市場を動かすことなく取引きができる流動性の高い市場をターゲットとしました。そのターゲットとして選ばれたのがFX、コモディティ、国債そしてS&P 500でした。
- **ポジションサイズ**：タートルズは市場のボラティリティに合わせてポジションサイズを柔軟に決定しました。こうすることでポジション価値の変動を平均化することができるからです。この手法は、各ポジションが各市場で同じ規模になるよう確保することで、分散投資の効果も狙っていました。また、市場の流動性を測定するために、20日指数平滑移動平均を用いました（IG証券の取引きプラットフォームでは市場の変動性を示すATR指数を使用して、類似の方法を試すことができます）。
- **エントリー**：エントリーする際は、２種類の異なる方法が用いられました。1つ目の方法は、20日間の高値をブレイクアウトする瞬間に買い、20日間の安値のブレイクアウトでは売りを仕掛ける手法です。もうひとつは55日の高安を基準としたブレイクアウト戦略です。この手法を用いて最大4つの取引を行うことができます。取引きの兆候を見逃すと利益獲得のチャンスを逃し、それらが重なると全体的なパフォーマンスが落ち込むため、タートルズはすべての兆候を確実にキャッチするよう叩き込まれました。
- **ストップロス**：タートルズは、予想外の損失を被ることを事前に防ぐため、常にストップロス（損切りの注文）を使用するよう叩き込まれました。彼らはこの重要性を理解し、取引きを行う前に自分が取れるポジションのサイズを決定し、ストップ注文を必ず各取引きの際に付けました。このようにしてタートルズは、巨額の損失に直面したことで有名となったニック・リーソンの教訓をもとに、想定外の損失を被ることを回避していきました。また、よりボラティリティが高い市場には、より幅広い値幅のストップを付けるといった工夫も取り入れました。
- **清算ルール**：早期にポジションを清算する場合、その取引で利益を得る可能性が小さくなる傾向があります。そこでタートルズが学んだ取引ルールの1つが、多くの取引を行うことでした。そのうち、大きな利益を生んだのはごくわずかでしたが、同時に損失額も少額でした。清算については、ロング（買い）ポジションに対しては10日間の安値で清算、ショート（売り）ポジションに対して20日間の高値で清算というのが１つめのルールでした。もうひとつの清算ルールには、20日間の高安を用いました。
- **タクティクス**：最後に、タートルズは指値注文の使用と変化の速い市場に対処する方法（急いで「最良」価格を得ようとするのではなく、注文する前に落ち着くのを待つ方法）について叩き込まれました。また、モメンタムから利益を得るために、最も強気な相場で買い、最も弱気な相場で売ることも教わりました。

# ラベリング方法
では、さっそくラベリング方法について紹介したい。
一見かなり複雑に見えるが、投資戦略自体はそこまで複雑ではない。
簡単に解説する。
- **取引市場** : これは流動性があれば良いので、正直どの商品でも構わない。
- **ポジションサイズ** : 予測においては、まずは期待収益率が正であることが大切なので、サイズの決定はその後からでも遅くはない。寧ろ、1単位だけのベッティングで期待収益率が負であるのに、ベッティングサイズを変更して正に持っていくのは評価上やや危険である。
- **エントリー** : これが恐らく一番難しいポイントである。というのも、任意の時刻でラベル付け出来ないので、フィルタリングという概念が必要である。
- **清算ルール** : ラベリングではここが最も重要である。
- **タクティクス** : これは市場の心理に関する話なので、定式化出来るのであれば、組み込んでも面白いのかもしれません。

# 実装
データフレームの形式は、```columns=["open", "high", "low", "close"], index=DatetimeIndex(dtype = datetime64[ns])```である。
'<table border="1" class="dataframe">  <thead>    <tr style="text-align: right;">      <th></th>      <th>open</th>      <th>high</th>      <th>low</th>      <th>close</th>    </tr>    <tr>      <th>date</th>      <th></th>      <th></th>      <th></th>      <th></th>    </tr>  </thead>  <tbody>    <tr>      <th>2020-02-21 16:30:00</th>      <td>23080.0</td>      <td>23080.0</td>      <td>23070.0</td>      <td>23070.0</td>    </tr>    <tr>      <th>2020-02-21 16:31:00</th>      <td>23070.0</td>      <td>23070.0</td>      <td>23070.0</td>      <td>23070.0</td>    </tr>    <tr>      <th>2020-02-21 16:32:00</th>      <td>23070.0</td>      <td>23080.0</td>      <td>23070.0</td>      <td>23080.0</td>    </tr>    <tr>      <th>2020-02-21 16:33:00</th>      <td>23070.0</td>      <td>23070.0</td>      <td>23070.0</td>      <td>23070.0</td>    </tr>    <tr>      <th>2020-02-21 16:34:00</th>      <td>23070.0</td>      <td>23070.0</td>      <td>23070.0</td>      <td>23070.0</td>    </tr>  </tbody></table>'
- **エントリーの定義**

```python
T = 60*10
long_entry = df["high"] > df["high"].rolling(T).max().shift(1)
short_entry = df["low"] < df["low"].rolling(T).min().shift(1)
```
これは過去$T$期間の高値を超えているかどうかである。これはロングしかしていない。

- **清算条件の定義**

```python
close_long = df["low"] < df["low"].rolling(T//2).min().shift(1)
close_short = df["high"] > df["high"].rolling(T//2).max().shift(1)
```
過去$\frac{T}{2}$の安値更新で決済しているが、一般性を持たせるならば$\frac{T}{2}$である必要は全くない。

- **ラベリング**

```python
import numpy as np
label = []
X = df["close"].values
N = len(X)
long = long_entry.values
short = short_entry.values
l_close = close_long.values
s_close = close_short.values
for n in tqdm(range(N)):
    if long[n]:
        for m in range(n, N):
            if l_close[m]:
                label.append(X[m]-X[n])
                break
    if short[n]:
        for m in range(n, N-1):
            if s_close[m]:
                label.append(X[n]-X[m])
                break
    else:
        label.append(np.nan)
labels = pd.DataFrame(label[:N], index=df.index[:len(label[:N])], columns=["bin"])
labels
```

ここでは敢えて ``df.loc``を使用していない。理由としては、探索に時間がかかるからである。
戦略としては、最高値を観測した足の終値で取引をし、最安値を観測した足の終値で清算するとする。

# 検証
今回は最適化は行わないが、どのようにラベルの効果を見れるのかを見よう。
条件、足の間隔は1分、$T=600$とする。
![turtles_labeling.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/562670/80622a4f-2419-a290-a9e9-5fb5ce10e41e.png)

今回の実験では、単純に10時間の高値(安値)を更新したらロング(ショート)し、5時間の安値(高値)を更新したら清算するという戦略を行った。