diff --git a/catalyst/exchange/bitfinex/__init__.py b/catalyst/exchange/bitfinex/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/catalyst/exchange/bitfinex/bitfinex.py b/catalyst/exchange/bitfinex/bitfinex.py deleted file mode 100644 index 91192493..00000000 --- a/catalyst/exchange/bitfinex/bitfinex.py +++ /dev/null @@ -1,709 +0,0 @@ -import base64 -import datetime -import hashlib -import hmac -import json -import re -import time - -import numpy as np -import pandas as pd -import pytz -import requests -import six -from catalyst.assets._assets import TradingPair -from logbook import Logger - -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 ( - ExchangeRequestError, - InvalidHistoryFrequencyError, - InvalidOrderStyle, OrderCancelError) -from catalyst.exchange.exchange_execution import ExchangeLimitOrder, \ - ExchangeStopLimitOrder, ExchangeStopOrder -from catalyst.exchange.utils.exchange_utils import \ - get_exchange_symbols_filename, \ - download_exchange_symbols, get_symbols_string -from catalyst.finance.order import Order, ORDER_STATUS -from catalyst.protocol import Account -# Trying to account for REST api instability -# https://stackoverflow.com/questions/15431044/can-i-set-max-retries-for-requests-request -from catalyst.utils.deprecate import deprecated - -requests.adapters.DEFAULT_RETRIES = 20 - -BITFINEX_URL = 'https://api.bitfinex.com' - -log = Logger('Bitfinex', level=LOG_LEVEL) -warning_logger = Logger('AlgoWarning') - - -@deprecated -class Bitfinex(Exchange): - def __init__(self, key, secret, base_currency, portfolio=None): - self.url = BITFINEX_URL - self.key = key - self.secret = secret.encode('UTF-8') - self.name = 'bitfinex' - self.color = 'green' - - self.assets = dict() - self.load_assets() - - self.local_assets = dict() - self.load_assets(is_local=True) - - self.base_currency = base_currency - self._portfolio = portfolio - self.minute_writer = None - self.minute_reader = None - - # The candle limit for each request - self.num_candles_limit = 1000 - - # Max is 90 but playing it safe - # https://www.bitfinex.com/posts/188 - self.max_requests_per_minute = 80 - self.request_cpt = dict() - - self.bundle = ExchangeBundle(self.name) - - def _request(self, operation, data, version='v1'): - payload_object = { - 'request': '/{}/{}'.format(version, operation), - 'nonce': '{0:f}'.format(time.time() * 1000000), - # convert to string - 'options': {} - } - - if data is None: - payload_dict = payload_object - else: - payload_dict = payload_object.copy() - payload_dict.update(data) - - payload_json = json.dumps(payload_dict) - if six.PY3: - payload = base64.b64encode(bytes(payload_json, 'utf-8')) - else: - payload = base64.b64encode(payload_json) - - m = hmac.new(self.secret, payload, hashlib.sha384) - m = m.hexdigest() - - # headers - headers = { - 'X-BFX-APIKEY': self.key, - 'X-BFX-PAYLOAD': payload, - 'X-BFX-SIGNATURE': m - } - - if data is None: - request = requests.get( - '{url}/{version}/{operation}'.format( - url=self.url, - version=version, - operation=operation - ), data={}, - headers=headers) - else: - request = requests.post( - '{url}/{version}/{operation}'.format( - url=self.url, - version=version, - operation=operation - ), - headers=headers) - - return request - - def _get_v2_symbol(self, asset): - pair = asset.symbol.split('_') - symbol = 't' + pair[0].upper() + pair[1].upper() - return symbol - - def _get_v2_symbols(self, assets): - """ - Workaround to support Bitfinex v2 - TODO: Might require a separate asset dictionary - - :param assets: - :return: - """ - - v2_symbols = [] - for asset in assets: - v2_symbols.append(self._get_v2_symbol(asset)) - - return v2_symbols - - def _create_order(self, order_status): - """ - Create a Catalyst order object from a Bitfinex order dictionary - :param order_status: - :return: Order - """ - if order_status['is_cancelled']: - status = ORDER_STATUS.CANCELLED - elif not order_status['is_live']: - log.info('found executed order {}'.format(order_status)) - status = ORDER_STATUS.FILLED - else: - status = ORDER_STATUS.OPEN - - amount = float(order_status['original_amount']) - filled = float(order_status['executed_amount']) - - if order_status['side'] == 'sell': - amount = -amount - filled = -filled - - price = float(order_status['price']) - order_type = order_status['type'] - - stop_price = None - limit_price = None - - # TODO: is this comprehensive enough? - if order_type.endswith('limit'): - limit_price = price - elif order_type.endswith('stop'): - stop_price = price - - executed_price = float(order_status['avg_execution_price']) - - # TODO: bitfinex does not specify comission. - # I could calculate it but not sure if it's worth it. - commission = None - - date = pd.Timestamp.utcfromtimestamp(float(order_status['timestamp'])) - date = pytz.utc.localize(date) - order = Order( - dt=date, - asset=self.assets[order_status['symbol']], - amount=amount, - stop=stop_price, - limit=limit_price, - filled=filled, - id=str(order_status['id']), - commission=commission - ) - order.status = status - - return order, executed_price - - def get_balances(self): - log.debug('retrieving wallets balances') - try: - self.ask_request() - response = self._request('balances', None) - balances = response.json() - except Exception as e: - raise ExchangeRequestError(error=e) - - if 'message' in balances: - raise ExchangeRequestError( - error='unable to fetch balance {}'.format(balances['message']) - ) - - std_balances = dict() - for balance in balances: - currency = balance['currency'].lower() - std_balances[currency] = float(balance['available']) - - return std_balances - - @property - def account(self): - account = Account() - - account.settled_cash = None - account.accrued_interest = None - account.buying_power = None - account.equity_with_loan = None - account.total_positions_value = None - account.total_positions_exposure = None - account.regt_equity = None - account.regt_margin = None - account.initial_margin_requirement = None - account.maintenance_margin_requirement = None - account.available_funds = None - account.excess_liquidity = None - account.cushion = None - account.day_trades_remaining = None - account.leverage = None - account.net_leverage = None - account.net_liquidation = None - - return account - - @property - def time_skew(self): - # TODO: research the time skew conditions - return pd.Timedelta('0s') - - def get_account(self): - # TODO: fetch account data and keep in cache - return None - - def get_candles(self, freq, assets, bar_count=None, - start_dt=None, end_dt=None): - """ - Retrieve OHLVC candles from Bitfinex - - :param data_frequency: - :param assets: - :param bar_count: - :return: - - Available Frequencies - --------------------- - '1m', '5m', '15m', '30m', '1h', '3h', '6h', '12h', '1D', '7D', '14D', - '1M' - """ - log.debug( - 'retrieving {bars} {freq} candles on {exchange} from ' - '{end_dt} for markets {symbols}, '.format( - bars=bar_count, - freq=freq, - exchange=self.name, - end_dt=end_dt, - symbols=get_symbols_string(assets) - ) - ) - - allowed_frequencies = ['1T', '5T', '15T', '30T', '60T', '180T', - '360T', '720T', '1D', '7D', '14D', '30D'] - if freq not in allowed_frequencies: - raise InvalidHistoryFrequencyError(frequency=freq) - - freq_match = re.match(r'([0-9].*)(T|H|D)', freq, re.M | re.I) - if freq_match: - number = int(freq_match.group(1)) - unit = freq_match.group(2) - - if unit == 'T': - if number in [60, 180, 360, 720]: - number = number / 60 - converted_unit = 'h' - else: - converted_unit = 'm' - else: - converted_unit = unit - - frequency = '{}{}'.format(number, converted_unit) - - else: - raise InvalidHistoryFrequencyError(frequency=freq) - - # Making sure that assets are iterable - asset_list = [assets] if isinstance(assets, TradingPair) else assets - ohlc_map = dict() - for asset in asset_list: - symbol = self._get_v2_symbol(asset) - url = '{url}/v2/candles/trade:{frequency}:{symbol}'.format( - url=self.url, - frequency=frequency, - symbol=symbol - ) - - if bar_count: - is_list = True - url += '/hist?limit={}'.format(int(bar_count)) - - def get_ms(date): - epoch = datetime.datetime.utcfromtimestamp(0) - epoch = epoch.replace(tzinfo=pytz.UTC) - - return (date - epoch).total_seconds() * 1000.0 - - if start_dt is not None: - start_ms = get_ms(start_dt) - url += '&start={0:f}'.format(start_ms) - - if end_dt is not None: - end_ms = get_ms(end_dt) - url += '&end={0:f}'.format(end_ms) - - else: - is_list = False - url += '/last' - - try: - self.ask_request() - response = requests.get(url) - except Exception as e: - raise ExchangeRequestError(error=e) - - if 'error' in response.content: - raise ExchangeRequestError( - error='Unable to retrieve candles: {}'.format( - response.content) - ) - - candles = response.json() - - def ohlc_from_candle(candle): - last_traded = pd.Timestamp.utcfromtimestamp( - candle[0] / 1000.0) - last_traded = last_traded.replace(tzinfo=pytz.UTC) - ohlc = dict( - open=np.float64(candle[1]), - high=np.float64(candle[3]), - low=np.float64(candle[4]), - close=np.float64(candle[2]), - volume=np.float64(candle[5]), - price=np.float64(candle[2]), - last_traded=last_traded - ) - return ohlc - - if is_list: - ohlc_bars = [] - # We can to list candles from old to new - for candle in reversed(candles): - ohlc = ohlc_from_candle(candle) - ohlc_bars.append(ohlc) - - ohlc_map[asset] = ohlc_bars - - else: - ohlc = ohlc_from_candle(candles) - ohlc_map[asset] = ohlc - - return ohlc_map[assets] \ - if isinstance(assets, TradingPair) else ohlc_map - - def create_order(self, asset, amount, is_buy, style): - """ - Creating order on the exchange. - - :param asset: - :param amount: - :param is_buy: - :param style: - :return: - """ - exchange_symbol = self.get_symbol(asset) - if isinstance(style, ExchangeLimitOrder) \ - or isinstance(style, ExchangeStopLimitOrder): - price = style.get_limit_price(is_buy) - order_type = 'limit' - - elif isinstance(style, ExchangeStopOrder): - price = style.get_stop_price(is_buy) - order_type = 'stop' - - else: - raise InvalidOrderStyle(exchange=self.name, - style=style.__class__.__name__) - - req = dict( - symbol=exchange_symbol, - amount=str(float(abs(amount))), - price="{:.20f}".format(float(price)), - side='buy' if is_buy else 'sell', - type='exchange ' + order_type, # TODO: support margin trades - exchange=self.name, - is_hidden=False, - is_postonly=False, - use_all_available=0, - ocoorder=False, - buy_price_oco=0, - sell_price_oco=0 - ) - - date = pd.Timestamp.utcnow() - try: - self.ask_request() - response = self._request('order/new', req) - order_status = response.json() - except Exception as e: - raise ExchangeRequestError(error=e) - - if 'message' in order_status: - raise ExchangeRequestError( - error='unable to create Bitfinex order {}'.format( - order_status['message']) - ) - - order_id = str(order_status['id']) - order = Order( - dt=date, - asset=asset, - amount=amount, - stop=style.get_stop_price(is_buy), - limit=style.get_limit_price(is_buy), - id=order_id - ) - - return order - - def get_open_orders(self, asset=None): - """Retrieve all of the current open orders. - - Parameters - ---------- - asset : Asset - If passed and not None, return only the open orders for the given - asset instead of all open orders. - - Returns - ------- - open_orders : dict[list[Order]] or list[Order] - If no asset is passed this will return a dict mapping Assets - to a list containing all the open orders for the asset. - If an asset is passed then this will return a list of the open - orders for this asset. - """ - try: - self.ask_request() - response = self._request('orders', None) - order_statuses = response.json() - except Exception as e: - raise ExchangeRequestError(error=e) - - if 'message' in order_statuses: - raise ExchangeRequestError( - error='Unable to retrieve open orders: {}'.format( - order_statuses['message']) - ) - - orders = [] - for order_status in order_statuses: - order, executed_price = self._create_order(order_status) - if asset is None or asset == order.sid: - orders.append(order) - - return orders - - def get_order(self, order_id): - """Lookup an order based on the order id returned from one of the - order functions. - - Parameters - ---------- - order_id : str - The unique identifier for the order. - - Returns - ------- - order : Order - The order object. - """ - try: - self.ask_request() - response = self._request( - 'order/status', {'order_id': int(order_id)}) - order_status = response.json() - except Exception as e: - raise ExchangeRequestError(error=e) - - if 'message' in order_status: - raise ExchangeRequestError( - error='Unable to retrieve order status: {}'.format( - order_status['message']) - ) - return self._create_order(order_status) - - def cancel_order(self, order_param): - """Cancel an open order. - - Parameters - ---------- - order_param : str or Order - The order_id or order object to cancel. - """ - order_id = order_param.id \ - if isinstance(order_param, Order) else order_param - - try: - self.ask_request() - response = self._request('order/cancel', {'order_id': order_id}) - status = response.json() - except Exception as e: - raise ExchangeRequestError(error=e) - - if 'message' in status: - raise OrderCancelError( - order_id=order_id, - exchange=self.name, - error=status['message'] - ) - - def tickers(self, assets): - """ - Fetch ticket data for assets - https://docs.bitfinex.com/v2/reference#rest-public-tickers - - :param assets: - :return: - """ - symbols = self._get_v2_symbols(assets) - log.debug('fetching tickers {}'.format(symbols)) - - try: - self.ask_request() - response = requests.get( - '{url}/v2/tickers?symbols={symbols}'.format( - url=self.url, - symbols=','.join(symbols), - ) - ) - except Exception as e: - raise ExchangeRequestError(error=e) - - if 'error' in response.content: - raise ExchangeRequestError( - error='Unable to retrieve tickers: {}'.format( - response.content) - ) - - try: - tickers = response.json() - except Exception as e: - raise ExchangeRequestError(error=e) - - ticks = dict() - for index, ticker in enumerate(tickers): - if not len(ticker) == 11: - raise ExchangeRequestError( - error='Invalid ticker in response: {}'.format(ticker) - ) - - ticks[assets[index]] = dict( - timestamp=pd.Timestamp.utcnow(), - bid=ticker[1], - ask=ticker[3], - last_price=ticker[7], - low=ticker[10], - high=ticker[9], - volume=ticker[8], - ) - - log.debug('got tickers {}'.format(ticks)) - return ticks - - def generate_symbols_json(self, filename=None, source_dates=False): - symbol_map = {} - - if not source_dates: - fn, r = download_exchange_symbols(self.name) - with open(fn) as data_file: - cached_symbols = json.load(data_file) - - response = self._request('symbols', None) - - for symbol in response.json(): - if (source_dates): - start_date = self.get_symbol_start_date(symbol) - else: - try: - start_date = cached_symbols[symbol]['start_date'] - except KeyError: - start_date = time.strftime('%Y-%m-%d') - - try: - end_daily = cached_symbols[symbol]['end_daily'] - except KeyError: - end_daily = 'N/A' - - try: - end_minute = cached_symbols[symbol]['end_minute'] - except KeyError: - end_minute = 'N/A' - - symbol_map[symbol] = dict( - symbol=symbol[:-3] + '_' + symbol[-3:], - start_date=start_date, - end_daily=end_daily, - end_minute=end_minute, - ) - - if (filename is None): - filename = get_exchange_symbols_filename(self.name) - - with open(filename, 'w') as f: - json.dump(symbol_map, f, sort_keys=True, indent=2, - separators=(',', ':')) - - def get_symbol_start_date(self, symbol): - - print(symbol) - symbol_v2 = 't' + symbol.upper() - - """ - For each symbol we retrieve candles with Monhtly resolution - We get the first month, and query again with daily resolution - around that date, and we get the first date - """ - url = '{url}/v2/candles/trade:1M:{symbol}/hist'.format( - url=self.url, - symbol=symbol_v2 - ) - - try: - self.ask_request() - response = requests.get(url) - except Exception as e: - raise ExchangeRequestError(error=e) - - """ - If we don't get any data back for our monthly-resolution query - it means that symbol started trading less than a month ago, so - arbitrarily set the ref. date to 15 days ago to be safe with - +/- 31 days - """ - if (len(response.json())): - startmonth = response.json()[-1][0] - else: - startmonth = int((time.time() - 15 * 24 * 3600) * 1000) - - """ - Query again with daily resolution setting the start and end around - the startmonth we got above. Avoid end dates greater than - now: time.time() - """ - url = ('{url}/v2/candles/trade:1D:{symbol}/hist?start={start}' - '&end={end}').format( - url=self.url, - symbol=symbol_v2, - start=startmonth - 3600 * 24 * 31 * 1000, - end=min(startmonth + 3600 * 24 * 31 * 1000, - int(time.time() * 1000))) - - try: - self.ask_request() - response = requests.get(url) - except Exception as e: - raise ExchangeRequestError(error=e) - - return time.strftime('%Y-%m-%d', - time.gmtime(int(response.json()[-1][0] / 1000))) - - def get_orderbook(self, asset, order_type='all', limit=100): - exchange_symbol = asset.exchange_symbol - try: - self.ask_request() - # TODO: implement limit - response = self._request( - 'book/{}'.format(exchange_symbol), None) - data = response.json() - - except Exception as e: - raise ExchangeRequestError(error=e) - - # TODO: filter by type - result = dict() - for order_type in data: - result[order_type] = [] - - for entry in data[order_type]: - result[order_type].append(dict( - rate=float(entry['price']), - quantity=float(entry['amount']) - )) - - return result diff --git a/catalyst/exchange/bitfinex/symbols.json b/catalyst/exchange/bitfinex/symbols.json deleted file mode 100644 index ab0f38f9..00000000 --- a/catalyst/exchange/bitfinex/symbols.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "neobtc": { - "symbol": "neo_btc", - "start_date": "2017-09-07", - "precision": 5 - }, - "neousd": { - "symbol": "neo_usd", - "start_date": "2017-09-07" - }, - "neoeth": { - "symbol": "neo_eth", - "start_date": "2017-09-07" - }, - "btcusd": { - "symbol": "btc_usd", - "start_date": "2010-01-01" - }, - "bchusd": { - "symbol": "bch_usd", - "start_date": "2010-01-01" - }, - "ltcusd": { - "symbol": "ltc_usd", - "start_date": "2010-01-01" - }, - "ltcbtc": { - "symbol": "ltc_btc", - "start_date": "2010-01-01" - }, - "ethusd": { - "symbol": "eth_usd", - "start_date": "2017-01-01" - }, - "ethbtc": { - "symbol": "eth_btc", - "start_date": "2017-01-01" - }, - "etcbtc": { - "symbol": "etc_btc", - "start_date": "2017-01-01" - }, - "etcusd": { - "symbol": "etc_usd", - "start_date": "2017-01-01" - }, - "rrtusd": { - "symbol": "rrt_usd", - "start_date": "2010-01-01" - }, - "rrtbtc": { - "symbol": "rrt_btc", - "start_date": "2010-01-01" - }, - "zecusd": { - "symbol": "zec_usd", - "start_date": "2010-01-01" - }, - "zecbtc": { - "symbol": "zec_btc", - "start_date": "2010-01-01" - }, - "xmrusd": { - "symbol": "xmr_usd", - "start_date": "2010-01-01" - }, - "xmrbtc": { - "symbol": "xmr_btc", - "start_date": "2010-01-01" - }, - "dshusd": { - "symbol": "dsh_usd", - "start_date": "2010-01-01" - }, - "dshbtc": { - "symbol": "dsh_btc", - "start_date": "2010-01-01" - }, - "bccbtc": { - "symbol": "bcc_btc", - "start_date": "2010-01-01" - }, - "bcubtc": { - "symbol": "bcu_btc", - "start_date": "2010-01-01" - }, - "bccusd": { - "symbol": "bcc_usd", - "start_date": "2010-01-01" - }, - "bcuusd": { - "symbol": "bcu_usd", - "start_date": "2010-01-01" - }, - "xrpusd": { - "symbol": "xrp_usd", - "start_date": "2010-01-01" - }, - "xrpbtc": { - "symbol": "xrp_btc", - "start_date": "2010-01-01" - }, - "iotusd": { - "symbol": "iot_usd", - "start_date": "2010-01-01" - }, - "iotbtc": { - "symbol": "iot_btc", - "start_date": "2010-01-01" - }, - "ioteth": { - "symbol": "iot_eth", - "start_date": "2010-01-01" - }, - "eosusd": { - "symbol": "eos_usd", - "start_date": "2010-01-01" - }, - "eosbtc": { - "symbol": "eos_btc", - "start_date": "2010-01-01" - }, - "eoseth": { - "symbol": "eos_eth", - "start_date": "2010-01-01" - } -} \ No newline at end of file diff --git a/catalyst/exchange/bittrex/__init__.py b/catalyst/exchange/bittrex/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/catalyst/exchange/bittrex/bittrex.py b/catalyst/exchange/bittrex/bittrex.py deleted file mode 100644 index fae15430..00000000 --- a/catalyst/exchange/bittrex/bittrex.py +++ /dev/null @@ -1,417 +0,0 @@ -import json -import time - -import pandas as pd -from catalyst.assets._assets import TradingPair -from logbook import Logger -from six.moves import urllib - -from catalyst.constants import LOG_LEVEL -from catalyst.exchange.bittrex.bittrex_api import Bittrex_api -from catalyst.exchange.exchange import Exchange -from catalyst.exchange.exchange_bundle import ExchangeBundle -from catalyst.exchange.exchange_errors import InvalidHistoryFrequencyError, \ - ExchangeRequestError, InvalidOrderStyle, OrderNotFound, OrderCancelError, \ - CreateOrderError -from catalyst.exchange.utils.exchange_utils import \ - get_exchange_symbols_filename, \ - download_exchange_symbols, get_symbols_string -from catalyst.finance.execution import LimitOrder, StopLimitOrder -from catalyst.finance.order import Order, ORDER_STATUS -# TODO: consider using this: https://github.com/mondeja/bittrex_v2 -from catalyst.utils.deprecate import deprecated - -log = Logger('Bittrex', level=LOG_LEVEL) - -URL2 = 'https://bittrex.com/Api/v2.0' - - -@deprecated -class Bittrex(Exchange): - def __init__(self, key, secret, base_currency, portfolio=None): - self.api = Bittrex_api(key=key, secret=secret) - self.name = 'bittrex' - self.color = 'blue' - self.base_currency = base_currency - self._portfolio = portfolio - - self.num_candles_limit = 2000 - - # Not sure what the rate limit is but trying to play it safe - # https://bitcoin.stackexchange.com/questions/53778/bittrex-api-rate-limit - self.max_requests_per_minute = 60 - self.request_cpt = dict() - - self.minute_writer = None - self.minute_reader = None - - self.assets = dict() - self.load_assets() - - self.local_assets = dict() - self.load_assets(is_local=True) - - self.bundle = ExchangeBundle(self.name) - - @property - def account(self): - pass - - @property - def time_skew(self): - # TODO: research the time skew conditions - return pd.Timedelta('0s') - - def sanitize_curency_symbol(self, exchange_symbol): - """ - Helper method used to build the universal pair. - Include any symbol mapping here if appropriate. - - :param exchange_symbol: - :return universal_symbol: - """ - return exchange_symbol.lower() - - def get_balances(self): - balances = self.api.getbalances() - try: - log.debug('retrieving wallet balances') - self.ask_request() - - except Exception as e: - raise ExchangeRequestError(error=e) - - std_balances = dict() - try: - for balance in balances: - currency = balance['Currency'].lower() - std_balances[currency] = balance['Available'] - - except TypeError: - raise ExchangeRequestError(error=balances) - - return std_balances - - def create_order(self, asset, amount, is_buy, style): - log.info('creating {} order'.format('buy' if is_buy else 'sell')) - exchange_symbol = self.get_symbol(asset) - - if isinstance(style, LimitOrder) or isinstance(style, StopLimitOrder): - if isinstance(style, StopLimitOrder): - log.warn('{} will ignore the stop price'.format(self.name)) - - price = style.get_limit_price(is_buy) - try: - self.ask_request() - if is_buy: - order_status = self.api.buylimit(exchange_symbol, amount, - price) - else: - order_status = self.api.selllimit(exchange_symbol, - abs(amount), price) - except Exception as e: - raise ExchangeRequestError(error=e) - - if 'uuid' in order_status: - order_id = order_status['uuid'] - order = Order( - dt=pd.Timestamp.utcnow(), - asset=asset, - amount=amount, - stop=style.get_stop_price(is_buy), - limit=style.get_limit_price(is_buy), - id=order_id - ) - return order - else: - if order_status == 'INSUFFICIENT_FUNDS': - log.warn('not enough funds to create order') - return None - elif order_status == 'DUST_TRADE_DISALLOWED_MIN_VALUE_50K_SAT': - log.warn('Your order is too small, order at least 50K' - ' Satoshi') - return None - else: - raise CreateOrderError( - exchange=self.name, - error=order_status - ) - else: - raise InvalidOrderStyle(exchange=self.name, - style=style.__class__.__name__) - - def get_open_orders(self, asset): - symbol = self.get_symbol(asset) - try: - self.ask_request() - open_orders = self.api.getopenorders(symbol) - except Exception as e: - raise ExchangeRequestError(error=e) - - orders = list() - for order_status in open_orders: - order = self._create_order(order_status) - orders.append(order) - - return orders - - def _create_order(self, order_status): - log.info( - 'creating catalyst order from Bittrex {}'.format(order_status)) - if order_status['CancelInitiated']: - status = ORDER_STATUS.CANCELLED - elif order_status['Closed'] is not None: - status = ORDER_STATUS.FILLED - else: - status = ORDER_STATUS.OPEN - - date = pd.to_datetime(order_status['Opened'], utc=True) - amount = order_status['Quantity'] - filled = amount - order_status['QuantityRemaining'] - order = Order( - dt=date, - asset=self.assets[order_status['Exchange']], - amount=amount, - stop=None, # Not yet supported by Bittrex - limit=order_status['Limit'], - filled=filled, - id=order_status['OrderUuid'], - commission=order_status['CommissionPaid'] - ) - order.status = status - - executed_price = order_status['PricePerUnit'] - - return order, executed_price - - def get_order(self, order_id): - log.info('retrieving order {}'.format(order_id)) - try: - self.ask_request() - order_status = self.api.getorder(order_id) - except Exception as e: - raise ExchangeRequestError(error=e) - - if order_status is None: - raise OrderNotFound(order_id=order_id, exchange=self.name) - - return self._create_order(order_status) - - def cancel_order(self, order_param): - order_id = order_param.id \ - if isinstance(order_param, Order) else order_param - log.info('cancelling order {}'.format(order_id)) - - try: - self.ask_request() - status = self.api.cancel(order_id) - except Exception as e: - raise ExchangeRequestError(error=e) - - if 'message' in status: - raise OrderCancelError( - order_id=order_id, - exchange=self.name, - error=status['message'] - ) - - def get_candles(self, freq, assets, bar_count=None, - start_dt=None, end_dt=None): - """ - Supported Intervals - ------------------- - day, oneMin, fiveMin, thirtyMin, hour - - :param freq: - :param assets: - :param bar_count: - :param start_dt - :param end_dt - :return: - """ - - # TODO: this has no effect at the moment - if end_dt is None: - end_dt = pd.Timestamp.utcnow() - - log.debug( - 'retrieving {bars} {freq} candles on {exchange} from ' - '{end_dt} for markets {symbols}, '.format( - bars=bar_count, - freq=freq, - exchange=self.name, - end_dt=end_dt, - symbols=get_symbols_string(assets) - ) - ) - - if freq == '1T': - frequency = 'oneMin' - elif freq == '5T': - frequency = 'fiveMin' - elif freq == '30T': - frequency = 'thirtyMin' - elif freq == '60T': - frequency = 'hour' - elif freq == '1D': - frequency = 'day' - else: - raise InvalidHistoryFrequencyError(frequency=freq) - - # Making sure that assets are iterable - asset_list = [assets] if isinstance(assets, TradingPair) else assets - for asset in asset_list: - end = int(time.mktime(end_dt.timetuple())) - url = '{url}/pub/market/GetTicks?marketName={symbol}' \ - '&tickInterval={frequency}&_={end}'.format( - url=URL2, - symbol=self.get_symbol(asset), - frequency=frequency, - end=end, ) - - try: - data = json.loads(urllib.request.urlopen(url).read().decode()) - except Exception as e: - raise ExchangeRequestError(error=e) - - if data['message']: - raise ExchangeRequestError( - error='Unable to fetch candles {}'.format(data['message']) - ) - - candles = data['result'] - - def ohlc_from_candle(candle): - ohlc = dict( - open=candle['O'], - high=candle['H'], - low=candle['L'], - close=candle['C'], - volume=candle['V'], - price=candle['C'], - last_traded=pd.to_datetime(candle['T'], utc=True) - ) - return ohlc - - ordered_candles = list(reversed(candles)) - ohlc_map = dict() - if bar_count is None: - ohlc_map[asset] = ohlc_from_candle(ordered_candles[0]) - else: - # TODO: optimize - ohlc_bars = [] - for candle in ordered_candles[:bar_count]: - ohlc = ohlc_from_candle(candle) - ohlc_bars.append(ohlc) - - ohlc_map[asset] = ohlc_bars - - return ohlc_map[assets] \ - if isinstance(assets, TradingPair) else ohlc_map - - def tickers(self, assets): - """ - As of v1.1, Bittrex only allows one ticker at the time. - So we have to make multiple calls to fetch multiple assets. - - :param assets: - :return: - """ - log.info('retrieving tickers') - - ticks = dict() - for asset in assets: - symbol = self.get_symbol(asset) - try: - self.ask_request() - ticker = self.api.getticker(symbol) - except Exception as e: - raise ExchangeRequestError(error=e) - - # TODO: catch invalid ticker - ticks[asset] = dict( - timestamp=pd.Timestamp.utcnow(), - bid=ticker['Bid'], - ask=ticker['Ask'], - last_price=ticker['Last'] - ) - - log.debug('got tickers {}'.format(ticks)) - return ticks - - def get_account(self): - log.info('retrieving account data') - pass - - def generate_symbols_json(self, filename=None): - symbol_map = {} - - fn, r = download_exchange_symbols(self.name) - with open(fn) as data_file: - cached_symbols = json.load(data_file) - - markets = self.api.getmarkets() - for market in markets: - exchange_symbol = market['MarketName'] - symbol = '{market}_{base}'.format( - market=self.sanitize_curency_symbol(market['MarketCurrency']), - base=self.sanitize_curency_symbol(market['BaseCurrency']) - ) - - try: - end_daily = cached_symbols[exchange_symbol]['end_daily'] - except KeyError: - end_daily = 'N/A' - - try: - end_minute = cached_symbols[exchange_symbol]['end_minute'] - except KeyError: - end_minute = 'N/A' - - symbol_map[exchange_symbol] = dict( - symbol=symbol, - start_date=pd.to_datetime(market['Created'], - utc=True).strftime("%Y-%m-%d"), - end_daily=end_daily, - end_minute=end_minute, - ) - - if (filename is None): - filename = get_exchange_symbols_filename(self.name) - - with open(filename, 'w') as f: - json.dump(symbol_map, f, sort_keys=True, indent=2, - separators=(',', ':')) - - def get_orderbook(self, asset, order_type='all', limit=100): - if order_type == 'all': - order_type = 'both' - elif order_type == 'bid': - order_type = 'buy' - elif order_type == 'ask': - order_type = 'sell' - else: - raise ValueError('invalid type') - - exchange_symbol = asset.exchange_symbol - data = self.api.getorderbook( - market=exchange_symbol, - type=order_type, - depth=100 - ) - - result = dict() - for exchange_type in data: - if exchange_type == 'buy': - order_type = 'bids' - elif exchange_type == 'sell': - order_type = 'asks' - - result[order_type] = [] - for entry in data[exchange_type]: - result[order_type].append(dict( - rate=entry['Rate'], - quantity=entry['Quantity'] - )) - - return result diff --git a/catalyst/exchange/bittrex/bittrex_api.py b/catalyst/exchange/bittrex/bittrex_api.py deleted file mode 100644 index ca5f9c4a..00000000 --- a/catalyst/exchange/bittrex/bittrex_api.py +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env python -import json -import time -import hmac -import hashlib -import ssl - -# Workaround for backwards compatibility -# https://stackoverflow.com/questions/3745771/urllib-request-in-python-2-7 -from six.moves import urllib - -urlopen = urllib.request.urlopen - - -class Bittrex_api(object): - def __init__(self, key, secret): - self.key = key - self.secret = secret - self.public = ['getmarkets', 'getcurrencies', 'getticker', - 'getmarketsummaries', 'getmarketsummary', - 'getorderbook', 'getmarkethistory'] - self.market = ['buylimit', 'buymarket', 'selllimit', 'sellmarket', - 'cancel', 'getopenorders'] - self.account = ['getbalances', 'getbalance', 'getdepositaddress', - 'withdraw', 'getorder', 'getorderhistory', - 'getwithdrawalhistory', 'getdeposithistory'] - - def query(self, method, values={}): - if method in self.public: - url = 'https://bittrex.com/api/v1.1/public/' - elif method in self.market: - url = 'https://bittrex.com/api/v1.1/market/' - elif method in self.account: - url = 'https://bittrex.com/api/v1.1/account/' - else: - return 'Something went wrong, sorry.' - - url += method + '?' + urllib.parse.urlencode(values) - - if method not in self.public: - url += '&apikey=' + self.key - url += '&nonce=' + str(int(time.time())) - - signature = hmac.new(self.secret.encode('utf-8'), - url.encode('utf-8'), - hashlib.sha512).hexdigest() - headers = {'apisign': signature} - else: - headers = {} - - req = urllib.request.Request(url, headers=headers) - response = json.loads(urlopen( - req, context=ssl._create_unverified_context()).read()) - - if response["result"]: - return response["result"] - else: - return response["message"] - - def getmarkets(self): - return self.query('getmarkets') - - def getcurrencies(self): - return self.query('getcurrencies') - - def getticker(self, market): - return self.query('getticker', {'market': market}) - - def getmarketsummaries(self): - return self.query('getmarketsummaries') - - def getmarketsummary(self, market): - return self.query('getmarketsummary', {'market': market}) - - def getorderbook(self, market, type, depth=20): - return self.query('getorderbook', - {'market': market, 'type': type, 'depth': depth}) - - def getmarkethistory(self, market, count=20): - return self.query('getmarkethistory', - {'market': market, 'count': count}) - - def buylimit(self, market, quantity, rate): - return self.query('buylimit', {'market': market, 'quantity': quantity, - 'rate': rate}) - - def buymarket(self, market, quantity): - return self.query('buymarket', - {'market': market, 'quantity': quantity}) - - def selllimit(self, market, quantity, rate): - return self.query('selllimit', {'market': market, 'quantity': quantity, - 'rate': rate}) - - def sellmarket(self, market, quantity): - return self.query('sellmarket', - {'market': market, 'quantity': quantity}) - - def cancel(self, uuid): - return self.query('cancel', {'uuid': uuid}) - - def getopenorders(self, market): - return self.query('getopenorders', {'market': market}) - - def getbalances(self): - return self.query('getbalances') - - def getbalance(self, currency): - return self.query('getbalance', {'currency': currency}) - - def getdepositaddress(self, currency): - return self.query('getdepositaddress', {'currency': currency}) - - def withdraw(self, currency, quantity, address): - return self.query('withdraw', - {'currency': currency, 'quantity': quantity, - 'address': address}) - - def getorder(self, uuid): - return self.query('getorder', {'uuid': uuid}) - - def getorderhistory(self, market, count): - return self.query('getorderhistory', - {'market': market, 'count': count}) - - def getwithdrawalhistory(self, currency, count): - return self.query('getwithdrawalhistory', - {'currency': currency, 'count': count}) - - def getdeposithistory(self, currency, count): - return self.query('getdeposithistory', - {'currency': currency, 'count': count}) diff --git a/catalyst/exchange/bittrex/extensions-example.py b/catalyst/exchange/bittrex/extensions-example.py deleted file mode 100644 index 4b087899..00000000 --- a/catalyst/exchange/bittrex/extensions-example.py +++ /dev/null @@ -1,7 +0,0 @@ -from catalyst.data.bundles import register -from catalyst.exchange.exchange_bundle import exchange_bundle - -symbols = ( - 'neo_btc', -) -register('exchange_bitfinex', exchange_bundle('bitfinex', symbols)) diff --git a/catalyst/exchange/poloniex/__init__.py b/catalyst/exchange/poloniex/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/catalyst/exchange/poloniex/poloniex.py b/catalyst/exchange/poloniex/poloniex.py deleted file mode 100644 index 6f26b905..00000000 --- a/catalyst/exchange/poloniex/poloniex.py +++ /dev/null @@ -1,662 +0,0 @@ -import json -import time -from collections import defaultdict - -import numpy as np -import pandas as pd -import pytz -from catalyst.assets._assets import TradingPair -from logbook import Logger -# import six -from six import iteritems - -from catalyst.constants import LOG_LEVEL -# from websocket import create_connection -from catalyst.exchange.exchange import Exchange -from catalyst.exchange.exchange_bundle import ExchangeBundle -from catalyst.exchange.exchange_errors import ( - ExchangeRequestError, - InvalidHistoryFrequencyError, - InvalidOrderStyle, - OrphanOrderError, - OrphanOrderReverseError) -from catalyst.exchange.exchange_execution import ExchangeLimitOrder, \ - ExchangeStopLimitOrder -from catalyst.exchange.poloniex.poloniex_api import Poloniex_api -from catalyst.exchange.utils.exchange_utils import \ - get_exchange_symbols_filename, \ - download_exchange_symbols, get_symbols_string -from catalyst.finance.order import Order, ORDER_STATUS -from catalyst.finance.transaction import Transaction -from catalyst.protocol import Account -from catalyst.utils.deprecate import deprecated - -log = Logger('Poloniex', level=LOG_LEVEL) - - -@deprecated -class Poloniex(Exchange): - def __init__(self, key, secret, base_currency, portfolio=None): - self.api = Poloniex_api(key=key, secret=secret) - self.name = 'poloniex' - - self.assets = dict() - self.load_assets() - - self.local_assets = dict() - self.load_assets(is_local=True) - - self.base_currency = base_currency - self._portfolio = portfolio - self.minute_writer = None - self.minute_reader = None - self.transactions = defaultdict(list) - - self.num_candles_limit = 2000 - self.max_requests_per_minute = 60 - self.request_cpt = dict() - - self.bundle = ExchangeBundle(self.name) - - def sanitize_curency_symbol(self, exchange_symbol): - """ - Helper method used to build the universal pair. - Include any symbol mapping here if appropriate. - - :param exchange_symbol: - :return universal_symbol: - """ - return exchange_symbol.lower() - - def _create_order(self, order_status): - """ - Create a Catalyst order object from the Exchange order dictionary - :param order_status: - :return: Order - """ - # if order_status['is_cancelled']: - # status = ORDER_STATUS.CANCELLED - # elif not order_status['is_live']: - # log.info('found executed order {}'.format(order_status)) - # status = ORDER_STATUS.FILLED - # else: - status = ORDER_STATUS.OPEN - - amount = float(order_status['amount']) - # filled = float(order_status['executed_amount']) - filled = None - - if order_status['type'] == 'sell': - amount = -amount - # filled = -filled - - price = float(order_status['rate']) - - stop_price = None - limit_price = None - - # TODO: is this comprehensive enough? - # if order_type.endswith('limit'): - # limit_price = price - # elif order_type.endswith('stop'): - # stop_price = price - - # executed_price = float(order_status['avg_execution_price']) - executed_price = price - - # TODO: Set Poloniex comission - commission = None - - # date=pd.Timestamp.utcfromtimestamp(float(order_status['timestamp'])) - # date=pytz.utc.localize(date) - date = None - - order = Order( - dt=date, - asset=self.assets[order_status['symbol']], - # No such field in Poloniex - amount=amount, - stop=stop_price, - limit=limit_price, - filled=filled, - id=str(order_status['orderNumber']), - commission=commission - ) - order.status = status - - return order, executed_price - - def get_balances(self): - balances = self.api.returnbalances() - try: - log.debug('retrieving wallets balances') - except Exception as e: - log.debug(e) - raise ExchangeRequestError(error=e) - - if 'error' in balances: - raise ExchangeRequestError( - error='unable to fetch balance {}'.format(balances['error']) - ) - - std_balances = dict() - for (key, value) in iteritems(balances): - currency = key.lower() - std_balances[currency] = float(value) - - return std_balances - - @property - def account(self): - account = Account() - - account.settled_cash = None - account.accrued_interest = None - account.buying_power = None - account.equity_with_loan = None - account.total_positions_value = None - account.total_positions_exposure = None - account.regt_equity = None - account.regt_margin = None - account.initial_margin_requirement = None - account.maintenance_margin_requirement = None - account.available_funds = None - account.excess_liquidity = None - account.cushion = None - account.day_trades_remaining = None - account.leverage = None - account.net_leverage = None - account.net_liquidation = None - - return account - - @property - def time_skew(self): - # TODO: research the time skew conditions - return pd.Timedelta('0s') - - def get_account(self): - # TODO: fetch account data and keep in cache - return None - - def get_candles(self, freq, assets, bar_count=None, - start_dt=None, end_dt=None): - """ - Retrieve OHLVC candles from Poloniex - - :param freq: - :param assets: - :param bar_count: - :return: - - Available Frequencies - --------------------- - '5m', '15m', '30m', '2h', '4h', '1D' - """ - - if end_dt is None: - end_dt = pd.Timestamp.utcnow() - - log.debug( - 'retrieving {bars} {freq} candles on {exchange} from ' - '{end_dt} for markets {symbols}, '.format( - bars=bar_count, - freq=freq, - exchange=self.name, - end_dt=end_dt, - symbols=get_symbols_string(assets) - ) - ) - - if freq == '1T' and (bar_count == 1 or bar_count is None): - # TODO: use the order book instead - # We use the 5m to fetch the last bar - frequency = 300 - elif freq == '5T': - frequency = 300 - elif freq == '15T': - frequency = 900 - elif freq == '30T': - frequency = 1800 - elif freq == '120T': - frequency = 7200 - elif freq == '240T': - frequency = 14400 - elif freq == '1D': - frequency = 86400 - else: - # Poloniex does not offer 1m data candles - # It is likely to error out there frequently - raise InvalidHistoryFrequencyError(frequency=freq) - - # Making sure that assets are iterable - asset_list = [assets] if isinstance(assets, TradingPair) else assets - ohlc_map = dict() - - for asset in asset_list: - delta = end_dt - pd.to_datetime('1970-1-1', utc=True) - end = int(delta.total_seconds()) - - if bar_count is None: - start = end - 2 * frequency - else: - start = end - bar_count * frequency - - try: - response = self.api.returnchartdata( - self.get_symbol(asset), frequency, start, end - ) - except Exception as e: - raise ExchangeRequestError(error=e) - - if 'error' in response: - raise ExchangeRequestError( - error='Unable to retrieve candles: {}'.format( - response.content) - ) - - def ohlc_from_candle(candle): - last_traded = pd.Timestamp.utcfromtimestamp(candle['date']) - last_traded = last_traded.replace(tzinfo=pytz.UTC) - - ohlc = dict( - open=np.float64(candle['open']), - high=np.float64(candle['high']), - low=np.float64(candle['low']), - close=np.float64(candle['close']), - volume=np.float64(candle['volume']), - price=np.float64(candle['close']), - last_traded=last_traded - ) - - return ohlc - - if bar_count is None: - ohlc_map[asset] = ohlc_from_candle(response[0]) - else: - ohlc_bars = [] - for candle in response: - ohlc = ohlc_from_candle(candle) - ohlc_bars.append(ohlc) - ohlc_map[asset] = ohlc_bars - - return ohlc_map[assets] \ - if isinstance(assets, TradingPair) else ohlc_map - - def create_order(self, asset, amount, is_buy, style): - """ - Creating order on the exchange. - - :param asset: - :param amount: - :param is_buy: - :param style: - :return: - """ - exchange_symbol = self.get_symbol(asset) - - if (isinstance(style, ExchangeLimitOrder) - or isinstance(style, ExchangeStopLimitOrder)): - if isinstance(style, ExchangeStopLimitOrder): - log.warn('{} will ignore the stop price'.format(self.name)) - - price = style.get_limit_price(is_buy) - - try: - if (is_buy): - response = self.api.buy(exchange_symbol, amount, price) - else: - response = self.api.sell(exchange_symbol, -amount, price) - except Exception as e: - raise ExchangeRequestError(error=e) - - date = pd.Timestamp.utcnow() - - if ('orderNumber' in response): - order_id = str(response['orderNumber']) - order = Order( - dt=date, - asset=asset, - amount=amount, - stop=style.get_stop_price(is_buy), - limit=style.get_limit_price(is_buy), - id=order_id - ) - return order - else: - log.warn( - '{} order failed: {}'.format('buy' if is_buy else 'sell', - response['error'])) - return None - else: - raise InvalidOrderStyle(exchange=self.name, - style=style.__class__.__name__) - - def get_open_orders(self, asset='all'): - """Retrieve all of the current open orders. - - Parameters - ---------- - asset : Asset - If passed and not 'all', return only the open orders for the given - asset instead of all open orders. - - Returns - ------- - open_orders : dict[list[Order]] or list[Order] - If 'all' is passed this will return a dict mapping Assets - to a list containing all the open orders for the asset. - If an asset is passed then this will return a list of the open - orders for this asset. - """ - - return self.portfolio.open_orders - - """ - TODO: Why going to the exchange if we already have this info locally? - And why creating all these Orders if we later discard them? - """ - - try: - if (asset == 'all'): - response = self.api.returnopenorders('all') - else: - response = self.api.returnopenorders(self.get_symbol(asset)) - except Exception as e: - raise ExchangeRequestError(error=e) - - if 'error' in response: - raise ExchangeRequestError( - error='Unable to retrieve open orders: {}'.format( - response['message']) - ) - - print(self.portfolio.open_orders) - - # TODO: Need to handle openOrders for 'all' - orders = list() - for order_status in response: - # will Throw error b/c Polo doesn't track order['symbol'] - order, executed_price = self._create_order(order_status) - if asset is None or asset == order.sid: - orders.append(order) - - return orders - - def get_order(self, order_id): - """Lookup an order based on the order id returned from one of the - order functions. - - Parameters - ---------- - order_id : str - The unique identifier for the order. - - Returns - ------- - order : Order - The order object. - """ - - try: - order = self._portfolio.open_orders[order_id] - except Exception as e: - raise OrphanOrderError(order_id=order_id, exchange=self.name) - - return order - - # TODO: Need to decide whether we fetch orders locally or from exchnage - # The code below is ignored - - try: - response = self.api.returnopenorders(self.get_symbol(order.sid)) - except Exception as e: - raise ExchangeRequestError(error=e) - - for o in response: - if (int(o['orderNumber']) == int(order_id)): - return order - - return None - - def cancel_order(self, order_param): - """Cancel an open order. - - Parameters - ---------- - order_param : str or Order - The order_id or order object to cancel. - """ - - if (isinstance(order_param, Order)): - order = order_param - else: - order = self._portfolio.open_orders[order_param] - - try: - response = self.api.cancelorder(order.id) - except Exception as e: - raise ExchangeRequestError(error=e) - - if 'error' in response: - log.info( - 'Unable to cancel order {order_id} on exchange {exchange} ' - '{error}.'.format( - order_id=order.id, - exchange=self.name, - error=response['error'] - )) - - # raise OrderCancelError( - # order_id=order.id, - # exchange=self.name, - # error=response['error'] - # ) - - self.portfolio.remove_order(order) - - def tickers(self, assets): - """ - Fetch ticket data for assets - https://docs.bitfinex.com/v2/reference#rest-public-tickers - - :param assets: - :return: - """ - symbols = self.get_symbols(assets) - - log.debug('fetching tickers {}'.format(symbols)) - - try: - response = self.api.returnticker() - except Exception as e: - raise ExchangeRequestError(error=e) - - if 'error' in response: - raise ExchangeRequestError( - error='Unable to retrieve tickers: {}'.format( - response['error']) - ) - - ticks = dict() - - for index, symbol in enumerate(symbols): - ticks[assets[index]] = dict( - timestamp=pd.Timestamp.utcnow(), - bid=float(response[symbol]['highestBid']), - ask=float(response[symbol]['lowestAsk']), - last_price=float(response[symbol]['last']), - low=float(response[symbol]['lowestAsk']), - # TODO: Polo does not provide low - high=float(response[symbol]['highestBid']), - # TODO: Polo does not provide high - volume=float(response[symbol]['baseVolume']), - ) - - log.debug('got tickers {}'.format(ticks)) - return ticks - - def generate_symbols_json(self, filename=None, source_dates=False): - symbol_map = {} - - if not source_dates: - fn, r = download_exchange_symbols(self.name) - with open(fn) as data_file: - cached_symbols = json.load(data_file) - - response = self.api.returnticker() - - for exchange_symbol in response: - base, market = self.sanitize_curency_symbol(exchange_symbol).split( - '_') - symbol = '{market}_{base}'.format(market=market, base=base) - - if (source_dates): - start_date = self.get_symbol_start_date(exchange_symbol) - else: - try: - start_date = cached_symbols[exchange_symbol]['start_date'] - except KeyError: - start_date = time.strftime('%Y-%m-%d') - - try: - end_daily = cached_symbols[exchange_symbol]['end_daily'] - except KeyError: - end_daily = 'N/A' - - try: - end_minute = cached_symbols[exchange_symbol]['end_minute'] - except KeyError: - end_minute = 'N/A' - - symbol_map[exchange_symbol] = dict( - symbol=symbol, - start_date=start_date, - end_daily=end_daily, - end_minute=end_minute, - ) - - if (filename is None): - filename = get_exchange_symbols_filename(self.name) - - with open(filename, 'w') as f: - json.dump(symbol_map, f, sort_keys=True, indent=2, - separators=(',', ':')) - - def get_symbol_start_date(self, symbol): - try: - r = self.api.returnchartdata(symbol, 86400, pd.to_datetime( - '2010-1-1').value // 10 ** 9) - except Exception as e: - raise ExchangeRequestError(error=e) - - return time.strftime('%Y-%m-%d', time.gmtime(int(r[0]['date']))) - - def check_open_orders(self): - """ - Need to override this function for Poloniex: - - Loop through the list of open orders in the Portfolio object. - Check if any transactions have been executed: - If so, create a transaction and apply to the Portfolio. - Check if the order is still open: - If not, remove it from open orders - - :return: - transactions: Transaction[] - """ - transactions = list() - if self.portfolio.open_orders: - for order_id in list(self.portfolio.open_orders): - - order = self._portfolio.open_orders[order_id] - log.debug('found open order: {}'.format(order_id)) - - try: - order_open = self.get_order(order_id) - except Exception as e: - raise ExchangeRequestError(error=e) - - if (order_open): - delta = pd.Timestamp.utcnow() - order.dt - log.info( - 'order {order_id} still open after {delta}'.format( - order_id=order_id, - delta=delta) - ) - - try: - response = self.api.returnordertrades(order_id) - except Exception as e: - raise ExchangeRequestError(error=e) - - if ('error' in response): - if (not order_open): - raise OrphanOrderReverseError(order_id=order_id, - exchange=self.name) - else: - for tx in response: - """ - We maintain a list of dictionaries of transactions that - correspond to partially filled orders, indexed by - order_id. Every time we query executed transactions - from the exchange, we check if we had that transaction - for that order already. If not, we process it. - - When an order if fully filled, we flush the dict of - transactions associated with that order. - """ - if (not filter( - lambda item: item['order_id'] == tx['tradeID'], - self.transactions[order_id])): - log.debug( - 'Got new transaction for order {}: amount {}, ' - 'price {}'.format( - order_id, tx['amount'], tx['rate'])) - tx['amount'] = float(tx['amount']) - if (tx['type'] == 'sell'): - tx['amount'] = -tx['amount'] - transaction = Transaction( - asset=order.asset, - amount=tx['amount'], - dt=pd.to_datetime(tx['date'], utc=True), - price=float(tx['rate']), - order_id=tx['tradeID'], - # it's a misnomer, but keep for compatibility - commission=float(tx['fee']) - ) - self.transactions[order_id].append(transaction) - self.portfolio.execute_transaction(transaction) - transactions.append(transaction) - - if (not order_open): - """ - Since transactions have been executed individually - the only thing left to do is remove them from list - of open_orders - """ - del self.portfolio.open_orders[order_id] - del self.transactions[order_id] - - return transactions - - def get_orderbook(self, asset, order_type='all'): - exchange_symbol = asset.exchange_symbol - data = self.api.returnOrderBook(market=exchange_symbol) - - result = dict() - for order_type in data: - # TODO: filter by type - if order_type != 'asks' and order_type != 'bids': - continue - - result[order_type] = [] - for entry in data[order_type]: - if len(entry) == 2: - result[order_type].append( - dict( - rate=float(entry[0]), - quantity=float(entry[1]) - ) - ) - return result diff --git a/catalyst/exchange/poloniex/poloniex_api.py b/catalyst/exchange/poloniex/poloniex_api.py deleted file mode 100644 index 6f339192..00000000 --- a/catalyst/exchange/poloniex/poloniex_api.py +++ /dev/null @@ -1,212 +0,0 @@ -#!/usr/bin/env python -import json -import time -import hmac -import hashlib -import ssl - -from six.moves import urllib - -# Workaround for backwards compatibility -# https://stackoverflow.com/questions/3745771/urllib-request-in-python-2-7 -urlopen = urllib.request.urlopen - - -class Poloniex_api(object): - def __init__(self, key, secret): - self.key = key - self.secret = secret - - self.max_requests_per_second = 6 - self.request_cpt = dict() - - self.public = ['returnTicker', 'return24Volume', 'returnOrderBook', - 'returnTradeHistory', 'returnChartData', - 'returnCurrencies', 'returnLoanOrders'] - self.trading = ['returnBalances', 'returnCompleteBalances', - 'returnDepositAddresses', - 'generateNewAddress', 'returnDepositsWithdrawals', - 'returnOpenOrders', - 'returnTradeHistory', 'returnOrderTrades', - 'buy', 'sell', 'cancelOrder', 'moveOrder', - 'withdraw', 'returnFeeInfo', - 'returnAvailableAccountBalances', - 'returnTradableBalances', 'transferBalance', - 'returnMarginAccountSummary', 'marginBuy', - 'marginSell', - 'getMarginPosition', 'closeMarginPosition', - 'createLoanOffer', - 'cancelLoanOffer', 'returnOpenLoanOffers', - 'returnActiveLoans', - 'returnLendingHistory', 'toggleAutoRenew'] - - def ask_request(self): - """ - Asks permission to issue a request to the exchange. - The primary purpose is to avoid hitting rate limits. - - The application will pause if the maximum requests per minute - permitted by the exchange is exceeded. - - :return boolean: - - """ - now = time.time() - if not self.request_cpt: - self.request_cpt = dict() - self.request_cpt[now] = 0 - return True - - cpt_date = list(self.request_cpt.keys())[0] - cpt = self.request_cpt[cpt_date] - - if now > cpt_date + 1: - self.request_cpt = dict() - self.request_cpt[now] = 0 - return True - - if cpt >= self.max_requests_per_second: - - time.sleep(1) - - now = time.time() - self.request_cpt = dict() - self.request_cpt[now] = 0 - return True - else: - self.request_cpt[cpt_date] += 1 - - def query(self, method, req={}): - - if method in self.public: - url = 'https://poloniex.com/public?command=' + method + '&' + \ - urllib.parse.urlencode(req) - headers = {} - post_data = None - elif method in self.trading: - url = 'https://poloniex.com/tradingApi' - req['command'] = method - req['nonce'] = int(time.time() * 1000) - post_data = urllib.parse.urlencode(req) - - signature = hmac.new(self.secret.encode('utf-8'), - post_data.encode('utf-8'), - hashlib.sha512).hexdigest() - headers = {'Sign': signature, 'Key': self.key} - - post_data = post_data.encode('utf-8') - else: - raise ValueError( - 'Method "' + method + '" not found in neither the Public API ' - 'or Trading API endpoints' - ) - - self.ask_request() - req = urllib.request.Request( - url, - data=post_data, - headers=headers, - ) - resource = urlopen(req, context=ssl._create_unverified_context()) - content = resource.read().decode('utf-8') - return json.loads(content) - - def returnticker(self): - return self.query('returnTicker', {}) - - def return24volume(self): - return self.query('return24Volume', {}) - - def returnOrderBook(self, market='all'): - return self.query('returnOrderBook', {'currencyPair': market}) - - def returntradehistory(self, market, start=None, end=None): - if (start is not None and end is not None): - return self.query('returntradehistory', - {'currencyPair': market, 'start': start, - 'end': end}) - else: - return self.query('returntradehistory', {'currencyPair': market}) - - def returnchartdata(self, market, period, start, end=9999999999): - return self.query('returnChartData', - {'currencyPair': market, 'period': period, - 'start': start, 'end': end}) - - def returncurrencies(self): - return self.query('returnCurrencies', {}) - - def returnloadorders(self, market): - return self.query('returnLoanOrders', {'currency': market}) - - def returnbalances(self): - return self.query('returnBalances') - - def returncompletebalances(self, account): - if (account): - return self.query('returnCompleteBalances', {'account': account}) - else: - return self.query('returnCompleteBalances') - - def returndepositaddresses(self): - return self.query('returnDepositAddresses') - - def generatenewaddress(self, currency): - return self.query('generateNewAddress', {'currency': currency}) - - def returnDepositsWithdrawals(self, start, end): - return self.query('returnDepositsWithdrawals', - {'start': start, 'end': end}) - - def returnopenorders(self, market): - return self.query('returnOpenOrders', {'currencyPair': market}) - - def returnordertrades(self, ordernumber): - return self.query('returnOrderTrades', {'orderNumber': ordernumber}) - - def buy(self, market, amount, rate, fillorkill=0, immediateorcancel=0, - postonly=0): - if (fillorkill): - return self.query('buy', {'currencyPair': market, 'rate': rate, - 'amount': amount, - 'fillOrKill': fillorkill, }) - elif (immediateorcancel): - return self.query('buy', {'currencyPair': market, 'rate': rate, - 'amount': amount, - 'immediateOrCancel': immediateorcancel}) - elif (postonly): - return self.query('buy', {'currencyPair': market, 'rate': rate, - 'amount': amount, - 'postOnly': postonly, }) - else: - return self.query('buy', {'currencyPair': market, 'rate': rate, - 'amount': amount, }) - - def sell(self, market, amount, rate, fillorkill=0, immediateorcancel=0, - postonly=0): - if (fillorkill): - return self.query('sell', {'currencyPair': market, 'rate': rate, - 'amount': amount, - 'fillOrKill': fillorkill, }) - elif (immediateorcancel): - return self.query('sell', {'currencyPair': market, 'rate': rate, - 'amount': amount, - 'immediateOrCancel': immediateorcancel}) - elif (postonly): - return self.query('sell', {'currencyPair': market, 'rate': rate, - 'amount': amount, - 'postOnly': postonly, }) - else: - return self.query('sell', {'currencyPair': market, 'rate': rate, - 'amount': amount, }) - - def cancelorder(self, ordernumber): - return self.query('cancelOrder', {'orderNumber': ordernumber}) - - def withdraw(self, currency, quantity, address): - return self.query('withdraw', - {'currency': currency, 'amount': quantity, - 'address': address}) - - def returnfeeinfo(self): - return self.query('returnFeeInfo')