本文档参考backtrader官方文档,是官方文档的完整中文翻译,可作为backtrader中文教程、backtrader中文参考手册、backtrader中文开发手册、backtrader入门资料使用。
快速入门章节目录
- 快速入门
- 使用平台
- 从0到100:一步一步的演示
- 基本设置
- 设置现金
- 添加一个数据源
- 我们的第一个策略
- 给策略添加执行逻辑
- 不仅要买...还要卖
- 券商说:打钱!
- 自定义策略:参数
- 添加指标
- 可视化检查:绘图
- 让我们优化
- 结论
快速入门
使用平台
让我们通过一系列的示例(从几乎空白到完全成熟的策略)来运行,但在此之前,让我们大致解释一下使用backtrader时的两个基本概念。
-
Lines线
数据源、指标和策略都有线。
一条线是一系列点的连续性,这些点连接在一起形成这条线。当谈论市场时,数据源通常每天有以下一组点:
- Open, High, Low, Close, Volume, OpenInterest (开盘价、最高价、最低价、收盘价、成交量、持仓量)
时间序列中的
开盘价
是一条线。因此,数据源通常有6条线。如果我们还考虑
DateTime
(这是单个点的实际参考),我们可以计算出7条线。 -
索引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 bt
if __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 bt
if __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.00
Final 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 bt
if __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)
# 将数据源添加到Cerebro
cerebro.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
价格。
DataSeries(Data 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].close
def 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)
# 将数据源添加到Cerebro
cerebro.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].close
def 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)
# 将数据源添加到Cerebro
cerebro.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属性的访问
- 方法buy和sell返回已创建(尚未执行)的订单
- 订单状态的更改将通过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 = None
def 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 = None
def 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)
# 将数据源添加到Cerebro
cerebro.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] dataseries
self.dataclose = self.datas[0].close
# To keep track of pending orders and buy price/commission
self.order = None
self.buyprice = None
self.buycomm = None
def notify_order(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 enough cash
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: # Sell
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('Order Canceled/Margin/Rejected')
self.order = None
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
(trade.pnl, trade.pnlcomm))
def next(self):
# Simply log the closing price of the series from the reference
self.log('Close, %.2f' % self.dataclose[0])
# Check if an order is pending ... if yes, we cannot send a 2nd one
if self.order:
return
# Check if we are in the market
if not self.position:
# Not yet ... we MIGHT BUY if ...
if self.dataclose[0] < self.dataclose[-1]:
# current close less than previous close
if 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 order
self.order = self.buy()
else:
# Already in the market ... we might sell
if 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 order
self.order = self.sell()
if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()
# Add a strategy
cerebro.addstrategy(TestStrategy)
# Datas are in a subfolder of the samples. Need to find where the script is
# because it could have been called from anywhere
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt')
# Create a Data Feed
data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
# Do not pass values before this date
fromdate=datetime.datetime(2000, 1, 1),
# Do not pass values before this date
todate=datetime.datetime(2000, 12, 31),
# Do not pass values after this date
reverse=False)
# Add the Data Feed to Cerebro
cerebro.adddata(data)
# Set our desired cash start
cerebro.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 conditions
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
# Run over everything
cerebro.run()
# Print out the final result
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, 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作为值来调用buy和sell。
退出逻辑被修改:
# 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] dataseries
self.dataclose = self.datas[0].close
# To keep track of pending orders and buy price/commission
self.order = None
self.buyprice = None
self.buycomm = None
def notify_order(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 enough cash
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: # Sell
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('Order Canceled/Margin/Rejected')
self.order = None
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
(trade.pnl, trade.pnlcomm))
def next(self):
# Simply log the closing price of the series from the reference
self.log('Close, %.2f' % self.dataclose[0])
# Check if an order is pending ... if yes, we cannot send a 2nd one
if self.order:
return
# Check if we are in the market
if not self.position:
# Not yet ... we MIGHT BUY if ...
if self.dataclose[0] < self.dataclose[-1]:
# current close less than previous close
if 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 order
self.order = self.buy()
else:
# Already in the market ... we might sell
if 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 order
self.order = self.sell()
if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()
# Add a strategy
cerebro.addstrategy(TestStrategy)
# Datas are in a subfolder of the samples. Need to find where the script is
# because it could have been called from anywhere
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt')
# Create a Data Feed
data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
# Do not pass values before this date
fromdate=datetime.datetime(2000, 1, 1),
# Do not pass values before this date
todate=datetime.datetime(2000, 12, 31),
# Do not pass values after this date
reverse=False)
# Add the Data Feed to Cerebro
cerebro.adddata(data)
# Set our desired cash start
cerebro.broker.setcash(100000.0)
# Add a FixedSize sizer according to the stake
cerebro.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 conditions
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
# Run over everything
cerebro.run()
# Print out the final result
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, 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] dataseries
self.dataclose = self.datas[0].close
# To keep track of pending orders and buy price/commission
self.order = None
self.buyprice = None
self.buycomm = None
# Add a MovingAverageSimple indicator
self.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 do
return
# Check if an order has been completed
# Attention: broker could reject order if not enough cash
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: # Sell
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('Order Canceled/Margin/Rejected')
self.order = None
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
(trade.pnl, trade.pnlcomm))
def next(self):
# Simply log the closing price of the series from the reference
self.log('Close, %.2f' % self.dataclose[0])
# Check if an order is pending ... if yes, we cannot send a 2nd one
if self.order:
return
# Check if we are in the market
if 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 order
self.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 order
self.order = self.sell()
if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()
# Add a strategy
cerebro.addstrategy(TestStrategy)
# Datas are in a subfolder of the samples. Need to find where the script is
# because it could have been called from anywhere
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt')
# Create a Data Feed
data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
# Do not pass values before this date
fromdate=datetime.datetime(2000, 1, 1),
# Do not pass values before this date
todate=datetime.datetime(2000, 12, 31),
# Do not pass values after this date
reverse=False)
# Add the Data Feed to Cerebro
cerebro.adddata(data)
# Set our desired cash start
cerebro.broker.setcash(1000.0)
# Add a FixedSize sizer according to the stake
cerebro.addsizer(bt.sizers.FixedSize, stake=10)
# Set the commission
cerebro.broker.setcommission(commission=0.0)
# Print out the starting conditions
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
# Run over everything
cerebro.run()
# Print out the final result
print('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] dataseries
self.dataclose = self.datas[0].close
# To keep track of pending orders and buy price/commission
self.order = None
self.buyprice = None
self.buycomm = None
# Add a MovingAverageSimple indicator
self.sma = bt.indicators.SimpleMovingAverage(
self.datas[0], period=self.params.maperiod)
# 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)
def notify_order(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 enough cash
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: # Sell
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('Order Canceled/Margin/Rejected')
# Write down: no pending order
self.order = None
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
(trade.pnl, trade.pnlcomm))
def next(self):
# Simply log the closing price of the series from the reference
self.log('Close, %.2f' % self.dataclose[0])
# Check if an order is pending ... if yes, we cannot send a 2nd one
if self.order:
return
# Check if we are in the market
if 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 order
self.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 order
self.order = self.sell()
if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()
# Add a strategy
cerebro.addstrategy(TestStrategy)
# Datas are in a subfolder of the samples. Need to find where the script is
# because it could have been called from anywhere
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt')
# Create a Data Feed
data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
# Do not pass values before this date
fromdate=datetime.datetime(2000, 1, 1),
# Do not pass values before this date
todate=datetime.datetime(2000, 12, 31),
# Do not pass values after this date
reverse=False)
# Add the Data Feed to Cerebro
cerebro.adddata(data)
# Set our desired cash start
cerebro.broker.setcash(1000.0)
# Add a FixedSize sizer according to the stake
cerebro.addsizer(bt.sizers.FixedSize, stake=10)
# Set the commission
cerebro.broker.setcommission(commission=0.0)
# Print out the starting conditions
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
# Run over everything
cerebro.run()
# Print out the final result
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
# Plot the result
cerebro.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] dataseries
self.dataclose = self.datas[0].close
# To keep track of pending orders and buy price/commission
self.order = None
self.buyprice = None
self.buycomm = None
# Add a MovingAverageSimple indicator
self.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 do
return
# Check if an order has been completed
# Attention: broker could reject order if not enough cash
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: # Sell
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('Order Canceled/Margin/Rejected')
# Write down: no pending order
self.order = None
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
(trade.pnl, trade.pnlcomm))
def next(self):
# Simply log the closing price of the series from the reference
self.log('Close, %.2f' % self.dataclose[0])
# Check if an order is pending ... if yes, we cannot send a 2nd one
if self.order:
return
# Check if we are in the market
if 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 order
self.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 order
self.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 entity
cerebro = bt.Cerebro()
# Add a strategy
strats = 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 anywhere
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../datas/orcl-1995-2014.txt')
# Create a Data Feed
data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
# Do not pass values before this date
fromdate=datetime.datetime(2000, 1, 1),
# Do not pass values before this date
todate=datetime.datetime(2000, 12, 31),
# Do not pass values after this date
reverse=False)
# Add the Data Feed to Cerebro
cerebro.adddata(data)
# Set our desired cash start
cerebro.broker.setcash(1000.0)
# Add a FixedSize sizer according to the stake
cerebro.addsizer(bt.sizers.FixedSize, stake=10)
# Set the commission
cerebro.broker.setcommission(commission=0.0)
# Run over everything
cerebro.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的结果略有不同。
结论
示例逐步的展示了如何从一个简单的脚本到一个完整的交易系统,甚至可以绘制结果并进行优化。
可以做很多事情来提高获胜的机会:
- 自定义指标
创建指标很容易(甚至绘制它们也很容易)
- 仓位管理
对于许多人来说,资金管理是成功的关键
-
订单类型(限价,止损,止损限价)
-
其他一些
为了确保可以充分利用上述所有项目,文档提供了对它们(和其他主题)的深入了解。
查看目录并继续阅读…并开发。
祝你好运!