Sistema de backtest de alta frecuencia basado en cada transacción y los defectos del backtest de línea K

El autor:La bondad, Creado: 2020-06-16 10:30:19, Actualizado: 2023-11-01 20:26:21

img

Cuando escribíInvestigación sobre la estrategia de cobertura de divisas de futuros de Binance, también lancé un motor de backtest. Y el primer informe se basó en el backtest de K-line de una hora, que verificó la efectividad de la estrategia. Pero el tiempo de sueño de la estrategia de código abierto real es 1 segundo, que es una estrategia de bastante alta frecuencia. Obviamente, usar el backtest de K-line por hora no puede producir resultados precisos. Más tarde, se agregaron los resultados del backtest de la línea de nivel K de minutos, y los ingresos de backtest han mejorado mucho, pero todavía es imposible determinar qué parámetros se deben usar en el caso de secondes level, y la comprensión de toda la estrategia no es muy clara. La razón principal es el importante inconveniente del backtest basado en K-line.

Problemas basados en la prueba posterior de la línea K

En primer lugar, ¿qué es la línea K histórica? Los datos de la línea K contienen cuatro precios de alto, abierto, bajo, cerrado, las dos primeras veces y el volumen del intervalo. La mayoría de las plataformas y marcos de cuantización se basan en la prueba de retroceso de la línea K, y la plataforma FMZ también proporciona una prueba de retroceso de nivel de tick.

La primera es la cuestión del tiempo. El tiempo del precio más alto y el precio más bajo de los datos de la línea K no se dan y no necesitan ser considerados, pero los precios de apertura y cierre más importantes no son el tiempo de apertura y cierre. Incluso las variedades comerciales menos populares a menudo no tienen comercio por más de diez segundos, y cuando hacemos pruebas de retroceso a la estrategia de múltiples variedades, a menudo asumimos que su precio de apertura y precio de cierre son los mismos, que también se basa en la prueba de retroceso del precio de cierre.

