Merge branch 'freqtrade:develop' into plot_hyperopt_stats

This commit is contained in:
Italo 2022-03-20 15:42:53 +00:00 committed by GitHub
commit 112738d68d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 203 additions and 136 deletions

View File

@ -26,7 +26,7 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME -i TIMEFRAME, --timeframe TIMEFRAME
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`). Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
--timerange TIMERANGE --timerange TIMERANGE
Specify what timerange of data to use. Specify what timerange of data to use.
@ -63,7 +63,7 @@ optional arguments:
`30m`, `1h`, `1d`). `30m`, `1h`, `1d`).
--strategy-list STRATEGY_LIST [STRATEGY_LIST ...] --strategy-list STRATEGY_LIST [STRATEGY_LIST ...]
Provide a space-separated list of strategies to Provide a space-separated list of strategies to
backtest. Please note that ticker-interval needs to be backtest. Please note that timeframe needs to be
set either in config or via command line. When using set either in config or via command line. When using
this together with `--export trades`, the strategy- this together with `--export trades`, the strategy-
name is injected into the filename (so `backtest- name is injected into the filename (so `backtest-

View File

@ -24,7 +24,7 @@ By default, loop runs every few seconds (`internals.process_throttle_secs`) and
* Fetch open trades from persistence. * Fetch open trades from persistence.
* Calculate current list of tradable pairs. * Calculate current list of tradable pairs.
* Download ohlcv data for the pairlist including all [informative pairs](strategy-customization.md#get-data-for-non-tradeable-pairs) * Download OHLCV data for the pairlist including all [informative pairs](strategy-customization.md#get-data-for-non-tradeable-pairs)
This step is only executed once per Candle to avoid unnecessary network traffic. This step is only executed once per Candle to avoid unnecessary network traffic.
* Call `bot_loop_start()` strategy callback. * Call `bot_loop_start()` strategy callback.
* Analyze strategy per pair. * Analyze strategy per pair.

View File

@ -86,7 +86,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade). <br>*Defaults to `false`.* <br> **Datatype:** Boolean | `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
| `last_stake_amount_min_ratio` | Defines minimum stake amount that has to be left and executed. Applies only to the last stake amount when it's amended to a reduced value (i.e. if `amend_last_stake_amount` is set to `true`). [More information below](#configuring-amount-per-trade). <br>*Defaults to `0.5`.* <br> **Datatype:** Float (as ratio) | `last_stake_amount_min_ratio` | Defines minimum stake amount that has to be left and executed. Applies only to the last stake amount when it's amended to a reduced value (i.e. if `amend_last_stake_amount` is set to `true`). [More information below](#configuring-amount-per-trade). <br>*Defaults to `0.5`.* <br> **Datatype:** Float (as ratio)
| `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals. <br>*Defaults to `0.05` (5%).* <br> **Datatype:** Positive Float as ratio. | `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals. <br>*Defaults to `0.05` (5%).* <br> **Datatype:** Positive Float as ratio.
| `timeframe` | The timeframe (former ticker interval) to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** String | `timeframe` | The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** String
| `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency). <br> **Datatype:** String | `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency). <br> **Datatype:** String
| `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode. <br>*Defaults to `true`.* <br> **Datatype:** Boolean | `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode. <br>*Defaults to `true`.* <br> **Datatype:** Boolean
| `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in Dry Run mode.<br>*Defaults to `1000`.* <br> **Datatype:** Float | `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in Dry Run mode.<br>*Defaults to `1000`.* <br> **Datatype:** Float

View File

@ -24,6 +24,10 @@ Please refer to [pairlists](plugins.md#pairlists-and-pairlist-handlers) instead.
Did only download the latest 500 candles, so was ineffective in getting good backtest data. Did only download the latest 500 candles, so was ineffective in getting good backtest data.
Removed in 2019-7-dev (develop branch) and in freqtrade 2019.8. Removed in 2019-7-dev (develop branch) and in freqtrade 2019.8.
### `ticker_interval` (now `timeframe`)
Support for `ticker_interval` terminology was deprecated in 2020.6 in favor of `timeframe` - and compatibility code was removed in 2022.3.
### Allow running multiple pairlists in sequence ### Allow running multiple pairlists in sequence
The former `"pairlist"` section in the configuration has been removed, and is replaced by `"pairlists"` - being a list to specify a sequence of pairlists. The former `"pairlist"` section in the configuration has been removed, and is replaced by `"pairlists"` - being a list to specify a sequence of pairlists.

View File

@ -222,7 +222,7 @@ usage: freqtrade edge [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME -i TIMEFRAME, --timeframe TIMEFRAME
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`). Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
--timerange TIMERANGE --timerange TIMERANGE
Specify what timerange of data to use. Specify what timerange of data to use.

View File

@ -210,6 +210,9 @@ OKX requires a passphrase for each api key, you will therefore need to add this
## Gate.io ## Gate.io
!!! Tip "Stoploss on Exchange"
Gate.io supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange..
Gate.io allows the use of `POINT` to pay for fees. As this is not a tradable currency (no regular market available), automatic fee calculations will fail (and default to a fee of 0). Gate.io allows the use of `POINT` to pay for fees. As this is not a tradable currency (no regular market available), automatic fee calculations will fail (and default to a fee of 0).
The configuration parameter `exchange.unknown_fee_rate` can be used to specify the exchange rate between Point and the stake currency. Obviously, changing the stake-currency will also require changes to this value. The configuration parameter `exchange.unknown_fee_rate` can be used to specify the exchange rate between Point and the stake currency. Obviously, changing the stake-currency will also require changes to this value.

View File

@ -55,7 +55,7 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME -i TIMEFRAME, --timeframe TIMEFRAME
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`). Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
--timerange TIMERANGE --timerange TIMERANGE
Specify what timerange of data to use. Specify what timerange of data to use.

View File

@ -65,7 +65,7 @@ optional arguments:
_today.json` _today.json`
--timerange TIMERANGE --timerange TIMERANGE
Specify what timerange of data to use. Specify what timerange of data to use.
-i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME -i TIMEFRAME, --timeframe TIMEFRAME
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`). Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
--no-trades Skip using trades from backtesting file and DB. --no-trades Skip using trades from backtesting file and DB.
@ -330,7 +330,7 @@ optional arguments:
--trade-source {DB,file} --trade-source {DB,file}
Specify the source for trades (Can be DB or file Specify the source for trades (Can be DB or file
(backtest file)) Default: file (backtest file)) Default: file
-i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME -i TIMEFRAME, --timeframe TIMEFRAME
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`). Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
--auto-open Automatically open generated plot. --auto-open Automatically open generated plot.

View File

@ -24,7 +24,7 @@ These modes can be configured with these values:
``` ```
!!! Note !!! Note
Stoploss on exchange is only supported for Binance (stop-loss-limit), Huobi (stop-limit), Kraken (stop-loss-market, stop-loss-limit), FTX (stop limit and stop-market) and kucoin (stop-limit and stop-market) as of now. Stoploss on exchange is only supported for Binance (stop-loss-limit), Huobi (stop-limit), Kraken (stop-loss-market, stop-loss-limit), FTX (stop limit and stop-market) Gateio (stop-limit), and Kucoin (stop-limit and stop-market) as of now.
<ins>Do not set too low/tight stoploss value if using stop loss on exchange!</ins> <ins>Do not set too low/tight stoploss value if using stop loss on exchange!</ins>
If set to low/tight then you have greater risk of missing fill on the order and stoploss will not work. If set to low/tight then you have greater risk of missing fill on the order and stoploss will not work.

View File

@ -325,7 +325,7 @@ stoploss = -0.10
For the full documentation on stoploss features, look at the dedicated [stoploss page](stoploss.md). For the full documentation on stoploss features, look at the dedicated [stoploss page](stoploss.md).
### Timeframe (formerly ticker interval) ### Timeframe
This is the set of candles the bot should download and use for the analysis. This is the set of candles the bot should download and use for the analysis.
Common values are `"1m"`, `"5m"`, `"15m"`, `"1h"`, however all values supported by your exchange should work. Common values are `"1m"`, `"5m"`, `"15m"`, `"1h"`, however all values supported by your exchange should work.

View File

@ -277,6 +277,7 @@ Starting capital is either taken from the `available_capital` setting, or calcul
> **BITTREX:** Buying ETH/BTC with limit `0.03400000` (`1.000000 ETH`, `225.290 USD`) > **BITTREX:** Buying ETH/BTC with limit `0.03400000` (`1.000000 ETH`, `225.290 USD`)
Omitting the pair will open a query asking for the pair to buy (based on the current whitelist). Omitting the pair will open a query asking for the pair to buy (based on the current whitelist).
Trades crated through `/forcebuy` will have the buy-tag of `forceentry`.
![Telegram force-buy screenshot](assets/telegram_forcebuy.png) ![Telegram force-buy screenshot](assets/telegram_forcebuy.png)

View File

@ -517,20 +517,25 @@ Requires a configuration with specified `pairlists` attribute.
Can be used to generate static pairlists to be used during backtesting / hyperopt. Can be used to generate static pairlists to be used during backtesting / hyperopt.
``` ```
usage: freqtrade test-pairlist [-h] [-c PATH] usage: freqtrade test-pairlist [-h] [-v] [-c PATH]
[--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]] [--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]]
[-1] [--print-json] [-1] [--print-json] [--exchange EXCHANGE]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
-c PATH, --config PATH -c PATH, --config PATH
Specify configuration file (default: `config.json`). Specify configuration file (default:
Multiple --config options may be used. Can be set to `userdir/config.json` or `config.json` whichever
`-` to read config from stdin. exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...] --quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]
Specify quote currency(-ies). Space-separated list. Specify quote currency(-ies). Space-separated list.
-1, --one-column Print output in one column. -1, --one-column Print output in one column.
--print-json Print list of pairs or market symbols in JSON format. --print-json Print list of pairs or market symbols in JSON format.
--exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no
config is provided.
``` ```
### Examples ### Examples

