Merge pull request #619 from gcarq/feature/catch-exchange-errors
granular exception handling and retrying mechanism for ccxt
This commit is contained in:
commit
c72d4665a1
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,7 +1,7 @@
|
|||||||
# Freqtrade rules
|
# Freqtrade rules
|
||||||
freqtrade/tests/testdata/*.json
|
freqtrade/tests/testdata/*.json
|
||||||
hyperopt_conf.py
|
hyperopt_conf.py
|
||||||
config.json
|
config*.json
|
||||||
*.sqlite
|
*.sqlite
|
||||||
.hyperopt
|
.hyperopt
|
||||||
logfile.txt
|
logfile.txt
|
||||||
|
@ -16,9 +16,9 @@ class OperationalException(BaseException):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class NetworkException(BaseException):
|
class TemporaryError(BaseException):
|
||||||
"""
|
"""
|
||||||
Network related error.
|
Temporary network or exchange related error.
|
||||||
This could happen when an exchange is congested, unavailable, or the user
|
This could happen when an exchange is congested, unavailable, or the user
|
||||||
has networking problems. Usually resolves itself after a time.
|
has networking problems. Usually resolves itself after a time.
|
||||||
"""
|
"""
|
||||||
|
@ -8,7 +8,7 @@ from datetime import datetime
|
|||||||
import ccxt
|
import ccxt
|
||||||
import arrow
|
import arrow
|
||||||
|
|
||||||
from freqtrade import OperationalException, DependencyException, NetworkException
|
from freqtrade import OperationalException, DependencyException, TemporaryError
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -16,7 +16,7 @@ logger = logging.getLogger(__name__)
|
|||||||
# Current selected exchange
|
# Current selected exchange
|
||||||
_API: ccxt.Exchange = None
|
_API: ccxt.Exchange = None
|
||||||
|
|
||||||
_CONF: dict = {}
|
_CONF: Dict = {}
|
||||||
API_RETRY_COUNT = 4
|
API_RETRY_COUNT = 4
|
||||||
|
|
||||||
# Holds all open sell orders for dry_run
|
# Holds all open sell orders for dry_run
|
||||||
@ -34,15 +34,16 @@ def retrier(f):
|
|||||||
count = kwargs.pop('count', API_RETRY_COUNT)
|
count = kwargs.pop('count', API_RETRY_COUNT)
|
||||||
try:
|
try:
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
except (NetworkException, DependencyException) as ex:
|
except (TemporaryError, DependencyException) as ex:
|
||||||
logger.warning('%s returned exception: "%s"', f, ex)
|
logger.warning('%s() returned exception: "%s"', f.__name__, ex)
|
||||||
if count > 0:
|
if count > 0:
|
||||||
count -= 1
|
count -= 1
|
||||||
kwargs.update({'count': count})
|
kwargs.update({'count': count})
|
||||||
logger.warning('retrying %s still for %s times', f, count)
|
logger.warning('retrying %s() still for %s times', f.__name__, count)
|
||||||
return wrapper(*args, **kwargs)
|
return wrapper(*args, **kwargs)
|
||||||
else:
|
else:
|
||||||
raise OperationalException('Giving up retrying: %s', f)
|
logger.warning('Giving up retrying: %s()', f.__name__)
|
||||||
|
raise ex
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
@ -153,10 +154,10 @@ def buy(pair: str, rate: float, amount: float) -> Dict:
|
|||||||
'Tried to buy amount {} at rate {} (total {}).'
|
'Tried to buy amount {} at rate {} (total {}).'
|
||||||
'Message: {}'.format(pair, amount, rate, rate*amount, e)
|
'Message: {}'.format(pair, amount, rate, rate*amount, e)
|
||||||
)
|
)
|
||||||
except ccxt.NetworkError as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise NetworkException(
|
raise TemporaryError(
|
||||||
'Could not place buy order due to networking error. Message: {}'.format(e)
|
'Could not place buy order due to {}. Message: {}'.format(
|
||||||
)
|
e.__class__.__name__, e))
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e)
|
raise OperationalException(e)
|
||||||
|
|
||||||
@ -191,23 +192,30 @@ def sell(pair: str, rate: float, amount: float) -> Dict:
|
|||||||
'Tried to sell amount {} at rate {} (total {}).'
|
'Tried to sell amount {} at rate {} (total {}).'
|
||||||
'Message: {}'.format(pair, amount, rate, rate*amount, e)
|
'Message: {}'.format(pair, amount, rate, rate*amount, e)
|
||||||
)
|
)
|
||||||
except ccxt.NetworkError as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise NetworkException(
|
raise TemporaryError(
|
||||||
'Could not place sell order due to networking error. Message: {}'.format(e)
|
'Could not place sell order due to {}. Message: {}'.format(
|
||||||
)
|
e.__class__.__name__, e))
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e)
|
raise OperationalException(e)
|
||||||
|
|
||||||
|
|
||||||
|
@retrier
|
||||||
def get_balance(currency: str) -> float:
|
def get_balance(currency: str) -> float:
|
||||||
if _CONF['dry_run']:
|
if _CONF['dry_run']:
|
||||||
return 999.9
|
return 999.9
|
||||||
|
|
||||||
# ccxt exception is already handled by get_balances
|
# ccxt exception is already handled by get_balances
|
||||||
balances = get_balances()
|
balances = get_balances()
|
||||||
return balances[currency]['free']
|
balance = balances.get(currency)
|
||||||
|
if balance is None:
|
||||||
|
raise TemporaryError(
|
||||||
|
'Could not get {} balance due to malformed exchange response: {}'.format(
|
||||||
|
currency, balances))
|
||||||
|
return balance['free']
|
||||||
|
|
||||||
|
|
||||||
|
@retrier
|
||||||
def get_balances() -> dict:
|
def get_balances() -> dict:
|
||||||
if _CONF['dry_run']:
|
if _CONF['dry_run']:
|
||||||
return {}
|
return {}
|
||||||
@ -221,10 +229,10 @@ def get_balances() -> dict:
|
|||||||
balances.pop("used", None)
|
balances.pop("used", None)
|
||||||
|
|
||||||
return balances
|
return balances
|
||||||
except ccxt.NetworkError as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise NetworkException(
|
raise TemporaryError(
|
||||||
'Could not get balance due to networking error. Message: {}'.format(e)
|
'Could not get balance due to {}. Message: {}'.format(
|
||||||
)
|
e.__class__.__name__, e))
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e)
|
raise OperationalException(e)
|
||||||
|
|
||||||
@ -233,17 +241,17 @@ def get_balances() -> dict:
|
|||||||
def get_tickers() -> Dict:
|
def get_tickers() -> Dict:
|
||||||
try:
|
try:
|
||||||
return _API.fetch_tickers()
|
return _API.fetch_tickers()
|
||||||
except ccxt.NetworkError as e:
|
|
||||||
raise NetworkException(
|
|
||||||
'Could not load tickers due to networking error. Message: {}'.format(e)
|
|
||||||
)
|
|
||||||
except ccxt.BaseError as e:
|
|
||||||
raise OperationalException(e)
|
|
||||||
except ccxt.NotSupported as e:
|
except ccxt.NotSupported as e:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
'Exchange {} does not support fetching tickers in batch.'
|
'Exchange {} does not support fetching tickers in batch.'
|
||||||
'Message: {}'.format(_API.name, e)
|
'Message: {}'.format(_API.name, e)
|
||||||
)
|
)
|
||||||
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
|
raise TemporaryError(
|
||||||
|
'Could not load tickers due to {}. Message: {}'.format(
|
||||||
|
e.__class__.__name__, e))
|
||||||
|
except ccxt.BaseError as e:
|
||||||
|
raise OperationalException(e)
|
||||||
|
|
||||||
|
|
||||||
# TODO: remove refresh argument, keeping it to keep track of where it was intended to be used
|
# TODO: remove refresh argument, keeping it to keep track of where it was intended to be used
|
||||||
@ -251,10 +259,10 @@ def get_tickers() -> Dict:
|
|||||||
def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict:
|
def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict:
|
||||||
try:
|
try:
|
||||||
return _API.fetch_ticker(pair)
|
return _API.fetch_ticker(pair)
|
||||||
except ccxt.NetworkError as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise NetworkException(
|
raise TemporaryError(
|
||||||
'Could not load tickers due to networking error. Message: {}'.format(e)
|
'Could not load ticker history due to {}. Message: {}'.format(
|
||||||
)
|
e.__class__.__name__, e))
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e)
|
raise OperationalException(e)
|
||||||
|
|
||||||
@ -263,37 +271,39 @@ def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict:
|
|||||||
def get_ticker_history(pair: str, tick_interval: str) -> List[Dict]:
|
def get_ticker_history(pair: str, tick_interval: str) -> List[Dict]:
|
||||||
try:
|
try:
|
||||||
return _API.fetch_ohlcv(pair, timeframe=tick_interval)
|
return _API.fetch_ohlcv(pair, timeframe=tick_interval)
|
||||||
except ccxt.NetworkError as e:
|
|
||||||
raise NetworkException(
|
|
||||||
'Could not load ticker history due to networking error. Message: {}'.format(e)
|
|
||||||
)
|
|
||||||
except ccxt.BaseError as e:
|
|
||||||
raise OperationalException('Could not fetch ticker data. Msg: {}'.format(e))
|
|
||||||
except ccxt.NotSupported as e:
|
except ccxt.NotSupported as e:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
'Exchange {} does not support fetching historical candlestick data.'
|
'Exchange {} does not support fetching historical candlestick data.'
|
||||||
'Message: {}'.format(_API.name, e)
|
'Message: {}'.format(_API.name, e)
|
||||||
)
|
)
|
||||||
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
|
raise TemporaryError(
|
||||||
|
'Could not load ticker history due to {}. Message: {}'.format(
|
||||||
|
e.__class__.__name__, e))
|
||||||
|
except ccxt.BaseError as e:
|
||||||
|
raise OperationalException('Could not fetch ticker data. Msg: {}'.format(e))
|
||||||
|
|
||||||
|
|
||||||
|
@retrier
|
||||||
def cancel_order(order_id: str, pair: str) -> None:
|
def cancel_order(order_id: str, pair: str) -> None:
|
||||||
if _CONF['dry_run']:
|
if _CONF['dry_run']:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return _API.cancel_order(order_id, pair)
|
return _API.cancel_order(order_id, pair)
|
||||||
except ccxt.NetworkError as e:
|
|
||||||
raise NetworkException(
|
|
||||||
'Could not get order due to networking error. Message: {}'.format(e)
|
|
||||||
)
|
|
||||||
except ccxt.InvalidOrder as e:
|
except ccxt.InvalidOrder as e:
|
||||||
raise DependencyException(
|
raise DependencyException(
|
||||||
'Could not cancel order. Message: {}'.format(e)
|
'Could not cancel order. Message: {}'.format(e)
|
||||||
)
|
)
|
||||||
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
|
raise TemporaryError(
|
||||||
|
'Could not cancel order due to {}. Message: {}'.format(
|
||||||
|
e.__class__.__name__, e))
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e)
|
raise OperationalException(e)
|
||||||
|
|
||||||
|
|
||||||
|
@retrier
|
||||||
def get_order(order_id: str, pair: str) -> Dict:
|
def get_order(order_id: str, pair: str) -> Dict:
|
||||||
if _CONF['dry_run']:
|
if _CONF['dry_run']:
|
||||||
order = _DRY_RUN_OPEN_ORDERS[order_id]
|
order = _DRY_RUN_OPEN_ORDERS[order_id]
|
||||||
@ -303,18 +313,19 @@ def get_order(order_id: str, pair: str) -> Dict:
|
|||||||
return order
|
return order
|
||||||
try:
|
try:
|
||||||
return _API.fetch_order(order_id, pair)
|
return _API.fetch_order(order_id, pair)
|
||||||
except ccxt.NetworkError as e:
|
|
||||||
raise NetworkException(
|
|
||||||
'Could not get order due to networking error. Message: {}'.format(e)
|
|
||||||
)
|
|
||||||
except ccxt.InvalidOrder as e:
|
except ccxt.InvalidOrder as e:
|
||||||
raise DependencyException(
|
raise DependencyException(
|
||||||
'Could not get order. Message: {}'.format(e)
|
'Could not get order. Message: {}'.format(e)
|
||||||
)
|
)
|
||||||
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
|
raise TemporaryError(
|
||||||
|
'Could not get order due to {}. Message: {}'.format(
|
||||||
|
e.__class__.__name__, e))
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e)
|
raise OperationalException(e)
|
||||||
|
|
||||||
|
|
||||||
|
@retrier
|
||||||
def get_trades_for_order(order_id: str, pair: str, since: datetime) -> List:
|
def get_trades_for_order(order_id: str, pair: str, since: datetime) -> List:
|
||||||
if _CONF['dry_run']:
|
if _CONF['dry_run']:
|
||||||
return []
|
return []
|
||||||
@ -327,7 +338,7 @@ def get_trades_for_order(order_id: str, pair: str, since: datetime) -> List:
|
|||||||
return matched_trades
|
return matched_trades
|
||||||
|
|
||||||
except ccxt.NetworkError as e:
|
except ccxt.NetworkError as e:
|
||||||
raise NetworkException(
|
raise TemporaryError(
|
||||||
'Could not get trades due to networking error. Message: {}'.format(e)
|
'Could not get trades due to networking error. Message: {}'.format(e)
|
||||||
)
|
)
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
@ -345,13 +356,14 @@ def get_pair_detail_url(pair: str) -> str:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
@retrier
|
||||||
def get_markets() -> List[dict]:
|
def get_markets() -> List[dict]:
|
||||||
try:
|
try:
|
||||||
return _API.fetch_markets()
|
return _API.fetch_markets()
|
||||||
except ccxt.NetworkError as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise NetworkException(
|
raise TemporaryError(
|
||||||
'Could not load markets due to networking error. Message: {}'.format(e)
|
'Could not load markets due to {}. Message: {}'.format(
|
||||||
)
|
e.__class__.__name__, e))
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e)
|
raise OperationalException(e)
|
||||||
|
|
||||||
@ -364,14 +376,22 @@ def get_id() -> str:
|
|||||||
return _API.id
|
return _API.id
|
||||||
|
|
||||||
|
|
||||||
|
@retrier
|
||||||
def get_fee(symbol='ETH/BTC', type='', side='', amount=1,
|
def get_fee(symbol='ETH/BTC', type='', side='', amount=1,
|
||||||
price=1, taker_or_maker='maker') -> float:
|
price=1, taker_or_maker='maker') -> float:
|
||||||
# validate that markets are loaded before trying to get fee
|
try:
|
||||||
if _API.markets is None or len(_API.markets) == 0:
|
# validate that markets are loaded before trying to get fee
|
||||||
_API.load_markets()
|
if _API.markets is None or len(_API.markets) == 0:
|
||||||
|
_API.load_markets()
|
||||||
|
|
||||||
return _API.calculate_fee(symbol=symbol, type=type, side=side, amount=amount,
|
return _API.calculate_fee(symbol=symbol, type=type, side=side, amount=amount,
|
||||||
price=price, takerOrMaker=taker_or_maker)['rate']
|
price=price, takerOrMaker=taker_or_maker)['rate']
|
||||||
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
|
raise TemporaryError(
|
||||||
|
'Could not get fee info due to {}. Message: {}'.format(
|
||||||
|
e.__class__.__name__, e))
|
||||||
|
except ccxt.BaseError as e:
|
||||||
|
raise OperationalException(e)
|
||||||
|
|
||||||
|
|
||||||
def get_amount_lots(pair: str, amount: float) -> float:
|
def get_amount_lots(pair: str, amount: float) -> float:
|
||||||
|
@ -3,7 +3,6 @@ Freqtrade is the main module of this bot. It contains the class Freqtrade()
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
@ -15,7 +14,8 @@ import requests
|
|||||||
from cachetools import cached, TTLCache
|
from cachetools import cached, TTLCache
|
||||||
|
|
||||||
from freqtrade import (
|
from freqtrade import (
|
||||||
DependencyException, OperationalException, exchange, persistence, __version__
|
DependencyException, OperationalException, TemporaryError,
|
||||||
|
exchange, persistence, __version__,
|
||||||
)
|
)
|
||||||
from freqtrade.analyze import Analyze
|
from freqtrade.analyze import Analyze
|
||||||
from freqtrade.constants import Constants
|
from freqtrade.constants import Constants
|
||||||
@ -173,7 +173,7 @@ class FreqtradeBot(object):
|
|||||||
self.check_handle_timedout(self.config['unfilledtimeout'])
|
self.check_handle_timedout(self.config['unfilledtimeout'])
|
||||||
Trade.session.flush()
|
Trade.session.flush()
|
||||||
|
|
||||||
except (requests.exceptions.RequestException, json.JSONDecodeError) as error:
|
except TemporaryError as error:
|
||||||
logger.warning('%s, retrying in 30 seconds...', error)
|
logger.warning('%s, retrying in 30 seconds...', error)
|
||||||
time.sleep(Constants.RETRY_TIMEOUT)
|
time.sleep(Constants.RETRY_TIMEOUT)
|
||||||
except OperationalException:
|
except OperationalException:
|
||||||
@ -360,27 +360,30 @@ class FreqtradeBot(object):
|
|||||||
Tries to execute a sell trade
|
Tries to execute a sell trade
|
||||||
:return: True if executed
|
:return: True if executed
|
||||||
"""
|
"""
|
||||||
# Get order details for actual price per unit
|
try:
|
||||||
if trade.open_order_id:
|
# Get order details for actual price per unit
|
||||||
# Update trade with order values
|
if trade.open_order_id:
|
||||||
logger.info('Found open order for %s', trade)
|
# Update trade with order values
|
||||||
order = exchange.get_order(trade.open_order_id, trade.pair)
|
logger.info('Found open order for %s', trade)
|
||||||
# Try update amount (binance-fix)
|
order = exchange.get_order(trade.open_order_id, trade.pair)
|
||||||
try:
|
# Try update amount (binance-fix)
|
||||||
new_amount = self.get_real_amount(trade, order)
|
try:
|
||||||
if order['amount'] != new_amount:
|
new_amount = self.get_real_amount(trade, order)
|
||||||
order['amount'] = new_amount
|
if order['amount'] != new_amount:
|
||||||
# Fee was applied, so set to 0
|
order['amount'] = new_amount
|
||||||
trade.fee_open = 0
|
# Fee was applied, so set to 0
|
||||||
|
trade.fee_open = 0
|
||||||
|
|
||||||
except OperationalException as exception:
|
except OperationalException as exception:
|
||||||
logger.warning("could not update trade amount: %s", exception)
|
logger.warning("could not update trade amount: %s", exception)
|
||||||
|
|
||||||
trade.update(order)
|
trade.update(order)
|
||||||
|
|
||||||
if trade.is_open and trade.open_order_id is None:
|
if trade.is_open and trade.open_order_id is None:
|
||||||
# Check if we can sell our current pair
|
# Check if we can sell our current pair
|
||||||
return self.handle_trade(trade)
|
return self.handle_trade(trade)
|
||||||
|
except DependencyException as exception:
|
||||||
|
logger.warning('Unable to sell trade: %s', exception)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_real_amount(self, trade: Trade, order: Dict) -> float:
|
def get_real_amount(self, trade: Trade, order: Dict) -> float:
|
||||||
@ -482,7 +485,7 @@ class FreqtradeBot(object):
|
|||||||
"""Buy timeout - cancel order
|
"""Buy timeout - cancel order
|
||||||
:return: True if order was fully cancelled
|
:return: True if order was fully cancelled
|
||||||
"""
|
"""
|
||||||
exchange.cancel_order(trade.open_order_id)
|
exchange.cancel_order(trade.open_order_id, trade.pair)
|
||||||
if order['remaining'] == order['amount']:
|
if order['remaining'] == order['amount']:
|
||||||
# if trade is not partially completed, just delete the trade
|
# if trade is not partially completed, just delete the trade
|
||||||
Trade.session.delete(trade)
|
Trade.session.delete(trade)
|
||||||
@ -512,7 +515,7 @@ class FreqtradeBot(object):
|
|||||||
"""
|
"""
|
||||||
if order['remaining'] == order['amount']:
|
if order['remaining'] == order['amount']:
|
||||||
# if trade is not partially completed, just cancel the trade
|
# if trade is not partially completed, just cancel the trade
|
||||||
exchange.cancel_order(trade.open_order_id)
|
exchange.cancel_order(trade.open_order_id, trade.pair)
|
||||||
trade.close_rate = None
|
trade.close_rate = None
|
||||||
trade.close_profit = None
|
trade.close_profit = None
|
||||||
trade.close_date = None
|
trade.close_date = None
|
||||||
|
@ -4,15 +4,15 @@ import logging
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from random import randint
|
from random import randint
|
||||||
from unittest.mock import MagicMock, PropertyMock
|
from unittest.mock import MagicMock, PropertyMock
|
||||||
import ccxt
|
|
||||||
|
|
||||||
|
import ccxt
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from freqtrade import OperationalException, DependencyException, NetworkException
|
import freqtrade.exchange as exchange
|
||||||
|
from freqtrade import OperationalException, DependencyException, TemporaryError
|
||||||
from freqtrade.exchange import (init, validate_pairs, buy, sell, get_balance, get_balances,
|
from freqtrade.exchange import (init, validate_pairs, buy, sell, get_balance, get_balances,
|
||||||
get_ticker, get_ticker_history, cancel_order, get_name, get_fee,
|
get_ticker, get_ticker_history, cancel_order, get_name, get_fee,
|
||||||
get_id, get_pair_detail_url, get_amount_lots)
|
get_id, get_pair_detail_url, get_amount_lots)
|
||||||
import freqtrade.exchange as exchange
|
|
||||||
from freqtrade.tests.conftest import log_has
|
from freqtrade.tests.conftest import log_has
|
||||||
|
|
||||||
API_INIT = False
|
API_INIT = False
|
||||||
@ -149,7 +149,7 @@ def test_buy_prod(default_conf, mocker):
|
|||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
buy(pair='ETH/BTC', rate=200, amount=1)
|
buy(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
|
||||||
with pytest.raises(NetworkException):
|
with pytest.raises(TemporaryError):
|
||||||
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.NetworkError)
|
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
buy(pair='ETH/BTC', rate=200, amount=1)
|
buy(pair='ETH/BTC', rate=200, amount=1)
|
||||||
@ -199,7 +199,7 @@ def test_sell_prod(default_conf, mocker):
|
|||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
sell(pair='ETH/BTC', rate=200, amount=1)
|
sell(pair='ETH/BTC', rate=200, amount=1)
|
||||||
|
|
||||||
with pytest.raises(NetworkException):
|
with pytest.raises(TemporaryError):
|
||||||
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.NetworkError)
|
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
sell(pair='ETH/BTC', rate=200, amount=1)
|
sell(pair='ETH/BTC', rate=200, amount=1)
|
||||||
@ -263,15 +263,17 @@ def test_get_balances_prod(default_conf, mocker):
|
|||||||
assert get_balances()['1ST']['total'] == 10.0
|
assert get_balances()['1ST']['total'] == 10.0
|
||||||
assert get_balances()['1ST']['used'] == 0.0
|
assert get_balances()['1ST']['used'] == 0.0
|
||||||
|
|
||||||
with pytest.raises(NetworkException):
|
with pytest.raises(TemporaryError):
|
||||||
api_mock.fetch_balance = MagicMock(side_effect=ccxt.NetworkError)
|
api_mock.fetch_balance = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
get_balances()
|
get_balances()
|
||||||
|
assert api_mock.fetch_balance.call_count == exchange.API_RETRY_COUNT + 1
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
with pytest.raises(OperationalException):
|
||||||
api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError)
|
api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError)
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
get_balances()
|
get_balances()
|
||||||
|
assert api_mock.fetch_balance.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
# This test is somewhat redundant with
|
# This test is somewhat redundant with
|
||||||
@ -311,7 +313,7 @@ def test_get_ticker(default_conf, mocker):
|
|||||||
assert ticker['bid'] == 0.5
|
assert ticker['bid'] == 0.5
|
||||||
assert ticker['ask'] == 1
|
assert ticker['ask'] == 1
|
||||||
|
|
||||||
with pytest.raises(OperationalException): # test retrier
|
with pytest.raises(TemporaryError): # test retrier
|
||||||
api_mock.fetch_ticker = MagicMock(side_effect=ccxt.NetworkError)
|
api_mock.fetch_ticker = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
get_ticker(pair='ETH/BTC', refresh=True)
|
get_ticker(pair='ETH/BTC', refresh=True)
|
||||||
@ -369,7 +371,7 @@ def test_get_ticker_history(default_conf, mocker):
|
|||||||
assert ticks[0][4] == 9
|
assert ticks[0][4] == 9
|
||||||
assert ticks[0][5] == 10
|
assert ticks[0][5] == 10
|
||||||
|
|
||||||
with pytest.raises(OperationalException): # test retrier
|
with pytest.raises(TemporaryError): # test retrier
|
||||||
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NetworkError)
|
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
# new symbol to get around cache
|
# new symbol to get around cache
|
||||||
@ -398,20 +400,23 @@ def test_cancel_order(default_conf, mocker):
|
|||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
assert cancel_order(order_id='_', pair='TKN/BTC') == 123
|
assert cancel_order(order_id='_', pair='TKN/BTC') == 123
|
||||||
|
|
||||||
with pytest.raises(NetworkException):
|
with pytest.raises(TemporaryError):
|
||||||
api_mock.cancel_order = MagicMock(side_effect=ccxt.NetworkError)
|
api_mock.cancel_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
cancel_order(order_id='_', pair='TKN/BTC')
|
cancel_order(order_id='_', pair='TKN/BTC')
|
||||||
|
assert api_mock.cancel_order.call_count == exchange.API_RETRY_COUNT + 1
|
||||||
|
|
||||||
with pytest.raises(DependencyException):
|
with pytest.raises(DependencyException):
|
||||||
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
cancel_order(order_id='_', pair='TKN/BTC')
|
cancel_order(order_id='_', pair='TKN/BTC')
|
||||||
|
assert api_mock.cancel_order.call_count == exchange.API_RETRY_COUNT + 1
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
with pytest.raises(OperationalException):
|
||||||
api_mock.cancel_order = MagicMock(side_effect=ccxt.BaseError)
|
api_mock.cancel_order = MagicMock(side_effect=ccxt.BaseError)
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
cancel_order(order_id='_', pair='TKN/BTC')
|
cancel_order(order_id='_', pair='TKN/BTC')
|
||||||
|
assert api_mock.cancel_order.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
def test_get_order(default_conf, mocker):
|
def test_get_order(default_conf, mocker):
|
||||||
@ -430,20 +435,23 @@ def test_get_order(default_conf, mocker):
|
|||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
assert exchange.get_order('X', 'TKN/BTC') == 456
|
assert exchange.get_order('X', 'TKN/BTC') == 456
|
||||||
|
|
||||||
with pytest.raises(NetworkException):
|
with pytest.raises(TemporaryError):
|
||||||
api_mock.fetch_order = MagicMock(side_effect=ccxt.NetworkError)
|
api_mock.fetch_order = MagicMock(side_effect=ccxt.NetworkError)
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
exchange.get_order(order_id='_', pair='TKN/BTC')
|
exchange.get_order(order_id='_', pair='TKN/BTC')
|
||||||
|
assert api_mock.fetch_order.call_count == exchange.API_RETRY_COUNT + 1
|
||||||
|
|
||||||
with pytest.raises(DependencyException):
|
with pytest.raises(DependencyException):
|
||||||
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder)
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
exchange.get_order(order_id='_', pair='TKN/BTC')
|
exchange.get_order(order_id='_', pair='TKN/BTC')
|
||||||
|
assert api_mock.fetch_order.call_count == exchange.API_RETRY_COUNT + 1
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
with pytest.raises(OperationalException):
|
||||||
api_mock.fetch_order = MagicMock(side_effect=ccxt.BaseError)
|
api_mock.fetch_order = MagicMock(side_effect=ccxt.BaseError)
|
||||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||||
exchange.get_order(order_id='_', pair='TKN/BTC')
|
exchange.get_order(order_id='_', pair='TKN/BTC')
|
||||||
|
assert api_mock.fetch_order.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
def test_get_name(default_conf, mocker):
|
def test_get_name(default_conf, mocker):
|
||||||
|
@ -6,7 +6,6 @@ import random
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from typing import List
|
from typing import List
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
import pytest
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
@ -18,19 +17,6 @@ from freqtrade.arguments import Arguments
|
|||||||
from freqtrade.optimize.backtesting import Backtesting, start, setup_configuration
|
from freqtrade.optimize.backtesting import Backtesting, start, setup_configuration
|
||||||
from freqtrade.tests.conftest import log_has
|
from freqtrade.tests.conftest import log_has
|
||||||
|
|
||||||
# Avoid to reinit the same object again and again
|
|
||||||
_BACKTESTING = None
|
|
||||||
_BACKTESTING_INITIALIZED = False
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
|
||||||
def init_backtesting(default_conf, mocker):
|
|
||||||
global _BACKTESTING_INITIALIZED, _BACKTESTING
|
|
||||||
if not _BACKTESTING_INITIALIZED:
|
|
||||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
|
||||||
_BACKTESTING = Backtesting(default_conf)
|
|
||||||
_BACKTESTING_INITIALIZED = True
|
|
||||||
|
|
||||||
|
|
||||||
def get_args(args) -> List[str]:
|
def get_args(args) -> List[str]:
|
||||||
return Arguments(args, '').get_parsed_arg()
|
return Arguments(args, '').get_parsed_arg()
|
||||||
@ -96,8 +82,9 @@ def load_data_test(what):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def simple_backtest(config, contour, num_results) -> None:
|
def simple_backtest(config, contour, num_results, mocker) -> None:
|
||||||
backtesting = _BACKTESTING
|
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||||
|
backtesting = Backtesting(config)
|
||||||
|
|
||||||
data = load_data_test(contour)
|
data = load_data_test(contour)
|
||||||
processed = backtesting.tickerdata_to_dataframe(data)
|
processed = backtesting.tickerdata_to_dataframe(data)
|
||||||
@ -128,12 +115,14 @@ def _load_pair_as_ticks(pair, tickfreq):
|
|||||||
|
|
||||||
|
|
||||||
# FIX: fixturize this?
|
# FIX: fixturize this?
|
||||||
def _make_backtest_conf(conf=None, pair='UNITTEST/BTC', record=None):
|
def _make_backtest_conf(mocker, conf=None, pair='UNITTEST/BTC', record=None):
|
||||||
data = optimize.load_data(None, ticker_interval='8m', pairs=[pair])
|
data = optimize.load_data(None, ticker_interval='8m', pairs=[pair])
|
||||||
data = trim_dictlist(data, -200)
|
data = trim_dictlist(data, -200)
|
||||||
|
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||||
|
backtesting = Backtesting(conf)
|
||||||
return {
|
return {
|
||||||
'stake_amount': conf['stake_amount'],
|
'stake_amount': conf['stake_amount'],
|
||||||
'processed': _BACKTESTING.tickerdata_to_dataframe(data),
|
'processed': backtesting.tickerdata_to_dataframe(data),
|
||||||
'max_open_trades': 10,
|
'max_open_trades': 10,
|
||||||
'realistic': True,
|
'realistic': True,
|
||||||
'record': record
|
'record': record
|
||||||
@ -169,21 +158,6 @@ def _trend_alternate(dataframe=None):
|
|||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
|
|
||||||
def _run_backtest_1(fun, backtest_conf):
|
|
||||||
# strategy is a global (hidden as a singleton), so we
|
|
||||||
# emulate strategy being pure, by override/restore here
|
|
||||||
# if we dont do this, the override in strategy will carry over
|
|
||||||
# to other tests
|
|
||||||
old_buy = _BACKTESTING.populate_buy_trend
|
|
||||||
old_sell = _BACKTESTING.populate_sell_trend
|
|
||||||
_BACKTESTING.populate_buy_trend = fun # Override
|
|
||||||
_BACKTESTING.populate_sell_trend = fun # Override
|
|
||||||
results = _BACKTESTING.backtest(backtest_conf)
|
|
||||||
_BACKTESTING.populate_buy_trend = old_buy # restore override
|
|
||||||
_BACKTESTING.populate_sell_trend = old_sell # restore override
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
# Unit tests
|
# Unit tests
|
||||||
def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None:
|
def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None:
|
||||||
"""
|
"""
|
||||||
@ -287,12 +261,13 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_start(mocker, init_backtesting, fee, default_conf, caplog) -> None:
|
def test_start(mocker, fee, default_conf, caplog) -> None:
|
||||||
"""
|
"""
|
||||||
Test start() function
|
Test start() function
|
||||||
"""
|
"""
|
||||||
start_mock = MagicMock()
|
start_mock = MagicMock()
|
||||||
mocker.patch('freqtrade.exchange.get_fee', fee)
|
mocker.patch('freqtrade.exchange.get_fee', fee)
|
||||||
|
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.start', start_mock)
|
mocker.patch('freqtrade.optimize.backtesting.Backtesting.start', start_mock)
|
||||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||||
read_data=json.dumps(default_conf)
|
read_data=json.dumps(default_conf)
|
||||||
@ -342,16 +317,16 @@ def test_backtesting_init(mocker, default_conf) -> None:
|
|||||||
assert callable(backtesting.populate_sell_trend)
|
assert callable(backtesting.populate_sell_trend)
|
||||||
|
|
||||||
|
|
||||||
def test_tickerdata_to_dataframe(init_backtesting, default_conf) -> None:
|
def test_tickerdata_to_dataframe(default_conf, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test Backtesting.tickerdata_to_dataframe() method
|
Test Backtesting.tickerdata_to_dataframe() method
|
||||||
"""
|
"""
|
||||||
|
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||||
timerange = ((None, 'line'), None, -100)
|
timerange = ((None, 'line'), None, -100)
|
||||||
tick = optimize.load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange)
|
tick = optimize.load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange)
|
||||||
tickerlist = {'UNITTEST/BTC': tick}
|
tickerlist = {'UNITTEST/BTC': tick}
|
||||||
|
|
||||||
backtesting = _BACKTESTING
|
backtesting = Backtesting(default_conf)
|
||||||
data = backtesting.tickerdata_to_dataframe(tickerlist)
|
data = backtesting.tickerdata_to_dataframe(tickerlist)
|
||||||
assert len(data['UNITTEST/BTC']) == 100
|
assert len(data['UNITTEST/BTC']) == 100
|
||||||
|
|
||||||
@ -361,11 +336,12 @@ def test_tickerdata_to_dataframe(init_backtesting, default_conf) -> None:
|
|||||||
assert data['UNITTEST/BTC'].equals(data2['UNITTEST/BTC'])
|
assert data['UNITTEST/BTC'].equals(data2['UNITTEST/BTC'])
|
||||||
|
|
||||||
|
|
||||||
def test_get_timeframe(init_backtesting) -> None:
|
def test_get_timeframe(default_conf, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test Backtesting.get_timeframe() method
|
Test Backtesting.get_timeframe() method
|
||||||
"""
|
"""
|
||||||
backtesting = _BACKTESTING
|
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||||
|
backtesting = Backtesting(default_conf)
|
||||||
|
|
||||||
data = backtesting.tickerdata_to_dataframe(
|
data = backtesting.tickerdata_to_dataframe(
|
||||||
optimize.load_data(
|
optimize.load_data(
|
||||||
@ -379,11 +355,12 @@ def test_get_timeframe(init_backtesting) -> None:
|
|||||||
assert max_date.isoformat() == '2017-11-14T22:59:00+00:00'
|
assert max_date.isoformat() == '2017-11-14T22:59:00+00:00'
|
||||||
|
|
||||||
|
|
||||||
def test_generate_text_table(init_backtesting):
|
def test_generate_text_table(default_conf, mocker):
|
||||||
"""
|
"""
|
||||||
Test Backtesting.generate_text_table() method
|
Test Backtesting.generate_text_table() method
|
||||||
"""
|
"""
|
||||||
backtesting = _BACKTESTING
|
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||||
|
backtesting = Backtesting(default_conf)
|
||||||
|
|
||||||
results = pd.DataFrame(
|
results = pd.DataFrame(
|
||||||
{
|
{
|
||||||
@ -451,13 +428,13 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
|
|||||||
assert log_has(line, caplog.record_tuples)
|
assert log_has(line, caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
def test_backtest(init_backtesting, default_conf, fee, mocker) -> None:
|
def test_backtest(default_conf, fee, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test Backtesting.backtest() method
|
Test Backtesting.backtest() method
|
||||||
"""
|
"""
|
||||||
mocker.patch('freqtrade.exchange.get_fee', fee)
|
mocker.patch('freqtrade.exchange.get_fee', fee)
|
||||||
|
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||||
backtesting = _BACKTESTING
|
backtesting = Backtesting(default_conf)
|
||||||
|
|
||||||
data = optimize.load_data(None, ticker_interval='5m', pairs=['UNITTEST/BTC'])
|
data = optimize.load_data(None, ticker_interval='5m', pairs=['UNITTEST/BTC'])
|
||||||
data = trim_dictlist(data, -200)
|
data = trim_dictlist(data, -200)
|
||||||
@ -472,13 +449,13 @@ def test_backtest(init_backtesting, default_conf, fee, mocker) -> None:
|
|||||||
assert not results.empty
|
assert not results.empty
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_1min_ticker_interval(init_backtesting, default_conf, fee, mocker) -> None:
|
def test_backtest_1min_ticker_interval(default_conf, fee, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test Backtesting.backtest() method with 1 min ticker
|
Test Backtesting.backtest() method with 1 min ticker
|
||||||
"""
|
"""
|
||||||
mocker.patch('freqtrade.exchange.get_fee', fee)
|
mocker.patch('freqtrade.exchange.get_fee', fee)
|
||||||
|
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||||
backtesting = _BACKTESTING
|
backtesting = Backtesting(default_conf)
|
||||||
|
|
||||||
# Run a backtesting for an exiting 5min ticker_interval
|
# Run a backtesting for an exiting 5min ticker_interval
|
||||||
data = optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC'])
|
data = optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC'])
|
||||||
@ -494,11 +471,12 @@ def test_backtest_1min_ticker_interval(init_backtesting, default_conf, fee, mock
|
|||||||
assert not results.empty
|
assert not results.empty
|
||||||
|
|
||||||
|
|
||||||
def test_processed(init_backtesting) -> None:
|
def test_processed(default_conf, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Test Backtesting.backtest() method with offline data
|
Test Backtesting.backtest() method with offline data
|
||||||
"""
|
"""
|
||||||
backtesting = _BACKTESTING
|
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||||
|
backtesting = Backtesting(default_conf)
|
||||||
|
|
||||||
dict_of_tickerrows = load_data_test('raise')
|
dict_of_tickerrows = load_data_test('raise')
|
||||||
dataframes = backtesting.tickerdata_to_dataframe(dict_of_tickerrows)
|
dataframes = backtesting.tickerdata_to_dataframe(dict_of_tickerrows)
|
||||||
@ -510,69 +488,90 @@ def test_processed(init_backtesting) -> None:
|
|||||||
assert col in cols
|
assert col in cols
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_pricecontours(init_backtesting, default_conf, fee, mocker) -> None:
|
def test_backtest_pricecontours(default_conf, fee, mocker) -> None:
|
||||||
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
|
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
|
||||||
tests = [['raise', 17], ['lower', 0], ['sine', 17]]
|
tests = [['raise', 17], ['lower', 0], ['sine', 17]]
|
||||||
for [contour, numres] in tests:
|
for [contour, numres] in tests:
|
||||||
simple_backtest(default_conf, contour, numres)
|
simple_backtest(default_conf, contour, numres, mocker)
|
||||||
|
|
||||||
|
|
||||||
# Test backtest using offline data (testdata directory)
|
# Test backtest using offline data (testdata directory)
|
||||||
def test_backtest_ticks(init_backtesting, default_conf, fee, mocker):
|
def test_backtest_ticks(default_conf, fee, mocker):
|
||||||
mocker.patch('freqtrade.exchange.get_fee', fee)
|
mocker.patch('freqtrade.exchange.get_fee', fee)
|
||||||
|
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||||
ticks = [1, 5]
|
ticks = [1, 5]
|
||||||
fun = _BACKTESTING.populate_buy_trend
|
fun = Backtesting(default_conf).populate_buy_trend
|
||||||
for _ in ticks:
|
for _ in ticks:
|
||||||
backtest_conf = _make_backtest_conf(conf=default_conf)
|
backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
|
||||||
results = _run_backtest_1(fun, backtest_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
|
backtesting.populate_buy_trend = fun # Override
|
||||||
|
backtesting.populate_sell_trend = fun # Override
|
||||||
|
results = backtesting.backtest(backtest_conf)
|
||||||
assert not results.empty
|
assert not results.empty
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_clash_buy_sell(init_backtesting, default_conf):
|
def test_backtest_clash_buy_sell(mocker, default_conf):
|
||||||
# Override the default buy trend function in our default_strategy
|
# Override the default buy trend function in our default_strategy
|
||||||
def fun(dataframe=None):
|
def fun(dataframe=None):
|
||||||
buy_value = 1
|
buy_value = 1
|
||||||
sell_value = 1
|
sell_value = 1
|
||||||
return _trend(dataframe, buy_value, sell_value)
|
return _trend(dataframe, buy_value, sell_value)
|
||||||
|
|
||||||
backtest_conf = _make_backtest_conf(conf=default_conf)
|
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||||
results = _run_backtest_1(fun, backtest_conf)
|
backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
|
||||||
|
backtesting = Backtesting(default_conf)
|
||||||
|
backtesting.populate_buy_trend = fun # Override
|
||||||
|
backtesting.populate_sell_trend = fun # Override
|
||||||
|
results = backtesting.backtest(backtest_conf)
|
||||||
assert results.empty
|
assert results.empty
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_only_sell(init_backtesting, default_conf):
|
def test_backtest_only_sell(mocker, default_conf):
|
||||||
# Override the default buy trend function in our default_strategy
|
# Override the default buy trend function in our default_strategy
|
||||||
def fun(dataframe=None):
|
def fun(dataframe=None):
|
||||||
buy_value = 0
|
buy_value = 0
|
||||||
sell_value = 1
|
sell_value = 1
|
||||||
return _trend(dataframe, buy_value, sell_value)
|
return _trend(dataframe, buy_value, sell_value)
|
||||||
|
|
||||||
backtest_conf = _make_backtest_conf(conf=default_conf)
|
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||||
results = _run_backtest_1(fun, backtest_conf)
|
backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
|
||||||
|
backtesting = Backtesting(default_conf)
|
||||||
|
backtesting.populate_buy_trend = fun # Override
|
||||||
|
backtesting.populate_sell_trend = fun # Override
|
||||||
|
results = backtesting.backtest(backtest_conf)
|
||||||
assert results.empty
|
assert results.empty
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_alternate_buy_sell(init_backtesting, default_conf, fee, mocker):
|
def test_backtest_alternate_buy_sell(default_conf, fee, mocker):
|
||||||
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
|
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
|
||||||
backtest_conf = _make_backtest_conf(conf=default_conf, pair='UNITTEST/BTC')
|
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||||
results = _run_backtest_1(_trend_alternate, backtest_conf)
|
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC')
|
||||||
|
backtesting = Backtesting(default_conf)
|
||||||
|
backtesting.populate_buy_trend = _trend_alternate # Override
|
||||||
|
backtesting.populate_sell_trend = _trend_alternate # Override
|
||||||
|
results = backtesting.backtest(backtest_conf)
|
||||||
assert len(results) == 3
|
assert len(results) == 3
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_record(init_backtesting, default_conf, fee, mocker):
|
def test_backtest_record(default_conf, fee, mocker):
|
||||||
names = []
|
names = []
|
||||||
records = []
|
records = []
|
||||||
|
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||||
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
|
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
'freqtrade.optimize.backtesting.file_dump_json',
|
'freqtrade.optimize.backtesting.file_dump_json',
|
||||||
new=lambda n, r: (names.append(n), records.append(r))
|
new=lambda n, r: (names.append(n), records.append(r))
|
||||||
)
|
)
|
||||||
backtest_conf = _make_backtest_conf(
|
backtest_conf = _make_backtest_conf(
|
||||||
|
mocker,
|
||||||
conf=default_conf,
|
conf=default_conf,
|
||||||
pair='UNITTEST/BTC',
|
pair='UNITTEST/BTC',
|
||||||
record="trades"
|
record="trades"
|
||||||
)
|
)
|
||||||
results = _run_backtest_1(_trend_alternate, backtest_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
|
backtesting.populate_buy_trend = _trend_alternate # Override
|
||||||
|
backtesting.populate_sell_trend = _trend_alternate # Override
|
||||||
|
results = backtesting.backtest(backtest_conf)
|
||||||
assert len(results) == 3
|
assert len(results) == 3
|
||||||
# Assert file_dump_json was only called once
|
# Assert file_dump_json was only called once
|
||||||
assert names == ['backtest-result.json']
|
assert names == ['backtest-result.json']
|
||||||
@ -595,7 +594,7 @@ def test_backtest_record(init_backtesting, default_conf, fee, mocker):
|
|||||||
assert dur > 0
|
assert dur > 0
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_start_live(init_backtesting, default_conf, mocker, caplog):
|
def test_backtest_start_live(default_conf, mocker, caplog):
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
|
conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
|
||||||
mocker.patch('freqtrade.exchange.get_ticker_history',
|
mocker.patch('freqtrade.exchange.get_ticker_history',
|
||||||
|
@ -16,7 +16,7 @@ import pytest
|
|||||||
import requests
|
import requests
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
|
|
||||||
from freqtrade import DependencyException, OperationalException
|
from freqtrade import DependencyException, OperationalException, TemporaryError
|
||||||
from freqtrade.freqtradebot import FreqtradeBot
|
from freqtrade.freqtradebot import FreqtradeBot
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
from freqtrade.state import State
|
from freqtrade.state import State
|
||||||
@ -451,7 +451,7 @@ def test_process_exchange_failures(default_conf, ticker, markets, mocker) -> Non
|
|||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
get_ticker=ticker,
|
get_ticker=ticker,
|
||||||
get_markets=markets,
|
get_markets=markets,
|
||||||
buy=MagicMock(side_effect=requests.exceptions.RequestException)
|
buy=MagicMock(side_effect=TemporaryError)
|
||||||
)
|
)
|
||||||
sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None)
|
sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user