
영구 그리드 전략은 플랫폼에서 인기 있는 고전적 전략입니다. 스팟 그리드와 비교해 보면 코인을 보유할 필요가 없고 레버리지를 추가할 수 있어 스팟 그리드보다 훨씬 편리합니다. 그러나 Inventor Quantitative Platform에서 직접 백테스트를 하는 것은 불가능하기 때문에 통화를 선별하고 매개변수 최적화를 결정하는 데 도움이 되지 않습니다. 이 글에서는 데이터 수집, 백테스트 프레임워크, 백테스트 기능, 매개변수 최적화를 포함한 전체 Python 백테스트 프로세스를 소개합니다. 등등. Juypter Notebook에서 직접 시도해 볼 수 있습니다.
일반적으로 K-라인 데이터로 충분합니다. 정확성을 위해 K-라인 기간이 짧을수록 좋습니다. 그러나 백테스트 시간과 데이터 볼륨은 균형을 이루어야 합니다. 이 글에서는 백테스트를 위해 지난 2년 동안의 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 #初始的资产
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 #扣除手续费
self.account['USDT']['fee'] += price*amount*self.fee
self.account[symbol]['fee'] += price*amount*self.fee
if cover_amount > 0: #先平仓
self.account['USDT']['realised_profit'] += -direction*(price - self.account[symbol]['hold_price'])*cover_amount #利润
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): #对资产进行更新
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로 다시 상승했습니다. 이 전략의 기본 초기 가격은 8.6U로 그리드에 매우 불리합니다. 전략이지만 기본 매개변수 백테스트 2년간 총 수익은 9200U였고, 어느 시점에서 7500U의 손실이 있었습니다.

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 row in df.iterrows():
kline = row[1] #这样会测一根K线只会产生一个买单或一个卖单,不是特别精确
buy_price = (value / pct - value) / ((value / pct) / init_price + e.account[symbol]['amount']) #买单价格,由于是挂单成交,也是最终的撮合价格
sell_price = (value / pct + value) / ((value / pct) / init_price + e.account[symbol]['amount'])
if kline.low < buy_price: #K线最低价低于当前挂单价,买单成交
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로 설정된 경우 전략은 처음에 숏 포지션을 취하고 많은 수의 숏 포지션을 보유하게 됩니다. 이 예에서 17,000 U의 숏 포지션을 직접 보유하므로 더 큰 위험에 직면하게 됩니다.

그리드 간격은 보류 주문 간의 거리를 결정합니다. 분명히 간격이 작을수록 거래가 더 빈번해지고, 단일 거래의 이익이 낮아지고, 처리 수수료가 높아집니다. 하지만 그리드 간격이 작아지고 그리드 값이 변하지 않을 경우 가격이 변할 때 전체 포지션이 증가하고 직면하는 위험도 완전히 달라진다는 점에 유의해야 합니다. 따라서 그리드 간격의 효과를 백테스트하려면 그리드 값을 변환하는 것이 필요합니다.
백테스트는 5mK 라인 데이터를 사용하고 K 라인에서는 단 하나의 거래만 수행됩니다. 이는 분명히 현실과 맞지 않습니다. 특히 디지털 통화의 변동성이 매우 크기 때문입니다. 간격이 작으면 실제 거래에 비해 백테스팅에서 많은 거래를 놓칠 것입니다. 간격이 클수록 참조 값이 생깁니다. 이러한 백테스팅 메커니즘에 따르면 도출된 결론은 정확하지 않습니다. 틱 레벨 주문 흐름 데이터의 백테스팅을 통해 최적의 그리드 간격은 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
앞서 언급했듯이 변동성이 동일할 때 보유 가치가 클수록 위험 비례 방식이 높아집니다. 그러나 급격한 하락이 아닌 한 전체 자금의 1%와 그리드 간격의 1%를 합친 대부분의 시장 상황에 대처할 수 있어야 합니다. 이 DYDX 사례에서는 가격이 약 90% 하락하면서 마진 콜이 발생했습니다. 하지만 DYDX는 주로 하락한다는 점에 유의하세요. 하락할 때 그리드 전략은 롱으로 가고 최대 하락은 100%입니다. 하지만 상승에는 제한이 없고 위험은 훨씬 더 높습니다. 따라서 그리드 전략은 사용자에게 잠재력이 있다고 생각되는 통화만 선택하고 매수 포지션을 취할 것을 권장합니다.
회귀 가격은 초기 가격입니다. 현재 가격과 초기 가격의 차이와 그리드 크기는 얼마나 많은 포지션을 보유해야 하는지 결정합니다. 회귀 가격이 현재 가격보다 높게 설정되면 그리드 전략은 롱으로 전환되고 그 반대도 마찬가지다. 기본 회귀 가격은 전략이 시작된 가격입니다. 위험을 줄이기 위해 롱 전용 그리드를 사용하는 것이 좋습니다. 자연스러운 아이디어는 가격이 상승하더라도 조정하지 않고도 롱 포지션을 유지할 수 있도록 회귀 가격을 변경할 수 있는지 여부입니다. 당신 자신. 올해 비트코인 시장을 예로 들면, 비트코인 가격은 연초 15,000에서 연말 43,000으로 상승했습니다. 연초부터 그리드 전략을 실행하기 시작하면 기본 수익률은 15,000이고 롱과 숏 트레이드를 모두 수행합니다. 구체적인 수익률은 아래 그림과 같습니다. BTC로 인해 돈을 완전히 잃게 됩니다. 상승합니다. 롱온리 모드가 올바르게 설정되지 않은 경우에도 포지션을 유지하지 않고도 가격이 빠르게 한도를 초과할 수 있습니다.

첫째, 전략이 시작될 때 회귀 가격은 시작 시점 가격의 1.6배로 설정됩니다. 이런 식으로 그리드 전략은 가격이 1.6배에서 현재 가격으로 하락한 것으로 간주하고 롱 포지션을 유지하기 시작합니다. 이 가격 차이의 일부로 인해 발생한 포지션입니다. 이후 가격이 초과하면 반환 가격이 /1.6일 때 초기 가격이 재설정되므로 차이의 최소 60%는 항상 롱 포지션에 사용할 수 있습니다. 백테스트 결과는 다음과 같습니다.

물론, 시장에 대해 더 낙관적이라면 이 비율을 더 큰 값으로 설정할 수 있으며, 최종 수익은 그에 따라 증가합니다. 물론, 시장이 하락하면 이 설정은 포지션을 유지하는 위험도 증가시킵니다.