Explicación detallada de la optimización del parámetro de la estrategia de la red de contratos perpetuos

El autor:- ¿ Por qué?, Creado: 2023-12-11 15:04:25, Actualizado: 2024-01-02 21:24:31

img

La estrategia de red perpetua es una estrategia clásica popular en la plataforma FMZ. En comparación con la red spot, no es necesario tener monedas, y se puede agregar apalancamiento, lo que es mucho más conveniente que la red spot. Sin embargo, ya que no es posible realizar pruebas de retroceso en la plataforma FMZ Quant directamente, no es propicio para seleccionar monedas y determinar la optimización de parámetros. En este artículo, presentaremos el proceso completo de backtesting de Python, incluida la recopilación de datos, el marco de backtesting, las funciones de backtesting, la optimización de parámetros, etc. Puede probarlo usted mismo en el cuaderno juypter.

Recopilación de datos

Generalmente, es suficiente usar datos de línea K. Para mayor precisión, cuanto menor sea el período de línea K, mejor. Sin embargo, para equilibrar el tiempo de backtest y el volumen de datos, en este artículo, usamos 5min de datos de los últimos dos años para backtesting. El volumen de datos final superó las 200,000 líneas. Elegimos DYDX como moneda. Por supuesto, la moneda específica y el período de línea K se pueden seleccionar de acuerdo con sus propios intereses.

import requests
from datetime import date,datetime
import time
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import requests, zipfile, io
%matplotlib inline

def GetKlines(symbol='BTC',start='2020-8-10',end='2021-8-10',period='1h'):
    Klines = []
    start_time = int(time.mktime(datetime.strptime(start, "%Y-%m-%d").timetuple()))*1000
    end_time = int(time.mktime(datetime.strptime(end, "%Y-%m-%d").timetuple()))*1000
    while start_time < end_time:
        res = requests.get('https://fapi.binance.com/fapi/v1/klines?symbol=%sUSDT&interval=%s&startTime=%s&limit=1000'%(symbol,period,start_time))
        res_list = res.json()
        Klines += res_list
        start_time = res_list[-1][0]
    return pd.DataFrame(Klines,columns=['time','open','high','low','close','amount','end_time','volume','count','buy_amount','buy_volume','null']).astype('float')

df = GetKlines(symbol='DYDX',start='2022-1-1',end='2023-12-7',period='5m')
df = df.drop_duplicates()

Marco de pruebas de retroceso

Para backtesting, seguimos eligiendo el marco comúnmente utilizado que admite contratos perpetuos USDT en múltiples monedas, que es simple y fácil de usar.

class Exchange:
    
    def __init__(self, trade_symbols, fee=0.0004, initial_balance=10000):
        self.initial_balance = initial_balance #Initial assets
        self.fee = fee
        self.trade_symbols = trade_symbols
        self.account = {'USDT':{'realised_profit':0, 'unrealised_profit':0, 'total':initial_balance, 'fee':0}}
        for symbol in trade_symbols:
            self.account[symbol] = {'amount':0, 'hold_price':0, 'value':0, 'price':0, 'realised_profit':0,'unrealised_profit':0,'fee':0}
            
    def Trade(self, symbol, direction, price, amount):
        
        cover_amount = 0 if direction*self.account[symbol]['amount'] >=0 else min(abs(self.account[symbol]['amount']), amount)
        open_amount = amount - cover_amount
        self.account['USDT']['realised_profit'] -= price*amount*self.fee #Deduction of handling fee
        self.account['USDT']['fee'] += price*amount*self.fee
        self.account[symbol]['fee'] += price*amount*self.fee

        if cover_amount > 0: #Close the position first.
            self.account['USDT']['realised_profit'] += -direction*(price - self.account[symbol]['hold_price'])*cover_amount  #Profits
            self.account[symbol]['realised_profit'] += -direction*(price - self.account[symbol]['hold_price'])*cover_amount
            
            self.account[symbol]['amount'] -= -direction*cover_amount
            self.account[symbol]['hold_price'] = 0 if self.account[symbol]['amount'] == 0 else self.account[symbol]['hold_price']
            
        if open_amount > 0:
            total_cost = self.account[symbol]['hold_price']*direction*self.account[symbol]['amount'] + price*open_amount
            total_amount = direction*self.account[symbol]['amount']+open_amount
            
            self.account[symbol]['hold_price'] = total_cost/total_amount
            self.account[symbol]['amount'] += direction*open_amount
                    
    
    def Buy(self, symbol, price, amount):
        self.Trade(symbol, 1, price, amount)
        
    def Sell(self, symbol, price, amount):
        self.Trade(symbol, -1, price, amount)
        
    def Update(self, close_price): #Updating of assets
        self.account['USDT']['unrealised_profit'] = 0
        for symbol in self.trade_symbols:
            self.account[symbol]['unrealised_profit'] = (close_price[symbol] - self.account[symbol]['hold_price'])*self.account[symbol]['amount']
            self.account[symbol]['price'] = close_price[symbol]
            self.account[symbol]['value'] = abs(self.account[symbol]['amount'])*close_price[symbol]
            self.account['USDT']['unrealised_profit'] += self.account[symbol]['unrealised_profit']
        self.account['USDT']['total'] = round(self.account['USDT']['realised_profit'] + self.initial_balance + self.account['USDT']['unrealised_profit'],6)