View File

@ -51,7 +51,7 @@ ARGS_LIST_PAIRS = ["exchange", "print_list", "list_pairs_print_json", "print_one
"print_csv", "base_currencies", "quote_currencies", "list_pairs_all"] "print_csv", "base_currencies", "quote_currencies", "list_pairs_all"]
ARGS_TEST_PAIRLIST = ["verbosity", "config", "quote_currencies", "print_one_column", ARGS_TEST_PAIRLIST = ["verbosity", "config", "quote_currencies", "print_one_column",
"list_pairs_print_json"] "list_pairs_print_json", "exchange"]
ARGS_CREATE_USERDIR = ["user_data_dir", "reset"] ARGS_CREATE_USERDIR = ["user_data_dir", "reset"]

View File

@ -117,7 +117,7 @@ AVAILABLE_CLI_OPTIONS = {
), ),
# Optimize common # Optimize common
"timeframe": Arg( "timeframe": Arg(
'-i', '--timeframe', '--ticker-interval', '-i', '--timeframe',
help='Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).', help='Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).',
), ),
"timerange": Arg( "timerange": Arg(
@ -169,7 +169,7 @@ AVAILABLE_CLI_OPTIONS = {
"strategy_list": Arg( "strategy_list": Arg(
'--strategy-list', '--strategy-list',
help='Provide a space-separated list of strategies to backtest. ' help='Provide a space-separated list of strategies to backtest. '
'Please note that ticker-interval needs to be set either in config ' 'Please note that timeframe needs to be set either in config '
'or via command line. When using this together with `--export trades`, ' 'or via command line. When using this together with `--export trades`, '
'the strategy-name is injected into the filename ' 'the strategy-name is injected into the filename '
'(so `backtest-data.json` becomes `backtest-data-SampleStrategy.json`', '(so `backtest-data.json` becomes `backtest-data-SampleStrategy.json`',

View File

@ -100,16 +100,11 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
"from the edge configuration." "from the edge configuration."
) )
if 'ticker_interval' in config: if 'ticker_interval' in config:
logger.warning(
"DEPRECATED: " raise OperationalException(
"DEPRECATED: 'ticker_interval' detected. "
"Please use 'timeframe' instead of 'ticker_interval." "Please use 'timeframe' instead of 'ticker_interval."
) )
if 'timeframe' in config:
raise OperationalException(
"Both 'timeframe' and 'ticker_interval' detected."
"Please remove 'ticker_interval' from your configuration to continue operating."
)
config['timeframe'] = config['ticker_interval']
if 'protections' in config: if 'protections' in config:
logger.warning("DEPRECATED: Setting 'protections' in the configuration is deprecated.") logger.warning("DEPRECATED: Setting 'protections' in the configuration is deprecated.")

View File

@ -219,9 +219,11 @@ class Edge:
""" """
final = [] final = []
for pair, info in self._cached_pairs.items(): for pair, info in self._cached_pairs.items():
if info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and \ if (
info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)) and \ info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2))
pair in pairs: and info.winrate > float(self.edge_config.get('minimum_winrate', 0.60))
and pair in pairs
):
final.append(pair) final.append(pair)
if self._final_pairs != final: if self._final_pairs != final:
@ -246,8 +248,8 @@ class Edge:
""" """
final = [] final = []
for pair, info in self._cached_pairs.items(): for pair, info in self._cached_pairs.items():
if info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and \ if (info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and
info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)): info.winrate > float(self.edge_config.get('minimum_winrate', 0.60))):
final.append({ final.append({
'Pair': pair, 'Pair': pair,
'Winrate': info.winrate, 'Winrate': info.winrate,

View File

@ -882,11 +882,11 @@ class Exchange:
raise OperationalException(e) from e raise OperationalException(e) from e
@retrier(retries=API_FETCH_ORDER_RETRY_COUNT) @retrier(retries=API_FETCH_ORDER_RETRY_COUNT)
def fetch_order(self, order_id: str, pair: str) -> Dict: def fetch_order(self, order_id: str, pair: str, params={}) -> Dict:
if self._config['dry_run']: if self._config['dry_run']:
return self.fetch_dry_run_order(order_id) return self.fetch_dry_run_order(order_id)
try: try:
order = self._api.fetch_order(order_id, pair) order = self._api.fetch_order(order_id, pair, params=params)
self._log_exchange_response('fetch_order', order) self._log_exchange_response('fetch_order', order)
return order return order
except ccxt.OrderNotFound as e: except ccxt.OrderNotFound as e:
@ -929,7 +929,7 @@ class Exchange:
and order.get('filled') == 0.0) and order.get('filled') == 0.0)
@retrier @retrier
def cancel_order(self, order_id: str, pair: str) -> Dict: def cancel_order(self, order_id: str, pair: str, params={}) -> Dict:
if self._config['dry_run']: if self._config['dry_run']:
try: try:
order = self.fetch_dry_run_order(order_id) order = self.fetch_dry_run_order(order_id)
@ -940,7 +940,7 @@ class Exchange:
return {} return {}
try: try:
order = self._api.cancel_order(order_id, pair) order = self._api.cancel_order(order_id, pair, params=params)
self._log_exchange_response('cancel_order', order) self._log_exchange_response('cancel_order', order)
return order return order
except ccxt.InvalidOrder as e: except ccxt.InvalidOrder as e:

View File

@ -22,13 +22,34 @@ class Gateio(Exchange):
_ft_has: Dict = { _ft_has: Dict = {
"ohlcv_candle_limit": 1000, "ohlcv_candle_limit": 1000,
"ohlcv_volume_currency": "quote", "ohlcv_volume_currency": "quote",
"stoploss_order_types": {"limit": "limit"},
"stoploss_on_exchange": True,
} }
_headers = {'X-Gate-Channel-Id': 'freqtrade'}
def validate_ordertypes(self, order_types: Dict) -> None: def validate_ordertypes(self, order_types: Dict) -> None:
super().validate_ordertypes(order_types) super().validate_ordertypes(order_types)
if any(v == 'market' for k, v in order_types.items()): if any(v == 'market' for k, v in order_types.items()):
raise OperationalException( raise OperationalException(
f'Exchange {self.name} does not support market orders.') f'Exchange {self.name} does not support market orders.')
def fetch_stoploss_order(self, order_id: str, pair: str, params={}) -> Dict:
return self.fetch_order(
order_id=order_id,
pair=pair,
params={'stop': True}
)
def cancel_stoploss_order(self, order_id: str, pair: str, params={}) -> Dict:
return self.cancel_order(
order_id=order_id,
pair=pair,
params={'stop': True}
)
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
"""
Verify stop_loss against stoploss-order value (limit or price)
Returns True if adjustment is necessary.
"""
return stop_loss > float(order['stopPrice'])

View File

@ -1428,14 +1428,14 @@ class FreqtradeBot(LoggingMixin):
def handle_order_fee(self, trade: Trade, order_obj: Order, order: Dict[str, Any]) -> None: def handle_order_fee(self, trade: Trade, order_obj: Order, order: Dict[str, Any]) -> None:
# Try update amount (binance-fix) # Try update amount (binance-fix)
try: try:
new_amount = self.get_real_amount(trade, order) new_amount = self.get_real_amount(trade, order, order_obj)
if not isclose(safe_value_fallback(order, 'filled', 'amount'), new_amount, if not isclose(safe_value_fallback(order, 'filled', 'amount'), new_amount,
abs_tol=constants.MATH_CLOSE_PREC): abs_tol=constants.MATH_CLOSE_PREC):
order_obj.ft_fee_base = trade.amount - new_amount order_obj.ft_fee_base = trade.amount - new_amount
except DependencyException as exception: except DependencyException as exception:
logger.warning("Could not update trade amount: %s", exception) logger.warning("Could not update trade amount: %s", exception)
def get_real_amount(self, trade: Trade, order: Dict) -> float: def get_real_amount(self, trade: Trade, order: Dict, order_obj: Order) -> float:
""" """
Detect and update trade fee. Detect and update trade fee.
Calls trade.update_fee() upon correct detection. Calls trade.update_fee() upon correct detection.
@ -1453,7 +1453,7 @@ class FreqtradeBot(LoggingMixin):
# use fee from order-dict if possible # use fee from order-dict if possible
if self.exchange.order_has_fee(order): if self.exchange.order_has_fee(order):
fee_cost, fee_currency, fee_rate = self.exchange.extract_cost_curr_rate(order) fee_cost, fee_currency, fee_rate = self.exchange.extract_cost_curr_rate(order)
logger.info(f"Fee for Trade {trade} [{order.get('side')}]: " logger.info(f"Fee for Trade {trade} [{order_obj.ft_order_side}]: "
f"{fee_cost:.8g} {fee_currency} - rate: {fee_rate}") f"{fee_cost:.8g} {fee_currency} - rate: {fee_rate}")
if fee_rate is None or fee_rate < 0.02: if fee_rate is None or fee_rate < 0.02:
# Reject all fees that report as > 2%. # Reject all fees that report as > 2%.
@ -1465,17 +1465,18 @@ class FreqtradeBot(LoggingMixin):
return self.apply_fee_conditional(trade, trade_base_currency, return self.apply_fee_conditional(trade, trade_base_currency,
amount=order_amount, fee_abs=fee_cost) amount=order_amount, fee_abs=fee_cost)
return order_amount return order_amount
return self.fee_detection_from_trades(trade, order, order_amount, order.get('trades', [])) return self.fee_detection_from_trades(
trade, order, order_obj, order_amount, order.get('trades', []))
def fee_detection_from_trades(self, trade: Trade, order: Dict, order_amount: float, def fee_detection_from_trades(self, trade: Trade, order: Dict, order_obj: Order,
trades: List) -> float: order_amount: float, trades: List) -> float:
""" """
fee-detection fallback to Trades. fee-detection fallback to Trades.
Either uses provided trades list or the result of fetch_my_trades to get correct fee. Either uses provided trades list or the result of fetch_my_trades to get correct fee.
""" """
if not trades: if not trades:
trades = self.exchange.get_trades_for_order( trades = self.exchange.get_trades_for_order(
self.exchange.get_order_id_conditional(order), trade.pair, trade.open_date) self.exchange.get_order_id_conditional(order), trade.pair, order_obj.order_date)
if len(trades) == 0: if len(trades) == 0:
logger.info("Applying fee on amount for %s failed: myTrade-Dict empty found", trade) logger.info("Applying fee on amount for %s failed: myTrade-Dict empty found", trade)

View File

@ -87,7 +87,7 @@ class Backtesting:
validate_config_consistency(self.config) validate_config_consistency(self.config)
if "timeframe" not in self.config: if "timeframe" not in self.config:
raise OperationalException("Timeframe (ticker interval) needs to be set in either " raise OperationalException("Timeframe needs to be set in either "
"configuration or as cli argument `--timeframe 5m`") "configuration or as cli argument `--timeframe 5m`")
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)

View File

@ -29,15 +29,13 @@ class IHyperOpt(ABC):
Class attributes you can use: Class attributes you can use:
timeframe -> int: value of the timeframe to use for the strategy timeframe -> int: value of the timeframe to use for the strategy
""" """
ticker_interval: str # DEPRECATED
timeframe: str timeframe: str
strategy: IStrategy strategy: IStrategy
def __init__(self, config: dict) -> None: def __init__(self, config: dict) -> None:
self.config = config self.config = config
# Assign ticker_interval to be used in hyperopt # Assign timeframe to be used in hyperopt
IHyperOpt.ticker_interval = str(config['timeframe']) # DEPRECATED
IHyperOpt.timeframe = str(config['timeframe']) IHyperOpt.timeframe = str(config['timeframe'])
def generate_estimator(self, dimensions: List[Dimension], **kwargs) -> EstimatorType: def generate_estimator(self, dimensions: List[Dimension], **kwargs) -> EstimatorType:
@ -192,7 +190,7 @@ class IHyperOpt(ABC):
Categorical([True, False], name='trailing_only_offset_is_reached'), Categorical([True, False], name='trailing_only_offset_is_reached'),
] ]
# This is needed for proper unpickling the class attribute ticker_interval # This is needed for proper unpickling the class attribute timeframe
# which is set to the actual value by the resolver. # which is set to the actual value by the resolver.
# Why do I still need such shamanic mantras in modern python? # Why do I still need such shamanic mantras in modern python?
def __getstate__(self): def __getstate__(self):
@ -202,5 +200,4 @@ class IHyperOpt(ABC):
def __setstate__(self, state): def __setstate__(self, state):
self.__dict__.update(state) self.__dict__.update(state)
IHyperOpt.ticker_interval = state['timeframe']
IHyperOpt.timeframe = state['timeframe'] IHyperOpt.timeframe = state['timeframe']

