📚

約定履歴と戯れて、タイムバーに情報を追加してみる

2022/11/06に公開

きっかけ

以前からタイムバーを約定履歴から作るなら、タイムバーの中にあるトレードの統計的特性を約定履歴から作って、タイムバーの内部のより細かい情報として追加したいと思っていたので、今回はそれをやってみました。

やること

  • OHLCVの情報に加えて、全約定価格の重み付き平均、重み付き分散、重み付き歪度、重み付き尖度を計算して、タイムバーの情報として追加します

本当は重み付き分位点の情報を追加したいのですが、まだ求め方を今ひとつ理解できていないので保留しています。ここにかなり丁寧な説明があるので、後で読んで理解したいものです。ベータ関数ってなんぞ?

計算のしかた

stackoverflowに書いてあったものを使うことにしました。

n=1のときはいわゆるVWAPというやつになります。(困った時のWikipedia)

n=4のときはPandasのkurt()の流儀に合わせて3を引いた値を返すようにします。

Wikipediaより引用

Wikipedia

尖度には、4次の標準化モーメントとも呼ばれる\mu_4 / \sigma^4から3を引いて正規分布の尖度を 0 とする定義と、4次の標準化モーメントをそのまま用いて正規分布の尖度を 3 とする定義があることに注意。

コード

いくつか自分で理解しづらかったポイントを抜き出しておきます。

重み付きのn次モーメントを計算する関数
def calc_weighted_moment(values, weights, n, sum_weights = None, weighted_mean = None, weighted_var = None):
    assert n > 0
    assert values.shape == weights.shape

    if sum_weights is not None:
        _sum_weights = sum_weights
    else:
        _sum_weights = np.sum(weights)

    if _sum_weights == 0:
        return np.nan

    if n == 1:
        return np.sum(weights * values) / _sum_weights

    if weighted_mean is not None:
        _weighted_mean = weighted_mean
    else:
        _weighted_mean = np.sum(weights * values) / _sum_weights

    if n == 2:
        return np.sum(weights * (values - _weighted_mean) ** 2) / _sum_weights

    if weighted_var is not None:
        _weighted_var = weighted_var
    else:
        _weighted_var = np.sum(weights * (values - _weighted_mean) ** 2) / _sum_weights
    _weighted_std = np.sqrt(_weighted_var)

    if n == 4:
        return np.sum(weights * ((values - _weighted_mean) / _weighted_std) ** n) / _sum_weights - 3
    return np.sum(weights * ((values - _weighted_mean) / _weighted_std) ** n) / _sum_weights
上の関数を使って重み付きn次モーメントのDataFrameを生成して、タイムバーのDataFrameと結合する部分
_interval_str = f'{interval}S'
...(中略)...
    def custom_resampler(x):
        _total_quote_qty = x['quote_qty'].sum()
        _buy_trade_count = len(x[x['is_buyer_maker'] == False].index)
        _sell_trade_count = len(x) - _buy_trade_count
        _buy_quote_qty = x.loc[x['is_buyer_maker'] == False, 'quote_qty'].sum().astype(float)
        _sell_quote_qty = _total_quote_qty - _buy_quote_qty
        
        _weighted_price_mean = calc_weighted_moment(x['price'], x['quote_qty'], 1, sum_weights = _total_quote_qty)
        _weighted_price_var = calc_weighted_moment(x['price'], x['quote_qty'], 2, sum_weights = _total_quote_qty, weighted_mean = _weighted_price_mean)
        _weighted_price_std = np.sqrt(_weighted_price_var)
        _weighted_price_skew = calc_weighted_moment(x['price'], x['quote_qty'], 3, sum_weights = _total_quote_qty, weighted_mean = _weighted_price_mean, weighted_var = _weighted_price_var)
        _weighted_price_kurt = calc_weighted_moment(x['price'], x['quote_qty'], 4, sum_weights = _total_quote_qty, weighted_mean = _weighted_price_mean, weighted_var = _weighted_price_var)

        return pd.Series([_buy_trade_count, _sell_trade_count, _buy_quote_qty, _sell_quote_qty, _weighted_price_mean, _weighted_price_var, _weighted_price_skew, _weighted_price_kurt, _weighted_price_std], ['buy_trade_count', 'sell_trade_count', 'buy_quote_qty', 'sell_quote_qty', 'vw_price_mean', 'vw_price_var', 'vw_price_skew', 'vw_price_kurt', 'vw_price_std'])

    _df_statistics = _df.groupby(pd.Grouper(freq = _interval_str)).apply(custom_resampler)
    _df_timebar = pd.concat([_df_timebar, _df_statistics], axis = 1)

コード全体はこちらです。
https://github.com/WannabeBotter/binance_exercise/blob/main/timebar_generate.py

結果

とりあえずそれっぽいデータがタイムバーのデータファイルに入るようになりました。よかったよかった。


それっぽいデータ

おまけ

せっかくなので、ちょっと散布図を書いてみました。

  • 対象期間は2019-09-08から2022-11-04です
  • 対象銘柄はBTCUSDT永久先物です
  • 対象の時間足は1時間足です

結果を見た感じ、ひと目でめっちゃつよい! というわけではなさそうですね。将来役に立つといいんですが。

実験用のコード
import pandas as pd
import numpy as np
import datetime
import matplotlib.pyplot as plt
import japanize_matplotlib
from tqdm.auto import tqdm
import exercise_util

df_timebar_1h = exercise_util.concat_timebar_files('BTCUSDT', 3600)

df_timebar_1h['lr'] = np.log(df_timebar_1h['close']) - np.log(df_timebar_1h['close'].shift(1))
df_timebar_1h['lr_future'] = df_timebar_1h['lr'].shift(-1)
df_timebar_1h['vw_price_std_future'] = df_timebar_1h['vw_price_std'].shift(-1)
df_timebar_1h.replace([np.inf, -np.inf], np.nan, inplace = True)
df_timebar_1h = df_timebar_1h.dropna()

exercise_util.show_correlation(np.log(df_timebar_1h['close']) - np.log(df_timebar_1h['vw_price_mean']), df_timebar_1h['lr_future'], xaxis_label = 'クローズ価格 - 重み付き平均価格', yaxis_label = '未来の対数リターン', legend_loc = 'upper right')
exercise_util.show_correlation(df_timebar_1h['vw_price_skew'], df_timebar_1h['lr_future'], xaxis_label = '重み付き歪度', yaxis_label = '未来の対数リターン', legend_loc = 'upper right')
exercise_util.show_correlation(df_timebar_1h['vw_price_std'], df_timebar_1h['vw_price_std_future'], xaxis_label = '重み付き標準偏差', yaxis_label = '未来の重み付き標準偏差', legend_loc = 'upper right')


あるバーのクローズ価格と重み付き平均価格の変化率 vs 1本後のバーの対数リターン


あるバーの重み付き歪度 vs 1本後のバーの対数リターン


あるバーの重み付き標準偏差 vs 1本後のバーの重み付き標準偏差

Discussion