Beispiel #1
0
class CandleGenerator(object):

    __slots__ = '_from_tf', '_to_tf', '_candle', '_last_timestamp', '_last_consumed'

    def __init__(self, from_tf, to_tf):
        """
        @param to_tf Generated candle time unit.
        """
        if from_tf and (int(to_tf) % int(from_tf) != 0):
            raise (ValueError(
                "From timeframe %s must be an integral divider of to timeframe %s"
                % (from_tf, to_tf)))

        self._from_tf = float(from_tf)
        self._to_tf = float(to_tf)
        self._candle = None
        self._last_timestamp = 0
        self._last_consumed = 0

    @property
    def current(self):
        """
        If exists returns the current non closed candle.
        """
        return self._candle

    @current.setter
    def current(self, candle):
        self._candle = candle

    @property
    def last_timestamp(self):
        return self._last_timestamp

    @property
    def last_consumed(self):
        return self._last_consumed

    @property
    def from_tf(self):
        return self._from_tf

    @property
    def to_tf(self):
        return self._to_tf

    def generate_from_candles(self, from_candles, ignore_non_ended=True):
        """
        Generate as many higher candles as possible from the array of candles given in parameters.
        @note Non ended candles are ignored because it will false the volume.
        """
        to_candles = []
        self._last_consumed = 0

        for from_candle in from_candles:
            to_candle = self.update_from_candle(from_candle)
            if to_candle:
                to_candles.append(to_candle)

            self._last_consumed += 1

        return to_candles

    def generate_from_ticks(self, from_ticks):
        """
        Generate as many higher candles as possible from the array of ticks given in parameters.
        """
        to_candles = []
        self._last_consumed = 0

        for from_tick in from_ticks:
            to_candle = self.update_from_tick(from_tick)
            if to_candle:
                to_candles.append(to_candle)

            self._last_consumed += 1

        return to_candles

    def basetime(self, timestamp):
        if self._to_tf < 7 * 24 * 60 * 60:
            # simplest
            return int(timestamp / self._to_tf) * self._to_tf
        elif self._to_tf == 7 * 24 * 60 * 60:
            # must find the UTC first day of week
            dt = datetime.utcfromtimestamp(timestamp)
            dt = dt.replace(
                hour=0, minute=0, second=0, microsecond=0,
                tzinfo=UTC()) - timedelta(days=dt.weekday())
            return dt.timestamp()
        elif self._to_tf == 30 * 24 * 60 * 60:
            # replace by first day of month at 00h00 UTC
            dt = datetime.utcfromtimestamp(timestamp)
            dt = dt.replace(day=1,
                            hour=0,
                            minute=0,
                            second=0,
                            microsecond=0,
                            tzinfo=UTC())
            return dt.timestamp()

    def update_from_tick(self, from_tick):
        if from_tick is None:
            return None

        if from_tick[0] <= self._last_timestamp:
            # already done (and what if two consecutives ticks have the same timestamp ?)
            return None

        # basetime can be slow, uses only to create a new candle
        # base_time = self.basetime(from_tick[0])
        ended_candle = None

        # if self._candle and self._candle.timestamp+self._to_tf <= base_time:
        if self._candle and from_tick[
                0] >= self._candle.timestamp + self._to_tf:
            # need to close the candle and to open a new one
            self._candle.set_consolidated(True)
            ended_candle = self._candle

            self._candle = None

        if self._candle is None:
            # open a new one
            base_time = self.basetime(from_tick[0])  # from_tick[0] directly ?
            self._candle = Candle(base_time, self._to_tf)

            self._candle.set_consolidated(False)

            # all open, close, low high from the initial candle
            self._candle.set_bid(from_tick[1])
            self._candle.set_ofr(from_tick[2])

        # update volumes
        self._candle._volume += from_tick[3]

        # update bid prices

        # bid high/low
        self._candle._bid_high = max(self._candle._bid_high, from_tick[1])
        self._candle._bid_low = min(self._candle._bid_low, from_tick[1])

        # potential close
        self._candle._bid_close = from_tick[1]

        # update ofr prices

        # ofr high/low
        self._candle._ofr_high = max(self._candle._ofr_high, from_tick[2])
        self._candle._ofr_low = min(self._candle._ofr_low, from_tick[2])

        # potential close
        self._candle._ofr_close = from_tick[2]

        # keep last timestamp
        self._last_timestamp = from_tick[0]

        return ended_candle

    def update_from_candle(self, from_candle):
        """
        From a timeframe, create/update candle to another timeframe, that must be greater and a multiple of.
        Example of creating/updating hourly candle for 1 minute candles.

        Must be called each time a new candle of the lesser timeframe is append.
        It only create the last or update the current candle.

        A non ended candle is ignored because it will false the volume.
        """
        if from_candle is None or not from_candle.ended:
            return None

        if self._from_tf != from_candle.timeframe:
            raise ValueError(
                "From candle must be of time unit %s but %s is provided" %
                (self._from_tf, from_candle.timeframe))

        if from_candle.timestamp <= self._last_timestamp:
            # already done
            return None

        base_time = self.basetime(from_candle.timestamp)
        ended_candle = None

        if self._candle and self._candle.timestamp + self._to_tf <= base_time:
            # need to close the candle and to open a new one
            self._candle.set_consolidated(True)
            ended_candle = self._candle

            self._candle = None

        if self._candle is None:
            # open a new one
            self._candle = Candle(base_time, self._to_tf)

            self._candle.set_consolidated(False)

            # all open, close, low high from the initial candle
            self._candle.copy_bid(from_candle)
            self._candle.copy_ofr(from_candle)

        # update volumes
        self._candle._volume += from_candle.volume

        # update bid prices
        self._candle._bid_high = max(self._candle._bid_high,
                                     from_candle._bid_high)
        self._candle._bid_low = min(self._candle._bid_low,
                                    from_candle._bid_low)

        # potential close
        self._candle._bid_close = from_candle._bid_close

        # update ofr prices
        self._candle._ofr_high = max(self._candle._ofr_high,
                                     from_candle._ofr_high)
        self._candle._ofr_low = min(self._candle._ofr_low,
                                    from_candle._ofr_low)

        # potential close
        self._candle._ofr_close = from_candle._ofr_close

        # keep last timestamp
        self._last_timestamp = from_candle.timestamp

        return ended_candle
