Ejemplo n.º 1
0
    async def _candle(self, msg: dict, timestamp: float):
        """
        {
            'kline': [
                [1625332980, 60, 346285000, 346300000, 346390000, 346300000, 346390000, 49917, 144121225]
            ],
            'sequence': 9048385626,
            'symbol': 'BTCUSD',
            'type': 'incremental'
        }
        """
        symbol = self.exchange_symbol_to_std_symbol(msg['symbol'])

        for entry in msg['kline']:
            ts, _, _, open, high, low, close, _, volume = entry
            c = Candle(
                self.id,
                symbol,
                ts,
                ts + self.candle_interval_map[self.candle_interval],
                self.candle_interval,
                None,
                Decimal(open / self.price_scale[symbol]),
                Decimal(close / self.price_scale[symbol]),
                Decimal(high / self.price_scale[symbol]),
                Decimal(low / self.price_scale[symbol]),
                Decimal(volume),
                None,
                None
            )
            await self.callback(CANDLES, c, timestamp)
Ejemplo n.º 2
0
 async def _candle(self, msg: list, pair: str, timestamp: float):
     """
     [327,
         ['1621988141.603324',   start
          '1621988160.000000',   end
          '38220.70000',         open
          '38348.80000',         high
          '38220.70000',         low
          '38320.40000',         close
          '38330.59222',         vwap
          '3.23539643',          volume
          42                     count
         ],
     'ohlc-1',
     'XBT/USD']
     """
     start, end, open, high, low, close, _, volume, count = msg[1]
     interval = int(msg[-2].split("-")[-1])
     c = Candle(self.id,
                pair,
                float(end) - (interval * 60),
                float(end),
                self.normalize_candle_interval[interval],
                count,
                Decimal(open),
                Decimal(close),
                Decimal(high),
                Decimal(low),
                Decimal(volume),
                None,
                float(start),
                raw=msg)
     await self.callback(CANDLES, c, timestamp)
Ejemplo n.º 3
0
 async def _candles(self, msg: dict, timestamp: float):
     '''
     {
         'candle_start_time': 1638134700000000,
         'close': None,
         'high': None,
         'last_updated': 1638134700318213,
         'low': None,
         'open': None,
         'resolution': '1m',
         'symbol': 'BTC_USDT',
         'timestamp': 1638134708903082,
         'type': 'candlestick_1m',
         'volume': 0
     }
     '''
     interval = self.normalize_candle_interval[msg['resolution']]
     c = Candle(self.id,
                self.exchange_symbol_to_std_symbol(msg['symbol']),
                self.timestamp_normalize(msg['candle_start_time']),
                self.timestamp_normalize(msg['candle_start_time']) +
                timedelta_str_to_sec(interval) - 1,
                interval,
                None,
                Decimal(msg['open'] if msg['open'] else 0),
                Decimal(msg['close'] if msg['close'] else 0),
                Decimal(msg['high'] if msg['high'] else 0),
                Decimal(msg['low'] if msg['low'] else 0),
                Decimal(msg['volume'] if msg['volume'] else 0),
                False,
                self.timestamp_normalize(msg['timestamp']),
                raw=msg)
     await self.callback(CANDLES, c, timestamp)
Ejemplo n.º 4
0
 async def _candle(self, msg: dict, timestamp: float):
     '''
     {
         'action': 'update',
         'arg': {
             'instType': 'sp',
             'channel': 'candle1m',
             'instId': 'BTCUSDT'
         },
         'data': [['1649014920000', '46434.2', '46437.98', '46434.2', '46437.98', '0.9469']]
     }
     '''
     for entry in msg['data']:
         t = Candle(self.id,
                    self.exchange_symbol_to_std_symbol(
                        msg['arg']['instId']),
                    self.timestamp_normalize(int(entry[0])),
                    self.timestamp_normalize(int(entry[0])) +
                    timedelta_str_to_sec(self.candle_interval),
                    self.candle_interval,
                    None,
                    Decimal(entry[1]),
                    Decimal(entry[4]),
                    Decimal(entry[2]),
                    Decimal(entry[3]),
                    Decimal(entry[5]),
                    None,
                    self.timestamp_normalize(int(entry[0])),
                    raw=entry)
         await self.callback(CANDLES, t, timestamp)
