天勤 – 信易科技 https://www.shinnytech.com 专业的期货/期权/衍生品相关软件系统开发商 Thu, 15 Aug 2024 09:07:43 +0000 zh-CN hourly 1 https://wordpress.org/?v=6.2.8 https://www.shinnytech.com/wp-content/uploads/cropped-信易-0-32x32.png 天勤 – 信易科技 https://www.shinnytech.com 32 32 天勤量化智能机器人上线! https://www.shinnytech.com/blog/tqsdk_llm/ Thu, 15 Aug 2024 07:47:38 +0000 https://www.shinnytech.com/?p=28608 在使用天勤的过程中用户总会有各种各样的问题,当用户是初学者时,他们可能会考虑 天勤量化能实现哪些功能 实现这个 […]

天勤量化智能机器人上线!最先出现在信易科技

]]>
在使用天勤的过程中用户总会有各种各样的问题,当用户是初学者时,他们可能会考虑

  • 天勤量化能实现哪些功能
  • 实现这个功能的大概代码格式是什么样
  • 文档中关于某些函数的具体参数是什么意思

 

当用户在实际运行的过程中时,他们可能会遇到

  • 遇到天勤或者python的报错,该怎么解决
  • 策略代码逻辑的优化该如何
  • 具体某个功能天勤如何实现

 

这些都会成为用户使用天勤量化过程中的障碍

当我们看到人工智能模型大行其道时,我们可能会想,我们能否也有一个自己的机器人助手,了解天勤的运行机制,能回答我们关于天勤的各种问题呢?

现在我们做到了,通过和国内的大语言模型结合,我们针对天勤量化,提供了一个专门的智能机器人来尝试回答用户关于天勤的各种问题

点击即可免费拥有这样的机器人 https://udify.app/chat/im02prcHNEOVbPAx

功能演示

让我们来看看这个机器人能解决哪些问题

函数原理讲解

TargetposTask是天勤里面使用频率很高的一个函数,但是很多人并不清楚它具体在怎么工作,让我们来找机器人试一下

 

我们接着再追问

策略/功能实现示例

除了函数原理的讲解以外,它还可以帮助我们直接实现部分代码/策略,供用户参考

策略参考示例

报错信息排查

最后编程中遇到的一些常见报错我们也可以尝试让它来帮忙解决

相信这些演示一定能让你有足够的兴趣来尝试我们这个功能强大且针对 tqsdk 优化后的机器人了

点击这里立刻使用吧~https://udify.app/chat/im02prcHNEOVbPAx

由于大语言模型的特性,因此回答的答案也有可能有错误,请使用时仔细确认问题答案!

天勤量化智能机器人上线!最先出现在信易科技

]]>
天勤量化 2.8.0,支持用户免费回测! https://www.shinnytech.com/blog/tqsdk-free-backtest/ Fri, 06 Aug 2021 01:44:33 +0000 https://www.shinnytech.com/?p=16483 天勤量化回测免费啦!

天勤量化 2.8.0,支持用户免费回测!最先出现在信易科技

]]>

       回测一直以来天勤量化作为特色功能,以实盘几乎不变的代码,不仅支持期货而且支持期权的回测功能和完善的回测报告,受到大家的好评。

       但是回测作为收费功能,并不能让所有免费用户便捷的体验和使用。

在本次天勤量化 2.8.0 的版本变更中,我们很高兴的告诉大家:

       只要你的天勤版本在2.8.0及以上,作为免费用户现在也可以一天进行3次免费回测了!

 

       对于新用户如果有python基础,想要学习天勤量化来进行回测或进行交易也十分方便,下面以一段简单回测代码为例:

from datetime import date
from tqsdk import TqApi, TqAuth, TqBacktest, TargetPosTask
# 在创建 api 实例时传入 TqBacktest 就会进入回测模式,设置web_gui=True开启图形化界面
api = TqApi(backtest=TqBacktest(start_dt=date(2018, 5, 2), end_dt=date(2018, 6, 2)),web_gui=True, auth=TqAuth("信易账户", "账户密码"))
# 获得 m1901 5分钟K线的引用
klines = api.get_kline_serial("DCE.m1901", 5 * 60, data_length=15)
# 创建 m1901 的目标持仓 task,该 task 负责调整 m1901 的仓位到指定的目标仓位
target_pos = TargetPosTask(api, "DCE.m1901")
while True:
    api.wait_update()
    if api.is_changing(klines):
        ma = sum(klines.close.iloc[-15:]) / 15
        print("最新价", klines.close.iloc[-1], "MA", ma)
        if klines.close.iloc[-1] > ma:
            print("最新价大于MA: 目标多头5手")
            # 设置目标持仓为多头5手
            target_pos.set_target_volume(5)
        elif klines.close.iloc[-1] < ma:
            print("最新价小于MA: 目标空仓")
            # 设置目标持仓为空仓
            target_pos.set_target_volume(0)

回测完成之后即可观察详细的回测报告

 

赶快行动起来体验简单但强大,很多功能还免费的天勤量化吧!

天勤量化 2.8.0,支持用户免费回测!最先出现在信易科技

]]>
盒式套利(仅获取合适的盒式套利组合) https://www.shinnytech.com/blog/box_spread/ https://www.shinnytech.com/blog/box_spread/#respond Mon, 30 Nov 2020 09:15:22 +0000 https://www.shinnytech.com/?p=14223 什么是盒式套利? 盒式套利是一种期权无风险套利,一旦套利成功,获得的利润不受任何市场波动和价格影响,但是相对应 […]

盒式套利(仅获取合适的盒式套利组合)最先出现在信易科技

]]>
什么是盒式套利?

盒式套利是一种期权无风险套利,一旦套利成功,获得的利润不受任何市场波动和价格影响,但是相对应的获得的利润在有效市场上相对来说不高且套利机会很少。盒式套利主要承担的风险为期权对手方提前行权的风险,利润影响的主要因素是手续费。

怎么组合一个盒式套利?

盒式套利的组成需要一共四个期权,算是较为麻烦的期权套利组合,因此我们将它拆分成两种简单的期权套利模型:熊市看跌期权套利和牛市看涨期权套利组合,这样理解起来会更加的简单。

熊市看跌期权套利

熊市看跌期权套利的交易方式是买进一个执行价格较高的看跌期权,同时卖出一个到期日相同、但是执行价格较低的看跌期权。整体组合的收益情况如下图所示,我们可以看到在标的物价格低于较低执行价时候有一个稳定不变的收益,同时在标的物价格高于较高时有一个稳定的风险敞口亏损。在两个执行价之间时,收益成下降趋势。

牛市看涨期权套利

牛市看涨期权套利的交易方式是买进一个执行价格较低的看涨期权,同时卖出一个到期日相同、但是执行价格较高的看涨期权。整体组合的收益情况如下图所示,我们可以看到在标的物价格低于较低执行价时候有一个稳定的风险敞口亏损,同时在标的物价格高于较高时有一个稳定不变的收益。在两个执行价之间时,收益成上涨趋势。

盒式期权套利

盒式套利的交易方式是将一个熊市看跌期权套利和一个牛市看涨期权套利组合,同时交易四种期权合约,且两种简单期权套利组合中使用的高低两个执行价格相同。根据上面的介绍我们可以发现在标的物价格低于较低执行价的时候,熊市看跌期权组合有稳定的收益,而牛市看涨期期权组合有稳定的损益;反之在标的物价格高于较高执行价的时候,熊市看跌期权组合有稳定的损益,而牛市看涨期期权组合有稳定的收益。在两个执行价之间时,两种套利组合呈现出完全相反的趋势互相抵消。因此当两种套利组合时,如下图所示,红色的线为盒式价差期权套利组合的收益,在无套利机会的情况下,它应该等于无风险收益率。为什么会等于无风险收益率呢?因为在无套利机会的时候,风险相同的投资产品应该收益相同,而盒式套利就是一个几乎无风险的套利组合。换句话说,当盒式套利的收益高于无风险收益率时,市场便出现了套利机会,如果此时同时完成四个期权合约交易,我们便可以完成一个盒式期权无风险套利。

如何判断盒式期权套利机会是否存在?

盒式期套利组合的权损益情况

首先我们先搞清楚一个盒式期权套利组合的盈亏由来,以及最大盈亏各是多少,之后便可以测试不同的期权组合,找出有套利机会的四个期权合约,做成一个完整的盒式期权套利组合。

我们假设较低的执行价为Slow,较高的执行价为Shigh,持有到到期时的期权组合价值为FVE,最大整体收益为MP,最大整体损益为ML,权利金净支出为NPP,手续费等各项杂费为C。

