最近,私はBuOu
デジタル通貨ペア取引 (Digital currency pair trading) は,価格偏差から利益を得るため,高度に相関する2つの暗号通貨を同時に購入・販売し,統計的仲介に基づく取引戦略である.この記事では,この戦略の原則,利益メカニズム,通貨選択方法,潜在的なリスクおよび改善方法,およびいくつかの実践的なPythonコード例を紹介する.
ペア取引戦略は,2つのデジタル通貨の価格との間の歴史的相関に依存する. 2つの通貨の価格が強い相関を示すとき,その価格動向は一般的に同期している. 2つの間の価格比が特定の時点で大幅に偏差した場合,それは一時的な異常とみなされ,価格は通常のレベルに戻る傾向がある. デジタル通貨市場は高度に相互に関連している.主要なデジタル通貨 (Bitcoinなどの) が大幅に変動すると,通常他のデジタル通貨に調整された反応を引き起こす. いくつかの通貨は,同じ投資機関,同じマーケットメーカーの存在,同じトレックによる非常に明らかなポジティブな相関を持つ可能性があります. いくつかの通貨は否定的に相関していますが,否定的に相関する通貨が少なくなり,それらはすべて市場動向に影響を受けますので,それらはしばしば一貫した市場動向を持っています.
通貨Aと通貨Bが高い価格相関関係を持っていると仮定する.ある時点で,A/B価格比の平均値は1.ある時点で,A/B価格比が0.001以上,すなわち1.001以上偏っている場合,あなたは以下の方法で取引することができます.Bにロングポジションを開いて,Aにショートポジションを開く.逆に,A/B価格比が0.999以下である場合:Aにロングポジションを開いて,Bにショートポジションを開く.
利潤性の鍵は,価格が平均値から逸脱して正常に戻ったときのスプレッド利益にあります.価格偏差は通常短期的なので,価格が平均値に戻るとトレーダーはポジションを閉じてスプレッドから利益を得ることができます.
このコードは直接使用できます.AnancodaをダウンロードしてJupyerノートブックでデバッグするのが最善です.一般的に使用されるデータ分析用のパッケージを直接含みます.
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
Info = requests.get('https://fapi.binance.com/fapi/v1/exchangeInfo')
b_symbols = [s['symbol'] for s in Info.json()['symbols'] if s['contractType'] == 'PERPETUAL' and s['status'] == 'TRADING' and s['quoteAsset'] == 'USDT']
b_symbols = list(filter(lambda x: x[-4:] == 'USDT', [s.split('_')[0] for s in b_symbols]))
b_symbols = [x[:-4] for x in b_symbols]
print(b_symbols) # Get all trading pairs being traded
GetKlines関数の主な機能は,指定された取引ペア永久契約の歴史的なKラインデータをバイナンス取引所から取得し,パンドラデータフレームにデータを保存することです.Kラインデータには,開通価格,最高価格,最低価格,閉じる価格,取引量などの情報が含まれます.今回は主に閉じる価格データを使用します.
def GetKlines(symbol='BTCUSDT',start='2020-8-10',end='2024-7-01',period='1h',base='fapi',v = 'v1'):
Klines = []
start_time = int(time.mktime(datetime.strptime(start, "%Y-%m-%d").timetuple()))*1000 + 8*60*60*1000
end_time = min(int(time.mktime(datetime.strptime(end, "%Y-%m-%d").timetuple()))*1000 + 8*60*60*1000,time.time()*1000)
intervel_map = {'m':60*1000,'h':60*60*1000,'d':24*60*60*1000}
while start_time < end_time:
time.sleep(0.3)
mid_time = start_time+1000*int(period[:-1])*intervel_map[period[-1]]
url = 'https://'+base+'.binance.com/'+base+'/'+v+'/klines?symbol=%s&interval=%s&startTime=%s&endTime=%s&limit=1000'%(symbol,period,start_time,mid_time)
res = requests.get(url)
res_list = res.json()
if type(res_list) == list and len(res_list) > 0:
start_time = res_list[-1][0]+int(period[:-1])*intervel_map[period[-1]]
Klines += res_list
if type(res_list) == list and len(res_list) == 0:
start_time = start_time+1000*int(period[:-1])*intervel_map[period[-1]]
if mid_time >= end_time:
break
df = pd.DataFrame(Klines,columns=['time','open','high','low','close','amount','end_time','volume','count','buy_amount','buy_volume','null']).astype('float')
df.index = pd.to_datetime(df.time,unit='ms')
return df
データ量は比較的大きい. ダウンロード速度を上げるため,過去3ヶ月の時間単位K線データのみを取得します. df_closeにはすべての通貨の閉値データが含まれています.
start_date = '2024-04-01'
end_date = '2024-07-05'
period = '1h'
df_dict = {}
for symbol in b_symbols:
print(symbol)
if symbol in df_dict.keys():
continue
df_s = GetKlines(symbol=symbol+'USDT',start=start_date,end=end_date,period=period)
if not df_s.empty:
df_dict[symbol] = df_s
df_close = pd.DataFrame(index=pd.date_range(start=start_date, end=end_date, freq=period),columns=df_dict.keys())
for symbol in symbols:
df_close[symbol] = df_dict[symbol].close
df_close = df_close.dropna(how='all')
次のバックテストのために交換オブジェクトを定義します
class Exchange:
def __init__(self, trade_symbols, fee=0.0002, 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, 'leverage':0, 'hold':0, 'long':0, 'short':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 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 #profit
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): #Update the assets
self.account['USDT']['unrealised_profit'] = 0
self.account['USDT']['hold'] = 0
self.account['USDT']['long'] = 0
self.account['USDT']['short'] = 0
for symbol in self.trade_symbols:
if not np.isnan(close_price[symbol]):
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'] = self.account[symbol]['amount']*close_price[symbol]
if self.account[symbol]['amount'] > 0:
self.account['USDT']['long'] += self.account[symbol]['value']
if self.account[symbol]['amount'] < 0:
self.account['USDT']['short'] += self.account[symbol]['value']
self.account['USDT']['hold'] += abs(self.account[symbol]['value'])
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)
self.account['USDT']['leverage'] = round(self.account['USDT']['hold']/self.account['USDT']['total'],3)
相互関係計算は,統計学で2つの変数間の線形関係を測定するために使用される方法である.最も一般的に使用される相互関係計算方法は,ピアソン相互関係係数である.以下は,相互関係計算の原理,公式および実施方法である.ピアソン相互関係係数は,2つの変数間の線形関係を測定するために使用され,その値範囲は-1から1の間である:
ピアソン相関係数は,両変数の共変数と標準偏差を計算することによって,両変数の相関を決定する.式は以下のとおりである.
その内:
もちろん,計算方法についてはあまり心配する必要はありません.すべての通貨の相関を計算するために,Pythonで1行のコードを使用できます.図は相関熱マップを示しています.赤は正相関を表し,青は負相関を表し,色が暗くなるほど相関が強くなります.この領域のほとんどは暗赤なので,デジタル通貨の正相関は非常に強いことがわかります.
import seaborn as sns
corr = df_close.corr()
plt.figure(figsize=(20, 20))
sns.heatmap(corr, annot=False, cmap='coolwarm', vmin=-1, vmax=1)
plt.title('Correlation Heatmap of Cryptocurrency Closing Prices', fontsize=20);
関連性に基づいて,最も関連性のある通貨ペアのトップ20が選択されます.結果は以下のとおりです.それらの関連性は非常に強く,すべて0.99以上です.
MANA SAND 0.996562
ICX ZIL 0.996000
STORJ FLOW 0.994193
FLOW SXP 0.993861
STORJ SXP 0.993822
IOTA ZIL 0.993204
SAND 0.993095
KAVA SAND 0.992303
ZIL SXP 0.992285
SAND 0.992103
DYDX ZIL 0.992053
DENT REEF 0.991789
RDNT MANTA 0.991690
STMX STORJ 0.991222
BIGTIME ACE 0.990987
RDNT HOOK 0.990718
IOST GAS 0.990643
ZIL HOOK 0.990576
MATIC FLOW 0.990564
MANTA HOOK 0.990563
対応コードは以下のとおりです.
corr_pairs = corr.unstack()
# Remove self-correlation (i.e. values on the diagonal)
corr_pairs = corr_pairs[corr_pairs != 1]
sorted_corr_pairs = corr_pairs.sort_values(kind="quicksort")
# Extract the top 20 most and least correlated currency pairs
most_correlated = sorted_corr_pairs.tail(40)[::-2]
print("The top 20 most correlated currency pairs are:")
print(most_correlated)
具体的なバックテストコードは以下の通りである.デモ戦略は主に2つの暗号通貨 (IOTAとZIL) の価格比を観察し,この比の変化に応じて取引する.具体的なステップは以下のとおりである:
e
取引手数料は0.02%ですavg
.value = 1000
.df_close
.diff
.aim_value
買取・売却は,現在口座の状況と価格状況に基づいて決定されます.pair_a
買ってpair_b
operations.pair_a
そして売るpair_b
操作が実行されます.avg
最新の価格比率を反映する.res_list
.res_list
データフレームへres
詳細な分析とプレゼンテーションのためにpair_a = 'IOTA'
pair_b = "ZIL"
e = Exchange([pair_a,pair_b], fee=0.0002, initial_balance=10000) #Exchange definition is placed in the comments section
res_list = []
index_list = []
avg = df_close[pair_a][0] / df_close[pair_b][0]
value = 1000
for idx, row in df_close.iterrows():
diff = (row[pair_a] / row[pair_b] - avg)/avg
aim_value = -value * diff / 0.01
if -aim_value + e.account[pair_a]['amount']*row[pair_a] > 0.5*value:
e.Sell(pair_a,row[pair_a],(-aim_value + e.account[pair_a]['amount']*row[pair_a])/row[pair_a])
e.Buy(pair_b,row[pair_b],(-aim_value - e.account[pair_b]['amount']*row[pair_b])/row[pair_b])
if -aim_value + e.account[pair_a]['amount']*row[pair_a] < -0.5*value:
e.Buy(pair_a, row[pair_a],(aim_value - e.account[pair_a]['amount']*row[pair_a])/row[pair_a])
e.Sell(pair_b, row[pair_b],(aim_value + e.account[pair_b]['amount']*row[pair_b])/row[pair_b])
avg = 0.99*avg + 0.01*row[pair_a] / row[pair_b]
index_list.append(idx)
e.Update(row)
res_list.append([e.account['USDT']['total'],e.account['USDT']['hold'],
e.account['USDT']['fee'],e.account['USDT']['long'],e.account['USDT']['short']])
res = pd.DataFrame(data=res_list, columns=['total','hold', 'fee', 'long', 'short'],index = index_list)
res['total'].plot(grid=True);
合計4つの通貨グループがバックテストされ,結果は理想的でした.現在の相関計算は将来のデータを使用しているため,非常に正確ではありません.この記事では,以前の相関計算と後のバックテスト取引に基づいてデータを2つの部分に分けます.結果は少し異なりますが,悪くはありません.私たちはそれを実践し検証するユーザーに任します.
理論上,ペア取引戦略は利益をもたらすかもしれないが,実際の運用には依然としていくつかのリスクがある. 通貨間の相関は時間の経過とともに変化し,戦略が失敗する可能性がある. 極端な市場状況下で,価格偏差が増加し,大きな損失を引き起こす可能性がある. 特定の通貨の低流動性は取引を実行しやすくしたりコストを増やすこともあり,頻繁な取引によって生じる手数料は利益を損なう可能性がある.
リスクを軽減し,戦略の安定性を向上させるために,以下の改善措置を考慮することができる.通貨間の相関を定期的に再計算し,取引ペアを適時に調整する. 取引の最大損失を制御するためにストップ・ロスを設定し,利益を取るポイントを設定する. リスクを多様化するために,複数の通貨ペアを同時に取引する.
デジタル通貨ペア取引戦略は,通貨価格の相関性を活用し,価格が逸脱したときの仲介操作を実行することで利益を達成する.この戦略は高い理論的実行可能性を有する.この戦略に基づいた簡単なライブ取引戦略ソースコードは後でリリースされる.より多くの質問またはさらなる議論が必要な場合は,自由に連絡してください.