
В предыдущей статье было дано предварительное введение в методы расчета различных средних цен и дан пересмотр средней цены. В этой статье мы продолжаем углубляться в эту тему.
Данные о потоке ордеров и данные десяти уровней глубины собираются в ходе реальной торговли, а частота обновления составляет 100 мс. Реальный рынок содержит только данные о покупке и продаже, которые обновляются в режиме реального времени. Для простоты он пока не используется. Учитывая, что объем данных слишком велик, сохраняется только 100 000 строк подробных данных, а рыночные условия для каждого уровня также разделяются на отдельные столбцы.
from datetime import date,datetime
import time
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import ast
%matplotlib inline
tick_size = 0.0001
trades = pd.read_csv('YGGUSDT_aggTrade.csv',names=['type','event_time', 'agg_trade_id','symbol', 'price', 'quantity', 'first_trade_id', 'last_trade_id',
'transact_time', 'is_buyer_maker'])
trades = trades.groupby(['transact_time','is_buyer_maker']).agg({
'transact_time':'last',
'agg_trade_id': 'last',
'price': 'first',
'quantity': 'sum',
'first_trade_id': 'first',
'last_trade_id': 'last',
'is_buyer_maker': 'last',
})
trades.index = pd.to_datetime(trades['transact_time'], unit='ms')
trades.index.rename('time', inplace=True)
trades['interval'] = trades['transact_time'] - trades['transact_time'].shift()
depths = pd.read_csv('YGGUSDT_depth.csv',names=['type','event_time', 'transact_time','symbol', 'u1', 'u2', 'u3', 'bids','asks'])
depths = depths.iloc[:100000]
depths['bids'] = depths['bids'].apply(ast.literal_eval).copy()
depths['asks'] = depths['asks'].apply(ast.literal_eval).copy()
def expand_bid(bid_data):
expanded = {}
for j, (price, quantity) in enumerate(bid_data):
expanded[f'bid_{j}_price'] = float(price)
expanded[f'bid_{j}_quantity'] = float(quantity)
return pd.Series(expanded)
def expand_ask(ask_data):
expanded = {}
for j, (price, quantity) in enumerate(ask_data):
expanded[f'ask_{j}_price'] = float(price)
expanded[f'ask_{j}_quantity'] = float(quantity)
return pd.Series(expanded)
# 应用到每一行,得到新的df
expanded_df_bid = depths['bids'].apply(expand_bid)
expanded_df_ask = depths['asks'].apply(expand_ask)
# 在原有df上进行扩展
depths = pd.concat([depths, expanded_df_bid, expanded_df_ask], axis=1)
depths.index = pd.to_datetime(depths['transact_time'], unit='ms')
depths.index.rename('time', inplace=True);
trades = trades[trades['transact_time'] < depths['transact_time'].iloc[-1]]
Давайте сначала посмотрим на распределение этих 20 рыночных условий. Оно соответствует ожиданиям. Чем дальше от открытия рынка, тем больше отложенных ордеров, а ордера на покупку и ордера на продажу примерно симметричны.
bid_mean_list = []
ask_mean_list = []
for i in range(20):
bid_mean_list.append(round(depths[f'bid_{i}_quantity'].mean(),0))
ask_mean_list.append(round(depths[f'ask_{i}_quantity'].mean(),0))
plt.figure(figsize=(10, 5))
plt.plot(bid_mean_list);
plt.plot(ask_mean_list);
plt.grid(True)

Объедините данные о глубине и данные о транзакциях для облегчения оценки точности прогноза. Здесь мы гарантируем, что данные о транзакции будут позже, чем данные о глубине. Не принимая во внимание задержку, мы напрямую вычисляем среднеквадратичную ошибку между прогнозируемым значением и фактической ценой транзакции. Используется для измерения точности прогнозов.
Судя по результатам, ошибка mid_price, среднего значения пары покупка-продажа, самая большая. После изменения на weight_mid_price ошибка сразу становится намного меньше, и она еще больше улучшается путем корректировки взвешенной средней цены. После того, как вчерашняя статья была опубликована, некоторые люди сообщили, что они использовали только I^3⁄2. Я проверил это здесь и обнаружил, что результат был лучше. После размышлений о причине, это должно быть различие в частоте событий. Когда I близко к -1 и 1, это событие с низкой вероятностью. Для того, чтобы исправить эти низкие вероятности, прогнозирование событий с высокой частотой не так точен. Поэтому, чтобы более точно учесть высокочастотные события, я внес некоторые корректировки (это чисто экспериментальные параметры, и они не очень полезны для реальной торговли):

