可转债双低策略量化回测python源码(已更新)

鉴于复制黏贴到集思录的代码给一些朋友造成困扰,我已重新上传了新的回测代码,改动点:
1、预先取数,大幅优化回测速度;
2、可用不同调仓频率;
3、原先是卖出的资金全部买入,会造成仓位越来越不平衡,这点极不合理。现在是按理论仓位和实际资金做平衡,终于解决了困扰我很久的交易问题。比如目标转债数量4只,手持3只,总市值28万,理论上每只应配置7万。如果现金有5万则5万买入1只,如果现金有9万,则8万买入一只,剩余1万留待后续交易时平衡过去。

下载链接: https://pan.baidu.com/s/1XHqk4dFuqwslTfDp73OTSw 提取码: p658

————以下是原帖:————
自15年接触量化交易,一直都是在折腾股票策略,果仁、聚宽都在实盘自动交易中。今年受双低转债策略启发,投入了可转债,习惯上是想要量化回测策略历史效果的。几个量化平台看了看,也就优矿勉强会搞:有可转债收盘价等数据,代码格式什么的不算复杂,不过优矿是无法回测可转债的,只能自己计算持仓和现金。于是,我就写了这么个策略,敝帚自珍了许久,想想还不知道有多少疏漏给自己埋了多大的坑呢,还是放出来供有兴趣者批评指正吧。

start = '2020-01-02' # 回测起始时间
end = '2020-11-13' # 回测结束时间
universe = DynamicUniverse('HS300') # 证券池,支持股票、基金、期货、指数四种资产
benchmark = 'HS300' # 策略参考标准
freq = 'd' # 策略类型,'d'表示日间策略使用日线回测,'m'表示日内策略使用分钟线回测
refresh_rate = (1, ['14:52']) # 调仓频率,表示执行handle_data的时间间隔,若freq = 'd' 时间间隔的单位为交易日,取盘前数据,若freq = 'm' 时间间隔为分钟

def initialize(context):
global MyPosition, HighValue, MyCash, Withdraw, HoldRank, HoldNum
MyPosition = {} #持仓
MyCash = 100000 #现金
HighValue = MyCash #最高市值
Withdraw = 0 #最大回撤
HoldRank = 10 #排名多少之后卖出
HoldNum = 4 #持债支数

def handle_data(context):

global MyCash, HighValue, Withdraw
previous_date = context.previous_date.strftime('%Y%m%d')
today_date = context.now.strftime('%Y%m%d')

#每天重新计算双低排名
ConBonds = DataAPI.SecIDGet(partyID=u"",ticker=u"",cnSpell=u"",assetClass=u"B",exchangeCD="XSHE,XSHG",listStatusCD="",field=u"secID",pandas="1")
data = DataAPI.MktConsBondPerfGet(beginDate=today_date,endDate=today_date,secID=ConBonds['secID'],tickerBond=u"",tickerEqu=u"",field=u"secID,closePriceBond,bondPremRatio",pandas="1")
data.set_index('secID',inplace=True)
data['DoubleLow'] = data.closePriceBond + data.bondPremRatio
data = data.sort_values(by="DoubleLow" , ascending=True)
PosValue = MyCash

#抛出不在持有排名HoldRank的
for stock in MyPosition.keys():
try:
CurPrice = data.loc[stock]['closePriceBond']
except:
NoPrice = DataAPI.MktConsBondPerfGet(beginDate=previous_date,endDate=previous_date,secID=stock,tickerBond=u"",tickerEqu=u"",field=u"closePriceBond",pandas="1")
CurPrice = NoPrice.closePriceBond[0]
PosValue += MyPosition[stock] * CurPrice * 10 #计算当前市值
if stock not in data.index[:HoldRank]:
MyCash += MyPosition[stock] * CurPrice * 9.9 # 卖出后回收现金,交易摩擦成本按百分之一,10*0.99=9.9
del MyPosition[stock]
if PosValue > HighValue:HighValue = PosValue
if (HighValue - PosValue) / HighValue > Withdraw:Withdraw = (HighValue - PosValue) / HighValue

#买入排在HoldRank内的,总持有数量HoldNum
for i in range(HoldRank):
if len(MyPosition) == HoldNum or len(data.index) < HoldNum:break
if data.index[i] not in MyPosition.keys():
MyPosition[data.index[i]] = int(MyCash / (HoldNum - len(MyPosition)) / data['closePriceBond'][i] / 10) # 简单粗暴地资金均分买入
MyCash -= MyPosition[data.index[i]] * data['closePriceBond'][i] * 10 # 买入时不再计算交易摩擦成本,直接扣减
print(today_date + ': 最高市值 ' + str(HighValue) + ' , 当前市值 ' + str(PosValue) + ' , 最大回撤 ' + str(round(Withdraw*100,2)))
print(MyPosition)
0

