BackTrader 中文文档(一)(4)

简介: BackTrader 中文文档(一)

BackTrader 中文文档(一)(3)https://developer.aliyun.com/article/1489211

cerebro.plot()

Being the location for sure after cerebro.run() has been called.
In order to display the automatic plotting capabilities and a couple of easy customizations, the following will be done:
*   A 2^(nd) MovingAverage (Exponential) will be added. The defaults will plot it (just like the 1^(st)) with the data.
*   A 3^(rd) MovingAverage (Weighted) will be added. Customized to plot in an own plot (even if not sensible)
*   A Stochastic (Slow) will be added. No change to the defaults.
*   A MACD will be added. No change to the defaults.
*   A RSI will be added. No change to the defaults.
*   A MovingAverage (Simple) will be applied to the RSI. No change to the defaults (it will be plotted with the RSI)
*   An AverageTrueRange will be added. Changed defaults to avoid it being plotted.
The entire set of additions to the **init** method of the Strategy:

`# 绘图显示的指标

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`

Note
Even if *indicators* are not explicitly added to a member variable of the strategy (like self.sma = MovingAverageSimple…), they will autoregister with the strategy and will influence the minimum period for *next* and will be part of the plotting.
In the example only *RSI* is added to a temporary variable *rsi* with the only intention to create a MovingAverageSmoothed on it.
The example now:

`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 = (
    ('maperiod', 15),
)
def log(self, txt, dt=None):

‘’’ 此策略的日志函数’‘’

dt = dt or self.datas[0].datetime.date(0)
    print('%s, %s' % (dt.isoformat(), txt))
def __init__(self):
    # 保持对数据[0] 数据系列中的 "close" 线的引用
    self.dataclose = self.datas[0].close
    # 为了跟踪待处理的订单和买入价格/佣金
    self.order = None
    self.buyprice = None
    self.buycomm = None
    # 添加一个 MovingAverageSimple 指标
    self.sma = bt.indicators.SimpleMovingAverage(
        self.datas[0], period=self.params.maperiod)
    # 用于绘图的指标
    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)
def notify_order(self, order):
    if order.status in [order.Submitted, order.Accepted]:
        # 提交/接受到/由经纪人的买入/卖出订单 - 无需操作
        返回
    # 检查订单是否已完成
    # 注意: 如果资金不足,经纪人可能会拒绝订单
    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:
        返回
    self.log('操作利润, 总额 %.2f, 净额 %.2f' %
            (trade.pnl, trade.pnlcomm))
def next(self):
    # 简单记录来自参考系列的收盘价格
    self.log('关闭, %.2f' % self.dataclose[0])
    # 检查订单是否待处理...如果是,我们不能发送第二个订单
    if self.order:
        返回
    # 检查我们是否在市场中
    if not self.position:
        # 还没有...如果...的话,我们可能会买入
        如果 self.dataclose[0] > self.sma[0]:
            # 买, 买, 买!!!(所有可能的默认参数)
            self.log('买入创建, %.2f' % self.dataclose[0])
            # 跟踪创建的订单以避免第二个订单
            self.order = self.buy()
    否则:
        如果 self.dataclose[0] < self.sma[0]:
            # 卖, 卖, 卖!!!(所有可能的默认参数)
            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(1000.0)
# 根据股份添加一个固定大小的 sizer
cerebro.addsizer(bt.sizers.FixedSize, stake=10)
# 设置佣金
cerebro.broker.setcommission(commission=0.0)
# 打印出起始条件
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
# 在所有内容上运行
cerebro.run()
# 打印出最终结果
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
# 绘制结果
cerebro.plot()`
After the execution the output is:

起始投资组合价值:1000.00

2000-02-18T00:00:00,收盘价,27.61

2000-02-22T00:00:00,收盘价,27.97

2000-02-22T00:00:00,买入创建,27.97

2000-02-23T00:00:00,买入执行,数量 10,价格:28.38,成本:283.80,佣金 0.00

2000-02-23T00:00:00,收盘价,29.73

2000-12-21T00:00:00,买入创建,27.82

2000-12-22T00:00:00,买入执行,数量 10,价格:28.65,成本:286.50,佣金 0.00

2000-12-22T00:00:00,收盘价,30.06

2000-12-26T00:00:00,收盘价,29.17

2000-12-27T00:00:00,收盘价,28.94

2000-12-28T00:00:00,收盘价,29.29

