永久契約グリッド戦略パラメータ最適化の詳細な説明

作者: リン・ハーンリディア作成日:2023年12月11日 15:04:25 更新日:2024年1月2日 21:24:31

img

永久グリッド戦略は,FMZプラットフォームで人気のある古典的な戦略である.スポットグリッドと比較して,通貨を持つ必要がなく,レバレッジを追加することができます.これはスポットグリッドよりもはるかに便利です.しかし,FMZ量子プラットフォームで直接バックテストを行うことは不可能なので,通貨のスクリーニングやパラメータ最適化を決定するのに有利ではありません.この記事では,データ収集,バックテストフレームワーク,バックテスト機能,パラメータ最適化などを含む完全なPythonバックテストプロセスを紹介します. juypterノートブックで自分で試してみることができます.

データ収集

一般的には,K線データを使用するだけで十分です.正確性のために,K線期間が小さいほど,よりよいです.しかし,バックテスト時間とデータ容量をバランスするために,この記事では,バックテストのために過去2年間の5minのデータを使用します.最終データ容量は20万行を超えました.通貨として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)

グリッドバックテスト機能

格子戦略の原則は非常にシンプルである.価格が上昇すると売却し,価格が下がると購入する.特に3つのパラメータを含む:初期価格,格子間隔,取引価値.DYDXの市場は大きく変動する.初期低値8.6Uから1Uに低下し,最近の牛市で3Uに上昇した.戦略のデフォルト初期価格は8.6Uで,グリッド戦略にとって非常に不利であるが,デフォルトパラメータをバックテストした2年間で9200Uの総利益が得られ,この期間中に7500Uの損失が発生した.

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に設定された場合,戦略は初期にショートになり,多くのショートポジションを保持します.この例では,17,000Uのショートオーダーは直接保持されており,より大きなリスクに直面しています.

img

格子間隔設定

格子間隔は,待機中の注文間の距離を決定する.明らかに,間隔が小さくなるほど,取引が頻度が高まるほど,単一の取引の利益が低くなり,処理手数料が高くなる.しかし,格子間隔が小さくなり,格子価値が変化しないにつれて,価格が変化すると,総ポジションが増加し,直面するリスクは完全に異なります.したがって,格子間隔の効果をバックテストするには,格子値を変換する必要があります.

バックテストは5mのK線データを使用し,各K線は1回のみ取引されるため,特にデジタル通貨の変動が非常に高いため,明らかに非現実的です. ライブ取引と比較して,バックテストでは多くのトランザクションをマイスするスペースが小さいため,より大きなスペースだけが基準値を持つでしょう.このバックテストメカニズムでは,得られた結論は正確ではありません. ティックレベルのオーダーフローデータバックテストを通じて,最適なグリッド間隔は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%低下しますが,上昇に制限はありませんし,リスクははるかに高いです.したがって,グリッド戦略は,ユーザーが潜在的な通貨のために長ポジションモードのみを選択することをお勧めします.


もっと