Результат оказался немного лучше. Как упоминалось в предыдущей статье, стратегии должны прогнозироваться с большим количеством данных. С большей глубиной и данными о выполнении заказов улучшение, которое может быть получено за счет запутывания с рыночной ценой, уже очень слабое.
df = pd.merge_asof(trades, depths, on='transact_time', direction='backward')
df['spread'] = round(df['ask_0_price'] - df['bid_0_price'],4)
df['mid_price'] = (df['bid_0_price']+ df['ask_0_price']) / 2
df['I'] = (df['bid_0_quantity'] - df['ask_0_quantity']) / (df['bid_0_quantity'] + df['ask_0_quantity'])
df['weight_mid_price'] = df['mid_price'] + df['spread']*df['I']/2
df['adjust_mid_price'] = df['mid_price'] + df['spread']*(df['I'])*(df['I']**8+1)/4
df['adjust_mid_price_2'] = df['mid_price'] + df['spread']*df['I']*(df['I']**2+1)/4
df['adjust_mid_price_3'] = df['mid_price'] + df['spread']*df['I']**3/2
df['adjust_mid_price_4'] = df['mid_price'] + df['spread']*(df['I']+0.3)*(df['I']**4+0.7)/3.8
print('平均值 mid_price的误差:', ((df['price']-df['mid_price'])**2).sum())
print('挂单量加权 mid_price的误差:', ((df['price']-df['weight_mid_price'])**2).sum())
print('调整后的 mid_price的误差:', ((df['price']-df['adjust_mid_price'])**2).sum())
print('调整后的 mid_price_2的误差:', ((df['price']-df['adjust_mid_price_2'])**2).sum())
print('调整后的 mid_price_3的误差:', ((df['price']-df['adjust_mid_price_3'])**2).sum())
print('调整后的 mid_price_4的误差:', ((df['price']-df['adjust_mid_price_4'])**2).sum())
平均值 mid_price的误差: 0.0048751924999999845
挂单量加权 mid_price的误差: 0.0048373440193987035
调整后的 mid_price的误差: 0.004803654771638586
调整后的 mid_price_2的误差: 0.004808216498329721
调整后的 mid_price_3的误差: 0.004794984755260528
调整后的 mid_price_4的误差: 0.0047909595497071375
Здесь мы используем идею предыдущей статьи, чтобы изучить различные диапазоны значений определенного влияющего параметра и изменения цены сделки, чтобы измерить вклад этого параметра в среднюю цену. Как показано на графике глубины первого уровня, по мере увеличения I цена следующей транзакции с большей вероятностью изменится в положительную сторону, что означает, что I вносит положительный вклад.
Вторая партия была обработана таким же образом, и было обнаружено, что хотя эффект был немного меньше, чем у первой партии, он все равно был существенным. Третий уровень глубины также вносит небольшой вклад, но монотонность гораздо хуже, а более глубокие значения по сути не имеют никакой референтной ценности.
В соответствии с различными уровнями вклада, различные веса присваиваются параметрам дисбаланса трех уровней. Фактическая проверка показывает, что ошибки прогнозирования еще больше уменьшаются для различных методов расчета.
bins = np.linspace(-1, 1, 50)
df['change'] = (df['price'].pct_change().shift(-1))/tick_size
df['I_bins'] = pd.cut(df['I'], bins, labels=bins[1:])
df['I_2'] = (df['bid_1_quantity'] - df['ask_1_quantity']) / (df['bid_1_quantity'] + df['ask_1_quantity'])
df['I_2_bins'] = pd.cut(df['I_2'], bins, labels=bins[1:])
df['I_3'] = (df['bid_2_quantity'] - df['ask_2_quantity']) / (df['bid_2_quantity'] + df['ask_2_quantity'])
df['I_3_bins'] = pd.cut(df['I_3'], bins, labels=bins[1:])
df['I_4'] = (df['bid_3_quantity'] - df['ask_3_quantity']) / (df['bid_3_quantity'] + df['ask_3_quantity'])
df['I_4_bins'] = pd.cut(df['I_4'], bins, labels=bins[1:])
fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(8, 5))
axes[0][0].plot(df.groupby('I_bins')['change'].mean())
axes[0][0].set_title('I')
axes[0][0].grid(True)
axes[0][1].plot(df.groupby('I_2_bins')['change'].mean())
axes[0][1].set_title('I 2')
axes[0][1].grid(True)
axes[1][0].plot(df.groupby('I_3_bins')['change'].mean())
axes[1][0].set_title('I 3')
axes[1][0].grid(True)
axes[1][1].plot(df.groupby('I_4_bins')['change'].mean())
axes[1][1].set_title('I 4')
axes[1][1].grid(True)
plt.tight_layout();