FVE = Shigh – Slow

ML = NPP + C

MP = FVE – ML

从公式可以看出,我们最终目标是要得到MP最大值的期权组合,但是在这个公式中我们忽略了很重要的一点,就是当投资组合的收益率低于无风险收益率的时候便没有什么意义了,还不如去买无风险国债更实在,因此我们要计算的应该是考虑了无风险收益率后的MP。

我们重新来整理下上面的公式,由于ML都是期初支付的,我们不需要进行折现,需要折现的只有FVE,我们假设从今天开始到合约到期日的相差T个月,无风险收益率为Rf,则新的FVE为:

FVEnew = FVE/(1+Rf*(T/12))

MPnew = FVEnew – ML

在整个策略中我们需要做的目标就很明确了,用合适的公式表示出MPnew,然后当MPnew为正时候即可同时下单四个期权,整个套利策略也变得简单多了。

如何找到合适的期权进行MPnew的测试:

每个标的资产的期权很多很多,如何快速的用天勤找到合适的一组4个合约来做盈利测试呢?这里只是提供一个我的思路,并不一定是最优思路。

首先用天勤的query函数取标的物的当前合约,只取一个交易方向的即可,可以是CALL可以是PUT,示例中取的是DEC.m2105的CALL期权:

quote_put = api.query_options("DCE.m2105",option_class="CALL", expired=False)

之后我们遍历整个CALL期权列表,取其中任意两个CALL期权组合(非排列)来取执行价:
strike_price1 = int(x[12:17])  # 取第一个期权的执行价
strike_price2 = int(y[12:17])  # 取序列后一个期权的执行价

通过排序取得低执行价和高执行价两种价格,这样我们就有了任意组合的两种执行价了:
strike_high = max(strike_price1, strike_price2)  # 取一组期权执行价中高的一个
strike_low = min(strike_price1, strike_price2)  # 取一组期权执行价中低的一个

最后我们要做的就很简单了,组合出4个期权合约的合约名,即可完成一个盒式期权套利组合了。
symbol_lowc = "DCE.m2105-C-" + str(strike_low)  # 组合出低执行价看涨期权
symbol_highc = "DCE.m2105-C-" + str(strike_high)  # 组合出高执行价看涨期权
symbol_highp = "DCE.m2105-P-" + str(strike_high)  # 组合出低执行价看跌期权
symbol_lowp = "DCE.m2105-P-" + str(strike_low)  # 组合出低执行价看跌期权

之后的计算就按照之前介绍的方法,算出最终的MPnew,Rf一般来说取SHIBOR就可以了,根据到期权到期日的时间来取对应的SHIBOR,以DEC.m2105为例,距离今天有大约半年的时间,所以我们取得SHIBOR为6个月的SHIBOR,需要注意的查询到的SHIBOR都是为年化收益率,所以需要用T来做处理,也就是SHIBOR-6month/2,具体的可以看后面的完整代码。

策略交易逻辑

由于盒式套利的套利机会不多,且实际交易费用影响较大,所以这里只提供查询盒式套利机会的获取,并不做回测和交易,具体这部分可以自行发挥,并不复杂。

策略代码

#!/usr/bin/env python # -*- coding: utf-8 -*- __author__ = "Lisiheng"
from tqsdk import TqApi

api = TqApi(auth="信易账户,信易密码")
quote_put = api.query_options("DCE.m2105",option_class="CALL", expired=False)
#  取现有上市的DCE.m2105 期权合约
for i, x in enumerate(quote_put):
    for j, y in enumerate(quote_put[i + 1:]):
        strike_price1 = int(x[12:17])  # 取第一个期权的执行价
        strike_price2 = int(y[12:17])  # 取序列后一个期权的执行价
        strike_high = max(strike_price1, strike_price2)  # 取一组期权执行价中高的一个
        strike_low = min(strike_price1, strike_price2)  # 取一组期权执行价中低的一个
        symbol_lowc = "DCE.m2105-C-" + str(strike_low)  # 组合出低执行价看涨期权
        symbol_highc = "DCE.m2105-C-" + str(strike_high)  # 组合出高执行价看涨期权
        symbol_highp = "DCE.m2105-P-" + str(strike_high)  # 组合出低执行价看跌期权
        symbol_lowp = "DCE.m2105-P-" + str(strike_low)  # 组合出低执行价看跌期权
        buy_call = api.get_quote(symbol_lowc)  # 获取要买入的看涨期权实时行情
        sell_call = api.get_quote(symbol_highc)  # 获取要卖出的看涨期权实时行情
        sell_put = api.get_quote(symbol_lowp)  # 获取要卖出的看涨期权实时行情
        buy_put = api.get_quote(symbol_highp)  # 获取要买入的看涨期权实时行情
        while True:
            api.wait_update()
            if api.is_changing(buy_call, "ask_price1") or api.is_changing(sell_call, "bid_price1") or \
                    api.is_changing(buy_put, "ask_price1") or api.is_changing(sell_put, "bid_price1"):
                print(sell_put.bid_price1)
                premium_receive = (sell_call.bid_price1 + sell_put.bid_price1)*buy_call.volume_multiple  # 计算应收权利金
                premium_paid = (buy_call.ask_price1 + buy_put.ask_price1)*buy_call.volume_multiple  # 计算应付权利金
                commission = 0  # 计算交易成本
                SHIBOR_6m = 0.031810  # 取得持有到期期间的SHIBOR作为无风险利率
                discount_factor = 1/(1+SHIBOR_6m/2)  # 计算折现率
                net_premium_paid = premium_paid + commission - premium_receive  # 计算净支出
                max_profit = int(symbol_highc[12:17]) - int(symbol_lowc[12:17])  # 计算最大收入
                profit = max_profit*discount_factor - net_premium_paid  # 计算总收入
                if profit > 0:  # 判断是否超过无风险套利收益的可能
                    print("收益", profit, "买", symbol_lowc, symbol_highp, "卖", symbol_highc, symbol_lowp)  # 得出交易组合

盒式套利(仅获取合适的盒式套利组合)最先出现在信易科技

]]>
https://www.shinnytech.com/blog/box_spread/feed/ 0
利用均值回归进行价差套利 https://www.shinnytech.com/blog/mean_reversion/ https://www.shinnytech.com/blog/mean_reversion/#comments Tue, 24 Nov 2020 08:40:02 +0000 https://www.shinnytech.com/?p=14182 什么是均值回归? 均值回归,起初是金融学的一个重要概念。均值回归是指股票价格、房产价格等社会现象、自然现象(气 […]

利用均值回归进行价差套利最先出现在信易科技

]]>
什么是均值回归?

均值回归,起初是金融学的一个重要概念。均值回归是指股票价格、房产价格等社会现象、自然现象(气温、降水),无论高于或低于价值中枢(或均值)都会以很高的概率向价值中枢回归的趋势。根据这个理论,一种上涨或者下跌的趋势不管其延续的时间多长都不能永远持续下去,最终均值回归的规律一定会出现:涨得太多了,就会向平均值移动下跌;跌得太多了,就会向平均值移动上升。

均值回归是一种数学方法,通常用于投资股票使用,但它可以适用于其他进程。笼统的概念,无论是股票的高和低价格暂时的,股票的价格往往会在一段时间内的平均价格。国内的期货市场日渐活跃起来,成交量的增长以及T+0交易的优势,使得期货交易也可以应用均值回归来进行套利。

怎么判断是否均值回归?

均值回归的趋势是价格围绕着一个固定的均值以某种关系运动,因此首先我们必须要确定我们选定的合约必须有稳定的均值,且价格波动要围绕着均值。期货合约一段时间的价格可以认为是一个时间序列,那么如果能证明时间序列的平稳性,也就证明了残差的平稳。这时候我们可以认为价格时间序列是平稳的,并且会围绕残差的平均值回归。

那么为什么我们要去做均值回归的套利策略而不针对某一个合约去单独进行均值回归套利呢?原因在于单独的某个期货合约价格的时间序列大部分情况下并不平稳,但是相关性较强的两个产品价格之差比较容易呈现稳定的均值回归现象。举个例子来说,豆油和豆粕的价格本身可能并没有很强的均值回归现象,但是如果豆油和豆粕的价格之差呢?由于他们两者的相关性很强,所以很有可能呈现出较强的均值回归现象,如果他们的价差符合均值回归,那么对于两者进行跨品种套利就可行了。为了验证价差是否具有均值回归,我们需要用到ADF检验(Augmented Dickey-Fuller test),以及协整检验Cointegration Test,因此我们需要额外安装的Statesmodel和numpy。

如何在一堆期货合约中取得最佳的一组套利合约?