Ejemplo n.º 5
0
 async def _candles(self, msg: dict, symbol: str, timestamp: float):
     """
     {
         'data': {
             'symbol': 'BTC-USDT',
             'candles': ['1619196960', '49885.4', '49821', '49890.5', '49821', '2.60137567', '129722.909001802'],
             'time': 1619196997007846442
         },
         'subject': 'trade.candles.update',
         'topic': '/market/candles:BTC-USDT_1min',
         'type': 'message'
     }
     """
     symbol, interval = symbol.split("_")
     interval = self.normalize_candle_interval[interval]
     start, open, close, high, low, vol, _ = msg['data']['candles']
     end = int(start) + timedelta_str_to_sec(interval) - 1
     c = Candle(self.id,
                symbol,
                int(start),
                end,
                interval,
                None,
                Decimal(open),
                Decimal(close),
                Decimal(high),
                Decimal(low),
                Decimal(vol),
                None,
                msg['data']['time'] / 1000000000,
                raw=msg)
     await self.callback(CANDLES, c, timestamp)
Ejemplo n.º 6
0
    def test_candle_history_specific_time(self):
        expected = [
            Candle(
                Coinbase.id,
                'BTC-USD',
                1578733200,
                1578733200 + 3600,
                '1h',
                None,
                Decimal('8054.66'),
                Decimal('8109.53'),
                Decimal('8122'),
                Decimal('8054.64'),
                Decimal('78.91111363'),
                True,
                1578733200
            ),
            Candle(
                Coinbase.id,
                'BTC-USD',
                1578736800,
                1578736800 + 3600,
                '1h',
                None,
                Decimal('8110.95'),
                Decimal('8050.94'),
                Decimal('8110.95'),
                Decimal('8045.67'),
                Decimal('71.11516828'),
                True,
                1578736800
            )
        ]
        s = '2020-01-11 09:00:00'
        e = '2020-01-11 10:00:00'
        candle_history = []
        for entry in public.candles_sync('BTC-USD', start=s, end=e, interval='1h'):
            candle_history.extend(entry)

        assert len(candle_history) == 2
        assert candle_history == expected
Ejemplo n.º 7
0
def test_candle():
    t = Candle(
        'BINANCE_FUTURES',
        'BTC-USD-PERP',
        time(),
        time() + 60,
        '1m',
        54,
        Decimal(10),
        Decimal(100),
        Decimal(200),
        Decimal(10),
        Decimal(1234.5432),
        True,
        time(),
    )
    d = t.to_dict(numeric_type=str)
    d = json.dumps(d)
    d = json.loads(d)
    t2 = Candle.from_dict(d)
    assert t == t2
Ejemplo n.º 8
0
    def test_candles(self):
        expected = Candle(b.id, 'BTC-USDT', 1577836800.0, 1577836859.999, '1m',
                          493, Decimal('7195.24'), Decimal('7186.68'),
                          Decimal('7196.25'), Decimal('7183.14'),
                          Decimal('51.642812'), True, 1577836859.999)
        ret = []
        for data in b.candles_sync('BTC-USDT',
                                   start='2020-01-01 00:00:00',
                                   end='2020-01-01 00:00:59'):
            ret.extend(data)

        assert len(ret) == 1
        assert ret[0] == expected
Ejemplo n.º 9
0
 def _candle_normalize(self, symbol: str, data: list,
                       interval: str) -> dict:
     return Candle(self.id,
                   symbol,
                   data[0],
                   data[0] + timedelta_str_to_sec(interval),
                   interval,
                   None,
                   Decimal(data[3]),
                   Decimal(data[4]),
                   Decimal(data[2]),
                   Decimal(data[1]),
                   Decimal(data[5]),
                   True,
                   data[0],
                   raw=data)
