BackTrader 中文文档(一)

原文: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,…

  • 可以同时运行任意数量的数据源(受内存限制,显然)

    警告

    注意幸存者偏差!

  • 可以混合和运行多个时间框架

  • 集成重采样和重播功能

内置电池的经纪人

  • 订单类型:MarketLimitStopStopLimitStopTrailStopTrailLimitOCOBracketMarketOnClose

  • 多空卖出

  • 未来类工具的持续现金调整

  • 用户定义的佣金方案和信用利息

  • 基金模式

  • 成交量填充策略

  • 自定义滑点

策略 - 交易逻辑

  • 在操作之前自动计算热身期

  • 多个策略(针对同一经纪人)可以并行运行

  • 多种订单生成方法(buy/sellorder_target_xxx、自动信号)

  • 事件通知:传入数据、数据源提供者、订单、交易、定时器

指标

超过 122 种指标,常见的指标都在其中

  • 许多移动平均线(SMAEMA,…)、经典指标(MACDStochasticRSI,…)和其他指标

  • ta-lib集成

性能分析器

几个内置的性能分析器(TimeReturnsTradeAnalyzerSharpeRatioVWRSQN,…)

绘图(额外)

使用单个命令进行自动化(可定制)绘图

注意

为了使其工作,必须安装matplotlib

大小调整器

定义并插入智能自动化的押注策略

观察者

代理可以被绘制,并且可以观察系统中的一切(通常用于绘制统计数据)

杂项

  • 定时器用于随时间重复的操作

  • 交易日历

  • 时区支持

纯 Python

使用最强大且易于使用的编程语言之一。无需外部库。

  • 使用面向对象的方法轻松地将拼图的各个部分拼合在一起

  • 操作符在可能的情况下进行重载,以提供自然语言构造,例如:

    av_diff = bt.ind.SMA(period=30) - bt.ind.SMA(period=15)` 
    

    其中av_diff将包含3015周期的简单移动平均线的差值

  • 对于语言结构,不能被覆盖,比如andorif,提供了等效的函数以确保没有功能丢失,例如

    av_and = bt.And(av_diff > 0, self.data.close < bt.ind.SMA(period=30))` 
    

你好,算法交易!

原文:www.backtrader.com/home/helloalgotrading/

一个经典的简单移动平均线交叉策略,可以轻松地以不同方式实现。下面呈现的三个片段的结果和图表是相同的。

image

from datetime import datetime
import backtrader as bt

# Create a subclass of Strategy to define the indicators and logic

class SmaCross(bt.Strategy):
    # list of parameters which are configurable for the strategy
    params = dict(
        pfast=10,  # period for the fast moving average
        pslow=30   # period for the slow moving average
    )

    def __init__(self):
        sma1 = bt.ind.SMA(period=self.p.pfast)  # fast moving average
        sma2 = bt.ind.SMA(period=self.p.pslow)  # slow moving average
        self.crossover = bt.ind.CrossOver(sma1, sma2)  # crossover signal

    def next(self):
        if not self.position:  # not in the market
            if self.crossover > 0:  # if fast crosses slow to the upside
                self.buy()  # enter long

        elif self.crossover < 0:  # in the market & cross to the downside
            self.close()  # close long position

cerebro = 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 feed

cerebro.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 logic

class SmaCross(bt.Strategy):
    # list of parameters which are configurable for the strategy
    params = dict(
        pfast=10,  # period for the fast moving average
        pslow=30   # period for the slow moving average
    )

    def __init__(self):
        sma1 = bt.ind.SMA(period=self.p.pfast)  # fast moving average
        sma2 = bt.ind.SMA(period=self.p.pslow)  # slow moving average
        self.crossover = bt.ind.CrossOver(sma1, sma2)  # crossover signal

    def next(self):
        if not self.position:  # not in the market
            if self.crossover > 0:  # if fast crosses slow to the upside
                self.order_target_size(target=1)  # enter long

        elif self.crossover < 0:  # in the market & cross to the downside
            self.order_target_size(target=0)  # close long position

cerebro = 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 feed

cerebro.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 signals