首先我们选取一系列的目标期货合约,为了计算方便,示例选择了同种标的物在不同时期的合约,为什么这么做呢?原因是因为同种标的物的相关系数较强,我们认为系数近似于1,不然需要进行OLS(最小二乘法)来获取两种合约的交易比列,并不是本策略展示的最简方式,也就是进行相同手数不同方向同时交易的套利方式。利用statemodel模块的功能,我们能非常简单的就对数列进行ADF检验和协整检验。

遍历函数列表,对每个期货合约时间序列进行ADF检验

这一步的目的是筛选出可以做协整测试的期货合约组合,因为协整检验的前提条件是两个时间序列必须是平稳的。由于大部分的金融资产比如期货本身的价格很难是平稳的,所以我们直接对合约价格时间序列的一阶差分做ADF检验,因为如果两个时间序列是同阶单整的,则可以进行协整检验。对于ADF检验,我们需要做以下3步:

1. 一阶差分我们需要引用numpy库中的diff函数:

np.diff(k1),k1为第一个期货合约1分钟kline最新价的序列。

2. 之后引用ADF检验函数即可做检测了:

adf1 = st.adfuller(np.diff(k1)),st.adfuller为statemodel模块里引用的功能

3. 最后判断是否通过ADF检验:

判断方法为t-value是否小于1%置信度下的t-value,如果通过检测则可以让此期货合约去做协整检验

协整检验,并且找出最佳组合:

对列表中的所有合约做完ADF一阶差分检验后,我们将他们两两组合来做协整检测(注意是组合并不是排列,不考虑排序,默认放在序列前面的合约为第一个合约)。在协整检验完成后我们再从中取得最佳的合约组合,整个过程分为以下几步:

1. 对于ADF检验的通过的期货合约组合进行协整检验:

cov = st.coint(k1, k2)

2. 验证协整检验是否通过:

判断方法为t-value是否小于1%置信度下的t-value,如果通过检测则证明两个期货合约有协整关系,即他们的残差是符合均值回归的。

3. 找出p-value最小的一组期货合约:

协整检验中,p-value越低,代表着两个时间序列的协整性越强。换句话说以p-value最低的两个期货合约作为套利组合,呈现出的结果更加符合我们的预期,也更适合我们做的简单模型,即两时间序列之间的系数为1, 不需要去额外用OLS取系数。

策略交易逻辑

做完以上检验后,我们知道两个期货合约的差值(即残差)是符合均值回归的,因此我们可以取一个残差的序列,用第一个合约1分钟Kline的最新价序列K1减去第二个合约1分钟kline的最新价,得到一个价差序列diff,那么我们可以认为两个合约的价差应该是围绕着diff序列的均值以某种程度回归的。

之后我们算取一些理论价差作为交易信号。取两个极值作为开仓判断信号,如99%和1%分为点的两个价差作为开仓判断价差,同时将最接近均值的两个值作为平仓信号,如50%和40%分位点的两个价差作为平仓判断价格,交易的操作与之前的跨期套利相似。

将实际价差和理论价差进行对比,假设有均值回归现象,那么当实际价差突破开仓信号上线或跌破开仓信号下线时,可以进行开仓操作;同理当实际价差处于平仓信号区间内时,可以进行平仓操作。

当实际价差突破开仓信号上线时,我们认为实际价差被高估,可以多头价差,实际操作为卖出第一个合约同时买入相同手数的第二个合约。

当实际价差跌破开仓信号下线时,我们认为实际价差被低估,可以空头价差,实际操作为买入第一个合约同时卖出相同手数的第二个合约。

当实际价差处于平仓信号区间内时,我们认为实际价差已经回归均值,可以平仓操作,实际操作为同时平仓两个期货合约。

由于同时进入两种合约的相反交易方向,且同种标的物的两个合约相关性较高,盈利并不会被个别合约的涨跌影响,所以风险敞口只需要看整体价差即可,敞口较低。

策略代码

#!/usr/bin/env python
#  -*- coding: utf-8 -*-
__author__ = "Lisiheng"


# 导入模块
import numpy as np
import statsmodels.tsa.stattools as st
from tqsdk import TqApi, TqAuth, TargetPosTask

api = TqApi(web_gui=True, auth=TqAuth("信易账号", "信易密码"))
labels = ["SHFE.cu2003", "SHFE.cu2004", "SHFE.cu2005", "SHFE.cu2006", "SHFE.cu2007", "SHFE.cu2008", "SHFE.cu2009",
              "SHFE.cu2010"]


# 判断两个期货合约是否有均值回归的关系
def portfolio(labels: list, api):
    # 定义变量
    mincov = 1000000
    ins_x = ""
    ins_y = ""
    klines1 = None
    klines2 = None
    # 循环下面操作找出相关性最强的组合合约
    for i, x in enumerate(labels):
        for j, y in enumerate(labels[i + 1:]):
            klines1 = api.get_kline_serial(x, 60, data_length=4000)  # 取第一个合约的1分钟K线
            klines2 = api.get_kline_serial(y, 60, data_length=4000)  # 取第二个合约的1分钟K线
            k1 = klines1['close']  # 取第一个合约K线收盘价的序列
            k2 = klines2['close']  # 取第一个合约K线收盘价的序列
            adf1 = st.adfuller(np.diff(k1))  # 对一个合约取到的最新价做一阶差分,并且做ADF在1%置信度的检测
            adf2 = st.adfuller(np.diff(k2))  # 对一个合约取到的最新价做一阶差分,并且做ADF在1%置信度的检测
            if adf1[0] < adf1[4]['1%'] and adf2[0] < adf2[4]['1%']:  # 判断两个合约的差分在1%置信度是否平稳
                cov = st.coint(k1, k2)  # 如果平稳做协整测试
                cov_t = cov[0]  # 取协整测试的t-value
                cov_p = cov[1]  # 取协整测试的p-value
                if cov_t < cov[2][0] and cov_p < mincov:  # 协整测试在1%置信度通过后并取P-value最小的一组合约
                    mincov = cov_p
                    ins_x = x
                    ins_y = y
                    return x, y, k1, k2


#  获取关于交易信号的各种参数
symbol1, symbol2, kline1_new, kline2_new = portfolio(labels, api)
diff_theory = kline1_new - kline2_new  # 通过测试后得到的新合约组kline的收盘价序列取价差序列
up_open = np.percentile(diff_theory, 99)  # 取99%分为点的价差作为上限开仓线
up_close = np.percentile(diff_theory, 50)  # 取99%分为点的价差作为上限平仓线
down_open = np.percentile(diff_theory, 1)  # 取1%分为点的价差作为下限开仓线
down_close = np.percentile(diff_theory, 40)  # 取99%分为点的价差作为下限平仓线
target1 = TargetPosTask(api, symbol1)  # 设置第一个合约的TargetPosTask
target2 = TargetPosTask(api, symbol2)  # 设置第二个合约的TargetPosTask
pos1 = api.get_position(symbol1)  # 获取第一个合约的仓位情况
pos2 = api.get_position(symbol2)  # 获取第二个合约的仓位情况
quote1 = api.get_quote(symbol1)  # 获取第一个合约的最新价格
quote2 = api.get_quote(symbol2)  # 获取第二个合约的最新价格
#  通过信号判断进行交易
while True:
    api.wait_update()
    if api.is_changing(quote1, "last_price") or api.is_changing(quote2, "last_price"):
        diff_real = kline1_new.iloc[-1] - kline2_new.iloc[-1]  # 实际的价差,当第一个和第二个合约最新价变化时,更新函数
        if (pos1.pos_long == 0 and pos1.pos_short == 0) and (pos2.pos_long == 0 and pos2.pos_short == 0) and \
                diff_real > up_open:  # 判断是否仓位为0且实际价差大于上限开仓价
            target1.set_target_volume(-20)  # 第一个合约开仓空头20手
            target2.set_target_volume(20)  # 第二个合约同时开仓多头20手
        elif (pos1.pos_long == 0 and pos1.pos_short == 0) and (pos2.pos_long == 0 and pos2.pos_short == 0) and \
                diff_real < down_open:  # 判断是否仓位为0且实际价差小于下限开仓价
            target1.set_target_volume(20)  # 第一个合约开仓多头20手
            target2.set_target_volume(-20)  # 第二个合约同时开仓空头20手
        elif pos1.pos_long != 0 or pos1.pos_short != 0 or pos2.pos_long != 0 or pos2.pos_short != 0 and \
                down_close < diff_real < up_close:  # 判断是否有持仓,且价差是否均值回归到指定值
            target1.set_target_volume(0)  # 第一个合约平仓
            target2.set_target_volume(0)  # 第二个合约同时平仓