Ejemplo n.º 10
0
    async def candles(self,
                      symbol: str,
                      start=None,
                      end=None,
                      interval='1m',
                      retry_count=1,
                      retry_delay=60):
        sym = self.std_symbol_to_exchange_symbol(symbol)
        ep = f'{self.api}klines?symbol={sym}&interval={interval}&limit=1000'

        start, end = self._interval_normalize(start, end)
        if start and end:
            start = int(start * 1000)
            end = int(end * 1000)

        while True:
            if start and end:
                endpoint = f'{ep}&startTime={start}&endTime={end}'
            else:
                endpoint = ep
            r = await self.http_conn.read(endpoint,
                                          retry_count=retry_count,
                                          retry_delay=retry_delay)
            data = json.loads(r, parse_float=Decimal)
            start = data[-1][6]
            data = [
                Candle(self.id,
                       symbol,
                       self.timestamp_normalize(e[0]),
                       self.timestamp_normalize(e[6]),
                       interval,
                       e[8],
                       Decimal(e[1]),
                       Decimal(e[4]),
                       Decimal(e[2]),
                       Decimal(e[3]),
                       Decimal(e[5]),
                       True,
                       self.timestamp_normalize(e[6]),
                       raw=e) for e in data
            ]
            yield data

            if len(data) < 1000 or end is None:
                break
            await asyncio.sleep(1 / self.request_limit)
Ejemplo n.º 11
0
    async def candles(self,
                      symbol: str,
                      start=None,
                      end=None,
                      interval='1m',
                      retry_count=1,
                      retry_delay=60):
        _interval = self.candle_mappings[interval]
        sym = self.std_symbol_to_exchange_symbol(symbol)
        base_endpoint = f"{self.api}candles/trade:{_interval}:{sym}"
        start, end = self._interval_normalize(start, end)
        offset = timedelta_str_to_sec(interval)

        while True:
            if start and end:
                endpoint = f"{base_endpoint}/hist?limit=10000&start={int(start * 1000)}&end={int(end * 1000)}&sort=1"
            else:
                endpoint = f"{base_endpoint}/last"

            r = await self.http_conn.read(endpoint,
                                          retry_delay=retry_delay,
                                          retry_count=retry_count)
            data = json.loads(r, parse_float=Decimal)
            if not isinstance(data[0], list):
                data = [data]
            data = [
                Candle(self.id,
                       symbol,
                       self.timestamp_normalize(e[0]),
                       self.timestamp_normalize(e[0]) + offset,
                       interval,
                       None,
                       Decimal(e[1]),
                       Decimal(e[2]),
                       Decimal(e[3]),
                       Decimal(e[4]),
                       Decimal(e[5]),
                       True,
                       self.timestamp_normalize(e[0]),
                       raw=e) for e in data
            ]
            yield data

            if not end or len(data) < 10000:
                break
            start = data[-1].start + offset
Ejemplo n.º 12
0
    async def candle(self, msg: dict, timestamp: float):
        """
        {
            'sequence': 134514,
            'marketSymbol': 'BTC-USDT',
            'interval': 'MINUTE_1',
            'delta': {
                'startsAt': datetime.datetime(2021, 6, 14, 1, 12, tzinfo=datetime.timezone.utc),
                'open': '39023.31434847',
                'high': '39023.31434847',
                'low': '39023.31434847',
                'close': '39023.31434847',
                'volume': '0.05944473',
                'quoteVolume': '2319.73038514'
            },
            'candleType': 'TRADE'
        }
        """
        start = self.timestamp_normalize(msg['delta']['startsAt'])
        offset = 0
        if self.candle_interval == '1m':
            offset = 60
        elif self.candle_interval == '5m':
            offset = 300
        elif self.candle_interval == '1h':
            offset = 3600
        elif self.candle_interval == '1d':
            offset = 86400
        end = start + offset

        c = Candle(self.id,
                   self.exchange_symbol_to_std_symbol(msg['marketSymbol']),
                   start,
                   end,
                   self.candle_interval,
                   None,
                   Decimal(msg['delta']['open']),
                   Decimal(msg['delta']['close']),
                   Decimal(msg['delta']['high']),
                   Decimal(msg['delta']['low']),
                   Decimal(msg['delta']['volume']),
                   None,
                   None,
                   raw=msg)
        await self.callback(CANDLES, c, timestamp)