class SmaCross(bt.SignalStrategy):
    # list of parameters which are configurable for the strategy
    params = dict(
        pfast=10,  # period for the fast moving average
        pslow=30   # period for the slow moving average
    )

    def __init__(self):
        sma1 = bt.ind.SMA(period=self.p.pfast)  # fast moving average
        sma2 = bt.ind.SMA(period=self.p.pslow)  # slow moving average
        crossover = bt.ind.CrossOver(sma1, sma2)  # crossover signal
        self.signal_add(bt.SIGNAL_LONG, crossover)  # use it as LONG signal

cerebro = 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 feed

cerebro.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 家量化交易公司

那是很久以前的事了,作者已经知道更多了,包括其他类型的交易公司,如“能源交易”等行业。

为什么没有提供名称?

  1. 我从未问过

  2. 他们从未问过

那你能支撑这些说法吗?

在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. 易用性

  2. 回到 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的两个基本概念之前。

  1. 线

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

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

    • 开盘价、最高价、最低价、收盘价、成交量、持仓量

    一系列“开盘价”随时间的变化是一条线。因此,一个数据源通常有 6 条线。

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

  2. 第 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 bt

if __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 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

任务完成。让我们转向风云变幻的水域。

添加一个 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 bt

if __name__ == '__main__':
    # Create a cerebro entity
    cerebro = bt.Cerebro()

    # 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 after this date
        todate=datetime.datetime(2000, 12, 31),
        reverse=False)

    # Add the Data Feed to Cerebro
    cerebro.adddata(data)

    # Set our desired cash start
    cerebro.broker.setcash(100000.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())

执行后的输出为:

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”价格。

DataSeriesData 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] dataseries
        self.dataclose = self.datas[0].close

    def next(self):
        # Simply log the closing price of the series from the reference
        self.log('Close, %.2f' % self.dataclose[0])

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)

    # 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-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] dataseries
        self.dataclose = self.datas[0].close

    def next(self):
        # Simply log the closing price of the series from the reference
        self.log('Close, %.2f' % self.dataclose[0])

        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 all possible default parameters)
                self.log('BUY CREATE, %.2f' % self.dataclose[0])
                self.buy()

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)

    # 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, 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属性的访问权限

  • buysell方法返回创建的(尚未执行)订单

  • 订单状态的更改将通过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] dataseries
        self.dataclose = self.datas[0].close

        # To keep track of pending orders
        self.order = 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, %.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 order
        self.order = None

    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)

    # 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-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] 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-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)```

```py

Please 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 = None

    self.buyprice = None

    self.buycomm = None

def notify_order(self, order):

    if order.status in [order.Submitted, order.Accepted]:

        # 由经纪人提交/接受的购买/卖出订单 - 无需操作

        返回

    # 检查订单是否已完成

    # 注意:如果资金不足,经纪人可能会拒绝订单

    if order.status in [order.Completed]:

        if order.isbuy():

            self.log(

                '已执行购买,价格:%.2f,成本:%.2f,佣金:%.2f' %

                (order.executed.price,

                order.executed.value,

                order.executed.comm))

            self.buyprice = order.executed.price

            self.buycomm = order.executed.comm

        else:  # 卖出

            self.log('卖出执行,价格:%.2f,成本:%.2f,佣金:%.2f' %

                    (order.executed.price,

                    order.executed.value,

                    order.executed.comm))

        self.bar_executed = len(self)

    elif order.status in [order.Canceled, order.Margin, order.Rejected]:

        self.log('订单已取消/保证金/拒绝')

    self.order = None

def notify_trade(self, trade):

    if not trade.isclosed:

        返回

    self.log('操作利润,总额:%.2f,净额:%.2f' %

            (trade.pnl, trade.pnlcomm))

def next(self):

    # 简单记录参考系列的收盘价格

    self.log('关闭, %.2f' % self.dataclose[0])

    # 检查订单是否挂起 ... 如果是,则不能发送第二个订单

    if self.order:

        返回

    # 检查我们是否在市场中

    if not self.position:

        # 还没有...如果...我们可能会购买

        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)

# 将数据源添加到 Cerebro

cerebro.adddata(data)

# 设置我们想要的现金起始值

cerebro.broker.setcash(100000.0)

# 根据股份添加一个固定大小的 sizer

cerebro.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 indicator

Having 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 market

Most 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.

Note

The 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 = None

    self.buyprice = None

    self.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.price

            self.buycomm = order.executed.comm

        else:  # 卖出

            self.log('卖出执行,价格:%.2f,成本:%.2f,佣金%.2f' %

                    (order.executed.price,

                    order.executed.value,

                    order.executed.comm))

        self.bar_executed = len(self)

    elif order.status in [order.Canceled, order.Margin, order.Rejected]:

        self.log('订单已取消/保证金/拒绝')

    self.order = None

def notify_trade(self, trade):

    if not trade.isclosed:

        返回

    self.log('操作利润,总%.2f,净%.2f' %

            (trade.pnl, trade.pnlcomm))

def next(self):

    # 仅记录来自参考系列的收盘价

    self.log('关闭,%.2f' % self.dataclose[0])

    # 检查订单是否待定... 如果是,我们不能发送第二个订单

    if self.order:

        返回

    # 检查我们是否在市场中

    if not self.position:

        # 还没有...如果...,我们可能会买

        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)

# 添加数据源到 Cerebro

cerebro.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 occurs

The *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.

Note

The 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)

Note

The Yahoo Data Feed (starting with version `1.8.11.99` allows to specify if rounding has to happen and how many decimals)

### Visual Inspection: Plotting

A 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.

Note

To plot you need to have *matplotlib* installed

Once 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`


