Remove Bittrex and Interface classes

This commit is contained in:
enenn 2018-03-23 23:38:54 +01:00
parent 800c327b12
commit a5f5987450
7 changed files with 15 additions and 409 deletions

View File

@ -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,

View File

@ -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']]

View File

@ -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
},
...
"""

View File

@ -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]:

View File

@ -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

View File

@ -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

View File

@ -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: