Merge branch 'develop' into keep_dataframe_noapi

This commit is contained in:
Matthias 2020-06-30 07:46:52 +02:00
commit c2a6f70b4c
49 changed files with 635 additions and 230 deletions

View File

@ -82,6 +82,7 @@
"listen_port": 8080, "listen_port": 8080,
"verbosity": "info", "verbosity": "info",
"jwt_secret_key": "somethingrandom", "jwt_secret_key": "somethingrandom",
"CORS_origins": [],
"username": "", "username": "",
"password": "" "password": ""
}, },

View File

@ -87,6 +87,7 @@
"listen_port": 8080, "listen_port": 8080,
"verbosity": "info", "verbosity": "info",
"jwt_secret_key": "somethingrandom", "jwt_secret_key": "somethingrandom",
"CORS_origins": [],
"username": "", "username": "",
"password": "" "password": ""
}, },

View File

@ -64,6 +64,7 @@
"sort_key": "quoteVolume", "sort_key": "quoteVolume",
"refresh_period": 1800 "refresh_period": 1800
}, },
{"method": "AgeFilter", "min_days_listed": 10},
{"method": "PrecisionFilter"}, {"method": "PrecisionFilter"},
{"method": "PriceFilter", "low_price_ratio": 0.01}, {"method": "PriceFilter", "low_price_ratio": 0.01},
{"method": "SpreadFilter", "max_spread_ratio": 0.005} {"method": "SpreadFilter", "max_spread_ratio": 0.005}
@ -123,6 +124,7 @@
"listen_port": 8080, "listen_port": 8080,
"verbosity": "info", "verbosity": "info",
"jwt_secret_key": "somethingrandom", "jwt_secret_key": "somethingrandom",
"CORS_origins": [],
"username": "freqtrader", "username": "freqtrader",
"password": "SuperSecurePassword" "password": "SuperSecurePassword"
}, },

View File

@ -93,6 +93,7 @@
"listen_port": 8080, "listen_port": 8080,
"verbosity": "info", "verbosity": "info",
"jwt_secret_key": "somethingrandom", "jwt_secret_key": "somethingrandom",
"CORS_origins": [],
"username": "", "username": "",
"password": "" "password": ""
}, },

View File

