O recurso está a ser carregado... Carregamento...

Sistema de backtesting de alta frequência baseado em transação por transação e defeitos do backtesting de linha K

Autora:FMZ~Lydia, Criado: 2022-11-30 12:38:50, Atualizado: 2023-09-11 20:01:01

img

Sistema de backtesting de alta frequência baseado em transação por transação e defeitos do backtesting de linha K

Eu lancei um mecanismo de backtesting no artigo Estudo sobre Estratégia de Hedging Multicurrency do Binance Futures (https://www.fmz.com/digest-topic/5584O primeiro relatório é baseado no backtesting de uma hora de linha K, que verifica a eficácia da estratégia. No entanto, na verdade, o tempo de sono da estratégia pública é de 1s, que é uma estratégia de frequência bastante alta.https://www.fmz.com/digest-topic/5621Como resultado, os retornos do backtesting melhoraram muito, mas ainda é impossível determinar quais parâmetros devem ser usados no caso do segundo nível, e a compreensão de toda a estratégia não é muito clara.

Problemas baseados no backtesting da linha K

Primeiro de tudo, o que é a linha histórica K? Um dado da linha K inclui quatro preços: o preço mais alto, o preço de abertura, o preço mais baixo e o preço de fechamento, o tempo de início, o tempo de término e a quantidade do intervalo de negociação. A maioria das plataformas e estruturas quantitativas são testadas com base na linha K, e a plataforma FMZ Quant também fornece backtesting de nível Tick. A velocidade do backtesting da linha K é muito rápida, e não é um problema na maioria dos casos, mas também há falhas muito sérias, especialmente a estratégia multi-variedade e a estratégia de alta frequência do backtesting, que dificilmente podem tirar conclusões corretas.

Em primeiro lugar, é uma questão de tempo. O tempo do preço mais alto e mais baixo dos dados da linha K não é dado, por isso é desnecessário considerar, mas os preços de abertura e fechamento mais importantes não são os tempos de abertura e fechamento da posição. Mesmo que as variedades de negociação sejam impopulares, elas geralmente não são negociadas por mais de dez segundos.

Imagine usar a linha do minuto para testar a arbitragem de duas variedades. A diferença entre elas é geralmente de 10 yuans. Agora, fica claro que às 10:01, o preço de fechamento do contrato A é de 100 yuans, o preço de fechamento do contrato B é de 112 yuans e a diferença é de 12 yuans. Então a estratégia começa a se proteger. Em certo momento, a diferença retorna e a estratégia ganha 2 yuans de lucros de retorno.

No entanto, a situação real pode acontecer às 10:00:45, o contrato A gerou uma transação de 100 yuans, e então não houve transação. O contrato B gerou uma transação de 112 yuans às 10:00:58. Às 10:01, ambos os preços não existiam. Qual era o preço de abertura neste momento? E quanta diferença poderia obter a cobertura? Não sabemos. Uma situação possível é que às 10:00:58, a tendência de compra de um e venda de um do contrato A é de 101.9-102.1, e não há spread de 2 yuans, o que enganará muito nossa otimização de estratégia.

O segundo é o matchmaking. O matchmaking real é o preço e o tempo primeiro. Se o comprador exceder o preço de venda, ele / ela geralmente concluirá a transação no preço de venda, caso contrário, ele / ela entrará no livro de pedidos e esperará.

O último é o impacto da transação da própria estratégia no mercado. Se for um backtest de fundo pequeno, o impacto será pequeno. No entanto, se a quantidade de negociação representar uma grande proporção, ele terá um impacto no mercado. Não só o ponto de queda de preço será grande quando a transação for concluída imediatamente, mas se sua ordem de compra for concluída no backtest, ele realmente antecipa a transação de outros traders originais que querem comprar, o que terá um impacto de efeito borboleta no mercado. No entanto, esse impacto não pode ser quantificado e só pode ser dito pela experiência que a negociação de alta frequência só pode acomodar pequenos fundos.

Backtesting baseado em profundidade e tick em tempo real

FMZ fornece o real bot nível backtesting, que pode obter o real histórico de 20 níveis de profundidade, em tempo real segundo tick, transação por transação e outros dados, e com base nisso, ele fez o real bot função de reprodução (https://www.fmz.com/m/databaseEste tipo de medição de backtest tem uma grande quantidade de dados e uma velocidade lenta, que só pode ser usada por dois dias. Para estratégias que são de frequência relativamente alta ou exigem um julgamento de tempo rigoroso, o backtesting de bot real é necessário. Os pares de negociação e o tempo coletados pela FMZ não são muito longos, mas há mais de 70 bilhões de peças de dados históricos. O mecanismo de correspondência atual é que, se a ordem de compra for maior que a ordem de venda, ela será completamente correspondida imediatamente sem olhar para a quantidade, e se a ordem de compra for menor que a ordem de venda, ela entrará na fila de correspondência. Este mecanismo de backtesting resolve os dois primeiros problemas da backtesting da linha K, mas ainda não pode resolver o último problema. E porque a quantidade de dados é muito grande, a velocidade e o tempo de backtest são limitados.

img img

Mecanismo de backtesting baseado no fluxo de ordens transação por transação

No entanto, um tipo de dados é a intenção real de transação do mercado, refletindo o histórico de transações mais real - ou seja, transação por transação. Neste artigo, proponho um sistema de backtesting de alta frequência baseado no fluxo de pedidos, que reduzirá muito a quantidade de dados no backtesting no nível real do bot e, até certo ponto, simulará o impacto do volume de negociação no mercado.

Eu baixei a transação por transação dos últimos 5 dias Binance XTZ contrato perpétuo (endereço de download:https://www.fmz.com/upload/asset/1ff487b007e1a848ead.csv) Como variedade menos popular, existem 213.000 dados.

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

Os dados são uma lista bidimensional, ordenada por tempo de transação. Os significados específicos são: nome da espécie, preço da transação, carimbo de tempo da transação, quantidade da transação e se a ordem de venda é ativamente executada. Há compra e venda. Cada transação inclui o comprador e o vendedor.

Em primeiro lugar, de acordo com a direção da transação, podemos especular a compra e venda no mercado com precisão. Se for uma ordem de venda ativa, o preço de compra neste momento é o preço da transação. Se for uma ordem de compra ativa, o preço de venda é o preço da transação. Se houver uma nova transação, atualizaremos a nova posição de abertura. Se não for atualizada, o último resultado será mantido. É fácil lançar o último momento dos dados acima. O preço de compra é 2.903 e o preço de venda é 2.904.

De acordo com o fluxo de ordens, ele pode ser combinado da seguinte forma: tomar uma ordem de compra como exemplo, o preço é o preço, e a quantidade da ordem é a quantidade. Neste momento, comprar uma e vender uma das posições de abertura são propostas e pedir, respectivamente. Se o preço for menor do que a oferta e maior do que a oferta, ele será julgado como o fabricante primeiro, e a prioridade pode ser dada ao matchmaking. Então todas as transações com um preço de transação menor ou igual ao preço durante a vida da ordem serão combinadas com essa ordem (se o preço for menor ou igual à oferta, não pode ser dada prioridade à transação, e as ordens com um preço de transação menor do que o preço serão combinadas com essa ordem). A existência da compra é preço, e a quantidade da transação é a quantidade de transações por transação, até que a transação seja completamente encerrada ou cancelada. Se a diferença for maior do que o preço, ele será considerado um tomador.

É fácil ver o problema desse matchmaking. Se a ordem é um tomador, a situação real é que a transação pode ser feita imediatamente, em vez de esperar por uma nova ordem para combiná-la. Em primeiro lugar, não consideramos o número de ordens listadas no mercado. Mesmo que houvesse dados, o julgamento direto da transação mudou a profundidade e afetou o mercado. O matchmaking baseado em novas ordens é equivalente a substituir suas ordens com as ordens reais no histórico, que não excederá o limite de quantidade de transação do próprio mercado em qualquer caso, e o lucro final não pode exceder o lucro máximo gerado pelo mercado.

Há também alguns pequenos detalhes. Se o preço de compra de uma ordem for igual ao preço de compra, ainda há uma certa probabilidade de que a ordem seja correspondida ao preço de compra. A prioridade da ordem e a probabilidade de transação precisam ser consideradas, o que é mais complexo e não será considerado aqui.

Código de correspondência

Os objetos de troca podem se referir à introdução no início, basicamente inalterada. Somente a diferença entre as comissões do fabricante e do tomador é adicionada, e a velocidade de backtesting é otimizada.

    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

Alguns pormenores devem ser observados:

-1. Quando há uma nova transação, devemos combinar a ordem primeiro, e depois colocar a ordem de acordo com o último preço. -2. Cada ordem tem dois atributos: maker se é maker, e prioridade prioridade de correspondência. Tomando a ordem de compra como exemplo, quando o preço de compra é menor que o preço de venda, é marcada como maker, e quando o preço de compra é maior que o preço de compra, é marcada como prioridade de correspondência. A prioridade determina se a correspondência se o preço é igual ao preço de compra, e maker determina a comissão. -3. O fabricante e a prioridade da ordem são atualizados. Por exemplo, há uma grande ordem de compra que excede as posições de abertura, quando há um preço maior que o preço de compra, neste momento, a quantidade de transação restante será fabricante. -4. O intervalo da estratégia é necessário, o que pode representar o atraso do mercado.

Testes de retorno das estratégias de rede

Finalmente, chegamos ao estágio de backtesting real. Aqui, vamos testar uma estratégia de grade mais clássica para ver se ela alcançou o efeito esperado. O princípio da estratégia é que cada vez que o preço aumenta em 1%, manteremos um certo valor de ordens de posição curta (caso contrário, manteremos ordens de posição longa), e calcularemos a ordem de compra e venda e pendê-los antecipadamente. O código não será liberado. Encapsular todos os códigos na funçãoGrid ('XTZ ', 100,0.31000, maker_fee=-0.00002, taker_fee=0.0003)Os parâmetros são: par de negociação, valor de detenção com desvio de preço de 1%, densidade de ordens de 0,3%, intervalo de espera de ms, comissão de ordem pendente e comissão de tomador.

O mercado da XTZ esteve em choque nos últimos 5 dias, o que é muito adequado para a estratégia da rede.

img

Os rendimentos medidos pelo mecanismo tradicional de backtesting certamente aumentarão proporcionalmente ao aumento das posições.

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

Um total de quatro grupos foram testados com valores de posição de 100, 1000, 10000 e 100000, e o tempo total de backtesting foi de 1,3 s. Os resultados são os seguintes:

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

Pode-se ver que os lucros realizados finais são de 28,4%, 27,5%, 26,9% e 22,6% do valor da posição, respectivamente. Isso também está de acordo com a situação real. Quanto maior o valor da posição, maior será o valor da ordem, e mais prováveis serão as transações parciais. Os retornos realizados finais serão menores em relação ao valor da ordem.

img

Também podemos fazer backtest sobre o impacto de diferentes parâmetros no retorno do backtest, como a densidade de pedidos pendentes, tempo de sono e comissões.

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

O lucro aumentou um pouco. Isso ocorre porque apenas um grupo de pedidos está pendente para a estratégia, e alguns pedidos não podem obter o preço flutuante porque não têm tempo para mudar. O tempo de sono reduzido melhora esse problema. Isso também mostra a importância de pedidos pendentes de vários grupos na estratégia da rede.

Resumo

Este artigo propõe um novo sistema de backtesting baseado no fluxo de pedidos de forma inovadora, que pode em parte simular a situação de correspondência, como pedido pendente, tomada de ordem, transação parcial e atraso, em parte reflete o impacto do volume de fundo estratégico nos retornos, e tem um importante valor de referência para estratégias de alta frequência e estratégias de hedging.


Relacionados

Mais.