df['adjust_mid_price_4'] = df['mid_price'] + df['spread']*(df['I']+0.3)*(df['I']**4+0.7)/3.8
df['adjust_mid_price_5'] = df['mid_price'] + df['spread']*(0.7*df['I']+0.3*df['I_2'])/2
df['adjust_mid_price_6'] = df['mid_price'] + df['spread']*(0.7*df['I']+0.3*df['I_2'])**3/2
df['adjust_mid_price_7'] = df['mid_price'] + df['spread']*(0.7*df['I']+0.3*df['I_2']+0.3)*((0.7*df['I']+0.3*df['I_2'])**4+0.7)/3.8
df['adjust_mid_price_8'] = df['mid_price'] + df['spread']*(0.7*df['I']+0.2*df['I_2']+0.1*df['I_3']+0.3)*((0.7*df['I']+0.3*df['I_2']+0.1*df['I_3'])**4+0.7)/3.8
print('调整后的 mid_price_4的误差:', ((df['price']-df['adjust_mid_price_4'])**2).sum())
print('调整后的 mid_price_5的误差:', ((df['price']-df['adjust_mid_price_5'])**2).sum())
print('调整后的 mid_price_6的误差:', ((df['price']-df['adjust_mid_price_6'])**2).sum())
print('调整后的 mid_price_7的误差:', ((df['price']-df['adjust_mid_price_7'])**2).sum())
print('调整后的 mid_price_8的误差:', ((df['price']-df['adjust_mid_price_8'])**2).sum())
调整后的 mid_price_4的误差: 0.0047909595497071375
调整后的 mid_price_5的误差: 0.0047884350488318714
调整后的 mid_price_6的误差: 0.0047778319053133735
调整后的 mid_price_7的误差: 0.004773578540592192
调整后的 mid_price_8的误差: 0.004771415189297518
Данные о транзакциях напрямую отражают степень длинных и коротких позиций. В конце концов, это опцион, который подразумевает реальные деньги, а стоимость размещения ордера значительно ниже, и даже есть случаи преднамеренного мошенничества при размещении ордеров. Поэтому при прогнозировании средней цены стратегия должна ориентироваться на данные о транзакциях.
Учитывая форму, определите дисбаланс среднего количества поступивших заказов VI, Vb, Vs, представляющий среднее количество заказов на покупку и на продажу в рамках единичного события соответственно.

Результаты показывают, что количество прибывших товаров за короткий период времени является наиболее значимым для прогнозирования изменения цен. Когда VI находится в диапазоне (0,1-0,9), он отрицательно коррелирует с ценой, но за пределами диапазона он положительно коррелирует с цена. Это говорит о том, что когда рынок не экстремальный, он в основном характеризуется колебаниями, и цены вернутся к среднему значению. Когда возникают экстремальные рыночные условия, такие как большое количество ордеров на покупку, подавляющих ордера на продажу, тренд выйдет из тренда . Даже если мы проигнорируем эти маловероятные ситуации и просто предположим, что тренд и VI удовлетворяют отрицательной линейной зависимости, ошибка прогнозирования средней цены значительно уменьшится. Буква «а» в формуле представляет собой коэффициент.