Note

Even if *indicators* are not explicitly added to a member variable of the strategy (like self.sma = MovingAverageSimple…), they will autoregister with the strategy and will influence the minimum period for *next* and will be part of the plotting.

In the example only *RSI* is added to a temporary variable *rsi* with the only intention to create a MovingAverageSmoothed on it.

The example now:

`from future import (absolute_import, division, print_function,

                    unicode_literals)

import datetime # 用于日期对象

import os.path # 用于管理路径

import sys # 用于查找脚本名称(在 argv[0] 中)

导入 backtrader 平台

import backtrader as bt

创建策略

class TestStrategy(bt.Strategy):

params = (

    ('maperiod', 15),

)

def log(self, txt, dt=None):

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

    dt = dt or self.datas[0].datetime.date(0)

    print('%s, %s' % (dt.isoformat(), txt))

def __init__(self):

    # 保持对数据[0] 数据系列中的 "close" 线的引用

    self.dataclose = self.datas[0].close

    # 为了跟踪待处理的订单和买入价格/佣金

    self.order = None

    self.buyprice = None

    self.buycomm = None

    # 添加一个 MovingAverageSimple 指标

    self.sma = bt.indicators.SimpleMovingAverage(

        self.datas[0], period=self.params.maperiod)

    # 用于绘图的指标

    bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)

    bt.indicators.WeightedMovingAverage(self.datas[0], period=25,

                                        subplot=True)

    bt.indicators.StochasticSlow(self.datas[0])

    bt.indicators.MACDHisto(self.datas[0])

    rsi = bt.indicators.RSI(self.datas[0])

    bt.indicators.SmoothedMovingAverage(rsi, period=10)

    bt.indicators.ATR(self.datas[0], plot=False)

def notify_order(self, order):

    if order.status in [order.Submitted, order.Accepted]:

        # 提交/接受到/由经纪人的买入/卖出订单 - 无需操作

        返回

    # 检查订单是否已完成

    # 注意: 如果资金不足,经纪人可能会拒绝订单

    if order.status in [order.Completed]:

        if order.isbuy():

            self.log(

                '买入执行, 价格: %.2f, 成本: %.2f, 佣金 %.2f' %

                (order.executed.price,

                order.executed.value,

                order.executed.comm))

            self.buyprice = order.executed.price

            self.buycomm = order.executed.comm

        else:  # 卖出

            self.log('卖出执行, 价格: %.2f, 成本: %.2f, 佣金 %.2f' %

                    (order.executed.price,

                    order.executed.value,

                    order.executed.comm))

        self.bar_executed = len(self)

    elif order.status in [order.Canceled, order.Margin, order.Rejected]:

        self.log('订单取消/保证金/拒绝')

    # 写下:没有待处理的订单

    self.order = None

def notify_trade(self, trade):

    if not trade.isclosed:

        返回

    self.log('操作利润, 总额 %.2f, 净额 %.2f' %

            (trade.pnl, trade.pnlcomm))

def next(self):

    # 简单记录来自参考系列的收盘价格

    self.log('关闭, %.2f' % self.dataclose[0])

    # 检查订单是否待处理...如果是,我们不能发送第二个订单

    if self.order:

        返回

    # 检查我们是否在市场中

    if not self.position:

        # 还没有...如果...的话,我们可能会买入

        如果 self.dataclose[0] > self.sma[0]:

            # 买, 买, 买!!!(所有可能的默认参数)

            self.log('买入创建, %.2f' % self.dataclose[0])

            # 跟踪创建的订单以避免第二个订单

            self.order = self.buy()

    否则:

        如果 self.dataclose[0] < self.sma[0]:

            # 卖, 卖, 卖!!!(所有可能的默认参数)

            self.log('卖出创建, %.2f' % self.dataclose[0])

            # 跟踪创建的订单以避免第二个订单

            self.order = self.sell()

if name == ‘main’:

# 创建一个 cerebro 实体

cerebro = bt.Cerebro()

# 添加一个策略

cerebro.addstrategy(TestStrategy)

# 数据位于示例的子文件夹中。需要找到脚本所在的位置

# 因为它可能从任何地方调用

modpath = os.path.dirname(os.path.abspath(sys.argv[0]))

datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')

# 创建一个数据源

data = bt.feeds.YahooFinanceCSVData(

    dataname=datapath,

    # 不要在此日期之前传递值

    fromdate=datetime.datetime(2000, 1, 1),

    # 请勿在此日期之前传递值

    todate=datetime.datetime(2000, 12, 31),

    # 请勿在此日期之后传递值

    reverse=False)

# 添加数据源到 Cerebro

cerebro.adddata(data)

# 设置我们期望的起始现金

cerebro.broker.setcash(1000.0)

# 根据股份添加一个固定大小的 sizer

cerebro.addsizer(bt.sizers.FixedSize, stake=10)

# 设置佣金

cerebro.broker.setcommission(commission=0.0)

# 打印出起始条件

print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

# 在所有内容上运行

cerebro.run()

# 打印出最终结果

print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

# 绘制结果

cerebro.plot()`

