Système de backtest à haute fréquence basé sur chaque transaction et les défauts du backtest en ligne K

Auteur:La bonté, Créé: 2020-06-16 10:30:19, Mis à jour: 2023-11-01 20:26:21

img

Quand j'ai écritRecherche sur la stratégie de couverture multi-monnaie des contrats à terme de Binance, j'ai également publié un moteur de backtest. Et le premier rapport était basé sur le backtest d'une heure de K-line, qui a vérifié l'efficacité de la stratégie. Mais le temps de sommeil de la stratégie open source réelle est de 1 seconde, ce qui est une stratégie de fréquence assez élevée. Évidemment, l'utilisation du backtest horaire de K-line ne peut pas produire des résultats précis. Plus tard, les résultats du backtest du niveau minute de la ligne K ont été ajoutés, et les revenus du backtest se sont considérablement améliorés, mais il est toujours impossible de déterminer quels paramètres devraient être utilisés dans le cas du niveau de secondes, et la compréhension de l'ensemble de la stratégie n'est pas très claire. La raison principale est l'inconvénient important du backtest basé sur la ligne K.

Problèmes basés sur le backtest en ligne K

Tout d'abord, qu'est-ce que la ligne K historique? Une ligne K contient quatre prix de haut, d'ouverture, de bas, de fermeture, les deux premiers times et le volume de l'intervalle. La plupart des plates-formes et des frameworks de quantification sont basés sur le backtest K-line, et la plate-forme FMZ fournit également un backtest de niveau tick.

La première est la question du temps. Le temps du prix le plus élevé et le prix le plus bas des données de la ligne K ne sont pas donnés et ne doivent pas être considérés, mais les prix d'ouverture et de fermeture les plus importants ne sont pas les temps d'ouverture et de fermeture. Même les variétés de trading les moins populaires n'ont souvent pas de transaction pendant plus de dix secondes, et lorsque nous testons la stratégie multi-variété, nous supposons souvent que leur prix d'ouverture et leur prix de fermeture sont les mêmes, ce qui est également basé sur le backtest du prix de fermeture.

Imaginez utiliser la ligne de niveau minute K pour vérifier l'arbitrage de deux variétés. La différence entre elles est généralement de 10 yuans ((ou dollars). Maintenant, à 10:01, le prix de clôture du contrat A est de 100, le contrat B est de 112 et la différence est de 12 yuans. Ainsi, la stratégie commence à se protéger. À un certain moment, la différence de prix est revenue et la stratégie a réalisé un profit de retour de 2 yuans.

mais la situation réelle peut être que à 10h45, le contrat A a produit une transaction de 100 yuans, après quoi il n'y a pas eu de transaction, le contrat B a eu une transaction de 112 yuans à 10h58, à 10h01:00 Les deux prix n'existent pas. Quel est le prix du marché à ce moment-là, et combien l'opération de couverture peut-elle obtenir? Je ne peux pas savoir. Une situation possible est: à 10h58, le prix de l'ordre en attente de Acheter 1 et Vendre 1 du contrat A est101.9à102.1Il n'y a pas de différence de 2 yuans.

Le deuxième est le problème du matchmaking. Le vrai matchmaking est la priorité du prix et la priorité du temps. Si l'acheteur dépasse le prix de Vendre 1, il traite généralement directement au prix de Vendre 1, sinon il entre dans le carnet de commandes en attente et attend. Les données de la ligne K n'ont évidemment pas de prix Acheter 1 et Vendre 1, il est impossible de simuler le niveau de correspondance des prix en détail.

La dernière est l'impact de la stratégie elle-même sur le marché. Si c'est un backtest de petits fonds, l'impact n'est pas grand. Mais si le volume de transactions est important, il aura un impact sur le marché. Non seulement le glissement de prix sera important lorsque vous passez un ordre de grand volume, si vous achetez un ordre long exécuté, ce type d'action saisira réellement les ordres d'autres traders qui voulaient à l'origine acheter, l'effet "papillon" aura un impact sur le marché. Cet effet ne peut pas être quantifié. Nous ne pouvons que dire par expérience que le trading à haute fréquence ne peut accueillir que de petits fonds.

Test de retour basé sur la profondeur et le tick en temps réel