alpha=0.1
df['avg_buy_interval'] = None
df['avg_sell_interval'] = None
df.loc[df['is_buyer_maker'] == True, 'avg_buy_interval'] = df[df['is_buyer_maker'] == True]['transact_time'].diff().ewm(alpha=alpha).mean()
df.loc[df['is_buyer_maker'] == False, 'avg_sell_interval'] = df[df['is_buyer_maker'] == False]['transact_time'].diff().ewm(alpha=alpha).mean()
df['avg_buy_quantity'] = None
df['avg_sell_quantity'] = None
df.loc[df['is_buyer_maker'] == True, 'avg_buy_quantity'] = df[df['is_buyer_maker'] == True]['quantity'].ewm(alpha=alpha).mean()
df.loc[df['is_buyer_maker'] == False, 'avg_sell_quantity'] = df[df['is_buyer_maker'] == False]['quantity'].ewm(alpha=alpha).mean()
df['avg_buy_quantity'] = df['avg_buy_quantity'].fillna(method='ffill')
df['avg_sell_quantity'] = df['avg_sell_quantity'].fillna(method='ffill')
df['avg_buy_interval'] = df['avg_buy_interval'].fillna(method='ffill')
df['avg_sell_interval'] = df['avg_sell_interval'].fillna(method='ffill')
df['avg_buy_rate'] = 1000 / df['avg_buy_interval']
df['avg_sell_rate'] =1000 / df['avg_sell_interval']
df['avg_buy_volume'] = df['avg_buy_rate']*df['avg_buy_quantity']
df['avg_sell_volume'] = df['avg_sell_rate']*df['avg_sell_quantity']
df['I'] = (df['bid_0_quantity']- df['ask_0_quantity']) / (df['bid_0_quantity'] + df['ask_0_quantity'])
df['OI'] = (df['avg_buy_rate']-df['avg_sell_rate']) / (df['avg_buy_rate'] + df['avg_sell_rate'])
df['QI'] = (df['avg_buy_quantity']-df['avg_sell_quantity']) / (df['avg_buy_quantity'] + df['avg_sell_quantity'])
df['VI'] = (df['avg_buy_volume']-df['avg_sell_volume']) / (df['avg_buy_volume'] + df['avg_sell_volume'])
bins = np.linspace(-1, 1, 50)
df['VI_bins'] = pd.cut(df['VI'], bins, labels=bins[1:])
plt.plot(df.groupby('VI_bins')['change'].mean());
plt.grid(True)

df['adjust_mid_price'] = df['mid_price'] + df['spread']*df['I']/2
df['adjust_mid_price_9'] = df['mid_price'] + df['spread']*(-df['OI'])*2
df['adjust_mid_price_10'] = df['mid_price'] + df['spread']*(-df['VI'])*1.4
print('调整后的mid_price 的误差:', ((df['price']-df['adjust_mid_price'])**2).sum())
print('调整后的mid_price_9 的误差:', ((df['price']-df['adjust_mid_price_9'])**2).sum())
print('调整后的mid_price_10的误差:', ((df['price']-df['adjust_mid_price_10'])**2).sum())
调整后的mid_price 的误差: 0.0048373440193987035
调整后的mid_price_9 的误差: 0.004629586542840461
调整后的mid_price_10的误差: 0.004401790287167206
Учитывая, что и отложенные ордера, и данные о транзакциях полезны для прогнозирования средней цены, эти два параметра можно объединить. Присвоение веса здесь произвольно и не учитывает граничные условия. В крайних случаях прогнозируемая средняя цена может быть Это не между покупкой и продажей, но если погрешность можно уменьшить, эти детали не имеют значения.
Наконец, ошибка прогноза снизилась с первоначальных 0,00487 до 0,0043. Мы не будем вдаваться в подробности. Еще многое предстоит изучить о средней цене. В конце концов, прогнозирование средней цены — это прогнозирование цены. Вы можете попробовать сами .
#注意VI需要延后一个使用
df['price_change'] = np.log(df['price']/df['price'].rolling(40).mean())
df['CI'] = -1.5*df['VI'].shift()+0.7*(0.7*df['I']+0.2*df['I_2']+0.1*df['I_3'])**3 + 150*df['price_change'].shift(1)
df['adjust_mid_price_11'] = df['mid_price'] + df['spread']*(df['CI'])
print('调整后的mid_price_11的误差:', ((df['price']-df['adjust_mid_price_11'])**2).sum())
调整后的mid_price_11的误差: 0.00421125960463469
В этой статье объединены данные глубины и данные транзакций для дальнейшего улучшения метода расчета средней цены. В этой статье представлен метод измерения точности и повышения точности прогнозов изменения цен. В целом, различные параметры не являются очень строгими и приведены только для справки. При более точной средней цене следующим шагом будет фактическое применение средней цены для бэктестинга. В этой части также много контента, поэтому мы на некоторое время прекратим обновления.