BackTrader 中文文档(二十一)(3)https://developer.aliyun.com/article/1505419
方法 2
制作一个小脚本(请参见下面的完整代码)以更好地控制我们的操作。但结果是一样的。
核心相当小:
cerebro = bt.Cerebro() for ticker in args.tickers.split(','): data = bt.feeds.YahooFinanceData(dataname=ticker, fromdate=fromdate, todate=todate) cerebro.adddata(data) cerebro.addanalyzer(Screener_SMA, period=args.period) cerebro.run(runonce=False, stdstats=False, writer=True)
其余大部分是关于参数解析的。
对于 10
天(再次缩短输出):
$ ./st-screener.py =============================================================================== Cerebro: ... ... ... - Screener_SMA: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Params: - period: 10 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Analysis: - over: (u'NVDA', 63.04, 58.327), (u'AAPL', 108.18, 106.926), (u'YHOO', 42.94, 39.629000000000005), (u'IBM', 161.95, 161.221), (u'ORCL', 41.09, 41.032) - under: (u'TSLA', 224.91, 228.423)
结果相同。所以让我们避免重复为 50
天做这个。
结论
方法 1的btrun
和方法 2的小脚本都使用完全相同的analyzer,因此提供相同的结果。
而backtrader已经能够应对又一个小挑战。
两个最后的注意事项:
- 这两种方法都使用内置的writer功能来提供输出。
- 作为
btrun
的参数,带有--writer
。 - 作为参数传递给
cerebro.run
时,带有writer=True
。
- 在两种情况下,
runonce
都已被停用。这是为了确保在线数据保持同步,因为结果可能具有不同的长度(其中一个股票可能交易较少)。
脚本用法
$ ./st-screener.py --help usage: st-screener.py [-h] [--tickers TICKERS] [--period PERIOD] SMA Stock Screener optional arguments: -h, --help show this help message and exit --tickers TICKERS Yahoo Tickers to consider, COMMA separated (default: YHOO,IBM,AAPL,TSLA,ORCL,NVDA) --period PERIOD SMA period (default: 10)
完整的脚本
#!/usr/bin/env python # -*- coding: utf-8; py-indent-offset:4 -*- ############################################################################### # # Copyright (C) 2015, 2016 Daniel Rodriguez # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # ############################################################################### from __future__ import (absolute_import, division, print_function, unicode_literals) import argparse import datetime import backtrader as bt class Screener_SMA(bt.Analyzer): params = dict(period=10) def start(self): self.smas = {data: bt.indicators.SMA(data, period=self.p.period) for data in self.datas} def stop(self): self.rets['over'] = list() self.rets['under'] = list() for data, sma in self.smas.items(): node = data._name, data.close[0], sma[0] if data > sma: # if data.close[0] > sma[0] self.rets['over'].append(node) else: self.rets['under'].append(node) DEFAULTTICKERS = ['YHOO', 'IBM', 'AAPL', 'TSLA', 'ORCL', 'NVDA'] def run(args=None): args = parse_args(args) todate = datetime.date.today() # Get from date from period +X% for weekeends/bank/holidays: let's double fromdate = todate - datetime.timedelta(days=args.period * 2) cerebro = bt.Cerebro() for ticker in args.tickers.split(','): data = bt.feeds.YahooFinanceData(dataname=ticker, fromdate=fromdate, todate=todate) cerebro.adddata(data) cerebro.addanalyzer(Screener_SMA, period=args.period) cerebro.run(runonce=False, stdstats=False, writer=True) def parse_args(pargs=None): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, description='SMA Stock Screener') parser.add_argument('--tickers', required=False, action='store', default=','.join(DEFAULTTICKERS), help='Yahoo Tickers to consider, COMMA separated') parser.add_argument('--period', required=False, action='store', type=int, default=10, help=('SMA period')) if pargs is not None: return parser.parse_args(pargs) return parser.parse_args() if __name__ == '__main__': run()
带有信号的策略
原文:
www.backtrader.com/blog/posts/2016-08-01-signal-strategy/signal-strategy/
也可以在不编写策略的情况下操作backtrader。尽管这是首选方式,由于构成机器的对象层次结构,使用信号也是可能的。
注意
从版本1.8.0.x
起可用
快速总结:
- 不是编写一个策略类,实例化指标,编写买入/卖出逻辑…
- 最终用户添加信号(无论如何是指标),其余工作在后台完成
快速示例:
import backtrader as bt data = bt.feeds.OneOfTheFeeds(dataname='mydataname') cerebro.adddata(data) cerebro.add_signal(bt.SIGNAL_LONGSHORT, MySignal) cerebro.run()
Et voilá!。
当然,信号本身是缺失的。让我们定义一个非常简单的信号,它产生:
- 如果
收盘
价格高于简单移动平均线,则为买入
指示 - 如果
收盘
价格低于简单移动平均线,则为卖出
指示
定义:
class MySignal(bt.Indicator): lines = ('signal',) params = (('period', 30),) def __init__(self): self.lines.signal = self.data - bt.indicators.SMA(period=self.p.period)
现在真的完成了。当执行run
时,Cerebro将负责实例化一个特殊的策略实例,该实例知道如何处理信号。
初始常见问题
- 买入/卖出操作的成交量如何确定?
cerebro实例会自动向策略添加FixedSize
调整器。最终用户可以通过cerebro.addsizer
更改调整器以改变策略 - 订单如何执行?
执行类型为市价
,有效期为直到取消
信号技术细节
从技术和理论角度来看可以描述为:
- 调用时返回另一个对象的可调用函数(仅一次)
这在大多数情况下是一个类的实例化,但不一定是 - 支持
__getitem__
接口。唯一请求的键/索引将是0
从实际角度看,看上面的示例,信号是:
- 来自backtrader生态系统的lines对象,主要是指标
当使用其他指标时,比如在示例中使用简单移动平均线时,这很有帮助。
信号指示
当使用signal[0]
查询信号时,会提供指示,含义是:
> 0
->买入指示
´< 0
->卖出指示
´== 0
-> 无指示
示例使用self.data - SMA
进行简单算术运算,并且:
- 当
数据
高于SMA
时发出买入指示
- 当
数据
低于SMA
时发出卖出指示
注意
当未指定特定价格字段用于数据
时,收盘
价格是参考价格。
信号类型
下面示例中指示的常量直接从主backtrader模块中获取,如:
import backtrader as bt bt.SIGNAL_LONG
有 5 种信号类型,分为 2 组。
主要组:
LONGSHORT
:此信号的买入
和卖出
指示都被采纳LONG
:
买入
指示用于开多头头寸卖出
指示用于平仓多头头寸。但是:- 如果系统中存在
LONGEXIT
(见下文)信号,将用于退出多头头寸 - 如果有
SHORT
信号可用且没有LONGEXIT
可用,则会用于在开空头之前关闭多头
空头
:
空头
指示被用来做空多头
指示被用来关闭空头头寸。但是:- 如果系统中存在
SHORTEXIT
信号,则将用于退出空头 - 如果有
LONG
信号可用且没有SHORTEXIT
可用,则会用于在开多头之前关闭空头
退出组:
这两个信号旨在覆盖其他信号,并提供退出多头
/空头
头寸的标准
LONGEXIT
:空头
指示被用来退出多头
头寸SHORTEXIT
:多头
指示被用来退出空头头寸
积累和订单并发
上面显示的Signal示例将不断发出多头和空头指示,因为它只是从SMA
值中减去close
价格,这将始终是> 0
和< 0
(有几次== 0
)
这将导致持续生成订单,产生 2 种情况:
积累
:即使已经在市场中,信号也会产生新订单,这将增加市场中的头寸并发
:新订单将被生成,而不必等待其他订单的执行
为了避免这种情况,默认行为是:
- 不积累
- 不允许并发
如果希望实现这两种行为中的任何一种,可以通过cerebro
进行控制:
cerebro.signal_accumulate(True)
(或False
以重新禁用它)cerebro.signal_concurrency(True)
(或False
以重新禁用它)
示例
backtrader源代码包含一个用于测试功能的示例。
要使用的主要信号。
class SMACloseSignal(bt.Indicator): lines = ('signal',) params = (('period', 30),) def __init__(self): self.lines.signal = self.data - bt.indicators.SMA(period=self.p.period)
以及如果指定了选项,则退出信号。
class SMAExitSignal(bt.Indicator): lines = ('signal',) params = (('p1', 5), ('p2', 30),) def __init__(self): sma1 = bt.indicators.SMA(period=self.p.p1) sma2 = bt.indicators.SMA(period=self.p.p2) self.lines.signal = sma1 - sma2
第一次运行:多头和空头
$ ./signals-strategy.py --plot --signal longshort
输出
注意:
- Signal被绘制。这是正常的,因为它只是一个指标,适用于它的绘图规则
- 策略实际上是
多头
和空头
。这可以看出,因为现金水平从未回到价值水平 - 旁注:即使是一个愚蠢的想法…(并且没有佣金),策略也没有亏钱…
第二次运行:仅多头
$ ./signals-strategy.py --plot --signal longonly
输出
注意:
- 这里每次卖出后现金水平都会回到价值水平,这意味着策略已经退出市场
- 旁注:再次没有损失金钱…
第三次运行:仅空头
$ ./signals-strategy.py --plot --signal shortonly
输出
注意:
- 第 1 次操作是一个卖出,如预期的那样,比前面 2 个示例中的第 1 次操作晚发生。直到
close
低于SMA
且简单的减法产生负数时才会发生 - 这里每次买入后现金水平都会回到价值水平,这意味着策略已经退出市场
- 旁注:最终系统会亏钱
第四次运行:多头 + 多头退出
$ ./signals-strategy.py --plot --signal longonly --exitsignal longexit
输出
注意:
- 许多交易都是相同的,但有些会提前中断,因为快速移动平均线在退出信号中向下穿越慢速移动平均线。
- 系统展现了其longonly属性,现金在每笔交易结束时成为价值。
- 旁注:再次提到金钱……即使进行了一些修改的交易
用法
$ ./signals-strategy.py --help usage: signals-strategy.py [-h] [--data DATA] [--fromdate FROMDATE] [--todate TODATE] [--cash CASH] [--smaperiod SMAPERIOD] [--exitperiod EXITPERIOD] [--signal {longshort,longonly,shortonly}] [--exitsignal {longexit,shortexit}] [--plot [kwargs]] Sample for Signal concepts optional arguments: -h, --help show this help message and exit --data DATA Specific data to be read in (default: ../../datas/2005-2006-day-001.txt) --fromdate FROMDATE Starting date in YYYY-MM-DD format (default: None) --todate TODATE Ending date in YYYY-MM-DD format (default: None) --cash CASH Cash to start with (default: 50000) --smaperiod SMAPERIOD Period for the moving average (default: 30) --exitperiod EXITPERIOD Period for the exit control SMA (default: 5) --signal {longshort,longonly,shortonly} Signal type to use for the main signal (default: longshort) --exitsignal {longexit,shortexit} Signal type to use for the exit signal (default: None) --plot [kwargs], -p [kwargs] Plot the read data applying any kwargs passed For example: --plot style="candle" (to plot candles) (default: None)
代码
from __future__ import (absolute_import, division, print_function, unicode_literals) import argparse import collections import datetime import backtrader as bt MAINSIGNALS = collections.OrderedDict( (('longshort', bt.SIGNAL_LONGSHORT), ('longonly', bt.SIGNAL_LONG), ('shortonly', bt.SIGNAL_SHORT),) ) EXITSIGNALS = { 'longexit': bt.SIGNAL_LONGEXIT, 'shortexit': bt.SIGNAL_LONGEXIT, } class SMACloseSignal(bt.Indicator): lines = ('signal',) params = (('period', 30),) def __init__(self): self.lines.signal = self.data - bt.indicators.SMA(period=self.p.period) class SMAExitSignal(bt.Indicator): lines = ('signal',) params = (('p1', 5), ('p2', 30),) def __init__(self): sma1 = bt.indicators.SMA(period=self.p.p1) sma2 = bt.indicators.SMA(period=self.p.p2) self.lines.signal = sma1 - sma2 def runstrat(args=None): args = parse_args(args) cerebro = bt.Cerebro() cerebro.broker.set_cash(args.cash) dkwargs = dict() if args.fromdate is not None: fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d') dkwargs['fromdate'] = fromdate if args.todate is not None: todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d') dkwargs['todate'] = todate # if dataset is None, args.data has been given data = bt.feeds.BacktraderCSVData(dataname=args.data, **dkwargs) cerebro.adddata(data) cerebro.add_signal(MAINSIGNALS[args.signal], SMACloseSignal, period=args.smaperiod) if args.exitsignal is not None: cerebro.add_signal(EXITSIGNALS[args.exitsignal], SMAExitSignal, p1=args.exitperiod, p2=args.smaperiod) cerebro.run() if args.plot: pkwargs = dict(style='bar') if args.plot is not True: # evals to True but is not True npkwargs = eval('dict(' + args.plot + ')') # args were passed pkwargs.update(npkwargs) cerebro.plot(**pkwargs) def parse_args(pargs=None): parser = argparse.ArgumentParser( formatter_class=argparse.ArgumentDefaultsHelpFormatter, description='Sample for Signal concepts') parser.add_argument('--data', required=False, default='../../datas/2005-2006-day-001.txt', help='Specific data to be read in') parser.add_argument('--fromdate', required=False, default=None, help='Starting date in YYYY-MM-DD format') parser.add_argument('--todate', required=False, default=None, help='Ending date in YYYY-MM-DD format') parser.add_argument('--cash', required=False, action='store', type=float, default=50000, help=('Cash to start with')) parser.add_argument('--smaperiod', required=False, action='store', type=int, default=30, help=('Period for the moving average')) parser.add_argument('--exitperiod', required=False, action='store', type=int, default=5, help=('Period for the exit control SMA')) parser.add_argument('--signal', required=False, action='store', default=MAINSIGNALS.keys()[0], choices=MAINSIGNALS, help=('Signal type to use for the main signal')) parser.add_argument('--exitsignal', required=False, action='store', default=None, choices=EXITSIGNALS, help=('Signal type to use for the exit signal')) # Plot options parser.add_argument('--plot', '-p', nargs='?', required=False, metavar='kwargs', const=True, help=('Plot the read data applying any kwargs passed\n' '\n' 'For example:\n' '\n' ' --plot style="candle" (to plot candles)\n')) if pargs is not None: return parser.parse_args(pargs) return parser.parse_args() if __name__ == '__main__': runstrat()