高周波取引戦略について考える (2)

作者: リン・ハーン小草, 作成日:2023-08-04 16:14:27, 更新日:2023-09-19 09:08:17

img

論文は,高周波取引戦略を中心に,累積的な取引量モデリングと価格ショックに焦点を当てた.論文は,単一の取引,固定間隔の価格ショック,および取引が価格に与える影響を分析することによって,最適な取引先モデルを初步的に提案した.このモデルは,取引量と価格ショックを理解した上で,最適な取引先を見つけようと試みた.モデルの仮説は深く議論され,実際の予想利益とモデル予測を比較することによって,最適な取引先ポジションを初步的に評価した.

累積された取引モデル

この記事では,ある取引が1つの値よりも大きい確率表記を示しています.

img

また,時間間の取引量の分布に関心があり,直感的に,各取引量と注文頻度に関連しているはずである. 下ではデータを固定間隔で処理する. 上記のようにその分布を描画する.

from datetime import date,datetime
import time
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
trades = pd.read_csv('HOOKUSDT-aggTrades-2023-01-27.csv')
trades['date'] = pd.to_datetime(trades['transact_time'], unit='ms')
trades.index = trades['date']
buy_trades = trades[trades['is_buyer_maker']==False].copy()
buy_trades = buy_trades.groupby('transact_time').agg({
    'agg_trade_id': 'last',
    'price': 'last',
    'quantity': 'sum',
    'first_trade_id': 'first',
    'last_trade_id': 'last',
    'is_buyer_maker': 'last',
    'date': 'last',
    'transact_time':'last'
})
buy_trades['interval']=buy_trades['transact_time'] - buy_trades['transact_time'].shift()
buy_trades.index = buy_trades['date']

1s間隔の1sごとに取引を組み合わせ,取引されていない部分を取り除き,上記の単一の取引の分布で調整すると,よりよい結果が得られる. 1s内の取引を単一の取引と見なすことで,この問題は解決された問題になる.しかし周期が伸びると (取引頻率に比べて) 誤差が増加することが判明した.この誤差は,前回のパレト分布修正によって引き起こされていることが研究で明らかになった.これは,周期が伸びると,単一の取引が多く含まれ,複数の取引の組み合わせがパレト分布に近づくにつれて,修正項が削除される状況を示している.

df_resampled = buy_trades['quantity'].resample('1S').sum()
df_resampled = df_resampled.to_frame(name='quantity')
df_resampled = df_resampled[df_resampled['quantity']>0]
buy_trades
agg_trade_id について 価格 first_trade_id について last_trade_id について is_buyer_maker 購入者_メーカー 日付 トランザクション_タイム インターバル 異なった
2023-01-27 00:00:00.161 1138369 2.901 54.3 3806199 3806201 偽り 2023-01-27 00:00:00.161 1674777600161 NaN 0.001
2023-01-27 00:00:04.140 1138370 2.901 291.3 3806202 3806203 偽り 2023-01-27 00:00:04.140 1674777604140 3979.0 0.000
2023-01-27 00:00:04.339 1138373 2.902 55.1 3806205 3806207 偽り 2023-01-27 00:00:04.339 1674777604339 199.0 0.001
2023-01-27 00:00:04.772 1138374 2.902 1032.7 3806208 3806223 偽り 2023-01-27 00:00:04.772 1674777604772 433.0 0.000
2023-01-27 00:00:05.562 1138375 2.901 3.5 3806224 3806224 偽り 2023-01-27 00:00:05.562 1674777605562 790.0 0.000
2023-01-27 23:59:57.739 1544370 3.572 394.8 5074645 5074651 偽り 2023-01-27 23:59:57.739 1674863997739 1224.0 0.002
2023-01-27 23:59:57.902 1544372 3.573 177.6 5074652 5074655 偽り 2023-01-27 23:59:57.902 1674863997902 163.0 0.001
2023-01-27 23:59:58.107 1544373 3.573 139.8 5074656 5074656 偽り 2023-01-27 23:59:58.107 1674863998107 205.0 0.000
2023-01-27 23:59:58.302 1544374 3.573 60.5 5074657 5074657 偽り 2023-01-27 23:59:58.302 1674863998302 195.0 0.000
2023-01-27 23:59:59.894 1544376 3.571 12.1 5074662 5074664 偽り 2023-01-27 23:59:59.894 1674863999894 1592.0 0.000
#1s内的累计分布
depths = np.array(range(0, 3000, 5))
probabilities = np.array([np.mean(df_resampled['quantity'] > depth) for depth in depths])
mean = df_resampled['quantity'].mean()
alpha = np.log(np.mean(df_resampled['quantity'] > mean))/np.log(2.05)
probabilities_s = np.array([((1+20**(-depth/mean))*depth/mean+1)**(alpha) for depth in depths])