After the execution the output is:

起始投资组合价值:1000.00

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

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

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

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

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

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

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

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

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

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

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

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

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

最终投资组合价值:981.00`


**The final result has changed even if the logic hasn’t**. This is true but the logic has not been applied to the same number of bars.

Note

As explained before, the platform will first call next when all indicators are ready to produce a value. In this plotting example (very clear in the chart) the MACD is the last indicator to be fully ready (all 3 lines producing an output). The 1^(st) BUY order is no longer scheduled during Jan 2000 but close to the end of Feb 2000.

The chart:

![image](https://gitcode.net/OpenDocCN/flygon-quant-docs-zh/-/raw/master/docs/backtrader/img/ea78429a6e60755c2d70c3857cb63606.png)

### Let’s Optimize

Many trading books say each market and each traded stock (or commodity or ..) have different rythms. That there is no such thing as a one size fits all.

Before the plotting sample, when the strategy started using an indicator the period default value was 15 bars. It’s a strategy parameter and this can be used in an optimization to change the value of the parameter and see which one better fits the market.

Note

There is plenty of literature about Optimization and associated pros and cons. But the advice will always point in the same direction: do not overoptimize. If a trading idea is not sound, optimizing may end producing a positive result which is only valid for the backtested dataset.

The sample is modified to optimize the period of the Simple Moving Average. For the sake of clarity any output with regards to Buy/Sell orders has been removed

The example now:

from __future__ import (absolute_import, division, print_function,

                    unicode_literals)

import datetime # 用于日期时间对象

import os.path # 用于管理路径

import sys # 用于查找脚本名称(在 argv[0] 中)

导入 backtrader 平台

import backtrader as bt

创建一个策略

class TestStrategy(bt.Strategy):

params = (

    ('maperiod', 15),

    ('printlog', False),

)

def log(self, txt, dt=None, doprint=False):

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

    if self.params.printlog or doprint:

        dt = dt or self.datas[0].datetime.date(0)

        `print('%s, %s' % (dt.isoformat(), txt))`

def __init__(self):

    # 保持对数据[0]数据系列中“close”线的引用

    self.dataclose = self.datas[0].close

    # 跟踪挂单和买入价格/佣金

    self.order = None

    self.buyprice = None

    self.buycomm = None

    # 添加一个 MovingAverageSimple 指标

    self.sma = bt.indicators.SimpleMovingAverage(

        self.datas[0],周期=self.params.maperiod)

def notify_order(self, order):

    if order.status in [order.Submitted, order.Accepted]:

        # 提交/接受经纪人的买入/卖出订单 - 没有操作可执行

        返回

    # 检查订单是否已完成

    # 注意:如果现金不足,经纪人可能会拒绝订单

    if order.status in [order.Completed]:

        if order.isbuy():

            self.log(

                'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %

                (order.executed.price,

                order.executed.value,

                order.executed.comm))

            self.buyprice = order.executed.price

            self.buycomm = order.executed.comm

        else:  # 卖出

            self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %

                    (order.executed.price,

                    order.executed.value,

                    order.executed.comm))

        self.bar_executed = len(self)

    elif order.status in [order.Canceled, order.Margin, order.Rejected]:

        self.log('订单取消/保证金/拒绝')

    # 写下:没有挂单

    self.order = None

def notify_trade(self, trade):

    如果交易未关闭:

        返回

    self.log('操作利润,毛利 %.2f,净利 %.2f' %

            (trade.pnl, trade.pnlcomm))

def next(self):

    # 简单地记录来自参考系列的收盘价

    self.log('收盘,%.2f' % self.dataclose[0])

    # 检查是否有挂单... 如果有,我们不能发送第二个挂单

    如果存在挂单...

        返回

    # 检查我们是否在市场上

    如果没有持仓...

        # 还没有... 如果...,我们可能会买入

        如果 self.dataclose[0] > self.sma[0]:

            # 买入,买入,买入!!!(带有所有可能的默认参数)

            self.log('买入创建,%.2f' % self.dataclose[0])

            # 跟踪已创建的订单以避免第二个订单

            self.order = self.buy()

    否则:

        如果 self.dataclose[0] < self.sma[0]:

            # 卖出,卖出,卖出!!!(带有所有可能的默认参数)

            self.log('卖出创建,%.2f' % self.dataclose[0])

            # 跟踪已创建的订单以避免第二个订单

            self.order = self.sell()

def stop(self):

    self.log('(MA 周期 %2d)结束价值 %.2f' %

            (self.params.maperiod, self.broker.getvalue()), doprint=True)

如果 name == ‘main’:

# 创建一个 cerebro 实体

cerebro = bt.Cerebro()

# 添加一种策略

strats = cerebro.optstrategy(

    TestStrategy,

    maperiod=range(10, 31))

# 数据位于样本的子文件夹中。需要找到脚本所在的位置

# 因为它可能是从任何地方调用的

modpath = os.path.dirname(os.path.abspath(sys.argv[0]))

datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')

# 创建一个数据提供

data = bt.feeds.YahooFinanceCSVData(

    dataname=datapath,

    # 不要传递此日期之前的值

    fromdate=datetime.datetime(2000, 1, 1),

    # 不要传递此日期之前的值

    todate=datetime.datetime(2000, 12, 31),

    # 不要传递此日期之后的值

    reverse=False)

# 将数据提供添加到 Cerebro

cerebro.adddata(data)

# 设置我们期望的初始现金

cerebro.broker.setcash(1000.0)

# 根据股份添加一个固定大小的调整器

cerebro.addsizer(bt.sizers.FixedSize, stake=10)

# 设置佣金

cerebro.broker.setcommission(commission=0.0)

# 对一切进行操作

cerebro.run(maxcpus=1)`