walkerdu

赞同来自:

@zhouo7o
优矿数据好像又用不了了,要转专业版
是的,以前能用的函数现在要买专业版才行,彻底悲剧了。要么改用聚宽,我现在暂时退出了可转债,就没有动力去回测了。
2022-10-14 15:09 来自江苏 引用
0

zhouo7o

赞同来自:

优矿数据好像又用不了了,要转专业版
2022-10-14 09:34 来自上海 引用
0

nimrodlee

赞同来自:

请教一下,我把代码复制到优矿,为什么运行之后什么都没有输出?
2022-04-09 09:55 引用
0

goodduck

赞同来自:

测了时间跨度高点的,直接跑的比基准还低,需要再思考研究一下
2022-02-13 21:21 引用
0

goodduck

赞同来自:

这个可转债的策略是有,没有使用数据,如initial data
2022-02-13 21:14 引用
0

无双0

赞同来自:

我怎么下载下来用python运行,可是显示就是定义了函数。。是多加了空行吗? 也没有任何输出。。
2021-09-26 10:35 引用
0

杨行空军

赞同来自:

我试了下 没有交易啊 不知道哪里出问题了
2021-08-23 15:01 引用
0

觅南山

赞同来自:

楼主好人。感谢楼主
2021-08-22 21:38 引用
2

luckzpz - 像爱惜自己生命一样保护本金

赞同来自: 低低手 TuesFool

不好意思,复制百度网盘代码的时候没有下载,按它打开的复制。
所以出现了一点点错误,支付一个金币表示感谢
2021-08-22 19:48 引用
1

luckzpz - 像爱惜自己生命一样保护本金

赞同来自: ryanxzqn

果然没有print
抱歉,我下载了就能看到print了
2021-08-22 19:46 引用
0

luckzpz - 像爱惜自己生命一样保护本金

赞同来自:

感谢楼主关注,我就是按你百度网盘的内容贴的。
from datetime import datetime
import pandas as pd
start = '2021-01-01' # 回测起始时间
end = '2021-08-19' # 回测结束时间
benchmark = 'HS300' # 策略参考标准
freq = 'd' # 策略类型,'d'表示日间策略使用日线回测,'m'表示日内策略使用分钟线回测
refresh_rate = (1, ['14:52']) # 调仓频率,表示执行handle_data的时间间隔,若freq = 'd' 时间间隔的单位为交易日,取盘前数据,若freq = 'm' 时间间隔为分钟

def initialize(context):
global MyPosition, HighValue, MyCash, Withdraw, HoldRank, HoldNum, initData
MyPosition = {}
MyCash = 1000000
HighValue = MyCash
Withdraw = 0
HoldRank = 10
HoldNum = 4

ConBonds = DataAPI.BondGet(typeID="02020113",field=u"secID",pandas="1")
ZhuanGu = DataAPI.BondConvStockItemGet(secID=ConBonds['secID'],field=u"secID,convEndtime,convStoptime",pandas="1")
MeiRi = DataAPI.MktConsBondPerfGet(beginDate=start,endDate=end,secID=ConBonds['secID'],field=u"tradeDate,secID,closePriceBond,bondPremRatio",pandas="1")
initData = pd.merge(MeiRi,ZhuanGu,on='secID')
initData.fillna('2031-01-01',inplace = True)
initData['Daoqi'] = 0

for i in range(len(initData)):
initData.iloc[i,6] = (datetime.strptime(initData.iloc[i,0],"%Y-%m-%d") - datetime.strptime(min(initData.iloc[i,5],initData.iloc[i,4]),"%Y-%m-%d")).days

def handle_data(context):

global MyCash, HighValue, Withdraw
today_date = context.now.strftime('%Y-%m-%d')

#每天重新计算双低排名
data = initData.query('tradeDate=="' + today_date +'" and Daoqi < 0') #如果用不同的调仓频率,比如3天调仓,就把 < 0 改成 < -3
data.set_index('secID',inplace=True)

data['DoubleLow'] = data.closePriceBond + data.bondPremRatio
data = data.sort_values(by="DoubleLow" , ascending=True)
PosValue = MyCash

#抛出不在持有排名HoldRank的
for stock in MyPosition.keys():
CurPrice = DataAPI.MktConsBondPerfGet(beginDate=today_date,endDate=today_date,secID=stock,field=u"closePriceBond",pandas="1").closePriceBond[0]
PosValue += MyPosition[stock] * CurPrice * 10
if stock not in data.index[:HoldRank]:
MyCash += MyPosition[stock] * CurPrice * 9.9 # * 10 * 交易摩擦成本
del MyPosition[stock]
if PosValue > HighValue:HighValue = PosValue
if (HighValue - PosValue) / HighValue > Withdraw:Withdraw = (HighValue - PosValue) / HighValue