plt.figure(figsize=(10, 5))
plt.plot(depths, probabilities)
plt.plot(depths, probabilities_s)
plt.xlabel('Depth')
plt.ylabel('Probability of execution')
plt.title('Execution probability at different depths')
plt.grid(True)

png

df_resampled = buy_trades['quantity'].resample('30S').sum()
df_resampled = df_resampled.to_frame(name='quantity')
df_resampled = df_resampled[df_resampled['quantity']>0]
depths = np.array(range(0, 12000, 20))
probabilities = np.array([np.mean(df_resampled['quantity'] > depth) for depth in depths])
mean = df_resampled['quantity'].mean()
alpha = np.log(np.mean(df_resampled['quantity'] > mean))/np.log(2.05)
probabilities_s = np.array([((1+20**(-depth/mean))*depth/mean+1)**(alpha) for depth in depths])
alpha = np.log(np.mean(df_resampled['quantity'] > mean))/np.log(2)
probabilities_s_2 = np.array([(depth/mean+1)**alpha for depth in depths]) # 无修正

plt.figure(figsize=(10, 5))
plt.plot(depths, probabilities,label='Probabilities (True)')
plt.plot(depths, probabilities_s, label='Probabilities (Simulation 1)')
plt.plot(depths, probabilities_s_2, label='Probabilities (Simulation 2)')
plt.xlabel('Depth')
plt.ylabel('Probability of execution')
plt.title('Execution probability at different depths')
plt.legend() 
plt.grid(True)

png

現在,異なる時間累積された取引量の分布について,一般的な式をまとめ,各回別々の統計を使わずに,単筆の取引の分布で調整します.ここで省略する手順は,公式を直接与えます:

img

中には,avg_intervalが1つの取引の平均間隔を表し,avg_interval_Tが推定される間隔を表示し,少し回転している.もし1sの取引を推定するなら,統計1sに含まれる取引の平均間隔が必要である.もし注文が到着する確率がパルソン分布に一致する場合は,ここで直接推定できるはずだが,実際の偏差は大きいので,ここで説明しない.

ここで,ある時間間隔で取引が特定の値よりも大きい確率と,実際に深さの位置にある取引の確率が大きく異なるべきであることを注意してください. 待機時間が長くなるほど,オーダーブックが変化する可能性が高く,取引も深さの変化を引き起こすため,同じ深さの位置にある取引の確率がデータの更新とともにリアルタイムに変化します.

df_resampled = buy_trades['quantity'].resample('2S').sum()
df_resampled = df_resampled.to_frame(name='quantity')
df_resampled = df_resampled[df_resampled['quantity']>0]
depths = np.array(range(0, 6500, 10))
probabilities = np.array([np.mean(df_resampled['quantity'] > depth) for depth in depths])
mean = buy_trades['quantity'].mean()
adjust = buy_trades['interval'].mean() / 2620
alpha = np.log(np.mean(buy_trades['quantity'] > mean))/0.7178397931503168
probabilities_s = np.array([((1+20**(-depth*adjust/mean))*depth*adjust/mean+1)**(alpha) for depth in depths])

plt.figure(figsize=(10, 5))
plt.plot(depths, probabilities)
plt.plot(depths, probabilities_s)
plt.xlabel('Depth')
plt.ylabel('Probability of execution')
plt.title('Execution probability at different depths')
plt.grid(True)

png

単一取引価格ショック

トランザクションデータは宝物であり,掘り出すデータもたくさんあります. 戦略のリストランキング位置に影響する価格への注文の衝撃に非常に注意すべきです. 同様に,transact_timeの総合データに基づいて,最後の価格と最初の価格の差を計算します. もし1つの注文しかなければ,差は0です. 奇妙なことに,少数のデータ結果もマイナスであり,データ排列順序の問題で,ここでは深入しません.

結果では,衝撃を起こすことのできない割合は77%に達し,1つのチックの割合は16.5%,2つのチックの割合は3.7%,3つのチックの割合は1.2%,4つ以上のチックの割合は1%未満である.これは基本的には指数関数の特徴に準拠しているが,適合は正確ではない.