Imagínese usar la línea de nivel de minuto K para probar el arbitraje de dos variedades. La diferencia entre ellas es generalmente de 10 yuanes ((o dólares). Ahora, a las 10:01, el precio de cierre del contrato A es de 100, el contrato B es de 112 y la diferencia es de 12 yuanes. Así que la estrategia comienza a cubrirse. En cierto momento, la diferencia de precio regresó, y la estrategia obtuvo una ganancia de retorno de 2 yuanes.

Pero la situación real puede ser que a las 10:00:45, el contrato A produjo una transacción de 100 yuanes, después de lo cual no hubo transacción, el contrato B tuvo una transacción de 112 yuanes a las 10:00:58, a las 10:01:00 Ambos precios no existen. ¿Cuál es el precio de mercado en este momento, y cuánto puede obtener la operación de cobertura? No puedo saber. Una situación posible es: a las 10:00:58, el precio de la orden pendiente de Buy 1 y Sell 1 del contrato A es101.9En el102.1, y no hay diferencia de 2 yuanes en absoluto.

El segundo es el problema de emparejamiento. El emparejamiento real es la prioridad de precio y la prioridad de tiempo. Si el comprador excede el precio de Vender 1, generalmente negociará directamente al precio de Vender 1, de lo contrario entrará en el libro de pedidos pendiente y esperará. Los datos de la línea K obviamente no tienen el precio de Comprar 1 y Vender 1, es imposible simular el nivel de emparejamiento de precios en detalle.

El último es el impacto de la estrategia en sí misma en el mercado. Si se trata de una prueba de retroceso de fondos de pequeña cantidad, el impacto no es grande. Pero si el volumen de transacciones es grande, tendrá un impacto en el mercado. No solo el deslizamiento de precios será grande cuando colocas una orden de gran volumen, si compras una orden larga ejecutada, este tipo de acción realmente aprovecha las órdenes de otros comerciantes que originalmente querían comprar, el efecto "mariposa" tendrá un impacto en el mercado. Este efecto no se puede cuantificar. Solo podemos decir por experiencia que el comercio de alta frecuencia solo puede acomodar pequeños fondos.

Prueba de retroceso basada en la profundidad y el tick en tiempo real

FMZ proporciona backtest de nivel real, que puede obtener datos históricos reales20 layer depth price, de segundo nivel en tiempo realTicks, Each Individual TransactionBasándose en estas características, FMZ creó una función de reproducción de transacciones en tiempo real.

Este tipo de cantidad de datos de backtest es muy grande, y la velocidad de backtest también es muy lenta, generalmente solo puede backtest durante dos días. Para estrategias de frecuencia relativamente alta o de tiempo crítico, es necesaria una backtest a nivel de mercado real.

El mecanismo de emparejamiento actual es que si la orden de compra es mayor que el Vender 1, se emparejará completamente inmediatamente sin mirar la cantidad, y si es menor que el Vender 1, entrará en la cola de coincidencia para esperar.

img

Mecanismo de prueba posterior basado en el flujo de transacciones orden por orden

Hay muy poca información en la línea K, y la profundidad del precio también puede ser una profundidad falsa, pero hay un tipo de datos que es la voluntad de transacción real del mercado, que refleja el historial de transacciones más real, es decir,Each Individual TransactionEste artículo propondrá un sistema de backtest de alta frecuencia basado en el flujo de pedidos, que reducirá en gran medida el volumen de datos de backtest a nivel de mercado real y, en cierta medida, simulará el impacto del volumen de operaciones en el mercado.

He descargado la transacción de los últimos 5 días Binance XTZ contrato perpetuo (dirección de descarga:https://www.fmz.com/upload/asset/1ff487b007e1a848ead.csv), como una variedad poco popular, tiene un total de 213000 datos de transacciones, veamos primero la composición de los datos:

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

Los datos son una lista bidimensional, ordenada en orden cronológico. Los significados específicos son los siguientes: nombre de variedad, precio de la transacción, marca de tiempo de la transacción, cantidad de la transacción, si se trata de una transacción activa de orden de venta.Makery el vendedor es un activoTaker, los últimos datos sonTrue.

En primer lugar, de acuerdo con la dirección de la transacción, puede especular con bastante precisión sobre el Buy 1 y Sell 1 en el mercado. Si es una orden de venta activa, entonces el precio de Buy 1 en este momento es el precio de la transacción, si es una orden de compra activa, el precio de Sell 1 será el precio de la transacción. Si hay una nueva transacción, entonces todo el precio se renovará y actualizará. El último resultado se conservará si no hay renovación y actualización. Es fácil introducir el último momento de los datos anteriores, el precio de Buy 1 es 2.903, y el Sell 1 es 2.904.

De acuerdo con el flujo de pedidos, se puede emparejar de la siguiente manera: tomar una orden de compra como ejemplo, el precio esprice, la cantidad de pedido esamount, entonces comprar y vender 1 en este momento sonbidyaskEn el caso de lospricees inferior aasky superior abid, entonces se juzga comomakerPrimero, y la prioridad se puede igualar para hacer un trato, entonces todos los tratos con un precio de transacción inferior o igual a lapricedurante el tiempo de existencia de la orden se combinará con este orden (sipricees menor o igual abid, no se da prioridad a la transacción.pricese corresponden con este orden.)

El precio correspondiente esprice, y el volumen es el volumen de transacciones deEach Individual Transaction, hasta que el pedido se complete por completo o se cancele.ask, se juzga como untakerDespués de eso, durante el tiempo en que existe la orden, todas las operaciones con un precio de transacción inferior o igual apricese coinciden con esta orden, y el precio de coincidencia es el precio de transacción de laEach Individual TransactionLa distinción entremakerytakerEn el caso de las estrategias de alta frecuencia, esta diferencia debe tenerse en cuenta.

Es fácil ver un problema con este tipo de coincidencia.takerEn primer lugar, no consideramos el volumen de órdenes pendientes, incluso si hay algunos datos, directamente juzgar la transacción también ha cambiado la profundidad del precio, afectando al mercado.

Basado en el emparejamiento de nuevas órdenes, es equivalente a reemplazar las órdenes existentes en el historial con sus órdenes. En cualquier caso, no excederá el límite del propio volumen de negociación del mercado, y la ganancia final no puede exceder la ganancia máxima generada por el mercado. Parte del mecanismo de emparejamiento también afecta el volumen de órdenes, que a su vez afecta los ingresos de la estrategia, lo que refleja cuantitativamente la capacidad de la estrategia.

Si el precio de compra de la orden es igual a Buy 1, todavía existe una cierta probabilidad de que el precio de compra coincida con Buy 1, este tipo de situación no se considerará aquí.

Código correspondiente

Los objetos de intercambio pueden referirse a la introducción al principio, básicamente sin cambios, sólo añadiendo la diferencia entremakerytakerEn la actualidad, la mayoría de los sistemas de prueba de compatibilidad se utilizan para realizar pruebas de compatibilidad con los sistemas de prueba de compatibilidad.

 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

Algunos detalles a tener en cuenta:

  • Cuando hay una nueva transacción, primero debemos hacer coincidir el pedido, y luego colocar el pedido de acuerdo con el último precio.

  • Cada orden tiene dos atributos: creadorsi es un creador, prioridadprioridad de coincidencia, tomando una orden de compra como ejemplo, cuando el precio de compra es inferior a Vender 1, se marca comomaker, y cuando el precio de compra es mayor que Buy 1, se marca comoPriority matching, prioritydetermina si el precio es igual o no al precio de compra, y el fabricante determina la comisión de transacción.

  • ElmakerypriorityCuando el precio es mayor que el precio de compra, el volumen restante será el valor de la compra.maker.

  • EstrategiaintervalEn el caso de la Comisión, la Comisión no puede hacer nada.

Prueba posterior de la estrategia de red

Finalmente, es la etapa de backtest real. Vamos a backtest una de las estrategias de la cuadrícula más clásica aquí para ver si podemos lograr los resultados esperados. El principio de la estrategia es que cada vez que el precio aumenta en un 1%, mantenemos una orden corta de un cierto valor (inversamente, mantenemos una orden larga), calcular la orden de compra y la orden de venta por adelantado.Grid('XTZ', 100, 0.3, 1000, maker_fee=-0.00002, taker_fee=0.0003)función, los parámetros son: el par de operaciones, el precio se desvía del valor de retención de 1%, la densidad de órdenes pendientes es de 0.3%, el intervalo de reposoms, comisiones por órdenes pendientes y comisiones por órdenes ejecutadas.

El precio de mercado de XTZ ha estado en shock durante los últimos 5 días, lo cual es muy adecuado para las redes.

img

En primer lugar, se hace un backtest sobre el efecto de las diferentes posiciones de tenencia en el rendimiento de los beneficios.

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 cuatro grupos fueron sometidos a backtest, el valor de las posiciones de tenencia fue de 100, 1000, 10000, 100.000, y el tiempo total de backtest fue de 1,3 s. Los resultados son los siguientes:

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

Se puede ver que las ganancias realizadas finales son de 28.4%, 27.5%, 26.9% y 22.6% del valor de la posición de tenencia respectivamente. Esto también está en línea con la situación real. Cuanto mayor sea el valor de la posición de tenencia, mayor sea el valor de la orden pendiente, más probable es que se produzca una transacción parcial, y menor sea la ganancia realizada final en relación con el monto de la orden pendiente.

img

También podemos hacer pruebas de retroceso del impacto de diferentes parámetros en los ingresos de las pruebas de retroceso, como la densidad de pedidos pendientes, el tiempo de sueño, la tarifa de transacción, etc. Tomemos el tiempo de sueño como ejemplo, cambiémoslo a 100 ms y comparemos el tiempo de sueño a 1000 ms para ver el retorno de ganancias. Los resultados de las pruebas de retroceso son los siguientes:

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

Los ingresos han aumentado un poco, porque la estrategia sólo envía un conjunto de pedidos, algunos pedidos no serán capaces de ejecutar los precios fluctuantes porque no tienen tiempo para cambiar, y la reducción del tiempo de sueño mejora este problema.

En resumen

Este artículo propone innovadoramente un nuevo sistema de backtest basado en el flujo de pedidos, que puede simular parcialmente la situación de coincidencia de pedidos pendientes, pedidos ejecutados, pedidos ejecutados parcialmente, retrasos, etc., y refleja parcialmente el impacto de la cantidad de fondos de estrategia en los ingresos. Para estrategias de alta frecuencia y cobertura, tiene un valor de referencia importante.


Relacionados

Más.