backtrader实现真正的多股组合操作策略,看这一篇就够了,用定时器进入策略逻辑

更新时间:2023-08-02 19:53:01 阅读: 评论:0

backtrader实现真正的多股组合操作策略,看这⼀篇就够了,
⽤定时器进⼊策略逻辑
以下策略来⾃我们编写的。
多股组合操作,是⼀种⾼级的操作模式。多股组合操作通常有两种模式,⼀种是定期调仓,即定期再平衡,⽐如每周1调仓,或每⽉1号调仓等。另⼀种是⾮定期调仓,⽐如每⽇判断,进⾏调仓,或者每隔n天进⾏调仓。
定期调仓(再平衡)通常通过定时器timer来进⼊调仓逻辑,⽽⾮定期调仓通常通过传统的策略next⽅法进⼊调仓逻辑。当然,这并⾮绝对。
打羽毛球能减肥吗这两种多股调仓操作,都不能⽤backtrader内置的⾃动确定最⼩期的⽅法来做(⽐如为了求20⽇均线,⾃动跳过前20个bar),因为有些股票有交易的⽇期很靠后,它的最⼩期很⼤,其他股票也会采⽤这个最⼩期,这会导致其他股票浪费最⼩期前的数据,因此,必须⾃⼰控制最⼩期,也就是prenext⽅法⾥必须写上lf.next()直接跳转到next。然后如果⽤到了⽐如5⽇均线这样的指标,你要⾃⼰判断数据对象线长度是否够长。下⾯的案例策略没有⽤到此类技术指标,因此没有判断线长,理论上从第⼀根bar就可执⾏逻辑。
尽管⽹上有⼀个⽤backtrader执⾏多股组合回测的案例,但并未很好地处理好多股回测中的⼀些问题。本⽂将给出完善的处理⽅案。
(基于next的⾮定期再平衡的策略实现请参考我们编写的教程和视频课程)
本⽂介绍基于定时器timer的多股定期再平衡策略的实现.
本案例的⽬的是介绍使⽤backtrader进⾏组合管理时,要注意的⼀些技术要点,策略本⾝仅供参考。策略的⼤致逻辑如下:每年5⽉1
⽇,9⽉1⽇,11⽉1⽇进⾏组合再平衡操作(若该⽇休市,则顺延到开市⽇进⾏再平衡操作)。
⾸先加载⼀组股票(股票池),在再平衡⽇,从股票池挑出⾄少上市3年,且净资产收益率roe>0.1,市盈率 pe在0到100间的股票,这组选出的股票再按成交量从⼤到⼩排序,选出前100只股票(如果选出的股票少于100只,则按实际来),将全部账户价值按等⽐例分配买⼊这些股票。
该策略反应了如下⼏个技术要点,把这些要点整明⽩,基本上就可⽤于实战了,代码更详细的解读特别是定时器timer的⽤法参考我们编写的教程和视频课程:
1 扩展PandasData类
2 第⼀个数据应该对应指数,作为时间基准
3 数据预处理:删除原始数据中⽆交易的及缺指标的记录
4 先平仓再执⾏后续买卖
5 下单量的计算⽅法
6 如何保证先卖后买以空出资⾦
7 怎样按明⽇开盘价计算下单数量
8 为⾏情数据对象提供名字
9 买卖数量如何设为100的整数倍
10 设置符合中国股市的佣⾦模式,考虑印花税
11 涨跌停板的处理
# 考虑中国佣⾦,下单量100的整数倍,涨跌停板,滑点请假借口
# 考虑⼀个技术指标,展⽰怎样处理最⼩期问题
from datetime import datetime, time
from datetime import timedelta
import pandas as pd
import numpy as np
import backtrader as bt
import os.path  # 管理路径
import os.path  # 管理路径
import sys  # 发现脚本名字(in argv[0])
import glob
单位租车
from backtrader.feeds import PandasData  # ⽤于扩展DataFeed
# 创建新的data feed类
class PandasDataExtend(PandasData):
# 增加线
lines = ('pe', 'roe', 'marketdays')
params = (('pe', 15),
('roe', 16),
('marketdays', 17), )  # 上市天数
class stampDutyCommissionScheme(bt.CommInfoBa):
'''
本佣⾦模式下,买⼊股票仅⽀付佣⾦,卖出股票⽀付佣⾦和印花税.        '''
params = (
('stamp_duty', 0.005),  # 印花税率
('commission', 0.001),  # 佣⾦率
('stocklike', True),
('commtype', bt.CommInfoBaM_PERC),
)
def _getcommission(lf, size, price, pudoexec):
'''
If size is greater than 0, this indicates a long / buying of shares.        If size is less than 0, it idicates a short / lling of shares.
'''
if size > 0:  # 买⼊,不考虑印花税
return size * price * ission
elif size < 0:  # 卖出,考虑印花税
return size * price * (lf.p.stamp_duty + ission)
el:
return 0  # just in ca for some reason the size is 0.
class Strategy(bt.Strategy):
params = dict(
rebal_monthday=[1],  # 每⽉1⽇执⾏再平衡
num_volume=100,  # 成交量取前100名
period = 5,
)
韩国美女照片
# ⽇志函数
def log(lf, txt, dt=None):
# 以第⼀个数据data0,即指数作为时间基准
dt = dt or lf.data0.datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def __init__(lf):
lf.lastRanks = []  # 上次交易股票的列表
# 0号是指数,不进⼊选股池,从1号往后进⼊股票池计算机技术与科学
lf.stocks = lf.datas[1:]
# 记录以往订单,在再平衡⽇要全部取消未成交的订单
# 移动平均线指标
晚上剪指甲有什么说法
lf.sma={d:bt.ind.SMA(d,period=lf.p.period) for d in lf.stocks}
# 定时器
lf.add_timer(
when= bt.Timer.SESSION_START,
monthdays=bal_monthday,  # 每⽉1号触发再平衡
monthcarry=True,  # 若再平衡⽇不是交易⽇,则顺延触发notify_timer
)
def notify_timer(lf, timer, when, *args, **kwargs):
# 只在5,9,11⽉的1号执⾏再平衡
if lf.data0.datetime.date(0).month in [5,9,11]:
# def next(lf):
#    print('next 账户总值', lf.data0.datetime.datetime(0), value())
#    for d in lf.stocks:
#        position(d).size!=0):
#            print(d._name, '持仓' ,lf.getposition(d).size)
def notify_order(lf, order):
if order.status in [order.Submitted, order.Accepted]:
# 订单状态 submitted/accepted,⽆动作
return
# 订单完成
if order.status in [order.Completed]:
if order.isbuy():
lf.log('买单执⾏,%s, %.2f, %i' % (order.data._name,
elif order.isll():
lf.log('卖单执⾏, %s, %.2f, %i' % (order.data._name,
el:
lf.log('订单作废 %s, %s, isbuy=%i, size %i, open price %.2f' %
(order.data._name, statusname(), order.isbuy(), ated.size, order.data.open[0]))
# 记录交易收益情况
def notify_trade(lf, trade):
if trade.isclod:
print('⽑收益 %0.2f, 扣佣后收益 % 0.2f, 佣⾦ %.2f, 市值 %.2f, 现⾦ %.2f' %
(trade.pnl, trade.pnlcomm, ission, value(), ash()))
def rebalance_portfolio(lf):
# 从指数取得当前⽇期
lf.currDate = lf.data0.datetime.date(0)
print('rebalance_portfolio currDate', lf.currDate, len(lf.stocks))
# 如果是指数的最后⼀本bar,则退出,防⽌取下⼀⽇开盘价越界错
if len(lf.datas[0]) == lf.data0.buflen():
return
# 取消以往所下订单(已成交的不会起作⽤)
for o der_list:
lf.cancel(o)
# for d in lf.stocks:
#    print('sma', d._name, lf.sma[d][0],lf.sma[d][1], d.marketdays[0])
# 最终标的选取过程
# 1 先做排除筛选过程
lf.ranks = [d for d in lf.stocks if
len(d) > 0  # 重要,到今⽇⾄少要有⼀根实际bar
and d.marketdays > 3*365  # 到今天⾄少上市
# 今⽇未停牌 (若去掉此句,则今⽇停牌的也可能进⼊,并下订单,次⽇若复牌,则次⽇可能成交)(假设原始数据中已删除⽆交易的记录)                      and d.datetime.date(0) == lf.currDate
>= 0.1
and d.pe < 100
and d.pe > 0
and len(d) >= lf.p.period
and d.clo[0] > lf.sma[d][1]
]
# 2 再做排序挑选过程
lf.ranks.sort(key=lambda d: d.volume, rever=True)  # 按成交量从⼤到⼩排序
lf.ranks = lf.ranks[0:lf.p.num_volume]  # 取前num_volume名
if len(lf.ranks) == 0:  # ⽆股票选中,则返回
return
# 3 以往买⼊的标的,本次不在标的中,则先平仓
data_toclo = t(lf.lastRanks) - t(lf.ranks)
for d in data_toclo:
print('ll 平仓', d._name, lf.getposition(d).size)
油炸虾仁o = lf.clo(data=d)
# 4 本次标的下单
# 每只股票买⼊资⾦百分⽐,预留2%的资⾦以应付佣⾦和计算误差
buypercentage = (1-0.02)/len(lf.ranks)
# 得到⽬标市值
targetvalue = buypercentage * value()
# 为保证先卖后买,股票要按持仓市值从⼤到⼩排序
lf.ranks.sort(key=lambda d: value([d]), rever=True)
lf.log('下单, 标的个数 %i, targetvalue %.2f, 当前总市值 %.2f' %
(len(lf.ranks), targetvalue, value()))
for d in lf.ranks:
# 按次⽇开盘价计算下单量,下单量是100的整数倍
size = int(
abs((value([d]) - targetvalue) / d.open[1] // 100 * 100))
validday = d.datetime.datetime(1)  # 该股下⼀实际交易⽇
if value([d]) > targetvalue:  # 持仓过多,要卖
# 次⽇跌停价近似值
lowerprice = d.clo[0]*0.9+0.02
o = lf.ll(data=d, size=size, exectype=bt.Order.Limit,
price=lowerprice, valid=validday)
el:  # 持仓过少,要买
# 次⽇涨停价近似值
upperprice = d.clo[0]*1.1-0.02
o = lf.buy(data=d, size=size, exectype=bt.Order.Limit,
price=upperprice, valid=validday)
lf.lastRanks = lf.ranks  # 跟踪上次买⼊的标的
>>>>>#
# 主程序开始
>>>>>
cerebro = bt.Cerebro(stdstats=Fal)
cerebro.addobrver(bt.obrvers.Broker)
cerebro.addobrver(bt.obrvers.Trades)
# cerebro.broker.t_coc(True)  # 以订单创建⽇的收盘价成交
# cerebro.broker.t_coo(True) # 以次⽇开盘价成交
datadir = './dataswind'  # 数据⽂件位于本脚本所在⽬录的data⼦⽬录中
datafilelist = glob.glob(os.path.join(datadir, '*'))  # 数据⽂件路径列表
maxstocknum = 20  # 股票池最⼤股票数⽬
# 注意,排序第⼀个⽂件必须是指数数据,作为时间基准
datafilelist = datafilelist[0:maxstocknum]  # 截取指定数量的股票池
print(datafilelist)
# 将⽬录datadir中的数据⽂件加载进系统
for fname in datafilelist:
df = pd.read_csv(
fname,
skiprows=0,  # 不忽略⾏
header=0,  # 列头在0⾏
)
# df = df[~df['交易状态'].isin(['停牌⼀天'])]  # 去掉停牌⽇记录
df['date'] = pd.to_datetime(df['date'])  # 转成⽇期类型
df = df.dropna()
# print(df.info())
# print(df.head())
data = PandasDataExtend(
dataname=df,
datetime=0,  # ⽇期列
open=2,  # 开盘价所在列
high=3,  # 最⾼价所在列
low=4,  # 最低价所在列
clo=5,  # 收盘价价所在列
volume=6,  # 成交量所在列
pe=7,
roe=8,
marketdays=9,
openinterest=-1,  # ⽆未平仓量列
fromdate=datetime(2002, 4, 1),  # 起始⽇2002, 4, 1
todate=datetime(2015, 12, 31),  # 结束⽇ 2015, 12, 31
plot=Fal
)
ticker = fname[-13:-4]  # 从⽂件路径名取得股票代码
cerebro.adddata(data, name=ticker)
cerebro.addstrategy(Strategy)
startcash = 10000000
ash(startcash)
# 防⽌下单时现⾦不够被拒绝。只在执⾏时检查现⾦够不够。
cerebro.broker.t_checksubmit(Fal)
comminfo = stampDutyCommissionScheme(stamp_duty=0.001, commission=0.001) cerebro.broker.addcommissioninfo(comminfo)
>gazed

本文发布于:2023-08-02 19:53:01,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/82/1126986.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:股票   数据   订单   交易   下单   策略   记录   调仓
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图