値差を発生させる取引量を統計化し,衝撃が大きすぎる誤差を取り除いた.基本的には線形関係に適合し,約1000回に1回のティックの価格変動を発生させる.また,各盤近くの価格に平均約1000回を掛けた単位の量として理解することができる.

diff_df = trades[trades['is_buyer_maker']==False].groupby('transact_time')['price'].agg(lambda x: abs(round(x.iloc[-1] - x.iloc[0],3)) if len(x) > 1 else 0)
buy_trades['diff'] = buy_trades['transact_time'].map(diff_df)
diff_counts = buy_trades['diff'].value_counts()
diff_counts[diff_counts>10]/diff_counts.sum()
0.000    0.769965
0.001    0.165527
0.002    0.037826
0.003    0.012546
0.004    0.005986
0.005    0.003173
0.006    0.001964
0.007    0.001036
0.008    0.000795
0.009    0.000474
0.010    0.000227
0.011    0.000187
0.012    0.000087
0.013    0.000080
Name: diff, dtype: float64
diff_group = buy_trades.groupby('diff').agg({
    'quantity': 'mean',
    'diff': 'last',
})
diff_group['quantity'][diff_group['diff']>0][diff_group['diff']<0.01].plot(figsize=(10,5),grid=True);

png

固定間隔の価格ショック

統計2s内の価格ショックは,ここには負の値があるという違いがある. もちろん,ここでは支払いを統計するだけで,対称位置が1tick大きい. 取引量と衝撃の関係を観察し続けて,統計が0以上の結果のみを推定し,結論と単一の注文はほぼ同じであり,ほぼ線形的な関係であり,各ティックは約2000の量を必要とします.

df_resampled = buy_trades.resample('2S').agg({ 
    'price': ['first', 'last', 'count'],
    'quantity': 'sum'
})
df_resampled['price_diff'] = round(df_resampled[('price', 'last')] - df_resampled[('price', 'first')],3)
df_resampled['price_diff'] = df_resampled['price_diff'].fillna(0)
result_df_raw = pd.DataFrame({
    'price_diff': df_resampled['price_diff'],
    'quantity_sum': df_resampled[('quantity', 'sum')],
    'data_count': df_resampled[('price', 'count')]
})
result_df = result_df_raw[result_df_raw['price_diff'] != 0]
result_df['price_diff'][abs(result_df['price_diff'])<0.016].value_counts().sort_index().plot.bar(figsize=(10,5));

png

result_df['price_diff'].value_counts()[result_df['price_diff'].value_counts()>30]
 0.001    7176
-0.001    3665
 0.002    3069
-0.002    1536
 0.003    1260
 0.004     692
-0.003     608
 0.005     391
-0.004     322
 0.006     259
-0.005     192
 0.007     146
-0.006     112
 0.008      82
 0.009      75
-0.007      75
-0.008      65
 0.010      51
 0.011      41
-0.010      31
Name: price_diff, dtype: int64
diff_group = result_df.groupby('price_diff').agg({ 'quantity_sum': 'mean'})
diff_group[(diff_group.index>0) & (diff_group.index<0.015)].plot(figsize=(10,5),grid=True);

png

取引量の価格ショック

前面は1つのティックの変化に必要な取引量を求めているが,それは衝撃が既に起こったと仮定した場合に構築されているため,正確ではない.

このデータでは,1sでサンプリングされ,100個あたり1ステップの長さで,この数値範囲内の価格変動を統計しています.

  1. 取引量が500以下になると,期待される価格変動は下落である.これは予想通りであり,売り込みも価格に影響している.
  2. 取引量が少ない場合,線形関係に対応する,つまり取引量が大きいほど,価格上昇が大きい.
  3. 決済取引が大きくなるほど,価格変動が大きくなる.これはしばしば価格の突破を表し,突破後に価格が戻ってくる可能性がある.
  4. ポイントグラフの上の部分,つまり取引量が価格上昇に対応する部分に注目すべきです.
  5. この取引対に対してのみ,取引量による価格変動の粗略な関係を示します.

img

Cは価格の変化を表し,Qは取引量を表します.