利用均值回归进行价差套利最先出现在信易科技

]]>
https://www.shinnytech.com/blog/mean_reversion/feed/ 5
传统模型跨期套利策略(传统商品期货) https://www.shinnytech.com/blog/multiple-maturity-arbitrage/ https://www.shinnytech.com/blog/multiple-maturity-arbitrage/#respond Fri, 06 Nov 2020 03:34:19 +0000 https://www.shinnytech.com/?p=13969 什么是跨期套利? 跨期套利是套利交易中最普遍的一种,是利用同一商品但不同交割月份之间正常价格差距出现异常变化时 […]

传统模型跨期套利策略(传统商品期货)最先出现在信易科技

]]>
什么是跨期套利?

跨期套利是套利交易中最普遍的一种,是利用同一商品但不同交割月份之间正常价格差距出现异常变化时进行对冲而获利的,又可分为牛市套利(bull spread)和熊市套利(bear spread两种形式。例如在进行金属牛市套利时,交易所买入近期交割月份的金属合约,同时卖出远期交割月份的金属合约,希望近期合约价格上涨幅度大于远期合约价格的上涨幅度;而熊市套利则相反,即卖出近期交割月份合约,买入远期交割月份合约,并期望远期合约价格下跌幅度小于近期合约的价格下跌幅度。

怎么判断不合理价差?

合理价差的运用在正向市场中有效,在正向市场中,以相邻月份为例,远月比近月的价格高出的部分主要是持有成本-持有收益,此处为简单的传统跨期套利模拟,我们假设所有的期货处于正向市场中(大部分情况下,期货市场呈现正向市场)。由于是传统的商品期货,持有期间很少会出现持有收益的情况,因为基本上只需要考虑持有期间的成本即可。持有成本又可以大致分为交割成本(包括运输费持仓费等)和时间成本(持有商品时候损失的现金流收益),交割成本以仓储费和交易费为主,我们可以把他们看作一个常量整体(需要投资者自己调研和设置,本策略中的常量并不代表市场上可供参考和使用的数值),而时间成本就是资金流的时间价值。如果同一个标的资产的不同交割月份的两个期货合约之间的价差,和我们计算的理论值偏离过大,我们可以认为出现了不合理价差,也同时出现了交易机会。

如何利用不合理价差进行套利交易?

我们计算的理论值价差代表的是我们目标期货合约由于交割月份不同应该具有的合理价值,而实际的价差代表的是市场中投资者对于期货涨跌走势的态度以及市场中供求关系的表现。假如市场是理性的,那么在理论值的价差和市场中实际的价差应相差不大。因此当理论值偏离当时的实际价差过高或者过低时,说明市场中的非理性冲击导致价差偏离理论值,我们可以进入空头或者多头头寸,预期价差会回归一个合理价值,这是均值回归概念的应用也是跨期套利的理论基础。确定合理价值变成了最重要的一环,在本策略中我们使用的最简单的持有成本传统模型。此模型的优势是在简单易操作,缺点是由于模型本身简单和理论假设较多,可能会忽略现实中的其他因素的影响。

合理价差策略原理

传统持有成本模型中,我们可以通过无风险利率(取十年国债收益率)来计算合理的远期合约价值(不考虑交易成本),公式为:

远期合约价值的计算公式

Flater = Fearly*(1+Rf*month/12)

其中Flater为远期合约合理价值,Fearly为近期合约最新价,Rf为无风险利率,month为远近期货合约之间的时间差,month/12用来转化年化利率为持有期间利率。

之后用近期价格减去远期理论价格即可得到理论价差的值,而用近期价格减去远期价格则可以得到实际的价差代码如下:

interest_rate = 1 + 0.0404*3/12 # 远近合约月份之间的利率系数理论值
theory_price = quote1.last_price * interest_rate # 根据利率系数计算出的远期合约理论价格
theory = quote1.last_price – theory_price # 近期实际价格和远期理论价格的价差
real = quote1.last_price – quote2.last_price # 近期实际价格和远期实际价格的价差

策略交易逻辑

将实际基差和理论价差进行对比,我们可以得到他们之间的差值。假设实际价差最终会回归到理性价差,那么当实际价差高于或低于理论价值一个特定值的时候(我们自己设定),可以进行开仓操作;同理当实际价差和理论价差相差的绝对值小于某个特定值的时候(我们自己设定),可以进行平仓操作。

当实际价差高出理论价差特定值时,我们认为实际价差被高估,可以多头价差,实际操作为卖出近期合约同时买入相同手数的远期合约。

当实际价差低于理论价差特定值时,我们认为实际价差被低估,可以空头价差,实际操作为买入近期合约同时卖出相同手数的远期合约。

当实际价差接近理论价差时,我们认为实际价差是理性价差,可以平仓操作,实际操作为同时平仓近期和远期合约。

由于同时进入两种合约的相反交易方向,且同种标的物的两个合约相关性较高,盈利并不会被个别合约的涨跌影响过多,所以风险敞口只需要看整体价差即可,开仓和平仓条件也只需要看远近合约价差。

回测

注意:以上参数皆是默认数值,并不严谨。在真正交易的过程中,我们要根据对应合约品种和行情的变化,调试这个区间以适应当下的行情状态。

策略代码

#!/usr/bin/env python
#  -*- coding: utf-8 -*-
__author__ = "Lisiheng"

from tqsdk import TqApi, TqAuth, TargetPosTask, TqSim
# 设置初始资金,信易账号,模拟模式,回测模式和时间,web_gui开启
api = TqApi(TqSim(init_balance=10000000), auth=TqAuth(“信易账户”, “账号密码”), web_gui=True)
# 参数和合约的相关初始设置
symbol1 = “SHFE.cu2006” # 近期合约
symbol2 = “SHFE.cu2010” # 远期合约
target_pos1 = TargetPosTask(api, symbol1) # 近期合约TargetPosTask设置
target_pos2 = TargetPosTask(api, symbol2) # 远期合约TargetPosTask设置
quote1 = api.get_quote(symbol1) # 取得近期合约最新行情
quote2 = api.get_quote(symbol2) # 取得远期合约最新行情
pos1 = api.get_position(symbol1) # 取得近期合约持仓情况
pos2 = api.get_position(symbol2) # 取得远期合约持仓情况
interest_rate = 1 + 0.0404*3/12 # 远近合约月份之间的利率系数理论值
# 定义价差相关参数
def difference():
theory_price = quote1.last_price * interest_rate # 根据利率系数计算出的远期合约理论价格
theory = quote1.last_price – theory_price # 近期实际价格和远期理论价格的价差
real = quote1.last_price – quote2.last_price # 近期实际价格和远期实际价格的价差
floor = theory – 200 # -200为自己设置的参数,扩大利润区间下界
cap = theory + 200 # +200为自己设置的参数,扩大利润区间上界
close_low = theory – 50 # -50为自己设置的参数,扩大平仓区间上界
close_high = theory + 50 # +50为自己设置的参数,扩大平仓区间下界
return theory_price, theory, real, floor, cap, close_low, close_high
while True:
api.wait_update()
# 如果近期远期合约最新价变动,相关理论价格和实际价格涉及到的价差更新
if api.is_changing(quote1, “last_price”) or api.is_changing(quote2, “last_price”):
dif_theory_price, dif_theory, dif_real, dif_floor, dif_cap, dif_closelow, dif_closehigh = difference()
# 判定目前近期和远期仓位是否为空仓,以及判定价差关系
if (pos1.pos_long == 0 and pos1.pos_short == 0) and (pos2.pos_long == 0 and pos2.pos_short == 0)
and (dif_real < dif_floor):
print(“可以买近卖远交易开仓”, dif_real, dif_theory)
target_pos1.set_target_volume(30) # 实际价差被低估的情况时,近期合约cu2006净持仓调整为30手
target_pos2.set_target_volume(-30) # 实际价差被低估的情况时,同时远期合约cu2008净持仓调整为-30手
# 判定目前近期和远期仓位是否为空仓,以及判定价差关系
elif (pos1.pos_long == 0 and pos1.pos_short == 0) and (pos2.pos_long == 0 and pos2.pos_short == 0)
and (dif_real > dif_cap): # +200为自己设置的参数,扩大利润区间
print(“可以卖近买远开仓”, dif_real, dif_theory)
target_pos1.set_target_volume(-30) # 实际价差被高估的情况时,近期合约cu2006净持仓调整为-30手
target_pos2.set_target_volume(30) # 实际价差被高估的情况时,同时远期合约cu2008净持仓调整为30手
# 判定目前近期和远期仓位是否都有仓位,以及判定价差关系
elif (pos1.pos_long != 0 or pos1.pos_short != 0 or pos2.pos_long != 0 or pos2.pos_short != 0)
and (dif_closelow < dif_real < dif_closehigh):
target_pos1.set_target_volume(0) # 实际价差接近理论值的情况时,近期合约cu2006的净持仓调整为0手
target_pos2.set_target_volume(0) # 实际价差接近理论值的情况时,远期合约cu2008的净持仓调整为0手
print(“平仓”, dif_real, dif_theory)

传统模型跨期套利策略(传统商品期货)最先出现在信易科技

]]>
https://www.shinnytech.com/blog/multiple-maturity-arbitrage/feed/ 0
1.7.0天勤量化正式支持期权模拟交易、实盘交易和回测 https://www.shinnytech.com/blog/tqsdk-option-announcement/ Thu, 26 Mar 2020 09:23:01 +0000 https://www.shinnytech.com/?p=9367 期权这个被誉为金融衍生品上的皇冠,天勤量化现已正式支持!

