Pensamiento sobre estrategias de trading de alta frecuencia (2)

El autor:Las hierbas, Creado: 2023-08-04 16:14:27, Actualizado: 2023-09-19 09:08:17

img

El artículo se centra en la estrategia de operaciones de alta frecuencia, con un enfoque en el modelado acumulativo de la transacción y el impacto de los precios. El artículo propone un modelo preliminar de la posición óptima suspendida mediante el análisis de transacciones individuales, los impactos de precios de intervalos fijos y el efecto de las transacciones en los precios.

Modelado de tráfico acumulado

El artículo anterior muestra una expresión de probabilidad de que una transacción sea mayor que un determinado valor:

img

También nos preocupa la distribución del volumen de transacciones durante un período de tiempo, que intuitivamente debería estar relacionada con el volumen de transacciones y la frecuencia de pedidos por transacción. A continuación, procesamos los datos en intervalos fijos.

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']

Si se combinan las transacciones por 1s en cada intervalo, se elimina la parte que no se realizó y se ajusta a la distribución de transacciones individuales de arriba, el resultado es mejor, se considera que todas las transacciones dentro de 1s se consideran como una sola transacción, el problema se convierte en un problema resuelto. Pero cuando el ciclo se extiende (relativamente a la frecuencia de transacciones), se encuentra que el error aumenta, y el estudio encontró que este error es causado por la corrección de la distribución de Pareto anterior.

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
el nombre del operador. precio cantidad el nombre del operador el número de transacción es_comprador_fabricante fecha tiempo de la transacción el intervalo Diferencia
2023-01-27 00:00:00.161 1138369 2.901 54.3 3806199 3806201 No es cierto. 2023-01-27 00:00:00.161 1674777600161 No 0.001
2023-01-27 00:00:04.140 1138370 2.901 291.3 3806202 3806203 No es cierto. 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 No es cierto. 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 No es cierto. 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 No es cierto. 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 No es cierto. 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 No es cierto. 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 No es cierto. 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 No es cierto. 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 No es cierto. 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

Ahora, para la distribución de transacciones acumuladas en diferentes tiempos, se resume una fórmula general, que se adapta a la distribución de transacciones individuales, sin tener que hacer estadísticas separadas cada vez.

img

Donde avg_interval indica el intervalo medio de una transacción, y avg_interval_T indica el intervalo medio del intervalo que se necesita estimar, es decir, un poco de vuelta. Si queremos estimar el intervalo de 1s, necesitamos contar el intervalo medio de los eventos que se incluyen en el intervalo de 1s. Si la probabilidad de llegada de la orden se ajusta a la distribución de Parsons, aquí se debe estimar directamente, pero el desvío real es muy grande, no se explica aquí.

Tenga en cuenta que la probabilidad de que el volumen de transacciones sea mayor que el valor específico en un intervalo de tiempo aquí y la probabilidad de transacciones en la ubicación real en la profundidad deben diferir mucho, ya que cuanto más tiempo se espera, mayor es la probabilidad de que el libro de pedidos cambie, y las transacciones también causan cambios en la profundidad, por lo que la probabilidad de transacciones en la misma ubicación de profundidad cambia en tiempo real a medida que se actualizan los datos.

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

Los precios de las transacciones de billetes se han disparado

Los datos de transacciones son un tesoro, y hay muchos datos para extraer. Debemos estar muy atentos al impacto que los pedidos producen en el precio, lo que afecta la posición de la estrategia de lista. También se calcula la diferencia entre el último precio y el primer precio según los datos agregados de transact_time, si solo hay un pedido, la diferencia es 0.

Los resultados muestran que el porcentaje de ticks sin causar impactos es de hasta un 77%, el porcentaje de ticks 1 es del 16.5%, los ticks 2 son del 3.7%, los ticks 3 son del 1.2%, y los ticks 4 o más son de menos del 1%.

