Стратегия вечной сетки является популярной классической стратегией на платформе 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 на недавнем бычьем рынке.
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
Установка начальной цены влияет на начальную позицию стратегии. По умолчанию начальная цена для бэкстеста прямо сейчас - это начальная цена при запуске, то есть никакая позиция не удерживается при запуске. И мы знаем, что сетевая стратегия реализует все прибыли, когда цена вернется на начальную стадию, поэтому если стратегия может правильно предсказать будущий рынок, когда она будет запущена, доход значительно улучшится. Здесь мы установили начальную цену на 3U, а затем бэкстест. В конце концов, максимальный вывод был 9200U, а конечная прибыль составила 13372U. Конечная стратегия не удерживает позиции. Прибыль - это все колебания прибыли, а разница между прибылью параметров по умолчанию - это потеря позиции, вызванная неточным суждением о конечной цене.
Однако, если начальная цена установлена на 3U, стратегия будет идти коротко в начале и держать большое количество коротких позиций.
Расположение сетки определяет расстояние между ожидаемыми ордерами. Очевидно, чем меньше расстояние, тем чаще транзакции, тем меньше прибыль от одной сделки, и чем выше плата за обработку. Однако стоит отметить, что по мере того, как расстояние сетки становится меньше, а стоимость сетки остается неизменной, когда цена меняется, общий объем позиций увеличится, и риски, с которыми сталкиваются, совершенно разные. Поэтому, чтобы проверить эффект расстояния сетки, необходимо конвертировать стоимость сетки.
Поскольку бэкстест использует 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 рекомендует пользователям выбирать только режим длинной позиции для валют, в которые они верят.