OHLCデータからテクニカル指標を求める

## 検証環境

- MacOS Mojave 10.14.4
- Python 3.6.8
- numpy 1.15.0
- pandas 0.23.4
- matplotlib 2.2.2
- mpl-finance 0.10.0

## OHLCデータ

OHLCデータは、Cryptowatchから取得した`BTCFX/JPY`の1分足データを使用しました。
`https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc?periods=60&after=1557172800&before=1557176340`

```python
import json
import requests
import datetime
import numpy as np
import matplotlib.dates as mdates
import matplotlib.pyplot as plt
from datetime import datetime, timedelta, timezone
from pandas import DataFrame, Series, to_datetime, concat
from mpl_finance import candlestick_ohlc

if __name__ == '__main__':
    response = json.loads(requests.get("https://api.cryptowat.ch/markets/bitflyer/btcfxjpy/ohlc?periods=60&after=1557172800&before=1557176340").text)

    # 7番目の要素はdocsに記載がないので何なのか不明
    df = pd.DataFrame(response['result']['60'], columns=['date','open','high','low','close','volume','unknown']).dropna().drop(columns=['volume','unknown'])
    # print(df.head(3))
    #         date    open    high     low   close
    # 0  1557432000  664434  665011  663901  664755
    # 1  1557432060  664826  664826  663800  663834
    # 2  1557432120  663834  664117  663723  664069

    # レスポンスのタイムスタンプがUnixTimestamp形式なのでdatetime型に変換し、pandas.DatetimeIndexを設定する
    df['date'] = pd.to_datetime(df['date'], unit='s', utc=True)
    df.set_index('date', inplace=True)
    df.index = df.index.tz_convert('Asia/Tokyo')
    # print(df.head(3))
    #                             open    high     low   close
    # date                                                     
    # 2019-05-10 05:00:00+09:00  664434  665011  663901  664755
    # 2019-05-10 05:01:00+09:00  664826  664826  663800  663834
    # 2019-05-10 05:02:00+09:00  663834  664117  663723  664069

    plt.style.use('ggplot')

    ax = plt.subplot()
    ax.xaxis.set_major_locator(mdates.AutoDateLocator())
    ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M', tz=timezone(timedelta(hours=9))))

    # candlestick_ohlc の第二引数に渡すタプルイテレータを生成
    # @see https://github.com/matplotlib/mpl_finance/blob/master/mpl_finance.py
    quotes = zip(mdates.date2num(df.index), df['open'], df['high'], df['low'], df['close'])
    candlestick_ohlc(ax, quotes, width=(1/24/len(df))*0.7, colorup='g', colordown='r')

    plt.show()
```

![Figure_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55616/3dd2cc2f-910f-f908-9365-f825e5f476dd.png)

## 単純移動平均 (SMA)

```python
def sma(ohlc: DataFrame, period=21) -> Series:
    return Series(
        ohlc['close'].rolling(period).mean(),
        name=f'SMA {period}',
    )
```

```python
sma(df, 5).plot.line(color='y', legend=True)
sma(df, 10).plot.line(color='c', legend=True)
sma(df, 21).plot.line(color='m', legend=True)
```

![Figure_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55616/31731972-896e-dbb0-30a7-4a07fcd79984.png)


## 指数平滑移動平均 (EMA)

```python
def ema(ohlc: DataFrame, expo=21) -> Series:
    return Series(
        ohlc['close'].ewm(span=expo).mean(),
        name=f'EMA {expo}',
    )
```

```python
ema(df, 5).plot.line(color='y', legend=True)
ema(df, 10).plot.line(color='c', legend=True)
ema(df, 21).plot.line(color='m', legend=True)
```
![Figure_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55616/a107e98e-be2b-256b-016a-d4479c3a6894.png)

## 加重移動平均 (WMA)

```python
def wma(ohlc: DataFrame, period: int = 9) -> Series:
    # WMA = ( 価格 * n + 価格(1) * n-1 + ... 価格(n-1) * 1) / ( n * (n+1) / 2 )
    denominator = (period * (period + 1)) / 2
    weights = Series(np.arange(1, period + 1)).iloc[::-1]

    return Series(
        ohlc['close'].rolling(period, min_periods=period).apply(lambda x: np.sum(weights * x) / denominator, raw=True),
        name=f'WMA {period}'
    )
```

```python
wma(df, 5).plot.line(color='y', legend=True)
wma(df, 10).plot.line(color='c', legend=True)
wma(df, 21).plot.line(color='m', legend=True)
```

![Figure_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55616/800d9c83-12e4-e6b6-97a9-19cc2d78e9bf.png)


## 標準偏差

```python:標準偏差
def sd(ohlc: DataFrame, period=10, ddof=0) -> float:
    return ohlc['close'].tail(period).std(ddof=ddof)
```

`ddof`は減算する自由度の数を示します。`ddof=1`を指定することで、不偏分散による標準偏差となります。
https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.std.html
> Delta Degrees of Freedom. The divisor used in calculations is N - ddof, where N represents the number of elements.

```python
>>> sd(df, ddof=0)
232.7845570479279
>>> sd(df, ddof=1)
245.37646812828467
```

## 参考文献

- [pandas.DataFrame — pandas 0.23.4 documentation](https://pandas.pydata.org/pandas-docs/version/0.23.4/generated/pandas.DataFrame.html)
- [Market Data REST API - Cryptowatch](https://cryptowat.ch/docs/api)
- [CryptoWatchのAPIを用いたローソク足チャート生成](https://qiita.com/0x0/items/883fd45c4bd3eb50fcb3)