Backtrader中文笔记之 Strategy - Signals(二次修复)
参考连接:https://www.backtrader.com/docu/signal_strategy/signal_strategy/
前面没有很好的学习这个框架,走马观花的写了一遍,导致前面错误一堆,罪过,罪过,现在补上
该信号主要用来不独立写strategy的情况下,通过指标的形式与信号的形式进行交易
Strategy with Signals
Operating backtrader is also possible without having to write a Strategy. Although this is the preferred way, due to the object hierarchy which makes up the machinery, using Signals is also possible.
Quick summary:
-
Instead of writing a Strategy class, instantiating Indicators, writing the buy/sell logic …
-
The end user add Signals (indicators anyhow) and the rest is done in the background
- 代替编写一个策略类,实例化指标,编写买/卖逻辑…
- 最终用户添加信号(指标),其余的在后台完成
import backtrader as bt data = bt.feeds.OneOfTheFeeds(dataname='mydataname') cerebro.adddata(data)
# 直接添加信号 cerebro.add_signal(bt.SIGNAL_LONGSHORT, MySignal) cerebro.run()
Of course the Signal itself is missing. Let’s define a very dumb Signal which yields:
当然,信号本身已经消失了。让我们定义一个非常哑的信号,它会产生:
-
Long
indication if theclose
price is above a Simple Moving Average - 多头指收盘价高于简单移动平均线
-
Short
indication if theclose
price is below a Simple Moving Average - 收盘价低于简单移动平均线时的空头指示
The definition:
class MySignal(bt.Indicator): lines = ('signal',) params = (('period', 30),) def __init__(self): self.lines.signal = self.data - bt.indicators.SMA(period=self.p.period)
And now it is really done. When run
is executed Cerebro will take care of instantiating a special Strategy instance which knows what to do with the Signals.
现在它真的完成了。当运行执行时,Cerebro会实例化一个特殊的策略实例,该实例知道如何处理信号。
Initial FAQ
问题
-
How is the volume of buy/sell operations determined?
- 买卖操作的数量是如何确定的?
-
A cerebro instance adds automatically a
FixedSize
sizer to strategies. The end user can change the sizer to alter the policy withcerebro.addsizer
- cerebro实例会自动向策略添加一个固定大小的sizer。最终用户可以用
cerebro
.addsizer来改变规格来改变策略 -
How are orders executed?
- 订单是如何执行的?
-
The execution type is
Market
and the validity is Good Until Canceled - 执行类型为市场,有效期至取消
Signals technicalities
信号技术问题
From a technical and theoretical point of view can be as described:
从技术和理论的角度可以描述如下:
-
A callable that returns another object when called (only once)
- 当调用一个可调用对象,返回另外一个对象
-
This is in most cases the instantiation of a class, but must not be
- 在大多数情况下,这是类的实例化,但并不是一定这样
-
Supports the
__getitem__
interface. The only requested key/index will be0
- 支持__getitem__接口。唯一请求的键/索引为0
From a practical point of view and looking at the example above a Signal is:
从实际的角度来看,上面的例子一个信号是:
-
A lines object from the backtrader ecosystem, mostly an Indicator
- lines对象来自backtrader生态系统,主要是一个指标
This helps when using other Indicators like when in the example the Simple Moving Average is used.
- 这有助于使用其他指标,如在示例中使用简单移动平均线。
Signals indications
信号指标
The signals delivers indications when queried with signal[0]
and the meaning is:
当信号[0]询问时,该信号给出指示,其意义为:
-
> 0
->long indication
- >0为多单
-
< 0
->short indication
- <0为空单
-
== 0
-> No indication - 等于0,没有显示
The example does simple arithmetic with self.data - SMA
and:
这个例子用self.data - SMA做了简单的算术
-
Issues a
long indication
when thedata
is above theSMA
- 当数据高于SMA指数时,发出长信号
-
Issues a
short indication
when thedata
is below theSMA
- 当数据低于SMA时发出一个简短的指示
Note
When no specific price field is indicated for the data
, the close
price is the reference price is.
请注意
当数据没有指定价格字段时,收盘价为参考价格。
Signals Types
信号类型
The constants indicated below as seen in the example above, are directly available from the main backtrader module as in:
如下所示的常量如上图所示,可以直接从主backtrader模块中获得,如下所示:
import backtrader as bt bt.SIGNAL_LONG
There are 5 types of Signals, broken in 2 groups.
有5种类型的信号,分为两组。
Main Group:
主要小组
-
LONGSHORT
: bothlong
andshort
indications from this signal are taken - 长-短:从这个信号中取长和短的指标
-
LONG
:-
long
indications are taken to go long - 多头的指标会执行多单
-
short
indications are taken to close the long position. But: - 空头指标用来平仓。但是:
-
If a
LONGEXIT
(see below) signal is in the system it will be used to exit the long - 如果
LONGEXIT
(见下面)信号在系统中,它将被用来退出多单 -
If a
SHORT
signal is available and noLONGEXIT
is available , it will be used to close along
before opening ashort
-
如果有空头信号,而没有
LONGEXIT
,则在打开空头之前,它将用来关闭多头。也就是存在LONGSHORT
-
-
SHORT
:-
short
indications are taken to go short - 空头指标会执行空单
-
long
indications are taken to close the short position. But: -
If a
SHORTEXIT
(see below) signal is in the system it will be used to exit the short -
If a
LONG
signal is available and noSHORTEXIT
is available , it will be used to close ashort
before opening along
-
Exit Group:
退出小组
This 2 signals are meant to override others and provide criteria for exitins a long
/ short
position
这两个信号是用来覆盖其他信号,并提供做多/做空的标准
-
LONGEXIT
:short
indications are taken to exitlong
positions - 多头退出:空头指示用于退出多头头寸
-
SHORTEXIT
:long
indications are taken to exitshort
positions - 做空指标:多头指标用来退出空头头寸
这个主要是用来提供额外的退出标准的
Accumulation and Order Concurrency
累加和订单并发
The sample Signal shown above will issue long and short indications on a constant basis, because it simply substracts the SMA
value from the close
price and this will always be either > 0
and < 0
( 0
is mathematically possible, but unlikely to really happen)
上面显示的样本信号将在一个常数的基础上发出多和空的指示,因为它只是简单地从收盘价减去SMA值,它将总是> 0和< 0(0在数学上是可能的,但不太可能真的发生)
This would lead to a continuous generation of orders that would produce 2 situations:
这将导致订单的连续产生,从而产生两种情况:
-
Accumulation
: even if already in the market, the signals would produce new orders which would increase the possition in the market - 积累:即使已经持有订单,信号也会产生新的订单,从而增加在市场中的位置
-
Concurrency
: new orders would be generated without waiting for the execution of other orders - 并发性:在不等待其他订单执行的情况下生成新订单
To avoid this the default behavior is:
为了避免这种情况,默认行为是:
-
To Not Accumulate
- 不积累
-
To Not allow Concurrency
- 不允许并发
Should any of these two behaviors be wished, this can be controlled via cerebro
with:
如果你希望这两种行为中的任何一种,都可以通过cerebro来控制:
-
cerebro.signal_accumulate(True)
(orFalse
to re-disable it) -
cerebro.signal_concurrency(True)
(orFalse
to re-disable it)
The sample
The backtrader sources contain a sample to test the functionality.
Main signal to be used.
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)
And the Exit Signal in case the option is specified.
以及指定选项时的退出信号。
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
First run: long and short
$ ./signals-strategy.py --plot --signal longshort
The output
输出:
To notice:
-
The Signal is plotted. This is normal given it is simply an indicator and the plotting rules for it apply
- 信号被绘制出来。这是正常的,因为它只是一个指标,并且适用于它的绘图规则
-
The strategy is really
long
andshort
. This can be seen because the cash level never goes back to be the value level - 这种策略一直多单,空单。这可以看出,因为现金水平永远不会回到价值水平
-
Side note: even for a dumb idea … (and without commission) the strategy hasn’t lost money …
- 附注:即使是一个愚蠢的想法(并且没有佣金),这个策略也不会赔钱……
Second run: long only
$ ./signals-strategy.py --plot --signal longonly
The output
To notice:
注意
-
Here the cash level goes back to be the value level after each sell, which means the strategy is out of the market
- 在这里,现金水平回到每次出售后的价值水平,这意味着该策略已退出市场
-
Side note: Again no money has been lost …
- 旁注:有一次赚钱了
Third run: short only
$ ./signals-strategy.py --plot --signal shortonly
The output
To notice:
-
The 1st operation is a sell as expected and takes place later than the 1st operation in the 2 examples above. Not until the
close
is below theSMA
and the simple substraction yields a minus - 第一个预期的卖出操作,它发生的时间比上面两个示例中的第一个操作晚。直到收盘时低于SMA指数,简单的减法就会得到一个负数
-
Here the cash level goes back to be the value level after each buy, which means the strategy is out of the market
- 在这里,每次买入后,现金水平回到价值水平,这意味着策略退出了市场
-
Side note: Finally the system loses money
- 旁注:最后系统赔钱了
Fourth run: long + longexit
$ ./signals-strategy.py --plot --signal longonly --exitsignal longexit
The output
To notice:
-
Many of the trades are the same, but some are interrupted earlier because the fast moving average in the exit signal crosses the slow moving average to the downside
- 许多交易都是相同的,但有些交易中断得更早,因为出口信号中的快速移动平均线穿过缓慢移动平均线下行
-
The system shows its longonly property with the cash becoming the value at the end of each trade
- 该系统显示其长期属性,现金成为每笔交易结束时的价值
-
Side note: Again money is made … even with some modified trades
- 边注:即使是修改了一些交易,仍然可以赚钱
这其实就是给了一个独立的退出策略而已.
Usage
$ ./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)
The code
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()