在量化交易、程序化交易领域“对冲”这个词汇可谓是非常基础的概念,在数字货币量化交易中,经常使用到的对冲策略有:期现对冲、跨期对冲、现货对冲,其本质都是对于差价的交易。可能说到对冲这个概念、原理、细节,很多刚踏入量化交易领域的同学还不是很清楚,没关系,下面我们一起使用发明者量化交易平台提供的「研究环境」这个工具,一起来轻松的学习、掌握这个知识。
在发明者量化的控制中心,点击「研究环境」就可以跳转到这个工具的页面:
这里我直接上传了这个分析文件:
这个分析文件是对于回测时的一次期现对冲开仓平仓做的过程分析,期货交易所为OKEX期货,合约为季度合约quarter
。现货交易所为OKEX币币交易,交易对为BTC_USDT
,分析期现对冲操作流程的,可以看下面具体研究环境文件,写了两个版本,一个Python语言描述,一个JavaScript语言描述。
期现对冲原理分析.ipynb下载
In [1]:
from fmz import *
task = VCtx(\'\'\'backtest
start: 2019-09-19 00:00:00
end: 2019-09-28 12:00:00
period: 15m
exchanges: [{\"eid\":\"Futures_OKCoin\",\"currency\":\"BTC_USD\", \"stocks\":1}, {\"eid\":\"OKEX\",\"currency\":\"BTC_USDT\",\"balance\":10000,\"stocks\":0}]
\'\'\')
# 创建回测环境
import matplotlib.pyplot as plt
import numpy as np
# 导入画图的库 matplotlib 和 numpy 库
In [2]:
exchanges[0].SetContractType(\"quarter\") # 第一个交易所对象OKEX期货(eid:Futures_OKCoin)调用设置当前合约的函数,设置为季度合约
initQuarterAcc = exchanges[0].GetAccount() # OKEX期货交易所初始时的账户信息,记录在变量initQuarterAcc
initQuarterAcc
Out[2]:
{\'Balance\': 0.0, \'FrozenBalance\': 0.0, \'Stocks\': 1.0, \'FrozenStocks\': 0.0}
In [3]:
initSpotAcc = exchanges[1].GetAccount() # OKEX现货交易所初始时的账户信息,记录在变量initSpotAcc
initSpotAcc
Out[3]:
{\'Balance\': 10000.0, \'FrozenBalance\': 0.0, \'Stocks\': 0.0, \'FrozenStocks\': 0.0}
In [4]:
quarterTicker1 = exchanges[0].GetTicker() # 获取期货交易所行情,记录在变量quarterTicker1
quarterTicker1
Out[4]:
{\'Time\': 1568851210000, \'High\': 10441.25002, \'Low\': 10441.25, \'Sell\': 10441.25002, \'Buy\': 10441.25, \'Last\': 10441.25001, \'Volume\': 1772.0, \'OpenInterest\': 0.0}
In [5]:
spotTicker1 = exchanges[1].GetTicker() # 获取现货交易所行情,记录在变量spotTicker1
spotTicker1
Out[5]:
{\'Time\': 1568851210000, \'High\': 10156.60000002, \'Low\': 10156.6, \'Sell\': 10156.60000002, \'Buy\': 10156.6, \'Last\': 10156.60000001, \'Volume\': 7.4443, \'OpenInterest\': 0.0}
In [6]:
quarterTicker1.Buy - spotTicker1.Sell # 期货做空,现货做多的差价
Out[6]:
284.64999997999985
In [7]:
exchanges[0].SetDirection(\"sell\") # 设置期货交易所,交易方向为做空
quarterId1 = exchanges[0].Sell(quarterTicker1.Buy, 10) # 期货做空下单,下单量为10张合约,返回的订单ID记录在变量quarterId1
exchanges[0].GetOrder(quarterId1) # 查询期货订单ID为quarterId1的订单详情
Out[7]:
{\'Id\': 1, \'Price\': 10441.25, \'Amount\': 10.0, \'DealAmount\': 10.0, \'AvgPrice\': 10441.25, \'Type\': 1, \'Offset\': 0, \'Status\': 1, \'ContractType\': b\'quarter\'}
In [8]:
spotAmount = 10 * 100 / quarterTicker1.Buy # 计算10张合约等值的币数,作为现货的下单量
spotId1 = exchanges[1].Buy(spotTicker1.Sell, spotAmount) # 现货交易所下单
exchanges[1].GetOrder(spotId1) # 查询现货订单ID为spotId1的订单详情
Out[8]:
{\'Id\': 1, \'Price\': 10156.60000002, \'Amount\': 0.0957, \'DealAmount\': 0.0957, \'AvgPrice\': 10156.60000002, \'Type\': 0, \'Offset\': 0, \'Status\': 1, \'ContractType\': b\'BTC_USDT_OKEX\'}
可以看到订单quarterId1、spotId1订单都完全成交,即对冲开仓完成。
In [9]:
Sleep(1000 * 60 * 60 * 24 * 7) # 持仓一段时间,等待差价变小平仓。
等待时间过后,准备平仓。获取当前的行情quarterTicker2
、spotTicker2
并且打印。
期货交易所对象的交易方向设置为平空仓:exchanges[0].SetDirection(\"closesell\")
下单平仓。
打印平仓订单的详情,显示平仓订单完全成交,平仓完成。
In [10]:
quarterTicker2 = exchanges[0].GetTicker() # 获取当前期货交易所的行情,记录在变量quarterTicker2
quarterTicker2
Out[10]:
{\'Time\': 1569456010000, \'High\': 8497.20002, \'Low\': 8497.2, \'Sell\': 8497.20002, \'Buy\': 8497.2, \'Last\': 8497.20001, \'Volume\': 4311.0, \'OpenInterest\': 0.0}
In [11]:
spotTicker2 = exchanges[1].GetTicker() # 获取当前现货交易所的行情,记录在变量spotTicker2
spotTicker2
Out[11]:
{\'Time\': 1569456114600, \'High\': 8444.70000001, \'Low\': 8444.69999999, \'Sell\': 8444.70000001, \'Buy\': 8444.69999999, \'Last\': 8444.7, \'Volume\': 78.6273, \'OpenInterest\': 0.0}
In [12]:
quarterTicker2.Sell - spotTicker2.Buy # 期货空头仓位平仓,现货多头仓位平仓的差价
Out[12]:
52.5000200100003
In [13]:
exchanges[0].SetDirection(\"closesell\") # 设置期货交易所当前交易方向为平空仓
quarterId2 = exchanges[0].Buy(quarterTicker2.Sell, 10) # 期货交易所下单平仓,并且记录下单ID,记录到变量quarterId2
exchanges[0].GetOrder(quarterId2) # 查询期货平仓订单详情
Out[13]:
{\'Id\': 2, \'Price\': 8497.20002, \'Amount\': 10.0, \'DealAmount\': 10.0, \'AvgPrice\': 8493.95335, \'Type\': 0, \'Offset\': 1, \'Status\': 1, \'ContractType\': b\'quarter\'}
In [14]:
spotId2 = exchanges[1].Sell(spotTicker2.Buy, spotAmount) # 现货交易所下单平仓,并且记录下单ID,记录到变量spotId2
exchanges[1].GetOrder(spotId2) # 查询现货平仓订单详情
Out[14]:
{\'Id\': 2, \'Price\': 8444.69999999, \'Amount\': 0.0957, \'DealAmount\': 0.0957, \'AvgPrice\': 8444.69999999, \'Type\': 1, \'Offset\': 0, \'Status\': 1, \'ContractType\': b\'BTC_USDT_OKEX\'}
In [15]:
nowQuarterAcc = exchanges[0].GetAccount() # 获取当前期货交易所账户信息,记录在变量nowQuarterAcc
nowQuarterAcc
Out[15]:
{\'Balance\': 0.0, \'FrozenBalance\': 0.0, \'Stocks\': 1.021786026184, \'FrozenStocks\': 0.0}
In [16]:
nowSpotAcc = exchanges[1].GetAccount() # 获取当前现货交易所账户信息,记录在变量nowSpotAcc
nowSpotAcc
Out[16]:
{\'Balance\': 9834.74705446, \'FrozenBalance\': 0.0, \'Stocks\': 0.0, \'FrozenStocks\': 0.0}
通过对比最初账户和当前账户,计算出此次对冲操作的收益盈亏。
In [17]:
diffStocks = abs(nowQuarterAcc.Stocks - initQuarterAcc.Stocks)
diffBalance = nowSpotAcc.Balance - initSpotAcc.Balance
if nowQuarterAcc.Stocks - initQuarterAcc.Stocks > 0 :
print(\"收益:\", diffStocks * spotTicker2.Buy + diffBalance)
else :
print(\"收益:\", diffBalance - diffStocks * spotTicker2.Buy)
Out[17]:
收益: 18.72350977580652
下面我们看下为什么此次对冲是盈利的。我们可以看到画出的图表,期货价格是蓝色的线,现货价格是橙色的线,两个价格都是下降的,期货价格下降的比现货价格快。
In [18]:
xQuarter = [1, 2]
yQuarter = [quarterTicker1.Buy, quarterTicker2.Sell]
xSpot = [1, 2]
ySpot = [spotTicker1.Sell, spotTicker2.Buy]
plt.plot(xQuarter, yQuarter, linewidth=5)
plt.plot(xSpot, ySpot, linewidth=5)
plt.show()
Out[18]:
我们再看下差价的变化情况,差价是从对冲开仓时的284(即期货做空,现货最多),到平仓时的52(期货空头持仓平仓,现货多仓平仓)。差价是从大到小。
In [19]:
xDiff = [1, 2]
yDiff = [quarterTicker1.Buy - spotTicker1.Sell, quarterTicker2.Sell - spotTicker2.Buy]
plt.plot(xDiff, yDiff, linewidth=5)
plt.show()
Out[19]:
只要a1-b1即时刻1的期货现货差价大于a2-b2即时刻2时的期货现货差价,就可以推出a1 – a2 > b1 – b2。
有三种情况:(期货现货持仓头寸规模相同)
不存在 a1 – a2小于0,b1 – b2大于0这种情况,因为已经限定了a1 – a2 > b1 – b2。同样如果a1 – a2等于0,由于a1 – a2 > b1 – b2限定,b1 – b2就一定是小于0的。所以只要是期货做空,现货做多的对冲方式,符合条件a1 – b1 > a2 – b2,的开仓平仓操作,即为盈利对冲。
例如以下模型为其中一种情况:
In [20]:
a1 = 10
b1 = 5
a2 = 11
b2 = 9
# a1 - b1 > a2 - b2 推出 : a1 - a2 > b1 - b2
xA = [1, 2]
yA = [a1, a2]
xB = [1, 2]
yB = [b1, b2]
plt.plot(xA, yA, linewidth=5)
plt.plot(xB, yB, linewidth=5)
plt.show()
Out[20]:
研究环境不止支持Python,还支持JavaScript
下面我也给出一个JavaScript的研究环境范例:
期现对冲原理分析(JavaScript).ipynb下载
In [1]:
// 导入需要的程序包, 在发明者 \"策略编辑页面\" 点击 \"保存回测设置\" 即可获取字符串配置, 转换为对象即可
var fmz = require(\"fmz\") // 引入后自动导入 talib, TA, plot 库
var task = fmz.VCtx({
start: \'2019-09-19 00:00:00\',
end: \'2019-09-28 12:00:00\',
period: \'15m\',
exchanges: [{\"eid\":\"Futures_OKCoin\",\"currency\":\"BTC_USD\",\"stocks\":1},{\"eid\":\"OKEX\",\"currency\":\"BTC_USDT\",\"balance\":10000,\"stocks\":0}]
})
In [2]:
exchanges[0].SetContractType(\"quarter\") // 第一个交易所对象OKEX期货(eid:Futures_OKCoin)调用设置当前合约的函数,设置为季度合约
var initQuarterAcc = exchanges[0].GetAccount() // OKEX期货交易所初始时的账户信息,记录在变量initQuarterAcc
initQuarterAcc
Out[2]:
{ Balance: 0, FrozenBalance: 0, Stocks: 1, FrozenStocks: 0 }
In [3]:
var initSpotAcc = exchanges[1].GetAccount() // OKEX现货交易所初始时的账户信息,记录在变量initSpotAcc
initSpotAcc
Out[3]:
{ Balance: 10000, FrozenBalance: 0, Stocks: 0, FrozenStocks: 0 }
In [4]:
var quarterTicker1 = exchanges[0].GetTicker() // 获取期货交易所行情,记录在变量quarterTicker1
quarterTicker1
Out[4]:
{ Time: 1568851210000, High: 10441.25002, Low: 10441.25, Sell: 10441.25002, Buy: 10441.25, Last: 10441.25001, Volume: 1772, OpenInterest: 0 }
In [5]:
var spotTicker1 = exchanges[1].GetTicker() // 获取现货交易所行情,记录在变量spotTicker1
spotTicker1
Out[5]:
{ Time: 1568851210000, High: 10156.60000002, Low: 10156.6, Sell: 10156.60000002, Buy: 10156.6, Last: 10156.60000001, Volume: 7.4443, OpenInterest: 0 }
In [6]:
quarterTicker1.Buy - spotTicker1.Sell // 期货做空,现货做多的差价
Out[6]:
284.64999997999985
In [7]:
exchanges[0].SetDirection(\"sell\") // 设置期货交易所,交易方向为做空
var quarterId1 = exchanges[0].Sell(quarterTicker1.Buy, 10) // 期货做空下单,下单量为10张合约,返回的订单ID记录在变量quarterId1
exchanges[0].GetOrder(quarterId1) // 查询期货订单ID为quarterId1的订单详情
Out[7]:
{ Id: 1, Price: 10441.25, Amount: 10, DealAmount: 10, AvgPrice: 10441.25, Type: 1, Offset: 0, Status: 1, ContractType: \'quarter\' }
In [8]:
var spotAmount = 10 * 100 / quarterTicker1.Buy // 计算10张合约等值的币数,作为现货的下单量
var spotId1 = exchanges[1].Buy(spotTicker1.Sell, spotAmount) // 现货交易所下单
exchanges[1].GetOrder(spotId1) // 查询现货订单ID为spotId1的订单详情
Out[8]:
{ Id: 1, Price: 10156.60000002, Amount: 0.0957, DealAmount: 0.0957, AvgPrice: 10156.60000002, Type: 0, Offset: 0, Status: 1, ContractType: \'BTC_USDT_OKEX\' }
可以看到订单quarterId1、spotId1订单都完全成交,即对冲开仓完成。
In [9]:
Sleep(1000 * 60 * 60 * 24 * 7) // 持仓一段时间,等待差价变小平仓。
等待时间过后,准备平仓。获取当前的行情quarterTicker2
、spotTicker2
并且打印。
期货交易所对象的交易方向设置为平空仓:exchanges[0].SetDirection(\"closesell\")
下单平仓。
打印平仓订单的详情,显示平仓订单完全成交,平仓完成。
In [10]:
var quarterTicker2 = exchanges[0].GetTicker() // 获取当前期货交易所的行情,记录在变量quarterTicker2
quarterTicker2
Out[10]:
{ Time: 1569456010000, High: 8497.20002, Low: 8497.2, Sell: 8497.20002, Buy: 8497.2, Last: 8497.20001, Volume: 4311, OpenInterest: 0 }
In [11]:
var spotTicker2 = exchanges[1].GetTicker() // 获取当前现货交易所的行情,记录在变量spotTicker2
spotTicker2
Out[11]:
{ Time: 1569456114600, High: 8444.70000001, Low: 8444.69999999, Sell: 8444.70000001, Buy: 8444.69999999, Last: 8444.7, Volume: 78.6273, OpenInterest: 0 }
In [12]:
quarterTicker2.Sell - spotTicker2.Buy // 期货空头仓位平仓,现货多头仓位平仓的差价
Out[12]:
52.5000200100003
In [13]:
exchanges[0].SetDirection(\"closesell\") // 设置期货交易所当前交易方向为平空仓
var quarterId2 = exchanges[0].Buy(quarterTicker2.Sell, 10) // 期货交易所下单平仓,并且记录下单ID,记录到变量quarterId2
exchanges[0].GetOrder(quarterId2) // 查询期货平仓订单详情
Out[13]:
{ Id: 2, Price: 8497.20002, Amount: 10, DealAmount: 10, AvgPrice: 8493.95335, Type: 0, Offset: 1, Status: 1, ContractType: \'quarter\' }
In [14]:
var spotId2 = exchanges[1].Sell(spotTicker2.Buy, spotAmount) // 现货交易所下单平仓,并且记录下单ID,记录到变量spotId2
exchanges[1].GetOrder(spotId2) // 查询现货平仓订单详情
Out[14]:
{ Id: 2, Price: 8444.69999999, Amount: 0.0957, DealAmount: 0.0957, AvgPrice: 8444.69999999, Type: 1, Offset: 0, Status: 1, ContractType: \'BTC_USDT_OKEX\' }
In [15]:
var nowQuarterAcc = exchanges[0].GetAccount() // 获取当前期货交易所账户信息,记录在变量nowQuarterAcc
nowQuarterAcc
Out[15]:
{ Balance: 0, FrozenBalance: 0, Stocks: 1.021786026184, FrozenStocks: 0 }
In [16]:
var nowSpotAcc = exchanges[1].GetAccount() // 获取当前现货交易所账户信息,记录在变量nowSpotAcc
nowSpotAcc
Out[16]:
{ Balance: 9834.74705446, FrozenBalance: 0, Stocks: 0, FrozenStocks: 0 }
通过对比最初账户和当前账户,计算出此次对冲操作的收益盈亏。
In [17]:
var diffStocks = Math.abs(nowQuarterAcc.Stocks - initQuarterAcc.Stocks)
var diffBalance = nowSpotAcc.Balance - initSpotAcc.Balance
if (nowQuarterAcc.Stocks - initQuarterAcc.Stocks > 0) {
console.log(\"收益:\", diffStocks * spotTicker2.Buy + diffBalance)
} else {
console.log(\"收益:\", diffBalance - diffStocks * spotTicker2.Buy)
}
Out[17]:
收益: 18.72350977580652
下面我们看下为什么此次对冲是盈利的。我们可以看到画出的图表,期货价格是蓝色的线,现货价格是橙色的线,两个价格都是下降的,期货价格下降的比现货价格快。
In [18]:
var objQuarter = {
\"index\" : [1, 2], // 索引index 为1 即第一个时刻,开仓时刻,2为平仓时刻。
\"arrPrice\" : [quarterTicker1.Buy, quarterTicker2.Sell],
}
var objSpot = {
\"index\" : [1, 2],
\"arrPrice\" : [spotTicker1.Sell, spotTicker2.Buy],
}
plot([{name: \'quarter\', x: objQuarter.index, y: objQuarter.arrPrice}, {name: \'spot\', x: objSpot.index, y: objSpot.arrPrice}])
Out[18]:
我们再看下差价的变化情况,差价是从对冲开仓时的284(即期货做空,现货最多),到平仓时的52(期货空头持仓平仓,现货多仓平仓)。差价是从大到小。
In [19]:
var arrDiffPrice = [quarterTicker1.Buy - spotTicker1.Sell, quarterTicker2.Sell - spotTicker2.Buy]
plot(arrDiffPrice)
Out[19]:
只要a1-b1即时刻1的期货现货差价大于a2-b2即时刻2时的期货现货差价,就可以推出a1 – a2 > b1 – b2。
有三种情况:(期货现货持仓头寸规模相同)
不存在 a1 – a2小于0,b1 – b2大于0这种情况,因为已经限定了a1 – a2 > b1 – b2。同样如果a1 – a2等于0,由于a1 – a2 > b1 – b2限定,b1 – b2就一定是小于0的。所以只要是期货做空,现货做多的对冲方式,符合条件a1 – b1 > a2 – b2,的开仓平仓操作,即为盈利对冲。
例如以下模型为其中一种情况:
In [20]:
var a1 = 10
var b1 = 5
var a2 = 11
var b2 = 9
// a1 - b1 > a2 - b2 推出 : a1 - a2 > b1 - b2
var objA = {
\"index\" : [1, 2],
\"arrPrice\" : [a1, a2],
}
var objB = {
\"index\" : [1, 2],
\"arrPrice\" : [b1, b2],
}
plot([{name : \"a\", x : objA.index, y : objA.arrPrice}, {name : \"b\", x : objB.index, y : objB.arrPrice}])
Out[20]:
小伙伴们赶紧动手试一下吧!
买好币上币库:https://www.kucoin.com/r/1f7w3