Instead of calling *addstrategy* to add a stratey class to Cerebro, the call is made to *optstrategy*. And instead of passing a value a range of values is passed.

One of the “Strategy” hooks is added, the *stop* method, which will be called when the data has been exhausted and backtesting is over. It’s used to print the final net value of the portfolio in the broker (it was done in Cerebro previously)

The system will execute the strategy for each value of the range. The following will be output:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


结果:

+   对于低于 18 的周期,该策略(免佣)会亏钱。

+   对于周期在 18 到 26 之间(两者都包括在内),该策略赚钱。

+   超过 26 后又会损失金钱。

而该策略在给定数据集下的获胜周期是:

+   20 根棒子,在 1000 美元/欧元的基础上赢得了 78.00 个单位(7.8%)

注意

绘图示例中的额外指标已被移除,操作的开始只受到正在优化的简单移动平均线的影响。因此,周期为 15 时结果略有不同。

### 结论

增量样本展示了如何从一个基本脚本发展到一个完全工作的交易系统,甚至绘制了结果并且可以优化。

可以做更多事情来尝试提高获胜的机会:

+   自定义指标

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

+   大小调整器

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

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

+   其他一些

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

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

祝你好运

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/549087.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

打一把王者的时间,学会web页面测试方法与测试用例编写

