def _st_start(self, instart=True, tmout=None): if self.p.historical: self.put_notification(self.DELAYED) dtend = None if self.todate < float('inf'): dtend = num2date(self.todate) dtbegin = None if self.fromdate > float('-inf'): dtbegin = num2date(self.fromdate) self.qhist = self.o.candles( self.p.dataname, dtbegin, dtend, self._timeframe, self._compression, candleFormat=self._candleFormat, includeFirst=self.p.includeFirst) self._state = self._ST_HISTORBACK return True self.qlive = self.o.streaming_prices(self.p.dataname, tmout=tmout) if instart: self._statelivereconn = self.p.backfill_start else: self._statelivereconn = self.p.backfill if self._statelivereconn: self.put_notification(self.DELAYED) self._state = self._ST_LIVE if instart: self._reconns = self.p.reconnections return True # no return before - implicit continue
def _st_start(self): if self.p.historical: self.put_notification(self.DELAYED) dtend = None if self.todate < float('inf'): dtend = num2date(self.todate) dtbegin = None if self.fromdate > float('-inf'): dtbegin = num2date(self.fromdate) self.qhist = self.ib.reqHistoricalDataEx( contract=self.contract, enddate=dtend, begindate=dtbegin, timeframe=self._timeframe, compression=self._compression, what=self.p.what, useRTH=self.p.useRTH, tz=self._tz, sessionend=self.p.sessionend) self._state = self._ST_HISTORBACK return True # continue before # Live is requested if not self.ib.reconnect(resub=True): self.put_notification(self.DISCONNECTED) self._state = self._ST_OVER return False # failed - was so self._statelivereconn = self.p.backfill_start if self.p.backfill_start: self.put_notification(self.DELAYED) self._state = self._ST_LIVE return True # no return before - implicit continue
def _getnexteos(self): '''Returns the next eos using a trading calendar if available''' if self._clone: return self.data._getnexteos() if not len(self): return datetime.datetime.min, 0.0 dt = self.lines.datetime[0] dtime = num2date(dt) if self._calendar is None: nexteos = datetime.datetime.combine(dtime, self.p.sessionend) nextdteos = self.date2num(nexteos) # locl'ed -> utc-like nexteos = num2date(nextdteos) # utc while dtime > nexteos: nexteos += datetime.timedelta(days=1) # already utc-like nextdteos = date2num(nexteos) # -> utc-like else: # returns times in utc _, nexteos = self._calendar.schedule(dtime, self._tz) nextdteos = date2num(nexteos) # nextos is already utc return nexteos, nextdteos
def log(self, txt, dt=None, nodate=False): if not nodate: dt = dt or self.data.datetime[0] dt = bt.num2date(dt) print('%s, %s' % (dt.isoformat(), txt)) else: print('---------- %s' % (txt))
def notify_order(self, order): if not order.alive(): print('{} {} {}@{}'.format( bt.num2date(order.executed.dt), 'buy' if order.isbuy() else 'sell', order.executed.size, order.executed.price) )
def notify_order(self, order): curdtstr = self.data.datetime.datetime().strftime('%a %Y-%m-%d %H:%M:%S') if order.status in [order.Completed]: dtstr = bt.num2date(order.executed.dt).strftime('%a %Y-%m-%d %H:%M:%S') if order.isbuy(): print('%s: BUY EXECUTED, on:' % curdtstr, dtstr) self.order = None else: # Sell print('%s: SELL EXECUTED, on:' % curdtstr, dtstr)
def notify_order(self, order): if order.status != order.Completed: return self.order = None print('{} {} Executed at price {}'.format( bt.num2date(order.executed.dt).date(), 'Buy' * order.isbuy() or 'Sell', order.executed.price) )
def stop(self): # Must have stats.broker cur_year = None value_start = 0.0 value_cur = 0.0 value_end = 0.0 self.rets = list() self.ret = OrderedDict() for i in xrange(len(self.strategy.data)): dt = num2date(self.strategy.data.datetime.getzeroval(i)) value_cur = self.strategy.stats.broker.value.getzeroval(i) if dt.year > cur_year: if cur_year is not None: annualret = (value_end / value_start) - 1.0 self.rets.append(annualret) self.ret[cur_year] = annualret # changing between real years, use last value as new start value_start = value_end else: # No value set whatsoever, use the currently loaded value value_start = value_cur cur_year = dt.year # No matter what, the last value is always the last loaded value value_end = value_cur if cur_year not in self.ret: # finish calculating pending data annualret = (value_end / value_start) - 1.0 self.rets.append(annualret) self.ret[cur_year] = annualret
def _load(self): if self.contract is None: return False # nothing can be done while True: if self._state == self._ST_LIVE: try: msg = (self._storedmsg.pop(None, None) or self.qlive.get(timeout=self.p.qcheck)) except queue.Empty: return None # indicate timeout situation if msg is None: # Conn broken during historical/backfilling self.put_notification(self.CONNBROKEN) # Try to reconnect if not self.ib.reconnect(resub=True): self.put_notification(self.DISCONNECTED) return False # failed self._statelivereconn = self.p.backfill continue if msg == -354: self.put_notification(self.NOTSUBSCRIBED) return False elif msg == -1100: # conn broken # Tell to wait for a message to do a backfill # self._state = self._ST_DISCONN self._statelivereconn = self.p.backfill continue elif msg == -1102: # conn broken/restored tickerId maintained # The message may be duplicated if not self._statelivereconn: self._statelivereconn = self.p.backfill continue elif msg == -1101: # conn broken/restored tickerId gone # The message may be duplicated if not self._statelivereconn: self._statelivereconn = self.p.backfill self.reqdata() # resubscribe continue elif isinstance(msg, integer_types): # Unexpected notification for historical data skip it # May be a "not connected not yet processed" self.put_notification(self.UNKNOWN, msg) continue # Process the message according to expected return type if not self._statelivereconn: if self._laststatus != self.LIVE: if self.qlive.qsize() <= 1: # very short live queue self.put_notification(self.LIVE) if self._usertvol: ret = self._load_rtvolume(msg) else: ret = self._load_rtbar(msg) if ret: return True # could not load bar ... go and get new one continue # Fall through to processing reconnect - try to backfill self._storedmsg[None] = msg # keep the msg # else do a backfill if self._laststatus != self.DELAYED: self.put_notification(self.DELAYED) dtend = None if len(self) > 1: # len == 1 ... forwarded for the 1st time dtbegin = self.datetime.datetime(-1) elif self.fromdate > float('-inf'): dtbegin = num2date(self.fromdate) else: # 1st bar and no begin set # passing None to fetch max possible in 1 request dtbegin = None dtend = msg.datetime if self._usertvol else msg.time self.qhist = self.ib.reqHistoricalDataEx( self.contract, dtend, dtbegin, self._timeframe, self._compression, what=self.p.what, useRTH=self.p.useRTH) self._state = self._ST_HISTORBACK self._statelivereconn = False # no longer in live continue elif self._state == self._ST_HISTORBACK: msg = self.qhist.get() if msg is None: # Conn broken during historical/backfilling # Situation not managed. Simply bail out self.put_notification(self.DISCONNECTED) return False # error management cancelled the queue elif msg == -354: # Data not subscribed self.put_notification(self.NOTSUBSCRIBED) return False elif msg == -420: # No permissions for the data self.put_notification(self.NOTSUBSCRIBED) return False elif isinstance(msg, integer_types): # Unexpected notification for historical data skip it # May be a "not connected not yet processed" self.put_notification(self.UNKNOWN, msg) continue if msg.date is not None: if self._load_rtbar(msg, hist=True): return True # loading worked # the date is from overlapping historical request continue # End of histdata if self.p.historical: # only historical self.put_notification(self.DISCONNECTED) return False # end of historical # Live is also wished - go for it self._state = self._ST_LIVE continue elif self._state == self._ST_START: if self.p.historical: self.put_notification(self.DELAYED) dtend = None if self.todate < float('inf'): dtend = num2date(self.todate) dtbegin = None if self.fromdate > float('-inf'): dtbegin = num2date(self.fromdate) self.qhist = self.ib.reqHistoricalDataEx( self.contract, dtend, dtbegin, self._timeframe, self._compression, what=self.p.what, useRTH=self.p.useRTH) self._state = self._ST_HISTORBACK continue # Live is requested if not self.ib.reconnect(resub=True): self.put_notification(self.DISCONNECTED) return False # failed self._statelivereconn = self.p.backfill_start if not self.p.backfill_start: self.put_notification(self.DELAYED) self._state = self._ST_LIVE elif self._state == self._ST_FROM: if not self.p.backfill_from.next(): # additional data source is consumed self._state = self._ST_START continue # copy lines of the same name for alias in self.lines.getaliases(): lsrc = getattr(self.p.backfill_from.lines, alias) ldst = getattr(self.lines, alias) ldst[0] = lsrc[0] return True
def _load(self): if self._state == self._ST_OVER: return False while True: if self._state == self._ST_LIVE: try: msg = (self._storedmsg.pop(None, None) or self.qlive.get(timeout=self._qcheck)) except queue.Empty: return None # indicate timeout situation if msg is None: # Conn broken during historical/backfilling self.put_notification(self.CONNBROKEN) # Try to reconnect if not self.p.reconnect or self._reconns == 0: # Can no longer reconnect self.put_notification(self.DISCONNECTED) self._state = self._ST_OVER return False # failed self._reconns -= 1 self._st_start(instart=False, tmout=self.p.reconntimeout) continue if 'code' in msg: self.put_notification(self.CONNBROKEN) code = msg['code'] if code not in [599, 598, 596]: self.put_notification(self.DISCONNECTED) self._state = self._ST_OVER return False # failed if not self.p.reconnect or self._reconns == 0: # Can no longer reconnect self.put_notification(self.DISCONNECTED) self._state = self._ST_OVER return False # failed # Can reconnect self._reconns -= 1 self._st_start(instart=False, tmout=self.p.reconntimeout) continue self._reconns = self.p.reconnections # Process the message according to expected return type if not self._statelivereconn: if self._laststatus != self.LIVE: if self.qlive.qsize() <= 1: # very short live queue self.put_notification(self.LIVE) ret = self._load_tick(msg) if ret: return True # could not load bar ... go and get new one continue # Fall through to processing reconnect - try to backfill self._storedmsg[None] = msg # keep the msg # else do a backfill if self._laststatus != self.DELAYED: self.put_notification(self.DELAYED) dtend = None if len(self) > 1: # len == 1 ... forwarded for the 1st time dtbegin = self.datetime.datetime(-1) elif self.fromdate > float('-inf'): dtbegin = num2date(self.fromdate) else: # 1st bar and no begin set # passing None to fetch max possible in 1 request dtbegin = None dtend = datetime.utcfromtimestamp(int(msg['time'])) self.qhist = self.o.candles( self.p.dataname, dtbegin, dtend, self._timeframe, self._compression, candleFormat=self._candleFormat, includeFirst=self.p.includeFirst) self._state = self._ST_HISTORBACK self._statelivereconn = False # no longer in live continue elif self._state == self._ST_HISTORBACK: msg = self.qhist.get() if msg is None: # Conn broken during historical/backfilling # Situation not managed. Simply bail out self.put_notification(self.DISCONNECTED) self._state = self._ST_OVER return False # error management cancelled the queue elif 'code' in msg: # Error self.put_notification(self.NOTSUBSCRIBED) self.put_notification(self.DISCONNECTED) self._state = self._ST_OVER return False if msg: if self._load_history(msg): return True # loading worked continue # not loaded ... date may have been seen else: # End of histdata if self.p.historical: # only historical self.put_notification(self.DISCONNECTED) self._state = self._ST_OVER return False # end of historical # Live is also wished - go for it self._state = self._ST_LIVE continue elif self._state == self._ST_FROM: if not self.p.backfill_from.next(): # additional data source is consumed self._state = self._ST_START continue # copy lines of the same name for alias in self.lines.getlinealiases(): lsrc = getattr(self.p.backfill_from.lines, alias) ldst = getattr(self.lines, alias) ldst[0] = lsrc[0] return True elif self._state == self._ST_START: if not self._st_start(instart=False): self._state = self._ST_OVER return False
def __init__(self, action, **kwargs): # Marker to indicate an openOrder has been seen with # PendinCancel/Cancelled which is indication of an upcoming # cancellation self._willexpire = False self.ordtype = self.Buy if action == 'BUY' else self.Sell super(IBOrder, self).__init__() ib.ext.Order.Order.__init__(self) # Invoke 2nd base class # Now fill in the specific IB parameters self.m_orderType = self._IBOrdTypes[self.exectype] self.m_permid = 0 # 'B' or 'S' should be enough self.m_action = bytes(action) # Set the prices self.m_lmtPrice = 0.0 self.m_auxPrice = 0.0 if self.exectype == self.Market: # is it really needed for Market? pass elif self.exectype == self.Close: # is it ireally needed for Close? pass elif self.exectype == self.Limit: self.m_lmtPrice = self.price elif self.exectype == self.Stop: self.m_auxPrice = self.price # stop price / exec is market elif self.exectype == self.StopLimit: self.m_lmtPrice = self.pricelimit # req limit execution self.m_auxPrice = self.price # trigger price self.m_totalQuantity = abs(self.size) # ib takes only positives self.m_transmit = True # Time In Force: DAY, GTC, IOC, GTD if self.valid is None: tif = 'GTC' # Good til cancelled elif isinstance(self.valid, (datetime, date)): tif = 'GTD' # Good til date self.m_goodTillDate = bytes(self.valid.strftime('%Y%m%d %H:%M:%S')) elif isinstance(self.valid, (timedelta, )): if self.valid == self.DAY: tif = 'DAY' else: tif = 'GTD' # Good til date valid = datetime.now() + self.valid # .now, using localtime self.m_goodTillDate = bytes(valid.strftime('%Y%m%d %H:%M:%S')) elif self.valid == 0: tif = 'DAY' else: tif = 'GTD' # Good til date valid = num2date(self.valid) self.m_goodTillDate = bytes(valid.strftime('%Y%m%d %H:%M:%S')) self.m_tif = bytes(tif) # pass any custom arguments to the order for k in kwargs: setattr(self, (not hasattr(self, k)) * 'm_' + k, kwargs[k])
def num2date(self, dt=None, tz=None, naive=False): if dt is None: dt = self.lines.datetime[0] if tz is None: tz = self._tz return num2date(dt, tz, naive)
def _load(self): if self.contract is None or self._state == self._ST_OVER: return False # nothing can be done while True: if self._state == self._ST_LIVE: try: msg = (self._storedmsg.pop(None, None) or self.qlive.get(timeout=self._qcheck)) except queue.Empty: if True: return None # Code invalidated until further checking is done if not self._statelivereconn: return None # indicate timeout situation # Awaiting data and nothing came in - fake it up until now dtend = self.num2date(date2num(datetime.datetime.utcnow())) dtbegin = None if len(self) > 1: dtbegin = self.num2date(self.datetime[-1]) self.qhist = self.ib.reqHistoricalDataEx( contract=self.contract, enddate=dtend, begindate=dtbegin, timeframe=self._timeframe, compression=self._compression, what=self.p.what, useRTH=self.p.useRTH, tz=self._tz, sessionend=self.p.sessionend) if self._laststatus != self.DELAYED: self.put_notification(self.DELAYED) self._state = self._ST_HISTORBACK self._statelivereconn = False continue # to reenter the loop and hit st_historback if msg is None: # Conn broken during historical/backfilling self.put_notification(self.CONNBROKEN) # Try to reconnect if not self.ib.reconnect(resub=True): self.put_notification(self.DISCONNECTED) return False # failed self._statelivereconn = self.p.backfill continue if msg == -354: self.put_notification(self.NOTSUBSCRIBED) return False elif msg == -1100: # conn broken # Tell to wait for a message to do a backfill # self._state = self._ST_DISCONN self._statelivereconn = self.p.backfill continue elif msg == -1102: # conn broken/restored tickerId maintained # The message may be duplicated if not self._statelivereconn: self._statelivereconn = self.p.backfill continue elif msg == -1101: # conn broken/restored tickerId gone # The message may be duplicated if not self._statelivereconn: self._statelivereconn = self.p.backfill self.reqdata() # resubscribe continue elif isinstance(msg, integer_types): # Unexpected notification for historical data skip it # May be a "not connected not yet processed" self.put_notification(self.UNKNOWN, msg) continue # Process the message according to expected return type if not self._statelivereconn: if self._laststatus != self.LIVE: if self.qlive.qsize() <= 1: # very short live queue self.put_notification(self.LIVE) if self._usertvol: ret = self._load_rtvolume(msg) else: ret = self._load_rtbar(msg) if ret: return True # could not load bar ... go and get new one continue # Fall through to processing reconnect - try to backfill self._storedmsg[None] = msg # keep the msg # else do a backfill if self._laststatus != self.DELAYED: self.put_notification(self.DELAYED) dtend = None if len(self) > 1: # len == 1 ... forwarded for the 1st time # get begin date in utc-like format like msg.datetime dtbegin = num2date(self.datetime[-1]) elif self.fromdate > float('-inf'): dtbegin = num2date(self.fromdate) else: # 1st bar and no begin set # passing None to fetch max possible in 1 request dtbegin = None dtend = msg.datetime if self._usertvol else msg.time self.qhist = self.ib.reqHistoricalDataEx( contract=self.contract, enddate=dtend, begindate=dtbegin, timeframe=self._timeframe, compression=self._compression, what=self.p.what, useRTH=self.p.useRTH, tz=self._tz, sessionend=self.p.sessionend) self._state = self._ST_HISTORBACK self._statelivereconn = False # no longer in live continue elif self._state == self._ST_HISTORBACK: msg = self.qhist.get() if msg is None: # Conn broken during historical/backfilling # Situation not managed. Simply bail out self.put_notification(self.DISCONNECTED) return False # error management cancelled the queue elif msg == -354: # Data not subscribed self.put_notification(self.NOTSUBSCRIBED) return False elif msg == -420: # No permissions for the data self.put_notification(self.NOTSUBSCRIBED) return False elif isinstance(msg, integer_types): # Unexpected notification for historical data skip it # May be a "not connected not yet processed" self.put_notification(self.UNKNOWN, msg) continue if msg.date is not None: if self._load_rtbar(msg, hist=True): return True # loading worked # the date is from overlapping historical request continue # End of histdata if self.p.historical: # only historical self.put_notification(self.DISCONNECTED) return False # end of historical # Live is also wished - go for it self._state = self._ST_LIVE continue elif self._state == self._ST_FROM: if not self.p.backfill_from.next(): # additional data source is consumed self._state = self._ST_START continue # copy lines of the same name for alias in self.lines.getlinealiases(): lsrc = getattr(self.p.backfill_from.lines, alias) ldst = getattr(self.lines, alias) ldst[0] = lsrc[0] return True elif self._state == self._ST_START: if not self._st_start(): return False
def log(self, txt, dt=None): """Вывод строки с датой на консоль""" dt = bt.num2date(self.datas[0].datetime[0]).date( ) if dt is None else dt # Заданная дата или дата текущего бара print(f'{dt.strftime("%d.%m.%Y")}, {txt}' ) # Выводим дату с заданным текстом на консоль
def log(self, txt, dt=None): """Вывод строки с датой на консоль""" dt = bt.num2date(self.datas[0].datetime[0]) if dt is None else dt print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}')
def next(self): msg = '***** NEXT: {} '.format( bt.num2date(self.data.datetime[0], pytz.timezone('Asia/Shanghai'))) for d in self.datas: msg += '{} {} {} '.format(d._name, len(d), d.close[0]) print(msg)
def _load(self): if self.contract is None: self.put_notification(self.DISCONNECTED) return False # nothing can be done while True: if self._state == self._ST_START: if self.p.historical: self.put_notification(self.DELAYED) dtend = None if self.todate < float('inf'): dtend = num2date(self.todate) dtbegin = None if self.fromdate > float('-inf'): dtbegin = num2date(self.fromdate) self.qhist = self.ib.reqHistoricalDataEx( self.contract, dtend, dtbegin, self._timeframe, self._compression, what=self.p.what, useRTH=self.p.useRTH) self._state = self._ST_HISTORBACK continue # Live is requested if not self.ib.reconnect(resub=True): self.put_notification(self.DISCONNECTED) return False # failed self.put_notification(self.DELAYED) self._statelivereconn = True # attempt backfilling self._state = self._ST_LIVE elif self._state == self._ST_HISTORBACK: msg = self.qhist.get() if msg is None: # Conn broken during historical/backfilling # Situation not managed. Simply bail out self.put_notification(self.DISCONNECTED) return False # error management cancelled the queue if msg.date is not None: if self._load_rtbar(msg, hist=True): return True # loading worked # the date is from overlapping historical request continue # End of histdata if self.p.historical: # only historical self.put_notification(self.DISCONNECTED) return False # end of historical # Live is also wished - go for it self._state = self._ST_LIVE continue elif self._state == self._ST_LIVE: try: # FIXME: timeout as parameter and automatically calculated # with a potentially top limit msg = self._storedmsg.pop( None, self.qlive.get(timeout=2.0)) except queue.Empty: return None # indicate timeout situation if msg is None: # Conn broken during historical/backfilling self.put_notification(self.CONNBROKEN) # Try to reconnect if not self.ib.reconnect(resub=True): self.put_notification(self.DISCONNECTED) return False # failed self._statelivereconn = True continue if msg == -1102: # conn broken/restored / tickerId maintained self._statelivereconn = True # backfill and live again continue elif msg == -1101: # conn broken/restored tickerId gone self._statelivereconn = True # backfill and live again self.reqdata() # resubscribe continue # Process the message according to expected return type if not self._statelivereconn: if self._laststatus != self.LIVE: if self.qlive.qsize() <= 1: # very short live queue self.put_notification(self.LIVE) if not self.p.useRT or self.cashtype: return self._load_rtvolume(msg) return self._load_rtbar(msg) # Fall through to processing reconnect - try to backfill self._storedmsg[None] = msg # keep the msg # else do a backfill self.put_notification(self.DELAYED) dtend = None if len(self) > 1: # len == 1 ... forwarded for the 1st time dtbegin = self.datetime.datetime(-1) elif self.fromdate > float('-inf'): dtbegin = num2date(self.fromdate) else: # 1st bar and no begin set # passing None to fetch max possible in 1 request dtbegin = None rtvolume = not self.p.useRT or self.cashtype dtend = msg.datetime if rtvolume else msg.time self.qhist = self.ib.reqHistoricalDataEx( self.contract, dtend, dtbegin, self._timeframe, self._compression, what=self.p.what, useRTH=self.p.useRTH) self._state = self._ST_HISTORBACK self._statelivereconn = False continue
def _load(self): ''' steps 1 - check if we status live. If so process message - Check for error codes in message and change status appropriately - Process the message as long as the status is not trying to reconnect - Setup a backfill if data is missing. 2 - If not, is the status set to perform a backfill? ''' if self._state == self._ST_OVER: return False while True: if self._state == self._ST_LIVE: try: msg = (self._storedmsg.pop(None, None) or self.qlive.get(timeout=self._qcheck)) except queue.Empty: return None # indicate timeout situation if msg is None: # Conn broken during historical/backfilling self.put_notification(self.CONNBROKEN) self.put_notification(self.DISCONNECTED) self._state = self._ST_OVER return False # failed #TODO handle error messages in feed #Check for empty data. Sometimes all the fields return None... if msg['UTM'] is None: return None #self._reconns = self.p.reconnections # Process the message according to expected return type if not self._statelivereconn: if self._laststatus != self.LIVE: if self.qlive.qsize() <= 1: # very short live queue self.put_notification(self.LIVE) ret = self._load_tick(msg) if ret: return True # could not load bar ... go and get new one continue dtend = None if len(self) > 1: # len == 1 ... forwarded for the 1st time dtbegin = self.datetime.datetime(-1) elif self.fromdate > float('-inf'): dtbegin = num2date(self.fromdate) else: # 1st bar and no begin set # passing None to fetch max possible in 1 request dtbegin = None if dtbegin: dtend = datetime.utcfromtimestamp(int(msg['time']) / 10**6) self.qhist = self.o.candles(self.p.dataname, dtbegin, dtend, self._timeframe, self._compression) else: self.qhist = self.o.candles(self.p.dataname, dtbegin, dtend, self._timeframe, self._compression, numpoints=True, bars=self.p.backfill_bars) self._state = self._ST_HISTORBACK self._statelivereconn = False # no longer in live continue elif self._state == self._ST_HISTORBACK: msg = self.qhist.get() if msg is None: # Conn broken during historical/backfilling # Situation not managed. Simply bail out self.put_notification(self.DISCONNECTED) self._state = self._ST_OVER return False # error management cancelled the queue if msg: if self._load_history(msg): return True # loading worked continue # not loaded ... date may have been seen else: # End of histdata if self.p.historical: # only historical self.put_notification(self.DISCONNECTED) self._state = self._ST_OVER return False # end of historical # Live is also wished - go for it self._state = self._ST_LIVE continue elif self._state == self._ST_START: if not self._st_start(instart=False): self._state = self._ST_OVER return False
def log(self, txt, dt=None): if self.p.printout: dt = dt or self.data.datetime[0] dt = bt.num2date(dt) print(f'{dt.isoformat()} {txt}')
def next(self): print('*' * 5, 'NEXT:', bt.num2date(self.data0.datetime[0]), self.data0._name, self.data0.open[0], bt.TimeFrame.getname(self.data0._timeframe), len(self.data0))
def log(txt, dt=None, data=None): dt = dt or data.datetime[0] dt = bt.num2date(dt) _logger.info('%s, %s' % (dt.isoformat(), txt))
def get_clock_time_at_idx(self, idx): return bt.num2date(self._clk.array[idx], self._tz)
if False: # save the indicator print('save the indicator') Indicator_utl = HdfUtility() stat = cerebro.runstrats[0][0] indicator_byvt = stat.Mmt_ind params = cerebro.runstrats[0][0].od_params for indicator, vt in indicator_byvt.items(): indicator_data = indicator.array tradingday = cerebro.datasbyname(vt + '0000').datetime.array df = pd.DataFrame({ 'Indicator': indicatoe_data, 'Date': tradingday }) date_parse = lambda x: num2date(x) df['Date'] = df['Date'].apply(date_parse) excode = params['excode'][params['vt'].index(vt)] Indicator_utl.hdfWrite( EXT_Hdf_Path, excode, vt, df.set_index('Date'), kind1='Indicator', kind2=stat.indicator_name, kind3={'window_prd': stat.params.rollover_window_prd}) # 由于多个品种,所以品种的收盘价数据就不画了 for data in cerebro.datas: data.plotinfo.plot = False cerebro.plot()
def notify_order(self, order): date = self.data.datetime.datetime().date() if order.status == order.Submitted: print('-'*32,' NOTIFY ORDER ','-'*32) print(f'{order.info.ticker} Order Submitted') print('{}, Status: {}, Type: {}-{}, Ref: {}, Size: {}, Price: {}'.format( date, order.status, order.ordtypename(), order.getordername(), order.ref, order.size, 'NA' if not order.price else round(order.price,5) )) print('-'*80) if order.status == order.Accepted: print('-'*32,' NOTIFY ORDER ','-'*32) print(f'{order.info.ticker} Order Accepted') print('{}, Status: {}, Type: {}-{}, Ref: {}, Size: {}, Price: {}'.format( date, order.status, order.ordtypename(), order.getordername(), order.ref, order.size, 'NA' if not order.price else round(order.price,5) )) print('-'*80) if order.status == order.Completed: print('-'*32,' NOTIFY ORDER ','-'*32) print(f'{order.info.ticker} Completed') print('{}, Status: {}, Type: {}-{}, Ref: {}, Size: {}, Price: {}'.format( date, order.status, order.getordername(), order.ordtypename(), order.ref, order.size, 'NA' if not order.price else round(order.price,5) )) print('Created: {} Price: {} Size: {}'.format(bt.num2date(order.created.dt), order.created.price,order.created.size)) print('-'*80) if order.status == order.Canceled: print('-'*32,' NOTIFY ORDER ','-'*32) print(f'Order Canceled') print('{}, Status {}: Type: {}-{}, Ref: {}, Size: {}, Price: {}'.format( date, order.status, order.getordername(), order.ordtypename(), order.ref, order.size, 'NA' if not order.price else round(order.price,5) )) print('-'*80) if order.status == order.Rejected: print('-'*32,' NOTIFY ORDER ','-'*32) print(f'WARNING! {order.info.ticker} Order Rejected') print('{}, Status: {}, Type: {}-{}, Ref: {}, Size: {}, Price: {}'.format( date, order.status, order.getordername(), order.ordtypename(), order.ref, order.size, 'NA' if not order.price else round(order.price,5) )) print('-'*80)
def log(self, txt, dt=None): dt = dt or bt.num2date(self.datetime[0]) print('%s, %s' % (dt.isoformat(), txt))
def notify_order(self, order): if not order.alive(): print('{} {} {}@{}'.format(bt.num2date(order.executed.dt), 'buy' if order.isbuy() else 'sell', order.executed.size, order.executed.price))
def next(self): # 记录收盘价 if self.order: # 检查是否有指令等待执行, return for i, data in enumerate(self.datas): position = self.getposition(data=data) name = data._name if not position: if data.close[0] > self.sma[i][0]: # 执行买入 upper_price = round(data.close[0]*1.05,2) self.log('BUY %s CREATE,close_price:%.2f,upper_price:%.2f' % (name, data.close[0],upper_price)) self.order = self.buy(data,exectype=bt.Order.Limit,price=upper_price,valid=bt.num2date(data.datetime[1])) # self.order = self.buy(data) else: # 执行卖出条件判断:收盘价格跌破15日均线 if data.close[0] < self.sma[i][0]: self.log('SELL %s CREATE, %.2f' % (name, data.close[0])) # 执行卖出 self.order = self.sell(data)
def log(self, txt, dt=None): dt = dt or self.data.datetime[0] dt = bt.num2date(dt) print('[%s] %s' % (dt.isoformat(), txt))
def log(self, txt, dt=None): ''' Logging function fot this strategy''' dt = dt or self.data.datetime[0] if isinstance(dt, float): dt = bt.num2date(dt) print('%s, %s' % (dt.isoformat(), txt))
def num2date(self, dt=None, tz=None, naive=True): if dt is None: return num2date(self.lines.datetime[0], tz or self._tz, naive) return num2date(dt, tz or self._tz, naive)
def next(self): print('*' * 5, 'NEXT:', bt.num2date(self.data.datetime[0]), self.data._name, self.data.open[0], self.data.high[0], self.data.low[0], self.data.close[0], self.data.volume[0], bt.TimeFrame.getname(self.data._timeframe), len(self.data))
def log(self, txt, dt=None): if self.p.printout: dt = dt or self.data.datetime[0] dt = bt.num2date(dt) print('%s, %s' % (dt.isoformat(), txt))
def _blueprint_strategy(self, strategy: bt.Strategy, start=None, end=None, **kwargs): if not strategy.datas: return if not len(strategy): return strat_figures = [] self._fp.analyzers = [a for _, a in strategy.analyzers.getitems()] st_dtime = strategy.lines.datetime.plot() if start is None: start = 0 if end is None: end = len(st_dtime) if isinstance(start, datetime.date): start = bisect.bisect_left(st_dtime, bt.date2num(start)) if isinstance(end, datetime.date): end = bisect.bisect_right(st_dtime, bt.date2num(end)) if end < 0: end = len(st_dtime) + 1 + end # -1 = len() -2 = len() - 1 # TODO: using a pandas.DataFrame is desired. On bokeh 0.12.13 this failed cause of this issue: # https://github.com/bokeh/bokeh/issues/7400 strat_clk: array[float] = strategy.lines.datetime.plotrange(start, end) if self._fp.cds is None: # we use timezone of first data dtline = [bt.num2date(x, strategy.datas[0]._tz) for x in strat_clk] # add an index line to use as x-axis (instead of datetime axis) to avoid datetime gaps (e.g. weekends) indices = list(range(0, len(dtline))) self._fp.cds = ColumnDataSource( data=dict(datetime=dtline, index=indices)) self._build_graph(strategy.datas, strategy.getindicators(), strategy.getobservers()) start, end = Bokeh._get_start_end(strategy, start, end) # reset hover container to not mix hovers with other strategies hoverc = HoverContainer() for master, slaves in self._data_graph.items(): plotabove = getattr(master.plotinfo, 'plotabove', False) bf = Figure(strategy, self._fp.cds, hoverc, start, end, self.p.scheme, type(master), plotabove) strat_figures.append(bf) bf.plot(master, strat_clk, None) for s in slaves: bf.plot(s, strat_clk, master) for v in self._volume_graphs: bf = Figure(strategy, self._fp.cds, hoverc, start, end, self.p.scheme) bf.plot_volume(v, strat_clk, 1.0, start, end) # apply legend click policy for f in strat_figures: f.figure.legend.click_policy = self.p.scheme.legend_click for f in strat_figures: f.figure.legend.background_fill_color = self.p.scheme.legend_background_color f.figure.legend.label_text_color = self.p.scheme.legend_text_color # link axis for i in range(1, len(strat_figures)): strat_figures[i].figure.x_range = strat_figures[0].figure.x_range # configure xaxis visibility if self.p.scheme.xaxis_pos == "bottom": for i, f in enumerate(strat_figures): f.figure.xaxis.visible = False if i <= len( strat_figures) else True hoverc.apply_hovertips(strat_figures) self._fp.figures += strat_figures
def _plot_strategy(self, strategy: bt.Strategy, start=None, end=None, **kwargs): if not strategy.datas: return if not len(strategy): return strat_figures = [] # reset hover container to not mix hovers with other strategies hoverc = HoverContainer() for name, a in strategy.analyzers.getitems(): self._fp.analyzers.append((name, a, strategy, None)) # TODO: using a pandas.DataFrame is desired. On bokeh 0.12.13 this failed cause of this issue: # https://github.com/bokeh/bokeh/issues/7400 if self._fp.cds is None: # use datetime line of first data as master datetime. also convert it according to user provided tz dtline = pandas.Series([ bt.num2date(x, strategy.datas[0]._tz) for x in strategy.datas[0].lines.datetime.plotrange( start, end) ], name="DateTime") self._fp.cds = ColumnDataSource(data={'datetime': dtline}) self._build_graph(strategy.datas, strategy.getindicators(), strategy.getobservers()) start, end = Bokeh._get_start_end(strategy, start, end) for master, slaves in self._data_graph.items(): plotabove = getattr(master.plotinfo, 'plotabove', False) bf = Figure(strategy, self._fp.cds, hoverc, start, end, self.p.scheme, type(master), plotabove) strat_figures.append(bf) bf.plot(master, None) for s in slaves: bf.plot(s, master) for v in self._volume_graphs: bf = Figure(strategy, self._fp.cds, hoverc, start, end, self.p.scheme) bf.plot_volume(v, 1.0, start, end) # apply legend click policy for f in strat_figures: f.figure.legend.click_policy = self.p.scheme.legend_click for f in strat_figures: f.figure.legend.background_fill_color = self.p.scheme.legend_background_color f.figure.legend.label_text_color = self.p.scheme.legend_text_color # link axis for i in range(1, len(strat_figures)): strat_figures[i].figure.x_range = strat_figures[0].figure.x_range # configure xaxis visibility if self.p.scheme.xaxis_pos == "bottom": for i, f in enumerate(strat_figures): f.figure.xaxis.visible = False if i <= len( strat_figures) else True hoverc.apply_hovertips(strat_figures) self._fp.figures += strat_figures
def __init__(self, action, **kwargs): # Marker to indicate an openOrder has been seen with # PendinCancel/Cancelled which is indication of an upcoming # cancellation self._willexpire = False self.ordtype = self.Buy if action == 'BUY' else self.Sell super(IBOrder, self).__init__() ib.ext.Order.Order.__init__(self) # Invoke 2nd base class # Now fill in the specific IB parameters self.m_orderType = self._IBOrdTypes[self.exectype] self.m_permid = 0 # 'B' or 'S' should be enough self.m_action = bytes(action) # Set the prices self.m_lmtPrice = 0.0 self.m_auxPrice = 0.0 if self.exectype == self.Market: # is it really needed for Market? pass elif self.exectype == self.Close: # is it ireally needed for Close? pass elif self.exectype == self.Limit: self.m_lmtPrice = self.price elif self.exectype == self.Stop: self.m_auxPrice = self.price # stop price / exec is market elif self.exectype == self.StopLimit: self.m_lmtPrice = self.pricelimit # req limit execution self.m_auxPrice = self.price # trigger price self.m_totalQuantity = abs(self.size) # ib takes only positives self.m_transmit = True # Time In Force: DAY, GTC, IOC, GTD if self.valid is None: tif = 'GTC' # Good til cancelled elif isinstance(self.valid, (datetime, date)): tif = 'GTD' # Good til date self.m_goodTillDate = bytes(self.valid.strftime('%Y%m%d %H:%M:%S')) elif isinstance(self.valid, (timedelta,)): if self.valid == self.DAY: tif = 'DAY' else: tif = 'GTD' # Good til date valid = datetime.now() + self.valid # .now, using localtime self.m_goodTillDate = bytes(valid.strftime('%Y%m%d %H:%M:%S')) elif self.valid == 0: tif = 'DAY' else: tif = 'GTD' # Good til date valid = num2date(self.valid) self.m_goodTillDate = bytes(valid.strftime('%Y%m%d %H:%M:%S')) self.m_tif = bytes(tif) # pass any custom arguments to the order for k in kwargs: setattr(self, (not hasattr(self, k)) * 'm_' + k, kwargs[k])
def _load(self): if self._state == self._ST_OVER: return False while True: if self._state == self._ST_LIVE: try: msg = (self._storedmsg.pop(None, None) or self.qlive.get(timeout=self._qcheck)) except queue.Empty: return None if 'msg' in msg: self.put_notification(self.CONNBROKEN) if not self.p.reconnect or self._reconns == 0: # Can no longer reconnect self.put_notification(self.DISCONNECTED) self._state = self._ST_OVER return False # failed # sleep only on reconnect if self._reconns != self.p.reconnections: _time.sleep(self.o.p.reconntimeout) # Can reconnect self._reconns -= 1 self._st_start(instart=False) continue self._reconns = self.p.reconnections # Process the message according to expected return type if not self._statelivereconn: if self._laststatus != self.LIVE: if self.qlive.qsize() <= 1: # very short live queue self.put_notification(self.LIVE) if msg: if self.p.candles: ret = self._load_candle(msg) else: ret = self._load_tick(msg) if ret: return True # could not load bar ... go and get new one continue # Fall through to processing reconnect - try to backfill self._storedmsg[None] = msg # keep the msg # else do a backfill if self._laststatus != self.DELAYED: self.put_notification(self.DELAYED) dtend = None if len(self) > 1: # len == 1 ... forwarded for the 1st time dtbegin = self.datetime.datetime(-1).astimezone( timezone.utc) elif self.fromdate > float('-inf'): dtbegin = num2date(self.fromdate, tz=timezone.utc) else: # 1st bar and no begin set # passing None to fetch max possible in 1 request dtbegin = None if msg: dtend = datetime.utcfromtimestamp(float(msg['time'])) # TODO not sure if incomplete candles may destruct something self.qhist = self.o.candles(self.p.dataname, dtbegin, dtend, self._timeframe, self._compression, candleFormat=self._candleFormat, includeFirst=True, onlyComplete=False) self._state = self._ST_HISTORBACK self._statelivereconn = False # no longer in live continue elif self._state == self._ST_HISTORBACK: msg = self.qhist.get() if msg is None: continue elif 'msg' in msg: # Error if not self.p.reconnect or self._reconns == 0: # Can no longer reconnect self.put_notification(self.DISCONNECTED) self._state = self._ST_OVER return False # failed # Can reconnect self._reconns -= 1 self._st_start(instart=False) continue if msg: if self._load_candle(msg): return True # loading worked continue # not loaded ... date may have been seen else: # End of histdata if self.p.historical: # only historical self.put_notification(self.DISCONNECTED) self._state = self._ST_OVER return False # end of historical # Live is also wished - go for it self._state = self._ST_LIVE continue elif self._state == self._ST_FROM: if not self.p.backfill_from.next(): # additional data source is consumed self._state = self._ST_START continue # copy lines of the same name for alias in self.l.getlinealiases(): lsrc = getattr(self.p.backfill_from.lines, alias) ldst = getattr(self.lines, alias) ldst[0] = lsrc[0] return True elif self._state == self._ST_START: if not self._st_start(instart=False): self._state = self._ST_OVER return False
def load(self): while True: # move data pointer forward for new bar self.forward() if self._fromstack(): # bar is available return True 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 # Get a reference to current loaded time dt = self.lines.datetime[0] # A bar has been loaded, adapt the time if self._tzinput: # Input has been converted at face value but it's not UTC in # the input stream dtime = num2date(dt) # get it in a naive datetime # localize it dtime = self._tzinput.localize(dtime) # pytz compatible-ized self.lines.datetime[0] = dt = date2num(dtime) # keep UTC val # Check standard date from/to filters if dt < self.fromdate: # discard loaded bar and carry on self.backwards() continue if dt > self.todate: # discard loaded bar and break out self.backwards(force=True) break # Pass through filters retff = False for ff, fargs, fkwargs in self._filters: # previous filter may have put things onto the stack if self._barstack: for i in range(len(self._barstack)): self._fromstack(forward=True) retff = ff(self, *fargs, **fkwargs) else: retff = ff(self, *fargs, **fkwargs) if retff: # bar removed from systemn break # out of the inner loop if retff: # bar removed from system - loop to get new bar continue # in the greater loop # Checks let the bar through ... notify it return True # Out of the loop ... no more bars or past todate return False
def notify_trade(self, trade): if trade.isclosed: brokervalue = self.strategy.broker.getvalue() dir = 'short' if trade.history[0].event.size > 0: dir = 'long' pricein = trade.history[len(trade.history) - 1].status.price priceout = trade.history[len(trade.history) - 1].event.price datein = bt.num2date(trade.history[0].status.dt) dateout = bt.num2date(trade.history[len(trade.history) - 1].status.dt) if trade.data._timeframe >= bt.TimeFrame.Days: datein = datein.date() dateout = dateout.date() pcntchange = 100 * priceout / pricein - 100 pnl = trade.history[len(trade.history) - 1].status.pnlcomm pnlpcnt = 100 * pnl / brokervalue barlen = trade.history[len(trade.history) - 1].status.barlen pbar = (pnl / barlen) if barlen else pnl # to avoid divide by 0 error self.cumprofit += pnl size = value = 0.0 for record in trade.history: if abs(size) < abs(record.status.size): size = record.status.size value = record.status.value highest_in_trade = max(trade.data.high.get(ago=0, size=barlen + 1)) lowest_in_trade = min(trade.data.low.get(ago=0, size=barlen + 1)) hp = 100 * (highest_in_trade - pricein) / pricein lp = 100 * (lowest_in_trade - pricein) / pricein if dir == 'long': mfe = hp mae = lp if dir == 'short': mfe = -lp mae = -hp analyzer_result = { 'run_id': self.strategy.db_run_id, 'strategy': self.strategy.alias, 'recorded_time': datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 'ref': trade.ref, 'ticker': trade.data._name, 'direction': dir, 'datein': datein, 'pricein': pricein, 'dateout': dateout, 'priceout': priceout, 'change_percentage': round(pcntchange, 2), 'pnl': pnl, 'pnl_percentage': round(pnlpcnt, 2), 'size': size, 'value': value, 'cumpnl': self.cumprofit, 'nbars': barlen, 'pnl_per_bar': round(pbar, 2), 'mfe_percentage': round(mfe, 2), 'mae_percentage': round(mae, 2) } write_to_db.write_to_db(conn=self.conn, data_dict=analyzer_result, table='position_performance') self.trades.append(analyzer_result)
def log(self, txt, dt=None, doprint=False): ''' Logging function fot this strategy''' if self.params.printlog or doprint: dt = dt or bt.num2date(self.data.datetime[0]) print('%s, %s' % (dt, txt))
def _load(self): if self._state == self._ST_OVER: return False while True: if self._state == self._ST_LIVE: try: msg = (self._storedmsg.pop(None, None) or self.qlive.get(timeout=self._qcheck)) except queue.Empty: return None # indicate timeout situation if msg is None: # Conn broken during historical/backfilling self.put_notification(self.CONNBROKEN) # Try to reconnect if not self.p.reconnect or self._reconns == 0: # Can no longer reconnect self.put_notification(self.DISCONNECTED) self._state = self._ST_OVER return False # failed self._reconns -= 1 self._st_start(instart=False, tmout=self.p.reconntimeout) continue if 'code' in msg: self.put_notification(self.CONNBROKEN) code = msg['code'] if code not in [599, 598, 596]: self.put_notification(self.DISCONNECTED) self._state = self._ST_OVER return False # failed if not self.p.reconnect or self._reconns == 0: # Can no longer reconnect self.put_notification(self.DISCONNECTED) self._state = self._ST_OVER return False # failed # Can reconnect self._reconns -= 1 self._st_start(instart=False, tmout=self.p.reconntimeout) continue self._reconns = self.p.reconnections # Process the message according to expected return type if not self._statelivereconn: if self._laststatus != self.LIVE: if self.qlive.qsize() <= 1: # very short live queue self.put_notification(self.LIVE) ret = self._load_tick(msg) if ret: return True # could not load bar ... go and get new one continue # Fall through to processing reconnect - try to backfill self._storedmsg[None] = msg # keep the msg # else do a backfill if self._laststatus != self.DELAYED: self.put_notification(self.DELAYED) dtend = None if len(self) > 1: # len == 1 ... forwarded for the 1st time dtbegin = self.datetime.datetime(-1) elif self.fromdate > float('-inf'): dtbegin = num2date(self.fromdate) else: # 1st bar and no begin set # passing None to fetch max possible in 1 request dtbegin = None dtend = datetime.utcfromtimestamp(int(msg['time']) / 10 ** 6) self.qhist = self.o.candles( self.p.dataname, dtbegin, dtend, self._timeframe, self._compression, candleFormat=self._candleFormat, includeFirst=self.p.includeFirst) self._state = self._ST_HISTORBACK self._statelivereconn = False # no longer in live continue elif self._state == self._ST_HISTORBACK: msg = self.qhist.get() if msg is None: # Conn broken during historical/backfilling # Situation not managed. Simply bail out self.put_notification(self.DISCONNECTED) self._state = self._ST_OVER return False # error management cancelled the queue elif 'code' in msg: # Error self.put_notification(self.NOTSUBSCRIBED) self.put_notification(self.DISCONNECTED) self._state = self._ST_OVER return False if msg: if self._load_history(msg): return True # loading worked continue # not loaded ... date may have been seen else: # End of histdata if self.p.historical: # only historical self.put_notification(self.DISCONNECTED) self._state = self._ST_OVER return False # end of historical # Live is also wished - go for it self._state = self._ST_LIVE continue elif self._state == self._ST_FROM: if not self.p.backfill_from.next(): # additional data source is consumed self._state = self._ST_START continue # copy lines of the same name for alias in self.lines.getlinealiases(): lsrc = getattr(self.p.backfill_from.lines, alias) ldst = getattr(self.lines, alias) ldst[0] = lsrc[0] return True elif self._state == self._ST_START: if not self._st_start(instart=False): self._state = self._ST_OVER return False
def log(self, txt, dt=None): dt = dt or self.data.datetime[0] dt = bt.num2date(dt) print(f"{dt.isoformat()}, {txt}")
def next(self): # Simply log the closing price of the series from the reference self.log(bt.num2date(self.datas[0].datetime[0]), bt.num2date(self.datas[1].datetime[0]), self.datas[0].open[0], self.datas[1].open[0])