2000-12-29T00:00:00,收盘价,27.41

2000-12-29T00:00:00,卖出创建,27.41

最终投资组合价值:981.00`

**The final result has changed even if the logic hasn’t**. This is true but the logic has not been applied to the same number of bars.
Note
As explained before, the platform will first call next when all indicators are ready to produce a value. In this plotting example (very clear in the chart) the MACD is the last indicator to be fully ready (all 3 lines producing an output). The 1^(st) BUY order is no longer scheduled during Jan 2000 but close to the end of Feb 2000.
The chart:
![image](https://gitcode.net/OpenDocCN/flygon-quant-docs-zh/-/raw/master/docs/backtrader/img/ea78429a6e60755c2d70c3857cb63606.png)
### Let’s Optimize
Many trading books say each market and each traded stock (or commodity or ..) have different rythms. That there is no such thing as a one size fits all.
Before the plotting sample, when the strategy started using an indicator the period default value was 15 bars. It’s a strategy parameter and this can be used in an optimization to change the value of the parameter and see which one better fits the market.
Note
There is plenty of literature about Optimization and associated pros and cons. But the advice will always point in the same direction: do not overoptimize. If a trading idea is not sound, optimizing may end producing a positive result which is only valid for the backtested dataset.
The sample is modified to optimize the period of the Simple Moving Average. For the sake of clarity any output with regards to Buy/Sell orders has been removed
The example now:

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 = (
    ('maperiod', 15),
    ('printlog', False),
)
def log(self, txt, dt=None, doprint=False):

‘’’ 用于此策略的日志记录函数’‘’

if self.params.printlog or doprint:
        dt = dt or self.datas[0].datetime.date(0)
        `print('%s, %s' % (dt.isoformat(), txt))`
def __init__(self):
    # 保持对数据[0]数据系列中“close”线的引用
    self.dataclose = self.datas[0].close
    # 跟踪挂单和买入价格/佣金
    self.order = None
    self.buyprice = None
    self.buycomm = None
    # 添加一个 MovingAverageSimple 指标
    self.sma = bt.indicators.SimpleMovingAverage(
        self.datas[0],周期=self.params.maperiod)
def notify_order(self, order):
    if order.status in [order.Submitted, order.Accepted]:
        # 提交/接受经纪人的买入/卖出订单 - 没有操作可执行
        返回
    # 检查订单是否已完成
    # 注意:如果现金不足,经纪人可能会拒绝订单
    if order.status in [order.Completed]:
        if order.isbuy():
            self.log(
                'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                (order.executed.price,
                order.executed.value,
                order.executed.comm))
            self.buyprice = order.executed.price
            self.buycomm = order.executed.comm
        else:  # 卖出
            self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.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):
    如果交易未关闭:
        返回
    self.log('操作利润,毛利 %.2f,净利 %.2f' %
            (trade.pnl, trade.pnlcomm))
def next(self):
    # 简单地记录来自参考系列的收盘价
    self.log('收盘,%.2f' % self.dataclose[0])
    # 检查是否有挂单... 如果有,我们不能发送第二个挂单
    如果存在挂单...
        返回
    # 检查我们是否在市场上
    如果没有持仓...
        # 还没有... 如果...,我们可能会买入
        如果 self.dataclose[0] > self.sma[0]:
            # 买入,买入,买入!!!(带有所有可能的默认参数)
            self.log('买入创建,%.2f' % self.dataclose[0])
            # 跟踪已创建的订单以避免第二个订单
            self.order = self.buy()
    否则:
        如果 self.dataclose[0] < self.sma[0]:
            # 卖出,卖出,卖出!!!(带有所有可能的默认参数)
            self.log('卖出创建,%.2f' % self.dataclose[0])
            # 跟踪已创建的订单以避免第二个订单
            self.order = self.sell()
def stop(self):
    self.log('(MA 周期 %2d)结束价值 %.2f' %
            (self.params.maperiod, self.broker.getvalue()), doprint=True)

如果 name == ‘main’:

# 创建一个 cerebro 实体
cerebro = bt.Cerebro()
# 添加一种策略
strats = cerebro.optstrategy(
    TestStrategy,
    maperiod=range(10, 31))
# 数据位于样本的子文件夹中。需要找到脚本所在的位置
# 因为它可能是从任何地方调用的
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(1000.0)
# 根据股份添加一个固定大小的调整器
cerebro.addsizer(bt.sizers.FixedSize, stake=10)
# 设置佣金
cerebro.broker.setcommission(commission=0.0)
# 对一切进行操作
cerebro.run(maxcpus=1)`
Instead of calling *addstrategy* to add a stratey class to Cerebro, the call is made to *optstrategy*. And instead of passing a value a range of values is passed.
One of the “Strategy” hooks is added, the *stop* method, which will be called when the data has been exhausted and backtesting is over. It’s used to print the final net value of the portfolio in the broker (it was done in Cerebro previously)
The system will execute the strategy for each value of the range. The following will be output:

