这篇文章写于自己,在长时间不复习后的快速启动,目前最全的教程,如果在哪里写的不满您心意了,请您私信告诉我,不要直接开始在评论区喷,谢谢您,请您关注我,到500粉丝,或者专栏破100订阅,后续会发免费且专业的视频和文字教程
正文
self.sma = SimpleMovingAverage(.....)
七个点
数据源、指标和策略都有*线*。
一条线是一系列点的连续性,这些点连接在一起形成线条。在谈论市场时,每天的数据源通常有以下一组点:
开盘价、最高价、最低价、收盘价、成交量、持仓量、日期时间
平均线策略
self.sma = SimpleMovingAverage(.....)
移动
av = self.sma[0]
访问
previous_value = self.sma[-1]
实例化backtrader
import backtrader as bt
if __name__ == '__main__':
cerebro = bt.Cerebro()
cerebro.run()
设置资金
cerebro.broker.setcash(100000.0)
如果不设置默认是10000.0
添加数据源
数据获取
bt.feed
的主要功能
-
数据加载:从外部数据源(如文件、API、数据库等)加载数据。
-
数据格式化:将加载的数据转换为
backtrader
所需的内部格式。 -
数据提供:在回测或实时交易过程中,按需提供数据点给策略。
bt.feeds常用参数
bt.feeds.YahooFinanceData
:从 Yahoo Finance 加载历史数据。
bt.feeds.PandasData
:从 Pandas DataFrame 加载数据。
bt.feeds.GenericCSVData
:从 CSV 文件加载数据。
bt.feeds.IBData
:从 Interactive Brokers 加载实时数据。
数据格式化
基准数据HS300
hs300 = ak.index_zh_a_hist(symbol=benchmark_code, period='daily', start_date=start_date,
end_date=end_date) if bar == "daily" else ak.index_zh_a_hist_min_em(
symbol=benchmark_code,
period='1',
start_date=start_date,
end_date=end_date)
hs300 = hs300.iloc[:, :6]
hs300.columns = ['datetime', 'open', 'close', 'high', 'low', 'volume']
hs300.index = pd.to_datetime(hs300.datetime)
hs300['openinterest'] = 0
columns_to_keep = ['datetime', 'open', 'high', 'low', 'close', 'volume', 'openinterest'] # 转换为backtrader要求的数据格式
hs300 = hs300[columns_to_keep].copy()
hs300 = hs300.dropna()
hs300 = bt.feeds.PandasData(dataname=hs300)
股票数据
data = ak.stock_zh_a_daily(symbol=symbol, adjust="qfq", start_date=start_date,
end_date=end_date) if bar == "daily" else ak.stock_zh_a_minute(symbol=symbol,
adjust="qfq")
data = data.set_index(pd.to_datetime(data['date'])) if bar == "daily" else data.set_index(
pd.to_datetime(data['day']))
data = data.dropna()
# data['open'] = data['open'] * 100
data['close'] = data['close'] * 10
data = bt.feeds.PandasData(dataname=data)
添加数据
name用于指定股票数据
cerebro.adddata(data, name=symbol)
cerebro.adddata(hs300, name='000300')
DataSeries
DataSeries ( Data Feeds 中的底层类)对象具有用于访问众所周知的OHLC(开盘价、最高价、最低价、收盘价)每日值的别名。这应该简化我们的打印逻辑的创建。
hs300['openinterest'] = 0
columns_to_keep = ['datetime', 'open', 'high', 'low', 'close', 'volume', 'openinterest']
这两行代码的作用是准备数据以符合 backtrader
框架的要求。以下是对这两行代码的详细解释:
hs300['openinterest'] = 0
这行代码的作用是为数据集 hs300
添加一个名为 openinterest
的列,并将其所有值初始化为 0。
-
openinterest
:在金融数据中,openinterest
表示未平仓合约的数量。对于股票数据,通常没有未平仓合约的概念,因此这个字段通常设置为 0。backtrader
框架在处理数据时需要这个字段,即使它的值为 0。
columns_to_keep = ['datetime', 'open', 'high', 'low', 'close', 'volume', 'openinterest']
这行代码定义了一个列表 columns_to_keep
,其中包含了 backtrader
框架所需的数据列名称。
datetime
:时间戳,表示每个数据点的时间。
open
:开盘价。
high
:最高价。
low
:最低价。
close
:收盘价。
volume
:成交量。
openinterest
:未平仓合约数量,通常设置为 0。
self.datas[0].datetime
是一个特殊的字段,它通常包含时间戳数据。backtrader
提供了多种方式来访问和处理时间戳数据。
self.datas[0].datetime.date(0)
:返回当前时间戳的日期部分(datetime.date
对象)。
self.datas[0].datetime.datetime(0)
:返回当前时间戳的完整日期和时间(datetime.datetime
对象)。
self.datas[0].datetime.timestamp(0)
:返回当前时间戳的浮点数表示(从1970年1月1日开始的秒数)。
卖出操作
这个代码实现了,连续两天降价就买入的,上涨就卖出的操作,但是 只能支持一个订单
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import datetime # 用于处理日期时间对象
import os.path # 用于管理路径
import sys # 用于找出脚本名称(在argv[0]中)
# 导入backtrader平台
import backtrader as bt
# 创建一个策略
class TestStrategy(bt.Strategy):
def log(self, txt, dt=None):
''' 本策略的日志记录函数'''
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def __init__(self):
# 保留对数据序列中"收盘价"行的引用
self.dataclose = self.datas[0].close
# 跟踪挂起的订单
self.order = None
def notify_order(self, order):
# 如果订单状态是已提交或已接受,无需操作
if order.status in [order.Submitted, order.Accepted]:
return
# 检查订单是否已完成
# 注意:如果资金不足,经纪人可能会拒绝订单
if order.status in [order.Completed]:
# 如果是买单,记录执行价格
if order.isbuy():
self.log('买入执行,%.2f' % order.executed.price)
# 如果是卖单,记录执行价格
elif order.issell():
self.log('卖出执行,%.2f' % order.executed.price)
# 记录执行订单时的条形图索引
self.bar_executed = len(self)
# 如果订单被取消、保证金不足或被拒绝,记录下来
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('订单取消/保证金不足/被拒绝')
# 记录没有挂起的订单
self.order = None
def next(self):
# 简单记录数据序列的收盘价
self.log('收盘价, %.2f' % self.dataclose[0])
# 检查是否有挂起的订单,如果有,不能发第二个订单
if self.order:
return
# 检查是否已经在市场中
if not self.position:
# 还没有...如果满足条件我们可能会买入...
if self.dataclose[0] < self.dataclose[-1]:
# 当前收盘价低于前一天的收盘价
if self.dataclose[-1] < self.dataclose[-2]:
# 前一天的收盘价低于前前一天的收盘价
# 买入,买入,买入!!!(使用默认参数)
self.log('创建买入订单, %.2f' % self.dataclose[0])
# 跟踪创建的订单以避免第二个订单
self.order = self.buy()
else:
# 已经在市场中...我们可能会卖出
if len(self) >= (self.bar_executed + 5):
# 卖出,卖出,卖出!!!(使用所有可能的默认参数)
self.log('创建卖出订单, %.2f' % self.dataclose[0])
# 跟踪创建的订单以避免第二个订单
self.order = self.sell()
if __name__ == '__main__':
# 创建一个Cerebro实体
cerebro = bt.Cerebro()
# 添加策略
cerebro.addstrategy(TestStrategy)
# 数据在示例的子文件夹中。需要找到脚本的位置
# 因为它可能从任何地方被调用
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')
# 创建一个数据源
data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
# 不要传递此日期之前的值
fromdate=datetime.datetime(2000, 1, 1),
# 不要传递此日期之后的值
todate=datetime.datetime(2000, 12, 31),
# 不要传递此日期之后的值
reverse=False)
# 将数据源添加到Cerebro
cerebro.adddata(data)
# 设置我们希望的初始现金
cerebro.broker.setcash(100000.0)
# 打印初始条件
print('初始投资组合价值:%.2f' % cerebro.broker.getvalue())
# 运行所有
cerebro.run()
# 打印最终结果
print('最终投资组合价值:%.2f' % cerebro.broker.getvalue())
佣金的设置、
佣金设置之后,后续可以进行记录,并扣去用户金额
cerebro.broker.setcommission(commission=0.001) # 0.1% ...除以100去掉百分号
这个代码实现了如何设置佣金并打印出来
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import datetime # 用于处理日期时间对象
import os.path # 用于管理路径
import sys # 用于找出脚本名称(在argv[0]中)
# 导入backtrader平台
import backtrader as bt
# 创建一个策略
class TestStrategy(bt.Strategy):
def log(self, txt, dt=None):
''' 本策略的日志记录函数'''
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def __init__(self):
# 保留对数据序列中"收盘价"行的引用
self.dataclose = self.datas[0].close
# 跟踪挂起的订单、买入价格和手续费
self.order = None
self.buyprice = None
self.buycomm = None
def notify_order(self, order):
# 如果订单状态是已提交或已接受,无需操作
if order.status in [order.Submitted, order.Accepted]:
return
# 检查订单是否已完成
# 注意:如果资金不足,经纪人可能会拒绝订单
if order.status in [order.Completed]:
if order.isbuy():
self.log('买入执行,价格:%.2f,成本:%.2f,手续费 %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
else: # 卖出
self.log('卖出执行,价格:%.2f,成本:%.2f,手续费 %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
# 记录执行订单时的条形图索引
self.bar_executed = len(self)
# 如果订单被取消、保证金不足或被拒绝,记录下来
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('订单取消/保证金不足/被拒绝')
# 记录没有挂起的订单
self.order = None
def notify_trade(self, trade):
# 如果交易未关闭,则返回
if not trade.isclosed:
return
# 记录交易的利润,毛利润和净利润
self.log('操作利润,毛利润 %.2f,净利润 %.2f' %
(trade.pnl, trade.pnlcomm))
def next(self):
# 简单记录数据序列的收盘价
self.log('收盘价, %.2f' % self.dataclose[0])
# 检查是否有挂起的订单,如果有,不能发第二个订单
if self.order:
return
# 检查是否已经在市场中
if not self.position:
# 还没有...如果满足条件我们可能会买入...
if self.dataclose[0] < self.dataclose[-1]:
# 当前收盘价低于前一天的收盘价
if self.dataclose[-1] < self.dataclose[-2]:
# 前一天的收盘价低于前前一天的收盘价
# 买入,买入,买入!!!(使用默认参数)
self.log('创建买入订单, %.2f' % self.dataclose[0])
# 跟踪创建的订单以避免第二个订单
self.order = self.buy()
else:
# 已经在市场中...我们可能会卖出
if len(self) >= (self.bar_executed + 5):
# 卖出,卖出,卖出!!!(使用所有可能的默认参数)
self.log('创建卖出订单, %.2f' % self.dataclose[0])
# 跟踪创建的订单以避免第二个订单
self.order = self.sell()
if __name__ == '__main__':
# 创建一个Cerebro实体
cerebro = bt.Cerebro()
# 添加策略
cerebro.addstrategy(TestStrategy)
# 数据在示例的子文件夹中。需要找到脚本的位置
# 因为它可能从任何地方被调用
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')
# 创建一个数据源
data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
# 不要传递此日期之前的值
fromdate=datetime.datetime(2000, 1, 1),
# 不要传递此日期之后的值
todate=datetime.datetime(2000, 12, 31),
# 不要传递此日期之后的值
自定义策略参数
设置自定义策略中,需要接收的参数,这个参数需要手动传入,和function一样
params = (
(‘myparam’, 27), (‘exitbars’, 5),
)
在向cerebro中添加分析器,策略等时需要自己携带参数
# 添加策略
cerebro.addstrategy(TestStrategy, myparam=20, exitbars=7)
如果公司使用的是较老版本可以使用addsizer
cerebro.addsizer(bt.sizers.FixedSize, stake=10)`
展示策略传参
from __future__ import (absolute_import, division, print_function,
unicode_literals)
import datetime # 用于处理日期时间对象
import os.path # 用于管理路径
import sys # 用于找出脚本名称(在argv[0]中)
# 导入backtrader平台
import backtrader as bt
# 创建一个策略
class TestStrategy(bt.Strategy):
params = (
('exitbars', 5), # 设置退出条形图的数量
)
def log(self, txt, dt=None):
''' 本策略的日志记录函数'''
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def __init__(self):
# 保留对数据序列中"收盘价"行的引用
self.dataclose = self.datas[0].close
# 跟踪挂起的订单、买入价格和手续费
self.order = None
self.buyprice = None
self.buycomm = None
def notify_order(self, order):
# 如果订单状态是已提交或已接受,无需操作
if order.status in [order.Submitted, order.Accepted]:
return
# 检查订单是否已完成
# 注意:如果资金不足,经纪人可能会拒绝订单
if order.status in [order.Completed]:
if order.isbuy():
self.log('买入执行,价格:%.2f,成本:%.2f,手续费 %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
else: # 卖出
self.log('卖出执行,价格:%.2f,成本:%.2f,手续费 %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
# 记录执行订单时的条形图索引
self.bar_executed = len(self)
# 如果订单被取消、保证金不足或被拒绝,记录下来
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('订单取消/保证金不足/被拒绝')
# 记录没有挂起的订单
self.order = None
def notify_trade(self, trade):
# 如果交易未关闭,则返回
if not trade.isclosed:
return
# 记录交易的利润,毛利润和净利润
self.log('操作利润,毛利润 %.2f,净利润 %.2f' %
(trade.pnl, trade.pnlcomm))
def next(self):
# 简单记录数据序列的收盘价
self.log('收盘价, %.2f' % self.dataclose[0])
# 检查是否有挂起的订单,如果有,不能发第二个订单
if self.order:
return
# 检查是否已经在市场中
if not self.position:
# 还没有...如果满足条件我们可能会买入...
if self.dataclose[0] < self.dataclose[-1]:
# 当前收盘价低于前一天的收盘价
if self.dataclose[-1] < self.dataclose[-2]:
# 前一天的收盘价低于前前一天的收盘价
# 买入,买入,买入!!!(使用默认参数)
self.log('创建买入订单, %.2f' % self.dataclose[0])
# 跟踪创建的订单以避免第二个订单
self.order = self.buy()
else:
# 已经在市场中...我们可能会卖出
if len(self) >= (self.bar_executed + self.params.exitbars):
# 卖出,卖出,卖出!!!(使用所有可能的默认参数)
self.log('创建卖出订单, %.2f' % self.dataclose[0])
# 跟踪创建的订单以避免第二个订单
self.order = self.sell()
if __name__ == '__main__':
# 创建一个Cerebro实体
cerebro = bt.Cerebro()
# 添加策略
cerebro.addstrategy(TestStrategy)
# 数据在示例的子文件夹中。需要找到脚本的位置
# 因为它可能从任何地方被调用
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')
# 创建一个数据源
data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
# 不要传递此日期之前的值
fromdate=datetime.datetime(2000, 1, 1),
# 不要传递此日期之后的值
添加指标
-
如果收盘价大于平均值,以市价“买入”
-
如果处于市场中,则在收盘价小于平均值时“卖出”
self.sma = bt.indicators.MovingAverageSimple(self.datas[0], period=self.params.maperiod)
自动格式化小数
backtrader 提供的Yahoo数据源在应用调整后,将值四舍五入到小数点后两位。
最终绘图返回图像
安装绘图依赖matplotlib
pip install matplotlib
运行绘图
cerebro.plot()
需要画那条线就使用bt.indicators进行绘画
# 用于绘图显示的指标
bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
bt.indicators.WeightedMovingAverage(self.datas[0], period=25).subplot = True
bt.indicators.StochasticSlow(self.datas[0])
bt.indicators.MACDHisto(self.datas[0])
rsi = bt.indicators.RSI(self.datas[0])
bt.indicators.SmoothedMovingAverage(rsi, period=10)
bt.indicators.ATR(self.datas[0]).plot = False
策略方法
init
用于在自定义策略中进行初始化操作
start
用于执行基础逻辑
next
用于在每个start后执行
stop
用于在数据耗尽时执行
一键导入backtrader依赖
import backtrader as bt
import backtrader.indicators as btind
import backtrader.feeds as btfeeds
DataSeries快捷访问
dataseries的快捷访问可以通过self.datas 数组的项目可以使用额外的自动成员变量直接访问:
- self.data 目标是 self.datas[0]
当计算时可以直接计算
sma = btind.SimpleMovingAverage(self.data, period=self.params.period)
class MyStrategy(bt.Strategy):
params = dict(period=20)
def __init__(self):
sma = btind.SimpleMovingAverage(self.data, period=self.params.period)
也可以直接省略数据源,因为我们已经进行了添加
class MyStrategy(bt.Strategy):
params = dict(period=20)
def __init__(self):``sma = btind.SimpleMovingAverage(period=self.params.period)
所有的操作指标都可以操作
所有的内容都是可以直接操作的
比如:
class MyStrategy(bt.Strategy):
params = dict(period1 = 20, period2 = 25, period3 = 10, period4)
def __init__(self):
sm1 = btind.SimpleMovingAverage(self.data,self.p.period1)
sm2 = btind.SimpleMovingAverage(sm1,self.p.period2)
Num = sum2 - sum1 + self.data.close
boolNum = sum2 >= sum1
参数
带有默认值的参数被声明为类属性(元组的元组或类似字典的对象)
关键字参数( `` kwargs`` )会被扫描以寻找匹配的参数,如果找到则从 `` kwargs`` 中删除,并将值赋给对应的参数
最后,可以通过访问成员变量
self.params
(简写:self.p
)来在类的实例中使用参数
Lines指标线
params = ((‘period’, 20),)
def __init__(self):
# 获取简单移动平均线
sma = btind.SimpleMovingAverage(self.data, period=self.p.period)
self.movav = btind.SimpleMovingAverage(self.data, period=self.p.period)
def next(self):
if self.movav.lines.sma[0] > self.data.lines.close[0]:
print(‘简单移动平均线大于收盘价’)
两个包含 lines
属性的对象已经暴露出来:
self.data
它有一个lines
属性,并包含一个close
属性
self.movav
是一个SimpleMovingAverage
指标 它有一个lines
属性,并包含一个sma
属性
而且, lines ,即 close
和 sma
可以从点( 索引 0 )进行查询并比较值。
还存在一种简写访问方式:
xxx.lines
可以简写为xxx.l
xxx.lines.name
可以简写为xxx.lines_name
Strategies 和 Indicators 这类复杂对象提供了快速访问数据的 lines
`- ``self.data_name``提供了直接访问``self.data.lines.name``的方式 - 同样适用于编号数据变量: ``self.data1_name
-> self.data1.lines.name
此外,可以直接访问线路名称:
-
通过
self.data.close
和self.movav.sma
但是,与前面的表示不同,这种表示方式无法清楚地表明是否正在访问 lines 。
传输线长度
传输线在执行过程中会动态增长,因此可以随时通过调用Python的标准 len 函数来测量其长度。
这适用于例如:
-
数据源(Data Feeds)
-
策略(Strategies)
-
指示器(Indicators)
在数据被 预加载 的情况下,另外一个属性适用于 数据源(Data Feeds) :
-
方法 buflen
该方法返回 数据源(Data Feed) 可用的实际柱数量。
len 和`buflen`之间的区别 -
len
报告已处理的条的数量
buflen
报告数据源已加载的总条数
如果两者返回相同的值,那么要么没有预加载的数据,要么条的处理已经消耗了所有的预加载条(除非系统连接了实时数据源,否则这意味着处理结束)
Lines和Params的继承
一种”元语言” 用于支持 Params 和*Lines*的声明。已经尽力与Python的标准继承规则兼容。
继承应该按预期工作:
支持多重继承
继承基类的Params
如果多个基类定义了相同的param,则使用继承列表中最后一个类的默认值
如果在子类中重新定义了相同的param,则新的默认值将覆盖基类的默认值
支持多重继承
继承所有基类的Lines。如果在基类中多次使用了相同的名称,则只会有一个版本的线路
索引:0 和 -1 *** ** ** ** ** ** ** ** ** Lines*如前所述是线系列,它们由一系列的点组成,当这些点在时间轴上连接在一起时形成一条线(就像将所有收盘价沿时间轴连接在一起时)
要在常规代码中访问这些点,选择使用以 0 为基础的方法来获取/设置当前的 get/set 实例。
策略仅用于 获取*值。指标还可 设置*值。
从前面简单策略示例中可以简单看到 next
方法:
if self.movav.lines.sma[0] > self.data.lines.close[0]:
print(‘Simple Moving Average is greater than the closing price’)
该逻辑是通过应用索引 0
来获取移动平均线和当前收盘价的当前值。
注意
实际上,对于索引 0
和应用逻辑/算术运算符进行比较时,可以直接进行比较,如下所示:
```python if self.movav.lines.sma > self.data.lines.close:
…
``` 请看本文档后面的操作符解释。
设置被用于开发一个`指标`,因为指标需要通过设置当前输出值来进行计算。可以按照以下方式计算当前“get/set”点的简单移动平均值:
self.line[0] = math.fsum(self.data.get(0, size=self.p.period)) / self.p.period
访问前一个“set”点是根据Python对数组/可迭代对象的定义进行建模:
-
它指向数组的最后一个元素
该平台认为在当前实时的“get/set”点之前,最后一个“set”项为 -1
。
因此,将当前的“close”与 前一个 “close”进行比较是一个 0
对 -1
的事情。例如,在策略中:
if self.data.close[0] > self.data.close[-1]:
print(‘今天的收盘价较高’)
当然,在逻辑上, -1
之前的“set”价格将以 -2,-3,...
进行访问。
切片 *** ** **
backtrader 不支持对 lines 对象进行切片,这是一个设计决策,遵循``[0]``和``[-1]``的索引方案。对于常规的可索引Python对象,您可以执行以下操作:```python
myslice = self.my_sma[0:] # 从开头到结尾取一个切片
但请记住,选择0意味着选择当前的值,之后没有值了。另外:
myslice = self.my_sma[0:-1] # 从开头到结尾减去1取一个切片
再次说明…0表示当前的值,-1表示最新(前一个)的值。这就是为什么从0到-1的切片在 backtrader 生态系统中没有意义。
如果将来支持切片,它可能会是这样的:
myslice = self.my_sma[:0] # 从当前点向前切片到开头
或者:
myslice = self.my_sma[-1:0] # 最后一个值和当前值
或者:
myslice = self.my_sma[-3:-1] # 从最后一个值向前切片到倒数第三个值 ```获取切片 =========
仍然可以获取具有最新值的数组。语法为:
myslice = self.my_sma.get(ago=0, size=1) # 显示默认值
这将返回一个包含 1
个值( size=1
)的数组,以当前时刻 0
作为向后查找的起点。
要从当前时刻获取10个值(即最后10个值):
myslice = self.my_sma.get(size=10) # 默认时刻为0
当然,数组的顺序是您期望的。最左侧的值是最旧的值,最右侧的值是最新的值(它是一个普通的Python数组,而不是一个 lines 对象)
要跳过仅当前时刻并获取最后10个值:
myslice = self.my_sma.get(ago=-1, size=10)
行:延迟索引 *** ** ** ** ** ** []
操作符的语法用于在“next”逻辑阶段提取单个值。 Lines 对象还支持通过 延迟的Lines对象 来通过 __init__
阶段访问值的附加表示法。
假设我们对逻辑感兴趣的是将之前的 close 值与 简单移动平均线 的实际值进行比较。可以在每个“next”迭代中手动执行此操作,也可以生成一个预先制作的’lines’对象:
class MyStrategy(bt.Strategy):
params = dict(period=20)
def __init__(self):
self.movav = btind.SimpleMovingAverage(self.data, period=self.p.period) self.cmpval = self.data.close(-1) > self.sma
def next(self):
if self.cmpval[0]:
print(‘之前的收盘价高于移动平均线’)
在这里,使用了”(delay)”符号:
这会生成一个
close
价格的副本,但是延迟了“-1”。而比较
self.data.close(-1) > self.sma
生成另一个 lines 对象,如果条件为True
,则返回1
,如果为False
,则返回0
。
Lines耦合 **************操作符 ()
可以像上面展示的那样与 delay
值一起使用,以提供 lines 对象的延迟版本。
如果使用语法时 未提供 delay
值,则返回一个 LinesCoupler
lines 对象。它旨在在具有不同时间框架的 datas 之间建立耦合。
具有不同时间框架的数据源具有不同的 长度,在其上操作的指标会复制数据的长度。例如:
每年的日数据源大约有250个柱状图
每年的周数据源有52个柱状图
尝试创建一个操作(例如)来比较两个在上述数据上操作的 简单移动平均线 将会失败。不清楚如何将每日时间框架的250个柱状图与每周时间框架的52个柱状图配对。
读者可以想象背后进行了一种 date
比较,以找出一天 - 周的对应关系,但是:
Indicators
只是数学公式,不包含 datetime 信息它们对环境一无所知,只知道如果数据提供了足够的值,就可以进行计算。
空调用符 ()
来拯救我们:class MyStrategy(bt.Strategy):
params = dict(period=20)
def __init__(self):
# data0是每日数据 sma0 = btind.SMA(self.data0, period=15) # 15日均线 # data1是每周数据 sma1 = btind.SMA(self.data1, period=5) # 5周均线
self.buysig = sma0 > sma1()
def next(self):
if self.buysig[0]:
print(‘每日均线大于每周均线1’)
这里,较大时间框架的指标 sma1 与每日时间框架通过 sma1() 耦合。这返回一个与 sma0 的较大数量的条形兼容的对象,并复制由 sma1 产生的值,从而将52周条形有效地分散在250日条中
运算符,使用自然结构 *** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** **
为了实现“易用性”的目标,该平台允许(在Python的限制下)使用运算符。为了进一步增强此目标,使用运算符分为两个阶段。
阶段1 - 运算符创建对象
一个示例已经被看到,即使并非特意如此。在初始化阶段(__init__方法)中,像指标和策略这样的对象会由运算符创建可以进行操作、赋值或保留为后续在策略逻辑的评估阶段中使用的对象。再次给出 SimpleMovingAverage 的一个潜在实现,进一步分解成步骤。
SimpleMovingAverage 指标的 __init__ 方法中的代码可能如下所示:
def __init__(self):
# 求 N 期值的和 - datasum 现在是一个 Lines 对象 # 当使用运算符 [] 和索引 0 查询时,返回当前和
datasum = btind.SumN(self.data, period=self.params.period)
# datasum (虽然是 Lines 对象,但只有一行) 可以被自然地除以 int/float # 就像这个例子。它实际上也可以被另一个 Lines 对象除以。 # 运算返回一个赋值给 “av” 的对象,再次使用 [0] 查询时返回当前瞬时平均值
av = datasum / self.params.period
# av Lines 对象可以被自然地赋值给该指标提供的命名行。 # 使用该指标的其他对象将直接访问该计算
self.line.sma = av
在 Strategy 的初始化期间展示了更完整的用例:
class MyStrategy(bt.Strategy):
sma = btind.SimpleMovingAverage(self.data, period=20)
close_over_sma = self.data.close > sma sma_dist_to_high = self.data.high - sma
sma_dist_small = sma_dist_to_high < 3.5
# 不幸的是,“and”不能在Python中重载, # 因为它是一种语言结构而不是运算符, # 因此平台必须提供一个函数来模拟它
sell_sig = bt.And(close_over_sma, sma_dist_small)
在上述操作完成后, sell_sig 是一个 Lines 对象, 可以在策略的逻辑中使用,指示条件是否满足。
第二阶段 - 自然的运算符
首先我们要记住,策略有一个 next
方法,在系统处理每个柱时调用这个方法。在第二阶段,运算符实际处于模式2中。基于前面的例子:
class MyStrategy(bt.Strategy):def __init__(self): self.sma = sma = btind.SimpleMovinAverage(self.data, period=20) close_over_sma = self.data.close > sma self.sma_dist_to_high = self.data.high - sma sma_dist_small = sma_dist_to_high < 3.5 # 不幸的是,在 Python 中无法重写"and",因为它是一种语言结构而不是运算符,所以平台必须提供一个函数来模拟它 self.sell_sig = bt.And(close_over_sma, sma_dist_small)
def next(self):
# 虽然这看起来不像是一个”运算符”实际上它确实是,因为该对象正在被测试以获得一个 True/False 的响应
if self.sma > 30.0:
print(‘sma 大于 30.0’)
if self.sma > self.data.close:
print(‘sma 高于收盘价’)```python
if self.sell_sig: # 如果 sell_sig 为 True,也可以写成:if sell_sig == True:
print(‘sell sig 为 True’)
else:
print(‘sell sig 为 False’)
if self.sma_dist_to_high > 5.0:
print(‘距离 sma 到高点的距离大于 5.0’)
这不是一个非常有用的策略,只是一个例子。在阶段 2 中,操作符返回预期的值(如果测试真值则返回布尔值,如果与浮点数进行比较则返回浮点数),并且算术运算也会返回预期的结果。
一些未覆盖的操作符/函数
Python 不允许覆盖所有内容,因此提供了一些函数来处理这些情况。
操作符:逻辑控制:
and
->And
or
->Or
逻辑控制:
if
->If
函数:
any
->Any
all
->All
cmp
->Cmp
max
->Max
min
->Min
sum
->Sum
Sum
实际上使用math.fsum
作为底层操作,因为该平台处理的是浮点数,而应用常规的sum
可能会影响精度。
reduce
->Reduce
这些实用运算符/函数用于操作可迭代对象。可迭代对象的元素可以是常规的 Python 数值类型(整数、浮点数,…),也可以是具有 Lines 的对象。
以下是一个生成非常简单的购买信号的示例代码:
def __init__(self):
sma1 = btind.SMA(self.data.close, period=15) self.buysig = bt.And(sma1 > self.data.close, sma1 > self.data.high)
def next(self):
if self.buysig[0]:
pass # 在这里做些什么
显然,如果 sma1
高于最高价,则它肯定比收盘价更高。但是,重点是展示了 bt.And
的用法。
使用 bt.If
:
```python class MyStrategy(bt.Strategy):
def __init__(self):
sma1 = btind.SMA(self.data.close, period=15) high_or_low = bt.If(sma1 > self.data.close, self.data.low, self.data.high) sma2 = btind.SMA(high_or_low, period=15)
解析:
-
对
data.close
进行period=15
的SMA
计算- 然后-
如果 sma 的值大于
close
,则返回low
,否则返回high
请记住,当调用
bt.If
时,并不会返回实际值。它返回一个类似于 SimpleMovingAverage 的 Lines 对象。系统运行时将会计算这些值。
-
生成的
bt.If
Lines 对象随后被输送到第二个SMA
中,该对象有时会使用low
价格,有时会使用high
价格进行计算。
-
这些 函数 也接受数值参数。下面是一个带有修改的示例:
``` python class MyStrategy(bt.Strategy):
def __init__(self):
sma1 = btind.SMA(self.data.close, period=15) high_or_30 = bt.If(sma1 > self.data.close, 30.0, self.data.high) sma2 = btind.SMA(high_or_30, period=15)
现在,第二个移动平均线根据 sma
和 close
的逻辑状态,选择使用 30.0
或者 high
价格进行计算。.. 注意::
值 30
在内部被转换为一个伪可迭代对象,该对象总是返回 30
详细解读
最小周期
在最小周期中我们使用next方法进行,什么叫做最小周期,就是在回测数据中,会以行的形式回执,那么最小周期就是每一行走完的迭代过程
迭代器的方法
-
next 方法
它将在每次迭代时调用。 行迭代器 具有的将作为逻辑/计算基础的 datas 数组已经被平台移动到了下一个索引位置(除非进行了“数据回放”)。
当满足 行迭代器 的 最小周期 时,将调用该方法。关于这一点稍后会详细解释。但是因为它们不是常规的迭代器,所以还有两个额外的方法:
-
prenext
在`行迭代器``的 最小周期 满足之前调用。
-
nextstart
当`行迭代器``的 最小周期 满足时,仅调用 一次 。
默认行为是将调用转发给
next
,但如果需要的话,当然可以覆盖它。
迭代器的快捷方法
为了加快操作速度, 指示器 支持了一个批处理操作模式,被称为 runonce 。这并不是严格需要的(只需要一个 next
方法就足够了),但它大大缩短了时间。
runonce 方法规则无效化了使用索引为0的 get/set 位置,并依赖于对保存数据的底层数组进行直接访问,并传递给每个状态的正确索引。
下面定义的方法遵循了 next
家族的命名方式: - once(self, start, end)
当满足最小周期时调用。在内部数组的从开始到结束的位置之间必须进行处理,这些位置是从内部数组的起始位置开始计算的。
preonce(self, start, end)
在满足最小周期之前调用。
oncestart(self, start, end)
当满足最小周期时,仅调用一次。
默认行为是将调用转发给
once
,但如果需要,可以覆盖。
图片胜过千言万语,在这种情况下可能还有一个例子。SimpleMovingAverage能够解释它:
class SimpleMovingAverage(指标):
lines = (‘sma’,) params = dict(period=20) def __init__(self):
def prenext(self):
print(‘prenext:: 当前周期:’, len(self))
def nextstart(self):
print(‘nextstart:: 当前周期:’, len(self)) # 模拟默认操作…调用next self.next()
def next(self):
print(‘next:: 当前周期:’, len(self))
实例化可以如下所示:
sma = btind.SimpleMovingAverage(self.data, period=25)
简要解释:
假设传递给移动平均值的数据是标准数据源,其默认周期为
1
,即数据源产生一根没有初始延迟的K线。然后, “period=25” 实例化的移动平均值将按以下方式调用其方法:
prenext
调用 24 次
nextstart
调用 1 次(随后调用next
)
next
调用 n 次,直到 数据源 耗尽为止让我们来使用杀手指标:在另一个简单移动平均线(SimpleMovingAverage)上计算一个简单移动平均线(SimpleMovingAverage)。实例化的代码如下:
```python sma1 = btind.SimpleMovingAverage(self.data, period=25)
sma2 = btind.SimpleMovingAverage(sma1, period=20) ```
现在发生的情况是:
-
对于 sma1 ,与上述相同
-
sma2 接收了一个 数据源 ,其 最小周期 为25,即我们的 sma1
-
执行了以下 SMA2 方法:
-
prenext 方法首先执行了25 + 18次,总共43次 - 前25次用于产生 sma1 的第一个可靠值 - 后18次用于累计额外的 sma1 值- 以19个值的总数进行调用(在第25次调用之后还有18次)。
-
-
nextstart
会被调用1次(依次调用next
) -
next
会被调用n次,直到 数据流 用尽
当系统已经处理了44个条形图时,平台会调用 next
。
最小周期 已经自动调整为传入的`数据`。
策略和指标都遵循这种行为:
-
只有当自动计算的最小周期达到之后,才会调用
next
(除了首次调用nextstart
)
对于 runonce 批处理操作模式,对于 preonce
, oncestart
和 once
也适用相同的规则。
最小周期 的行为可以被修改,尽管不建议这样做。如果希望修改,可以在策略或指标中使用 setminperiod(minperiod)
方法。
开始运行
启动和运行至少需要 3 个 Lines 对象:- 一个数据源
-
一个策略(实际上是从策略派生的类)
-
一个Cerebro(西班牙语中的“大脑”)
策略(派生)类
在继续之前,并为了更简化的方法,请检查文档中的*信号*部分,如果不希望子类化策略。使用本平台的目标是对数据进行回测,这是在策略(派生类)中完成的。
至少需要自定义的有两个方法:
__init__
next
在初始化过程中,对数据进行指标计算和其他计算的准备,以便后续应用逻辑。
接下来的方法会被调用来对每个数据条应用逻辑。
如果传递了不同时间框架的数据源(因此有不同的数据条数),则 next
方法将被调用用于主数据源(在cerebro中传递的第一个数据源),它必须是时间框架较小的数据。
如果使用了数据回放功能, next
方法将多次被调用,用于对同一数据条进行回放。
一个基本的策略派生类:
class MyStrategy(bt.Strategy):
self.sma = btind.SimpleMovingAverage(self.data, period=20)
def next(self):
if self.sma > self.data.close:
self.buy()
elif self.sma < self.data.close:
self.sell()
策略有其他可重写的方法(或挂钩点):
python class MyStrategy(bt.Strategy):
def __init__(self):
self.sma = btind.SimpleMovingAverage(self.data, period=20)
def next(self):
if self.sma > self.data.close:
submitted_order = self.buy()
elif self.sma < self.data.close:
submitted_order = self.sell()
def start(self):
print(‘即将开始回测’)
def stop(self):
print(‘回测已完成’)
def notify_order(self, order):
print(‘已接收到一个新的/更改的/执行的/撤销的订单’)
“start”和“stop”方法应该是不言自明的。按照打印函数中的文本,预计“notify_order”方法会在策略需要通知时被调用。使用案例:
请求买入或卖出(如下所示)
买入/卖出将返回一个“order”,该订单将被提交给经纪人。将提交的订单保留下来由调用者决定。
例如,可用于确保在订单未决时不提交新订单。
如果订单被接受/执行/取消/更改,经纪人将通过通知方法将状态更改(例如执行数量)发送回策略
buy
/sell
/close
使用底层的 经纪人 和*大小配置器*向经纪人发送买入/卖出订单。
也可以手动创建一个订单并将其传递给经纪人来完成相同的操作。但是,这个平台旨在让使用者能够轻松使用。
close
会获取当前的市场仓位并立即平仓。
getposition
(或属性 “position”)返回当前的市场仓位。
setsizer
/getsizer
(或属性 “sizer”)这些允许设置/获取底层的仓位大小配置器。可以通过不同的仓位大小配置器来对同一情况进行不同的配置(固定大小、与资本成比例、指数增长)。
python class MyStrategy(bt.Strategy):
params = ((‘period’, 20),)
def __init__(self):
self.sma = btind.SimpleMovingAverage(self.data, period=self.params.period)
注意如何不再用固定值20实例化 SimpleMovingAverage
,而是用已为策略定义的参数”period”来实例化。
Cerebro
一旦数据源可用并且策略已定义,Cerebro实例是将所有内容汇总并执行操作的关键。实例化一个很简单:
python cerebro = bt.Cerebro() ```如果没有特殊要求,缺省值会生效。
创建了一个默认的经纪人
操作没有佣金
数据源将被预加载
默认的执行模式是batch(批量操作),速度更快
所有指标都必须支持
runonce
模式以获得最快的速度。平台中包含的指标都支持。自定义指标不需要实现runonce功能。
Cerebro
将模拟它,这意味着那些不兼容runonce的指标将运行得较慢。但系统的大部分操作仍将在批处理模式下运行。
由于已经有了数据源和策略(之前创建的),将它们组合并使其运行的标准方式是:
cerebro.adddata(data) cerebro.addstrategy(MyStrategy, period=25) cerebro.run()
请注意以下事项:
数据源“实例”被添加进来
MyStrategy “类”与参数(kwargs)一起被添加进来,这些参数将被传递给它。
MyStrategy的实例化将由cerebro在后台完成,并且
addstrategy
中的任何kwargs都将被传递给它。用户可以根据需要添加任意多的策略和数据源。策略之间如何协调通信(如果需要的话)并没有受到平台的限制。
当然,Cerebro提供了额外的可能性:
决定预加载和操作模式:
cerebro = bt.Cerebro(runonce=True, preload=True)这里有一个限制:
runonce
需要预加载(否则无法运行批量操作)。当然,预加载数据源不强制要求使用runonce
。
setbroker
/getbroker
(和 broker 属性)如果需要,可以设置自定义的经纪人。也可以访问实际的经纪人实例。
绘图。在普通情况下非常简单:
cerebro.run() cerebro.plot()plot 函数可以接受一些参数用于自定义。-
numfigs=1
如果图太密集,可以将其分解为多个图。
plotter=None
可以传递一个自定义的绘图实例,cerebro 不会自动初始化一个默认的实例。
**kwargs
- 标准关键字参数这些参数将传递给绘图实例。
如上所述,Cerebro 接收一个派生自 Strategy 类的类(而不是实例)以及调用 “run” 时将传递给它的关键字参数。
这样做是为了实现优化。相同的 Strategy 类将根据需要实例化多次,并使用新的参数。如果一个实例被传递给 cerebro… 这是不可能的。如下所示,要求进行优化:
cerebro.optstrategy(MyStrategy, period=xrange(10, 20))
optstrategy
方法和 addstrategy
具有相同的签名,但会执行额外的工作以确保优化如预期运行。 一个策略可以期望作为策略的一个普通参数的是一个 range ,而 addstrategy
不会对传递的参数进行任何假设。
另一方面, optstrategy
会理解可迭代对象是一组值,必须按照顺序传递给每个 Strategy 类的实例化。
需要注意的是,传递的是一组值而不是单个值。在这个简单的例子中,将尝试使用这个策略的 10 个值 10 -> 19(20 是上限)。
如果开发了一个具有额外参数的更复杂的策略,可以将它们全部传递给 optstrategy 。不需要进行优化的参数可以直接传递,而无需用户创建一个只包含一个值的假可迭代对象。例如:
` cerebro.optstrategy(MyStrategy, period=xrange(10, 20), factor=3.5) `
optstrategy
方法会创建 dummy iterable 来处理 factor(这是一个必需的),dummy iterable 只有一个元素(在这个例子中为 3.5)。
交互式 Python Shell 和某些类型的冻结可执行文件在 Windows 下对 Python 的 multiprocessing
模块存在问题。
Cerebro
这个类是 backtrader
的核心,因为它充当以下功能的中央点:
收集所有输入( 数据源 )、参与者( 策略)、观察者(* 观察器 )、分析器( 分析器 )和文档编写器( 编写器*), 确保节目在任何时刻都可以继续进行。
执行回测/实时数据供给/交易
返回结果
提供绘图功能
收集输入
-
首先创建一个
cerebro
实例:cerebro = bt.Cerebro(**kwargs)
可以使用一些
**kwargs
来控制执行情况,详情请参见参考文档 (同样的参数也可以稍后应用于run
方法)1. 添加 数据源
最常见的模式是
cerebro.adddata(data)
, 其中data
是一个已经实例化的 数据源 。例如:data = bt.BacktraderCSVData(dataname='mypath.days', timeframe=bt.TimeFrame.Days) cerebro.adddata(data)
重新取样 和 回放 数据也可以采用相同的模式:
cerebro.addstrategy(MyStrategy, myparam1=value1, myparam2=value2)
data = bt.BacktraderCSVData(dataname='mypath.min', timeframe=bt.TimeFrame.Minutes) cerebro.resampledata(data, timeframe=bt.TimeFrame.Days) 或者:: data = bt.BacktraderCSVData(dataname='mypath.min', timeframe=bt.TimeFrame.Minutes) cerebro.replaydata(data, timeframe=bt.TimeFrame.Days) 系统可以接受任意数量的数据源,包括混合常规数据与重新取样和/或回放的数据。当然,其中的一些组合肯定是没有意义的,为了能够组合数据,有一个约束: *时间对齐* 。参见 :doc:`data-multitimeframe/data-multitimeframe`, :doc:`data-resampling/data-resampling` - 重新取样 和 :doc:`data-replay/data-replay` 部分。
添加 策略
与已经是类实例的 数据源
不同, cerebro
直接接受 策略
类和要传递给它的参数。其背后的原理是: 在优化方案中,类将被实例化多次,并传递不同的参数 。即使没有运行任何*优化*,该模式仍然适用:
cerebro.addstrategy(MyStrategy, myparam1=value1, myparam2=value2)
当进行 优化*时,参数必须作为可迭代对象添加。有关详细解释,请参见 优化*部分。基本模式如下:
cerebro.optstrategy(MyStrategy, myparam1=range(10, 20))
这将运行 MyStrategy
10次, myparam1
的值从10到19(请记住Python中的范围是半开区间,不会达到 20
)
其他元素
还可以添加一些其他元素来增强回测体验。相关方法有:
addwriter
addanalyzer
addobserver
(或addobservermulti
)4. 更改经纪人
Cerebro会使用 backtrader
中的默认经纪人,但是可以进行覆盖:
`python broker = MyBroker() cerebro.broker = broker # 使用getbroker/setbroker方法的属性 `
接收通知
如果 数据源 和/或 经纪人 发送通知(或创建通知的 存储提供者 ),可以通过 Cerebro.notify_store
方法接收到它们。有三种方法可以处理这些通知:
-
通过
addnotifycallback(callback)
调用,在cerebro
实例中添加一个*回调*。回调函数必须支持以下签名:`python callback(msg, *args, **kwargs) `
实际接收到的
msg
、 `` args`` 和 ``**kwargs`` 是实现定义的(完全取决于 数据/经纪人/存储 ),但通常可以期望它们是 可打印*的,以便接收和实验。 -
在添加到
cerebro
实例的Strategy
子类中重写notify_store
方法。签名:
notify_store(self, msg, *args, **kwargs)
- 子类化Cerebro
并覆盖notify_store
(与Strategy
中的签名相同)这应该是最不推荐的方法
执行回测
有一个单独的方法可以执行,但它支持多个选项(也可以在实例化时指定)以决定如何运行:
result = cerebro.run(**kwargs)
请参考下面的参考资料以了解可用的参数。
标准观察者
cerebro
(除非另有说明)自动实例化*三个*标准 观察者
一个 Broker 观察者,用于跟踪
cash
和value
(投资组合)一个 Trades 观察者,应该显示每个交易的效果如何
一个 Buy/Sell 观察者,应该记录操作执行的时间点
回测结果分析
我们刚才有讲过
result = cerebro.run(**kwargs)
run
返回的 result
的格式会根据是否使用了 优化*(使用 ``optstrategy`` 添加了 策略*)而有所不同:
所有使用
addstrategy
添加的策略
result
将是一个在回测期间运行的实例列表使用
optstrategy
添加了 1 个或多个策略
result
将是一个list
的list
。每个内部列表将包含每次优化运行之后的策略
优化 的默认行为已更改为仅返回系统中存在的* 分析器*,以减轻计算机核心之间的消息传递负担。
如果希望返回完整的策略集合作为返回值,请将参数 optreturn
设置为 False
。提供绘图功能
数据源参数讲解
在 backtrader
中,有一组数据源解析器(截止到撰写时,全部为基于CSV的),让您可以从不同的源加载数据。
Yahoo(在线或已经保存到文件中)
VisualChart(请参阅 www.visualchart.com <http://www.visualchart.com> _)
Backtrader CSV(自有的用于测试的格式)
通用的CSV支持
我们刚才在讲述了数据来源的别名,接下来还有许多类似的参数
dataname
(默认值: None) 必须提供
具体含义根据数据来源类型而变化(文件位置,代码标记,…) -
name
(默认值:’’)用于在绘图中作为装饰目的。如果未指定,则可以从
dataname
(例如文件路径的最后一部分)派生
fromdate
(默认值:mindate)- 指示应忽略任何早于此日期时间的Python日期时间对象
todate
(默认值:maxdate)- 指示应忽略任何晚于此日期时间的Python日期时间对象
timeframe
(默认值:TimeFrame.Days)- 可能的值:
Ticks
,Seconds
,Minutes
,Days
,Weeks
,Months
和Years
compression
(默认值:1)- 每根K线实际包含的条形数。信息性。仅在数据重采样/重新播放时有效。 -
sessionstart
(默认值:无) - 数据的会话开始时间的指示。可以用于诸如重新取样之类的目的。
sessionend
(默认值:无)- 数据的会话结束时间的指示。可以用于诸如重新取样之类的目的
CSV 数据源常见参数
参数(额外的通用参数):
headers
(默认值:True)指示传入的数据是否有一个初始的标题行
separator
(默认值:”,”)要考虑的分隔符,用于将每个 CSV 行进行标记化.. _generic-csv-datafeed:
GenericCSVData
这个类提供了一个通用接口,允许解析几乎所有的 CSV 文件格式。
根据参数定义的顺序和字段存在性解析 CSV 文件
特定参数(或特定含义):
dataname
要解析的文件名或类似文件的对象
datetime
(默认值:0)包含日期(或日期时间)字段的列
time
(默认值:-1)包含时间字段的列,如果与日期时间字段分离(-1 表示不存在)
open
(默认值:1),high
(默认值:2),low
(默认值:3),close
(默认值:4),volume
(默认值:5),openinterest
(默认值:6) 含有对应字段的列索引如果传递了一个负值(例如:-1),表示该字段在CSV数据中不存在
nullvalue
(默认值:float(‘NaN’))如果缺少应该存在的值(CSV字段为空),将使用的值
dtformat
(默认值: %Y-%m-%d %H:%M:%S)用于解析日期时间CSV字段的格式
tmformat
(默认值: %H:%M:%S)用于解析时间CSV字段的格式,如果“存在”(对于“时间”CSV字段,默认情况下应该不存在)
满足以下要求的示例用法:
限制输入到2000年
使用HLOC顺序而不是OHLC
将缺失值替换为零(0.0)
提供每天的数据条和日期时间仅为YYYY-MM-DD格式
没有“openinterest”列存在The code:
import datetime import backtrader as bt import backtrader.feeds as btfeeds
data = btfeeds.GenericCSVData(
dataname=’mydata.csv’,
fromdate=datetime.datetime(2000, 1, 1), todate=datetime.datetime(2000, 12, 31),
nullvalue=0.0,
dtformat=(‘%Y-%m-%d’),
datetime=0, high=1, low=2, open=3, close=4, volume=5, openinterest=-1
)
…- 限制输入年份为2000
-
使用HLOC顺序而不是OHLC顺序
-
将缺失值替换为零(0.0)
-
提供了逐笔回测数据,包含单独的日期和时间列 - 日期的格式为YYYY-MM-DD - 时间的格式为HH.MM.SS(不同于通常的HH:MM:SS)
-
没有
openinterest
列存在
import datetime
import backtrader as bt
import backtrader.feeds as btfeed
...
...
data = btfeeds.GenericCSVData(
dataname='mydata.csv',
fromdate=datetime.datetime(2000, 1, 1),
todate=datetime.datetime(2000, 12, 31),
nullvalue=0.0,
dtformat=('%Y-%m-%d'),
tmformat=('%H.%M.%S'),
datetime=0,
time=1,
high=2,
low=3,
open=4,
close=5,
volume=6,
openinterest=-1
)
import datetime import backtrader.feeds as btfeed
class MyHLOC(btfreeds.GenericCSVData):
params = (
(‘fromdate’, datetime.datetime(2000, 1, 1)), (‘todate’, datetime.datetime(2000, 12, 31)), (‘nullvalue’, 0.0), (‘dtformat’, (‘%Y-%m-%d’)), (‘tmformat’, (‘%H.%M.%S’)),
(‘datetime’, 0), (‘time’, 1), (‘high’, 2), (‘low’, 3), (‘open’, 4), (‘close’, 5), (‘volume’, 6), (‘openinterest’, -1)
)
现在可以通过提供 dataname
来重用这个新的类了:
data = btfeeds.MyHLOC(dataname='mydata.csv')
未完待续........
十天不更新就喊我退钱