df_resampled = buy_trades.resample('1S').agg({ 
    'price': ['first', 'last', 'count'],
    'quantity': 'sum'
})
df_resampled['price_diff'] = round(df_resampled[('price', 'last')] - df_resampled[('price', 'first')],3)
df_resampled['price_diff'] = df_resampled['price_diff'].fillna(0)
result_df_raw = pd.DataFrame({
    'price_diff': df_resampled['price_diff'],
    'quantity_sum': df_resampled[('quantity', 'sum')],
    'data_count': df_resampled[('price', 'count')]
})
result_df = result_df_raw[result_df_raw['price_diff'] != 0]
df = result_df.copy()
bins = np.arange(0, 30000, 100)  # 
labels = [f'{i}-{i+100-1}' for i in bins[:-1]]  
df.loc[:, 'quantity_group'] = pd.cut(df['quantity_sum'], bins=bins, labels=labels)
grouped = df.groupby('quantity_group')['price_diff'].mean()
grouped_df = pd.DataFrame(grouped).reset_index()
grouped_df['quantity_group_center'] = grouped_df['quantity_group'].apply(lambda x: (float(x.split('-')[0]) + float(x.split('-')[1])) / 2)

plt.figure(figsize=(10,5))
plt.scatter(grouped_df['quantity_group_center'], grouped_df['price_diff'],s=10)
plt.plot(grouped_df['quantity_group_center'], np.array(grouped_df['quantity_group_center'].values)/2e6-0.000352,color='red')
plt.xlabel('quantity_group_center')
plt.ylabel('average price_diff')
plt.title('Scatter plot of average price_diff by quantity_group')
plt.grid(True)

png

grouped_df.head(10)
量_グループ 価格_差 数量_グループ_センター
0 0-199 -0.000302 99.5
1 100-299 -0.000124 199.5
2 200-399 -0.000068 299.5
3 300-499 -0.000017 399.5
4 400-599 -0.000048 499.5
5 500-699 0.000098 599.5
6 600-799 0.000006 699.5
7 700-899 0.000261 799.5
8 800-999 0.000186 899.5
9 900-1099 0.000299 999.5

初期最上位シングル位置

取引量モデルと取引量対価衝撃の粗略なモデルがあれば,最適な挂牌位置を計算できるようである.

  1. 衝撃後の価格が原価に戻ると仮定します (これはもちろん不可能性であり,衝撃後の価格の変化を再分析する必要があります)
  2. この期間中に取引量と注文頻度の分布が予測通りであるとする (これはまた不正確で,ここで1日の値で推定され,取引には明らかな集積現象がある).
  3. 模擬時間内に1つのセールが起こると仮定し,平衡します.
  4. 注文が完了した後に,他の支払いが価格を上昇させ続けると仮定し,特に少量の場合は顕著です.

まず,1s間の累積決済の確率が,Qよりも大きいという単純な期待回報を,期待回報 (つまり衝撃の価格) により掛けます.

img

画像によると,期待収益は最大で約2500回,平均取引量の約2.5倍である.つまり,売り札は2500位に吊るされるべきである.また強調する必要があるのは,横軸が1sを代表する取引量は,単純に深さの位置に等しくならない.そして,これは現在も重要な深さのデータが不足している時に,取引の推測のみに基づいている.

概要

異なる時間間隔の取引量分布は,単一の取引量分布に関する単純なスケーリングであることが判明した.また,価格ショックと取引確率に基づいて,単純な期待収益モデルを作成し,このモデルの結果は私たちの期待に応じた.売る取引量が小さく,価格が下がることを予測する場合は,利潤の余地が必要であり,取引量が大きいほど,中間に最適なサイズがあり,戦略が探すハンディングリストの位置が低い可能性が高い.もちろん,このモデルはあまりにも単純であり,次の記事で,私はさらに詳しく話します.

#1s内的累计分布
df_resampled = buy_trades['quantity'].resample('1S').sum()
df_resampled = df_resampled.to_frame(name='quantity')
df_resampled = df_resampled[df_resampled['quantity']>0]

depths = np.array(range(0, 15000, 10))
mean = df_resampled['quantity'].mean()
alpha = np.log(np.mean(df_resampled['quantity'] > mean))/np.log(2.05)
probabilities_s = np.array([((1+20**(-depth/mean))*depth/mean+1)**(alpha) for depth in depths])
profit_s = np.array([depth/2e6-0.000352 for depth in depths])
plt.figure(figsize=(10, 5))
plt.plot(depths, probabilities_s*profit_s)
plt.xlabel('Q')
plt.ylabel('Excpet profit')
plt.grid(True)

png


もっと

オーク量化 🐂🍺