Explicação detalhada da otimização do parâmetro de estratégia da rede de contratos perpétuos

Autora:Lydia., Criado: 2023-12-11 15:04:25, Atualizado: 2024-01-02 21:24:31

img

A estratégia da grade perpétua é uma estratégia clássica popular na plataforma FMZ. Em comparação com a grade de spot, não há necessidade de ter moedas e a alavancagem pode ser adicionada, o que é muito mais conveniente do que a grade de spot. No entanto, como não é possível fazer backtest na plataforma FMZ Quant diretamente, não é propício para a triagem de moedas e determinar a otimização de parâmetros. Neste artigo, apresentaremos o processo completo de backtesting do Python, incluindo coleta de dados, estrutura de backtesting, funções de backtesting, otimização de parâmetros, etc. Você pode experimentá-lo sozinho no notebook juypter.

Recolha de dados

Em geral, é suficiente usar dados de linha K. Para precisão, quanto menor o período de linha K, melhor. No entanto, para equilibrar o tempo de backtest e o volume de dados, neste artigo, usamos 5min de dados dos últimos dois anos para backtest. O volume de dados final excedeu 200.000 linhas. Escolhemos DYDX como a moeda. É claro, a moeda específica e o período de linha K podem ser selecionados de acordo com seus próprios interesses.

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()

Quadro de backtesting

Para backtesting, continuamos a escolher a estrutura comumente usada que suporta contratos perpétuos USDT em várias moedas, que é simples e 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)

Função de teste de retalho

O princípio da estratégia da grade é muito simples. Vender quando o preço sobe e comprar quando o preço cai. Especificamente envolve três parâmetros: preço inicial, espaçamento da grade e valor de negociação. O mercado da DYDX flutua muito. Caiu do mínimo inicial de 8,6U para 1U, e depois subiu de volta para 3U no recente mercado de touros. O preço inicial padrão da estratégia é de 8,6U, o que é muito desfavorável para a estratégia da grade, mas os parâmetros padrão testados de volta um lucro total de 9200U foi feito em dois anos, e uma perda de 7500U foi feita durante o período.

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

Efeito inicial no preço

A definição do preço inicial afeta a posição inicial da estratégia. O preço inicial padrão para o backtest agora é o preço inicial na inicialização, ou seja, nenhuma posição é mantida na inicialização. E sabemos que a estratégia de grade irá realizar todos os lucros quando o preço retornar ao estágio inicial, por isso, se a estratégia pode prever corretamente o mercado futuro quando for lançada, o rendimento será significativamente melhorado. Aqui, definimos o preço inicial para 3U e depois backtest. No final, o drawdown máximo foi de 9200U, e o lucro final foi de 13372U. A estratégia final não mantém posições. O lucro é todos os lucros de flutuação, e a diferença entre os lucros dos parâmetros padrão é a perda de posição causada por julgamento incorreto do preço final.

No entanto, se o preço inicial for definido em 3U, a estratégia será curta no início e manterá um grande número de posições curtas.

img

Configurações de espaçamento da grade

O espaçamento da grade determina a distância entre as ordens pendentes. Obviamente, quanto menor o espaçamento, mais frequentes as transações, menor o lucro de uma única transação e maior a taxa de manipulação. No entanto, vale a pena notar que, à medida que o espaçamento da grade se torna menor e o valor da grade permanece inalterado, quando o preço muda, as posições totais aumentam e os riscos enfrentados são completamente diferentes. Portanto, para testar o efeito do espaçamento da grade, é necessário converter o valor da grade.

Uma vez que o backtest usa dados de 5m K-line, e cada K-line é negociado apenas uma vez, o que é obviamente irrealista, especialmente porque a volatilidade das moedas digitais é muito alta. Um espaçamento menor perderá muitas transações no backtesting em comparação com a negociação ao vivo. Apenas um espaçamento maior terá valor de referência. Neste mecanismo de backtesting, as conclusões tiradas não são precisas. Através do backtesting de dados de fluxo de pedidos em nível de tick, o espaçamento de grade ideal deve ser 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 da transacção da rede

Como mencionado anteriormente, quando as flutuações são as mesmas, quanto maior o valor da participação, o risco é proporcional. No entanto, desde que não haja um rápido declínio, 1% dos fundos totais e 1% do espaçamento da grade devem ser capazes de lidar com a maioria das condições de mercado. Neste exemplo do DYDX, uma queda de quase 90% também desencadeou uma liquidação. No entanto, deve-se notar que o DYDX cai principalmente. Quando a estratégia da grade fica longa quando cai, ela cairá em 100% no máximo, enquanto não há limite para o aumento, e o risco é muito maior. Portanto, a Grid Strategy recomenda que os usuários escolham apenas o modo de posição longa para moedas que acreditam ter potencial.


Mais.