Подробное объяснение оптимизации параметров стратегии сетки постоянных контрактов

Автор:Лидия., Создано: 2023-12-11 15:04:25, Обновлено: 2024-01-02 21:24:31

img

Стратегия вечной сетки является популярной классической стратегией на платформе FMZ. По сравнению со спотовой сеткой, нет необходимости иметь валюты, и можно добавить рычаг давления, что намного удобнее, чем спотовая сетка. Однако, поскольку невозможно напрямую делать обратные тесты на платформе FMZ Quant, это не способствует скринингу валют и определению оптимизации параметров. В этой статье мы представим полный процесс обратного тестирования Python, включая сбор данных, структуру обратного тестирования, функции обратного тестирования, оптимизацию параметров и т. Д. Вы можете попробовать это самостоятельно в блокноте juypter.

Сбор данных

В целом, достаточно использовать данные K-линии. Для точности, чем меньше период K-линии, тем лучше. Однако, чтобы сбалансировать время бэкстеста и объем данных, в этой статье мы используем 5 мин данных за последние два года для бэкстеста. Окончательный объем данных превысил 200 000 линий. Мы выбираем DYDX в качестве валюты. Конечно, конкретную валюту и период K-линии можно выбрать в соответствии с вашими собственными интересами.

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

Рамочная система обратного тестирования

Для обратного тестирования мы продолжаем выбирать широко используемую структуру, которая поддерживает USDT вечные контракты в нескольких валютах, которая проста и проста в использовании.

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)

Функция обратного теста сетки

Принцип стратегии сетки очень прост. Продайте, когда цена растет, и покупайте, когда цена падает. Она конкретно включает в себя три параметра: начальную цену, расстояние между сетками и торговую стоимость. Рынок DYDX сильно колеблется. Он упал с первоначального минимума в 8,6U до 1U, а затем снова поднялся до 3U на недавнем бычьем рынке.

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

Первоначальное влияние на цену

Установка начальной цены влияет на начальную позицию стратегии. По умолчанию начальная цена для бэкстеста прямо сейчас - это начальная цена при запуске, то есть никакая позиция не удерживается при запуске. И мы знаем, что сетевая стратегия реализует все прибыли, когда цена вернется на начальную стадию, поэтому если стратегия может правильно предсказать будущий рынок, когда она будет запущена, доход значительно улучшится. Здесь мы установили начальную цену на 3U, а затем бэкстест. В конце концов, максимальный вывод был 9200U, а конечная прибыль составила 13372U. Конечная стратегия не удерживает позиции. Прибыль - это все колебания прибыли, а разница между прибылью параметров по умолчанию - это потеря позиции, вызванная неточным суждением о конечной цене.

Однако, если начальная цена установлена на 3U, стратегия будет идти коротко в начале и держать большое количество коротких позиций.

img

Настройки расстояния сетки

Расположение сетки определяет расстояние между ожидаемыми ордерами. Очевидно, чем меньше расстояние, тем чаще транзакции, тем меньше прибыль от одной сделки, и чем выше плата за обработку. Однако стоит отметить, что по мере того, как расстояние сетки становится меньше, а стоимость сетки остается неизменной, когда цена меняется, общий объем позиций увеличится, и риски, с которыми сталкиваются, совершенно разные. Поэтому, чтобы проверить эффект расстояния сетки, необходимо конвертировать стоимость сетки.

Поскольку бэкстест использует 5 миллионов данных K-линии, и каждая K-линия торгуется только один раз, что, очевидно, нереально, особенно поскольку волатильность цифровых валют очень высока. Меньшее расстояние пропустит многие транзакции в бэкстестинге по сравнению с живой торговлей. Только большое расстояние будет иметь референтное значение. В этом механизме бэкстестинга выводы, сделанные, не точны.

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

Стоимость сетевой транзакции

Как упоминалось ранее, когда колебания одинаковы, чем больше стоимость холдинга, тем пропорциональнее риск. Однако, пока не происходит быстрое снижение, 1% от общего количества средств и 1% от расстояния между сетками должны быть в состоянии справиться с большинством рыночных условий. В этом примере DYDX падение почти 90% также вызвало ликвидацию. Однако следует отметить, что DYDX в основном падает. Когда стратегия сетки длинная, когда она падает, она упадет максимум на 100%, в то время как нет ограничения на рост, и риск намного выше. Поэтому Grid Strategy рекомендует пользователям выбирать только режим длинной позиции для валют, в которые они верят.


Больше