Ejemplo n.º 13
0
 async def _candle(self, msg: dict, timestamp: float):
     """
     {
         'e': 'kline',
         'E': 1615927655524,
         's': 'BTCUSDT',
         'k': {
             't': 1615927620000,
             'T': 1615927679999,
             's': 'BTCUSDT',
             'i': '1m',
             'f': 710917276,
             'L': 710917780,
             'o': '56215.99000000',
             'c': '56232.07000000',
             'h': '56238.59000000',
             'l': '56181.99000000',
             'v': '13.80522200',
             'n': 505,
             'x': False,
             'q': '775978.37383076',
             'V': '7.19660600',
             'Q': '404521.60814919',
             'B': '0'
         }
     }
     """
     if self.candle_closed_only and not msg['k']['x']:
         return
     c = Candle(self.id,
                self.exchange_symbol_to_std_symbol(msg['s']),
                msg['k']['t'] / 1000,
                msg['k']['T'] / 1000,
                msg['k']['i'],
                msg['k']['n'],
                Decimal(msg['k']['o']),
                Decimal(msg['k']['c']),
                Decimal(msg['k']['h']),
                Decimal(msg['k']['l']),
                Decimal(msg['k']['v']),
                msg['k']['x'],
                self.timestamp_normalize(msg['E']),
                raw=msg)
     await self.callback(CANDLES, c, timestamp)
Ejemplo n.º 14
0
    async def _candle(self, msg: dict, timestamp: float):
        '''
        {
            'instrument_name': 'BTC_USDT',
            'subscription': 'candlestick.14D.BTC_USDT',
            'channel': 'candlestick',
            'depth': 300,
            'interval': '14D',
            'data': [
                {
                    't': 1636934400000,
                    'o': Decimal('65502.68'),
                    'h': Decimal('66336.25'),
                    'l': Decimal('55313.95'),
                    'c': Decimal('57582.1'),
                    'v': Decimal('366802.492134')
                }
            ]
        }
        '''
        interval = msg['interval']
        if interval == '14D':
            interval = '2w'
        elif interval == '7D':
            interval = '1w'
        elif interval == '1D':
            interval = '1d'

        for entry in msg['data']:
            c = Candle(self.id,
                       self.exchange_symbol_to_std_symbol(msg['instrument_name']),
                       entry['t'] / 1000,
                       entry['t'] / 1000 + timedelta_str_to_sec(interval) - 1,
                       interval,
                       None,
                       entry['o'],
                       entry['c'],
                       entry['h'],
                       entry['l'],
                       entry['v'],
                       None,
                       None,
                       raw=entry)
            await self.callback(CANDLES, c, timestamp)
Ejemplo n.º 15
0
    async def candles(self,
                      symbol: str,
                      start=None,
                      end=None,
                      interval='1m',
                      retry_count=1,
                      retry_delay=60):
        sym = self.std_symbol_to_exchange_symbol(symbol)
        interval_sec = timedelta_str_to_sec(interval)
        base = f'{self.api}/markets/{sym}/candles?resolution={interval_sec}'
        start, end = self._interval_normalize(start, end)

        while True:
            endpoint = base
            if start and end:
                endpoint = f'{base}&start_time={start}&end_time={end}'

            r = await self.http_conn.read(endpoint,
                                          retry_count=retry_count,
                                          retry_delay=retry_delay)
            data = json.loads(r, parse_float=Decimal)['result']
            data = [
                Candle(self.id,
                       symbol,
                       self.timestamp_normalize(e['startTime']),
                       self.timestamp_normalize(e['startTime']) + interval_sec,
                       interval,
                       None,
                       Decimal(e['open']),
                       Decimal(e['close']),
                       Decimal(e['high']),
                       Decimal(e['low']),
                       Decimal(e['volume']),
                       True,
                       self.timestamp_normalize(e['startTime']),
                       raw=e) for e in data
            ]
            yield data

            end = data[0].start - interval_sec
            if not start or len(data) < 1501:
                break
            await asyncio.sleep(1 / self.request_limit)