View File

@ -174,16 +174,17 @@ def drop_orders_table(engine, table_back_name: str):
def migrate_orders_table(engine, table_back_name: str, cols_order: List): def migrate_orders_table(engine, table_back_name: str, cols_order: List):
ft_fee_base = get_column_def(cols_order, 'ft_fee_base', 'null') ft_fee_base = get_column_def(cols_order, 'ft_fee_base', 'null')
average = get_column_def(cols_order, 'average', 'null')
# let SQLAlchemy create the schema as required # let SQLAlchemy create the schema as required
with engine.begin() as connection: with engine.begin() as connection:
connection.execute(text(f""" connection.execute(text(f"""
insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id,
status, symbol, order_type, side, price, amount, filled, average, remaining, cost, status, symbol, order_type, side, price, amount, filled, average, remaining,
order_date, order_filled_date, order_update_date, ft_fee_base) cost, order_date, order_filled_date, order_update_date, ft_fee_base)
select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id,
status, symbol, order_type, side, price, amount, filled, null average, remaining, cost, status, symbol, order_type, side, price, amount, filled, {average} average, remaining,
order_date, order_filled_date, order_update_date, {ft_fee_base} cost, order_date, order_filled_date, order_update_date, {ft_fee_base} ft_fee_base
from {table_back_name} from {table_back_name}
""")) """))

