3
focar em
1273
Seguidores

Um sistema de backtesting de alta frequência baseado em transação por transação e nos defeitos do backtesting de K-line

Criado em: 2020-06-04 16:48:02, atualizado em: 2024-12-10 20:32:01
comments   7
hits   4551

Um sistema de backtesting de alta frequência baseado em transação por transação e nos defeitos do backtesting de K-line

Estou aquiEstratégia de hedge multimoeda Binance short over-subindo long over-caindoUm mecanismo de backtesting também foi lançado ao mesmo tempo. O primeiro relatório verificou a eficácia da estratégia com base em um backtest de K-line de uma hora. No entanto, o tempo de dormência real da estratégia pública é de 1s, o que é uma estratégia de frequência muito alta. É obviamente impossível obter resultados precisos por meio de backtesting com K-line por hora. Adicionado posteriormenteBacktest de linha de minutoComo resultado, o lucro do backtesting aumentou muito, mas ainda é impossível determinar quais parâmetros devem ser usados ​​na situação de segundo nível, e a compreensão de toda a estratégia não é muito clara. O principal motivo é a importante desvantagem do backtesting baseado na linha K.

Problemas com o backtesting da linha K

Primeiramente, o que é a linha K histórica? Os dados de uma linha K incluem quatro preços: abertura máxima e fechamento mínimo, dois horários de início e intervalo de volume de negociação. A maioria das plataformas e estruturas quantitativas são baseadas em backtesting K-line, e a plataforma quantitativa FMZ também fornece backtesting em nível de tick. O backtesting da linha K é muito rápido e não apresenta problemas na maioria dos casos, mas também apresenta falhas muito sérias, especialmente ao fazer backtesting de estratégias multivariadas e estratégias de alta frequência, sendo quase impossível tirar conclusões corretas.

A primeira é a questão do tempo. O tempo dos preços mais altos e mais baixos dos dados da linha K não é fornecido, então não há necessidade de considerá-lo. Mas os preços de abertura e fechamento mais importantes não começam na abertura e hora de fechamento. Mesmo para produtos de negociação menos populares, muitas vezes não há negociação por mais de dez segundos. Quando fazemos backtest de estratégias multiproduto, muitas vezes assumimos que seus preços de abertura e fechamento são simultâneos. Esta também é a base para backtest do preço de fechamento.

Imagine usar a linha de minuto para fazer um backtest da arbitragem de duas variedades. A diferença de preço delas é geralmente de 10 yuans. Agora, descobriu-se que às 10:01, o preço de fechamento do contrato A é 100 e o do contrato B é 112. A diferença de preço é 12 yuans. Então a estratégia começa a fazer hedge. Neste momento, a diferença de preço retornou, e a estratégia obteve um lucro de retorno de 2 yuans.

A situação real pode ser que às 10:00:45, o contrato A gerou uma transação de 100 yuans, e não houve nenhuma transação depois disso. Às 10:00:58, o contrato B gerou uma transação de 112 yuans. Às 10:01, ambos os preços não existem, qual é o preço de mercado neste momento e quanta diferença de preço pode ser obtida com hedge? Não há como saber. Um cenário possível é: às 10:00:58, o preço de compra e venda do contrato A é 101,9-102,1, e não há nenhuma diferença de preço de 2 yuans. Isso irá enganar muito a otimização da nossa estratégia.

A segunda questão é a correspondência. A correspondência real prioriza preço e tempo. Se o comprador exceder o preço de venda, a transação geralmente será concluída diretamente no preço de venda. Caso contrário, ela entrará no livro de ordens e aguardará. Os dados da linha K obviamente não têm um preço de compra e venda, e é impossível simular a correspondência em nível detalhado.

Por fim, há o impacto da estratégia em si no mercado. Se for um backtest de capital pequeno, o impacto não será significativo. Mas se o volume de negociação for responsável por uma grande proporção, isso terá um impacto no mercado. Não só o deslizamento de preço será grande quando a transação for executada imediatamente, mas se sua ordem de compra for executada por meio de backtesting, ela realmente impedirá as transações de outros traders que originalmente queriam comprar, e o efeito borboleta terá um impacto no mercado. Esse impacto não pode ser quantificado, e só podemos dizer, com base na experiência, que a negociação de alta frequência só pode acomodar fundos pequenos.

Backtesting baseado em profundidade e ticks em tempo real

A FMZ fornece backtesting em tempo real, que pode obter profundidade histórica real de 20 níveis, tick de segundo nível em tempo real, dados de transação por transação e, com base nisso,Função de reprodução em tempo real. A quantidade de dados de backtesting é extremamente grande e a velocidade é muito lenta, geralmente apenas dois dias. Para estratégias de frequência relativamente alta ou estratégias que exigem julgamento de tempo rigoroso, o backtesting em tempo real é necessário. Os pares de transações e períodos de tempo coletados pelo FMZ não são longos, mas há mais de 70 bilhões 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á totalmente correspondida imediatamente, sem considerar o volume; se for menor que a ordem de venda, ela entrará na fila de correspondência. Esse mecanismo de backtesting resolve os dois primeiros problemas do backtesting da linha K, mas ainda não consegue resolver o último problema. E como a quantidade de dados é tão grande, a velocidade e o intervalo de tempo do backtesting são limitados.

