Merge branch 'feat/short' into pr/samgermain/5378
This commit is contained in:
commit
ca44d2e092
@ -1,4 +1,4 @@
|
|||||||
FROM python:3.9.6-slim-buster as base
|
FROM python:3.9.7-slim-buster as base
|
||||||
|
|
||||||
# Setup env
|
# Setup env
|
||||||
ENV LANG C.UTF-8
|
ENV LANG C.UTF-8
|
||||||
|
@ -42,7 +42,7 @@ docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${CACHE_I
|
|||||||
docker tag freqtrade:$TAG_PLOT_ARM ${CACHE_IMAGE}:$TAG_PLOT_ARM
|
docker tag freqtrade:$TAG_PLOT_ARM ${CACHE_IMAGE}:$TAG_PLOT_ARM
|
||||||
|
|
||||||
# Run backtest
|
# Run backtest
|
||||||
docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG_ARM} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy DefaultStrategy
|
docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG_ARM} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy StrategyTestV2
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "failed running backtest"
|
echo "failed running backtest"
|
||||||
|
@ -53,7 +53,7 @@ docker build --cache-from freqtrade:${TAG} --build-arg sourceimage=${CACHE_IMAGE
|
|||||||
docker tag freqtrade:$TAG_PLOT ${CACHE_IMAGE}:$TAG_PLOT
|
docker tag freqtrade:$TAG_PLOT ${CACHE_IMAGE}:$TAG_PLOT
|
||||||
|
|
||||||
# Run backtest
|
# Run backtest
|
||||||
docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy DefaultStrategy
|
docker run --rm -v $(pwd)/config_examples/config_bittrex.example.json:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy StrategyTestV2
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "failed running backtest"
|
echo "failed running backtest"
|
||||||
|
@ -174,7 +174,7 @@
|
|||||||
"heartbeat_interval": 60
|
"heartbeat_interval": 60
|
||||||
},
|
},
|
||||||
"disable_dataframe_checks": false,
|
"disable_dataframe_checks": false,
|
||||||
"strategy": "DefaultStrategy",
|
"strategy": "SampleStrategy",
|
||||||
"strategy_path": "user_data/strategies/",
|
"strategy_path": "user_data/strategies/",
|
||||||
"dataformat_ohlcv": "json",
|
"dataformat_ohlcv": "json",
|
||||||
"dataformat_trades": "jsongz"
|
"dataformat_trades": "jsongz"
|
||||||
|
@ -335,7 +335,7 @@ Once the optimized parameters and conditions have been implemented into your str
|
|||||||
|
|
||||||
To achieve same results (number of trades, their durations, profit, etc.) than during Hyperopt, please use same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting.
|
To achieve same results (number of trades, their durations, profit, etc.) than during Hyperopt, please use same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting.
|
||||||
|
|
||||||
Should results don't match, please double-check to make sure you transferred all conditions correctly.
|
Should results not match, please double-check to make sure you transferred all conditions correctly.
|
||||||
Pay special care to the stoploss (and trailing stoploss) parameters, as these are often set in configuration files, which override changes to the strategy.
|
Pay special care to the stoploss (and trailing stoploss) parameters, as these are often set in configuration files, which override changes to the strategy.
|
||||||
You should also carefully review the log of your backtest to ensure that there were no parameters inadvertently set by the configuration (like `stoploss` or `trailing_stop`).
|
You should also carefully review the log of your backtest to ensure that there were no parameters inadvertently set by the configuration (like `stoploss` or `trailing_stop`).
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
|||||||
[-p PAIRS [PAIRS ...]] [--eps] [--dmmp]
|
[-p PAIRS [PAIRS ...]] [--eps] [--dmmp]
|
||||||
[--enable-protections]
|
[--enable-protections]
|
||||||
[--dry-run-wallet DRY_RUN_WALLET]
|
[--dry-run-wallet DRY_RUN_WALLET]
|
||||||
|
[--timeframe-detail TIMEFRAME_DETAIL]
|
||||||
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
|
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
|
||||||
[--export {none,trades}] [--export-filename PATH]
|
[--export {none,trades}] [--export-filename PATH]
|
||||||
|
|
||||||
@ -55,6 +56,9 @@ optional arguments:
|
|||||||
--dry-run-wallet DRY_RUN_WALLET, --starting-balance DRY_RUN_WALLET
|
--dry-run-wallet DRY_RUN_WALLET, --starting-balance DRY_RUN_WALLET
|
||||||
Starting balance, used for backtesting / hyperopt and
|
Starting balance, used for backtesting / hyperopt and
|
||||||
dry-runs.
|
dry-runs.
|
||||||
|
--timeframe-detail TIMEFRAME_DETAIL
|
||||||
|
Specify detail timeframe for backtesting (`1m`, `5m`,
|
||||||
|
`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 ticker-interval needs to be
|
||||||
@ -62,7 +66,7 @@ optional arguments:
|
|||||||
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-
|
||||||
data.json` becomes `backtest-data-
|
data.json` becomes `backtest-data-
|
||||||
DefaultStrategy.json`
|
SampleStrategy.json`
|
||||||
--export {none,trades}
|
--export {none,trades}
|
||||||
Export backtest results (default: trades).
|
Export backtest results (default: trades).
|
||||||
--export-filename PATH
|
--export-filename PATH
|
||||||
@ -425,7 +429,12 @@ It contains some useful key metrics about performance of your strategy on backte
|
|||||||
- `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command).
|
- `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command).
|
||||||
- `Market change`: Change of the market during the backtest period. Calculated as average of all pairs changes from the first to the last candle using the "close" column.
|
- `Market change`: Change of the market during the backtest period. Calculated as average of all pairs changes from the first to the last candle using the "close" column.
|
||||||
|
|
||||||
### Assumptions made by backtesting
|
### Further backtest-result analysis
|
||||||
|
|
||||||
|
To further analyze your backtest results, you can [export the trades](#exporting-trades-to-file).
|
||||||
|
You can then load the trades to perform further analysis as shown in our [data analysis](data-analysis.md#backtesting) backtesting section.
|
||||||
|
|
||||||
|
## Assumptions made by backtesting
|
||||||
|
|
||||||
Since backtesting lacks some detailed information about what happens within a candle, it needs to take a few assumptions:
|
Since backtesting lacks some detailed information about what happens within a candle, it needs to take a few assumptions:
|
||||||
|
|
||||||
@ -456,10 +465,30 @@ Also, keep in mind that past results don't guarantee future success.
|
|||||||
|
|
||||||
In addition to the above assumptions, strategy authors should carefully read the [Common Mistakes](strategy-customization.md#common-mistakes-when-developing-strategies) section, to avoid using data in backtesting which is not available in real market conditions.
|
In addition to the above assumptions, strategy authors should carefully read the [Common Mistakes](strategy-customization.md#common-mistakes-when-developing-strategies) section, to avoid using data in backtesting which is not available in real market conditions.
|
||||||
|
|
||||||
### Further backtest-result analysis
|
### Improved backtest accuracy
|
||||||
|
|
||||||
To further analyze your backtest results, you can [export the trades](#exporting-trades-to-file).
|
One big limitation of backtesting is it's inability to know how prices moved intra-candle (was high before close, or viceversa?).
|
||||||
You can then load the trades to perform further analysis as shown in our [data analysis](data-analysis.md#backtesting) backtesting section.
|
So assuming you run backtesting with a 1h timeframe, there will be 4 prices for that candle (Open, High, Low, Close).
|
||||||
|
|
||||||
|
While backtesting does take some assumptions (read above) about this - this can never be perfect, and will always be biased in one way or the other.
|
||||||
|
To mitigate this, freqtrade can use a lower (faster) timeframe to simulate intra-candle movements.
|
||||||
|
|
||||||
|
To utilize this, you can append `--timeframe-detail 5m` to your regular backtesting command.
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
freqtrade backtesting --strategy AwesomeStrategy --timeframe 1h --timeframe-detail 5m
|
||||||
|
```
|
||||||
|
|
||||||
|
This will load 1h data as well as 5m data for the timeframe. The strategy will be analyzed with the 1h timeframe - and for every "open trade candle" (candles where a trade is open) the 5m data will be used to simulate intra-candle movements.
|
||||||
|
All callback functions (`custom_sell()`, `custom_stoploss()`, ... ) will be running for each 5m candle once the trade is opened (so 12 times in the above example of 1h timeframe, and 5m detailed timeframe).
|
||||||
|
|
||||||
|
`--timeframe-detail` must be smaller than the original timeframe, otherwise backtesting will fail to start.
|
||||||
|
|
||||||
|
Obviously this will require more memory (5m data is bigger than 1h data), and will also impact runtime (depending on the amount of trades and trade durations).
|
||||||
|
Also, data must be available / downloaded already.
|
||||||
|
|
||||||
|
!!! Tip
|
||||||
|
You can use this function as the last part of strategy development, to ensure your strategy is not exploiting one of the [backtesting assumptions](#assumptions-made-by-backtesting). Strategies that perform similarly well with this mode have a good chance to perform well in dry/live modes too (although only forward-testing (dry-mode) can really confirm a strategy).
|
||||||
|
|
||||||
## Backtesting multiple strategies
|
## Backtesting multiple strategies
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ This page provides you some basic concepts on how Freqtrade works and operates.
|
|||||||
* **Strategy**: Your trading strategy, telling the bot what to do.
|
* **Strategy**: Your trading strategy, telling the bot what to do.
|
||||||
* **Trade**: Open position.
|
* **Trade**: Open position.
|
||||||
* **Open Order**: Order which is currently placed on the exchange, and is not yet complete.
|
* **Open Order**: Order which is currently placed on the exchange, and is not yet complete.
|
||||||
* **Pair**: Tradable pair, usually in the format of Quote/Base (e.g. XRP/USDT).
|
* **Pair**: Tradable pair, usually in the format of Base/Quote (e.g. XRP/USDT).
|
||||||
* **Timeframe**: Candle length to use (e.g. `"5m"`, `"1h"`, ...).
|
* **Timeframe**: Candle length to use (e.g. `"5m"`, `"1h"`, ...).
|
||||||
* **Indicators**: Technical indicators (SMA, EMA, RSI, ...).
|
* **Indicators**: Technical indicators (SMA, EMA, RSI, ...).
|
||||||
* **Limit order**: Limit orders which execute at the defined limit price or better.
|
* **Limit order**: Limit orders which execute at the defined limit price or better.
|
||||||
|
@ -456,7 +456,7 @@ class MyAwesomeStrategy(IStrategy):
|
|||||||
"only_per_pair": False
|
"only_per_pair": False
|
||||||
})
|
})
|
||||||
|
|
||||||
return protection
|
return prot
|
||||||
|
|
||||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
# ...
|
# ...
|
||||||
@ -827,8 +827,8 @@ After you run Hyperopt for the desired amount of epochs, you can later list all
|
|||||||
|
|
||||||
Once the optimized strategy has been implemented into your strategy, you should backtest this strategy to make sure everything is working as expected.
|
Once the optimized strategy has been implemented into your strategy, you should backtest this strategy to make sure everything is working as expected.
|
||||||
|
|
||||||
To achieve same results (number of trades, their durations, profit, etc.) than during Hyperopt, please use same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting.
|
To achieve same the results (number of trades, their durations, profit, etc.) as during Hyperopt, please use the same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting.
|
||||||
|
|
||||||
Should results don't match, please double-check to make sure you transferred all conditions correctly.
|
Should results not match, please double-check to make sure you transferred all conditions correctly.
|
||||||
Pay special care to the stoploss (and trailing stoploss) parameters, as these are often set in configuration files, which override changes to the strategy.
|
Pay special care to the stoploss (and trailing stoploss) parameters, as these are often set in configuration files, which override changes to the strategy.
|
||||||
You should also carefully review the log of your backtest to ensure that there were no parameters inadvertently set by the configuration (like `stoploss` or `trailing_stop`).
|
You should also carefully review the log of your backtest to ensure that there were no parameters inadvertently set by the configuration (like `stoploss` or `trailing_stop`).
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
mkdocs==1.2.2
|
mkdocs==1.2.2
|
||||||
mkdocs-material==7.2.4
|
mkdocs-material==7.2.5
|
||||||
mdx_truly_sane_lists==1.2
|
mdx_truly_sane_lists==1.2
|
||||||
pymdown-extensions==8.2
|
pymdown-extensions==8.2
|
||||||
|
@ -22,7 +22,7 @@ ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "dataformat_ohlcv",
|
|||||||
"max_open_trades", "stake_amount", "fee", "pairs"]
|
"max_open_trades", "stake_amount", "fee", "pairs"]
|
||||||
|
|
||||||
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions",
|
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions",
|
||||||
"enable_protections", "dry_run_wallet",
|
"enable_protections", "dry_run_wallet", "timeframe_detail",
|
||||||
"strategy_list", "export", "exportfilename"]
|
"strategy_list", "export", "exportfilename"]
|
||||||
|
|
||||||
ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
|
ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
|
||||||
|
@ -66,16 +66,22 @@ def ask_user_config() -> Dict[str, Any]:
|
|||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"name": "stake_amount",
|
"name": "stake_amount",
|
||||||
"message": "Please insert your stake amount:",
|
"message": f"Please insert your stake amount (Number or '{UNLIMITED_STAKE_AMOUNT}'):",
|
||||||
"default": "0.01",
|
"default": "0.01",
|
||||||
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val),
|
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val),
|
||||||
|
"filter": lambda val: '"' + UNLIMITED_STAKE_AMOUNT + '"'
|
||||||
|
if val == UNLIMITED_STAKE_AMOUNT
|
||||||
|
else val
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"name": "max_open_trades",
|
"name": "max_open_trades",
|
||||||
"message": f"Please insert max_open_trades (Integer or '{UNLIMITED_STAKE_AMOUNT}'):",
|
"message": f"Please insert max_open_trades (Integer or '{UNLIMITED_STAKE_AMOUNT}'):",
|
||||||
"default": "3",
|
"default": "3",
|
||||||
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_int(val)
|
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_int(val),
|
||||||
|
"filter": lambda val: '"' + UNLIMITED_STAKE_AMOUNT + '"'
|
||||||
|
if val == UNLIMITED_STAKE_AMOUNT
|
||||||
|
else val
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
@ -135,6 +135,10 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
help='Override the value of the `stake_amount` configuration setting.',
|
help='Override the value of the `stake_amount` configuration setting.',
|
||||||
),
|
),
|
||||||
# Backtesting
|
# Backtesting
|
||||||
|
"timeframe_detail": Arg(
|
||||||
|
'--timeframe-detail',
|
||||||
|
help='Specify detail timeframe for backtesting (`1m`, `5m`, `30m`, `1h`, `1d`).',
|
||||||
|
),
|
||||||
"position_stacking": Arg(
|
"position_stacking": Arg(
|
||||||
'--eps', '--enable-position-stacking',
|
'--eps', '--enable-position-stacking',
|
||||||
help='Allow buying the same pair multiple times (position stacking).',
|
help='Allow buying the same pair multiple times (position stacking).',
|
||||||
@ -162,7 +166,7 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
'Please note that ticker-interval needs to be set either in config '
|
'Please note that ticker-interval 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-DefaultStrategy.json`',
|
'(so `backtest-data.json` becomes `backtest-data-SampleStrategy.json`',
|
||||||
nargs='+',
|
nargs='+',
|
||||||
),
|
),
|
||||||
"export": Arg(
|
"export": Arg(
|
||||||
|
@ -74,8 +74,6 @@ def start_new_strategy(args: Dict[str, Any]) -> None:
|
|||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|
||||||
if "strategy" in args and args["strategy"]:
|
if "strategy" in args and args["strategy"]:
|
||||||
if args["strategy"] == "DefaultStrategy":
|
|
||||||
raise OperationalException("DefaultStrategy is not allowed as name.")
|
|
||||||
|
|
||||||
new_path = config['user_data_dir'] / USERPATH_STRATEGIES / (args['strategy'] + '.py')
|
new_path = config['user_data_dir'] / USERPATH_STRATEGIES / (args['strategy'] + '.py')
|
||||||
|
|
||||||
@ -128,8 +126,6 @@ def start_new_hyperopt(args: Dict[str, Any]) -> None:
|
|||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|
||||||
if 'hyperopt' in args and args['hyperopt']:
|
if 'hyperopt' in args and args['hyperopt']:
|
||||||
if args['hyperopt'] == 'DefaultHyperopt':
|
|
||||||
raise OperationalException("DefaultHyperopt is not allowed as name.")
|
|
||||||
|
|
||||||
new_path = config['user_data_dir'] / USERPATH_HYPEROPTS / (args['hyperopt'] + '.py')
|
new_path = config['user_data_dir'] / USERPATH_HYPEROPTS / (args['hyperopt'] + '.py')
|
||||||
|
|
||||||
|
@ -242,6 +242,9 @@ class Configuration:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
self._args_to_config(config, argname='timeframe_detail',
|
||||||
|
logstring='Parameter --timeframe-detail detected, '
|
||||||
|
'using {} for intra-candle backtesting ...')
|
||||||
self._args_to_config(config, argname='stake_amount',
|
self._args_to_config(config, argname='stake_amount',
|
||||||
logstring='Parameter --stake-amount detected, '
|
logstring='Parameter --stake-amount detected, '
|
||||||
'overriding stake_amount to: {} ...')
|
'overriding stake_amount to: {} ...')
|
||||||
|
@ -49,6 +49,8 @@ USERPATH_NOTEBOOKS = 'notebooks'
|
|||||||
TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent']
|
TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent']
|
||||||
ENV_VAR_PREFIX = 'FREQTRADE__'
|
ENV_VAR_PREFIX = 'FREQTRADE__'
|
||||||
|
|
||||||
|
NON_OPEN_EXCHANGE_STATES = ('cancelled', 'canceled', 'closed', 'expired')
|
||||||
|
|
||||||
|
|
||||||
# Define decimals per coin for outputs
|
# Define decimals per coin for outputs
|
||||||
# Only used for outputs.
|
# Only used for outputs.
|
||||||
|
@ -19,7 +19,8 @@ from ccxt.base.decimal_to_precision import (ROUND_DOWN, ROUND_UP, TICK_SIZE, TRU
|
|||||||
decimal_to_precision)
|
decimal_to_precision)
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade.constants import DEFAULT_AMOUNT_RESERVE_PERCENT, ListPairsWithTimeframes
|
from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES,
|
||||||
|
ListPairsWithTimeframes)
|
||||||
from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list
|
from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list
|
||||||
from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError,
|
from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError,
|
||||||
InvalidOrderException, OperationalException, PricingError,
|
InvalidOrderException, OperationalException, PricingError,
|
||||||
@ -351,9 +352,16 @@ class Exchange:
|
|||||||
def validate_stakecurrency(self, stake_currency: str) -> None:
|
def validate_stakecurrency(self, stake_currency: str) -> None:
|
||||||
"""
|
"""
|
||||||
Checks stake-currency against available currencies on the exchange.
|
Checks stake-currency against available currencies on the exchange.
|
||||||
|
Only runs on startup. If markets have not been loaded, there's been a problem with
|
||||||
|
the connection to the exchange.
|
||||||
:param stake_currency: Stake-currency to validate
|
:param stake_currency: Stake-currency to validate
|
||||||
:raise: OperationalException if stake-currency is not available.
|
:raise: OperationalException if stake-currency is not available.
|
||||||
"""
|
"""
|
||||||
|
if not self._markets:
|
||||||
|
raise OperationalException(
|
||||||
|
'Could not load markets, therefore cannot start. '
|
||||||
|
'Please investigate the above error for more details.'
|
||||||
|
)
|
||||||
quote_currencies = self.get_quote_currencies()
|
quote_currencies = self.get_quote_currencies()
|
||||||
if stake_currency not in quote_currencies:
|
if stake_currency not in quote_currencies:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
@ -804,7 +812,7 @@ class Exchange:
|
|||||||
:param order: Order dict as returned from fetch_order()
|
:param order: Order dict as returned from fetch_order()
|
||||||
:return: True if order has been cancelled without being filled, False otherwise.
|
:return: True if order has been cancelled without being filled, False otherwise.
|
||||||
"""
|
"""
|
||||||
return (order.get('status') in ('closed', 'canceled', 'cancelled')
|
return (order.get('status') in NON_OPEN_EXCHANGE_STATES
|
||||||
and order.get('filled') == 0.0)
|
and order.get('filled') == 0.0)
|
||||||
|
|
||||||
@retrier
|
@retrier
|
||||||
|
@ -433,11 +433,12 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
# TODO-lev: Does the below need to be adjusted for shorts?
|
# TODO-lev: Does the below need to be adjusted for shorts?
|
||||||
if self._check_depth_of_market_buy(pair, bid_check_dom):
|
if self._check_depth_of_market_buy(pair, bid_check_dom):
|
||||||
# TODO-lev: pass in "enter" as side.
|
# TODO-lev: pass in "enter" as side.
|
||||||
return self.execute_buy(pair, stake_amount, enter_tag=enter_tag)
|
|
||||||
|
return self.execute_entry(pair, stake_amount, buy_tag=enter_tag)
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return self.execute_buy(pair, stake_amount, enter_tag=enter_tag)
|
return self.execute_entry(pair, stake_amount, buy_tag=enter_tag)
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -465,8 +466,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
logger.info(f"Bids to asks delta for {pair} does not satisfy condition.")
|
logger.info(f"Bids to asks delta for {pair} does not satisfy condition.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = None,
|
def execute_entry(self, pair: str, stake_amount: float, price: Optional[float] = None,
|
||||||
forcebuy: bool = False, enter_tag: Optional[str] = None) -> bool:
|
forcebuy: bool = False, enter_tag: Optional[str] = None) -> bool:
|
||||||
"""
|
"""
|
||||||
Executes a limit buy for the given pair
|
Executes a limit buy for the given pair
|
||||||
:param pair: pair for which we want to create a LIMIT_BUY
|
:param pair: pair for which we want to create a LIMIT_BUY
|
||||||
@ -746,7 +747,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
trade.stoploss_order_id = None
|
trade.stoploss_order_id = None
|
||||||
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
||||||
logger.warning('Selling the trade forcefully')
|
logger.warning('Selling the trade forcefully')
|
||||||
self.execute_sell(trade, trade.stop_loss, sell_reason=SellCheckTuple(
|
self.execute_trade_exit(trade, trade.stop_loss, sell_reason=SellCheckTuple(
|
||||||
sell_type=SellType.EMERGENCY_SELL))
|
sell_type=SellType.EMERGENCY_SELL))
|
||||||
|
|
||||||
except ExchangeError:
|
except ExchangeError:
|
||||||
@ -864,7 +865,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
|
|
||||||
if should_exit.sell_flag:
|
if should_exit.sell_flag:
|
||||||
logger.info(f'Exit for {trade.pair} detected. Reason: {should_exit.sell_type}')
|
logger.info(f'Exit for {trade.pair} detected. Reason: {should_exit.sell_type}')
|
||||||
self.execute_sell(trade, sell_rate, should_exit)
|
self.execute_trade_exit(trade, sell_rate, should_exit)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -946,7 +947,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
was_trade_fully_canceled = False
|
was_trade_fully_canceled = False
|
||||||
|
|
||||||
# Cancelled orders may have the status of 'canceled' or 'closed'
|
# Cancelled orders may have the status of 'canceled' or 'closed'
|
||||||
if order['status'] not in ('cancelled', 'canceled', 'closed'):
|
if order['status'] not in constants.NON_OPEN_EXCHANGE_STATES:
|
||||||
filled_val = order.get('filled', 0.0) or 0.0
|
filled_val = order.get('filled', 0.0) or 0.0
|
||||||
filled_stake = filled_val * trade.open_rate
|
filled_stake = filled_val * trade.open_rate
|
||||||
minstake = self.exchange.get_min_pair_stake_amount(
|
minstake = self.exchange.get_min_pair_stake_amount(
|
||||||
@ -962,7 +963,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
# Avoid race condition where the order could not be cancelled coz its already filled.
|
# Avoid race condition where the order could not be cancelled coz its already filled.
|
||||||
# Simply bailing here is the only safe way - as this order will then be
|
# Simply bailing here is the only safe way - as this order will then be
|
||||||
# handled in the next iteration.
|
# handled in the next iteration.
|
||||||
if corder.get('status') not in ('cancelled', 'canceled', 'closed'):
|
if corder.get('status') not in constants.NON_OPEN_EXCHANGE_STATES:
|
||||||
logger.warning(f"Order {trade.open_order_id} for {trade.pair} not cancelled.")
|
logger.warning(f"Order {trade.open_order_id} for {trade.pair} not cancelled.")
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
@ -1065,9 +1066,9 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
raise DependencyException(
|
raise DependencyException(
|
||||||
f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}")
|
f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}")
|
||||||
|
|
||||||
def execute_sell(self, trade: Trade, limit: float, sell_reason: SellCheckTuple) -> bool:
|
def execute_trade_exit(self, trade: Trade, limit: float, sell_reason: SellCheckTuple) -> bool:
|
||||||
"""
|
"""
|
||||||
Executes a limit sell for the given trade and limit
|
Executes a trade exit for the given trade and limit
|
||||||
:param trade: Trade instance
|
:param trade: Trade instance
|
||||||
:param limit: limit rate for the sell order
|
:param limit: limit rate for the sell order
|
||||||
:param sell_reason: Reason the sell was triggered
|
:param sell_reason: Reason the sell was triggered
|
||||||
@ -1143,7 +1144,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
trade.close_rate_requested = limit
|
trade.close_rate_requested = limit
|
||||||
trade.sell_reason = sell_reason.sell_reason
|
trade.sell_reason = sell_reason.sell_reason
|
||||||
# In case of market sell orders the order can be closed immediately
|
# In case of market sell orders the order can be closed immediately
|
||||||
if order.get('status', 'unknown') == 'closed':
|
if order.get('status', 'unknown') in ('closed', 'expired'):
|
||||||
self.update_trade_state(trade, trade.open_order_id, order)
|
self.update_trade_state(trade, trade.open_order_id, order)
|
||||||
Trade.commit()
|
Trade.commit()
|
||||||
|
|
||||||
|
@ -89,6 +89,17 @@ class Backtesting:
|
|||||||
"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)
|
||||||
|
# Load detail timeframe if specified
|
||||||
|
self.timeframe_detail = str(self.config.get('timeframe_detail', ''))
|
||||||
|
if self.timeframe_detail:
|
||||||
|
self.timeframe_detail_min = timeframe_to_minutes(self.timeframe_detail)
|
||||||
|
if self.timeframe_min <= self.timeframe_detail_min:
|
||||||
|
raise OperationalException(
|
||||||
|
"Detail timeframe must be smaller than strategy timeframe.")
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.timeframe_detail_min = 0
|
||||||
|
self.detail_data: Dict[str, DataFrame] = {}
|
||||||
|
|
||||||
self.pairlists = PairListManager(self.exchange, self.config)
|
self.pairlists = PairListManager(self.exchange, self.config)
|
||||||
if 'VolumePairList' in self.pairlists.name_list:
|
if 'VolumePairList' in self.pairlists.name_list:
|
||||||
@ -191,6 +202,23 @@ class Backtesting:
|
|||||||
self.progress.set_new_value(1)
|
self.progress.set_new_value(1)
|
||||||
return data, self.timerange
|
return data, self.timerange
|
||||||
|
|
||||||
|
def load_bt_data_detail(self) -> None:
|
||||||
|
"""
|
||||||
|
Loads backtest detail data (smaller timeframe) if necessary.
|
||||||
|
"""
|
||||||
|
if self.timeframe_detail:
|
||||||
|
self.detail_data = history.load_data(
|
||||||
|
datadir=self.config['datadir'],
|
||||||
|
pairs=self.pairlists.whitelist,
|
||||||
|
timeframe=self.timeframe_detail,
|
||||||
|
timerange=self.timerange,
|
||||||
|
startup_candles=0,
|
||||||
|
fail_without_data=True,
|
||||||
|
data_format=self.config.get('dataformat_ohlcv', 'json'),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.detail_data = {}
|
||||||
|
|
||||||
def prepare_backtest(self, enable_protections):
|
def prepare_backtest(self, enable_protections):
|
||||||
"""
|
"""
|
||||||
Backtesting setup method - called once for every call to "backtest()".
|
Backtesting setup method - called once for every call to "backtest()".
|
||||||
@ -334,7 +362,8 @@ class Backtesting:
|
|||||||
else:
|
else:
|
||||||
return sell_row[OPEN_IDX]
|
return sell_row[OPEN_IDX]
|
||||||
|
|
||||||
def _get_sell_trade_entry(self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]:
|
def _get_sell_trade_entry_for_candle(self, trade: LocalTrade,
|
||||||
|
sell_row: Tuple) -> Optional[LocalTrade]:
|
||||||
sell_candle_time = sell_row[DATE_IDX].to_pydatetime()
|
sell_candle_time = sell_row[DATE_IDX].to_pydatetime()
|
||||||
enter = sell_row[LONG_IDX] if trade.is_short else sell_row[SHORT_IDX]
|
enter = sell_row[LONG_IDX] if trade.is_short else sell_row[SHORT_IDX]
|
||||||
exit_ = sell_row[ELONG_IDX] if trade.is_short else sell_row[ESHORT_IDX]
|
exit_ = sell_row[ELONG_IDX] if trade.is_short else sell_row[ESHORT_IDX]
|
||||||
@ -365,6 +394,35 @@ class Backtesting:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _get_sell_trade_entry(self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]:
|
||||||
|
if self.timeframe_detail and trade.pair in self.detail_data:
|
||||||
|
sell_candle_time = sell_row[DATE_IDX].to_pydatetime()
|
||||||
|
sell_candle_end = sell_candle_time + timedelta(minutes=self.timeframe_min)
|
||||||
|
|
||||||
|
detail_data = self.detail_data[trade.pair]
|
||||||
|
detail_data = detail_data.loc[
|
||||||
|
(detail_data['date'] >= sell_candle_time) &
|
||||||
|
(detail_data['date'] < sell_candle_end)
|
||||||
|
]
|
||||||
|
if len(detail_data) == 0:
|
||||||
|
# Fall back to "regular" data if no detail data was found for this candle
|
||||||
|
return self._get_sell_trade_entry_for_candle(trade, sell_row)
|
||||||
|
detail_data['enter_long'] = sell_row[LONG_IDX]
|
||||||
|
detail_data['exit_long'] = sell_row[ELONG_IDX]
|
||||||
|
detail_data['enter_short'] = sell_row[SHORT_IDX]
|
||||||
|
detail_data['exit_short'] = sell_row[ESHORT_IDX]
|
||||||
|
headers = ['date', 'open', 'high', 'low', 'close', 'enter_long', 'exit_long',
|
||||||
|
'enter_short', 'exit_short']
|
||||||
|
for det_row in detail_data[headers].values.tolist():
|
||||||
|
res = self._get_sell_trade_entry_for_candle(trade, det_row)
|
||||||
|
if res:
|
||||||
|
return res
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
else:
|
||||||
|
return self._get_sell_trade_entry_for_candle(trade, sell_row)
|
||||||
|
|
||||||
def _enter_trade(self, pair: str, row: List, direction: str) -> Optional[LocalTrade]:
|
def _enter_trade(self, pair: str, row: List, direction: str) -> Optional[LocalTrade]:
|
||||||
try:
|
try:
|
||||||
stake_amount = self.wallets.get_trade_stake_amount(pair, None)
|
stake_amount = self.wallets.get_trade_stake_amount(pair, None)
|
||||||
@ -626,6 +684,7 @@ class Backtesting:
|
|||||||
data: Dict[str, Any] = {}
|
data: Dict[str, Any] = {}
|
||||||
|
|
||||||
data, timerange = self.load_bt_data()
|
data, timerange = self.load_bt_data()
|
||||||
|
self.load_bt_data_detail()
|
||||||
logger.info("Dataload complete. Calculating indicators")
|
logger.info("Dataload complete. Calculating indicators")
|
||||||
|
|
||||||
for strat in self.strategylist:
|
for strat in self.strategylist:
|
||||||
|
@ -368,6 +368,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
|
|||||||
'max_open_trades_setting': (config['max_open_trades']
|
'max_open_trades_setting': (config['max_open_trades']
|
||||||
if config['max_open_trades'] != float('inf') else -1),
|
if config['max_open_trades'] != float('inf') else -1),
|
||||||
'timeframe': config['timeframe'],
|
'timeframe': config['timeframe'],
|
||||||
|
'timeframe_detail': config.get('timeframe_detail', ''),
|
||||||
'timerange': config.get('timerange', ''),
|
'timerange': config.get('timerange', ''),
|
||||||
'enable_protections': config.get('enable_protections', False),
|
'enable_protections': config.get('enable_protections', False),
|
||||||
'strategy_name': strategy,
|
'strategy_name': strategy,
|
||||||
|
@ -13,7 +13,7 @@ from sqlalchemy.orm import Query, declarative_base, relationship, scoped_session
|
|||||||
from sqlalchemy.pool import StaticPool
|
from sqlalchemy.pool import StaticPool
|
||||||
from sqlalchemy.sql.schema import UniqueConstraint
|
from sqlalchemy.sql.schema import UniqueConstraint
|
||||||
|
|
||||||
from freqtrade.constants import DATETIME_PRINT_FORMAT
|
from freqtrade.constants import DATETIME_PRINT_FORMAT, NON_OPEN_EXCHANGE_STATES
|
||||||
from freqtrade.enums import SellType
|
from freqtrade.enums import SellType
|
||||||
from freqtrade.exceptions import DependencyException, OperationalException
|
from freqtrade.exceptions import DependencyException, OperationalException
|
||||||
from freqtrade.leverage import interest
|
from freqtrade.leverage import interest
|
||||||
@ -164,7 +164,7 @@ class Order(_DECL_BASE):
|
|||||||
self.order_date = datetime.fromtimestamp(order['timestamp'] / 1000, tz=timezone.utc)
|
self.order_date = datetime.fromtimestamp(order['timestamp'] / 1000, tz=timezone.utc)
|
||||||
|
|
||||||
self.ft_is_open = True
|
self.ft_is_open = True
|
||||||
if self.status in ('closed', 'canceled', 'cancelled'):
|
if self.status in NON_OPEN_EXCHANGE_STATES:
|
||||||
self.ft_is_open = False
|
self.ft_is_open = False
|
||||||
if (order.get('filled', 0.0) or 0.0) > 0:
|
if (order.get('filled', 0.0) or 0.0) > 0:
|
||||||
self.order_filled_date = datetime.now(timezone.utc)
|
self.order_filled_date = datetime.now(timezone.utc)
|
||||||
|
@ -46,11 +46,14 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac
|
|||||||
if (
|
if (
|
||||||
not ApiServer._bt
|
not ApiServer._bt
|
||||||
or lastconfig.get('timeframe') != strat.timeframe
|
or lastconfig.get('timeframe') != strat.timeframe
|
||||||
|
or lastconfig.get('timeframe_detail') != btconfig.get('timeframe_detail')
|
||||||
or lastconfig.get('dry_run_wallet') != btconfig.get('dry_run_wallet', 0)
|
or lastconfig.get('dry_run_wallet') != btconfig.get('dry_run_wallet', 0)
|
||||||
or lastconfig.get('timerange') != btconfig['timerange']
|
or lastconfig.get('timerange') != btconfig['timerange']
|
||||||
):
|
):
|
||||||
from freqtrade.optimize.backtesting import Backtesting
|
from freqtrade.optimize.backtesting import Backtesting
|
||||||
ApiServer._bt = Backtesting(btconfig)
|
ApiServer._bt = Backtesting(btconfig)
|
||||||
|
if ApiServer._bt.timeframe_detail:
|
||||||
|
ApiServer._bt.load_bt_data_detail()
|
||||||
|
|
||||||
# Only reload data if timeframe changed.
|
# Only reload data if timeframe changed.
|
||||||
if (
|
if (
|
||||||
|
@ -324,6 +324,7 @@ class PairHistory(BaseModel):
|
|||||||
class BacktestRequest(BaseModel):
|
class BacktestRequest(BaseModel):
|
||||||
strategy: str
|
strategy: str
|
||||||
timeframe: Optional[str]
|
timeframe: Optional[str]
|
||||||
|
timeframe_detail: Optional[str]
|
||||||
timerange: Optional[str]
|
timerange: Optional[str]
|
||||||
max_open_trades: Optional[int]
|
max_open_trades: Optional[int]
|
||||||
stake_amount: Optional[Union[float, str]]
|
stake_amount: Optional[Union[float, str]]
|
||||||
|
@ -557,7 +557,7 @@ class RPC:
|
|||||||
current_rate = self._freqtrade.exchange.get_rate(
|
current_rate = self._freqtrade.exchange.get_rate(
|
||||||
trade.pair, refresh=False, side="sell")
|
trade.pair, refresh=False, side="sell")
|
||||||
sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL)
|
sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL)
|
||||||
self._freqtrade.execute_sell(trade, current_rate, sell_reason)
|
self._freqtrade.execute_trade_exit(trade, current_rate, sell_reason)
|
||||||
# ---- EOF def _exec_forcesell ----
|
# ---- EOF def _exec_forcesell ----
|
||||||
|
|
||||||
if self._freqtrade.state != State.RUNNING:
|
if self._freqtrade.state != State.RUNNING:
|
||||||
@ -613,7 +613,7 @@ class RPC:
|
|||||||
stakeamount = self._freqtrade.wallets.get_trade_stake_amount(pair)
|
stakeamount = self._freqtrade.wallets.get_trade_stake_amount(pair)
|
||||||
|
|
||||||
# execute buy
|
# execute buy
|
||||||
if self._freqtrade.execute_buy(pair, stakeamount, price, forcebuy=True):
|
if self._freqtrade.execute_entry(pair, stakeamount, price, forcebuy=True):
|
||||||
Trade.commit()
|
Trade.commit()
|
||||||
trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first()
|
trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first()
|
||||||
return trade
|
return trade
|
||||||
|
@ -120,6 +120,8 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
# and wallets - access to the current balance.
|
# and wallets - access to the current balance.
|
||||||
dp: Optional[DataProvider] = None
|
dp: Optional[DataProvider] = None
|
||||||
wallets: Optional[Wallets] = None
|
wallets: Optional[Wallets] = None
|
||||||
|
# Filled from configuration
|
||||||
|
stake_currency: str
|
||||||
# container variable for strategy source code
|
# container variable for strategy source code
|
||||||
__source__: str = ''
|
__source__: str = ''
|
||||||
|
|
||||||
|
@ -36,6 +36,6 @@
|
|||||||
"BNB/TUSD",
|
"BNB/TUSD",
|
||||||
"BNB/USDC",
|
"BNB/USDC",
|
||||||
"BNB/USDS",
|
"BNB/USDS",
|
||||||
"BNB/USDT",
|
"BNB/USDT"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Include all requirements to run the bot.
|
# Include all requirements to run the bot.
|
||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
|
|
||||||
plotly==5.2.1
|
plotly==5.3.0
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
numpy==1.21.2
|
numpy==1.21.2
|
||||||
pandas==1.3.2
|
pandas==1.3.2
|
||||||
|
|
||||||
ccxt==1.55.28
|
ccxt==1.55.56
|
||||||
# Pin cryptography for now due to rust build errors with piwheels
|
# Pin cryptography for now due to rust build errors with piwheels
|
||||||
cryptography==3.4.7
|
cryptography==3.4.8
|
||||||
aiohttp==3.7.4.post0
|
aiohttp==3.7.4.post0
|
||||||
SQLAlchemy==1.4.23
|
SQLAlchemy==1.4.23
|
||||||
python-telegram-bot==13.7
|
python-telegram-bot==13.7
|
||||||
@ -31,7 +31,7 @@ python-rapidjson==1.4
|
|||||||
sdnotify==0.3.2
|
sdnotify==0.3.2
|
||||||
|
|
||||||
# API Server
|
# API Server
|
||||||
fastapi==0.68.0
|
fastapi==0.68.1
|
||||||
uvicorn==0.15.0
|
uvicorn==0.15.0
|
||||||
pyjwt==2.1.0
|
pyjwt==2.1.0
|
||||||
aiofiles==0.7.0
|
aiofiles==0.7.0
|
||||||
|
2
setup.sh
2
setup.sh
@ -119,6 +119,7 @@ function install_mac_newer_python_dependencies() {
|
|||||||
echo "-------------------------"
|
echo "-------------------------"
|
||||||
brew install hdf5
|
brew install hdf5
|
||||||
fi
|
fi
|
||||||
|
export HDF5_DIR=$(brew --prefix)
|
||||||
|
|
||||||
if [ ! $(brew --prefix --installed c-blosc 2>/dev/null) ]
|
if [ ! $(brew --prefix --installed c-blosc 2>/dev/null) ]
|
||||||
then
|
then
|
||||||
@ -127,6 +128,7 @@ function install_mac_newer_python_dependencies() {
|
|||||||
echo "-------------------------"
|
echo "-------------------------"
|
||||||
brew install c-blosc
|
brew install c-blosc
|
||||||
fi
|
fi
|
||||||
|
export CBLOSC_DIR=$(brew --prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
# Install bot MacOS
|
# Install bot MacOS
|
||||||
|
@ -510,17 +510,6 @@ def test_start_new_strategy(mocker, caplog):
|
|||||||
start_new_strategy(get_args(args))
|
start_new_strategy(get_args(args))
|
||||||
|
|
||||||
|
|
||||||
def test_start_new_strategy_DefaultStrat(mocker, caplog):
|
|
||||||
args = [
|
|
||||||
"new-strategy",
|
|
||||||
"--strategy",
|
|
||||||
"DefaultStrategy"
|
|
||||||
]
|
|
||||||
with pytest.raises(OperationalException,
|
|
||||||
match=r"DefaultStrategy is not allowed as name\."):
|
|
||||||
start_new_strategy(get_args(args))
|
|
||||||
|
|
||||||
|
|
||||||
def test_start_new_strategy_no_arg(mocker, caplog):
|
def test_start_new_strategy_no_arg(mocker, caplog):
|
||||||
args = [
|
args = [
|
||||||
"new-strategy",
|
"new-strategy",
|
||||||
@ -552,17 +541,6 @@ def test_start_new_hyperopt(mocker, caplog):
|
|||||||
start_new_hyperopt(get_args(args))
|
start_new_hyperopt(get_args(args))
|
||||||
|
|
||||||
|
|
||||||
def test_start_new_hyperopt_DefaultHyperopt(mocker, caplog):
|
|
||||||
args = [
|
|
||||||
"new-hyperopt",
|
|
||||||
"--hyperopt",
|
|
||||||
"DefaultHyperopt"
|
|
||||||
]
|
|
||||||
with pytest.raises(OperationalException,
|
|
||||||
match=r"DefaultHyperopt is not allowed as name\."):
|
|
||||||
start_new_hyperopt(get_args(args))
|
|
||||||
|
|
||||||
|
|
||||||
def test_start_new_hyperopt_no_arg(mocker):
|
def test_start_new_hyperopt_no_arg(mocker):
|
||||||
args = [
|
args = [
|
||||||
"new-hyperopt",
|
"new-hyperopt",
|
||||||
@ -827,9 +805,9 @@ def test_start_list_strategies(mocker, caplog, capsys):
|
|||||||
# pargs['config'] = None
|
# pargs['config'] = None
|
||||||
start_list_strategies(pargs)
|
start_list_strategies(pargs)
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert "TestStrategyLegacy" in captured.out
|
assert "TestStrategyLegacyV1" in captured.out
|
||||||
assert "legacy_strategy.py" not in captured.out
|
assert "legacy_strategy_v1.py" not in captured.out
|
||||||
assert "DefaultStrategy" in captured.out
|
assert "StrategyTestV2" in captured.out
|
||||||
|
|
||||||
# Test regular output
|
# Test regular output
|
||||||
args = [
|
args = [
|
||||||
@ -842,9 +820,9 @@ def test_start_list_strategies(mocker, caplog, capsys):
|
|||||||
# pargs['config'] = None
|
# pargs['config'] = None
|
||||||
start_list_strategies(pargs)
|
start_list_strategies(pargs)
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert "TestStrategyLegacy" in captured.out
|
assert "TestStrategyLegacyV1" in captured.out
|
||||||
assert "legacy_strategy.py" in captured.out
|
assert "legacy_strategy_v1.py" in captured.out
|
||||||
assert "DefaultStrategy" in captured.out
|
assert "StrategyTestV2" in captured.out
|
||||||
|
|
||||||
|
|
||||||
def test_start_list_hyperopts(mocker, caplog, capsys):
|
def test_start_list_hyperopts(mocker, caplog, capsys):
|
||||||
@ -861,7 +839,7 @@ def test_start_list_hyperopts(mocker, caplog, capsys):
|
|||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert "TestHyperoptLegacy" not in captured.out
|
assert "TestHyperoptLegacy" not in captured.out
|
||||||
assert "legacy_hyperopt.py" not in captured.out
|
assert "legacy_hyperopt.py" not in captured.out
|
||||||
assert "DefaultHyperOpt" in captured.out
|
assert "HyperoptTestSepFile" in captured.out
|
||||||
assert "test_hyperopt.py" not in captured.out
|
assert "test_hyperopt.py" not in captured.out
|
||||||
|
|
||||||
# Test regular output
|
# Test regular output
|
||||||
@ -876,7 +854,7 @@ def test_start_list_hyperopts(mocker, caplog, capsys):
|
|||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert "TestHyperoptLegacy" not in captured.out
|
assert "TestHyperoptLegacy" not in captured.out
|
||||||
assert "legacy_hyperopt.py" not in captured.out
|
assert "legacy_hyperopt.py" not in captured.out
|
||||||
assert "DefaultHyperOpt" in captured.out
|
assert "HyperoptTestSepFile" in captured.out
|
||||||
|
|
||||||
|
|
||||||
def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
|
def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
|
||||||
|
@ -384,7 +384,7 @@ def get_default_conf(testdatadir):
|
|||||||
"user_data_dir": Path("user_data"),
|
"user_data_dir": Path("user_data"),
|
||||||
"verbosity": 3,
|
"verbosity": 3,
|
||||||
"strategy_path": str(Path(__file__).parent / "strategy" / "strats"),
|
"strategy_path": str(Path(__file__).parent / "strategy" / "strats"),
|
||||||
"strategy": "DefaultStrategy",
|
"strategy": "StrategyTestV2",
|
||||||
"disableparamexport": True,
|
"disableparamexport": True,
|
||||||
"internals": {},
|
"internals": {},
|
||||||
"export": "none",
|
"export": "none",
|
||||||
|
@ -33,7 +33,7 @@ def mock_trade_1(fee):
|
|||||||
open_rate=0.123,
|
open_rate=0.123,
|
||||||
exchange='binance',
|
exchange='binance',
|
||||||
open_order_id='dry_run_buy_12345',
|
open_order_id='dry_run_buy_12345',
|
||||||
strategy='DefaultStrategy',
|
strategy='StrategyTestV2',
|
||||||
timeframe=5,
|
timeframe=5,
|
||||||
)
|
)
|
||||||
o = Order.parse_from_ccxt_object(mock_order_1(), 'ETH/BTC', 'buy')
|
o = Order.parse_from_ccxt_object(mock_order_1(), 'ETH/BTC', 'buy')
|
||||||
@ -87,7 +87,7 @@ def mock_trade_2(fee):
|
|||||||
exchange='binance',
|
exchange='binance',
|
||||||
is_open=False,
|
is_open=False,
|
||||||
open_order_id='dry_run_sell_12345',
|
open_order_id='dry_run_sell_12345',
|
||||||
strategy='DefaultStrategy',
|
strategy='StrategyTestV2',
|
||||||
timeframe=5,
|
timeframe=5,
|
||||||
sell_reason='sell_signal',
|
sell_reason='sell_signal',
|
||||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
||||||
@ -146,7 +146,7 @@ def mock_trade_3(fee):
|
|||||||
close_profit_abs=0.000155,
|
close_profit_abs=0.000155,
|
||||||
exchange='binance',
|
exchange='binance',
|
||||||
is_open=False,
|
is_open=False,
|
||||||
strategy='DefaultStrategy',
|
strategy='StrategyTestV2',
|
||||||
timeframe=5,
|
timeframe=5,
|
||||||
sell_reason='roi',
|
sell_reason='roi',
|
||||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
||||||
@ -189,7 +189,7 @@ def mock_trade_4(fee):
|
|||||||
open_rate=0.123,
|
open_rate=0.123,
|
||||||
exchange='binance',
|
exchange='binance',
|
||||||
open_order_id='prod_buy_12345',
|
open_order_id='prod_buy_12345',
|
||||||
strategy='DefaultStrategy',
|
strategy='StrategyTestV2',
|
||||||
timeframe=5,
|
timeframe=5,
|
||||||
)
|
)
|
||||||
o = Order.parse_from_ccxt_object(mock_order_4(), 'ETC/BTC', 'buy')
|
o = Order.parse_from_ccxt_object(mock_order_4(), 'ETC/BTC', 'buy')
|
||||||
|
@ -93,7 +93,7 @@ def test_load_backtest_data_new_format(testdatadir):
|
|||||||
def test_load_backtest_data_multi(testdatadir):
|
def test_load_backtest_data_multi(testdatadir):
|
||||||
|
|
||||||
filename = testdatadir / "backtest-result_multistrat.json"
|
filename = testdatadir / "backtest-result_multistrat.json"
|
||||||
for strategy in ('DefaultStrategy', 'TestStrategy'):
|
for strategy in ('StrategyTestV2', 'TestStrategy'):
|
||||||
bt_data = load_backtest_data(filename, strategy=strategy)
|
bt_data = load_backtest_data(filename, strategy=strategy)
|
||||||
assert isinstance(bt_data, DataFrame)
|
assert isinstance(bt_data, DataFrame)
|
||||||
assert set(bt_data.columns) == set(BT_DATA_COLUMNS_MID)
|
assert set(bt_data.columns) == set(BT_DATA_COLUMNS_MID)
|
||||||
@ -128,7 +128,7 @@ def test_load_trades_from_db(default_conf, fee, mocker):
|
|||||||
for col in BT_DATA_COLUMNS:
|
for col in BT_DATA_COLUMNS:
|
||||||
if col not in ['index', 'open_at_end']:
|
if col not in ['index', 'open_at_end']:
|
||||||
assert col in trades.columns
|
assert col in trades.columns
|
||||||
trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='DefaultStrategy')
|
trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='StrategyTestV2')
|
||||||
assert len(trades) == 4
|
assert len(trades) == 4
|
||||||
trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='NoneStrategy')
|
trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='NoneStrategy')
|
||||||
assert len(trades) == 0
|
assert len(trades) == 0
|
||||||
@ -186,7 +186,7 @@ def test_load_trades(default_conf, mocker):
|
|||||||
db_url=default_conf.get('db_url'),
|
db_url=default_conf.get('db_url'),
|
||||||
exportfilename=default_conf.get('exportfilename'),
|
exportfilename=default_conf.get('exportfilename'),
|
||||||
no_trades=False,
|
no_trades=False,
|
||||||
strategy="DefaultStrategy",
|
strategy="StrategyTestV2",
|
||||||
)
|
)
|
||||||
|
|
||||||
assert db_mock.call_count == 1
|
assert db_mock.call_count == 1
|
||||||
|
@ -380,7 +380,7 @@ def test_file_dump_json_tofile(testdatadir) -> None:
|
|||||||
def test_get_timerange(default_conf, mocker, testdatadir) -> None:
|
def test_get_timerange(default_conf, mocker, testdatadir) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
data = strategy.advise_all_indicators(
|
data = strategy.advise_all_indicators(
|
||||||
@ -398,7 +398,7 @@ def test_get_timerange(default_conf, mocker, testdatadir) -> None:
|
|||||||
def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) -> None:
|
def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
data = strategy.advise_all_indicators(
|
data = strategy.advise_all_indicators(
|
||||||
@ -422,7 +422,7 @@ def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir)
|
|||||||
def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> None:
|
def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
|
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
timerange = TimeRange('index', 'index', 200, 250)
|
timerange = TimeRange('index', 'index', 200, 250)
|
||||||
|
@ -557,7 +557,7 @@ def test_reload_markets_exception(default_conf, mocker, caplog):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("stake_currency", ['ETH', 'BTC', 'USDT'])
|
@pytest.mark.parametrize("stake_currency", ['ETH', 'BTC', 'USDT'])
|
||||||
def test_validate_stake_currency(default_conf, stake_currency, mocker, caplog):
|
def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog):
|
||||||
default_conf['stake_currency'] = stake_currency
|
default_conf['stake_currency'] = stake_currency
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
type(api_mock).load_markets = MagicMock(return_value={
|
type(api_mock).load_markets = MagicMock(return_value={
|
||||||
@ -571,7 +571,7 @@ def test_validate_stake_currency(default_conf, stake_currency, mocker, caplog):
|
|||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_validate_stake_currency_error(default_conf, mocker, caplog):
|
def test_validate_stakecurrency_error(default_conf, mocker, caplog):
|
||||||
default_conf['stake_currency'] = 'XRP'
|
default_conf['stake_currency'] = 'XRP'
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
type(api_mock).load_markets = MagicMock(return_value={
|
type(api_mock).load_markets = MagicMock(return_value={
|
||||||
@ -587,6 +587,13 @@ def test_validate_stake_currency_error(default_conf, mocker, caplog):
|
|||||||
'Available currencies are: BTC, ETH, USDT'):
|
'Available currencies are: BTC, ETH, USDT'):
|
||||||
Exchange(default_conf)
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
type(api_mock).load_markets = MagicMock(side_effect=ccxt.NetworkError('No connection.'))
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException,
|
||||||
|
match=r'Could not load markets, therefore cannot start\. Please.*'):
|
||||||
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_get_quote_currencies(default_conf, mocker):
|
def test_get_quote_currencies(default_conf, mocker):
|
||||||
ex = get_patched_exchange(mocker, default_conf)
|
ex = get_patched_exchange(mocker, default_conf)
|
||||||
|
@ -16,7 +16,7 @@ def hyperopt_conf(default_conf):
|
|||||||
hyperconf.update({
|
hyperconf.update({
|
||||||
'datadir': Path(default_conf['datadir']),
|
'datadir': Path(default_conf['datadir']),
|
||||||
'runmode': RunMode.HYPEROPT,
|
'runmode': RunMode.HYPEROPT,
|
||||||
'hyperopt': 'DefaultHyperOpt',
|
'hyperopt': 'HyperoptTestSepFile',
|
||||||
'hyperopt_loss': 'ShortTradeDurHyperOptLoss',
|
'hyperopt_loss': 'ShortTradeDurHyperOptLoss',
|
||||||
'hyperopt_path': str(Path(__file__).parent / 'hyperopts'),
|
'hyperopt_path': str(Path(__file__).parent / 'hyperopts'),
|
||||||
'epochs': 1,
|
'epochs': 1,
|
||||||
|
@ -11,7 +11,7 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib
|
|||||||
from freqtrade.optimize.hyperopt_interface import IHyperOpt
|
from freqtrade.optimize.hyperopt_interface import IHyperOpt
|
||||||
|
|
||||||
|
|
||||||
class DefaultHyperOpt(IHyperOpt):
|
class HyperoptTestSepFile(IHyperOpt):
|
||||||
"""
|
"""
|
||||||
Default hyperopt provided by the Freqtrade bot.
|
Default hyperopt provided by the Freqtrade bot.
|
||||||
You can override it with your own Hyperopt
|
You can override it with your own Hyperopt
|
@ -1,7 +1,7 @@
|
|||||||
# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, unused-argument
|
# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, unused-argument
|
||||||
|
|
||||||
import random
|
import random
|
||||||
from datetime import timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from unittest.mock import MagicMock, PropertyMock
|
from unittest.mock import MagicMock, PropertyMock
|
||||||
|
|
||||||
@ -159,7 +159,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca
|
|||||||
args = [
|
args = [
|
||||||
'backtesting',
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
'--export', 'none'
|
'--export', 'none'
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -194,7 +194,7 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
|
|||||||
args = [
|
args = [
|
||||||
'backtesting',
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
'--datadir', '/foo/bar',
|
'--datadir', '/foo/bar',
|
||||||
'--timeframe', '1m',
|
'--timeframe', '1m',
|
||||||
'--enable-position-stacking',
|
'--enable-position-stacking',
|
||||||
@ -244,7 +244,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog)
|
|||||||
args = [
|
args = [
|
||||||
'backtesting',
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
'--stake-amount', '1',
|
'--stake-amount', '1',
|
||||||
'--starting-balance', '2'
|
'--starting-balance', '2'
|
||||||
]
|
]
|
||||||
@ -255,7 +255,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog)
|
|||||||
args = [
|
args = [
|
||||||
'backtesting',
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
'--stake-amount', '1',
|
'--stake-amount', '1',
|
||||||
'--starting-balance', '0.5'
|
'--starting-balance', '0.5'
|
||||||
]
|
]
|
||||||
@ -273,7 +273,7 @@ def test_start(mocker, fee, default_conf, caplog) -> None:
|
|||||||
args = [
|
args = [
|
||||||
'backtesting',
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
]
|
]
|
||||||
pargs = get_args(args)
|
pargs = get_args(args)
|
||||||
start_backtesting(pargs)
|
start_backtesting(pargs)
|
||||||
@ -306,7 +306,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None:
|
|||||||
def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None:
|
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'] = ['DefaultStrategy',
|
default_conf['strategy_list'] = ['StrategyTestV2',
|
||||||
'SampleStrategy']
|
'SampleStrategy']
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
|
||||||
@ -344,7 +344,7 @@ def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None:
|
|||||||
assert len(processed['UNITTEST/BTC']) == 102
|
assert len(processed['UNITTEST/BTC']) == 102
|
||||||
|
|
||||||
# Load strategy to compare the result between Backtesting function and strategy are the same
|
# Load strategy to compare the result between Backtesting function and strategy are the same
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
processed2 = strategy.advise_all_indicators(data)
|
processed2 = strategy.advise_all_indicators(data)
|
||||||
@ -445,6 +445,15 @@ def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) ->
|
|||||||
with pytest.raises(OperationalException, match='VolumePairList not allowed for backtesting.'):
|
with pytest.raises(OperationalException, match='VolumePairList not allowed for backtesting.'):
|
||||||
Backtesting(default_conf)
|
Backtesting(default_conf)
|
||||||
|
|
||||||
|
default_conf.update({
|
||||||
|
'pairlists': [{"method": "StaticPairList"}],
|
||||||
|
'timeframe_detail': '1d',
|
||||||
|
})
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException,
|
||||||
|
match='Detail timeframe must be smaller than strategy timeframe.'):
|
||||||
|
Backtesting(default_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, tickers) -> None:
|
def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, tickers) -> None:
|
||||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
||||||
@ -477,7 +486,7 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti
|
|||||||
Backtesting(default_conf)
|
Backtesting(default_conf)
|
||||||
|
|
||||||
# Multiple strategies
|
# Multiple strategies
|
||||||
default_conf['strategy_list'] = ['DefaultStrategy', 'TestStrategyLegacy']
|
default_conf['strategy_list'] = ['StrategyTestV2', 'TestStrategyLegacyV1']
|
||||||
with pytest.raises(OperationalException,
|
with pytest.raises(OperationalException,
|
||||||
match='PrecisionFilter not allowed for backtesting multiple strategies.'):
|
match='PrecisionFilter not allowed for backtesting multiple strategies.'):
|
||||||
Backtesting(default_conf)
|
Backtesting(default_conf)
|
||||||
@ -495,7 +504,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
|
|||||||
pair = 'UNITTEST/BTC'
|
pair = 'UNITTEST/BTC'
|
||||||
row = [
|
row = [
|
||||||
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0),
|
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0),
|
||||||
1, # Sell
|
1, # Buy
|
||||||
0.001, # Open
|
0.001, # Open
|
||||||
0.0011, # Close
|
0.0011, # Close
|
||||||
0, # Sell
|
0, # Sell
|
||||||
@ -549,6 +558,88 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
|
|||||||
backtesting.cleanup()
|
backtesting.cleanup()
|
||||||
|
|
||||||
|
|
||||||
|
def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
|
||||||
|
default_conf['use_sell_signal'] = False
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||||
|
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
|
||||||
|
patch_exchange(mocker)
|
||||||
|
default_conf['timeframe_detail'] = '1m'
|
||||||
|
default_conf['max_open_trades'] = 2
|
||||||
|
backtesting = Backtesting(default_conf)
|
||||||
|
backtesting._set_strategy(backtesting.strategylist[0])
|
||||||
|
pair = 'UNITTEST/BTC'
|
||||||
|
row = [
|
||||||
|
pd.Timestamp(year=2020, month=1, day=1, hour=4, minute=55, tzinfo=timezone.utc),
|
||||||
|
1, # Buy
|
||||||
|
200, # Open
|
||||||
|
201, # Close
|
||||||
|
0, # Sell
|
||||||
|
195, # Low
|
||||||
|
201.5, # High
|
||||||
|
'', # Buy Signal Name
|
||||||
|
]
|
||||||
|
|
||||||
|
trade = backtesting._enter_trade(pair, row=row)
|
||||||
|
assert isinstance(trade, LocalTrade)
|
||||||
|
|
||||||
|
row_sell = [
|
||||||
|
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0, tzinfo=timezone.utc),
|
||||||
|
0, # Buy
|
||||||
|
200, # Open
|
||||||
|
201, # Close
|
||||||
|
0, # Sell
|
||||||
|
195, # Low
|
||||||
|
210.5, # High
|
||||||
|
'', # Buy Signal Name
|
||||||
|
]
|
||||||
|
row_detail = pd.DataFrame(
|
||||||
|
[
|
||||||
|
[
|
||||||
|
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0, tzinfo=timezone.utc),
|
||||||
|
1, 200, 199, 0, 197, 200.1, '',
|
||||||
|
], [
|
||||||
|
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=1, tzinfo=timezone.utc),
|
||||||
|
0, 199, 199.5, 0, 199, 199.7, '',
|
||||||
|
], [
|
||||||
|
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=2, tzinfo=timezone.utc),
|
||||||
|
0, 199.5, 200.5, 0, 199, 200.8, '',
|
||||||
|
], [
|
||||||
|
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=3, tzinfo=timezone.utc),
|
||||||
|
0, 200.5, 210.5, 0, 193, 210.5, '', # ROI sell (?)
|
||||||
|
], [
|
||||||
|
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=4, tzinfo=timezone.utc),
|
||||||
|
0, 200, 199, 0, 193, 200.1, '',
|
||||||
|
],
|
||||||
|
], columns=["date", "buy", "open", "close", "sell", "low", "high", "buy_tag"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# No data available.
|
||||||
|
res = backtesting._get_sell_trade_entry(trade, row_sell)
|
||||||
|
assert res is not None
|
||||||
|
assert res.sell_reason == SellType.ROI.value
|
||||||
|
assert res.close_date_utc == datetime(2020, 1, 1, 5, 0, tzinfo=timezone.utc)
|
||||||
|
|
||||||
|
# Enter new trade
|
||||||
|
trade = backtesting._enter_trade(pair, row=row)
|
||||||
|
assert isinstance(trade, LocalTrade)
|
||||||
|
# Assign empty ... no result.
|
||||||
|
backtesting.detail_data[pair] = pd.DataFrame(
|
||||||
|
[], columns=["date", "buy", "open", "close", "sell", "low", "high", "buy_tag"])
|
||||||
|
|
||||||
|
res = backtesting._get_sell_trade_entry(trade, row)
|
||||||
|
assert res is None
|
||||||
|
|
||||||
|
# Assign backtest-detail data
|
||||||
|
backtesting.detail_data[pair] = row_detail
|
||||||
|
|
||||||
|
res = backtesting._get_sell_trade_entry(trade, row_sell)
|
||||||
|
assert res is not None
|
||||||
|
assert res.sell_reason == SellType.ROI.value
|
||||||
|
# Sell at minute 3 (not available above!)
|
||||||
|
assert res.close_date_utc == datetime(2020, 1, 1, 5, 3, tzinfo=timezone.utc)
|
||||||
|
assert round(res.close_rate, 3) == round(209.0225, 3)
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
||||||
default_conf['use_sell_signal'] = False
|
default_conf['use_sell_signal'] = False
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||||
@ -704,7 +795,7 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir,
|
|||||||
|
|
||||||
|
|
||||||
def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
|
def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
|
||||||
# Override the default buy trend function in our default_strategy
|
# Override the default buy trend function in our StrategyTestV2
|
||||||
def fun(dataframe=None, pair=None):
|
def fun(dataframe=None, pair=None):
|
||||||
buy_value = 1
|
buy_value = 1
|
||||||
sell_value = 1
|
sell_value = 1
|
||||||
@ -720,7 +811,7 @@ def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
|
|||||||
|
|
||||||
|
|
||||||
def test_backtest_only_sell(mocker, default_conf, testdatadir):
|
def test_backtest_only_sell(mocker, default_conf, testdatadir):
|
||||||
# Override the default buy trend function in our default_strategy
|
# Override the default buy trend function in our StrategyTestV2
|
||||||
def fun(dataframe=None, pair=None):
|
def fun(dataframe=None, pair=None):
|
||||||
buy_value = 0
|
buy_value = 0
|
||||||
sell_value = 1
|
sell_value = 1
|
||||||
@ -849,7 +940,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
|||||||
args = [
|
args = [
|
||||||
'backtesting',
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
'--datadir', str(testdatadir),
|
'--datadir', str(testdatadir),
|
||||||
'--timeframe', '1m',
|
'--timeframe', '1m',
|
||||||
'--timerange', '1510694220-1510700340',
|
'--timerange', '1510694220-1510700340',
|
||||||
@ -920,8 +1011,8 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
|||||||
'--enable-position-stacking',
|
'--enable-position-stacking',
|
||||||
'--disable-max-market-positions',
|
'--disable-max-market-positions',
|
||||||
'--strategy-list',
|
'--strategy-list',
|
||||||
'DefaultStrategy',
|
'StrategyTestV2',
|
||||||
'TestStrategyLegacy',
|
'TestStrategyLegacyV1',
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
args = get_args(args)
|
||||||
start_backtesting(args)
|
start_backtesting(args)
|
||||||
@ -943,8 +1034,8 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
|||||||
'Backtesting with data from 2017-11-14 21:17:00 '
|
'Backtesting with data from 2017-11-14 21:17:00 '
|
||||||
'up to 2017-11-14 22:58:00 (0 days).',
|
'up to 2017-11-14 22:58:00 (0 days).',
|
||||||
'Parameter --enable-position-stacking detected ...',
|
'Parameter --enable-position-stacking detected ...',
|
||||||
'Running backtesting for Strategy DefaultStrategy',
|
'Running backtesting for Strategy StrategyTestV2',
|
||||||
'Running backtesting for Strategy TestStrategyLegacy',
|
'Running backtesting for Strategy TestStrategyLegacyV1',
|
||||||
]
|
]
|
||||||
|
|
||||||
for line in exists:
|
for line in exists:
|
||||||
@ -1024,8 +1115,8 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
|
|||||||
'--enable-position-stacking',
|
'--enable-position-stacking',
|
||||||
'--disable-max-market-positions',
|
'--disable-max-market-positions',
|
||||||
'--strategy-list',
|
'--strategy-list',
|
||||||
'DefaultStrategy',
|
'StrategyTestV2',
|
||||||
'TestStrategyLegacy',
|
'TestStrategyLegacyV1',
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
args = get_args(args)
|
||||||
start_backtesting(args)
|
start_backtesting(args)
|
||||||
@ -1041,8 +1132,8 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
|
|||||||
'Backtesting with data from 2017-11-14 21:17:00 '
|
'Backtesting with data from 2017-11-14 21:17:00 '
|
||||||
'up to 2017-11-14 22:58:00 (0 days).',
|
'up to 2017-11-14 22:58:00 (0 days).',
|
||||||
'Parameter --enable-position-stacking detected ...',
|
'Parameter --enable-position-stacking detected ...',
|
||||||
'Running backtesting for Strategy DefaultStrategy',
|
'Running backtesting for Strategy StrategyTestV2',
|
||||||
'Running backtesting for Strategy TestStrategyLegacy',
|
'Running backtesting for Strategy TestStrategyLegacyV1',
|
||||||
]
|
]
|
||||||
|
|
||||||
for line in exists:
|
for line in exists:
|
||||||
@ -1054,3 +1145,102 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
|
|||||||
assert 'LEFT OPEN TRADES REPORT' in captured.out
|
assert 'LEFT OPEN TRADES REPORT' in captured.out
|
||||||
assert '2017-11-14 21:17:00 -> 2017-11-14 22:58:00 | Max open trades : 1' in captured.out
|
assert '2017-11-14 21:17:00 -> 2017-11-14 22:58:00 | Max open trades : 1' in captured.out
|
||||||
assert 'STRATEGY SUMMARY' in captured.out
|
assert 'STRATEGY SUMMARY' in captured.out
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.filterwarnings("ignore:deprecated")
|
||||||
|
def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
|
||||||
|
caplog, testdatadir, capsys):
|
||||||
|
# Tests detail-data loading
|
||||||
|
default_conf.update({
|
||||||
|
"use_sell_signal": True,
|
||||||
|
"sell_profit_only": False,
|
||||||
|
"sell_profit_offset": 0.0,
|
||||||
|
"ignore_roi_if_buy_signal": False,
|
||||||
|
})
|
||||||
|
patch_exchange(mocker)
|
||||||
|
result1 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'],
|
||||||
|
'profit_ratio': [0.0, 0.0],
|
||||||
|
'profit_abs': [0.0, 0.0],
|
||||||
|
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
|
||||||
|
'2018-01-30 03:30:00', ], utc=True
|
||||||
|
),
|
||||||
|
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
|
||||||
|
'2018-01-30 05:35:00', ], utc=True),
|
||||||
|
'trade_duration': [235, 40],
|
||||||
|
'is_open': [False, False],
|
||||||
|
'stake_amount': [0.01, 0.01],
|
||||||
|
'open_rate': [0.104445, 0.10302485],
|
||||||
|
'close_rate': [0.104969, 0.103541],
|
||||||
|
'sell_reason': [SellType.ROI, SellType.ROI]
|
||||||
|
})
|
||||||
|
result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
|
||||||
|
'profit_ratio': [0.03, 0.01, 0.1],
|
||||||
|
'profit_abs': [0.01, 0.02, 0.2],
|
||||||
|
'open_date': pd.to_datetime(['2018-01-29 18:40:00',
|
||||||
|
'2018-01-30 03:30:00',
|
||||||
|
'2018-01-30 05:30:00'], utc=True
|
||||||
|
),
|
||||||
|
'close_date': pd.to_datetime(['2018-01-29 20:45:00',
|
||||||
|
'2018-01-30 05:35:00',
|
||||||
|
'2018-01-30 08:30:00'], utc=True),
|
||||||
|
'trade_duration': [47, 40, 20],
|
||||||
|
'is_open': [False, False, False],
|
||||||
|
'stake_amount': [0.01, 0.01, 0.01],
|
||||||
|
'open_rate': [0.104445, 0.10302485, 0.122541],
|
||||||
|
'close_rate': [0.104969, 0.103541, 0.123541],
|
||||||
|
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
||||||
|
})
|
||||||
|
backtestmock = MagicMock(side_effect=[
|
||||||
|
{
|
||||||
|
'results': result1,
|
||||||
|
'config': default_conf,
|
||||||
|
'locks': [],
|
||||||
|
'rejected_signals': 20,
|
||||||
|
'final_balance': 1000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'results': result2,
|
||||||
|
'config': default_conf,
|
||||||
|
'locks': [],
|
||||||
|
'rejected_signals': 20,
|
||||||
|
'final_balance': 1000,
|
||||||
|
}
|
||||||
|
])
|
||||||
|
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
|
||||||
|
PropertyMock(return_value=['XRP/ETH']))
|
||||||
|
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
|
||||||
|
|
||||||
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
|
args = [
|
||||||
|
'backtesting',
|
||||||
|
'--config', 'config.json',
|
||||||
|
'--datadir', str(testdatadir),
|
||||||
|
'--strategy-path', str(Path(__file__).parents[1] / 'strategy/strats'),
|
||||||
|
'--timeframe', '5m',
|
||||||
|
'--timeframe-detail', '1m',
|
||||||
|
'--strategy-list',
|
||||||
|
'StrategyTestV2'
|
||||||
|
]
|
||||||
|
args = get_args(args)
|
||||||
|
start_backtesting(args)
|
||||||
|
|
||||||
|
# check the logs, that will contain the backtest result
|
||||||
|
exists = [
|
||||||
|
'Parameter -i/--timeframe detected ... Using timeframe: 5m ...',
|
||||||
|
'Parameter --timeframe-detail detected, using 1m for intra-candle backtesting ...',
|
||||||
|
f'Using data directory: {testdatadir} ...',
|
||||||
|
'Loading data from 2019-10-11 00:00:00 '
|
||||||
|
'up to 2019-10-13 11:10:00 (2 days).',
|
||||||
|
'Backtesting with data from 2019-10-11 01:40:00 '
|
||||||
|
'up to 2019-10-13 11:10:00 (2 days).',
|
||||||
|
'Running backtesting for Strategy StrategyTestV2',
|
||||||
|
]
|
||||||
|
|
||||||
|
for line in exists:
|
||||||
|
assert log_has(line, caplog)
|
||||||
|
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert 'BACKTESTING REPORT' in captured.out
|
||||||
|
assert 'SELL REASON STATS' in captured.out
|
||||||
|
assert 'LEFT OPEN TRADES REPORT' in captured.out
|
||||||
|
@ -16,7 +16,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca
|
|||||||
args = [
|
args = [
|
||||||
'edge',
|
'edge',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
]
|
]
|
||||||
|
|
||||||
config = setup_optimize_configuration(get_args(args), RunMode.EDGE)
|
config = setup_optimize_configuration(get_args(args), RunMode.EDGE)
|
||||||
@ -46,7 +46,7 @@ def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> N
|
|||||||
args = [
|
args = [
|
||||||
'edge',
|
'edge',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
'--datadir', '/foo/bar',
|
'--datadir', '/foo/bar',
|
||||||
'--timeframe', '1m',
|
'--timeframe', '1m',
|
||||||
'--timerange', ':100',
|
'--timerange', ':100',
|
||||||
@ -80,7 +80,7 @@ def test_start(mocker, fee, edge_conf, caplog) -> None:
|
|||||||
args = [
|
args = [
|
||||||
'edge',
|
'edge',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
]
|
]
|
||||||
pargs = get_args(args)
|
pargs = get_args(args)
|
||||||
start_edge(pargs)
|
start_edge(pargs)
|
||||||
|
@ -22,7 +22,7 @@ from freqtrade.strategy.hyper import IntParameter
|
|||||||
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
|
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
|
||||||
patched_configuration_load_config_file)
|
patched_configuration_load_config_file)
|
||||||
|
|
||||||
from .hyperopts.default_hyperopt import DefaultHyperOpt
|
from .hyperopts.hyperopt_test_sep_file import HyperoptTestSepFile
|
||||||
|
|
||||||
|
|
||||||
# TODO-lev: This file
|
# TODO-lev: This file
|
||||||
@ -34,7 +34,7 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca
|
|||||||
args = [
|
args = [
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--hyperopt', 'DefaultHyperOpt',
|
'--hyperopt', 'HyperoptTestSepFile',
|
||||||
]
|
]
|
||||||
|
|
||||||
config = setup_optimize_configuration(get_args(args), RunMode.HYPEROPT)
|
config = setup_optimize_configuration(get_args(args), RunMode.HYPEROPT)
|
||||||
@ -66,7 +66,7 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo
|
|||||||
args = [
|
args = [
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--hyperopt', 'DefaultHyperOpt',
|
'--hyperopt', 'HyperoptTestSepFile',
|
||||||
'--datadir', '/foo/bar',
|
'--datadir', '/foo/bar',
|
||||||
'--timeframe', '1m',
|
'--timeframe', '1m',
|
||||||
'--timerange', ':100',
|
'--timerange', ':100',
|
||||||
@ -118,7 +118,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None
|
|||||||
args = [
|
args = [
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--hyperopt', 'DefaultHyperOpt',
|
'--hyperopt', 'HyperoptTestSepFile',
|
||||||
'--stake-amount', '1',
|
'--stake-amount', '1',
|
||||||
'--starting-balance', '2'
|
'--starting-balance', '2'
|
||||||
]
|
]
|
||||||
@ -128,7 +128,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None
|
|||||||
args = [
|
args = [
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
'--stake-amount', '1',
|
'--stake-amount', '1',
|
||||||
'--starting-balance', '0.5'
|
'--starting-balance', '0.5'
|
||||||
]
|
]
|
||||||
@ -139,7 +139,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None
|
|||||||
def test_hyperoptresolver(mocker, default_conf, caplog) -> None:
|
def test_hyperoptresolver(mocker, default_conf, caplog) -> None:
|
||||||
patched_configuration_load_config_file(mocker, default_conf)
|
patched_configuration_load_config_file(mocker, default_conf)
|
||||||
|
|
||||||
hyperopt = DefaultHyperOpt
|
hyperopt = HyperoptTestSepFile
|
||||||
delattr(hyperopt, 'populate_indicators')
|
delattr(hyperopt, 'populate_indicators')
|
||||||
delattr(hyperopt, 'populate_buy_trend')
|
delattr(hyperopt, 'populate_buy_trend')
|
||||||
delattr(hyperopt, 'populate_sell_trend')
|
delattr(hyperopt, 'populate_sell_trend')
|
||||||
@ -147,7 +147,7 @@ def test_hyperoptresolver(mocker, default_conf, caplog) -> None:
|
|||||||
'freqtrade.resolvers.hyperopt_resolver.HyperOptResolver.load_object',
|
'freqtrade.resolvers.hyperopt_resolver.HyperOptResolver.load_object',
|
||||||
MagicMock(return_value=hyperopt(default_conf))
|
MagicMock(return_value=hyperopt(default_conf))
|
||||||
)
|
)
|
||||||
default_conf.update({'hyperopt': 'DefaultHyperOpt'})
|
default_conf.update({'hyperopt': 'HyperoptTestSepFile'})
|
||||||
x = HyperOptResolver.load_hyperopt(default_conf)
|
x = HyperOptResolver.load_hyperopt(default_conf)
|
||||||
assert not hasattr(x, 'populate_indicators')
|
assert not hasattr(x, 'populate_indicators')
|
||||||
assert not hasattr(x, 'populate_buy_trend')
|
assert not hasattr(x, 'populate_buy_trend')
|
||||||
@ -187,7 +187,7 @@ def test_start_not_installed(mocker, default_conf, import_fails) -> None:
|
|||||||
args = [
|
args = [
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--hyperopt', 'DefaultHyperOpt',
|
'--hyperopt', 'HyperoptTestSepFile',
|
||||||
'--hyperopt-path',
|
'--hyperopt-path',
|
||||||
str(Path(__file__).parent / "hyperopts"),
|
str(Path(__file__).parent / "hyperopts"),
|
||||||
'--epochs', '5',
|
'--epochs', '5',
|
||||||
@ -208,7 +208,7 @@ def test_start(mocker, hyperopt_conf, caplog) -> None:
|
|||||||
args = [
|
args = [
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--hyperopt', 'DefaultHyperOpt',
|
'--hyperopt', 'HyperoptTestSepFile',
|
||||||
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
|
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
|
||||||
'--epochs', '5'
|
'--epochs', '5'
|
||||||
]
|
]
|
||||||
@ -232,7 +232,7 @@ def test_start_no_data(mocker, hyperopt_conf) -> None:
|
|||||||
args = [
|
args = [
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--hyperopt', 'DefaultHyperOpt',
|
'--hyperopt', 'HyperoptTestSepFile',
|
||||||
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
|
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
|
||||||
'--epochs', '5'
|
'--epochs', '5'
|
||||||
]
|
]
|
||||||
@ -250,7 +250,7 @@ def test_start_filelock(mocker, hyperopt_conf, caplog) -> None:
|
|||||||
args = [
|
args = [
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--hyperopt', 'DefaultHyperOpt',
|
'--hyperopt', 'HyperoptTestSepFile',
|
||||||
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
|
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
|
||||||
'--epochs', '5'
|
'--epochs', '5'
|
||||||
]
|
]
|
||||||
|
@ -167,9 +167,9 @@ def test__pprint_dict():
|
|||||||
|
|
||||||
def test_get_strategy_filename(default_conf):
|
def test_get_strategy_filename(default_conf):
|
||||||
|
|
||||||
x = HyperoptTools.get_strategy_filename(default_conf, 'DefaultStrategy')
|
x = HyperoptTools.get_strategy_filename(default_conf, 'StrategyTestV2')
|
||||||
assert isinstance(x, Path)
|
assert isinstance(x, Path)
|
||||||
assert x == Path(__file__).parents[1] / 'strategy/strats/default_strategy.py'
|
assert x == Path(__file__).parents[1] / 'strategy/strats/strategy_test_v2.py'
|
||||||
|
|
||||||
x = HyperoptTools.get_strategy_filename(default_conf, 'NonExistingStrategy')
|
x = HyperoptTools.get_strategy_filename(default_conf, 'NonExistingStrategy')
|
||||||
assert x is None
|
assert x is None
|
||||||
@ -177,7 +177,7 @@ def test_get_strategy_filename(default_conf):
|
|||||||
|
|
||||||
def test_export_params(tmpdir):
|
def test_export_params(tmpdir):
|
||||||
|
|
||||||
filename = Path(tmpdir) / "DefaultStrategy.json"
|
filename = Path(tmpdir) / "StrategyTestV2.json"
|
||||||
assert not filename.is_file()
|
assert not filename.is_file()
|
||||||
params = {
|
params = {
|
||||||
"params_details": {
|
"params_details": {
|
||||||
@ -205,12 +205,12 @@ def test_export_params(tmpdir):
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
HyperoptTools.export_params(params, "DefaultStrategy", filename)
|
HyperoptTools.export_params(params, "StrategyTestV2", filename)
|
||||||
|
|
||||||
assert filename.is_file()
|
assert filename.is_file()
|
||||||
|
|
||||||
content = rapidjson.load(filename.open('r'))
|
content = rapidjson.load(filename.open('r'))
|
||||||
assert content['strategy_name'] == 'DefaultStrategy'
|
assert content['strategy_name'] == 'StrategyTestV2'
|
||||||
assert 'params' in content
|
assert 'params' in content
|
||||||
assert "buy" in content["params"]
|
assert "buy" in content["params"]
|
||||||
assert "sell" in content["params"]
|
assert "sell" in content["params"]
|
||||||
@ -223,7 +223,7 @@ def test_try_export_params(default_conf, tmpdir, caplog, mocker):
|
|||||||
default_conf['disableparamexport'] = False
|
default_conf['disableparamexport'] = False
|
||||||
export_mock = mocker.patch("freqtrade.optimize.hyperopt_tools.HyperoptTools.export_params")
|
export_mock = mocker.patch("freqtrade.optimize.hyperopt_tools.HyperoptTools.export_params")
|
||||||
|
|
||||||
filename = Path(tmpdir) / "DefaultStrategy.json"
|
filename = Path(tmpdir) / "StrategyTestV2.json"
|
||||||
assert not filename.is_file()
|
assert not filename.is_file()
|
||||||
params = {
|
params = {
|
||||||
"params_details": {
|
"params_details": {
|
||||||
@ -252,17 +252,17 @@ def test_try_export_params(default_conf, tmpdir, caplog, mocker):
|
|||||||
FTHYPT_FILEVERSION: 2,
|
FTHYPT_FILEVERSION: 2,
|
||||||
|
|
||||||
}
|
}
|
||||||
HyperoptTools.try_export_params(default_conf, "DefaultStrategy22", params)
|
HyperoptTools.try_export_params(default_conf, "StrategyTestV222", params)
|
||||||
|
|
||||||
assert log_has("Strategy not found, not exporting parameter file.", caplog)
|
assert log_has("Strategy not found, not exporting parameter file.", caplog)
|
||||||
assert export_mock.call_count == 0
|
assert export_mock.call_count == 0
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
|
|
||||||
HyperoptTools.try_export_params(default_conf, "DefaultStrategy", params)
|
HyperoptTools.try_export_params(default_conf, "StrategyTestV2", params)
|
||||||
|
|
||||||
assert export_mock.call_count == 1
|
assert export_mock.call_count == 1
|
||||||
assert export_mock.call_args_list[0][0][1] == 'DefaultStrategy'
|
assert export_mock.call_args_list[0][0][1] == 'StrategyTestV2'
|
||||||
assert export_mock.call_args_list[0][0][2].name == 'default_strategy.json'
|
assert export_mock.call_args_list[0][0][2].name == 'strategy_test_v2.json'
|
||||||
|
|
||||||
|
|
||||||
def test_params_print(capsys):
|
def test_params_print(capsys):
|
||||||
|
@ -4,7 +4,7 @@ from unittest.mock import MagicMock
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.optimize.default_hyperopt_loss import ShortTradeDurHyperOptLoss
|
from freqtrade.optimize.hyperopt_loss_short_trade_dur import ShortTradeDurHyperOptLoss
|
||||||
from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver
|
from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ def test_text_table_bt_results():
|
|||||||
|
|
||||||
|
|
||||||
def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
|
def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
StrategyResolver.load_strategy(default_conf)
|
StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
results = {'DefStrat': {
|
results = {'DefStrat': {
|
||||||
|
@ -879,7 +879,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
|
|||||||
'open_trade_value': 15.1668225,
|
'open_trade_value': 15.1668225,
|
||||||
'sell_reason': None,
|
'sell_reason': None,
|
||||||
'sell_order_status': None,
|
'sell_order_status': None,
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'buy_tag': None,
|
'buy_tag': None,
|
||||||
'timeframe': 5,
|
'timeframe': 5,
|
||||||
'exchange': 'binance',
|
'exchange': 'binance',
|
||||||
@ -984,7 +984,7 @@ def test_api_forcebuy(botclient, mocker, fee):
|
|||||||
close_rate=0.265441,
|
close_rate=0.265441,
|
||||||
id=22,
|
id=22,
|
||||||
timeframe=5,
|
timeframe=5,
|
||||||
strategy="DefaultStrategy"
|
strategy="StrategyTestV2"
|
||||||
))
|
))
|
||||||
mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock)
|
mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock)
|
||||||
|
|
||||||
@ -1034,7 +1034,7 @@ def test_api_forcebuy(botclient, mocker, fee):
|
|||||||
'open_trade_value': 0.24605460,
|
'open_trade_value': 0.24605460,
|
||||||
'sell_reason': None,
|
'sell_reason': None,
|
||||||
'sell_order_status': None,
|
'sell_order_status': None,
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'buy_tag': None,
|
'buy_tag': None,
|
||||||
'timeframe': 5,
|
'timeframe': 5,
|
||||||
'exchange': 'binance',
|
'exchange': 'binance',
|
||||||
@ -1101,7 +1101,7 @@ def test_api_pair_candles(botclient, ohlcv_history):
|
|||||||
f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}")
|
f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}")
|
||||||
assert_response(rc)
|
assert_response(rc)
|
||||||
assert 'strategy' in rc.json()
|
assert 'strategy' in rc.json()
|
||||||
assert rc.json()['strategy'] == 'DefaultStrategy'
|
assert rc.json()['strategy'] == 'StrategyTestV2'
|
||||||
assert 'columns' in rc.json()
|
assert 'columns' in rc.json()
|
||||||
assert 'data_start_ts' in rc.json()
|
assert 'data_start_ts' in rc.json()
|
||||||
assert 'data_start' in rc.json()
|
assert 'data_start' in rc.json()
|
||||||
@ -1139,19 +1139,19 @@ def test_api_pair_history(botclient, ohlcv_history):
|
|||||||
# No pair
|
# No pair
|
||||||
rc = client_get(client,
|
rc = client_get(client,
|
||||||
f"{BASE_URI}/pair_history?timeframe={timeframe}"
|
f"{BASE_URI}/pair_history?timeframe={timeframe}"
|
||||||
"&timerange=20180111-20180112&strategy=DefaultStrategy")
|
"&timerange=20180111-20180112&strategy=StrategyTestV2")
|
||||||
assert_response(rc, 422)
|
assert_response(rc, 422)
|
||||||
|
|
||||||
# No Timeframe
|
# No Timeframe
|
||||||
rc = client_get(client,
|
rc = client_get(client,
|
||||||
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC"
|
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC"
|
||||||
"&timerange=20180111-20180112&strategy=DefaultStrategy")
|
"&timerange=20180111-20180112&strategy=StrategyTestV2")
|
||||||
assert_response(rc, 422)
|
assert_response(rc, 422)
|
||||||
|
|
||||||
# No timerange
|
# No timerange
|
||||||
rc = client_get(client,
|
rc = client_get(client,
|
||||||
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
|
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
|
||||||
"&strategy=DefaultStrategy")
|
"&strategy=StrategyTestV2")
|
||||||
assert_response(rc, 422)
|
assert_response(rc, 422)
|
||||||
|
|
||||||
# No strategy
|
# No strategy
|
||||||
@ -1163,14 +1163,14 @@ def test_api_pair_history(botclient, ohlcv_history):
|
|||||||
# Working
|
# Working
|
||||||
rc = client_get(client,
|
rc = client_get(client,
|
||||||
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
|
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
|
||||||
"&timerange=20180111-20180112&strategy=DefaultStrategy")
|
"&timerange=20180111-20180112&strategy=StrategyTestV2")
|
||||||
assert_response(rc, 200)
|
assert_response(rc, 200)
|
||||||
assert rc.json()['length'] == 289
|
assert rc.json()['length'] == 289
|
||||||
assert len(rc.json()['data']) == rc.json()['length']
|
assert len(rc.json()['data']) == rc.json()['length']
|
||||||
assert 'columns' in rc.json()
|
assert 'columns' in rc.json()
|
||||||
assert 'data' in rc.json()
|
assert 'data' in rc.json()
|
||||||
assert rc.json()['pair'] == 'UNITTEST/BTC'
|
assert rc.json()['pair'] == 'UNITTEST/BTC'
|
||||||
assert rc.json()['strategy'] == 'DefaultStrategy'
|
assert rc.json()['strategy'] == 'StrategyTestV2'
|
||||||
assert rc.json()['data_start'] == '2018-01-11 00:00:00+00:00'
|
assert rc.json()['data_start'] == '2018-01-11 00:00:00+00:00'
|
||||||
assert rc.json()['data_start_ts'] == 1515628800000
|
assert rc.json()['data_start_ts'] == 1515628800000
|
||||||
assert rc.json()['data_stop'] == '2018-01-12 00:00:00+00:00'
|
assert rc.json()['data_stop'] == '2018-01-12 00:00:00+00:00'
|
||||||
@ -1179,7 +1179,7 @@ def test_api_pair_history(botclient, ohlcv_history):
|
|||||||
# No data found
|
# No data found
|
||||||
rc = client_get(client,
|
rc = client_get(client,
|
||||||
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
|
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
|
||||||
"&timerange=20200111-20200112&strategy=DefaultStrategy")
|
"&timerange=20200111-20200112&strategy=StrategyTestV2")
|
||||||
assert_response(rc, 502)
|
assert_response(rc, 502)
|
||||||
assert rc.json()['error'] == ("Error querying /api/v1/pair_history: "
|
assert rc.json()['error'] == ("Error querying /api/v1/pair_history: "
|
||||||
"No data for UNITTEST/BTC, 5m in 20200111-20200112 found.")
|
"No data for UNITTEST/BTC, 5m in 20200111-20200112 found.")
|
||||||
@ -1217,21 +1217,21 @@ def test_api_strategies(botclient):
|
|||||||
|
|
||||||
assert_response(rc)
|
assert_response(rc)
|
||||||
assert rc.json() == {'strategies': [
|
assert rc.json() == {'strategies': [
|
||||||
'DefaultStrategy',
|
|
||||||
'HyperoptableStrategy',
|
'HyperoptableStrategy',
|
||||||
'TestStrategyLegacy'
|
'StrategyTestV2',
|
||||||
|
'TestStrategyLegacyV1'
|
||||||
]}
|
]}
|
||||||
|
|
||||||
|
|
||||||
def test_api_strategy(botclient):
|
def test_api_strategy(botclient):
|
||||||
ftbot, client = botclient
|
ftbot, client = botclient
|
||||||
|
|
||||||
rc = client_get(client, f"{BASE_URI}/strategy/DefaultStrategy")
|
rc = client_get(client, f"{BASE_URI}/strategy/StrategyTestV2")
|
||||||
|
|
||||||
assert_response(rc)
|
assert_response(rc)
|
||||||
assert rc.json()['strategy'] == 'DefaultStrategy'
|
assert rc.json()['strategy'] == 'StrategyTestV2'
|
||||||
|
|
||||||
data = (Path(__file__).parents[1] / "strategy/strats/default_strategy.py").read_text()
|
data = (Path(__file__).parents[1] / "strategy/strats/strategy_test_v2.py").read_text()
|
||||||
assert rc.json()['code'] == data
|
assert rc.json()['code'] == data
|
||||||
|
|
||||||
rc = client_get(client, f"{BASE_URI}/strategy/NoStrat")
|
rc = client_get(client, f"{BASE_URI}/strategy/NoStrat")
|
||||||
@ -1288,7 +1288,7 @@ def test_api_backtesting(botclient, mocker, fee, caplog):
|
|||||||
|
|
||||||
# start backtesting
|
# start backtesting
|
||||||
data = {
|
data = {
|
||||||
"strategy": "DefaultStrategy",
|
"strategy": "StrategyTestV2",
|
||||||
"timeframe": "5m",
|
"timeframe": "5m",
|
||||||
"timerange": "20180110-20180111",
|
"timerange": "20180110-20180111",
|
||||||
"max_open_trades": 3,
|
"max_open_trades": 3,
|
||||||
|
@ -1236,7 +1236,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
|
|||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
|
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
|
||||||
assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
|
assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
|
||||||
assert '*Strategy:* `DefaultStrategy`' in msg_mock.call_args_list[0][0][0]
|
assert '*Strategy:* `StrategyTestV2`' in msg_mock.call_args_list[0][0][0]
|
||||||
assert '*Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
|
assert '*Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
@ -1245,7 +1245,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
|
|||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
|
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
|
||||||
assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
|
assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
|
||||||
assert '*Strategy:* `DefaultStrategy`' in msg_mock.call_args_list[0][0][0]
|
assert '*Strategy:* `StrategyTestV2`' in msg_mock.call_args_list[0][0][0]
|
||||||
assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
|
assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,5 +5,5 @@ import nonexiting_module # noqa
|
|||||||
from freqtrade.strategy.interface import IStrategy
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
|
||||||
|
|
||||||
class TestStrategyLegacy(IStrategy):
|
class TestStrategyLegacyV1(IStrategy):
|
||||||
pass
|
pass
|
||||||
|
@ -10,7 +10,7 @@ from freqtrade.strategy.interface import IStrategy
|
|||||||
# --------------------------------
|
# --------------------------------
|
||||||
|
|
||||||
# This class is a sample. Feel free to customize it.
|
# This class is a sample. Feel free to customize it.
|
||||||
class TestStrategyLegacy(IStrategy):
|
class TestStrategyLegacyV1(IStrategy):
|
||||||
"""
|
"""
|
||||||
This is a test strategy using the legacy function headers, which will be
|
This is a test strategy using the legacy function headers, which will be
|
||||||
removed in a future update.
|
removed in a future update.
|
@ -7,9 +7,9 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib
|
|||||||
from freqtrade.strategy.interface import IStrategy
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
|
||||||
|
|
||||||
class DefaultStrategy(IStrategy):
|
class StrategyTestV2(IStrategy):
|
||||||
"""
|
"""
|
||||||
Default Strategy provided by freqtrade bot.
|
Strategy used by tests freqtrade bot.
|
||||||
Please do not modify this strategy, it's intended for internal use only.
|
Please do not modify this strategy, it's intended for internal use only.
|
||||||
Please look at the SampleStrategy in the user_data/strategy directory
|
Please look at the SampleStrategy in the user_data/strategy directory
|
||||||
or strategy repository https://github.com/freqtrade/freqtrade-strategies
|
or strategy repository https://github.com/freqtrade/freqtrade-strategies
|
@ -4,20 +4,20 @@ from pandas import DataFrame
|
|||||||
|
|
||||||
from freqtrade.persistence.models import Trade
|
from freqtrade.persistence.models import Trade
|
||||||
|
|
||||||
from .strats.default_strategy import DefaultStrategy
|
from .strats.strategy_test_v2 import StrategyTestV2
|
||||||
|
|
||||||
|
|
||||||
def test_default_strategy_structure():
|
def test_strategy_test_v2_structure():
|
||||||
assert hasattr(DefaultStrategy, 'minimal_roi')
|
assert hasattr(StrategyTestV2, 'minimal_roi')
|
||||||
assert hasattr(DefaultStrategy, 'stoploss')
|
assert hasattr(StrategyTestV2, 'stoploss')
|
||||||
assert hasattr(DefaultStrategy, 'timeframe')
|
assert hasattr(StrategyTestV2, 'timeframe')
|
||||||
assert hasattr(DefaultStrategy, 'populate_indicators')
|
assert hasattr(StrategyTestV2, 'populate_indicators')
|
||||||
assert hasattr(DefaultStrategy, 'populate_buy_trend')
|
assert hasattr(StrategyTestV2, 'populate_buy_trend')
|
||||||
assert hasattr(DefaultStrategy, 'populate_sell_trend')
|
assert hasattr(StrategyTestV2, 'populate_sell_trend')
|
||||||
|
|
||||||
|
|
||||||
def test_default_strategy(result, fee):
|
def test_strategy_test_v2(result, fee):
|
||||||
strategy = DefaultStrategy({})
|
strategy = StrategyTestV2({})
|
||||||
|
|
||||||
metadata = {'pair': 'ETH/BTC'}
|
metadata = {'pair': 'ETH/BTC'}
|
||||||
assert type(strategy.minimal_roi) is dict
|
assert type(strategy.minimal_roi) is dict
|
||||||
|
@ -23,11 +23,11 @@ from freqtrade.strategy.interface import SellCheckTuple
|
|||||||
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
||||||
from tests.conftest import log_has, log_has_re
|
from tests.conftest import log_has, log_has_re
|
||||||
|
|
||||||
from .strats.default_strategy import DefaultStrategy
|
from .strats.strategy_test_v2 import StrategyTestV2
|
||||||
|
|
||||||
|
|
||||||
# Avoid to reinit the same object again and again
|
# Avoid to reinit the same object again and again
|
||||||
_STRATEGY = DefaultStrategy(config={})
|
_STRATEGY = StrategyTestV2(config={})
|
||||||
_STRATEGY.dp = DataProvider({}, None, None)
|
_STRATEGY.dp = DataProvider({}, None, None)
|
||||||
|
|
||||||
|
|
||||||
@ -149,7 +149,7 @@ def test_get_signal_no_sell_column(default_conf, mocker, caplog, ohlcv_history):
|
|||||||
|
|
||||||
|
|
||||||
def test_ignore_expired_candle(default_conf):
|
def test_ignore_expired_candle(default_conf):
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
strategy.ignore_buying_expired_candle_after = 60
|
strategy.ignore_buying_expired_candle_after = 60
|
||||||
|
|
||||||
@ -234,7 +234,7 @@ def test_assert_df(ohlcv_history, caplog):
|
|||||||
|
|
||||||
|
|
||||||
def test_advise_all_indicators(default_conf, testdatadir) -> None:
|
def test_advise_all_indicators(default_conf, testdatadir) -> None:
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
||||||
@ -245,7 +245,7 @@ def test_advise_all_indicators(default_conf, testdatadir) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_advise_all_indicators_copy(mocker, default_conf, testdatadir) -> None:
|
def test_advise_all_indicators_copy(mocker, default_conf, testdatadir) -> None:
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
aimock = mocker.patch('freqtrade.strategy.interface.IStrategy.advise_indicators')
|
aimock = mocker.patch('freqtrade.strategy.interface.IStrategy.advise_indicators')
|
||||||
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
||||||
@ -263,7 +263,7 @@ def test_min_roi_reached(default_conf, fee) -> None:
|
|||||||
min_roi_list = [{20: 0.05, 55: 0.01, 0: 0.1},
|
min_roi_list = [{20: 0.05, 55: 0.01, 0: 0.1},
|
||||||
{0: 0.1, 20: 0.05, 55: 0.01}]
|
{0: 0.1, 20: 0.05, 55: 0.01}]
|
||||||
for roi in min_roi_list:
|
for roi in min_roi_list:
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
strategy.minimal_roi = roi
|
strategy.minimal_roi = roi
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
@ -302,7 +302,7 @@ def test_min_roi_reached2(default_conf, fee) -> None:
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
for roi in min_roi_list:
|
for roi in min_roi_list:
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
strategy.minimal_roi = roi
|
strategy.minimal_roi = roi
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
@ -337,7 +337,7 @@ def test_min_roi_reached3(default_conf, fee) -> None:
|
|||||||
30: 0.05,
|
30: 0.05,
|
||||||
55: 0.30,
|
55: 0.30,
|
||||||
}
|
}
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
strategy.minimal_roi = min_roi
|
strategy.minimal_roi = min_roi
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
@ -390,7 +390,7 @@ def test_min_roi_reached3(default_conf, fee) -> None:
|
|||||||
def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, trailing, custom,
|
def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, trailing, custom,
|
||||||
profit2, adjusted2, expected2, custom_stop) -> None:
|
profit2, adjusted2, expected2, custom_stop) -> None:
|
||||||
|
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
|
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
@ -438,7 +438,7 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili
|
|||||||
|
|
||||||
def test_custom_sell(default_conf, fee, caplog) -> None:
|
def test_custom_sell(default_conf, fee, caplog) -> None:
|
||||||
|
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
|
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
@ -500,7 +500,7 @@ def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None:
|
|||||||
advise_sell=sell_mock,
|
advise_sell=sell_mock,
|
||||||
|
|
||||||
)
|
)
|
||||||
strategy = DefaultStrategy({})
|
strategy = StrategyTestV2({})
|
||||||
strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'})
|
strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'})
|
||||||
assert ind_mock.call_count == 1
|
assert ind_mock.call_count == 1
|
||||||
assert buy_mock.call_count == 1
|
assert buy_mock.call_count == 1
|
||||||
@ -531,7 +531,7 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) ->
|
|||||||
advise_sell=sell_mock,
|
advise_sell=sell_mock,
|
||||||
|
|
||||||
)
|
)
|
||||||
strategy = DefaultStrategy({})
|
strategy = StrategyTestV2({})
|
||||||
strategy.dp = DataProvider({}, None, None)
|
strategy.dp = DataProvider({}, None, None)
|
||||||
strategy.process_only_new_candles = True
|
strategy.process_only_new_candles = True
|
||||||
|
|
||||||
@ -563,7 +563,7 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) ->
|
|||||||
|
|
||||||
@pytest.mark.usefixtures("init_persistence")
|
@pytest.mark.usefixtures("init_persistence")
|
||||||
def test_is_pair_locked(default_conf):
|
def test_is_pair_locked(default_conf):
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
PairLocks.timeframe = default_conf['timeframe']
|
PairLocks.timeframe = default_conf['timeframe']
|
||||||
PairLocks.use_db = True
|
PairLocks.use_db = True
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
@ -616,7 +616,7 @@ def test_is_pair_locked(default_conf):
|
|||||||
|
|
||||||
|
|
||||||
def test_is_informative_pairs_callback(default_conf):
|
def test_is_informative_pairs_callback(default_conf):
|
||||||
default_conf.update({'strategy': 'TestStrategyLegacy'})
|
default_conf.update({'strategy': 'TestStrategyLegacyV1'})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
# Should return empty
|
# Should return empty
|
||||||
# Uses fallback to base implementation
|
# Uses fallback to base implementation
|
||||||
|
@ -18,7 +18,7 @@ def test_search_strategy():
|
|||||||
|
|
||||||
s, _ = StrategyResolver._search_object(
|
s, _ = StrategyResolver._search_object(
|
||||||
directory=default_location,
|
directory=default_location,
|
||||||
object_name='DefaultStrategy',
|
object_name='StrategyTestV2',
|
||||||
add_source=True,
|
add_source=True,
|
||||||
)
|
)
|
||||||
assert issubclass(s, IStrategy)
|
assert issubclass(s, IStrategy)
|
||||||
@ -74,10 +74,10 @@ def test_load_strategy_base64(result, caplog, default_conf):
|
|||||||
|
|
||||||
|
|
||||||
def test_load_strategy_invalid_directory(result, caplog, default_conf):
|
def test_load_strategy_invalid_directory(result, caplog, default_conf):
|
||||||
default_conf['strategy'] = 'DefaultStrategy'
|
default_conf['strategy'] = 'StrategyTestV2'
|
||||||
extra_dir = Path.cwd() / 'some/path'
|
extra_dir = Path.cwd() / 'some/path'
|
||||||
with pytest.raises(OperationalException):
|
with pytest.raises(OperationalException):
|
||||||
StrategyResolver._load_strategy('DefaultStrategy', config=default_conf,
|
StrategyResolver._load_strategy('StrategyTestV2', config=default_conf,
|
||||||
extra_dir=extra_dir)
|
extra_dir=extra_dir)
|
||||||
|
|
||||||
assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog)
|
assert log_has_re(r'Path .*' + r'some.*path.*' + r'.* does not exist', caplog)
|
||||||
@ -100,7 +100,7 @@ def test_load_strategy_noname(default_conf):
|
|||||||
|
|
||||||
|
|
||||||
def test_strategy(result, default_conf):
|
def test_strategy(result, default_conf):
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
|
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
metadata = {'pair': 'ETH/BTC'}
|
metadata = {'pair': 'ETH/BTC'}
|
||||||
@ -129,7 +129,7 @@ def test_strategy(result, default_conf):
|
|||||||
def test_strategy_override_minimal_roi(caplog, default_conf):
|
def test_strategy_override_minimal_roi(caplog, default_conf):
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'minimal_roi': {
|
'minimal_roi': {
|
||||||
"20": 0.1,
|
"20": 0.1,
|
||||||
"0": 0.5
|
"0": 0.5
|
||||||
@ -146,7 +146,7 @@ def test_strategy_override_minimal_roi(caplog, default_conf):
|
|||||||
def test_strategy_override_stoploss(caplog, default_conf):
|
def test_strategy_override_stoploss(caplog, default_conf):
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'stoploss': -0.5
|
'stoploss': -0.5
|
||||||
})
|
})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
@ -158,7 +158,7 @@ def test_strategy_override_stoploss(caplog, default_conf):
|
|||||||
def test_strategy_override_trailing_stop(caplog, default_conf):
|
def test_strategy_override_trailing_stop(caplog, default_conf):
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'trailing_stop': True
|
'trailing_stop': True
|
||||||
})
|
})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
@ -171,7 +171,7 @@ def test_strategy_override_trailing_stop(caplog, default_conf):
|
|||||||
def test_strategy_override_trailing_stop_positive(caplog, default_conf):
|
def test_strategy_override_trailing_stop_positive(caplog, default_conf):
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'trailing_stop_positive': -0.1,
|
'trailing_stop_positive': -0.1,
|
||||||
'trailing_stop_positive_offset': -0.2
|
'trailing_stop_positive_offset': -0.2
|
||||||
|
|
||||||
@ -191,7 +191,7 @@ def test_strategy_override_timeframe(caplog, default_conf):
|
|||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
|
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'timeframe': 60,
|
'timeframe': 60,
|
||||||
'stake_currency': 'ETH'
|
'stake_currency': 'ETH'
|
||||||
})
|
})
|
||||||
@ -207,7 +207,7 @@ def test_strategy_override_process_only_new_candles(caplog, default_conf):
|
|||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
|
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'process_only_new_candles': True
|
'process_only_new_candles': True
|
||||||
})
|
})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
@ -227,7 +227,7 @@ def test_strategy_override_order_types(caplog, default_conf):
|
|||||||
'stoploss_on_exchange': True,
|
'stoploss_on_exchange': True,
|
||||||
}
|
}
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'order_types': order_types
|
'order_types': order_types
|
||||||
})
|
})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
@ -241,12 +241,12 @@ def test_strategy_override_order_types(caplog, default_conf):
|
|||||||
" 'stoploss_on_exchange': True}.", caplog)
|
" 'stoploss_on_exchange': True}.", caplog)
|
||||||
|
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'order_types': {'buy': 'market'}
|
'order_types': {'buy': 'market'}
|
||||||
})
|
})
|
||||||
# Raise error for invalid configuration
|
# Raise error for invalid configuration
|
||||||
with pytest.raises(ImportError,
|
with pytest.raises(ImportError,
|
||||||
match=r"Impossible to load Strategy 'DefaultStrategy'. "
|
match=r"Impossible to load Strategy 'StrategyTestV2'. "
|
||||||
r"Order-types mapping is incomplete."):
|
r"Order-types mapping is incomplete."):
|
||||||
StrategyResolver.load_strategy(default_conf)
|
StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
@ -260,7 +260,7 @@ def test_strategy_override_order_tif(caplog, default_conf):
|
|||||||
}
|
}
|
||||||
|
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'order_time_in_force': order_time_in_force
|
'order_time_in_force': order_time_in_force
|
||||||
})
|
})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
@ -273,12 +273,12 @@ def test_strategy_override_order_tif(caplog, default_conf):
|
|||||||
" {'buy': 'fok', 'sell': 'gtc'}.", caplog)
|
" {'buy': 'fok', 'sell': 'gtc'}.", caplog)
|
||||||
|
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'order_time_in_force': {'buy': 'fok'}
|
'order_time_in_force': {'buy': 'fok'}
|
||||||
})
|
})
|
||||||
# Raise error for invalid configuration
|
# Raise error for invalid configuration
|
||||||
with pytest.raises(ImportError,
|
with pytest.raises(ImportError,
|
||||||
match=r"Impossible to load Strategy 'DefaultStrategy'. "
|
match=r"Impossible to load Strategy 'StrategyTestV2'. "
|
||||||
r"Order-time-in-force mapping is incomplete."):
|
r"Order-time-in-force mapping is incomplete."):
|
||||||
StrategyResolver.load_strategy(default_conf)
|
StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
@ -286,7 +286,7 @@ def test_strategy_override_order_tif(caplog, default_conf):
|
|||||||
def test_strategy_override_use_sell_signal(caplog, default_conf):
|
def test_strategy_override_use_sell_signal(caplog, default_conf):
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
})
|
})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
assert strategy.use_sell_signal
|
assert strategy.use_sell_signal
|
||||||
@ -296,7 +296,7 @@ def test_strategy_override_use_sell_signal(caplog, default_conf):
|
|||||||
assert default_conf['use_sell_signal']
|
assert default_conf['use_sell_signal']
|
||||||
|
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'use_sell_signal': False,
|
'use_sell_signal': False,
|
||||||
})
|
})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
@ -309,7 +309,7 @@ def test_strategy_override_use_sell_signal(caplog, default_conf):
|
|||||||
def test_strategy_override_use_sell_profit_only(caplog, default_conf):
|
def test_strategy_override_use_sell_profit_only(caplog, default_conf):
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
})
|
})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
assert not strategy.sell_profit_only
|
assert not strategy.sell_profit_only
|
||||||
@ -319,7 +319,7 @@ def test_strategy_override_use_sell_profit_only(caplog, default_conf):
|
|||||||
assert not default_conf['sell_profit_only']
|
assert not default_conf['sell_profit_only']
|
||||||
|
|
||||||
default_conf.update({
|
default_conf.update({
|
||||||
'strategy': 'DefaultStrategy',
|
'strategy': 'StrategyTestV2',
|
||||||
'sell_profit_only': True,
|
'sell_profit_only': True,
|
||||||
})
|
})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
@ -332,7 +332,7 @@ def test_strategy_override_use_sell_profit_only(caplog, default_conf):
|
|||||||
@pytest.mark.filterwarnings("ignore:deprecated")
|
@pytest.mark.filterwarnings("ignore:deprecated")
|
||||||
def test_deprecate_populate_indicators(result, default_conf):
|
def test_deprecate_populate_indicators(result, default_conf):
|
||||||
default_location = Path(__file__).parent / "strats"
|
default_location = Path(__file__).parent / "strats"
|
||||||
default_conf.update({'strategy': 'TestStrategyLegacy',
|
default_conf.update({'strategy': 'TestStrategyLegacyV1',
|
||||||
'strategy_path': default_location})
|
'strategy_path': default_location})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
with warnings.catch_warnings(record=True) as w:
|
with warnings.catch_warnings(record=True) as w:
|
||||||
@ -367,7 +367,7 @@ def test_deprecate_populate_indicators(result, default_conf):
|
|||||||
def test_call_deprecated_function(result, monkeypatch, default_conf, caplog):
|
def test_call_deprecated_function(result, monkeypatch, default_conf, caplog):
|
||||||
default_location = Path(__file__).parent / "strats"
|
default_location = Path(__file__).parent / "strats"
|
||||||
del default_conf['timeframe']
|
del default_conf['timeframe']
|
||||||
default_conf.update({'strategy': 'TestStrategyLegacy',
|
default_conf.update({'strategy': 'TestStrategyLegacyV1',
|
||||||
'strategy_path': default_location})
|
'strategy_path': default_location})
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
metadata = {'pair': 'ETH/BTC'}
|
metadata = {'pair': 'ETH/BTC'}
|
||||||
@ -397,8 +397,7 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog):
|
|||||||
|
|
||||||
|
|
||||||
def test_strategy_interface_versioning(result, default_conf):
|
def test_strategy_interface_versioning(result, default_conf):
|
||||||
# Tests interface compatibility with Interface version 2.
|
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
metadata = {'pair': 'ETH/BTC'}
|
metadata = {'pair': 'ETH/BTC'}
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ def test_parse_args_backtesting_custom() -> None:
|
|||||||
'-c', 'test_conf.json',
|
'-c', 'test_conf.json',
|
||||||
'--ticker-interval', '1m',
|
'--ticker-interval', '1m',
|
||||||
'--strategy-list',
|
'--strategy-list',
|
||||||
'DefaultStrategy',
|
'StrategyTestV2',
|
||||||
'SampleStrategy'
|
'SampleStrategy'
|
||||||
]
|
]
|
||||||
call_args = Arguments(args).get_parsed_arg()
|
call_args = Arguments(args).get_parsed_arg()
|
||||||
|
@ -404,7 +404,7 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
|
|||||||
arglist = [
|
arglist = [
|
||||||
'backtesting',
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
]
|
]
|
||||||
|
|
||||||
args = Arguments(arglist).get_parsed_arg()
|
args = Arguments(arglist).get_parsed_arg()
|
||||||
@ -441,7 +441,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
|
|||||||
arglist = [
|
arglist = [
|
||||||
'backtesting',
|
'backtesting',
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'StrategyTestV2',
|
||||||
'--datadir', '/foo/bar',
|
'--datadir', '/foo/bar',
|
||||||
'--userdir', "/tmp/freqtrade",
|
'--userdir', "/tmp/freqtrade",
|
||||||
'--ticker-interval', '1m',
|
'--ticker-interval', '1m',
|
||||||
@ -498,7 +498,7 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non
|
|||||||
'--ticker-interval', '1m',
|
'--ticker-interval', '1m',
|
||||||
'--export', 'trades',
|
'--export', 'trades',
|
||||||
'--strategy-list',
|
'--strategy-list',
|
||||||
'DefaultStrategy',
|
'StrategyTestV2',
|
||||||
'TestStrategy'
|
'TestStrategy'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ def test_check_available_stake_amount(default_conf, ticker, mocker, fee, limit_b
|
|||||||
limit_buy_order_open['id'] = str(i)
|
limit_buy_order_open['id'] = str(i)
|
||||||
result = freqtrade.wallets.get_trade_stake_amount('ETH/BTC')
|
result = freqtrade.wallets.get_trade_stake_amount('ETH/BTC')
|
||||||
assert pytest.approx(result) == expected[i]
|
assert pytest.approx(result) == expected[i]
|
||||||
freqtrade.execute_buy('ETH/BTC', result)
|
freqtrade.execute_entry('ETH/BTC', result)
|
||||||
else:
|
else:
|
||||||
with pytest.raises(DependencyException):
|
with pytest.raises(DependencyException):
|
||||||
freqtrade.wallets.get_trade_stake_amount('ETH/BTC')
|
freqtrade.wallets.get_trade_stake_amount('ETH/BTC')
|
||||||
@ -584,8 +584,8 @@ def test_create_trades_preopen(default_conf, ticker, fee, mocker, limit_buy_orde
|
|||||||
patch_get_signal(freqtrade)
|
patch_get_signal(freqtrade)
|
||||||
|
|
||||||
# Create 2 existing trades
|
# Create 2 existing trades
|
||||||
freqtrade.execute_buy('ETH/BTC', default_conf['stake_amount'])
|
freqtrade.execute_entry('ETH/BTC', default_conf['stake_amount'])
|
||||||
freqtrade.execute_buy('NEO/BTC', default_conf['stake_amount'])
|
freqtrade.execute_entry('NEO/BTC', default_conf['stake_amount'])
|
||||||
|
|
||||||
assert len(Trade.get_open_trades()) == 2
|
assert len(Trade.get_open_trades()) == 2
|
||||||
# Change order_id for new orders
|
# Change order_id for new orders
|
||||||
@ -777,7 +777,7 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None:
|
|||||||
assert ("ETH/BTC", default_conf["timeframe"]) in refresh_mock.call_args[0][0]
|
assert ("ETH/BTC", default_conf["timeframe"]) in refresh_mock.call_args[0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order_open) -> None:
|
def test_execute_entry(mocker, default_conf, fee, limit_buy_order, limit_buy_order_open) -> None:
|
||||||
patch_RPCManager(mocker)
|
patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
@ -800,7 +800,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
|||||||
)
|
)
|
||||||
pair = 'ETH/BTC'
|
pair = 'ETH/BTC'
|
||||||
|
|
||||||
assert not freqtrade.execute_buy(pair, stake_amount)
|
assert not freqtrade.execute_entry(pair, stake_amount)
|
||||||
assert buy_rate_mock.call_count == 1
|
assert buy_rate_mock.call_count == 1
|
||||||
assert buy_mm.call_count == 0
|
assert buy_mm.call_count == 0
|
||||||
assert freqtrade.strategy.confirm_trade_entry.call_count == 1
|
assert freqtrade.strategy.confirm_trade_entry.call_count == 1
|
||||||
@ -808,7 +808,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
|||||||
|
|
||||||
limit_buy_order_open['id'] = '22'
|
limit_buy_order_open['id'] = '22'
|
||||||
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
|
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
|
||||||
assert freqtrade.execute_buy(pair, stake_amount)
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
assert buy_rate_mock.call_count == 1
|
assert buy_rate_mock.call_count == 1
|
||||||
assert buy_mm.call_count == 1
|
assert buy_mm.call_count == 1
|
||||||
call_args = buy_mm.call_args_list[0][1]
|
call_args = buy_mm.call_args_list[0][1]
|
||||||
@ -827,7 +827,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
|||||||
# Test calling with price
|
# Test calling with price
|
||||||
limit_buy_order_open['id'] = '33'
|
limit_buy_order_open['id'] = '33'
|
||||||
fix_price = 0.06
|
fix_price = 0.06
|
||||||
assert freqtrade.execute_buy(pair, stake_amount, fix_price)
|
assert freqtrade.execute_entry(pair, stake_amount, fix_price)
|
||||||
# Make sure get_rate wasn't called again
|
# Make sure get_rate wasn't called again
|
||||||
assert buy_rate_mock.call_count == 0
|
assert buy_rate_mock.call_count == 0
|
||||||
|
|
||||||
@ -845,7 +845,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
|||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||||
MagicMock(return_value=limit_buy_order))
|
MagicMock(return_value=limit_buy_order))
|
||||||
assert freqtrade.execute_buy(pair, stake_amount)
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
trade = Trade.query.all()[2]
|
trade = Trade.query.all()[2]
|
||||||
assert trade
|
assert trade
|
||||||
assert trade.open_order_id is None
|
assert trade.open_order_id is None
|
||||||
@ -862,7 +862,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
|||||||
limit_buy_order['id'] = '555'
|
limit_buy_order['id'] = '555'
|
||||||
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||||
MagicMock(return_value=limit_buy_order))
|
MagicMock(return_value=limit_buy_order))
|
||||||
assert freqtrade.execute_buy(pair, stake_amount)
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
trade = Trade.query.all()[3]
|
trade = Trade.query.all()[3]
|
||||||
assert trade
|
assert trade
|
||||||
assert trade.open_order_id == '555'
|
assert trade.open_order_id == '555'
|
||||||
@ -874,7 +874,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
|||||||
limit_buy_order['id'] = '556'
|
limit_buy_order['id'] = '556'
|
||||||
|
|
||||||
freqtrade.strategy.custom_stake_amount = lambda **kwargs: 150.0
|
freqtrade.strategy.custom_stake_amount = lambda **kwargs: 150.0
|
||||||
assert freqtrade.execute_buy(pair, stake_amount)
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
trade = Trade.query.all()[4]
|
trade = Trade.query.all()[4]
|
||||||
assert trade
|
assert trade
|
||||||
assert trade.stake_amount == 150
|
assert trade.stake_amount == 150
|
||||||
@ -882,7 +882,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
|||||||
# Exception case
|
# Exception case
|
||||||
limit_buy_order['id'] = '557'
|
limit_buy_order['id'] = '557'
|
||||||
freqtrade.strategy.custom_stake_amount = lambda **kwargs: 20 / 0
|
freqtrade.strategy.custom_stake_amount = lambda **kwargs: 20 / 0
|
||||||
assert freqtrade.execute_buy(pair, stake_amount)
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
trade = Trade.query.all()[5]
|
trade = Trade.query.all()[5]
|
||||||
assert trade
|
assert trade
|
||||||
assert trade.stake_amount == 2.0
|
assert trade.stake_amount == 2.0
|
||||||
@ -897,20 +897,20 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
|||||||
limit_buy_order['id'] = '66'
|
limit_buy_order['id'] = '66'
|
||||||
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||||
MagicMock(return_value=limit_buy_order))
|
MagicMock(return_value=limit_buy_order))
|
||||||
assert not freqtrade.execute_buy(pair, stake_amount)
|
assert not freqtrade.execute_entry(pair, stake_amount)
|
||||||
|
|
||||||
# Fail to get price...
|
# Fail to get price...
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0))
|
mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0))
|
||||||
|
|
||||||
with pytest.raises(PricingError, match="Could not determine buy price."):
|
with pytest.raises(PricingError, match="Could not determine buy price."):
|
||||||
freqtrade.execute_buy(pair, stake_amount)
|
freqtrade.execute_entry(pair, stake_amount)
|
||||||
|
|
||||||
# In case of custom entry price
|
# In case of custom entry price
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_rate', return_value=0.50)
|
mocker.patch('freqtrade.exchange.Exchange.get_rate', return_value=0.50)
|
||||||
limit_buy_order['status'] = 'open'
|
limit_buy_order['status'] = 'open'
|
||||||
limit_buy_order['id'] = '5566'
|
limit_buy_order['id'] = '5566'
|
||||||
freqtrade.strategy.custom_entry_price = lambda **kwargs: 0.508
|
freqtrade.strategy.custom_entry_price = lambda **kwargs: 0.508
|
||||||
assert freqtrade.execute_buy(pair, stake_amount)
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
trade = Trade.query.all()[6]
|
trade = Trade.query.all()[6]
|
||||||
assert trade
|
assert trade
|
||||||
assert trade.open_rate_requested == 0.508
|
assert trade.open_rate_requested == 0.508
|
||||||
@ -925,7 +925,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
|||||||
get_rate=MagicMock(return_value=10),
|
get_rate=MagicMock(return_value=10),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert freqtrade.execute_buy(pair, stake_amount)
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
trade = Trade.query.all()[7]
|
trade = Trade.query.all()[7]
|
||||||
assert trade
|
assert trade
|
||||||
assert trade.open_rate_requested == 10
|
assert trade.open_rate_requested == 10
|
||||||
@ -934,13 +934,13 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
|||||||
limit_buy_order['status'] = 'open'
|
limit_buy_order['status'] = 'open'
|
||||||
limit_buy_order['id'] = '5568'
|
limit_buy_order['id'] = '5568'
|
||||||
freqtrade.strategy.custom_entry_price = lambda **kwargs: "string price"
|
freqtrade.strategy.custom_entry_price = lambda **kwargs: "string price"
|
||||||
assert freqtrade.execute_buy(pair, stake_amount)
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
trade = Trade.query.all()[8]
|
trade = Trade.query.all()[8]
|
||||||
assert trade
|
assert trade
|
||||||
assert trade.open_rate_requested == 10
|
assert trade.open_rate_requested == 10
|
||||||
|
|
||||||
|
|
||||||
def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) -> None:
|
def test_execute_entry_confirm_error(mocker, default_conf, fee, limit_buy_order) -> None:
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
@ -958,18 +958,18 @@ def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) -
|
|||||||
pair = 'ETH/BTC'
|
pair = 'ETH/BTC'
|
||||||
|
|
||||||
freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=ValueError)
|
freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=ValueError)
|
||||||
assert freqtrade.execute_buy(pair, stake_amount)
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
|
|
||||||
limit_buy_order['id'] = '222'
|
limit_buy_order['id'] = '222'
|
||||||
freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=Exception)
|
freqtrade.strategy.confirm_trade_entry = MagicMock(side_effect=Exception)
|
||||||
assert freqtrade.execute_buy(pair, stake_amount)
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
|
|
||||||
limit_buy_order['id'] = '2223'
|
limit_buy_order['id'] = '2223'
|
||||||
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
|
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=True)
|
||||||
assert freqtrade.execute_buy(pair, stake_amount)
|
assert freqtrade.execute_entry(pair, stake_amount)
|
||||||
|
|
||||||
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False)
|
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False)
|
||||||
assert not freqtrade.execute_buy(pair, stake_amount)
|
assert not freqtrade.execute_entry(pair, stake_amount)
|
||||||
|
|
||||||
|
|
||||||
def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None:
|
def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None:
|
||||||
@ -2008,7 +2008,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open,
|
|||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
trade.is_open = True
|
trade.is_open = True
|
||||||
|
|
||||||
# FIX: sniffing logs, suggest handle_trade should not execute_sell
|
# FIX: sniffing logs, suggest handle_trade should not execute_trade_exit
|
||||||
# instead that responsibility should be moved out of handle_trade(),
|
# instead that responsibility should be moved out of handle_trade(),
|
||||||
# we might just want to check if we are in a sell condition without
|
# we might just want to check if we are in a sell condition without
|
||||||
# executing
|
# executing
|
||||||
@ -2635,7 +2635,7 @@ def test_handle_cancel_sell_cancel_exception(mocker, default_conf) -> None:
|
|||||||
assert freqtrade.handle_cancel_sell(trade, order, reason) == 'error cancelling order'
|
assert freqtrade.handle_cancel_sell(trade, order, reason) == 'error cancelling order'
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None:
|
def test_execute_trade_exit_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None:
|
||||||
rpc_mock = patch_RPCManager(mocker)
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -2663,16 +2663,16 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
|
|||||||
fetch_ticker=ticker_sell_up
|
fetch_ticker=ticker_sell_up
|
||||||
)
|
)
|
||||||
# Prevented sell ...
|
# Prevented sell ...
|
||||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'],
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
||||||
assert rpc_mock.call_count == 0
|
assert rpc_mock.call_count == 0
|
||||||
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
|
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
|
||||||
|
|
||||||
# Repatch with true
|
# Repatch with true
|
||||||
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
|
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
|
||||||
|
|
||||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'],
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
||||||
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
|
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
|
||||||
|
|
||||||
assert rpc_mock.call_count == 1
|
assert rpc_mock.call_count == 1
|
||||||
@ -2699,7 +2699,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
|
|||||||
} == last_msg
|
} == last_msg
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) -> None:
|
def test_execute_trade_exit_down(default_conf, ticker, fee, ticker_sell_down, mocker) -> None:
|
||||||
rpc_mock = patch_RPCManager(mocker)
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -2724,8 +2724,8 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker)
|
|||||||
fetch_ticker=ticker_sell_down
|
fetch_ticker=ticker_sell_down
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
|
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_down()['bid'],
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||||
|
|
||||||
assert rpc_mock.call_count == 2
|
assert rpc_mock.call_count == 2
|
||||||
last_msg = rpc_mock.call_args_list[-1][0][0]
|
last_msg = rpc_mock.call_args_list[-1][0][0]
|
||||||
@ -2751,7 +2751,8 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker)
|
|||||||
} == last_msg
|
} == last_msg
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_custom_exit_price(default_conf, ticker, fee, ticker_sell_up, mocker) -> None:
|
def test_execute_trade_exit_custom_exit_price(default_conf, ticker, fee, ticker_sell_up,
|
||||||
|
mocker) -> None:
|
||||||
rpc_mock = patch_RPCManager(mocker)
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -2784,8 +2785,8 @@ def test_execute_sell_custom_exit_price(default_conf, ticker, fee, ticker_sell_u
|
|||||||
# Set a custom exit price
|
# Set a custom exit price
|
||||||
freqtrade.strategy.custom_exit_price = lambda **kwargs: 1.170e-05
|
freqtrade.strategy.custom_exit_price = lambda **kwargs: 1.170e-05
|
||||||
|
|
||||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'],
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL))
|
sell_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL))
|
||||||
|
|
||||||
# Sell price must be different to default bid price
|
# Sell price must be different to default bid price
|
||||||
|
|
||||||
@ -2815,8 +2816,8 @@ def test_execute_sell_custom_exit_price(default_conf, ticker, fee, ticker_sell_u
|
|||||||
} == last_msg
|
} == last_msg
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fee,
|
def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(default_conf, ticker, fee,
|
||||||
ticker_sell_down, mocker) -> None:
|
ticker_sell_down, mocker) -> None:
|
||||||
rpc_mock = patch_RPCManager(mocker)
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -2846,8 +2847,8 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
|
|||||||
# Setting trade stoploss to 0.01
|
# Setting trade stoploss to 0.01
|
||||||
|
|
||||||
trade.stop_loss = 0.00001099 * 0.99
|
trade.stop_loss = 0.00001099 * 0.99
|
||||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
|
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_down()['bid'],
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||||
|
|
||||||
assert rpc_mock.call_count == 2
|
assert rpc_mock.call_count == 2
|
||||||
last_msg = rpc_mock.call_args_list[-1][0][0]
|
last_msg = rpc_mock.call_args_list[-1][0][0]
|
||||||
@ -2874,7 +2875,8 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
|
|||||||
} == last_msg
|
} == last_msg
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, caplog) -> None:
|
def test_execute_trade_exit_sloe_cancel_exception(
|
||||||
|
mocker, default_conf, ticker, fee, caplog) -> None:
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
|
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
|
||||||
side_effect=InvalidOrderException())
|
side_effect=InvalidOrderException())
|
||||||
@ -2901,14 +2903,14 @@ def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, c
|
|||||||
freqtrade.config['dry_run'] = False
|
freqtrade.config['dry_run'] = False
|
||||||
trade.stoploss_order_id = "abcd"
|
trade.stoploss_order_id = "abcd"
|
||||||
|
|
||||||
freqtrade.execute_sell(trade=trade, limit=1234,
|
freqtrade.execute_trade_exit(trade=trade, limit=1234,
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||||
assert create_order_mock.call_count == 2
|
assert create_order_mock.call_count == 2
|
||||||
assert log_has('Could not cancel stoploss order abcd', caplog)
|
assert log_has('Could not cancel stoploss order abcd', caplog)
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticker_sell_up,
|
def test_execute_trade_exit_with_stoploss_on_exchange(default_conf, ticker, fee, ticker_sell_up,
|
||||||
mocker) -> None:
|
mocker) -> None:
|
||||||
|
|
||||||
default_conf['exchange']['name'] = 'binance'
|
default_conf['exchange']['name'] = 'binance'
|
||||||
rpc_mock = patch_RPCManager(mocker)
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
@ -2952,8 +2954,8 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke
|
|||||||
fetch_ticker=ticker_sell_up
|
fetch_ticker=ticker_sell_up
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'],
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
assert trade
|
assert trade
|
||||||
@ -2961,8 +2963,8 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke
|
|||||||
assert rpc_mock.call_count == 3
|
assert rpc_mock.call_count == 3
|
||||||
|
|
||||||
|
|
||||||
def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, fee,
|
def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf, ticker, fee,
|
||||||
mocker) -> None:
|
mocker) -> None:
|
||||||
default_conf['exchange']['name'] = 'binance'
|
default_conf['exchange']['name'] = 'binance'
|
||||||
rpc_mock = patch_RPCManager(mocker)
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
@ -3033,8 +3035,8 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, f
|
|||||||
assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL
|
assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_market_order(default_conf, ticker, fee,
|
def test_execute_trade_exit_market_order(default_conf, ticker, fee,
|
||||||
ticker_sell_up, mocker) -> None:
|
ticker_sell_up, mocker) -> None:
|
||||||
rpc_mock = patch_RPCManager(mocker)
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -3060,8 +3062,8 @@ def test_execute_sell_market_order(default_conf, ticker, fee,
|
|||||||
)
|
)
|
||||||
freqtrade.config['order_types']['sell'] = 'market'
|
freqtrade.config['order_types']['sell'] = 'market'
|
||||||
|
|
||||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'],
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
||||||
|
|
||||||
assert not trade.is_open
|
assert not trade.is_open
|
||||||
assert trade.close_profit == 0.0620716
|
assert trade.close_profit == 0.0620716
|
||||||
@ -3091,8 +3093,8 @@ def test_execute_sell_market_order(default_conf, ticker, fee,
|
|||||||
} == last_msg
|
} == last_msg
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee,
|
def test_execute_trade_exit_insufficient_funds_error(default_conf, ticker, fee,
|
||||||
ticker_sell_up, mocker) -> None:
|
ticker_sell_up, mocker) -> None:
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds')
|
mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds')
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -3119,8 +3121,8 @@ def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee,
|
|||||||
)
|
)
|
||||||
|
|
||||||
sell_reason = SellCheckTuple(sell_type=SellType.ROI)
|
sell_reason = SellCheckTuple(sell_type=SellType.ROI)
|
||||||
assert not freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
assert not freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'],
|
||||||
sell_reason=sell_reason)
|
sell_reason=sell_reason)
|
||||||
assert mock_insuf.call_count == 1
|
assert mock_insuf.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
@ -3376,8 +3378,8 @@ def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplo
|
|||||||
fetch_ticker=ticker_sell_down
|
fetch_ticker=ticker_sell_down
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
|
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_down()['bid'],
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||||
trade.close(ticker_sell_down()['bid'])
|
trade.close(ticker_sell_down()['bid'])
|
||||||
assert freqtrade.strategy.is_pair_locked(trade.pair)
|
assert freqtrade.strategy.is_pair_locked(trade.pair)
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ from freqtrade.strategy.interface import SellCheckTuple
|
|||||||
from tests.conftest import get_patched_freqtradebot, patch_get_signal
|
from tests.conftest import get_patched_freqtradebot, patch_get_signal
|
||||||
|
|
||||||
|
|
||||||
def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||||
limit_buy_order, mocker) -> None:
|
limit_buy_order, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Tests workflow of selling stoploss_on_exchange.
|
Tests workflow of selling stoploss_on_exchange.
|
||||||
|
@ -70,7 +70,6 @@ def test_add_indicators(default_conf, testdatadir, caplog):
|
|||||||
indicators1 = {"ema10": {}}
|
indicators1 = {"ema10": {}}
|
||||||
indicators2 = {"macd": {"color": "red"}}
|
indicators2 = {"macd": {"color": "red"}}
|
||||||
|
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
# Generate buy/sell signals and indicators
|
# Generate buy/sell signals and indicators
|
||||||
@ -112,7 +111,6 @@ def test_add_areas(default_conf, testdatadir, caplog):
|
|||||||
"fill_to": "macdhist"}}
|
"fill_to": "macdhist"}}
|
||||||
|
|
||||||
ind_plain = {"macd": {"fill_to": "macdhist"}}
|
ind_plain = {"macd": {"fill_to": "macdhist"}}
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
# Generate buy/sell signals and indicators
|
# Generate buy/sell signals and indicators
|
||||||
@ -239,7 +237,6 @@ def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir)
|
|||||||
data = history.load_pair_history(pair=pair, timeframe='1m',
|
data = history.load_pair_history(pair=pair, timeframe='1m',
|
||||||
datadir=testdatadir, timerange=timerange)
|
datadir=testdatadir, timerange=timerange)
|
||||||
|
|
||||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
|
||||||
strategy = StrategyResolver.load_strategy(default_conf)
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
# Generate buy/sell signals and indicators
|
# Generate buy/sell signals and indicators
|
||||||
|
@ -157,13 +157,13 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_r
|
|||||||
assert result == result1
|
assert result == result1
|
||||||
|
|
||||||
# create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)'
|
# create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)'
|
||||||
freqtrade.execute_buy('ETH/USDT', result)
|
freqtrade.execute_entry('ETH/USDT', result)
|
||||||
|
|
||||||
result = freqtrade.wallets.get_trade_stake_amount('LTC/USDT')
|
result = freqtrade.wallets.get_trade_stake_amount('LTC/USDT')
|
||||||
assert result == result1
|
assert result == result1
|
||||||
|
|
||||||
# create 2 trades, order amount should be None
|
# create 2 trades, order amount should be None
|
||||||
freqtrade.execute_buy('LTC/BTC', result)
|
freqtrade.execute_entry('LTC/BTC', result)
|
||||||
|
|
||||||
result = freqtrade.wallets.get_trade_stake_amount('XRP/USDT')
|
result = freqtrade.wallets.get_trade_stake_amount('XRP/USDT')
|
||||||
assert result == 0
|
assert result == 0
|
||||||
|
File diff suppressed because one or more lines are too long
2
tests/testdata/backtest-result_new.json
vendored
2
tests/testdata/backtest-result_new.json
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user