Denken Sie über die Strategie des Hochfrequenz-Handels nach.

Schriftsteller:Das Gras, Erstellt: 2023-08-04 16:14:27, Aktualisiert: 2023-09-19 09:08:17

img

Der Artikel beschäftigt sich hauptsächlich mit Hochfrequenz-Handelsstrategien, wobei der Schwerpunkt auf kumulativer Transaktionsmodellierung und Preis-Schocks liegt. Durch die Analyse von Einzeltransaktionen, Festintervall-Preis-Schocks und der Auswirkungen von Transaktionen auf Preise wird ein vorläufiges Optimums-Hängestellungsmodell vorgeschlagen.

Modellierung der kumulierten Transaktionen

Der vorherige Artikel ergab die Wahrscheinlichkeitsformel für eine Transaktion, bei der die Zahl der Transaktionen größer ist als ein bestimmter Wert:

img

Wir interessieren uns auch für die Verteilung der Transaktionen über einen bestimmten Zeitraum, die intuitiv mit der Anzahl der Transaktionen und der Häufigkeit der Bestellungen verknüpft sein sollte.

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

Wenn man einzelne Transaktionen in 1s-Intervallen zu einer Anzahl von Transaktionen zusammenschließt, die nicht getätigten Transaktionen entfernt und die Transaktionsverteilung der einzelnen Transaktionen anhand der oben genannten Transaktionsverteilung anpasst, wird das Problem zu einem gelösten Problem. Aber wenn der Zyklus verlängert wird (im Verhältnis zur Transaktionsfrequenz), wird der Fehler erhöht, der durch eine vorherige Pareto-Verteilungsanpassung verursacht wird.

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 Preis Anzahl erste_Handel_id Last_trade_id ist_Käufer_Hersteller Datum Transaktionszeit Abstand Unterschied
2023-01-27 00:00:00.161 1138369 2.901 54.3 3806199 3806201 Nicht zutreffend 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 Nicht zutreffend 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 Nicht zutreffend 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 Nicht zutreffend 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 Nicht zutreffend 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 Nicht zutreffend 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 Nicht zutreffend 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 Nicht zutreffend 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 Nicht zutreffend 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 Nicht zutreffend 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

Nun wird eine allgemeine Formel für die Verteilung von Transaktionen zusammengefasst, die sich auf verschiedene zeitlich kumulierte Transaktionen beziehen, und mit der Verteilung der Transaktionen zusammengestellt, ohne jedes Mal eine getrennte Statistik zu verwenden.

img

Dabei ist der durchschnittliche Abstand zwischen den Transaktionen der einzelnen Transaktionen angegeben, der durchschnittliche Abstand zwischen den Intervallen, die geschätzt werden müssen, angegeben. Wenn wir die Transaktionen von 1s abschätzen wollen, müssen wir die durchschnittlichen Ereignisintervalle der Transaktionen, die in den 1s enthalten sind, abschätzen.

Beachten Sie, dass die Wahrscheinlichkeit, dass hier ein Handel in einem bestimmten Intervall größer ist als ein bestimmter Wert, und die Wahrscheinlichkeit, dass ein Handel in der tatsächlichen Lage in der Tiefe stattfindet, größer sein sollten, da die längere Wartezeit die Wahrscheinlichkeit für eine Änderung des Auftragsbuchs erhöht, und dass die Transaktionen auch zu einer Veränderung der Tiefe führen, so dass die Wahrscheinlichkeit für Transaktionen in derselben Tiefe in Echtzeit ändert sich, wenn die Daten aktualisiert werden.

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

Einzigartige Transaktionspreise

Die Transaktionsdaten sind ein Schatz, und es gibt noch viele Daten, die für die Ausgrabung verfügbar sind. Wir sollten sehr auf die Auswirkungen der Bestellungen auf den Preis achten, die sich auf die Auflistungsposition der Strategie auswirken. Auch auf der Grundlage der Aggregation von Transact_time-Daten wird die Differenz zwischen dem letzten Preis und dem ersten Preis berechnet.

Das Ergebnis zeigt, dass der Anteil der nicht verursachten Stoßfälle bei 77%, bei einem Tic 16,5%, bei zwei, bei 3,7%, bei drei, bei 1,2 und bei vier oder mehr Tick weniger als 1% beträgt.