FMZ fournit un backtest au niveau réel, qui peut obtenir des données historiques réelles20 layer depth price, deuxième niveau en temps réelTicks, Each Individual TransactionSur la base de ces caractéristiques, FMZ a créé une fonction de lecture des transactions en temps réel.

Cette quantité de données de backtest est très importante, et la vitesse de backtest est également très lente, généralement seulement deux jours. Pour les stratégies à fréquence relativement élevée ou à temps critique, un backtest au niveau du marché réel est nécessaire.

Le mécanisme de matchmaking actuel est que si l'ordre d'achat est supérieur à la Vendre 1, il sera complètement correspondu immédiatement sans regarder le montant, et s'il est inférieur à la Vendre 1, il entrera dans la file d'attente de correspondance pour attendre.

img

Mécanisme de backtest basé sur le flux de transaction ordre par ordre

Il y a trop peu d'informations dans la ligne K, et la profondeur de prix peut aussi être une fausse profondeur, mais il y a une sorte de données qui est la volonté de transaction réelle du marché, qui reflète l'historique de transaction le plus réel, c'est,Each Individual TransactionCet article proposera un système de backtest basé sur le flux des commandes, qui réduira considérablement le volume des données de backtest au niveau du marché réel et simulera dans une certaine mesure l'impact du volume des transactions sur le marché.

J'ai téléchargé la transaction des 5 derniers jours Binance XTZ contrat perpétuel (adresse de téléchargement:https://www.fmz.com/upload/asset/1ff487b007e1a848ead.csv), en tant que variété peu populaire, elle comporte un total de 213000 données de transactions, regardons d'abord la composition des données:

