BackTrader 中文文档(二十八)(3)

简介: BackTrader 中文文档(二十八)

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 数据的文件名或类似文件的对象)

其他一些参数如namecompressiontimeframe仅供参考,除非您计划执行重新采样。

当然更重要的是,新定义参数的含义:

  • 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已经拥有了所有需要的信息,并且已经将大部分信息填入了被通知到strategyorder中,而broker需要做出的唯一决定是是否将额外的信息位放入订单中,或者它可以自己计算operations

由于策略已经获得了orders,并且将operations保留在列表中似乎很自然,broker只是在订单部分/完全关闭头寸时添加实际的盈亏,将计算责任留给了strategy

这进一步简化了Operations Observer的实际角色,即观察新关闭的Operation并记录下来。这是它一直应该有的角色。

下面的代码已经被重新设计,不再计算盈亏数字,而只是注意到notify_operation中通知的数字。

现在图表反映出了真实的盈亏数字(cashvalue已经是真实的)

旧的期货记录:

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操作之间的差异,不仅在cashvalue的变化中。

期货佣金

股票佣金

代码

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将被理解为以百分比表示,并应用于buysell操作的price * size组件
  • 如果设置了margin,则commission将被理解为与buysell操作的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

相关文章
|
5天前
|
测试技术 C语言 Python
BackTrader 中文文档(二十八)(4)
BackTrader 中文文档(二十八)
14 0
|
5天前
BackTrader 中文文档(二十八)(2)
BackTrader 中文文档(二十八)
11 0
|
5天前
|
Python
BackTrader 中文文档(二十八)(1)
BackTrader 中文文档(二十八)
10 0
|
5天前
BackTrader 中文文档(二十九)(2)
BackTrader 中文文档(二十九)
11 0
|
5天前
|
存储 API
BackTrader 中文文档(二十九)(4)
BackTrader 中文文档(二十九)
14 0
|
5天前
|
算法 索引
BackTrader 中文文档(二十九)(1)
BackTrader 中文文档(二十九)
11 0
|
5天前
BackTrader 中文文档(二十九)(3)
BackTrader 中文文档(二十九)
10 0
|
5天前
|
算法 索引 Python
BackTrader 中文文档(二十七)(2)
BackTrader 中文文档(二十七)
15 0
|
5天前
|
索引
BackTrader 中文文档(二十七)(3)
BackTrader 中文文档(二十七)
10 0
|
5天前
|
存储 数据库连接 数据库
BackTrader 中文文档(二十七)(4)
BackTrader 中文文档(二十七)
10 0