Die Transaktionsmenge, die den entsprechenden Preisdifferenz verursacht, wird statistisch berechnet, um die zu große Verfälschung von Schocks zu beseitigen, und entspricht grundsätzlich einer linearen Beziehung, bei der ungefähr 1 Ticks für jede Menge von 1000 Preisschwankungen entstehen.

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

Preis-Schocks mit festen Abständen

Der Preisschlag innerhalb der Statistik 2s unterscheidet sich darin, dass hier ein Negativwert besteht, natürlich weil hier nur Zahlungen statistisch erfasst werden, ist die symmetrische Position ein Ticker größer.

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

Der Preisschlag der Transaktionen

Die vorherige Suche nach einem Tickschlag ist nicht genau, da sie auf der Annahme basiert, dass ein Schock bereits stattgefunden hat.

Hier werden die Daten nach 1s, also nach einem Schritt pro 100 Stück, gesammelt und die Preisänderungen in diesem Bereich berechnet.

  1. Wenn die Transaktionen unter 500 liegen, ist die erwartete Preisänderung eine Abnahme, was der Erwartung entspricht, denn auch die Verkaufsbestellung beeinflusst den Preis.
  2. Bei niedrigeren Transaktionen entspricht dies einer linearen Beziehung, d.h. je größer die Transaktionen sind, desto höher ist der Preisanstieg.
  3. Je größer die Transaktionsmenge, desto größer ist der Preiswandel, was oft einen Preisbruch darstellt, der nach dem Durchbruch zurückkehren kann, was zusammen mit einer festen Intervallsamplung zu einer Datenunsicherheit führt.
  4. Man sollte sich auf den oberen Teil des Scatterdiagramms konzentrieren, also auf den Teil, in dem die Transaktionen den Preissteigerungen entsprechen.
  5. Geben Sie einen groben Überblick über die Beziehung zwischen der Transaktionsmenge und den Preisänderungen für dieses Transaktionspaar:

img

Bei den C-Tippen handelt es sich um die Preisänderung, bei den Q-Tippen um die Transaktionsmenge.

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)
Menge_Gruppe Preisdifferenz Anzahl_Gruppe_Zentrum
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

Erstmaliges Vorzugsplatz

Mit einer Modellierung der Transaktionen und einem groben Modell, in dem die Transaktionen den Preisschlag entsprechen, scheint es möglich zu sein, die optimale Aufschlagsposition zu berechnen.

  1. Angenommen, der Preis kehrt nach dem Schock zu seinem ursprünglichen Wert zurück (das ist natürlich unwahrscheinlich und erfordert eine erneute Analyse der Preisänderungen nach dem Schock)
  2. Angenommen, die Verteilung der Transaktionen und der Orderfrequenz in dieser Zeit entspricht der Prognose (dies ist auch nicht korrekt, da hier der Wert eines Tages geschätzt wird, während die Transaktionen eine deutliche Ansammlung haben).
  3. Nehmen wir an, dass nur eine Verkaufsanfrage in der Simulationszeit auftritt, und dann wird ein Ausgleich eingelegt.
  4. Angenommen, nach der Auftragserteilung gibt es andere Zahlungen, die den Preis weiter erhöhen, besonders wenn die Menge sehr niedrig ist, wird dieser Effekt hier ignoriert und einfach angenommen, dass es zurückkehrt.

Schreiben Sie zunächst eine einfache Erwartungsrendite, also die Wahrscheinlichkeit, dass die kumulierte Zahlung in 1s größer ist als Q, multipliziert mit der erwarteten Rendite (d. h. dem Preis des Stoßes):

img

Gemäß dem Bild ist die Erwartung einer Ertragsgröße von ungefähr 2500 und ungefähr 2,5 mal der durchschnittlichen Transaktion. Das heißt, die Verkaufs- und Verkaufsoption sollte in der Position 2500 hängen. Es muss erneut betont werden, dass die Transaktionsgröße innerhalb der Querrichtung 1s nicht einfach gleichbedeutend mit der Tiefe ist.

Zusammenfassung

Es wurde festgestellt, dass die Handelsdurchschnittsverteilung eine einfache Vergrößerung der Handelsdurchschnittsverteilung ist. Es wurde auch ein einfaches Ertragsmodell für die Erwartungen nach Preisschlag und Handelswahrscheinlichkeit erstellt, das unseren Erwartungen entspricht. Wenn die Verkaufsdurchschnittszahl klein ist und ein Preisrückgang voraussagt, ist ein gewisses Maß erforderlich, um einen Gewinnraum zu erzielen.

#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


Mehr

Ock-Quantität 🐂🍺