1.7.0天勤量化正式支持期权模拟交易、实盘交易和回测最先出现在信易科技

]]>
随着2019年期权市场品种先后扩容,沪深300ETF期权、沪深300指数期货期权,加上股指期货逐步松绑,使同学们在风险管理时有了更多、更好的对冲工具,提供了构建更多的策略组合的可能性,很多同学也对期权这个被誉为金融衍生品上的皇冠是跃跃欲试。

期权由于其本身品种计算的复杂性,天然的套利属性,和合约数量众多的特性,我们认为期权程序化交易对比手工交易将会占有更大的优势。


但由于现在大多数仿真环境并不支持期权的量化模拟交易,或者由于量化接口的种种局限,让期权量化的模拟和实盘对于大部分用户来说并不是那么顺利。


因此,天勤量化(TqSdk) 1.7.0版本决定顺势而上!在1.7.0中我们做出了一系列改动,相信能够能带给用户更好的期权量化体验

  1. 正式支持使用tqsdk进行商品期权和股指期权模拟交易
  2. 支持期权的回测功能
  3. 提供期权对应的BS定价公式、隐含波动率、希腊字母等期权相应计算字段

于此同时为了更好的提供不同需求服务给用户,使用TqAccount指定账户交易期权功能,目前只对申请权限的用户开放,想要申请请点击链接





 

1.7.0天勤量化正式支持期权模拟交易、实盘交易和回测最先出现在信易科技

]]>
功能优化:更加便捷、完善的支持多合约K线同时订阅! https://www.shinnytech.com/blog/get-multiple-klines/ Fri, 22 Nov 2019 06:20:50 +0000 https://www.shinnytech.com/?p=7743 更加便捷、完善的支持多合约K线同时订阅!

功能优化:更加便捷、完善的支持多合约K线同时订阅!最先出现在信易科技

]]>
TqSdk 1.2.0版本中,我们希望向大家着重介绍一下 get_kline_serial() 中新增的多合约K线同时订阅功能。

这个时候有同学可能就会问了,我之前版本中也可以分批使用几次get_kline_serial()传输进入不同的合约代码来获取K线,那这个功能有什么具体的好处呢?

具体我们认为有以下两个优点:

第一点,代码更简洁优美

以示例钢厂利润套利策略为例,以前我们订阅螺纹钢,铁矿石,焦炭期货合约行情,我们需要这样去写

klines_rb = api.get_kline_serial(SYMBOL_rb, 86400)
klines_i = api.get_kline_serial(SYMBOL_i, 86400)
klines_j = api.get_kline_serial(SYMBOL_j, 86400)

而现在我们只需要一行代码即可

klines = api.get_kline_serial([SYMBOL_rb,SYMBOL_i,SYMBOL_j], 86400)

然后主合约的字段名为原始K线数据字段,从第一个副合约开始,字段名在原始字段后加数字,如第一个副合约的开盘价为 “open1” , 第二个副合约的收盘价为 “close2″。

第二点,K线是更加严格意义上的时间对齐

举例来说,螺纹钢的夜盘时间是21:00-23:00,焦炭和铁矿石的交易时间是21:00—23:30,如果我们用日线的收盘价做相减那么是正确的日线时间对齐,但是如果我们订阅的是5min K线然后做的相减,就会造成时间差错位比如在23.25分的以下代码,就是螺纹钢在23.00时的收盘价减去23.25min 焦炭和铁矿石的收盘价

index_spread = klines_rb.close - 1.6 * klines_i.close - 0.5 * klines_j.close

在我们更新K线多合约品种后能有效的避免这个问题,它会遵循以下法则:

  • 每条K线都包含了订阅的所有合约数据,即:如果任意一个合约(无论主、副)在某个时刻没有数据(即使其他合约在此时有数据), 则不能对齐,此多合约K线在该时刻那条数据被跳过,现象表现为K线不连续(如主合约有夜盘,而副合约无夜盘,则生成的多合约K线无夜盘时间的数据)。
  • 若设置了较大的序列长度参数,而所有可对齐的数据并没有这么多,则序列前面部分数据为NaN(这与获取单合约K线且数据不足序列长度时情况相似)。
  • 若主合约与副合约的交易时间在所有合约数据中最晚一根K线时间开始往回的 8964*周期 时间段内完全不重合,则无法生成多合约K线,程序会报出获取数据超时异常。

即时间严格对齐,用该价差序列可以更准确提供我们想要的信息

index_spread = klines.close - 1.6 * klines.close1 - 0.5 * klines.close2

同时需要注意的是,目前多合约K线订阅功能暂不支持回测,若在回测时获取多合约K线,程序会报出获取数据超时异常。

 

 

功能优化:更加便捷、完善的支持多合约K线同时订阅!最先出现在信易科技

]]>
钢厂利润套利策略(螺纹钢、铁矿石、焦炭套利,难度:初级) https://www.shinnytech.com/blog/rb-i-jt-spread/ https://www.shinnytech.com/blog/rb-i-jt-spread/#respond Mon, 11 Nov 2019 07:42:35 +0000 https://www.shinnytech.com/?p=7600 “如果炼钢利润过高,铁矿和 焦炭价格会跟涨,挤压炼钢利润;炼钢利润过低,钢材价格回升。”

钢厂利润套利策略(螺纹钢、铁矿石、焦炭套利,难度:初级)最先出现在信易科技

]]>
钢厂利润套利逻辑

目前国内商品期货套利模式主要包括产业链套利、跨期套利、内外盘套利和期现套利。我们针对黑色产业链期货进行研究,利用产业链关系进行钢厂利润套利,涉及螺纹钢、铁矿、焦炭品种,炼钢工艺中影响总成本的主要因素是原料成本,即铁矿石、焦炭成本。根据研报内容,我们获知钢材的成本 可以通过如下方式进行计算:

螺纹钢期货价格 = 1.6 铁矿石期货价格 + 0.5 焦炭期货价格 + 其他成本(东方证券《衍生品系列研究之五-商品期货套利策略实证》)

上述等式是无套利的情形,而市场上的期货价格是波动的,上述等式在实际的市场中是不等的。如果从价差的变动来看,上述等式左右两边的价差可以理解为钢厂炼钢的利润,那么价差的波动就是钢厂利润的波动,因此追随钢厂利润波动的模式就是钢厂利润套利的模式,在实际操作中,我们用指数合约代替实际价格

钢厂利润 = 1 螺纹钢指数合约价格 – 1.6 铁矿石指数合约价格 – 0.5 * 焦炭指数合约价格 – 其他成本

def cal_spread(klines_rb, klines_i, klines_j):
    index_spread = klines_rb.close - 1.6 * klines_i.close - 0.5 * klines_j.close
    ma_spread = ma(index_spread, 10)
    spread_std = np.std(index_spread)
    klines_rb["index_spread"] = index_spread
    klines_rb["index_spread.board"] = "index_spread"

    return index_spread, ma_spread, spread_std

关于钢厂炼钢利润波动的逻辑,参考研报内容:如果炼钢利润过高,铁矿和 焦炭价格会跟涨,挤压炼钢利润;炼钢利润过低,钢材价格回升。我们可以看到钢厂利润波动的逻辑性较强,基于此,当钢厂利润达到高位时,可以做空利润,即做空螺纹钢做多铁矿石焦炭,当钢厂利润处于底位时,可以做多利润,即做多螺纹钢做空铁矿石焦炭。

一般的套利做法是设置固定的价差值进行套利,在价格偏离价差平均水平时进行多空操作,下面是通过期货指数绘制的钢厂利润曲线

