J'ai publié un moteur de backtesting dans l'article
Tout d'abord, qu'est-ce que la ligne K historique? Une ligne K comprend quatre prix: le prix le plus élevé, le prix d'ouverture, le prix le plus bas et le prix de clôture, l'heure de début, l'heure de fin et la quantité d'intervalle de négociation. La plupart des plates-formes quantitatives et des cadres sont testés en arrière-plan sur la base de la ligne K, et la plate-forme FMZ Quant fournit également un backtesting de niveau Tick. La vitesse du backtesting de la ligne K est très rapide, et ce n'est pas un problème dans la plupart des cas, mais il y a aussi des défauts très graves, en particulier la stratégie multi-variété et la stratégie à haute fréquence du backtesting, qui ne peuvent guère tirer de bonnes conclusions.
Tout d'abord, il s'agit d'une question de temps. Le temps du prix le plus élevé et le plus bas des données de la ligne K n'est pas donné, il n'est donc pas nécessaire de considérer, mais les prix d'ouverture et de fermeture les plus importants ne sont pas les temps d'ouverture et de fermeture des positions. Même si les variétés de trading sont impopulaires, elles ne sont souvent pas négociées pendant plus de dix secondes. Lorsque nous backtestons plusieurs variétés de stratégies, nous préférons souvent que leurs prix d'ouverture et de fermeture soient les mêmes, ce qui est également la base du backtesting de prix de fermeture.
Imaginez utiliser la ligne minute pour vérifier l'arbitrage de deux variétés. La différence entre elles est généralement de 10 yuans. Maintenant, on constate qu'à 10:01, le prix de clôture du contrat A est de 100 yuans, le prix de clôture du contrat B est de 112 yuans et la différence est de 12 yuans. Ainsi, la stratégie commence à se couvrir. À un certain moment, la différence revient et la stratégie gagne 2 yuans de bénéfices de retour.
Cependant, la situation réelle peut se produire à 10h45, le contrat A a généré une transaction de 100 yuans, puis il n'y a pas eu de transaction. Le contrat B a généré une transaction de 112 yuans à 10h58. À 10h01, les deux prix n'existaient pas. Quel était le prix d'ouverture à ce moment-là? Et quelle différence la couverture pourrait-elle obtenir? Nous ne savons pas. Une situation possible est que à 10h58, la tendance d'achat et de vente d'un contrat A est de 101,9-102,1, et il n'y a pas de spread de 2 yuans du tout, ce qui induira grandement en erreur notre optimisation de la stratégie.
La seconde est le matchmaking. Le matchmaking réel est le prix et le temps d'abord. Si l'acheteur dépasse le prix de vente, il / elle conclut généralement la transaction au prix de vente, sinon, il / elle entrera dans le carnet de commandes et attend.
Le dernier est l'impact de la transaction de la stratégie elle-même sur le marché. S'il s'agit d'un petit backtest de fonds, l'impact sera faible. Cependant, si la quantité de négociation représente une grande proportion, elle aura un impact sur le marché. Non seulement le point de glissement de prix sera grand lorsque la transaction est complétée immédiatement, mais si votre ordre d'achat est complété dans le backtest, il prévient en fait la transaction d'autres traders originaux qui veulent acheter, ce qui aura un effet papillon impact sur le marché. Cependant, cet impact ne peut pas être quantifié, et il ne peut être dit que par expérience que le trading à haute fréquence ne peut accueillir que de petits fonds.
FMZ fournit le backtesting de niveau de bot réel, qui peut obtenir la profondeur historique réelle de 20 niveaux, la tique de seconde en temps réel, transaction par transaction et d'autres données, et sur cette base, il a fait la fonction de lecture de bot réel (https://www.fmz.com/m/databaseCe type de mesure de backtest a une grande quantité de données et une vitesse lente, qui ne peut être utilisée que pendant deux jours. Pour les stratégies qui sont relativement fréquentes ou nécessitent un jugement strict sur le temps, le vrai backtesting bot est nécessaire. Les paires de trading et le temps collectés par FMZ ne sont pas trop longs, mais il y a plus de 70 milliards de données historiques. Le mécanisme de correspondance actuel est que si l'ordre d'achat est supérieur à l'ordre de vente, il sera complètement correspondu immédiatement sans regarder la quantité, et si l'ordre d'achat est inférieur à l'ordre de vente, il entrera dans la file d'attente de correspondance. Ce mécanisme de backtesting résout les deux premiers problèmes du backtesting K-line, mais il ne peut toujours pas résoudre le dernier problème. Et parce que la quantité de données est trop grande, le backtest et la vitesse sont limités.
Il y a trop peu d'informations sur la K-Line, et la profondeur peut également être fausse. Cependant, un type de données est l'intention de transaction réelle du marché, reflétant l'historique de transaction le plus réel - c'est-à-dire transaction par transaction.
J'ai téléchargé la transaction par transaction des 5 derniers jours
[['XTZ', 1590981301905, 2.905, 0.4, 'False\n'],
['XTZ', 1590981303044, 2.903, 3.6, 'True\n'],
['XTZ', 1590981303309, 2.903, 3.7, 'True\n'],
['XTZ', 1590981303738, 2.903, 238.1, 'True\n'],
['XTZ', 1590981303892, 2.904, 0.1, 'False\n'],
['XTZ', 1590981305250, 2.904, 0.1, 'False\n'],
['XTZ', 1590981305643, 2.903, 197.3, 'True\n'],
Les données sont une liste bidimensionnelle, triée par temps de transaction. Les significations spécifiques sont: nom de l'espèce, prix de transaction, horodatage de transaction, quantité de transaction et si l'ordre de vente est activement exécuté. Il y a à la fois achat et vente. Chaque transaction comprend l'acheteur et le vendeur. Si l'acheteur est un fabricant de marché et le vendeur est un preneur de transaction actif, les dernières données seront vraies.
Tout d'abord, selon la direction de la transaction, nous pouvons spéculer sur l'achat et la vente sur le marché avec précision. S'il s'agit d'un ordre de vente actif, le prix d'achat à ce moment-là est le prix de la transaction. S'il s'agit d'un ordre d'achat actif, le prix de vente est le prix de la transaction. S'il y a une nouvelle transaction, nous mettrons à jour la nouvelle position d'ouverture. S'il n'est pas mis à jour, le dernier résultat sera conservé. Il est facile de lancer le dernier moment des données ci-dessus. Le prix d'achat est de 2.903 et le prix de vente est de 2.904.
Selon le flux d'ordres, il peut être apparié de la manière suivante: prenez un ordre d'achat à titre d'exemple, le prix est le prix, et la quantité de l'ordre est le montant. À ce moment, acheter un et vendre un de la position d'ouverture sont respectivement soumission et demande. Si le prix est inférieur à la demande et supérieur à l'offre, il sera jugé comme le fabricant d'abord, et la priorité peut être donnée au matchmaking. Ensuite, toutes les transactions dont le prix de transaction est inférieur ou égal au prix au cours de la durée de vie de l'ordre seront appariées à cet ordre (si le prix est inférieur ou égal à l'offre, la priorité ne peut pas être donnée à la transaction, et les ordres dont le prix de transaction est inférieur au prix seront appariés à cet ordre). Le prix de correspondance est l'existence du prix, et la quantité de transaction est la quantité de transaction par transaction, jusqu'à ce que l'ordre soit complètement fermé ou ann
Il est facile de voir le problème de ce matchmaking. Si l'ordre est un preneur, la situation réelle est que la transaction peut être effectuée immédiatement, plutôt que d'attendre qu'un nouvel ordre l'accompagne. Tout d'abord, nous n'avons pas pris en compte le nombre d'ordres répertoriés sur le marché. Même s'il y avait des données, le jugement direct de la transaction a changé la profondeur et a affecté le marché. Le matchmaking basé sur de nouveaux ordres équivaut à remplacer vos ordres par les ordres réels dans l'histoire, qui ne dépassera en aucun cas la limite de quantité de transaction du marché lui-même, et le profit final ne peut pas dépasser le profit maximal généré par le marché. Une partie du mécanisme de matchmaking affecte également la quantité de transactions des ordres, affectant ainsi les rendements de la stratégie, ce qui reflète quantitativement la stratégie.
Il y a aussi quelques petits détails. Si le prix d'achat d'un ordre est égal au prix d'achat, il y a encore une certaine probabilité que l'ordre sera assorti au prix d'achat. La priorité de l'ordre et la probabilité de transaction doivent être considérées, ce qui est plus complexe et ne sera pas considéré ici.
Les objets d'échange peuvent se référer à l'introduction au début, essentiellement inchangée. Seule la différence entre les commissions de fabricant et de preneur est ajoutée, et la vitesse de backtesting est optimisée.
symbol = 'XTZ'
loop_time = 0
intervel = 1000 #The sleep time of the strategy is 1000ms
init_price = data[0][2] #Initial price
e = Exchange([symbol],initial_balance=1000000,maker_fee=maker_fee,taker_fee=taker_fee,log='') #Initialize the exchange
depth = {'ask':data[0][2], 'bid':data[0][2]} #depth
order = {'buy':{'price':0,'amount':0,'maker':False,'priority':False,'id':0},
'sell':{'price':0,'amount':0,'maker':False,'priority':False,'id':0}} #Order
for tick in data:
price = int(tick[2]/tick_sizes[symbol])*tick_sizes[symbol] #Transaction price
trade_amount = tick[3] #Number of transactions
time_stamp = tick[1] #Transaction timestamp
if tick[4] == 'False\n':
depth['ask'] = price
else:
depth['bid'] = price
if depth['bid'] < order['buy']['price']:
order['buy']['priority'] = True
if depth['ask'] > order['sell']['price']:
order['sell']['priority'] = True
if price > order['buy']['price']:
order['buy']['maker'] = True
if price < order['sell']['price']:
order['sell']['maker'] = True
#Order network delay can also be used as one of the matching conditions, which is not considered here
cond1 = order['buy']['priority'] and order['buy']['price'] >= price and order['buy']['amount'] > 0
cond2 = not order['buy']['priority'] and order['buy']['price'] > price and order['buy']['amount'] > 0
cond3 = order['sell']['priority'] and order['sell']['price'] <= price and order['sell']['amount'] > 0
cond4 = not order['sell']['priority'] and order['sell']['price'] < price and order['sell']['amount'] > 0
if cond1 or cond2:
buy_price = order['buy']['price'] if order['buy']['maker'] else price
e.Buy(symbol, buy_price, min(order['buy']['amount'],trade_amount), order['buy']['id'], order['buy']['maker'])
order['buy']['amount'] -= min(order['buy']['amount'],trade_amount)
e.Update(time_stamp,[symbol],{symbol:price})
if cond3 or cond4:
sell_price = order['sell']['price'] if order['sell']['maker'] else price
e.Sell(symbol, sell_price, min(order['sell']['amount'],trade_amount), order['sell']['id'], order['sell']['maker'])
order['sell']['amount'] -= min(order['sell']['amount'],trade_amount)
e.Update(time_stamp,[symbol],{symbol:price})
if time_stamp - loop_time > intervel:
order = get_order(e,depth,order) #Trading logic, not given here
loop_time += int((time_stamp - loop_time)/intervel)*intervel
Il convient de noter quelques détails:
-1. Lorsqu'il y a une nouvelle transaction, nous devrions faire correspondre l'ordre d'abord, et ensuite placer l'ordre selon le dernier prix.
-2. Chaque ordre a deux attributs: le fabricant
Enfin, nous arrivons au stade de backtesting réel. Ici, nous allons backtest une stratégie de grille la plus classique pour voir si elle a atteint l'effet attendu. Le principe de la stratégie est que chaque fois que le prix augmente de 1%, nous tiendrons une certaine valeur d'ordres de position courte (sinon, nous tiendrons des ordres de position longue), et nous calculerons l'ordre d'achat et d'achat et les attendre à l'avance. Le code ne sera pas publié. Encapsuler tous les codes dans la fonctionGrid ('XTZ ', 100,0.31000, maker_fee=-0.00002, taker_fee=0.0003)
Les paramètres sont les suivants: paire de négociation, valeur de détention avec écart de prix de 1%, densité des ordres de 0,3%, intervalle de sommeil de ms, commission des ordres en attente et commission des preneurs.
Le marché de XTZ a été en état de choc au cours des 5 derniers jours, ce qui est très approprié pour la stratégie de réseau.
Les résultats mesurés par le mécanisme de backtesting traditionnel augmenteront certainement proportionnellement à l'augmentation des positions.
e1 = Grid('XTZ',100,0.3,1000,maker_fee=-0.00002,taker_fee=0.0003)
print(e1.account['USDT'])
e2 = Grid('XTZ',1000,0.3,1000,maker_fee=-0.00002,taker_fee=0.0003)
print(e2.account['USDT'])
e3 = Grid('XTZ',10000,0.3,1000,maker_fee=-0.00002,taker_fee=0.0003)
print(e3.account['USDT'])
e4 = Grid('XTZ',100000,0.3,1000,maker_fee=-0.00002,taker_fee=0.0003)
print(e4.account['USDT'])
Au total, quatre groupes ont été testés avec des valeurs de position de 100, 1000, 10000 et 100000, et le temps total de backtesting a été de 1,3 s. Les résultats sont les suivants:
{'realised_profit': 28.470993031132966, 'margin': 0.7982662957624465, 'unrealised_profit': 0.0104554474048441, 'total': 10000028.481448, 'leverage': 0.0, 'fee': -0.3430967859046398, 'maker_fee': -0.36980249726699727, 'taker_fee': 0.026705711362357405}
{'realised_profit': 275.63148945320177, 'margin': 14.346335829979132, 'unrealised_profit': 4.4382117331794045e-14, 'total': 10000275.631489, 'leverage': 0.0, 'fee': -3.3102045933457784, 'maker_fee': -3.5800688964477048, 'taker_fee': 0.2698643031019274}
{'realised_profit': 2693.8701498889504, 'margin': 67.70120400534114, 'unrealised_profit': 0.5735269329348516, 'total': 10002694.443677, 'leverage': 0.0001, 'fee': -33.984021415250744, 'maker_fee': -34.879233866850974, 'taker_fee': 0.8952124516001403}
{'realised_profit': 22610.231198585603, 'margin': 983.3853688758861, 'unrealised_profit': -20.529965947304365, 'total': 10022589.701233, 'leverage': 0.002, 'fee': -200.87094000385412, 'maker_fee': -261.5849078470078, 'taker_fee': 60.71396784315319}
On peut voir que les bénéfices finaux réalisés sont respectivement de 28,4%, 27,5%, 26,9% et 22,6% de la valeur de la position. Cela est également conforme à la situation réelle. Plus la valeur de la position est élevée, plus la valeur de l'ordre sera élevée et plus les transactions partielles seront probables. Les rendements finaux réalisés seront plus petits par rapport au montant de l'ordre. La figure ci-dessous montre la comparaison des rendements relatifs avec des valeurs de position de 100 et 10000 respectivement:
Nous pouvons également effectuer un backtest sur l'impact de différents paramètres sur le retour du backtest, tels que la densité des commandes en attente, le temps de sommeil et les commissions. Prenons le temps de sommeil à titre d'exemple, changez-le à 100 ms, comparez-le avec le temps de sommeil de 1000 ms et observez les retours. Les résultats du backtest sont les suivants:
{'realised_profit': 29.079440803790423, 'margin': 0.7982662957624695, 'unrealised_profit': 0.0104554474048441, 'total': 10000029.089896, 'leverage': 0.0, 'fee': -0.3703702128662524, 'maker_fee': -0.37938946377435134, 'taker_fee': 0.009019250908098965}
Le profit a légèrement augmenté. Cela est dû au fait que seul un groupe d'ordres est en attente de la stratégie, et certains ordres ne peuvent pas obtenir le prix fluctuant parce qu'ils n'ont pas le temps de changer. Le temps de sommeil réduit améliore ce problème. Cela montre également l'importance des ordres en attente multi-groupe dans la stratégie de la grille.
Ce document propose un nouveau système de backtesting basé sur le flux d'ordres de manière innovante, qui peut en partie simuler la situation de correspondance telle que l'ordre en attente, la prise d'ordres, la transaction partielle et le retard, reflète en partie l'impact du volume de fonds stratégiques sur les rendements, et a une valeur de référence importante pour les stratégies à haute fréquence et les stratégies de couverture. Le backtesting de haute précision indique la direction pour l'optimisation des paramètres de stratégie. Il a également été vérifié par des tests de bot réels à long terme.