quickstart Guide快速入门

news/2024/11/24 6:59:17/

快速入门

使用平台

让我们通过一系列的示例(从几乎空白到完全成熟的策略)来运行,但在此之前,让我们大致解释一下使用backtrader时的两个基本概念。

  1. Lines线

    数据源、指标和策略都有线

    一条线是一系列点的连续性,这些点连接在一起形成这条线。当谈论市场时,数据源通常每天有以下一组点:

    • Open, High, Low, Close, Volume, OpenInterest (开盘价、最高价、最低价、收盘价、成交量、持仓量)

    时间序列中的开盘价是一条线。因此,数据源通常有6条线。

    如果我们还考虑DateTime(这是单个点的实际参考),我们可以计算出7条线。

  2. 索引0的用法

    在访问线中的值时,使用索引:0访问当前值

    last输出值使用*-1访问。这符合Python可迭代对象的惯例(线可以迭代,因此是可迭代的),其中索引-1*用于访问可迭代/数组的last项。

    在我们的情况下,访问的是最后一个输出值。

    因此,索引0紧随*-1*之后,用于访问线中的当前时刻。

有了这个想法,如果我们想在初始化期间创建一个简单移动平均线的策略:

    self.sma = SimpleMovingAverage(.....)

访问此移动平均线的当前值的最简单和最简单的方法是:

    av = self.sma[0]

无需知道已处理了多少个bar/分钟/天/月,因为0是当前时刻的唯一标识。

按照Python的传统,使用*-1*访问last输出值:

    previous_value = self.sma[-1]

当然,可以使用-2、-3等访问早期的输出值。

从0到100:样本

基本设置

让我们开始吧。

from __future__ import (absolute_import, division, print_function,unicode_literals)
# 导入Backtrader
import backtrader as btif __name__ == '__main__':# 创建一个Cerebro引擎实例cerebro = bt.Cerebro()# 打印初始资金print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())# 执行回测cerebro.run()# 打印最终资金print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

上面示例代码实现(虽然什么也没做,但是流程走完了):

  • 导入Backtrader
  • 实例化Cerebro引擎
  • 打印初始资金
  • 执行回测
  • 打印最终资金

代码的执行结果(小白注意不用拷贝执行,这是运行后得到的内容):

Starting Portfolio Value: 10000.00
Final Portfolio Value: 10000.00

在这个例子中:

  • 导入了backtrader

  • 实例化了Cerebro引擎

  • 生成的cerebro实例被告知run(循环数据)

  • 并打印出了结果

虽然看起来不起眼,但让我们明确指出一些东西:

  • Cerebro引擎在后台创建了一个broker实例
  • 该实例已经有一些现金可以开始交易

这种幕后经纪人实例化是平台中的一个常见特征,以简化用户的生活。如果用户没有设置经纪人,则会放置一个默认经纪人。

而10K的测试货币量是回测常用的一个货币价值。

设置现金

在金融世界中,只有输家才从10k开始。让我们改变现金并再次运行示例。

from __future__ import (absolute_import, division, print_function,unicode_literals)import backtrader as btif __name__ == '__main__':cerebro = bt.Cerebro()cerebro.broker.setcash(100000.0)print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())cerebro.run()print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

执行后的输出为:

  Starting Portfolio Value: 1000000.00Final Portfolio Value: 1000000.00

本节任务完成,让我们提升任务的难度。

添加一个数据源

有现金很有趣,但所有这一切的目的是让策略在我们提供的数据源资产上自动操作,而不需要动手。

因此…没有数据源->没有乐趣。让我们将其添加到不断增长的示例中。

from __future__ import (absolute_import, division, print_function,unicode_literals)import datetime  # 用于日期时间对象
import os.path  # 用于管理路径
import sys  # 用于查找脚本名称(在argv [0]中)# 导入backtrader平台
import backtrader as btif __name__ == '__main__':# 创建cerebro实体cerebro = bt.Cerebro()# 数据在样本的子文件夹中。需要找到脚本所在的位置# 因为它可以从任何地方调用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)# 将数据源添加到Cerebrocerebro.adddata(data)# 设置我们所需的现金起始值cerebro.broker.setcash(100000.0)# 打印出起始条件print('起始投资组合价值:%.2f' % cerebro.broker.getvalue())# 运行所有cerebro.run()# 打印出最终结果print('最终投资组合价值:%.2f' % cerebro.broker.getvalue())

执行后的输出为:

起始投资组合价值:100000.00
最终投资组合价值:100000.00

模板代码的数量略有增加,因为我们添加了:

  • 找出我们的示例脚本所在的位置,以便能够定位示例数据源文件

  • datetime对象以过滤我们将要操作的数据源中的数据

除此之外,Data Feed被创建并添加到cerebro中。

输出没有改变,如果它改变了,那将是一个奇迹。

注意:Yahoo Online以日期降序发送CSV数据,这不是标准约定。reversed=True参数考虑到CSV数据在文件中已经被反转并具有标准预期的日期升序。

我们的第一个策略

现金在broker中,Data Feed也在那里。我们马上就可以跑生意了。

让我们将策略放入方程式中,并打印每天(bar)的Close价格。

DataSeriesData Feeds中的基础类)对象具有访问众所周知的OHLC(Open High Low Close)每日值的别名。这应该简化我们的打印逻辑的创建。

# 导入所需模块
from __future__ import (absolute_import, division, print_function, unicode_literals)
import datetime  # 日期时间模块
import os.path  # 路径模块
import sys  # 系统模块# 导入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):# 保留对数据序列中`close`线的引用self.dataclose = self.datas[0].closedef next(self):# 记录数据序列的收盘价self.log('收盘价, %.2f' % self.dataclose[0])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)# 将数据源添加到Cerebrocerebro.adddata(data)# 设置初始资金cerebro.broker.setcash(100000.0)# 打印初始条件print('初始资产价值: %.2f' % cerebro.broker.getvalue())# 运行策略cerebro.run()# 打印最终结果print('最终资产价值: %.2f' % cerebro.broker.getvalue())

