В статье рассматриваются высокочастотные торговые стратегии, основное внимание уделяется моделированию накопленного объема сделок и ценовым шокам. Статья предлагает предварительную модель оптимальной односторонней позиции путем анализа единичных сделок, ценовых шоков с фиксированными интервалами и влияния сделок на цены.
В предыдущей статье высказывалось выражение вероятности того, что одна сделка будет совершена больше, чем определенное значение:
Мы также интересуемся распределением объемов сделок за определенное время, которое интуитивно должно быть связано с количеством сделок и частотой заказов на каждое действие.
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 как единые, то эта проблема станет решенной. Однако, когда цикл увеличивается (относительно частоты сделок), ошибка увеличивается, а исследование обнаружило, что эта ошибка вызвана предыдущей поправкой к распределению Парето.
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 | это_покупатель_производитель | дата | время транзакции | интервал | Дифференциация | |
---|---|---|---|---|---|---|---|---|---|---|
2023-01-27 00:00:00.161 | 1138369 | 2.901 | 54.3 | 3806199 | 3806201 | Неправда | 2023-01-27 00:00:00.161 | 1674777600161 | НаН | 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)
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)
Теперь для распределения различных временных накопленных объемов сделок можно обобщить универсальную формулу, чтобы привести ее в соответствие с распределением сделок в одиночку, без использования отдельной статистики каждый раз.
где avg_interval обозначает средний интервал сделок, а 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)
Данные о сделках - это сокровище, и есть много данных, которые можно извлечь. Мы должны быть очень внимательны к влиянию заказов на цены, которые влияют на стратегию размещения. Также, исходя из данных, собранных с помощью транзакции_time, вычисляется разница между последней ценой и первой ценой, если есть только один заказ, разница равна нулю.
Результаты показывают, что доля не вызвавших удар достигает 77%, доля одного тика - 16,5%, 2 тика - 3,7%, 3 тика - 1,2% и более 4 тиков - менее 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);
Статистика 2s, отличающаяся тем, что здесь будет отрицательное значение, конечно, поскольку здесь только статистика оплаты, симметричная позиция будет больше одного тика. Продолжаем наблюдать отношения в объеме сделок и удар, только результаты статистики больше 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));
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);
Предыдущий запрос требует количества сделок, требуемых для изменения тика, но не точный, поскольку основан на предположении, что удар уже произошел. Теперь, в свою очередь, смотрим на ценовые потрясения, вызванные изменением сделок.
Здесь данные проанализированы по 1s, на 100 штук в длину одного шага, и в этом диапазоне оценивается изменение цены. Приведены некоторые более ценные выводы:
Среди них,
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)
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 |
С помощью моделирования объемов сделок и грубой модели, в которой объемы сделок соответствуют ценовым воздействиям, кажется, что можно вычислить оптимальные позиции подвешенного списка. Может быть, стоит сделать несколько предположений, прежде чем дать безответственную оптимальную ценовую позицию.
Начнем с простого ожидаемого дохода, то есть вероятность того, что суммарный платеж в течение 1s будет больше Q, умноженный на ожидаемый доход (т.е. на цену удара):
Согласно изображению, ожидаемая прибыль максимальна примерно около 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)
Ок количественный 🐂🍺