从上面的图中我们发现价格并没有一个稳定的回复价格,即价差的分布并不对称,这样的序列显然不适合用传统的回复套利方法,在本报告中我们采用类似布林通道的策略思想,比如当价差超越长期或者 短期均值一个标准差之时,可以认为此时的价差水平偏高,因此我们做空价格相对高的期货,做多 价格相对低的期货,而当二者的价差回归到一个长期或短期均值的时候同时对二者进行平仓。这样 策略获得价差回复的收益

  • 研报中模型具体设置为
    • 开仓条件:价差在10日均值加1倍标准差和1.2倍标准差之间,有回归趋势开仓。
    • 平仓条件:回归到10日均值进行平仓。

策略原理

在该示例中,在回测过程中我们设置不同的开仓标准以及止损 等条件,发现以下设置更为合适

    • 开仓条件:价差在15日均值加0.5倍标准差之间,有回归趋势开仓。
    • 开仓条件:
      • 1.价差序列下穿上轨,利润冲高回落进行回复,策略空螺纹钢、多焦煤焦炭
      • 2.价差序列上穿下轨,利润过低回复上升,策略多螺纹钢、空焦煤焦炭
if index_spread.iloc[-1] > 0.5 * spread_std + ma_spread.iloc[-1]:
    target_pos_rb.set_target_volume(-100)
    target_pos_i.set_target_volume(100)
    target_pos_j.set_target_volume(100)

elif index_spread.iloc[-1] < ma_spread.iloc[-1] - 0.5 * spread_std:
    target_pos_rb.set_target_volume(100)
    target_pos_i.set_target_volume(-100)
    target_pos_j.set_target_volume(-100)

策略回测

初始账户资金:1000万

回测日期:2019.06.04-2019.08.11

多、空头开仓手数:100手

合约:SHFE.rb2001,DCE.i2001,DCE.j2001

钢厂套利策略回测结果
合约代码 合约品种 收益率 风险度 最大回撤 年化夏普率
SHFE.rb2001,DCE.i2001,DCE.j2001 31.49% 18.60% 10.01% 2.649

天勤量化源码

""" 钢厂利润套利策略 注: 该示例策略仅用于功能示范, 
实盘时请根据自己的策略/经验进行修改 """

from tqsdk import TqApi, TargetPosTask
from tqsdk.tafunc import ma
import numpy as np

api = TqApi()

SYMBOL_rb = "SHFE.rb2001"
SYMBOL_i = "DCE.i2001"
SYMBOL_j = "DCE.j2001"

klines_rb = api.get_kline_serial(SYMBOL_rb, 86400)
klines_i = api.get_kline_serial(SYMBOL_i, 86400)
klines_j = api.get_kline_serial(SYMBOL_j, 86400)

target_pos_rb = TargetPosTask(api, SYMBOL_rb)
target_pos_i = TargetPosTask(api, SYMBOL_i)
target_pos_j = TargetPosTask(api, SYMBOL_j)

# 计算钢厂利润线,并将利润线画到副图
def cal_spread(klines_rb, klines_i, klines_j):
    index_spread = klines_rb.close - 1.6 * klines_i.close - 0.5 * klines_j.close
    ma_spread = ma(index_spread, 10)
    spread_std = np.std(index_spread)
    klines_rb["index_spread"] = index_spread
    klines_rb["index_spread.board"] = "index_spread"

    return index_spread, ma_spread, spread_std


index_spread, ma_spread, spread_std = cal_spread(klines_rb, klines_i, klines_j)

print("ma_spread是%.2f,index_spread是%.2f,spread_std是%.2f" % (ma_spread.iloc[-1], index_spread.iloc[-1], spread_std))


while True:
    api.wait_update()
    # 每次有新日线生成时重新计算利润线
    if api.is_changing(klines_j.iloc[-1], "datetime"):
        index_spread, ma_spread, spread_std = cal_spread(klines_rb, klines_i, klines_j)
        print("ma_spread是%.2f,index_spread是%.2f,spread_std是%.2f" % (
            ma_spread.iloc[-1], index_spread.iloc[-1], spread_std))

    # 价差序列下穿上轨,利润冲高回落进行回复,策略空螺纹钢、多焦煤焦炭
    if index_spread.iloc[-1] > 0.5 * spread_std + ma_spread.iloc[-1]:
        target_pos_rb.set_target_volume(-100)
        target_pos_i.set_target_volume(100)
        target_pos_j.set_target_volume(100)

    # 价差序列上穿下轨,利润过低回复上升,策略多螺纹钢、空焦煤焦炭
    elif index_spread.iloc[-1] < ma_spread.iloc[-1] - 0.5 * spread_std:
        target_pos_rb.set_target_volume(100)
        target_pos_i.set_target_volume(-100)
        target_pos_j.set_target_volume(-100)

 

钢厂利润套利策略(螺纹钢、铁矿石、焦炭套利,难度:初级)最先出现在信易科技

]]>
https://www.shinnytech.com/blog/rb-i-jt-spread/feed/ 0
菜油、豆油、棕榈油多品种套利策略(难度:初级) https://www.shinnytech.com/blog/oi-y-p-spreads/ Wed, 23 Oct 2019 06:51:15 +0000 https://www.shinnytech.com/?p=7373 "跨品种套利的逻辑在于寻找不同品种但具有一定相关性的商品间的相对稳定的关系追逐价差波动的利润。"

菜油、豆油、棕榈油多品种套利策略(难度:初级)最先出现在信易科技

]]>
跨品种套利是指利用两种不同、但相互关联的资产间的价格差异进行套利交易。跨品种套利的逻辑在于寻找不同品种但具有一定相关性的商品间的相对稳定的关系,以期价差或者价比从偏离区域回到正常区间过程中追逐价差波动的利润。

多品种套利讲解

举例来说,豆粕和菜粕是期货合约中的经典组合套利,它们都可以用作动物饲料来源,所以存在一定的替代关系。然后市场上一般豆粕和菜粕的价格比与其蛋白质含量比会有直接联系,因此量化交易者可以根据历史数据进行分析和推测,来设定关于这个套利组合的自己策略代码,当其中一只合约价格较大偏离了策略设定价格时,便可通过量化交易第一时间进行套利操作。

豆油、棕榈油与菜籽油期货的价格协整

本文策略选择的标的为豆油、棕榈油和菜籽油期货主力合约,让我们先来看一看从18年到现在三种油期货的价格!

可以看到,大部分时候三种油的价格都是接近平行的,至少也是通向运动的,这说明三种油期货的价格是协整的~

换句话说,三种油期货的价差应该是较为稳定的,当价差出现较大变动时,我们便认为其中有期货被高估了或是被低估了,相应的我们便进行做空与做多操作。

下面让我们来看一看具体应该怎么实现~

策略实现

通过价格曲线的局部放大图我们可以看到,由于三条曲线几乎是平行的,就使得三条曲线之间产生了两段间距,但是又由于价格波动还是会有差异,这就使得这两段间距的宽度会发生变化,因此,我们就想到用两段间距的宽度变化来衡量相对价差的扩大和缩小。

在图中,上间距相比下间距来说变大了,因此我们可以认为是菜籽油的价格相对豆油的价格被高估了,于是我们做空菜籽油,做多豆油。基本思路就是这样,相对还是套利思想中很简单的

下面我们要做的,就是创建一个指标,来反映上间距与下间距的相对变化,我们把这个指标叫做index_spread,让我们来看一看index_spread怎么计算吧~

index_spread = ((豆油价 – 棕榈油价) – (菜油价-豆油价))/(菜油价 – 棕榈油价)

# 设置指标计算函数,计算三种合约品种的相对位置
def cal_spread(klines_y, klines_p, klines_oi):
    index_spread = ((klines_y.close - klines_p.close) - (klines_oi.close - klines_oi.close)) / (
            klines_oi.close - klines_p.close)

这个指标表示的是下间距比上间距多出来的百分比,该指标越大,则说明下间距相比上间距大,也就是相对于Y,P被低估,我们就做空Y,做多P;反之则做多Y,做空OI。

让我们来把 index_spread 的指标图打印出来看看,具体是什么样子?

从图中可以看到该指标的波动性还是蛮大的,用来用作判断的指标是不错的选择~

那该怎么用这个指标判断间距的变大和缩小呢?我们用的计算该指标的 5日与15日MA值,来进行判断

开仓逻辑

