Merge branch 'freqtrade:develop' into plot_hyperopt_stats
This commit is contained in:
commit
112738d68d
@ -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-
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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"]
|
||||||
|
|
||||||
|
@ -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`',
|
||||||
|
@ -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.")
|
||||||
|
@ -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,
|
||||||
|
@ -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:
|
||||||
|
@ -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'])
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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']
|
||||||
|
@ -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}
|
||||||
"""))
|
"""))
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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:
|
||||||
"""
|
"""
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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(
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
2
setup.py
2
setup.py
@ -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',
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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'
|
||||||
|
@ -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:
|
||||||
"""
|
"""
|
||||||
|
@ -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'})
|
||||||
|
@ -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'
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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', [
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user