Pensamento sobre estratégias de negociação de alta frequência (2)

Autora:Ervas daninhas, Criado: 2023-08-04 16:14:27, Atualizado: 2023-09-19 09:08:17

img

O artigo aborda principalmente estratégias de negociação de alta frequência, com foco em modelagem cumulativa do volume de transações e choques de preços. O artigo propõe um modelo inicial de posições de negociação preferenciais pendentes, com base em uma compreensão do volume de transações e dos choques de preços, para tentar encontrar as posições de negociação mais adequadas.

Modelagem de tráfego acumulado

O artigo anterior mostra a expressão de probabilidade de uma transação única ser maior do que um determinado valor:

img

Também estamos preocupados com a distribuição do volume de transações durante um período de tempo, que deve ser intuitivamente relacionada ao volume de transações e à frequência de pedidos por transação.

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

Combinar transações individuais em cada 1s de intervalo para o volume de transações, remover as partes não transacionadas e combinar com a distribuição de transações individuais acima, o resultado é melhor, considerar todas as transações dentro de 1s como uma única transação, o problema se torna um problema resolvido. Mas quando o ciclo é alargado (relativamente à frequência de transações), o erro aumenta, e o estudo descobriu que esse erro é causado pela modificação do distribuição 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
agg_trade_id Preço Quantidade primeiro_trade_id último_trade_id é_comprador_fabricante data transact_time (tempo da transacção) intervalo diferença
2023-01-27 00:00:00.161 1138369 2.901 54.3 3806199 3806201 Falso 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 Falso 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 Falso 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 Falso 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 Falso 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 Falso 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 Falso 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 Falso 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 Falso 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 Falso 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

Agora, resuma uma fórmula geral para a distribuição de transações acumuladas em diferentes períodos de tempo, ajustando-a com a distribuição de transações individuais, sem usar estatísticas separadas de cada vez.

img

O intervalo avg_interval_T indica o intervalo médio de transações individuais e o intervalo médio de intervalos a estimar, o que é um pouco desviado. Se quisermos estimar o intervalo de 1s, é necessário estudar o intervalo médio de eventos de transações dentro de 1s. Se a probabilidade de chegada de um pedido estiver de acordo com a distribuição de Parsons, aqui deve ser possível estimar diretamente, mas o desvio real é grande, não será explicado aqui.

Observe que a probabilidade de uma transação em um intervalo de tempo maior do que um determinado valor e a probabilidade de uma transação em um local na profundidade real devem diferir muito, porque quanto mais tempo de espera, maior a probabilidade de mudanças no livro de pedidos, e as transações também levam a mudanças na profundidade, portanto, a probabilidade de uma transação na mesma profundidade varia em tempo real com a atualização dos dados.

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

Preço de transação único

Os dados de transações são um tesouro, e há muitos dados para serem extraídos. Devemos estar muito atentos ao impacto dos pedidos sobre o preço, o que afeta o posicionamento da listagem da estratégia. Também com base nos dados agregados do transact_time, calcula-se a diferença entre o último preço e o primeiro preço, se houver apenas um pedido, a diferença é zero.

Os resultados mostram que a proporção de não-impactos é de até 77%, a proporção de 1 tick é de 16,5%, 2 ticks são de 3,7%, 3 ticks são de 1,2%, e mais de 4 ticks são de menos de 1%.

A quantidade de transações causando o diferencial correspondente é estatisticamente calculada, eliminando a distorção de impacto muito grande, basicamente de acordo com a relação linear, com cerca de 1 tick de variação de preço por cada 1000 volumes. Também pode ser entendido como uma quantidade média de pendências de cerca de 1000 volumes de preço por prato.

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

Choque de preços a intervalos fixos

A diferença aqui é que haverá um impacto negativo no preço dentro dos 2s. É claro que, como aqui só são estatísticas de pagamentos, a posição simétrica é maior que um tick. Continuando a observar a relação entre volume de transações e impacto, somente resultados estatísticos maiores que 0, conclusões e ordens individuais são quase idênticas, e também uma relação linear aproximada, cada tick requer cerca de 2000 quantidades.

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

Choque de preços no volume de transações

A primeira busca o volume de transação necessário para uma mudança de tick, mas não é precisa, pois é baseada no caso de assumir que o choque já ocorreu. Agora, por sua vez, olha para o choque de preço causado pelo volume de transação.

Aqui, os dados são amostrados por 1s, um passo por cada 100 unidades, e a variação de preços é estatística dentro desse intervalo.

  1. Quando o volume de transações de compra é inferior a 500, a mudança de preço esperada é para baixo, o que está de acordo com as expectativas, depois de tudo, também os pedidos de venda estão afetando o preço.
  2. Quando o volume de transações é baixo, corresponde a uma relação linear, ou seja, quanto maior o volume de transações, maior o aumento de preços.
  3. A maior quantidade de transações, a maior variação de preços, muitas vezes representando um rompimento de preços, que pode retornar após o rompimento, juntamente com a amostragem de intervalos fixos, causando instabilidade nos dados.
  4. O que deve ser observado é a parte superior do gráfico, onde o volume de transações corresponde ao aumento do preço.
  5. Para este único par de transações, dê uma relação entre o volume de transações e a variação de preços em uma versão básica:

img

Em seguida, o ponto C representa a mudança de preço e o ponto Q representa o volume de transações.

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)
quantidade_grupo preço_diferença quantidade_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

Posições iniciais de preferência

Com um modelo grosseiro de transações completas e transações correspondentes a choques de preços, parece ser possível calcular a posição ideal para a suspensão.

  1. Suponha que o preço retorne ao valor original após o choque (isso é, obviamente, improvável e requer uma reanálise das mudanças de preço após o choque)
  2. Suponha que a distribuição do volume de transações e da frequência de pedidos durante esse período seja conforme o previsto (isto também não é preciso, pois aqui a estimativa é feita com o valor de um dia, e as transações têm um fenômeno de aglomeração evidente).
  3. Suponha que só ocorra uma venda no tempo de simulação, e então o equilíbrio.
  4. Suponha que, após a realização do pedido, outros pagamentos continuem a elevar o preço, especialmente quando a quantidade é muito baixa.

Primeiro, escreva um simples retorno esperado, ou seja, a probabilidade de pagamento acumulado maior que Q em 1s, multiplicado pelo retorno esperado (ou seja, o preço do impacto):

img

De acordo com a imagem, a expectativa de ganho é maior em cerca de 2500, cerca de 2,5 vezes o volume médio de transações. Ou seja, os pedidos de venda devem ser pendurados na posição 2500. É necessário enfatizar novamente que o volume de transações no eixo transversal representa 1s, não pode ser simplesmente igual à posição de profundidade.

Resumo

Descobrimos que a distribuição de transações em diferentes intervalos de tempo é uma simples escalada da distribuição de transações individuais. Também, com base no impacto de preços e na probabilidade de transações, fizemos um modelo de ganhos esperados simples, cujo resultado está de acordo com nossas expectativas. Se o volume de transações vendidas for pequeno, o que indica uma queda de preços, um certo volume é necessário para o espaço de lucro, e quanto maior o volume de transações, menor a probabilidade, há um tamanho ideal no meio, também a posição estratégica de busca.

#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


Mais.

Quantificação de óculos 🐂🍺