Module backtrader.strategy
Expand source code
#!/usr/bin389/env python
# -*- coding: utf-8; py-indent-offset:4 -*-
###############################################################################
#
# Copyright (C) 2015-2023 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 collections
import copy
import datetime
import inspect
import itertools
import operator
from .utils.py3 import (filter, keys, integer_types, iteritems, itervalues,
map, MAXINT, string_types, with_metaclass)
import backtrader as bt
from .lineiterator import LineIterator, StrategyBase
from .lineroot import LineSingle
from .lineseries import LineSeriesStub
from .metabase import ItemCollection, findowner
from .trade import Trade
from .utils import OrderedDict, AutoOrderedDict, AutoDictList
class MetaStrategy(StrategyBase.__class__):
_indcol = dict()
def __new__(meta, name, bases, dct):
# Hack to support original method name for notify_order
if 'notify' in dct:
# rename 'notify' to 'notify_order'
dct['notify_order'] = dct.pop('notify')
if 'notify_operation' in dct:
# rename 'notify' to 'notify_order'
dct['notify_trade'] = dct.pop('notify_operation')
return super(MetaStrategy, meta).__new__(meta, name, bases, dct)
def __init__(cls, name, bases, dct):
'''
Class has already been created ... register subclasses
'''
# Initialize the class
super(MetaStrategy, cls).__init__(name, bases, dct)
if not cls.aliased and \
name != 'Strategy' and not name.startswith('_'):
cls._indcol[name] = cls
def donew(cls, *args, **kwargs):
_obj, args, kwargs = super(MetaStrategy, cls).donew(*args, **kwargs)
# Find the owner and store it
_obj.env = _obj.cerebro = cerebro = findowner(_obj, bt.Cerebro)
_obj._id = cerebro._next_stid()
return _obj, args, kwargs
def dopreinit(cls, _obj, *args, **kwargs):
_obj, args, kwargs = \
super(MetaStrategy, cls).dopreinit(_obj, *args, **kwargs)
_obj.broker = _obj.env.broker
_obj._sizer = bt.sizers.FixedSize()
_obj._orders = list()
_obj._orderspending = list()
_obj._trades = collections.defaultdict(AutoDictList)
_obj._tradespending = list()
_obj.stats = _obj.observers = ItemCollection()
_obj.analyzers = ItemCollection()
_obj._alnames = collections.defaultdict(itertools.count)
_obj.writers = list()
_obj._slave_analyzers = list()
_obj._tradehistoryon = False
return _obj, args, kwargs
def dopostinit(cls, _obj, *args, **kwargs):
_obj, args, kwargs = \
super(MetaStrategy, cls).dopostinit(_obj, *args, **kwargs)
_obj._sizer.set(_obj, _obj.broker)
return _obj, args, kwargs
class Strategy(with_metaclass(MetaStrategy, StrategyBase)):
'''
Base class to be subclassed for user defined strategies.
'''
_ltype = LineIterator.StratType
csv = True
_oldsync = False # update clock using old methodology : data 0
# keep the latest delivered data date in the line
lines = ('datetime',)
def qbuffer(self, savemem=0, replaying=False):
'''Enable the memory saving schemes. Possible values for ``savemem``:
0: No savings. Each lines object keeps in memory all values
1: All lines objects save memory, using the strictly minimum needed
Negative values are meant to be used when plotting is required:
-1: Indicators at Strategy Level and Observers do not enable memory
savings (but anything declared below it does)
-2: Same as -1 plus activation of memory saving for any indicators
which has declared *plotinfo.plot* as False (will not be plotted)
'''
if savemem < 0:
# Get any attribute which labels itself as Indicator
for ind in self._lineiterators[self.IndType]:
subsave = isinstance(ind, (LineSingle,))
if not subsave and savemem < -1:
subsave = not ind.plotinfo.plot
ind.qbuffer(savemem=subsave)
elif savemem > 0:
for data in self.datas:
data.qbuffer(replaying=replaying)
for line in self.lines:
line.qbuffer(savemem=1)
# Save in all object types depending on the strategy
for itcls in self._lineiterators:
for it in self._lineiterators[itcls]:
it.qbuffer(savemem=1)
def _periodset(self):
dataids = [id(data) for data in self.datas]
_dminperiods = collections.defaultdict(list)
for lineiter in self._lineiterators[LineIterator.IndType]:
# if multiple datas are used and multiple timeframes the larger
# timeframe may place larger time constraints in calling next.
clk = getattr(lineiter, '_clock', None)
if clk is None:
clk = getattr(lineiter._owner, '_clock', None)
if clk is None:
continue
while True:
if id(clk) in dataids:
break # already top-level clock (data feed)
# See if the current clock has higher level clocks
clk2 = getattr(clk, '_clock', None)
if clk2 is None:
clk2 = getattr(clk._owner, '_clock', None)
if clk2 is None:
break # if no clock found, bail out
clk = clk2 # keep the ref and try to go up the hierarchy
if clk is None:
continue # no clock found, go to next
# LineSeriesStup wraps a line and the clock is the wrapped line and
# no the wrapper itself.
if isinstance(clk, LineSeriesStub):
clk = clk.lines[0]
_dminperiods[clk].append(lineiter._minperiod)
self._minperiods = list()
for data in self.datas:
# Do not only consider the data as clock but also its lines which
# may have been individually passed as clock references and
# discovered as clocks above
# Initialize with data min period if any
dlminperiods = _dminperiods[data]
for l in data.lines: # search each line for min periods
if l in _dminperiods:
dlminperiods += _dminperiods[l] # found, add it
# keep the reference to the line if any was found
_dminperiods[data] = [max(dlminperiods)] if dlminperiods else []
dminperiod = max(_dminperiods[data] or [data._minperiod])
self._minperiods.append(dminperiod)
# Set the minperiod
minperiods = \
[x._minperiod for x in self._lineiterators[LineIterator.IndType]]
self._minperiod = max(minperiods or [self._minperiod])
def _addwriter(self, writer):
'''
Unlike the other _addxxx functions this one receives an instance
because the writer works at cerebro level and is only passed to the
strategy to simplify the logic
'''
self.writers.append(writer)
def _addindicator(self, indcls, *indargs, **indkwargs):
indcls(*indargs, **indkwargs)
def _addanalyzer_slave(self, ancls, *anargs, **ankwargs):
'''Like _addanalyzer but meant for observers (or other entities) which
rely on the output of an analyzer for the data. These analyzers have
not been added by the user and are kept separate from the main
analyzers
Returns the created analyzer
'''
analyzer = ancls(*anargs, **ankwargs)
self._slave_analyzers.append(analyzer)
return analyzer
def _getanalyzer_slave(self, idx):
return self._slave_analyzers.append[idx]
def _addanalyzer(self, ancls, *anargs, **ankwargs):
anname = ankwargs.pop('_name', '') or ancls.__name__.lower()
nsuffix = next(self._alnames[anname])
anname += str(nsuffix or '') # 0 (first instance) gets no suffix
analyzer = ancls(*anargs, **ankwargs)
self.analyzers.append(analyzer, anname)
def _addobserver(self, multi, obscls, *obsargs, **obskwargs):
obsname = obskwargs.pop('obsname', '')
if not obsname:
obsname = obscls.__name__.lower()
if not multi:
newargs = list(itertools.chain(self.datas, obsargs))
obs = obscls(*newargs, **obskwargs)
self.stats.append(obs, obsname)
return
setattr(self.stats, obsname, list())
l = getattr(self.stats, obsname)
for data in self.datas:
obs = obscls(data, *obsargs, **obskwargs)
l.append(obs)
def _getminperstatus(self):
# check the min period status connected to datas
dlens = map(operator.sub, self._minperiods, map(len, self.datas))
self._minperstatus = minperstatus = max(dlens)
return minperstatus
def prenext_open(self):
pass
def nextstart_open(self):
self.next_open()
def next_open(self):
pass
def _oncepost_open(self):
minperstatus = self._minperstatus
if minperstatus < 0:
self.next_open()
elif minperstatus == 0:
self.nextstart_open() # only called for the 1st value
else:
self.prenext_open()
def _oncepost(self, dt):
for indicator in self._lineiterators[LineIterator.IndType]:
if len(indicator._clock) > len(indicator):
indicator.advance()
if self._oldsync:
# Strategy has not been reset, the line is there
self.advance()
else:
# strategy has been reset to beginning. advance step by step
self.forward()
self.lines.datetime[0] = dt
self._notify()
minperstatus = self._getminperstatus()
if minperstatus < 0:
self.next()
elif minperstatus == 0:
self.nextstart() # only called for the 1st value
else:
self.prenext()
self._next_analyzers(minperstatus, once=True)
self._next_observers(minperstatus, once=True)
self.clear()
def _clk_update(self):
if self._oldsync:
clk_len = super(Strategy, self)._clk_update()
self.lines.datetime[0] = max(d.datetime[0]
for d in self.datas if len(d))
return clk_len
newdlens = [len(d) for d in self.datas]
if any(nl > l for l, nl in zip(self._dlens, newdlens)):
self.forward()
self.lines.datetime[0] = max(d.datetime[0]
for d in self.datas if len(d))
self._dlens = newdlens
return len(self)
def _next_open(self):
minperstatus = self._minperstatus
if minperstatus < 0:
self.next_open()
elif minperstatus == 0:
self.nextstart_open() # only called for the 1st value
else:
self.prenext_open()
def _next(self):
super(Strategy, self)._next()
minperstatus = self._getminperstatus()
self._next_analyzers(minperstatus)
self._next_observers(minperstatus)
self.clear()
def _next_observers(self, minperstatus, once=False):
for observer in self._lineiterators[LineIterator.ObsType]:
for analyzer in observer._analyzers:
if minperstatus < 0:
analyzer._next()
elif minperstatus == 0:
analyzer._nextstart() # only called for the 1st value
else:
analyzer._prenext()
if once:
if len(self) > len(observer):
if self._oldsync:
observer.advance()
else:
observer.forward()
if minperstatus < 0:
observer.next()
elif minperstatus == 0:
observer.nextstart() # only called for the 1st value
elif len(observer):
observer.prenext()
else:
observer._next()
def _next_analyzers(self, minperstatus, once=False):
for analyzer in self.analyzers:
if minperstatus < 0:
analyzer._next()
elif minperstatus == 0:
analyzer._nextstart() # only called for the 1st value
else:
analyzer._prenext()
def _settz(self, tz):
self.lines.datetime._settz(tz)
def _start(self):
self._periodset()
for analyzer in itertools.chain(self.analyzers, self._slave_analyzers):
analyzer._start()
for obs in self.observers:
if not isinstance(obs, list):
obs = [obs] # support of multi-data observers
for o in obs:
o._start()
# change operators to stage 2
self._stage2()
self._dlens = [len(data) for data in self.datas]
self._minperstatus = MAXINT # start in prenext
self.start()
def start(self):
'''Called right before the backtesting is about to be started.'''
pass
def getwriterheaders(self):
self.indobscsv = [self]
indobs = itertools.chain(
self.getindicators_lines(), self.getobservers())
self.indobscsv.extend(filter(lambda x: x.csv, indobs))
headers = list()
# prepare the indicators/observers data headers
for iocsv in self.indobscsv:
name = iocsv.plotinfo.plotname or iocsv.__class__.__name__
headers.append(name)
headers.append('len')
headers.extend(iocsv.getlinealiases())
return headers
def getwritervalues(self):
values = list()
for iocsv in self.indobscsv:
name = iocsv.plotinfo.plotname or iocsv.__class__.__name__
values.append(name)
lio = len(iocsv)
values.append(lio)
if lio:
values.extend(map(lambda l: l[0], iocsv.lines.itersize()))
else:
values.extend([''] * iocsv.lines.size())
return values
def getwriterinfo(self):
wrinfo = AutoOrderedDict()
wrinfo['Params'] = self.p._getkwargs()
sections = [
['Indicators', self.getindicators_lines()],
['Observers', self.getobservers()]
]
for sectname, sectitems in sections:
sinfo = wrinfo[sectname]
for item in sectitems:
itname = item.__class__.__name__
sinfo[itname].Lines = item.lines.getlinealiases() or None
sinfo[itname].Params = item.p._getkwargs() or None
ainfo = wrinfo.Analyzers
# Internal Value Analyzer
ainfo.Value.Begin = self.broker.startingcash
ainfo.Value.End = self.broker.getvalue()
# no slave analyzers for writer
for aname, analyzer in self.analyzers.getitems():
ainfo[aname].Params = analyzer.p._getkwargs() or None
ainfo[aname].Analysis = analyzer.get_analysis()
return wrinfo
def _stop(self):
self.stop()
for analyzer in itertools.chain(self.analyzers, self._slave_analyzers):
analyzer._stop()
# change operators back to stage 1 - allows reuse of datas
self._stage1()
def stop(self):
'''Called right before the backtesting is about to be stopped'''
pass
def set_tradehistory(self, onoff=True):
self._tradehistoryon = onoff
def clear(self):
self._orders.extend(self._orderspending)
self._orderspending = list()
self._tradespending = list()
def _addnotification(self, order, quicknotify=False):
if not order.p.simulated:
self._orderspending.append(order)
if quicknotify:
qorders = [order]
qtrades = []
if not order.executed.size:
if quicknotify:
self._notify(qorders=qorders, qtrades=qtrades)
return
tradedata = order.data._compensate
if tradedata is None:
tradedata = order.data
datatrades = self._trades[tradedata][order.tradeid]
if not datatrades:
trade = Trade(data=tradedata, tradeid=order.tradeid,
historyon=self._tradehistoryon)
datatrades.append(trade)
else:
trade = datatrades[-1]
for exbit in order.executed.iterpending():
if exbit is None:
break
if exbit.closed:
trade.update(order,
exbit.closed,
exbit.price,
exbit.closedvalue,
exbit.closedcomm,
exbit.pnl,
comminfo=order.comminfo)
if trade.isclosed:
self._tradespending.append(copy.copy(trade))
if quicknotify:
qtrades.append(copy.copy(trade))
# Update it if needed
if exbit.opened:
if trade.isclosed:
trade = Trade(data=tradedata, tradeid=order.tradeid,
historyon=self._tradehistoryon)
datatrades.append(trade)
trade.update(order,
exbit.opened,
exbit.price,
exbit.openedvalue,
exbit.openedcomm,
exbit.pnl,
comminfo=order.comminfo)
# This extra check covers the case in which different tradeid
# orders have put the position down to 0 and the next order
# "opens" a position but "closes" the trade
if trade.isclosed:
self._tradespending.append(copy.copy(trade))
if quicknotify:
qtrades.append(copy.copy(trade))
if trade.justopened:
self._tradespending.append(copy.copy(trade))
if quicknotify:
qtrades.append(copy.copy(trade))
if quicknotify:
self._notify(qorders=qorders, qtrades=qtrades)
def _notify(self, qorders=[], qtrades=[]):
if self.cerebro.p.quicknotify:
# need to know if quicknotify is on, to not reprocess pendingorders
# and pendingtrades, which have to exist for things like observers
# which look into it
procorders = qorders
proctrades = qtrades
else:
procorders = self._orderspending
proctrades = self._tradespending
for order in procorders:
if order.exectype != order.Historical or order.histnotify:
self.notify_order(order)
for analyzer in itertools.chain(self.analyzers,
self._slave_analyzers):
analyzer._notify_order(order)
for trade in proctrades:
self.notify_trade(trade)
for analyzer in itertools.chain(self.analyzers,
self._slave_analyzers):
analyzer._notify_trade(trade)
if qorders:
return # cash is notified on a regular basis
cash = self.broker.getcash()
value = self.broker.getvalue()
fundvalue = self.broker.fundvalue
fundshares = self.broker.fundshares
self.notify_cashvalue(cash, value)
self.notify_fund(cash, value, fundvalue, fundshares)
for analyzer in itertools.chain(self.analyzers, self._slave_analyzers):
analyzer._notify_cashvalue(cash, value)
analyzer._notify_fund(cash, value, fundvalue, fundshares)
def add_timer(self, when,
offset=datetime.timedelta(), repeat=datetime.timedelta(),
weekdays=[], weekcarry=False,
monthdays=[], monthcarry=True,
allow=None,
tzdata=None, cheat=False,
*args, **kwargs):
'''
**Note**: can be called during ``__init__`` or ``start``
Schedules a timer to invoke either a specified callback or the
``notify_timer`` of one or more strategies.
Arguments:
- ``when``: can be
- ``datetime.time`` instance (see below ``tzdata``)
- ``bt.timer.SESSION_START`` to reference a session start
- ``bt.timer.SESSION_END`` to reference a session end
- ``offset`` which must be a ``datetime.timedelta`` instance
Used to offset the value ``when``. It has a meaningful use in
combination with ``SESSION_START`` and ``SESSION_END``, to indicated
things like a timer being called ``15 minutes`` after the session
start.
- ``repeat`` which must be a ``datetime.timedelta`` instance
Indicates if after a 1st call, further calls will be scheduled
within the same session at the scheduled ``repeat`` delta
Once the timer goes over the end of the session it is reset to the
original value for ``when``
- ``weekdays``: a **sorted** iterable with integers indicating on
which days (iso codes, Monday is 1, Sunday is 7) the timers can
be actually invoked
If not specified, the timer will be active on all days
- ``weekcarry`` (default: ``False``). If ``True`` and the weekday was
not seen (ex: trading holiday), the timer will be executed on the
next day (even if in a new week)
- ``monthdays``: a **sorted** iterable with integers indicating on
which days of the month a timer has to be executed. For example
always on day *15* of the month
If not specified, the timer will be active on all days
- ``monthcarry`` (default: ``True``). If the day was not seen
(weekend, trading holiday), the timer will be executed on the next
available day.
- ``allow`` (default: ``None``). A callback which receives a
`datetime.date`` instance and returns ``True`` if the date is
allowed for timers or else returns ``False``
- ``tzdata`` which can be either ``None`` (default), a ``pytz``
instance or a ``data feed`` instance.
``None``: ``when`` is interpreted at face value (which translates
to handling it as if it where UTC even if it's not)
``pytz`` instance: ``when`` will be interpreted as being specified
in the local time specified by the timezone instance.
``data feed`` instance: ``when`` will be interpreted as being
specified in the local time specified by the ``tz`` parameter of
the data feed instance.
**Note**: If ``when`` is either ``SESSION_START`` or
``SESSION_END`` and ``tzdata`` is ``None``, the 1st *data feed*
in the system (aka ``self.data0``) will be used as the reference
to find out the session times.
- ``cheat`` (default ``False``) if ``True`` the timer will be called
before the broker has a chance to evaluate the orders. This opens
the chance to issue orders based on opening price for example right
before the session starts
- ``*args``: any extra args will be passed to ``notify_timer``
- ``**kwargs``: any extra kwargs will be passed to ``notify_timer``
Return Value:
- The created timer
'''
return self.cerebro._add_timer(
owner=self, when=when, offset=offset, repeat=repeat,
weekdays=weekdays, weekcarry=weekcarry,
monthdays=monthdays, monthcarry=monthcarry,
allow=allow,
tzdata=tzdata, strats=False, cheat=cheat,
*args, **kwargs)
def notify_timer(self, timer, when, *args, **kwargs):
'''Receives a timer notification where ``timer`` is the timer which was
returned by ``add_timer``, and ``when`` is the calling time. ``args``
and ``kwargs`` are any additional arguments passed to ``add_timer``
The actual ``when`` time can be later, but the system may have not be
able to call the timer before. This value is the timer value and no the
system time.
'''
pass
def notify_cashvalue(self, cash, value):
'''
Receives the current fund value, value status of the strategy's broker
'''
pass
def notify_fund(self, cash, value, fundvalue, shares):
'''
Receives the current cash, value, fundvalue and fund shares
'''
pass
def notify_order(self, order):
'''
Receives an order whenever there has been a change in one
'''
pass
def notify_trade(self, trade):
'''
Receives a trade whenever there has been a change in one
'''
pass
def notify_store(self, msg, *args, **kwargs):
'''Receives a notification from a store provider'''
pass
def notify_data(self, data, status, *args, **kwargs):
'''Receives a notification from data'''
pass
def getdatanames(self):
'''
Returns a list of the existing data names
'''
return keys(self.env.datasbyname)
def getdatabyname(self, name):
'''
Returns a given data by name using the environment (cerebro)
'''
return self.env.datasbyname[name]
def cancel(self, order):
'''Cancels the order in the broker'''
self.broker.cancel(order)
def buy(self, data=None,
size=None, price=None, plimit=None,
exectype=None, valid=None, tradeid=0, oco=None,
trailamount=None, trailpercent=None,
parent=None, transmit=True,
**kwargs):
'''Create a buy (long) order and send it to the broker
- ``data`` (default: ``None``)
For which data the order has to be created. If ``None`` then the
first data in the system, ``self.datas[0] or self.data0`` (aka
``self.data``) will be used
- ``size`` (default: ``None``)
Size to use (positive) of units of data to use for the order.
If ``None`` the ``sizer`` instance retrieved via ``getsizer`` will
be used to determine the size.
- ``price`` (default: ``None``)
Price to use (live brokers may place restrictions on the actual
format if it does not comply to minimum tick size requirements)
``None`` is valid for ``Market`` and ``Close`` orders (the market
determines the price)
For ``Limit``, ``Stop`` and ``StopLimit`` orders this value
determines the trigger point (in the case of ``Limit`` the trigger
is obviously at which price the order should be matched)
- ``plimit`` (default: ``None``)
Only applicable to ``StopLimit`` orders. This is the price at which
to set the implicit *Limit* order, once the *Stop* has been
triggered (for which ``price`` has been used)
- ``trailamount`` (default: ``None``)
If the order type is StopTrail or StopTrailLimit, this is an
absolute amount which determines the distance to the price (below
for a Sell order and above for a buy order) to keep the trailing
stop
- ``trailpercent`` (default: ``None``)
If the order type is StopTrail or StopTrailLimit, this is a
percentage amount which determines the distance to the price (below
for a Sell order and above for a buy order) to keep the trailing
stop (if ``trailamount`` is also specified it will be used)
- ``exectype`` (default: ``None``)
Possible values:
- ``Order.Market`` or ``None``. A market order will be executed
with the next available price. In backtesting it will be the
opening price of the next bar
- ``Order.Limit``. An order which can only be executed at the given
``price`` or better
- ``Order.Stop``. An order which is triggered at ``price`` and
executed like an ``Order.Market`` order
- ``Order.StopLimit``. An order which is triggered at ``price`` and
executed as an implicit *Limit* order with price given by
``pricelimit``
- ``Order.Close``. An order which can only be executed with the
closing price of the session (usually during a closing auction)
- ``Order.StopTrail``. An order which is triggered at ``price``
minus ``trailamount`` (or ``trailpercent``) and which is updated
if the price moves away from the stop
- ``Order.StopTrailLimit``. An order which is triggered at
``price`` minus ``trailamount`` (or ``trailpercent``) and which
is updated if the price moves away from the stop
- ``valid`` (default: ``None``)
Possible values:
- ``None``: this generates an order that will not expire (aka
*Good till cancel*) and remain in the market until matched or
canceled. In reality brokers tend to impose a temporal limit,
but this is usually so far away in time to consider it as not
expiring
- ``datetime.datetime`` or ``datetime.date`` instance: the date
will be used to generate an order valid until the given
datetime (aka *good till date*)
- ``Order.DAY`` or ``0`` or ``timedelta()``: a day valid until
the *End of the Session* (aka *day* order) will be generated
- ``numeric value``: This is assumed to be a value corresponding
to a datetime in ``matplotlib`` coding (the one used by
``backtrader``) and will used to generate an order valid until
that time (*good till date*)
- ``tradeid`` (default: ``0``)
This is an internal value applied by ``backtrader`` to keep track
of overlapping trades on the same asset. This ``tradeid`` is sent
back to the *strategy* when notifying changes to the status of the
orders.
- ``oco`` (default: ``None``)
Another ``order`` instance. This order will become part of an OCO
(Order Cancel Others) group. The execution of one of the orders,
immediately cancels all others in the same group
- ``parent`` (default: ``None``)
Controls the relationship of a group of orders, for example a buy
which is bracketed by a high-side limit sell and a low side stop
sell. The high/low side orders remain inactive until the parent
order has been either executed (they become active) or is
canceled/expires (the children are also canceled) bracket orders
have the same size
- ``transmit`` (default: ``True``)
Indicates if the order has to be **transmitted**, ie: not only
placed in the broker but also issued. This is meant for example to
control bracket orders, in which one disables the transmission for
the parent and 1st set of children and activates it for the last
children, which triggers the full placement of all bracket orders.
- ``**kwargs``: additional broker implementations may support extra
parameters. ``backtrader`` will pass the *kwargs* down to the
created order objects
Example: if the 4 order execution types directly supported by
``backtrader`` are not enough, in the case of for example
*Interactive Brokers* the following could be passed as *kwargs*::
orderType='LIT', lmtPrice=10.0, auxPrice=9.8
This would override the settings created by ``backtrader`` and
generate a ``LIMIT IF TOUCHED`` order with a *touched* price of 9.8
and a *limit* price of 10.0.
Returns:
- the submitted order
'''
if isinstance(data, string_types):
data = self.getdatabyname(data)
data = data if data is not None else self.datas[0]
size = size if size is not None else self.getsizing(data, isbuy=True)
if size:
return self.broker.buy(
self, data,
size=abs(size), price=price, plimit=plimit,
exectype=exectype, valid=valid, tradeid=tradeid, oco=oco,
trailamount=trailamount, trailpercent=trailpercent,
parent=parent, transmit=transmit,
**kwargs)
return None
def sell(self, data=None,
size=None, price=None, plimit=None,
exectype=None, valid=None, tradeid=0, oco=None,
trailamount=None, trailpercent=None,
parent=None, transmit=True,
**kwargs):
'''
To create a selll (short) order and send it to the broker
See the documentation for ``buy`` for an explanation of the parameters
Returns: the submitted order
'''
if isinstance(data, string_types):
data = self.getdatabyname(data)
data = data if data is not None else self.datas[0]
size = size if size is not None else self.getsizing(data, isbuy=False)
if size:
return self.broker.sell(
self, data,
size=abs(size), price=price, plimit=plimit,
exectype=exectype, valid=valid, tradeid=tradeid, oco=oco,
trailamount=trailamount, trailpercent=trailpercent,
parent=parent, transmit=transmit,
**kwargs)
return None
def close(self, data=None, size=None, **kwargs):
'''
Counters a long/short position closing it
See the documentation for ``buy`` for an explanation of the parameters
Note:
- ``size``: automatically calculated from the existing position if
not provided (default: ``None``) by the caller
Returns: the submitted order
'''
if isinstance(data, string_types):
data = self.getdatabyname(data)
elif data is None:
data = self.data
possize = self.getposition(data, self.broker).size
size = abs(size if size is not None else possize)
if possize > 0:
return self.sell(data=data, size=size, **kwargs)
elif possize < 0:
return self.buy(data=data, size=size, **kwargs)
return None
def buy_bracket(self, data=None, size=None, price=None, plimit=None,
exectype=bt.Order.Limit, valid=None, tradeid=0,
trailamount=None, trailpercent=None, oargs={},
stopprice=None, stopexec=bt.Order.Stop, stopargs={},
limitprice=None, limitexec=bt.Order.Limit, limitargs={},
**kwargs):
'''
Create a bracket order group (low side - buy order - high side). The
default behavior is as follows:
- Issue a **buy** order with execution ``Limit``
- Issue a *low side* bracket **sell** order with execution ``Stop``
- Issue a *high side* bracket **sell** order with execution
``Limit``.
See below for the different parameters
- ``data`` (default: ``None``)
For which data the order has to be created. If ``None`` then the
first data in the system, ``self.datas[0] or self.data0`` (aka
``self.data``) will be used
- ``size`` (default: ``None``)
Size to use (positive) of units of data to use for the order.
If ``None`` the ``sizer`` instance retrieved via ``getsizer`` will
be used to determine the size.
**Note**: The same size is applied to all 3 orders of the bracket
- ``price`` (default: ``None``)
Price to use (live brokers may place restrictions on the actual
format if it does not comply to minimum tick size requirements)
``None`` is valid for ``Market`` and ``Close`` orders (the market
determines the price)
For ``Limit``, ``Stop`` and ``StopLimit`` orders this value
determines the trigger point (in the case of ``Limit`` the trigger
is obviously at which price the order should be matched)
- ``plimit`` (default: ``None``)
Only applicable to ``StopLimit`` orders. This is the price at which
to set the implicit *Limit* order, once the *Stop* has been
triggered (for which ``price`` has been used)
- ``trailamount`` (default: ``None``)
If the order type is StopTrail or StopTrailLimit, this is an
absolute amount which determines the distance to the price (below
for a Sell order and above for a buy order) to keep the trailing
stop
- ``trailpercent`` (default: ``None``)
If the order type is StopTrail or StopTrailLimit, this is a
percentage amount which determines the distance to the price (below
for a Sell order and above for a buy order) to keep the trailing
stop (if ``trailamount`` is also specified it will be used)
- ``exectype`` (default: ``bt.Order.Limit``)
Possible values: (see the documentation for the method ``buy``
- ``valid`` (default: ``None``)
Possible values: (see the documentation for the method ``buy``
- ``tradeid`` (default: ``0``)
Possible values: (see the documentation for the method ``buy``
- ``oargs`` (default: ``{}``)
Specific keyword arguments (in a ``dict``) to pass to the main side
order. Arguments from the default ``**kwargs`` will be applied on
top of this.
- ``**kwargs``: additional broker implementations may support extra
parameters. ``backtrader`` will pass the *kwargs* down to the
created order objects
Possible values: (see the documentation for the method ``buy``
**Note**: this ``kwargs`` will be applied to the 3 orders of a
bracket. See below for specific keyword arguments for the low and
high side orders
- ``stopprice`` (default: ``None``)
Specific price for the *low side* stop order
- ``stopexec`` (default: ``bt.Order.Stop``)
Specific execution type for the *low side* order
- ``stopargs`` (default: ``{}``)
Specific keyword arguments (in a ``dict``) to pass to the low side
order. Arguments from the default ``**kwargs`` will be applied on
top of this.
- ``limitprice`` (default: ``None``)
Specific price for the *high side* stop order
- ``stopexec`` (default: ``bt.Order.Limit``)
Specific execution type for the *high side* order
- ``limitargs`` (default: ``{}``)
Specific keyword arguments (in a ``dict``) to pass to the high side
order. Arguments from the default ``**kwargs`` will be applied on
top of this.
High/Low Side orders can be suppressed by using:
- ``limitexec=None`` to suppress the *high side*
- ``stopexec=None`` to suppress the *low side*
Returns:
- A list containing the 3 orders [order, stop side, limit side]
- If high/low orders have been suppressed the return value will still
contain 3 orders, but those suppressed will have a value of
``None``
'''
kargs = dict(size=size,
data=data, price=price, plimit=plimit, exectype=exectype,
valid=valid, tradeid=tradeid,
trailamount=trailamount, trailpercent=trailpercent)
kargs.update(oargs)
kargs.update(kwargs)
kargs['transmit'] = limitexec is None and stopexec is None
o = self.buy(**kargs)
if stopexec is not None:
# low side / stop
kargs = dict(data=data, price=stopprice, exectype=stopexec,
valid=valid, tradeid=tradeid)
kargs.update(stopargs)
kargs.update(kwargs)
kargs['parent'] = o
kargs['transmit'] = limitexec is None
kargs['size'] = o.size
ostop = self.sell(**kargs)
else:
ostop = None
if limitexec is not None:
# high side / limit
kargs = dict(data=data, price=limitprice, exectype=limitexec,
valid=valid, tradeid=tradeid)
kargs.update(limitargs)
kargs.update(kwargs)
kargs['parent'] = o
kargs['transmit'] = True
kargs['size'] = o.size
olimit = self.sell(**kargs)
else:
olimit = None
return [o, ostop, olimit]
def sell_bracket(self, data=None,
size=None, price=None, plimit=None,
exectype=bt.Order.Limit, valid=None, tradeid=0,
trailamount=None, trailpercent=None,
oargs={},
stopprice=None, stopexec=bt.Order.Stop, stopargs={},
limitprice=None, limitexec=bt.Order.Limit, limitargs={},
**kwargs):
'''
Create a bracket order group (low side - buy order - high side). The
default behavior is as follows:
- Issue a **sell** order with execution ``Limit``
- Issue a *high side* bracket **buy** order with execution ``Stop``
- Issue a *low side* bracket **buy** order with execution ``Limit``.
See ``bracket_buy`` for the meaning of the parameters
High/Low Side orders can be suppressed by using:
- ``stopexec=None`` to suppress the *high side*
- ``limitexec=None`` to suppress the *low side*
Returns:
- A list containing the 3 orders [order, stop side, limit side]
- If high/low orders have been suppressed the return value will still
contain 3 orders, but those suppressed will have a value of
``None``
'''
kargs = dict(size=size,
data=data, price=price, plimit=plimit, exectype=exectype,
valid=valid, tradeid=tradeid,
trailamount=trailamount, trailpercent=trailpercent)
kargs.update(oargs)
kargs.update(kwargs)
kargs['transmit'] = limitexec is None and stopexec is None
o = self.sell(**kargs)
if stopexec is not None:
# high side / stop
kargs = dict(data=data, price=stopprice, exectype=stopexec,
valid=valid, tradeid=tradeid)
kargs.update(stopargs)
kargs.update(kwargs)
kargs['parent'] = o
kargs['transmit'] = limitexec is None # transmit if last
kargs['size'] = o.size
ostop = self.buy(**kargs)
else:
ostop = None
if limitexec is not None:
# low side / limit
kargs = dict(data=data, price=limitprice, exectype=limitexec,
valid=valid, tradeid=tradeid)
kargs.update(limitargs)
kargs.update(kwargs)
kargs['parent'] = o
kargs['transmit'] = True
kargs['size'] = o.size
olimit = self.buy(**kargs)
else:
olimit = None
return [o, ostop, olimit]
def order_target_size(self, data=None, target=0, **kwargs):
'''
Place an order to rebalance a position to have final size of ``target``
The current ``position`` size is taken into account as the start point
to achieve ``target``
- If ``target`` > ``pos.size`` -> buy ``target - pos.size``
- If ``target`` < ``pos.size`` -> sell ``pos.size - target``
It returns either:
- The generated order
or
- ``None`` if no order has been issued (``target == position.size``)
'''
if isinstance(data, string_types):
data = self.getdatabyname(data)
elif data is None:
data = self.data
possize = self.getposition(data, self.broker).size
if not target and possize:
return self.close(data=data, size=possize, **kwargs)
elif target > possize:
return self.buy(data=data, size=target - possize, **kwargs)
elif target < possize:
return self.sell(data=data, size=possize - target, **kwargs)
return None # no execution target == possize
def order_target_value(self, data=None, target=0.0, price=None, **kwargs):
'''
Place an order to rebalance a position to have final value of
``target``
The current ``value`` is taken into account as the start point to
achieve ``target``
- If no ``target`` then close postion on data
- If ``target`` > ``value`` then buy on data
- If ``target`` < ``value`` then sell on data
It returns either:
- The generated order
or
- ``None`` if no order has been issued
'''
if isinstance(data, string_types):
data = self.getdatabyname(data)
elif data is None:
data = self.data
possize = self.getposition(data, self.broker).size
if not target and possize: # closing a position
return self.close(data=data, size=possize, price=price, **kwargs)
else:
value = self.broker.getvalue(datas=[data])
comminfo = self.broker.getcommissioninfo(data)
# Make sure a price is there
price = price if price is not None else data.close[0]
if target > value:
size = comminfo.getsize(price, target - value)
return self.buy(data=data, size=size, price=price, **kwargs)
elif target < value:
size = comminfo.getsize(price, value - target)
return self.sell(data=data, size=size, price=price, **kwargs)
return None # no execution size == possize
def order_target_percent(self, data=None, target=0.0, **kwargs):
'''
Place an order to rebalance a position to have final value of
``target`` percentage of current portfolio ``value``
``target`` is expressed in decimal: ``0.05`` -> ``5%``
It uses ``order_target_value`` to execute the order.
Example:
- ``target=0.05`` and portfolio value is ``100``
- The ``value`` to be reached is ``0.05 * 100 = 5``
- ``5`` is passed as the ``target`` value to ``order_target_value``
The current ``value`` is taken into account as the start point to
achieve ``target``
The ``position.size`` is used to determine if a position is ``long`` /
``short``
- If ``target`` > ``value``
- buy if ``pos.size >= 0`` (Increase a long position)
- sell if ``pos.size < 0`` (Increase a short position)
- If ``target`` < ``value``
- sell if ``pos.size >= 0`` (Decrease a long position)
- buy if ``pos.size < 0`` (Decrease a short position)
It returns either:
- The generated order
or
- ``None`` if no order has been issued (``target == position.size``)
'''
if isinstance(data, string_types):
data = self.getdatabyname(data)
elif data is None:
data = self.data
possize = self.getposition(data, self.broker).size
target *= self.broker.getvalue()
return self.order_target_value(data=data, target=target, **kwargs)
def getposition(self, data=None, broker=None):
'''
Returns the current position for a given data in a given broker.
If both are None, the main data and the default broker will be used
A property ``position`` is also available
'''
data = data if data is not None else self.datas[0]
broker = broker or self.broker
return broker.getposition(data)
position = property(getposition)
def getpositionbyname(self, name=None, broker=None):
'''
Returns the current position for a given name in a given broker.
If both are None, the main data and the default broker will be used
A property ``positionbyname`` is also available
'''
data = self.datas[0] if not name else self.getdatabyname(name)
broker = broker or self.broker
return broker.getposition(data)
positionbyname = property(getpositionbyname)
def getpositions(self, broker=None):
'''
Returns the current by data positions directly from the broker
If the given ``broker`` is None, the default broker will be used
A property ``positions`` is also available
'''
broker = broker or self.broker
return broker.positions
positions = property(getpositions)
def getpositionsbyname(self, broker=None):
'''
Returns the current by name positions directly from the broker
If the given ``broker`` is None, the default broker will be used
A property ``positionsbyname`` is also available
'''
broker = broker or self.broker
positions = broker.positions
posbyname = collections.OrderedDict()
for name, data in iteritems(self.env.datasbyname):
posbyname[name] = positions[data]
return posbyname
positionsbyname = property(getpositionsbyname)
def _addsizer(self, sizer, *args, **kwargs):
if sizer is None:
self.setsizer(bt.sizers.FixedSize())
else:
self.setsizer(sizer(*args, **kwargs))
def setsizer(self, sizer):
'''
Replace the default (fixed stake) sizer
'''
self._sizer = sizer
sizer.set(self, self.broker)
return sizer
def getsizer(self):
'''
Returns the sizer which is in used if automatic statke calculation is
used
Also available as ``sizer``
'''
return self._sizer
sizer = property(getsizer, setsizer)
def getsizing(self, data=None, isbuy=True):
'''
Return the stake calculated by the sizer instance for the current
situation
'''
data = data if data is not None else self.datas[0]
return self._sizer.getsizing(data, isbuy=isbuy)
class MetaSigStrategy(Strategy.__class__):
def __new__(meta, name, bases, dct):
# map user defined next to custom to be able to call own method before
if 'next' in dct:
dct['_next_custom'] = dct.pop('next')
cls = super(MetaSigStrategy, meta).__new__(meta, name, bases, dct)
# after class creation remap _next_catch to be next
cls.next = cls._next_catch
return cls
def dopreinit(cls, _obj, *args, **kwargs):
_obj, args, kwargs = \
super(MetaSigStrategy, cls).dopreinit(_obj, *args, **kwargs)
_obj._signals = collections.defaultdict(list)
_data = _obj.p._data
if _data is None:
_obj._dtarget = _obj.data0
elif isinstance(_data, integer_types):
_obj._dtarget = _obj.datas[_data]
elif isinstance(_data, string_types):
_obj._dtarget = _obj.getdatabyname(_data)
elif isinstance(_data, bt.LineRoot):
_obj._dtarget = _data
else:
_obj._dtarget = _obj.data0
return _obj, args, kwargs
def dopostinit(cls, _obj, *args, **kwargs):
_obj, args, kwargs = \
super(MetaSigStrategy, cls).dopostinit(_obj, *args, **kwargs)
for sigtype, sigcls, sigargs, sigkwargs in _obj.p.signals:
_obj._signals[sigtype].append(sigcls(*sigargs, **sigkwargs))
# Record types of signals
_obj._longshort = bool(_obj._signals[bt.SIGNAL_LONGSHORT])
_obj._long = bool(_obj._signals[bt.SIGNAL_LONG])
_obj._short = bool(_obj._signals[bt.SIGNAL_SHORT])
_obj._longexit = bool(_obj._signals[bt.SIGNAL_LONGEXIT])
_obj._shortexit = bool(_obj._signals[bt.SIGNAL_SHORTEXIT])
return _obj, args, kwargs
class SignalStrategy(with_metaclass(MetaSigStrategy, Strategy)):
'''This subclass of ``Strategy`` is meant to to auto-operate using
**signals**.
*Signals* are usually indicators and the expected output values:
- ``> 0`` is a ``long`` indication
- ``< 0`` is a ``short`` indication
There are 5 types of *Signals*, broken in 2 groups.
**Main Group**:
- ``LONGSHORT``: both ``long`` and ``short`` 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
- If a ``SHORT`` signal is available and no ``LONGEXIT`` is available
, it will be used to close a ``long`` before opening a ``short``
- ``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 no ``SHORTEXIT`` is available
, it will be used to close a ``short`` before opening a ``long``
**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 exit ``long``
positions
- ``SHORTEXIT``: ``long`` indications are taken to exit ``short``
positions
**Order Issuing**
Orders execution type is ``Market`` and validity is ``None`` (*Good until
Canceled*)
Params:
- ``signals`` (default: ``[]``): a list/tuple of lists/tuples that allows
the instantiation of the signals and allocation to the right type
This parameter is expected to be managed through ``cerebro.add_signal``
- ``_accumulate`` (default: ``False``): allow to enter the market
(long/short) even if already in the market
- ``_concurrent`` (default: ``False``): allow orders to be issued even if
orders are already pending execution
- ``_data`` (default: ``None``): if multiple datas are present in the
system which is the target for orders. This can be
- ``None``: The first data in the system will be used
- An ``int``: indicating the data that was inserted at that position
- An ``str``: name given to the data when creating it (parameter
``name``) or when adding it cerebro with ``cerebro.adddata(...,
name=)``
- A ``data`` instance
'''
params = (
('signals', []),
('_accumulate', False),
('_concurrent', False),
('_data', None),
)
def _start(self):
self._sentinel = None # sentinel for order concurrency
super(SignalStrategy, self)._start()
def signal_add(self, sigtype, signal):
self._signals[sigtype].append(signal)
def _notify(self, qorders=[], qtrades=[]):
# Nullify the sentinel if done
procorders = qorders or self._orderspending
if self._sentinel is not None:
for order in procorders:
if order == self._sentinel and not order.alive():
self._sentinel = None
break
super(SignalStrategy, self)._notify(qorders=qorders, qtrades=qtrades)
def _next_catch(self):
self._next_signal()
if hasattr(self, '_next_custom'):
self._next_custom()
def _next_signal(self):
if self._sentinel is not None and not self.p._concurrent:
return # order active and more than 1 not allowed
sigs = self._signals
nosig = [[0.0]]
# Calculate current status of the signals
ls_long = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_LONGSHORT] or nosig)
ls_short = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_LONGSHORT] or nosig)
l_enter0 = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_LONG] or nosig)
l_enter1 = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_LONG_INV] or nosig)
l_enter2 = all(x[0] for x in sigs[bt.SIGNAL_LONG_ANY] or nosig)
l_enter = l_enter0 or l_enter1 or l_enter2
s_enter0 = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_SHORT] or nosig)
s_enter1 = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_SHORT_INV] or nosig)
s_enter2 = all(x[0] for x in sigs[bt.SIGNAL_SHORT_ANY] or nosig)
s_enter = s_enter0 or s_enter1 or s_enter2
l_ex0 = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_LONGEXIT] or nosig)
l_ex1 = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_LONGEXIT_INV] or nosig)
l_ex2 = all(x[0] for x in sigs[bt.SIGNAL_LONGEXIT_ANY] or nosig)
l_exit = l_ex0 or l_ex1 or l_ex2
s_ex0 = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_SHORTEXIT] or nosig)
s_ex1 = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_SHORTEXIT_INV] or nosig)
s_ex2 = all(x[0] for x in sigs[bt.SIGNAL_SHORTEXIT_ANY] or nosig)
s_exit = s_ex0 or s_ex1 or s_ex2
# Use oppossite signales to start reversal (by closing)
# but only if no "xxxExit" exists
l_rev = not self._longexit and s_enter
s_rev = not self._shortexit and l_enter
# Opposite of individual long and short
l_leav0 = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_LONG] or nosig)
l_leav1 = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_LONG_INV] or nosig)
l_leav2 = all(x[0] for x in sigs[bt.SIGNAL_LONG_ANY] or nosig)
l_leave = l_leav0 or l_leav1 or l_leav2
s_leav0 = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_SHORT] or nosig)
s_leav1 = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_SHORT_INV] or nosig)
s_leav2 = all(x[0] for x in sigs[bt.SIGNAL_SHORT_ANY] or nosig)
s_leave = s_leav0 or s_leav1 or s_leav2
# Invalidate long leave if longexit signals are available
l_leave = not self._longexit and l_leave
# Invalidate short leave if shortexit signals are available
s_leave = not self._shortexit and s_leave
# Take size and start logic
size = self.getposition(self._dtarget).size
if not size:
if ls_long or l_enter:
self._sentinel = self.buy(self._dtarget)
elif ls_short or s_enter:
self._sentinel = self.sell(self._dtarget)
elif size > 0: # current long position
if ls_short or l_exit or l_rev or l_leave:
# closing position - not relevant for concurrency
self.close(self._dtarget)
if ls_short or l_rev:
self._sentinel = self.sell(self._dtarget)
if ls_long or l_enter:
if self.p._accumulate:
self._sentinel = self.buy(self._dtarget)
elif size < 0: # current short position
if ls_long or s_exit or s_rev or s_leave:
# closing position - not relevant for concurrency
self.close(self._dtarget)
if ls_long or s_rev:
self._sentinel = self.buy(self._dtarget)
if ls_short or s_enter:
if self.p._accumulate:
self._sentinel = self.sell(self._dtarget)
Classes
class MetaSigStrategy (name, bases, dct)
-
Dirty job manager for a LineSeries
-
During new (class creation), it reads "lines", "plotinfo", "plotlines" class variable definitions and turns them into Classes of type Lines or AutoClassInfo (plotinfo/plotlines)
-
During "new" (instance creation) the lines/plotinfo/plotlines classes are substituted in the instance with instances of the aforementioned classes and aliases are added for the "lines" held in the "lines" instance
Additionally and for remaining kwargs, these are matched against args in plotinfo and if existent are set there and removed from kwargs
Remember that this Metaclass has a MetaParams (from metabase) as root class and therefore "params" defined for the class have been removed from kwargs at an earlier state
Class has already been created … register subclasses
Expand source code
class MetaSigStrategy(Strategy.__class__): def __new__(meta, name, bases, dct): # map user defined next to custom to be able to call own method before if 'next' in dct: dct['_next_custom'] = dct.pop('next') cls = super(MetaSigStrategy, meta).__new__(meta, name, bases, dct) # after class creation remap _next_catch to be next cls.next = cls._next_catch return cls def dopreinit(cls, _obj, *args, **kwargs): _obj, args, kwargs = \ super(MetaSigStrategy, cls).dopreinit(_obj, *args, **kwargs) _obj._signals = collections.defaultdict(list) _data = _obj.p._data if _data is None: _obj._dtarget = _obj.data0 elif isinstance(_data, integer_types): _obj._dtarget = _obj.datas[_data] elif isinstance(_data, string_types): _obj._dtarget = _obj.getdatabyname(_data) elif isinstance(_data, bt.LineRoot): _obj._dtarget = _data else: _obj._dtarget = _obj.data0 return _obj, args, kwargs def dopostinit(cls, _obj, *args, **kwargs): _obj, args, kwargs = \ super(MetaSigStrategy, cls).dopostinit(_obj, *args, **kwargs) for sigtype, sigcls, sigargs, sigkwargs in _obj.p.signals: _obj._signals[sigtype].append(sigcls(*sigargs, **sigkwargs)) # Record types of signals _obj._longshort = bool(_obj._signals[bt.SIGNAL_LONGSHORT]) _obj._long = bool(_obj._signals[bt.SIGNAL_LONG]) _obj._short = bool(_obj._signals[bt.SIGNAL_SHORT]) _obj._longexit = bool(_obj._signals[bt.SIGNAL_LONGEXIT]) _obj._shortexit = bool(_obj._signals[bt.SIGNAL_SHORTEXIT]) return _obj, args, kwargs
Ancestors
- MetaStrategy
- MetaLineIterator
- MetaLineSeries
- MetaLineRoot
- MetaParams
- MetaBase
- builtins.type
Methods
def dopostinit(cls, _obj, *args, **kwargs)
-
Expand source code
def dopostinit(cls, _obj, *args, **kwargs): _obj, args, kwargs = \ super(MetaSigStrategy, cls).dopostinit(_obj, *args, **kwargs) for sigtype, sigcls, sigargs, sigkwargs in _obj.p.signals: _obj._signals[sigtype].append(sigcls(*sigargs, **sigkwargs)) # Record types of signals _obj._longshort = bool(_obj._signals[bt.SIGNAL_LONGSHORT]) _obj._long = bool(_obj._signals[bt.SIGNAL_LONG]) _obj._short = bool(_obj._signals[bt.SIGNAL_SHORT]) _obj._longexit = bool(_obj._signals[bt.SIGNAL_LONGEXIT]) _obj._shortexit = bool(_obj._signals[bt.SIGNAL_SHORTEXIT]) return _obj, args, kwargs
def dopreinit(cls, _obj, *args, **kwargs)
-
Expand source code
def dopreinit(cls, _obj, *args, **kwargs): _obj, args, kwargs = \ super(MetaSigStrategy, cls).dopreinit(_obj, *args, **kwargs) _obj._signals = collections.defaultdict(list) _data = _obj.p._data if _data is None: _obj._dtarget = _obj.data0 elif isinstance(_data, integer_types): _obj._dtarget = _obj.datas[_data] elif isinstance(_data, string_types): _obj._dtarget = _obj.getdatabyname(_data) elif isinstance(_data, bt.LineRoot): _obj._dtarget = _data else: _obj._dtarget = _obj.data0 return _obj, args, kwargs
Inherited members
-
class MetaStrategy (name, bases, dct)
-
Dirty job manager for a LineSeries
-
During new (class creation), it reads "lines", "plotinfo", "plotlines" class variable definitions and turns them into Classes of type Lines or AutoClassInfo (plotinfo/plotlines)
-
During "new" (instance creation) the lines/plotinfo/plotlines classes are substituted in the instance with instances of the aforementioned classes and aliases are added for the "lines" held in the "lines" instance
Additionally and for remaining kwargs, these are matched against args in plotinfo and if existent are set there and removed from kwargs
Remember that this Metaclass has a MetaParams (from metabase) as root class and therefore "params" defined for the class have been removed from kwargs at an earlier state
Class has already been created … register subclasses
Expand source code
class MetaStrategy(StrategyBase.__class__): _indcol = dict() def __new__(meta, name, bases, dct): # Hack to support original method name for notify_order if 'notify' in dct: # rename 'notify' to 'notify_order' dct['notify_order'] = dct.pop('notify') if 'notify_operation' in dct: # rename 'notify' to 'notify_order' dct['notify_trade'] = dct.pop('notify_operation') return super(MetaStrategy, meta).__new__(meta, name, bases, dct) def __init__(cls, name, bases, dct): ''' Class has already been created ... register subclasses ''' # Initialize the class super(MetaStrategy, cls).__init__(name, bases, dct) if not cls.aliased and \ name != 'Strategy' and not name.startswith('_'): cls._indcol[name] = cls def donew(cls, *args, **kwargs): _obj, args, kwargs = super(MetaStrategy, cls).donew(*args, **kwargs) # Find the owner and store it _obj.env = _obj.cerebro = cerebro = findowner(_obj, bt.Cerebro) _obj._id = cerebro._next_stid() return _obj, args, kwargs def dopreinit(cls, _obj, *args, **kwargs): _obj, args, kwargs = \ super(MetaStrategy, cls).dopreinit(_obj, *args, **kwargs) _obj.broker = _obj.env.broker _obj._sizer = bt.sizers.FixedSize() _obj._orders = list() _obj._orderspending = list() _obj._trades = collections.defaultdict(AutoDictList) _obj._tradespending = list() _obj.stats = _obj.observers = ItemCollection() _obj.analyzers = ItemCollection() _obj._alnames = collections.defaultdict(itertools.count) _obj.writers = list() _obj._slave_analyzers = list() _obj._tradehistoryon = False return _obj, args, kwargs def dopostinit(cls, _obj, *args, **kwargs): _obj, args, kwargs = \ super(MetaStrategy, cls).dopostinit(_obj, *args, **kwargs) _obj._sizer.set(_obj, _obj.broker) return _obj, args, kwargs
Ancestors
- MetaLineIterator
- MetaLineSeries
- MetaLineRoot
- MetaParams
- MetaBase
- builtins.type
Subclasses
Methods
def dopostinit(cls, _obj, *args, **kwargs)
-
Expand source code
def dopostinit(cls, _obj, *args, **kwargs): _obj, args, kwargs = \ super(MetaStrategy, cls).dopostinit(_obj, *args, **kwargs) _obj._sizer.set(_obj, _obj.broker) return _obj, args, kwargs
def dopreinit(cls, _obj, *args, **kwargs)
-
Expand source code
def dopreinit(cls, _obj, *args, **kwargs): _obj, args, kwargs = \ super(MetaStrategy, cls).dopreinit(_obj, *args, **kwargs) _obj.broker = _obj.env.broker _obj._sizer = bt.sizers.FixedSize() _obj._orders = list() _obj._orderspending = list() _obj._trades = collections.defaultdict(AutoDictList) _obj._tradespending = list() _obj.stats = _obj.observers = ItemCollection() _obj.analyzers = ItemCollection() _obj._alnames = collections.defaultdict(itertools.count) _obj.writers = list() _obj._slave_analyzers = list() _obj._tradehistoryon = False return _obj, args, kwargs
Inherited members
-
class SignalStrategy (*args, **kwargs)
-
This subclass of
Strategy
is meant to to auto-operate using signals.Signals are usually indicators and the expected output values:
-
> 0
is along
indication -
< 0
is ashort
indication
There are 5 types of Signals, broken in 2 groups.
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 -
If a
SHORT
signal is available and noLONGEXIT
is available , it will be used to close along
before opening ashort
-
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
Order Issuing
Orders execution type is
Market
and validity isNone
(Good until Canceled)Params
signals
(default:[]
): a list/tuple of lists/tuples that allows the instantiation of the signals and allocation to the right type
This parameter is expected to be managed through
cerebro.add_signal
-
_accumulate
(default:False
): allow to enter the market (long/short) even if already in the market -
_concurrent
(default:False
): allow orders to be issued even if orders are already pending execution -
_data
(default:None
): if multiple datas are present in the system which is the target for orders. This can be -
None
: The first data in the system will be used -
An
int
: indicating the data that was inserted at that position -
An
str
: name given to the data when creating it (parametername
) or when adding it cerebro withcerebro.adddata(..., name=)
-
A
data
instance
Expand source code
class SignalStrategy(with_metaclass(MetaSigStrategy, Strategy)): '''This subclass of ``Strategy`` is meant to to auto-operate using **signals**. *Signals* are usually indicators and the expected output values: - ``> 0`` is a ``long`` indication - ``< 0`` is a ``short`` indication There are 5 types of *Signals*, broken in 2 groups. **Main Group**: - ``LONGSHORT``: both ``long`` and ``short`` 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 - If a ``SHORT`` signal is available and no ``LONGEXIT`` is available , it will be used to close a ``long`` before opening a ``short`` - ``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 no ``SHORTEXIT`` is available , it will be used to close a ``short`` before opening a ``long`` **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 exit ``long`` positions - ``SHORTEXIT``: ``long`` indications are taken to exit ``short`` positions **Order Issuing** Orders execution type is ``Market`` and validity is ``None`` (*Good until Canceled*) Params: - ``signals`` (default: ``[]``): a list/tuple of lists/tuples that allows the instantiation of the signals and allocation to the right type This parameter is expected to be managed through ``cerebro.add_signal`` - ``_accumulate`` (default: ``False``): allow to enter the market (long/short) even if already in the market - ``_concurrent`` (default: ``False``): allow orders to be issued even if orders are already pending execution - ``_data`` (default: ``None``): if multiple datas are present in the system which is the target for orders. This can be - ``None``: The first data in the system will be used - An ``int``: indicating the data that was inserted at that position - An ``str``: name given to the data when creating it (parameter ``name``) or when adding it cerebro with ``cerebro.adddata(..., name=)`` - A ``data`` instance ''' params = ( ('signals', []), ('_accumulate', False), ('_concurrent', False), ('_data', None), ) def _start(self): self._sentinel = None # sentinel for order concurrency super(SignalStrategy, self)._start() def signal_add(self, sigtype, signal): self._signals[sigtype].append(signal) def _notify(self, qorders=[], qtrades=[]): # Nullify the sentinel if done procorders = qorders or self._orderspending if self._sentinel is not None: for order in procorders: if order == self._sentinel and not order.alive(): self._sentinel = None break super(SignalStrategy, self)._notify(qorders=qorders, qtrades=qtrades) def _next_catch(self): self._next_signal() if hasattr(self, '_next_custom'): self._next_custom() def _next_signal(self): if self._sentinel is not None and not self.p._concurrent: return # order active and more than 1 not allowed sigs = self._signals nosig = [[0.0]] # Calculate current status of the signals ls_long = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_LONGSHORT] or nosig) ls_short = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_LONGSHORT] or nosig) l_enter0 = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_LONG] or nosig) l_enter1 = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_LONG_INV] or nosig) l_enter2 = all(x[0] for x in sigs[bt.SIGNAL_LONG_ANY] or nosig) l_enter = l_enter0 or l_enter1 or l_enter2 s_enter0 = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_SHORT] or nosig) s_enter1 = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_SHORT_INV] or nosig) s_enter2 = all(x[0] for x in sigs[bt.SIGNAL_SHORT_ANY] or nosig) s_enter = s_enter0 or s_enter1 or s_enter2 l_ex0 = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_LONGEXIT] or nosig) l_ex1 = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_LONGEXIT_INV] or nosig) l_ex2 = all(x[0] for x in sigs[bt.SIGNAL_LONGEXIT_ANY] or nosig) l_exit = l_ex0 or l_ex1 or l_ex2 s_ex0 = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_SHORTEXIT] or nosig) s_ex1 = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_SHORTEXIT_INV] or nosig) s_ex2 = all(x[0] for x in sigs[bt.SIGNAL_SHORTEXIT_ANY] or nosig) s_exit = s_ex0 or s_ex1 or s_ex2 # Use oppossite signales to start reversal (by closing) # but only if no "xxxExit" exists l_rev = not self._longexit and s_enter s_rev = not self._shortexit and l_enter # Opposite of individual long and short l_leav0 = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_LONG] or nosig) l_leav1 = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_LONG_INV] or nosig) l_leav2 = all(x[0] for x in sigs[bt.SIGNAL_LONG_ANY] or nosig) l_leave = l_leav0 or l_leav1 or l_leav2 s_leav0 = all(x[0] > 0.0 for x in sigs[bt.SIGNAL_SHORT] or nosig) s_leav1 = all(x[0] < 0.0 for x in sigs[bt.SIGNAL_SHORT_INV] or nosig) s_leav2 = all(x[0] for x in sigs[bt.SIGNAL_SHORT_ANY] or nosig) s_leave = s_leav0 or s_leav1 or s_leav2 # Invalidate long leave if longexit signals are available l_leave = not self._longexit and l_leave # Invalidate short leave if shortexit signals are available s_leave = not self._shortexit and s_leave # Take size and start logic size = self.getposition(self._dtarget).size if not size: if ls_long or l_enter: self._sentinel = self.buy(self._dtarget) elif ls_short or s_enter: self._sentinel = self.sell(self._dtarget) elif size > 0: # current long position if ls_short or l_exit or l_rev or l_leave: # closing position - not relevant for concurrency self.close(self._dtarget) if ls_short or l_rev: self._sentinel = self.sell(self._dtarget) if ls_long or l_enter: if self.p._accumulate: self._sentinel = self.buy(self._dtarget) elif size < 0: # current short position if ls_long or s_exit or s_rev or s_leave: # closing position - not relevant for concurrency self.close(self._dtarget) if ls_long or s_rev: self._sentinel = self.buy(self._dtarget) if ls_short or s_enter: if self.p._accumulate: self._sentinel = self.sell(self._dtarget)
Ancestors
Class variables
var alias
var aliased
var frompackages
var linealias
var packages
var params
var plotinfo
var plotlines
Methods
def signal_add(self, sigtype, signal)
-
Expand source code
def signal_add(self, sigtype, signal): self._signals[sigtype].append(signal)
Inherited members
Strategy
:add_timer
addminperiod
buy
buy_bracket
cancel
close
getdatabyname
getdatanames
getposition
getpositionbyname
getpositions
getpositionsbyname
getsizer
getsizing
incminperiod
lines
minbuffer
next
nextstart
notify_cashvalue
notify_data
notify_fund
notify_order
notify_store
notify_timer
notify_trade
once
oncestart
order_target_percent
order_target_size
order_target_value
position
positionbyname
positions
positionsbyname
prenext
preonce
qbuffer
sell
sell_bracket
setminperiod
setsizer
sizer
start
stop
updateminperiod
-
class Strategy (*args, **kwargs)
-
Base class to be subclassed for user defined strategies.
Expand source code
class Strategy(with_metaclass(MetaStrategy, StrategyBase)): ''' Base class to be subclassed for user defined strategies. ''' _ltype = LineIterator.StratType csv = True _oldsync = False # update clock using old methodology : data 0 # keep the latest delivered data date in the line lines = ('datetime',) def qbuffer(self, savemem=0, replaying=False): '''Enable the memory saving schemes. Possible values for ``savemem``: 0: No savings. Each lines object keeps in memory all values 1: All lines objects save memory, using the strictly minimum needed Negative values are meant to be used when plotting is required: -1: Indicators at Strategy Level and Observers do not enable memory savings (but anything declared below it does) -2: Same as -1 plus activation of memory saving for any indicators which has declared *plotinfo.plot* as False (will not be plotted) ''' if savemem < 0: # Get any attribute which labels itself as Indicator for ind in self._lineiterators[self.IndType]: subsave = isinstance(ind, (LineSingle,)) if not subsave and savemem < -1: subsave = not ind.plotinfo.plot ind.qbuffer(savemem=subsave) elif savemem > 0: for data in self.datas: data.qbuffer(replaying=replaying) for line in self.lines: line.qbuffer(savemem=1) # Save in all object types depending on the strategy for itcls in self._lineiterators: for it in self._lineiterators[itcls]: it.qbuffer(savemem=1) def _periodset(self): dataids = [id(data) for data in self.datas] _dminperiods = collections.defaultdict(list) for lineiter in self._lineiterators[LineIterator.IndType]: # if multiple datas are used and multiple timeframes the larger # timeframe may place larger time constraints in calling next. clk = getattr(lineiter, '_clock', None) if clk is None: clk = getattr(lineiter._owner, '_clock', None) if clk is None: continue while True: if id(clk) in dataids: break # already top-level clock (data feed) # See if the current clock has higher level clocks clk2 = getattr(clk, '_clock', None) if clk2 is None: clk2 = getattr(clk._owner, '_clock', None) if clk2 is None: break # if no clock found, bail out clk = clk2 # keep the ref and try to go up the hierarchy if clk is None: continue # no clock found, go to next # LineSeriesStup wraps a line and the clock is the wrapped line and # no the wrapper itself. if isinstance(clk, LineSeriesStub): clk = clk.lines[0] _dminperiods[clk].append(lineiter._minperiod) self._minperiods = list() for data in self.datas: # Do not only consider the data as clock but also its lines which # may have been individually passed as clock references and # discovered as clocks above # Initialize with data min period if any dlminperiods = _dminperiods[data] for l in data.lines: # search each line for min periods if l in _dminperiods: dlminperiods += _dminperiods[l] # found, add it # keep the reference to the line if any was found _dminperiods[data] = [max(dlminperiods)] if dlminperiods else [] dminperiod = max(_dminperiods[data] or [data._minperiod]) self._minperiods.append(dminperiod) # Set the minperiod minperiods = \ [x._minperiod for x in self._lineiterators[LineIterator.IndType]] self._minperiod = max(minperiods or [self._minperiod]) def _addwriter(self, writer): ''' Unlike the other _addxxx functions this one receives an instance because the writer works at cerebro level and is only passed to the strategy to simplify the logic ''' self.writers.append(writer) def _addindicator(self, indcls, *indargs, **indkwargs): indcls(*indargs, **indkwargs) def _addanalyzer_slave(self, ancls, *anargs, **ankwargs): '''Like _addanalyzer but meant for observers (or other entities) which rely on the output of an analyzer for the data. These analyzers have not been added by the user and are kept separate from the main analyzers Returns the created analyzer ''' analyzer = ancls(*anargs, **ankwargs) self._slave_analyzers.append(analyzer) return analyzer def _getanalyzer_slave(self, idx): return self._slave_analyzers.append[idx] def _addanalyzer(self, ancls, *anargs, **ankwargs): anname = ankwargs.pop('_name', '') or ancls.__name__.lower() nsuffix = next(self._alnames[anname]) anname += str(nsuffix or '') # 0 (first instance) gets no suffix analyzer = ancls(*anargs, **ankwargs) self.analyzers.append(analyzer, anname) def _addobserver(self, multi, obscls, *obsargs, **obskwargs): obsname = obskwargs.pop('obsname', '') if not obsname: obsname = obscls.__name__.lower() if not multi: newargs = list(itertools.chain(self.datas, obsargs)) obs = obscls(*newargs, **obskwargs) self.stats.append(obs, obsname) return setattr(self.stats, obsname, list()) l = getattr(self.stats, obsname) for data in self.datas: obs = obscls(data, *obsargs, **obskwargs) l.append(obs) def _getminperstatus(self): # check the min period status connected to datas dlens = map(operator.sub, self._minperiods, map(len, self.datas)) self._minperstatus = minperstatus = max(dlens) return minperstatus def prenext_open(self): pass def nextstart_open(self): self.next_open() def next_open(self): pass def _oncepost_open(self): minperstatus = self._minperstatus if minperstatus < 0: self.next_open() elif minperstatus == 0: self.nextstart_open() # only called for the 1st value else: self.prenext_open() def _oncepost(self, dt): for indicator in self._lineiterators[LineIterator.IndType]: if len(indicator._clock) > len(indicator): indicator.advance() if self._oldsync: # Strategy has not been reset, the line is there self.advance() else: # strategy has been reset to beginning. advance step by step self.forward() self.lines.datetime[0] = dt self._notify() minperstatus = self._getminperstatus() if minperstatus < 0: self.next() elif minperstatus == 0: self.nextstart() # only called for the 1st value else: self.prenext() self._next_analyzers(minperstatus, once=True) self._next_observers(minperstatus, once=True) self.clear() def _clk_update(self): if self._oldsync: clk_len = super(Strategy, self)._clk_update() self.lines.datetime[0] = max(d.datetime[0] for d in self.datas if len(d)) return clk_len newdlens = [len(d) for d in self.datas] if any(nl > l for l, nl in zip(self._dlens, newdlens)): self.forward() self.lines.datetime[0] = max(d.datetime[0] for d in self.datas if len(d)) self._dlens = newdlens return len(self) def _next_open(self): minperstatus = self._minperstatus if minperstatus < 0: self.next_open() elif minperstatus == 0: self.nextstart_open() # only called for the 1st value else: self.prenext_open() def _next(self): super(Strategy, self)._next() minperstatus = self._getminperstatus() self._next_analyzers(minperstatus) self._next_observers(minperstatus) self.clear() def _next_observers(self, minperstatus, once=False): for observer in self._lineiterators[LineIterator.ObsType]: for analyzer in observer._analyzers: if minperstatus < 0: analyzer._next() elif minperstatus == 0: analyzer._nextstart() # only called for the 1st value else: analyzer._prenext() if once: if len(self) > len(observer): if self._oldsync: observer.advance() else: observer.forward() if minperstatus < 0: observer.next() elif minperstatus == 0: observer.nextstart() # only called for the 1st value elif len(observer): observer.prenext() else: observer._next() def _next_analyzers(self, minperstatus, once=False): for analyzer in self.analyzers: if minperstatus < 0: analyzer._next() elif minperstatus == 0: analyzer._nextstart() # only called for the 1st value else: analyzer._prenext() def _settz(self, tz): self.lines.datetime._settz(tz) def _start(self): self._periodset() for analyzer in itertools.chain(self.analyzers, self._slave_analyzers): analyzer._start() for obs in self.observers: if not isinstance(obs, list): obs = [obs] # support of multi-data observers for o in obs: o._start() # change operators to stage 2 self._stage2() self._dlens = [len(data) for data in self.datas] self._minperstatus = MAXINT # start in prenext self.start() def start(self): '''Called right before the backtesting is about to be started.''' pass def getwriterheaders(self): self.indobscsv = [self] indobs = itertools.chain( self.getindicators_lines(), self.getobservers()) self.indobscsv.extend(filter(lambda x: x.csv, indobs)) headers = list() # prepare the indicators/observers data headers for iocsv in self.indobscsv: name = iocsv.plotinfo.plotname or iocsv.__class__.__name__ headers.append(name) headers.append('len') headers.extend(iocsv.getlinealiases()) return headers def getwritervalues(self): values = list() for iocsv in self.indobscsv: name = iocsv.plotinfo.plotname or iocsv.__class__.__name__ values.append(name) lio = len(iocsv) values.append(lio) if lio: values.extend(map(lambda l: l[0], iocsv.lines.itersize())) else: values.extend([''] * iocsv.lines.size()) return values def getwriterinfo(self): wrinfo = AutoOrderedDict() wrinfo['Params'] = self.p._getkwargs() sections = [ ['Indicators', self.getindicators_lines()], ['Observers', self.getobservers()] ] for sectname, sectitems in sections: sinfo = wrinfo[sectname] for item in sectitems: itname = item.__class__.__name__ sinfo[itname].Lines = item.lines.getlinealiases() or None sinfo[itname].Params = item.p._getkwargs() or None ainfo = wrinfo.Analyzers # Internal Value Analyzer ainfo.Value.Begin = self.broker.startingcash ainfo.Value.End = self.broker.getvalue() # no slave analyzers for writer for aname, analyzer in self.analyzers.getitems(): ainfo[aname].Params = analyzer.p._getkwargs() or None ainfo[aname].Analysis = analyzer.get_analysis() return wrinfo def _stop(self): self.stop() for analyzer in itertools.chain(self.analyzers, self._slave_analyzers): analyzer._stop() # change operators back to stage 1 - allows reuse of datas self._stage1() def stop(self): '''Called right before the backtesting is about to be stopped''' pass def set_tradehistory(self, onoff=True): self._tradehistoryon = onoff def clear(self): self._orders.extend(self._orderspending) self._orderspending = list() self._tradespending = list() def _addnotification(self, order, quicknotify=False): if not order.p.simulated: self._orderspending.append(order) if quicknotify: qorders = [order] qtrades = [] if not order.executed.size: if quicknotify: self._notify(qorders=qorders, qtrades=qtrades) return tradedata = order.data._compensate if tradedata is None: tradedata = order.data datatrades = self._trades[tradedata][order.tradeid] if not datatrades: trade = Trade(data=tradedata, tradeid=order.tradeid, historyon=self._tradehistoryon) datatrades.append(trade) else: trade = datatrades[-1] for exbit in order.executed.iterpending(): if exbit is None: break if exbit.closed: trade.update(order, exbit.closed, exbit.price, exbit.closedvalue, exbit.closedcomm, exbit.pnl, comminfo=order.comminfo) if trade.isclosed: self._tradespending.append(copy.copy(trade)) if quicknotify: qtrades.append(copy.copy(trade)) # Update it if needed if exbit.opened: if trade.isclosed: trade = Trade(data=tradedata, tradeid=order.tradeid, historyon=self._tradehistoryon) datatrades.append(trade) trade.update(order, exbit.opened, exbit.price, exbit.openedvalue, exbit.openedcomm, exbit.pnl, comminfo=order.comminfo) # This extra check covers the case in which different tradeid # orders have put the position down to 0 and the next order # "opens" a position but "closes" the trade if trade.isclosed: self._tradespending.append(copy.copy(trade)) if quicknotify: qtrades.append(copy.copy(trade)) if trade.justopened: self._tradespending.append(copy.copy(trade)) if quicknotify: qtrades.append(copy.copy(trade)) if quicknotify: self._notify(qorders=qorders, qtrades=qtrades) def _notify(self, qorders=[], qtrades=[]): if self.cerebro.p.quicknotify: # need to know if quicknotify is on, to not reprocess pendingorders # and pendingtrades, which have to exist for things like observers # which look into it procorders = qorders proctrades = qtrades else: procorders = self._orderspending proctrades = self._tradespending for order in procorders: if order.exectype != order.Historical or order.histnotify: self.notify_order(order) for analyzer in itertools.chain(self.analyzers, self._slave_analyzers): analyzer._notify_order(order) for trade in proctrades: self.notify_trade(trade) for analyzer in itertools.chain(self.analyzers, self._slave_analyzers): analyzer._notify_trade(trade) if qorders: return # cash is notified on a regular basis cash = self.broker.getcash() value = self.broker.getvalue() fundvalue = self.broker.fundvalue fundshares = self.broker.fundshares self.notify_cashvalue(cash, value) self.notify_fund(cash, value, fundvalue, fundshares) for analyzer in itertools.chain(self.analyzers, self._slave_analyzers): analyzer._notify_cashvalue(cash, value) analyzer._notify_fund(cash, value, fundvalue, fundshares) def add_timer(self, when, offset=datetime.timedelta(), repeat=datetime.timedelta(), weekdays=[], weekcarry=False, monthdays=[], monthcarry=True, allow=None, tzdata=None, cheat=False, *args, **kwargs): ''' **Note**: can be called during ``__init__`` or ``start`` Schedules a timer to invoke either a specified callback or the ``notify_timer`` of one or more strategies. Arguments: - ``when``: can be - ``datetime.time`` instance (see below ``tzdata``) - ``bt.timer.SESSION_START`` to reference a session start - ``bt.timer.SESSION_END`` to reference a session end - ``offset`` which must be a ``datetime.timedelta`` instance Used to offset the value ``when``. It has a meaningful use in combination with ``SESSION_START`` and ``SESSION_END``, to indicated things like a timer being called ``15 minutes`` after the session start. - ``repeat`` which must be a ``datetime.timedelta`` instance Indicates if after a 1st call, further calls will be scheduled within the same session at the scheduled ``repeat`` delta Once the timer goes over the end of the session it is reset to the original value for ``when`` - ``weekdays``: a **sorted** iterable with integers indicating on which days (iso codes, Monday is 1, Sunday is 7) the timers can be actually invoked If not specified, the timer will be active on all days - ``weekcarry`` (default: ``False``). If ``True`` and the weekday was not seen (ex: trading holiday), the timer will be executed on the next day (even if in a new week) - ``monthdays``: a **sorted** iterable with integers indicating on which days of the month a timer has to be executed. For example always on day *15* of the month If not specified, the timer will be active on all days - ``monthcarry`` (default: ``True``). If the day was not seen (weekend, trading holiday), the timer will be executed on the next available day. - ``allow`` (default: ``None``). A callback which receives a `datetime.date`` instance and returns ``True`` if the date is allowed for timers or else returns ``False`` - ``tzdata`` which can be either ``None`` (default), a ``pytz`` instance or a ``data feed`` instance. ``None``: ``when`` is interpreted at face value (which translates to handling it as if it where UTC even if it's not) ``pytz`` instance: ``when`` will be interpreted as being specified in the local time specified by the timezone instance. ``data feed`` instance: ``when`` will be interpreted as being specified in the local time specified by the ``tz`` parameter of the data feed instance. **Note**: If ``when`` is either ``SESSION_START`` or ``SESSION_END`` and ``tzdata`` is ``None``, the 1st *data feed* in the system (aka ``self.data0``) will be used as the reference to find out the session times. - ``cheat`` (default ``False``) if ``True`` the timer will be called before the broker has a chance to evaluate the orders. This opens the chance to issue orders based on opening price for example right before the session starts - ``*args``: any extra args will be passed to ``notify_timer`` - ``**kwargs``: any extra kwargs will be passed to ``notify_timer`` Return Value: - The created timer ''' return self.cerebro._add_timer( owner=self, when=when, offset=offset, repeat=repeat, weekdays=weekdays, weekcarry=weekcarry, monthdays=monthdays, monthcarry=monthcarry, allow=allow, tzdata=tzdata, strats=False, cheat=cheat, *args, **kwargs) def notify_timer(self, timer, when, *args, **kwargs): '''Receives a timer notification where ``timer`` is the timer which was returned by ``add_timer``, and ``when`` is the calling time. ``args`` and ``kwargs`` are any additional arguments passed to ``add_timer`` The actual ``when`` time can be later, but the system may have not be able to call the timer before. This value is the timer value and no the system time. ''' pass def notify_cashvalue(self, cash, value): ''' Receives the current fund value, value status of the strategy's broker ''' pass def notify_fund(self, cash, value, fundvalue, shares): ''' Receives the current cash, value, fundvalue and fund shares ''' pass def notify_order(self, order): ''' Receives an order whenever there has been a change in one ''' pass def notify_trade(self, trade): ''' Receives a trade whenever there has been a change in one ''' pass def notify_store(self, msg, *args, **kwargs): '''Receives a notification from a store provider''' pass def notify_data(self, data, status, *args, **kwargs): '''Receives a notification from data''' pass def getdatanames(self): ''' Returns a list of the existing data names ''' return keys(self.env.datasbyname) def getdatabyname(self, name): ''' Returns a given data by name using the environment (cerebro) ''' return self.env.datasbyname[name] def cancel(self, order): '''Cancels the order in the broker''' self.broker.cancel(order) def buy(self, data=None, size=None, price=None, plimit=None, exectype=None, valid=None, tradeid=0, oco=None, trailamount=None, trailpercent=None, parent=None, transmit=True, **kwargs): '''Create a buy (long) order and send it to the broker - ``data`` (default: ``None``) For which data the order has to be created. If ``None`` then the first data in the system, ``self.datas[0] or self.data0`` (aka ``self.data``) will be used - ``size`` (default: ``None``) Size to use (positive) of units of data to use for the order. If ``None`` the ``sizer`` instance retrieved via ``getsizer`` will be used to determine the size. - ``price`` (default: ``None``) Price to use (live brokers may place restrictions on the actual format if it does not comply to minimum tick size requirements) ``None`` is valid for ``Market`` and ``Close`` orders (the market determines the price) For ``Limit``, ``Stop`` and ``StopLimit`` orders this value determines the trigger point (in the case of ``Limit`` the trigger is obviously at which price the order should be matched) - ``plimit`` (default: ``None``) Only applicable to ``StopLimit`` orders. This is the price at which to set the implicit *Limit* order, once the *Stop* has been triggered (for which ``price`` has been used) - ``trailamount`` (default: ``None``) If the order type is StopTrail or StopTrailLimit, this is an absolute amount which determines the distance to the price (below for a Sell order and above for a buy order) to keep the trailing stop - ``trailpercent`` (default: ``None``) If the order type is StopTrail or StopTrailLimit, this is a percentage amount which determines the distance to the price (below for a Sell order and above for a buy order) to keep the trailing stop (if ``trailamount`` is also specified it will be used) - ``exectype`` (default: ``None``) Possible values: - ``Order.Market`` or ``None``. A market order will be executed with the next available price. In backtesting it will be the opening price of the next bar - ``Order.Limit``. An order which can only be executed at the given ``price`` or better - ``Order.Stop``. An order which is triggered at ``price`` and executed like an ``Order.Market`` order - ``Order.StopLimit``. An order which is triggered at ``price`` and executed as an implicit *Limit* order with price given by ``pricelimit`` - ``Order.Close``. An order which can only be executed with the closing price of the session (usually during a closing auction) - ``Order.StopTrail``. An order which is triggered at ``price`` minus ``trailamount`` (or ``trailpercent``) and which is updated if the price moves away from the stop - ``Order.StopTrailLimit``. An order which is triggered at ``price`` minus ``trailamount`` (or ``trailpercent``) and which is updated if the price moves away from the stop - ``valid`` (default: ``None``) Possible values: - ``None``: this generates an order that will not expire (aka *Good till cancel*) and remain in the market until matched or canceled. In reality brokers tend to impose a temporal limit, but this is usually so far away in time to consider it as not expiring - ``datetime.datetime`` or ``datetime.date`` instance: the date will be used to generate an order valid until the given datetime (aka *good till date*) - ``Order.DAY`` or ``0`` or ``timedelta()``: a day valid until the *End of the Session* (aka *day* order) will be generated - ``numeric value``: This is assumed to be a value corresponding to a datetime in ``matplotlib`` coding (the one used by ``backtrader``) and will used to generate an order valid until that time (*good till date*) - ``tradeid`` (default: ``0``) This is an internal value applied by ``backtrader`` to keep track of overlapping trades on the same asset. This ``tradeid`` is sent back to the *strategy* when notifying changes to the status of the orders. - ``oco`` (default: ``None``) Another ``order`` instance. This order will become part of an OCO (Order Cancel Others) group. The execution of one of the orders, immediately cancels all others in the same group - ``parent`` (default: ``None``) Controls the relationship of a group of orders, for example a buy which is bracketed by a high-side limit sell and a low side stop sell. The high/low side orders remain inactive until the parent order has been either executed (they become active) or is canceled/expires (the children are also canceled) bracket orders have the same size - ``transmit`` (default: ``True``) Indicates if the order has to be **transmitted**, ie: not only placed in the broker but also issued. This is meant for example to control bracket orders, in which one disables the transmission for the parent and 1st set of children and activates it for the last children, which triggers the full placement of all bracket orders. - ``**kwargs``: additional broker implementations may support extra parameters. ``backtrader`` will pass the *kwargs* down to the created order objects Example: if the 4 order execution types directly supported by ``backtrader`` are not enough, in the case of for example *Interactive Brokers* the following could be passed as *kwargs*:: orderType='LIT', lmtPrice=10.0, auxPrice=9.8 This would override the settings created by ``backtrader`` and generate a ``LIMIT IF TOUCHED`` order with a *touched* price of 9.8 and a *limit* price of 10.0. Returns: - the submitted order ''' if isinstance(data, string_types): data = self.getdatabyname(data) data = data if data is not None else self.datas[0] size = size if size is not None else self.getsizing(data, isbuy=True) if size: return self.broker.buy( self, data, size=abs(size), price=price, plimit=plimit, exectype=exectype, valid=valid, tradeid=tradeid, oco=oco, trailamount=trailamount, trailpercent=trailpercent, parent=parent, transmit=transmit, **kwargs) return None def sell(self, data=None, size=None, price=None, plimit=None, exectype=None, valid=None, tradeid=0, oco=None, trailamount=None, trailpercent=None, parent=None, transmit=True, **kwargs): ''' To create a selll (short) order and send it to the broker See the documentation for ``buy`` for an explanation of the parameters Returns: the submitted order ''' if isinstance(data, string_types): data = self.getdatabyname(data) data = data if data is not None else self.datas[0] size = size if size is not None else self.getsizing(data, isbuy=False) if size: return self.broker.sell( self, data, size=abs(size), price=price, plimit=plimit, exectype=exectype, valid=valid, tradeid=tradeid, oco=oco, trailamount=trailamount, trailpercent=trailpercent, parent=parent, transmit=transmit, **kwargs) return None def close(self, data=None, size=None, **kwargs): ''' Counters a long/short position closing it See the documentation for ``buy`` for an explanation of the parameters Note: - ``size``: automatically calculated from the existing position if not provided (default: ``None``) by the caller Returns: the submitted order ''' if isinstance(data, string_types): data = self.getdatabyname(data) elif data is None: data = self.data possize = self.getposition(data, self.broker).size size = abs(size if size is not None else possize) if possize > 0: return self.sell(data=data, size=size, **kwargs) elif possize < 0: return self.buy(data=data, size=size, **kwargs) return None def buy_bracket(self, data=None, size=None, price=None, plimit=None, exectype=bt.Order.Limit, valid=None, tradeid=0, trailamount=None, trailpercent=None, oargs={}, stopprice=None, stopexec=bt.Order.Stop, stopargs={}, limitprice=None, limitexec=bt.Order.Limit, limitargs={}, **kwargs): ''' Create a bracket order group (low side - buy order - high side). The default behavior is as follows: - Issue a **buy** order with execution ``Limit`` - Issue a *low side* bracket **sell** order with execution ``Stop`` - Issue a *high side* bracket **sell** order with execution ``Limit``. See below for the different parameters - ``data`` (default: ``None``) For which data the order has to be created. If ``None`` then the first data in the system, ``self.datas[0] or self.data0`` (aka ``self.data``) will be used - ``size`` (default: ``None``) Size to use (positive) of units of data to use for the order. If ``None`` the ``sizer`` instance retrieved via ``getsizer`` will be used to determine the size. **Note**: The same size is applied to all 3 orders of the bracket - ``price`` (default: ``None``) Price to use (live brokers may place restrictions on the actual format if it does not comply to minimum tick size requirements) ``None`` is valid for ``Market`` and ``Close`` orders (the market determines the price) For ``Limit``, ``Stop`` and ``StopLimit`` orders this value determines the trigger point (in the case of ``Limit`` the trigger is obviously at which price the order should be matched) - ``plimit`` (default: ``None``) Only applicable to ``StopLimit`` orders. This is the price at which to set the implicit *Limit* order, once the *Stop* has been triggered (for which ``price`` has been used) - ``trailamount`` (default: ``None``) If the order type is StopTrail or StopTrailLimit, this is an absolute amount which determines the distance to the price (below for a Sell order and above for a buy order) to keep the trailing stop - ``trailpercent`` (default: ``None``) If the order type is StopTrail or StopTrailLimit, this is a percentage amount which determines the distance to the price (below for a Sell order and above for a buy order) to keep the trailing stop (if ``trailamount`` is also specified it will be used) - ``exectype`` (default: ``bt.Order.Limit``) Possible values: (see the documentation for the method ``buy`` - ``valid`` (default: ``None``) Possible values: (see the documentation for the method ``buy`` - ``tradeid`` (default: ``0``) Possible values: (see the documentation for the method ``buy`` - ``oargs`` (default: ``{}``) Specific keyword arguments (in a ``dict``) to pass to the main side order. Arguments from the default ``**kwargs`` will be applied on top of this. - ``**kwargs``: additional broker implementations may support extra parameters. ``backtrader`` will pass the *kwargs* down to the created order objects Possible values: (see the documentation for the method ``buy`` **Note**: this ``kwargs`` will be applied to the 3 orders of a bracket. See below for specific keyword arguments for the low and high side orders - ``stopprice`` (default: ``None``) Specific price for the *low side* stop order - ``stopexec`` (default: ``bt.Order.Stop``) Specific execution type for the *low side* order - ``stopargs`` (default: ``{}``) Specific keyword arguments (in a ``dict``) to pass to the low side order. Arguments from the default ``**kwargs`` will be applied on top of this. - ``limitprice`` (default: ``None``) Specific price for the *high side* stop order - ``stopexec`` (default: ``bt.Order.Limit``) Specific execution type for the *high side* order - ``limitargs`` (default: ``{}``) Specific keyword arguments (in a ``dict``) to pass to the high side order. Arguments from the default ``**kwargs`` will be applied on top of this. High/Low Side orders can be suppressed by using: - ``limitexec=None`` to suppress the *high side* - ``stopexec=None`` to suppress the *low side* Returns: - A list containing the 3 orders [order, stop side, limit side] - If high/low orders have been suppressed the return value will still contain 3 orders, but those suppressed will have a value of ``None`` ''' kargs = dict(size=size, data=data, price=price, plimit=plimit, exectype=exectype, valid=valid, tradeid=tradeid, trailamount=trailamount, trailpercent=trailpercent) kargs.update(oargs) kargs.update(kwargs) kargs['transmit'] = limitexec is None and stopexec is None o = self.buy(**kargs) if stopexec is not None: # low side / stop kargs = dict(data=data, price=stopprice, exectype=stopexec, valid=valid, tradeid=tradeid) kargs.update(stopargs) kargs.update(kwargs) kargs['parent'] = o kargs['transmit'] = limitexec is None kargs['size'] = o.size ostop = self.sell(**kargs) else: ostop = None if limitexec is not None: # high side / limit kargs = dict(data=data, price=limitprice, exectype=limitexec, valid=valid, tradeid=tradeid) kargs.update(limitargs) kargs.update(kwargs) kargs['parent'] = o kargs['transmit'] = True kargs['size'] = o.size olimit = self.sell(**kargs) else: olimit = None return [o, ostop, olimit] def sell_bracket(self, data=None, size=None, price=None, plimit=None, exectype=bt.Order.Limit, valid=None, tradeid=0, trailamount=None, trailpercent=None, oargs={}, stopprice=None, stopexec=bt.Order.Stop, stopargs={}, limitprice=None, limitexec=bt.Order.Limit, limitargs={}, **kwargs): ''' Create a bracket order group (low side - buy order - high side). The default behavior is as follows: - Issue a **sell** order with execution ``Limit`` - Issue a *high side* bracket **buy** order with execution ``Stop`` - Issue a *low side* bracket **buy** order with execution ``Limit``. See ``bracket_buy`` for the meaning of the parameters High/Low Side orders can be suppressed by using: - ``stopexec=None`` to suppress the *high side* - ``limitexec=None`` to suppress the *low side* Returns: - A list containing the 3 orders [order, stop side, limit side] - If high/low orders have been suppressed the return value will still contain 3 orders, but those suppressed will have a value of ``None`` ''' kargs = dict(size=size, data=data, price=price, plimit=plimit, exectype=exectype, valid=valid, tradeid=tradeid, trailamount=trailamount, trailpercent=trailpercent) kargs.update(oargs) kargs.update(kwargs) kargs['transmit'] = limitexec is None and stopexec is None o = self.sell(**kargs) if stopexec is not None: # high side / stop kargs = dict(data=data, price=stopprice, exectype=stopexec, valid=valid, tradeid=tradeid) kargs.update(stopargs) kargs.update(kwargs) kargs['parent'] = o kargs['transmit'] = limitexec is None # transmit if last kargs['size'] = o.size ostop = self.buy(**kargs) else: ostop = None if limitexec is not None: # low side / limit kargs = dict(data=data, price=limitprice, exectype=limitexec, valid=valid, tradeid=tradeid) kargs.update(limitargs) kargs.update(kwargs) kargs['parent'] = o kargs['transmit'] = True kargs['size'] = o.size olimit = self.buy(**kargs) else: olimit = None return [o, ostop, olimit] def order_target_size(self, data=None, target=0, **kwargs): ''' Place an order to rebalance a position to have final size of ``target`` The current ``position`` size is taken into account as the start point to achieve ``target`` - If ``target`` > ``pos.size`` -> buy ``target - pos.size`` - If ``target`` < ``pos.size`` -> sell ``pos.size - target`` It returns either: - The generated order or - ``None`` if no order has been issued (``target == position.size``) ''' if isinstance(data, string_types): data = self.getdatabyname(data) elif data is None: data = self.data possize = self.getposition(data, self.broker).size if not target and possize: return self.close(data=data, size=possize, **kwargs) elif target > possize: return self.buy(data=data, size=target - possize, **kwargs) elif target < possize: return self.sell(data=data, size=possize - target, **kwargs) return None # no execution target == possize def order_target_value(self, data=None, target=0.0, price=None, **kwargs): ''' Place an order to rebalance a position to have final value of ``target`` The current ``value`` is taken into account as the start point to achieve ``target`` - If no ``target`` then close postion on data - If ``target`` > ``value`` then buy on data - If ``target`` < ``value`` then sell on data It returns either: - The generated order or - ``None`` if no order has been issued ''' if isinstance(data, string_types): data = self.getdatabyname(data) elif data is None: data = self.data possize = self.getposition(data, self.broker).size if not target and possize: # closing a position return self.close(data=data, size=possize, price=price, **kwargs) else: value = self.broker.getvalue(datas=[data]) comminfo = self.broker.getcommissioninfo(data) # Make sure a price is there price = price if price is not None else data.close[0] if target > value: size = comminfo.getsize(price, target - value) return self.buy(data=data, size=size, price=price, **kwargs) elif target < value: size = comminfo.getsize(price, value - target) return self.sell(data=data, size=size, price=price, **kwargs) return None # no execution size == possize def order_target_percent(self, data=None, target=0.0, **kwargs): ''' Place an order to rebalance a position to have final value of ``target`` percentage of current portfolio ``value`` ``target`` is expressed in decimal: ``0.05`` -> ``5%`` It uses ``order_target_value`` to execute the order. Example: - ``target=0.05`` and portfolio value is ``100`` - The ``value`` to be reached is ``0.05 * 100 = 5`` - ``5`` is passed as the ``target`` value to ``order_target_value`` The current ``value`` is taken into account as the start point to achieve ``target`` The ``position.size`` is used to determine if a position is ``long`` / ``short`` - If ``target`` > ``value`` - buy if ``pos.size >= 0`` (Increase a long position) - sell if ``pos.size < 0`` (Increase a short position) - If ``target`` < ``value`` - sell if ``pos.size >= 0`` (Decrease a long position) - buy if ``pos.size < 0`` (Decrease a short position) It returns either: - The generated order or - ``None`` if no order has been issued (``target == position.size``) ''' if isinstance(data, string_types): data = self.getdatabyname(data) elif data is None: data = self.data possize = self.getposition(data, self.broker).size target *= self.broker.getvalue() return self.order_target_value(data=data, target=target, **kwargs) def getposition(self, data=None, broker=None): ''' Returns the current position for a given data in a given broker. If both are None, the main data and the default broker will be used A property ``position`` is also available ''' data = data if data is not None else self.datas[0] broker = broker or self.broker return broker.getposition(data) position = property(getposition) def getpositionbyname(self, name=None, broker=None): ''' Returns the current position for a given name in a given broker. If both are None, the main data and the default broker will be used A property ``positionbyname`` is also available ''' data = self.datas[0] if not name else self.getdatabyname(name) broker = broker or self.broker return broker.getposition(data) positionbyname = property(getpositionbyname) def getpositions(self, broker=None): ''' Returns the current by data positions directly from the broker If the given ``broker`` is None, the default broker will be used A property ``positions`` is also available ''' broker = broker or self.broker return broker.positions positions = property(getpositions) def getpositionsbyname(self, broker=None): ''' Returns the current by name positions directly from the broker If the given ``broker`` is None, the default broker will be used A property ``positionsbyname`` is also available ''' broker = broker or self.broker positions = broker.positions posbyname = collections.OrderedDict() for name, data in iteritems(self.env.datasbyname): posbyname[name] = positions[data] return posbyname positionsbyname = property(getpositionsbyname) def _addsizer(self, sizer, *args, **kwargs): if sizer is None: self.setsizer(bt.sizers.FixedSize()) else: self.setsizer(sizer(*args, **kwargs)) def setsizer(self, sizer): ''' Replace the default (fixed stake) sizer ''' self._sizer = sizer sizer.set(self, self.broker) return sizer def getsizer(self): ''' Returns the sizer which is in used if automatic statke calculation is used Also available as ``sizer`` ''' return self._sizer sizer = property(getsizer, setsizer) def getsizing(self, data=None, isbuy=True): ''' Return the stake calculated by the sizer instance for the current situation ''' data = data if data is not None else self.datas[0] return self._sizer.getsizing(data, isbuy=isbuy)
Ancestors
Subclasses
Class variables
var alias
var aliased
var csv
var frompackages
var linealias
var packages
var params
var plotinfo
var plotlines
Instance variables
var position
-
Returns the current position for a given data in a given broker.
If both are None, the main data and the default broker will be used
A property
position
is also availableExpand source code
def getposition(self, data=None, broker=None): ''' Returns the current position for a given data in a given broker. If both are None, the main data and the default broker will be used A property ``position`` is also available ''' data = data if data is not None else self.datas[0] broker = broker or self.broker return broker.getposition(data)
var positionbyname
-
Returns the current position for a given name in a given broker.
If both are None, the main data and the default broker will be used
A property
positionbyname
is also availableExpand source code
def getpositionbyname(self, name=None, broker=None): ''' Returns the current position for a given name in a given broker. If both are None, the main data and the default broker will be used A property ``positionbyname`` is also available ''' data = self.datas[0] if not name else self.getdatabyname(name) broker = broker or self.broker return broker.getposition(data)
var positions
-
Returns the current by data positions directly from the broker
If the given
broker
is None, the default broker will be usedA property
positions
is also availableExpand source code
def getpositions(self, broker=None): ''' Returns the current by data positions directly from the broker If the given ``broker`` is None, the default broker will be used A property ``positions`` is also available ''' broker = broker or self.broker return broker.positions
var positionsbyname
-
Returns the current by name positions directly from the broker
If the given
broker
is None, the default broker will be usedA property
positionsbyname
is also availableExpand source code
def getpositionsbyname(self, broker=None): ''' Returns the current by name positions directly from the broker If the given ``broker`` is None, the default broker will be used A property ``positionsbyname`` is also available ''' broker = broker or self.broker positions = broker.positions posbyname = collections.OrderedDict() for name, data in iteritems(self.env.datasbyname): posbyname[name] = positions[data] return posbyname
var sizer
-
Returns the sizer which is in used if automatic statke calculation is used
Also available as
sizer
Expand source code
def getsizer(self): ''' Returns the sizer which is in used if automatic statke calculation is used Also available as ``sizer`` ''' return self._sizer
Methods
def add_timer(self, when, offset=datetime.timedelta(0), repeat=datetime.timedelta(0), weekdays=[], weekcarry=False, monthdays=[], monthcarry=True, allow=None, tzdata=None, cheat=False, *args, **kwargs)
-
Note: can be called during
__init__
orstart
Schedules a timer to invoke either a specified callback or the
notify_timer
of one or more strategies.Arguments
-
when
: can be -
datetime.time
instance (see belowtzdata
) bt.timer.SESSION_START
to reference a session start-
bt.timer.SESSION_END
to reference a session end -
offset
which must be adatetime.timedelta
instance
Used to offset the value
when
. It has a meaningful use in combination withSESSION_START
andSESSION_END
, to indicated things like a timer being called15 minutes
after the session start.-
repeat
which must be adatetime.timedelta
instanceIndicates if after a 1st call, further calls will be scheduled within the same session at the scheduled
repeat
deltaOnce the timer goes over the end of the session it is reset to the original value for
when
-
weekdays
: a sorted iterable with integers indicating on which days (iso codes, Monday is 1, Sunday is 7) the timers can be actually invokedIf not specified, the timer will be active on all days
-
weekcarry
(default:False
). IfTrue
and the weekday was not seen (ex: trading holiday), the timer will be executed on the next day (even if in a new week) -
monthdays
: a sorted iterable with integers indicating on which days of the month a timer has to be executed. For example always on day 15 of the monthIf not specified, the timer will be active on all days
-
monthcarry
(default:True
). If the day was not seen (weekend, trading holiday), the timer will be executed on the next available day. -
allow
(default:None
). A callback which receives a `datetime.dateinstance and returns
True`` if the date is allowed for timers or else returnsFalse
-
tzdata
which can be eitherNone
(default), apytz
instance or adata feed
instance.None
:when
is interpreted at face value (which translates to handling it as if it where UTC even if it's not)pytz
instance:when
will be interpreted as being specified in the local time specified by the timezone instance.data feed
instance:when
will be interpreted as being specified in the local time specified by thetz
parameter of the data feed instance.Note: If
when
is eitherSESSION_START
orSESSION_END
andtzdata
isNone
, the 1st data feed in the system (akaself.data0
) will be used as the reference to find out the session times. -
cheat
(defaultFalse
) ifTrue
the timer will be called before the broker has a chance to evaluate the orders. This opens the chance to issue orders based on opening price for example right before the session starts -
*args
: any extra args will be passed tonotify_timer
-
**kwargs
: any extra kwargs will be passed tonotify_timer
Return Value:
- The created timer
Expand source code
def add_timer(self, when, offset=datetime.timedelta(), repeat=datetime.timedelta(), weekdays=[], weekcarry=False, monthdays=[], monthcarry=True, allow=None, tzdata=None, cheat=False, *args, **kwargs): ''' **Note**: can be called during ``__init__`` or ``start`` Schedules a timer to invoke either a specified callback or the ``notify_timer`` of one or more strategies. Arguments: - ``when``: can be - ``datetime.time`` instance (see below ``tzdata``) - ``bt.timer.SESSION_START`` to reference a session start - ``bt.timer.SESSION_END`` to reference a session end - ``offset`` which must be a ``datetime.timedelta`` instance Used to offset the value ``when``. It has a meaningful use in combination with ``SESSION_START`` and ``SESSION_END``, to indicated things like a timer being called ``15 minutes`` after the session start. - ``repeat`` which must be a ``datetime.timedelta`` instance Indicates if after a 1st call, further calls will be scheduled within the same session at the scheduled ``repeat`` delta Once the timer goes over the end of the session it is reset to the original value for ``when`` - ``weekdays``: a **sorted** iterable with integers indicating on which days (iso codes, Monday is 1, Sunday is 7) the timers can be actually invoked If not specified, the timer will be active on all days - ``weekcarry`` (default: ``False``). If ``True`` and the weekday was not seen (ex: trading holiday), the timer will be executed on the next day (even if in a new week) - ``monthdays``: a **sorted** iterable with integers indicating on which days of the month a timer has to be executed. For example always on day *15* of the month If not specified, the timer will be active on all days - ``monthcarry`` (default: ``True``). If the day was not seen (weekend, trading holiday), the timer will be executed on the next available day. - ``allow`` (default: ``None``). A callback which receives a `datetime.date`` instance and returns ``True`` if the date is allowed for timers or else returns ``False`` - ``tzdata`` which can be either ``None`` (default), a ``pytz`` instance or a ``data feed`` instance. ``None``: ``when`` is interpreted at face value (which translates to handling it as if it where UTC even if it's not) ``pytz`` instance: ``when`` will be interpreted as being specified in the local time specified by the timezone instance. ``data feed`` instance: ``when`` will be interpreted as being specified in the local time specified by the ``tz`` parameter of the data feed instance. **Note**: If ``when`` is either ``SESSION_START`` or ``SESSION_END`` and ``tzdata`` is ``None``, the 1st *data feed* in the system (aka ``self.data0``) will be used as the reference to find out the session times. - ``cheat`` (default ``False``) if ``True`` the timer will be called before the broker has a chance to evaluate the orders. This opens the chance to issue orders based on opening price for example right before the session starts - ``*args``: any extra args will be passed to ``notify_timer`` - ``**kwargs``: any extra kwargs will be passed to ``notify_timer`` Return Value: - The created timer ''' return self.cerebro._add_timer( owner=self, when=when, offset=offset, repeat=repeat, weekdays=weekdays, weekcarry=weekcarry, monthdays=monthdays, monthcarry=monthcarry, allow=allow, tzdata=tzdata, strats=False, cheat=cheat, *args, **kwargs)
-
def buy(self, data=None, size=None, price=None, plimit=None, exectype=None, valid=None, tradeid=0, oco=None, trailamount=None, trailpercent=None, parent=None, transmit=True, **kwargs)
-
Create a buy (long) order and send it to the broker
-
data
(default:None
)For which data the order has to be created. If
None
then the first data in the system,self.datas[0] or self.data0
(akaself.data
) will be used -
size
(default:None
)Size to use (positive) of units of data to use for the order.
If
None
thesizer
instance retrieved viagetsizer
will be used to determine the size. -
price
(default:None
)Price to use (live brokers may place restrictions on the actual format if it does not comply to minimum tick size requirements)
None
is valid forMarket
andClose
orders (the market determines the price)For
Limit
,Stop
andStopLimit
orders this value determines the trigger point (in the case ofLimit
the trigger is obviously at which price the order should be matched) -
plimit
(default:None
)Only applicable to
StopLimit
orders. This is the price at which to set the implicit Limit order, once the Stop has been triggered (for whichprice
has been used) -
trailamount
(default:None
)If the order type is StopTrail or StopTrailLimit, this is an absolute amount which determines the distance to the price (below for a Sell order and above for a buy order) to keep the trailing stop
-
trailpercent
(default:None
)If the order type is StopTrail or StopTrailLimit, this is a percentage amount which determines the distance to the price (below for a Sell order and above for a buy order) to keep the trailing stop (if
trailamount
is also specified it will be used) -
exectype
(default:None
)Possible values:
-
Order.Market
orNone
. A market order will be executed with the next available price. In backtesting it will be the opening price of the next bar -
Order.Limit
. An order which can only be executed at the givenprice
or better -
Order.Stop
. An order which is triggered atprice
and executed like anOrder.Market
order -
Order.StopLimit
. An order which is triggered atprice
and executed as an implicit Limit order with price given bypricelimit
-
Order.Close
. An order which can only be executed with the closing price of the session (usually during a closing auction) -
Order.StopTrail
. An order which is triggered atprice
minustrailamount
(ortrailpercent
) and which is updated if the price moves away from the stop -
Order.StopTrailLimit
. An order which is triggered atprice
minustrailamount
(ortrailpercent
) and which is updated if the price moves away from the stop
-
-
valid
(default:None
)Possible values:
-
None
: this generates an order that will not expire (aka Good till cancel) and remain in the market until matched or canceled. In reality brokers tend to impose a temporal limit, but this is usually so far away in time to consider it as not expiring -
datetime.datetime
ordatetime.date
instance: the date will be used to generate an order valid until the given datetime (aka good till date) -
Order.DAY
or0
ortimedelta()
: a day valid until the End of the Session (aka day order) will be generated -
numeric value
: This is assumed to be a value corresponding to a datetime inmatplotlib
coding (the one used bybacktrader
) and will used to generate an order valid until that time (good till date)
-
-
tradeid
(default:0
)This is an internal value applied by
backtrader
to keep track of overlapping trades on the same asset. Thistradeid
is sent back to the strategy when notifying changes to the status of the orders. -
oco
(default:None
)Another
order
instance. This order will become part of an OCO (Order Cancel Others) group. The execution of one of the orders, immediately cancels all others in the same group -
parent
(default:None
)Controls the relationship of a group of orders, for example a buy which is bracketed by a high-side limit sell and a low side stop sell. The high/low side orders remain inactive until the parent order has been either executed (they become active) or is canceled/expires (the children are also canceled) bracket orders have the same size
-
transmit
(default:True
)Indicates if the order has to be transmitted, ie: not only placed in the broker but also issued. This is meant for example to control bracket orders, in which one disables the transmission for the parent and 1st set of children and activates it for the last children, which triggers the full placement of all bracket orders.
-
**kwargs
: additional broker implementations may support extra parameters.backtrader
will pass the kwargs down to the created order objectsExample: if the 4 order execution types directly supported by
backtrader
are not enough, in the case of for example Interactive Brokers the following could be passed as kwargs::orderType='LIT', lmtPrice=10.0, auxPrice=9.8
This would override the settings created by
backtrader
and generate aLIMIT IF TOUCHED
order with a touched price of 9.8 and a limit price of 10.0.
Returns
- the submitted order
Expand source code
def buy(self, data=None, size=None, price=None, plimit=None, exectype=None, valid=None, tradeid=0, oco=None, trailamount=None, trailpercent=None, parent=None, transmit=True, **kwargs): '''Create a buy (long) order and send it to the broker - ``data`` (default: ``None``) For which data the order has to be created. If ``None`` then the first data in the system, ``self.datas[0] or self.data0`` (aka ``self.data``) will be used - ``size`` (default: ``None``) Size to use (positive) of units of data to use for the order. If ``None`` the ``sizer`` instance retrieved via ``getsizer`` will be used to determine the size. - ``price`` (default: ``None``) Price to use (live brokers may place restrictions on the actual format if it does not comply to minimum tick size requirements) ``None`` is valid for ``Market`` and ``Close`` orders (the market determines the price) For ``Limit``, ``Stop`` and ``StopLimit`` orders this value determines the trigger point (in the case of ``Limit`` the trigger is obviously at which price the order should be matched) - ``plimit`` (default: ``None``) Only applicable to ``StopLimit`` orders. This is the price at which to set the implicit *Limit* order, once the *Stop* has been triggered (for which ``price`` has been used) - ``trailamount`` (default: ``None``) If the order type is StopTrail or StopTrailLimit, this is an absolute amount which determines the distance to the price (below for a Sell order and above for a buy order) to keep the trailing stop - ``trailpercent`` (default: ``None``) If the order type is StopTrail or StopTrailLimit, this is a percentage amount which determines the distance to the price (below for a Sell order and above for a buy order) to keep the trailing stop (if ``trailamount`` is also specified it will be used) - ``exectype`` (default: ``None``) Possible values: - ``Order.Market`` or ``None``. A market order will be executed with the next available price. In backtesting it will be the opening price of the next bar - ``Order.Limit``. An order which can only be executed at the given ``price`` or better - ``Order.Stop``. An order which is triggered at ``price`` and executed like an ``Order.Market`` order - ``Order.StopLimit``. An order which is triggered at ``price`` and executed as an implicit *Limit* order with price given by ``pricelimit`` - ``Order.Close``. An order which can only be executed with the closing price of the session (usually during a closing auction) - ``Order.StopTrail``. An order which is triggered at ``price`` minus ``trailamount`` (or ``trailpercent``) and which is updated if the price moves away from the stop - ``Order.StopTrailLimit``. An order which is triggered at ``price`` minus ``trailamount`` (or ``trailpercent``) and which is updated if the price moves away from the stop - ``valid`` (default: ``None``) Possible values: - ``None``: this generates an order that will not expire (aka *Good till cancel*) and remain in the market until matched or canceled. In reality brokers tend to impose a temporal limit, but this is usually so far away in time to consider it as not expiring - ``datetime.datetime`` or ``datetime.date`` instance: the date will be used to generate an order valid until the given datetime (aka *good till date*) - ``Order.DAY`` or ``0`` or ``timedelta()``: a day valid until the *End of the Session* (aka *day* order) will be generated - ``numeric value``: This is assumed to be a value corresponding to a datetime in ``matplotlib`` coding (the one used by ``backtrader``) and will used to generate an order valid until that time (*good till date*) - ``tradeid`` (default: ``0``) This is an internal value applied by ``backtrader`` to keep track of overlapping trades on the same asset. This ``tradeid`` is sent back to the *strategy* when notifying changes to the status of the orders. - ``oco`` (default: ``None``) Another ``order`` instance. This order will become part of an OCO (Order Cancel Others) group. The execution of one of the orders, immediately cancels all others in the same group - ``parent`` (default: ``None``) Controls the relationship of a group of orders, for example a buy which is bracketed by a high-side limit sell and a low side stop sell. The high/low side orders remain inactive until the parent order has been either executed (they become active) or is canceled/expires (the children are also canceled) bracket orders have the same size - ``transmit`` (default: ``True``) Indicates if the order has to be **transmitted**, ie: not only placed in the broker but also issued. This is meant for example to control bracket orders, in which one disables the transmission for the parent and 1st set of children and activates it for the last children, which triggers the full placement of all bracket orders. - ``**kwargs``: additional broker implementations may support extra parameters. ``backtrader`` will pass the *kwargs* down to the created order objects Example: if the 4 order execution types directly supported by ``backtrader`` are not enough, in the case of for example *Interactive Brokers* the following could be passed as *kwargs*:: orderType='LIT', lmtPrice=10.0, auxPrice=9.8 This would override the settings created by ``backtrader`` and generate a ``LIMIT IF TOUCHED`` order with a *touched* price of 9.8 and a *limit* price of 10.0. Returns: - the submitted order ''' if isinstance(data, string_types): data = self.getdatabyname(data) data = data if data is not None else self.datas[0] size = size if size is not None else self.getsizing(data, isbuy=True) if size: return self.broker.buy( self, data, size=abs(size), price=price, plimit=plimit, exectype=exectype, valid=valid, tradeid=tradeid, oco=oco, trailamount=trailamount, trailpercent=trailpercent, parent=parent, transmit=transmit, **kwargs) return None
-
def buy_bracket(self, data=None, size=None, price=None, plimit=None, exectype=2, valid=None, tradeid=0, trailamount=None, trailpercent=None, oargs={}, stopprice=None, stopexec=3, stopargs={}, limitprice=None, limitexec=2, limitargs={}, **kwargs)
-
Create a bracket order group (low side - buy order - high side). The default behavior is as follows:
-
Issue a buy order with execution
Limit
-
Issue a low side bracket sell order with execution
Stop
-
Issue a high side bracket sell order with execution
Limit
.
See below for the different parameters
-
data
(default:None
)For which data the order has to be created. If
None
then the first data in the system,self.datas[0] or self.data0
(akaself.data
) will be used -
size
(default:None
)Size to use (positive) of units of data to use for the order.
If
None
thesizer
instance retrieved viagetsizer
will be used to determine the size.Note: The same size is applied to all 3 orders of the bracket
-
price
(default:None
)Price to use (live brokers may place restrictions on the actual format if it does not comply to minimum tick size requirements)
None
is valid forMarket
andClose
orders (the market determines the price)For
Limit
,Stop
andStopLimit
orders this value determines the trigger point (in the case ofLimit
the trigger is obviously at which price the order should be matched) -
plimit
(default:None
)Only applicable to
StopLimit
orders. This is the price at which to set the implicit Limit order, once the Stop has been triggered (for whichprice
has been used) -
trailamount
(default:None
)If the order type is StopTrail or StopTrailLimit, this is an absolute amount which determines the distance to the price (below for a Sell order and above for a buy order) to keep the trailing stop
-
trailpercent
(default:None
)If the order type is StopTrail or StopTrailLimit, this is a percentage amount which determines the distance to the price (below for a Sell order and above for a buy order) to keep the trailing stop (if
trailamount
is also specified it will be used) -
exectype
(default:bt.Order.Limit
)Possible values: (see the documentation for the method
buy
-
valid
(default:None
)Possible values: (see the documentation for the method
buy
-
tradeid
(default:0
)Possible values: (see the documentation for the method
buy
-
oargs
(default:{}
)Specific keyword arguments (in a
dict
) to pass to the main side order. Arguments from the default**kwargs
will be applied on top of this. -
**kwargs
: additional broker implementations may support extra parameters.backtrader
will pass the kwargs down to the created order objectsPossible values: (see the documentation for the method
buy
Note: this
kwargs
will be applied to the 3 orders of a bracket. See below for specific keyword arguments for the low and high side orders -
stopprice
(default:None
)Specific price for the low side stop order
-
stopexec
(default:bt.Order.Stop
)Specific execution type for the low side order
-
stopargs
(default:{}
)Specific keyword arguments (in a
dict
) to pass to the low side order. Arguments from the default**kwargs
will be applied on top of this. -
limitprice
(default:None
)Specific price for the high side stop order
-
stopexec
(default:bt.Order.Limit
)Specific execution type for the high side order
-
limitargs
(default:{}
)Specific keyword arguments (in a
dict
) to pass to the high side order. Arguments from the default**kwargs
will be applied on top of this.
High/Low Side orders can be suppressed by using:
-
limitexec=None
to suppress the high side -
stopexec=None
to suppress the low side
Returns
-
A list containing the 3 orders [order, stop side, limit side]
-
If high/low orders have been suppressed the return value will still contain 3 orders, but those suppressed will have a value of
None
Expand source code
def buy_bracket(self, data=None, size=None, price=None, plimit=None, exectype=bt.Order.Limit, valid=None, tradeid=0, trailamount=None, trailpercent=None, oargs={}, stopprice=None, stopexec=bt.Order.Stop, stopargs={}, limitprice=None, limitexec=bt.Order.Limit, limitargs={}, **kwargs): ''' Create a bracket order group (low side - buy order - high side). The default behavior is as follows: - Issue a **buy** order with execution ``Limit`` - Issue a *low side* bracket **sell** order with execution ``Stop`` - Issue a *high side* bracket **sell** order with execution ``Limit``. See below for the different parameters - ``data`` (default: ``None``) For which data the order has to be created. If ``None`` then the first data in the system, ``self.datas[0] or self.data0`` (aka ``self.data``) will be used - ``size`` (default: ``None``) Size to use (positive) of units of data to use for the order. If ``None`` the ``sizer`` instance retrieved via ``getsizer`` will be used to determine the size. **Note**: The same size is applied to all 3 orders of the bracket - ``price`` (default: ``None``) Price to use (live brokers may place restrictions on the actual format if it does not comply to minimum tick size requirements) ``None`` is valid for ``Market`` and ``Close`` orders (the market determines the price) For ``Limit``, ``Stop`` and ``StopLimit`` orders this value determines the trigger point (in the case of ``Limit`` the trigger is obviously at which price the order should be matched) - ``plimit`` (default: ``None``) Only applicable to ``StopLimit`` orders. This is the price at which to set the implicit *Limit* order, once the *Stop* has been triggered (for which ``price`` has been used) - ``trailamount`` (default: ``None``) If the order type is StopTrail or StopTrailLimit, this is an absolute amount which determines the distance to the price (below for a Sell order and above for a buy order) to keep the trailing stop - ``trailpercent`` (default: ``None``) If the order type is StopTrail or StopTrailLimit, this is a percentage amount which determines the distance to the price (below for a Sell order and above for a buy order) to keep the trailing stop (if ``trailamount`` is also specified it will be used) - ``exectype`` (default: ``bt.Order.Limit``) Possible values: (see the documentation for the method ``buy`` - ``valid`` (default: ``None``) Possible values: (see the documentation for the method ``buy`` - ``tradeid`` (default: ``0``) Possible values: (see the documentation for the method ``buy`` - ``oargs`` (default: ``{}``) Specific keyword arguments (in a ``dict``) to pass to the main side order. Arguments from the default ``**kwargs`` will be applied on top of this. - ``**kwargs``: additional broker implementations may support extra parameters. ``backtrader`` will pass the *kwargs* down to the created order objects Possible values: (see the documentation for the method ``buy`` **Note**: this ``kwargs`` will be applied to the 3 orders of a bracket. See below for specific keyword arguments for the low and high side orders - ``stopprice`` (default: ``None``) Specific price for the *low side* stop order - ``stopexec`` (default: ``bt.Order.Stop``) Specific execution type for the *low side* order - ``stopargs`` (default: ``{}``) Specific keyword arguments (in a ``dict``) to pass to the low side order. Arguments from the default ``**kwargs`` will be applied on top of this. - ``limitprice`` (default: ``None``) Specific price for the *high side* stop order - ``stopexec`` (default: ``bt.Order.Limit``) Specific execution type for the *high side* order - ``limitargs`` (default: ``{}``) Specific keyword arguments (in a ``dict``) to pass to the high side order. Arguments from the default ``**kwargs`` will be applied on top of this. High/Low Side orders can be suppressed by using: - ``limitexec=None`` to suppress the *high side* - ``stopexec=None`` to suppress the *low side* Returns: - A list containing the 3 orders [order, stop side, limit side] - If high/low orders have been suppressed the return value will still contain 3 orders, but those suppressed will have a value of ``None`` ''' kargs = dict(size=size, data=data, price=price, plimit=plimit, exectype=exectype, valid=valid, tradeid=tradeid, trailamount=trailamount, trailpercent=trailpercent) kargs.update(oargs) kargs.update(kwargs) kargs['transmit'] = limitexec is None and stopexec is None o = self.buy(**kargs) if stopexec is not None: # low side / stop kargs = dict(data=data, price=stopprice, exectype=stopexec, valid=valid, tradeid=tradeid) kargs.update(stopargs) kargs.update(kwargs) kargs['parent'] = o kargs['transmit'] = limitexec is None kargs['size'] = o.size ostop = self.sell(**kargs) else: ostop = None if limitexec is not None: # high side / limit kargs = dict(data=data, price=limitprice, exectype=limitexec, valid=valid, tradeid=tradeid) kargs.update(limitargs) kargs.update(kwargs) kargs['parent'] = o kargs['transmit'] = True kargs['size'] = o.size olimit = self.sell(**kargs) else: olimit = None return [o, ostop, olimit]
-
def cancel(self, order)
-
Cancels the order in the broker
Expand source code
def cancel(self, order): '''Cancels the order in the broker''' self.broker.cancel(order)
def clear(self)
-
Expand source code
def clear(self): self._orders.extend(self._orderspending) self._orderspending = list() self._tradespending = list()
def close(self, data=None, size=None, **kwargs)
-
Counters a long/short position closing it
See the documentation for
buy
for an explanation of the parametersNote
size
: automatically calculated from the existing position if not provided (default:None
) by the caller
Returns: the submitted order
Expand source code
def close(self, data=None, size=None, **kwargs): ''' Counters a long/short position closing it See the documentation for ``buy`` for an explanation of the parameters Note: - ``size``: automatically calculated from the existing position if not provided (default: ``None``) by the caller Returns: the submitted order ''' if isinstance(data, string_types): data = self.getdatabyname(data) elif data is None: data = self.data possize = self.getposition(data, self.broker).size size = abs(size if size is not None else possize) if possize > 0: return self.sell(data=data, size=size, **kwargs) elif possize < 0: return self.buy(data=data, size=size, **kwargs) return None
def getdatabyname(self, name)
-
Returns a given data by name using the environment (cerebro)
Expand source code
def getdatabyname(self, name): ''' Returns a given data by name using the environment (cerebro) ''' return self.env.datasbyname[name]
def getdatanames(self)
-
Returns a list of the existing data names
Expand source code
def getdatanames(self): ''' Returns a list of the existing data names ''' return keys(self.env.datasbyname)
def getposition(self, data=None, broker=None)
-
Returns the current position for a given data in a given broker.
If both are None, the main data and the default broker will be used
A property
position
is also availableExpand source code
def getposition(self, data=None, broker=None): ''' Returns the current position for a given data in a given broker. If both are None, the main data and the default broker will be used A property ``position`` is also available ''' data = data if data is not None else self.datas[0] broker = broker or self.broker return broker.getposition(data)
def getpositionbyname(self, name=None, broker=None)
-
Returns the current position for a given name in a given broker.
If both are None, the main data and the default broker will be used
A property
positionbyname
is also availableExpand source code
def getpositionbyname(self, name=None, broker=None): ''' Returns the current position for a given name in a given broker. If both are None, the main data and the default broker will be used A property ``positionbyname`` is also available ''' data = self.datas[0] if not name else self.getdatabyname(name) broker = broker or self.broker return broker.getposition(data)
def getpositions(self, broker=None)
-
Returns the current by data positions directly from the broker
If the given
broker
is None, the default broker will be usedA property
positions
is also availableExpand source code
def getpositions(self, broker=None): ''' Returns the current by data positions directly from the broker If the given ``broker`` is None, the default broker will be used A property ``positions`` is also available ''' broker = broker or self.broker return broker.positions
def getpositionsbyname(self, broker=None)
-
Returns the current by name positions directly from the broker
If the given
broker
is None, the default broker will be usedA property
positionsbyname
is also availableExpand source code
def getpositionsbyname(self, broker=None): ''' Returns the current by name positions directly from the broker If the given ``broker`` is None, the default broker will be used A property ``positionsbyname`` is also available ''' broker = broker or self.broker positions = broker.positions posbyname = collections.OrderedDict() for name, data in iteritems(self.env.datasbyname): posbyname[name] = positions[data] return posbyname
def getsizer(self)
-
Returns the sizer which is in used if automatic statke calculation is used
Also available as
sizer
Expand source code
def getsizer(self): ''' Returns the sizer which is in used if automatic statke calculation is used Also available as ``sizer`` ''' return self._sizer
def getsizing(self, data=None, isbuy=True)
-
Return the stake calculated by the sizer instance for the current situation
Expand source code
def getsizing(self, data=None, isbuy=True): ''' Return the stake calculated by the sizer instance for the current situation ''' data = data if data is not None else self.datas[0] return self._sizer.getsizing(data, isbuy=isbuy)
def getwriterheaders(self)
-
Expand source code
def getwriterheaders(self): self.indobscsv = [self] indobs = itertools.chain( self.getindicators_lines(), self.getobservers()) self.indobscsv.extend(filter(lambda x: x.csv, indobs)) headers = list() # prepare the indicators/observers data headers for iocsv in self.indobscsv: name = iocsv.plotinfo.plotname or iocsv.__class__.__name__ headers.append(name) headers.append('len') headers.extend(iocsv.getlinealiases()) return headers
def getwriterinfo(self)
-
Expand source code
def getwriterinfo(self): wrinfo = AutoOrderedDict() wrinfo['Params'] = self.p._getkwargs() sections = [ ['Indicators', self.getindicators_lines()], ['Observers', self.getobservers()] ] for sectname, sectitems in sections: sinfo = wrinfo[sectname] for item in sectitems: itname = item.__class__.__name__ sinfo[itname].Lines = item.lines.getlinealiases() or None sinfo[itname].Params = item.p._getkwargs() or None ainfo = wrinfo.Analyzers # Internal Value Analyzer ainfo.Value.Begin = self.broker.startingcash ainfo.Value.End = self.broker.getvalue() # no slave analyzers for writer for aname, analyzer in self.analyzers.getitems(): ainfo[aname].Params = analyzer.p._getkwargs() or None ainfo[aname].Analysis = analyzer.get_analysis() return wrinfo
def getwritervalues(self)
-
Expand source code
def getwritervalues(self): values = list() for iocsv in self.indobscsv: name = iocsv.plotinfo.plotname or iocsv.__class__.__name__ values.append(name) lio = len(iocsv) values.append(lio) if lio: values.extend(map(lambda l: l[0], iocsv.lines.itersize())) else: values.extend([''] * iocsv.lines.size()) return values
def next_open(self)
-
Expand source code
def next_open(self): pass
def nextstart_open(self)
-
Expand source code
def nextstart_open(self): self.next_open()
def notify_cashvalue(self, cash, value)
-
Receives the current fund value, value status of the strategy's broker
Expand source code
def notify_cashvalue(self, cash, value): ''' Receives the current fund value, value status of the strategy's broker ''' pass
def notify_data(self, data, status, *args, **kwargs)
-
Receives a notification from data
Expand source code
def notify_data(self, data, status, *args, **kwargs): '''Receives a notification from data''' pass
def notify_fund(self, cash, value, fundvalue, shares)
-
Receives the current cash, value, fundvalue and fund shares
Expand source code
def notify_fund(self, cash, value, fundvalue, shares): ''' Receives the current cash, value, fundvalue and fund shares ''' pass
def notify_order(self, order)
-
Receives an order whenever there has been a change in one
Expand source code
def notify_order(self, order): ''' Receives an order whenever there has been a change in one ''' pass
def notify_store(self, msg, *args, **kwargs)
-
Receives a notification from a store provider
Expand source code
def notify_store(self, msg, *args, **kwargs): '''Receives a notification from a store provider''' pass
def notify_timer(self, timer, when, *args, **kwargs)
-
Receives a timer notification where
timer
is the timer which was returned byadd_timer
, andwhen
is the calling time.args
andkwargs
are any additional arguments passed toadd_timer
The actual
when
time can be later, but the system may have not be able to call the timer before. This value is the timer value and no the system time.Expand source code
def notify_timer(self, timer, when, *args, **kwargs): '''Receives a timer notification where ``timer`` is the timer which was returned by ``add_timer``, and ``when`` is the calling time. ``args`` and ``kwargs`` are any additional arguments passed to ``add_timer`` The actual ``when`` time can be later, but the system may have not be able to call the timer before. This value is the timer value and no the system time. ''' pass
def notify_trade(self, trade)
-
Receives a trade whenever there has been a change in one
Expand source code
def notify_trade(self, trade): ''' Receives a trade whenever there has been a change in one ''' pass
def order_target_percent(self, data=None, target=0.0, **kwargs)
-
Place an order to rebalance a position to have final value of
target
percentage of current portfoliovalue
target
is expressed in decimal:0.05
->5%
It uses
order_target_value
to execute the order.Example
-
target=0.05
and portfolio value is100
-
The
value
to be reached is0.05 * 100 = 5
-
5
is passed as thetarget
value toorder_target_value
The current
value
is taken into account as the start point to achievetarget
The
position.size
is used to determine if a position islong
/short
-
If
target
>value
- buy if
pos.size >= 0
(Increase a long position) - sell if
pos.size < 0
(Increase a short position)
- buy if
-
If
target
<value
- sell if
pos.size >= 0
(Decrease a long position) - buy if
pos.size < 0
(Decrease a short position)
- sell if
It returns either:
- The generated order
or
None
if no order has been issued (target == position.size
)
Expand source code
def order_target_percent(self, data=None, target=0.0, **kwargs): ''' Place an order to rebalance a position to have final value of ``target`` percentage of current portfolio ``value`` ``target`` is expressed in decimal: ``0.05`` -> ``5%`` It uses ``order_target_value`` to execute the order. Example: - ``target=0.05`` and portfolio value is ``100`` - The ``value`` to be reached is ``0.05 * 100 = 5`` - ``5`` is passed as the ``target`` value to ``order_target_value`` The current ``value`` is taken into account as the start point to achieve ``target`` The ``position.size`` is used to determine if a position is ``long`` / ``short`` - If ``target`` > ``value`` - buy if ``pos.size >= 0`` (Increase a long position) - sell if ``pos.size < 0`` (Increase a short position) - If ``target`` < ``value`` - sell if ``pos.size >= 0`` (Decrease a long position) - buy if ``pos.size < 0`` (Decrease a short position) It returns either: - The generated order or - ``None`` if no order has been issued (``target == position.size``) ''' if isinstance(data, string_types): data = self.getdatabyname(data) elif data is None: data = self.data possize = self.getposition(data, self.broker).size target *= self.broker.getvalue() return self.order_target_value(data=data, target=target, **kwargs)
-
def order_target_size(self, data=None, target=0, **kwargs)
-
Place an order to rebalance a position to have final size of
target
The current
position
size is taken into account as the start point to achievetarget
-
If
target
>pos.size
-> buytarget - pos.size
-
If
target
<pos.size
-> sellpos.size - target
It returns either:
- The generated order
or
None
if no order has been issued (target == position.size
)
Expand source code
def order_target_size(self, data=None, target=0, **kwargs): ''' Place an order to rebalance a position to have final size of ``target`` The current ``position`` size is taken into account as the start point to achieve ``target`` - If ``target`` > ``pos.size`` -> buy ``target - pos.size`` - If ``target`` < ``pos.size`` -> sell ``pos.size - target`` It returns either: - The generated order or - ``None`` if no order has been issued (``target == position.size``) ''' if isinstance(data, string_types): data = self.getdatabyname(data) elif data is None: data = self.data possize = self.getposition(data, self.broker).size if not target and possize: return self.close(data=data, size=possize, **kwargs) elif target > possize: return self.buy(data=data, size=target - possize, **kwargs) elif target < possize: return self.sell(data=data, size=possize - target, **kwargs) return None # no execution target == possize
-
def order_target_value(self, data=None, target=0.0, price=None, **kwargs)
-
Place an order to rebalance a position to have final value of
target
The current
value
is taken into account as the start point to achievetarget
- If no
target
then close postion on data - If
target
>value
then buy on data - If
target
<value
then sell on data
It returns either:
- The generated order
or
None
if no order has been issued
Expand source code
def order_target_value(self, data=None, target=0.0, price=None, **kwargs): ''' Place an order to rebalance a position to have final value of ``target`` The current ``value`` is taken into account as the start point to achieve ``target`` - If no ``target`` then close postion on data - If ``target`` > ``value`` then buy on data - If ``target`` < ``value`` then sell on data It returns either: - The generated order or - ``None`` if no order has been issued ''' if isinstance(data, string_types): data = self.getdatabyname(data) elif data is None: data = self.data possize = self.getposition(data, self.broker).size if not target and possize: # closing a position return self.close(data=data, size=possize, price=price, **kwargs) else: value = self.broker.getvalue(datas=[data]) comminfo = self.broker.getcommissioninfo(data) # Make sure a price is there price = price if price is not None else data.close[0] if target > value: size = comminfo.getsize(price, target - value) return self.buy(data=data, size=size, price=price, **kwargs) elif target < value: size = comminfo.getsize(price, value - target) return self.sell(data=data, size=size, price=price, **kwargs) return None # no execution size == possize
- If no
def prenext_open(self)
-
Expand source code
def prenext_open(self): pass
def qbuffer(self, savemem=0, replaying=False)
-
Enable the memory saving schemes. Possible values for
savemem
:0: No savings. Each lines object keeps in memory all values
1: All lines objects save memory, using the strictly minimum needed
Negative values are meant to be used when plotting is required:
-1: Indicators at Strategy Level and Observers do not enable memory savings (but anything declared below it does)
-2: Same as -1 plus activation of memory saving for any indicators which has declared plotinfo.plot as False (will not be plotted)
Expand source code
def qbuffer(self, savemem=0, replaying=False): '''Enable the memory saving schemes. Possible values for ``savemem``: 0: No savings. Each lines object keeps in memory all values 1: All lines objects save memory, using the strictly minimum needed Negative values are meant to be used when plotting is required: -1: Indicators at Strategy Level and Observers do not enable memory savings (but anything declared below it does) -2: Same as -1 plus activation of memory saving for any indicators which has declared *plotinfo.plot* as False (will not be plotted) ''' if savemem < 0: # Get any attribute which labels itself as Indicator for ind in self._lineiterators[self.IndType]: subsave = isinstance(ind, (LineSingle,)) if not subsave and savemem < -1: subsave = not ind.plotinfo.plot ind.qbuffer(savemem=subsave) elif savemem > 0: for data in self.datas: data.qbuffer(replaying=replaying) for line in self.lines: line.qbuffer(savemem=1) # Save in all object types depending on the strategy for itcls in self._lineiterators: for it in self._lineiterators[itcls]: it.qbuffer(savemem=1)
def sell(self, data=None, size=None, price=None, plimit=None, exectype=None, valid=None, tradeid=0, oco=None, trailamount=None, trailpercent=None, parent=None, transmit=True, **kwargs)
-
To create a selll (short) order and send it to the broker
See the documentation for
buy
for an explanation of the parametersReturns: the submitted order
Expand source code
def sell(self, data=None, size=None, price=None, plimit=None, exectype=None, valid=None, tradeid=0, oco=None, trailamount=None, trailpercent=None, parent=None, transmit=True, **kwargs): ''' To create a selll (short) order and send it to the broker See the documentation for ``buy`` for an explanation of the parameters Returns: the submitted order ''' if isinstance(data, string_types): data = self.getdatabyname(data) data = data if data is not None else self.datas[0] size = size if size is not None else self.getsizing(data, isbuy=False) if size: return self.broker.sell( self, data, size=abs(size), price=price, plimit=plimit, exectype=exectype, valid=valid, tradeid=tradeid, oco=oco, trailamount=trailamount, trailpercent=trailpercent, parent=parent, transmit=transmit, **kwargs) return None
def sell_bracket(self, data=None, size=None, price=None, plimit=None, exectype=2, valid=None, tradeid=0, trailamount=None, trailpercent=None, oargs={}, stopprice=None, stopexec=3, stopargs={}, limitprice=None, limitexec=2, limitargs={}, **kwargs)
-
Create a bracket order group (low side - buy order - high side). The default behavior is as follows:
-
Issue a sell order with execution
Limit
-
Issue a high side bracket buy order with execution
Stop
-
Issue a low side bracket buy order with execution
Limit
.
See
bracket_buy
for the meaning of the parametersHigh/Low Side orders can be suppressed by using:
-
stopexec=None
to suppress the high side -
limitexec=None
to suppress the low side
Returns
-
A list containing the 3 orders [order, stop side, limit side]
-
If high/low orders have been suppressed the return value will still contain 3 orders, but those suppressed will have a value of
None
Expand source code
def sell_bracket(self, data=None, size=None, price=None, plimit=None, exectype=bt.Order.Limit, valid=None, tradeid=0, trailamount=None, trailpercent=None, oargs={}, stopprice=None, stopexec=bt.Order.Stop, stopargs={}, limitprice=None, limitexec=bt.Order.Limit, limitargs={}, **kwargs): ''' Create a bracket order group (low side - buy order - high side). The default behavior is as follows: - Issue a **sell** order with execution ``Limit`` - Issue a *high side* bracket **buy** order with execution ``Stop`` - Issue a *low side* bracket **buy** order with execution ``Limit``. See ``bracket_buy`` for the meaning of the parameters High/Low Side orders can be suppressed by using: - ``stopexec=None`` to suppress the *high side* - ``limitexec=None`` to suppress the *low side* Returns: - A list containing the 3 orders [order, stop side, limit side] - If high/low orders have been suppressed the return value will still contain 3 orders, but those suppressed will have a value of ``None`` ''' kargs = dict(size=size, data=data, price=price, plimit=plimit, exectype=exectype, valid=valid, tradeid=tradeid, trailamount=trailamount, trailpercent=trailpercent) kargs.update(oargs) kargs.update(kwargs) kargs['transmit'] = limitexec is None and stopexec is None o = self.sell(**kargs) if stopexec is not None: # high side / stop kargs = dict(data=data, price=stopprice, exectype=stopexec, valid=valid, tradeid=tradeid) kargs.update(stopargs) kargs.update(kwargs) kargs['parent'] = o kargs['transmit'] = limitexec is None # transmit if last kargs['size'] = o.size ostop = self.buy(**kargs) else: ostop = None if limitexec is not None: # low side / limit kargs = dict(data=data, price=limitprice, exectype=limitexec, valid=valid, tradeid=tradeid) kargs.update(limitargs) kargs.update(kwargs) kargs['parent'] = o kargs['transmit'] = True kargs['size'] = o.size olimit = self.buy(**kargs) else: olimit = None return [o, ostop, olimit]
-
def set_tradehistory(self, onoff=True)
-
Expand source code
def set_tradehistory(self, onoff=True): self._tradehistoryon = onoff
def setsizer(self, sizer)
-
Replace the default (fixed stake) sizer
Expand source code
def setsizer(self, sizer): ''' Replace the default (fixed stake) sizer ''' self._sizer = sizer sizer.set(self, self.broker) return sizer
def start(self)
-
Called right before the backtesting is about to be started.
Expand source code
def start(self): '''Called right before the backtesting is about to be started.''' pass
def stop(self)
-
Called right before the backtesting is about to be stopped
Expand source code
def stop(self): '''Called right before the backtesting is about to be stopped''' pass
Inherited members