Remove Bittrex and Interface classes
This commit is contained in:
parent
800c327b12
commit
a5f5987450
@ -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,
|
||||
|
@ -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']]
|
@ -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
|
||||
},
|
||||
...
|
||||
"""
|
@ -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]:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user