View File

@ -98,7 +98,7 @@ class AgeFilter(IPairList):
""" """
Validate age for the ticker Validate age for the ticker
:param pair: Pair that's currently validated :param pair: Pair that's currently validated
:param ticker: ticker dict as returned from ccxt.fetch_tickers() :param daily_candles: Downloaded daily candles
: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
""" """
# Check symbol in cache # Check symbol in cache

View File

@ -51,7 +51,7 @@ class PrecisionFilter(IPairList):
:param ticker: ticker dict as returned from ccxt.fetch_tickers() :param ticker: ticker dict as returned from ccxt.fetch_tickers()
: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'] * self._stoploss stop_price = ticker['last'] * self._stoploss
# Adjust stop-prices to precision # Adjust stop-prices to precision
sp = self._exchange.price_to_precision(pair, stop_price) sp = self._exchange.price_to_precision(pair, stop_price)

View File

@ -4,6 +4,7 @@ Spread pair list filter
import logging import logging
from typing import Any, Dict from typing import Any, Dict
from freqtrade.exceptions import OperationalException
from freqtrade.plugins.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
@ -20,6 +21,12 @@ class SpreadFilter(IPairList):
self._max_spread_ratio = pairlistconfig.get('max_spread_ratio', 0.005) self._max_spread_ratio = pairlistconfig.get('max_spread_ratio', 0.005)
self._enabled = self._max_spread_ratio != 0 self._enabled = self._max_spread_ratio != 0
if not self._exchange.exchange_has('fetchTickers'):
raise OperationalException(
'Exchange does not support fetchTickers, therefore SpreadFilter cannot be used.'
'Please edit your config and restart the bot.'
)
@property @property
def needstickers(self) -> bool: def needstickers(self) -> bool:
""" """

View File

@ -90,7 +90,7 @@ class VolatilityFilter(IPairList):
""" """
Validate trading range Validate trading range
:param pair: Pair that's currently validated :param pair: Pair that's currently validated
:param ticker: ticker dict as returned from ccxt.fetch_tickers() :param daily_candles: Downloaded daily candles
: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
""" """
# Check symbol in cache # Check symbol in cache

View File

@ -88,7 +88,7 @@ class RangeStabilityFilter(IPairList):
""" """
Validate trading range Validate trading range
:param pair: Pair that's currently validated :param pair: Pair that's currently validated
:param ticker: ticker dict as returned from ccxt.fetch_tickers() :param daily_candles: Downloaded daily candles
: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
""" """
# Check symbol in cache # Check symbol in cache