Ejemplo n.º 16
0
    async def candles(self,
                      symbol: str,
                      start=None,
                      end=None,
                      interval='1m',
                      retry_count=1,
                      retry_delay=60):
        sym = self.std_symbol_to_exchange_symbol(symbol)
        interval_sec = timedelta_str_to_sec(interval)
        base = f'{self.api}ohlc/{sym}/?step={interval_sec}&limit=1000'
        start, end = self._interval_normalize(start, end)

        while True:
            endpoint = base
            if start and end:
                endpoint = f'{base}&start={int(start)}&end={int(end)}'

            r = await self.http_conn.read(endpoint,
                                          retry_count=retry_count,
                                          retry_delay=retry_delay)
            data = json.loads(r, parse_float=Decimal)['data']['ohlc']
            data = [
                Candle(self.id,
                       symbol,
                       float(e['timestamp']),
                       float(e['timestamp']) + interval_sec,
                       interval,
                       None,
                       Decimal(e['open']),
                       Decimal(e['close']),
                       Decimal(e['high']),
                       Decimal(e['low']),
                       Decimal(e['volume']),
                       True,
                       float(e['timestamp']),
                       raw=e) for e in data
            ]
            yield data

            end = data[0].start - interval_sec
            if not start or start >= end:
                break
            await asyncio.sleep(1 / self.request_limit)
Ejemplo n.º 17
0
    async def _candles(self, msg: dict, timestamp: float):
        """
        {
            "jsonrpc": "2.0",
            "method": "updateCandles",
            "params": {
                "data": [
                    {
                        "timestamp": "2017-10-19T16:30:00.000Z",
                        "open": "0.054614",
                        "close": "0.054465",
                        "min": "0.054339",
                        "max": "0.054724",
                        "volume": "141.268",
                        "volumeQuote": "7.709353873"
                    }
                ],
                "symbol": "ETHBTC",
                "period": "M30"
            }
        }
        """

        interval = str(self.normalize_interval[msg['period']])

        for candle in msg['data']:
            start = self.timestamp_normalize(candle['timestamp'])
            end = start + timedelta_str_to_sec(interval) - 1
            c = Candle(self.id,
                       self.exchange_symbol_to_std_symbol(msg['symbol']),
                       start,
                       end,
                       interval,
                       None,
                       Decimal(candle['open']),
                       Decimal(candle['close']),
                       Decimal(candle['max']),
                       Decimal(candle['min']),
                       Decimal(candle['volume']),
                       None,
                       None,
                       raw=candle)
            await self.callback(CANDLES, c, timestamp)
Ejemplo n.º 18
0
    async def _candle(self, msg: dict, timestamp: float):
        '''
        {
            "topic": "klineV2.1.BTCUSD",                //topic name
            "data": [{
                "start": 1572425640,                    //start time of the candle
                "end": 1572425700,                      //end time of the candle
                "open": 9200,                           //open price
                "close": 9202.5,                        //close price
                "high": 9202.5,                         //max price
                "low": 9196,                            //min price
                "volume": 81790,                        //volume
                "turnover": 8.889247899999999,          //turnover
                "confirm": False,                       //snapshot flag (indicates if candle is closed or not)
                "cross_seq": 297503466,
                "timestamp": 1572425676958323           //cross time
            }],
            "timestamp_e6": 1572425677047994            //server time
        }
        '''
        symbol = self.exchange_symbol_to_std_symbol(
            msg['topic'].split(".")[-1])
        ts = msg['timestamp_e6'] / 1_000_000

        for entry in msg['data']:
            if self.candle_closed_only and not entry['confirm']:
                continue
            c = Candle(self.id,
                       symbol,
                       entry['start'],
                       entry['end'],
                       self.candle_interval,
                       entry['confirm'],
                       Decimal(entry['open']),
                       Decimal(entry['close']),
                       Decimal(entry['high']),
                       Decimal(entry['low']),
                       Decimal(entry['volume']),
                       None,
                       ts,
                       raw=entry)
            await self.callback(CANDLES, c, timestamp)