@ -592,7 +592,7 @@ Pairlist Handlers define the list of pairs (pairlist) that the bot should trade.
In your configuration, you can use Static Pairlist (defined by the [`StaticPairList`](#static-pair-list) Pairlist Handler) and Dynamic Pairlist (defined by the [`VolumePairList`](#volume-pair-list) Pairlist Handler). In your configuration, you can use Static Pairlist (defined by the [`StaticPairList`](#static-pair-list) Pairlist Handler) and Dynamic Pairlist (defined by the [`VolumePairList`](#volume-pair-list) Pairlist Handler).
Additionaly, [`PrecisionFilter`](#precisionfilter), [`PriceFilter`](#pricefilter), [`ShuffleFilter`](#shufflefilter) and [`SpreadFilter`](#spreadfilter) act as Pairlist Filters, removing certain pairs and/or moving their positions in the pairlist. Additionaly, [`AgeFilter`](#agefilter), [`PrecisionFilter`](#precisionfilter), [`PriceFilter`](#pricefilter), [`ShuffleFilter`](#shufflefilter) and [`SpreadFilter`](#spreadfilter) act as Pairlist Filters, removing certain pairs and/or moving their positions in the pairlist.
If multiple Pairlist Handlers are used, they are chained and a combination of all Pairlist Handlers forms the resulting pairlist the bot uses for trading and backtesting. Pairlist Handlers are executed in the sequence they are configured. You should always configure either `StaticPairList` or `VolumePairList` as the starting Pairlist Handler. If multiple Pairlist Handlers are used, they are chained and a combination of all Pairlist Handlers forms the resulting pairlist the bot uses for trading and backtesting. Pairlist Handlers are executed in the sequence they are configured. You should always configure either `StaticPairList` or `VolumePairList` as the starting Pairlist Handler.
@ -602,6 +602,7 @@ Inactive markets are always removed from the resulting pairlist. Explicitly blac
* [`StaticPairList`](#static-pair-list) (default, if not configured differently) * [`StaticPairList`](#static-pair-list) (default, if not configured differently)
* [`VolumePairList`](#volume-pair-list) * [`VolumePairList`](#volume-pair-list)
* [`AgeFilter`](#agefilter)
* [`PrecisionFilter`](#precisionfilter) * [`PrecisionFilter`](#precisionfilter)
* [`PriceFilter`](#pricefilter) * [`PriceFilter`](#pricefilter)
* [`ShuffleFilter`](#shufflefilter) * [`ShuffleFilter`](#shufflefilter)
@ -645,6 +646,16 @@ The `refresh_period` setting allows to define the period (in seconds), at which
}], }],
``` ```
#### AgeFilter
Removes pairs that have been listed on the exchange for less than `min_days_listed` days (defaults to `10`).
When pairs are first listed on an exchange they can suffer huge price drops and volatility
in the first few days while the pair goes through its price-discovery period. Bots can often
be caught out buying before the pair has finished dropping in price.
This filter allows freqtrade to ignore pairs until they have been listed for at least `min_days_listed` days.
#### PrecisionFilter #### PrecisionFilter
Filters low-value coins which would not allow setting stoplosses. Filters low-value coins which would not allow setting stoplosses.
@ -692,6 +703,7 @@ The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets,
"number_assets": 20, "number_assets": 20,
"sort_key": "quoteVolume", "sort_key": "quoteVolume",
}, },
{"method": "AgeFilter", "min_days_listed": 10},
{"method": "PrecisionFilter"}, {"method": "PrecisionFilter"},
{"method": "PriceFilter", "low_price_ratio": 0.01}, {"method": "PriceFilter", "low_price_ratio": 0.01},
{"method": "SpreadFilter", "max_spread_ratio": 0.005}, {"method": "SpreadFilter", "max_spread_ratio": 0.005},

View File

@ -49,6 +49,16 @@ You can use the `/forcesell all` command from Telegram.
Please look at the [advanced setup documentation Page](advanced-setup.md#running-multiple-instances-of-freqtrade). Please look at the [advanced setup documentation Page](advanced-setup.md#running-multiple-instances-of-freqtrade).
### I'm getting "Missing data fillup" messages in the log
This message is just a warning that the latest candles had missing candles in them.
Depending on the exchange, this can indicate that the pair didn't have a trade for the timeframe you are using - and the exchange does only return candles with volume.
On low volume pairs, this is a rather common occurance.
If this happens for all pairs in the pairlist, this might indicate a recent exchange downtime. Please check your exchange's public channels for details.
Irrespectively of the reason, Freqtrade will fill up these candles with "empty" candles, where open, high, low and close are set to the previous candle close - and volume is empty. In a chart, this will look like a `_` - and is aligned with how exchanges usually represent 0 volume candles.
### I'm getting the "RESTRICTED_MARKET" message in the log ### I'm getting the "RESTRICTED_MARKET" message in the log
Currently known to happen for US Bittrex users. Currently known to happen for US Bittrex users.

View File

@ -1,2 +1,2 @@
mkdocs-material==5.3.0 mkdocs-material==5.3.3
mdx_truly_sane_lists==1.2 mdx_truly_sane_lists==1.2

View File

@ -13,6 +13,7 @@ Sample configuration:
"listen_port": 8080, "listen_port": 8080,
"verbosity": "info", "verbosity": "info",
"jwt_secret_key": "somethingrandom", "jwt_secret_key": "somethingrandom",
"CORS_origins": [],
"username": "Freqtrader", "username": "Freqtrader",
"password": "SuperSecret1!" "password": "SuperSecret1!"
}, },
@ -232,3 +233,26 @@ Since the access token has a short timeout (15 min) - the `token/refresh` reques
> curl -X POST --header "Authorization: Bearer ${refresh_token}"http://localhost:8080/api/v1/token/refresh > curl -X POST --header "Authorization: Bearer ${refresh_token}"http://localhost:8080/api/v1/token/refresh
{"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1ODkxMTk5NzQsIm5iZiI6MTU4OTExOTk3NCwianRpIjoiMDBjNTlhMWUtMjBmYS00ZTk0LTliZjAtNWQwNTg2MTdiZDIyIiwiZXhwIjoxNTg5MTIwODc0LCJpZGVudGl0eSI6eyJ1IjoiRnJlcXRyYWRlciJ9LCJmcmVzaCI6ZmFsc2UsInR5cGUiOiJhY2Nlc3MifQ.1seHlII3WprjjclY6DpRhen0rqdF4j6jbvxIhUFaSbs"} {"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1ODkxMTk5NzQsIm5iZiI6MTU4OTExOTk3NCwianRpIjoiMDBjNTlhMWUtMjBmYS00ZTk0LTliZjAtNWQwNTg2MTdiZDIyIiwiZXhwIjoxNTg5MTIwODc0LCJpZGVudGl0eSI6eyJ1IjoiRnJlcXRyYWRlciJ9LCJmcmVzaCI6ZmFsc2UsInR5cGUiOiJhY2Nlc3MifQ.1seHlII3WprjjclY6DpRhen0rqdF4j6jbvxIhUFaSbs"}
``` ```
## CORS
All web-based frontends are subject to [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) - Cross-Origin Resource Sharing.
Since most of the requests to the Freqtrade API must be authenticated, a proper CORS policy is key to avoid security problems.
Also, the standard disallows `*` CORS policies for requests with credentials, so this setting must be set appropriately.
Users can configure this themselves via the `CORS_origins` configuration setting.
It consists of a list of allowed sites that are allowed to consume resources from the bot's API.
Assuming your application is deployed as `https://frequi.freqtrade.io/home/` - this would mean that the following configuration becomes necessary:
```jsonc
{
//...
"jwt_secret_key": "somethingrandom",
"CORS_origins": ["https://frequi.freqtrade.io"],
//...
}
```
!!! Note
We strongly recommend to also set `jwt_secret_key` to something random and known only to yourself to avoid unauthorized access to your bot.

View File

@ -22,7 +22,8 @@ ORDERBOOK_SIDES = ['ask', 'bid']
ORDERTYPE_POSSIBILITIES = ['limit', 'market'] ORDERTYPE_POSSIBILITIES = ['limit', 'market']
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList',
'PrecisionFilter', 'PriceFilter', 'ShuffleFilter', 'SpreadFilter'] 'AgeFilter', 'PrecisionFilter', 'PriceFilter',
'ShuffleFilter', 'SpreadFilter']
AVAILABLE_DATAHANDLERS = ['json', 'jsongz'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz']
DRY_RUN_WALLET = 1000 DRY_RUN_WALLET = 1000
MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons
@ -221,6 +222,8 @@ CONF_SCHEMA = {
}, },
'username': {'type': 'string'}, 'username': {'type': 'string'},
'password': {'type': 'string'}, 'password': {'type': 'string'},
'jwt_secret_key': {'type': 'string'},
'CORS_origins': {'type': 'array', 'items': {'type': 'string'}},
'verbosity': {'type': 'string', 'enum': ['error', 'info']}, 'verbosity': {'type': 'string', 'enum': ['error', 'info']},
}, },
'required': ['enabled', 'listen_ip_address', 'listen_port', 'username', 'password'] 'required': ['enabled', 'listen_ip_address', 'listen_port', 'username', 'password']