Función de prueba de retroceso de la red

El principio de la estrategia de la red es muy simple. Vender cuando el precio sube y comprar cuando el precio cae. En concreto, implica tres parámetros: precio inicial, espaciamiento de la red y valor comercial. El mercado de DYDX fluctúa mucho.

img

symbol = 'DYDX'
value = 100
pct = 0.01

def Grid(fee=0.0002, value=100, pct=0.01, init = df.close[0]):
    e = Exchange([symbol], fee=0.0002, initial_balance=10000)
    init_price = init
    res_list = [] #For storing intermediate results
    for row in df.iterrows():
        kline = row[1] #To backtest a K-line will only generate one buy order or one sell order, which is not particularly accurate.
        buy_price = (value / pct - value) / ((value / pct) / init_price + e.account[symbol]['amount']) #The buy order price, as it is a pending order transaction, is also the final aggregated price
        sell_price = (value / pct + value) / ((value / pct) / init_price + e.account[symbol]['amount'])
        if kline.low < buy_price: #The lowest price of the K-line is lower than the current pending order price, the buy order is filled
            e.Buy(symbol,buy_price,value/buy_price)
        if kline.high > sell_price:
            e.Sell(symbol,sell_price,value/sell_price)
        e.Update({symbol:kline.close})
        res_list.append([kline.time, kline.close, e.account[symbol]['amount'], e.account['USDT']['total']-e.initial_balance,e.account['USDT']['fee'] ])
    res = pd.DataFrame(data=res_list, columns=['time','price','amount','profit', 'fee'])
    res.index = pd.to_datetime(res.time,unit='ms')
    return res

img

Impacto inicial del precio

La configuración del precio inicial afecta a la posición inicial de la estrategia. El precio inicial predeterminado para la prueba de retroceso ahora es el precio inicial en el inicio, es decir, no se mantiene ninguna posición en el inicio. Y sabemos que la estrategia de red realizará todas las ganancias cuando el precio regrese a la etapa inicial, por lo que si la estrategia puede predecir correctamente el mercado futuro cuando se lanza, los ingresos mejorarán significativamente. Aquí, establecemos el precio inicial a 3U y luego la prueba de retroceso. Al final, el descenso máximo fue de 9200U, y la ganancia final fue de 13372U. La estrategia final no mantiene posiciones. La ganancia es todas las ganancias de fluctuación, y la diferencia entre las ganancias de los parámetros predeterminados es la pérdida de posición causada por un juicio inexacto del precio final.

Sin embargo, si el precio inicial se establece en 3U, la estrategia será corta al principio y mantendrá un gran número de posiciones cortas.

img

Configuración de espaciado de la cuadrícula

El intervalo de la cuadrícula determina la distancia entre las órdenes pendientes. Obviamente, cuanto menor sea el intervalo, más frecuentes sean las transacciones, menor sea la ganancia de una sola transacción y mayor sea la tarifa de manejo. Sin embargo, vale la pena señalar que a medida que el intervalo de la cuadrícula se vuelve más pequeño y el valor de la cuadrícula permanece sin cambios, cuando el precio cambia, las posiciones totales aumentarán, y los riesgos enfrentados son completamente diferentes. Por lo tanto, para probar el efecto del intervalo de la cuadrícula, es necesario convertir el valor de la cuadrícula.

Como el backtest utiliza datos de 5 millones de líneas K, y cada línea K solo se negocia una vez, lo que obviamente no es realista, especialmente porque la volatilidad de las monedas digitales es muy alta. Un espaciamiento más pequeño perderá muchas transacciones en el backtesting en comparación con el comercio en vivo. Solo un espaciamiento más grande tendrá valor de referencia. En este mecanismo de backtesting, las conclusiones extraídas no son precisas. A través del backtesting de datos de flujo de pedidos a nivel de tick, el espaciamiento óptimo de la cuadrícula debe ser de 0.005-0.01.

for p in [0.0005, 0.001 ,0.002 ,0.005, 0.01, 0.02, 0.05]:
    res = Grid( fee=0.0002, value=value*p/0.01, pct=p, init =3)
    print(p, round(min(res['profit']),0), round(res['profit'][-1],0), round(res['fee'][-1],0))
    
0.0005 -8378.0 144.0 237.0
0.001 -9323.0 1031.0 465.0
0.002 -9306.0 3606.0 738.0
0.005 -9267.0 9457.0 781.0
0.01 -9228.0 13375.0 550.0
0.02 -9183.0 15212.0 309.0
0.05 -9037.0 16263.0 131.0

Valor de la transacción de la red

Como se mencionó anteriormente, cuando las fluctuaciones son las mismas, cuanto mayor sea el valor de la tenencia, el riesgo es proporcional. Sin embargo, siempre que no haya una disminución rápida, el 1% de los fondos totales y el 1% del espaciamiento de la red deberían poder hacer frente a la mayoría de las condiciones del mercado. En este ejemplo de DYDX, una caída de casi el 90% también desencadenó una liquidación. Sin embargo, debe tenerse en cuenta que DYDX cae principalmente. Cuando la estrategia de red va largo cuando cae, caerá en un 100% como máximo, mientras que no hay límite en el aumento, y el riesgo es mucho mayor. Por lo tanto, Grid Strategy recomienda a los usuarios que elijan solo el modo de posición larga para las monedas que creen potencial.


Más.