一、输入框 1、字符型输入框&#xff1a; &#xff08;1&#xff09;字符型输入框&#xff1a;英文全角、英文半角、数字、空或者空格、特殊字符“~&#xff01;#&#xffe5;%……&*&#xff1f;[]{}”特别要注意单引号和&符号。禁止直接输入特殊字符时&#xff0c;…

Web App 入门指南:构建预测模型 App 的利器(shiny)

Web App 入门指南&#xff1a;构建预测模型 App 的利器 简介 近年来&#xff0c;随着机器学习和人工智能技术的快速发展&#xff0c;预测模型在各行各业得到了广泛应用。为了方便地部署和使用预测模型&#xff0c;将模型构建成 Web App 是一种非常好的选择。Web App 无需下载…

27.8k Star,AI智能体项目GPT Pilot:第一个真正的人工智能开发者(附部署视频教程)

作者&#xff1a;Aitrainee | AI进修生 排版太难了&#xff0c;请点击这里查看原文&#xff1a;27.8k Star&#xff0c;AI智能体项目GPT Pilot&#xff1a;第一个真正的人工智能开发者&#xff08;附部署视频教程&#xff09; 今天介绍一下一个人工智能智能体的项目GPT Pilot。…

Postman 环境变量配置初始调用登录脚本赋值Token

效果 新建环境 切换 Environments 标签下 点击上面加号增加环境变量 使用环境变量 使用{{变量名}}引用变量使用 Pre-request Script 全局 一般授权接口都需要再调用接口前&#xff0c;进行登录授权&#xff0c;这里使用了全局的请求前脚本调用。 脚本示例 // 基础地址 var…

前端跨域怎么办?

如果网上搜到的方法都不可行或者比较麻烦&#xff0c;可以尝试改变浏览器的设置&#xff08;仅为临时方案&#xff09; 1.新建一个Chrome浏览器的快捷方式 2.鼠标右键&#xff0c;进入属性&#xff0c;将以下命令复制粘贴到目标位置&#xff08;可根据Chrome实际存放位置修改…

数据结构DAY4--哈希表

哈希表 概念&#xff1a;相当于字典&#xff0c;可以根据数据的关键字来寻找相关数据的查找表。 步骤&#xff1a;建立->插入->遍历->查找->销毁 建立 建立数据&#xff0c;形式随意&#xff0c;但一般为结构体&#xff08;储存的数据量大&#xff09;&#xff…

vivado AXI 接口事件

AXI 接口事件 在 Vivado 硬件管理器中 &#xff0c; 如果使用 System ILA IP 对设计 AXI 接口进行调试 &#xff0c; 那么“波形 (Waveform) ”窗口会显示对 应于 System ILA 所探测的接口的接口插槽、事件和信号组。正如下图所示 &#xff0c; “ Waveform ”窗口会显示…

牛客2024 【牛客赛文X】春招冲刺 ONT84 子数组的最小值之和【中等 单调栈 Java、Go、PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/a7401d0dd4ec4071a31fd434e150bcc2 思路 单调栈解决的问题单调栈解决的问题是在一个数组中想知道所有数中&#xff0c; 左边离他近的比他大的和右边离他近的比他大的数 思考的问题&#xff1a;如果知道所有数上…

移植speexdsp到OpenHarmony标准系统④

五、在OpenHarmony编译体系下增量编译Speexdsp 建议先增量编译生成三方库的动态链接库和可执行文件,验证是否成功把三方库加入OpenHarmonybian编译体系。 成功编译出so和可执行文件&#xff0c;即成功把三方库加入到ohos编译体系。之后还要验证三方库在ohos运行&#xff0c;功…

动态IP代理API的应用与优点

“动态”意味着每次连接或每隔一段时间&#xff0c;用户的IP地址都会发生改变。由于IP地址的不断变化&#xff0c;用户可以避免因频繁访问同一网站而导致的IP被封锁的问题。API叫做应用程序接口&#xff0c;是一种让软件之间相互通信的接口。API允许用户通过编程方式来调用动态…