View File

@ -13,7 +13,7 @@ from pandas import DataFrame
from freqtrade.constants import ListPairsWithTimeframes, PairWithTimeframe from freqtrade.constants import ListPairsWithTimeframes, PairWithTimeframe
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
@ -132,7 +132,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]:

View File

@ -270,6 +270,11 @@ def _download_trades_history(exchange: Exchange,
# DEFAULT_TRADES_COLUMNS: 0 -> timestamp # DEFAULT_TRADES_COLUMNS: 0 -> timestamp
# DEFAULT_TRADES_COLUMNS: 1 -> id # DEFAULT_TRADES_COLUMNS: 1 -> id
if trades and since < trades[0][0]:
# since is before the first trade
logger.info(f"Start earlier than available data. Redownloading trades for {pair}...")
trades = []
from_id = trades[-1][1] if trades else None from_id = trades[-1][1] if trades else None
if trades and since < trades[-1][0]: if trades and since < trades[-1][0]:
# Reset since to the last available point # Reset since to the last available point

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
@ -765,7 +765,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
@ -783,8 +783,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)
@ -900,8 +900,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
@ -933,7 +933,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
@ -1220,7 +1220,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

View File

@ -65,20 +65,6 @@ class Backtesting:
self.strategylist: List[IStrategy] = [] self.strategylist: List[IStrategy] = []
self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config) self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config)
self.pairlists = PairListManager(self.exchange, self.config)
if 'VolumePairList' in self.pairlists.name_list:
raise OperationalException("VolumePairList not allowed for backtesting.")
self.pairlists.refresh_pairlist()
if len(self.pairlists.whitelist) == 0:
raise OperationalException("No pair in whitelist.")
if config.get('fee'):
self.fee = config['fee']
else:
self.fee = self.exchange.get_fee(symbol=self.pairlists.whitelist[0])
if self.config.get('runmode') != RunMode.HYPEROPT: if self.config.get('runmode') != RunMode.HYPEROPT:
self.dataprovider = DataProvider(self.config, self.exchange) self.dataprovider = DataProvider(self.config, self.exchange)
IStrategy.dp = self.dataprovider IStrategy.dp = self.dataprovider
@ -101,6 +87,25 @@ class Backtesting:
self.timeframe = str(self.config.get('timeframe')) self.timeframe = str(self.config.get('timeframe'))
self.timeframe_min = timeframe_to_minutes(self.timeframe) self.timeframe_min = timeframe_to_minutes(self.timeframe)
self.pairlists = PairListManager(self.exchange, self.config)
if 'VolumePairList' in self.pairlists.name_list:
raise OperationalException("VolumePairList not allowed for backtesting.")
if len(self.strategylist) > 1 and 'PrecisionFilter' in self.pairlists.name_list:
raise OperationalException(
"PrecisionFilter not allowed for backtesting multiple strategies."
)
self.pairlists.refresh_pairlist()
if len(self.pairlists.whitelist) == 0:
raise OperationalException("No pair in whitelist.")
if config.get('fee'):
self.fee = config['fee']
else:
self.fee = self.exchange.get_fee(symbol=self.pairlists.whitelist[0])
# Get maximum required startup period # Get maximum required startup period
self.required_startup = max([strat.startup_candle_count for strat in self.strategylist]) self.required_startup = max([strat.startup_candle_count for strat in self.strategylist])
# Load one (first) strategy # Load one (first) strategy