View File

@ -44,7 +44,6 @@ class HyperOptLossResolver(IResolver):
extra_dir=config.get('hyperopt_path')) extra_dir=config.get('hyperopt_path'))
# Assign timeframe to be used in hyperopt # Assign timeframe to be used in hyperopt
hyperoptloss.__class__.ticker_interval = str(config['timeframe'])
hyperoptloss.__class__.timeframe = str(config['timeframe']) hyperoptloss.__class__.timeframe = str(config['timeframe'])
return hyperoptloss return hyperoptloss

View File

@ -45,14 +45,6 @@ class StrategyResolver(IResolver):
strategy_name, config=config, strategy_name, config=config,
extra_dir=config.get('strategy_path')) extra_dir=config.get('strategy_path'))
if hasattr(strategy, 'ticker_interval') and not hasattr(strategy, 'timeframe'):
# Assign ticker_interval to timeframe to keep compatibility
if 'timeframe' not in config:
logger.warning(
"DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'."
)
strategy.timeframe = strategy.ticker_interval
if strategy._ft_params_from_file: if strategy._ft_params_from_file:
# Set parameters from Hyperopt results file # Set parameters from Hyperopt results file
params = strategy._ft_params_from_file params = strategy._ft_params_from_file
@ -145,10 +137,6 @@ class StrategyResolver(IResolver):
""" """
Normalize attributes to have the correct type. Normalize attributes to have the correct type.
""" """
# Assign deprecated variable - to not break users code relying on this.
if hasattr(strategy, 'timeframe'):
strategy.ticker_interval = strategy.timeframe
# Sort and apply type conversions # Sort and apply type conversions
if hasattr(strategy, 'minimal_roi'): if hasattr(strategy, 'minimal_roi'):
strategy.minimal_roi = dict(sorted( strategy.minimal_roi = dict(sorted(

View File

@ -137,7 +137,7 @@ def show_config(rpc: Optional[RPC] = Depends(get_rpc_optional), config=Depends(g
def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)): def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)):
ordertype = payload.ordertype.value if payload.ordertype else None ordertype = payload.ordertype.value if payload.ordertype else None
stake_amount = payload.stakeamount if payload.stakeamount else None stake_amount = payload.stakeamount if payload.stakeamount else None
entry_tag = payload.entry_tag if payload.entry_tag else None entry_tag = payload.entry_tag if payload.entry_tag else 'forceentry'
trade = rpc._rpc_forcebuy(payload.pair, payload.price, ordertype, stake_amount, entry_tag) trade = rpc._rpc_forcebuy(payload.pair, payload.price, ordertype, stake_amount, entry_tag)

View File

@ -582,7 +582,7 @@ class RPC:
else: else:
try: try:
pair = self._freqtrade.exchange.get_valid_pair_combination(coin, stake_currency) pair = self._freqtrade.exchange.get_valid_pair_combination(coin, stake_currency)
rate = tickers.get(pair, {}).get('bid', None) rate = tickers.get(pair, {}).get('last', None)
if rate: if rate:
if pair.startswith(stake_currency) and not pair.endswith(stake_currency): if pair.startswith(stake_currency) and not pair.endswith(stake_currency):
rate = 1.0 / rate rate = 1.0 / rate
@ -713,7 +713,7 @@ class RPC:
def _rpc_forcebuy(self, pair: str, price: Optional[float], order_type: Optional[str] = None, def _rpc_forcebuy(self, pair: str, price: Optional[float], order_type: Optional[str] = None,
stake_amount: Optional[float] = None, stake_amount: Optional[float] = None,
buy_tag: Optional[str] = None) -> Optional[Trade]: buy_tag: Optional[str] = 'forceentry') -> Optional[Trade]:
""" """
Handler for forcebuy <asset> <price> Handler for forcebuy <asset> <price>
Buys a pair trade at the given or current price Buys a pair trade at the given or current price

View File

@ -55,7 +55,7 @@ class IStrategy(ABC, HyperStrategyMixin):
Attributes you can use: Attributes you can use:
minimal_roi -> Dict: Minimal ROI designed for the strategy minimal_roi -> Dict: Minimal ROI designed for the strategy
stoploss -> float: optimal stoploss designed for the strategy stoploss -> float: optimal stoploss designed for the strategy
timeframe -> str: value of the timeframe (ticker interval) to use with the strategy timeframe -> str: value of the timeframe to use with the strategy
""" """
# Strategy interface version # Strategy interface version
# Default to version 2 # Default to version 2
@ -81,7 +81,6 @@ class IStrategy(ABC, HyperStrategyMixin):
use_custom_stoploss: bool = False use_custom_stoploss: bool = False
# associated timeframe # associated timeframe
ticker_interval: str # DEPRECATED
timeframe: str timeframe: str
# Optional order types # Optional order types

View File

@ -6,8 +6,8 @@
coveralls==3.3.1 coveralls==3.3.1
flake8==4.0.1 flake8==4.0.1
flake8-tidy-imports==4.6.0 flake8-tidy-imports==4.6.0
mypy==0.931 mypy==0.940
pytest==7.0.1 pytest==7.1.0
pytest-asyncio==0.18.2 pytest-asyncio==0.18.2
pytest-cov==3.0.0 pytest-cov==3.0.0
pytest-mock==3.7.0 pytest-mock==3.7.0
@ -17,12 +17,12 @@ isort==5.10.1
time-machine==2.6.0 time-machine==2.6.0
# Convert jupyter notebooks to markdown documents # Convert jupyter notebooks to markdown documents
nbconvert==6.4.2 nbconvert==6.4.4
# mypy types # mypy types
types-cachetools==4.2.10 types-cachetools==5.0.0
types-filelock==3.2.5 types-filelock==3.2.5
types-requests==2.27.11 types-requests==2.27.12
types-tabulate==0.8.5 types-tabulate==0.8.5
# Extensions to datetime library # Extensions to datetime library

View File

@ -1,8 +1,8 @@
numpy==1.22.2 numpy==1.22.3
pandas==1.4.1 pandas==1.4.1
pandas-ta==0.3.14b pandas-ta==0.3.14b
ccxt==1.75.12 ccxt==1.76.5
# Pin cryptography for now due to rust build errors with piwheels # Pin cryptography for now due to rust build errors with piwheels
cryptography==36.0.1 cryptography==36.0.1
aiohttp==3.8.1 aiohttp==3.8.1
@ -32,7 +32,7 @@ sdnotify==0.3.2
# API Server # API Server
fastapi==0.75.0 fastapi==0.75.0
uvicorn==0.17.5 uvicorn==0.17.6
pyjwt==2.3.0 pyjwt==2.3.0
aiofiles==0.8.0 aiofiles==0.8.0
psutil==5.9.0 psutil==5.9.0

View File

@ -42,7 +42,7 @@ setup(
], ],
install_requires=[ install_requires=[
# from requirements.txt # from requirements.txt
'ccxt>=1.74.17', 'ccxt>=1.76.5',
'SQLAlchemy', 'SQLAlchemy',
'python-telegram-bot>=13.4', 'python-telegram-bot>=13.4',
'arrow>=0.17.0', 'arrow>=0.17.0',

View File

@ -1695,7 +1695,7 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None:
exchange._api_async.fetch_ohlcv.reset_mock() exchange._api_async.fetch_ohlcv.reset_mock()
caplog.clear() caplog.clear()
# Call with invalid timeframe # Call with invalid timeframe
res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '3m')],cache=False) res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '3m')], cache=False)
assert not res assert not res
assert len(res) == 0 assert len(res) == 0
assert log_has_re(r'Cannot download \(IOTA\/ETH, 3m\).*', caplog) assert log_has_re(r'Cannot download \(IOTA\/ETH, 3m\).*', caplog)