Um sistema de backtesting de alta frequência baseado em transação por transação e nos defeitos do backtesting de K-line

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

Há muito pouca informação sobre a linha K e a profundidade pode ser falsa, mas há um tipo de dado que reflete a verdadeira intenção de transação do mercado e reflete o histórico de transações mais autêntico, ou seja, transação por transação. Este artigo proporá um sistema de backtesting de alta frequência baseado no fluxo de ordens, o que reduzirá bastante a quantidade de dados para backtesting em tempo real e simulará o impacto do volume de negociação no mercado até certo ponto.

Baixei os registros de transações do contrato perpétuo Binance XTZ nos últimos 5 dias (endereço de download: https://www.fmz.com/upload/asset/1ff487b007e1a848ead.csv). Como um produto não muito popular, há 213.000 transações em total. Dados, vamos primeiro olhar para a composição dos 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, classificada por tempo de transação. Os significados específicos são: nome do produto, preço da transação, registro de data e hora da transação, quantidade da transação e se é uma transação de ordem de venda ativa. Há compradores e vendedores, e cada transação inclui um comprador e um vendedor. Se o comprador for um market maker e o vendedor for um taker, o último dado é True.

Primeiro, com base na direção da transação, os preços de compra e venda no mercado podem ser inferidos com bastante 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, a nova cotação será atualizada. Se não for atualizada, o resultado anterior será retido. É fácil deduzir que no último momento dos dados acima, o preço de compra era 2,903 e o preço de venda era 2,904.

De acordo com o fluxo de ordens, a correspondência pode ser feita assim: tomando uma ordem de compra como exemplo, o preço é preço, a quantidade da ordem é quantia, e as ordens de compra e venda são oferta e demanda, respectivamente. Se o preço for menor que o pedido e maior que o lance, ele é primeiro determinado como um maker e pode ser correspondido com o pedido primeiro. Então, dentro do tempo de existência do pedido, todas as transações com um preço de transação menor ou igual ao preço será correspondido com esta ordem (se o preço for menor que o ask, a ordem será correspondida com o bid). Se o preço bid for igual ou maior que o preço bid, a ordem não pode ser negociada primeiro . Todas as ordens com um preço de transação menor que o preço de oferta serão correspondidas com esta ordem. O preço de correspondência é o preço de oferta, e o volume de transação é o volume de transação de cada transação até que a ordem seja totalmente executada ou cancelada. Se o preço for maior que o pedido, ele é considerado um taker. Depois disso, todas as transações com um preço de transação menor ou igual ao preço dentro do tempo de existência da ordem serão correspondidas com esta ordem, e o preço correspondente será ser o preço de transação da transação. A distinção entre makers e takers é porque as exchanges basicamente incentivam a colocação de ordens e oferecem taxas de transação preferenciais. Para estratégias de alta frequência, essa distinção deve ser levada em conta.

É fácil ver um problema com esse tipo de correspondência. Se a ordem for um taker, a situação real é que ela pode ser executada imediatamente, em vez de esperar por novas ordens para combiná-la. Primeiro, não levamos em conta o volume de ordens pendentes. Mesmo que houvesse dados, julgar diretamente a transação mudaria a profundidade e afetaria o mercado. A correspondência com base em novas ordens é equivalente a substituir as ordens reais no histórico pelas suas ordens. Em qualquer caso, não excederá o limite do próprio volume de negociação do mercado, e o lucro final não excederá o lucro máximo gerado pelo mercado. Alguns mecanismos de correspondência também afetam o volume de transações de ordens, o que por sua vez afeta os retornos da estratégia e reflete quantitativamente a capacidade da estratégia. Não haverá um backtest tradicional onde o lucro será duplicado se a quantidade de fundos for duplicada.

Existem alguns pequenos detalhes. Se o preço de compra da ordem for igual ao preço de compra única, ainda há uma certa probabilidade de que ele seja correspondido ao preço de compra única. É necessário considerar a prioridade da ordem pendente e a probabilidade de transação, etc. É relativamente complicado e não será considerado aqui.

Código correspondente

Os objetos de troca podem se referir à introdução no início, que são basicamente inalterados. Apenas a diferença entre as taxas do maker e do taker é adicionada, e a velocidade do backtesting é otimizada. A seguir, apresentaremos principalmente o código correspondente.

    symbol = 'XTZ'
    loop_time = 0
    intervel = 1000 #策略的休眠时间为1000ms
    init_price = data[0][2] #初始价格
    e = Exchange([symbol],initial_balance=1000000,maker_fee=maker_fee,taker_fee=taker_fee,log='') #初始化交易所
    depth = {'ask':data[0][2], 'bid':data[0][2]} #深度
    order = {'buy':{'price':0,'amount':0,'maker':False,'priority':False,'id':0},
             'sell':{'price':0,'amount':0,'maker':False,'priority':False,'id':0}} #订单
    for tick in data:
        price = int(tick[2]/tick_sizes[symbol])*tick_sizes[symbol] #成交价格
        trade_amount = tick[3] #成交数量
        time_stamp = tick[1] #成交时间戳
        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
        
        #订单网络延时也可以作为撮合条件之一,这里没考虑
        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) #交易逻辑,这里未给出
            loop_time += int((time_stamp - loop_time)/intervel)*intervel

