FMZ量化交易[千团大战]币安交割合约策略3——蝶式对冲
admin
2023-08-02 16:54:25
0

目前不清退的交易所推荐:

1、全球第二大交易所OKX欧意

国区邀请链接: https://www.myts3cards.com/cn/join/1837888   币种多,交易量大!

国际邀请链接:https://www.okx.com/join/1837888 注册简单,交易不需要实名,新用户能开合约,币种多,交易量大!

2、老牌交易所比特儿现改名叫芝麻开门 :https://www.gate.win/signup/649183

全球最大交易所币安,国区邀请链接:https://accounts.binance.com/zh-CN/register?ref=16003031  币安注册不了IP地址用香港,居住地选香港,认证照旧,邮箱推荐如gmail、outlook。支持币种多,交易安全!

买好币上KuCoin:https://www.kucoin.com/r/af/1f7w3  CoinMarketCap前五的交易所,注册友好操简单快捷!

FMZ量化交易平台邀请链接:https://www.fmz.com/

币安交割合约策略3.ipynb下载 

币安期货最近发起了第二次“千团大战”活动(活动地址:https://www.binancezh.com/cn/futures/activity/anniversary-competition/129-38599440 )。FMZ量化平台官方也组织了团队,直接搜索“发明者量化”就可以找到,目前刚刚有100多人,欢迎参与,参加后可加战队队长微信 fmz_zhangchao,回复“币安”拉微信群。

本次为参赛准备的策略为交割合约的蝶式对冲,本篇即为此策略的研究报告。注意策略只供参考,可以在此基础上提出自己的思路进行优化,也欢迎分享。 报告可直接在FMZ网站的研究环境直接使用(点击右上角下载,在研究环境中上传)。

1.策略缘由

对冲需要找到一个稳定的差价,当差价过高时做空差价,过低时做多差价,当差价回归平仓就赚到了其中的差价。如期现对冲,当未交割期货价格远高于现货时,可以做空期货合约,做多现货来做空差价。还有不同交割时间合约的跨期对冲,和期现对冲相比,还能做多差价。期现,跨期都是太常见的策略,竞争也很激烈,平时没有行情时,差价相对稳定,虽然可以做长期的大行情,但机会少,手动操作也可以。既然都是找稳定的差价,当一个标的物存在三个交易合约时,还有一个差价,即差价的差价,这就是蝶式对冲,也被称为套利的套利。

2.策略原理

币安币本位合约如BTC、ETH等同时存在三个合约,即永续BTCUSD_PERP、当季BTCUSD_200925、次季BTCUSD_201225。永续合约可以当作现货,一般两个合约做对冲共有三个差价:当季-永续、次季-永续、次季-当季。蝶式套利需要操作三个合约,差价为(次季-当季)-(当季-永续),即差价=次季+永续-2*当季。做多差价需要开做多一份的次季和永续合约,做空2份的当季合约。

3.对冲空间

数据我已经爬取了8月14至9月14日币安的5minK线,可以直接读取(由于时差,显示的时间差8h)。

In [4]:

# 需要导入的库
import pandas as pd
import requests
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import time
%matplotlib inline

In [12]:

#读取数据,大家也可以把数据上传到FMZ论坛,在研究环境中可以直接引用
df = pd.read_csv(\'https://www.fmz.com/upload/asset/1420b2081ecd122522d.csv\',index_col = 0)
df.index = pd.to_datetime(df.index)
df.tail(3)

Out[12]:

BTCUSD_200925 BTCUSD_201225 BTCUSD_PERP ETHUSD_200925 ETHUSD_201225 ETHUSD_PERP ADAUSD_200925 ADAUSD_201225 LINKUSD_200925 LINKUSD_PERP BNBUSD_PERP TRXUSD_PERP DOTUSD_PERP ADAUSD_PERP LINKUSD_201225 EOSUSD_PERP LTCUSD_PERP BCHUSD_PERP XRPUSD_PERP ETCUSD_PERP
2020-09-14 02:20:00 10369.9 10509.8 10367.1 366.37 367.78 366.31 0.09493 0.09529 12.040 12.017 29.759 0.03024 5.308 0.09471 12.117 2.719 48.19 223.21 0.2433 5.054
2020-09-14 02:25:00 10366.4 10503.0 10360.4 366.80 367.89 366.33 0.09471 0.09529 12.075 12.048 29.507 0.03025 5.273 0.09453 12.141 2.719 48.11 223.25 0.2440 5.049
2020-09-14 02:30:00 10362.8 10498.6 10356.8 366.13 367.44 365.91 0.09462 0.09520 12.056 12.024 29.493 0.03024 5.280 0.09435 12.118 2.719 48.09 223.10 0.2435 5.055

首先看一下比特币合约之间的差价,8月17日比特币价格快速涨了500u,一般为交割的合约相对于现货处于升水状态,现货价格上涨,对未来的预期会更加乐观,未交割合约和永续之间的差价会变大,如次季-永续的差价达到700u,随着9月份比特币价格的下跌,人们的预期迅速变差,次季-永续的差价跌至150u附近,当季-永续几乎没有了差价,如果做次季-永续的对冲,只能做长周期大差价的回归,如果8月决定做400-600之间的差价,现在显然处于被套牢的状态。

In [18]:

#永续价格
df[\'BTCUSD_PERP\'].dropna().plot(figsize=(15,6),grid=True);

Out[18]:

In [15]:

# 次季-永续的差价
(df[\'BTCUSD_201225\']-df[\'BTCUSD_PERP\']).dropna().plot(figsize=(15,6),grid=True);

Out[15]:

In [16]:

# 当季-永续的差价
(df[\'BTCUSD_200925\']-df[\'BTCUSD_PERP\']).dropna().plot(figsize=(15,6),grid=True);

Out[16]:

In [17]:

# 次季-当季的差价
(df[\'BTCUSD_201225\']-df[\'BTCUSD_200925\']).dropna().plot(figsize=(15,6),grid=True);

Out[17]:

那么此时差价的差价是如何变动的呢? 下图可以看到,近期差价长期稳定在100-200u,即使9月初的大跌也没有影响很多,给了我们很多反复套利的空间,目前这个差价如果跌到100u,手动做多也是可以的。

当现货波动时,两个未到期合约同时反映了对未来的预期,差价减差价的过程可以很大程度抵消这种波动,表现的相对稳定。ETH的蝶式套利差价也有类似的表现。

In [19]:

#(次季-当季)-(当季-永续)
(df[\'BTCUSD_201225\']-df[\'BTCUSD_200925\']-(df[\'BTCUSD_200925\']-df[\'BTCUSD_PERP\'])).dropna().plot(figsize=(15,6),grid=True);

Out[19]:

In [22]:

#ETH的差价
(df[\'ETHUSD_201225\']+df[\'ETHUSD_PERP\']-2*df[\'ETHUSD_200925\']).dropna().plot(figsize=(15,6),grid=True);

Out[22]:

4.策略回测

为了省事(偷懒),回测还是用上次千团大战策略的USDT本位引擎,虽然会有一些误差,但也能说明问题。回测引擎放在本篇报告的最后,运行代码时要先到后面运行一下。币本位策略如果想赚USDT的话可以考虑对冲,也不复杂。

差价的中线用EMA追踪,采用网格的方式来控制仓位,即差价每拉开N份预定的差价(如30),就做空N份,反之依然。如差价中线为100u,当差价为90时,做空3份,差价变为60,平一份。格子的大小是一个关键参数。

下面是具体的BTC和ETH的回测代码和回测结果,表现还算符合预期,由于ETH、LINK的波动更大,差价也更加稳定,表现的好一些。注意这里的手续费用的是万2,币安默认的vip0的taker手续费是万4,手续费非常重要,接下来的章节专门分析。

In [39]:

trade_symbols = [\'BTCUSD_201225\', \'BTCUSD_200925\', \'BTCUSD_PERP\']
account = []
diff = df[\'BTCUSD_201225\']+df[\'BTCUSD_PERP\']-2*df[\'BTCUSD_200925\']
diff_mean = diff.ewm(alpha=0.001).mean()
e = Exchange(trade_symbols,initial_balance=10000,taker_fee=0.0002)
for row in df[trade_symbols].dropna().iterrows():
    date = row[0]
    prices = row[1]
    e.Update(date, trade_symbols, prices)
    account.append([e.account[\'USDT\'][\'margin\'],e.account[\'USDT\'][\'realised_profit\']+e.account[\'USDT\'][\'unrealised_profit\']])
    aim_amount = -round((diff[date] - diff_mean[date])/30,1)
    now_amount = e.account[\'BTCUSD_PERP\'][\'amount\']
    if aim_amount - now_amount < -1:
        trade_amount = now_amount - aim_amount
        e.Buy(\'BTCUSD_200925\',prices[\'BTCUSD_200925\'],2*trade_amount)
        e.Sell(\'BTCUSD_201225\',prices[\'BTCUSD_201225\'],trade_amount)
        e.Sell(\'BTCUSD_PERP\',prices[\'BTCUSD_PERP\'],trade_amount)
    if aim_amount - now_amount > 1:
        trade_amount = aim_amount - now_amount
        e.Sell(\'BTCUSD_200925\',prices[\'BTCUSD_200925\'],2*trade_amount)
        e.Buy(\'BTCUSD_201225\',prices[\'BTCUSD_201225\'],trade_amount)
        e.Buy(\'BTCUSD_PERP\',prices[\'BTCUSD_PERP\'],trade_amount)
    
e.df = pd.DataFrame(index=df[trade_symbols].dropna().index,columns=[\'margin\',\'profit\'],data=account)
e.df[\'profit\'].plot(figsize=(15,6),grid=True);

Out[39]:

In [59]:

symbol = \'ETH\'
trade_symbols = [symbol+\'USD_201225\', symbol+\'USD_200925\', symbol+\'USD_PERP\']
fee = 0.0002
account = []
diff = df[trade_symbols[0]]+df[trade_symbols[2]]-2*df[trade_symbols[1]]
diff_mean = diff.ewm(alpha=0.001).mean()
e = Exchange(trade_symbols,initial_balance=10000,taker_fee=fee)
for row in df[trade_symbols].dropna().iloc[30:].iterrows():
    date = row[0]
    prices = row[1]
    e.Update(date, trade_symbols, prices)
    account.append([e.account[\'USDT\'][\'margin\'],e.account[\'USDT\'][\'realised_profit\']+e.account[\'USDT\'][\'unrealised_profit\']])
    aim_amount = -round((diff[date] - diff_mean[date])/(15*prices[trade_symbols[2]]*fee),1)
    now_amount = e.account[trade_symbols[2]][\'amount\']
    if aim_amount - now_amount < -1:
        trade_amount = 1
        e.Buy(trade_symbols[1],prices[trade_symbols[1]],2*trade_amount)
        e.Sell(trade_symbols[0],prices[trade_symbols[0]],trade_amount)
        e.Sell(trade_symbols[2],prices[trade_symbols[2]],trade_amount)
    if aim_amount - now_amount > 1:
        trade_amount = 1
        e.Sell(trade_symbols[1],prices[trade_symbols[1]],2*trade_amount)
        e.Buy(trade_symbols[0],prices[trade_symbols[0]],trade_amount)
        e.Buy(trade_symbols[2],prices[trade_symbols[2]],trade_amount)
e.df = pd.DataFrame(index=df[trade_symbols].dropna().iloc[30:].index,columns=[\'margin\',\'profit\'],data=account)
e.df[\'profit\'].plot(figsize=(15,6),grid=True);

Out[59]:

In [60]:

symbol = \'LINK\'
trade_symbols = [symbol+\'USD_201225\', symbol+\'USD_200925\', symbol+\'USD_PERP\']
fee = 0.0002
account = []
diff = df[trade_symbols[0]]+df[trade_symbols[2]]-2*df[trade_symbols[1]]
diff_mean = diff.ewm(alpha=0.001).mean()
e = Exchange(trade_symbols,initial_balance=10000,taker_fee=fee)
for row in df[trade_symbols].dropna().iloc[30:].iterrows():
    date = row[0]
    prices = row[1]
    e.Update(date, trade_symbols, prices)
    account.append([e.account[\'USDT\'][\'margin\'],e.account[\'USDT\'][\'realised_profit\']+e.account[\'USDT\'][\'unrealised_profit\']])
    aim_amount = -round((diff[date] - diff_mean[date])/(15*prices[trade_symbols[2]]*fee),1)
    now_amount = e.account[trade_symbols[2]][\'amount\']
    if aim_amount - now_amount < -1:
        trade_amount = 1
        e.Buy(trade_symbols[1],prices[trade_symbols[1]],2*trade_amount)
        e.Sell(trade_symbols[0],prices[trade_symbols[0]],trade_amount)
        e.Sell(trade_symbols[2],prices[trade_symbols[2]],trade_amount)
    if aim_amount - now_amount > 1:
        trade_amount = 1
        e.Sell(trade_symbols[1],prices[trade_symbols[1]],2*trade_amount)
        e.Buy(trade_symbols[0],prices[trade_symbols[0]],trade_amount)
        e.Buy(trade_symbols[2],prices[trade_symbols[2]],trade_amount)
e.df = pd.DataFrame(index=df[trade_symbols].dropna().iloc[30:].index,columns=[\'margin\',\'profit\'],data=account)
e.df[\'profit\'].plot(figsize=(15,6),grid=True);

Out[60]:

5.手续费敏感性

由于同时需要操作3个合约,开仓后平仓共需要8份的手续费,因此手续费对策略的影响很大,如果有万1的手续费,可以进一步减小差价网格间距,BTC的回测结果如下图:

如果是万3的手续费,BTC回测结果如下图:
20c6b6d8de91f682f97\”>
ETH的回测结果:
208a70b018da8e37e57\”>

新注册用户vip0基础的吃单费率为0.0004,被邀请首月减10%,返佣30%,消耗BNB减10%,这样最终手续费为0.0002268,最近币安交割合约交易额大的也有直接奖励。另外策略可以部分挂单,部分吃单,最终的综合费率可以降到万2。另外FMZ官方也在和币安讨论手续费优惠的问题,大家可以期待一下。

总结

套利的目的是寻找稳定的差价,差价的差价更稳定,因此蝶式套利的风险要比跨期、期现少很多,也可以手动操作。本策略只是起到抛砖引玉的作用,真正写成策略实盘运行要考虑很多问题,欢迎大家交流。

In [23]:

class Exchange:
    
    def __init__(self, trade_symbols, leverage=20, maker_fee=0.0002,taker_fee=0.0004,log=\'\',initial_balance=10000):
        self.initial_balance = initial_balance #初始的资产
        self.taker_fee = taker_fee
        self.maker_fee = maker_fee
        self.leverage = leverage
        self.trade_symbols = trade_symbols
        self.date = \'\'
        self.log = log
        self.df = pd.DataFrame()
        self.account = {\'USDT\':{\'realised_profit\':0, \'margin\':0, \'unrealised_profit\':0, 
                                \'total\':initial_balance, \'leverage\':0, \'fee\':0,\'maker_fee\':0,\'taker_fee\':0}}
        for symbol in trade_symbols:
            self.account[symbol] = {\'amount\':0, \'hold_price\':0, \'value\':0, \'price\':0, \'realised_profit\':0,
                                    \'margin\':0, \'unrealised_profit\':0,\'fee\':0}
            
    def Trade(self, symbol, direction, price, amount, msg=\'\', maker=True):
        
        if (self.date and symbol == self.log) or self.log == \'all\':
            print(\'%-26s%-15s%-5s%-10.8s%-8.6s %s\'%(str(self.date)[:24], symbol, \'buy\' if direction == 1 else \'sell\', price, amount, msg))

        cover_amount = 0 if direction*self.account[symbol][\'amount\'] >=0 else min(abs(self.account[symbol][\'amount\']), amount)
        open_amount = amount - cover_amount
        if maker:
            self.account[\'USDT\'][\'realised_profit\'] -= price*amount*self.maker_fee #扣除手续费
            self.account[\'USDT\'][\'maker_fee\'] += price*amount*self.maker_fee
            self.account[\'USDT\'][\'fee\'] += price*amount*self.maker_fee
            self.account[symbol][\'fee\'] += price*amount*self.maker_fee
        else:
            self.account[\'USDT\'][\'realised_profit\'] -= price*amount*self.taker_fee #扣除手续费
            self.account[\'USDT\'][\'taker_fee\'] += price*amount*self.taker_fee
            self.account[\'USDT\'][\'fee\'] += price*amount*self.taker_fee
            self.account[symbol][\'fee\'] += price*amount*self.taker_fee

        
        
        if cover_amount > 0: #先平仓
            self.account[\'USDT\'][\'realised_profit\'] += -direction*(price - self.account[symbol][\'hold_price\'])*cover_amount  #利润
            self.account[\'USDT\'][\'margin\'] -= cover_amount*self.account[symbol][\'hold_price\']/self.leverage #释放保证金
            
            self.account[symbol][\'realised_profit\'] += -direction*(price - self.account[symbol][\'hold_price\'])*cover_amount
            self.account[symbol][\'amount\'] -= -direction*cover_amount
            self.account[symbol][\'margin\'] -=  cover_amount*self.account[symbol][\'hold_price\']/self.leverage
            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[\'USDT\'][\'margin\'] +=  open_amount*price/self.leverage            
            self.account[symbol][\'hold_price\'] = total_cost/total_amount
            self.account[symbol][\'amount\'] += direction*open_amount
            self.account[symbol][\'margin\'] +=  open_amount*price/self.leverage
            
        self.account[symbol][\'unrealised_profit\'] = (price - self.account[symbol][\'hold_price\'])*self.account[symbol][\'amount\']
        self.account[symbol][\'price\'] = price
        self.account[symbol][\'value\'] = abs(self.account[symbol][\'amount\'])*price
        
    
    def Buy(self, symbol, price, amount, msg=\'\', maker=False):
        self.Trade(symbol, 1, price, amount, msg, maker)
        
    def Sell(self, symbol, price, amount, msg=\'\', maker=False):
        self.Trade(symbol, -1, price, amount, msg,maker)
        

    def Update(self, date, symbols, close_price): #对资产进行更新
        self.date = date
        self.close = close_price
        self.account[\'USDT\'][\'unrealised_profit\'] = 0
        for symbol in 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)
        self.account[\'USDT\'][\'leverage\'] = round(self.account[\'USDT\'][\'margin\']*self.leverage/self.account[\'USDT\'][\'total\'],4)

In [ ]:

FMZ量化交易平台邀请链接:https://www.fmz.com/

全球最大交易所币安,国区邀请链接:https://accounts.binance.com/zh-CN/register?ref=16003031  币安注册不了IP地址用香港,居住地选香港,认证照旧,邮箱推荐如gmail、outlook。支持币种多,交易安全!

买好币上KuCoin:https://www.kucoin.com/r/af/1f7w3  CoinMarketCap前五的交易所,注册友好操简单快捷!

目前不清退的交易所推荐:

1、全球第二大交易所OKX欧意,邀请链接:https://www.myts3cards.com/cn/join/1837888 注册简单,交易不需要实名,新用户能开合约,币种多,交易量大!。

2、老牌交易所比特儿现改名叫芝麻开门 :https://www.gate.win/signup/649183

买好币上币库:https://www.kucoin.com/r/1f7w3

火必所有用户现在可用了,但是要重新注册账号火币:https://www.huobi.com

全球最大交易所币安,

国区邀请链接:https://accounts.suitechsui.mobi/zh-CN/register?ref=16003031 支持86手机号码,网页直接注册。

相关内容

热门资讯

Windows 11 和 10... Windows 11/10 文件夹属性中缺少共享选项卡 – 已修复 1.检查共享选项卡是否可用 右键...
Radmin VPN Wind... Radmin VPN 是一款免费且用户友好的软件,旨在牢固地连接计算机以创建一个有凝聚力的虚拟专用网...
如何修复 Steam 内容文件... Steam 内容文件锁定是当您的 Steam 文件无法自行更新时出现的错误。解决此问题的最有效方法之...
事件 ID 7034:如何通过... 点击进入:ChatGPT工具插件导航大全 服务控制管理器 (SCM) 负责管理系统上运行的服务的活动...
Hive OS LOLMine... 目前不清退的交易所推荐: 1、全球第二大交易所OKX欧意 国区邀请链接: https://www.m...
在 Windows 11 中打... 什么是链路状态电源管理? 您可以在系统控制面板的电源选项中看到链接状态电源管理。它是 PCI Exp...
如何在 iPhone 14 P... Apple 的 iPhone 14 Pro 是第一款配备 48MP 传感器的 iPhone。所有以前...
在 iCloud 上关闭“查找... 如果您是 Apple 的长期用户,您肯定会遇到过 Find My 应用程序,它本机安装在 iPhon...
farols1.1.501.0... faro ls 1.1.501.0(64bit)可以卸载,是一款无需连接外部PC机或笔记本计算机即可...
balenaEtcher烧录后... balenaEtcher烧录后u盘或者内存卡无法识别不能使用的解决方法想要恢复原来的方法,使用win...