Les ressources ont été chargées... Je charge...

Système de backtesting haute fréquence basé sur transaction par transaction et défauts du backtesting K-line

Auteur:FMZ~Lydia, Créé: 2022-11-30 12:38:50, Mis à jour: 2023-09-11 20:01:01

img

Système de backtesting haute fréquence basé sur transaction par transaction et défauts du backtesting K-line

J'ai publié un moteur de backtesting dans l'article Étude sur la stratégie de couverture multi-monnaie des contrats à terme Binance (https://www.fmz.com/digest-topic/5584Cependant, en fait, le temps de sommeil de la stratégie publique est de 1s, ce qui est une stratégie de fréquence assez élevée. Il est évidemment impossible d'obtenir des résultats précis en utilisant le backtesting de la ligne K d'une heure.https://www.fmz.com/digest-topic/5621En conséquence, les rendements du backtesting se sont beaucoup améliorés, mais il est encore impossible de déterminer quels paramètres devraient être utilisés dans le cas du deuxième niveau, et la compréhension de l'ensemble de la stratégie n'est pas très claire.

Problèmes basés sur le backtesting de la ligne K

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.

Test en arrière-plan basé sur la profondeur et le tick en temps réel

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.

img img

Mécanisme de backtesting basé sur le flux des ordres transaction par transaction

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 Binance XTZ contrat perpétuel (adresse de téléchargement:https://www.fmz.com/upload/asset/1ff487b007e1a848ead.csvLes données sont de 213 000 pièces, une variété moins populaire.

[['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.

Code de matchmaking

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 s'il s'agit d'un fabricant, et la priorité priorité de correspondance. Prenant l'ordre d'achat comme exemple, lorsque le prix d'achat est inférieur au prix de vente, il est marqué comme fabricant, et lorsque le prix d'achat est supérieur au prix d'achat, il est marqué comme priorité de correspondance. La priorité détermine si elle correspond si le prix est égal au prix d'achat, et le fabricant détermine la commission. -3. Le fabricant et la priorité de l'ordre sont mis à jour. Par exemple, il y a un grand ordre d'achat qui dépasse les positions d'ouverture, quand il y a un prix supérieur au prix d'achat, à ce moment, la quantité de transaction restante sera fabricant. L'intervalle de la stratégie est nécessaire, ce qui peut représenter le retard du marché.

Tests en arrière-plan des stratégies de réseau

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.

img

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:

img

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.

Résumé

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.


Relationnée

Plus de