`2000-12-29,(MA 周期 10)结束价值 880.30

2000-12-29,(MA 周期 11)结束价值 880.00

2000-12-29,(MA 周期 12)结束价值 830.30

2000-12-29,(MA 周期 13)结束价值 893.90

2000-12-29,(MA 周期 14)结束价值 896.90

2000-12-29,(MA 周期 15)结束价值 973.90

2000-12-29,(MA 周期 16)结束价值 959.40

2000-12-29,(MA 周期 17)结束价值 949.80

2000-12-29,(MA 周期 18)结束价值 1011.90

2000-12-29,(MA 周期 19)结束价值 1041.90

2000-12-29,(MA 周期 20)结束价值 1078.00

2000-12-29,(MA 周期 21)结束价值 1058.80

2000-12-29,(MA 周期 22)结束价值 1061.50

2000-12-29,(MA 周期 23)结束价值 1023.00

2000-12-29,(MA 周期 24)结束价值 1020.10

2000-12-29,(MA 周期 25)结束价值 1013.30

2000-12-29,(MA 周期 26)结束价值 998.30

2000-12-29,(MA 周期 27)结束价值 982.20

2000-12-29,(MA 周期 28)结束价值 975.70

2000 年 12 月 29 日,(MA 周期 29)结束价值为 983.30

2000 年 12 月 29 日,(MA 周期 30)结束价值为 979.80`

结果:
+   对于低于 18 的周期,该策略(免佣)会亏钱。
+   对于周期在 18 到 26 之间(两者都包括在内),该策略赚钱。
+   超过 26 后又会损失金钱。
而该策略在给定数据集下的获胜周期是:
+   20 根棒子,在 1000 美元/欧元的基础上赢得了 78.00 个单位(7.8%)
注意
绘图示例中的额外指标已被移除,操作的开始只受到正在优化的简单移动平均线的影响。因此,周期为 15 时结果略有不同。
### 结论
增量样本展示了如何从一个基本脚本发展到一个完全工作的交易系统,甚至绘制了结果并且可以优化。
可以做更多事情来尝试提高获胜的机会:
+   自定义指标
    创建一个指标很容易(甚至绘制它们也很容易)
+   大小调整器
    资金管理对于许多人来说是成功的关键
+   订单类型(限价,止损,止损限价)
+   其他一些
为了确保上述所有项目都能得到充分利用,文档提供了对它们(以及其他主题)的深入了解
查看目录并继续阅读……并发展。
祝你好运


相关文章
|
6月前
|
Unix 索引 Python
BackTrader 中文文档(一)(2)
BackTrader 中文文档(一)
156 0
|
6月前
|
索引
BackTrader 中文文档(六)(2)
BackTrader 中文文档(六)
87 0
|
6月前
|
存储 编解码 API
BackTrader 中文文档(四)(1)
BackTrader 中文文档(四)
71 1
|
6月前
|
索引
BackTrader 中文文档(三)(4)
BackTrader 中文文档(三)
64 0
|
6月前
|
Python
BackTrader 中文文档(五)(4)
BackTrader 中文文档(五)
73 0
|
6月前
|
存储 编解码 网络架构
BackTrader 中文文档(二)(4)
BackTrader 中文文档(二)
107 0
|
6月前
|
存储 安全 Unix
BackTrader 中文文档(四)(2)
BackTrader 中文文档(四)
57 0
|
6月前
|
存储 DataX Python
BackTrader 中文文档(五)(1)
BackTrader 中文文档(五)
114 0
|
6月前
|
Python
BackTrader 中文文档(六)(3)
BackTrader 中文文档(六)
86 0
|
6月前
|
存储 API 索引
BackTrader 中文文档(四)(3)
BackTrader 中文文档(四)
51 0