Skip to content

Commit

Permalink
Release 1.7.1.93
Browse files Browse the repository at this point in the history
  • Loading branch information
mementum committed Jul 28, 2016
2 parents 8b33d44 + c490641 commit e70ee42
Show file tree
Hide file tree
Showing 15 changed files with 538 additions and 65 deletions.
10 changes: 6 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ backtrader
:alt: PyPi Version
:scale: 100%
:target: https://pypi.python.org/pypi/backtrader/
.. image:: https://img.shields.io/pypi/dm/backtrader.svg
:alt: PyPi Monthly Donwloads
:scale: 100%
:target: https://pypi.python.org/pypi/backtrader/

.. .. image:: https://img.shields.io/pypi/dm/backtrader.svg
:alt: PyPi Monthly Donwloads
:scale: 100%
:target: https://pypi.python.org/pypi/backtrader/
.. image:: https://img.shields.io/pypi/l/backtrader.svg
:alt: License
:scale: 100%
Expand Down
21 changes: 14 additions & 7 deletions backtrader/broker/bbroker.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ def check_submitted(self):

def submit_accept(self, order):
order.pannotated = None
order.submit()
order.accept()
self.pending.append(order)
self.notify(order)
Expand Down Expand Up @@ -475,20 +476,26 @@ def notify(self, order):
self.notifs.append(order.clone())

def _try_exec_close(self, order, pclose):
if len(order.data) > order.plen:
dt0 = order.data.datetime[0]

if dt0 > order.dteos or (self.p.eosbar and dt0 == order.dteos):
# pannotated allows to keep track of the closing bar if there is no
# information which lets us know that the current bar is the closing
# bar (like matching end of session bar)
# The actual matching will be done one bar afterwards but using the
# information from the actual closing bar

dt0 = order.data.datetime[0]
# don't use "len" -> in replay the close can be reached with same len
if dt0 > order.created.dt: # can only execute after creation time
# or (self.p.eosbar and dt0 == order.dteos):
if dt0 >= order.dteos:
# past the end of session or right at it and eosbar is True
if order.pannotated and dt0 != order.dteos:
if order.pannotated and dt0 > order.dteos:
ago = -1
execprice = order.pannotated
else:
ago = 0
execprice = pclose

self._execute(order, ago=0, price=execprice)

self._execute(order, ago=ago, price=execprice)
return