Beispiel #2
0
    def update_ohlc(self, market_id, tf, ts, bid, ofr, volume):
        """
        Update the current OHLC or create a new one, and save them.
        @param market_id str Unique market identifier
        @param tf float Timeframe (normalized timeframe at second)
        @param ts float Timestamp of the update or of the tick/trade
        @param bid float Bid price.
        @param ofr float Offer/ask price.
        @param volume float Volume transacted or 0 if unspecified.
        """
        ended_ohlc = None
        ohlc = None

        # last ohlc per market id
        last_ohlc_by_timeframe = self._last_ohlc.get(market_id)
        if last_ohlc_by_timeframe is None:
            # not found for this market insert it
            self._last_ohlc[market_id] = {tf: None}
            last_ohlc_by_timeframe = self._last_ohlc[market_id]

        if tf not in last_ohlc_by_timeframe:
            last_ohlc_by_timeframe[tf] = None
        else:
            ohlc = last_ohlc_by_timeframe[tf]

        if ohlc and ts >= ohlc.timestamp + tf:
            # need to close the current ohlc
            ohlc.set_consolidated(True)
            ended_ohlc = ohlc

            last_ohlc_by_timeframe[tf] = None
            ohlc = None

        if ohlc is None:
            # open a new one if necessary
            base_time = Instrument.basetime(tf, ts)
            ohlc = Candle(base_time, tf)

            ohlc.set_consolidated(False)

            if bid:
                ohlc.set_bid(bid)
            if ofr:
                ohlc.set_ofr(ofr)

            last_ohlc_by_timeframe[tf] = ohlc

        if ts >= ohlc.timestamp:
            # update the current OHLC
            if volume:
                ohlc._volume += volume

            if bid:
                if not ohlc._bid_open:
                    ohlc.set_bid(bid)

                # update bid prices
                ohlc._bid_high = max(ohlc._bid_high, bid)
                ohlc._bid_low = min(ohlc._bid_low, bid)

                # potential close
                ohlc._bid_close = bid

            if ofr:
                if not ohlc.ofr_open:
                    ohlc.set_ofr(ofr)

                # update ofr prices
                ohlc._ofr_high = max(ohlc._ofr_high, ofr)
                ohlc._ofr_low = min(ohlc._ofr_low, ofr)

                # potential close
                ohlc._ofr_close = ofr

        # stored timeframes only
        if ended_ohlc and (tf in self.STORED_TIMEFRAMES):
            Database.inst().store_market_ohlc(
                (self.name, market_id, int(ended_ohlc.timestamp * 1000), tf,
                 ended_ohlc.bid_open, ended_ohlc.bid_high, ended_ohlc.bid_low,
                 ended_ohlc.bid_close, ended_ohlc.ofr_open,
                 ended_ohlc.ofr_high, ended_ohlc.ofr_low, ended_ohlc.ofr_close,
                 ended_ohlc.volume))

        return ohlc