Alguns detalhes a serem observados:

  • 1. Quando houver uma nova transação, você deve primeiro corresponder ao pedido e, em seguida, fazer um pedido com base no preço mais recente.
  • 2. Cada ordem tem dois atributos: maker - se é um maker, prioridade - prioridade correspondente. Tomando uma ordem de compra como exemplo, quando o preço de compra é menor que o preço de venda, ela é marcada como maker, e quando o o preço de compra é maior que o preço de compra, é marcado como uma ordem de compra. Correspondência de prioridade, a prioridade determina se o preço é igual ao preço de oferta ou não, e o criador determina a taxa de manuseio.
  • 3. O maker e a prioridade da ordem são atualizados. Por exemplo, se uma grande ordem de compra for colocada que exceda o preço de mercado, quando um preço maior que o preço de compra aparecer, o volume de negociação restante será o maker.
  • 4. O intervalo da estratégia é necessário, o que pode representar o atraso do mercado.

Estratégias de grade de backtesting

Finalmente, chegamos ao estágio de backtesting real. Faremos backtest de uma das estratégias de grid mais clássicas para ver se ela atinge os resultados esperados. O princípio da estratégia é que toda vez que o preço sobe 1%, mantemos uma ordem curta de um determinado valor (ou vice-versa, mantemos uma ordem longa), calculamos as ordens de compra e venda e as colocamos com antecedência. O código não foi divulgado. Encapsule todo o código emGrid('XTZ',100,0.3,1000,maker_fee=-0.00002,taker_fee=0.0003)Na função, os parâmetros são: par de negociação, desvio de preço de 1% do valor de retenção, densidade de ordens de 0,3%, intervalo de espera em ms, taxa do criador de ordens e taxa do tomador de ordens.

Nos últimos cinco dias, o mercado XTZ esteve em uma fase volátil, o que é muito adequado para a rede. /carregar/ativo/1e235fa08ed9dce82a3.png

Primeiro, fazemos backtest do impacto de diferentes tamanhos de posição nos retornos. Os retornos medidos pelo mecanismo tradicional de backtesting certamente aumentarão proporcionalmente com o aumento nas 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 retenção de 100, 1000, 10000 e 100000, respectivamente, e o tempo total de teste foi de 1,3s. 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 observar que os lucros finais realizados foram 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 o valor da ordem pendente, mais provável é que uma transação parcial ocorra, e o lucro final realizado será menor em relação ao volume de pedidos pendentes. A figura a seguir compara os retornos relativos de participações com valores de 100 e 10.000 respectivamente: Um sistema de backtesting de alta frequência baseado em transação por transação e nos defeitos do backtesting de K-line

Também podemos testar o impacto de diferentes parâmetros nos retornos de backtesting, como densidade de pedidos, tempo de inatividade, taxas de manuseio, etc. Tomando o tempo de sono como exemplo, altere-o para 100 ms e compare-o com o tempo de sono de 1000 ms para ver os benefícios. Os resultados do backtest são os seguintes:

{'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 ligeiramente. Isso ocorre porque a estratégia só coloca um conjunto de ordens. Algumas ordens não podem se beneficiar dos preços flutuantes porque não podem ser alteradas no tempo. A redução do tempo de dormência melhorou esse problema. Isso também ilustra a importância de colocar vários grupos de ordens em uma estratégia de grade.

Resumir

Este artigo propõe de forma inovadora um novo sistema de backtesting baseado no fluxo de ordens, que pode simular parcialmente as condições de correspondência de ordens pendentes, recebimento de ordens, transações parciais, atrasos, etc., e refletir parcialmente o impacto dos fundos de estratégia nos retornos. Ele tem referências importantes valor para estratégias de hedge, e o backtesting de alta precisão aponta a direção para otimizar os parâmetros da estratégia. Também foi verificado por meio de negociações reais de longo prazo. Ele também controla melhor a quantidade de dados necessária para o backtesting, e a velocidade do backtesting também é muito rápida.