View File

@ -0,0 +1,76 @@
"""
Minimum age (days listed) pair list filter
"""
import logging
import arrow
from typing import Any, Dict
from freqtrade.misc import plural
from freqtrade.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__)
class AgeFilter(IPairList):
# Checked symbols cache (dictionary of ticker symbol => timestamp)
_symbolsChecked: Dict[str, int] = {}
def __init__(self, exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any],
pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
self._min_days_listed = pairlistconfig.get('min_days_listed', 10)
self._enabled = self._min_days_listed >= 1
@property
def needstickers(self) -> bool:
"""
Boolean property defining if tickers are necessary.
If no Pairlist requires tickers, an empty List is passed
as tickers argument to filter_pairlist
"""
return True
def short_desc(self) -> str:
"""
Short whitelist method description - used for startup-messages
"""
return (f"{self.name} - Filtering pairs with age less than "
f"{self._min_days_listed} {plural(self._min_days_listed, 'day')}.")
def _validate_pair(self, ticker: dict) -> bool:
"""
Validate age for the ticker
:param ticker: ticker dict as returned from ccxt.load_markets()
:return: True if the pair can stay, False if it should be removed
"""
# Check symbol in cache
if ticker['symbol'] in self._symbolsChecked:
return True
since_ms = int(arrow.utcnow()
.floor('day')
.shift(days=-self._min_days_listed)
.float_timestamp) * 1000
daily_candles = self._exchange.get_historic_ohlcv(pair=ticker['symbol'],
timeframe='1d',
since_ms=since_ms)
if daily_candles is not None:
if len(daily_candles) > self._min_days_listed:
# We have fetched at least the minimum required number of daily candles
# Add to cache, store the time we last checked this symbol
self._symbolsChecked[ticker['symbol']] = int(arrow.utcnow().float_timestamp) * 1000
return True
else:
self.log_on_refresh(logger.info, f"Removed {ticker['symbol']} from whitelist, "
f"because age is less than "
f"{self._min_days_listed} "
f"{plural(self._min_days_listed, 'day')}")
return False
return False

View File

@ -68,7 +68,7 @@ class IPairList(ABC):
def needstickers(self) -> bool: def needstickers(self) -> bool:
""" """
Boolean property defining if tickers are necessary. Boolean property defining if tickers are necessary.
If no Pairlist requries tickers, an empty List is passed If no Pairlist requires tickers, an empty List is passed
as tickers argument to filter_pairlist as tickers argument to filter_pairlist
""" """

View File

@ -5,7 +5,7 @@ import logging
from typing import Any, Dict from typing import Any, Dict
from freqtrade.pairlist.IPairList import IPairList from freqtrade.pairlist.IPairList import IPairList
from freqtrade.exceptions import OperationalException
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -17,6 +17,10 @@ class PrecisionFilter(IPairList):
pairlist_pos: int) -> None: pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos) super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
if 'stoploss' not in self._config:
raise OperationalException(
'PrecisionFilter can only work with stoploss defined. Please add the '
'stoploss key to your configuration (overwrites eventual strategy settings).')
self._stoploss = self._config['stoploss'] self._stoploss = self._config['stoploss']
self._enabled = self._stoploss != 0 self._enabled = self._stoploss != 0
@ -27,7 +31,7 @@ class PrecisionFilter(IPairList):
def needstickers(self) -> bool: def needstickers(self) -> bool:
""" """
Boolean property defining if tickers are necessary. Boolean property defining if tickers are necessary.
If no Pairlist requries tickers, an empty List is passed If no Pairlist requires tickers, an empty List is passed
as tickers argument to filter_pairlist as tickers argument to filter_pairlist
""" """
return True return True

View File

@ -24,7 +24,7 @@ class PriceFilter(IPairList):
def needstickers(self) -> bool: def needstickers(self) -> bool:
""" """
Boolean property defining if tickers are necessary. Boolean property defining if tickers are necessary.
If no Pairlist requries tickers, an empty List is passed If no Pairlist requires tickers, an empty List is passed
as tickers argument to filter_pairlist as tickers argument to filter_pairlist
""" """
return True return True

View File

@ -25,7 +25,7 @@ class ShuffleFilter(IPairList):
def needstickers(self) -> bool: def needstickers(self) -> bool:
""" """
Boolean property defining if tickers are necessary. Boolean property defining if tickers are necessary.
If no Pairlist requries tickers, an empty List is passed If no Pairlist requires tickers, an empty List is passed
as tickers argument to filter_pairlist as tickers argument to filter_pairlist
""" """
return False return False

View File

@ -24,7 +24,7 @@ class SpreadFilter(IPairList):
def needstickers(self) -> bool: def needstickers(self) -> bool:
""" """
Boolean property defining if tickers are necessary. Boolean property defining if tickers are necessary.
If no Pairlist requries tickers, an empty List is passed If no Pairlist requires tickers, an empty List is passed
as tickers argument to filter_pairlist as tickers argument to filter_pairlist
""" """
return True return True

View File

@ -28,7 +28,7 @@ class StaticPairList(IPairList):
def needstickers(self) -> bool: def needstickers(self) -> bool:
""" """
Boolean property defining if tickers are necessary. Boolean property defining if tickers are necessary.
If no Pairlist requries tickers, an empty List is passed If no Pairlist requires tickers, an empty List is passed
as tickers argument to filter_pairlist as tickers argument to filter_pairlist
""" """
return False return False

View File

@ -54,7 +54,7 @@ class VolumePairList(IPairList):
def needstickers(self) -> bool: def needstickers(self) -> bool:
""" """
Boolean property defining if tickers are necessary. Boolean property defining if tickers are necessary.
If no Pairlist requries tickers, an empty List is passed If no Pairlist requires tickers, an empty List is passed
as tickers argument to filter_pairlist as tickers argument to filter_pairlist
""" """
return True return True

View File

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

View File

@ -90,7 +90,9 @@ class ApiServer(RPC):
self._config = freqtrade.config self._config = freqtrade.config
self.app = Flask(__name__) self.app = Flask(__name__)
self._cors = CORS(self.app, self._cors = CORS(self.app,
resources={r"/api/*": {"supports_credentials": True, }} resources={r"/api/*": {
"supports_credentials": True,
"origins": self._config['api_server'].get('CORS_origins', [])}}
) )
# Setup the Flask-JWT-Extended extension # Setup the Flask-JWT-Extended extension

View File

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

View File

@ -59,6 +59,7 @@
"listen_port": 8080, "listen_port": 8080,
"verbosity": "info", "verbosity": "info",
"jwt_secret_key": "somethingrandom", "jwt_secret_key": "somethingrandom",
"CORS_origins": [],
"username": "", "username": "",
"password": "" "password": ""
}, },

View File

@ -1,11 +1,11 @@
# requirements without requirements installable via conda # requirements without requirements installable via conda
# mainly used for Raspberry pi installs # mainly used for Raspberry pi installs
ccxt==1.30.2 ccxt==1.30.48
SQLAlchemy==1.3.17 SQLAlchemy==1.3.18
python-telegram-bot==12.7 python-telegram-bot==12.8
arrow==0.15.6 arrow==0.15.7
cachetools==4.1.0 cachetools==4.1.1
requests==2.23.0 requests==2.24.0
urllib3==1.25.9 urllib3==1.25.9
wrapt==1.12.1 wrapt==1.12.1
jsonschema==3.2.0 jsonschema==3.2.0

View File

@ -7,9 +7,9 @@ coveralls==2.0.0
flake8==3.8.3 flake8==3.8.3
flake8-type-annotations==0.1.0 flake8-type-annotations==0.1.0
flake8-tidy-imports==4.1.0 flake8-tidy-imports==4.1.0
mypy==0.780 mypy==0.782
pytest==5.4.3 pytest==5.4.3
pytest-asyncio==0.12.0 pytest-asyncio==0.14.0
pytest-cov==2.10.0 pytest-cov==2.10.0
pytest-mock==3.1.1 pytest-mock==3.1.1
pytest-random-order==1.0.4 pytest-random-order==1.0.4

View File

@ -2,9 +2,9 @@
-r requirements.txt -r requirements.txt
# Required for hyperopt # Required for hyperopt
scipy==1.4.1 scipy==1.5.0
scikit-learn==0.23.1 scikit-learn==0.23.1
scikit-optimize==0.7.4 scikit-optimize==0.7.4
filelock==3.0.12 filelock==3.0.12
joblib==0.15.1 joblib==0.15.1
progressbar2==3.51.3 progressbar2==3.51.4

View File

@ -1,5 +1,5 @@
# Include all requirements to run the bot. # Include all requirements to run the bot.
-r requirements.txt -r requirements.txt
plotly==4.8.1 plotly==4.8.2

View File

@ -1,5 +1,5 @@
# Load common requirements # Load common requirements
-r requirements-common.txt -r requirements-common.txt
numpy==1.18.5 numpy==1.19.0
pandas==1.0.4 pandas==1.0.5

View File

@ -1425,7 +1425,7 @@ def trades_for_order():
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def trades_history(): def trades_history():
return [[1565798399463, '126181329', None, 'buy', 0.019627, 0.04, 0.00078508], return [[1565798389463, '126181329', None, 'buy', 0.019627, 0.04, 0.00078508],
[1565798399629, '126181330', None, 'buy', 0.019627, 0.244, 0.004788987999999999], [1565798399629, '126181330', None, 'buy', 0.019627, 0.244, 0.004788987999999999],
[1565798399752, '126181331', None, 'sell', 0.019626, 0.011, 0.00021588599999999999], [1565798399752, '126181331', None, 'sell', 0.019626, 0.011, 0.00021588599999999999],
[1565798399862, '126181332', None, 'sell', 0.019626, 0.011, 0.00021588599999999999], [1565798399862, '126181332', None, 'sell', 0.019626, 0.011, 0.00021588599999999999],

View File

@ -5,7 +5,7 @@ import pytest
from pandas import DataFrame from pandas import DataFrame
from freqtrade.data.dataprovider import DataProvider from freqtrade.data.dataprovider import DataProvider
from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.exceptions import ExchangeError, OperationalException
from freqtrade.pairlist.pairlistmanager import PairListManager from freqtrade.pairlist.pairlistmanager import PairListManager
from freqtrade.state import RunMode from freqtrade.state import RunMode
from tests.conftest import get_patched_exchange from tests.conftest import get_patched_exchange
@ -165,7 +165,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)

View File

@ -557,6 +557,7 @@ def test_download_trades_history(trades_history, mocker, default_conf, testdatad
assert ght_mock.call_count == 1 assert ght_mock.call_count == 1
# Check this in seconds - since we had to convert to seconds above too. # Check this in seconds - since we had to convert to seconds above too.
assert int(ght_mock.call_args_list[0][1]['since'] // 1000) == since_time2 - 5 assert int(ght_mock.call_args_list[0][1]['since'] // 1000) == since_time2 - 5
assert ght_mock.call_args_list[0][1]['from_id'] is not None
# clean files freshly downloaded # clean files freshly downloaded
_clean_test_file(file1) _clean_test_file(file1)
@ -568,6 +569,27 @@ def test_download_trades_history(trades_history, mocker, default_conf, testdatad
pair='ETH/BTC') pair='ETH/BTC')
assert log_has_re('Failed to download historic trades for pair: "ETH/BTC".*', caplog) assert log_has_re('Failed to download historic trades for pair: "ETH/BTC".*', caplog)
file2 = testdatadir / 'XRP_ETH-trades.json.gz'
_backup_file(file2, True)
ght_mock.reset_mock()
mocker.patch('freqtrade.exchange.Exchange.get_historic_trades',
ght_mock)
# Since before first start date
since_time = int(trades_history[0][0] // 1000) - 500
timerange = TimeRange('date', None, since_time, 0)
assert _download_trades_history(data_handler=data_handler, exchange=exchange,
pair='XRP/ETH', timerange=timerange)
assert ght_mock.call_count == 1
assert int(ght_mock.call_args_list[0][1]['since'] // 1000) == since_time
assert ght_mock.call_args_list[0][1]['from_id'] is None
assert log_has_re(r'Start earlier than available data. Redownloading trades for.*', caplog)
_clean_test_file(file2)
def test_convert_trades_to_ohlcv(mocker, default_conf, testdatadir, caplog): def test_convert_trades_to_ohlcv(mocker, default_conf, testdatadir, caplog):

View File

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

View File

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

View File

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

View File

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

View File

@ -401,6 +401,38 @@ def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) ->
Backtesting(default_conf) Backtesting(default_conf)
def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, tickers) -> None:
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.data.history.get_timerange', get_timerange)
patch_exchange(mocker)
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest')
mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['XRP/BTC']))
mocker.patch('freqtrade.pairlist.pairlistmanager.PairListManager.refresh_pairlist')
default_conf['ticker_interval'] = "1m"
default_conf['datadir'] = testdatadir
default_conf['export'] = None
# Use stoploss from strategy
del default_conf['stoploss']
default_conf['timerange'] = '20180101-20180102'
default_conf['pairlists'] = [{"method": "VolumePairList", "number_assets": 5}]
with pytest.raises(OperationalException, match='VolumePairList not allowed for backtesting.'):
Backtesting(default_conf)
default_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PrecisionFilter"}, ]
Backtesting(default_conf)
# Multiple strategies
default_conf['strategy_list'] = ['DefaultStrategy', 'TestStrategyLegacy']
with pytest.raises(OperationalException,
match='PrecisionFilter not allowed for backtesting multiple strategies.'):
Backtesting(default_conf)
def test_backtest(default_conf, fee, mocker, testdatadir) -> None: def test_backtest(default_conf, fee, mocker, testdatadir) -> None:
default_conf['ask_strategy']['use_sell_signal'] = False default_conf['ask_strategy']['use_sell_signal'] = False
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)

View File

@ -57,6 +57,31 @@ def whitelist_conf_2(default_conf):
return default_conf return default_conf
@pytest.fixture(scope="function")
def whitelist_conf_3(default_conf):
default_conf['stake_currency'] = 'BTC'
default_conf['exchange']['pair_whitelist'] = [
'ETH/BTC', 'TKN/BTC', 'BLK/BTC', 'LTC/BTC',
'BTT/BTC', 'HOT/BTC', 'FUEL/BTC', 'XRP/BTC'
]
default_conf['exchange']['pair_blacklist'] = [
'BLK/BTC'
]
default_conf['pairlists'] = [
{
"method": "VolumePairList",
"number_assets": 5,
"sort_key": "quoteVolume",
"refresh_period": 0,
},
{
"method": "AgeFilter",
"min_days_listed": 2
}
]
return default_conf
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def static_pl_conf(whitelist_conf): def static_pl_conf(whitelist_conf):
whitelist_conf['pairlists'] = [ whitelist_conf['pairlists'] = [
@ -220,11 +245,20 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
# No pair for ETH, all handlers # No pair for ETH, all handlers
([{"method": "StaticPairList"}, ([{"method": "StaticPairList"},
{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, {"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
{"method": "AgeFilter", "min_days_listed": 2},
{"method": "PrecisionFilter"}, {"method": "PrecisionFilter"},
{"method": "PriceFilter", "low_price_ratio": 0.03}, {"method": "PriceFilter", "low_price_ratio": 0.03},
{"method": "SpreadFilter", "max_spread_ratio": 0.005}, {"method": "SpreadFilter", "max_spread_ratio": 0.005},
{"method": "ShuffleFilter"}], {"method": "ShuffleFilter"}],
"ETH", []), "ETH", []),
# AgeFilter and VolumePairList (require 2 days only, all should pass age test)
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
{"method": "AgeFilter", "min_days_listed": 2}],
"BTC", ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC', 'HOT/BTC']),
# AgeFilter and VolumePairList (require 10 days, all should fail age test)
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
{"method": "AgeFilter", "min_days_listed": 10}],
"BTC", []),
# Precisionfilter and quote volume # Precisionfilter and quote volume
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
{"method": "PrecisionFilter"}], {"method": "PrecisionFilter"}],
@ -272,7 +306,10 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
# ShuffleFilter, no seed # ShuffleFilter, no seed
([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"}, ([{"method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume"},
{"method": "ShuffleFilter"}], {"method": "ShuffleFilter"}],
"USDT", 3), # whitelist_result is integer -- check only lenght of randomized pairlist "USDT", 3), # whitelist_result is integer -- check only length of randomized pairlist
# AgeFilter only
([{"method": "AgeFilter", "min_days_listed": 2}],
"BTC", 'filter_at_the_beginning'), # OperationalException expected
# PrecisionFilter after StaticPairList # PrecisionFilter after StaticPairList
([{"method": "StaticPairList"}, ([{"method": "StaticPairList"},
{"method": "PrecisionFilter"}], {"method": "PrecisionFilter"}],
@ -307,8 +344,8 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
"BTC", 'static_in_the_middle'), "BTC", 'static_in_the_middle'),
]) ])
def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers, def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers,
pairlists, base_currency, whitelist_result, ohlcv_history_list, pairlists, base_currency,
caplog) -> None: whitelist_result, caplog) -> None:
whitelist_conf['pairlists'] = pairlists whitelist_conf['pairlists'] = pairlists
whitelist_conf['stake_currency'] = base_currency whitelist_conf['stake_currency'] = base_currency
@ -324,8 +361,12 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
mocker.patch.multiple('freqtrade.exchange.Exchange', mocker.patch.multiple('freqtrade.exchange.Exchange',
get_tickers=tickers, get_tickers=tickers,
markets=PropertyMock(return_value=shitcoinmarkets), markets=PropertyMock(return_value=shitcoinmarkets)
) )
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list),
)
# Set whitelist_result to None if pairlist is invalid and should produce exception # Set whitelist_result to None if pairlist is invalid and should produce exception
if whitelist_result == 'filter_at_the_beginning': if whitelist_result == 'filter_at_the_beginning':
@ -346,6 +387,10 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
len(whitelist) == whitelist_result len(whitelist) == whitelist_result
for pairlist in pairlists: for pairlist in pairlists:
if pairlist['method'] == 'AgeFilter' and pairlist['min_days_listed'] and \
len(ohlcv_history_list) <= pairlist['min_days_listed']:
assert log_has_re(r'^Removed .* from whitelist, because age is less than '
r'.* day.*', caplog)
if pairlist['method'] == 'PrecisionFilter' and whitelist_result: if pairlist['method'] == 'PrecisionFilter' and whitelist_result:
assert log_has_re(r'^Removed .* from whitelist, because stop price .* ' assert log_has_re(r'^Removed .* from whitelist, because stop price .* '
r'would be <= stop limit.*', caplog) r'would be <= stop limit.*', caplog)
@ -362,6 +407,17 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
assert not log_has(logmsg, caplog) assert not log_has(logmsg, caplog)
def test_PrecisionFilter_error(mocker, whitelist_conf, tickers) -> None:
whitelist_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PrecisionFilter"}]
del whitelist_conf['stoploss']
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
with pytest.raises(OperationalException,
match=r"PrecisionFilter can only work with stoploss defined\..*"):
PairListManager(MagicMock, whitelist_conf)
def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None: def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None:
default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}] default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}]
@ -468,6 +524,29 @@ def test_volumepairlist_caching(mocker, markets, whitelist_conf, tickers):
assert freqtrade.pairlists._pairlist_handlers[0]._last_refresh == lrf assert freqtrade.pairlists._pairlist_handlers[0]._last_refresh == lrf
def test_agefilter_caching(mocker, markets, whitelist_conf_3, tickers, ohlcv_history_list):
mocker.patch.multiple('freqtrade.exchange.Exchange',
markets=PropertyMock(return_value=markets),
exchange_has=MagicMock(return_value=True),
get_tickers=tickers
)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_historic_ohlcv=MagicMock(return_value=ohlcv_history_list),
)
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf_3)
assert freqtrade.exchange.get_historic_ohlcv.call_count == 0
freqtrade.pairlists.refresh_pairlist()
assert freqtrade.exchange.get_historic_ohlcv.call_count > 0
previous_call_count = freqtrade.exchange.get_historic_ohlcv.call_count
freqtrade.pairlists.refresh_pairlist()
# Should not have increased since first call.
assert freqtrade.exchange.get_historic_ohlcv.call_count == previous_call_count
def test_pairlistmanager_no_pairlist(mocker, markets, whitelist_conf, caplog): def test_pairlistmanager_no_pairlist(mocker, markets, whitelist_conf, caplog):
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))

View File

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

View File

@ -24,6 +24,7 @@ def botclient(default_conf, mocker):
default_conf.update({"api_server": {"enabled": True, default_conf.update({"api_server": {"enabled": True,
"listen_ip_address": "127.0.0.1", "listen_ip_address": "127.0.0.1",
"listen_port": 8080, "listen_port": 8080,
"CORS_origins": ['http://example.com'],
"username": _TEST_USER, "username": _TEST_USER,
"password": _TEST_PASS, "password": _TEST_PASS,
}}) }})
@ -40,13 +41,13 @@ def client_post(client, url, data={}):
content_type="application/json", content_type="application/json",
data=data, data=data,
headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS), headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS),
'Origin': 'example.com'}) 'Origin': 'http://example.com'})
def client_get(client, url): def client_get(client, url):
# Add fake Origin to ensure CORS kicks in # Add fake Origin to ensure CORS kicks in
return client.get(url, headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS), return client.get(url, headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS),
'Origin': 'example.com'}) 'Origin': 'http://example.com'})
def assert_response(response, expected_code=200, needs_cors=True): def assert_response(response, expected_code=200, needs_cors=True):
@ -54,6 +55,7 @@ def assert_response(response, expected_code=200, needs_cors=True):
assert response.content_type == "application/json" assert response.content_type == "application/json"
if needs_cors: if needs_cors:
assert ('Access-Control-Allow-Credentials', 'true') in response.headers._list assert ('Access-Control-Allow-Credentials', 'true') in response.headers._list
assert ('Access-Control-Allow-Origin', 'http://example.com') in response.headers._list
def test_api_not_found(botclient): def test_api_not_found(botclient):
@ -110,7 +112,7 @@ def test_api_token_login(botclient):
rc = client.get(f"{BASE_URI}/count", rc = client.get(f"{BASE_URI}/count",
content_type="application/json", content_type="application/json",
headers={'Authorization': f'Bearer {rc.json["access_token"]}', headers={'Authorization': f'Bearer {rc.json["access_token"]}',
'Origin': 'example.com'}) 'Origin': 'http://example.com'})
assert_response(rc) assert_response(rc)
@ -122,7 +124,7 @@ def test_api_token_refresh(botclient):
content_type="application/json", content_type="application/json",
data=None, data=None,
headers={'Authorization': f'Bearer {rc.json["refresh_token"]}', headers={'Authorization': f'Bearer {rc.json["refresh_token"]}',
'Origin': 'example.com'}) 'Origin': 'http://example.com'})
assert_response(rc) assert_response(rc)
assert 'access_token' in rc.json assert 'access_token' in rc.json
assert 'refresh_token' not in rc.json assert 'refresh_token' not in rc.json

View File

@ -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)
@ -1107,7 +1106,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'])
@ -1169,7 +1168,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
@ -1182,7 +1181,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
@ -1207,7 +1206,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
@ -1215,18 +1214,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)
@ -1237,7 +1236,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
@ -1258,8 +1257,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)
@ -1292,7 +1291,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)
@ -1375,7 +1374,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
@ -1475,7 +1474,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)
@ -1485,7 +1484,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)
@ -1555,7 +1554,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
@ -1632,7 +1631,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'])
@ -1656,7 +1655,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
@ -1677,7 +1676,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'])
@ -1716,8 +1715,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)
@ -1741,8 +1740,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)
@ -1767,7 +1766,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'
@ -1784,7 +1783,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()
@ -1800,8 +1799,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)
@ -2028,7 +2027,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
) )
@ -2077,7 +2076,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
) )
@ -2107,7 +2106,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
) )
@ -2134,7 +2133,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
) )
@ -2160,7 +2159,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)
@ -2207,7 +2206,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)
@ -2238,7 +2237,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)
@ -2265,7 +2264,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)
@ -2293,7 +2292,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),
) )
@ -2331,7 +2330,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),
) )
@ -2375,7 +2374,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)
@ -2840,7 +2839,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
@ -4083,7 +4082,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')

View File

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