Ejemplo n.º 19
0
    def test_candles(self):
        expected = Candle(
            o.id,
            'BTC-USDT',
            1609459200.0,
            1609459260.0,
            '1m',
            None,
            Decimal('28914.8'),      # open
            Decimal('28959.1'),      # close
            Decimal('28959.1'),      # high
            Decimal('28914.8'),      # low
            Decimal('13.22459039'),  # volume
            True,
            1609459200.0
        )
        ret = []
        for data in o.candles_sync('BTC-USDT', start='2021-01-01 00:00:00', end='2021-01-01 00:00:01', interval='1m'):
            ret.extend(data)

        assert len(ret) == 1
        assert ret[0] == expected
Ejemplo n.º 20
0
 async def _candle(self, msg: dict, ts: float):
     '''
     {
         'ch': 'candles/M1',
         'update': {
             'BTCUSDT': [{
                 't': 1633805940000,
                 'o': '54849.03',
                 'c': '54849.03',
                 'h': '54849.03',
                 'l': '54849.03',
                 'v': '0.00766',
                 'q': '420.1435698'
             }]
         }
     }
     '''
     interval = msg['ch'].split("/")[-1]
     for sym, updates in msg['update'].items():
         symbol = self.exchange_symbol_to_std_symbol(sym)
         for u in updates:
             c = Candle(
                 self.id,
                 symbol,
                 u['t'] / 1000,
                 u['t'] / 1000 +
                 timedelta_str_to_sec(self.normalize_interval[interval]) -
                 0.1,
                 self.normalize_interval[interval],
                 None,
                 Decimal(u['o']),
                 Decimal(u['c']),
                 Decimal(u['h']),
                 Decimal(u['l']),
                 Decimal(u['v']),
                 None,
                 self.timestamp_normalize(u['t']),
                 raw=msg)
             await self.callback(CANDLES, c, ts)
Ejemplo n.º 21
0
 async def _candles(self, msg: dict, symbol: str, interval: str,
                    timestamp: float):
     """
     {
         'ch': 'market.btcusdt.kline.1min',
         'ts': 1618700872863,
         'tick': {
             'id': 1618700820,
             'open': Decimal('60751.62'),
             'close': Decimal('60724.73'),
             'low': Decimal('60724.73'),
             'high': Decimal('60751.62'),
             'amount': Decimal('2.1990737759143966'),
             'vol': Decimal('133570.944386'),
             'count': 235}
         }
     }
     """
     interval = self.normalize_candle_interval[interval]
     start = int(msg['tick']['id'])
     end = start + timedelta_str_to_sec(interval) - 1
     c = Candle(self.id,
                self.exchange_symbol_to_std_symbol(symbol),
                start,
                end,
                interval,
                msg['tick']['count'],
                Decimal(msg['tick']['open']),
                Decimal(msg['tick']['close']),
                Decimal(msg['tick']['high']),
                Decimal(msg['tick']['low']),
                Decimal(msg['tick']['amount']),
                None,
                self.timestamp_normalize(msg['ts']),
                raw=msg)
     await self.callback(CANDLES, c, timestamp)
