def __init__(self, exchange: Exchange): super().__init__() self.exchange = exchange self.closed_orders = {} self.orders_history = {} self.balance_history = [] self.last_balance_check = None self.peak_balance = 0 self.max_drawdown = 0 self.last_price = None self.symbol = None self.has_position = True if "bitmex" in str( exchange.__class__) else False self.is_backtest = True if "TestEX" in str( exchange.__class__) or "BitmexBacktest" in str( exchange.__class__) else False exchange.seconds() self.candles = None self.last_candle = None self.timeframes = { "1m": 60, "5m": 300, "15m": 900, "30m": 1800, "1h": 3600, "4h": 14400, "6h": 21600, "12h": 43200, "1d": 86400, "2d": 172800, "4d": 345600, "1w": 604800, "2w": 1209600 }
def get_supported_pair_for(currency: CurrencyName, exchange: ccxt.Exchange) -> str: assert exchange result = '' exchange.load_markets() market_ids = { f'BTC{currency.value}', f'BTC/{currency.value}', f'XBT{currency.value}', f'XBT/{currency.value}', f'BTC{currency.value}'.lower(), f'XBT{currency.value}'.lower()} market_ids_found = list(market_ids & exchange.markets_by_id.keys()) if not market_ids_found: market_ids_found = list(market_ids & set([market['symbol'] for market in exchange.markets_by_id.values()])) if market_ids_found: logger.debug(f'market_ids_found: {market_ids_found}') market_id = market_ids_found[0] market = exchange.markets_by_id[exchange.market_id(market_id)] if market: result = market['symbol'] logger.debug(f'Found market {market}, with symbol {result}') return result
def get_ohlcv(exchange: ccxt.Exchange, symbol: str, timeframe: str) -> pd.DataFrame: # Check if fetching of OHLC Data is supported if not exchange.has["fetchOHLCV"]: raise ValueError( '{} does not support fetching OHLC data. Please use another exchange' .format(exchange)) # Check requested timeframe is available. If not return a helpful error. if timeframe not in exchange.timeframes: print('-' * 36, ' ERROR ', '-' * 35) print('The requested timeframe ({}) is not available from {}\n'.format( args.timeframe, args.exchange)) print('Available timeframes are:') for key in exchange.timeframes.keys(): print(' - ' + key) raise ValueError # Check if the symbol is available on the Exchange exchange.load_markets() if symbol not in exchange.symbols: print('-' * 36, ' ERROR ', '-' * 35) print('The requested symbol ({}) is not available from {}\n'.format( symbol, exchange)) print('Available symbols are:') for key in exchange.symbols: print(' - ' + key) print('-' * 80) raise ValueError # Get data data = exchange.fetch_ohlcv(symbol, timeframe) header = ['Timestamp', 'Open', 'High', 'Low', 'Close', 'Volume'] return pd.DataFrame(data, columns=header)
def timeframe_to_seconds(ticker_interval: str) -> int: """ Translates the timeframe interval value written in the human readable form ('1m', '5m', '1h', '1d', '1w', etc.) to the number of seconds for one timeframe interval. """ return Exchange.parse_timeframe(ticker_interval)
def get_candle(exchange: ccxt.Exchange, currency: CurrencyName = currency) -> tuple[ccxt.Exchange, Candle | None]: assert exchange assert currency result = (exchange, None) exchange.load_markets() if currency.value in exchange.currencies: try: candle = None candle = request_single(exchange, currency) if candle: result = exchange, candle except Exception as e: logger.error(f'error requesting data from exchange: {e}') return result
def set_sandbox(self, api: ccxt.Exchange, exchange_config: dict, name: str) -> None: if exchange_config.get('sandbox'): if api.urls.get('test'): api.urls['api'] = api.urls['test'] logger.info("Enabled Sandbox API on %s", name) else: logger.warning(name, "No Sandbox URL in CCXT, exiting. " "Please check your config.json") raise OperationalException(f'Exchange {name} does not provide a sandbox api')
def __init__(self, snapshot={}, depth=None): copy = Exchange.extend( snapshot, { 'asks': order_book_side.IndexedAsks(snapshot.get('asks', []), depth), 'bids': order_book_side.IndexedBids(snapshot.get('bids', []), depth), }) super(IndexedOrderBook, self).__init__(copy, depth)
def reset(self, snapshot): self['asks']._index.clear() for ask in snapshot.get('asks', []): self['asks'].storeArray(ask) self['bids']._index.clear() for bid in snapshot.get('bids', []): self['bids'].storeArray(bid) self['nonce'] = snapshot.get('nonce') self['timestamp'] = snapshot.get('timestamp') self['datetime'] = Exchange.iso8601(self['timestamp'])
def fetch_trades_unbatched(exchange: ccxt.Exchange): """ Some exchanges like Binance don't support fetching all trades at once and need to fetch per trading pair (market). """ markets = exchange.load_markets() trades = [] for market in markets: try: trades += exchange.fetch_my_trades(market) except ReadTimeout as err: print(err) continue # exchange.rateLimit is milliseconds but time.sleep expects seconds # plus add an extra 2 seconds as some exchanges like Bitfinex have varying rate limits # and still return rate limit exceeded errors when using the value provided by CCXT time.sleep((exchange.rateLimit / 1000) + 2) return trades
def get_exchange_details(exchange: ccxt.Exchange) -> ExchangeDetails: result = None assert exchange exchange.load_markets() currencies = [] if exchange.currencies: currencies = [c for c in settings.currencies if c in exchange.currencies] details = ExchangeDetails( id = exchange.id, name = exchange.name, url = exchange.urls['www'], countries = exchange.countries, currencies = get_supported_currencies(exchange)) # TODO(nochiel) TEST result = details return result
def fetch_ohlcv(self, exchange: ccxt.Exchange, symbol, since): while True: try: candles = exchange.fetch_ohlcv(symbol, self.dataframe, since=since, limit=60 * 12) return candles except (ccxt.errors.DDoSProtection, ccxt.errors.RequestTimeout) as e: self.logger.debug(error_class=e.__class__.__name__, exchange=exchange.id) time.sleep(3) except ccxt.errors.BaseError as e: self.logger.debug(error_class=e.__class__.__name__) self.logger.exception() time.sleep(5)
def __init__(self, snapshot={}, depth=None): self.cache = [] depth = depth or sys.maxsize defaults = { 'bids': [], 'asks': [], 'timestamp': None, 'datetime': None, 'nonce': None, } # do not mutate snapshot defaults.update(snapshot) if not isinstance(defaults['asks'], order_book_side.OrderBookSide): defaults['asks'] = order_book_side.Asks(defaults['asks'], depth) if not isinstance(defaults['bids'], order_book_side.OrderBookSide): defaults['bids'] = order_book_side.Bids(defaults['bids'], depth) defaults['datetime'] = Exchange.iso8601(defaults.get('timestamp')) # merge to self super(OrderBook, self).__init__(defaults)
def get_history(*, exchange: ccxt.Exchange, since: datetime, limit: int, timeframe: str, pair: str) -> list[Candle] | None: assert exchange logger.debug(f'{exchange} {pair} {since}') result = None _since = round(since.timestamp() * 1e3) params = dict() if exchange == "bitfinex": # TODO(nochiel) Is this needed? # params['end'] = round(end.timestamp() * 1e3) ... candles = None # @nochiel: Re. Bitmex. Rate-limiting is very aggressive on authenticated API calls. # For a large number of requests this throttling doesn't help and Bitmex will increase # its rate limit to 3600 seconds! # Serializing requests won't help either wait = exchange.rateLimit * 1e-3 while wait: try: candles = exchange.fetchOHLCV( symbol = pair, limit = limit, timeframe = timeframe, since = _since, params = params) wait = 0 except (ccxt.errors.RateLimitExceeded, ccxt.errors.DDoSProtection) as e: logger.error(f'rate-limited on {exchange}: {e}') logger.error(f'waiting {wait} seconds on {exchange} before making another request') time.sleep(wait) wait *= 2 if wait > 120: raise Exception(f'{exchange} has rate limited spotbit') from e except Exception as e: logger.error(f'{exchange} candle request error: {e}') if candles: result = [Candle( timestamp = candle[OHLCV.timestamp], open = candle[OHLCV.open], high = candle[OHLCV.high], low = candle[OHLCV.low], close = candle[OHLCV.close], volume = candle[OHLCV.volume] ) for candle in candles] return result
def get_market_data(exchange: ccxt.Exchange): markets = exchange.load_markets(reload=True) for symbol in markets: data = prepare_data(data=markets[symbol]) load_to_mongo(coll=symbol, data=data)
return Trade(symbol=trade.symbol, trade_type=trade.trade_type, amount=order['filled'], price=order['price']) def reset(self): self._markets = self._exchange.contract self._exchange.close() time.sleep(3) self._exchange.get_balance() self._initial_balance = self._exchange.position.loc[self._base_asset, 'available'] self._performance = pd.DataFrame([], columns=['balance', 'net_worth']) if __name__ == '__main__': okex = Exchange('okex/mock-luyh-okex') exchange = OnetokenExchange(exchange=okex, base_asset='btc') exchange.reset() base_precision = exchange.base_precision asset_precision = exchange.asset_precision portfolio = exchange.portfolio trades = exchange.trades print('develop')
def timeframe_to_msecs(ticker_interval: str) -> int: """ Same as above, but returns milliseconds. """ return Exchange.parse_timeframe(ticker_interval) * 1000
def timeframe_to_minutes(ticker_interval: str) -> int: """ Same as above, but returns minutes. """ return Exchange.parse_timeframe(ticker_interval) // 60
def convert_datetime_to_timestamp(self, exchange_api: ccxt.Exchange, datetime): return exchange_api.parse8601(datetime)
def get_time(): return Exchange.milliseconds()
def request_single(exchange: ccxt.Exchange, currency: CurrencyName) -> Candle | None: ''' Make a single request, without having to loop through all exchanges and currency pairs. ''' assert exchange and isinstance(exchange, ccxt.Exchange) assert currency exchange.load_markets() pair = get_supported_pair_for(currency, exchange) if not pair: return None result = None latest_candle = None dt = None if exchange.has['fetchOHLCV']: logger.debug('fetchOHLCV') timeframe = '1m' match exchange.id: case 'btcalpha' | 'hollaex': timeframe = '1h' case 'poloniex': timeframe = '5m' # Some exchanges have explicit limits on how many candles you can get at once # TODO(nochiel) Simplify this by checking for 2 canonical limits to use. limit = 1000 match exchange.id: case 'bitstamp': limit = 1000 case 'bybit': limit = 200 case 'eterbase': limit = 1000000 case 'exmo': limit = 3000 case 'btcalpha': limit = 720 since = round((datetime.now() - timedelta(hours=1)).timestamp() * 1e3) # TODO(nochiel) TEST other exchanges requiring special conditions: bitstamp, bitmart? params = [] if exchange.id == 'bitfinex': params = { 'limit':100, 'start': since, 'end': round(datetime.now().timestamp() * 1e3) } try: candles = exchange.fetchOHLCV( symbol = pair, timeframe = timeframe, limit = limit, since = since, params = params) latest_candle = candles[-1] except Exception as e: logger.error(f'error requesting candle from {exchange.name}: {e}') else: # TODO(nochiel) TEST logger.debug(f'fetch_ticker: {pair}') candle = None try: candle = exchange.fetch_ticker(pair) except Exception as e: logger.error(f'error on {exchange} fetch_ticker: {e}') latest_candle = candle if latest_candle: result = Candle( timestamp = latest_candle[OHLCV.timestamp], open = latest_candle[OHLCV.open], high = latest_candle[OHLCV.high], low = latest_candle[OHLCV.low], close = latest_candle[OHLCV.close], volume = latest_candle[OHLCV.volume] ) return result