指数上涨,短期MA上穿长期,则认为相对于y,oi被低估,做多oi,做空y
指数下跌,短期MA下穿长期,则认为相对于y,p被高估,做多y,做空p
如果该指数表现平稳,当前数值大于0.98*短期MA,小于1.02倍短期MA,则平仓赚取利润
# 指数上涨,短期上穿长期,则认为相对于y,oi被低估,做多oi,做空y
if (ma_short.iloc[-2] > ma_long.iloc[-2]) and (ma_short.iloc[-3] < ma_long.iloc[-3]) and (
        index_spread.iloc[-2] > 1.02 * ma_short.iloc[-2]):
    target_pos_y.set_target_volume(-100)
    target_pos_oi.set_target_volume(100)

# 指数下跌,短期下穿长期,则认为相对于y,p被高估,做多y,做空p
elif (ma_short.iloc[-2] < ma_long.iloc[-2]) and (ma_short.iloc[-3] > ma_long.iloc[-3]) and (
        index_spread.iloc[-2] < 0.98 * ma_short.iloc[-2]):
    target_pos_y.set_target_volume(100)
    target_pos_p.set_target_volume(-100)

# 现在策略表现平稳,则平仓,赚取之前策略收益
elif ma_short.iloc[-2] * 0.98 < index_spread.iloc[-2] < ma_long.iloc[-2] * 1.02:
    target_pos_oi.set_target_volume(0)
    target_pos_p.set_target_volume(0)
    target_pos_y.set_target_volume(0)

策略源码

#!/usr/bin/env python
#  -*- coding: utf-8 -*-
__author__ = "Ringo"

"""
豆油、棕榈油、菜油套利策略
注: 该示例策略仅用于功能示范, 实盘时请根据自己的策略/经验进行修改
"""
from tqsdk import TqApi, TargetPosTask
from tqsdk.tafunc import ma

# 设定豆油,菜油,棕榈油指定合约
SYMBOL_Y = "DCE.y2001"
SYMBOL_OI = "CZCE.OI001"
SYMBOL_P = "DCE.p2001"

api = TqApi()

klines_y = api.get_kline_serial(SYMBOL_Y, 24 * 60 * 60)
klines_oi = api.get_kline_serial(SYMBOL_OI, 24 * 60 * 60)
klines_p = api.get_kline_serial(SYMBOL_P, 24 * 60 * 60)

target_pos_oi = TargetPosTask(api, SYMBOL_OI)
target_pos_y = TargetPosTask(api, SYMBOL_Y)
target_pos_p = TargetPosTask(api, SYMBOL_P)


# 设置指标计算函数,计算三种合约品种的相对位置,并将指标画在副图
def cal_spread(klines_y, klines_p, klines_oi):
    index_spread = ((klines_y.close - klines_p.close) - (klines_oi.close - klines_y.close)) / (
            klines_oi.close - klines_p.close)
    klines_y["index_spread"] = index_spread
    ma_short = ma(index_spread, 5)
    ma_long = ma(index_spread, 15)
    return index_spread, ma_short, ma_long


index_spread, ma_short, ma_long = cal_spread(klines_y, klines_p, klines_oi)

klines_y["index_spread.board"] = "index_spread"

print("ma_short是%.2f,ma_long是%.2f,index_spread是%.2f" % (ma_short.iloc[-2], ma_long.iloc[-2], index_spread.iloc[-2]))

while True:
    api.wait_update()
    if api.is_changing(klines_y.iloc[-1], "datetime"):
        index_spread, ma_short, ma_long = cal_spread(klines_y, klines_p, klines_oi)
        print("日线更新,ma_short是%.2f,ma_long是%.2f,index_spread是%.2f" % (
            ma_short.iloc[-2], ma_long.iloc[-2], index_spread.iloc[-2]))

    # 指数上涨,短期上穿长期,则认为相对于y,oi被低估,做多oi,做空y
    if (ma_short.iloc[-2] > ma_long.iloc[-2]) and (ma_short.iloc[-3] < ma_long.iloc[-3]) and (
            index_spread.iloc[-2] > 1.02 * ma_short.iloc[-2]):
        target_pos_y.set_target_volume(-100)
        target_pos_oi.set_target_volume(100)
    # 指数下跌,短期下穿长期,则认为相对于y,p被高估,做多y,做空p
    elif (ma_short.iloc[-2] < ma_long.iloc[-2]) and (ma_short.iloc[-3] > ma_long.iloc[-3]) and (
            index_spread.iloc[-2] < 0.98 * ma_short.iloc[-2]):
        target_pos_y.set_target_volume(100)
        target_pos_p.set_target_volume(-100)
    # 现在策略表现平稳,则平仓,赚取之前策略收益
    elif ma_short.iloc[-2] * 0.98 < index_spread.iloc[-2] < ma_long.iloc[-2] * 1.02:
        target_pos_oi.set_target_volume(0)
        target_pos_p.set_target_volume(0)
        target_pos_y.set_target_volume(0)

菜油、豆油、棕榈油多品种套利策略(难度:初级)最先出现在信易科技

]]>
小工具:期货现货价差 https://www.shinnytech.com/blog/futures-spot-spreads/ Fri, 11 Oct 2019 06:54:53 +0000 https://www.shinnytech.com/?p=7012 基于天勤量化(TqSdk)利用现货最新价减去期货主连最新价的小工具

小工具:期货现货价差最先出现在信易科技

]]>
期货现货价差小工具

这个小工具是基于天勤量化(TqSdk)利用现货最新价减去期货主连最新价,来帮助用户实时得到实时最新差价信息,具体实现源码在文章最后。

而天勤量化则是信易科技既快期系列后新推出的期货 Python 量化开发包,目前提供vs code 插件版运行,和直接使用 TqSdk这个开发包来运行

VS Code插件版

TqSdk IDE运行版

天勤量化的优势

相比专有语言,天勤量化更灵活,本质上是一个 Python 的第三方开发包,只需要 pip install tqsdk 即可运行,拥有专有语言无法比拟的自由度,能实现它们难以轻松达成的套利策略等,更可以配合 Python其他丰富的第三方拓展包来实现更多可能性

相比网页版的 Python平台,我们觉得我们是更安全的,自己赚钱的代码托付给网页的云服务器,难免有卧榻之侧岂容他人鼾睡之感。另外我们的自由度也是更高的,我们支持全国130余家的期货公司登陆,同时可以自由自在使用自己喜欢的IDE进行编程

相比其他开源 Python平台,我们入门更简单,具体框架机制也更科学,功能也更强大,点击查看我们和其他框架具体区别,同时我们也有被评价为“开源平台里最详细的开发文档和支持最及时的用户论坛

另外,我们还提供免费的交易通道和历史行情数据,没有任何手续费上浮,如果你已经心动了,现在可以选择先了解 天勤量化(TqSdk)开发包

期货现货价差小工具的实现

小工具的实现具体要让TqSdk配合其他扩展库来使用,分别是GUI库搭配使用,另外一个是生成独立的应用程序,配合 Pyinstaller 使用,如果你已经有较为不错的 Python基础,那么我们相信,实现起来一定不是难事,实现之后的效果图展示如下

期货价差小工具实现源码

#!usr/bin/env python3
#-*- coding:utf-8 -*-
"""
@author: yanqiong
@file: futures_spot_spreads.py
@create_on: 2019/9/27
@description: 演示如何使用 Tqsdk 计算期货和现货价差,并使用 GUI 界面展示
    除了 Tqsdk 还需要提前安装的工具包 :
    PySimpleGUI (https://pysimplegui.readthedocs.io/en/latest/)
    matplotlib (https://matplotlib.org/)
    mplcursors (https://mplcursors.readthedocs.io/en/stable/index.html)
"""

import sys
import math
import asyncio
import numpy as np
import pandas as pd
import PySimpleGUI as sg
import webbrowser
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import mplcursors
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk

from tqsdk import TqApi

DataLength = 40 # 显示最近10日价差
IndexList = np.arange(DataLength)

# ------------------------------- TqSdk Helper Code -------------------------------
links = {
     "_LINK_":  "https://www.shinnytech.com/blog/futures-spot-spreads/",
     "_TITLE_": "https://www.shinnytech.com/tianqin/#utm_source=pc-customer&utm_medium=banner&utm_campaign=little-instrument"
}

def open_link (url = 'https://www.shinnytech.com/tianqin/'):
    webbrowser.open_new(url)

def get_product_name (product_id):
    return product_id.split(' - ')[1]

def get_quote_sn (symbol):
    return symbol.split('.')[1]

# ------------------------------- Tqsdk 业务代码 -------------------------------
loop = asyncio.get_event_loop()
api = TqApi(loop=loop)

# 现货、期货合约对应列表
SymbolDict = {
    "al - 铝": ["SSWE.ALH", "KQ.m@SHFE.al", "orange"],
    "cu - 铜": ["SSWE.CUH", "KQ.m@SHFE.cu", "deepskyblue"],
    "ni - 镍": ["SSWE.NIH", "KQ.m@SHFE.ni", "red"],
    "pb - 铅": ["SSWE.PBH", "KQ.m@SHFE.pb", "lightseagreen"],
    "ru - 天然橡胶": ["SSWE.RUH", "KQ.m@SHFE.ru", "olive"],
    "sn - 锡": ["SSWE.SNH", "KQ.m@SHFE.sn", "burlywood"],
    "zn - 锌": ["SSWE.ZNH", "KQ.m@SHFE.zn", "deeppink"]
}
for i in SymbolDict:
    symbol = api.get_quote(SymbolDict[i][1]).underlying_symbol
    SymbolDict[i].append(symbol)
    SymbolDict[i].append(get_quote_sn(symbol))
ProductList = list(SymbolDict.keys())
SelectedProductId = ProductList[0] # 默认选择的品种

klines_series = [[api.get_kline_serial(SymbolDict[i][0], 86400, DataLength), api.get_kline_serial(SymbolDict[i][3], 86400, DataLength)] for i in SymbolDict]
def prepare_data (product_id) :
    ind = ProductList.index(product_id)
    return pd.to_datetime(klines_series[ind][1]["datetime"] / 1e9, unit='s', origin=pd.Timestamp('1970-01-01')), klines_series[ind][1]["close"] - klines_series[ind][0]["close"]
dt_series, spread_series = prepare_data(SelectedProductId)

# ------------------------------- Matplotlib Code -------------------------------
fig, ax = plt.subplots()
fig.set_size_inches(6, 4)

def format_date(x, pos=None):
    if pos is None:
        return dt_series[int(x)].strftime("%Y%m%d")
    else:
        ind = np.clip(int(x + 0.5), 0, DataLength - 1) # 保证下标不越界
        return dt_series[ind].strftime("%Y%m%d")
ax.xaxis.set_major_formatter(ticker.FuncFormatter(format_date)) # 格式化 X 轴显示

def prepare_plot():
    if dt_series[0] == dt_series[1]:
        return
    [spread_min, spread_max] = [np.min(spread_series), np.max(spread_series)]
    padding = (spread_max - spread_min) * 0.05
    ax.set_ylim(spread_min - padding, spread_max + padding)
    lines = ax.plot(IndexList, spread_series, "o-", color=SymbolDict[SelectedProductId][2])
    c2 = mplcursors.cursor(lines, hover=True)
    @c2.connect("add")
    def _(sel):
        ann = sel.annotation
        ann.get_bbox_patch().set(fc="#DBEDAC", alpha=.5)
        date_text = ann.get_text().replace('x=', 'date: ').split('\n')[0]
        i = math.floor(sel.target[0])
        ann.set_text("{}\n spread: {}".format(date_text, math.floor(spread_series[i])))
    ax.grid(True)
    fig.autofmt_xdate()

fig = plt.gcf()  # if using Pyplot then get the figure from the plot
figure_x, figure_y, figure_w, figure_h = fig.bbox.bounds

# ------------------------------- Matplotlib helper code -----------------------
def draw_figure(canvas, figure):
    figure_canvas_agg = FigureCanvasTkAgg(figure, canvas)
    figure_canvas_agg.draw()
    figure_canvas_agg.get_tk_widget().pack(side='top', fill='both', expand=1)
    return figure_canvas_agg

def draw_toolbar(canvas, root):
    toolbar = NavigationToolbar2Tk(canvas, root)
    toolbar.update()
    canvas._tkcanvas.pack(side='top', fill='both', expand=1)
    return

fig_canvas_agg = None

# ------------------------------- PySimpleGui Task Code  -----------------------
async def gui_task():
    global SelectedProductId, dt_series, spread_series, fig_canvas_agg
    # 获取合约引用
    quote_spot = api.get_quote(SymbolDict[SelectedProductId][0])
    quote_future = api.get_quote(SymbolDict[SelectedProductId][3])
    # 界面布局
    fontStyle = 'Any 12'
    layout = [
              [
                  sg.Frame(
                  title='',
                  font='Any 12',
                  border_width=0,
                  size=(40, 12),
                  layout=[
                      [sg.Listbox(values=ProductList, default_values=[ProductList[0]], enable_events=True, select_mode=sg.LISTBOX_SELECT_MODE_SINGLE, size=(20, 7), pad=((80, 0), (0, 20)), font='Any 12', key="_PRODUCT_")],
                      [
                          sg.Text("行情最后更新时间", size=(20, 1), font=fontStyle, justification="right"),
                          sg.Text("-------", size=(20, 1), font=fontStyle, key="datetime")
                      ],
                      [
                          sg.Text(get_product_name(SelectedProductId) + SymbolDict[SelectedProductId][4] + "最新价", size=(20, 1), font=fontStyle, justification="right", key="future.name"),
                          sg.Text("期货报价", font=fontStyle, key="future.last"),
                          sg.Text("期货涨跌幅", font=fontStyle, key="future.change")
                      ],
                      [
                          sg.Text(get_product_name(SelectedProductId) + "现货最新价", size=(20, 1), font=fontStyle, justification="right", key="spot.name"),
                          sg.Text("仓单报价", font=fontStyle, key="spot.last")
                      ],
                      [
                          sg.Text("期货-现货价差", size=(20, 1), font=fontStyle, justification="right"),
                          sg.Text("差价", size=(20, 1), font=fontStyle, key="spread")],
                      [sg.Text('_' * 60, text_color="grey")],
                      [
                          sg.Text("点击了解", font='Any 10', text_color="grey", size=(18, 1), pad=(0,0), justification="right"),
                          sg.Text("【天勤量化】", font='Any 10', text_color="blue", auto_size_text=True, pad=(0,0), key='_TITLE_', enable_events=True),
                          sg.Text("如何实现", font='Any 10', text_color="grey",  auto_size_text=True, pad=(0,0)),
                          sg.Text("【期货现货价差小工具】", font='Any 10', text_color="blue",  auto_size_text=True, pad=(0,0), key='_LINK_', enable_events=True)
                       ]
                  ],
                  relief=sg.RELIEF_SUNKEN,
                  tooltip='Use these to set flags'),
                  sg.Canvas(size=(figure_w, figure_h), key='canvas')
              ]
            ]
    window = sg.Window('期货现货价差 - 天勤量化', layout, finalize=True)
    # 在 canvas 处绘制图表
    prepare_plot()
    fig_canvas_agg = draw_figure(window['canvas'].TKCanvas, fig)
    # toolbar_canvas_agg = draw_toolbar(fig_canvas_agg, window.TKroot)


    while True:
        event, values = window.Read(timeout=0)
        if event == "_PRODUCT_":
            if SelectedProductId != values[event][0]:
                SelectedProductId = values[event][0]
                dt_series, spread_series = prepare_data(SelectedProductId)
                if len(ax.lines) > 0: ax.lines.pop()
                prepare_plot()
                fig_canvas_agg.draw()
                window.Element('spot.name').Update(get_product_name(SelectedProductId) + "现货最新价")
                window.Element('future.name').Update(get_product_name(SelectedProductId) + SymbolDict[SelectedProductId][4] + "最新价")
                window.Element('future.change').Update("()")
                # 更新合约引用
                quote_spot = api.get_quote(SymbolDict[SelectedProductId][0])
                quote_future = api.get_quote(SymbolDict[SelectedProductId][3])
        elif event == "_LINK_" or event == "_TITLE_":
            open_link(links[event])
        if event is None or event == 'Exit':
            sys.exit(0)

        # 更新界面数据
        window.Element('datetime').Update(quote_future.datetime[:19])
        window.Element('spot.last').Update('nan' if math.isnan(quote_spot.last_price) else int(quote_spot.last_price))
        window.Element('future.last').Update('nan' if math.isnan(quote_future.last_price) else int(quote_future.last_price))

        future_change = (quote_future.last_price - quote_future.pre_settlement) / quote_future.pre_settlement * 100
        if math.isnan(future_change):
            window.Element('future.change').Update("(nan)", text_color = "black")
        else:
            window.Element('future.change').Update(
                "({}%)".format(round(future_change, 2)), text_color="red" if future_change >= 0 else "green")

        spread = quote_future.last_price - quote_spot.last_price
        window.Element('spread').Update('nan' if  math.isnan(spread) else int(spread))
        await asyncio.sleep(0.001)  # 注意, 这里必须使用 asyncio.sleep, 不能用time.sleep

loop.create_task(gui_task())

# ------------------------------- TqApi Task Code  -----------------------
while True:
    api.wait_update()

小工具:期货现货价差最先出现在信易科技

]]>