El volumen de transacciones causado por el diferencial correspondiente, eliminando la falta de verdad de un impacto demasiado grande, se ajusta básicamente a una relación lineal, con una fluctuación de precios de aproximadamente 1 tick por cada 1000 volúmenes. También se puede entender que la cantidad media de ticks en el precio cercano a cada plato es de aproximadamente 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

El impacto de los precios a intervalos fijos

La diferencia es que aquí habrá un impacto negativo, por supuesto, ya que aquí solo se estadísticas de pagos, la posición simétrica será mayor de una tick. Continuando observando la relación entre el volumen de transacciones y el impacto, solo se obtendrán resultados estadísticos mayores de 0, la conclusión y el orden individual son similares, también una relación lineal aproximada, cada tick requiere aproximadamente una cantidad de 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

El impacto en el precio de las transacciones

El anterior busca el volumen de transacción necesario para un cambio de tick, pero no es preciso, ya que se basa en el supuesto de que el impacto ya ha ocurrido; ahora, a su vez, se observa el impacto de precio que trae el cambio de tick.

Aquí los datos se muestran por 1s, con un paso por cada 100 cantidades, y se estadísticas el movimiento de los precios dentro de este rango de cantidades.

  1. Cuando el volumen de compra es inferior a 500, el cambio de precio esperado es bajista, lo que es conforme a lo esperado, después de todo, también hay pedidos de venta que afectan el precio.
  2. Cuando el volumen de transacciones es bajo, corresponde a una relación lineal, es decir, el mayor volumen de transacciones, el mayor aumento de precios.
  3. La mayor cantidad de transacciones, mayor es la variación del precio, que a menudo representa una ruptura en el precio, que puede regresar después de la ruptura, junto con el muestreo a intervalos fijos, lo que crea inestabilidad en los datos.
  4. Se debe prestar atención a la parte superior del gráfico de puntos dispersos, es decir, la parte en la que el volumen de transacciones corresponde a la subida de precios.
  5. Para este par de transacciones, si se da una relación en bruto entre el volumen de transacciones y el cambio de precio:

img

En este caso, el valor C representa el cambio en el precio y el valor Q representa el volumen de compra y venta.

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)
cantidad_grupo precio_diferencia cantidad_grupo_centro
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

Posiciones preferentes iniciales

Con el modelo de transacciones completas y el modelo de transacciones correspondientes a los impactos de precios, parece que se puede calcular la posición óptima de la lista suspendida.

  1. Supongamos que el precio regresa a su valor original después del impacto (esto, por supuesto, es poco probable y requiere un análisis de los cambios en el precio después del impacto)
  2. Supongamos que la distribución del volumen de transacciones y la frecuencia de pedidos durante este período se ajusta a lo previsto (esto también es inexacto, ya que aquí se estima con el valor de un día, y las transacciones tienen un fenómeno de acumulación evidente).
  3. Supongamos que solo hay una venta en el tiempo de simulación y luego se estabiliza.
  4. Supongamos que después de la transacción de la orden, hay otros pagos que continúan aumentando los precios, especialmente cuando la cantidad es muy baja.

Primero escriba un rendimiento esperado simple, es decir, la probabilidad de que el pago acumulado sea mayor que Q en 1s, multiplicado por el rendimiento esperado (es decir, el precio del impacto):

img

De acuerdo con la imagen, la ganancia esperada es máxima alrededor de 2500, aproximadamente 2.5 veces el volumen promedio de transacciones. Es decir, los pedidos de venta deben colgar en la posición de 2500.

Resumen

Descubrimos que la distribución de entradas en diferentes intervalos de tiempo es una simple escalada de la distribución de entradas en una sola transacción. También se hizo un modelo de rendimiento esperado simple basado en el impacto del precio y la probabilidad de transacción, el resultado de este modelo se ajusta a nuestras expectativas, si la cantidad de entradas vendidas es pequeña, lo que predice una caída de los precios, se necesita un cierto volumen para obtener un margen de beneficio, mientras que el mayor volumen de transacciones, la probabilidad es menor, hay un tamaño óptimo en el medio, y también la estrategia de búsqueda de la posición suspendida.

#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


Más.

Cuantificación de orcs 🐂🍺