原文:
www.backtrader.com/
主页
欢迎来到 backtrader!
原文:
www.backtrader.com/
一个功能丰富的 Python 框架,用于回测和交易
backtrader允许您专注于编写可重复使用的交易策略、指标和分析器,而不必花时间构建基础设施。
开源 - GitHub
使用、修改、审计和分享它。秘诀在于调味料,而你就是厨师。这只是工具。
文档 & 博客
查看快速入门、详尽的文档、博客中的深入主题和想法。
社区(已禁用)
它被滥用了。可能会在未来回来。与此同时,可以尝试例如:stackoverflow “backtrader”。
你好,算法交易!
查看一个简单移动平均线交叉策略的快速示例(仅限多头)
特性
查看backtrader提供的所有好东西的快速概述。
特性
原文:
www.backtrader.com/home/features/
实时交易
与 Interactive Brokers、Oanda v1、VisualChart 以及外部第三方经纪人(alpaca、Oanda v2、ccxt,…)一起使用
基于0
的索引
-
在数组中使用
0
表示当前时刻,以解决访问数组值时的前瞻性偏差 -
使用
-1
,-2
(即:负值)表示最后时刻,以与 Python 的定义保持同步 -
任何正索引表示未来(在
event-only
模式下测试代码,会出错)
事件和矢量化
-
交易逻辑和经纪人始终基于事件运行
-
如果可能,指标的计算是矢量化的(源数据可以预加载)
一切都可以在仅事件模式下运行,无需预加载数据,就像实时运行一样
数据源(包括实时数据)
-
内置支持多种数据源:CSV、数据库数据源、YahooFinance、交互经纪人、Oanda v1,…
-
可以同时运行任意数量的数据源(受内存限制,显然)
警告
注意幸存者偏差!
-
可以混合和运行多个时间框架
-
集成重采样和重播功能
内置电池的经纪人
-
订单类型:
Market
、Limit
、Stop
、StopLimit
、StopTrail
、StopTrailLimit
、OCO
、Bracket
、MarketOnClose
-
多空卖出
-
未来类工具的持续现金调整
-
用户定义的佣金方案和信用利息
-
基金模式
-
成交量填充策略
-
自定义滑点
策略 - 交易逻辑
-
在操作之前自动计算热身期
-
多个策略(针对同一经纪人)可以并行运行
-
多种订单生成方法(
buy/sell
、order_target_xxx
、自动信号) -
事件通知:传入数据、数据源提供者、订单、交易、定时器
指标
超过 122 种指标,常见的指标都在其中
-
许多移动平均线(
SMA
、EMA
,…)、经典指标(MACD
、Stochastic
、RSI
,…)和其他指标 -
ta-lib
集成
性能分析器
几个内置的性能分析器(TimeReturns
、TradeAnalyzer
、SharpeRatio
、VWR
、SQN
,…)
绘图(额外)
使用单个命令进行自动化(可定制)绘图
注意
为了使其工作,必须安装matplotlib
大小调整器
定义并插入智能自动化的押注策略
观察者
代理可以被绘制,并且可以观察系统中的一切(通常用于绘制统计数据)
杂项
-
定时器用于随时间重复的操作
-
交易日历
-
时区支持
纯 Python
使用最强大且易于使用的编程语言之一。无需外部库。
-
使用面向对象的方法轻松地将拼图的各个部分拼合在一起
-
操作符在可能的情况下进行重载,以提供自然语言构造,例如:
av_diff = bt.ind.SMA(period=30) - bt.ind.SMA(period=15)`
其中
av_diff
将包含30
和15
周期的简单移动平均线的差值 -
对于语言结构,不能被覆盖,比如
and
,or
,if
,提供了等效的函数以确保没有功能丢失,例如av_and = bt.And(av_diff > 0, self.data.close < bt.ind.SMA(period=30))`
你好,算法交易!
原文:
www.backtrader.com/home/helloalgotrading/
一个经典的简单移动平均线交叉策略,可以轻松地以不同方式实现。下面呈现的三个片段的结果和图表是相同的。
from datetime import datetime
import backtrader as bt# Create a subclass of Strategy to define the indicators and logicclass SmaCross(bt.Strategy):# list of parameters which are configurable for the strategyparams = dict(pfast=10, # period for the fast moving averagepslow=30 # period for the slow moving average)def __init__(self):sma1 = bt.ind.SMA(period=self.p.pfast) # fast moving averagesma2 = bt.ind.SMA(period=self.p.pslow) # slow moving averageself.crossover = bt.ind.CrossOver(sma1, sma2) # crossover signaldef next(self):if not self.position: # not in the marketif self.crossover > 0: # if fast crosses slow to the upsideself.buy() # enter longelif self.crossover < 0: # in the market & cross to the downsideself.close() # close long positioncerebro = bt.Cerebro() # create a "Cerebro" engine instance# Create a data feed
data = bt.feeds.YahooFinanceData(dataname='MSFT',fromdate=datetime(2011, 1, 1),todate=datetime(2012, 12, 31))cerebro.adddata(data) # Add the data feedcerebro.addstrategy(SmaCross) # Add the trading strategy
cerebro.run() # run it all
cerebro.plot() # and plot it with a single command
from datetime import datetime
import backtrader as bt# Create a subclass of Strategy to define the indicators and logicclass SmaCross(bt.Strategy):# list of parameters which are configurable for the strategyparams = dict(pfast=10, # period for the fast moving averagepslow=30 # period for the slow moving average)def __init__(self):sma1 = bt.ind.SMA(period=self.p.pfast) # fast moving averagesma2 = bt.ind.SMA(period=self.p.pslow) # slow moving averageself.crossover = bt.ind.CrossOver(sma1, sma2) # crossover signaldef next(self):if not self.position: # not in the marketif self.crossover > 0: # if fast crosses slow to the upsideself.order_target_size(target=1) # enter longelif self.crossover < 0: # in the market & cross to the downsideself.order_target_size(target=0) # close long positioncerebro = bt.Cerebro() # create a "Cerebro" engine instance# Create a data feed
data = bt.feeds.YahooFinanceData(dataname='MSFT',fromdate=datetime(2011, 1, 1),todate=datetime(2012, 12, 31))cerebro.adddata(data) # Add the data feedcerebro.addstrategy(SmaCross) # Add the trading strategy
cerebro.run() # run it all
cerebro.plot() # and plot it with a single command
from datetime import datetime
import backtrader as bt# Create a subclass of SignaStrategy to define the indicators and signalsclass SmaCross(bt.SignalStrategy):# list of parameters which are configurable for the strategyparams = dict(pfast=10, # period for the fast moving averagepslow=30 # period for the slow moving average)def __init__(self):sma1 = bt.ind.SMA(period=self.p.pfast) # fast moving averagesma2 = bt.ind.SMA(period=self.p.pslow) # slow moving averagecrossover = bt.ind.CrossOver(sma1, sma2) # crossover signalself.signal_add(bt.SIGNAL_LONG, crossover) # use it as LONG signalcerebro = bt.Cerebro() # create a "Cerebro" engine instance# Create a data feed
data = bt.feeds.YahooFinanceData(dataname='MSFT',fromdate=datetime(2011, 1, 1),todate=datetime(2012, 12, 31))cerebro.adddata(data) # Add the data feedcerebro.addstrategy(SmaCross) # Add the trading strategy
cerebro.run() # run it all
cerebro.plot() # and plot it with a single command
参考资料
谁在使用它
原文:
www.backtrader.com/home/references/who-is-using-it/
请参阅后面的章节了解使用情况的参考资料。
旧网站参考资料
原始网站说明backtrader至少被以下机构使用:
-
2 家 Eurostoxx50 银行
-
6 家量化交易公司
那是很久以前的事了,作者已经知道更多了,包括其他类型的交易公司,如“能源交易”等行业。
为什么没有提供名称?
-
我从未问过
-
他们从未问过
那你能支撑这些说法吗?
在Reddit - [Question] How popular is Backtrader on this sub?中已经问过(并得到了回答)。
让我们引用那个帖子的回答。
引用
不,没有清单。这实际上已经过时了:银行的数量仍然是2
(可能还有更多,但我不知道),但有超过 6 家公司在内部使用它,包括在能源市场工作的公司,因为compensation
功能允许购买和销售不同的资产来相互补偿(这可能在zipline
中不可用),从而允许使用现货和期货价格进行建模(这是能源市场的一个特点,以避免将实际商品交付给您)
这里的关键是必须定义使用情况。例如,我可以引用一位来自其中一家银行的人告诉我的话:“我们使用 backtrader 快速原型设计我们的想法并对其进行回测。如果它们被证明符合我们的预期,并经过进一步完善,它们将被重写为 Java 并放入我们的生产系统中”。
实际上,这是一个量化公司(我亲自访问过的)使用的相同方案:在backtrader中进行原型设计,然后在Java中进行生产。
正如您可以想象的那样,我不追踪那些使用backtrader的人的生活,所以也许一些银行和公司决定不再使用backtrader。
我也猜测一些银行和量化公司使用zipline
遵循相同的方案。
领英 - 文章/帖子
原文:
www.backtrader.com/home/references/linkedin/
Sourabh Sisodiya - Q’s on how do we backtest
领英 - 档案
一份将backtrader列入其个人资料的人员名单。
Ahmed Kamel Taha
Alain Glücksmann
Alex Mouturat
Alexandre GAZAGNES
Álvaro Martínez Pérez
Andrey Muzykin
Bingchen Liu
Carlos Chan
Chen Bo Calvin Zhang
David McCarty
Drew Wei
Frederick (Shu) Bei
Gabriel Ghellere
Harold Liu
Isabel María Villalba Jiménez
Ilya Rozhechenko
Jason L.
Joshua Wyatt Smith, Ph.D.
Justin Wagner
Karl Vernet
Ken Huang
King Hang Terence Siu
Marcelino Franco
Max Paton
Mike S.
Neil Murphy
- 同样也在:Neil Murphy - Remote Python
Piotr Yordanov
Raul Martin
Ruochen (Larry) Pan
Robert Ungvari
Serhii Ovsiienko
Shaozhen Huang
Simon Garland
Sina Pournia
Siyi Li
Tianshu Zhang
蒂姆·莱利
维亚切斯拉夫·佐托夫
温忠
邓兴建
李杨琦
伊宁·成
教育 - 论文
原文:
www.backtrader.com/home/references/education/
aCubeIT - 人工智能学院
出售一门课程,其中包括使用 backtrader 制作预测算法。
- 基于情感分析的股票预测
Arxiv.org - Kartikay Gupta(通讯作者),Niladri Chatterjee
- 在考虑先导滞后关系的情况下选择股票对进行对冲交易
布罗克大学
- 使用深度学习预测股票趋势
哥伦比亚大学
-
金融数学硕士项目
哥伦比亚大学“金融数学硕士项目”中的一些学生在简历中列出了 backtrader
伊尔汉姆学院
- 检验不同神经网络在预测股票价格方面的效率
苏黎世联邦理工学院 - Alain Glücksmann - 硕士论文
- 比特币交易策略的回测
信息技术学院计算机图形与多媒体系
布尔诺 - 捷克共和国
- 使用人工神经网络进行算法交易
硕士论文
语言:捷克语
香港科技大学
- 用于初学者投资者的不同股票预测方法的比较分析系统
泰国国家发展管理学院
-
基于深度 Q 网络(DQN)的稳健金融交易系统
博士论文
NTU - 计算机科学与信息工程
国立台湾大学
-
2018 年金融 Python 编程
-
2019 年金融 Python 编程
大数据和深度学习的最新进展…
- 亚马逊 - 大数据和深度学习的最新进展:第 INNS 大数据和深度学习会议 INNSBDDL2019 的论文集,于 Sestri Levante,Genova 举办,… 神经网络学会图书 1
在第 182 页被提及为用于回测的“具体工具”
科学直达
-
技术分析和情绪嵌入用于市场趋势预测
PDF:
sentic.net/market-trend-prediction.pdf
VTAD(德国技术分析协会)
翻译:德国技术分析协会(注册)
- Spreer - 具有特异风险的交易
语言:德语
伍斯特理工学院 - 数字 WPI
- Maier/Richardson/Gonsalves 编写的交易系统开发
世界金融会议 - 2019 年第 25 届:投资
- 基于不同算法的加密货币组合选择和交易策略
博客 - 文章
原文:博客列表
展示 backtrader 的博客列表。
Aadhunik
- 这是我如何在 Backtrader 中实现超级趋势指标的方法(Python)
这是我如何在 Backtrader 中实现超级趋势指标的方法
精算数据科学
- 使用 Backtrader 创建性能报告
Alpha Over Beta
- 用于对冲股票的交易波动率
Analyzing Alpha
-
Backtrader:开始回测交易策略
-
行业动量:解释与回测
-
止损:解释与最佳策略
-
2019 年最佳 21 个 Python 交易工具
Andreas Clenow - 《追随趋势》
- Python 回测
Angel List - Justin Wagner
- 加密货币机器人与编码策略以及回测
Backtest Rookies(多篇文章)
- Backtrader:数据回放
CSDN - 千塘夏家子
-
Backtrader 量化平台教程(8)时间框架
语言:中文
DevTo - dennislwm
- 如何用 4 个 GIF 步骤 Dockerize Backtrader
如何用 4 个 GIF 步骤 Dockerize Backtrader
EtherSchtroumpf
- TOP30 策略 1.0 版
Finanzas.com
-
加密货币交易算法:设计一个系统
语言:西班牙语
Intrinio 博客
- 用 Intrinio & Backtrader 开始量化
LinkedIn - Majid AliAkbar
- 金融和金融数据科学家的最佳 Python 库/软件包
领英 - Ali Kokaz
- 探索横截面均值回归交易策略
Medium - Chul Lim
- 算法交易/程序交易
语言:韩语
Medium - Dim Norin
- Klinger Volume Oscillator 测试
Medium - Etienne Brunet
- 我对人工智能量化基金和 DIY 基金的看法
Medium - Sumit Ojha
- 加密货币中的持有与智能持有
Medium - Towards Data Science
-
交易策略:使用 Backtrader 进行回测
-
Backtesting 你的第一个交易策略
-
使用新闻文章情感分析的算法交易
-
量化交易员关于生成交易思路的建议
Medium - ttamg
- TradingBot 系列 —— 交易机器人的架构
Medium - Ugur Akyol
- Backtrader 机器人的前行工作表
Medium - Wen Chang
-
手写一个 AI 交易机器人
语言:中文
Medium - You Xie
- IGNIS 空投的事件研究
我的金融市场
- 用 Python 回测交易策略
NTGuardian
- 开始使用 Backtrader
利润加
- 使用 Backtrader 创建绩效报告
Pythonic Finance
-
比较用于自动交易的 Python 平台
-
优化交易策略的参数
-
为您的交易策略添加指标
-
定制交易策略
-
使用 backtrader 介绍自动化交易
QuantInsti
- 安装 Python 软件包 ;
Rankia.com
-
使用技术分析交易加密货币
语言: 西班牙语
-
使用技术分析交易加密货币
语言: 葡萄牙语
Renbuar 博客
- 均值回归策略
ScienceWal
-
使用 backtrader 进行交易策略回测
语言: 印尼语
SeanGTKelly 博客
- 基础算法交易第 2 部分 - 回测
SmudlaTrader
- Faber 每日和每周策略
Teddy Koker
-
Python 中的"移动股票"动量策略
-
改进 Python 中的横截面均值回归策略
-
在 Python 中回测横截面均值回归策略
-
使用 Backtrader 在 Python 中回测杠杆 ETF 组合
The Startup - Roman Paolucci
- 创建盈利的股票交易策略
The Lab - swapniljariwala
- 使用 NSEpy 和 backtrader 的简单示例
视频
原文:
www.backtrader.com/home/references/videos/
使用 Python 和 GUI 项目概述 backtrader
使用 Backtrader 框架在 Python 中进行策略回测
Python Backtrader 入门指南
使用 Python3 和 GUI 项目概述 backtrader
教程:Python 中的深度强化学习算法交易
教程:如何在 Python 中对比特币交易策略进行回测
使用 Backtrader 框架进行策略回测
Python 中最佳的算法交易回测框架
Python 和 BAcktrader 的算法交易
-
第 1 部分
-
第 2 部分
-
第 3 部分
金叉算法交易策略与 Python 和 Backtrader
-
第 1 部分
-
第 2 部分
-
第 3 部分
-
第 4 部分
-
第 5 部分
评论 / 提及
原文:
www.backtrader.com/home/references/reviews/
4-chan
boards.4channel.org/biz/thread/7372143/ai-anon-again-here-a-prediction-from-96-hours-ago
Bitcointalk
- 关于仅使用 SMA 交叉进行交易,使用 1 天图表:无懈可击吗?
Bogleheads.org
- “钢化” 拥有个股的情况
EliteTrader
- 我从哪里开始?
GAIN Capital
- www.backtrader.com 的整合
Hacker News
-
模糊 MA 系统与 GA 优化交易棕榈油期货
-
有人将金融工程工具设计为爱好吗?
Ian Mobbs
- Backtrader
Medium - Hackernoon
- 9 个量化交易的好工具
Medium - Sten Alferd
- Python 在金融应用开发中是否合适?
NetGuru
- Python 在金融和金融科技中的应用
PyConUK 2017
-
randomactsofcartography.wordpress.com/2017/11/01/what-i-learned-at-pyconuk-2017/
-
www.slideshare.net/BrianTsang11/pycon-2017-preevent
Qiita
- Python 算法交易库
QuantLabs
- 量化交易的终极 Python 软件包列表
Quantopian 论坛
-
比较各种 Python 实时交易平台
-
忘掉其他工具,使用backtrader!
QuantStart
- 选择用于回测和自动化执行的平台
Quora
-
量化交易是如何工作的?个人如何设置?以及背后的技术和逻辑是什么?
-
用 Python 学习和测试算法交易模型的最佳方式是什么?
-
作为个人进入算法交易(而不是对冲基金的雇员)的好处和坏处是什么?
-
我需要学习怎样的数学才能进行算法交易?
-
金融领域中 R/Python 的常见实际用途是什么?
-
随着算法交易的兴起,手动交易会发生什么变化?
-
www.quora.com/Which-is-the-best-service-provider-for-algo-trading
-
用 Python 回测交易策略的最佳库是什么?
-
数据科学/分析对量化/算法交易有用吗?
-
哪种算法交易软件最好?
Statsmage
- Python 中开源回测框架的当前状态
开发者大会
(葡萄牙语)
-
Creating robots for buying and selling cryptocurrencies
-
幻灯片
TODOTrader
- 博客
TopQuant
-
“中国的 Python 量化启示者和开拓者”
-
类别:backtrader
Traders-Mag
- 加密货币算法交易 - 使用技术分析进行加密货币交易
X-trader 论坛
- 关于 Quantopian
工作机会
原文:
www.backtrader.com/home/references/jobs/
Alpaca
- AngelCo - Alpaca 的开源开发人员
CodeMentor
- 需要具有 Backtrader 经验的 Python 专家来构建自动交易机器人(远程)
自由职业者
-
Python(backtrader/zipline)和 Interactive Brokers
-
编译 Python 程序(BackTrader 和 Interactive Brokers API)并构建用户界面的网站
-
Python 回测平台
-
James A.的项目–2
-
金融市场交易框架
-
Saira I.的项目
-
Tensorflow
-
算法交易 Python 平台
-
私人项目或比赛#15215084
-
需要具有 backtrader(算法交易)知识的自由职业者
FL.ru
- Python - 创建用于测试交易策略的平台
Guru.com
- Backtrader 分析器
语言:俄语
浩来网络技术(上海)有限公司
- 量化工程师
语言:中文
MindPool
- 数据分析师
MQL5
-
将 MT4 指标转换为 Python(Backtrader)
-
MQL5 测试到 Python
-
将 MQL 性能指标翻译成 Python -
PeoplerPerHour
- Backtrader 实时数据集成 - python
QuantInsti
-
LinkedIn 示例 ){target=_blank}
-
AngelCo - 量化学习的 Python 开发者
-
智慧招聘 - Python 开发者
-
时代招聘 - Python 开发者
Shixian
-
量化回测系统
注意:中文。
Upwork
-
自由职业者列表 “backtrader”
-
加密货币交易机器人 UI
-
加密货币交易机器人 UI
-
ZuluQuant
(同时也在这里:社区 - ZuluQuant - 合同职位)
-
BackTrader 助手。简单快速赚钱的工作
-
在 backtrader 和 python 中开发交易算法
智慧招聘
- Python 架构师 - 数据结构/算法
Companies
原文:
www.backtrader.com/home/references/companies/
列出了列出(或已列出)backtrader 或公开使用参考存在的公司清单(专业资料、博客文章、文章等等)
注意
这并不是任何形式的认可、工作证明、官方认可(和任何其他免责声明)
注意
在“公司”下面,您将找到选择将其提供的类 Unix 发行版
Companies
Agora EAFI
agoraeaf.com/
来自 LinkedIn 档案和 Rankia/Finanzas.com 文章
Alpaca
pypi.org/project/alpaca-backtrader-api/
Alpha Over Beta
- Trading Volatility to Hedge Equity https://www.alphaoverbeta.net/trading-volatility-to-hedge-equity/ -
总计
ch.linkedin.com/in/karl-vernet-599a464/de
卡尔·维尔内特(Karl Vernet)的工作的一部分。
数据交易
datatrading.info/
提供算法交易服务的小商店。
Intrinio
intrinio.com/
NORGATE DATA
pypi.org/project/norgatedata/
Quantify Capital
www.quantifycapital.in/
QuantInsti
www.quantinsti.com/
来自博客文章、招聘启事…
StormDealers Ltd.
www.stormdealers.com/
列在网站上
TradeFab
tradefab.org/
引言:“Backtrader Development Development of Python Backtrader scripts, including backtesting/optimization support.”
ZuluQuant
zuluquant.com/(似乎已经消失)
将 backtrader 列为使用的技术之一,并在社区发布了一份工作启事
发行
FreeBSD Ports
- FreeBSD
NetBSD
- NetBSD
文档
介绍
原文:
www.backtrader.com/docu/
欢迎来到backtrader文档!
该平台有两个主要目标:
-
易用性
-
回到 1
注意
基于*《功夫小子》*的规则(Mr. Miyagi制定的)而松散地制定。
运行该平台的基础知识:
-
创建一个策略
-
决定潜在的可调参数
-
实例化策略中需要的指标
-
写下进入/退出市场的逻辑
-
提示
或者:
- 准备一些指标作为多头/空头信号
然后
-
创建一个Cerebro引擎
- 首先:注入策略(或基于信号的策略)
然后:
-
加载并注入数据源(一旦创建,请使用
cerebro.adddata
) -
然后执行
cerebro.run()
-
若要视觉反馈,请使用:
cerebro.plot()
该平台高度可配置
希望您作为用户能够发现该平台既实用又有趣。
安装
原文:
www.backtrader.com/docu/installation/
要求和版本
backtrader
是自包含的,没有外部依赖项(除非你想要绘图)
基本要求是:
-
Python 2.7
-
Python 3.2 / 3.3 / 3.4 / 3.5
-
pypy/pypy3
若需要绘图功能,还需要额外的要求:
-
Matplotlib >= 1.4.1
其他版本可能也可以,但这是用于开发的版本
注意:在撰写本文时,Matplotlib 不支持 pypy/pypy3
Python 2.x/3.x 兼容
开发工作在 Python 2.7 下进行,有时也在 3.4 下进行。本地同时运行两个版本的测试。
在 Travis 下,使用连续集成检查与 3.2 / 3.3 / 3.5 以及 pypy/pyp3 的兼容性
从 pypi 安装
例如使用 pip:
pip install backtrader
使用相同语法也可以应用 easy_install
从 pypi 安装(包括 matplotlib)
若需要绘图功能,请使用此选项:
pip install backtrader[plotting]
这会引入 matplotlib,它将进一步引入其他依赖项。
你可能更喜欢(或只能使用…)easy_install
从源码安装
首先从 github 网站下载一个发布版或最新的压缩包:
github.com/mementum/backtrader
解压后运行以下命令:
python setup.py install
从源码在你的项目中运行
从 github 网站下载一个发布版或最新的压缩包:
github.com/mementum/backtrader
然后将 backtrader 包目录复制到你自己的项目中。例如,在类 Unix 操作系统下:
tar xzf backtrader.tgz
cd backtrader
cp -r backtrader project_directory
请记住,你随后需要手动安装 matplotlib
以进行绘图。
快速开始
原文:
www.backtrader.com/docu/quickstart/quickstart/
注意
快速入门指南中使用的数据文件会不时更新,这意味着调整后的收盘价
会变化,以及收盘价
(以及其他组件)。这意味着实际输出可能与撰写文档时的情况不同。
使用平台
让我们通过一系列的例子运行一遍(从几乎空白到完全成熟的策略),但在粗略解释backtrader的两个基本概念之前。
-
线
数据源、指标和策略都有线。
一条线是一系列的点,当它们连接在一起时形成这条线。当谈到市场时,一个数据源通常每天有以下一组点:
- 开盘价、最高价、最低价、收盘价、成交量、持仓量
一系列“开盘价”随时间的变化是一条线。因此,一个数据源通常有 6 条线。
如果我们还考虑“DateTime”(这是单个点的实际参考),我们可以计算出 7 条线。
-
第 0 个指数方法
当访问线中的值时,当前值通过索引访问:0
通过*-1访问“最后”输出值。这符合 Python 对可迭代对象的惯例(一条线可以被迭代,因此是可迭代的),其中索引-1*用于访问可迭代/数组的“最后”项。
在我们的情况下,访问的是最后的输出值。
因此,作为* -1 之后的索引 0 *,它用于访问当前行。
考虑到这一点,如果我们想象一个在初始化过程中创建的简单移动平均策略:
self.sma = SimpleMovingAverage(.....)
访问当前移动平均线的最简单和最简单的方法:
av = self.sma[0]
无需知道已处理了多少个条/分钟/天/月,因为“0”唯一标识当前时刻。
按照 Python 的传统,通过*-1*来访问“最后”输出值:
previous_value = self.sma[-1]
当然,早期的输出值可以用-2、-3 等来访问
从 0 到 100:样本
基本设置
让我们开始吧。
from __future__ import (absolute_import, division, print_function,unicode_literals)import backtrader as btif __name__ == '__main__':cerebro = bt.Cerebro()print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())cerebro.run()print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
执行后输出为:
Starting Portfolio Value: 10000.00
Final Portfolio Value: 10000.00
在这个例子中:
-
backtrader 被导入
-
Cerebro 引擎被实例化
-
创建的cerebro实例被告知运行(循环遍历数据)
-
结果输出并打印出来
虽然看起来不起眼,但让我们明确指出一些事情:
-
Cerebro 引擎在后台创建了一个broker实例
-
实例已经有一些现金可以开始了
在幕后经纪人实例化是该平台的一个固定特征,以简化用户的生活。如果用户未设置经纪人,则会放置一个默认的经纪人。
10000 个货币单位是一些经纪人开始使用的常见值。
设置现金
在金融世界中,确实只有“失败者”才从 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.00
Final Portfolio Value: 1000000.00
任务完成。让我们转向风云变幻的水域。
添加一个 Data Feed
拥有现金很有趣,但所有这一切背后的目的是让一个自动化策略通过对我们视为 Data Feed 的资产进行操作而无需动手指就能增加现金。
因此… 没有 Data Feed -> 没趣。让我们给这个不断增长的示例添加一个。
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 btif __name__ == '__main__':# Create a cerebro entitycerebro = bt.Cerebro()# 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 after this datetodate=datetime.datetime(2000, 12, 31),reverse=False)# Add the Data Feed to Cerebrocerebro.adddata(data)# Set our desired cash startcerebro.broker.setcash(100000.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())
执行后的输出为:
Starting Portfolio Value: 1000000.00
Final Portfolio Value: 1000000.00
模板代码的数量略有增加,因为我们添加了:
-
找出我们示例脚本所在的位置,以便能够定位示例 Data Feed 文件
-
有 datetime 对象用于过滤我们将要操作的 Data Feed 中的数据。
除此之外,Data Feed 被创建并添加到 cerebro 中。
输出没有变化,如果有变化将是个奇迹。
注意
Yahoo Online 以日期降序发送 CSV 数据,这不是标准约定。reversed=True 参数考虑到 CSV 文件中的数据已经被 反转,并具有标准预期的日期升序。
我们的第一个策略
现金在 broker 中,而 Data Feed 在那里。看起来,危险的生意就在拐角处。
让我们将一个策略引入到等式中,并打印每天(每个 bar)的“Close”价格。
DataSeries(Data Feeds 中的基础类)对象具有访问已知 OHLC(开盘价 最高价 最低价 收盘价)日常值的别名。这应该能够简化我们的打印逻辑的创建。
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 for 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].closedef next(self):# Simply log the closing price of the series from the referenceself.log('Close, %.2f' % self.dataclose[0])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)# 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-03T00:00:00, Close, 27.85
2000-01-04T00:00:00, Close, 25.39
2000-01-05T00:00:00, Close, 24.05
...
...
...
2000-12-26T00:00:00, Close, 29.17
2000-12-27T00:00:00, Close, 28.94
2000-12-28T00:00:00, Close, 29.29
2000-12-29T00:00:00, Close, 27.41
Final Portfolio Value: 100000.00
有人说股票市场是危险的生意,但似乎并不是这样。
让我们解释一些魔法:
-
在调用 init 后,策略已经拥有了平台上存在的数据列表。
这是一个标准的 Python list,可以按插入顺序访问数据。
列表中的第一个数据 self.datas[0] 是默认用于交易操作的数据,并且用于保持所有策略元素同步(它是系统时钟)。
-
self.dataclose = self.datas[0].close 保持对 close 线 的引用。稍后只需要一级间接引用就能访问 close 值。
-
策略的 next 方法将在系统时钟的每个 bar 上调用(self.datas[0])。直到其他因素开始起作用,如 指标,它们需要一些 bar 才能开始产生输出。稍后会详细介绍。
在策略中添加一些逻辑
让我们通过查看一些图表来尝试一些疯狂的想法。
- 如果价格连续下跌 3 个交易会话… 买买买!!!
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].closedef next(self):# Simply log the closing price of the series from the referenceself.log('Close, %.2f' % self.dataclose[0])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 all possible default parameters)self.log('BUY CREATE, %.2f' % self.dataclose[0])self.buy()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)# 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, 27.85
2000-01-04, Close, 25.39
2000-01-05, Close, 24.05
2000-01-05, BUY CREATE, 24.05
2000-01-06, Close, 22.63
2000-01-06, BUY CREATE, 22.63
2000-01-07, Close, 24.37
...
...
...
2000-12-20, BUY CREATE, 26.88
2000-12-21, Close, 27.82
2000-12-22, Close, 30.06
2000-12-26, Close, 29.17
2000-12-27, Close, 28.94
2000-12-27, BUY CREATE, 28.94
2000-12-28, Close, 29.29
2000-12-29, Close, 27.41
Final Portfolio Value: 99725.08
发出了多个“BUY”创建订单,我们的投资组合价值减少了。显然有几个重要的事情缺失了。
-
订单已创建,但不清楚是否已执行,何时执行以及以什么价格执行。
下一个示例将在此基础上建立,通过监听订单状态通知。
好奇的读者可能会问买了多少股票,购买了什么资产以及订单是如何执行的。在可能的情况下(在这种情况下是可能的),平台会填补这些空白:
-
self.datas[0](主数据,也称为系统时钟)是目标资产,如果没有指定其他资产
-
股份是由position sizer在后台提供的,它使用固定的股份,“1”是默认值。稍后将进行修改。
-
订单是“市价”执行的。经纪人(在前面的示例中显示)使用下一根 bar 的开盘价执行此操作,因为那是当前检查的 bar 之后的第一个 tick。
-
到目前为止,订单已经执行了,没有任何佣金(稍后会详细介绍)
不仅买……还卖
在了解如何进入市场(做多)之后,需要一个“退出概念”,并且还要了解策略是否处于市场中。
-
幸运的是,Strategy 对象为默认的data feed提供了对position属性的访问权限
-
buy和sell方法返回创建的(尚未执行)订单
-
订单状态的更改将通过notify方法通知策略
*“退出概念”*将是一个简单的概念:
-
在过了 5 个 bar(第 6 个 bar)之后退出,无论好坏都要退出
请注意,没有暗示“时间”或“时间框架”:bar 的数量。bar 可以表示 1 分钟、1 小时、1 天、1 周或任何其他时间段。
尽管我们知道数据源是每日的,但策略不对此做任何假设。
此外,为了简化:
- 只有在市场中还没有持仓时才允许买入订单
注意
next方法没有传递“bar index”,因此似乎不清楚如何理解 5 个 bar 是否已经过去,但这已经以 Pythonic 的方式进行了建模:在对象上调用len,它将告诉您它的lines长度。只需记录(保存在变量中)操作发生的长度,然后查看当前长度是否相差 5 个 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):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 ordersself.order = 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, %.2f' % order.executed.price)elif order.issell():self.log('SELL EXECUTED, %.2f' % order.executed.price)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 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)# 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-03T00:00:00, Close, 27.85
2000-01-04T00:00:00, Close, 25.39
2000-01-05T00:00:00, Close, 24.05
2000-01-05T00:00:00, BUY CREATE, 24.05
2000-01-06T00:00:00, BUY EXECUTED, 23.61
2000-01-06T00:00:00, Close, 22.63
2000-01-07T00:00:00, Close, 24.37
2000-01-10T00:00:00, Close, 27.29
2000-01-11T00:00:00, Close, 26.49
2000-01-12T00:00:00, Close, 24.90
2000-01-13T00:00:00, Close, 24.77
2000-01-13T00:00:00, SELL CREATE, 24.77
2000-01-14T00:00:00, SELL EXECUTED, 25.70
2000-01-14T00:00:00, Close, 25.18
...
...
...
2000-12-15T00:00:00, SELL CREATE, 26.93
2000-12-18T00:00:00, SELL EXECUTED, 28.29
2000-12-18T00:00:00, Close, 30.18
2000-12-19T00:00:00, Close, 28.88
2000-12-20T00:00:00, Close, 26.88
2000-12-20T00:00:00, BUY CREATE, 26.88
2000-12-21T00:00:00, BUY EXECUTED, 26.23
2000-12-21T00:00:00, Close, 27.82
2000-12-22T00:00:00, Close, 30.06
2000-12-26T00:00:00, Close, 29.17
2000-12-27T00:00:00, Close, 28.94
2000-12-28T00:00:00, Close, 29.29
2000-12-29T00:00:00, Close, 27.41
2000-12-29T00:00:00, SELL CREATE, 27.41
Final Portfolio Value: 100018.53
烈焰般的船舱!!! 系统赚了钱……一定有问题。
经纪人说:给我看看钱!
钱被称为“佣金”。
让我们为每次操作(买入和卖出……是的,经纪人很贪婪……)添加合理的*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-03T00:00:00, Close, 27.85
2000-01-04T00:00:00, Close, 25.39
2000-01-05T00:00:00, Close, 24.05
2000-01-05T00:00:00, BUY CREATE, 24.05
2000-01-06T00:00:00, BUY EXECUTED, Price: 23.61, Cost: 23.61, Commission 0.02
2000-01-06T00:00:00, Close, 22.63
2000-01-07T00:00:00, Close, 24.37
2000-01-10T00:00:00, Close, 27.29
2000-01-11T00:00:00, Close, 26.49
2000-01-12T00:00:00, Close, 24.90
2000-01-13T00:00:00, Close, 24.77
2000-01-13T00:00:00, SELL CREATE, 24.77
2000-01-14T00:00:00, SELL EXECUTED, Price: 25.70, Cost: 25.70, Commission 0.03
2000-01-14T00:00:00, OPERATION PROFIT, GROSS 2.09, NET 2.04
2000-01-14T00:00:00, Close, 25.18
...
...
...
2000-12-15T00:00:00, SELL CREATE, 26.93
2000-12-18T00:00:00, SELL EXECUTED, Price: 28.29, Cost: 28.29, Commission 0.03
2000-12-18T00:00:00, OPERATION PROFIT, GROSS -0.06, NET -0.12
2000-12-18T00:00:00, Close, 30.18
2000-12-19T00:00:00, Close, 28.88
2000-12-20T00:00:00, Close, 26.88
2000-12-20T00:00:00, BUY CREATE, 26.88
2000-12-21T00:00:00, BUY EXECUTED, Price: 26.23, Cost: 26.23, Commission 0.03
2000-12-21T00:00:00, Close, 27.82
2000-12-22T00:00:00, Close, 30.06
2000-12-26T00:00:00, Close, 29.17
2000-12-27T00:00:00, Close, 28.94
2000-12-28T00:00:00, Close, 29.29
2000-12-29T00:00:00, Close, 27.41
2000-12-29T00:00:00, SELL CREATE, 27.41
Final Portfolio Value: 100016.98
上帝保佑女王!!! 系统仍然赚了钱。
在继续之前,让我们通过过滤“OPERATION PROFIT”行来注意一些事情:
2000-01-14T00:00:00, OPERATION PROFIT, GROSS 2.09, NET 2.04
2000-02-07T00:00:00, OPERATION PROFIT, GROSS 3.68, NET 3.63
2000-02-28T00:00:00, OPERATION PROFIT, GROSS 4.48, NET 4.42
2000-03-13T00:00:00, OPERATION PROFIT, GROSS 3.48, NET 3.41
2000-03-22T00:00:00, OPERATION PROFIT, GROSS -0.41, NET -0.49
2000-04-07T00:00:00, OPERATION PROFIT, GROSS 2.45, NET 2.37
2000-04-20T00:00:00, OPERATION PROFIT, GROSS -1.95, NET -2.02
2000-05-02T00:00:00, OPERATION PROFIT, GROSS 5.46, NET 5.39
2000-05-11T00:00:00, OPERATION PROFIT, GROSS -3.74, NET -3.81
2000-05-30T00:00:00, OPERATION PROFIT, GROSS -1.46, NET -1.53
2000-07-05T00:00:00, OPERATION PROFIT, GROSS -1.62, NET -1.69
2000-07-14T00:00:00, OPERATION PROFIT, GROSS 2.08, NET 2.01
2000-07-28T00:00:00, OPERATION PROFIT, GROSS 0.14, NET 0.07
2000-08-08T00:00:00, OPERATION PROFIT, GROSS 4.36, NET 4.29
2000-08-21T00:00:00, OPERATION PROFIT, GROSS 1.03, NET 0.95
2000-09-15T00:00:00, OPERATION PROFIT, GROSS -4.26, NET -4.34
2000-09-27T00:00:00, OPERATION PROFIT, GROSS 1.29, NET 1.22
2000-10-13T00:00:00, OPERATION PROFIT, GROSS -2.98, NET -3.04
2000-10-26T00:00:00, OPERATION PROFIT, GROSS 3.01, NET 2.95
2000-11-06T00:00:00, OPERATION PROFIT, GROSS -3.59, NET -3.65
2000-11-16T00:00:00, OPERATION PROFIT, GROSS 1.28, NET 1.23
2000-12-01T00:00:00, OPERATION PROFIT, GROSS 2.59, NET 2.54
2000-12-18T00:00:00, OPERATION PROFIT, GROSS -0.06, NET -0.12
加总“净”利润,最终数字是:
15.83
但系统在最后说了以下内容:
2000-12-29T00:00:00, SELL CREATE, 27.41
Final Portfolio Value: 100016.98
显然15.83不等于16.98。没有任何错误。15.83的“净”利润已经到手了。
不幸的是(或者幸运的是,为了更好地了解平台),在Data Feed的最后一天仍然有一个未平仓的头寸。即使已发送了一个卖出操作……但尚未执行。
经纪人计算的“最终投资组合价值”考虑了 2000-12-29 的“收盘”价格。实际执行价格将在下一个交易日设定,恰好是 2001-01-02。扩展 数据源”以考虑这一天的输出为:
2001-01-02T00:00:00, SELL EXECUTED, Price: 27.87, Cost: 27.87, Commission 0.03
2001-01-02T00:00:00, OPERATION PROFIT, GROSS 1.64, NET 1.59
2001-01-02T00:00:00, Close, 24.87
2001-01-02T00:00:00, BUY CREATE, 24.87
Final Portfolio Value: 100017.41
现在将之前的净利润添加到已完成操作的净利润中:
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)``````pyPlease read the section about *sizers*Using the parameters in the strategy is easy, as they are stored in a “params” attribute. If we for example want to set the stake fix, we can pass the stake parameter to the *position sizer* like this durint **init**:
`# 从参数设置 sizer stake
self.sizer.setsizing(self.params.stake)`
We could have also called *buy* and *sell* with a *stake* parameter and *self.params.stake* as the value.The logic to exit gets modified:
`# 已经在市场上……我们可能会出售
if len(self) >= (self.bar_executed + self.params.exitbars):`
With all this in mind the example evolves to look like:
`from future import (absolute_import, division, print_function,
unicode_literals)
导入 datetime # 用于日期时间对象
导入 os.path # 用于管理路径
导入 sys # 用于查找脚本名称(在 argv[0] 中)
导入 backtrader 平台
导入 backtrader as bt
创建策略
class TestStrategy(bt.Strategy):
params = (('exitbars', 5),)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 = Noneself.buyprice = Noneself.buycomm = Nonedef 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.priceself.buycomm = order.executed.commelse: # 卖出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 = Nonedef 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:# 还没有...如果...我们可能会购买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 + self.params.exitbars):# 卖出, 卖出, 卖出!!! (使用所有可能的默认参数)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)# 根据股份添加一个固定大小的 sizercerebro.addsizer(bt.sizers.FixedSize, stake=10)# 设置佣金 - 0.1% ... 除以 100 移除 %cerebro.broker.setcommission(commission=0.001)# 打印出起始条件print('起始投资组合价值: %.2f' % cerebro.broker.getvalue())# 运行全部内容cerebro.run()# 打印出最终结果print('最终投资组合价值: %.2f' % cerebro.broker.getvalue())`
After the execution the output is:
`起始投资组合价值: 100000.00
2000-01-03T00:00:00, 关闭, 27.85
2000-01-04T00:00:00, 关闭, 25.39
2000-01-05T00:00:00, 关闭, 24.05
2000-01-05T00:00:00, 创建购买, 24.05
2000-01-06T00:00:00, 购买已执行, 数量 10, 价格: 23.61, 成本: 236.10, 佣金 0.24
2000-01-06T00:00:00, 关闭, 22.63
…
…
…
2000-12-20T00:00:00, 创建购买, 26.88
2000-12-21T00:00:00, 购买已执行, 数量 10, 价格: 26.23, 成本: 262.30, 佣金 0.26
2000-12-21T00:00:00, 关闭, 27.82
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
最终投资组合价值: 100169.80`
In order to see the difference, the print outputs have also been extended to show the execution size.Having multiplied the stake by 10, the obvious has happened: the profit and loss has been multiplied by 10\. Instead of *16.98*, the surplus is now *169.80*### Adding an indicatorHaving heard of *indicators*, the next thing anyone would add to the strategy is one of them. For sure they must be much better than a simple *“3 lower closes”* strategy.Inspired in one of the examples from PyAlgoTrade a strategy using a Simple Moving Average.* Buy “AtMarket” if the close is greater than the Average* If in the market, sell if the close is smaller than the Average* Only 1 active operation is allowed in the marketMost of the existing code can be kept in place. Let’s add the average during **init** and keep a reference to it:
self.sma = bt.indicators.MovingAverageSimple(self.datas[0], period=self.params.maperiod)
And of course the logic to enter and exit the market will rely on the Average values. Look in the code for the logic.NoteThe starting cash will be 1000 monetary units to be in line with the PyAlgoTrade example and no commission will be applied
`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):
参数 = (('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 = Noneself.buyprice = Noneself.buycomm = None# 添加一个 MovingAverageSimple 指标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]:# 购买/卖出订单提交/接受给/由经纪人-无需操作返回# 检查订单是否已完成# 注意:如果现金不足,经纪人可能会拒绝订单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.priceself.buycomm = order.executed.commelse: # 卖出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 = Nonedef 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:# 还没有...如果...,我们可能会买if self.dataclose[0] > self.sma[0]:# 买,买,买!!! (具有所有可能的默认参数)self.log('购买创建,%.2f' % self.dataclose[0])# 跟踪创建的订单,以避免第二个订单self.order = self.buy()else:if 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)# 添加数据源到 Cerebrocerebro.adddata(data)# 设置我们所需的初始现金cerebro.broker.setcash(1000.0)# 添加一个固定大小的 Sizer 根据股票份额cerebro.addsizer(bt.sizers.FixedSize, stake=10)# 设置佣金cerebro.broker.setcommission(commission=0.0)# 打印初始条件print('起始组合价值: %.2f' % cerebro.broker.getvalue())# 遍历所有操作cerebro.run()# 打印最终结果print('最终组合价值: %.2f' % cerebro.broker.getvalue())`
Now, before skipping to the next section **LOOK CAREFULLY** to the first date which is shown in the log:* It’ no longer *2000-01-03*, the first trading day in the year 2K.It’s 2000-01-24 … *Who has stolen my cheese?*The missing days are not missing. The platform has adapted to the new circumstances:* An indicator (SimpleMovingAverage) has been added to the Strategy.* This indicator needs X bars to produce an output: in the example: 15* 2000-01-24 is the day in which the 15^(th) bar occursThe *backtrader* platform assumes that the Strategy has the indicator in place for a good reason, **to use it in the decision making process**. And it makes no sense to try to make decisions if the indicator is not yet ready and producing values.* *next* will be 1^(st) called when all indicators have already reached the minimum needed period to produce a value* In the example there is a single indicator, but the strategy could have any number of them.After the execution the output is:
`Starting Portfolio Value: 1000.00
2000-01-24T00:00:00, 收盘价, 25.55
2000-01-25T00:00:00, 收盘价, 26.61
2000-01-25T00:00:00, 买入信号, 26.61
2000-01-26T00:00:00, 买入执行, 数量 10, 价格: 26.76, 成本: 267.60, 手续费 0.00
2000-01-26T00:00:00, 收盘价, 25.96
2000-01-27T00:00:00, 收盘价, 24.43
2000-01-27T00:00:00, 卖出信号, 24.43
2000-01-28T00:00:00, 卖出执行, 数量 10, 价格: 24.28, 成本: 242.80, 手续费 0.00
2000-01-28T00:00:00, 操作盈利, 总额 -24.80, 净额 -24.80
2000-01-28T00:00:00, 收盘价, 22.34
2000-01-31T00:00:00, 收盘价, 23.55
2000-02-01T00:00:00, 收盘价, 25.46
2000-02-02T00:00:00, 收盘价, 25.61
2000-02-02T00:00:00, 买入信号, 25.61
2000-02-03T00:00:00, 买入执行, 数量 10, 价格: 26.11, 成本: 261.10, 手续费 0.00
…
…
…
2000-12-20T00:00:00, 卖出信号, 26.88
2000-12-21T00:00:00, 卖出执行, 数量 10, 价格: 26.23, 成本: 262.30, 手续费 0.00
2000-12-21T00:00:00, 操作盈利, 总额 -20.60, 净额 -20.60
2000-12-21T00:00:00, 收盘价, 27.82
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
最终组合价值: 973.90`
In the name of the King!!! A winning system turned into a losing one … and that with no commission. It may well be that **simply** adding an *indicator* is not the universal panacea.NoteThe same logic and data with PyAlgoTrade yields a slightly different result (slightly off). Looking at the entire printout reveals that some operations are not exactly the same. Being the culprit again the usual suspect: *rounding*.PyAlgoTrade does not round the datafeed values when applying the divided “adjusted close” to the data feed values.The Yahoo Data Feed provided by *backtrader* rounds the values down to 2 decimals after applying the adjusted close. Upon printing the values everything seems the same, but it’s obvious that sometimes that 5^(th) place decimal plays a role.Rounding down to 2 decimals seems more realistic, because Market Exchanges do only allow a number of decimals per asset (being that 2 decimals usually for stocks)NoteThe Yahoo Data Feed (starting with version `1.8.11.99` allows to specify if rounding has to happen and how many decimals)### Visual Inspection: PlottingA printout or log of the actual whereabouts of the system at each bar-instant is good but humans tend to be *visual* and therefore it seems right to offer a view of the same whereabouts as chart.NoteTo plot you need to have *matplotlib* installedOnce again defaults for plotting are there to assist the platform user. Plotting is incredibly a 1 line operation:
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`
NoteEven 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 = Noneself.buyprice = Noneself.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.priceself.buycomm = order.executed.commelse: # 卖出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 = Nonedef 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)# 添加数据源到 Cerebrocerebro.adddata(data)# 设置我们期望的起始现金cerebro.broker.setcash(1000.0)# 根据股份添加一个固定大小的 sizercerebro.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.NoteAs 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 OptimizeMany 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.NoteThere 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 removedThe 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 = Noneself.buyprice = Noneself.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.priceself.buycomm = order.executed.commelse: # 卖出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 = Nonedef 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)# 将数据提供添加到 Cerebrocerebro.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 时结果略有不同。### 结论增量样本展示了如何从一个基本脚本发展到一个完全工作的交易系统,甚至绘制了结果并且可以优化。可以做更多事情来尝试提高获胜的机会:+ 自定义指标创建一个指标很容易(甚至绘制它们也很容易)+ 大小调整器资金管理对于许多人来说是成功的关键+ 订单类型(限价,止损,止损限价)+ 其他一些为了确保上述所有项目都能得到充分利用,文档提供了对它们(以及其他主题)的深入了解查看目录并继续阅读……并发展。祝你好运