class CurrencyDataFilter(bt.with_metaclass(bt.metabase.MetaParams, object)): ''' The filter remodels the open, high, low, close to make HeikinAshi candlesticks ''' params = (('currencydata', None), ) def __init__(self, data): pass def __call__(self, data): o, h, l, c = data.open[0], data.high[0], data.low[0], data.close[0] #print("BLABLA") if self.p.currencydata is None: # use the 1st data in the system if none given print("Warning, no currency data given!") if len(self.p.currencydata): cc = self.p.currencydata.open[0] else: cc = 1 print(cc) print(data) #print("BLABLA2") data.close[0] = c / cc #print("CCHF %.2f, CUSD: %.2f, USDCHF: %.2f" %(c,c/cc,cc)) data.open[0] = o / cc data.high[0] = h / cc data.low[0] = l / cc #print("BLABLA3") return False # length of data stream is unaltered
class Chainer(bt.with_metaclass(MetaChainer, bt.DataBase)): '''Class that chains datas''' def islive(self): '''Returns ``True`` to notify ``Cerebro`` that preloading and runonce should be deactivated''' return True def __init__(self, *args): self._args = args def start(self): super(Chainer, self).start() for d in self._args: d.setenvironment(self._env) d._start() # put the references in a separate list to have pops self._ds = list(self._args) self._d = self._ds.pop(0) if self._ds else None self._lastdt = datetime.min def stop(self): super(Chainer, self).stop() for d in self._args: d.stop() def get_notifications(self): return [] if self._d is None else self._d.get_notifications() def _gettz(self): '''To be overriden by subclasses which may auto-calculate the timezone''' if self._args: return self._args[0]._gettz() return bt.utils.date.Localizer(self.p.tz) def _load(self): while self._d is not None: if not self._d.next(): # no values from current data source self._d = self._ds.pop(0) if self._ds else None continue # Cannot deliver a date equal or less than an alredy delivered dt = self._d.datetime.datetime() if dt <= self._lastdt: continue self._lastdt = dt for i in range(self._d.size()): self.lines[i][0] = self._d.lines[i][0] return True # Out of the loop -> self._d is None, no data feed to return from return False
class RollOver(bt.with_metaclass(MetaRollOver, bt.DataBase)): '''Class that rolls over to the next future when a condition is met Params: - ``checkdate`` (default: ``None``) This must be a *callable* with the following signature:: checkdate(dt, d): Where: - ``dt`` is a ``datetime.datetime`` object - ``d`` is the current data feed for the active future Expected Return Values: - ``True``: as long as the callable returns this, a switchover can happen to the next future If a commodity expires on the 3rd Friday of March, ``checkdate`` could return ``True`` for the entire week in which the expiration takes place. - ``False``: the expiration cannot take place - ``checkcondition`` (default: ``None``) **Note**: This will only be called if ``checkdate`` has returned ``True`` If ``None`` this will evaluate to ``True`` (execute roll over) internally Else this must be a *callable* with this signature:: checkcondition(d0, d1) Where: - ``d0`` is the current data feed for the active future - ``d1`` is the data feed for the next expiration Expected Return Values: - ``True``: roll-over to the next future Following with the example from ``checkdate``, this could say that the roll-over can only happend if the *volume* from ``d0`` is already less than the volume from ``d1`` - ``False``: the expiration cannot take place ''' params = ( # ('rolls', []), # array of futures to roll over ('checkdate', None), # callable ('checkcondition', None), # callable ) def islive(self): '''Returns ``True`` to notify ``Cerebro`` that preloading and runonce should be deactivated''' return True def __init__(self, *args): self._rolls = args def start(self): super(RollOver, self).start() for d in self._rolls: d.setenvironment(self._env) d._start() # put the references in a separate list to have pops self._ds = list(self._rolls) self._d = self._ds.pop(0) if self._ds else None self._dexp = None self._dts = [datetime.min for xx in self._ds] def stop(self): super(RollOver, self).stop() for d in self._rolls: d.stop() def _gettz(self): '''To be overriden by subclasses which may auto-calculate the timezone''' if self._rolls: return self._rolls[0]._gettz() return bt.utils.date.Localizer(self.p.tz) def _checkdate(self, dt, d): if self.p.checkdate is not None: return self.p.checkdate(dt, d) return False def _checkcondition(self, d0, d1): if self.p.checkcondition is not None: return self.p.checkcondition(d0, d1) return True def _load(self): while self._d is not None: if self._d.next() is not False: # no values from current data src if self._ds: self._d = self._ds.pop(0) self._dts.pop(0) else: self._d = None continue dt0 = self._d.datetime.datetime() # current dt for active data # Synchronize other datas using dt0 for i, d_dt in enumerate(zip(self._ds, self._dts)): d, dt = d_dt while dt < dt0: d.next() self._dts[i] = dt = d.datetime.datetime() # Move expired future as much as needed while self._dexp is not None: if not self._dexp.next(): self._dexp = None break if self._dexp.datetime.datetime() < dt0: continue if self._dexp is None and self._checkdate(dt0, self._d): # rule has been met ... check other factors only if 2 datas # still there if self._ds and self._checkcondition(self._d, self._ds[0]): # Time to switch to next data self._dexp = self._d self._d = self._ds.pop(0) self._dts.pop(0) # Fill the line and tell we die self.lines.datetime[0] = self._d.lines.datetime[0] self.lines.open[0] = self._d.lines.open[0] self.lines.high[0] = self._d.lines.high[0] self.lines.low[0] = self._d.lines.low[0] self.lines.close[0] = self._d.lines.close[0] self.lines.volume[0] = self._d.lines.volume[0] self.lines.openinterest[0] = self._d.lines.openinterest[0] return True # Out of the loop -> self._d is None, no data feed to return from return False
class VChartFile(bt.with_metaclass(MetaVChartFile, bt.DataBase)): ''' Support for `Visual Chart <www.visualchart.com>`_ binary on-disk files for both daily and intradaily formats. Note: - ``dataname``: Market code displayed by Visual Chart. Example: 015ES for EuroStoxx 50 continuous future ''' def start(self): super(VChartFile, self).start() self._store.start(data=self) # Choose extension and extraction/calculation parameters if self.p.timeframe < bt.TimeFrame.Minutes: ext = '.tck' # seconds will still need resampling # FIXME: find reference to tick counter for format elif self.p.timeframe < bt.TimeFrame.Days: ext = '.min' self._dtsize = 2 self._barsize = 32 self._barfmt = 'IIffffII' else: ext = '.fd' self._barsize = 28 self._dtsize = 1 self._barfmt = 'IffffII' # Construct full path basepath = self._store.get_datapath() # Example: 01 + 0 + 015ES + .fd -> 010015ES.fd dataname = '01' + '0' + self.p.dataname + ext # 015ES -> 0 + 015 -> 0015 mktcode = '0' + self.p.dataname[0:3] # basepath/0015/010015ES.fd path = os.path.join(basepath, mktcode, dataname) try: self.f = open(path, 'rb') except IOError: self.f = None def stop(self): if self.f is not None: self.f.close() self.f = None def _load(self): if self.f is None: return False # cannot load more try: bardata = self.f.read(self._barsize) except IOError: self.f = None # cannot return, nullify file return False # cannot load more if not bardata or len(bardata) < self._barsize: self.f = None # cannot return, nullify file return False # cannot load more try: bdata = unpack(self._barfmt, bardata) except: self.f = None return False # First Date y, md = divmod(bdata[0], 500) # Years stored as if they had 500 days m, d = divmod(md, 32) # Months stored as if they had 32 days dt = datetime(y, m, d) # Time if self._dtsize > 1: # Minute Bars # Daily Time is stored in seconds hhmm, ss = divmod(bdata[1], 60) hh, mm = divmod(hhmm, 60) dt = dt.replace(hour=hh, minute=mm, second=ss) else: # Daily Bars dt = datetime.combine(dt, self.p.sessionend) self.lines.datetime[0] = date2num(dt) # Store time # Get the rest of the fields o, h, l, c, v, oi = bdata[self._dtsize:] self.lines.open[0] = o self.lines.high[0] = h self.lines.low[0] = l self.lines.close[0] = c self.lines.volume[0] = v self.lines.openinterest[0] = oi return True # a bar has been successfully loaded
class DayStepsReplayFilter(bt.with_metaclass(bt.MetaParams, object)): ''' Replays a bar in 2 steps: - In the 1st step the "Open-High-Low" could be evaluated to decide if to act on the close (the close is still there ... should not be evaluated) - If a "Close" order has been executed In this 1st fragment the "Close" is replaced through the "open" althoug other alternatives would be possible like high - low average, or an algorithm based on where the "close" ac and - Open-High-Low-Close ''' params = ( ('closevol', 0.5), # 0 -> 1 amount of volume to keep for close ) # replaying = True def __init__(self, data): self.lastdt = None pass def __call__(self, data): # Make a copy of the new bar and remove it from stream datadt = data.datetime.date() # keep the date if self.lastdt == datadt: return False # skip bars that come again in the filter self.lastdt = datadt # keep ref to last seen bar # Make a copy of current data for ohlbar ohlbar = [data.lines[i][0] for i in range(data.size())] closebar = ohlbar[:] # Make a copy for the close # replace close price with o-h-l average ohlprice = ohlbar[data.Open] + ohlbar[data.High] + ohlbar[data.Low] ohlbar[data.Close] = ohlprice / 3.0 vol = ohlbar[data.Volume] # adjust volume ohlbar[data.Volume] = vohl = int(vol * (1.0 - self.p.closevol)) oi = ohlbar[data.OpenInterest] # adjust open interst ohlbar[data.OpenInterest] = 0 # Adjust times dt = datetime.datetime.combine(datadt, data.p.sessionstart) ohlbar[data.DateTime] = data.date2num(dt) # Ajust closebar to generate a single tick -> close price closebar[data.Open] = cprice = closebar[data.Close] closebar[data.High] = cprice closebar[data.Low] = cprice closebar[data.Volume] = vol - vohl ohlbar[data.OpenInterest] = oi # Adjust times dt = datetime.datetime.combine(datadt, data.p.sessionend) closebar[data.DateTime] = data.date2num(dt) # Update stream data.backwards(force=True) # remove the copied bar from stream data._add2stack(ohlbar) # add ohlbar to stack # Add 2nd part to stash to delay processing to next round data._add2stack(closebar, stash=True) return False # the length of the stream was not changed
class DayStepsCloseFilter(bt.with_metaclass(bt.MetaParams, object)): ''' Replays a bar in 2 steps: - In the 1st step the "Open-High-Low" could be evaluated to decide if to act on the close (the close is still there ... should not be evaluated) - If a "Close" order has been executed In this 1st fragment the "Close" is replaced through the "open" althoug other alternatives would be possible like high - low average, or an algorithm based on where the "close" ac and - Open-High-Low-Close ''' params = ( ('cvol', 0.5), # 0 -> 1 amount of volume to keep for close ) def __init__(self, data): self.pendingbar = None def __call__(self, data): # Make a copy of the new bar and remove it from stream closebar = [data.lines[i][0] for i in range(data.size())] datadt = data.datetime.date() # keep the date ohlbar = closebar[:] # Make an open-high-low bar # Adjust volume ohlbar[data.Volume] = int(closebar[data.Volume] * (1.0 - self.p.cvol)) dt = datetime.datetime.combine(datadt, data.p.sessionstart) ohlbar[data.DateTime] = data.date2num(dt) dt = datetime.datetime.combine(datadt, data.p.sessionend) closebar[data.DateTime] = data.date2num(dt) # Update stream data.backwards() # remove the copied bar from stream # Overwrite the new data bar with our pending data - except start point if self.pendingbar is not None: data._updatebar(self.pendingbar) self.pendingbar = closebar # update the pending bar to the new bar data._add2stack(ohlbar) # Add the openbar to the stack for processing return False # the length of the stream was not changed def last(self, data): '''Called when the data is no longer producing bars Can be called multiple times. It has the chance to (for example) produce extra bars''' if self.pendingbar is not None: data.backwards() # remove delivered open bar data._add2stack(self.pendingbar) # add remaining self.pendingbar = None # No further action return True # something delivered return False # nothing delivered here
class DaySplitter_Close(bt.with_metaclass(bt.MetaParams, object)): ''' Splits a daily bar in two parts simulating 2 ticks which will be used to replay the data: - First tick: ``OHLX`` The ``Close`` will be replaced by the *average* of ``Open``, ``High`` and ``Low`` The session opening time is used for this tick and - Second tick: ``CCCC`` The ``Close`` price will be used for the four components of the price The session closing time is used for this tick The volume will be split amongst the 2 ticks using the parameters: - ``closevol`` (default: ``0.5``) The value indicate which percentage, in absolute terms from 0.0 to 1.0, has to be assigned to the *closing* tick. The rest will be assigned to the ``OHLX`` tick. **This filter is meant to be used together with** ``cerebro.replaydata`` ''' params = ( ('closevol', 0.5), # 0 -> 1 amount of volume to keep for close ) # replaying = True def __init__(self, data): self.lastdt = None def __call__(self, data): # Make a copy of the new bar and remove it from stream datadt = data.datetime.date() # keep the date if self.lastdt == datadt: return False # skip bars that come again in the filter self.lastdt = datadt # keep ref to last seen bar # Make a copy of current data for ohlbar ohlbar = [data.lines[i][0] for i in range(data.size())] closebar = ohlbar[:] # Make a copy for the close # replace close price with o-h-l average ohlprice = ohlbar[data.Open] + ohlbar[data.High] + ohlbar[data.Low] ohlbar[data.Close] = ohlprice / 3.0 vol = ohlbar[data.Volume] # adjust volume ohlbar[data.Volume] = vohl = int(vol * (1.0 - self.p.closevol)) oi = ohlbar[data.OpenInterest] # adjust open interst ohlbar[data.OpenInterest] = 0 # Adjust times dt = datetime.datetime.combine(datadt, data.p.sessionstart) ohlbar[data.DateTime] = data.date2num(dt) # Ajust closebar to generate a single tick -> close price closebar[data.Open] = cprice = closebar[data.Close] closebar[data.High] = cprice closebar[data.Low] = cprice closebar[data.Volume] = vol - vohl ohlbar[data.OpenInterest] = oi # Adjust times dt = datetime.datetime.combine(datadt, data.p.sessionend) closebar[data.DateTime] = data.date2num(dt) # Update stream data.backwards(force=True) # remove the copied bar from stream data._add2stack(ohlbar) # add ohlbar to stack # Add 2nd part to stash to delay processing to next round data._add2stack(closebar, stash=True) return False # initial tick can be further processed from stack