#买入排在HoldRank内的,总持有数量HoldNum,通过现金和理论仓位对买入的仓位进行了平衡
BuyMoney = min(MyCash / (HoldNum - len(MyPosition)), (MyCash / (HoldNum - len(MyPosition)) + PosValue / HoldNum) / 2)
for i in range(HoldRank):
if len(MyPosition) == HoldNum or len(data.index) < HoldNum:break
if data.index[i] not in MyPosition.keys():
MyPosition[data.index[i]] = int(BuyMoney / data['closePriceBond'][i] / 10)
MyCash -= MyPosition[data.index[i]] * data['closePriceBond'][i] * 10
2021-08-22 19:45 引用
1

luckzpz - 像爱惜自己生命一样保护本金

赞同来自: Caroline9873

这个代码贴入优矿之后,感觉策略就没有执行过。
跑完之后只有基准的结果。
2021-08-22 16:43 引用
0

天上街市

赞同来自:

收藏不看系列
2021-08-22 10:09 引用
0

垂钓者的马扎

赞同来自:

年过半百,还要学习Python,否则真落伍了
2021-08-22 10:06 引用
5

walkerdu

赞同来自: 春雷滚滚 chenjishi GLZ0514 塔格奥

鉴于复制黏贴到集思录的代码给一些朋友造成困扰,我已重新上传了新的回测代码,改动点:
1、预先取数,大幅优化回测速度;
2、可用不同调仓频率;
3、原先是卖出的资金全部买入,会造成仓位越来越不平衡,这点极不合理。现在是按理论仓位和实际资金做平衡,终于解决了困扰我很久的交易问题。比如目标转债数量4只,手持3只,总市值28万,理论上每只应配置7万。如果现金有5万则5万买入1只,如果现金有9万,则8万买入一只,剩余1万留待后续交易时平衡过去。

下载链接: https://pan.baidu.com/s/1XHqk4dFuqwslTfDp73OTSw 提取码: p658
2021-08-21 13:00 引用
0

哥割格

赞同来自:

运行了,也同楼上,结果全是0
2021-08-21 10:18 引用
0

nybbyj

赞同来自:

新代码语法没有问题,但结果全部是0
20200102: 最高市值 100000 , 当前市值 100000 , 最大回撤 0.0
{}
20200103: 最高市值 100000 , 当前市值 100000 , 最大回撤 0.0
{}
20200106: 最高市值 100000 , 当前市值 100000 , 最大回撤 0.0
{}
20200107: 最高市值 100000 , 当前市值 100000 , 最大回撤 0.0
{}
20200108: 最高市值 100000 , 当前市值 100000 , 最大回撤 0.0
{}
20200109: 最高市值 100000 , 当前市值 100000 , 最大回撤 0.0
{}
20200110: 最高市值 100000 , 当前市值 100000 , 最大回撤 0.0
{}
20200113: 最高市值 100000 , 当前市值 100000 , 最大回撤 0.0
{}
20200114: 最高市值 100000 , 当前市值 100000 , 最大回撤 0.0
{}
20200115: 最高市值 100000 , 当前市值 100000 , 最大回撤 0.0
{}
20200116: 最高市值 100000 , 当前市值 100000 , 最大回撤 0.0
2021-08-20 00:06 引用
0

驴儿驾

赞同来自:

请教楼主,优矿源码改动如下:

coding: utf-8

In[ ]:

start = '2020-01-02' # 回测起始时间
end = '2021-08-16' # 回测结束时间
universe = DynamicUniverse('HS300') # 证券池,支持股票、基金、期货、指数四种资产
benchmark = 'HS300' # 策略参考标准
freq = 'd' # 策略类型,'d'表示日间策略使用日线回测,'m'表示日内策略使用分钟线回测
refresh_rate = (1, ['14:52']) # 调仓频率,表示执行handle_data的时间间隔,若freq = 'd' 时间间隔的单位为交易日,取盘前数据,若freq = 'm' 时间间隔为分钟

def initialize(context):
global initData
ConBonds = DataAPI.BondGet(typeID="02020113",field=u"secID,maturityDate",pandas="1")
initData = DataAPI.MktConsBondPerfGet(beginDate=start,endDate=end,secID=ConBonds['secID'],field=u"tradeDate,secID,closePriceBond,bondPremRatio",pandas="1")
global MyPosition, HighValue, MyCash, Withdraw, HoldRank, HoldNum
MyPosition = {} #持仓
MyCash = 100000 #现金
HighValue = MyCash #最高市值
Withdraw = 0 #最大回撤
HoldRank = 10 #排名多少之后卖出
HoldNum = 4 #持债支数

