diff --git a/catalyst/assets/_assets.pyx b/catalyst/assets/_assets.pyx index feb41710..af1e81a6 100644 --- a/catalyst/assets/_assets.pyx +++ b/catalyst/assets/_assets.pyx @@ -516,7 +516,7 @@ cdef class TradingPair(Asset): symbol = symbol.lower() try: - self.base_currency,self.quote_currency = symbol.split('_') + self.base_currency, self.quote_currency = symbol.split('_') except Exception as e: raise InvalidSymbolError(symbol=symbol, error=e) @@ -600,8 +600,8 @@ cdef class TradingPair(Asset): ------- boolean: whether the asset's exchange is open at the given minute. """ - #TODO: consider implementing to spot holds - return self.trading_state > 0 + #TODO: make more dymanic to catch holds + return True cpdef __reduce__(self): """ diff --git a/catalyst/exchange/ccxt/ccxt_exchange.py b/catalyst/exchange/ccxt/ccxt_exchange.py index 4d5b3939..ac978e58 100644 --- a/catalyst/exchange/ccxt/ccxt_exchange.py +++ b/catalyst/exchange/ccxt/ccxt_exchange.py @@ -84,6 +84,19 @@ class CCXT(Exchange): return None def get_market(self, symbol): + """ + The CCXT market. + + Parameters + ---------- + symbol: + The CCXT symbol. + + Returns + ------- + dict[str, Object] + + """ s = self.get_symbol(symbol) market = next( (market for market in self.markets if market['symbol'] == s), @@ -92,6 +105,17 @@ class CCXT(Exchange): return market def get_symbol(self, asset_or_symbol): + """ + The CCXT symbol. + + Parameters + ---------- + asset_or_symbol + + Returns + ------- + + """ symbol = asset_or_symbol if isinstance( asset_or_symbol, string_types ) else asset_or_symbol.symbol @@ -100,6 +124,17 @@ class CCXT(Exchange): return '{}/{}'.format(parts[0].upper(), parts[1].upper()) def get_catalyst_symbol(self, market_or_symbol): + """ + The Catalyst symbol. + + Parameters + ---------- + market_or_symbol + + Returns + ------- + + """ if isinstance(market_or_symbol, string_types): parts = market_or_symbol.split('/') return '{}_{}'.format(parts[0].lower(), parts[1].lower()) @@ -111,6 +146,19 @@ class CCXT(Exchange): ) def get_timeframe(self, freq): + """ + The CCXT timeframe from the Catalyst frequency. + + Parameters + ---------- + freq: str + The Catalyst frequency (Pandas convention) + + Returns + ------- + str + + """ freq_match = re.match(r'([0-9].*)?(m|M|d|D|h|H|T)', freq, re.M | re.I) if freq_match: candle_size = int(freq_match.group(1)) \ @@ -168,16 +216,47 @@ class CCXT(Exchange): except ExchangeSymbolsNotFound: return None - def fetch_asset_defs(self, market): + def get_asset_defs(self, market): + """ + The local and Catalyst definitions of the specified market. + + Parameters + ---------- + market: dict[str, Object] + The CCXT market dicts. + + Returns + ------- + dict[str, Object] + The asset definition. + + """ asset_defs = [] for is_local in (False, True): - asset_def = self.fetch_asset_def(market, is_local) + asset_def = self.get_asset_def(market, is_local) asset_defs.append((asset_def, is_local)) return asset_defs - def fetch_asset_def(self, market, is_local=False): + def get_asset_def(self, market, is_local=False): + """ + The asset definition (in symbols.json files) corresponding + to the the specified market. + + Parameters + ---------- + market: dict[str, Object] + The CCXT market dict. + is_local + Whether to search in local or Catalyst asset definitions. + + Returns + ------- + dict[str, Object] + The asset definition. + + """ exchange_symbol = market['id'] symbol_map = self._fetch_symbol_map(is_local) @@ -250,8 +329,7 @@ class CCXT(Exchange): self.assets = [] for market in self.markets: - log.debug('fetching asset for market: {}'.format(market['id'])) - asset_defs = self.fetch_asset_defs(market) + asset_defs = self.get_asset_defs(market) for asset_def in asset_defs: if asset_def[0] is not None or not asset_defs[1]: diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index f38bfa36..0301ebc0 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -175,71 +175,102 @@ class Exchange: return symbols - def get_assets(self, symbols=None, data_frequency=None): + def get_assets(self, symbols=None, data_frequency=None, + is_exchange_symbol=False, + is_local=None): """ The list of markets for the specified symbols. Parameters ---------- symbols: list[str] + data_frequency: str + is_exchange_symbol: bool + is_local: bool Returns ------- list[TradingPair] + A list of asset objects. + + Notes + ----- + See get_asset for details of each parameter. """ + if symbols is None: + # Make a distinct list of all symbols + symbols = list(set([asset.symbol for asset in self.assets])) + is_exchange_symbol = False - if symbols is not None: - assets = [] - for symbol in symbols: - asset = self.get_asset(symbol, data_frequency) - assets.append(asset) - return assets - else: - return self.assets + assets = [] + for symbol in symbols: + asset = self.get_asset( + symbol, data_frequency, is_exchange_symbol, is_local + ) + assets.append(asset) + return assets - def _find_asset(self, asset, symbol, data_frequency, is_exchange_symbol, - is_local=False): - for a in self.assets: - has_data = (data_frequency == 'minute' - and a.end_minute is not None) \ - or (data_frequency == 'daily' - and a.end_daily is not None) - - symbol_attr = a.exchange_symbol if is_exchange_symbol else a.symbol - if not asset and symbol_attr.lower() == symbol.lower() \ - and (not data_frequency or has_data): - asset = a - - return asset - - def get_asset(self, symbol, data_frequency=None, is_exchange_symbol=False): + def get_asset(self, symbol, data_frequency=None, is_exchange_symbol=False, + is_local=None): """ The market for the specified symbol. Parameters ---------- symbol: str + The Catalyst or exchange symbol. + + data_frequency: str + Check for asset corresponding to the specified data_frequency. + The same asset might exist in the Catalyst repository or + locally (following a CSV ingestion). Filtering by + data_frequency picks the right asset. + + is_exchange_symbol: bool + Whether the symbol uses the Catalyst or exchange convention. + + is_local: bool + For the local or Catalyst asset. Returns ------- TradingPair + The asset object. """ asset = None - log.debug('searching asset {} on the server'.format(symbol)) - asset = self._find_asset( - asset, symbol, data_frequency, is_exchange_symbol, False + log.debug( + 'searching assets for: {} {}'.format( + self.name, symbol + ) ) + for a in self.assets: + if asset is not None: + break - log.debug('asset {} not found on the server, searching local ' - 'assets'.format(symbol)) - asset = self._find_asset( - asset, symbol, data_frequency, is_exchange_symbol, True - ) + if is_local is not None: + data_source = 'local' if is_local else 'catalyst' + applies = (a.data_source == data_source) + + elif data_frequency is not None: + applies = ( + (data_frequency == 'minute' and a.end_minute is not None) + or (data_frequency == 'daily' and a.end_daily is not None) + ) + + else: + applies = True + + # The symbol provided may use the Catalyst or the exchange + # convention + key = a.exchange_symbol if is_exchange_symbol else a.symbol + if not asset and key.lower() == symbol.lower() and applies: + asset = a + + if asset is None: - if not asset: supported_symbols = sorted([ asset.symbol for asset in self.assets ]) @@ -250,6 +281,7 @@ class Exchange: supported_symbols=supported_symbols ) + log.debug('found asset: {}'.format(asset)) return asset def fetch_symbol_map(self, is_local=False): diff --git a/catalyst/exchange/exchange_bundle.py b/catalyst/exchange/exchange_bundle.py index 4f4e7fc6..07bc0207 100644 --- a/catalyst/exchange/exchange_bundle.py +++ b/catalyst/exchange/exchange_bundle.py @@ -715,7 +715,7 @@ class ExchangeBundle: ) mixin_market_params(self.exchange_name, params, market) - asset_def = self.exchange.fetch_asset_def(market, True) + asset_def = self.exchange.get_asset_def(market, True) if asset_def is not None: params['symbol'] = asset_def['symbol']