View File

@ -1,8 +1,11 @@
from unittest.mock import MagicMock
import pytest import pytest
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.exchange import Gateio from freqtrade.exchange import Gateio
from freqtrade.resolvers.exchange_resolver import ExchangeResolver from freqtrade.resolvers.exchange_resolver import ExchangeResolver
from tests.conftest import get_patched_exchange
def test_validate_order_types_gateio(default_conf, mocker): def test_validate_order_types_gateio(default_conf, mocker):
@ -26,3 +29,39 @@ def test_validate_order_types_gateio(default_conf, mocker):
with pytest.raises(OperationalException, with pytest.raises(OperationalException,
match=r'Exchange .* does not support market orders.'): match=r'Exchange .* does not support market orders.'):
ExchangeResolver.load_exchange('gateio', default_conf, True) ExchangeResolver.load_exchange('gateio', default_conf, True)
def test_fetch_stoploss_order_gateio(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, id='gateio')
fetch_order_mock = MagicMock()
exchange.fetch_order = fetch_order_mock
exchange.fetch_stoploss_order('1234', 'ETH/BTC')
assert fetch_order_mock.call_count == 1
assert fetch_order_mock.call_args_list[0][1]['order_id'] == '1234'
assert fetch_order_mock.call_args_list[0][1]['pair'] == 'ETH/BTC'
assert fetch_order_mock.call_args_list[0][1]['params'] == {'stop': True}
def test_cancel_stoploss_order_gateio(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, id='gateio')
cancel_order_mock = MagicMock()
exchange.cancel_order = cancel_order_mock
exchange.cancel_stoploss_order('1234', 'ETH/BTC')
assert cancel_order_mock.call_count == 1
assert cancel_order_mock.call_args_list[0][1]['order_id'] == '1234'
assert cancel_order_mock.call_args_list[0][1]['pair'] == 'ETH/BTC'
assert cancel_order_mock.call_args_list[0][1]['params'] == {'stop': True}
def test_stoploss_adjust_gateio(mocker, default_conf):
exchange = get_patched_exchange(mocker, default_conf, id='gateio')
order = {
'price': 1500,
'stopPrice': 1500,
}
assert exchange.stoploss_adjust(1501, order)
assert not exchange.stoploss_adjust(1499, order)

View File

@ -314,16 +314,15 @@ def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None:
patch_exchange(mocker) patch_exchange(mocker)
del default_conf['timeframe'] del default_conf['timeframe']
default_conf['strategy_list'] = ['StrategyTestV2', default_conf['strategy_list'] = ['StrategyTestV2',
'SampleStrategy'] 'HyperoptableStrategy']
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5)) mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
with pytest.raises(OperationalException): with pytest.raises(OperationalException,
match=r"Timeframe needs to be set in either configuration"):
Backtesting(default_conf) Backtesting(default_conf)
log_has("Ticker-interval needs to be set in either configuration "
"or as cli argument `--ticker-interval 5m`", caplog)
def test_data_with_fee(default_conf, mocker, testdatadir) -> None: def test_data_with_fee(default_conf, mocker) -> None:
patch_exchange(mocker) patch_exchange(mocker)
default_conf['fee'] = 0.1234 default_conf['fee'] = 0.1234

View File

@ -6,8 +6,7 @@ from unittest.mock import MagicMock
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_edge from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_edge
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
from freqtrade.optimize.edge_cli import EdgeCli from freqtrade.optimize.edge_cli import EdgeCli
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, from tests.conftest import get_args, log_has, patch_exchange, patched_configuration_load_config_file
patched_configuration_load_config_file)
def test_setup_optimize_configuration_without_arguments(mocker, default_conf, caplog) -> None: def test_setup_optimize_configuration_without_arguments(mocker, default_conf, caplog) -> None:
@ -30,7 +29,6 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca
assert 'datadir' in config assert 'datadir' in config
assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog)
assert 'timeframe' in config assert 'timeframe' in config
assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog)
assert 'timerange' not in config assert 'timerange' not in config
assert 'stoploss_range' not in config assert 'stoploss_range' not in config

View File

@ -63,7 +63,6 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca
assert 'datadir' in config assert 'datadir' in config
assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog)
assert 'timeframe' in config assert 'timeframe' in config
assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog)
assert 'position_stacking' not in config assert 'position_stacking' not in config
assert not log_has('Parameter --enable-position-stacking detected ...', caplog) assert not log_has('Parameter --enable-position-stacking detected ...', caplog)

View File

@ -782,6 +782,19 @@ def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None
get_patched_freqtradebot(mocker, default_conf) get_patched_freqtradebot(mocker, default_conf)
def test_pair_whitelist_not_supported_Spread(mocker, default_conf, tickers) -> None:
default_conf['pairlists'] = [{'method': 'StaticPairList'}, {'method': 'SpreadFilter'}]
mocker.patch.multiple('freqtrade.exchange.Exchange',
get_tickers=tickers,
exchange_has=MagicMock(return_value=False),
)
with pytest.raises(OperationalException,
match=r'Exchange does not support fetchTickers, .*'):
get_patched_freqtradebot(mocker, default_conf)
@pytest.mark.parametrize("pairlist", AVAILABLE_PAIRLISTS) @pytest.mark.parametrize("pairlist", AVAILABLE_PAIRLISTS)
def test_pairlist_class(mocker, whitelist_conf, markets, pairlist): def test_pairlist_class(mocker, whitelist_conf, markets, pairlist):
whitelist_conf['pairlists'][0]['method'] = pairlist whitelist_conf['pairlists'][0]['method'] = pairlist

