BackTrader 中文文档(二十八)(2)https://developer.aliyun.com/article/1505487
CSV 数据源开发
原文:
www.backtrader.com/blog/posts/2015-08-06-csv-data-feed-development/csv-data-feed-development/
backtrader 已经提供了一个通用的 CSV 数据源和一些特定的 CSV 数据源。总结如下:
- GenericCSVData
- VisualChartCSVData
- YahooFinanceData(用于在线下载)
- YahooFinanceCSVData(用于已下载的数据)
- BacktraderCSVData(内部…用于测试目的,但可以使用)
但即使如此,最终用户可能希望为特定的 CSV 数据源开发支持。
通常的格言是:“说起来容易做起来难”。实际上,结构设计得很简单。
步骤:
- 继承自
backtrader.CSVDataBase
- 如果需要,定义任何
params
- 在
start
方法中进行任何初始化 - 在
stop
方法中进行任何清理 - 定义一个
_loadline
方法,其中实际工作发生
此方法接收一个参数:linetokens。
正如名称所示,这包含了在根据separator
参数(从基类继承)分割当前行后的令牌。
如果在完成其工作后有新数据… 填充相应的行并返回True
如果没有可用的数据,因此解析已经结束:返回False
如果在幕后读取文件行的代码发现没有更多可解析的行,则可能甚至不需要返回False
。
已经考虑到的事项:
- 打开文件(或接收类似文件的对象)
- 如果存在,则跳过标题行
- 读取行
- 对行进行标记
- 预加载支持(一次性将整个数据源加载到内存中)
通常,一个例子胜过千言万语的要求描述。让我们使用从BacktraderCSVData
定义的简化版本的内部定义的 CSV 解析代码。这个不需要初始化或清理(例如,可以稍后打开套接字并关闭)。
注意
backtrader
数据源包含常见的行业标准数据源,这些是需要填充的。即:
- 日期时间
- 打开
- 高
- 低
- 关闭
- 成交量
- 持仓量
如果您的策略/算法或简单的数据查阅只需要,例如收盘价,您可以将其他内容保持不变(每次迭代都会在最终用户代码有机会执行任何操作之前自动使用 float(‘NaN’)值填充它们。
在本例中仅支持每日格式:
import itertools ... import backtrader import 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
的定义只是重新定义基类中的一个现有参数。在这种情况下,只需更改日期的格式化字符串。
哎呀……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), )
通用 CSV 数据源
原文:
www.backtrader.com/blog/posts/2015-08-04-generic-csv-datafeed/generic-csv-datafeed/
一个问题导致实现了GenericCSVData,可用于解析不同的 CSV 格式。
GitHub 上的问题,Issue #6清楚地显示需要有能够处理任何传入 CSV 数据源的东西。
参数声明中包含关键信息:
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), )
因为该类继承自 CSVDataBase,一些标准参数可用:
fromdate
(接受日期时间对象以限制起始日期)todate
(接受日期时间对象)以限制结束日期)headers
(默认值:True,指示 CSV 数据是否有标题行)separator
(默认值:“,”,分隔字段的字符)dataname
(包含 CSV 数据的文件名或类似文件的对象)
其他一些参数如name
,compression
和timeframe
仅供参考,除非您计划执行重新采样。
当然更重要的是,新定义参数的含义:
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 字段的默认值是不存在)
这可能足以涵盖许多不同的 CSV 格式和值的缺失。
涵盖以下要求的示例用法:
- 限制输入至 2000 年
- HLOC 顺序而不是 OHLC
- 缺失值将被替换为零(0.0)
- 提供每日 K 线数据,日期时间格式为 YYYY-MM-DD
- 没有
openinterest
列存在
代码:
import datetime import backtrader as bt import backtrader.feeds as btfeed ... ... data = btfeed.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)
- 提供分钟级 K 线数据,具有单独的日期和时间列
- 日期格式为 YYYY-MM-DD
- 时间格式为 HH.MM.SS
- 没有
openinterest
列存在
代码:
import datetime import backtrader as bt import backtrader.feeds as btfeed ... ... data = btfeed.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 )
改进佣金:股票与期货
原文:
www.backtrader.com/blog/posts/2015-07-31-commission-schemes-updated/commission-schemes-updated/
发布 backtrader 使用示例让我对一些缺失的东西有了了解。首先:
- 多核优化
- 佣金:股票与期货
后者告诉了我:
- 经纪人在利润和损失的计算方面做得很对,向调用策略提供了正确的订单通知
- 策略无法访问
operations
(又称trades
),这是订单已经开仓并关闭头寸的结果(后者显示出盈亏数字) - 绘制的
Operation
盈亏数字由一个Observer
收集,并且无法访问实际的commission scheme
,因此对于类似于期货的操作和类似于股票的操作,将显示相同的盈亏数字。
显然,需要进行一些小的内部重组才能实现:
- 向策略发送
Operation
通知 Operations
显示正确的盈亏数字
broker
已经拥有了所有需要的信息,并且已经将大部分信息填入了被通知到strategy
的order
中,而broker
需要做出的唯一决定是是否将额外的信息位放入订单中,或者它可以自己计算operations
。
由于策略已经获得了orders
,并且将operations
保留在列表中似乎很自然,broker
只是在订单部分/完全关闭头寸时添加实际的盈亏,将计算责任留给了strategy
。
这进一步简化了Operations Observer
的实际角色,即观察新关闭的Operation
并记录下来。这是它一直应该有的角色。
下面的代码已经被重新设计,不再计算盈亏数字,而只是注意到notify_operation
中通知的数字。
现在图表反映出了真实的盈亏数字(cash
和value
已经是真实的)
旧的期货记录:
2006-03-09, BUY CREATE, 3757.59 2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 2000.00, Comm 2.00 2006-04-11, SELL CREATE, 3788.81 2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 2000.00, Comm 2.00 2006-04-12, OPERATION PROFIT, GROSS 328.00, NET 324.00 2006-04-20, BUY CREATE, 3860.00 2006-04-21, BUY EXECUTED, Price: 3863.57, Cost: 2000.00, Comm 2.00 2006-04-28, SELL CREATE, 3839.90 2006-05-02, SELL EXECUTED, Price: 3839.24, Cost: 2000.00, Comm 2.00 2006-05-02, OPERATION PROFIT, GROSS -243.30, NET -247.30
新的期货记录:
2006-03-09, BUY CREATE, 3757.59 2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 2000.00, Comm 2.00 2006-04-11, SELL CREATE, 3788.81 2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 2000.00, Comm 2.00 2006-04-12, OPERATION PROFIT, GROSS 328.00, NET 324.00 2006-04-20, BUY CREATE, 3860.00 2006-04-21, BUY EXECUTED, Price: 3863.57, Cost: 2000.00, Comm 2.00 2006-04-28, SELL CREATE, 3839.90 2006-05-02, SELL EXECUTED, Price: 3839.24, Cost: 2000.00, Comm 2.00 2006-05-02, OPERATION PROFIT, GROSS -243.30, NET -247.30 2006-05-02, BUY CREATE, 3862.24
旧的股票记录:
2006-03-09, BUY CREATE, 3757.59 2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 3754.13, Comm 18.77 2006-04-11, SELL CREATE, 3788.81 2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 3786.93, Comm 18.93 2006-04-12, OPERATION PROFIT, GROSS 32.80, NET -4.91 2006-04-20, BUY CREATE, 3860.00 2006-04-21, BUY EXECUTED, Price: 3863.57, Cost: 3863.57, Comm 19.32 2006-04-28, SELL CREATE, 3839.90 2006-05-02, SELL EXECUTED, Price: 3839.24, Cost: 3839.24, Comm 19.20 2006-05-02, OPERATION PROFIT, GROSS -24.33, NET -62.84
新的股票记录:
2006-03-09, BUY CREATE, 3757.59 2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 3754.13, Comm 18.77 2006-04-11, SELL CREATE, 3788.81 2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 3786.93, Comm 18.93 2006-04-12, OPERATION PROFIT, GROSS 32.80, NET -4.91 2006-04-20, BUY CREATE, 3860.00 2006-04-21, BUY EXECUTED, Price: 3863.57, Cost: 3863.57, Comm 19.32 2006-04-28, SELL CREATE, 3839.90 2006-05-02, SELL EXECUTED, Price: 3839.24, Cost: 3839.24, Comm 19.20 2006-05-02, OPERATION PROFIT, GROSS -24.33, NET -62.84 2006-05-02, BUY CREATE, 3862.24
图表(仅新的图表)。现在可以清楚地看到futures-like
操作和stock-like
操作之间的差异,不仅在cash
和value
的变化中。
期货佣金
股票佣金
代码
from __future__ import (absolute_import, division, print_function, unicode_literals) import backtrader as bt import backtrader.feeds as btfeeds import backtrader.indicators as btind futures_like = True if futures_like: commission, margin, mult = 2.0, 2000.0, 10.0 else: commission, margin, mult = 0.005, None, 1 class SMACrossOver(bt.Strategy): def log(self, txt, dt=None): ''' Logging function fot this strategy''' dt = dt or self.datas[0].datetime.date(0) print('%s, %s' % (dt.isoformat(), txt)) def notify(self, order): if order.status in [order.Submitted, order.Accepted]: # Buy/Sell order submitted/accepted to/by broker - Nothing to do return # Check if an order has been completed # Attention: broker could reject order if not enougth cash if order.status in [order.Completed, order.Canceled, order.Margin]: if order.isbuy(): self.log( 'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' % (order.executed.price, order.executed.value, order.executed.comm)) else: # Sell self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' % (order.executed.price, order.executed.value, order.executed.comm)) def notify_trade(self, trade): if trade.isclosed: self.log('TRADE PROFIT, GROSS %.2f, NET %.2f' % (trade.pnl, trade.pnlcomm)) def __init__(self): sma = btind.SMA(self.data) # > 0 crossing up / < 0 crossing down self.buysell_sig = btind.CrossOver(self.data, sma) def next(self): if self.buysell_sig > 0: self.log('BUY CREATE, %.2f' % self.data.close[0]) self.buy() # keep order ref to avoid 2nd orders elif self.position and self.buysell_sig < 0: self.log('SELL CREATE, %.2f' % self.data.close[0]) self.sell() if __name__ == '__main__': # Create a cerebro entity cerebro = bt.Cerebro() # Add a strategy cerebro.addstrategy(SMACrossOver) # Create a Data Feed datapath = ('../../datas/2006-day-001.txt') data = bt.feeds.BacktraderCSVData(dataname=datapath) # Add the Data Feed to Cerebro cerebro.adddata(data) # set commission scheme -- CHANGE HERE TO PLAY cerebro.broker.setcommission( commission=commission, margin=margin, mult=mult) # Run over everything cerebro.run() # Plot the result cerebro.plot()
佣金:股票 vs 期货
原文:
www.backtrader.com/blog/posts/2015-07-26-commission-schemes/commission-schemes/
backtrader 的诞生是出于必要性。我自己…希望有一种感觉,我可以控制自己的回测平台并尝试新的想法。但是在这样做并从一开始就完全开源化之后,很明显它必须有一种方式来满足其他人的需求和愿望。
作为交易者未来,我本可以选择编写基于点数的计算和每轮固定价格的佣金,但那将是一个错误。
注意
2015 年 7 月 31 日
跟进帖子,附带新添加的操作/交易通知,修复交易 P&L 图表的绘制,并避免像下面示例中那样的手动计算。
改善佣金:股票 vs 期货
相反,backtrader
提供了使用常规%大小/价格基础方案和固定价格/点方案的可能性。选择权在你手上。
不可知论
在继续之前,让我们记住`backtrader`试图保持对数据表示的不可知。可以将不同的佣金方案应用于相同的数据集。 让我们看看如何做到这一点。 ## 使用经纪人快捷方式 这样可以使最终用户远离`CommissionInfo`对象,因为可以通过单个函数调用创建/设置佣金方案。在常规的`cerebro`创建/设置过程中,只需将调用添加到`broker`成员变量上即可。以下调用在使用*InteractiveBrokers*时为**Eurostoxx50**期货设置了一种常规佣金方案:
cerebro.broker.setcommission(commission=2.0, margin=2000.0, mult=10.0)
由于大多数用户通常只测试单个工具,因此这就是问题的全部。如果您已经为数据源指定了name
,因为图表上同时考虑了多个工具,因此此调用可以略微扩展为如下所示:
cerebro.broker.setcommission(commission=2.0, margin=2000.0, mult=10.0, name='Eurostoxxx50')
在这种情况下,这种即时佣金方案将仅应用于名称与Eurostoxx50
匹配的工具。
设置佣金参数的含义
commission
(默认值:0.0)每个操作的货币单位以绝对值或百分比形式的成本。在上面的示例中,每个buy
合约需要 2.0 欧元,每个sell
合约也是如此。这里的重要问题是何时使用绝对值或百分比值。
- 如果
margin
评估为False
(例如为 False、0 或 None),则将认为commission
表示price
乘以size
操作值的百分比。 - 如果
margin
是其他内容,则认为操作发生在类似期货
的工具上,并且commission
是每个size
合约的固定价格
margin
(默认值:None)使用期货
等工具时需要的保证金。如上所述
- 如果设置了no
margin
,则commission
将被理解为以百分比表示,并应用于buy
或sell
操作的price * size
组件 - 如果设置了
margin
,则commission
将被理解为与buy
或sell
操作的size
分量相乘的固定值
mult
(默认:1.0)
对于类似期货
的工具,这决定了要应用于利润和损失计算的乘数。
这就是期货同时具有吸引力和风险的原因。name
(默认:无)
限制佣金方案的应用于与name
匹配的工具。
这可以在创建数据源时设置。
如果不设置,方案将应用于系统中存在的任何数据。
现在有两个例子:股票 vs 期货
来自上述的期货示例:
cerebro.broker.setcommission(commission=2.0, margin=2000.0, mult=10.0)
股票的一个例子:
cerebro.broker.setcommission(commission=0.005) # 0.5% of the operation value
创建永久佣金方案
更持久的佣金方案可以通过直接使用CommissionInfo
类来创建。用户可以选择将此定义放在某处:
from bt import CommissionInfo commEurostoxx50 = CommissionInfo(commission=2.0, margin=2000.0, mult=10.0)
然后在另一个 Python 模块中应用它与addcommissioninfo
:
from mycomm import commEurostoxx50 ... cerebro.broker.addcomissioninfo(commEuroStoxx50, name='Eurostoxxx50')
CommissionInfo
是一个对象,它使用与backtrader
环境中的其他对象一样的params
声明。因此,上述内容也可以表示为:
from bt import CommissionInfo class CommEurostoxx50(CommissionInfo): params = dict(commission=2.0, margin=2000.0, mult=10.0)
后来:
from mycomm import CommEurostoxx50 ... cerebro.broker.addcomissioninfoCommEuroStoxx50(), name='Eurostoxxx50')
BackTrader 中文文档(二十八)(4)https://developer.aliyun.com/article/1505489