执行后的输出为::

初始资产价值: 100000.00
2000-01-03, 收盘价, 26.27
2000-01-04, 收盘价, 23.95.........
2000-12-28, 收盘价, 27.63
2000-12-29, 收盘价, 25.85
最终资产价值: 100000.00

有人说股票市场是冒险的生意,但似乎并非如此。

让我们解释一些神奇的事:

  • 在调用__init__时,策略已经有了一个存在于平台中的数据列表

    这是一个标准的Python的list,可以按照它们插入的顺序访问数据。

    列表中的第一个数据self.datas[0]是用于交易操作的默认数据,并且为了保持所有策略元素同步(它是系统时钟

  • self.dataclose = self.datas[0].close保留对close line的引用。只需要一个间接级别即可访问关闭值。

  • 策略next方法将在系统时钟(self.datas[0])的每个柱上调用。这是真实的,直到其他事情进入比如indicators,它需要一些柱才能开始产生输出。稍后再说。

给策略添加执行逻辑

让我们尝试一些疯狂的想法,通过查看一些图表

  • 如果价格连续下跌3个会话… 买买买!!!
from __future__ import (absolute_import, division, print_function,unicode_literals)import datetime  # 导入日期时间库
import os.path  # 导入路径管理库
import sys  # 导入系统库# 导入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):# 保留对数据序列中`close`线的引用self.dataclose = self.datas[0].closedef next(self):# 记录数据序列的收盘价self.log('Close, %.2f' % self.dataclose[0])if self.dataclose[0] < self.dataclose[-1]:# 当前收盘价小于前一个收盘价if self.dataclose[-1] < self.dataclose[-2]:# 前一个收盘价小于前一个收盘价# 买入self.log('BUY CREATE, %.2f' % self.dataclose[0])self.buy()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)# 将数据源添加到Cerebrocerebro.adddata(data)# 设置我们的期望现金起始值cerebro.broker.setcash(100000.0)# 打印出起始条件print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())# 运行策略cerebro.run()# 打印出最终结果print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

执行后的输出为:

Starting Portfolio Value: 100000.00
2000-01-03, Close, 26.27
2000-01-04, Close, 23.95
2000-01-05, Close, 22.68
2000-01-05, BUY CREATE, 22.68
2000-01-06, Close, 21.35
2000-01-06, BUY CREATE, 21.35
...
...
...
2000-12-20, BUY CREATE, 25.35
2000-12-21, Close, 26.24
2000-12-22, Close, 28.35
2000-12-26, Close, 27.52
2000-12-27, Close, 27.30
2000-12-27, BUY CREATE, 27.30
2000-12-28, Close, 27.63
2000-12-29, Close, 25.85
Final Portfolio Value: 99740.45

发出了几个买入订单,我们的组合价值减少了。显然缺少了一些重要的事情。

  • 订单已创建,但不知道是否执行,何时执行以及以什么价格执行。

    下一个示例将在此基础上构建,通过监听订单状态的通知来解决这个问题。

好奇的读者可能会问买了多少股票,买了哪些资产以及如何执行订单。在可能的情况下(在这种情况下),平台填补了这些空白:

  • self.datas[0](主数据,即系统时钟)是操作买卖的目标资产,如果没有指定其他资产的话(即默认操作datas[0])
  • 买卖股份数量由position sizer在幕后提供,使用固定股份,即默认值1。它将在稍后的示例中进行修改
  • 订单是市价执行的。经纪人(在前面的示例中显示)使用下一个条的开盘价执行此操作,因为那是当前正在检查的条下一个tick。
  • 订单迄今为止没有任何佣金(稍后会详细介绍)

不仅要买…还要卖

在了解如何进入市场(多头)之后,需要一个退出概念,即给策略添加从市场退出的逻辑(卖股)。

  • 幸运的是,Strategy对象提供了对默认数据源position属性的访问
  • 方法buysell返回已创建(尚未执行)的订单
  • 订单状态的更改将通过notify方法通知策略

退出概念将是一个简单的概念:

  • 在5个bar(第6个bar)之后退出,无论好坏

    请注意,1个bar的bar可以表示1分钟,1小时,1天,1周或其他任何长度的时间单位。

    尽管我们知道数据源是每日的,但策略不会对此做出任何假设。

此外,为了简化:

  • 仅在尚未进入市场时才允许购买订单

注意:next方法没有传递bar索引,因此似乎不清楚如何理解何时可能已经过去了5个bar,但是这已经以pythonic的方式建模:在对象上调用len,它将告诉您其lines的长度。
只需在操作中记录(保存在变量中)已经迭代过的bar的长度,然后查看当前长度是否相距5个bar。

from __future__ import (absolute_import, division, print_function,unicode_literals)import datetime  # 导入datetime模块
import os.path  # 导入os.path模块
import sys  # 导入sys模块# 导入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):# 保留对数据序列中`close`线的引用self.dataclose = self.datas[0].close# 跟踪待处理订单self.order = Nonedef 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 = Nonedef next(self):# 仅记录参考系列的收盘价self.log('Close, %.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)# 将数据源添加到Cerebrocerebro.adddata(data)# 设置我们的期望现金起始值cerebro.broker.setcash(100000.0)# 打印出起始条件print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())# 运行整个策略cerebro.run()# 打印出最终结果print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

执行后的输出为(打印日志是源文档未翻译,但代码中打印内容已翻译):

Starting Portfolio Value: 100000.00
2000-01-03, Close, 26.27
2000-01-04, Close, 23.95
2000-01-05, Close, 22.68
2000-01-05, 买入创建, 22.68
2000-01-06, 买入已执行, 22.27
...
...
2000-12-20, Close, 25.35
2000-12-20, 买入创建, 25.35
2000-12-21, 买入已执行, 24.74
2000-12-21, Close, 26.24
...
2000-12-28, Close, 27.63
2000-12-29, Close, 25.85
2000-12-29, 卖出创建, 25.85
Final Portfolio Value: 100017.52

系统赚了钱…一定有什么问题

券商说:打钱!

这笔钱叫做佣金

让我们为每次操作(买入和卖出)添加合理的*0.1%*佣金率(是的,经纪人很贪婪……)

只需要一行代码:

# 0.1% ... divide by 100 to remove the %
cerebro.broker.setcommission(commission=0.001)

熟悉平台后,我们想在买卖周期之后查看有无佣金的利润或损失。

from __future__ import (absolute_import, division, print_function,unicode_literals)import datetime  # For datetime objects
import os.path  # To manage paths
import sys  # To find out the script name (in argv[0])# Import the backtrader platform
import backtrader as bt# Create a Stratey
class TestStrategy(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 __init__(self):# Keep a reference to the "close" line in the data[0] dataseriesself.dataclose = self.datas[0].close# To keep track of pending orders and buy price/commissionself.order = Noneself.buyprice = Noneself.buycomm = Nonedef notify_order(self, order):if order.status in [order.Submitted, order.Accepted]:# Buy/Sell order submitted/accepted to/by broker - Nothing to doreturn# Check if an order has been completed# Attention: broker could reject order if not enough cashif 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.priceself.buycomm = order.executed.commelse:  # Sellself.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('Order Canceled/Margin/Rejected')self.order = Nonedef notify_trade(self, trade):if not trade.isclosed:returnself.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %(trade.pnl, trade.pnlcomm))def next(self):# Simply log the closing price of the series from the referenceself.log('Close, %.2f' % self.dataclose[0])# Check if an order is pending ... if yes, we cannot send a 2nd oneif self.order:return# Check if we are in the marketif not self.position:# Not yet ... we MIGHT BUY if ...if self.dataclose[0] < self.dataclose[-1]:# current close less than previous closeif self.dataclose[-1] < self.dataclose[-2]:# previous close less than the previous close# BUY, BUY, BUY!!! (with default parameters)self.log('BUY CREATE, %.2f' % self.dataclose[0])# Keep track of the created order to avoid a 2nd orderself.order = self.buy()else:# Already in the market ... we might sellif len(self) >= (self.bar_executed + 5):# SELL, SELL, SELL!!! (with all possible default parameters)self.log('SELL CREATE, %.2f' % self.dataclose[0])# Keep track of the created order to avoid a 2nd orderself.order = self.sell()if __name__ == '__main__':# Create a cerebro entitycerebro = bt.Cerebro()# Add a strategycerebro.addstrategy(TestStrategy)# Datas are in a subfolder of the samples. Need to find where the script is# because it could have been called from anywheremodpath = os.path.dirname(os.path.abspath(sys.argv[0]))datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt')# Create a Data Feeddata = bt.feeds.YahooFinanceCSVData(dataname=datapath,# Do not pass values before this datefromdate=datetime.datetime(2000, 1, 1),# Do not pass values before this datetodate=datetime.datetime(2000, 12, 31),# Do not pass values after this datereverse=False)# Add the Data Feed to Cerebrocerebro.adddata(data)# Set our desired cash startcerebro.broker.setcash(100000.0)# Set the commission - 0.1% ... divide by 100 to remove the %cerebro.broker.setcommission(commission=0.001)# Print out the starting conditionsprint('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())# Run over everythingcerebro.run()# Print out the final resultprint('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

执行后的输出为:

Starting Portfolio Value: 100000.00
2000-01-03, Close, 26.27
2000-01-04, Close, 23.95
2000-01-05, Close, 22.68
2000-01-05, BUY CREATE, 22.68
2000-01-06, BUY EXECUTED, Price: 22.27, Cost: 22.27, Comm 0.02
...
...
...
2000-12-21, BUY EXECUTED, Price: 24.74, Cost: 24.74, Comm 0.02
2000-12-21, Close, 26.24
2000-12-22, Close, 28.35
2000-12-26, Close, 27.52
2000-12-27, Close, 27.30
2000-12-28, Close, 27.63
2000-12-29, Close, 25.85
2000-12-29, SELL CREATE, 25.85
Final Portfolio Value: 100016.06

上帝保佑!系统仍然赚了钱。

在继续之前,让我们通过过滤OPERATION PROFIT行来注意一些事情:

2000-01-14, OPERATION PROFIT, GROSS 1.97, NET 1.92
2000-02-07, OPERATION PROFIT, GROSS 3.48, NET 3.43
2000-02-28, OPERATION PROFIT, GROSS 4.23, NET 4.17
2000-03-13, OPERATION PROFIT, GROSS 3.28, NET 3.21
2000-03-22, OPERATION PROFIT, GROSS -0.39, NET -0.46
2000-04-07, OPERATION PROFIT, GROSS 2.31, NET 2.24
2000-04-20, OPERATION PROFIT, GROSS -1.83, NET -1.90
2000-05-02, OPERATION PROFIT, GROSS 5.15, NET 5.08
2000-05-11, OPERATION PROFIT, GROSS -3.53, NET -3.59
2000-05-30, OPERATION PROFIT, GROSS -1.39, NET -1.45
2000-07-05, OPERATION PROFIT, GROSS -1.53, NET -1.60
2000-07-14, OPERATION PROFIT, GROSS 1.97, NET 1.90
2000-07-28, OPERATION PROFIT, GROSS 0.14, NET 0.07
2000-08-08, OPERATION PROFIT, GROSS 4.11, NET 4.04
2000-08-21, OPERATION PROFIT, GROSS 0.97, NET 0.90
2000-09-15, OPERATION PROFIT, GROSS -4.00, NET -4.08
2000-09-27, OPERATION PROFIT, GROSS 1.22, NET 1.15
2000-10-13, OPERATION PROFIT, GROSS -2.81, NET -2.87
2000-10-26, OPERATION PROFIT, GROSS 2.84, NET 2.78
2000-11-06, OPERATION PROFIT, GROSS -3.39, NET -3.45
2000-11-16, OPERATION PROFIT, GROSS 1.22, NET 1.17
2000-12-01, OPERATION PROFIT, GROSS 2.45, NET 2.41
2000-12-18, OPERATION PROFIT, GROSS -0.06, NET -0.11

利润相加,最终数字为:

15.83(备注原文数字,实际执行代码结果和文档有差异,此处示意)

但系统在最后说了以下内容:

2000-12-29, SELL CREATE, 25.85
Final Portfolio Value: 100016.06

显然,15.83不是16.06。没有任何错误。15.83利润已经到手了。

不幸的是(或者幸运的是,为了更好地理解平台),在数据源最后一天有一个未平仓头寸。即使已经发送了SELL操作……它还没有被执行。

经纪人计算的最终组合价值考虑了2000-12-29的收盘价格。实际执行价格将在下一个交易日(即2001-1-3)设置。将数据源扩展到考虑这一天后,输出为:

2000-12-29, SELL CREATE, 25.85
2001-01-02, SELL EXECUTED, Price: 26.30, Cost: 24.74, Comm 0.03
2001-01-02, OPERATION PROFIT, GROSS 1.56, NET 1.51
2001-01-02, Close, 23.46
2001-01-02, BUY CREATE, 23.46
Final Portfolio Value: 100016.48

现在将前面的利润添加到已完成操作的利润中:

15.83 + 1.59 = 17.42(备注原文数字,实际执行代码结果和文档有差异,此处示意)

(忽略print语句中的舍入误差),这是初始100000货币单位以上的额外组合。

自定义策略:参数

在策略中硬编码一些值并没有什么实际意义,而且很难轻松更改它们。参数很有用。

定义参数很容易,看起来像这样:

params = (('myparam', 27), ('exitbars', 5),)

这是一个标准的Python元组,里面有一些元组,以下可能更吸引人:

params = (('myparam', 27),('exitbars', 5),
)

无论哪种格式化策略都允许在将策略添加到Cerebro引擎时进行参数化:

# Add a strategy
cerebro.addstrategy(TestStrategy, myparam=20, exitbars=7)

注意:下面的setsizing方法已弃用。这个内容是为了让任何人查看旧的源代码示例而保留的。源代码已经更新为使用:

# 已经弃用
cerebro.addsizer(bt.sizers.FixedSize, stake=10)``

可以阅读sizers相关章节查看更多。

在策略中使用参数很容易,因为它们存储在params属性中。如果我们例如想要设置固定的股份,我们可以在__init__期间将股份参数传递给position sizer

# Set the sizer stake from the params
self.sizer.setsizing(self.params.stake)

我们也可以使用stake参数和self.params.stake作为值来调用buysell

退出逻辑被修改:

# Already in the market ... we might sell
if len(self) >= (self.bar_executed + self.params.exitbars):

考虑到所有这些,示例的演变如下:

from __future__ import (absolute_import, division, print_function,unicode_literals)import datetime  # For datetime objects
import os.path  # To manage paths
import sys  # To find out the script name (in argv[0])# Import the backtrader platform
import backtrader as bt# Create a Stratey
class TestStrategy(bt.Strategy):params = (('exitbars', 5),)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 __init__(self):# Keep a reference to the "close" line in the data[0] dataseriesself.dataclose = self.datas[0].close# To keep track of pending orders and buy price/commissionself.order = Noneself.buyprice = Noneself.buycomm = Nonedef notify_order(self, order):if order.status in [order.Submitted, order.Accepted]:# Buy/Sell order submitted/accepted to/by broker - Nothing to doreturn# Check if an order has been completed# Attention: broker could reject order if not enough cashif 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.priceself.buycomm = order.executed.commelse:  # Sellself.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('Order Canceled/Margin/Rejected')self.order = Nonedef notify_trade(self, trade):if not trade.isclosed:returnself.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %(trade.pnl, trade.pnlcomm))def next(self):# Simply log the closing price of the series from the referenceself.log('Close, %.2f' % self.dataclose[0])# Check if an order is pending ... if yes, we cannot send a 2nd oneif self.order:return# Check if we are in the marketif not self.position:# Not yet ... we MIGHT BUY if ...if self.dataclose[0] < self.dataclose[-1]:# current close less than previous closeif self.dataclose[-1] < self.dataclose[-2]:# previous close less than the previous close# BUY, BUY, BUY!!! (with default parameters)self.log('BUY CREATE, %.2f' % self.dataclose[0])# Keep track of the created order to avoid a 2nd orderself.order = self.buy()else:# Already in the market ... we might sellif len(self) >= (self.bar_executed + self.params.exitbars):# SELL, SELL, SELL!!! (with all possible default parameters)self.log('SELL CREATE, %.2f' % self.dataclose[0])# Keep track of the created order to avoid a 2nd orderself.order = self.sell()if __name__ == '__main__':# Create a cerebro entitycerebro = bt.Cerebro()# Add a strategycerebro.addstrategy(TestStrategy)# Datas are in a subfolder of the samples. Need to find where the script is# because it could have been called from anywheremodpath = os.path.dirname(os.path.abspath(sys.argv[0]))datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt')# Create a Data Feeddata = bt.feeds.YahooFinanceCSVData(dataname=datapath,# Do not pass values before this datefromdate=datetime.datetime(2000, 1, 1),# Do not pass values before this datetodate=datetime.datetime(2000, 12, 31),# Do not pass values after this datereverse=False)# Add the Data Feed to Cerebrocerebro.adddata(data)# Set our desired cash startcerebro.broker.setcash(100000.0)# Add a FixedSize sizer according to the stakecerebro.addsizer(bt.sizers.FixedSize, stake=10)# Set the commission - 0.1% ... divide by 100 to remove the %cerebro.broker.setcommission(commission=0.001)# Print out the starting conditionsprint('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())# Run over everythingcerebro.run()# Print out the final resultprint('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

执行后的输出为:

Starting Portfolio Value: 100000.00
2000-01-03, Close, 26.27
2000-01-04, Close, 23.95
2000-01-05, Close, 22.68
2000-01-05, BUY CREATE, 22.68
2000-01-06, BUY EXECUTED, Price: 22.27, Cost: 222.70, Comm 0.22
...
...
...
2000-12-28, Close, 27.63
2000-12-29, Close, 25.85
2000-12-29, SELL CREATE, 25.85
Final Portfolio Value: 100160.58

为了看到差异,打印输出也已扩展以显示执行大小。

将股份乘以10后,显然发生了以下情况:利润和损失乘以10。而不是16.58,剩余现金现在是165.80

添加指标

指标可以简单理解为:用于衡量市场趋势和价格变化的工具。指标可以是基于价格、成交量或其他市场数据的计算结果。

听说过指标后,下一步任何人都会添加一个指标到策略中。毫无疑问,它们一定比简单的*3个较低的收盘价*策略好得多。

受PyAlgoTrade中的一个示例启发,使用简单移动平均线的策略。

  • 如果收盘价大于平均值,则以市价买入
  • 如果在市场上,如果收盘价小于平均值,则卖出
  • 市场上只允许有1个活动操作

大部分现有的代码可以保持不变。让我们在__init__中添加平均值,并保留对它的引用:

  self.sma = bt.indicators.MovingAverageSimple(self.datas[0], period=self.params.maperiod)

当然,进入和退出市场的逻辑将依赖于平均值。在代码中查找逻辑。

注意:: 起始现金将为1000货币单位,以与PyAlgoTrade示例保持一致,并且不会应用佣金

from __future__ import (absolute_import, division, print_function,unicode_literals)import datetime  # For datetime objects
import os.path  # To manage paths
import sys  # To find out the script name (in argv[0])# Import the backtrader platform
import backtrader as bt# Create a Stratey
class TestStrategy(bt.Strategy):params = (('maperiod', 15),)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 __init__(self):# Keep a reference to the "close" line in the data[0] dataseriesself.dataclose = self.datas[0].close# To keep track of pending orders and buy price/commissionself.order = Noneself.buyprice = Noneself.buycomm = None# Add a MovingAverageSimple indicatorself.sma = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod)def notify_order(self, order):if order.status in [order.Submitted, order.Accepted]:# Buy/Sell order submitted/accepted to/by broker - Nothing to doreturn# Check if an order has been completed# Attention: broker could reject order if not enough cashif 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.priceself.buycomm = order.executed.commelse:  # Sellself.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('Order Canceled/Margin/Rejected')self.order = Nonedef notify_trade(self, trade):if not trade.isclosed:returnself.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %(trade.pnl, trade.pnlcomm))def next(self):# Simply log the closing price of the series from the referenceself.log('Close, %.2f' % self.dataclose[0])# Check if an order is pending ... if yes, we cannot send a 2nd oneif self.order:return# Check if we are in the marketif not self.position:# Not yet ... we MIGHT BUY if ...if self.dataclose[0] > self.sma[0]:# BUY, BUY, BUY!!! (with all possible default parameters)self.log('BUY CREATE, %.2f' % self.dataclose[0])# Keep track of the created order to avoid a 2nd orderself.order = self.buy()else:if self.dataclose[0] < self.sma[0]:# SELL, SELL, SELL!!! (with all possible default parameters)self.log('SELL CREATE, %.2f' % self.dataclose[0])# Keep track of the created order to avoid a 2nd orderself.order = self.sell()if __name__ == '__main__':# Create a cerebro entitycerebro = bt.Cerebro()# Add a strategycerebro.addstrategy(TestStrategy)# Datas are in a subfolder of the samples. Need to find where the script is# because it could have been called from anywheremodpath = os.path.dirname(os.path.abspath(sys.argv[0]))datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt')# Create a Data Feeddata = bt.feeds.YahooFinanceCSVData(dataname=datapath,# Do not pass values before this datefromdate=datetime.datetime(2000, 1, 1),# Do not pass values before this datetodate=datetime.datetime(2000, 12, 31),# Do not pass values after this datereverse=False)# Add the Data Feed to Cerebrocerebro.adddata(data)# Set our desired cash startcerebro.broker.setcash(1000.0)# Add a FixedSize sizer according to the stakecerebro.addsizer(bt.sizers.FixedSize, stake=10)# Set the commissioncerebro.broker.setcommission(commission=0.0)# Print out the starting conditionsprint('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())# Run over everythingcerebro.run()# Print out the final resultprint('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

现在,在跳转到下一节之前,请仔细查看日志中显示的第一个日期:

  • 它不再是2K年的第一个交易日2000-01-03

    它是2000-01-24 …

缺失的天数并不不是真的缺失。而是平台已适应新的情况:

  • 已将指标(SimpleMovingAverage)添加到策略中。

  • 此指标需要X个条形图才能产生输出:例如:15

  • 2000-01-24是第15个条形图出现的日期

backtrader平台假定策略已经有了指标,有一个很好的理由,在决策过程中使用它。如果指标尚未准备好并产生值,则尝试做出决策是没有意义的。

  • 当所有指标已经达到产生值所需的最小周期时,next将首先被调用

  • 在示例中只有一个指标,但是策略可以有任意数量的指标。

执行后的输出为:

Starting Portfolio Value: 1000.00
2000-01-24, Close, 24.10
2000-01-25, Close, 25.10
2000-01-25, BUY CREATE, 25.10
2000-01-26, BUY EXECUTED, Price: 25.24, Cost: 252.40, Comm 0.00
...
...
...
2000-12-21, OPERATION PROFIT, GROSS -19.40, NET -19.40
2000-12-21, Close, 26.24
2000-12-21, BUY CREATE, 26.24
2000-12-22, BUY EXECUTED, Price: 27.02, Cost: 270.20, Comm 0.00
2000-12-22, Close, 28.35
2000-12-26, Close, 27.52
2000-12-27, Close, 27.30
2000-12-28, Close, 27.63
2000-12-29, Close, 25.85
2000-12-29, SELL CREATE, 25.85
Final Portfolio Value: 975.60

一个获胜的系统变成了一个失败的系统…而且没有佣金。很可能仅仅添加一个指标,说明指标并不是万能药。

注意:在PyAlgoTrade中使用相同的逻辑和数据会产生略有不同的结果(略微偏离)。查看整个打印输出会发现一些操作不完全相同。罪魁祸首再次是通常的嫌疑人:四舍五入

PyAlgoTrade在将分裂的调整收盘价应用于数据源值时不会将其舍入到小数点后2位。

backtrader提供的Yahoo数据源将在应用调整后将值向下舍入到2位小数。在打印值时,一切似乎都是相同的,但很明显,有时第5位小数起作用。

向下舍入到2位小数似乎更为现实,因为Marke Exchange仅允许每个资产有一定数量的小数位数(通常为股票的2个小数位数)

注意:Yahoo数据源(从版本1.8.11.99开始)允许指定是否进行舍入以及舍入到多少位小数)

可视化检查:绘图

打印或记录系统在每个bar的实际执行情况是可以的,但人类是视觉动物,因此提供一个图表视图输出机制无疑是正确的。

注意:要绘制图形,需要安装matplotlib

再次,默认情况下绘图可帮助平台用户。绘图是一个仅有的1行操作:

cerebro.plot()

在调用cerebro.run()后,位置肯定是在那里。

为了显示自动绘图功能和一些简单的自定义,将执行以下操作:

  • 添加第二个移动平均线(指数)。默认情况下将其与数据一起绘制(就像第一个一样)。
  • 添加第三个移动平均线(加权)。自定义绘制在自己的图中(即使不明智)
  • 添加随机(慢)。默认情况下不更改。
  • 添加MACD。默认情况下不更改。
  • 添加RSI。默认情况下不更改。
  • 对RSI应用移动平均线(简单)。默认情况下不更改(将与RSI一起绘制)
  • 添加AverageTrueRange。更改默认值以避免绘制。

Strategy的init方法中添加的全部内容:

# Indicators for the plotting show
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

注意:即使indicators没有显式添加到策略的成员变量中(例如self.sma = MovingAverageSimple…),它们也将自动注册到策略中,并影响next的最小周期,并成为绘图的一部分。

在示例中,只有RSI被添加到临时变量rsi中,其唯一目的是在其上创建一个MovingAverageSmoothed。

现在的例子:

from __future__ import (absolute_import, division, print_function,unicode_literals)import datetime  # For datetime objects
import os.path  # To manage paths
import sys  # To find out the script name (in argv[0])# Import the backtrader platform
import backtrader as bt# Create a Stratey
class TestStrategy(bt.Strategy):params = (('maperiod', 15),)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 __init__(self):# Keep a reference to the "close" line in the data[0] dataseriesself.dataclose = self.datas[0].close# To keep track of pending orders and buy price/commissionself.order = Noneself.buyprice = Noneself.buycomm = None# Add a MovingAverageSimple indicatorself.sma = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod)# Indicators for the plotting showbt.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]:# Buy/Sell order submitted/accepted to/by broker - Nothing to doreturn# Check if an order has been completed# Attention: broker could reject order if not enough cashif 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.priceself.buycomm = order.executed.commelse:  # Sellself.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('Order Canceled/Margin/Rejected')# Write down: no pending orderself.order = Nonedef notify_trade(self, trade):if not trade.isclosed:returnself.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %(trade.pnl, trade.pnlcomm))def next(self):# Simply log the closing price of the series from the referenceself.log('Close, %.2f' % self.dataclose[0])# Check if an order is pending ... if yes, we cannot send a 2nd oneif self.order:return# Check if we are in the marketif not self.position:# Not yet ... we MIGHT BUY if ...if self.dataclose[0] > self.sma[0]:# BUY, BUY, BUY!!! (with all possible default parameters)self.log('BUY CREATE, %.2f' % self.dataclose[0])# Keep track of the created order to avoid a 2nd orderself.order = self.buy()else:if self.dataclose[0] < self.sma[0]:# SELL, SELL, SELL!!! (with all possible default parameters)self.log('SELL CREATE, %.2f' % self.dataclose[0])# Keep track of the created order to avoid a 2nd orderself.order = self.sell()if __name__ == '__main__':# Create a cerebro entitycerebro = bt.Cerebro()# Add a strategycerebro.addstrategy(TestStrategy)# Datas are in a subfolder of the samples. Need to find where the script is# because it could have been called from anywheremodpath = os.path.dirname(os.path.abspath(sys.argv[0]))datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt')# Create a Data Feeddata = bt.feeds.YahooFinanceCSVData(dataname=datapath,# Do not pass values before this datefromdate=datetime.datetime(2000, 1, 1),# Do not pass values before this datetodate=datetime.datetime(2000, 12, 31),# Do not pass values after this datereverse=False)# Add the Data Feed to Cerebrocerebro.adddata(data)# Set our desired cash startcerebro.broker.setcash(1000.0)# Add a FixedSize sizer according to the stakecerebro.addsizer(bt.sizers.FixedSize, stake=10)# Set the commissioncerebro.broker.setcommission(commission=0.0)# Print out the starting conditionsprint('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())# Run over everythingcerebro.run()# Print out the final resultprint('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())# Plot the resultcerebro.plot()

执行后的输出为:

Starting Portfolio Value: 1000.00
2000-02-18, Close, 26.05
2000-02-22, Close, 26.38
2000-02-22, BUY CREATE, 26.38
2000-02-23, BUY EXECUTED, Price: 26.77, Cost: 267.70, Comm 0.00
2000-02-23, Close, 28.05
...
...
...
2000-12-22, BUY EXECUTED, Price: 27.02, Cost: 270.20, Comm 0.00
2000-12-22, Close, 28.35
2000-12-26, Close, 27.52
2000-12-27, Close, 27.30
2000-12-28, Close, 27.63
2000-12-29, Close, 25.85
2000-12-29, SELL CREATE, 25.85
Final Portfolio Value: 982.30

最终结果已更改,即使逻辑没有。这是真的,但逻辑没有应用于相同数量的bar。

注意:如前所述,平台将在所有指标准备好生成值时首先调用next。在这个绘图示例中(在图表中非常清晰),MACD是最后一个完全准备好的指标(所有3条线都产生输出)。第一个BUY订单不再在2000年1月期间计划,而是接近2000年2月底。

图表:
请添加图片描述

让我们优化

许多交易书籍都说每个市场和每个交易的股票(或商品或…)都有不同的节奏。没有一种适合所有人的东西。

在绘图示例之前,当策略开始使用指标时,期间的默认值为15个bar。这是一个策略参数,可以在优化中使用该参数的值并查看哪个更适合市场。

注意:有很多关于优化和相关利弊的文献。但是建议总是指向同一个方向:不要过度优化。如果交易想法不合理,则优化可能会产生仅对回测数据集有效的正面结果。

示例已修改为优化简单移动平均线的期间。为了清晰起见,已删除与买入/卖出订单相关的任何输出

现在的例子:

from __future__ import (absolute_import, division, print_function,unicode_literals)import datetime  # For datetime objects
import os.path  # To manage paths
import sys  # To find out the script name (in argv[0])# Import the backtrader platform
import backtrader as bt# Create a Stratey
class TestStrategy(bt.Strategy):params = (('maperiod', 15),('printlog', False),)def log(self, txt, dt=None, doprint=False):''' Logging function fot this strategy'''if self.params.printlog or doprint:dt = dt or self.datas[0].datetime.date(0)print('%s, %s' % (dt.isoformat(), txt))def __init__(self):# Keep a reference to the "close" line in the data[0] dataseriesself.dataclose = self.datas[0].close# To keep track of pending orders and buy price/commissionself.order = Noneself.buyprice = Noneself.buycomm = None# Add a MovingAverageSimple indicatorself.sma = bt.indicators.SimpleMovingAverage(self.datas[0], period=self.params.maperiod)def notify_order(self, order):if order.status in [order.Submitted, order.Accepted]:# Buy/Sell order submitted/accepted to/by broker - Nothing to doreturn# Check if an order has been completed# Attention: broker could reject order if not enough cashif 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.priceself.buycomm = order.executed.commelse:  # Sellself.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('Order Canceled/Margin/Rejected')# Write down: no pending orderself.order = Nonedef notify_trade(self, trade):if not trade.isclosed:returnself.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %(trade.pnl, trade.pnlcomm))def next(self):# Simply log the closing price of the series from the referenceself.log('Close, %.2f' % self.dataclose[0])# Check if an order is pending ... if yes, we cannot send a 2nd oneif self.order:return# Check if we are in the marketif not self.position:# Not yet ... we MIGHT BUY if ...if self.dataclose[0] > self.sma[0]:# BUY, BUY, BUY!!! (with all possible default parameters)self.log('BUY CREATE, %.2f' % self.dataclose[0])# Keep track of the created order to avoid a 2nd orderself.order = self.buy()else:if self.dataclose[0] < self.sma[0]:# SELL, SELL, SELL!!! (with all possible default parameters)self.log('SELL CREATE, %.2f' % self.dataclose[0])# Keep track of the created order to avoid a 2nd orderself.order = self.sell()def stop(self):self.log('(MA Period %2d) Ending Value %.2f' %(self.params.maperiod, self.broker.getvalue()), doprint=True)if __name__ == '__main__':# Create a cerebro entitycerebro = bt.Cerebro()# Add a strategystrats = cerebro.optstrategy(TestStrategy,maperiod=range(10, 31))# Datas are in a subfolder of the samples. Need to find where the script is# because it could have been called from anywheremodpath = os.path.dirname(os.path.abspath(sys.argv[0]))datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt')# Create a Data Feeddata = bt.feeds.YahooFinanceCSVData(dataname=datapath,# Do not pass values before this datefromdate=datetime.datetime(2000, 1, 1),# Do not pass values before this datetodate=datetime.datetime(2000, 12, 31),# Do not pass values after this datereverse=False)# Add the Data Feed to Cerebrocerebro.adddata(data)# Set our desired cash startcerebro.broker.setcash(1000.0)# Add a FixedSize sizer according to the stakecerebro.addsizer(bt.sizers.FixedSize, stake=10)# Set the commissioncerebro.broker.setcommission(commission=0.0)# Run over everythingcerebro.run(maxcpus=1)

不是调用addstrategy将策略类添加到Cerebro中,而是调用optstrategy。而不是传递一个值,而是传递一系列值。

添加了Strategy钩子之一,stop方法,当数据用尽并且回测结束时将调用该方法。它用于在经纪人的投资组合中打印最终净值(之前在Cerebro中完成)

系统将为范围内的每个值执行策略。将输出以下内容:

2000-12-29, (MA Period 10) Ending Value 877.50
2000-12-29, (MA Period 11) Ending Value 878.70
2000-12-29, (MA Period 12) Ending Value 839.80
2000-12-29, (MA Period 13) Ending Value 899.90
2000-12-29, (MA Period 14) Ending Value 902.50
2000-12-29, (MA Period 15) Ending Value 975.60
2000-12-29, (MA Period 16) Ending Value 961.90
2000-12-29, (MA Period 17) Ending Value 952.60
2000-12-29, (MA Period 18) Ending Value 1011.00
2000-12-29, (MA Period 19) Ending Value 1039.40
2000-12-29, (MA Period 20) Ending Value 1073.20
2000-12-29, (MA Period 21) Ending Value 1055.10
2000-12-29, (MA Period 22) Ending Value 1057.60
2000-12-29, (MA Period 23) Ending Value 1021.50
2000-12-29, (MA Period 24) Ending Value 1018.80
2000-12-29, (MA Period 25) Ending Value 1012.40
2000-12-29, (MA Period 26) Ending Value 998.30
2000-12-29, (MA Period 27) Ending Value 983.10
2000-12-29, (MA Period 28) Ending Value 976.90
2000-12-29, (MA Period 29) Ending Value 984.20
2000-12-29, (MA Period 30) Ending Value 980.80

结果:

  • 对于小于18的期间,策略(无佣金)会亏钱。
  • 对于18到26之间的期间(包括两者),策略会赚钱。
  • 26以上再次失去了钱。

而该策略和给定数据集的获胜期为:

  • 20个bar,赢得1000 $的78.00个单位(7.8%)

注意::绘图示例中的额外指标已被删除,并且操作的开始仅受到正在优化的简单移动平均线的影响。因此,期间15的结果略有不同。

结论

示例逐步的展示了如何从一个简单的脚本到一个完整的交易系统,甚至可以绘制结果并进行优化。

可以做很多事情来提高获胜的机会:

  • 自定义指标

创建指标很容易(甚至绘制它们也很容易)

  • 仓位管理

对于许多人来说,资金管理是成功的关键

  • 订单类型(限价,止损,止损限价)

  • 其他一些

为了确保可以充分利用上述所有项目,文档提供了对它们(和其他主题)的深入了解。

查看目录并继续阅读…并开发。

祝你好运!


http://www.ppmy.cn/news/106719.html

相关文章

因果推断阶段系列19[阶段2-1]-机器学习预测模型与因果推断

因果推断系列19[阶段2-1]-机器学习预测模型与因果推断 1. 预测模型1.1 机器学习在行业中的应用1.2 机器学习入门2. 交叉验证3. 预测和策略3.1 单特征策略3.2 机器学习模型作为策略的输入3.3 细粒度策略小结1. 预测模型 因果推断的第一部分已经完成。该部分涵盖了因果推断的核心…

Spring Boot 如何自定义异常处理器

Spring Boot自定义异常处理器 在Spring Boot应用程序中&#xff0c;异常处理是一个非常重要的方面。如果您不处理异常&#xff0c;应用程序可能会崩溃或出现不可预料的行为。默认情况下&#xff0c;Spring Boot将未捕获的异常返回给客户端。这通常不是期望的行为&#xff0c;因…

Photoshop 2023 Beta 内置Ai绘图功能介绍安装教程

距离Adobe软件公司首次将图像编辑及数字绘画软件Photoshop推出到大众面前已经过去35年&#xff0c;最近该公司又再次书写了属于Photoshop的历史新篇章。 最近&#xff0c;Adobe 宣布 Photoshop&#xff08;Beta&#xff09;迎来更新&#xff0c;新增「创意填充&#xff08;Gen…

ecahrts饼图 指示线文字 自定义 (显示指示线和百分比)

1.指示线显示具体数值 formatter: "{c}" 代码&#xff1a; series: [{name: "",type: "pie",radius: "50%",center: [50%, 46%],data: this.chartData,emphasis: {itemStyle: {shadowBlur: 10,shadowOffsetX: 0,shadowColor: "…

正则表达式听着高大上?详解Python正则表达式(附速查字典)

目录 1 什么是正则表达式&#xff1f;2 常用元字符2.1 []2.2 ^2.3 $2.4 \2.5 {m,n}2.6 *2.7 2.8 ?2.9 .2.10 () 3 Python正则库re的使用3.1 字符串替换3.2 字符串分割 4 常用正则转义字符表5 常用正则表达式5.1 数字校验正则5.2 字符校验正则5.3 特殊需求正则 6 代码实战 1 什…

下载YouTube视频的一种方法

文章目录 工具名称下载方法使用方法1.只下载音频2.下载音频转换成mp3&#xff08;加上-x –audio-format参数&#xff09;3.下载视频&#xff08;带音频&#xff09;ID&#xff1a;22 | EXT&#xff1a;mp4 | 1280*720 下载的数据集&#xff1a;YouCook2 工具名称 yt-dlp 下载…

【计算机视觉 | 目标检测】arxiv 计算机视觉关于目标检测的学术速递(5月29日论文合集)

文章目录 一、检测相关(12篇)1.1 Linear Object Detection in Document Images using Multiple Object Tracking1.2 Hybrid Energy Based Model in the Feature Space for Out-of-Distribution Detection1.3 BEV-IO: Enhancing Birds-Eye-View 3D Detection with Instance Occu…

Arthas 入门到实战(五)动态改日志级别

1、查看当前包下日志级别 logger --include-no-appender --name com.fan.XXX name com.fan.XXX class ch.qos.logback.classic.Logger classLoader sun.misc.Launcher$…