शाश्वत ग्रिड रणनीति एफएमजेड प्लेटफॉर्म पर एक लोकप्रिय क्लासिक रणनीति है। स्पॉट ग्रिड की तुलना में, मुद्राओं की आवश्यकता नहीं है, और लीवरेज जोड़ा जा सकता है, जो स्पॉट ग्रिड की तुलना में बहुत अधिक सुविधाजनक है। हालांकि, चूंकि एफएमजेड क्वांट प्लेटफॉर्म पर सीधे बैकटेस्ट करना संभव नहीं है, इसलिए यह मुद्राओं की स्क्रीनिंग और पैरामीटर अनुकूलन निर्धारित करने के लिए अनुकूल नहीं है। इस लेख में, हम डेटा संग्रह, बैकटेस्टिंग फ्रेमवर्क, बैकटेस्टिंग फ़ंक्शन, पैरामीटर अनुकूलन, आदि सहित पूर्ण पायथन बैकटेस्टिंग प्रक्रिया का परिचय देंगे। आप इसे जूइप्टर नोटबुक में स्वयं आज़मा सकते हैं।
सामान्य तौर पर, के-लाइन डेटा का उपयोग करना पर्याप्त है। सटीकता के लिए, के-लाइन अवधि जितनी छोटी होगी, उतना ही बेहतर होगा। हालांकि, बैकटेस्ट समय और डेटा वॉल्यूम को संतुलित करने के लिए, इस लेख में, हम बैकटेस्टिंग के लिए पिछले दो वर्षों के 5min डेटा का उपयोग करते हैं। अंतिम डेटा वॉल्यूम 200,000 लाइनों से अधिक है। हम मुद्रा के रूप में DYDX चुनते हैं। बेशक, विशिष्ट मुद्रा और के-लाइन अवधि को आपकी अपनी रुचियों के अनुसार चुना जा सकता है।
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()
बैकटेस्टिंग के लिए, हम आम तौर पर इस्तेमाल किए जाने वाले ढांचे का चयन करना जारी रखते हैं जो कई मुद्राओं में यूएसडीटी स्थायी अनुबंधों का समर्थन करता है, जो सरल और उपयोग करने में आसान है।
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)
ग्रिड रणनीति का सिद्धांत बहुत सरल है। जब कीमत बढ़ती है तो बेचें और जब कीमत गिरती है तो खरीदें। इसमें विशेष रूप से तीन मापदंड शामिल हैंः प्रारंभिक मूल्य, ग्रिड अंतराल और व्यापारिक मूल्य। डीवाईडीएक्स का बाजार बहुत उतार-चढ़ाव करता है। यह 8.6U के प्रारंभिक निम्न से 1U तक गिर गया, और फिर हाल के बुल बाजार में 3U तक वापस बढ़ गया। रणनीति का डिफ़ॉल्ट प्रारंभिक मूल्य 8.6U है, जो ग्रिड रणनीति के लिए बहुत प्रतिकूल है, लेकिन डिफ़ॉल्ट मापदंडों ने बैकटेस्ट किया है। दो वर्षों में 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 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 पर सेट किया जाता है, तो रणनीति शुरुआत में छोटी हो जाएगी और बड़ी संख्या में छोटी स्थिति बनाए रखेगी। इस उदाहरण में, 17,000 U का एक छोटा आदेश सीधे रखा जाता है, इसलिए इसे अधिक जोखिमों का सामना करना पड़ता है।
ग्रिड स्पेसिंग लंबित ऑर्डर के बीच की दूरी को निर्धारित करती है। जाहिर है, जितना छोटा स्पेसिंग, अधिक बार लेनदेन, एक एकल लेनदेन का कम लाभ, और अधिक हैंडलिंग शुल्क। हालांकि, यह ध्यान देने योग्य है कि जैसे-जैसे ग्रिड स्पेसिंग छोटा हो जाता है और ग्रिड मूल्य अपरिवर्तित रहता है, जब मूल्य बदलता है, कुल पद बढ़ेंगे, और जोखिम का सामना करना पड़ रहा है पूरी तरह से अलग है। इसलिए, ग्रिड स्पेसिंग के प्रभाव को बैकटेस्ट करने के लिए, ग्रिड मूल्य को परिवर्तित करना आवश्यक है।
चूंकि बैकटेस्ट में 5m K-लाइन डेटा का उपयोग किया जाता है, और प्रत्येक 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% गिर जाएगी, जबकि वृद्धि की कोई सीमा नहीं है, और जोखिम बहुत अधिक है। इसलिए, ग्रिड रणनीति उपयोगकर्ताओं को केवल उन मुद्राओं के लिए लंबी स्थिति मोड चुनने की सलाह देती है जिनकी उन्हें संभावना है।