[['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 ordre chronologique. Les significations spécifiques sont les suivantes: nom de la variété, prix de la transaction, horodatage de la transaction, quantité de la transaction, s'il s'agit d'une transaction active d'ordre de vente.Makeret le vendeur est un actifTaker, les dernières données sontTrue.

Tout d'abord, selon la direction de la transaction, vous pouvez spéculer assez précisément sur le Buy 1 et Sell 1 sur le marché. S'il s'agit d'un ordre de vente actif, alors le prix Buy 1 à ce moment-là est le prix de la transaction, s'il s'agit d'un ordre d'achat actif, le prix Sell 1 sera le prix de la transaction. S'il y a une nouvelle transaction, alors tout le prix sera renouvelé et mis à jour. Le dernier résultat sera conservé s'il n'y a pas de renouvellement et de mise à jour. Il est facile d'introduire le dernier moment des données ci-dessus, le prix Buy 1 est 2.903, et le Sell 1 est 2.904.

Selon le flux des commandes, il peut être assorti de la façon suivante: prenez un ordre d'achat comme exemple, le prix estprice, la quantité de commande estamount, puis acheter et vendre 1 à ce moment sontbidetaskSi le taux depriceest inférieure àasket supérieur àbid, alors il est jugé commemakerd'abord, et la priorité peut être assortie pour faire une transaction, alors toutes les transactions avec un prix de transaction inférieur ou égal àpricependant le temps d'existence de l'ordre sera correspondre à cette commande (sipriceest inférieure ou égale àbidLes ordres dont le prix de transaction est inférieur àpricesont correspondantes à cet ordre.)

Le prix correspondant estprice, et le volume est le volume de transactions deEach Individual Transaction, jusqu'à ce que l'ordre soit entièrement complété ou l'ordre annulé.ask, il est jugé comme untakerAprès cela, pendant la durée de l'ordre, toutes les transactions dont le prix de transaction est inférieur ou égal àpricesont assortis à cet ordre, et le prix d'appariement est le prix de transaction de l'ordreEach Individual TransactionLa distinction entremakerettakerLes stratégies à haute fréquence sont essentiellement parce que l'échange encourage les commandes en attente et il y a des réductions pour les frais de transaction.

Il est facile de voir un problème avec ce type de correspondance.taker, la situation réelle est qu'il peut être exécuté immédiatement, plutôt que d'attendre une nouvelle commande pour être assorti avec elle. tout d'abord, nous n'avons pas considéré le volume des commandes en attente, même s'il y a des données, directement juger la transaction a également changé la profondeur de prix, affectant le marché.

En fonction de la correspondance des nouveaux ordres, il équivaut à remplacer les ordres existants dans l'histoire par vos ordres. En tout cas, il ne dépassera pas la limite du volume de négociation 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 correspondance affecte également le volume des ordres, ce qui à son tour affecte les revenus de la stratégie, reflétant quantitativement la capacité de la stratégie. Il n'y aura pas de backtest traditionnel, lorsque le montant des fonds double et le gain double.

Il y a encore quelques petits détails. Si le prix d'achat de l'ordre est égal à Buy 1, il y a encore une certaine probabilité que le prix d'achat sera égalé par Buy 1, ce genre de situation ne sera pas considéré ici.

Code correspondant

Les objets d'échange peuvent se référer à l'introduction au début, essentiellement inchangée, ajoutant seulement la différence entremakerettakerLes résultats de l'analyse de l'efficacité des tests de compatibilité sont les suivants:

 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] # executed price
        trade_amount = tick[3] # executed volume
        time_stamp = tick[1] # executed 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, 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

Quelques détails à noter:

  • Lorsqu'il y a une nouvelle transaction, nous devons d'abord faire correspondre l'ordre, puis placer l'ordre selon le dernier prix.

  • Chaque ordre a deux attributs: makers'il s'agit d'un maker, prioritépriorité correspondante, prenant un ordre d'achat à titre d'exemple, lorsque le prix d'achat est inférieur à Vendre 1, il est marqué commemaker, et lorsque le prix d'achat est supérieur à Buy 1, il est marqué commePriority matching, prioritydétermine si le prix est égal ou non au prix d'achat, et le fabricant détermine les frais de transaction.

  • LemakeretprioritySi un achat important a été effectué et dépasse la capacité du marché, le volume restant sera le volume de vente au détail.maker.

  • StratégieintervalIl s'agit d'un problème de qualité.

Test en arrière-plan de la stratégie du réseau

Enfin, c'est la phase de backtest réelle. Voyons backtest une des stratégies de grille les plus classiques ici pour voir si nous pouvons obtenir les résultats attendus. Le principe de la stratégie est que chaque fois que le prix augmente de 1%, nous tenons un ordre court d'une certaine valeur (inverse, nous tenons un ordre long), calculer l'ordre d'achat et de vente à l'avance. je ne vais pas vous montrer le code source. ils sont tous encapsulés dans leGrid('XTZ', 100, 0.3, 1000, maker_fee=-0.00002, taker_fee=0.0003)fonction, les paramètres sont les suivants: paire de négociation, déviation du prix de la valeur de détention de 1%, densité des ordres en attente est de 0,3%, intervalle de sommeilms, les frais de commande en attente et les frais de commande exécutés.

Le prix du marché de XTZ a été en état de choc au cours des 5 derniers jours, ce qui est très approprié pour les réseaux.

img

Nous effectuons d'abord un backtest sur l'effet des différentes positions de détention sur le rendement des bénéfices.

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

Un total de quatre groupes ont été testés en arrière-plan, la valeur des positions de détention était de 100, 1000, 10000, 100 000, et le temps total de test en arrière-plan était 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 de détention. Cela est également conforme à la situation réelle. Plus la valeur de la position de détention est élevée, plus la valeur de l'ordre en attente est élevée, plus il est probable qu'une transaction partielle se produise et plus le gain final réalisé est petit par rapport au montant de l'ordre en attente. Le tableau suivant est une comparaison des rendements relatifs de la valeur de la position de 100 et 10000 respectivement:

img

Nous pouvons également effectuer un backtest sur l'impact de différents paramètres sur les revenus de backtest, tels que la densité des commandes en attente, le temps de sommeil, les frais de transaction, etc. Prenons le temps de sommeil à titre d'exemple, changez-le à 100 ms, et comparez le temps de sommeil à 1000 ms pour voir le retour de profit.

{'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}

La stratégie de grille pour la mise en place de plusieurs ensembles d'ordres montre également l'importance de la stratégie de grille pour le placement de plusieurs ensembles d'ordres.

Pour résumer

Cet article propose de manière innovante un nouveau système de backtest basé sur le flux d'ordres, qui peut partiellement simuler la situation de correspondance des ordres en attente, des ordres exécutés, des ordres partiellement exécutés, des retards, etc., et reflète partiellement l'impact du montant des fonds de stratégie sur les revenus. Pour les stratégies à haute fréquence et de couverture, il a une valeur de référence importante. Le backtest de haute précision indique la direction de l'optimisation des paramètres de stratégie. Il a également été vérifié pendant longtemps.


Relationnée

Plus de