Ejemplo n.º 22
0
 async def _candles(self, msg: dict, timestamp: float):
     """
     {
         'time': 1619092863,
         'channel': 'spot.candlesticks',
         'event': 'update',
         'result': {
             't': '1619092860',
             'v': '1154.64627',
             'c': '54992.64',
             'h': '54992.64',
             'l': '54976.29',
             'o': '54976.29',
             'n': '1m_BTC_USDT'
         }
     }
     """
     interval, symbol = msg['result']['n'].split('_', 1)
     if interval == '7d':
         interval = '1w'
     c = Candle(self.id,
                self.exchange_symbol_to_std_symbol(symbol),
                float(msg['result']['t']),
                float(msg['result']['t']) + timedelta_str_to_sec(interval) -
                0.1,
                interval,
                None,
                Decimal(msg['result']['o']),
                Decimal(msg['result']['c']),
                Decimal(msg['result']['h']),
                Decimal(msg['result']['l']),
                Decimal(msg['result']['v']),
                None,
                float(msg['time']),
                raw=msg)
     await self.callback(CANDLES, c, timestamp)
Ejemplo n.º 23
0
    async def candles(self,
                      symbol: str,
                      start=None,
                      end=None,
                      interval='1m',
                      retry_count=1,
                      retry_delay=60):
        '''
        [
            {
            "market": "KRW-BTC",
            "candle_date_time_utc": "2021-09-11T00:32:00",
            "candle_date_time_kst": "2021-09-11T09:32:00",
            "opening_price": 55130000,
            "high_price": 55146000,
            "low_price": 55104000,
            "trade_price": 55145000,
            "timestamp": 1631320340367,
            "candle_acc_trade_price": 136592120.21198,
            "candle_acc_trade_volume": 2.47785284,
            "unit": 1
            },
            ...
        ]
        '''
        sym = self.std_symbol_to_exchange_symbol(symbol)
        offset = timedelta_str_to_sec(interval)
        interval_mins = int(timedelta_str_to_sec(interval) / 60)
        if interval == '1d':
            base = f'{self.api}candles/days/?market={sym}&count=200'
        elif interval == '1w':
            base = f'{self.api}candles/weeks/?market={sym}&count=200'
        elif interval == '1M':
            base = f'{self.api}candles/months/?market={sym}&count=200'
        else:
            base = f'{self.api}candles/minutes/{interval_mins}?market={sym}&count=200'
        start, end = self._interval_normalize(start, end)

        def _ts_norm(timestamp: datetime) -> float:
            # Upbit sends timezone naïve datetimes, so need to force to UTC before converting to timestamp
            assert timestamp.tzinfo is None
            return timestamp.replace(tzinfo=timezone.utc).timestamp()

        def retain(c: Candle, _last: set):
            if start and end:
                return c.start <= end and c.start not in _last
            return True

        _last = set()
        while True:
            endpoint = base
            if start and end:
                end_timestamp = datetime.utcfromtimestamp(start + offset * 200)
                end_timestamp = end_timestamp.replace(
                    microsecond=0).isoformat() + 'Z'
                endpoint = f'{base}&to={end_timestamp}'

            r = await self.http_conn.read(endpoint,
                                          retry_count=retry_count,
                                          retry_delay=retry_delay)
            data = json.loads(r, parse_float=Decimal)
            data = [
                Candle(self.id,
                       symbol,
                       _ts_norm(e['candle_date_time_utc']),
                       _ts_norm(e['candle_date_time_utc']) +
                       interval_mins * 60,
                       interval,
                       None,
                       Decimal(e['opening_price']),
                       None,
                       Decimal(e['high_price']),
                       Decimal(e['low_price']),
                       Decimal(e['candle_acc_trade_volume']),
                       True,
                       float(e['timestamp']) / 1000,
                       raw=e) for e in data
            ]
            data = list(
                sorted([c for c in data if retain(c, _last)],
                       key=lambda x: x.start))
            yield data

            # exchange downtime can cause gaps in candles, and because of the way pagination works, there will be overlap in ranges that
            # cover the downtime. Solution: remove duplicates by storing last values returned to client.
            _last = set([c.start for c in data])

            start = data[-1].start + offset
            if not end or start >= end:
                break
            await asyncio.sleep(1 / self.request_limit)