单细胞RNA测序(scRNA-seq)cellranger count的细胞定量和aggr整合

单细胞RNA测序(scRNA-seq)基础知识可查看以下文章: 单细胞RNA测序(scRNA-seq)工作流程入门 单细胞RNA测序(scRNA-seq)细胞分离与扩增 单细胞RNA测序(scRNA-seq)SRA数据下载及fastq-dumq数据拆分 单细胞RNA测序(scRNA-seq)Cellranger流程入门和数据质控 细胞定量…

【C语言】每日一题,快速提升(2)!

&#x1f525;博客主页&#x1f525;&#xff1a;【 坊钰_CSDN博客 】 欢迎各位点赞&#x1f44d;评论✍收藏⭐ 题目&#xff1a;杨氏矩阵 有一个数字矩阵&#xff0c;矩阵的每行从左到右是递增的&#xff0c;矩阵从上到下是递增的&#xff0c;请编写程序在这样的矩阵中查找某个…

SpringCloud的使用以及五大核心组件

一、SpringCloud介绍 微服务架构的提出者&#xff1a;马丁福勒 https://martinfowler.com/articles/microservices.html // 微服务架构的提出者&#xff1a;马丁福勒&#xff08;中午网&#xff09; http://blog.cuicc.com/blog/2015/07/22/microservices/ 马丁.福勒对微服务…

住宅IP代理和数据中心/机房IP代理之间的区别

一、什么是数据中心/机房IP代理&#xff1f; 数据中心/机房IP代理是使用数据中心拥有并进行分配和管理的IP的代理&#xff0c;俗称机房IP代理。 二、数据中心/机房IP代理的特点 与住宅代理通过使用ISP拥有和分配的IP地址的设备路由请求的情况不同&#xff0c;数据中心代理利…

企业管理员工微信必备

在微信私域管理系统后台&#xff0c;管理员可以对销售工作微信进行实时监管&#xff0c;以确保业务员的微信使用符合工作要求&#xff0c;并避免资源的浪费。通过监管业务员在手机端微信的一举一动&#xff0c;包括发送会话的次数、接收消息的次数、添加好友的数据等&#xff0…

Kotlin从0到1,让你一周快速上手!!

声明 大家好&#xff0c;这里是懒羊羊学长&#xff0c;如果需要pdf版以及其他资料&#xff0c;请加入群聊。群里每天更新面经、求职资料&#xff0c;经验分享等&#xff0c;大家感兴趣可以加一下。 Kotlin 声明1.Kotlin基础2. Kotlin函数3.Kotlin进阶4.Kotlin集合5.Kotlin高…

更改ip地址的几种方式有哪些

在数字化时代&#xff0c;IP地址作为网络设备的标识&#xff0c;对于我们在网络世界中的活动至关重要。然而&#xff0c;出于多种原因&#xff0c;如保护隐私、访问特定网站或进行网络测试&#xff0c;我们可能需要更改IP地址。虎观代理将详细介绍IP地址的更改方法与步骤&#…

纯golang开发的mqtt server

Mochi-MQTT Server github地址&#xff1a;https://github.com/mochi-mqtt/server Mochi-MQTT 是一个完全兼容的、可嵌入的高性能 Go MQTT v5&#xff08;以及 v3.1.1&#xff09;中间件/服务器。 Mochi MQTT 是一个完全兼容 MQTT v5 的可嵌入的中间件/服务器&#xff0c;完…

使用colab进行yolov5小demo练习

输入一张动物的图片进行目标检测和分类 !pip install yolov5 import torch from PIL import Image from torchvision import transforms from yolov5.models.experimental import attempt_load from yolov5.utils.general import non_max_suppression# 加载YOLOv5模型 device …

PLC远程通信:实现工业自动化的关键技术

在当今高度信息化和自动化的时代&#xff0c;工业领域对于实时数据的准确传输和迅速响应提出了更高要求。而PLC(可编程逻辑控制器)远程通信技术&#xff0c;正是能够实现工业自动化的关键技术之一。 首先&#xff0c;我们需要了解PLC远程通信的原理。PLC作为一种专用计算机控制…
最新文章