# If no exexcution has taken place ... annotate the closing price
Expand Down
44 changes: 36 additions & 8 deletions backtrader/cerebro.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,17 @@
class Cerebro(with_metaclass(MetaParams, object)):
'''Params:
- ``preload`` (default: True)
- ``preload`` (default: ``True``)
Whether to preload the different ``datas`` passed to cerebro for the
Strategies
- ``runonce`` (default: True)
- ``runonce`` (default: ``True``)
Run ``Indicators`` in vectorized mode to speed up the entire system.
Strategies and Observers will always be run on an event based basis
- ``live`` (default: False)
- ``live`` (default: ``False``)
If no data has reported itself as *live* (via the data's ``islive``
method but the end user still want to run in ``live`` mode, this
Expand All @@ -66,12 +66,27 @@ class Cerebro(with_metaclass(MetaParams, object)):
How many cores to use simultaneously for optimization
- stdstats (default: True)
- ``stdstats`` (default: ``True``)
If True default Observers will be added: Broker (Cash and Value),
Trades and BuySell
- ``exactbars`` (default: False)
- ``oldbuysell`` (default: ``False``)
If ``stdstats`` is ``True`` and observers are getting automatically
added, this switch controls the main behavior of the ``BuySell``
observer
- ``False``: use the modern behavior in which the buy / sell signals
are plotted below / above the low / high prices respectively to avoid
cluttering the plot
- ``True``: use the deprecated behavior in which the buy / sell signals
are plotted where the average price of the order executions for the
given moment in time is. This will of course be ON an OHLC bar or on
a Line on Cloe bar, difficulting the recognition of the plot.
- ``exactbars`` (default: ``False``)
With the default value each and every value stored in a line is kept in
memory
Expand Down Expand Up @@ -113,12 +128,12 @@ class Cerebro(with_metaclass(MetaParams, object)):
- ``runonce`` will be deactivated
- ``writer`` (default: False)
- ``writer`` (default: ``False``)
If set to True a default WriterFile will be created which will print to
stdout. It will be added to the strategy (in addition to any other
writers added by the user code)
- ``tradehistory`` (default: False)
- ``tradehistory`` (default: ``False``)
If set to True, it will activate update event logging in each trade for
all strategies. This can also be accomplished on a per strategy basis
with the strategy method ``set_tradehistory``
Expand All @@ -129,6 +144,7 @@ class Cerebro(with_metaclass(MetaParams, object)):
('runonce', True),
('maxcpus', None),
('stdstats', True),
('oldbuysell', False),
('lookahead', 0),
('exactbars', False),
('live', False),
Expand Down Expand Up @@ -217,6 +233,15 @@ def addobserver(self, obscls, *args, **kwargs):
self.observers.append((False, obscls, args, kwargs))

def addobservermulti(self, obscls, *args, **kwargs):
'''
Adds an ``Observer`` class to the mix. Instantiation will be done at
``run`` time
It will be added once per "data" in the system. A use case is a
buy/sell observer which observes individual datas.
A counter-example is the CashValue, which observes system-wide values
'''
self.observers.append((True, obscls, args, kwargs))

def addstorecb(self, callback):
Expand Down Expand Up @@ -598,7 +623,10 @@ def runstrategies(self, iterstrat):
for idx, strat in enumerate(runstrats):
if self.p.stdstats:
strat._addobserver(False, observers.Broker)
strat._addobserver(True, observers.BuySell)
if self.p.oldbuysell:
strat._addobserver(True, observers.BuySell)
else:
strat._addobserver(True, observers.BuySell, barplot=True)
strat._addobserver(False, observers.Trades)

for multi, obscls, obsargs, obskwargs in self.observers:
Expand Down
44 changes: 27 additions & 17 deletions backtrader/feed.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ def dopostinit(cls, _obj, *args, **kwargs):
_obj.p.sessionend = _obj.p.sessionend.time()

elif _obj.p.sessionend is None:
_obj.p.sessionend = datetime.time.max
# remove 9 to avoid precision rounding errors
_obj.p.sessionend = datetime.time(23, 59, 59, 999990)

if isinstance(_obj.p.fromdate, datetime.date):
# push it to the end of the day, or else intraday
Expand All @@ -99,6 +100,7 @@ def dopostinit(cls, _obj, *args, **kwargs):
_obj.mlen = list()

_obj._barstack = collections.deque() # for filter operations
_obj._barstash = collections.deque() # for filter operations

_obj._filters = list()
_obj._ffilters = list()
Expand Down Expand Up @@ -235,6 +237,7 @@ def qbuffer(self, savemem=0):

def start(self):
self._barstack = collections.deque()
self._barstash = collections.deque()
self.mlen = list()
self._laststatus = self.CONNECTED

Expand Down Expand Up @@ -383,18 +386,19 @@ def load(self):
if self._fromstack(): # bar is available
return True

_loadret = self._load()
if not _loadret: # no bar
# use force to make sure in exactbars the pointer is undone
# this covers especially (but not uniquely) the case in which
# the last bar has been seen and a backwards would ruin pointer
# accounting in the "stop" method of the strategy
self.backwards(force=True) # undo data pointer
if not self._fromstack(stash=True):
_loadret = self._load()
if not _loadret: # no bar use force to make sure in exactbars
# the pointer is undone this covers especially (but not
# uniquely) the case in which the last bar has been seen
# and a backwards would ruin pointer accounting in the
# "stop" method of the strategy
self.backwards(force=True) # undo data pointer

# return the actual returned value which may be None to signal
# no bar is available, but the data feed is not done. False
# means game over
return _loadret
# return the actual returned value which may be None to
# signal no bar is available, but the data feed is not
# done. False means game over
return _loadret

# Get a reference to current loaded time
dt = self.lines.datetime[0]
Expand Down Expand Up @@ -444,9 +448,12 @@ def load(self):
def _load(self):
return False

def _add2stack(self, bar):
def _add2stack(self, bar, stash=False):
'''Saves given bar (list of values) to the stack for later retrieval'''
self._barstack.append(bar)
if not stash:
self._barstack.append(bar)
else:
self._barstash.append(bar)

def _save2stack(self, erase=False, force=False):
'''Saves current bar to the bar stack for later retrieval
Expand All @@ -470,16 +477,19 @@ def _updatebar(self, bar, forward=False, ago=0):
for line, val in zip(self.itersize(), bar):
line[0 + ago] = val

def _fromstack(self, forward=False):
def _fromstack(self, forward=False, stash=False):
'''Load a value from the stack onto the lines to form the new bar
Returns True if values are present, False otherwise
'''
if self._barstack:

coll = self._barstack if not stash else self._barstash

if coll:
if forward:
self.forward()