View File

@ -605,8 +605,8 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
rpc._fiat_converter = CryptoToFiatConverter() rpc._fiat_converter = CryptoToFiatConverter()
result = rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency']) result = rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency'])
assert prec_satoshi(result['total'], 12.309096315) assert prec_satoshi(result['total'], 12.30909624)
assert prec_satoshi(result['value'], 184636.44472997) assert prec_satoshi(result['value'], 184636.443606915)
assert tickers.call_count == 1 assert tickers.call_count == 1
assert tickers.call_args_list[0][1]['cached'] is True assert tickers.call_args_list[0][1]['cached'] is True
assert 'USD' == result['symbol'] assert 'USD' == result['symbol']
@ -624,17 +624,16 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
'est_stake': 0.30794, 'est_stake': 0.30794,
'used': 4.0, 'used': 4.0,
'stake': 'BTC', 'stake': 'BTC',
}, },
{'free': 5.0, {'free': 5.0,
'balance': 10.0, 'balance': 10.0,
'currency': 'USDT', 'currency': 'USDT',
'est_stake': 0.0011563153318162476, 'est_stake': 0.0011562404610161968,
'used': 5.0, 'used': 5.0,
'stake': 'BTC', 'stake': 'BTC',
} }
] ]
assert result['total'] == 12.309096315331816 assert result['total'] == 12.309096240461017
def test_rpc_start(mocker, default_conf) -> None: def test_rpc_start(mocker, default_conf) -> None:
@ -1148,6 +1147,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
pair = 'LTC/BTC' pair = 'LTC/BTC'
trade = rpc._rpc_forcebuy(pair, 0.0001, order_type='limit', stake_amount=0.05) trade = rpc._rpc_forcebuy(pair, 0.0001, order_type='limit', stake_amount=0.05)
assert trade.stake_amount == 0.05 assert trade.stake_amount == 0.05
assert trade.buy_tag == 'forceentry'
# Test not buying # Test not buying
pair = 'XRP/BTC' pair = 'XRP/BTC'

View File

@ -31,9 +31,7 @@ class TestStrategyLegacyV1(IStrategy):
# This attribute will be overridden if the config file contains "stoploss" # This attribute will be overridden if the config file contains "stoploss"
stoploss = -0.10 stoploss = -0.10
# Optimal timeframe for the strategy timeframe = '5m'
# Keep the legacy value here to test compatibility
ticker_interval = '5m'
def populate_indicators(self, dataframe: DataFrame) -> DataFrame: def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
""" """

View File

@ -111,7 +111,6 @@ def test_strategy(result, default_conf):
assert default_conf['stoploss'] == -0.10 assert default_conf['stoploss'] == -0.10
assert strategy.timeframe == '5m' assert strategy.timeframe == '5m'
assert strategy.ticker_interval == '5m'
assert default_conf['timeframe'] == '5m' assert default_conf['timeframe'] == '5m'
df_indicators = strategy.advise_indicators(result, metadata=metadata) df_indicators = strategy.advise_indicators(result, metadata=metadata)
@ -376,7 +375,6 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog):
assert strategy._sell_fun_len == 2 assert strategy._sell_fun_len == 2
assert strategy.INTERFACE_VERSION == 1 assert strategy.INTERFACE_VERSION == 1
assert strategy.timeframe == '5m' assert strategy.timeframe == '5m'
assert strategy.ticker_interval == '5m'
indicator_df = strategy.advise_indicators(result, metadata=metadata) indicator_df = strategy.advise_indicators(result, metadata=metadata)
assert isinstance(indicator_df, DataFrame) assert isinstance(indicator_df, DataFrame)
@ -390,9 +388,6 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog):
assert isinstance(selldf, DataFrame) assert isinstance(selldf, DataFrame)
assert 'sell' in selldf assert 'sell' in selldf
assert log_has("DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'.",
caplog)
def test_strategy_interface_versioning(result, monkeypatch, default_conf): def test_strategy_interface_versioning(result, monkeypatch, default_conf):
default_conf.update({'strategy': 'StrategyTestV2'}) default_conf.update({'strategy': 'StrategyTestV2'})

View File

