币本位合约期现差价回归套利分析

Author: 小草, Created: 2021-09-16 14:57:39, Updated: 2023-09-20 09:08:16

img

期现差价回归套利

上次介绍了永续合约资金费率套利(https://www.fmz.com/digest-topic/6381 ), 通过做空永续合约期货,做多等量现货,持续的获取资金费率收益,在大的负溢价情况下,还能赚钱溢价回归收益。在8、9月份的牛市中,年化一度可达100%,是一个比较受欢迎的策略。

数字货币低风险套利中,还有一类策略没有介绍——期现基差回归套利。具体原理:

1.存在期货合约和现货的差价。交割合约的特点是到交割日才按照现货价格结算,因此在离交割日很远时,交割合约的交割非常容易收到市场情绪的影响,如果市场表现不错,更多的人会看好未来的价格,因此出现正溢价,当出现大跌时,往往会有负溢价。但总的来说交割合约的交割不会偏离现货太多,并且总会在交割日回归。

2.存在币本位交割合约,如币安有当季和次季交割合约。币本位交割合约和USDT本位永续合约区别较大,币本位使用币结算,而币价又在不断的变化,接下来将详细介绍。

本文代码可以直接运行,但由于网络原因,爬取数据部分需要科学上网,最好使用自己的电脑运行,或者使用Google的colab。

币本位合约盈亏计算

根据币安文档,收益= 交易方向×成交数量×合约倍数×(1 / 开仓价格- 1 / 平仓价格) = 持仓价值×(1 / 开仓价格- 1 / 平仓价格),做空时持仓价值为负。假如持有100张BTCUSD交割合约,每张价值100USD,开仓价格为10000USD,那么随着价格盈亏的情况如下:

通过计算分析,币本位做空或者做多的收益都是非线性的,以币收益衡量,做空最多亏一个持仓币量,赚币数量无上限,做多最多赚一个持仓币量,亏损则无上限。看起来做空更划算,但做空的收益伴随着币价的降低,按USD算其实没这么高。如果计算USD收益,情况正好相反,做空最多赚一个持仓价值,亏损则是无限的。

同时交易币本位合约,需要原本就持有币,如果考虑USD计价,此时 账户总价值 = (账户币数 + 持仓价值 ×(1 / 开仓价格- 1 / 平仓价格))×现货价格。如果持仓价值 = - 账户币数 × 开仓价格,即一倍杠杆做空,账户总价值 = 账户币数 × 开仓价格 × 平仓时现货价格 / 平仓价格, 考虑到交割合约的价格最终回归于现货价格,最终 账户总价值 = 账户币数 × 开仓价格。也就是说账户价值将以开仓价格锁定,不在随当前价格而改变,而根据分析,即使价格无限上涨,也不会爆仓。这就是套保的原理。一倍杠杆做空相当于把你现货的币以期货的价格提前卖出。

如果期货账户的币是从现货市场上购买的,那么 账户总收益 = 账户币数 × (开仓价格 - 现货购买价格),只要开仓,我们的收益就已经固定,和当前价格无关。其中的差价就是套利的收益。这就是期现套利的原理。

当然大部分情况下我们不必等待到交割日,如果差价降低可以提前平仓,这时候的 账户总收益 = 现货价值 × (开仓期现比/平仓期现比 - 1),只要开仓时期货现货价格比大于平仓时,并且能够覆盖手续费,此时就有收益。

import requests
from datetime import date,datetime
import time
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
value = 100*100 #持仓价值
open_price = 10000 #开仓价格10000USD
long_profit_list = []
short_profit_list = []
long_usdt_profit_list = []
short_usdt_profit_list = []
close_range = range(1000,30000,10)
for p in close_range:
    profit = value*(1/open_price-1/p)
    long_profit_list.append(profit)
    long_usdt_profit_list.append(profit*p)
    short_profit_list.append(-profit)
    short_usdt_profit_list.append(-profit*p)
#币本位计价收益
plt.figure(figsize=(12, 7), dpi=80)
plt.plot(close_range,long_profit_list,label='long');
plt.plot(close_range,short_profit_list);
plt.plot(close_range,[1]*len(close_range),'r--');
plt.plot(close_range,[-1]*len(close_range),'g--');
plt.ylabel('profit');
plt.xlabel('close price');
plt.grid(True)
plt.annotate('Short profit', xy=(4500, 1.5), xytext=(7000, 2.5),arrowprops=dict(facecolor='black'));
plt.annotate('Long profit', xy=(4500, -1.5), xytext=(7000, -2.5),arrowprops=dict(facecolor='black'));             
plt.annotate('Open price', xy=(10000, 0), xytext=(13000, 2.5),arrowprops=dict(facecolor='black'));
#收益用USD计算
plt.figure(figsize=(12, 7), dpi=80)
plt.plot(close_range,long_usdt_profit_list,label='long');
plt.plot(close_range,short_usdt_profit_list);

plt.ylabel('profit');
plt.xlabel('close price');
plt.annotate('Short profit', xy=(5000, 5000), xytext=(7000, 10000),arrowprops=dict(facecolor='black'));
plt.annotate('Long profit', xy=(5000, -5000), xytext=(7000, -10000),arrowprops=dict(facecolor='black'));   
plt.grid(True)

期现套利的步骤

  • 1.实时监控期现溢价的变化,达到设定值后,现货买币并且立即转入期货做空,做空价值为现货数量 × 开仓价格。
  • 2.等待溢价回归,达到设定值后,期货平仓,划转到现货卖出,获得收益。

具体细节

  • 1.不同交割日期的溢价有不同意义,如当季溢价5%和次季溢价5%,肯定优先选择当季进行套利。需要根据交割日期计算相应的年化。
  • 2.手续费需要考虑,牵扯到现货买入卖出,期货的开仓平仓,总共交易4次。
  • 3.期货和现货的交易要同时进行,才能锁定溢价,为避免市场冲击,可以分次多笔减仓。
  • 4.当期货账户里有币,可以直接进行并发开仓套利,而不用等待划转。同理现货的币也不用完全转入期货,方便平仓并发。
  • 5.要监控所有交易对,哪个有机会,溢价高就操作哪个。
  • 6.平仓的选择很重要,可以按照阶梯平仓,0溢价或者负溢价平完。

历史溢价变化

以币安交易所交割数据数据为例,共存在 BTCUSDT,ETHUSDT,ADAUSDT,LINKUSDT,BCHUSDT,DOTUSDT,XRPUSDT,LTCUSDT,BNBUSDT 9个交易对可以用于套利交易。这里选取ETH具体分析交割合约与现货的溢价变化。

今年以来ETH从600U起步,最高涨到5月份的4000U,然后跌倒6月、7月的2000U一下,最近又重新回到3500U,行情大起。考察到期日为210625,210924,211231的三个交割合约,其中210625溢价长期维持在8%,如果10%开始套利,6%平仓,4个月大概有4次机会,年化在50%以上。210924溢价最高15%以上,现在已经回归,还有较长时间到期的211231,最高溢价5%。可见只要耐心等待,ETH上套利机会很多。

读者可以自行更改交易对,情况基本类似,总的来说,今年1-4月份是个很好的区间。

## 当前交易对
Info = requests.get('https://dapi.binance.com/dapi/v1/exchangeInfo')
symbols = [s['symbol'] for s in Info.json()['symbols']]
symbols_nq = list(filter(lambda x:x.split('_')[-1]=='211231', symbols)) #次季合约
symbols_q = list(filter(lambda x:x.split('_')[-1]=='210924', symbols))  #当季合约
symbols_s = [s.split('_')[0]+'T' for s in symbols_nq] # 现货交易对
','.join(symbols_s)
def GetKlines(symbol='BTCUSDT',start='2020-8-10',end='2021-8-10',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 = int(time.mktime(datetime.strptime(end, "%Y-%m-%d").timetuple()))*1000 + 8*60*60*1000
    intervel_map = {'m':60*1000,'h':60*60*1000,'d':24*60*60*1000}
    while start_time < end_time:
        mid_time = min(start_time+1000*int(period[:-1])*intervel_map[period[-1]],end_time)
        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]
            Klines += res_list
        elif type(res_list) == list:
            start_time = start_time+1000*int(period[:-1])*intervel_map[period[-1]]
        else:
            print(url)
    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
symbol = 'ETH'
df_s = GetKlines(symbol=symbol+'USDT',start='2020-12-26',end='2021-9-15',period='1h',base='api',v='v3')
df_nq = GetKlines(symbol=symbol+'USD_211231',start='2021-6-26',end='2021-9-15',period='1h',base='dapi')
df_q = GetKlines(symbol=symbol+'USD_210924',start='2021-3-26',end='2021-9-15',period='1h',base='dapi')
df_lq = GetKlines(symbol=symbol+'USD_210625',start='2020-12-26',end='2021-6-24',period='1h',base='dapi') 
# 现货价格
df_s.close.dropna().plot(figsize=(16,6),grid=True);
# 上季合约溢价
(100*(df_lq.close-df_s.close)/df_s.close).dropna().plot(figsize=(16,6),grid=True);
# 当季合约的溢价
(100*(df_q.close-df_s.close)/df_s.close).dropna().plot(figsize=(16,6),grid=True);
# 次季合约溢价
(100*(df_nq.close-df_s.close)/df_s.close).dropna().plot(figsize=(16,6),grid=True);

当前交易机会

由于210924合约即将到期,这里主要考察还有3个月到期的211231,目前基本溢价在3%附近,最高的溢价是5%,可以说机会不是特别好,但210924到期后,将产生新的次季合约,距离交割时长6个月,会有更多的机会。

df_all = pd.DataFrame(index=pd.date_range(start='2021-6-26', end='2021-9-16', freq='1H'),columns=symbols_s)
for i in range(len(symbols_nq)):
    symbol_nq = symbols_nq[i]
    symbol_s = symbols_s[i]
    df_s = GetKlines(symbol=symbol_s,start='2021-6-26',end='2021-9-16',period='1h',base='api',v='v3')
    df_nq = GetKlines(symbol=symbol_nq,start='2021-6-26',end='2021-9-16',period='1h',base='dapi')
    df_all[symbol_s] = (100*(df_nq.close-df_s.close)/df_s.close).drop_duplicates()
df_all.dropna().plot(figsize=(16,10),grid=True);

总结

本文主要介绍了利用交割合约和现货之间差价的回归进行套利交易。这种套利方式是一种普遍的交易行为,有着很多优势:

  • 1.风险低,由于1倍杠杆做空不会爆仓,所以即使溢价扩大也没有风险,几乎是无风险套利。
  • 2.确定性高,交割合约的价格总会回归现货,套利完成后,不受当前价格涨跌的影响,最差的情况是拿到交割也会获取收益。
  • 3.操作原理简单,可以交易币种多,适合相对大一些的资金追求稳定的收益。
  • 4.收益不低,有时候会出现很深的负溢价,如果运气好掌握好节奏,利润非常可观。

主要风险:

  • 1.溢价如果长时间增长,会有较长时间的浮亏,此时平仓离场将会产生实际的亏损。
  • 2.交易所API故障,单腿交易。
  • 3.交易合约流动性差,且同类型策略太多,造成滑点过大,侵蚀利润。


More

Painter 草神,完整代码可以公开吗

randyshu 现在永续/现货溢价1个点不到,无法开单呀