def handle_data(context):

global MyCash, HighValue, Withdraw
previous_date = context.previous_date.strftime('%Y%m%d')
today_date = context.now.strftime('%Y%m%d')

#每天重新计算双低排名
data = initData.query('tradeDate=="' today_date '"')
data.set_index('secID',inplace=True)
data['DoubleLow'] = data.closePriceBond data.bondPremRatio
data = data.sort_values(by="DoubleLow" , ascending=True)
PosValue = MyCash

#抛出不在持有排名HoldRank的
for stock in MyPosition.keys():
try:
CurPrice = data.loc[stock]['closePriceBond']
except:
NoPrice = DataAPI.MktConsBondPerfGet(beginDate=previous_date,endDate=previous_date,secID=stock,tickerBond=u"",tickerEqu=u"",field=u"closePriceBond",pandas="1")
CurPrice = NoPrice.closePriceBond[0]
PosValue = MyPosition[stock] * CurPrice * 10 #计算当前市值
if stock not in data.index[:HoldRank]:
MyCash = MyPosition[stock] * CurPrice * 9.9 # 卖出后回收现金,交易摩擦成本按百分之一,10*0.99=9.9
del MyPosition[stock]
if PosValue > HighValue:HighValue = PosValue
if (HighValue - PosValue) / HighValue > Withdraw:Withdraw = (HighValue - PosValue) / HighValue

#买入排在HoldRank内的,总持有数量HoldNum
for i in range(HoldRank):
if len(MyPosition) == HoldNum or len(data.index) < HoldNum:break
if data.index[i] not in MyPosition.keys():
MyPosition[data.index[i]] = int(MyCash / (HoldNum - len(MyPosition)) / data['closePriceBond'][i] / 10) # 简单粗暴地资金均分买入
MyCash -= MyPosition[data.index[i]] * data['closePriceBond'][i] * 10 # 买入时不再计算交易摩擦成本,直接扣减
print(today_date ': 最高市值 ' str(HighValue) ' , 当前市值 ' str(PosValue) ' , 最大回撤 ' str(round(Withdraw*100,2)))
print(MyPosition)

以上优矿源码运行后提示语法错误,怎么回事?

File "<mercury-input-22-D98D14E60EC746028AD31647F8828A33>", line 29
data = initData.query('tradeDate=="' today_date '"')
^
SyntaxError: invalid syntax
2021-08-19 09:51 引用
0

塔格奥

赞同来自:

感谢楼主的分享
2021-08-10 10:19 引用
3

walkerdu

赞同来自: 陈干龙 天上街市 量化基本面

我来把以前代码里的巨坑填一下 -_-#

由于对优矿函数的不熟悉,导致以前写的代码里,取可转债数据的效率超级无敌低下,每次回测要巨长的时间,导致自己都懒得优化可转债策略。这两天为了获取到期时间,突然发现有个函数可以直接获取到市场里可转债的列表以及多日数据,因此优化策略如下:

简而言之,以前是每天取一次可转债数据,现在是初始化时就把回测期间每天的可转债数据全部放入缓存,每天从缓存里取一天的数据进行计算。以前的时间全耗在从优矿取数了,跑一次搞不好要一个小时,现在飞快,两三分钟搞定。

代码改动不多,看不懂的请不要问我,仅靠代码是不可能赚钱的。

def initialize(context):

初始化时增加这么几行

global initData
ConBonds = DataAPI.BondGet(typeID="02020113",field=u"secID,maturityDate",pandas="1")
initData = DataAPI.MktConsBondPerfGet(beginDate=start,endDate=end,secID=ConBonds['secID'],field=u"tradeDate,secID,closePriceBond,bondPremRatio",pandas="1")

每天重新计算双低排名

下面这两行删掉

ConBonds = DataAPI.SecIDGet(partyID=u"",ticker=u"",cnSpell=u"",assetClass=u"B",exchangeCD="XSHE,XSHG",listStatusCD="",field=u"secID",pandas="1")
data = DataAPI.MktConsBondPerfGet(beginDate=today_date,endDate=today_date,secID=ConBonds['secID'],tickerBond=u"",tickerEqu=u"",field=u"secID,closePriceBond,bondPremRatio",pandas="1")

换成下面这一行

data = initData.query('tradeDate=="' today_date '"')
2021-08-03 16:23 引用

要回复问题请先登录注册

发起人

问题状态

  • 最新活动: 2022-10-14 15:09
  • 浏览: 19915
  • 关注: 207