Merge pull request #3531 from freqtrade/exchange_errorhandling
Improve exchange errorhandling and API backoff
This commit is contained in:
commit
8a2f631ddd
@ -10,7 +10,7 @@ from typing import Any, Dict, List, Optional
|
|||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade.data.history import load_pair_history
|
from freqtrade.data.history import load_pair_history
|
||||||
from freqtrade.exceptions import DependencyException, OperationalException
|
from freqtrade.exceptions import ExchangeError, OperationalException
|
||||||
from freqtrade.exchange import Exchange
|
from freqtrade.exchange import Exchange
|
||||||
from freqtrade.state import RunMode
|
from freqtrade.state import RunMode
|
||||||
from freqtrade.constants import ListPairsWithTimeframes
|
from freqtrade.constants import ListPairsWithTimeframes
|
||||||
@ -105,7 +105,7 @@ class DataProvider:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return self._exchange.fetch_ticker(pair)
|
return self._exchange.fetch_ticker(pair)
|
||||||
except DependencyException:
|
except ExchangeError:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def orderbook(self, pair: str, maximum: int) -> Dict[str, List]:
|
def orderbook(self, pair: str, maximum: int) -> Dict[str, List]:
|
||||||
|
@ -37,7 +37,21 @@ class InvalidOrderException(FreqtradeException):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class TemporaryError(FreqtradeException):
|
class RetryableOrderError(InvalidOrderException):
|
||||||
|
"""
|
||||||
|
This is returned when the order is not found.
|
||||||
|
This Error will be repeated with increasing backof (in line with DDosError).
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ExchangeError(DependencyException):
|
||||||
|
"""
|
||||||
|
Error raised out of the exchange.
|
||||||
|
Has multiple Errors to determine the appropriate error.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class TemporaryError(ExchangeError):
|
||||||
"""
|
"""
|
||||||
Temporary network or exchange 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
|
||||||
@ -45,6 +59,13 @@ class TemporaryError(FreqtradeException):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class DDosProtection(TemporaryError):
|
||||||
|
"""
|
||||||
|
Temporary error caused by DDOS protection.
|
||||||
|
Bot will wait for a second and then retry.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class StrategyError(FreqtradeException):
|
class StrategyError(FreqtradeException):
|
||||||
"""
|
"""
|
||||||
Errors with custom user-code deteced.
|
Errors with custom user-code deteced.
|
||||||
|
@ -4,9 +4,11 @@ from typing import Dict
|
|||||||
|
|
||||||
import ccxt
|
import ccxt
|
||||||
|
|
||||||
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
|
from freqtrade.exceptions import (DDosProtection, ExchangeError,
|
||||||
OperationalException, TemporaryError)
|
InvalidOrderException, OperationalException,
|
||||||
|
TemporaryError)
|
||||||
from freqtrade.exchange import Exchange
|
from freqtrade.exchange import Exchange
|
||||||
|
from freqtrade.exchange.common import retrier
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -39,6 +41,7 @@ class Binance(Exchange):
|
|||||||
"""
|
"""
|
||||||
return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice'])
|
return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice'])
|
||||||
|
|
||||||
|
@retrier(retries=0)
|
||||||
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
|
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
|
||||||
"""
|
"""
|
||||||
creates a stoploss limit order.
|
creates a stoploss limit order.
|
||||||
@ -77,7 +80,7 @@ class Binance(Exchange):
|
|||||||
'stop price: %s. limit: %s', pair, stop_price, rate)
|
'stop price: %s. limit: %s', pair, stop_price, rate)
|
||||||
return order
|
return order
|
||||||
except ccxt.InsufficientFunds as e:
|
except ccxt.InsufficientFunds as e:
|
||||||
raise DependencyException(
|
raise ExchangeError(
|
||||||
f'Insufficient funds to create {ordertype} sell order on market {pair}.'
|
f'Insufficient funds to create {ordertype} sell order on market {pair}.'
|
||||||
f'Tried to sell amount {amount} at rate {rate}. '
|
f'Tried to sell amount {amount} at rate {rate}. '
|
||||||
f'Message: {e}') from e
|
f'Message: {e}') from e
|
||||||
@ -88,6 +91,8 @@ class Binance(Exchange):
|
|||||||
f'Could not create {ordertype} sell order on market {pair}. '
|
f'Could not create {ordertype} sell order on market {pair}. '
|
||||||
f'Tried to sell amount {amount} at rate {rate}. '
|
f'Tried to sell amount {amount} at rate {rate}. '
|
||||||
f'Message: {e}') from e
|
f'Message: {e}') from e
|
||||||
|
except ccxt.DDoSProtection as e:
|
||||||
|
raise DDosProtection(e) from e
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e
|
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
from freqtrade.exceptions import TemporaryError
|
from freqtrade.exceptions import (DDosProtection, RetryableOrderError,
|
||||||
|
TemporaryError)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -88,6 +92,13 @@ MAP_EXCHANGE_CHILDCLASS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_backoff(retrycount, max_retries):
|
||||||
|
"""
|
||||||
|
Calculate backoff
|
||||||
|
"""
|
||||||
|
return (max_retries - retrycount) ** 2 + 1
|
||||||
|
|
||||||
|
|
||||||
def retrier_async(f):
|
def retrier_async(f):
|
||||||
async def wrapper(*args, **kwargs):
|
async def wrapper(*args, **kwargs):
|
||||||
count = kwargs.pop('count', API_RETRY_COUNT)
|
count = kwargs.pop('count', API_RETRY_COUNT)
|
||||||
@ -99,6 +110,10 @@ def retrier_async(f):
|
|||||||
count -= 1
|
count -= 1
|
||||||
kwargs.update({'count': count})
|
kwargs.update({'count': count})
|
||||||
logger.warning('retrying %s() still for %s times', f.__name__, count)
|
logger.warning('retrying %s() still for %s times', f.__name__, count)
|
||||||
|
if isinstance(ex, DDosProtection):
|
||||||
|
backoff_delay = calculate_backoff(count + 1, API_RETRY_COUNT)
|
||||||
|
logger.debug(f"Applying DDosProtection backoff delay: {backoff_delay}")
|
||||||
|
await asyncio.sleep(backoff_delay)
|
||||||
return await wrapper(*args, **kwargs)
|
return await wrapper(*args, **kwargs)
|
||||||
else:
|
else:
|
||||||
logger.warning('Giving up retrying: %s()', f.__name__)
|
logger.warning('Giving up retrying: %s()', f.__name__)
|
||||||
@ -106,19 +121,31 @@ def retrier_async(f):
|
|||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
def retrier(f):
|
def retrier(_func=None, retries=API_RETRY_COUNT):
|
||||||
def wrapper(*args, **kwargs):
|
def decorator(f):
|
||||||
count = kwargs.pop('count', API_RETRY_COUNT)
|
@wraps(f)
|
||||||
try:
|
def wrapper(*args, **kwargs):
|
||||||
return f(*args, **kwargs)
|
count = kwargs.pop('count', retries)
|
||||||
except TemporaryError as ex:
|
try:
|
||||||
logger.warning('%s() returned exception: "%s"', f.__name__, ex)
|
return f(*args, **kwargs)
|
||||||
if count > 0:
|
except (TemporaryError, RetryableOrderError) as ex:
|
||||||
count -= 1
|
logger.warning('%s() returned exception: "%s"', f.__name__, ex)
|
||||||
kwargs.update({'count': count})
|
if count > 0:
|
||||||
logger.warning('retrying %s() still for %s times', f.__name__, count)
|
count -= 1
|
||||||
return wrapper(*args, **kwargs)
|
kwargs.update({'count': count})
|
||||||
else:
|
logger.warning('retrying %s() still for %s times', f.__name__, count)
|
||||||
logger.warning('Giving up retrying: %s()', f.__name__)
|
if isinstance(ex, DDosProtection) or isinstance(ex, RetryableOrderError):
|
||||||
raise ex
|
# increasing backoff
|
||||||
return wrapper
|
backoff_delay = calculate_backoff(count + 1, retries)
|
||||||
|
logger.debug(f"Applying DDosProtection backoff delay: {backoff_delay}")
|
||||||
|
time.sleep(backoff_delay)
|
||||||
|
return wrapper(*args, **kwargs)
|
||||||
|
else:
|
||||||
|
logger.warning('Giving up retrying: %s()', f.__name__)
|
||||||
|
raise ex
|
||||||
|
return wrapper
|
||||||
|
# Support both @retrier and @retrier(retries=2) syntax
|
||||||
|
if _func is None:
|
||||||
|
return decorator
|
||||||
|
else:
|
||||||
|
return decorator(_func)
|
||||||
|
@ -18,12 +18,13 @@ from ccxt.base.decimal_to_precision import (ROUND_DOWN, ROUND_UP, TICK_SIZE,
|
|||||||
TRUNCATE, decimal_to_precision)
|
TRUNCATE, decimal_to_precision)
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
|
from freqtrade.constants import ListPairsWithTimeframes
|
||||||
from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list
|
from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list
|
||||||
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
|
from freqtrade.exceptions import (DDosProtection, ExchangeError,
|
||||||
OperationalException, TemporaryError)
|
InvalidOrderException, OperationalException,
|
||||||
|
RetryableOrderError, TemporaryError)
|
||||||
from freqtrade.exchange.common import BAD_EXCHANGES, retrier, retrier_async
|
from freqtrade.exchange.common import BAD_EXCHANGES, retrier, retrier_async
|
||||||
from freqtrade.misc import deep_merge_dicts, safe_value_fallback
|
from freqtrade.misc import deep_merge_dicts, safe_value_fallback
|
||||||
from freqtrade.constants import ListPairsWithTimeframes
|
|
||||||
|
|
||||||
CcxtModuleType = Any
|
CcxtModuleType = Any
|
||||||
|
|
||||||
@ -351,7 +352,7 @@ class Exchange:
|
|||||||
for pair in [f"{curr_1}/{curr_2}", f"{curr_2}/{curr_1}"]:
|
for pair in [f"{curr_1}/{curr_2}", f"{curr_2}/{curr_1}"]:
|
||||||
if pair in self.markets and self.markets[pair].get('active'):
|
if pair in self.markets and self.markets[pair].get('active'):
|
||||||
return pair
|
return pair
|
||||||
raise DependencyException(f"Could not combine {curr_1} and {curr_2} to get a valid pair.")
|
raise ExchangeError(f"Could not combine {curr_1} and {curr_2} to get a valid pair.")
|
||||||
|
|
||||||
def validate_timeframes(self, timeframe: Optional[str]) -> None:
|
def validate_timeframes(self, timeframe: Optional[str]) -> None:
|
||||||
"""
|
"""
|
||||||
@ -518,15 +519,17 @@ class Exchange:
|
|||||||
amount, rate_for_order, params)
|
amount, rate_for_order, params)
|
||||||
|
|
||||||
except ccxt.InsufficientFunds as e:
|
except ccxt.InsufficientFunds as e:
|
||||||
raise DependencyException(
|
raise ExchangeError(
|
||||||
f'Insufficient funds to create {ordertype} {side} order on market {pair}.'
|
f'Insufficient funds to create {ordertype} {side} order on market {pair}.'
|
||||||
f'Tried to {side} amount {amount} at rate {rate}.'
|
f'Tried to {side} amount {amount} at rate {rate}.'
|
||||||
f'Message: {e}') from e
|
f'Message: {e}') from e
|
||||||
except ccxt.InvalidOrder as e:
|
except ccxt.InvalidOrder as e:
|
||||||
raise DependencyException(
|
raise ExchangeError(
|
||||||
f'Could not create {ordertype} {side} order on market {pair}.'
|
f'Could not create {ordertype} {side} order on market {pair}.'
|
||||||
f'Tried to {side} amount {amount} at rate {rate}.'
|
f'Tried to {side} amount {amount} at rate {rate}.'
|
||||||
f'Message: {e}') from e
|
f'Message: {e}') from e
|
||||||
|
except ccxt.DDoSProtection as e:
|
||||||
|
raise DDosProtection(e) from e
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
f'Could not place {side} order due to {e.__class__.__name__}. Message: {e}') from e
|
f'Could not place {side} order due to {e.__class__.__name__}. Message: {e}') from e
|
||||||
@ -606,6 +609,8 @@ class Exchange:
|
|||||||
balances.pop("used", None)
|
balances.pop("used", None)
|
||||||
|
|
||||||
return balances
|
return balances
|
||||||
|
except ccxt.DDoSProtection as e:
|
||||||
|
raise DDosProtection(e) from e
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
f'Could not get balance due to {e.__class__.__name__}. Message: {e}') from e
|
f'Could not get balance due to {e.__class__.__name__}. Message: {e}') from e
|
||||||
@ -620,6 +625,8 @@ class Exchange:
|
|||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
f'Exchange {self._api.name} does not support fetching tickers in batch. '
|
f'Exchange {self._api.name} does not support fetching tickers in batch. '
|
||||||
f'Message: {e}') from e
|
f'Message: {e}') from e
|
||||||
|
except ccxt.DDoSProtection as e:
|
||||||
|
raise DDosProtection(e) from e
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
f'Could not load tickers due to {e.__class__.__name__}. Message: {e}') from e
|
f'Could not load tickers due to {e.__class__.__name__}. Message: {e}') from e
|
||||||
@ -630,9 +637,11 @@ class Exchange:
|
|||||||
def fetch_ticker(self, pair: str) -> dict:
|
def fetch_ticker(self, pair: str) -> dict:
|
||||||
try:
|
try:
|
||||||
if pair not in self._api.markets or not self._api.markets[pair].get('active'):
|
if pair not in self._api.markets or not self._api.markets[pair].get('active'):
|
||||||
raise DependencyException(f"Pair {pair} not available")
|
raise ExchangeError(f"Pair {pair} not available")
|
||||||
data = self._api.fetch_ticker(pair)
|
data = self._api.fetch_ticker(pair)
|
||||||
return data
|
return data
|
||||||
|
except ccxt.DDoSProtection as e:
|
||||||
|
raise DDosProtection(e) from e
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
f'Could not load ticker due to {e.__class__.__name__}. Message: {e}') from e
|
f'Could not load ticker due to {e.__class__.__name__}. Message: {e}') from e
|
||||||
@ -766,6 +775,8 @@ class Exchange:
|
|||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
f'Exchange {self._api.name} does not support fetching historical '
|
f'Exchange {self._api.name} does not support fetching historical '
|
||||||
f'candle (OHLCV) data. Message: {e}') from e
|
f'candle (OHLCV) data. Message: {e}') from e
|
||||||
|
except ccxt.DDoSProtection as e:
|
||||||
|
raise DDosProtection(e) from e
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(f'Could not fetch historical candle (OHLCV) data '
|
raise TemporaryError(f'Could not fetch historical candle (OHLCV) data '
|
||||||
f'for pair {pair} due to {e.__class__.__name__}. '
|
f'for pair {pair} due to {e.__class__.__name__}. '
|
||||||
@ -802,6 +813,8 @@ class Exchange:
|
|||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
f'Exchange {self._api.name} does not support fetching historical trade data.'
|
f'Exchange {self._api.name} does not support fetching historical trade data.'
|
||||||
f'Message: {e}') from e
|
f'Message: {e}') from e
|
||||||
|
except ccxt.DDoSProtection as e:
|
||||||
|
raise DDosProtection(e) from e
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(f'Could not load trade history due to {e.__class__.__name__}. '
|
raise TemporaryError(f'Could not load trade history due to {e.__class__.__name__}. '
|
||||||
f'Message: {e}') from e
|
f'Message: {e}') from e
|
||||||
@ -933,7 +946,7 @@ class Exchange:
|
|||||||
def check_order_canceled_empty(self, order: Dict) -> bool:
|
def check_order_canceled_empty(self, order: Dict) -> bool:
|
||||||
"""
|
"""
|
||||||
Verify if an order has been cancelled without being partially filled
|
Verify if an order has been cancelled without being partially filled
|
||||||
:param order: Order dict as returned from get_order()
|
:param order: Order dict as returned from fetch_order()
|
||||||
:return: True if order has been cancelled without being filled, False otherwise.
|
:return: True if order has been cancelled without being filled, False otherwise.
|
||||||
"""
|
"""
|
||||||
return order.get('status') in ('closed', 'canceled') and order.get('filled') == 0.0
|
return order.get('status') in ('closed', 'canceled') and order.get('filled') == 0.0
|
||||||
@ -948,13 +961,15 @@ class Exchange:
|
|||||||
except ccxt.InvalidOrder as e:
|
except ccxt.InvalidOrder as e:
|
||||||
raise InvalidOrderException(
|
raise InvalidOrderException(
|
||||||
f'Could not cancel order. Message: {e}') from e
|
f'Could not cancel order. Message: {e}') from e
|
||||||
|
except ccxt.DDoSProtection as e:
|
||||||
|
raise DDosProtection(e) from e
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
f'Could not cancel order due to {e.__class__.__name__}. Message: {e}') from e
|
f'Could not cancel order due to {e.__class__.__name__}. Message: {e}') from e
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e) from e
|
raise OperationalException(e) from e
|
||||||
|
|
||||||
# Assign method to get_stoploss_order to allow easy overriding in other classes
|
# Assign method to fetch_stoploss_order to allow easy overriding in other classes
|
||||||
cancel_stoploss_order = cancel_order
|
cancel_stoploss_order = cancel_order
|
||||||
|
|
||||||
def is_cancel_order_result_suitable(self, corder) -> bool:
|
def is_cancel_order_result_suitable(self, corder) -> bool:
|
||||||
@ -968,7 +983,7 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
Cancel order returning a result.
|
Cancel order returning a result.
|
||||||
Creates a fake result if cancel order returns a non-usable result
|
Creates a fake result if cancel order returns a non-usable result
|
||||||
and get_order does not work (certain exchanges don't return cancelled orders)
|
and fetch_order does not work (certain exchanges don't return cancelled orders)
|
||||||
:param order_id: Orderid to cancel
|
:param order_id: Orderid to cancel
|
||||||
:param pair: Pair corresponding to order_id
|
:param pair: Pair corresponding to order_id
|
||||||
:param amount: Amount to use for fake response
|
:param amount: Amount to use for fake response
|
||||||
@ -981,7 +996,7 @@ class Exchange:
|
|||||||
except InvalidOrderException:
|
except InvalidOrderException:
|
||||||
logger.warning(f"Could not cancel order {order_id}.")
|
logger.warning(f"Could not cancel order {order_id}.")
|
||||||
try:
|
try:
|
||||||
order = self.get_order(order_id, pair)
|
order = self.fetch_order(order_id, pair)
|
||||||
except InvalidOrderException:
|
except InvalidOrderException:
|
||||||
logger.warning(f"Could not fetch cancelled order {order_id}.")
|
logger.warning(f"Could not fetch cancelled order {order_id}.")
|
||||||
order = {'fee': {}, 'status': 'canceled', 'amount': amount, 'info': {}}
|
order = {'fee': {}, 'status': 'canceled', 'amount': amount, 'info': {}}
|
||||||
@ -989,7 +1004,7 @@ class Exchange:
|
|||||||
return order
|
return order
|
||||||
|
|
||||||
@retrier
|
@retrier
|
||||||
def get_order(self, order_id: str, pair: str) -> Dict:
|
def fetch_order(self, order_id: str, pair: str) -> Dict:
|
||||||
if self._config['dry_run']:
|
if self._config['dry_run']:
|
||||||
try:
|
try:
|
||||||
order = self._dry_run_open_orders[order_id]
|
order = self._dry_run_open_orders[order_id]
|
||||||
@ -1000,17 +1015,22 @@ class Exchange:
|
|||||||
f'Tried to get an invalid dry-run-order (id: {order_id}). Message: {e}') from e
|
f'Tried to get an invalid dry-run-order (id: {order_id}). Message: {e}') from e
|
||||||
try:
|
try:
|
||||||
return self._api.fetch_order(order_id, pair)
|
return self._api.fetch_order(order_id, pair)
|
||||||
|
except ccxt.OrderNotFound as e:
|
||||||
|
raise RetryableOrderError(
|
||||||
|
f'Order not found (id: {order_id}). Message: {e}') from e
|
||||||
except ccxt.InvalidOrder as e:
|
except ccxt.InvalidOrder as e:
|
||||||
raise InvalidOrderException(
|
raise InvalidOrderException(
|
||||||
f'Tried to get an invalid order (id: {order_id}). Message: {e}') from e
|
f'Tried to get an invalid order (id: {order_id}). Message: {e}') from e
|
||||||
|
except ccxt.DDoSProtection as e:
|
||||||
|
raise DDosProtection(e) from e
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
f'Could not get order due to {e.__class__.__name__}. Message: {e}') from e
|
f'Could not get order due to {e.__class__.__name__}. Message: {e}') from e
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e) from e
|
raise OperationalException(e) from e
|
||||||
|
|
||||||
# Assign method to get_stoploss_order to allow easy overriding in other classes
|
# Assign method to fetch_stoploss_order to allow easy overriding in other classes
|
||||||
get_stoploss_order = get_order
|
fetch_stoploss_order = fetch_order
|
||||||
|
|
||||||
@retrier
|
@retrier
|
||||||
def fetch_l2_order_book(self, pair: str, limit: int = 100) -> dict:
|
def fetch_l2_order_book(self, pair: str, limit: int = 100) -> dict:
|
||||||
@ -1027,6 +1047,8 @@ class Exchange:
|
|||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
f'Exchange {self._api.name} does not support fetching order book.'
|
f'Exchange {self._api.name} does not support fetching order book.'
|
||||||
f'Message: {e}') from e
|
f'Message: {e}') from e
|
||||||
|
except ccxt.DDoSProtection as e:
|
||||||
|
raise DDosProtection(e) from e
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
f'Could not get order book due to {e.__class__.__name__}. Message: {e}') from e
|
f'Could not get order book due to {e.__class__.__name__}. Message: {e}') from e
|
||||||
@ -1063,7 +1085,8 @@ class Exchange:
|
|||||||
matched_trades = [trade for trade in my_trades if trade['order'] == order_id]
|
matched_trades = [trade for trade in my_trades if trade['order'] == order_id]
|
||||||
|
|
||||||
return matched_trades
|
return matched_trades
|
||||||
|
except ccxt.DDoSProtection as e:
|
||||||
|
raise DDosProtection(e) from e
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
f'Could not get trades due to {e.__class__.__name__}. Message: {e}') from e
|
f'Could not get trades due to {e.__class__.__name__}. Message: {e}') from e
|
||||||
@ -1080,6 +1103,8 @@ class Exchange:
|
|||||||
|
|
||||||
return self._api.calculate_fee(symbol=symbol, type=type, side=side, amount=amount,
|
return self._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.DDoSProtection as e:
|
||||||
|
raise DDosProtection(e) from e
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
f'Could not get fee info due to {e.__class__.__name__}. Message: {e}') from e
|
f'Could not get fee info due to {e.__class__.__name__}. Message: {e}') from e
|
||||||
@ -1129,7 +1154,7 @@ class Exchange:
|
|||||||
|
|
||||||
fee_to_quote_rate = safe_value_fallback(tick, tick, 'last', 'ask')
|
fee_to_quote_rate = safe_value_fallback(tick, tick, 'last', 'ask')
|
||||||
return round((order['fee']['cost'] * fee_to_quote_rate) / order['cost'], 8)
|
return round((order['fee']['cost'] * fee_to_quote_rate) / order['cost'], 8)
|
||||||
except DependencyException:
|
except ExchangeError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def extract_cost_curr_rate(self, order: Dict) -> Tuple[float, str, Optional[float]]:
|
def extract_cost_curr_rate(self, order: Dict) -> Tuple[float, str, Optional[float]]:
|
||||||
|
@ -4,8 +4,9 @@ from typing import Dict
|
|||||||
|
|
||||||
import ccxt
|
import ccxt
|
||||||
|
|
||||||
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
|
from freqtrade.exceptions import (DDosProtection, ExchangeError,
|
||||||
OperationalException, TemporaryError)
|
InvalidOrderException, OperationalException,
|
||||||
|
TemporaryError)
|
||||||
from freqtrade.exchange import Exchange
|
from freqtrade.exchange import Exchange
|
||||||
from freqtrade.exchange.common import retrier
|
from freqtrade.exchange.common import retrier
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ class Ftx(Exchange):
|
|||||||
"""
|
"""
|
||||||
return order['type'] == 'stop' and stop_loss > float(order['price'])
|
return order['type'] == 'stop' and stop_loss > float(order['price'])
|
||||||
|
|
||||||
|
@retrier(retries=0)
|
||||||
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
|
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
|
||||||
"""
|
"""
|
||||||
Creates a stoploss order.
|
Creates a stoploss order.
|
||||||
@ -59,7 +61,7 @@ class Ftx(Exchange):
|
|||||||
'stop price: %s.', pair, stop_price)
|
'stop price: %s.', pair, stop_price)
|
||||||
return order
|
return order
|
||||||
except ccxt.InsufficientFunds as e:
|
except ccxt.InsufficientFunds as e:
|
||||||
raise DependencyException(
|
raise ExchangeError(
|
||||||
f'Insufficient funds to create {ordertype} sell order on market {pair}. '
|
f'Insufficient funds to create {ordertype} sell order on market {pair}. '
|
||||||
f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
|
f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
|
||||||
f'Message: {e}') from e
|
f'Message: {e}') from e
|
||||||
@ -68,6 +70,8 @@ class Ftx(Exchange):
|
|||||||
f'Could not create {ordertype} sell order on market {pair}. '
|
f'Could not create {ordertype} sell order on market {pair}. '
|
||||||
f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
|
f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
|
||||||
f'Message: {e}') from e
|
f'Message: {e}') from e
|
||||||
|
except ccxt.DDoSProtection as e:
|
||||||
|
raise DDosProtection(e) from e
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e
|
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e
|
||||||
@ -75,7 +79,7 @@ class Ftx(Exchange):
|
|||||||
raise OperationalException(e) from e
|
raise OperationalException(e) from e
|
||||||
|
|
||||||
@retrier
|
@retrier
|
||||||
def get_stoploss_order(self, order_id: str, pair: str) -> Dict:
|
def fetch_stoploss_order(self, order_id: str, pair: str) -> Dict:
|
||||||
if self._config['dry_run']:
|
if self._config['dry_run']:
|
||||||
try:
|
try:
|
||||||
order = self._dry_run_open_orders[order_id]
|
order = self._dry_run_open_orders[order_id]
|
||||||
@ -96,6 +100,8 @@ class Ftx(Exchange):
|
|||||||
except ccxt.InvalidOrder as e:
|
except ccxt.InvalidOrder as e:
|
||||||
raise InvalidOrderException(
|
raise InvalidOrderException(
|
||||||
f'Tried to get an invalid order (id: {order_id}). Message: {e}') from e
|
f'Tried to get an invalid order (id: {order_id}). Message: {e}') from e
|
||||||
|
except ccxt.DDoSProtection as e:
|
||||||
|
raise DDosProtection(e) from e
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
f'Could not get order due to {e.__class__.__name__}. Message: {e}') from e
|
f'Could not get order due to {e.__class__.__name__}. Message: {e}') from e
|
||||||
@ -111,6 +117,8 @@ class Ftx(Exchange):
|
|||||||
except ccxt.InvalidOrder as e:
|
except ccxt.InvalidOrder as e:
|
||||||
raise InvalidOrderException(
|
raise InvalidOrderException(
|
||||||
f'Could not cancel order. Message: {e}') from e
|
f'Could not cancel order. Message: {e}') from e
|
||||||
|
except ccxt.DDoSProtection as e:
|
||||||
|
raise DDosProtection(e) from e
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
f'Could not cancel order due to {e.__class__.__name__}. Message: {e}') from e
|
f'Could not cancel order due to {e.__class__.__name__}. Message: {e}') from e
|
||||||
|
@ -4,8 +4,9 @@ from typing import Dict
|
|||||||
|
|
||||||
import ccxt
|
import ccxt
|
||||||
|
|
||||||
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
|
from freqtrade.exceptions import (DDosProtection, ExchangeError,
|
||||||
OperationalException, TemporaryError)
|
InvalidOrderException, OperationalException,
|
||||||
|
TemporaryError)
|
||||||
from freqtrade.exchange import Exchange
|
from freqtrade.exchange import Exchange
|
||||||
from freqtrade.exchange.common import retrier
|
from freqtrade.exchange.common import retrier
|
||||||
|
|
||||||
@ -45,6 +46,8 @@ class Kraken(Exchange):
|
|||||||
balances[bal]['free'] = balances[bal]['total'] - balances[bal]['used']
|
balances[bal]['free'] = balances[bal]['total'] - balances[bal]['used']
|
||||||
|
|
||||||
return balances
|
return balances
|
||||||
|
except ccxt.DDoSProtection as e:
|
||||||
|
raise DDosProtection(e) from e
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
f'Could not get balance due to {e.__class__.__name__}. Message: {e}') from e
|
f'Could not get balance due to {e.__class__.__name__}. Message: {e}') from e
|
||||||
@ -58,6 +61,7 @@ class Kraken(Exchange):
|
|||||||
"""
|
"""
|
||||||
return order['type'] == 'stop-loss' and stop_loss > float(order['price'])
|
return order['type'] == 'stop-loss' and stop_loss > float(order['price'])
|
||||||
|
|
||||||
|
@retrier(retries=0)
|
||||||
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
|
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
|
||||||
"""
|
"""
|
||||||
Creates a stoploss market order.
|
Creates a stoploss market order.
|
||||||
@ -84,7 +88,7 @@ class Kraken(Exchange):
|
|||||||
'stop price: %s.', pair, stop_price)
|
'stop price: %s.', pair, stop_price)
|
||||||
return order
|
return order
|
||||||
except ccxt.InsufficientFunds as e:
|
except ccxt.InsufficientFunds as e:
|
||||||
raise DependencyException(
|
raise ExchangeError(
|
||||||
f'Insufficient funds to create {ordertype} sell order on market {pair}.'
|
f'Insufficient funds to create {ordertype} sell order on market {pair}.'
|
||||||
f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
|
f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
|
||||||
f'Message: {e}') from e
|
f'Message: {e}') from e
|
||||||
@ -93,6 +97,8 @@ class Kraken(Exchange):
|
|||||||
f'Could not create {ordertype} sell order on market {pair}. '
|
f'Could not create {ordertype} sell order on market {pair}. '
|
||||||
f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
|
f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
|
||||||
f'Message: {e}') from e
|
f'Message: {e}') from e
|
||||||
|
except ccxt.DDoSProtection as e:
|
||||||
|
raise DDosProtection(e) from e
|
||||||
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
|
||||||
raise TemporaryError(
|
raise TemporaryError(
|
||||||
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e
|
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e
|
||||||
|
@ -11,14 +11,14 @@ from typing import Any, Dict, List, Optional
|
|||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
from cachetools import TTLCache
|
from cachetools import TTLCache
|
||||||
from requests.exceptions import RequestException
|
|
||||||
|
|
||||||
from freqtrade import __version__, constants, persistence
|
from freqtrade import __version__, constants, persistence
|
||||||
from freqtrade.configuration import validate_config_consistency
|
from freqtrade.configuration import validate_config_consistency
|
||||||
from freqtrade.data.converter import order_book_to_dataframe
|
from freqtrade.data.converter import order_book_to_dataframe
|
||||||
from freqtrade.data.dataprovider import DataProvider
|
from freqtrade.data.dataprovider import DataProvider
|
||||||
from freqtrade.edge import Edge
|
from freqtrade.edge import Edge
|
||||||
from freqtrade.exceptions import DependencyException, InvalidOrderException, PricingError
|
from freqtrade.exceptions import (DependencyException, ExchangeError,
|
||||||
|
InvalidOrderException, PricingError)
|
||||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date
|
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date
|
||||||
from freqtrade.misc import safe_value_fallback
|
from freqtrade.misc import safe_value_fallback
|
||||||
from freqtrade.pairlist.pairlistmanager import PairListManager
|
from freqtrade.pairlist.pairlistmanager import PairListManager
|
||||||
@ -755,7 +755,7 @@ class FreqtradeBot:
|
|||||||
logger.warning('Selling the trade forcefully')
|
logger.warning('Selling the trade forcefully')
|
||||||
self.execute_sell(trade, trade.stop_loss, sell_reason=SellType.EMERGENCY_SELL)
|
self.execute_sell(trade, trade.stop_loss, sell_reason=SellType.EMERGENCY_SELL)
|
||||||
|
|
||||||
except DependencyException:
|
except ExchangeError:
|
||||||
trade.stoploss_order_id = None
|
trade.stoploss_order_id = None
|
||||||
logger.exception('Unable to place a stoploss order on exchange.')
|
logger.exception('Unable to place a stoploss order on exchange.')
|
||||||
return False
|
return False
|
||||||
@ -773,8 +773,8 @@ class FreqtradeBot:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# First we check if there is already a stoploss on exchange
|
# First we check if there is already a stoploss on exchange
|
||||||
stoploss_order = self.exchange.get_stoploss_order(trade.stoploss_order_id, trade.pair) \
|
stoploss_order = self.exchange.fetch_stoploss_order(
|
||||||
if trade.stoploss_order_id else None
|
trade.stoploss_order_id, trade.pair) if trade.stoploss_order_id else None
|
||||||
except InvalidOrderException as exception:
|
except InvalidOrderException as exception:
|
||||||
logger.warning('Unable to fetch stoploss order: %s', exception)
|
logger.warning('Unable to fetch stoploss order: %s', exception)
|
||||||
|
|
||||||
@ -890,8 +890,8 @@ class FreqtradeBot:
|
|||||||
try:
|
try:
|
||||||
if not trade.open_order_id:
|
if not trade.open_order_id:
|
||||||
continue
|
continue
|
||||||
order = self.exchange.get_order(trade.open_order_id, trade.pair)
|
order = self.exchange.fetch_order(trade.open_order_id, trade.pair)
|
||||||
except (RequestException, DependencyException, InvalidOrderException):
|
except (ExchangeError, InvalidOrderException):
|
||||||
logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc())
|
logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc())
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -923,7 +923,7 @@ class FreqtradeBot:
|
|||||||
|
|
||||||
for trade in Trade.get_open_order_trades():
|
for trade in Trade.get_open_order_trades():
|
||||||
try:
|
try:
|
||||||
order = self.exchange.get_order(trade.open_order_id, trade.pair)
|
order = self.exchange.fetch_order(trade.open_order_id, trade.pair)
|
||||||
except (DependencyException, InvalidOrderException):
|
except (DependencyException, InvalidOrderException):
|
||||||
logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc())
|
logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc())
|
||||||
continue
|
continue
|
||||||
@ -1202,7 +1202,7 @@ class FreqtradeBot:
|
|||||||
# Update trade with order values
|
# Update trade with order values
|
||||||
logger.info('Found open order for %s', trade)
|
logger.info('Found open order for %s', trade)
|
||||||
try:
|
try:
|
||||||
order = action_order or self.exchange.get_order(order_id, trade.pair)
|
order = action_order or self.exchange.fetch_order(order_id, trade.pair)
|
||||||
except InvalidOrderException as exception:
|
except InvalidOrderException as exception:
|
||||||
logger.warning('Unable to fetch order %s: %s', order_id, exception)
|
logger.warning('Unable to fetch order %s: %s', order_id, exception)
|
||||||
return False
|
return False
|
||||||
|
@ -360,7 +360,7 @@ class Trade(_DECL_BASE):
|
|||||||
def update(self, order: Dict) -> None:
|
def update(self, order: Dict) -> None:
|
||||||
"""
|
"""
|
||||||
Updates this entity with amount and actual open/close rates.
|
Updates this entity with amount and actual open/close rates.
|
||||||
:param order: order retrieved by exchange.get_order()
|
:param order: order retrieved by exchange.fetch_order()
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
order_type = order['type']
|
order_type = order['type']
|
||||||
|
@ -11,7 +11,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
|||||||
import arrow
|
import arrow
|
||||||
from numpy import NAN, mean
|
from numpy import NAN, mean
|
||||||
|
|
||||||
from freqtrade.exceptions import DependencyException, TemporaryError
|
from freqtrade.exceptions import ExchangeError, PricingError
|
||||||
from freqtrade.misc import shorten_date
|
from freqtrade.misc import shorten_date
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
||||||
@ -126,11 +126,11 @@ class RPC:
|
|||||||
for trade in trades:
|
for trade in trades:
|
||||||
order = None
|
order = None
|
||||||
if trade.open_order_id:
|
if trade.open_order_id:
|
||||||
order = self._freqtrade.exchange.get_order(trade.open_order_id, trade.pair)
|
order = self._freqtrade.exchange.fetch_order(trade.open_order_id, trade.pair)
|
||||||
# calculate profit and send message to user
|
# calculate profit and send message to user
|
||||||
try:
|
try:
|
||||||
current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
|
current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
|
||||||
except DependencyException:
|
except (ExchangeError, PricingError):
|
||||||
current_rate = NAN
|
current_rate = NAN
|
||||||
current_profit = trade.calc_profit_ratio(current_rate)
|
current_profit = trade.calc_profit_ratio(current_rate)
|
||||||
current_profit_abs = trade.calc_profit(current_rate)
|
current_profit_abs = trade.calc_profit(current_rate)
|
||||||
@ -174,7 +174,7 @@ class RPC:
|
|||||||
# calculate profit and send message to user
|
# calculate profit and send message to user
|
||||||
try:
|
try:
|
||||||
current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
|
current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
|
||||||
except DependencyException:
|
except (PricingError, ExchangeError):
|
||||||
current_rate = NAN
|
current_rate = NAN
|
||||||
trade_percent = (100 * trade.calc_profit_ratio(current_rate))
|
trade_percent = (100 * trade.calc_profit_ratio(current_rate))
|
||||||
trade_profit = trade.calc_profit(current_rate)
|
trade_profit = trade.calc_profit(current_rate)
|
||||||
@ -286,7 +286,7 @@ class RPC:
|
|||||||
# Get current rate
|
# Get current rate
|
||||||
try:
|
try:
|
||||||
current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
|
current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
|
||||||
except DependencyException:
|
except (PricingError, ExchangeError):
|
||||||
current_rate = NAN
|
current_rate = NAN
|
||||||
profit_ratio = trade.calc_profit_ratio(rate=current_rate)
|
profit_ratio = trade.calc_profit_ratio(rate=current_rate)
|
||||||
|
|
||||||
@ -352,7 +352,7 @@ class RPC:
|
|||||||
total = 0.0
|
total = 0.0
|
||||||
try:
|
try:
|
||||||
tickers = self._freqtrade.exchange.get_tickers()
|
tickers = self._freqtrade.exchange.get_tickers()
|
||||||
except (TemporaryError, DependencyException):
|
except (ExchangeError):
|
||||||
raise RPCException('Error getting current tickers.')
|
raise RPCException('Error getting current tickers.')
|
||||||
|
|
||||||
self._freqtrade.wallets.update(require_update=False)
|
self._freqtrade.wallets.update(require_update=False)
|
||||||
@ -373,7 +373,7 @@ class RPC:
|
|||||||
if pair.startswith(stake_currency):
|
if pair.startswith(stake_currency):
|
||||||
rate = 1.0 / rate
|
rate = 1.0 / rate
|
||||||
est_stake = rate * balance.total
|
est_stake = rate * balance.total
|
||||||
except (TemporaryError, DependencyException):
|
except (ExchangeError):
|
||||||
logger.warning(f" Could not get rate for pair {coin}.")
|
logger.warning(f" Could not get rate for pair {coin}.")
|
||||||
continue
|
continue
|
||||||
total = total + (est_stake or 0)
|
total = total + (est_stake or 0)
|
||||||
@ -442,7 +442,7 @@ class RPC:
|
|||||||
def _exec_forcesell(trade: Trade) -> None:
|
def _exec_forcesell(trade: Trade) -> None:
|
||||||
# Check if there is there is an open order
|
# Check if there is there is an open order
|
||||||
if trade.open_order_id:
|
if trade.open_order_id:
|
||||||
order = self._freqtrade.exchange.get_order(trade.open_order_id, trade.pair)
|
order = self._freqtrade.exchange.fetch_order(trade.open_order_id, trade.pair)
|
||||||
|
|
||||||
# Cancel open LIMIT_BUY orders and close trade
|
# Cancel open LIMIT_BUY orders and close trade
|
||||||
if order and order['status'] == 'open' \
|
if order and order['status'] == 'open' \
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from pandas import DataFrame
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade.data.dataprovider import DataProvider
|
from freqtrade.data.dataprovider import DataProvider
|
||||||
|
from freqtrade.exceptions import ExchangeError, OperationalException
|
||||||
from freqtrade.pairlist.pairlistmanager import PairListManager
|
from freqtrade.pairlist.pairlistmanager import PairListManager
|
||||||
from freqtrade.exceptions import DependencyException, OperationalException
|
|
||||||
from freqtrade.state import RunMode
|
from freqtrade.state import RunMode
|
||||||
from tests.conftest import get_patched_exchange
|
from tests.conftest import get_patched_exchange
|
||||||
|
|
||||||
@ -164,7 +164,7 @@ def test_ticker(mocker, default_conf, tickers):
|
|||||||
assert 'symbol' in res
|
assert 'symbol' in res
|
||||||
assert res['symbol'] == 'ETH/BTC'
|
assert res['symbol'] == 'ETH/BTC'
|
||||||
|
|
||||||
ticker_mock = MagicMock(side_effect=DependencyException('Pair not found'))
|
ticker_mock = MagicMock(side_effect=ExchangeError('Pair not found'))
|
||||||
mocker.patch("freqtrade.exchange.Exchange.fetch_ticker", ticker_mock)
|
mocker.patch("freqtrade.exchange.Exchange.fetch_ticker", ticker_mock)
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
dp = DataProvider(default_conf, exchange)
|
dp = DataProvider(default_conf, exchange)
|
||||||
|
@ -5,8 +5,9 @@ import ccxt
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
|
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
|
||||||
OperationalException, TemporaryError)
|
OperationalException)
|
||||||
from tests.conftest import get_patched_exchange
|
from tests.conftest import get_patched_exchange
|
||||||
|
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('limitratio,expected', [
|
@pytest.mark.parametrize('limitratio,expected', [
|
||||||
@ -62,15 +63,9 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected):
|
|||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
||||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
||||||
|
|
||||||
with pytest.raises(TemporaryError):
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance",
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No connection"))
|
"stoploss", "create_order", retries=1,
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
||||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
|
||||||
|
|
||||||
with pytest.raises(OperationalException, match=r".*DeadBeef.*"):
|
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
|
||||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
|
||||||
|
|
||||||
|
|
||||||
def test_stoploss_order_dry_run_binance(default_conf, mocker):
|
def test_stoploss_order_dry_run_binance(default_conf, mocker):
|
||||||
|
@ -4,17 +4,17 @@ import copy
|
|||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from random import randint
|
from random import randint
|
||||||
from unittest.mock import MagicMock, Mock, PropertyMock
|
from unittest.mock import MagicMock, Mock, PropertyMock, patch
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
import ccxt
|
import ccxt
|
||||||
import pytest
|
import pytest
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
|
from freqtrade.exceptions import (DependencyException, InvalidOrderException, DDosProtection,
|
||||||
OperationalException, TemporaryError)
|
OperationalException, TemporaryError)
|
||||||
from freqtrade.exchange import Binance, Exchange, Kraken
|
from freqtrade.exchange import Binance, Exchange, Kraken
|
||||||
from freqtrade.exchange.common import API_RETRY_COUNT
|
from freqtrade.exchange.common import API_RETRY_COUNT, calculate_backoff
|
||||||
from freqtrade.exchange.exchange import (market_is_active, symbol_is_pair,
|
from freqtrade.exchange.exchange import (market_is_active, symbol_is_pair,
|
||||||
timeframe_to_minutes,
|
timeframe_to_minutes,
|
||||||
timeframe_to_msecs,
|
timeframe_to_msecs,
|
||||||
@ -37,12 +37,20 @@ def get_mock_coro(return_value):
|
|||||||
|
|
||||||
|
|
||||||
def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
|
def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
|
||||||
fun, mock_ccxt_fun, **kwargs):
|
fun, mock_ccxt_fun, retries=API_RETRY_COUNT + 1, **kwargs):
|
||||||
|
|
||||||
|
with patch('freqtrade.exchange.common.time.sleep'):
|
||||||
|
with pytest.raises(DDosProtection):
|
||||||
|
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.DDoSProtection("DDos"))
|
||||||
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
|
getattr(exchange, fun)(**kwargs)
|
||||||
|
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
|
||||||
|
|
||||||
with pytest.raises(TemporaryError):
|
with pytest.raises(TemporaryError):
|
||||||
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError("DeaDBeef"))
|
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError("DeaDBeef"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
getattr(exchange, fun)(**kwargs)
|
getattr(exchange, fun)(**kwargs)
|
||||||
assert api_mock.__dict__[mock_ccxt_fun].call_count == API_RETRY_COUNT + 1
|
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
with pytest.raises(OperationalException):
|
||||||
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
|
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
|
||||||
@ -51,12 +59,21 @@ def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
|
|||||||
assert api_mock.__dict__[mock_ccxt_fun].call_count == 1
|
assert api_mock.__dict__[mock_ccxt_fun].call_count == 1
|
||||||
|
|
||||||
|
|
||||||
async def async_ccxt_exception(mocker, default_conf, api_mock, fun, mock_ccxt_fun, **kwargs):
|
async def async_ccxt_exception(mocker, default_conf, api_mock, fun, mock_ccxt_fun,
|
||||||
|
retries=API_RETRY_COUNT + 1, **kwargs):
|
||||||
|
|
||||||
|
with patch('freqtrade.exchange.common.asyncio.sleep', get_mock_coro(None)):
|
||||||
|
with pytest.raises(DDosProtection):
|
||||||
|
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.DDoSProtection("Dooh"))
|
||||||
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
|
await getattr(exchange, fun)(**kwargs)
|
||||||
|
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
|
||||||
|
|
||||||
with pytest.raises(TemporaryError):
|
with pytest.raises(TemporaryError):
|
||||||
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError("DeadBeef"))
|
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError("DeadBeef"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock)
|
||||||
await getattr(exchange, fun)(**kwargs)
|
await getattr(exchange, fun)(**kwargs)
|
||||||
assert api_mock.__dict__[mock_ccxt_fun].call_count == API_RETRY_COUNT + 1
|
assert api_mock.__dict__[mock_ccxt_fun].call_count == retries
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
with pytest.raises(OperationalException):
|
||||||
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
|
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
|
||||||
@ -1127,9 +1144,10 @@ def test_get_balance_prod(default_conf, mocker, exchange_name):
|
|||||||
exchange.get_balance(currency='BTC')
|
exchange.get_balance(currency='BTC')
|
||||||
|
|
||||||
|
|
||||||
def test_get_balances_dry_run(default_conf, mocker):
|
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||||
|
def test_get_balances_dry_run(default_conf, mocker, exchange_name):
|
||||||
default_conf['dry_run'] = True
|
default_conf['dry_run'] = True
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||||
assert exchange.get_balances() == {}
|
assert exchange.get_balances() == {}
|
||||||
|
|
||||||
|
|
||||||
@ -1847,36 +1865,48 @@ def test_cancel_stoploss_order(default_conf, mocker, exchange_name):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||||
def test_get_order(default_conf, mocker, exchange_name):
|
def test_fetch_order(default_conf, mocker, exchange_name):
|
||||||
default_conf['dry_run'] = True
|
default_conf['dry_run'] = True
|
||||||
order = MagicMock()
|
order = MagicMock()
|
||||||
order.myid = 123
|
order.myid = 123
|
||||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||||
exchange._dry_run_open_orders['X'] = order
|
exchange._dry_run_open_orders['X'] = order
|
||||||
assert exchange.get_order('X', 'TKN/BTC').myid == 123
|
assert exchange.fetch_order('X', 'TKN/BTC').myid == 123
|
||||||
|
|
||||||
with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'):
|
with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'):
|
||||||
exchange.get_order('Y', 'TKN/BTC')
|
exchange.fetch_order('Y', 'TKN/BTC')
|
||||||
|
|
||||||
default_conf['dry_run'] = False
|
default_conf['dry_run'] = False
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.fetch_order = MagicMock(return_value=456)
|
api_mock.fetch_order = MagicMock(return_value=456)
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
assert exchange.get_order('X', 'TKN/BTC') == 456
|
assert exchange.fetch_order('X', 'TKN/BTC') == 456
|
||||||
|
|
||||||
with pytest.raises(InvalidOrderException):
|
with pytest.raises(InvalidOrderException):
|
||||||
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
exchange.get_order(order_id='_', pair='TKN/BTC')
|
exchange.fetch_order(order_id='_', pair='TKN/BTC')
|
||||||
assert api_mock.fetch_order.call_count == 1
|
assert api_mock.fetch_order.call_count == 1
|
||||||
|
|
||||||
|
api_mock.fetch_order = MagicMock(side_effect=ccxt.OrderNotFound("Order not found"))
|
||||||
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
|
with patch('freqtrade.exchange.common.time.sleep') as tm:
|
||||||
|
with pytest.raises(InvalidOrderException):
|
||||||
|
exchange.fetch_order(order_id='_', pair='TKN/BTC')
|
||||||
|
# Ensure backoff is called
|
||||||
|
assert tm.call_args_list[0][0][0] == 1
|
||||||
|
assert tm.call_args_list[1][0][0] == 2
|
||||||
|
assert tm.call_args_list[2][0][0] == 5
|
||||||
|
assert tm.call_args_list[3][0][0] == 10
|
||||||
|
assert api_mock.fetch_order.call_count == API_RETRY_COUNT + 1
|
||||||
|
|
||||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
|
||||||
'get_order', 'fetch_order',
|
'fetch_order', 'fetch_order',
|
||||||
order_id='_', pair='TKN/BTC')
|
order_id='_', pair='TKN/BTC')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||||
def test_get_stoploss_order(default_conf, mocker, exchange_name):
|
def test_fetch_stoploss_order(default_conf, mocker, exchange_name):
|
||||||
# Don't test FTX here - that needs a seperate test
|
# Don't test FTX here - that needs a seperate test
|
||||||
if exchange_name == 'ftx':
|
if exchange_name == 'ftx':
|
||||||
return
|
return
|
||||||
@ -1885,25 +1915,25 @@ def test_get_stoploss_order(default_conf, mocker, exchange_name):
|
|||||||
order.myid = 123
|
order.myid = 123
|
||||||
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
|
||||||
exchange._dry_run_open_orders['X'] = order
|
exchange._dry_run_open_orders['X'] = order
|
||||||
assert exchange.get_stoploss_order('X', 'TKN/BTC').myid == 123
|
assert exchange.fetch_stoploss_order('X', 'TKN/BTC').myid == 123
|
||||||
|
|
||||||
with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'):
|
with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'):
|
||||||
exchange.get_stoploss_order('Y', 'TKN/BTC')
|
exchange.fetch_stoploss_order('Y', 'TKN/BTC')
|
||||||
|
|
||||||
default_conf['dry_run'] = False
|
default_conf['dry_run'] = False
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.fetch_order = MagicMock(return_value=456)
|
api_mock.fetch_order = MagicMock(return_value=456)
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
assert exchange.get_stoploss_order('X', 'TKN/BTC') == 456
|
assert exchange.fetch_stoploss_order('X', 'TKN/BTC') == 456
|
||||||
|
|
||||||
with pytest.raises(InvalidOrderException):
|
with pytest.raises(InvalidOrderException):
|
||||||
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||||
exchange.get_stoploss_order(order_id='_', pair='TKN/BTC')
|
exchange.fetch_stoploss_order(order_id='_', pair='TKN/BTC')
|
||||||
assert api_mock.fetch_order.call_count == 1
|
assert api_mock.fetch_order.call_count == 1
|
||||||
|
|
||||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
|
||||||
'get_stoploss_order', 'fetch_order',
|
'fetch_stoploss_order', 'fetch_order',
|
||||||
order_id='_', pair='TKN/BTC')
|
order_id='_', pair='TKN/BTC')
|
||||||
|
|
||||||
|
|
||||||
@ -2111,6 +2141,13 @@ def test_get_markets(default_conf, mocker, markets,
|
|||||||
assert sorted(pairs.keys()) == sorted(expected_keys)
|
assert sorted(pairs.keys()) == sorted(expected_keys)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_markets_error(default_conf, mocker):
|
||||||
|
ex = get_patched_exchange(mocker, default_conf)
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=None))
|
||||||
|
with pytest.raises(OperationalException, match="Markets were not loaded."):
|
||||||
|
ex.get_markets('LTC', 'USDT', True, False)
|
||||||
|
|
||||||
|
|
||||||
def test_timeframe_to_minutes():
|
def test_timeframe_to_minutes():
|
||||||
assert timeframe_to_minutes("5m") == 5
|
assert timeframe_to_minutes("5m") == 5
|
||||||
assert timeframe_to_minutes("10m") == 10
|
assert timeframe_to_minutes("10m") == 10
|
||||||
@ -2271,3 +2308,15 @@ def test_calculate_fee_rate(mocker, default_conf, order, expected) -> None:
|
|||||||
|
|
||||||
ex = get_patched_exchange(mocker, default_conf)
|
ex = get_patched_exchange(mocker, default_conf)
|
||||||
assert ex.calculate_fee_rate(order) == expected
|
assert ex.calculate_fee_rate(order) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('retrycount,max_retries,expected', [
|
||||||
|
(0, 3, 10),
|
||||||
|
(1, 3, 5),
|
||||||
|
(2, 3, 2),
|
||||||
|
(3, 3, 1),
|
||||||
|
(0, 1, 2),
|
||||||
|
(1, 1, 1),
|
||||||
|
])
|
||||||
|
def test_calculate_backoff(retrycount, max_retries, expected):
|
||||||
|
assert calculate_backoff(retrycount, max_retries) == expected
|
||||||
|
@ -6,9 +6,9 @@ from unittest.mock import MagicMock
|
|||||||
import ccxt
|
import ccxt
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
|
from freqtrade.exceptions import DependencyException, InvalidOrderException
|
||||||
OperationalException, TemporaryError)
|
|
||||||
from tests.conftest import get_patched_exchange
|
from tests.conftest import get_patched_exchange
|
||||||
|
|
||||||
from .test_exchange import ccxt_exceptionhandlers
|
from .test_exchange import ccxt_exceptionhandlers
|
||||||
|
|
||||||
STOPLOSS_ORDERTYPE = 'stop'
|
STOPLOSS_ORDERTYPE = 'stop'
|
||||||
@ -85,15 +85,9 @@ def test_stoploss_order_ftx(default_conf, mocker):
|
|||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
|
||||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
||||||
|
|
||||||
with pytest.raises(TemporaryError):
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "ftx",
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No connection"))
|
"stoploss", "create_order", retries=1,
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
|
pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
||||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
|
||||||
|
|
||||||
with pytest.raises(OperationalException, match=r".*DeadBeef.*"):
|
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
|
|
||||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
|
||||||
|
|
||||||
|
|
||||||
def test_stoploss_order_dry_run_ftx(default_conf, mocker):
|
def test_stoploss_order_dry_run_ftx(default_conf, mocker):
|
||||||
@ -130,34 +124,34 @@ def test_stoploss_adjust_ftx(mocker, default_conf):
|
|||||||
assert not exchange.stoploss_adjust(1501, order)
|
assert not exchange.stoploss_adjust(1501, order)
|
||||||
|
|
||||||
|
|
||||||
def test_get_stoploss_order(default_conf, mocker):
|
def test_fetch_stoploss_order(default_conf, mocker):
|
||||||
default_conf['dry_run'] = True
|
default_conf['dry_run'] = True
|
||||||
order = MagicMock()
|
order = MagicMock()
|
||||||
order.myid = 123
|
order.myid = 123
|
||||||
exchange = get_patched_exchange(mocker, default_conf, id='ftx')
|
exchange = get_patched_exchange(mocker, default_conf, id='ftx')
|
||||||
exchange._dry_run_open_orders['X'] = order
|
exchange._dry_run_open_orders['X'] = order
|
||||||
assert exchange.get_stoploss_order('X', 'TKN/BTC').myid == 123
|
assert exchange.fetch_stoploss_order('X', 'TKN/BTC').myid == 123
|
||||||
|
|
||||||
with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'):
|
with pytest.raises(InvalidOrderException, match=r'Tried to get an invalid dry-run-order.*'):
|
||||||
exchange.get_stoploss_order('Y', 'TKN/BTC')
|
exchange.fetch_stoploss_order('Y', 'TKN/BTC')
|
||||||
|
|
||||||
default_conf['dry_run'] = False
|
default_conf['dry_run'] = False
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
api_mock.fetch_orders = MagicMock(return_value=[{'id': 'X', 'status': '456'}])
|
api_mock.fetch_orders = MagicMock(return_value=[{'id': 'X', 'status': '456'}])
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx')
|
||||||
assert exchange.get_stoploss_order('X', 'TKN/BTC')['status'] == '456'
|
assert exchange.fetch_stoploss_order('X', 'TKN/BTC')['status'] == '456'
|
||||||
|
|
||||||
api_mock.fetch_orders = MagicMock(return_value=[{'id': 'Y', 'status': '456'}])
|
api_mock.fetch_orders = MagicMock(return_value=[{'id': 'Y', 'status': '456'}])
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx')
|
||||||
with pytest.raises(InvalidOrderException, match=r"Could not get stoploss order for id X"):
|
with pytest.raises(InvalidOrderException, match=r"Could not get stoploss order for id X"):
|
||||||
exchange.get_stoploss_order('X', 'TKN/BTC')['status']
|
exchange.fetch_stoploss_order('X', 'TKN/BTC')['status']
|
||||||
|
|
||||||
with pytest.raises(InvalidOrderException):
|
with pytest.raises(InvalidOrderException):
|
||||||
api_mock.fetch_orders = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
api_mock.fetch_orders = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, id='ftx')
|
||||||
exchange.get_stoploss_order(order_id='_', pair='TKN/BTC')
|
exchange.fetch_stoploss_order(order_id='_', pair='TKN/BTC')
|
||||||
assert api_mock.fetch_orders.call_count == 1
|
assert api_mock.fetch_orders.call_count == 1
|
||||||
|
|
||||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'ftx',
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'ftx',
|
||||||
'get_stoploss_order', 'fetch_orders',
|
'fetch_stoploss_order', 'fetch_orders',
|
||||||
order_id='_', pair='TKN/BTC')
|
order_id='_', pair='TKN/BTC')
|
||||||
|
@ -6,8 +6,7 @@ from unittest.mock import MagicMock
|
|||||||
import ccxt
|
import ccxt
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
|
from freqtrade.exceptions import DependencyException, InvalidOrderException
|
||||||
OperationalException, TemporaryError)
|
|
||||||
from tests.conftest import get_patched_exchange
|
from tests.conftest import get_patched_exchange
|
||||||
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
from tests.exchange.test_exchange import ccxt_exceptionhandlers
|
||||||
|
|
||||||
@ -206,15 +205,9 @@ def test_stoploss_order_kraken(default_conf, mocker):
|
|||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
||||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
||||||
|
|
||||||
with pytest.raises(TemporaryError):
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken",
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No connection"))
|
"stoploss", "create_order", retries=1,
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
||||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
|
||||||
|
|
||||||
with pytest.raises(OperationalException, match=r".*DeadBeef.*"):
|
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
|
||||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
|
||||||
|
|
||||||
|
|
||||||
def test_stoploss_order_dry_run_kraken(default_conf, mocker):
|
def test_stoploss_order_dry_run_kraken(default_conf, mocker):
|
||||||
|
@ -8,12 +8,13 @@ import pytest
|
|||||||
from numpy import isnan
|
from numpy import isnan
|
||||||
|
|
||||||
from freqtrade.edge import PairInfo
|
from freqtrade.edge import PairInfo
|
||||||
from freqtrade.exceptions import DependencyException, TemporaryError
|
from freqtrade.exceptions import ExchangeError, TemporaryError
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
from freqtrade.rpc import RPC, RPCException
|
from freqtrade.rpc import RPC, RPCException
|
||||||
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
||||||
from freqtrade.state import State
|
from freqtrade.state import State
|
||||||
from tests.conftest import get_patched_freqtradebot, patch_get_signal, create_mock_trades
|
from tests.conftest import (create_mock_trades, get_patched_freqtradebot,
|
||||||
|
patch_get_signal)
|
||||||
|
|
||||||
|
|
||||||
# Functions for recurrent object patching
|
# Functions for recurrent object patching
|
||||||
@ -106,7 +107,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
|||||||
}
|
}
|
||||||
|
|
||||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
|
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
|
||||||
MagicMock(side_effect=DependencyException("Pair 'ETH/BTC' not available")))
|
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||||
results = rpc._rpc_trade_status()
|
results = rpc._rpc_trade_status()
|
||||||
assert isnan(results[0]['current_profit'])
|
assert isnan(results[0]['current_profit'])
|
||||||
assert isnan(results[0]['current_rate'])
|
assert isnan(results[0]['current_rate'])
|
||||||
@ -209,7 +210,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
|||||||
assert '-0.41% (-0.06)' == result[0][3]
|
assert '-0.41% (-0.06)' == result[0][3]
|
||||||
|
|
||||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
|
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
|
||||||
MagicMock(side_effect=DependencyException("Pair 'ETH/BTC' not available")))
|
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||||
result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||||
assert 'instantly' == result[0][2]
|
assert 'instantly' == result[0][2]
|
||||||
assert 'ETH/BTC' in result[0][1]
|
assert 'ETH/BTC' in result[0][1]
|
||||||
@ -365,7 +366,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
|||||||
|
|
||||||
# Test non-available pair
|
# Test non-available pair
|
||||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
|
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
|
||||||
MagicMock(side_effect=DependencyException("Pair 'ETH/BTC' not available")))
|
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||||
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
|
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
|
||||||
assert stats['trade_count'] == 2
|
assert stats['trade_count'] == 2
|
||||||
assert stats['first_trade_date'] == 'just now'
|
assert stats['first_trade_date'] == 'just now'
|
||||||
@ -606,7 +607,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
fetch_ticker=ticker,
|
fetch_ticker=ticker,
|
||||||
cancel_order=cancel_order_mock,
|
cancel_order=cancel_order_mock,
|
||||||
get_order=MagicMock(
|
fetch_order=MagicMock(
|
||||||
return_value={
|
return_value={
|
||||||
'status': 'closed',
|
'status': 'closed',
|
||||||
'type': 'limit',
|
'type': 'limit',
|
||||||
@ -652,7 +653,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
|
|||||||
trade = Trade.query.filter(Trade.id == '1').first()
|
trade = Trade.query.filter(Trade.id == '1').first()
|
||||||
filled_amount = trade.amount / 2
|
filled_amount = trade.amount / 2
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
'freqtrade.exchange.Exchange.get_order',
|
'freqtrade.exchange.Exchange.fetch_order',
|
||||||
return_value={
|
return_value={
|
||||||
'status': 'open',
|
'status': 'open',
|
||||||
'type': 'limit',
|
'type': 'limit',
|
||||||
@ -671,7 +672,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
|
|||||||
amount = trade.amount
|
amount = trade.amount
|
||||||
# make an limit-buy open trade, if there is no 'filled', don't sell it
|
# make an limit-buy open trade, if there is no 'filled', don't sell it
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
'freqtrade.exchange.Exchange.get_order',
|
'freqtrade.exchange.Exchange.fetch_order',
|
||||||
return_value={
|
return_value={
|
||||||
'status': 'open',
|
'status': 'open',
|
||||||
'type': 'limit',
|
'type': 'limit',
|
||||||
@ -688,7 +689,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
|
|||||||
freqtradebot.enter_positions()
|
freqtradebot.enter_positions()
|
||||||
# make an limit-sell open trade
|
# make an limit-sell open trade
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
'freqtrade.exchange.Exchange.get_order',
|
'freqtrade.exchange.Exchange.fetch_order',
|
||||||
return_value={
|
return_value={
|
||||||
'status': 'open',
|
'status': 'open',
|
||||||
'type': 'limit',
|
'type': 'limit',
|
||||||
|
@ -9,13 +9,12 @@ from unittest.mock import ANY, MagicMock, PropertyMock
|
|||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
|
||||||
|
|
||||||
from freqtrade.constants import (CANCEL_REASON, MATH_CLOSE_PREC,
|
from freqtrade.constants import (CANCEL_REASON, MATH_CLOSE_PREC,
|
||||||
UNLIMITED_STAKE_AMOUNT)
|
UNLIMITED_STAKE_AMOUNT)
|
||||||
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
|
from freqtrade.exceptions import (DependencyException, ExchangeError,
|
||||||
OperationalException, PricingError,
|
InvalidOrderException, OperationalException,
|
||||||
TemporaryError)
|
PricingError, TemporaryError)
|
||||||
from freqtrade.freqtradebot import FreqtradeBot
|
from freqtrade.freqtradebot import FreqtradeBot
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
from freqtrade.rpc import RPCMessageType
|
from freqtrade.rpc import RPCMessageType
|
||||||
@ -763,7 +762,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order,
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
fetch_ticker=ticker,
|
fetch_ticker=ticker,
|
||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
get_order=MagicMock(return_value=limit_buy_order),
|
fetch_order=MagicMock(return_value=limit_buy_order),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
@ -832,7 +831,7 @@ def test_process_trade_handling(default_conf, ticker, limit_buy_order, fee, mock
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
fetch_ticker=ticker,
|
fetch_ticker=ticker,
|
||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
get_order=MagicMock(return_value=limit_buy_order),
|
fetch_order=MagicMock(return_value=limit_buy_order),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
@ -859,7 +858,7 @@ def test_process_trade_no_whitelist_pair(default_conf, ticker, limit_buy_order,
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
fetch_ticker=ticker,
|
fetch_ticker=ticker,
|
||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
get_order=MagicMock(return_value=limit_buy_order),
|
fetch_order=MagicMock(return_value=limit_buy_order),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
@ -1064,7 +1063,7 @@ def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None
|
|||||||
patch_RPCManager(mocker)
|
patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
|
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order)
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
|
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
|
||||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
|
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
|
||||||
return_value=limit_buy_order['amount'])
|
return_value=limit_buy_order['amount'])
|
||||||
@ -1126,7 +1125,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
|
|||||||
trade.stoploss_order_id = 100
|
trade.stoploss_order_id = 100
|
||||||
|
|
||||||
hanging_stoploss_order = MagicMock(return_value={'status': 'open'})
|
hanging_stoploss_order = MagicMock(return_value={'status': 'open'})
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', hanging_stoploss_order)
|
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', hanging_stoploss_order)
|
||||||
|
|
||||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||||
assert trade.stoploss_order_id == 100
|
assert trade.stoploss_order_id == 100
|
||||||
@ -1139,7 +1138,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
|
|||||||
trade.stoploss_order_id = 100
|
trade.stoploss_order_id = 100
|
||||||
|
|
||||||
canceled_stoploss_order = MagicMock(return_value={'status': 'canceled'})
|
canceled_stoploss_order = MagicMock(return_value={'status': 'canceled'})
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', canceled_stoploss_order)
|
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', canceled_stoploss_order)
|
||||||
stoploss.reset_mock()
|
stoploss.reset_mock()
|
||||||
|
|
||||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||||
@ -1164,7 +1163,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
|
|||||||
'average': 2,
|
'average': 2,
|
||||||
'amount': limit_buy_order['amount'],
|
'amount': limit_buy_order['amount'],
|
||||||
})
|
})
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', stoploss_order_hit)
|
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hit)
|
||||||
assert freqtrade.handle_stoploss_on_exchange(trade) is True
|
assert freqtrade.handle_stoploss_on_exchange(trade) is True
|
||||||
assert log_has('STOP_LOSS_LIMIT is hit for {}.'.format(trade), caplog)
|
assert log_has('STOP_LOSS_LIMIT is hit for {}.'.format(trade), caplog)
|
||||||
assert trade.stoploss_order_id is None
|
assert trade.stoploss_order_id is None
|
||||||
@ -1172,18 +1171,18 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
|
|||||||
|
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
'freqtrade.exchange.Exchange.stoploss',
|
'freqtrade.exchange.Exchange.stoploss',
|
||||||
side_effect=DependencyException()
|
side_effect=ExchangeError()
|
||||||
)
|
)
|
||||||
trade.is_open = True
|
trade.is_open = True
|
||||||
freqtrade.handle_stoploss_on_exchange(trade)
|
freqtrade.handle_stoploss_on_exchange(trade)
|
||||||
assert log_has('Unable to place a stoploss order on exchange.', caplog)
|
assert log_has('Unable to place a stoploss order on exchange.', caplog)
|
||||||
assert trade.stoploss_order_id is None
|
assert trade.stoploss_order_id is None
|
||||||
|
|
||||||
# Fifth case: get_order returns InvalidOrder
|
# Fifth case: fetch_order returns InvalidOrder
|
||||||
# It should try to add stoploss order
|
# It should try to add stoploss order
|
||||||
trade.stoploss_order_id = 100
|
trade.stoploss_order_id = 100
|
||||||
stoploss.reset_mock()
|
stoploss.reset_mock()
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order',
|
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order',
|
||||||
side_effect=InvalidOrderException())
|
side_effect=InvalidOrderException())
|
||||||
mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss)
|
mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss)
|
||||||
freqtrade.handle_stoploss_on_exchange(trade)
|
freqtrade.handle_stoploss_on_exchange(trade)
|
||||||
@ -1194,7 +1193,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
|
|||||||
trade.stoploss_order_id = None
|
trade.stoploss_order_id = None
|
||||||
trade.is_open = False
|
trade.is_open = False
|
||||||
stoploss.reset_mock()
|
stoploss.reset_mock()
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_order')
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order')
|
||||||
mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss)
|
mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss)
|
||||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||||
assert stoploss.call_count == 0
|
assert stoploss.call_count == 0
|
||||||
@ -1215,8 +1214,8 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf, fee, caplog,
|
|||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
|
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
get_stoploss_order=MagicMock(return_value={'status': 'canceled'}),
|
fetch_stoploss_order=MagicMock(return_value={'status': 'canceled'}),
|
||||||
stoploss=MagicMock(side_effect=DependencyException()),
|
stoploss=MagicMock(side_effect=ExchangeError()),
|
||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
patch_get_signal(freqtrade)
|
patch_get_signal(freqtrade)
|
||||||
@ -1249,7 +1248,7 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf, caplog, fee,
|
|||||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||||
sell=sell_mock,
|
sell=sell_mock,
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
get_order=MagicMock(return_value={'status': 'canceled'}),
|
fetch_order=MagicMock(return_value={'status': 'canceled'}),
|
||||||
stoploss=MagicMock(side_effect=InvalidOrderException()),
|
stoploss=MagicMock(side_effect=InvalidOrderException()),
|
||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
@ -1332,7 +1331,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee, caplog,
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', stoploss_order_hanging)
|
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hanging)
|
||||||
|
|
||||||
# stoploss initially at 5%
|
# stoploss initially at 5%
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
@ -1432,7 +1431,7 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c
|
|||||||
}
|
}
|
||||||
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
|
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
|
||||||
side_effect=InvalidOrderException())
|
side_effect=InvalidOrderException())
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', stoploss_order_hanging)
|
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hanging)
|
||||||
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
|
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
|
||||||
assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/BTC.*", caplog)
|
assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/BTC.*", caplog)
|
||||||
|
|
||||||
@ -1442,7 +1441,7 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c
|
|||||||
# Fail creating stoploss order
|
# Fail creating stoploss order
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
cancel_mock = mocker.patch("freqtrade.exchange.Exchange.cancel_stoploss_order", MagicMock())
|
cancel_mock = mocker.patch("freqtrade.exchange.Exchange.cancel_stoploss_order", MagicMock())
|
||||||
mocker.patch("freqtrade.exchange.Exchange.stoploss", side_effect=DependencyException())
|
mocker.patch("freqtrade.exchange.Exchange.stoploss", side_effect=ExchangeError())
|
||||||
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
|
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
|
||||||
assert cancel_mock.call_count == 1
|
assert cancel_mock.call_count == 1
|
||||||
assert log_has_re(r"Could not create trailing stoploss order for pair ETH/BTC\..*", caplog)
|
assert log_has_re(r"Could not create trailing stoploss order for pair ETH/BTC\..*", caplog)
|
||||||
@ -1512,7 +1511,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', stoploss_order_hanging)
|
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hanging)
|
||||||
|
|
||||||
# stoploss initially at 20% as edge dictated it.
|
# stoploss initially at 20% as edge dictated it.
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
@ -1589,7 +1588,7 @@ def test_exit_positions(mocker, default_conf, limit_buy_order, caplog) -> None:
|
|||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
|
|
||||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
|
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order)
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
|
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
|
||||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
|
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
|
||||||
return_value=limit_buy_order['amount'])
|
return_value=limit_buy_order['amount'])
|
||||||
@ -1613,7 +1612,7 @@ def test_exit_positions(mocker, default_conf, limit_buy_order, caplog) -> None:
|
|||||||
|
|
||||||
def test_exit_positions_exception(mocker, default_conf, limit_buy_order, caplog) -> None:
|
def test_exit_positions_exception(mocker, default_conf, limit_buy_order, caplog) -> None:
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order)
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order)
|
||||||
|
|
||||||
trade = MagicMock()
|
trade = MagicMock()
|
||||||
trade.open_order_id = None
|
trade.open_order_id = None
|
||||||
@ -1634,7 +1633,7 @@ def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> No
|
|||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
|
|
||||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
|
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order)
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
|
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
|
||||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
|
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_real_amount',
|
||||||
return_value=limit_buy_order['amount'])
|
return_value=limit_buy_order['amount'])
|
||||||
@ -1673,8 +1672,8 @@ def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> No
|
|||||||
def test_update_trade_state_withorderdict(default_conf, trades_for_order, limit_buy_order, fee,
|
def test_update_trade_state_withorderdict(default_conf, trades_for_order, limit_buy_order, fee,
|
||||||
mocker):
|
mocker):
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
|
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
|
||||||
# get_order should not be called!!
|
# fetch_order should not be called!!
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_order', MagicMock(side_effect=ValueError))
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order', MagicMock(side_effect=ValueError))
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
Trade.session = MagicMock()
|
Trade.session = MagicMock()
|
||||||
amount = sum(x['amount'] for x in trades_for_order)
|
amount = sum(x['amount'] for x in trades_for_order)
|
||||||
@ -1698,8 +1697,8 @@ def test_update_trade_state_withorderdict_rounding_fee(default_conf, trades_for_
|
|||||||
limit_buy_order, mocker, caplog):
|
limit_buy_order, mocker, caplog):
|
||||||
trades_for_order[0]['amount'] = limit_buy_order['amount'] + 1e-14
|
trades_for_order[0]['amount'] = limit_buy_order['amount'] + 1e-14
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
|
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
|
||||||
# get_order should not be called!!
|
# fetch_order should not be called!!
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_order', MagicMock(side_effect=ValueError))
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order', MagicMock(side_effect=ValueError))
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
Trade.session = MagicMock()
|
Trade.session = MagicMock()
|
||||||
amount = sum(x['amount'] for x in trades_for_order)
|
amount = sum(x['amount'] for x in trades_for_order)
|
||||||
@ -1724,7 +1723,7 @@ def test_update_trade_state_withorderdict_rounding_fee(default_conf, trades_for_
|
|||||||
def test_update_trade_state_exception(mocker, default_conf,
|
def test_update_trade_state_exception(mocker, default_conf,
|
||||||
limit_buy_order, caplog) -> None:
|
limit_buy_order, caplog) -> None:
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_order', return_value=limit_buy_order)
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order', return_value=limit_buy_order)
|
||||||
|
|
||||||
trade = MagicMock()
|
trade = MagicMock()
|
||||||
trade.open_order_id = '123'
|
trade.open_order_id = '123'
|
||||||
@ -1741,7 +1740,7 @@ def test_update_trade_state_exception(mocker, default_conf,
|
|||||||
|
|
||||||
def test_update_trade_state_orderexception(mocker, default_conf, caplog) -> None:
|
def test_update_trade_state_orderexception(mocker, default_conf, caplog) -> None:
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_order',
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order',
|
||||||
MagicMock(side_effect=InvalidOrderException))
|
MagicMock(side_effect=InvalidOrderException))
|
||||||
|
|
||||||
trade = MagicMock()
|
trade = MagicMock()
|
||||||
@ -1757,8 +1756,8 @@ def test_update_trade_state_orderexception(mocker, default_conf, caplog) -> None
|
|||||||
|
|
||||||
def test_update_trade_state_sell(default_conf, trades_for_order, limit_sell_order, mocker):
|
def test_update_trade_state_sell(default_conf, trades_for_order, limit_sell_order, mocker):
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
|
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
|
||||||
# get_order should not be called!!
|
# fetch_order should not be called!!
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_order', MagicMock(side_effect=ValueError))
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order', MagicMock(side_effect=ValueError))
|
||||||
wallet_mock = MagicMock()
|
wallet_mock = MagicMock()
|
||||||
mocker.patch('freqtrade.wallets.Wallets.update', wallet_mock)
|
mocker.patch('freqtrade.wallets.Wallets.update', wallet_mock)
|
||||||
|
|
||||||
@ -1973,7 +1972,7 @@ def test_check_handle_timedout_buy_usercustom(default_conf, ticker, limit_buy_or
|
|||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
fetch_ticker=ticker,
|
fetch_ticker=ticker,
|
||||||
get_order=MagicMock(return_value=limit_buy_order_old),
|
fetch_order=MagicMock(return_value=limit_buy_order_old),
|
||||||
cancel_order=cancel_order_mock,
|
cancel_order=cancel_order_mock,
|
||||||
get_fee=fee
|
get_fee=fee
|
||||||
)
|
)
|
||||||
@ -2022,7 +2021,7 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, op
|
|||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
fetch_ticker=ticker,
|
fetch_ticker=ticker,
|
||||||
get_order=MagicMock(return_value=limit_buy_order_old),
|
fetch_order=MagicMock(return_value=limit_buy_order_old),
|
||||||
cancel_order_with_result=cancel_order_mock,
|
cancel_order_with_result=cancel_order_mock,
|
||||||
get_fee=fee
|
get_fee=fee
|
||||||
)
|
)
|
||||||
@ -2052,7 +2051,7 @@ def test_check_handle_cancelled_buy(default_conf, ticker, limit_buy_order_old, o
|
|||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
fetch_ticker=ticker,
|
fetch_ticker=ticker,
|
||||||
get_order=MagicMock(return_value=limit_buy_order_old),
|
fetch_order=MagicMock(return_value=limit_buy_order_old),
|
||||||
cancel_order=cancel_order_mock,
|
cancel_order=cancel_order_mock,
|
||||||
get_fee=fee
|
get_fee=fee
|
||||||
)
|
)
|
||||||
@ -2079,7 +2078,7 @@ def test_check_handle_timedout_buy_exception(default_conf, ticker, limit_buy_ord
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
validate_pairs=MagicMock(),
|
validate_pairs=MagicMock(),
|
||||||
fetch_ticker=ticker,
|
fetch_ticker=ticker,
|
||||||
get_order=MagicMock(side_effect=DependencyException),
|
fetch_order=MagicMock(side_effect=ExchangeError),
|
||||||
cancel_order=cancel_order_mock,
|
cancel_order=cancel_order_mock,
|
||||||
get_fee=fee
|
get_fee=fee
|
||||||
)
|
)
|
||||||
@ -2105,7 +2104,7 @@ def test_check_handle_timedout_sell_usercustom(default_conf, ticker, limit_sell_
|
|||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
fetch_ticker=ticker,
|
fetch_ticker=ticker,
|
||||||
get_order=MagicMock(return_value=limit_sell_order_old),
|
fetch_order=MagicMock(return_value=limit_sell_order_old),
|
||||||
cancel_order=cancel_order_mock
|
cancel_order=cancel_order_mock
|
||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
@ -2152,7 +2151,7 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old,
|
|||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
fetch_ticker=ticker,
|
fetch_ticker=ticker,
|
||||||
get_order=MagicMock(return_value=limit_sell_order_old),
|
fetch_order=MagicMock(return_value=limit_sell_order_old),
|
||||||
cancel_order=cancel_order_mock
|
cancel_order=cancel_order_mock
|
||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
@ -2183,7 +2182,7 @@ def test_check_handle_cancelled_sell(default_conf, ticker, limit_sell_order_old,
|
|||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
fetch_ticker=ticker,
|
fetch_ticker=ticker,
|
||||||
get_order=MagicMock(return_value=limit_sell_order_old),
|
fetch_order=MagicMock(return_value=limit_sell_order_old),
|
||||||
cancel_order_with_result=cancel_order_mock
|
cancel_order_with_result=cancel_order_mock
|
||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
@ -2210,7 +2209,7 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
|
|||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
fetch_ticker=ticker,
|
fetch_ticker=ticker,
|
||||||
get_order=MagicMock(return_value=limit_buy_order_old_partial),
|
fetch_order=MagicMock(return_value=limit_buy_order_old_partial),
|
||||||
cancel_order_with_result=cancel_order_mock
|
cancel_order_with_result=cancel_order_mock
|
||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
@ -2238,7 +2237,7 @@ def test_check_handle_timedout_partial_fee(default_conf, ticker, open_trade, cap
|
|||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
fetch_ticker=ticker,
|
fetch_ticker=ticker,
|
||||||
get_order=MagicMock(return_value=limit_buy_order_old_partial),
|
fetch_order=MagicMock(return_value=limit_buy_order_old_partial),
|
||||||
cancel_order_with_result=cancel_order_mock,
|
cancel_order_with_result=cancel_order_mock,
|
||||||
get_trades_for_order=MagicMock(return_value=trades_for_order),
|
get_trades_for_order=MagicMock(return_value=trades_for_order),
|
||||||
)
|
)
|
||||||
@ -2276,7 +2275,7 @@ def test_check_handle_timedout_partial_except(default_conf, ticker, open_trade,
|
|||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
fetch_ticker=ticker,
|
fetch_ticker=ticker,
|
||||||
get_order=MagicMock(return_value=limit_buy_order_old_partial),
|
fetch_order=MagicMock(return_value=limit_buy_order_old_partial),
|
||||||
cancel_order_with_result=cancel_order_mock,
|
cancel_order_with_result=cancel_order_mock,
|
||||||
get_trades_for_order=MagicMock(return_value=trades_for_order),
|
get_trades_for_order=MagicMock(return_value=trades_for_order),
|
||||||
)
|
)
|
||||||
@ -2320,7 +2319,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke
|
|||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
fetch_ticker=ticker,
|
fetch_ticker=ticker,
|
||||||
get_order=MagicMock(side_effect=requests.exceptions.RequestException('Oh snap')),
|
fetch_order=MagicMock(side_effect=ExchangeError('Oh snap')),
|
||||||
cancel_order=cancel_order_mock
|
cancel_order=cancel_order_mock
|
||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
@ -2774,7 +2773,7 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, f
|
|||||||
"fee": None,
|
"fee": None,
|
||||||
"trades": None
|
"trades": None
|
||||||
})
|
})
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_stoploss_order', stoploss_executed)
|
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_executed)
|
||||||
|
|
||||||
freqtrade.exit_positions(trades)
|
freqtrade.exit_positions(trades)
|
||||||
assert trade.stoploss_order_id is None
|
assert trade.stoploss_order_id is None
|
||||||
@ -4017,7 +4016,7 @@ def test_sync_wallet_dry_run(mocker, default_conf, ticker, fee, limit_buy_order,
|
|||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
def test_cancel_all_open_orders(mocker, default_conf, fee, limit_buy_order, limit_sell_order):
|
def test_cancel_all_open_orders(mocker, default_conf, fee, limit_buy_order, limit_sell_order):
|
||||||
default_conf['cancel_open_orders_on_exit'] = True
|
default_conf['cancel_open_orders_on_exit'] = True
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_order',
|
mocker.patch('freqtrade.exchange.Exchange.fetch_order',
|
||||||
side_effect=[DependencyException(), limit_sell_order, limit_buy_order])
|
side_effect=[DependencyException(), limit_sell_order, limit_buy_order])
|
||||||
buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_buy')
|
buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_buy')
|
||||||
sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_sell')
|
sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_sell')
|
||||||
|
@ -62,7 +62,7 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
|||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
amount_to_precision=lambda s, x, y: y,
|
amount_to_precision=lambda s, x, y: y,
|
||||||
price_to_precision=lambda s, x, y: y,
|
price_to_precision=lambda s, x, y: y,
|
||||||
get_stoploss_order=stoploss_order_mock,
|
fetch_stoploss_order=stoploss_order_mock,
|
||||||
cancel_stoploss_order=cancel_order_mock,
|
cancel_stoploss_order=cancel_order_mock,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user