Strategi grid abadi adalah strategi klasik yang populer di platform FMZ. Dibandingkan dengan grid spot, tidak perlu memiliki mata uang, dan leverage dapat ditambahkan, yang jauh lebih nyaman daripada grid spot. Namun, karena tidak mungkin melakukan backtesting di FMZ Quant Platform secara langsung, tidak kondusif untuk menyaring mata uang dan menentukan optimasi parameter.
Secara umum, cukup untuk menggunakan data K-line. Untuk akurasi, semakin kecil periode K-line, semakin baik. Namun, untuk menyeimbangkan waktu backtest dan volume data, dalam artikel ini, kami menggunakan 5min data dari dua tahun terakhir untuk backtesting. Volume data akhir melebihi 200.000 baris. Kami memilih DYDX sebagai mata uang. Tentu saja, mata uang tertentu dan periode K-line dapat dipilih sesuai dengan minat Anda sendiri.
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()
Untuk backtesting, kami terus memilih kerangka kerja yang umum digunakan yang mendukung kontrak abadi USDT dalam beberapa mata uang, yang sederhana dan mudah digunakan.
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)
Prinsip strategi grid sangat sederhana. Jual ketika harga naik dan beli ketika harga turun. Secara khusus melibatkan tiga parameter: harga awal, jarak grid, dan nilai perdagangan. Pasar DYDX berfluktuasi sangat. Ini turun dari level terendah awal 8,6U menjadi 1U, dan kemudian naik kembali menjadi 3U di pasar bull baru-baru ini. Harga awal default strategi adalah 8,6U, yang sangat tidak menguntungkan untuk strategi grid, tetapi parameter default backtested total keuntungan 9200U dibuat dalam dua tahun, dan kerugian 7500U dibuat selama periode tersebut.
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
Pengaturan harga awal mempengaruhi posisi awal strategi. Harga awal default untuk backtest saat ini adalah harga awal saat startup, yaitu tidak ada posisi yang dipegang saat startup. Dan kita tahu bahwa strategi grid akan mewujudkan semua keuntungan ketika harga kembali ke tahap awal, jadi jika strategi dapat memprediksi pasar masa depan dengan benar saat diluncurkan, pendapatan akan meningkat secara signifikan. Di sini, kita menetapkan harga awal menjadi 3U dan kemudian backtest. Pada akhirnya, penarikan maksimum adalah 9200U, dan keuntungan akhir adalah 13372U. Strategi akhir tidak memegang posisi. Keuntungan adalah semua keuntungan fluktuasi, dan perbedaan antara keuntungan dari parameter default adalah kerugian posisi yang disebabkan oleh penilaian harga akhir yang tidak akurat.
Namun, jika harga awal ditetapkan menjadi 3U, strategi akan short di awal dan memegang sejumlah besar posisi short.
Jarak kisi menentukan jarak antara pesanan yang menunggu. Jelas, semakin kecil jarak, semakin sering transaksi, semakin rendah keuntungan dari satu transaksi, dan semakin tinggi biaya penanganan. Namun, perlu dicatat bahwa sebagai jarak kisi menjadi lebih kecil dan nilai kisi tetap tidak berubah, ketika harga berubah, total posisi akan meningkat, dan risiko yang dihadapi sama sekali berbeda. Oleh karena itu, untuk menguji kembali efek dari jarak kisi, perlu untuk mengkonversi nilai kisi.
Karena backtest menggunakan data 5m K-line, dan setiap K-line hanya diperdagangkan sekali, yang jelas tidak realistis, terutama karena volatilitas mata uang digital sangat tinggi. Jarak yang lebih kecil akan kehilangan banyak transaksi dalam backtesting dibandingkan dengan perdagangan langsung. Hanya jarak yang lebih besar yang akan memiliki nilai referensi. Dalam mekanisme backtesting ini, kesimpulan yang diambil tidak akurat. Melalui backtesting data aliran pesanan tingkat tik, jarak kisi optimal harus 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
Seperti yang disebutkan sebelumnya, ketika fluktuasi sama, semakin besar nilai kepemilikan, risikonya proporsional. Namun, selama tidak ada penurunan yang cepat, 1% dari total dana dan 1% dari jarak grid harus dapat mengatasi sebagian besar kondisi pasar. Dalam contoh DYDX ini, penurunan hampir 90% juga memicu likuidasi. Namun, harus dicatat bahwa DYDX terutama jatuh. Ketika strategi grid pergi panjang ketika jatuh, itu akan turun maksimal 100%, sementara tidak ada batasan kenaikan, dan risikonya jauh lebih tinggi. Oleh karena itu, Strategi Grid merekomendasikan pengguna untuk memilih hanya mode posisi panjang untuk mata uang yang mereka yakini berpotensi.