From a5f59874501b15b52b30164d9ec77a6758352ba9 Mon Sep 17 00:00:00 2001 From: enenn Date: Fri, 23 Mar 2018 23:38:54 +0100 Subject: [PATCH] Remove Bittrex and Interface classes --- freqtrade/exchange/__init__.py | 9 - freqtrade/exchange/bittrex.py | 211 ------------------ freqtrade/exchange/interface.py | 172 -------------- freqtrade/optimize/backtesting.py | 4 +- freqtrade/tests/test_freqtradebot.py | 5 +- freqtrade/tests/test_persistence.py | 19 +- .../tests/testdata/download_backtest_data.py | 4 +- 7 files changed, 15 insertions(+), 409 deletions(-) delete mode 100644 freqtrade/exchange/bittrex.py delete mode 100644 freqtrade/exchange/interface.py diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 03d69a7ed..5070e89b2 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -10,8 +10,6 @@ import arrow from cachetools import cached, TTLCache from freqtrade import OperationalException, DependencyException, NetworkException -from freqtrade.exchange.bittrex import Bittrex -from freqtrade.exchange.interface import Exchange logger = logging.getLogger(__name__) @@ -25,13 +23,6 @@ _DRY_RUN_OPEN_ORDERS: Dict[str, Any] = {} _TICKER_CACHE: dict = {} -class Exchanges(enum.Enum): - """ - Maps supported exchange names to correspondent classes. - """ - BITTREX = Bittrex - - def init(config: dict) -> None: """ Initializes this module with the given config, diff --git a/freqtrade/exchange/bittrex.py b/freqtrade/exchange/bittrex.py deleted file mode 100644 index 0cba621af..000000000 --- a/freqtrade/exchange/bittrex.py +++ /dev/null @@ -1,211 +0,0 @@ -import logging -from typing import Dict, List, Optional - -from bittrex.bittrex import API_V1_1, API_V2_0 -from bittrex.bittrex import Bittrex as _Bittrex -from requests.exceptions import ContentDecodingError - -from freqtrade import OperationalException -from freqtrade.exchange.interface import Exchange - -logger = logging.getLogger(__name__) - -_API: _Bittrex = None -_API_V2: _Bittrex = None -_EXCHANGE_CONF: dict = {} - - -class Bittrex(Exchange): - """ - Bittrex API wrapper. - """ - # Base URL and API endpoints - BASE_URL: str = 'https://www.bittrex.com' - PAIR_DETAIL_METHOD: str = BASE_URL + '/Market/Index' - - def __init__(self, config: dict) -> None: - global _API, _API_V2, _EXCHANGE_CONF - - _EXCHANGE_CONF.update(config) - _API = _Bittrex( - api_key=_EXCHANGE_CONF['key'], - api_secret=_EXCHANGE_CONF['secret'], - calls_per_second=1, - api_version=API_V1_1, - ) - _API_V2 = _Bittrex( - api_key=_EXCHANGE_CONF['key'], - api_secret=_EXCHANGE_CONF['secret'], - calls_per_second=1, - api_version=API_V2_0, - ) - self.cached_ticker = {} - - @staticmethod - def _validate_response(response) -> None: - """ - Validates the given bittrex response - and raises a ContentDecodingError if a non-fatal issue happened. - """ - temp_error_messages = [ - 'NO_API_RESPONSE', - 'MIN_TRADE_REQUIREMENT_NOT_MET', - ] - if response['message'] in temp_error_messages: - raise ContentDecodingError(response['message']) - - @property - def fee(self) -> float: - # 0.25 %: See https://bittrex.com/fees - return 0.0025 - - def buy(self, pair: str, rate: float, amount: float) -> str: - data = _API.buy_limit(pair.replace('_', '-'), amount, rate) - if not data['success']: - Bittrex._validate_response(data) - raise OperationalException('{message} params=({pair}, {rate}, {amount})'.format( - message=data['message'], - pair=pair, - rate=rate, - amount=amount)) - return data['result']['uuid'] - - def sell(self, pair: str, rate: float, amount: float) -> str: - data = _API.sell_limit(pair.replace('_', '-'), amount, rate) - if not data['success']: - Bittrex._validate_response(data) - raise OperationalException('{message} params=({pair}, {rate}, {amount})'.format( - message=data['message'], - pair=pair, - rate=rate, - amount=amount)) - return data['result']['uuid'] - - def get_balance(self, currency: str) -> float: - data = _API.get_balance(currency) - if not data['success']: - Bittrex._validate_response(data) - raise OperationalException('{message} params=({currency})'.format( - message=data['message'], - currency=currency)) - return float(data['result']['Balance'] or 0.0) - - def get_balances(self): - data = _API.get_balances() - if not data['success']: - Bittrex._validate_response(data) - raise OperationalException('{message}'.format(message=data['message'])) - return data['result'] - - def get_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict: - if refresh or pair not in self.cached_ticker.keys(): - data = _API.get_ticker(pair.replace('_', '-')) - if not data['success']: - Bittrex._validate_response(data) - raise OperationalException('{message} params=({pair})'.format( - message=data['message'], - pair=pair)) - keys = ['Bid', 'Ask', 'Last'] - if not data.get('result') or\ - not all(key in data.get('result', {}) for key in keys) or\ - not all(data.get('result', {})[key] is not None for key in keys): - raise ContentDecodingError('Invalid response from Bittrex params=({pair})'.format( - pair=pair)) - # Update the pair - self.cached_ticker[pair] = { - 'bid': float(data['result']['Bid']), - 'ask': float(data['result']['Ask']), - 'last': float(data['result']['Last']), - } - return self.cached_ticker[pair] - - def get_ticker_history(self, pair: str, tick_interval: int) -> List[Dict]: - if tick_interval == 1: - interval = 'oneMin' - elif tick_interval == 5: - interval = 'fiveMin' - elif tick_interval == 30: - interval = 'thirtyMin' - elif tick_interval == 60: - interval = 'hour' - elif tick_interval == 1440: - interval = 'Day' - else: - raise ValueError('Unknown tick_interval: {}'.format(tick_interval)) - - data = _API_V2.get_candles(pair.replace('_', '-'), interval) - - # These sanity check are necessary because bittrex cannot keep their API stable. - if not data.get('result'): - raise ContentDecodingError('Invalid response from Bittrex params=({pair})'.format( - pair=pair)) - - for prop in ['C', 'V', 'O', 'H', 'L', 'T']: - for tick in data['result']: - if prop not in tick.keys(): - raise ContentDecodingError('Required property {} not present ' - 'in response params=({})'.format(prop, pair)) - - if not data['success']: - Bittrex._validate_response(data) - raise OperationalException('{message} params=({pair})'.format( - message=data['message'], - pair=pair)) - - return data['result'] - - def get_order(self, order_id: str) -> Dict: - data = _API.get_order(order_id) - if not data['success']: - Bittrex._validate_response(data) - raise OperationalException('{message} params=({order_id})'.format( - message=data['message'], - order_id=order_id)) - data = data['result'] - return { - 'id': data['OrderUuid'], - 'type': data['Type'], - 'pair': data['Exchange'].replace('-', '_'), - 'opened': data['Opened'], - 'rate': data['PricePerUnit'], - 'amount': data['Quantity'], - 'remaining': data['QuantityRemaining'], - 'closed': data['Closed'], - } - - def cancel_order(self, order_id: str) -> None: - data = _API.cancel(order_id) - if not data['success']: - Bittrex._validate_response(data) - raise OperationalException('{message} params=({order_id})'.format( - message=data['message'], - order_id=order_id)) - - def get_pair_detail_url(self, pair: str) -> str: - return self.PAIR_DETAIL_METHOD + '?MarketName={}'.format(pair.replace('_', '-')) - - def get_markets(self) -> List[str]: - data = _API.get_markets() - if not data['success']: - Bittrex._validate_response(data) - raise OperationalException(data['message']) - return [m['MarketName'].replace('-', '_') for m in data['result']] - - def get_market_summaries(self) -> List[Dict]: - data = _API.get_market_summaries() - if not data['success']: - Bittrex._validate_response(data) - raise OperationalException(data['message']) - return data['result'] - - def get_wallet_health(self) -> List[Dict]: - data = _API_V2.get_wallet_health() - if not data['success']: - Bittrex._validate_response(data) - raise OperationalException(data['message']) - return [{ - 'Currency': entry['Health']['Currency'], - 'IsActive': entry['Health']['IsActive'], - 'LastChecked': entry['Health']['LastChecked'], - 'Notice': entry['Currency'].get('Notice'), - } for entry in data['result']] diff --git a/freqtrade/exchange/interface.py b/freqtrade/exchange/interface.py deleted file mode 100644 index 6121a98b3..000000000 --- a/freqtrade/exchange/interface.py +++ /dev/null @@ -1,172 +0,0 @@ -from abc import ABC, abstractmethod -from typing import Dict, List, Optional - - -class Exchange(ABC): - @property - def name(self) -> str: - """ - Name of the exchange. - :return: str representation of the class name - """ - return self.__class__.__name__ - - @property - def fee(self) -> float: - """ - Fee for placing an order - :return: percentage in float - """ - - @abstractmethod - def buy(self, pair: str, rate: float, amount: float) -> str: - """ - Places a limit buy order. - :param pair: Pair as str, format: BTC_ETH - :param rate: Rate limit for order - :param amount: The amount to purchase - :return: order_id of the placed buy order - """ - - @abstractmethod - def sell(self, pair: str, rate: float, amount: float) -> str: - """ - Places a limit sell order. - :param pair: Pair as str, format: BTC_ETH - :param rate: Rate limit for order - :param amount: The amount to sell - :return: order_id of the placed sell order - """ - - @abstractmethod - def get_balance(self, currency: str) -> float: - """ - Gets account balance. - :param currency: Currency as str, format: BTC - :return: float - """ - - @abstractmethod - def get_balances(self) -> List[dict]: - """ - Gets account balances across currencies - :return: List of dicts, format: [ - { - 'Currency': str, - 'Balance': float, - 'Available': float, - 'Pending': float, - } - ... - ] - """ - - @abstractmethod - def get_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict: - """ - Gets ticker for given pair. - :param pair: Pair as str, format: BTC_ETC - :param refresh: Shall we query a new value or a cached value is enough - :return: dict, format: { - 'bid': float, - 'ask': float, - 'last': float - } - """ - - @abstractmethod - def get_ticker_history(self, pair: str, tick_interval: int) -> List[Dict]: - """ - Gets ticker history for given pair. - :param pair: Pair as str, format: BTC_ETC - :param tick_interval: ticker interval in minutes - :return: list, format: [ - { - 'O': float, (Open) - 'H': float, (High) - 'L': float, (Low) - 'C': float, (Close) - 'V': float, (Volume) - 'T': datetime, (Time) - 'BV': float, (Base Volume) - }, - ... - ] - """ - - def get_order(self, order_id: str) -> Dict: - """ - Get order details for the given order_id. - :param order_id: ID as str - :return: dict, format: { - 'id': str, - 'type': str, - 'pair': str, - 'opened': str ISO 8601 datetime, - 'closed': str ISO 8601 datetime, - 'rate': float, - 'amount': float, - 'remaining': int - } - """ - - @abstractmethod - def cancel_order(self, order_id: str) -> None: - """ - Cancels order for given order_id. - :param order_id: ID as str - :return: None - """ - - @abstractmethod - def get_pair_detail_url(self, pair: str) -> str: - """ - Returns the market detail url for the given pair. - :param pair: Pair as str, format: BTC_ETC - :return: URL as str - """ - - @abstractmethod - def get_markets(self) -> List[str]: - """ - Returns all available markets. - :return: List of all available pairs - """ - - @abstractmethod - def get_market_summaries(self) -> List[Dict]: - """ - Returns a 24h market summary for all available markets - :return: list, format: [ - { - 'MarketName': str, - 'High': float, - 'Low': float, - 'Volume': float, - 'Last': float, - 'TimeStamp': datetime, - 'BaseVolume': float, - 'Bid': float, - 'Ask': float, - 'OpenBuyOrders': int, - 'OpenSellOrders': int, - 'PrevDay': float, - 'Created': datetime - }, - ... - ] - """ - - @abstractmethod - def get_wallet_health(self) -> List[Dict]: - """ - Returns a list of all wallet health information - :return: list, format: [ - { - 'Currency': str, - 'IsActive': bool, - 'LastChecked': str, - 'Notice': str - }, - ... - """ diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index d8af47326..a3edfaa7d 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -6,6 +6,7 @@ This module contains the backtesting logic from argparse import Namespace from typing import Dict, Tuple, Any, List, Optional +import ccxt import arrow from pandas import DataFrame, Series from tabulate import tabulate @@ -15,7 +16,6 @@ from freqtrade import exchange from freqtrade.analyze import Analyze from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration -from freqtrade.exchange import Bittrex from freqtrade.logger import Logger from freqtrade.misc import file_dump_json from freqtrade.persistence import Trade @@ -52,7 +52,7 @@ class Backtesting(object): self.tickerdata_to_dataframe = self.analyze.tickerdata_to_dataframe self.populate_buy_trend = self.analyze.populate_buy_trend self.populate_sell_trend = self.analyze.populate_sell_trend - exchange._API = Bittrex({'key': '', 'secret': ''}) + exchange._API = ccxt.bittrex({'key': '', 'secret': ''}) @staticmethod def get_timeframe(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]: diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index cd271d273..caf07868c 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -17,7 +17,6 @@ import requests from sqlalchemy import create_engine from freqtrade import DependencyException, OperationalException -from freqtrade.exchange import Exchanges from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade from freqtrade.state import State @@ -261,7 +260,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, mocker) -> None: assert trade.stake_amount == 0.001 assert trade.is_open assert trade.open_date is not None - assert trade.exchange == Exchanges.BITTREX.name + assert trade.exchange == 'bittrex' # Simulate fulfilled LIMIT_BUY order for trade trade.update(limit_buy_order) @@ -423,7 +422,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order, assert trade.stake_amount == default_conf['stake_amount'] assert trade.is_open assert trade.open_date is not None - assert trade.exchange == Exchanges.BITTREX.name + assert trade.exchange == 'bittrex' assert trade.open_rate == 0.00001099 assert trade.amount == 90.99181073703367 diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index 70199b12a..adf1b37f3 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -4,7 +4,6 @@ import os import pytest from sqlalchemy import create_engine -from freqtrade.exchange import Exchanges from freqtrade.persistence import Trade, init, clean_dry_run_db @@ -122,7 +121,7 @@ def test_update_with_bittrex(limit_buy_order, limit_sell_order): pair='BTC_ETH', stake_amount=0.001, fee=0.0025, - exchange=Exchanges.BITTREX, + exchange='bittrex', ) assert trade.open_order_id is None assert trade.open_rate is None @@ -149,7 +148,7 @@ def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order): pair='BTC_ETH', stake_amount=0.001, fee=0.0025, - exchange=Exchanges.BITTREX, + exchange='bittrex', ) trade.open_order_id = 'something' @@ -171,7 +170,7 @@ def test_calc_close_trade_price_exception(limit_buy_order): pair='BTC_ETH', stake_amount=0.001, fee=0.0025, - exchange=Exchanges.BITTREX, + exchange='bittrex', ) trade.open_order_id = 'something' @@ -184,7 +183,7 @@ def test_update_open_order(limit_buy_order): pair='BTC_ETH', stake_amount=1.00, fee=0.1, - exchange=Exchanges.BITTREX, + exchange='bittrex', ) assert trade.open_order_id is None @@ -206,7 +205,7 @@ def test_update_invalid_order(limit_buy_order): pair='BTC_ETH', stake_amount=1.00, fee=0.1, - exchange=Exchanges.BITTREX, + exchange='bittrex', ) limit_buy_order['type'] = 'invalid' with pytest.raises(ValueError, match=r'Unknown order type'): @@ -218,7 +217,7 @@ def test_calc_open_trade_price(limit_buy_order): pair='BTC_ETH', stake_amount=0.001, fee=0.0025, - exchange=Exchanges.BITTREX, + exchange='bittrex', ) trade.open_order_id = 'open_trade' trade.update(limit_buy_order) # Buy @ 0.00001099 @@ -235,7 +234,7 @@ def test_calc_close_trade_price(limit_buy_order, limit_sell_order): pair='BTC_ETH', stake_amount=0.001, fee=0.0025, - exchange=Exchanges.BITTREX, + exchange='bittrex', ) trade.open_order_id = 'close_trade' trade.update(limit_buy_order) # Buy @ 0.00001099 @@ -256,7 +255,7 @@ def test_calc_profit(limit_buy_order, limit_sell_order): pair='BTC_ETH', stake_amount=0.001, fee=0.0025, - exchange=Exchanges.BITTREX, + exchange='bittrex', ) trade.open_order_id = 'profit_percent' trade.update(limit_buy_order) # Buy @ 0.00001099 @@ -286,7 +285,7 @@ def test_calc_profit_percent(limit_buy_order, limit_sell_order): pair='BTC_ETH', stake_amount=0.001, fee=0.0025, - exchange=Exchanges.BITTREX, + exchange='bittrex', ) trade.open_order_id = 'profit_percent' trade.update(limit_buy_order) # Buy @ 0.00001099 diff --git a/freqtrade/tests/testdata/download_backtest_data.py b/freqtrade/tests/testdata/download_backtest_data.py index ceb8388a1..46efe4511 100755 --- a/freqtrade/tests/testdata/download_backtest_data.py +++ b/freqtrade/tests/testdata/download_backtest_data.py @@ -6,7 +6,7 @@ import sys from freqtrade import exchange from freqtrade import misc -from freqtrade.exchange import Bittrex +from freqtrade.exchange import ccxt parser = misc.common_args_parser('download utility') parser.add_argument( @@ -28,7 +28,7 @@ PAIRS = list(set(PAIRS)) print('About to download pairs:', PAIRS) # Init Bittrex exchange -exchange._API = Bittrex({'key': '', 'secret': ''}) +exchange._API = ccxt.bittrex({'key': '', 'secret': ''}) for pair in PAIRS: for tick_interval in TICKER_INTERVALS: