Merge branch 'develop' into refactor-informative
This commit is contained in:
commit
6fa8750fea
@ -163,7 +163,7 @@ def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None:
|
|||||||
)
|
)
|
||||||
except TemplateNotFound:
|
except TemplateNotFound:
|
||||||
selections['exchange'] = render_template(
|
selections['exchange'] = render_template(
|
||||||
templatefile=f"subtemplates/exchange_generic.j2",
|
templatefile="subtemplates/exchange_generic.j2",
|
||||||
arguments=selections
|
arguments=selections
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -372,8 +372,8 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
),
|
),
|
||||||
"timeframes": Arg(
|
"timeframes": Arg(
|
||||||
'-t', '--timeframes',
|
'-t', '--timeframes',
|
||||||
help=f'Specify which tickers to download. Space-separated list. '
|
help='Specify which tickers to download. Space-separated list. '
|
||||||
f'Default: `1m 5m`.',
|
'Default: `1m 5m`.',
|
||||||
choices=['1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h',
|
choices=['1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h',
|
||||||
'6h', '8h', '12h', '1d', '3d', '1w'],
|
'6h', '8h', '12h', '1d', '3d', '1w'],
|
||||||
default=['1m', '5m'],
|
default=['1m', '5m'],
|
||||||
|
@ -51,7 +51,7 @@ def deploy_new_strategy(strategy_name: str, strategy_path: Path, subtemplate: st
|
|||||||
)
|
)
|
||||||
additional_methods = render_template_with_fallback(
|
additional_methods = render_template_with_fallback(
|
||||||
templatefile=f"subtemplates/strategy_methods_{subtemplate}.j2",
|
templatefile=f"subtemplates/strategy_methods_{subtemplate}.j2",
|
||||||
templatefallbackfile=f"subtemplates/strategy_methods_empty.j2",
|
templatefallbackfile="subtemplates/strategy_methods_empty.j2",
|
||||||
)
|
)
|
||||||
|
|
||||||
strategy_text = render_template(templatefile='base_strategy.py.j2',
|
strategy_text = render_template(templatefile='base_strategy.py.j2',
|
||||||
|
@ -367,8 +367,7 @@ class Exchange:
|
|||||||
f"Invalid timeframe '{timeframe}'. This exchange supports: {self.timeframes}")
|
f"Invalid timeframe '{timeframe}'. This exchange supports: {self.timeframes}")
|
||||||
|
|
||||||
if timeframe and timeframe_to_minutes(timeframe) < 1:
|
if timeframe and timeframe_to_minutes(timeframe) < 1:
|
||||||
raise OperationalException(
|
raise OperationalException("Timeframes < 1m are currently not supported by Freqtrade.")
|
||||||
f"Timeframes < 1m are currently not supported by Freqtrade.")
|
|
||||||
|
|
||||||
def validate_ordertypes(self, order_types: Dict) -> None:
|
def validate_ordertypes(self, order_types: Dict) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -874,10 +874,10 @@ class FreqtradeBot:
|
|||||||
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
|
||||||
|
|
||||||
trade_state_update = self.update_trade_state(trade, order)
|
fully_cancelled = self.update_trade_state(trade, order)
|
||||||
|
|
||||||
if (order['side'] == 'buy' and (
|
if (order['side'] == 'buy' and (order['status'] == 'open' or fully_cancelled) and (
|
||||||
trade_state_update
|
fully_cancelled
|
||||||
or self._check_timed_out('buy', order)
|
or self._check_timed_out('buy', order)
|
||||||
or strategy_safe_wrapper(self.strategy.check_buy_timeout,
|
or strategy_safe_wrapper(self.strategy.check_buy_timeout,
|
||||||
default_retval=False)(pair=trade.pair,
|
default_retval=False)(pair=trade.pair,
|
||||||
@ -885,8 +885,8 @@ class FreqtradeBot:
|
|||||||
order=order))):
|
order=order))):
|
||||||
self.handle_cancel_buy(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
self.handle_cancel_buy(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||||
|
|
||||||
elif (order['side'] == 'sell' and (
|
elif (order['side'] == 'sell' and (order['status'] == 'open' or fully_cancelled) and (
|
||||||
trade_state_update
|
fully_cancelled
|
||||||
or self._check_timed_out('sell', order)
|
or self._check_timed_out('sell', order)
|
||||||
or strategy_safe_wrapper(self.strategy.check_sell_timeout,
|
or strategy_safe_wrapper(self.strategy.check_sell_timeout,
|
||||||
default_retval=False)(pair=trade.pair,
|
default_retval=False)(pair=trade.pair,
|
||||||
@ -1121,6 +1121,11 @@ class FreqtradeBot:
|
|||||||
"""
|
"""
|
||||||
Sends rpc notification when a sell cancel occured.
|
Sends rpc notification when a sell cancel occured.
|
||||||
"""
|
"""
|
||||||
|
if trade.sell_order_status == reason:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
trade.sell_order_status = reason
|
||||||
|
|
||||||
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
|
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
|
||||||
profit_trade = trade.calc_profit(rate=profit_rate)
|
profit_trade = trade.calc_profit(rate=profit_rate)
|
||||||
current_rate = self.get_sell_rate(trade.pair, False)
|
current_rate = self.get_sell_rate(trade.pair, False)
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
Static List provider
|
PairList base class
|
||||||
|
"""
|
||||||
Provides lists as configured in config.json
|
|
||||||
|
|
||||||
"""
|
|
||||||
import logging
|
import logging
|
||||||
from abc import ABC, abstractmethod, abstractproperty
|
from abc import ABC, abstractmethod, abstractproperty
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
@ -13,6 +10,7 @@ from cachetools import TTLCache, cached
|
|||||||
|
|
||||||
from freqtrade.exchange import market_is_active
|
from freqtrade.exchange import market_is_active
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,14 +1,26 @@
|
|||||||
|
"""
|
||||||
|
Precision pair list filter
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from typing import Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from freqtrade.pairlist.IPairList import IPairList
|
from freqtrade.pairlist.IPairList import IPairList
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class PrecisionFilter(IPairList):
|
class PrecisionFilter(IPairList):
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Precalculate sanitized stoploss value to avoid recalculation for every pair
|
||||||
|
self._stoploss = 1 - abs(self._config['stoploss'])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def needstickers(self) -> bool:
|
def needstickers(self) -> bool:
|
||||||
"""
|
"""
|
||||||
@ -31,34 +43,32 @@ class PrecisionFilter(IPairList):
|
|||||||
:param ticker: ticker dict as returned from ccxt.load_markets()
|
:param ticker: ticker dict as returned from ccxt.load_markets()
|
||||||
:param stoploss: stoploss value as set in the configuration
|
:param stoploss: stoploss value as set in the configuration
|
||||||
(already cleaned to be 1 - stoploss)
|
(already cleaned to be 1 - stoploss)
|
||||||
:return: True if the pair can stay, false if it should be removed
|
:return: True if the pair can stay, False if it should be removed
|
||||||
"""
|
"""
|
||||||
stop_price = ticker['ask'] * stoploss
|
stop_price = ticker['ask'] * stoploss
|
||||||
|
|
||||||
# Adjust stop-prices to precision
|
# Adjust stop-prices to precision
|
||||||
sp = self._exchange.price_to_precision(ticker["symbol"], stop_price)
|
sp = self._exchange.price_to_precision(ticker["symbol"], stop_price)
|
||||||
|
|
||||||
stop_gap_price = self._exchange.price_to_precision(ticker["symbol"], stop_price * 0.99)
|
stop_gap_price = self._exchange.price_to_precision(ticker["symbol"], stop_price * 0.99)
|
||||||
logger.debug(f"{ticker['symbol']} - {sp} : {stop_gap_price}")
|
logger.debug(f"{ticker['symbol']} - {sp} : {stop_gap_price}")
|
||||||
|
|
||||||
if sp <= stop_gap_price:
|
if sp <= stop_gap_price:
|
||||||
self.log_on_refresh(logger.info,
|
self.log_on_refresh(logger.info,
|
||||||
f"Removed {ticker['symbol']} from whitelist, "
|
f"Removed {ticker['symbol']} from whitelist, "
|
||||||
f"because stop price {sp} would be <= stop limit {stop_gap_price}")
|
f"because stop price {sp} would be <= stop limit {stop_gap_price}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
|
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Filters and sorts pairlists and assigns and returns them again.
|
Filters and sorts pairlists and assigns and returns them again.
|
||||||
"""
|
"""
|
||||||
stoploss = self._config.get('stoploss')
|
|
||||||
if stoploss is not None:
|
|
||||||
# Precalculate sanitized stoploss value to avoid recalculation for every pair
|
|
||||||
stoploss = 1 - abs(stoploss)
|
|
||||||
# Copy list since we're modifying this list
|
# Copy list since we're modifying this list
|
||||||
for p in deepcopy(pairlist):
|
for p in deepcopy(pairlist):
|
||||||
ticker = tickers.get(p)
|
|
||||||
# Filter out assets which would not allow setting a stoploss
|
# Filter out assets which would not allow setting a stoploss
|
||||||
if not ticker or (stoploss and not self._validate_precision_filter(ticker, stoploss)):
|
if not self._validate_precision_filter(tickers[p], self._stoploss):
|
||||||
pairlist.remove(p)
|
pairlist.remove(p)
|
||||||
continue
|
|
||||||
|
|
||||||
return pairlist
|
return pairlist
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
|
"""
|
||||||
|
Price pair list filter
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from freqtrade.pairlist.IPairList import IPairList
|
from freqtrade.pairlist.IPairList import IPairList
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -38,14 +42,12 @@ class PriceFilter(IPairList):
|
|||||||
:return: True if the pair can stay, false if it should be removed
|
:return: True if the pair can stay, false if it should be removed
|
||||||
"""
|
"""
|
||||||
if ticker['last'] is None:
|
if ticker['last'] is None:
|
||||||
|
|
||||||
self.log_on_refresh(logger.info,
|
self.log_on_refresh(logger.info,
|
||||||
f"Removed {ticker['symbol']} from whitelist, because "
|
f"Removed {ticker['symbol']} from whitelist, because "
|
||||||
"ticker['last'] is empty (Usually no trade in the last 24h).")
|
"ticker['last'] is empty (Usually no trade in the last 24h).")
|
||||||
return False
|
return False
|
||||||
compare = ticker['last'] + self._exchange.price_get_one_pip(ticker['symbol'],
|
compare = self._exchange.price_get_one_pip(ticker['symbol'], ticker['last'])
|
||||||
ticker['last'])
|
changeperc = compare / ticker['last']
|
||||||
changeperc = (compare - ticker['last']) / ticker['last']
|
|
||||||
if changeperc > self._low_price_ratio:
|
if changeperc > self._low_price_ratio:
|
||||||
self.log_on_refresh(logger.info, f"Removed {ticker['symbol']} from whitelist, "
|
self.log_on_refresh(logger.info, f"Removed {ticker['symbol']} from whitelist, "
|
||||||
f"because 1 unit is {changeperc * 100:.3f}%")
|
f"because 1 unit is {changeperc * 100:.3f}%")
|
||||||
@ -60,14 +62,11 @@ class PriceFilter(IPairList):
|
|||||||
:param tickers: Tickers (from exchange.get_tickers()). May be cached.
|
:param tickers: Tickers (from exchange.get_tickers()). May be cached.
|
||||||
:return: new whitelist
|
:return: new whitelist
|
||||||
"""
|
"""
|
||||||
# Copy list since we're modifying this list
|
if self._low_price_ratio:
|
||||||
for p in deepcopy(pairlist):
|
# Copy list since we're modifying this list
|
||||||
ticker = tickers.get(p)
|
for p in deepcopy(pairlist):
|
||||||
if not ticker:
|
# Filter out assets which would not allow setting a stoploss
|
||||||
pairlist.remove(p)
|
if not self._validate_ticker_lowprice(tickers[p]):
|
||||||
|
pairlist.remove(p)
|
||||||
# Filter out assets which would not allow setting a stoploss
|
|
||||||
if self._low_price_ratio and not self._validate_ticker_lowprice(ticker):
|
|
||||||
pairlist.remove(p)
|
|
||||||
|
|
||||||
return pairlist
|
return pairlist
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
|
"""
|
||||||
|
Spread pair list filter
|
||||||
|
"""
|
||||||
import logging
|
import logging
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
from freqtrade.pairlist.IPairList import IPairList
|
from freqtrade.pairlist.IPairList import IPairList
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -31,8 +35,24 @@ class SpreadFilter(IPairList):
|
|||||||
return (f"{self.name} - Filtering pairs with ask/bid diff above "
|
return (f"{self.name} - Filtering pairs with ask/bid diff above "
|
||||||
f"{self._max_spread_ratio * 100}%.")
|
f"{self._max_spread_ratio * 100}%.")
|
||||||
|
|
||||||
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
|
def _validate_spread(self, ticker: dict) -> bool:
|
||||||
|
"""
|
||||||
|
Validate spread 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
|
||||||
|
"""
|
||||||
|
if 'bid' in ticker and 'ask' in ticker:
|
||||||
|
spread = 1 - ticker['bid'] / ticker['ask']
|
||||||
|
if spread > self._max_spread_ratio:
|
||||||
|
self.log_on_refresh(logger.info, f"Removed {ticker['symbol']} from whitelist, "
|
||||||
|
f"because spread {spread * 100:.3f}% >"
|
||||||
|
f"{self._max_spread_ratio * 100}%")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Filters and sorts pairlist and returns the whitelist again.
|
Filters and sorts pairlist and returns the whitelist again.
|
||||||
Called on each bot iteration - please use internal caching if necessary
|
Called on each bot iteration - please use internal caching if necessary
|
||||||
@ -41,19 +61,10 @@ class SpreadFilter(IPairList):
|
|||||||
:return: new whitelist
|
:return: new whitelist
|
||||||
"""
|
"""
|
||||||
# Copy list since we're modifying this list
|
# Copy list since we're modifying this list
|
||||||
|
|
||||||
spread = None
|
|
||||||
for p in deepcopy(pairlist):
|
for p in deepcopy(pairlist):
|
||||||
ticker = tickers.get(p)
|
ticker = tickers[p]
|
||||||
assert ticker is not None
|
# Filter out assets
|
||||||
if 'bid' in ticker and 'ask' in ticker:
|
if not self._validate_spread(ticker):
|
||||||
spread = 1 - ticker['bid'] / ticker['ask']
|
|
||||||
if not ticker or spread > self._max_spread_ratio:
|
|
||||||
self.log_on_refresh(logger.info, f"Removed {ticker['symbol']} from whitelist, "
|
|
||||||
f"because spread {spread * 100:.3f}% >"
|
|
||||||
f"{self._max_spread_ratio * 100}%")
|
|
||||||
pairlist.remove(p)
|
|
||||||
else:
|
|
||||||
pairlist.remove(p)
|
pairlist.remove(p)
|
||||||
|
|
||||||
return pairlist
|
return pairlist
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
"""
|
"""
|
||||||
Static List provider
|
Static Pair List provider
|
||||||
|
|
||||||
Provides lists as configured in config.json
|
Provides pair white list as it configured in config
|
||||||
|
"""
|
||||||
"""
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
from freqtrade.pairlist.IPairList import IPairList
|
from freqtrade.pairlist.IPairList import IPairList
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
Volume PairList provider
|
Volume PairList provider
|
||||||
|
|
||||||
Provides lists as configured in config.json
|
Provides dynamic pair list based on trade volumes
|
||||||
|
"""
|
||||||
"""
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
@ -11,8 +10,10 @@ from typing import Any, Dict, List
|
|||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.pairlist.IPairList import IPairList
|
from freqtrade.pairlist.IPairList import IPairList
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume']
|
SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume']
|
||||||
|
|
||||||
|
|
||||||
@ -24,8 +25,10 @@ class VolumePairList(IPairList):
|
|||||||
|
|
||||||
if 'number_assets' not in self._pairlistconfig:
|
if 'number_assets' not in self._pairlistconfig:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
f'`number_assets` not specified. Please check your configuration '
|
'`number_assets` not specified. Please check your configuration '
|
||||||
'for "pairlist.config.number_assets"')
|
'for "pairlist.config.number_assets"')
|
||||||
|
|
||||||
|
self._stake_currency = config['stake_currency']
|
||||||
self._number_pairs = self._pairlistconfig['number_assets']
|
self._number_pairs = self._pairlistconfig['number_assets']
|
||||||
self._sort_key = self._pairlistconfig.get('sort_key', 'quoteVolume')
|
self._sort_key = self._pairlistconfig.get('sort_key', 'quoteVolume')
|
||||||
self._min_value = self._pairlistconfig.get('min_value', 0)
|
self._min_value = self._pairlistconfig.get('min_value', 0)
|
||||||
@ -36,9 +39,11 @@ class VolumePairList(IPairList):
|
|||||||
'Exchange does not support dynamic whitelist.'
|
'Exchange does not support dynamic whitelist.'
|
||||||
'Please edit your config and restart the bot'
|
'Please edit your config and restart the bot'
|
||||||
)
|
)
|
||||||
|
|
||||||
if not self._validate_keys(self._sort_key):
|
if not self._validate_keys(self._sort_key):
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
f'key {self._sort_key} not in {SORT_VALUES}')
|
f'key {self._sort_key} not in {SORT_VALUES}')
|
||||||
|
|
||||||
if self._sort_key != 'quoteVolume':
|
if self._sort_key != 'quoteVolume':
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"DEPRECATED: using any key other than quoteVolume for VolumePairList is deprecated."
|
"DEPRECATED: using any key other than quoteVolume for VolumePairList is deprecated."
|
||||||
@ -76,42 +81,42 @@ class VolumePairList(IPairList):
|
|||||||
(self._last_refresh + self.refresh_period < datetime.now().timestamp())):
|
(self._last_refresh + self.refresh_period < datetime.now().timestamp())):
|
||||||
|
|
||||||
self._last_refresh = int(datetime.now().timestamp())
|
self._last_refresh = int(datetime.now().timestamp())
|
||||||
pairs = self._gen_pair_whitelist(pairlist, tickers,
|
pairs = self._gen_pair_whitelist(pairlist, tickers)
|
||||||
self._config['stake_currency'],
|
|
||||||
self._sort_key, self._min_value)
|
|
||||||
else:
|
else:
|
||||||
pairs = pairlist
|
pairs = pairlist
|
||||||
|
|
||||||
self.log_on_refresh(logger.info, f"Searching {self._number_pairs} pairs: {pairs}")
|
self.log_on_refresh(logger.info, f"Searching {self._number_pairs} pairs: {pairs}")
|
||||||
|
|
||||||
return pairs
|
return pairs
|
||||||
|
|
||||||
def _gen_pair_whitelist(self, pairlist: List[str], tickers: Dict,
|
def _gen_pair_whitelist(self, pairlist: List[str], tickers: Dict) -> List[str]:
|
||||||
base_currency: str, key: str, min_val: int) -> List[str]:
|
|
||||||
"""
|
"""
|
||||||
Updates the whitelist with with a dynamically generated list
|
Updates the whitelist with with a dynamically generated list
|
||||||
:param base_currency: base currency as str
|
:param pairlist: pairlist to filter or sort
|
||||||
:param key: sort key (defaults to 'quoteVolume')
|
|
||||||
:param tickers: Tickers (from exchange.get_tickers()).
|
:param tickers: Tickers (from exchange.get_tickers()).
|
||||||
:return: List of pairs
|
:return: List of pairs
|
||||||
"""
|
"""
|
||||||
if self._pairlist_pos == 0:
|
if self._pairlist_pos == 0:
|
||||||
# If VolumePairList is the first in the list, use fresh pairlist
|
# If VolumePairList is the first in the list, use fresh pairlist
|
||||||
# Check if pair quote currency equals to the stake currency.
|
# Check if pair quote currency equals to the stake currency.
|
||||||
filtered_tickers = [v for k, v in tickers.items()
|
filtered_tickers = [
|
||||||
if (self._exchange.get_pair_quote_currency(k) == base_currency
|
v for k, v in tickers.items()
|
||||||
and v[key] is not None)]
|
if (self._exchange.get_pair_quote_currency(k) == self._stake_currency
|
||||||
|
and v[self._sort_key] is not None)]
|
||||||
else:
|
else:
|
||||||
# If other pairlist is in front, use the incomming pairlist.
|
# If other pairlist is in front, use the incoming pairlist.
|
||||||
filtered_tickers = [v for k, v in tickers.items() if k in pairlist]
|
filtered_tickers = [v for k, v in tickers.items() if k in pairlist]
|
||||||
|
|
||||||
if min_val > 0:
|
if self._min_value > 0:
|
||||||
filtered_tickers = list(filter(lambda t: t[key] > min_val, filtered_tickers))
|
filtered_tickers = [
|
||||||
|
v for v in filtered_tickers if v[self._sort_key] > self._min_value]
|
||||||
|
|
||||||
sorted_tickers = sorted(filtered_tickers, reverse=True, key=lambda t: t[key])
|
sorted_tickers = sorted(filtered_tickers, reverse=True, key=lambda t: t[self._sort_key])
|
||||||
|
|
||||||
# Validate whitelist to only have active market pairs
|
# Validate whitelist to only have active market pairs
|
||||||
pairs = self._whitelist_for_active_markets([s['symbol'] for s in sorted_tickers])
|
pairs = self._whitelist_for_active_markets([s['symbol'] for s in sorted_tickers])
|
||||||
pairs = self._verify_blacklist(pairs, aswarning=False)
|
pairs = self._verify_blacklist(pairs, aswarning=False)
|
||||||
# Limit to X number of pairs
|
# Limit pairlist to the requested number of pairs
|
||||||
pairs = pairs[:self._number_pairs]
|
pairs = pairs[:self._number_pairs]
|
||||||
|
|
||||||
return pairs
|
return pairs
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
"""
|
"""
|
||||||
Static List provider
|
PairList manager class
|
||||||
|
"""
|
||||||
Provides lists as configured in config.json
|
|
||||||
|
|
||||||
"""
|
|
||||||
import logging
|
import logging
|
||||||
|
from copy import deepcopy
|
||||||
from typing import Dict, List, Tuple
|
from typing import Dict, List, Tuple
|
||||||
|
|
||||||
from cachetools import TTLCache, cached
|
from cachetools import TTLCache, cached
|
||||||
@ -83,25 +81,39 @@ class PairListManager():
|
|||||||
"""
|
"""
|
||||||
Run pairlist through all configured pairlists.
|
Run pairlist through all configured pairlists.
|
||||||
"""
|
"""
|
||||||
|
# Tickers should be cached to avoid calling the exchange on each call.
|
||||||
pairlist = self._whitelist.copy()
|
|
||||||
|
|
||||||
# tickers should be cached to avoid calling the exchange on each call.
|
|
||||||
tickers: Dict = {}
|
tickers: Dict = {}
|
||||||
if self._tickers_needed:
|
if self._tickers_needed:
|
||||||
tickers = self._get_cached_tickers()
|
tickers = self._get_cached_tickers()
|
||||||
|
|
||||||
|
# Adjust whitelist if filters are using tickers
|
||||||
|
pairlist = self._prepare_whitelist(self._whitelist.copy(), tickers)
|
||||||
|
|
||||||
# Process all pairlists in chain
|
# Process all pairlists in chain
|
||||||
for pl in self._pairlists:
|
for pl in self._pairlists:
|
||||||
pairlist = pl.filter_pairlist(pairlist, tickers)
|
pairlist = pl.filter_pairlist(pairlist, tickers)
|
||||||
|
|
||||||
# Validation against blacklist happens after the pairlists to ensure blacklist is respected.
|
# Validation against blacklist happens after the pairlists to ensure
|
||||||
|
# blacklist is respected.
|
||||||
pairlist = IPairList.verify_blacklist(pairlist, self.blacklist, True)
|
pairlist = IPairList.verify_blacklist(pairlist, self.blacklist, True)
|
||||||
|
|
||||||
self._whitelist = pairlist
|
self._whitelist = pairlist
|
||||||
|
|
||||||
|
def _prepare_whitelist(self, pairlist: List[str], tickers) -> List[str]:
|
||||||
|
"""
|
||||||
|
Prepare sanitized pairlist for Pairlist Filters that use tickers data - remove
|
||||||
|
pairs that do not have ticker available
|
||||||
|
"""
|
||||||
|
if self._tickers_needed:
|
||||||
|
# Copy list since we're modifying this list
|
||||||
|
for p in deepcopy(pairlist):
|
||||||
|
if p not in tickers:
|
||||||
|
pairlist.remove(p)
|
||||||
|
|
||||||
|
return pairlist
|
||||||
|
|
||||||
def create_pair_list(self, pairs: List[str], timeframe: str = None) -> ListPairsWithTimeframes:
|
def create_pair_list(self, pairs: List[str], timeframe: str = None) -> ListPairsWithTimeframes:
|
||||||
"""
|
"""
|
||||||
Create list of pair tuples with (pair, ticker_interval)
|
Create list of pair tuples with (pair, ticker_interval)
|
||||||
"""
|
"""
|
||||||
return [(pair, timeframe or self._config['ticker_interval']) for pair in pairs]
|
return [(pair, timeframe or self._config['ticker_interval']) for pair in pairs]
|
@ -86,7 +86,7 @@ def check_migrate(engine) -> None:
|
|||||||
logger.debug(f'trying {table_back_name}')
|
logger.debug(f'trying {table_back_name}')
|
||||||
|
|
||||||
# Check for latest column
|
# Check for latest column
|
||||||
if not has_column(cols, 'fee_close_cost'):
|
if not has_column(cols, 'sell_order_status'):
|
||||||
logger.info(f'Running database migration - backup available as {table_back_name}')
|
logger.info(f'Running database migration - backup available as {table_back_name}')
|
||||||
|
|
||||||
fee_open = get_column_def(cols, 'fee_open', 'fee')
|
fee_open = get_column_def(cols, 'fee_open', 'fee')
|
||||||
@ -113,6 +113,7 @@ def check_migrate(engine) -> None:
|
|||||||
close_profit_abs = get_column_def(
|
close_profit_abs = get_column_def(
|
||||||
cols, 'close_profit_abs',
|
cols, 'close_profit_abs',
|
||||||
f"(amount * close_rate * (1 - {fee_close})) - {open_trade_price}")
|
f"(amount * close_rate * (1 - {fee_close})) - {open_trade_price}")
|
||||||
|
sell_order_status = get_column_def(cols, 'sell_order_status', 'null')
|
||||||
|
|
||||||
# Schema migration necessary
|
# Schema migration necessary
|
||||||
engine.execute(f"alter table trades rename to {table_back_name}")
|
engine.execute(f"alter table trades rename to {table_back_name}")
|
||||||
@ -131,7 +132,7 @@ def check_migrate(engine) -> None:
|
|||||||
stake_amount, amount, open_date, close_date, open_order_id,
|
stake_amount, amount, open_date, close_date, open_order_id,
|
||||||
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
|
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
|
||||||
stoploss_order_id, stoploss_last_update,
|
stoploss_order_id, stoploss_last_update,
|
||||||
max_rate, min_rate, sell_reason, strategy,
|
max_rate, min_rate, sell_reason, sell_order_status, strategy,
|
||||||
ticker_interval, open_trade_price, close_profit_abs
|
ticker_interval, open_trade_price, close_profit_abs
|
||||||
)
|
)
|
||||||
select id, lower(exchange),
|
select id, lower(exchange),
|
||||||
@ -153,6 +154,7 @@ def check_migrate(engine) -> None:
|
|||||||
{initial_stop_loss_pct} initial_stop_loss_pct,
|
{initial_stop_loss_pct} initial_stop_loss_pct,
|
||||||
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
|
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
|
||||||
{max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason,
|
{max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason,
|
||||||
|
{sell_order_status} sell_order_status,
|
||||||
{strategy} strategy, {ticker_interval} ticker_interval,
|
{strategy} strategy, {ticker_interval} ticker_interval,
|
||||||
{open_trade_price} open_trade_price, {close_profit_abs} close_profit_abs
|
{open_trade_price} open_trade_price, {close_profit_abs} close_profit_abs
|
||||||
from {table_back_name}
|
from {table_back_name}
|
||||||
@ -228,6 +230,7 @@ class Trade(_DECL_BASE):
|
|||||||
# Lowest price reached
|
# Lowest price reached
|
||||||
min_rate = Column(Float, nullable=True)
|
min_rate = Column(Float, nullable=True)
|
||||||
sell_reason = Column(String, nullable=True)
|
sell_reason = Column(String, nullable=True)
|
||||||
|
sell_order_status = Column(String, nullable=True)
|
||||||
strategy = Column(String, nullable=True)
|
strategy = Column(String, nullable=True)
|
||||||
ticker_interval = Column(Integer, nullable=True)
|
ticker_interval = Column(Integer, nullable=True)
|
||||||
|
|
||||||
@ -267,6 +270,7 @@ class Trade(_DECL_BASE):
|
|||||||
'stake_amount': round(self.stake_amount, 8),
|
'stake_amount': round(self.stake_amount, 8),
|
||||||
'close_profit': self.close_profit,
|
'close_profit': self.close_profit,
|
||||||
'sell_reason': self.sell_reason,
|
'sell_reason': self.sell_reason,
|
||||||
|
'sell_order_status': self.sell_order_status,
|
||||||
'stop_loss': self.stop_loss,
|
'stop_loss': self.stop_loss,
|
||||||
'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None,
|
'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None,
|
||||||
'initial_stop_loss': self.initial_stop_loss,
|
'initial_stop_loss': self.initial_stop_loss,
|
||||||
@ -370,6 +374,7 @@ class Trade(_DECL_BASE):
|
|||||||
self.close_profit_abs = self.calc_profit()
|
self.close_profit_abs = self.calc_profit()
|
||||||
self.close_date = datetime.utcnow()
|
self.close_date = datetime.utcnow()
|
||||||
self.is_open = False
|
self.is_open = False
|
||||||
|
self.sell_order_status = 'closed'
|
||||||
self.open_order_id = None
|
self.open_order_id = None
|
||||||
logger.info(
|
logger.info(
|
||||||
'Marking %s as closed as the trade is fulfilled and found no open orders for it.',
|
'Marking %s as closed as the trade is fulfilled and found no open orders for it.',
|
||||||
|
@ -186,7 +186,7 @@ class RPC:
|
|||||||
|
|
||||||
def _rpc_daily_profit(
|
def _rpc_daily_profit(
|
||||||
self, timescale: int,
|
self, timescale: int,
|
||||||
stake_currency: str, fiat_display_currency: str) -> List[List[Any]]:
|
stake_currency: str, fiat_display_currency: str) -> Dict[str, Any]:
|
||||||
today = datetime.utcnow().date()
|
today = datetime.utcnow().date()
|
||||||
profit_days: Dict[date, Dict] = {}
|
profit_days: Dict[date, Dict] = {}
|
||||||
|
|
||||||
@ -206,28 +206,26 @@ class RPC:
|
|||||||
'trades': len(trades)
|
'trades': len(trades)
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
data = [
|
||||||
[
|
{
|
||||||
key,
|
'date': key,
|
||||||
'{value:.8f} {symbol}'.format(
|
'abs_profit': f'{float(value["amount"]):.8f}',
|
||||||
value=float(value['amount']),
|
'fiat_value': '{value:.3f}'.format(
|
||||||
symbol=stake_currency
|
|
||||||
),
|
|
||||||
'{value:.3f} {symbol}'.format(
|
|
||||||
value=self._fiat_converter.convert_amount(
|
value=self._fiat_converter.convert_amount(
|
||||||
value['amount'],
|
value['amount'],
|
||||||
stake_currency,
|
stake_currency,
|
||||||
fiat_display_currency
|
fiat_display_currency
|
||||||
) if self._fiat_converter else 0,
|
) if self._fiat_converter else 0,
|
||||||
symbol=fiat_display_currency
|
|
||||||
),
|
),
|
||||||
'{value} trade{s}'.format(
|
'trade_count': f'{value["trades"]}',
|
||||||
value=value['trades'],
|
}
|
||||||
s='' if value['trades'] < 2 else 's'
|
|
||||||
),
|
|
||||||
]
|
|
||||||
for key, value in profit_days.items()
|
for key, value in profit_days.items()
|
||||||
]
|
]
|
||||||
|
return {
|
||||||
|
'stake_currency': stake_currency,
|
||||||
|
'fiat_display_currency': fiat_display_currency,
|
||||||
|
'data': data
|
||||||
|
}
|
||||||
|
|
||||||
def _rpc_trade_history(self, limit: int) -> Dict:
|
def _rpc_trade_history(self, limit: int) -> Dict:
|
||||||
""" Returns the X last trades """
|
""" Returns the X last trades """
|
||||||
@ -547,5 +545,5 @@ class RPC:
|
|||||||
def _rpc_edge(self) -> List[Dict[str, Any]]:
|
def _rpc_edge(self) -> List[Dict[str, Any]]:
|
||||||
""" Returns information related to Edge """
|
""" Returns information related to Edge """
|
||||||
if not self._freqtrade.edge:
|
if not self._freqtrade.edge:
|
||||||
raise RPCException(f'Edge is not enabled.')
|
raise RPCException('Edge is not enabled.')
|
||||||
return self._freqtrade.edge.accepted_pairs()
|
return self._freqtrade.edge.accepted_pairs()
|
||||||
|
@ -226,11 +226,15 @@ class Telegram(RPC):
|
|||||||
# Adding stoploss and stoploss percentage only if it is not None
|
# Adding stoploss and stoploss percentage only if it is not None
|
||||||
"*Stoploss:* `{stop_loss:.8f}` " +
|
"*Stoploss:* `{stop_loss:.8f}` " +
|
||||||
("`({stop_loss_pct:.2f}%)`" if r['stop_loss_pct'] else ""),
|
("`({stop_loss_pct:.2f}%)`" if r['stop_loss_pct'] else ""),
|
||||||
|
|
||||||
"*Open Order:* `{open_order}`" if r['open_order'] else ""
|
|
||||||
]
|
]
|
||||||
|
if r['open_order']:
|
||||||
|
if r['sell_order_status']:
|
||||||
|
lines.append("*Open Order:* `{open_order}` - `{sell_order_status}`")
|
||||||
|
else:
|
||||||
|
lines.append("*Open Order:* `{open_order}`")
|
||||||
|
|
||||||
# Filter empty lines using list-comprehension
|
# Filter empty lines using list-comprehension
|
||||||
messages.append("\n".join([l for l in lines if l]).format(**r))
|
messages.append("\n".join([line for line in lines if line]).format(**r))
|
||||||
|
|
||||||
for msg in messages:
|
for msg in messages:
|
||||||
self._send_msg(msg)
|
self._send_msg(msg)
|
||||||
@ -276,14 +280,18 @@ class Telegram(RPC):
|
|||||||
stake_cur,
|
stake_cur,
|
||||||
fiat_disp_cur
|
fiat_disp_cur
|
||||||
)
|
)
|
||||||
stats_tab = tabulate(stats,
|
stats_tab = tabulate(
|
||||||
headers=[
|
[[day['date'],
|
||||||
'Day',
|
f"{day['abs_profit']} {stats['stake_currency']}",
|
||||||
f'Profit {stake_cur}',
|
f"{day['fiat_value']} {stats['fiat_display_currency']}",
|
||||||
f'Profit {fiat_disp_cur}',
|
f"{day['trade_count']} trades"] for day in stats['data']],
|
||||||
f'Trades'
|
headers=[
|
||||||
],
|
'Day',
|
||||||
tablefmt='simple')
|
f'Profit {stake_cur}',
|
||||||
|
f'Profit {fiat_disp_cur}',
|
||||||
|
'Trades',
|
||||||
|
],
|
||||||
|
tablefmt='simple')
|
||||||
message = f'<b>Daily Profit over the last {timescale} days</b>:\n<pre>{stats_tab}</pre>'
|
message = f'<b>Daily Profit over the last {timescale} days</b>:\n<pre>{stats_tab}</pre>'
|
||||||
self._send_msg(message, parse_mode=ParseMode.HTML)
|
self._send_msg(message, parse_mode=ParseMode.HTML)
|
||||||
except RPCException as e:
|
except RPCException as e:
|
||||||
|
@ -47,9 +47,9 @@ class Webhook(RPC):
|
|||||||
valuedict = self._config['webhook'].get('webhooksell', None)
|
valuedict = self._config['webhook'].get('webhooksell', None)
|
||||||
elif msg['type'] == RPCMessageType.SELL_CANCEL_NOTIFICATION:
|
elif msg['type'] == RPCMessageType.SELL_CANCEL_NOTIFICATION:
|
||||||
valuedict = self._config['webhook'].get('webhooksellcancel', None)
|
valuedict = self._config['webhook'].get('webhooksellcancel', None)
|
||||||
elif msg['type'] in(RPCMessageType.STATUS_NOTIFICATION,
|
elif msg['type'] in (RPCMessageType.STATUS_NOTIFICATION,
|
||||||
RPCMessageType.CUSTOM_NOTIFICATION,
|
RPCMessageType.CUSTOM_NOTIFICATION,
|
||||||
RPCMessageType.WARNING_NOTIFICATION):
|
RPCMessageType.WARNING_NOTIFICATION):
|
||||||
valuedict = self._config['webhook'].get('webhookstatus', None)
|
valuedict = self._config['webhook'].get('webhookstatus', None)
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError('Unknown message type: {}'.format(msg['type']))
|
raise NotImplementedError('Unknown message type: {}'.format(msg['type']))
|
||||||
|
@ -37,9 +37,7 @@ class Worker:
|
|||||||
self._heartbeat_msg: float = 0
|
self._heartbeat_msg: float = 0
|
||||||
|
|
||||||
# Tell systemd that we completed initialization phase
|
# Tell systemd that we completed initialization phase
|
||||||
if self._sd_notify:
|
self._notify("READY=1")
|
||||||
logger.debug("sd_notify: READY=1")
|
|
||||||
self._sd_notify.notify("READY=1")
|
|
||||||
|
|
||||||
def _init(self, reconfig: bool) -> None:
|
def _init(self, reconfig: bool) -> None:
|
||||||
"""
|
"""
|
||||||
@ -60,6 +58,15 @@ class Worker:
|
|||||||
self._sd_notify = sdnotify.SystemdNotifier() if \
|
self._sd_notify = sdnotify.SystemdNotifier() if \
|
||||||
self._config.get('internals', {}).get('sd_notify', False) else None
|
self._config.get('internals', {}).get('sd_notify', False) else None
|
||||||
|
|
||||||
|
def _notify(self, message: str) -> None:
|
||||||
|
"""
|
||||||
|
Removes the need to verify in all occurances if sd_notify is enabled
|
||||||
|
:param message: Message to send to systemd if it's enabled.
|
||||||
|
"""
|
||||||
|
if self._sd_notify:
|
||||||
|
logger.debug(f"sd_notify: {message}")
|
||||||
|
self._sd_notify.notify(message)
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
state = None
|
state = None
|
||||||
while True:
|
while True:
|
||||||
@ -89,17 +96,13 @@ class Worker:
|
|||||||
|
|
||||||
if state == State.STOPPED:
|
if state == State.STOPPED:
|
||||||
# Ping systemd watchdog before sleeping in the stopped state
|
# Ping systemd watchdog before sleeping in the stopped state
|
||||||
if self._sd_notify:
|
self._notify("WATCHDOG=1\nSTATUS=State: STOPPED.")
|
||||||
logger.debug("sd_notify: WATCHDOG=1\\nSTATUS=State: STOPPED.")
|
|
||||||
self._sd_notify.notify("WATCHDOG=1\nSTATUS=State: STOPPED.")
|
|
||||||
|
|
||||||
self._throttle(func=self._process_stopped, throttle_secs=self._throttle_secs)
|
self._throttle(func=self._process_stopped, throttle_secs=self._throttle_secs)
|
||||||
|
|
||||||
elif state == State.RUNNING:
|
elif state == State.RUNNING:
|
||||||
# Ping systemd watchdog before throttling
|
# Ping systemd watchdog before throttling
|
||||||
if self._sd_notify:
|
self._notify("WATCHDOG=1\nSTATUS=State: RUNNING.")
|
||||||
logger.debug("sd_notify: WATCHDOG=1\\nSTATUS=State: RUNNING.")
|
|
||||||
self._sd_notify.notify("WATCHDOG=1\nSTATUS=State: RUNNING.")
|
|
||||||
|
|
||||||
self._throttle(func=self._process_running, throttle_secs=self._throttle_secs)
|
self._throttle(func=self._process_running, throttle_secs=self._throttle_secs)
|
||||||
|
|
||||||
@ -154,9 +157,7 @@ class Worker:
|
|||||||
replaces it with the new instance
|
replaces it with the new instance
|
||||||
"""
|
"""
|
||||||
# Tell systemd that we initiated reconfiguration
|
# Tell systemd that we initiated reconfiguration
|
||||||
if self._sd_notify:
|
self._notify("RELOADING=1")
|
||||||
logger.debug("sd_notify: RELOADING=1")
|
|
||||||
self._sd_notify.notify("RELOADING=1")
|
|
||||||
|
|
||||||
# Clean up current freqtrade modules
|
# Clean up current freqtrade modules
|
||||||
self.freqtrade.cleanup()
|
self.freqtrade.cleanup()
|
||||||
@ -167,15 +168,11 @@ class Worker:
|
|||||||
self.freqtrade.notify_status('config reloaded')
|
self.freqtrade.notify_status('config reloaded')
|
||||||
|
|
||||||
# Tell systemd that we completed reconfiguration
|
# Tell systemd that we completed reconfiguration
|
||||||
if self._sd_notify:
|
self._notify("READY=1")
|
||||||
logger.debug("sd_notify: READY=1")
|
|
||||||
self._sd_notify.notify("READY=1")
|
|
||||||
|
|
||||||
def exit(self) -> None:
|
def exit(self) -> None:
|
||||||
# Tell systemd that we are exiting now
|
# Tell systemd that we are exiting now
|
||||||
if self._sd_notify:
|
self._notify("STOPPING=1")
|
||||||
logger.debug("sd_notify: STOPPING=1")
|
|
||||||
self._sd_notify.notify("STOPPING=1")
|
|
||||||
|
|
||||||
if self.freqtrade:
|
if self.freqtrade:
|
||||||
self.freqtrade.notify_status('process died')
|
self.freqtrade.notify_status('process died')
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# 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.27.49
|
ccxt==1.27.91
|
||||||
SQLAlchemy==1.3.16
|
SQLAlchemy==1.3.17
|
||||||
python-telegram-bot==12.7
|
python-telegram-bot==12.7
|
||||||
arrow==0.15.6
|
arrow==0.15.6
|
||||||
cachetools==4.1.0
|
cachetools==4.1.0
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
-r requirements-hyperopt.txt
|
-r requirements-hyperopt.txt
|
||||||
|
|
||||||
coveralls==2.0.0
|
coveralls==2.0.0
|
||||||
flake8==3.7.9
|
flake8==3.8.1
|
||||||
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.770
|
mypy==0.770
|
||||||
|
@ -1705,7 +1705,7 @@ def hyperopt_results():
|
|||||||
{
|
{
|
||||||
'loss': 0.4366182531161519,
|
'loss': 0.4366182531161519,
|
||||||
'params_dict': {
|
'params_dict': {
|
||||||
'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1190, 'roi_t2': 541, 'roi_t3': 408, 'roi_p1': 0.026035863879169705, 'roi_p2': 0.12508730043628782, 'roi_p3': 0.27766427921605896, 'stoploss': -0.2562930402099556}, # noqa: E501
|
'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1190, 'roi_t2': 541, 'roi_t3': 408, 'roi_p1': 0.026035863879169705, 'roi_p2': 0.12508730043628782, 'roi_p3': 0.27766427921605896, 'stoploss': -0.2562930402099556}, # noqa: E501
|
||||||
'params_details': {'buy': {'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.4287874435315165, 408: 0.15112316431545753, 949: 0.026035863879169705, 2139: 0}, 'stoploss': {'stoploss': -0.2562930402099556}}, # noqa: E501
|
'params_details': {'buy': {'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.4287874435315165, 408: 0.15112316431545753, 949: 0.026035863879169705, 2139: 0}, 'stoploss': {'stoploss': -0.2562930402099556}}, # noqa: E501
|
||||||
'results_metrics': {'trade_count': 2, 'avg_profit': -1.254995, 'total_profit': -0.00125625, 'profit': -2.50999, 'duration': 3930.0}, # noqa: E501
|
'results_metrics': {'trade_count': 2, 'avg_profit': -1.254995, 'total_profit': -0.00125625, 'profit': -2.50999, 'duration': 3930.0}, # noqa: E501
|
||||||
'results_explanation': ' 2 trades. Avg profit -1.25%. Total profit -0.00125625 BTC ( -2.51Σ%). Avg duration 3930.0 min.', # noqa: E501
|
'results_explanation': ' 2 trades. Avg profit -1.25%. Total profit -0.00125625 BTC ( -2.51Σ%). Avg duration 3930.0 min.', # noqa: E501
|
||||||
@ -1716,11 +1716,12 @@ def hyperopt_results():
|
|||||||
}, {
|
}, {
|
||||||
'loss': 20.0,
|
'loss': 20.0,
|
||||||
'params_dict': {
|
'params_dict': {
|
||||||
'mfi-value': 17, 'fastd-value': 38, 'adx-value': 48, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 96, 'sell-fastd-value': 68, 'sell-adx-value': 63, 'sell-rsi-value': 81, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 334, 'roi_t2': 683, 'roi_t3': 140, 'roi_p1': 0.06403981740598495, 'roi_p2': 0.055519840060645045, 'roi_p3': 0.3253712811342459, 'stoploss': -0.338070047333259}, # noqa: E501
|
'mfi-value': 17, 'fastd-value': 38, 'adx-value': 48, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 96, 'sell-fastd-value': 68, 'sell-adx-value': 63, 'sell-rsi-value': 81, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 334, 'roi_t2': 683, 'roi_t3': 140, 'roi_p1': 0.06403981740598495, 'roi_p2': 0.055519840060645045, 'roi_p3': 0.3253712811342459, 'stoploss': -0.338070047333259}, # noqa: E501
|
||||||
'params_details': {'buy': {'mfi-value': 17, 'fastd-value': 38, 'adx-value': 48, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, # noqa: E501
|
'params_details': {
|
||||||
'sell': {'sell-mfi-value': 96, 'sell-fastd-value': 68, 'sell-adx-value': 63, 'sell-rsi-value': 81, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, # noqa: E501
|
'buy': {'mfi-value': 17, 'fastd-value': 38, 'adx-value': 48, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, # noqa: E501
|
||||||
'roi': {0: 0.4449309386008759, 140: 0.11955965746663, 823: 0.06403981740598495, 1157: 0}, # noqa: E501
|
'sell': {'sell-mfi-value': 96, 'sell-fastd-value': 68, 'sell-adx-value': 63, 'sell-rsi-value': 81, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, # noqa: E501
|
||||||
'stoploss': {'stoploss': -0.338070047333259}},
|
'roi': {0: 0.4449309386008759, 140: 0.11955965746663, 823: 0.06403981740598495, 1157: 0}, # noqa: E501
|
||||||
|
'stoploss': {'stoploss': -0.338070047333259}},
|
||||||
'results_metrics': {'trade_count': 1, 'avg_profit': 0.12357, 'total_profit': 6.185e-05, 'profit': 0.12357, 'duration': 1200.0}, # noqa: E501
|
'results_metrics': {'trade_count': 1, 'avg_profit': 0.12357, 'total_profit': 6.185e-05, 'profit': 0.12357, 'duration': 1200.0}, # noqa: E501
|
||||||
'results_explanation': ' 1 trades. Avg profit 0.12%. Total profit 0.00006185 BTC ( 0.12Σ%). Avg duration 1200.0 min.', # noqa: E501
|
'results_explanation': ' 1 trades. Avg profit 0.12%. Total profit 0.00006185 BTC ( 0.12Σ%). Avg duration 1200.0 min.', # noqa: E501
|
||||||
'total_profit': 6.185e-05,
|
'total_profit': 6.185e-05,
|
||||||
@ -1767,8 +1768,9 @@ def hyperopt_results():
|
|||||||
}, {
|
}, {
|
||||||
'loss': 4.713497421432944,
|
'loss': 4.713497421432944,
|
||||||
'params_dict': {'mfi-value': 13, 'fastd-value': 41, 'adx-value': 21, 'rsi-value': 29, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower', 'sell-mfi-value': 99, 'sell-fastd-value': 60, 'sell-adx-value': 81, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 771, 'roi_t2': 620, 'roi_t3': 145, 'roi_p1': 0.0586919200378493, 'roi_p2': 0.04984118697312542, 'roi_p3': 0.37521058680247044, 'stoploss': -0.14613268022709905}, # noqa: E501
|
'params_dict': {'mfi-value': 13, 'fastd-value': 41, 'adx-value': 21, 'rsi-value': 29, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower', 'sell-mfi-value': 99, 'sell-fastd-value': 60, 'sell-adx-value': 81, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 771, 'roi_t2': 620, 'roi_t3': 145, 'roi_p1': 0.0586919200378493, 'roi_p2': 0.04984118697312542, 'roi_p3': 0.37521058680247044, 'stoploss': -0.14613268022709905}, # noqa: E501
|
||||||
'params_details': {'buy': {'mfi-value': 13, 'fastd-value': 41, 'adx-value': 21, 'rsi-value': 29, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 99, 'sell-fastd-value': 60, 'sell-adx-value': 81, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.4837436938134452, 145: 0.10853310701097472, 765: 0.0586919200378493, 1536: 0}, # noqa: E501
|
'params_details': {
|
||||||
'stoploss': {'stoploss': -0.14613268022709905}}, # noqa: E501
|
'buy': {'mfi-value': 13, 'fastd-value': 41, 'adx-value': 21, 'rsi-value': 29, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 99, 'sell-fastd-value': 60, 'sell-adx-value': 81, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.4837436938134452, 145: 0.10853310701097472, 765: 0.0586919200378493, 1536: 0}, # noqa: E501
|
||||||
|
'stoploss': {'stoploss': -0.14613268022709905}}, # noqa: E501
|
||||||
'results_metrics': {'trade_count': 318, 'avg_profit': -0.39833954716981146, 'total_profit': -0.06339929, 'profit': -126.67197600000004, 'duration': 3140.377358490566}, # noqa: E501
|
'results_metrics': {'trade_count': 318, 'avg_profit': -0.39833954716981146, 'total_profit': -0.06339929, 'profit': -126.67197600000004, 'duration': 3140.377358490566}, # noqa: E501
|
||||||
'results_explanation': ' 318 trades. Avg profit -0.40%. Total profit -0.06339929 BTC (-126.67Σ%). Avg duration 3140.4 min.', # noqa: E501
|
'results_explanation': ' 318 trades. Avg profit -0.40%. Total profit -0.06339929 BTC (-126.67Σ%). Avg duration 3140.4 min.', # noqa: E501
|
||||||
'total_profit': -0.06339929,
|
'total_profit': -0.06339929,
|
||||||
|
@ -517,9 +517,9 @@ def test_validate_pairs_restricted(default_conf, mocker, caplog):
|
|||||||
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||||
|
|
||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
assert log_has(f"Pair XRP/BTC is restricted for some users on this exchange."
|
assert log_has("Pair XRP/BTC is restricted for some users on this exchange."
|
||||||
f"Please check if you are impacted by this restriction "
|
"Please check if you are impacted by this restriction "
|
||||||
f"on the exchange and eventually remove XRP/BTC from your whitelist.", caplog)
|
"on the exchange and eventually remove XRP/BTC from your whitelist.", caplog)
|
||||||
|
|
||||||
|
|
||||||
def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog):
|
def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog):
|
||||||
|
@ -555,7 +555,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
|
|||||||
"""
|
"""
|
||||||
Buy every xth candle - sell every other xth -2 (hold on to pairs a bit)
|
Buy every xth candle - sell every other xth -2 (hold on to pairs a bit)
|
||||||
"""
|
"""
|
||||||
if metadata['pair'] in('ETH/BTC', 'LTC/BTC'):
|
if metadata['pair'] in ('ETH/BTC', 'LTC/BTC'):
|
||||||
multi = 20
|
multi = 20
|
||||||
else:
|
else:
|
||||||
multi = 18
|
multi = 18
|
||||||
|
@ -820,7 +820,7 @@ def test_continue_hyperopt(mocker, default_conf, caplog):
|
|||||||
Hyperopt(default_conf)
|
Hyperopt(default_conf)
|
||||||
|
|
||||||
assert unlinkmock.call_count == 0
|
assert unlinkmock.call_count == 0
|
||||||
assert log_has(f"Continuing on previous hyperopt results.", caplog)
|
assert log_has("Continuing on previous hyperopt results.", caplog)
|
||||||
|
|
||||||
|
|
||||||
def test_print_json_spaces_all(mocker, default_conf, caplog, capsys) -> None:
|
def test_print_json_spaces_all(mocker, default_conf, caplog, capsys) -> None:
|
||||||
|
@ -69,44 +69,44 @@ def test_log_on_refresh(mocker, static_pl_conf, markets, tickers):
|
|||||||
|
|
||||||
|
|
||||||
def test_load_pairlist_noexist(mocker, markets, default_conf):
|
def test_load_pairlist_noexist(mocker, markets, default_conf):
|
||||||
bot = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets))
|
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets))
|
||||||
plm = PairListManager(bot.exchange, default_conf)
|
plm = PairListManager(freqtrade.exchange, default_conf)
|
||||||
with pytest.raises(OperationalException,
|
with pytest.raises(OperationalException,
|
||||||
match=r"Impossible to load Pairlist 'NonexistingPairList'. "
|
match=r"Impossible to load Pairlist 'NonexistingPairList'. "
|
||||||
r"This class does not exist or contains Python code errors."):
|
r"This class does not exist or contains Python code errors."):
|
||||||
PairListResolver.load_pairlist('NonexistingPairList', bot.exchange, plm,
|
PairListResolver.load_pairlist('NonexistingPairList', freqtrade.exchange, plm,
|
||||||
default_conf, {}, 1)
|
default_conf, {}, 1)
|
||||||
|
|
||||||
|
|
||||||
def test_refresh_market_pair_not_in_whitelist(mocker, markets, static_pl_conf):
|
def test_refresh_market_pair_not_in_whitelist(mocker, markets, static_pl_conf):
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, static_pl_conf)
|
freqtrade = get_patched_freqtradebot(mocker, static_pl_conf)
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets))
|
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets))
|
||||||
freqtradebot.pairlists.refresh_pairlist()
|
freqtrade.pairlists.refresh_pairlist()
|
||||||
# List ordered by BaseVolume
|
# List ordered by BaseVolume
|
||||||
whitelist = ['ETH/BTC', 'TKN/BTC']
|
whitelist = ['ETH/BTC', 'TKN/BTC']
|
||||||
# Ensure all except those in whitelist are removed
|
# Ensure all except those in whitelist are removed
|
||||||
assert set(whitelist) == set(freqtradebot.pairlists.whitelist)
|
assert set(whitelist) == set(freqtrade.pairlists.whitelist)
|
||||||
# Ensure config dict hasn't been changed
|
# Ensure config dict hasn't been changed
|
||||||
assert (static_pl_conf['exchange']['pair_whitelist'] ==
|
assert (static_pl_conf['exchange']['pair_whitelist'] ==
|
||||||
freqtradebot.config['exchange']['pair_whitelist'])
|
freqtrade.config['exchange']['pair_whitelist'])
|
||||||
|
|
||||||
|
|
||||||
def test_refresh_static_pairlist(mocker, markets, static_pl_conf):
|
def test_refresh_static_pairlist(mocker, markets, static_pl_conf):
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, static_pl_conf)
|
freqtrade = get_patched_freqtradebot(mocker, static_pl_conf)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
exchange_has=MagicMock(return_value=True),
|
exchange_has=MagicMock(return_value=True),
|
||||||
markets=PropertyMock(return_value=markets),
|
markets=PropertyMock(return_value=markets),
|
||||||
)
|
)
|
||||||
freqtradebot.pairlists.refresh_pairlist()
|
freqtrade.pairlists.refresh_pairlist()
|
||||||
# List ordered by BaseVolume
|
# List ordered by BaseVolume
|
||||||
whitelist = ['ETH/BTC', 'TKN/BTC']
|
whitelist = ['ETH/BTC', 'TKN/BTC']
|
||||||
# Ensure all except those in whitelist are removed
|
# Ensure all except those in whitelist are removed
|
||||||
assert set(whitelist) == set(freqtradebot.pairlists.whitelist)
|
assert set(whitelist) == set(freqtrade.pairlists.whitelist)
|
||||||
assert static_pl_conf['exchange']['pair_blacklist'] == freqtradebot.pairlists.blacklist
|
assert static_pl_conf['exchange']['pair_blacklist'] == freqtrade.pairlists.blacklist
|
||||||
|
|
||||||
|
|
||||||
def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_conf):
|
def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_conf):
|
||||||
@ -116,7 +116,7 @@ def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_co
|
|||||||
get_tickers=tickers,
|
get_tickers=tickers,
|
||||||
exchange_has=MagicMock(return_value=True),
|
exchange_has=MagicMock(return_value=True),
|
||||||
)
|
)
|
||||||
bot = get_patched_freqtradebot(mocker, whitelist_conf)
|
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
|
||||||
# Remock markets with shitcoinmarkets since get_patched_freqtradebot uses the markets fixture
|
# Remock markets with shitcoinmarkets since get_patched_freqtradebot uses the markets fixture
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
@ -124,9 +124,9 @@ def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_co
|
|||||||
)
|
)
|
||||||
# argument: use the whitelist dynamically by exchange-volume
|
# argument: use the whitelist dynamically by exchange-volume
|
||||||
whitelist = ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC', 'HOT/BTC']
|
whitelist = ['ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC', 'HOT/BTC']
|
||||||
bot.pairlists.refresh_pairlist()
|
freqtrade.pairlists.refresh_pairlist()
|
||||||
|
|
||||||
assert whitelist == bot.pairlists.whitelist
|
assert whitelist == freqtrade.pairlists.whitelist
|
||||||
|
|
||||||
whitelist_conf['pairlists'] = [{'method': 'VolumePairList',
|
whitelist_conf['pairlists'] = [{'method': 'VolumePairList',
|
||||||
'config': {}
|
'config': {}
|
||||||
@ -136,7 +136,7 @@ def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_co
|
|||||||
with pytest.raises(OperationalException,
|
with pytest.raises(OperationalException,
|
||||||
match=r'`number_assets` not specified. Please check your configuration '
|
match=r'`number_assets` not specified. Please check your configuration '
|
||||||
r'for "pairlist.config.number_assets"'):
|
r'for "pairlist.config.number_assets"'):
|
||||||
PairListManager(bot.exchange, whitelist_conf)
|
PairListManager(freqtrade.exchange, whitelist_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
|
def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
|
||||||
@ -144,13 +144,13 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
|
|||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
exchange_has=MagicMock(return_value=True),
|
exchange_has=MagicMock(return_value=True),
|
||||||
)
|
)
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf)
|
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets_empty))
|
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets_empty))
|
||||||
|
|
||||||
# argument: use the whitelist dynamically by exchange-volume
|
# argument: use the whitelist dynamically by exchange-volume
|
||||||
whitelist = []
|
whitelist = []
|
||||||
whitelist_conf['exchange']['pair_whitelist'] = []
|
whitelist_conf['exchange']['pair_whitelist'] = []
|
||||||
freqtradebot.pairlists.refresh_pairlist()
|
freqtrade.pairlists.refresh_pairlist()
|
||||||
pairslist = whitelist_conf['exchange']['pair_whitelist']
|
pairslist = whitelist_conf['exchange']['pair_whitelist']
|
||||||
|
|
||||||
assert set(whitelist) == set(pairslist)
|
assert set(whitelist) == set(pairslist)
|
||||||
@ -206,6 +206,7 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
|
|||||||
pairlists, base_currency, whitelist_result,
|
pairlists, base_currency, whitelist_result,
|
||||||
caplog) -> None:
|
caplog) -> None:
|
||||||
whitelist_conf['pairlists'] = pairlists
|
whitelist_conf['pairlists'] = pairlists
|
||||||
|
whitelist_conf['stake_currency'] = base_currency
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
||||||
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
|
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
|
||||||
@ -215,7 +216,6 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
|
|||||||
markets=PropertyMock(return_value=shitcoinmarkets),
|
markets=PropertyMock(return_value=shitcoinmarkets),
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtrade.config['stake_currency'] = base_currency
|
|
||||||
freqtrade.pairlists.refresh_pairlist()
|
freqtrade.pairlists.refresh_pairlist()
|
||||||
whitelist = freqtrade.pairlists.whitelist
|
whitelist = freqtrade.pairlists.whitelist
|
||||||
|
|
||||||
@ -312,18 +312,18 @@ def test_volumepairlist_caching(mocker, markets, whitelist_conf, tickers):
|
|||||||
exchange_has=MagicMock(return_value=True),
|
exchange_has=MagicMock(return_value=True),
|
||||||
get_tickers=tickers
|
get_tickers=tickers
|
||||||
)
|
)
|
||||||
bot = get_patched_freqtradebot(mocker, whitelist_conf)
|
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
|
||||||
assert bot.pairlists._pairlists[0]._last_refresh == 0
|
assert freqtrade.pairlists._pairlists[0]._last_refresh == 0
|
||||||
assert tickers.call_count == 0
|
assert tickers.call_count == 0
|
||||||
bot.pairlists.refresh_pairlist()
|
freqtrade.pairlists.refresh_pairlist()
|
||||||
assert tickers.call_count == 1
|
assert tickers.call_count == 1
|
||||||
|
|
||||||
assert bot.pairlists._pairlists[0]._last_refresh != 0
|
assert freqtrade.pairlists._pairlists[0]._last_refresh != 0
|
||||||
lrf = bot.pairlists._pairlists[0]._last_refresh
|
lrf = freqtrade.pairlists._pairlists[0]._last_refresh
|
||||||
bot.pairlists.refresh_pairlist()
|
freqtrade.pairlists.refresh_pairlist()
|
||||||
assert tickers.call_count == 1
|
assert tickers.call_count == 1
|
||||||
# Time should not be updated.
|
# Time should not be updated.
|
||||||
assert bot.pairlists._pairlists[0]._last_refresh == lrf
|
assert freqtrade.pairlists._pairlists[0]._last_refresh == lrf
|
||||||
|
|
||||||
|
|
||||||
def test_pairlistmanager_no_pairlist(mocker, markets, whitelist_conf, caplog):
|
def test_pairlistmanager_no_pairlist(mocker, markets, whitelist_conf, caplog):
|
||||||
|
@ -60,6 +60,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
|||||||
'open_trade_price': ANY,
|
'open_trade_price': ANY,
|
||||||
'close_rate_requested': ANY,
|
'close_rate_requested': ANY,
|
||||||
'sell_reason': ANY,
|
'sell_reason': ANY,
|
||||||
|
'sell_order_status': ANY,
|
||||||
'min_rate': ANY,
|
'min_rate': ANY,
|
||||||
'max_rate': ANY,
|
'max_rate': ANY,
|
||||||
'strategy': ANY,
|
'strategy': ANY,
|
||||||
@ -82,7 +83,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
|||||||
} == results[0]
|
} == results[0]
|
||||||
|
|
||||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
|
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
|
||||||
MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available")))
|
MagicMock(side_effect=DependencyException("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'])
|
||||||
@ -103,6 +104,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
|||||||
'open_trade_price': ANY,
|
'open_trade_price': ANY,
|
||||||
'close_rate_requested': ANY,
|
'close_rate_requested': ANY,
|
||||||
'sell_reason': ANY,
|
'sell_reason': ANY,
|
||||||
|
'sell_order_status': ANY,
|
||||||
'min_rate': ANY,
|
'min_rate': ANY,
|
||||||
'max_rate': ANY,
|
'max_rate': ANY,
|
||||||
'strategy': ANY,
|
'strategy': ANY,
|
||||||
@ -165,7 +167,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(f"Pair 'ETH/BTC' not available")))
|
MagicMock(side_effect=DependencyException("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]
|
||||||
@ -203,16 +205,18 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
|
|||||||
# Try valid data
|
# Try valid data
|
||||||
update.message.text = '/daily 2'
|
update.message.text = '/daily 2'
|
||||||
days = rpc._rpc_daily_profit(7, stake_currency, fiat_display_currency)
|
days = rpc._rpc_daily_profit(7, stake_currency, fiat_display_currency)
|
||||||
assert len(days) == 7
|
assert len(days['data']) == 7
|
||||||
for day in days:
|
assert days['stake_currency'] == default_conf['stake_currency']
|
||||||
|
assert days['fiat_display_currency'] == default_conf['fiat_display_currency']
|
||||||
|
for day in days['data']:
|
||||||
# [datetime.date(2018, 1, 11), '0.00000000 BTC', '0.000 USD']
|
# [datetime.date(2018, 1, 11), '0.00000000 BTC', '0.000 USD']
|
||||||
assert (day[1] == '0.00000000 BTC' or
|
assert (day['abs_profit'] == '0.00000000' or
|
||||||
day[1] == '0.00006217 BTC')
|
day['abs_profit'] == '0.00006217')
|
||||||
|
|
||||||
assert (day[2] == '0.000 USD' or
|
assert (day['fiat_value'] == '0.000' or
|
||||||
day[2] == '0.767 USD')
|
day['fiat_value'] == '0.767')
|
||||||
# ensure first day is current date
|
# ensure first day is current date
|
||||||
assert str(days[0][0]) == str(datetime.utcnow().date())
|
assert str(days['data'][0]['date']) == str(datetime.utcnow().date())
|
||||||
|
|
||||||
# Try invalid data
|
# Try invalid data
|
||||||
with pytest.raises(RPCException, match=r'.*must be an integer greater than 0*'):
|
with pytest.raises(RPCException, match=r'.*must be an integer greater than 0*'):
|
||||||
@ -315,7 +319,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(f"Pair 'ETH/BTC' not available")))
|
MagicMock(side_effect=DependencyException("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'
|
||||||
|
@ -333,8 +333,10 @@ def test_api_daily(botclient, mocker, ticker, fee, markets):
|
|||||||
)
|
)
|
||||||
rc = client_get(client, f"{BASE_URI}/daily")
|
rc = client_get(client, f"{BASE_URI}/daily")
|
||||||
assert_response(rc)
|
assert_response(rc)
|
||||||
assert len(rc.json) == 7
|
assert len(rc.json['data']) == 7
|
||||||
assert rc.json[0][0] == str(datetime.utcnow().date())
|
assert rc.json['stake_currency'] == 'BTC'
|
||||||
|
assert rc.json['fiat_display_currency'] == 'USD'
|
||||||
|
assert rc.json['data'][0]['date'] == str(datetime.utcnow().date())
|
||||||
|
|
||||||
|
|
||||||
def test_api_trades(botclient, mocker, ticker, fee, markets):
|
def test_api_trades(botclient, mocker, ticker, fee, markets):
|
||||||
@ -520,6 +522,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
|
|||||||
'open_rate_requested': 1.098e-05,
|
'open_rate_requested': 1.098e-05,
|
||||||
'open_trade_price': 0.0010025,
|
'open_trade_price': 0.0010025,
|
||||||
'sell_reason': None,
|
'sell_reason': None,
|
||||||
|
'sell_order_status': None,
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'DefaultStrategy',
|
||||||
'ticker_interval': 5}]
|
'ticker_interval': 5}]
|
||||||
|
|
||||||
@ -626,6 +629,7 @@ def test_api_forcebuy(botclient, mocker, fee):
|
|||||||
'open_rate_requested': None,
|
'open_rate_requested': None,
|
||||||
'open_trade_price': 0.2460546025,
|
'open_trade_price': 0.2460546025,
|
||||||
'sell_reason': None,
|
'sell_reason': None,
|
||||||
|
'sell_order_status': None,
|
||||||
'strategy': None,
|
'strategy': None,
|
||||||
'ticker_interval': None
|
'ticker_interval': None
|
||||||
}
|
}
|
||||||
|
@ -170,6 +170,7 @@ def test_status(default_conf, update, mocker, fee, ticker,) -> None:
|
|||||||
'current_profit': -0.59,
|
'current_profit': -0.59,
|
||||||
'initial_stop_loss': 1.098e-05,
|
'initial_stop_loss': 1.098e-05,
|
||||||
'stop_loss': 1.099e-05,
|
'stop_loss': 1.099e-05,
|
||||||
|
'sell_order_status': None,
|
||||||
'initial_stop_loss_pct': -0.05,
|
'initial_stop_loss_pct': -0.05,
|
||||||
'stop_loss_pct': -0.01,
|
'stop_loss_pct': -0.01,
|
||||||
'open_order': '(limit buy rem=0.00000000)'
|
'open_order': '(limit buy rem=0.00000000)'
|
||||||
|
@ -73,7 +73,7 @@ def test_load_config_file_error(default_conf, mocker, caplog) -> None:
|
|||||||
mocker.patch('freqtrade.configuration.load_config.open', mocker.mock_open(read_data=filedata))
|
mocker.patch('freqtrade.configuration.load_config.open', mocker.mock_open(read_data=filedata))
|
||||||
mocker.patch.object(Path, "read_text", MagicMock(return_value=filedata))
|
mocker.patch.object(Path, "read_text", MagicMock(return_value=filedata))
|
||||||
|
|
||||||
with pytest.raises(OperationalException, match=f".*Please verify the following segment.*"):
|
with pytest.raises(OperationalException, match=r".*Please verify the following segment.*"):
|
||||||
load_config_file('somefile')
|
load_config_file('somefile')
|
||||||
|
|
||||||
|
|
||||||
|
@ -1976,6 +1976,10 @@ def test_check_handle_timedout_buy_usercustom(default_conf, ticker, limit_buy_or
|
|||||||
|
|
||||||
Trade.session.add(open_trade)
|
Trade.session.add(open_trade)
|
||||||
|
|
||||||
|
# Ensure default is to return empty (so not mocked yet)
|
||||||
|
freqtrade.check_handle_timedout()
|
||||||
|
assert cancel_order_mock.call_count == 0
|
||||||
|
|
||||||
# Return false - trade remains open
|
# Return false - trade remains open
|
||||||
freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False)
|
freqtrade.strategy.check_buy_timeout = MagicMock(return_value=False)
|
||||||
freqtrade.check_handle_timedout()
|
freqtrade.check_handle_timedout()
|
||||||
@ -2106,6 +2110,9 @@ def test_check_handle_timedout_sell_usercustom(default_conf, ticker, limit_sell_
|
|||||||
open_trade.is_open = False
|
open_trade.is_open = False
|
||||||
|
|
||||||
Trade.session.add(open_trade)
|
Trade.session.add(open_trade)
|
||||||
|
# Ensure default is false
|
||||||
|
freqtrade.check_handle_timedout()
|
||||||
|
assert cancel_order_mock.call_count == 0
|
||||||
|
|
||||||
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False)
|
freqtrade.strategy.check_sell_timeout = MagicMock(return_value=False)
|
||||||
# Return false - No impact
|
# Return false - No impact
|
||||||
@ -2407,30 +2414,47 @@ def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order,
|
|||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
def test_handle_cancel_sell_limit(mocker, default_conf) -> None:
|
def test_handle_cancel_sell_limit(mocker, default_conf, fee) -> None:
|
||||||
patch_RPCManager(mocker)
|
send_msg_mock = patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
cancel_order_mock = MagicMock()
|
cancel_order_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
cancel_order=cancel_order_mock
|
cancel_order=cancel_order_mock,
|
||||||
)
|
)
|
||||||
|
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate', return_value=0.245441)
|
||||||
|
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
freqtrade._notify_sell_cancel = MagicMock()
|
|
||||||
|
|
||||||
trade = MagicMock()
|
trade = Trade(
|
||||||
|
pair='LTC/ETH',
|
||||||
|
amount=2,
|
||||||
|
exchange='binance',
|
||||||
|
open_rate=0.245441,
|
||||||
|
open_order_id="123456",
|
||||||
|
open_date=arrow.utcnow().datetime,
|
||||||
|
fee_open=fee.return_value,
|
||||||
|
fee_close=fee.return_value,
|
||||||
|
)
|
||||||
order = {'remaining': 1,
|
order = {'remaining': 1,
|
||||||
'amount': 1,
|
'amount': 1,
|
||||||
'status': "open"}
|
'status': "open"}
|
||||||
reason = CANCEL_REASON['TIMEOUT']
|
reason = CANCEL_REASON['TIMEOUT']
|
||||||
assert freqtrade.handle_cancel_sell(trade, order, reason)
|
assert freqtrade.handle_cancel_sell(trade, order, reason)
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
|
assert send_msg_mock.call_count == 1
|
||||||
|
|
||||||
|
send_msg_mock.reset_mock()
|
||||||
|
|
||||||
order['amount'] = 2
|
order['amount'] = 2
|
||||||
assert (freqtrade.handle_cancel_sell(trade, order, reason)
|
assert freqtrade.handle_cancel_sell(trade, order, reason) == CANCEL_REASON['PARTIALLY_FILLED']
|
||||||
== CANCEL_REASON['PARTIALLY_FILLED'])
|
|
||||||
# Assert cancel_order was not called (callcount remains unchanged)
|
# Assert cancel_order was not called (callcount remains unchanged)
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
|
assert send_msg_mock.call_count == 1
|
||||||
|
assert freqtrade.handle_cancel_sell(trade, order, reason) == CANCEL_REASON['PARTIALLY_FILLED']
|
||||||
|
# Message should not be iterated again
|
||||||
|
assert trade.sell_order_status == CANCEL_REASON['PARTIALLY_FILLED']
|
||||||
|
assert send_msg_mock.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
def test_handle_cancel_sell_cancel_exception(mocker, default_conf) -> None:
|
def test_handle_cancel_sell_cancel_exception(mocker, default_conf) -> None:
|
||||||
@ -3129,10 +3153,8 @@ def test_trailing_stop_loss(default_conf, limit_buy_order, fee, caplog, mocker)
|
|||||||
caplog.set_level(logging.DEBUG)
|
caplog.set_level(logging.DEBUG)
|
||||||
# Sell as trailing-stop is reached
|
# Sell as trailing-stop is reached
|
||||||
assert freqtrade.handle_trade(trade) is True
|
assert freqtrade.handle_trade(trade) is True
|
||||||
assert log_has(
|
assert log_has("ETH/BTC - HIT STOP: current price at 0.000012, stoploss is 0.000015, "
|
||||||
f"ETH/BTC - HIT STOP: current price at 0.000012, "
|
"initial stoploss was at 0.000010, trade opened at 0.000011", caplog)
|
||||||
f"stoploss is 0.000015, "
|
|
||||||
f"initial stoploss was at 0.000010, trade opened at 0.000011", caplog)
|
|
||||||
assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value
|
assert trade.sell_reason == SellType.TRAILING_STOP_LOSS.value
|
||||||
|
|
||||||
|
|
||||||
@ -3175,8 +3197,8 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee,
|
|||||||
}))
|
}))
|
||||||
# stop-loss not reached, adjusted stoploss
|
# stop-loss not reached, adjusted stoploss
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
assert log_has(f"ETH/BTC - Using positive stoploss: 0.01 offset: 0 profit: 0.2666%", caplog)
|
assert log_has("ETH/BTC - Using positive stoploss: 0.01 offset: 0 profit: 0.2666%", caplog)
|
||||||
assert log_has(f"ETH/BTC - Adjusting stoploss...", caplog)
|
assert log_has("ETH/BTC - Adjusting stoploss...", caplog)
|
||||||
assert trade.stop_loss == 0.0000138501
|
assert trade.stop_loss == 0.0000138501
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||||
@ -3232,9 +3254,8 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee,
|
|||||||
}))
|
}))
|
||||||
# stop-loss not reached, adjusted stoploss
|
# stop-loss not reached, adjusted stoploss
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
assert log_has(f"ETH/BTC - Using positive stoploss: 0.01 offset: 0.011 profit: 0.2666%",
|
assert log_has("ETH/BTC - Using positive stoploss: 0.01 offset: 0.011 profit: 0.2666%", caplog)
|
||||||
caplog)
|
assert log_has("ETH/BTC - Adjusting stoploss...", caplog)
|
||||||
assert log_has(f"ETH/BTC - Adjusting stoploss...", caplog)
|
|
||||||
assert trade.stop_loss == 0.0000138501
|
assert trade.stop_loss == 0.0000138501
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||||
@ -3298,7 +3319,7 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, fee,
|
|||||||
# stop-loss should not be adjusted as offset is not reached yet
|
# stop-loss should not be adjusted as offset is not reached yet
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
|
|
||||||
assert not log_has(f"ETH/BTC - Adjusting stoploss...", caplog)
|
assert not log_has("ETH/BTC - Adjusting stoploss...", caplog)
|
||||||
assert trade.stop_loss == 0.0000098910
|
assert trade.stop_loss == 0.0000098910
|
||||||
|
|
||||||
# price rises above the offset (rises 12% when the offset is 5.5%)
|
# price rises above the offset (rises 12% when the offset is 5.5%)
|
||||||
@ -3310,9 +3331,8 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, fee,
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
assert log_has(f"ETH/BTC - Using positive stoploss: 0.05 offset: 0.055 profit: 0.1218%",
|
assert log_has("ETH/BTC - Using positive stoploss: 0.05 offset: 0.055 profit: 0.1218%", caplog)
|
||||||
caplog)
|
assert log_has("ETH/BTC - Adjusting stoploss...", caplog)
|
||||||
assert log_has(f"ETH/BTC - Adjusting stoploss...", caplog)
|
|
||||||
assert trade.stop_loss == 0.0000117705
|
assert trade.stop_loss == 0.0000117705
|
||||||
|
|
||||||
|
|
||||||
|
@ -477,6 +477,7 @@ def test_migrate_old(mocker, default_conf, fee):
|
|||||||
assert trade.close_rate_requested is None
|
assert trade.close_rate_requested is None
|
||||||
assert trade.close_rate is not None
|
assert trade.close_rate is not None
|
||||||
assert pytest.approx(trade.close_profit_abs) == trade.calc_profit()
|
assert pytest.approx(trade.close_profit_abs) == trade.calc_profit()
|
||||||
|
assert trade.sell_order_status is None
|
||||||
|
|
||||||
|
|
||||||
def test_migrate_new(mocker, default_conf, fee, caplog):
|
def test_migrate_new(mocker, default_conf, fee, caplog):
|
||||||
@ -756,6 +757,7 @@ def test_to_json(default_conf, fee):
|
|||||||
'stake_amount': 0.001,
|
'stake_amount': 0.001,
|
||||||
'close_profit': None,
|
'close_profit': None,
|
||||||
'sell_reason': None,
|
'sell_reason': None,
|
||||||
|
'sell_order_status': None,
|
||||||
'stop_loss': None,
|
'stop_loss': None,
|
||||||
'stop_loss_pct': None,
|
'stop_loss_pct': None,
|
||||||
'initial_stop_loss': None,
|
'initial_stop_loss': None,
|
||||||
@ -810,6 +812,7 @@ def test_to_json(default_conf, fee):
|
|||||||
'open_rate_requested': None,
|
'open_rate_requested': None,
|
||||||
'open_trade_price': 12.33075,
|
'open_trade_price': 12.33075,
|
||||||
'sell_reason': None,
|
'sell_reason': None,
|
||||||
|
'sell_order_status': None,
|
||||||
'strategy': None,
|
'strategy': None,
|
||||||
'ticker_interval': None}
|
'ticker_interval': None}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user