优化改进
backtrader 版本 1.8.12.99
包括了 数据源 和 结果 在多进程中的管理改进。
注意
对两者的行为进行了调整
这些选项的行为可以通过两个新的 Cerebro 参数进行控制:
optdatas
(默认:True
)
如果为True
并且正在优化(并且系统可以preload
并使用runonce
,数据预加载将仅在主进程中执行一次,以节省时间和资源。optreturn
(默认:True
)如果为True
,则优化结果将不是完整的Strategy
对象(以及所有数据,指标,观察器等),而是具有以下属性的对象(与Strategy
中相同):
params
(或p
)执行策略时的参数analyzers
执行的策略
- 在大多数情况下,只需要 analyzers 和使用哪些 params 来评估策略的表现。如果需要对生成的值(例如 指标)进行详细分析,请关闭此选项。
数据源管理
在 优化 场景中,这是 Cerebro 参数的可能组合:
preload=True
(默认)
在运行任何回测代码之前,数据源将被预加载runonce=True
(默认)
指标 将以批处理模式计算在一个紧密的 for 循环中,而不是逐步进行。
如果两个条件都为 True
并且 optdatas=True
,则:
- 在生成新子进程(负责执行 回测 的子进程)之前,数据源 将在主进程中预加载
结果管理
在 优化 场景中,评估每个 策略 运行时使用的不同参数时,两件事应该起到最重要的作用:
strategy.params
(或strategy.p
)
回测中使用的实际值集strategy.analyzers
负责提供 策略 实际表现评估的对象。示例:SharpeRatio_A
(年化 SharpeRatio)
当 optreturn=True
时,将创建占位符对象,而不是返回完整的 策略 实例,这些对象携带上述两个属性,以便进行评估。
这样可以避免传递大量生成的数据,例如 回测 期间指标生成的值
如果希望使用 完整的策略对象,只需在 cerebro 实例化 期间或进行 cerebro.run
时将 optreturn=False
。
一些测试运行
backtrader 源代码中的 优化 示例已经扩展,以添加对 optdatas
和 optreturn
的控制(实际上是禁用它们)
单核运行
作为参考,当 CPU 数量限制为 1
且未使用 multiprocessing
模块时发生了什么:
$ ./optimization.py --maxcpus 1 ================================================== ************************************************** -------------------------------------------------- OrderedDict([(u'smaperiod', 10), (u'macdperiod1', 12), (u'macdperiod2', 26), (u'macdperiod3', 9)]) ************************************************** -------------------------------------------------- OrderedDict([(u'smaperiod', 10), (u'macdperiod1', 13), (u'macdperiod2', 26), (u'macdperiod3', 9)]) ... ... OrderedDict([(u'smaperiod', 29), (u'macdperiod1', 19), (u'macdperiod2', 29), (u'macdperiod3', 14)]) ================================================== Time used: 184.922727833
多核运行
不限制 CPU 数量时,Python 的 multiprocessing
模块将尝试使用所有 CPU。optdatas
和 optreturn
将被禁用。
optdata
和optreturn
均处于激活状态
默认行为:
$ ./optimization.py ... ... ... ================================================== Time used: 56.5889185394
多核和数据提供以及结果改进带来的总改进意味着从184.92
秒降至56.58
秒。
请注意,示例使用252
根柱和指标仅生成长度为252
点的值。这只是一个例子。
真正的问题是这有多少归因于新行为。
optreturn
已停用
让我们将完整的策略对象传递给调用者:
$ ./optimization.py --no-optreturn ... ... ... ================================================== Time used: 67.056914007
执行时间增加了18.50%
(或加速比为15.62%
)。
optdatas
已停用
每个子进程被强制加载其自己的数据提供值集:
$ ./optimization.py --no-optdatas ... ... ... ================================================== Time used: 72.7238112637
执行时间增加了28.52%
(或加速比为22.19%
)。
两者均已停用
仍然使用多核但保持旧的非改进行为:
$ ./optimization.py --no-optdatas --no-optreturn ... ... ... ================================================== Time used: 83.6246643786
执行时间增加了47.79%
(或加速比为32.34%
)。
这表明使用多核是时间改进的主要贡献者。
注意
执行是在装有i7-4710HQ
(4 核/8 逻辑)和 16 GBytes RAM 的 Windows 10 64 位笔记本电脑上进行的。在其他条件下情况可能会有所不同
结论
- 优化期间减少时间的最大因素是使用多核
- 使用
optdatas
和optreturn
的示例运行显示每个的加速比约为22.19%
和15.62%
(在测试中两者一起为32.34%
)
示例用法
$ ./optimization.py --help usage: optimization.py [-h] [--data DATA] [--fromdate FROMDATE] [--todate TODATE] [--maxcpus MAXCPUS] [--no-runonce] [--exactbars EXACTBARS] [--no-optdatas] [--no-optreturn] [--ma_low MA_LOW] [--ma_high MA_HIGH] [--m1_low M1_LOW] [--m1_high M1_HIGH] [--m2_low M2_LOW] [--m2_high M2_HIGH] [--m3_low M3_LOW] [--m3_high M3_HIGH] Optimization optional arguments: -h, --help show this help message and exit --data DATA, -d DATA data to add to the system --fromdate FROMDATE, -f FROMDATE Starting date in YYYY-MM-DD format --todate TODATE, -t TODATE Starting date in YYYY-MM-DD format --maxcpus MAXCPUS, -m MAXCPUS Number of CPUs to use in the optimization - 0 (default): use all available CPUs - 1 -> n: use as many as specified --no-runonce Run in next mode --exactbars EXACTBARS Use the specified exactbars still compatible with preload 0 No memory savings -1 Moderate memory savings -2 Less moderate memory savings --no-optdatas Do not optimize data preloading in optimization --no-optreturn Do not optimize the returned values to save time --ma_low MA_LOW SMA range low to optimize --ma_high MA_HIGH SMA range high to optimize --m1_low M1_LOW MACD Fast MA range low to optimize --m1_high M1_HIGH MACD Fast MA range high to optimize --m2_low M2_LOW MACD Slow MA range low to optimize --m2_high M2_HIGH MACD Slow MA range high to optimize --m3_low M3_LOW MACD Signal range low to optimize --m3_high M3_HIGH MACD Signal range high to optimize
异常
设计目标之一是尽早退出,并让用户完全透明地了解错误的发生情况。目标是迫使自己拥有会在异常情况下中断并强制重新访问受影响部分的代码。
但时机已经成熟,一些异常可能会慢慢添加到平台中。
层次结构
所有异常的基类是BacktraderError
(它是Exception
的直接子类)
位置
- 在模块
errors
内,例如可以通过以下方式访问:
import backtrader as bt class Strategy(bt.Strategy): def __init__(self): if something_goes_wrong(): raise bt.errors.StrategySkipError`
- 直接来自
backtrader
,如下所示:
import backtrader as bt class Strategy(bt.Strategy): def __init__(self): if something_goes_wrong(): raise bt.StrategySkipError`
异常
StrategySkipError
请求平台跳过此策略进行回测。在实例化(__init__
)阶段引发
写入器
将以下内容写入流:
- 包含数据源、策略、指标和观察器的 csv 流
可以通过每个对象的csv
属性控制哪些对象实际进入 csv 流(对于数据源
和观察器
默认为 True / 对于指标
默认为 False) - 属性摘要
- 数据源
- 策略(行数和参数)
- 指标/观察器:(行数和参数)
- 分析器:(参数和分析结果)
只定义了一个称为 WriterFile
的写入器,可以添加到系统中:
- 通过将 cerebro 的
writer
参数设置为 True
将实例化标准的WriterFile
- 通过调用
Cerebro.addwriter(writerclass, **kwargs)
writerclass
将在回测执行期间使用给定的kwargs
实例化
鉴于标准的WriterFile
不会默认输出csv
,以下addwriter
调用将负责处理:
cerebro.addwriter(bt.WriterFile, csv=True)`
参考文献
backtrader.WriterFile 类
系统范围的写入器类。
可以用以下方式进行参数化:
out
(默认:sys.stdout
):要写入的输出流
如果传递了字符串,则将使用参数内容的文件名close_out
(默认:False
)
如果out
是一个流,则是否需要写入器显式关闭它csv
(默认:False
)
如果在执行期间需要将数据源、策略、观察器和指标的 csv 流写入流
可以通过每个对象的csv
属性控制哪些对象实际进入 csv 流(对于数据源
和观察器
默认为 True / 对于指标
默认为 False)csv_filternan
(默认:True
)是否需要将nan
值从 csv 流中清除(替换为空字段)csv_counter
(默认:True
)如果写入器应该保留并打印实际输出的行数计数器indent
(默认:2
)每个级别的缩进空格数separators
(默认:['=', '-', '+', '*', '.', '~', '"', '^', '#']
)
用于各个部分/子(子)部分的行分隔符使用的字符seplen
(默认:79
)
包括缩进在内的一行分隔符的总长度rounding
(默认:None
)
要将浮点数舍入到的小数位数。使用None
时不执行舍入
数据提要
数据源
backtrader
配备了一组数据源解析器(在撰写本文时全部基于 CSV),让您可以从不同来源加载数据。
- Yahoo(在线或已保存到文件中)
- VisualChart(请参阅www.visualchart.com
- Backtrader CSV(用于测试的自有格式)
- 通用 CSV 支持
从快速入门指南中应清楚地了解到,您可以将数据源添加到Cerebro
实例中。稍后,不同策略将可以在以下位置访问数据源:
- 一个数组 self.datas(插入顺序)
- 数组对象的别名:
- self.data 和 self.data0 指向第一个元素
- self.dataX 指向数组中索引为 X 的元素
有关插入方式的快速提醒:
import backtrader as bt import backtrader.feeds as btfeeds data = btfeeds.YahooFinanceCSVData(dataname='wheremydatacsvis.csv') cerebro = bt.Cerebro() cerebro.adddata(data) # a 'name' parameter can be passed for plotting purposes
数据源常见参数
此数据源可以直接从 Yahoo 下载数据并馈送到系统中。
参数:
dataname
(默认值:None)必须提供
其含义随数据源类型而异(文件位置、股票代码等)name
(默认值:‘’)
用于绘图的装饰目的。如果未指定,可能会从dataname
派生(例如:文件路径的最后部分)fromdate
(默认值:mindate)
Python 日期时间对象,指示应忽略任何早于此日期时间的日期时间todate
(默认值:maxdate)
Python 日期时间对象,指示应忽略任何晚于此日期时间的日期时间timeframe
(默认值:TimeFrame.Days)
潜在值:Ticks
,Seconds
,Minutes
,Days
,Weeks
,Months
和Years
compression
(默认值:1)
每根柱子的实际条数。信息性的。仅在数据重采样/重播中有效。sessionstart
(默认值:None)
数据的会话开始时间指示。可能被类用于重新采样等目的sessionend
(默认值:None)
数据的会话结束时间指示。可能被类用于重新采样等目的
CSV 数据源常见参数
参数(除了常见的之外):
headers
(默认值:True)
表示传递的数据是否具有初始标题行separator
(默认值:“,”)
考虑到每个 CSV 行的标记分隔符
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)
用于解析 datetime CSV 字段的格式tmformat
(默认:%H:%M:%S)
如果“存在”,则用于解析时间 CSV 字段的格式(“时间” CSV 字段的默认设置是不存在)
一个覆盖以下要求的示例用法:
- 限制输入至 2000 年
- HLOC 顺序而不是 OHLC
- 缺失值将被替换为零(0.0)
- 提供日线数据,日期时间仅为格式为 YYYY-MM-DD 的日期
- 没有
openinterest
列
代码:
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')
扩展数据源
GitHub 上的问题实际上推动了完成文档部分或帮助我理解 backtrader
是否具有我最初设想的易用性和灵活性,以及沿途做出的决策。
在这种情况下是 问题 #9。
最终问题似乎归结为:
- 最终用户是否能轻松地扩展现有机制,以添加额外信息,比如像
open
、high
等的其他现有价格信息点呢?
据我所知,问题的答案是:是的
发帖人似乎有以下需求(来自问题 #6):
- 正在解析为 CSV 格式的数据源
- 使用
GenericCSVData
加载信息
这种通用 csv 支持是针对这个 问题 #6 开发的。 - 这是一个额外的字段,显然包含需要传递的 P/E 信息,这些信息将传递给解析后的 CSV 数据。
让我们继续 CSV 数据源开发和 GenericCSVData 示例帖子。
步骤:
- 假设 P/E 信息被设置在解析后的 CSV 数据中。
- 使用
GenericCSVData
作为基类 - 将现有线(open/high/low/close/volumen/openinterest)扩展为
pe
- 给调用者添加一个参数,让其确定 P/E 信息的列位置。
结果:
from backtrader.feeds import GenericCSVData class GenericCSV_PE(GenericCSVData): # Add a 'pe' line to the inherited ones from the base class lines = ('pe',) # openinterest in GenericCSVData has index 7 ... add 1 # add the parameter to the parameters inherited from the base class params = (('pe', 8),)
工作完成了…
稍后,在策略中使用这个数据源时:
import backtrader as bt .... class MyStrategy(bt.Strategy): ... def next(self): if self.data.close > 2000 and self.data.pe < 12: # TORA TORA TORA --- Get off this market self.sell(stake=1000000, price=0.01, exectype=Order.Limit) ...
绘制那条额外的 P/E 线
显然,数据源中的这行额外信息没有自动化的绘图支持。
最好的替代方法是对该行进行简单移动平均,并在单独的轴上绘制它:
import backtrader as bt import backtrader.indicators as btind .... class MyStrategy(bt.Strategy): def __init__(self): # The indicator autoregisters and will plot even if no obvious # reference is kept to it in the class btind.SMA(self.data.pe, period=1, subplot=False) ... def next(self): if self.data.close > 2000 and self.data.pe < 12: # TORA TORA TORA --- Get off this market self.sell(stake=1000000, price=0.01, exectype=Order.Limit) ...
CSV 数据源开发
backtrader
已经提供了通用 CSV 数据源和一些特定的 CSV 数据源。总结:
- 通用 CSV 数据
- VisualChartCSV 数据
- YahooFinanceData(用于在线下载)
- YahooFinanceCSVData(用于已下载的数据)
- BacktraderCSVData(内部…用于测试目的,但可用)
但即使如此,最终用户可能希望为特定的 CSV 数据源开发支持。
通常的座右铭可能是:“说起来容易做起来难”。实际上,结构的目的是使其易于操作。
步骤:
- 继承自
backtrader.CSVDataBase
- 如有必要,定义任何
params
- 在
start
方法中进行任何初始化 - 在
stop
方法中进行任何清理 - 定义一个
_loadline
方法,在其中进行实际工作
此方法接收一个参数:linetokens。
如其名,此数据包含根据separator
参数(继承自基类)拆分当前行后的标记。
如果完成工作后有新数据……填充相应行并返回True
如果没有可用内容,因此解析已经结束:返回False
如果在幕后代码中读取文件行时发现没有更多行可解析,则可能甚至不需要返回False
。
已经考虑到的事项:
- 打开文件(或接收文件样式对象)
- 如果指示存在,则跳过标题行
- 读取行
- 对行进行标记
- 预加载支持(一次性在内存中加载整个数据源)
通常,一个例子胜过千言万语的需求描述。让我们使用BacktraderCSVData
中定义的内部 CSV 解析代码的简化版本。这个版本不需要初始化或清理(例如,可以是打开套接字,然后稍后关闭)。
注意
backtrader
数据源包含通常的行业标准数据源,需要填充。即:
- 日期时间
- 打开
- 高
- 低
- 关闭
- 成交量
- 持仓量
如果您的策略/算法或简单的数据查看仅需要例如收盘价,则可以将其他值保持不变(每次迭代结束之前,它们会自动填充为 float(‘NaN’)值,以使最终用户代码有机会执行任何操作。
在此示例中,仅支持每日格式:
import itertools ... import backtrader as bt class MyCSVData(bt.CSVDataBase): def start(self): # Nothing to do for this data feed type pass def stop(self): # Nothing to do for this data feed type pass def _loadline(self, linetokens): i = itertools.count(0) dttxt = linetokens[next(i)] # Format is YYYY-MM-DD y = int(dttxt[0:4]) m = int(dttxt[5:7]) d = int(dttxt[8:10]) dt = datetime.datetime(y, m, d) dtnum = date2num(dt) self.lines.datetime[0] = dtnum self.lines.open[0] = float(linetokens[next(i)]) self.lines.high[0] = float(linetokens[next(i)]) self.lines.low[0] = float(linetokens[next(i)]) self.lines.close[0] = float(linetokens[next(i)]) self.lines.volume[0] = float(linetokens[next(i)]) self.lines.openinterest[0] = float(linetokens[next(i)]) return True
代码期望所有字段已就位并可转换为浮点数,除日期时间外,日期时间具有固定的 YYYY-MM-DD 格式,并且可在不使用datetime.datetime.strptime
的情况下解析。
只需添加几行代码,即可满足更复杂的需求,以处理空值,日期格式解析。GenericCSVData
执行此操作。
购买者注意
使用GenericCSVData
现有数据源和继承,可以完成很多工作以支持各种格式。
让我们为Sierra Chart每日格式添加支持(始终以 CSV 格式存储)。
定义(通过查看一个**‘.dly’**数据文件:
- 字段:日期、开盘价、最高价、最低价、收盘价、成交量、持仓量
行业标准和已经由GenericCSVData
支持的标准顺序(也是行业标准) - 分隔符:,
- 日期格式:YYYY/MM/DD
针对这些文件的解析器:
class SierraChartCSVData(backtrader.feeds.GenericCSVData): params = (('dtformat', '%Y/%m/%d'),)
params
的定义只是重新定义基类中的一个现有参数。在这种情况下,只需更改日期的格式化字符串。
Et voilá … Sierra Chart 的解析器完成了。
下面是GenericCSVData
的参数定义,以便提醒:
class GenericCSVData(feed.CSVDataBase): params = ( ('nullvalue', float('NaN')), ('dtformat', '%Y-%m-%d %H:%M:%S'), ('tmformat', '%H:%M:%S'), ('datetime', 0), ('time', -1), ('open', 1), ('high', 2), ('low', 3), ('close', 4), ('volume', 5), ('openinterest', 6), )
二进制数据源开发
原文:
www.backtrader.com/docu/datafeed-develop-general/datafeed-develop-general/
注意
示例中使用的二进制文件goog.fd
属于 VisualChart,不能与backtrader
一起分发。
VisualChart可以免费下载,供有兴趣直接使用二进制文件的人使用。
CSV 数据源开发展示了如何添加新的基于 CSV 的数据源。现有的基类 CSVDataBase 提供了框架,大多数情况下子类可以简单地执行以下操作:
def _loadline(self, linetokens): # parse the linetokens here and put them in self.lines.close, # self.lines.high, etc return True # if data was parsed, else ... return False
基类负责参数、初始化、文件打开、读取行、拆分行为标记和其他额外的事情,比如跳过不符合用户可能已定义的日期范围(fromdate
、todate
)的行。
开发非 CSV 数据源遵循相同的模式,而不是下降到已分割的行标记。
要做的事情:
- 派生自
backtrader.feed.DataBase
。 - 添加任何你可能需要的参数
- 如果需要初始化,请覆盖
__init__(self)
和/或start(self)
。 - 如果需要任何清理代码,覆盖
stop(self)
。 - 工作发生在必须始终被覆盖的方法内:
_load(self)
让我们使用backtrader.feed.DataBase
已提供的参数:
from backtrader.utils.py3 import with_metaclass ... ... class DataBase(with_metaclass(MetaDataBase, dataseries.OHLCDateTime)): params = (('dataname', None), ('fromdate', datetime.datetime.min), ('todate', datetime.datetime.max), ('name', ''), ('compression', 1), ('timeframe', TimeFrame.Days), ('sessionend', None))
具有以下含义:
dataname
是数据源识别如何获取数据的参数。在CSVDataBase
的情况下,此参数应为文件路径或已经是类似文件的对象。fromdate
和todate
定义将传递给策略的日期范围。提供的任何值超出此范围的数据将被忽略。name
是为了绘图目的而设计的。timeframe
指示时间工作参考
可能的值:Ticks
、Seconds
、Minutes
、Days
、Weeks
、Months
和Years
。compression
(默认值:1)
每个柱子的实际条数。提供信息。仅在数据重采样/重播中有效。compression
- 如果传递了
sessionend
(一个 datetime.time 对象),将会添加到数据源datetime
行中,以便识别会话结束。
BackTrader 中文文档(三)(2)https://developer.aliyun.com/article/1489228