for line, val in zip(self.itersize(), self._barstack.popleft()):
for line, val in zip(self.itersize(), coll.popleft()):
line[0] = val

return True
Expand Down
68 changes: 63 additions & 5 deletions backtrader/observers/buysell.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,29 @@ class BuySell(Observer):
executions) and will plot them on the chart along the data around the
execution price level
Params: None
Params:
- ``barplot`` (default: ``False``) Plot buy signals below the minimum and
sell signals above the maximum.
If ``False`` it will plot on the average price of executions during a
bar
- ``bardist`` (default: ``0.015`` 1.5%) Distance to max/min when
``barplot`` is ``True``
'''
lines = ('buy', 'sell',)

plotinfo = dict(plot=True, subplot=False, plotlinelabels=True)
plotlines = dict(
buy=dict(marker='^', markersize=8.0, color='lime', fillstyle='full'),
sell=dict(marker='v', markersize=8.0, color='red', fillstyle='full')
buy=dict(marker='^', markersize=8.0, color='lime',
fillstyle='full', ls=''),
sell=dict(marker='v', markersize=8.0, color='red',
fillstyle='full', ls='')
)

params = (
('barplot', False), # plot above/below max/min for clarity in bar plot
('bardist', 0.015), # distance to max/min in absolute perc
)

def next(self):
Expand All @@ -55,6 +70,49 @@ def next(self):
else:
sell.append(order.executed.price)

# Take into account replay ... something could already be in there
# Write down the average buy/sell price
self.lines.buy[0] = math.fsum(buy)/float(len(buy) or 'NaN')
self.lines.sell[0] = math.fsum(sell)/float(len(sell) or 'NaN')

# BUY
curbuy = self.lines.buy[0]
if curbuy != curbuy: # NaN
curbuy = 0.0
self.curbuylen = curbuylen = 0
else:
curbuylen = self.curbuylen

buyops = (curbuy + math.fsum(buy))
buylen = curbuylen + len(buy)

value = buyops / float(buylen or 'NaN')
if not self.p.barplot:
self.lines.buy[0] = value
elif value == value: # Not NaN
pbuy = self.data.low[0] * (1 - self.p.bardist)
self.lines.buy[0] = pbuy

# Update buylen values
curbuy = buyops
self.curbuylen = buylen

# SELL
cursell = self.lines.sell[0]
if cursell != cursell: # NaN
cursell = 0.0
self.curselllen = curselllen = 0
else:
curselllen = self.curselllen

sellops = (cursell + math.fsum(sell))
selllen = curselllen + len(sell)

value = sellops / float(selllen or 'NaN')
if not self.p.barplot:
self.lines.sell[0] = value
elif value == value: # Not NaN
psell = self.data.high[0] * (1 + self.p.bardist)
self.lines.sell[0] = psell

# Update selllen values
cursell = sellops
self.curselllen = selllen
5 changes: 3 additions & 2 deletions backtrader/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,9 @@ def __init__(self):

# provisional end-of-session
session = self.data.p.sessionend
h, m, s = session.hour, session.minute, session.second
dteos = dtime.replace(hour=h, minute=m, second=s)
dteos = dtime.replace(hour=session.hour, minute=session.minute,
second=session.second,
microsecond=session.microsecond)

if dteos < dtime:
# eos before current time ... no ... must be at least next day
Expand Down
3 changes: 3 additions & 0 deletions backtrader/sizers/fixedsize.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ class FixedSize(bt.Sizer):
def _getsizing(self, comminfo, cash, data, isbuy):
return self.params.stake

def setsizing(self, stake):
self.p.stake = stake # OLD METHOD FOR SAMPLE COMPATIBILITY


SizerFix = FixedSize

Expand Down
2 changes: 1 addition & 1 deletion backtrader/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@
unicode_literals)


__version__ = '1.7.0.93'
__version__ = '1.7.1.93'
15 changes: 15 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
1.7.1.93:
- Pinkfish challange sample
- Add stash to feeds to allow filtered output to be resent to filters
- Restore deprecated setsizing method in FixedSize sizer for old quickstart
guide
- Rework quickstart tutorial and samples to use addsizer and deprecate
setsizing
- Allow BuySell observer to plot above / below high / low for clarity,
especially when plotting ohlc/candles bars
- Add support for observer orders during replay
- Improve Close order execution logic
- Fix microsecond precision errors in end of session calculations in order
and feed
- Docstrings cosmetic changes

1.7.0.93:
- Changes to support separate auto-documentation for a branch of an object
hierarchy
Expand Down
Loading

0 comments on commit e70ee42

Please sign in to comment.