Strategy with Signals
Operatingbacktrader is also possible without having to write aStrategy. Although this is the preferred way, due to the object hierarchywhich makes up the machinery, usingSignals is also possible.
Quick summary:
Instead of writing aStrategy class, instantiatingIndicators, writing thebuy/sell logic …
The end user addSignals (indicators anyhow) and the rest is done in the background
Quick example:
importbacktraderasbtdata=bt.feeds.OneOfTheFeeds(dataname='mydataname')cerebro.adddata(data)cerebro.add_signal(bt.SIGNAL_LONGSHORT,MySignal)cerebro.run()
Et voilá!.
Of course theSignal itself is missing. Let’s define a very dumbSignalwhich yields:
The definition:
classMySignal(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. Whenrun is executedCerebro will take care ofinstantiating a specialStrategy instance which knows what to do with theSignals.
InitialFAQ
How is the volume ofbuy/sell operations determined?
Acerebro instance adds automatically aFixedSize sizer tostrategies. The end user can change the sizer to alter the policy withcerebro.addsizer
How are orders executed?
The execution type isMarket and the validity isGood Until Canceled
Signals technicalities
From a technical and theoretical point of view can be as described:
A callable that returns anotherobject when called (only once)
This is in most cases the instantiation of a class, but must not be
Supports the__getitem__ interface. The only requestedkey/index will be0
From a practical point of view and looking at the example above aSignal is:
Alines object from thebacktrader ecosystem, mostly anIndicator
This helps when using otherIndicators like when in the example theSimpleMoving Average is used.
Signals indications
Thesignals delivers indications when queried withsignal[0] and themeaning is:
> 0 ->long indication
< 0 ->short indication
== 0 ->No indication
The example does simple arithmetic withself.data - SMA and:
Note
When no specific price field is indicated for thedata, theclose price is the reference price is.
Signals Types
Theconstants indicated below as seen in the example above, are directlyavailable from the mainbacktrader module as in:
importbacktraderasbtbt.SIGNAL_LONG
There are 5 types ofSignals, broken in 2 groups.
Main Group:
Exit Group:
This 2 signals are meant to override others and provide criteria for exitins along /short position
Accumulation and Order Concurrency
The sampleSignal shown above will issuelong andshort indications on aconstant basis, because it simply substracts theSMA value from theclose price and this will always be either> 0 and< 0 (0 ismathematically possible, but unlikely to really happen)
This would lead to a continuous generation oforders that would produce 2situations:
Accumulation: even if already in the market, thesignals 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 viacerebro with:
The sample
Thebacktrader sources contain a sample to test the functionality.
Main signal to be used.
classSMACloseSignal(bt.Indicator):lines=('signal',)params=(('period',30),)def__init__(self):self.lines.signal=self.data-bt.indicators.SMA(period=self.p.period)
And theExit Signal in case the option is specified.
classSMAExitSignal(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:
TheSignal is plotted. This is normal given it is simply an indicator and the plotting rules for it apply
The strategy is reallylong andshort. This can be seen because thecash level never goes back to be thevalue 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 thevalue level after eachsell, 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 asell as expected and takes place later than the 1st operation in the 2 examples above. Not until theclose is below theSMA and the simple substraction yields a minus
Here the cash level goes back to be thevalue level after eachbuy, 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 theexit signal crosses the slow moving average to the downside
The system shows itslongonly 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 --helpusage: 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 conceptsoptional 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)importargparseimportcollectionsimportdatetimeimportbacktraderasbtMAINSIGNALS=collections.OrderedDict((('longshort',bt.SIGNAL_LONGSHORT),('longonly',bt.SIGNAL_LONG),('shortonly',bt.SIGNAL_SHORT),))EXITSIGNALS={'longexit':bt.SIGNAL_LONGEXIT,'shortexit':bt.SIGNAL_LONGEXIT,}classSMACloseSignal(bt.Indicator):lines=('signal',)params=(('period',30),)def__init__(self):self.lines.signal=self.data-bt.indicators.SMA(period=self.p.period)classSMAExitSignal(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-sma2defrunstrat(args=None):args=parse_args(args)cerebro=bt.Cerebro()cerebro.broker.set_cash(args.cash)dkwargs=dict()ifargs.fromdateisnotNone:fromdate=datetime.datetime.strptime(args.fromdate,'%Y-%m-%d')dkwargs['fromdate']=fromdateifargs.todateisnotNone:todate=datetime.datetime.strptime(args.todate,'%Y-%m-%d')dkwargs['todate']=todate# if dataset is None, args.data has been givendata=bt.feeds.BacktraderCSVData(dataname=args.data,**dkwargs)cerebro.adddata(data)cerebro.add_signal(MAINSIGNALS[args.signal],SMACloseSignal,period=args.smaperiod)ifargs.exitsignalisnotNone:cerebro.add_signal(EXITSIGNALS[args.exitsignal],SMAExitSignal,p1=args.exitperiod,p2=args.smaperiod)cerebro.run()ifargs.plot:pkwargs=dict(style='bar')ifargs.plotisnotTrue:# evals to True but is not Truenpkwargs=eval('dict('+args.plot+')')# args were passedpkwargs.update(npkwargs)cerebro.plot(**pkwargs)defparse_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 optionsparser.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'))ifpargsisnotNone:returnparser.parse_args(pargs)returnparser.parse_args()if__name__=='__main__':runstrat()