mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-21 12:30:16 +08:00
Compare commits
46
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fc7dd6838 | ||
|
|
60e07c8a3c | ||
|
|
437e5c5b80 | ||
|
|
8bf639b01a | ||
|
|
a99a4f4e85 | ||
|
|
a1228a8ea2 | ||
|
|
2b0830bafc | ||
|
|
7ce382d52e | ||
|
|
42aac37f34 | ||
|
|
68f92dedd6 | ||
|
|
a30d498abf | ||
|
|
ebdb9e423e | ||
|
|
1f0c037d29 | ||
|
|
623ace1cbd | ||
|
|
a004825a09 | ||
|
|
cbc2ed2aaf | ||
|
|
1de881e17f | ||
|
|
6488ff5abe | ||
|
|
0d051a9496 | ||
|
|
964c90176b | ||
|
|
40cfc65e02 | ||
|
|
abc48494c2 | ||
|
|
73faa87269 | ||
|
|
c2f71cf852 | ||
|
|
fec829b82e | ||
|
|
25dc3ee737 | ||
|
|
5de67a5a61 | ||
|
|
b2f042e2c2 | ||
|
|
704c93dac9 | ||
|
|
478579ed8c | ||
|
|
c65a976b81 | ||
|
|
f9fa28c103 | ||
|
|
14c5ef3006 | ||
|
|
de2d3f6f54 | ||
|
|
b271b372d8 | ||
|
|
3f9a0727c0 | ||
|
|
271a51a393 | ||
|
|
69153295f0 | ||
|
|
1aed7c71f6 | ||
|
|
62d21f1aca | ||
|
|
48a89ad521 | ||
|
|
2225c40b76 | ||
|
|
ad0bc5c41a | ||
|
|
2a239fd5bb | ||
|
|
6b3f59ff76 | ||
|
|
e872b1fc82 |
+59
-10
@@ -433,7 +433,7 @@ cdef class TradingPair(Asset):
|
||||
'taker',
|
||||
'trading_state',
|
||||
'data_source',
|
||||
'decimals'
|
||||
'decimals',
|
||||
})
|
||||
def __init__(self,
|
||||
object symbol,
|
||||
@@ -455,7 +455,7 @@ cdef class TradingPair(Asset):
|
||||
float taker=0.0025,
|
||||
float lot=0,
|
||||
int decimals = 8,
|
||||
int trading_state=0,
|
||||
int trading_state=1,
|
||||
object data_source='catalyst'):
|
||||
"""
|
||||
Replicates the Asset constructor with some built-in conventions
|
||||
@@ -600,14 +600,51 @@ cdef class TradingPair(Asset):
|
||||
cpdef to_dict(self):
|
||||
"""
|
||||
Convert to a python dict.
|
||||
|
||||
Repeat constructor params:
|
||||
object symbol,
|
||||
object exchange,
|
||||
object start_date=None,
|
||||
object asset_name=None,
|
||||
int sid=0,
|
||||
float leverage=1.0,
|
||||
object end_daily=None,
|
||||
object end_minute=None,
|
||||
object end_date=None,
|
||||
object exchange_symbol=None,
|
||||
object first_traded=None,
|
||||
object auto_close_date=None,
|
||||
object exchange_full=None,
|
||||
float min_trade_size=0.0001,
|
||||
float max_trade_size=1000000,
|
||||
float maker=0.0015,
|
||||
float taker=0.0025,
|
||||
float lot=0,
|
||||
int decimals = 8,
|
||||
int trading_state=1,
|
||||
object data_source='catalyst',
|
||||
"""
|
||||
#TODO: missing fields
|
||||
super_dict = super(TradingPair, self).to_dict()
|
||||
super_dict['end_daily'] = self.end_daily
|
||||
super_dict['end_minute'] = self.end_minute
|
||||
super_dict['leverage'] = self.leverage
|
||||
super_dict['min_trade_size'] = self.min_trade_size
|
||||
return super_dict
|
||||
trading_pair_dict = dict(
|
||||
symbol=self.symbol,
|
||||
exchange=self.exchange,
|
||||
start_date=self.start_date,
|
||||
asset_name=self.asset_name,
|
||||
leverage=self.leverage,
|
||||
end_daily=self.end_daily,
|
||||
end_minute=self.end_minute,
|
||||
end_date=self.end_date,
|
||||
exchange_symbol=self.exchange_symbol,
|
||||
exchange_full=self.exchange_full,
|
||||
min_trade_size=self.min_trade_size,
|
||||
max_trade_size=self.max_trade_size,
|
||||
maker=self.maker,
|
||||
taker=self.taker,
|
||||
lot=self.lot,
|
||||
decimals=self.decimals,
|
||||
trading_state=self.trading_state,
|
||||
data_source=self.data_source,
|
||||
)
|
||||
return trading_pair_dict
|
||||
|
||||
def is_exchange_open(self, dt_minute):
|
||||
"""
|
||||
@@ -623,6 +660,16 @@ cdef class TradingPair(Asset):
|
||||
#TODO: make more dymanic to catch holds
|
||||
return True
|
||||
|
||||
def set_end_date(self, dt, data_frequency):
|
||||
if data_frequency == 'minute':
|
||||
self.end_minute = dt
|
||||
|
||||
else:
|
||||
self.end_daily = dt
|
||||
|
||||
def set_start_date(self, dt):
|
||||
self.start_date = dt
|
||||
|
||||
cpdef __reduce__(self):
|
||||
"""
|
||||
Function used by pickle to determine how to serialize/deserialize this
|
||||
@@ -646,7 +693,9 @@ cdef class TradingPair(Asset):
|
||||
self.lot,
|
||||
self.decimals,
|
||||
self.taker,
|
||||
self.maker))
|
||||
self.maker,
|
||||
self.trading_state,
|
||||
self.data_source))
|
||||
|
||||
def make_asset_array(int size, Asset asset):
|
||||
cdef np.ndarray out = np.empty([size], dtype=object)
|
||||
|
||||
@@ -11,7 +11,10 @@ LOG_LEVEL = int(os.environ.get('CATALYST_LOG_LEVEL', logbook.INFO))
|
||||
|
||||
SYMBOLS_URL = 'https://s3.amazonaws.com/enigmaco/catalyst-exchanges/' \
|
||||
'{exchange}/symbols.json'
|
||||
|
||||
EXCHANGE_CONFIG_URL = 'https://s3.amazonaws.com/enigmaco/ohlcv/' \
|
||||
'{exchange}/config.json'
|
||||
BUNDLE_URL = 'https://s3.amazonaws.com/enigmaco/ohlcv/' \
|
||||
'{exchange}/{data_frequency}/{name}.tar.gz'
|
||||
DATE_TIME_FORMAT = '%Y-%m-%d %H:%M'
|
||||
DATE_FORMAT = '%Y-%m-%d'
|
||||
|
||||
|
||||
@@ -33,12 +33,12 @@ def initialize(context):
|
||||
# parameters or values you're going to use.
|
||||
|
||||
# In our example, we're looking at Neo in Ether.
|
||||
context.market = symbol('bnb_eth')
|
||||
context.market = symbol('eth_btc')
|
||||
context.base_price = None
|
||||
context.current_day = None
|
||||
|
||||
context.RSI_OVERSOLD = 60
|
||||
context.RSI_OVERBOUGHT = 70
|
||||
context.RSI_OVERSOLD = 55
|
||||
context.RSI_OVERBOUGHT = 60
|
||||
context.CANDLE_SIZE = '15T'
|
||||
|
||||
context.start_time = time.time()
|
||||
@@ -248,14 +248,14 @@ if __name__ == '__main__':
|
||||
|
||||
if live:
|
||||
run_algorithm(
|
||||
capital_base=0.1,
|
||||
capital_base=0.03,
|
||||
initialize=initialize,
|
||||
handle_data=handle_data,
|
||||
analyze=analyze,
|
||||
exchange_name='binance',
|
||||
exchange_name='poloniex',
|
||||
live=True,
|
||||
algo_namespace=NAMESPACE,
|
||||
base_currency='eth',
|
||||
base_currency='btc',
|
||||
live_graph=False,
|
||||
simulate_orders=False,
|
||||
stats_output=None,
|
||||
@@ -274,7 +274,7 @@ if __name__ == '__main__':
|
||||
# -x bitfinex -s 2017-10-1 -e 2017-11-10 -c usdt -n mean-reversion \
|
||||
# --data-frequency minute --capital-base 10000
|
||||
run_algorithm(
|
||||
capital_base=0.035,
|
||||
capital_base=0.1,
|
||||
data_frequency='minute',
|
||||
initialize=initialize,
|
||||
handle_data=handle_data,
|
||||
|
||||
@@ -1,34 +1,33 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
import ccxt
|
||||
import pandas as pd
|
||||
import six
|
||||
from ccxt import InvalidOrder, NetworkError, \
|
||||
ExchangeError
|
||||
from logbook import Logger
|
||||
from six import string_types
|
||||
from catalyst.assets._assets import TradingPair
|
||||
from redo import retry
|
||||
|
||||
from catalyst.algorithm import MarketOrder
|
||||
from catalyst.assets._assets import TradingPair
|
||||
from catalyst.constants import LOG_LEVEL
|
||||
from catalyst.exchange.exchange import Exchange
|
||||
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
||||
from catalyst.exchange.exchange_errors import InvalidHistoryFrequencyError, \
|
||||
ExchangeSymbolsNotFound, ExchangeRequestError, InvalidOrderStyle, \
|
||||
UnsupportedHistoryFrequencyError, \
|
||||
ExchangeNotFoundError, CreateOrderError, InvalidHistoryTimeframeError, \
|
||||
UnsupportedHistoryFrequencyError
|
||||
MarketsNotFoundError, InvalidMarketError
|
||||
from catalyst.exchange.exchange_execution import ExchangeLimitOrder
|
||||
from catalyst.exchange.utils.exchange_utils import mixin_market_params, \
|
||||
get_exchange_folder, get_catalyst_symbol, \
|
||||
get_exchange_auth
|
||||
from catalyst.exchange.utils.ccxt_utils import get_exchange_config
|
||||
from catalyst.exchange.utils.datetime_utils import from_ms_timestamp, \
|
||||
get_epoch, \
|
||||
get_periods_range
|
||||
from catalyst.exchange.utils.exchange_utils import get_catalyst_symbol
|
||||
from catalyst.finance.order import Order, ORDER_STATUS
|
||||
from catalyst.finance.transaction import Transaction
|
||||
from ccxt import InvalidOrder, NetworkError, \
|
||||
ExchangeError
|
||||
from logbook import Logger
|
||||
from six import string_types
|
||||
|
||||
log = Logger('CCXT', level=LOG_LEVEL)
|
||||
|
||||
@@ -44,7 +43,7 @@ SUPPORTED_EXCHANGES = dict(
|
||||
|
||||
class CCXT(Exchange):
|
||||
def __init__(self, exchange_name, key,
|
||||
secret, password, base_currency):
|
||||
secret, password, base_currency, config=None):
|
||||
log.debug(
|
||||
'finding {} in CCXT exchanges:\n{}'.format(
|
||||
exchange_name, ccxt.exchanges
|
||||
@@ -64,6 +63,8 @@ class CCXT(Exchange):
|
||||
'password': password,
|
||||
})
|
||||
self.api.enableRateLimit = True
|
||||
self.has = self.api.has
|
||||
self.fees = self.api.fees
|
||||
|
||||
except Exception:
|
||||
raise ExchangeNotFoundError(exchange_name=exchange_name)
|
||||
@@ -71,6 +72,7 @@ class CCXT(Exchange):
|
||||
self._symbol_maps = [None, None]
|
||||
|
||||
self.name = exchange_name
|
||||
self.assets = []
|
||||
|
||||
self.base_currency = base_currency
|
||||
self.transactions = defaultdict(list)
|
||||
@@ -82,97 +84,123 @@ class CCXT(Exchange):
|
||||
self._common_symbols = dict()
|
||||
|
||||
self.bundle = ExchangeBundle(self.name)
|
||||
self.markets = None
|
||||
self._is_init = False
|
||||
self._config = config
|
||||
|
||||
def init(self):
|
||||
if self._is_init:
|
||||
return
|
||||
|
||||
exchange_folder = get_exchange_folder(self.name)
|
||||
filename = os.path.join(exchange_folder, 'cctx_markets.json')
|
||||
|
||||
if os.path.exists(filename):
|
||||
timestamp = os.path.getmtime(filename)
|
||||
dt = pd.to_datetime(timestamp, unit='s', utc=True)
|
||||
|
||||
if dt >= pd.Timestamp.utcnow().floor('1D'):
|
||||
with open(filename) as f:
|
||||
self.markets = json.load(f)
|
||||
|
||||
log.debug('loaded markets for {}'.format(self.name))
|
||||
|
||||
if self.markets is None:
|
||||
try:
|
||||
markets_symbols = self.api.load_markets()
|
||||
log.debug(
|
||||
'fetching {} markets:\n{}'.format(
|
||||
self.name, markets_symbols
|
||||
)
|
||||
if self._config is None:
|
||||
self._config = get_exchange_config(self.name)
|
||||
log.debug(
|
||||
'got exchange config {}:\n{}'.format(
|
||||
self.name, self._config
|
||||
)
|
||||
|
||||
self.markets = self.api.fetch_markets()
|
||||
with open(filename, 'w+') as f:
|
||||
json.dump(self.markets, f, indent=4)
|
||||
|
||||
except (ExchangeError, NetworkError) as e:
|
||||
log.warn(
|
||||
'unable to fetch markets {}: {}'.format(
|
||||
self.name, e
|
||||
)
|
||||
)
|
||||
raise ExchangeRequestError(error=e)
|
||||
)
|
||||
|
||||
self.load_assets()
|
||||
self._is_init = True
|
||||
|
||||
@staticmethod
|
||||
def find_exchanges(features=None, is_authenticated=False):
|
||||
ccxt_features = []
|
||||
if features is not None:
|
||||
for feature in features:
|
||||
if not feature.endswith('Bundle'):
|
||||
ccxt_features.append(feature)
|
||||
def load_assets(self):
|
||||
if self._config is None:
|
||||
raise ValueError('Exchange config not available.')
|
||||
|
||||
exchange_names = []
|
||||
for exchange_name in ccxt.exchanges:
|
||||
if is_authenticated:
|
||||
exchange_auth = get_exchange_auth(exchange_name)
|
||||
self.assets = []
|
||||
for asset_dict in self._config['assets']:
|
||||
asset = TradingPair(**asset_dict)
|
||||
self.assets.append(asset)
|
||||
|
||||
has_auth = (exchange_auth['key'] != ''
|
||||
and exchange_auth['secret'] != '')
|
||||
def _fetch_markets(self):
|
||||
markets_symbols = self.api.load_markets()
|
||||
log.debug(
|
||||
'fetching {} markets:\n{}'.format(
|
||||
self.name, markets_symbols
|
||||
)
|
||||
)
|
||||
try:
|
||||
markets = self.api.fetch_markets()
|
||||
|
||||
if not has_auth:
|
||||
continue
|
||||
except NetworkError as e:
|
||||
raise ExchangeRequestError(error=e)
|
||||
|
||||
log.debug('loading exchange: {}'.format(exchange_name))
|
||||
exchange = getattr(ccxt, exchange_name)()
|
||||
if not markets:
|
||||
raise MarketsNotFoundError(
|
||||
exchange=self.name,
|
||||
)
|
||||
|
||||
if ccxt_features is None:
|
||||
has_feature = True
|
||||
for market in markets:
|
||||
if 'id' not in market:
|
||||
raise InvalidMarketError(
|
||||
exchange=self.name,
|
||||
market=market,
|
||||
)
|
||||
return markets
|
||||
|
||||
else:
|
||||
try:
|
||||
has_feature = all(
|
||||
[exchange.has[feature] for feature in ccxt_features]
|
||||
)
|
||||
def create_exchange_config(self):
|
||||
config = dict(
|
||||
name=self.name,
|
||||
features=[feature for feature in self.has if self.has[feature]]
|
||||
)
|
||||
markets = retry(
|
||||
action=self._fetch_markets,
|
||||
attempts=5,
|
||||
sleeptime=5,
|
||||
retry_exceptions=(ExchangeRequestError,),
|
||||
cleanup=lambda: log.warn(
|
||||
'fetching markets again for {}'.format(self.name)
|
||||
),
|
||||
)
|
||||
|
||||
except Exception:
|
||||
has_feature = False
|
||||
config['assets'] = []
|
||||
for market in markets:
|
||||
asset = self.create_trading_pair(market=market)
|
||||
config['assets'].append(asset)
|
||||
|
||||
if has_feature:
|
||||
try:
|
||||
log.info('initializing {}'.format(exchange_name))
|
||||
exchange_names.append(exchange_name)
|
||||
return config
|
||||
|
||||
except Exception as e:
|
||||
log.warn(
|
||||
'unable to initialize exchange {}: {}'.format(
|
||||
exchange_name, e
|
||||
)
|
||||
)
|
||||
def create_trading_pair(self, market, start_dt=None, end_dt=None,
|
||||
leverage=1, end_daily=None, end_minute=None):
|
||||
"""
|
||||
Creating a TradingPair from market and asset data.
|
||||
|
||||
return exchange_names
|
||||
Parameters
|
||||
----------
|
||||
market: dict[str, Object]
|
||||
start_dt
|
||||
end_dt
|
||||
leverage
|
||||
end_daily
|
||||
end_minute
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
"""
|
||||
params = dict(
|
||||
exchange=self.name,
|
||||
data_source='catalyst',
|
||||
exchange_symbol=market['id'],
|
||||
symbol=get_catalyst_symbol(market),
|
||||
start_date=start_dt,
|
||||
end_date=end_dt,
|
||||
leverage=leverage,
|
||||
asset_name=market['symbol'],
|
||||
end_daily=end_daily,
|
||||
end_minute=end_minute,
|
||||
)
|
||||
self.apply_conditional_market_params(params, market)
|
||||
|
||||
return TradingPair(**params)
|
||||
|
||||
def load_assets(self):
|
||||
if self._config is None or 'error' in self._config:
|
||||
raise ValueError('Exchange config not available.')
|
||||
|
||||
self.assets = []
|
||||
for asset_dict in self._config['assets']:
|
||||
asset = TradingPair(**asset_dict)
|
||||
self.assets.append(asset)
|
||||
|
||||
def account(self):
|
||||
return None
|
||||
@@ -204,32 +232,11 @@ class CCXT(Exchange):
|
||||
|
||||
return frequencies
|
||||
|
||||
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),
|
||||
None,
|
||||
)
|
||||
return market
|
||||
|
||||
def substitute_currency_code(self, currency, source='catalyst'):
|
||||
if source == 'catalyst':
|
||||
currency = currency.upper()
|
||||
|
||||
key = self.api.common_currency_code(currency)
|
||||
key = self.api.common_currency_code(currency).lower()
|
||||
self._common_symbols[key] = currency.lower()
|
||||
return key
|
||||
|
||||
@@ -257,13 +264,7 @@ class CCXT(Exchange):
|
||||
if source == 'ccxt':
|
||||
if isinstance(asset_or_symbol, string_types):
|
||||
parts = asset_or_symbol.split('/')
|
||||
base_currency = self.substitute_currency_code(
|
||||
parts[0], source
|
||||
)
|
||||
quote_currency = self.substitute_currency_code(
|
||||
parts[1], source
|
||||
)
|
||||
return '{}_{}'.format(base_currency, quote_currency)
|
||||
return '{}_{}'.format(parts[0].lower(), parts[1].lower())
|
||||
|
||||
else:
|
||||
return asset_or_symbol.symbol
|
||||
@@ -274,13 +275,7 @@ class CCXT(Exchange):
|
||||
) else asset_or_symbol.symbol
|
||||
|
||||
parts = symbol.split('_')
|
||||
base_currency = self.substitute_currency_code(
|
||||
parts[0], source
|
||||
)
|
||||
quote_currency = self.substitute_currency_code(
|
||||
parts[1], source
|
||||
)
|
||||
return '{}/{}'.format(base_currency, quote_currency)
|
||||
return '{}/{}'.format(parts[0].upper(), parts[1].upper())
|
||||
|
||||
@staticmethod
|
||||
def map_frequency(value, source='ccxt', raise_error=True):
|
||||
@@ -406,7 +401,7 @@ class CCXT(Exchange):
|
||||
)
|
||||
|
||||
def get_candles(self, freq, assets, bar_count=1, start_dt=None,
|
||||
end_dt=None):
|
||||
end_dt=None, floor_dates=True):
|
||||
is_single = (isinstance(assets, TradingPair))
|
||||
if is_single:
|
||||
assets = [assets]
|
||||
@@ -453,16 +448,20 @@ class CCXT(Exchange):
|
||||
|
||||
candles[asset] = []
|
||||
for ohlcv in ohlcvs:
|
||||
candles[asset].append(dict(
|
||||
last_traded=pd.to_datetime(
|
||||
ohlcv[0], unit='ms', utc=True
|
||||
),
|
||||
open=ohlcv[1],
|
||||
high=ohlcv[2],
|
||||
low=ohlcv[3],
|
||||
close=ohlcv[4],
|
||||
volume=ohlcv[5]
|
||||
))
|
||||
dt = pd.to_datetime(ohlcv[0], unit='ms', utc=True)
|
||||
if floor_dates:
|
||||
dt = dt.floor('1T')
|
||||
|
||||
candles[asset].append(
|
||||
dict(
|
||||
last_traded=dt,
|
||||
open=ohlcv[1],
|
||||
high=ohlcv[2],
|
||||
low=ohlcv[3],
|
||||
close=ohlcv[4],
|
||||
volume=ohlcv[5],
|
||||
)
|
||||
)
|
||||
candles[asset] = sorted(
|
||||
candles[asset], key=lambda c: c['last_traded']
|
||||
)
|
||||
@@ -480,144 +479,53 @@ class CCXT(Exchange):
|
||||
except ExchangeSymbolsNotFound:
|
||||
return None
|
||||
|
||||
def get_asset_defs(self, market):
|
||||
def apply_conditional_market_params(self, params, market):
|
||||
"""
|
||||
The local and Catalyst definitions of the specified market.
|
||||
Applies a CCXT market dict to parameters of TradingPair init.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
market: dict[str, Object]
|
||||
The CCXT market dicts.
|
||||
params: dict[Object]
|
||||
market: dict[Object]
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict[str, Object]
|
||||
The asset definition.
|
||||
|
||||
"""
|
||||
asset_defs = []
|
||||
|
||||
for is_local in (False, True):
|
||||
asset_def = self.get_asset_def(market, is_local)
|
||||
asset_defs.append((asset_def, is_local))
|
||||
|
||||
return asset_defs
|
||||
|
||||
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)
|
||||
if symbol_map is not None:
|
||||
assets_lower = {k.lower(): v for k, v in symbol_map.items()}
|
||||
key = exchange_symbol.lower()
|
||||
|
||||
asset = assets_lower[key] if key in assets_lower else None
|
||||
if asset is not None:
|
||||
return asset
|
||||
|
||||
else:
|
||||
return None
|
||||
# TODO: make this more externalized / configurable
|
||||
# Consider representing in some type of JSON structure
|
||||
if 'active' in market:
|
||||
params['trading_state'] = 1 if market['active'] else 0
|
||||
|
||||
else:
|
||||
return None
|
||||
params['trading_state'] = 1
|
||||
|
||||
def create_trading_pair(self, market, asset_def=None, is_local=False):
|
||||
"""
|
||||
Creating a TradingPair from market and asset data.
|
||||
if 'lot' in market:
|
||||
params['min_trade_size'] = market['lot']
|
||||
params['lot'] = market['lot']
|
||||
|
||||
Parameters
|
||||
----------
|
||||
market: dict[str, Object]
|
||||
asset_def: dict[str, Object]
|
||||
is_local: bool
|
||||
if self.name == 'bitfinex':
|
||||
params['maker'] = 0.001
|
||||
params['taker'] = 0.002
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
"""
|
||||
data_source = 'local' if is_local else 'catalyst'
|
||||
params = dict(
|
||||
exchange=self.name,
|
||||
data_source=data_source,
|
||||
exchange_symbol=market['id'],
|
||||
)
|
||||
mixin_market_params(self.name, params, market)
|
||||
|
||||
if asset_def is not None:
|
||||
params['symbol'] = asset_def['symbol']
|
||||
|
||||
params['start_date'] = asset_def['start_date'] \
|
||||
if 'start_date' in asset_def else None
|
||||
|
||||
params['end_date'] = asset_def['end_date'] \
|
||||
if 'end_date' in asset_def else None
|
||||
|
||||
params['leverage'] = asset_def['leverage'] \
|
||||
if 'leverage' in asset_def else 1.0
|
||||
|
||||
params['asset_name'] = asset_def['asset_name'] \
|
||||
if 'asset_name' in asset_def else None
|
||||
|
||||
params['end_daily'] = asset_def['end_daily'] \
|
||||
if 'end_daily' in asset_def \
|
||||
and asset_def['end_daily'] != 'N/A' else None
|
||||
|
||||
params['end_minute'] = asset_def['end_minute'] \
|
||||
if 'end_minute' in asset_def \
|
||||
and asset_def['end_minute'] != 'N/A' else None
|
||||
elif 'maker' in market and 'taker' in market \
|
||||
and market['maker'] is not None \
|
||||
and market['taker'] is not None:
|
||||
params['maker'] = market['maker']
|
||||
params['taker'] = market['taker']
|
||||
|
||||
else:
|
||||
params['symbol'] = get_catalyst_symbol(market)
|
||||
# TODO: add as an optional column
|
||||
params['leverage'] = 1.0
|
||||
# TODO: default commission, make configurable
|
||||
params['maker'] = 0.0015
|
||||
params['taker'] = 0.0025
|
||||
|
||||
return TradingPair(**params)
|
||||
info = market['info'] if 'info' in market else None
|
||||
if info:
|
||||
if 'minimum_order_size' in info:
|
||||
params['min_trade_size'] = float(info['minimum_order_size'])
|
||||
|
||||
def load_assets(self):
|
||||
log.debug('loading assets for {}'.format(self.name))
|
||||
self.assets = []
|
||||
|
||||
for market in self.markets:
|
||||
if 'id' not in market:
|
||||
log.warn('invalid market: {}'.format(market))
|
||||
continue
|
||||
|
||||
asset_defs = self.get_asset_defs(market)
|
||||
|
||||
asset = None
|
||||
for asset_def in asset_defs:
|
||||
if asset_def[0] is not None or not asset_defs[1]:
|
||||
try:
|
||||
asset = self.create_trading_pair(
|
||||
market=market,
|
||||
asset_def=asset_def[0],
|
||||
is_local=asset_def[1]
|
||||
)
|
||||
self.assets.append(asset)
|
||||
|
||||
except TypeError as e:
|
||||
log.warn('unable to add asset: {}'.format(e))
|
||||
|
||||
if asset is None:
|
||||
asset = self.create_trading_pair(market=market)
|
||||
self.assets.append(asset)
|
||||
if 'lot' not in params:
|
||||
params['lot'] = params['min_trade_size']
|
||||
|
||||
def get_balances(self):
|
||||
try:
|
||||
@@ -755,18 +663,14 @@ class CCXT(Exchange):
|
||||
|
||||
side = 'buy' if amount > 0 else 'sell'
|
||||
if hasattr(self.api, 'amount_to_lots'):
|
||||
# TODO: is this right?
|
||||
if self.api.markets is None:
|
||||
self.api.load_markets()
|
||||
|
||||
# https://github.com/ccxt/ccxt/issues/1483
|
||||
adj_amount = round(abs(amount), asset.decimals)
|
||||
market = self.api.markets[symbol]
|
||||
if 'lots' in market and market['lots'] > amount:
|
||||
raise CreateOrderError(
|
||||
exchange=self.name,
|
||||
e='order amount lower than the smallest lot: {}'.format(
|
||||
amount
|
||||
adj_amount = self.api.amount_to_lots(
|
||||
symbol=symbol,
|
||||
amount=abs(amount),
|
||||
)
|
||||
if adj_amount != abs(amount):
|
||||
log.info(
|
||||
'adjusted order amount {} to {} based on lot size'.format(
|
||||
abs(amount), adj_amount,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -994,7 +898,7 @@ class CCXT(Exchange):
|
||||
symbol = self.get_symbol(asset_or_symbol) \
|
||||
if asset_or_symbol is not None else None
|
||||
self.api.cancel_order(id=order_id,
|
||||
symbol=symbol, params= params)
|
||||
symbol=symbol, params=params)
|
||||
|
||||
except (ExchangeError, NetworkError) as e:
|
||||
log.warn(
|
||||
@@ -1112,19 +1016,27 @@ class CCXT(Exchange):
|
||||
return result
|
||||
|
||||
def get_trades(self, asset, my_trades=True, start_dt=None, limit=100):
|
||||
if not my_trades:
|
||||
raise NotImplemented(
|
||||
'get_trades only supports "my trades"'
|
||||
)
|
||||
|
||||
# TODO: is it possible to sort this? Limit is useless otherwise.
|
||||
ccxt_symbol = self.get_symbol(asset)
|
||||
if start_dt:
|
||||
delta = start_dt - get_epoch()
|
||||
since = int(delta.total_seconds()) * 1000
|
||||
else:
|
||||
since = None
|
||||
|
||||
try:
|
||||
trades = self.api.fetch_my_trades(
|
||||
symbol=ccxt_symbol,
|
||||
since=start_dt,
|
||||
limit=limit,
|
||||
)
|
||||
if my_trades:
|
||||
trades = self.api.fetch_my_trades(
|
||||
symbol=ccxt_symbol,
|
||||
since=since,
|
||||
limit=limit,
|
||||
)
|
||||
else:
|
||||
trades = self.api.fetch_trades(
|
||||
symbol=ccxt_symbol,
|
||||
since=since,
|
||||
limit=limit,
|
||||
)
|
||||
except (ExchangeError, NetworkError) as e:
|
||||
log.warn(
|
||||
'unable to fetch trades {} / {}: {}'.format(
|
||||
|
||||
@@ -5,6 +5,8 @@ from time import sleep
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from logbook import Logger
|
||||
|
||||
from catalyst.constants import LOG_LEVEL
|
||||
from catalyst.data.data_portal import BASE_FIELDS
|
||||
from catalyst.exchange.exchange_bundle import ExchangeBundle
|
||||
@@ -17,9 +19,8 @@ from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \
|
||||
from catalyst.exchange.utils.datetime_utils import get_delta, \
|
||||
get_periods_range, \
|
||||
get_periods, get_start_dt, get_frequency
|
||||
from catalyst.exchange.utils.exchange_utils import get_exchange_symbols, \
|
||||
resample_history_df, has_bundle, get_candles_df
|
||||
from logbook import Logger
|
||||
from catalyst.exchange.utils.exchange_utils import \
|
||||
resample_history_df, has_bundle
|
||||
|
||||
log = Logger('Exchange', level=LOG_LEVEL)
|
||||
|
||||
@@ -256,10 +257,10 @@ class Exchange:
|
||||
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)
|
||||
data_frequency == 'minute' and a.end_minute is not None
|
||||
) or (
|
||||
data_frequency == 'daily' and a.end_daily is not None
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
@@ -292,16 +293,6 @@ class Exchange:
|
||||
log.debug('found asset: {}'.format(asset))
|
||||
return asset
|
||||
|
||||
def fetch_symbol_map(self, is_local=False):
|
||||
index = 1 if is_local else 0
|
||||
if self._symbol_maps[index] is not None:
|
||||
return self._symbol_maps[index]
|
||||
|
||||
else:
|
||||
symbol_map = get_exchange_symbols(self.name, is_local)
|
||||
self._symbol_maps[index] = symbol_map
|
||||
return symbol_map
|
||||
|
||||
@abstractmethod
|
||||
def init(self):
|
||||
"""
|
||||
@@ -313,24 +304,13 @@ class Exchange:
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def load_assets(self, is_local=False):
|
||||
def create_exchange_config(self):
|
||||
"""
|
||||
Populate the 'assets' attribute with a dictionary of Assets.
|
||||
The key of the resulting dictionary is the exchange specific
|
||||
currency pair symbol. The universal symbol is contained in the
|
||||
'symbol' attribute of each asset.
|
||||
|
||||
Notes
|
||||
-----
|
||||
The sid of each asset is calculated based on a numeric hash of the
|
||||
universal symbol. This simple approach avoids maintaining a mapping
|
||||
of sids.
|
||||
|
||||
This method can be omerridden if an exchange offers equivalent data
|
||||
via its api.
|
||||
Fetch the exchange market data and generate a config object
|
||||
Returns
|
||||
-------
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def get_spot_value(self, assets, field, dt=None, data_frequency='minute'):
|
||||
"""
|
||||
@@ -626,7 +606,7 @@ class Exchange:
|
||||
start_dt = get_start_dt(end_dt, adj_bar_count, data_frequency)
|
||||
trailing_dt = \
|
||||
series[asset].index[-1] + get_delta(1, data_frequency) \
|
||||
if asset in series else start_dt
|
||||
if asset in series else start_dt
|
||||
|
||||
# The get_history method supports multiple asset
|
||||
# Use the original frequency to let each api optimize
|
||||
@@ -667,16 +647,20 @@ class Exchange:
|
||||
|
||||
return df
|
||||
|
||||
def _check_low_balance(self, currency, balances, amount):
|
||||
def _check_low_balance(self, currency, balances, amount, open_orders=None):
|
||||
free = balances[currency]['free'] if currency in balances else 0.0
|
||||
|
||||
if open_orders:
|
||||
# TODO: make sure that this works
|
||||
free += sum([order.amount for order in open_orders])
|
||||
|
||||
if free < amount:
|
||||
return free, True
|
||||
|
||||
else:
|
||||
return free, False
|
||||
|
||||
def sync_positions(self, positions, cash=None,
|
||||
def sync_positions(self, positions, open_orders=None, cash=None,
|
||||
check_balances=False):
|
||||
"""
|
||||
Update the portfolio cash and position balances based on the
|
||||
@@ -706,7 +690,7 @@ class Exchange:
|
||||
balances=balances,
|
||||
amount=cash,
|
||||
)
|
||||
if is_lower:
|
||||
if is_lower and not open_orders:
|
||||
raise NotEnoughCashError(
|
||||
currency=self.base_currency,
|
||||
exchange=self.name,
|
||||
|
||||
@@ -18,9 +18,11 @@ from datetime import timedelta
|
||||
from os import listdir
|
||||
from os.path import isfile, join, exists
|
||||
|
||||
import catalyst.protocol as zp
|
||||
import logbook
|
||||
import pandas as pd
|
||||
from redo import retry
|
||||
|
||||
import catalyst.protocol as zp
|
||||
from catalyst.algorithm import TradingAlgorithm
|
||||
from catalyst.constants import LOG_LEVEL
|
||||
from catalyst.exchange.exchange_blotter import ExchangeBlotter
|
||||
@@ -50,7 +52,6 @@ from catalyst.utils.api_support import api_method
|
||||
from catalyst.utils.input_validation import error_keywords, ensure_upper_case
|
||||
from catalyst.utils.math_utils import round_nearest
|
||||
from catalyst.utils.preprocess import preprocess
|
||||
from redo import retry
|
||||
|
||||
log = logbook.Logger('exchange_algorithm', level=LOG_LEVEL)
|
||||
|
||||
@@ -670,6 +671,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
|
||||
required_cash = self.portfolio.cash if not orders else None
|
||||
cash, positions_value = exchange.sync_positions(
|
||||
positions=exchange_positions,
|
||||
open_orders=orders,
|
||||
check_balances=check_balances,
|
||||
cash=required_cash,
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import copy
|
||||
import os
|
||||
import shutil
|
||||
from datetime import timedelta
|
||||
@@ -8,8 +9,12 @@ from operator import is_not
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import pytz
|
||||
from catalyst import get_calendar
|
||||
from catalyst.assets._assets import TradingPair
|
||||
from logbook import Logger
|
||||
from pytz import UTC
|
||||
from six import itervalues
|
||||
|
||||
from catalyst import get_calendar
|
||||
from catalyst.constants import DATE_TIME_FORMAT, AUTO_INGEST
|
||||
from catalyst.constants import LOG_LEVEL
|
||||
from catalyst.data.minute_bars import BcolzMinuteOverlappingData, \
|
||||
@@ -22,15 +27,12 @@ from catalyst.exchange.exchange_errors import EmptyValuesInBundleError, \
|
||||
PricingDataNotLoadedError, DataCorruptionError, PricingDataValueError
|
||||
from catalyst.exchange.utils.bundle_utils import range_in_bundle, \
|
||||
get_bcolz_chunk, get_df_from_arrays, get_assets
|
||||
from catalyst.exchange.utils.datetime_utils import get_delta, get_start_dt, \
|
||||
get_period_label, get_month_start_end, get_year_start_end
|
||||
from catalyst.exchange.utils.exchange_utils import get_exchange_folder, \
|
||||
save_exchange_symbols, mixin_market_params, get_catalyst_symbol
|
||||
from catalyst.exchange.utils.datetime_utils import get_start_dt, \
|
||||
get_period_label, get_month_start_end, get_year_start_end, get_period, \
|
||||
timestr_to_dt
|
||||
from catalyst.exchange.utils.exchange_utils import get_exchange_folder
|
||||
from catalyst.utils.cli import maybe_show_progress
|
||||
from catalyst.utils.paths import ensure_directory
|
||||
from logbook import Logger
|
||||
from pytz import UTC
|
||||
from six import itervalues
|
||||
|
||||
log = Logger('exchange_bundle', level=LOG_LEVEL)
|
||||
|
||||
@@ -512,8 +514,8 @@ class ExchangeBundle:
|
||||
continue
|
||||
|
||||
dates = pd.date_range(
|
||||
start=get_period_label(adj_start, data_frequency),
|
||||
end=get_period_label(adj_end, data_frequency),
|
||||
start=get_period(adj_start, data_frequency),
|
||||
end=get_period(adj_end, data_frequency),
|
||||
freq='MS' if data_frequency == 'minute' else 'AS',
|
||||
tz=UTC
|
||||
)
|
||||
@@ -552,7 +554,9 @@ class ExchangeBundle:
|
||||
|
||||
# We sort the chunks by end date to ingest most recent data first
|
||||
chunks[asset].sort(
|
||||
key=lambda chunk: pd.to_datetime(chunk['period'])
|
||||
key=lambda chunk: timestr_to_dt(
|
||||
chunk['period'], data_frequency
|
||||
)
|
||||
)
|
||||
|
||||
return chunks
|
||||
@@ -607,7 +611,8 @@ class ExchangeBundle:
|
||||
exchange=self.exchange_name,
|
||||
frequency=data_frequency,
|
||||
symbol=asset.symbol
|
||||
)) as it:
|
||||
)
|
||||
) as it:
|
||||
for chunk in it:
|
||||
problems += self.ingest_ctable(
|
||||
asset=chunk['asset'],
|
||||
@@ -622,7 +627,9 @@ class ExchangeBundle:
|
||||
|
||||
# We sort the chunks by end date to ingest most recent data first
|
||||
all_chunks.sort(
|
||||
key=lambda chunk: pd.to_datetime(chunk['period'])
|
||||
key=lambda chunk: timestr_to_dt(
|
||||
chunk['period'], data_frequency
|
||||
)
|
||||
)
|
||||
with maybe_show_progress(
|
||||
all_chunks,
|
||||
@@ -631,7 +638,8 @@ class ExchangeBundle:
|
||||
'{exchange}'.format(
|
||||
exchange=self.exchange_name,
|
||||
frequency=data_frequency,
|
||||
)) as it:
|
||||
)
|
||||
) as it:
|
||||
for chunk in it:
|
||||
problems += self.ingest_ctable(
|
||||
asset=chunk['asset'],
|
||||
@@ -700,42 +708,36 @@ class ExchangeBundle:
|
||||
for symbol in symbols:
|
||||
start_dt = df.index.get_level_values(1).min()
|
||||
end_dt = df.index.get_level_values(1).max()
|
||||
end_dt_key = 'end_{}'.format(data_frequency)
|
||||
|
||||
market = self.exchange.get_market(symbol)
|
||||
if market is None:
|
||||
raise ValueError('symbol not available in the exchange.')
|
||||
try:
|
||||
asset = self.exchange.get_asset(symbol, is_local=True)
|
||||
except:
|
||||
asset = copy.deepcopy(self.exchange.get_asset(symbol))
|
||||
|
||||
params = dict(
|
||||
exchange=self.exchange.name,
|
||||
data_source='local',
|
||||
exchange_symbol=market['id'],
|
||||
)
|
||||
mixin_market_params(self.exchange_name, params, market)
|
||||
if asset.data_source == 'local':
|
||||
asset.start_date = asset.start_date \
|
||||
if asset.start_date < start_dt else start_dt
|
||||
|
||||
asset_def = self.exchange.get_asset_def(market, True)
|
||||
if asset_def is not None:
|
||||
params['symbol'] = asset_def['symbol']
|
||||
if data_frequency == 'daily':
|
||||
asset.end_date = asset.end_daily = asset.end_daily \
|
||||
if asset.end_daily > end_dt else end_dt
|
||||
|
||||
params['start_date'] = asset_def['start_date'] \
|
||||
if asset_def['start_date'] < start_dt else start_dt
|
||||
|
||||
params['end_date'] = asset_def[end_dt_key] \
|
||||
if asset_def[end_dt_key] > end_dt else end_dt
|
||||
|
||||
params['end_daily'] = end_dt \
|
||||
if data_frequency == 'daily' else asset_def['end_daily']
|
||||
|
||||
params['end_minute'] = end_dt \
|
||||
if data_frequency == 'minute' else asset_def['end_minute']
|
||||
else:
|
||||
asset.end_date = asset.end_minute = asset.end_minute \
|
||||
if asset.end_minute > end_dt else end_dt
|
||||
|
||||
else:
|
||||
params['symbol'] = get_catalyst_symbol(market)
|
||||
asset.data_source = 'local'
|
||||
asset.start_date = start_dt
|
||||
asset.end_dt = end_dt
|
||||
|
||||
params['end_daily'] = end_dt \
|
||||
if data_frequency == 'daily' else 'N/A'
|
||||
params['end_minute'] = end_dt \
|
||||
if data_frequency == 'minute' else 'N/A'
|
||||
if data_frequency == 'daily':
|
||||
asset.end_daily = end_dt
|
||||
asset.end_minute = None
|
||||
|
||||
else:
|
||||
asset.end_daily = None
|
||||
asset.end_minute = end_dt
|
||||
|
||||
if min_start_dt is None or start_dt < min_start_dt:
|
||||
min_start_dt = start_dt
|
||||
@@ -743,11 +745,9 @@ class ExchangeBundle:
|
||||
if max_end_dt is None or end_dt > max_end_dt:
|
||||
max_end_dt = end_dt
|
||||
|
||||
asset = TradingPair(**params)
|
||||
assets[market['id']] = asset
|
||||
|
||||
save_exchange_symbols(self.exchange_name, assets, True)
|
||||
assets[symbol] = asset
|
||||
|
||||
# TODO: update config.json
|
||||
writer = self.get_writer(
|
||||
start_dt=min_start_dt.replace(hour=00, minute=00),
|
||||
end_dt=max_end_dt.replace(hour=23, minute=59),
|
||||
|
||||
@@ -329,3 +329,17 @@ class NoCandlesReceivedFromExchange(ZiplineError):
|
||||
'Although requesting {bar_count} candles until {end_dt} of asset {asset}, '
|
||||
'an empty list of candles was received for {exchange}.'
|
||||
).strip()
|
||||
|
||||
|
||||
class MarketsNotFoundError(ZiplineError):
|
||||
msg = (
|
||||
'Exchange {exchange} contains no valid market so it is unusable in '
|
||||
'Catalyst.'
|
||||
).strip()
|
||||
|
||||
|
||||
class InvalidMarketError(ZiplineError):
|
||||
msg = (
|
||||
'Exchange {exchange} contains at least one incorrectly structured '
|
||||
'market: {market}, so it is unusable in Catalyst.'
|
||||
).strip()
|
||||
|
||||
@@ -5,6 +5,7 @@ from datetime import datetime
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from catalyst.constants import BUNDLE_URL
|
||||
from catalyst.data.bundles.core import download_without_progress
|
||||
from catalyst.exchange.utils.exchange_utils import get_exchange_bundles_folder
|
||||
import os
|
||||
@@ -48,10 +49,11 @@ def get_bcolz_chunk(exchange_name, symbol, data_frequency, period):
|
||||
path = os.path.join(root, name)
|
||||
|
||||
if not os.path.isdir(path):
|
||||
url = 'https://s3.amazonaws.com/enigmaco/catalyst-bundles/' \
|
||||
'exchange-{exchange}/{name}.tar.gz'.format(
|
||||
url = BUNDLE_URL.format(
|
||||
exchange=exchange_name,
|
||||
name=name)
|
||||
data_frequency=data_frequency,
|
||||
name=name,
|
||||
)
|
||||
|
||||
bytes = download_without_progress(url)
|
||||
with tarfile.open('r', fileobj=bytes) as tar:
|
||||
@@ -75,14 +77,14 @@ def get_df_from_arrays(arrays, periods):
|
||||
|
||||
"""
|
||||
ohlcv = dict()
|
||||
for index, field in enumerate(
|
||||
['open', 'high', 'low', 'close', 'volume']):
|
||||
for index, field in enumerate(['open', 'high', 'low', 'close', 'volume']):
|
||||
ohlcv[field] = arrays[index].flatten()
|
||||
|
||||
df = pd.DataFrame(
|
||||
data=ohlcv,
|
||||
index=periods
|
||||
)
|
||||
df.index.name = 'last_traded'
|
||||
return df
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
import json
|
||||
import os
|
||||
import pandas as pd
|
||||
from six.moves.urllib import request
|
||||
|
||||
from catalyst.assets._assets import TradingPair
|
||||
from ccxt import NetworkError
|
||||
from catalyst.constants import LOG_LEVEL, EXCHANGE_CONFIG_URL
|
||||
from catalyst.exchange.exchange_errors import MarketsNotFoundError, \
|
||||
InvalidMarketError
|
||||
from catalyst.exchange.utils.exchange_utils import get_catalyst_symbol, \
|
||||
get_exchange_folder, get_exchange_auth
|
||||
from catalyst.exchange.utils.serialization_utils import ExchangeJSONDecoder, \
|
||||
ExchangeJSONEncoder
|
||||
from logbook import Logger
|
||||
from redo import retry
|
||||
from ccxt.base.exchange import Exchange
|
||||
from catalyst.utils.paths import last_modified_time, data_root, \
|
||||
ensure_directory
|
||||
import ccxt
|
||||
|
||||
log = Logger('ccxt_utils', level=LOG_LEVEL)
|
||||
|
||||
|
||||
def scan_exchange_configs(features=None, history=None, is_authenticated=False,
|
||||
path=None):
|
||||
"""
|
||||
Finding exchanges from their config files
|
||||
|
||||
Parameters
|
||||
----------
|
||||
features
|
||||
is_authenticated
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
"""
|
||||
for exchange_name in ccxt.exchanges:
|
||||
config = get_exchange_config(exchange_name, path)
|
||||
if not config or 'error' in config:
|
||||
log.info(
|
||||
'skipping invalid exchange {}'.format(exchange_name)
|
||||
)
|
||||
|
||||
# Check if the exchange has an auth.json file
|
||||
if is_authenticated:
|
||||
exchange_auth = get_exchange_auth(exchange_name)
|
||||
has_auth = (exchange_auth['key'] != ''
|
||||
and exchange_auth['secret'] != '')
|
||||
|
||||
if not has_auth:
|
||||
continue
|
||||
|
||||
if features is None:
|
||||
has_features = True
|
||||
|
||||
else:
|
||||
try:
|
||||
supported_features = [
|
||||
feature for feature in features if
|
||||
feature in config['features']
|
||||
]
|
||||
has_features = len(supported_features) > 0
|
||||
except Exception:
|
||||
has_features = False
|
||||
|
||||
# TODO: filter by history
|
||||
if has_features:
|
||||
yield config
|
||||
|
||||
|
||||
def get_exchange_config(exchange_name, path=None, environ=None,
|
||||
expiry='1H'):
|
||||
"""
|
||||
The de-serialized content of the exchange's config.json.
|
||||
Parameters
|
||||
----------
|
||||
exchange_name: str
|
||||
The exchange name
|
||||
filename: str
|
||||
The target file
|
||||
environ:
|
||||
|
||||
Returns
|
||||
-------
|
||||
config: dict[srt, Object]
|
||||
The config dictionary.
|
||||
|
||||
"""
|
||||
try:
|
||||
if path is None:
|
||||
root = data_root(environ)
|
||||
path = os.path.join(root, 'exchanges')
|
||||
|
||||
folder = os.path.join(path, exchange_name)
|
||||
ensure_directory(folder)
|
||||
|
||||
filename = os.path.join(folder, 'config.json')
|
||||
url = EXCHANGE_CONFIG_URL.format(exchange=exchange_name)
|
||||
if os.path.isfile(filename):
|
||||
# If the file exists, only update periodically to avoid
|
||||
# unnecessary calls
|
||||
now = pd.Timestamp.utcnow()
|
||||
limit = pd.Timedelta(expiry)
|
||||
if pd.Timedelta(now - last_modified_time(filename)) > limit:
|
||||
try:
|
||||
request.urlretrieve(url=url, filename=filename)
|
||||
except Exception as e:
|
||||
log.warn(
|
||||
'unable to update config {} => {}: {}'.format(
|
||||
url, filename, e
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
request.urlretrieve(url=url, filename=filename)
|
||||
|
||||
with open(filename) as data_file:
|
||||
data = json.load(data_file, cls=ExchangeJSONDecoder)
|
||||
return data
|
||||
|
||||
except Exception as e:
|
||||
log.warn(
|
||||
'unable to download {} config: {}'.format(
|
||||
exchange_name, e
|
||||
)
|
||||
)
|
||||
return dict(error=e)
|
||||
|
||||
|
||||
def save_exchange_config(config, filename=None, environ=None):
|
||||
"""
|
||||
Save assets into an exchange_config file.
|
||||
Parameters
|
||||
----------
|
||||
exchange_name: str
|
||||
config
|
||||
environ
|
||||
Returns
|
||||
-------
|
||||
"""
|
||||
if filename is None:
|
||||
name = 'config.json'
|
||||
exchange_folder = get_exchange_folder(config['id'], environ)
|
||||
filename = os.path.join(exchange_folder, name)
|
||||
|
||||
with open(filename, 'w+') as handle:
|
||||
json.dump(config, handle, indent=4, cls=ExchangeJSONEncoder)
|
||||
|
||||
|
||||
def fetch_markets(ccxt_exchange):
|
||||
"""
|
||||
Fetches CCXT market objects.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ccxt_exchange: Exchange
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
"""
|
||||
markets_symbols = ccxt_exchange.load_markets()
|
||||
log.debug(
|
||||
'fetching {} markets:\n{}'.format(
|
||||
ccxt_exchange.name, markets_symbols
|
||||
)
|
||||
)
|
||||
markets = ccxt_exchange.fetch_markets()
|
||||
|
||||
if not markets:
|
||||
raise MarketsNotFoundError(
|
||||
exchange=ccxt_exchange.name,
|
||||
)
|
||||
|
||||
for market in markets:
|
||||
if 'id' not in market:
|
||||
raise InvalidMarketError(
|
||||
exchange=ccxt_exchange.name,
|
||||
market=market,
|
||||
)
|
||||
return markets
|
||||
|
||||
|
||||
def create_exchange_config(ccxt_exchange):
|
||||
"""
|
||||
Creates an exchange config structure.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ccxt_exchange: Exchange
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
"""
|
||||
exchange_name = ccxt_exchange.__class__.__name__
|
||||
config = dict(
|
||||
id=exchange_name,
|
||||
name=ccxt_exchange.name,
|
||||
features=[
|
||||
feature for feature in ccxt_exchange.has if
|
||||
ccxt_exchange.has[feature]
|
||||
]
|
||||
)
|
||||
markets = retry(
|
||||
action=fetch_markets,
|
||||
attempts=5,
|
||||
sleeptime=5,
|
||||
retry_exceptions=(NetworkError,),
|
||||
cleanup=lambda: log.warn(
|
||||
'fetching markets again for {}'.format(exchange_name)
|
||||
),
|
||||
args=(ccxt_exchange,)
|
||||
)
|
||||
|
||||
config['assets'] = []
|
||||
for market in markets:
|
||||
asset = create_trading_pair(exchange_name, market)
|
||||
config['assets'].append(asset)
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def create_trading_pair(exchange_name, market, start_dt=None, end_dt=None,
|
||||
leverage=1, end_daily=None, end_minute=None):
|
||||
"""
|
||||
Creating a TradingPair from market and asset data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
market: dict[str, Object]
|
||||
start_dt
|
||||
end_dt
|
||||
leverage
|
||||
end_daily
|
||||
end_minute
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
"""
|
||||
params = dict(
|
||||
exchange=exchange_name,
|
||||
data_source='catalyst',
|
||||
exchange_symbol=market['id'],
|
||||
symbol=get_catalyst_symbol(market),
|
||||
start_date=start_dt,
|
||||
end_date=end_dt,
|
||||
leverage=leverage,
|
||||
asset_name=market['symbol'],
|
||||
end_daily=end_daily,
|
||||
end_minute=end_minute,
|
||||
)
|
||||
apply_conditional_market_params(exchange_name, params, market)
|
||||
|
||||
return TradingPair(**params)
|
||||
|
||||
|
||||
def apply_conditional_market_params(exchange_name, params, market):
|
||||
"""
|
||||
Applies a CCXT market dict to parameters of TradingPair init.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
params: dict[Object]
|
||||
market: dict[Object]
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
"""
|
||||
# TODO: make this more externalized / configurable
|
||||
# Consider representing in some type of JSON structure
|
||||
if 'active' in market:
|
||||
params['trading_state'] = 1 if market['active'] else 0
|
||||
|
||||
else:
|
||||
params['trading_state'] = 1
|
||||
|
||||
if 'lot' in market:
|
||||
params['min_trade_size'] = market['lot']
|
||||
params['lot'] = market['lot']
|
||||
|
||||
if exchange_name == 'bitfinex':
|
||||
params['maker'] = 0.001
|
||||
params['taker'] = 0.002
|
||||
|
||||
elif 'maker' in market and 'taker' in market \
|
||||
and market['maker'] is not None \
|
||||
and market['taker'] is not None:
|
||||
params['maker'] = market['maker']
|
||||
params['taker'] = market['taker']
|
||||
|
||||
else:
|
||||
# TODO: default commission, make configurable
|
||||
params['maker'] = 0.0015
|
||||
params['taker'] = 0.0025
|
||||
|
||||
info = market['info'] if 'info' in market else None
|
||||
if info:
|
||||
if 'minimum_order_size' in info:
|
||||
params['min_trade_size'] = float(info['minimum_order_size'])
|
||||
|
||||
if 'lot' not in params:
|
||||
params['lot'] = params['min_trade_size']
|
||||
@@ -164,6 +164,12 @@ def get_start_dt(end_dt, bar_count, data_frequency, include_first=True):
|
||||
return start_dt
|
||||
|
||||
|
||||
def timestr_to_dt(timestr, data_frequency):
|
||||
dt_format = '%Y' if data_frequency == 'daily' else '%Y%m'
|
||||
dt = pd.to_datetime(timestr, format=dt_format, utc=True)
|
||||
return dt
|
||||
|
||||
|
||||
def get_period_label(dt, data_frequency):
|
||||
"""
|
||||
The period label for the specified date and frequency.
|
||||
@@ -177,6 +183,26 @@ def get_period_label(dt, data_frequency):
|
||||
-------
|
||||
str
|
||||
|
||||
"""
|
||||
if data_frequency == 'minute':
|
||||
return '{}{:02d}'.format(dt.year, dt.month)
|
||||
else:
|
||||
return '{}'.format(dt.year)
|
||||
|
||||
|
||||
def get_period(dt, data_frequency):
|
||||
"""
|
||||
The period label for the specified date and frequency.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dt: datetime
|
||||
data_frequency: str
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
|
||||
"""
|
||||
if data_frequency == 'minute':
|
||||
return '{}-{:02d}'.format(dt.year, dt.month)
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import pickle
|
||||
import shutil
|
||||
from datetime import date, datetime
|
||||
|
||||
import json
|
||||
import pandas as pd
|
||||
import pickle
|
||||
from catalyst.assets._assets import TradingPair
|
||||
from datetime import date, datetime
|
||||
from six import string_types
|
||||
from six.moves.urllib import request
|
||||
|
||||
from catalyst.constants import DATE_FORMAT, SYMBOLS_URL
|
||||
from catalyst.exchange.exchange_errors import ExchangeSymbolsNotFound
|
||||
from catalyst.constants import EXCHANGE_CONFIG_URL
|
||||
from catalyst.exchange.utils.serialization_utils import ExchangeJSONEncoder, \
|
||||
ExchangeJSONDecoder
|
||||
ExchangeJSONDecoder, ConfigJSONEncoder
|
||||
from catalyst.utils.deprecate import deprecated
|
||||
from catalyst.utils.paths import data_root, ensure_directory, \
|
||||
last_modified_time
|
||||
|
||||
@@ -69,7 +69,7 @@ def is_blacklist(exchange_name, environ=None):
|
||||
return os.path.exists(filename)
|
||||
|
||||
|
||||
def get_exchange_symbols_filename(exchange_name, is_local=False, environ=None):
|
||||
def get_exchange_config_filename(exchange_name, environ=None):
|
||||
"""
|
||||
The absolute path of the exchange's symbol.json file.
|
||||
|
||||
@@ -83,12 +83,12 @@ def get_exchange_symbols_filename(exchange_name, is_local=False, environ=None):
|
||||
str
|
||||
|
||||
"""
|
||||
name = 'symbols.json' if not is_local else 'symbols_local.json'
|
||||
name = 'config.json'
|
||||
exchange_folder = get_exchange_folder(exchange_name, environ)
|
||||
return os.path.join(exchange_folder, name)
|
||||
|
||||
|
||||
def download_exchange_symbols(exchange_name, environ=None):
|
||||
def download_exchange_config(exchange_name, filename, environ=None):
|
||||
"""
|
||||
Downloads the exchange's symbols.json from the repository.
|
||||
|
||||
@@ -102,15 +102,14 @@ def download_exchange_symbols(exchange_name, environ=None):
|
||||
str
|
||||
|
||||
"""
|
||||
filename = get_exchange_symbols_filename(exchange_name)
|
||||
url = SYMBOLS_URL.format(exchange=exchange_name)
|
||||
response = request.urlretrieve(url=url, filename=filename)
|
||||
return response
|
||||
url = EXCHANGE_CONFIG_URL.format(exchange=exchange_name)
|
||||
request.urlretrieve(url=url, filename=filename)
|
||||
|
||||
|
||||
def get_exchange_symbols(exchange_name, is_local=False, environ=None):
|
||||
@deprecated
|
||||
def get_exchange_config(exchange_name, filename=None, environ=None):
|
||||
"""
|
||||
The de-serialized content of the exchange's symbols.json.
|
||||
The de-serialized content of the exchange's config.json.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@@ -123,55 +122,48 @@ def get_exchange_symbols(exchange_name, is_local=False, environ=None):
|
||||
Object
|
||||
|
||||
"""
|
||||
filename = get_exchange_symbols_filename(exchange_name, is_local)
|
||||
|
||||
if not is_local and (not os.path.isfile(filename) or pd.Timedelta(
|
||||
pd.Timestamp('now', tz='UTC') - last_modified_time(
|
||||
filename)).days > 1):
|
||||
try:
|
||||
download_exchange_symbols(exchange_name, environ)
|
||||
except Exception:
|
||||
pass
|
||||
if filename is None:
|
||||
filename = get_exchange_config_filename(exchange_name)
|
||||
|
||||
if os.path.isfile(filename):
|
||||
with open(filename) as data_file:
|
||||
try:
|
||||
data = json.load(data_file, cls=ExchangeJSONDecoder)
|
||||
return data
|
||||
now = pd.Timestamp.utcnow()
|
||||
limit = pd.Timedelta('2H')
|
||||
if pd.Timedelta(now - last_modified_time(filename)) > limit:
|
||||
download_exchange_config(exchange_name, filename, environ)
|
||||
|
||||
except ValueError:
|
||||
return dict()
|
||||
else:
|
||||
raise ExchangeSymbolsNotFound(
|
||||
exchange=exchange_name,
|
||||
filename=filename
|
||||
)
|
||||
download_exchange_config(exchange_name, filename, environ)
|
||||
|
||||
with open(filename) as data_file:
|
||||
try:
|
||||
data = json.load(data_file, cls=ExchangeJSONDecoder)
|
||||
return data
|
||||
|
||||
except ValueError:
|
||||
return dict()
|
||||
|
||||
|
||||
def save_exchange_symbols(exchange_name, assets, is_local=False, environ=None):
|
||||
def save_exchange_config(exchange_name, config, filename=None, environ=None):
|
||||
"""
|
||||
Save assets into an exchange_symbols file.
|
||||
Save assets into an exchange_config file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
exchange_name: str
|
||||
assets: list[dict[str, object]]
|
||||
is_local: bool
|
||||
config
|
||||
environ
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
"""
|
||||
asset_dicts = dict()
|
||||
for symbol in assets:
|
||||
asset_dicts[symbol] = assets[symbol].to_dict()
|
||||
if filename is None:
|
||||
name = 'config.json'
|
||||
exchange_folder = get_exchange_folder(exchange_name, environ)
|
||||
filename = os.path.join(exchange_folder, name)
|
||||
|
||||
filename = get_exchange_symbols_filename(
|
||||
exchange_name, is_local, environ
|
||||
)
|
||||
with open(filename, 'wt') as handle:
|
||||
json.dump(asset_dicts, handle, indent=4, default=symbols_serial)
|
||||
with open(filename, 'w+') as handle:
|
||||
json.dump(config, handle, indent=4, cls=ConfigJSONEncoder)
|
||||
|
||||
|
||||
def get_symbols_string(assets):
|
||||
@@ -512,25 +504,6 @@ def has_bundle(exchange_name, data_frequency, environ=None):
|
||||
return os.path.isdir(folder)
|
||||
|
||||
|
||||
def symbols_serial(obj):
|
||||
"""
|
||||
JSON serializer for objects not serializable by default json code
|
||||
|
||||
Parameters
|
||||
----------
|
||||
obj: Object
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
|
||||
"""
|
||||
if isinstance(obj, (datetime, date)):
|
||||
return obj.floor('1D').strftime(DATE_FORMAT)
|
||||
|
||||
raise TypeError("Type %s not serializable" % type(obj))
|
||||
|
||||
|
||||
def perf_serial(obj):
|
||||
"""
|
||||
JSON serializer for objects not serializable by default json code
|
||||
@@ -620,46 +593,12 @@ def resample_history_df(df, freq, field, start_dt=None):
|
||||
return resampled_df
|
||||
|
||||
|
||||
def mixin_market_params(exchange_name, params, market):
|
||||
"""
|
||||
Applies a CCXT market dict to parameters of TradingPair init.
|
||||
def from_ms_timestamp(ms):
|
||||
return pd.to_datetime(ms, unit='ms', utc=True)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
params: dict[Object]
|
||||
market: dict[Object]
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
"""
|
||||
# TODO: make this more externalized / configurable
|
||||
if 'lot' in market:
|
||||
params['min_trade_size'] = market['lot']
|
||||
params['lot'] = market['lot']
|
||||
|
||||
if exchange_name == 'bitfinex':
|
||||
params['maker'] = 0.001
|
||||
params['taker'] = 0.002
|
||||
|
||||
elif 'maker' in market and 'taker' in market and \
|
||||
market['maker'] is not None and market['taker'] is not None:
|
||||
|
||||
params['maker'] = market['maker']
|
||||
params['taker'] = market['taker']
|
||||
|
||||
else:
|
||||
# TODO: default commission, make configurable
|
||||
params['maker'] = 0.0015
|
||||
params['taker'] = 0.0025
|
||||
|
||||
info = market['info'] if 'info' in market else None
|
||||
if info:
|
||||
if 'minimum_order_size' in info:
|
||||
params['min_trade_size'] = float(info['minimum_order_size'])
|
||||
|
||||
if 'lot' not in params:
|
||||
params['lot'] = params['min_trade_size']
|
||||
def get_epoch():
|
||||
return pd.to_datetime('1970-1-1', utc=True)
|
||||
|
||||
|
||||
def group_assets_by_exchange(assets):
|
||||
@@ -748,6 +687,37 @@ def get_candles_df(candles, field, freq, bar_count, end_dt):
|
||||
all_series[asset] = pd.Series(asset_df[field])
|
||||
|
||||
df = pd.DataFrame(all_series)
|
||||
|
||||
df.dropna(inplace=True)
|
||||
|
||||
return df
|
||||
|
||||
|
||||
def get_trades_df(trades):
|
||||
df = pd.DataFrame(trades)
|
||||
df.index = pd.to_datetime(df.pop('datetime'))
|
||||
df.index = df.index.tz_localize('UTC')
|
||||
|
||||
return df
|
||||
|
||||
|
||||
def candles_from_trades(trades_df, freq):
|
||||
"""
|
||||
Calculate OHLCV from candles.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
trades_df
|
||||
freq
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
||||
"""
|
||||
df = trades_df['price'].resample(freq).ohlc() # type: pd.DataFrame
|
||||
df['volume'] = trades_df['amount'].resample(freq).sum()
|
||||
|
||||
df.dropna(axis=0, how='all', inplace=True)
|
||||
df.sort_index(inplace=True, ascending=False)
|
||||
|
||||
return df
|
||||
|
||||
@@ -4,8 +4,9 @@ from catalyst.constants import LOG_LEVEL
|
||||
from catalyst.exchange.ccxt.ccxt_exchange import CCXT
|
||||
from catalyst.exchange.exchange import Exchange
|
||||
from catalyst.exchange.exchange_errors import ExchangeAuthEmpty
|
||||
from catalyst.exchange.utils.ccxt_utils import scan_exchange_configs
|
||||
from catalyst.exchange.utils.exchange_utils import get_exchange_auth, \
|
||||
get_exchange_folder, is_blacklist
|
||||
get_exchange_folder
|
||||
from logbook import Logger
|
||||
|
||||
log = Logger('factory', level=LOG_LEVEL)
|
||||
@@ -13,9 +14,12 @@ exchange_cache = dict()
|
||||
|
||||
|
||||
def get_exchange(exchange_name, base_currency=None, must_authenticate=False,
|
||||
skip_init=False, auth_alias=None):
|
||||
skip_init=False, auth_alias=None, config=None):
|
||||
key = (exchange_name, base_currency)
|
||||
if key in exchange_cache:
|
||||
if not skip_init:
|
||||
exchange_cache[key].init()
|
||||
|
||||
return exchange_cache[key]
|
||||
|
||||
exchange_auth = get_exchange_auth(exchange_name, alias=auth_alias)
|
||||
@@ -36,6 +40,7 @@ def get_exchange(exchange_name, base_currency=None, must_authenticate=False,
|
||||
password=exchange_auth['password'] if 'password'
|
||||
in exchange_auth.keys() else '',
|
||||
base_currency=base_currency,
|
||||
config=config,
|
||||
)
|
||||
exchange_cache[key] = exchange
|
||||
|
||||
@@ -53,8 +58,8 @@ def get_exchanges(exchange_names):
|
||||
return exchanges
|
||||
|
||||
|
||||
def find_exchanges(features=None, skip_blacklist=True, is_authenticated=False,
|
||||
base_currency=None):
|
||||
def find_exchanges(features=None, history=None, skip_blacklist=True, path=None,
|
||||
is_authenticated=False, base_currency=None):
|
||||
"""
|
||||
Find exchanges filtered by a list of feature.
|
||||
|
||||
@@ -72,28 +77,33 @@ def find_exchanges(features=None, skip_blacklist=True, is_authenticated=False,
|
||||
list[Exchange]
|
||||
|
||||
"""
|
||||
exchange_names = CCXT.find_exchanges(features, is_authenticated)
|
||||
|
||||
exchanges = []
|
||||
for exchange_name in exchange_names:
|
||||
if skip_blacklist and is_blacklist(exchange_name):
|
||||
return list(
|
||||
scan_exchanges(
|
||||
features,
|
||||
history,
|
||||
skip_blacklist,
|
||||
path,
|
||||
is_authenticated,
|
||||
base_currency
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def scan_exchanges(features=None, history=None, skip_blacklist=True, path=None,
|
||||
is_authenticated=False, base_currency=None):
|
||||
for config in scan_exchange_configs(
|
||||
features=features,
|
||||
history=history,
|
||||
is_authenticated=is_authenticated,
|
||||
path=path,
|
||||
):
|
||||
if skip_blacklist and (config is None or 'error' in config):
|
||||
continue
|
||||
|
||||
exchange = get_exchange(
|
||||
exchange_name=exchange_name,
|
||||
yield get_exchange(
|
||||
exchange_name=config['id'],
|
||||
skip_init=True,
|
||||
base_currency=base_currency,
|
||||
config=config,
|
||||
)
|
||||
|
||||
if features is not None:
|
||||
if 'dailyBundle' in features \
|
||||
and not exchange.has_bundle('daily'):
|
||||
continue
|
||||
|
||||
elif 'minuteBundle' in features \
|
||||
and not exchange.has_bundle('minute'):
|
||||
continue
|
||||
|
||||
exchanges.append(exchange)
|
||||
|
||||
return exchanges
|
||||
|
||||
@@ -3,15 +3,48 @@ import re
|
||||
from json import JSONEncoder
|
||||
|
||||
import pandas as pd
|
||||
from catalyst.constants import DATE_TIME_FORMAT
|
||||
from six import string_types
|
||||
|
||||
from datetime import date, datetime
|
||||
from catalyst.constants import DATE_TIME_FORMAT, DATE_FORMAT
|
||||
from catalyst.assets._assets import TradingPair
|
||||
|
||||
|
||||
class ConfigJSONEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
"""
|
||||
JSON serializer for objects not serializable by default json code
|
||||
|
||||
Parameters
|
||||
----------
|
||||
obj: Object
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
|
||||
"""
|
||||
if isinstance(obj, (datetime, date)):
|
||||
return obj.floor('1D').strftime(DATE_FORMAT)
|
||||
|
||||
elif isinstance(obj, TradingPair):
|
||||
return obj.to_dict()
|
||||
|
||||
|
||||
class ExchangeJSONEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, pd.Timestamp):
|
||||
return obj.strftime(DATE_TIME_FORMAT)
|
||||
|
||||
elif isinstance(obj, TradingPair):
|
||||
asset = obj.to_dict()
|
||||
asset['maker'] = round(asset['maker'], asset['decimals'])
|
||||
asset['taker'] = round(asset['taker'], asset['decimals'])
|
||||
asset['lot'] = round(asset['lot'], 4)
|
||||
asset['min_trade_size'] = round(asset['min_trade_size'], 4)
|
||||
asset['max_trade_size'] = round(asset['max_trade_size'], 4)
|
||||
return asset
|
||||
|
||||
# Let the base class default method raise the TypeError
|
||||
return JSONEncoder.default(self, obj)
|
||||
|
||||
|
||||
@@ -88,5 +88,7 @@ def safely_reduce_dtype(ser): # pandas.Series or numpy.array
|
||||
new_itemsize = np.min_scalar_type(val).itemsize
|
||||
if mx < new_itemsize:
|
||||
mx = new_itemsize
|
||||
if orig_dtype == 'int':
|
||||
mx = max(mx, 4)
|
||||
new_dtype = orig_dtype + str(mx * 8)
|
||||
return ser.astype(new_dtype)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from catalyst.api import symbol
|
||||
from catalyst.utils.run_algo import run_algorithm
|
||||
|
||||
coins = ['dash', 'btc', 'dash', 'etc', 'eth', 'ltc', 'nxt', 'rep', 'str', 'xmr', 'xrp', 'zec']
|
||||
coins = ['dash', 'btc', 'dash', 'etc', 'eth', 'ltc', 'nxt', 'rep', 'str',
|
||||
'xmr', 'xrp', 'zec']
|
||||
symbols = None
|
||||
|
||||
|
||||
@@ -13,20 +14,21 @@ def _handle_data(context, data):
|
||||
global symbols
|
||||
if symbols is None: symbols = [symbol(c + '_usdt') for c in coins]
|
||||
|
||||
print'getting history for: %s' % [s.symbol for s in symbols]
|
||||
print('getting history for: %s' % [s.symbol for s in symbols])
|
||||
history = data.history(symbols,
|
||||
['close', 'volume'],
|
||||
bar_count=1, # EXCEPTION, Change to 2
|
||||
frequency='5T')
|
||||
#print 'history: %s' % history.shape
|
||||
['close', 'volume'],
|
||||
bar_count=1, # EXCEPTION, Change to 2
|
||||
frequency='5T')
|
||||
# print 'history: %s' % history.shape
|
||||
|
||||
|
||||
run_algorithm(initialize=initialize,
|
||||
handle_data=_handle_data,
|
||||
analyze=lambda _, results: True,
|
||||
exchange_name='poloniex',
|
||||
base_currency='usdt',
|
||||
algo_namespace='issue-236',
|
||||
live=True,
|
||||
data_frequency='minute',
|
||||
capital_base=3000,
|
||||
simulate_orders=True)
|
||||
analyze=lambda _, results: True,
|
||||
exchange_name='poloniex',
|
||||
base_currency='usdt',
|
||||
algo_namespace='issue-236',
|
||||
live=True,
|
||||
data_frequency='minute',
|
||||
capital_base=3000,
|
||||
simulate_orders=True)
|
||||
|
||||
@@ -22,7 +22,7 @@ dependencies:
|
||||
- bcolz==0.12.1
|
||||
- bottleneck==1.2.1
|
||||
- chardet==3.0.4
|
||||
- ccxt==1.10.1094
|
||||
- ccxt==1.11.22
|
||||
# The Enigma Data Marketplace requires Python3 because it depends on
|
||||
# web3, which requires Python3, as building its dependencies breaks in Python2
|
||||
# - web3==4.0.0b7
|
||||
|
||||
@@ -31,7 +31,7 @@ dependencies:
|
||||
- botocore==1.8.41
|
||||
- bottleneck==1.2.1
|
||||
- cchardet==2.1.1
|
||||
- ccxt==1.10.1102
|
||||
- ccxt==1.11.22
|
||||
- chardet==3.0.4
|
||||
- click==6.7
|
||||
- contextlib2==0.5.5
|
||||
|
||||
@@ -81,7 +81,7 @@ empyrical==0.2.1
|
||||
tables==3.3.0
|
||||
|
||||
#Catalyst dependencies
|
||||
ccxt==1.10.1094
|
||||
ccxt==1.11.22
|
||||
boto3==1.4.8
|
||||
redo==1.6
|
||||
web3==4.0.0b11; python_version > '3.4'
|
||||
|
||||
@@ -14,7 +14,8 @@ from catalyst.exchange.utils.bundle_utils import get_bcolz_chunk, \
|
||||
from catalyst.exchange.utils.datetime_utils import get_start_dt
|
||||
from catalyst.exchange.utils.exchange_utils import get_exchange_folder
|
||||
from catalyst.exchange.utils.factory import get_exchange
|
||||
from catalyst.exchange.utils.stats_utils import df_to_string
|
||||
from catalyst.exchange.utils.stats_utils import df_to_string, \
|
||||
set_print_settings
|
||||
from catalyst.utils.paths import ensure_directory
|
||||
|
||||
log = getLogger('test_exchange_bundle')
|
||||
@@ -45,9 +46,9 @@ class TestExchangeBundle:
|
||||
exchange_name = 'binance'
|
||||
|
||||
exchange = get_exchange(exchange_name)
|
||||
exchange_bundle = ExchangeBundle(exchange)
|
||||
exchange_bundle = ExchangeBundle(exchange_name)
|
||||
assets = [
|
||||
exchange.get_asset('eth_btc')
|
||||
exchange.get_asset('bch_eth')
|
||||
]
|
||||
|
||||
start = pd.to_datetime('2018-03-01', utc=True)
|
||||
@@ -61,7 +62,8 @@ class TestExchangeBundle:
|
||||
exclude_symbols=None,
|
||||
start=start,
|
||||
end=end,
|
||||
show_progress=True
|
||||
show_progress=False,
|
||||
show_breakdown=False
|
||||
)
|
||||
|
||||
reader = exchange_bundle.get_reader(data_frequency)
|
||||
@@ -72,9 +74,15 @@ class TestExchangeBundle:
|
||||
start_dt=start,
|
||||
end_dt=end
|
||||
)
|
||||
print('found {} rows for {} ingestion\n{}'.format(
|
||||
len(arrays[0]), asset.symbol, arrays[0])
|
||||
periods = exchange_bundle.get_calendar_periods_range(
|
||||
start, end, data_frequency
|
||||
)
|
||||
|
||||
dx = get_df_from_arrays(arrays[0], periods)
|
||||
set_print_settings()
|
||||
print('found {} rows for last ingestion:\n{}\n{}'.format(
|
||||
len(dx), dx.head(10), dx.tail(10)
|
||||
))
|
||||
pass
|
||||
|
||||
def test_ingest_minute_all(self):
|
||||
@@ -222,9 +230,14 @@ class TestExchangeBundle:
|
||||
start_dt=start,
|
||||
end_dt=end
|
||||
)
|
||||
print('found {} rows for {} ingestion\n{}'.format(
|
||||
len(arrays[0]), asset.symbol, arrays[0])
|
||||
periods = exchange_bundle.get_calendar_periods_range(
|
||||
start, end, data_frequency
|
||||
)
|
||||
|
||||
dx = get_df_from_arrays(arrays, periods)
|
||||
print('found {} rows for last ingestion'.format(
|
||||
len(dx)
|
||||
))
|
||||
pass
|
||||
|
||||
def test_daily_data_to_minute_table(self):
|
||||
@@ -290,17 +303,22 @@ class TestExchangeBundle:
|
||||
for asset in assets:
|
||||
sid = asset.sid
|
||||
|
||||
daily_values = reader.load_raw_arrays(
|
||||
arrays = reader.load_raw_arrays(
|
||||
fields=['open', 'high', 'low', 'close', 'volume'],
|
||||
start_dt=start,
|
||||
end_dt=end,
|
||||
sids=[sid],
|
||||
)
|
||||
|
||||
print('found {} rows for last ingestion'.format(
|
||||
len(daily_values[0]))
|
||||
periods = exchange_bundle.get_calendar_periods_range(
|
||||
start, end, data_frequency
|
||||
)
|
||||
pass
|
||||
|
||||
dx = get_df_from_arrays(arrays, periods)
|
||||
print('found {} rows for last ingestion'.format(
|
||||
len(dx)
|
||||
))
|
||||
pass
|
||||
|
||||
def test_minute_bundle(self):
|
||||
# exchange_name = 'poloniex'
|
||||
|
||||
@@ -5,7 +5,8 @@ from catalyst.exchange.utils.stats_utils import set_print_settings
|
||||
from .base import BaseExchangeTestCase
|
||||
from catalyst.exchange.ccxt.ccxt_exchange import CCXT
|
||||
from catalyst.exchange.exchange_execution import ExchangeLimitOrder
|
||||
from catalyst.exchange.utils.exchange_utils import get_exchange_auth
|
||||
from catalyst.exchange.utils.exchange_utils import get_exchange_auth, \
|
||||
get_trades_df, candles_from_trades
|
||||
from catalyst.finance.order import Order
|
||||
|
||||
log = Logger('test_ccxt')
|
||||
@@ -14,12 +15,13 @@ log = Logger('test_ccxt')
|
||||
class TestCCXT(BaseExchangeTestCase):
|
||||
@classmethod
|
||||
def setup(self):
|
||||
exchange_name = 'bittrex'
|
||||
exchange_name = 'binance'
|
||||
auth = get_exchange_auth(exchange_name)
|
||||
self.exchange = CCXT(
|
||||
exchange_name=exchange_name,
|
||||
key=auth['key'],
|
||||
secret=auth['secret'],
|
||||
password=None,
|
||||
base_currency='usdt',
|
||||
)
|
||||
self.exchange.init()
|
||||
@@ -58,9 +60,9 @@ class TestCCXT(BaseExchangeTestCase):
|
||||
log.info('retrieving candles')
|
||||
candles = self.exchange.get_candles(
|
||||
freq='1T',
|
||||
assets=[self.exchange.get_asset('eth_btc')],
|
||||
assets=[self.exchange.get_asset('eng_eth')],
|
||||
bar_count=200,
|
||||
# start_dt=pd.to_datetime('2017-09-01', utc=True),
|
||||
start_dt=pd.to_datetime('2017-09-01', utc=True),
|
||||
)
|
||||
|
||||
for asset in candles:
|
||||
@@ -90,6 +92,37 @@ class TestCCXT(BaseExchangeTestCase):
|
||||
assert trades
|
||||
pass
|
||||
|
||||
def test_validate_volume(self):
|
||||
asset = self.exchange.get_asset('eng_eth')
|
||||
candles = self.exchange.get_candles(
|
||||
freq='1T',
|
||||
assets=[asset],
|
||||
bar_count=10,
|
||||
)
|
||||
df = pd.DataFrame(candles[asset])
|
||||
df.set_index('last_traded', drop=True, inplace=True)
|
||||
|
||||
df.drop_duplicates()
|
||||
df.sort_index(inplace=True, ascending=False)
|
||||
assert candles
|
||||
|
||||
start_dt = df.index[-1]
|
||||
trades = self.exchange.get_trades(
|
||||
asset, start_dt=start_dt, my_trades=False
|
||||
)
|
||||
assert trades
|
||||
|
||||
trades_df = get_trades_df(trades)
|
||||
df2 = candles_from_trades(trades_df, '1T')
|
||||
|
||||
set_print_settings()
|
||||
log.info(
|
||||
'comparing candles / resampled trades:\n{}\n{}'.format(
|
||||
df, df2
|
||||
)
|
||||
)
|
||||
pass
|
||||
|
||||
def test_get_executed_order(self):
|
||||
log.info('retrieving executed order')
|
||||
asset = self.exchange.get_asset('eng_eth')
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
from catalyst.exchange.utils.factory import get_exchange
|
||||
|
||||
|
||||
class TestConfig:
|
||||
def test_create_config(self):
|
||||
exchange = get_exchange('binance', skip_init=True)
|
||||
config = exchange.create_exchange_config()
|
||||
pass
|
||||
@@ -9,7 +9,7 @@ from catalyst.exchange.exchange_data_portal import (
|
||||
)
|
||||
from catalyst.exchange.utils.exchange_utils import get_common_assets
|
||||
from catalyst.exchange.utils.factory import get_exchanges
|
||||
from test_utils import rnd_history_date_days, rnd_bar_count
|
||||
from .test_utils import rnd_history_date_days, rnd_bar_count
|
||||
|
||||
log = Logger('test_bitfinex')
|
||||
|
||||
|
||||
@@ -197,6 +197,7 @@ class TestSuiteBundle:
|
||||
# population=exchange_population,
|
||||
# features=[bundle],
|
||||
# ) # Type: list[Exchange]
|
||||
# TODO: currently focusing on Binance, try other exchanges
|
||||
exchanges = [get_exchange('poloniex', skip_init=True)]
|
||||
|
||||
data_portal = TestSuiteBundle.get_data_portal(exchanges)
|
||||
@@ -204,17 +205,20 @@ class TestSuiteBundle:
|
||||
exchange.init()
|
||||
|
||||
frequencies = exchange.get_candle_frequencies(data_frequency)
|
||||
freq = random.sample(frequencies, 1)[0]
|
||||
# freq = random.sample(frequencies, 1)[0]
|
||||
freq = '5T'
|
||||
rnd = random.SystemRandom()
|
||||
# field = rnd.choice(['open', 'high', 'low', 'close', 'volume'])
|
||||
field = rnd.choice(['volume'])
|
||||
field = rnd.choice(['close'])
|
||||
|
||||
bar_count = random.randint(3, 6)
|
||||
# bar_count = random.randint(3, 6)
|
||||
bar_count = 5
|
||||
|
||||
assets = select_random_assets(
|
||||
exchange.assets, asset_population
|
||||
)
|
||||
end_dt = None
|
||||
# assets = select_random_assets(
|
||||
# exchange.assets, asset_population
|
||||
# )
|
||||
assets = [exchange.get_asset('bch_eth')]
|
||||
end_dt = pd.to_datetime('2018-03-01', utc=True)
|
||||
for asset in assets:
|
||||
attribute = 'end_{}'.format(data_frequency)
|
||||
asset_end_dt = getattr(asset, attribute)
|
||||
|
||||
@@ -5,63 +5,28 @@ from logging import Logger, WARNING
|
||||
from time import sleep
|
||||
|
||||
import pandas as pd
|
||||
from catalyst.assets._assets import TradingPair
|
||||
from logbook import TestHandler
|
||||
|
||||
from catalyst.exchange.exchange_errors import ExchangeRequestError
|
||||
from catalyst.assets._assets import TradingPair
|
||||
from catalyst.exchange.exchange_execution import ExchangeLimitOrder
|
||||
from catalyst.exchange.utils.exchange_utils import get_exchange_folder
|
||||
from catalyst.exchange.utils.factory import get_exchanges, get_exchange
|
||||
from catalyst.exchange.utils.test_utils import select_random_exchanges, \
|
||||
handle_exchange_error, select_random_assets
|
||||
select_random_assets
|
||||
from catalyst.testing import ZiplineTestCase
|
||||
from catalyst.testing.fixtures import WithLogger
|
||||
from catalyst.exchange.utils.factory import get_exchanges, get_exchange
|
||||
|
||||
log = Logger('TestSuiteExchange')
|
||||
|
||||
|
||||
class TestSuiteExchange(WithLogger, ZiplineTestCase):
|
||||
def _test_markets_exchange(self, exchange, attempts=0):
|
||||
assets = None
|
||||
try:
|
||||
exchange.init()
|
||||
|
||||
# Verify that the assets and markets are populated
|
||||
if not exchange.markets:
|
||||
raise ValueError(
|
||||
'no markets found'
|
||||
)
|
||||
if not exchange.assets:
|
||||
raise ValueError(
|
||||
'no assets derived from markets'
|
||||
)
|
||||
assets = exchange.assets
|
||||
|
||||
except ExchangeRequestError as e:
|
||||
sleep(5)
|
||||
|
||||
if attempts > 5:
|
||||
handle_exchange_error(exchange, e)
|
||||
|
||||
else:
|
||||
print(
|
||||
're-trying an exchange request {} {}'.format(
|
||||
exchange.name, attempts
|
||||
)
|
||||
)
|
||||
self._test_markets_exchange(exchange, attempts + 1)
|
||||
|
||||
except Exception as e:
|
||||
handle_exchange_error(exchange, e)
|
||||
|
||||
return assets
|
||||
|
||||
def test_markets(self):
|
||||
population = 3
|
||||
results = dict()
|
||||
|
||||
exchanges = select_random_exchanges(population) # Type: list[Exchange]
|
||||
for exchange in exchanges:
|
||||
exchange.init()
|
||||
assets = self._test_markets_exchange(exchange)
|
||||
|
||||
if assets is not None:
|
||||
|
||||
Reference in New Issue
Block a user