@ -111,17 +111,17 @@ def test_parse_args_strategy_path_invalid() -> None:
def test_parse_args_backtesting_invalid() -> None: def test_parse_args_backtesting_invalid() -> None:
with pytest.raises(SystemExit, match=r'2'): with pytest.raises(SystemExit, match=r'2'):
Arguments(['backtesting --ticker-interval']).get_parsed_arg() Arguments(['backtesting --timeframe']).get_parsed_arg()
with pytest.raises(SystemExit, match=r'2'): with pytest.raises(SystemExit, match=r'2'):
Arguments(['backtesting --ticker-interval', 'abc']).get_parsed_arg() Arguments(['backtesting --timeframe', 'abc']).get_parsed_arg()
def test_parse_args_backtesting_custom() -> None: def test_parse_args_backtesting_custom() -> None:
args = [ args = [
'backtesting', 'backtesting',
'-c', 'test_conf.json', '-c', 'test_conf.json',
'--ticker-interval', '1m', '--timeframe', '1m',
'--strategy-list', '--strategy-list',
'StrategyTestV2', 'StrategyTestV2',
'SampleStrategy' 'SampleStrategy'

View File

@ -443,7 +443,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
'--strategy', 'StrategyTestV2', '--strategy', 'StrategyTestV2',
'--datadir', '/foo/bar', '--datadir', '/foo/bar',
'--userdir', "/tmp/freqtrade", '--userdir', "/tmp/freqtrade",
'--ticker-interval', '1m', '--timeframe', '1m',
'--enable-position-stacking', '--enable-position-stacking',
'--disable-max-market-positions', '--disable-max-market-positions',
'--timerange', ':100', '--timerange', ':100',
@ -494,7 +494,7 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non
arglist = [ arglist = [
'backtesting', 'backtesting',
'--config', 'config.json', '--config', 'config.json',
'--ticker-interval', '1m', '--timeframe', '1m',
'--export', 'trades', '--export', 'trades',
'--strategy-list', '--strategy-list',
'StrategyTestV2', 'StrategyTestV2',
@ -1320,22 +1320,14 @@ def test_process_removed_setting(mocker, default_conf, caplog):
def test_process_deprecated_ticker_interval(default_conf, caplog): def test_process_deprecated_ticker_interval(default_conf, caplog):
message = "DEPRECATED: Please use 'timeframe' instead of 'ticker_interval." message = "DEPRECATED: Please use 'timeframe' instead of 'ticker_interval."
config = deepcopy(default_conf) config = deepcopy(default_conf)
process_temporary_deprecated_settings(config) process_temporary_deprecated_settings(config)
assert not log_has(message, caplog) assert not log_has(message, caplog)
del config['timeframe'] del config['timeframe']
config['ticker_interval'] = '15m' config['ticker_interval'] = '15m'
process_temporary_deprecated_settings(config)
assert log_has(message, caplog)
assert config['ticker_interval'] == '15m'
config = deepcopy(default_conf)
# Have both timeframe and ticker interval in config
# Can also happen when using ticker_interval in configuration, and --timeframe as cli argument
config['timeframe'] = '5m'
config['ticker_interval'] = '4h'
with pytest.raises(OperationalException, with pytest.raises(OperationalException,
match=r"Both 'timeframe' and 'ticker_interval' detected."): match=r"DEPRECATED: 'ticker_interval' detected. Please use.*"):
process_temporary_deprecated_settings(config) process_temporary_deprecated_settings(config)

View File

@ -3568,9 +3568,9 @@ def test_get_real_amount_quote(default_conf_usdt, trades_for_order, buy_order_fe
open_order_id="123456" open_order_id="123456"
) )
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
# Amount is reduced by "fee" # Amount is reduced by "fee"
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount - (amount * 0.001) assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == amount - (amount * 0.001)
assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992).', 'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992).',
caplog) caplog)
@ -3594,8 +3594,9 @@ def test_get_real_amount_quote_dust(default_conf_usdt, trades_for_order, buy_ord
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
walletmock.reset_mock() walletmock.reset_mock()
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
# Amount is kept as is # Amount is kept as is
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == amount
assert walletmock.call_count == 1 assert walletmock.call_count == 1
assert log_has_re(r'Fee amount for Trade.* was in base currency ' assert log_has_re(r'Fee amount for Trade.* was in base currency '
'- Eating Fee 0.008 into dust', caplog) '- Eating Fee 0.008 into dust', caplog)
@ -3616,8 +3617,9 @@ def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mock
) )
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
# Amount is reduced by "fee" # Amount is reduced by "fee"
assert freqtrade.get_real_amount(trade, buy_order_fee) == amount assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == amount
assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
'open_rate=0.24544100, open_since=closed) failed: myTrade-Dict empty found', 'open_rate=0.24544100, open_since=closed) failed: myTrade-Dict empty found',
caplog) caplog)
@ -3668,7 +3670,8 @@ def test_get_real_amount(
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', side_effect=ExchangeError) mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', side_effect=ExchangeError)
caplog.clear() caplog.clear()
assert freqtrade.get_real_amount(trade, buy_order) == amount - fee_reduction_amount order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
assert freqtrade.get_real_amount(trade, buy_order, order_obj) == amount - fee_reduction_amount
if expected_log: if expected_log:
assert log_has(expected_log, caplog) assert log_has(expected_log, caplog)
@ -3715,7 +3718,8 @@ def test_get_real_amount_multi(
# Amount is reduced by "fee" # Amount is reduced by "fee"
expected_amount = amount - (amount * fee_reduction_amount) expected_amount = amount - (amount * fee_reduction_amount)
assert freqtrade.get_real_amount(trade, buy_order_fee) == expected_amount order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == expected_amount
assert log_has( assert log_has(
( (
'Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' 'Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, '
@ -3750,8 +3754,9 @@ def test_get_real_amount_invalid_order(default_conf_usdt, trades_for_order, buy_
) )
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
# Amount does not change # Amount does not change
assert freqtrade.get_real_amount(trade, limit_buy_order_usdt) == amount assert freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj) == amount
def test_get_real_amount_fees_order(default_conf_usdt, market_buy_order_usdt_doublefee, def test_get_real_amount_fees_order(default_conf_usdt, market_buy_order_usdt_doublefee,
@ -3773,7 +3778,8 @@ def test_get_real_amount_fees_order(default_conf_usdt, market_buy_order_usdt_dou
# Amount does not change # Amount does not change
assert trade.fee_open == 0.0025 assert trade.fee_open == 0.0025
assert freqtrade.get_real_amount(trade, market_buy_order_usdt_doublefee) == 30.0 order_obj = Order.parse_from_ccxt_object(market_buy_order_usdt_doublefee, 'LTC/ETH', 'buy')
assert freqtrade.get_real_amount(trade, market_buy_order_usdt_doublefee, order_obj) == 30.0
assert tfo_mock.call_count == 0 assert tfo_mock.call_count == 0
# Fetch fees from trades dict if available to get "proper" values # Fetch fees from trades dict if available to get "proper" values
assert round(trade.fee_open, 4) == 0.001 assert round(trade.fee_open, 4) == 0.001
@ -3797,9 +3803,10 @@ def test_get_real_amount_wrong_amount(default_conf_usdt, trades_for_order, buy_o
) )
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
# Amount does not change # Amount does not change
with pytest.raises(DependencyException, match=r"Half bought\? Amounts don't match"): with pytest.raises(DependencyException, match=r"Half bought\? Amounts don't match"):
freqtrade.get_real_amount(trade, limit_buy_order_usdt) freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj)
def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_order, buy_order_fee, def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_order, buy_order_fee,
@ -3821,9 +3828,10 @@ def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_ord
) )
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy')
# Amount changes by fee amount. # Amount changes by fee amount.
assert isclose( assert isclose(
freqtrade.get_real_amount(trade, limit_buy_order_usdt), freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj),
amount - (amount * 0.001), amount - (amount * 0.001),
abs_tol=MATH_CLOSE_PREC, abs_tol=MATH_CLOSE_PREC,
) )
@ -3847,7 +3855,8 @@ def test_get_real_amount_open_trade(default_conf_usdt, fee, mocker):
'side': 'buy', 'side': 'buy',
} }
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
assert freqtrade.get_real_amount(trade, order) == amount order_obj = Order.parse_from_ccxt_object(order, 'LTC/ETH', 'buy')
assert freqtrade.get_real_amount(trade, order, order_obj) == amount
@pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [ @pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [

View File

@ -26,7 +26,9 @@ def test_ttl_cache():
assert 'a' in cache1h assert 'a' in cache1h
t.move_to("2021-09-01 05:59:59 +00:00") t.move_to("2021-09-01 05:59:59 +00:00")
assert 'a' not in cache
assert 'a' in cache1h assert 'a' in cache1h
t.move_to("2021-09-01 06:00:00 +00:00") t.move_to("2021-09-01 06:00:00 +00:00")
assert 'a' not in cache
assert 'a' not in cache1h assert 'a' not in cache1h