merged with feat/short
This commit is contained in:
commit
2d76c5642d
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
6
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -2,14 +2,16 @@ Thank you for sending your pull request. But first, have you included
|
||||
unit tests, and is your code PEP8 conformant? [More details](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
|
||||
|
||||
## Summary
|
||||
|
||||
Explain in one sentence the goal of this PR
|
||||
|
||||
Solve the issue: #___
|
||||
|
||||
## Quick changelog
|
||||
|
||||
- <change log #1>
|
||||
- <change log #2>
|
||||
- <change log 1>
|
||||
- <change log 1>
|
||||
|
||||
## What's new?
|
||||
|
||||
*Explain in details what this PR solve or improve. You can include visuals.*
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM python:3.9.6-slim-buster as base
|
||||
FROM python:3.9.7-slim-buster as base
|
||||
|
||||
# Setup env
|
||||
ENV LANG C.UTF-8
|
||||
@ -13,7 +13,7 @@ RUN mkdir /freqtrade \
|
||||
&& apt-get update \
|
||||
&& apt-get -y install sudo libatlas3-base curl sqlite3 libhdf5-serial-dev \
|
||||
&& apt-get clean \
|
||||
&& useradd -u 1000 -G sudo -U -m ftuser \
|
||||
&& useradd -u 1000 -G sudo -U -m -s /bin/bash ftuser \
|
||||
&& chown ftuser:ftuser /freqtrade \
|
||||
# Allow sudoers
|
||||
&& echo "ftuser ALL=(ALL) NOPASSWD: /bin/chown" >> /etc/sudoers
|
||||
|
@ -12,9 +12,12 @@ if [ ! -f "${INSTALL_LOC}/lib/libta_lib.a" ]; then
|
||||
&& curl 'http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD' -o config.sub \
|
||||
&& ./configure --prefix=${INSTALL_LOC}/ \
|
||||
&& make -j$(nproc) \
|
||||
&& which sudo && sudo make install || make install \
|
||||
&& cd ..
|
||||
&& which sudo && sudo make install || make install
|
||||
if [ -x "$(command -v apt-get)" ]; then
|
||||
echo "Updating library path using ldconfig"
|
||||
sudo ldconfig
|
||||
fi
|
||||
cd .. && rm -rf ./ta-lib/
|
||||
else
|
||||
echo "TA-lib already installed, skipping installation"
|
||||
fi
|
||||
# && sed -i.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h \
|
||||
|
@ -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
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
# 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
|
||||
echo "failed running backtest"
|
||||
|
@ -174,7 +174,7 @@
|
||||
"heartbeat_interval": 60
|
||||
},
|
||||
"disable_dataframe_checks": false,
|
||||
"strategy": "DefaultStrategy",
|
||||
"strategy": "SampleStrategy",
|
||||
"strategy_path": "user_data/strategies/",
|
||||
"dataformat_ohlcv": "json",
|
||||
"dataformat_trades": "jsongz"
|
||||
|
@ -80,7 +80,7 @@ To override a pre-defined space (`roi_space`, `generate_roi_table`, `stoploss_sp
|
||||
class MyAwesomeStrategy(IStrategy):
|
||||
class HyperOpt:
|
||||
# Define a custom stoploss space.
|
||||
def stoploss_space(self):
|
||||
def stoploss_space():
|
||||
return [SKDecimal(-0.05, -0.01, decimals=3, name='stoploss')]
|
||||
```
|
||||
|
||||
@ -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.
|
||||
|
||||
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.
|
||||
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]
|
||||
[--enable-protections]
|
||||
[--dry-run-wallet DRY_RUN_WALLET]
|
||||
[--timeframe-detail TIMEFRAME_DETAIL]
|
||||
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
|
||||
[--export {none,trades}] [--export-filename PATH]
|
||||
|
||||
@ -55,6 +56,9 @@ optional arguments:
|
||||
--dry-run-wallet DRY_RUN_WALLET, --starting-balance DRY_RUN_WALLET
|
||||
Starting balance, used for backtesting / hyperopt and
|
||||
dry-runs.
|
||||
--timeframe-detail TIMEFRAME_DETAIL
|
||||
Specify detail timeframe for backtesting (`1m`, `5m`,
|
||||
`30m`, `1h`, `1d`).
|
||||
--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]
|
||||
Provide a space-separated list of strategies to
|
||||
backtest. Please note that ticker-interval needs to be
|
||||
@ -62,7 +66,7 @@ optional arguments:
|
||||
this together with `--export trades`, the strategy-
|
||||
name is injected into the filename (so `backtest-
|
||||
data.json` becomes `backtest-data-
|
||||
DefaultStrategy.json`
|
||||
SampleStrategy.json`
|
||||
--export {none,trades}
|
||||
Export backtest results (default: trades).
|
||||
--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).
|
||||
- `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:
|
||||
|
||||
@ -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.
|
||||
|
||||
### Further backtest-result analysis
|
||||
### Improved backtest accuracy
|
||||
|
||||
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.
|
||||
One big limitation of backtesting is it's inability to know how prices moved intra-candle (was high before close, or viceversa?).
|
||||
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
|
||||
|
||||
|
@ -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.
|
||||
* **Trade**: Open position.
|
||||
* **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"`, ...).
|
||||
* **Indicators**: Technical indicators (SMA, EMA, RSI, ...).
|
||||
* **Limit order**: Limit orders which execute at the defined limit price or better.
|
||||
|
@ -444,8 +444,8 @@ The possible values are: `gtc` (default), `fok` or `ioc`.
|
||||
```
|
||||
|
||||
!!! Warning
|
||||
This is ongoing work. For now, it is supported only for binance.
|
||||
Please don't change the default value unless you know what you are doing and have researched the impact of using different values.
|
||||
This is ongoing work. For now, it is supported only for binance and kucoin.
|
||||
Please don't change the default value unless you know what you are doing and have researched the impact of using different values for your particular exchange.
|
||||
|
||||
### Exchange configuration
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
The `Edge Positioning` module uses probability to calculate your win rate and risk reward ratio. It will use these statistics to control your strategy trade entry points, position size and, stoploss.
|
||||
|
||||
!!! Warning
|
||||
WHen using `Edge positioning` with a dynamic whitelist (VolumePairList), make sure to also use `AgeFilter` and set it to at least `calculate_since_number_of_days` to avoid problems with missing data.
|
||||
When using `Edge positioning` with a dynamic whitelist (VolumePairList), make sure to also use `AgeFilter` and set it to at least `calculate_since_number_of_days` to avoid problems with missing data.
|
||||
|
||||
!!! Note
|
||||
`Edge Positioning` only considers *its own* buy/sell/stoploss signals. It ignores the stoploss, trailing stoploss, and ROI settings in the strategy configuration file.
|
||||
|
@ -4,6 +4,8 @@ This page combines common gotchas and informations which are exchange-specific a
|
||||
|
||||
## Binance
|
||||
|
||||
Binance supports [time_in_force](configuration.md#understand-order_time_in_force).
|
||||
|
||||
!!! Tip "Stoploss on Exchange"
|
||||
Binance supports `stoploss_on_exchange` and uses stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it.
|
||||
|
||||
@ -113,8 +115,12 @@ Kucoin requires a passphrase for each api key, you will therefore need to add th
|
||||
"key": "your_exchange_key",
|
||||
"secret": "your_exchange_secret",
|
||||
"password": "your_exchange_api_key_password",
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Kucoin supports [time_in_force](configuration.md#understand-order_time_in_force).
|
||||
|
||||
### Kucoin Blacklists
|
||||
|
||||
For Kucoin, please add `"KCS/<STAKE>"` to your blacklist to avoid issues.
|
||||
@ -158,6 +164,8 @@ For example, to test the order type `FOK` with Kraken, and modify candle limit t
|
||||
"order_time_in_force": ["gtc", "fok"],
|
||||
"ohlcv_candle_limit": 200
|
||||
}
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
!!! Warning
|
||||
|
@ -456,7 +456,7 @@ class MyAwesomeStrategy(IStrategy):
|
||||
"only_per_pair": False
|
||||
})
|
||||
|
||||
return protection
|
||||
return prot
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
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-material==7.2.4
|
||||
mkdocs-material==7.2.6
|
||||
mdx_truly_sane_lists==1.2
|
||||
pymdown-extensions==8.2
|
||||
|
@ -22,7 +22,7 @@ if __version__ == 'develop':
|
||||
# subprocess.check_output(
|
||||
# ['git', 'log', '--format="%h"', '-n 1'],
|
||||
# stderr=subprocess.DEVNULL).decode("utf-8").rstrip().strip('"')
|
||||
except Exception:
|
||||
except Exception: # pragma: no cover
|
||||
# git not available, ignore
|
||||
try:
|
||||
# Try Fallback to freqtrade_commit file (created by CI while building docker image)
|
||||
|
@ -22,7 +22,7 @@ ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "dataformat_ohlcv",
|
||||
"max_open_trades", "stake_amount", "fee", "pairs"]
|
||||
|
||||
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"]
|
||||
|
||||
ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
|
||||
|
@ -61,21 +61,27 @@ def ask_user_config() -> Dict[str, Any]:
|
||||
"type": "text",
|
||||
"name": "stake_currency",
|
||||
"message": "Please insert your stake currency:",
|
||||
"default": 'BTC',
|
||||
"default": 'USDT',
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"name": "stake_amount",
|
||||
"message": "Please insert your stake amount:",
|
||||
"default": "0.01",
|
||||
"message": f"Please insert your stake amount (Number or '{UNLIMITED_STAKE_AMOUNT}'):",
|
||||
"default": "100",
|
||||
"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",
|
||||
"name": "max_open_trades",
|
||||
"message": f"Please insert max_open_trades (Integer or '{UNLIMITED_STAKE_AMOUNT}'):",
|
||||
"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",
|
||||
@ -99,6 +105,8 @@ def ask_user_config() -> Dict[str, Any]:
|
||||
"bittrex",
|
||||
"kraken",
|
||||
"ftx",
|
||||
"kucoin",
|
||||
"gateio",
|
||||
Separator(),
|
||||
"other",
|
||||
],
|
||||
@ -122,6 +130,12 @@ def ask_user_config() -> Dict[str, Any]:
|
||||
"message": "Insert Exchange Secret",
|
||||
"when": lambda x: not x['dry_run']
|
||||
},
|
||||
{
|
||||
"type": "password",
|
||||
"name": "exchange_key_password",
|
||||
"message": "Insert Exchange API Key password",
|
||||
"when": lambda x: not x['dry_run'] and x['exchange_name'] == 'kucoin'
|
||||
},
|
||||
{
|
||||
"type": "confirm",
|
||||
"name": "telegram",
|
||||
|
@ -135,6 +135,10 @@ AVAILABLE_CLI_OPTIONS = {
|
||||
help='Override the value of the `stake_amount` configuration setting.',
|
||||
),
|
||||
# Backtesting
|
||||
"timeframe_detail": Arg(
|
||||
'--timeframe-detail',
|
||||
help='Specify detail timeframe for backtesting (`1m`, `5m`, `30m`, `1h`, `1d`).',
|
||||
),
|
||||
"position_stacking": Arg(
|
||||
'--eps', '--enable-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 '
|
||||
'or via command line. When using this together with `--export trades`, '
|
||||
'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='+',
|
||||
),
|
||||
"export": Arg(
|
||||
|
@ -74,8 +74,6 @@ def start_new_strategy(args: Dict[str, Any]) -> None:
|
||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||
|
||||
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')
|
||||
|
||||
@ -128,8 +126,6 @@ def start_new_hyperopt(args: Dict[str, Any]) -> None:
|
||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||
|
||||
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')
|
||||
|
||||
|
@ -242,6 +242,9 @@ class Configuration:
|
||||
except ValueError:
|
||||
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',
|
||||
logstring='Parameter --stake-amount detected, '
|
||||
'overriding stake_amount to: {} ...')
|
||||
|
@ -49,6 +49,8 @@ USERPATH_NOTEBOOKS = 'notebooks'
|
||||
TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent']
|
||||
ENV_VAR_PREFIX = 'FREQTRADE__'
|
||||
|
||||
NON_OPEN_EXCHANGE_STATES = ('cancelled', 'canceled', 'closed', 'expired')
|
||||
|
||||
|
||||
# Define decimals per coin for outputs
|
||||
# Only used for outputs.
|
||||
|
@ -18,6 +18,7 @@ class Binance(Exchange):
|
||||
_ft_has: Dict = {
|
||||
"stoploss_on_exchange": True,
|
||||
"order_time_in_force": ['gtc', 'fok', 'ioc'],
|
||||
"time_in_force_parameter": "timeInForce",
|
||||
"ohlcv_candle_limit": 1000,
|
||||
"trades_pagination": "id",
|
||||
"trades_pagination_arg": "fromId",
|
||||
|
@ -19,7 +19,8 @@ from ccxt.base.decimal_to_precision import (ROUND_DOWN, ROUND_UP, TICK_SIZE, TRU
|
||||
decimal_to_precision)
|
||||
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.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError,
|
||||
InvalidOrderException, OperationalException, PricingError,
|
||||
@ -53,12 +54,16 @@ class Exchange:
|
||||
# Parameters to add directly to buy/sell calls (like agreeing to trading agreement)
|
||||
_params: Dict = {}
|
||||
|
||||
# Additional headers - added to the ccxt object
|
||||
_headers: Dict = {}
|
||||
|
||||
# Dict to specify which options each exchange implements
|
||||
# This defines defaults, which can be selectively overridden by subclasses using _ft_has
|
||||
# or by specifying them in the configuration.
|
||||
_ft_has_default: Dict = {
|
||||
"stoploss_on_exchange": False,
|
||||
"order_time_in_force": ["gtc"],
|
||||
"time_in_force_parameter": "timeInForce",
|
||||
"ohlcv_params": {},
|
||||
"ohlcv_candle_limit": 500,
|
||||
"ohlcv_partial_candle": True,
|
||||
@ -168,7 +173,7 @@ class Exchange:
|
||||
asyncio.get_event_loop().run_until_complete(self._api_async.close())
|
||||
|
||||
def _init_ccxt(self, exchange_config: Dict[str, Any], ccxt_module: CcxtModuleType = ccxt,
|
||||
ccxt_kwargs: dict = None) -> ccxt.Exchange:
|
||||
ccxt_kwargs: Dict = {}) -> ccxt.Exchange:
|
||||
"""
|
||||
Initialize ccxt with given config and return valid
|
||||
ccxt instance.
|
||||
@ -187,6 +192,10 @@ class Exchange:
|
||||
}
|
||||
if ccxt_kwargs:
|
||||
logger.info('Applying additional ccxt config: %s', ccxt_kwargs)
|
||||
if self._headers:
|
||||
# Inject static headers after the above output to not confuse users.
|
||||
ccxt_kwargs = deep_merge_dicts({'headers': self._headers}, ccxt_kwargs)
|
||||
if ccxt_kwargs:
|
||||
ex_config.update(ccxt_kwargs)
|
||||
try:
|
||||
|
||||
@ -351,9 +360,16 @@ class Exchange:
|
||||
def validate_stakecurrency(self, stake_currency: str) -> None:
|
||||
"""
|
||||
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
|
||||
: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()
|
||||
if stake_currency not in quote_currencies:
|
||||
raise OperationalException(
|
||||
@ -708,7 +724,8 @@ class Exchange:
|
||||
|
||||
params = self._params.copy()
|
||||
if time_in_force != 'gtc' and ordertype != 'market':
|
||||
params.update({'timeInForce': time_in_force})
|
||||
param = self._ft_has.get('time_in_force_parameter', '')
|
||||
params.update({param: time_in_force})
|
||||
|
||||
try:
|
||||
# Set the precision for amount and price(rate) as accepted by the exchange
|
||||
@ -804,7 +821,7 @@ class Exchange:
|
||||
:param order: Order dict as returned from fetch_order()
|
||||
: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)
|
||||
|
||||
@retrier
|
||||
|
@ -21,4 +21,6 @@ class Kucoin(Exchange):
|
||||
_ft_has: Dict = {
|
||||
"l2_limit_range": [20, 100],
|
||||
"l2_limit_range_required": False,
|
||||
"order_time_in_force": ['gtc', 'fok', 'ioc'],
|
||||
"time_in_force_parameter": "timeInForce",
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
self.state = State[initial_state.upper()] if initial_state else State.STOPPED
|
||||
|
||||
# Protect sell-logic from forcesell and vice versa
|
||||
self._sell_lock = Lock()
|
||||
self._exit_lock = Lock()
|
||||
LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe))
|
||||
|
||||
if self.config.get("trading_mode"):
|
||||
@ -186,14 +186,14 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
self.strategy.analyze(self.active_pair_whitelist)
|
||||
|
||||
with self._sell_lock:
|
||||
with self._exit_lock:
|
||||
# Check and handle any timed out open orders
|
||||
self.check_handle_timedout()
|
||||
|
||||
# Protect from collisions with forcesell.
|
||||
# Without this, freqtrade my try to recreate stoploss_on_exchange orders
|
||||
# while selling is in process, since telegram messages arrive in an different thread.
|
||||
with self._sell_lock:
|
||||
with self._exit_lock:
|
||||
trades = Trade.get_open_trades()
|
||||
# First process current opened trades (positions)
|
||||
self.exit_positions(trades)
|
||||
@ -316,9 +316,9 @@ class FreqtradeBot(LoggingMixin):
|
||||
if sell_order:
|
||||
self.refind_lost_order(trade)
|
||||
else:
|
||||
self.reupdate_buy_order_fees(trade)
|
||||
self.reupdate_enter_order_fees(trade)
|
||||
|
||||
def reupdate_buy_order_fees(self, trade: Trade):
|
||||
def reupdate_enter_order_fees(self, trade: Trade):
|
||||
"""
|
||||
Get buy order from database, and try to reupdate.
|
||||
Handles trades where the initial fee-update did not work.
|
||||
@ -453,11 +453,11 @@ class FreqtradeBot(LoggingMixin):
|
||||
if ((bid_check_dom.get('enabled', False)) and
|
||||
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
|
||||
if self._check_depth_of_market_buy(pair, bid_check_dom):
|
||||
return self.execute_buy(pair, stake_amount, buy_tag=buy_tag)
|
||||
return self.execute_entry(pair, stake_amount, buy_tag=buy_tag)
|
||||
else:
|
||||
return False
|
||||
|
||||
return self.execute_buy(pair, stake_amount, buy_tag=buy_tag)
|
||||
return self.execute_entry(pair, stake_amount, buy_tag=buy_tag)
|
||||
else:
|
||||
return False
|
||||
|
||||
@ -485,8 +485,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
logger.info(f"Bids to asks delta for {pair} does not satisfy condition.")
|
||||
return False
|
||||
|
||||
def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = None,
|
||||
forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool:
|
||||
def execute_entry(self, pair: str, stake_amount: float, price: Optional[float] = None,
|
||||
forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool:
|
||||
"""
|
||||
Executes a limit buy for the given pair
|
||||
:param pair: pair for which we want to create a LIMIT_BUY
|
||||
@ -496,21 +496,21 @@ class FreqtradeBot(LoggingMixin):
|
||||
time_in_force = self.strategy.order_time_in_force['buy']
|
||||
|
||||
if price:
|
||||
buy_limit_requested = price
|
||||
enter_limit_requested = price
|
||||
else:
|
||||
# Calculate price
|
||||
proposed_buy_rate = self.exchange.get_rate(pair, refresh=True, side="buy")
|
||||
proposed_enter_rate = self.exchange.get_rate(pair, refresh=True, side="buy")
|
||||
custom_entry_price = strategy_safe_wrapper(self.strategy.custom_entry_price,
|
||||
default_retval=proposed_buy_rate)(
|
||||
default_retval=proposed_enter_rate)(
|
||||
pair=pair, current_time=datetime.now(timezone.utc),
|
||||
proposed_rate=proposed_buy_rate)
|
||||
proposed_rate=proposed_enter_rate)
|
||||
|
||||
buy_limit_requested = self.get_valid_price(custom_entry_price, proposed_buy_rate)
|
||||
enter_limit_requested = self.get_valid_price(custom_entry_price, proposed_enter_rate)
|
||||
|
||||
if not buy_limit_requested:
|
||||
if not enter_limit_requested:
|
||||
raise PricingError('Could not determine buy price.')
|
||||
|
||||
min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, buy_limit_requested,
|
||||
min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, enter_limit_requested,
|
||||
self.strategy.stoploss)
|
||||
|
||||
if not self.edge:
|
||||
@ -518,7 +518,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount,
|
||||
default_retval=stake_amount)(
|
||||
pair=pair, current_time=datetime.now(timezone.utc),
|
||||
current_rate=buy_limit_requested, proposed_stake=stake_amount,
|
||||
current_rate=enter_limit_requested, proposed_stake=stake_amount,
|
||||
min_stake=min_stake_amount, max_stake=max_stake_amount)
|
||||
stake_amount = self.wallets._validate_stake_amount(pair, stake_amount, min_stake_amount)
|
||||
|
||||
@ -528,27 +528,27 @@ class FreqtradeBot(LoggingMixin):
|
||||
logger.info(f"Buy signal found: about create a new trade for {pair} with stake_amount: "
|
||||
f"{stake_amount} ...")
|
||||
|
||||
amount = stake_amount / buy_limit_requested
|
||||
amount = stake_amount / enter_limit_requested
|
||||
order_type = self.strategy.order_types['buy']
|
||||
if forcebuy:
|
||||
# Forcebuy can define a different ordertype
|
||||
order_type = self.strategy.order_types.get('forcebuy', order_type)
|
||||
|
||||
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
|
||||
pair=pair, order_type=order_type, amount=amount, rate=buy_limit_requested,
|
||||
pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested,
|
||||
time_in_force=time_in_force, current_time=datetime.now(timezone.utc)):
|
||||
logger.info(f"User requested abortion of buying {pair}")
|
||||
return False
|
||||
amount = self.exchange.amount_to_precision(pair, amount)
|
||||
order = self.exchange.create_order(pair=pair, ordertype=order_type, side="buy",
|
||||
amount=amount, rate=buy_limit_requested,
|
||||
amount=amount, rate=enter_limit_requested,
|
||||
time_in_force=time_in_force)
|
||||
order_obj = Order.parse_from_ccxt_object(order, pair, 'buy')
|
||||
order_id = order['id']
|
||||
order_status = order.get('status', None)
|
||||
|
||||
# we assume the order is executed at the price requested
|
||||
buy_limit_filled_price = buy_limit_requested
|
||||
enter_limit_filled_price = enter_limit_requested
|
||||
amount_requested = amount
|
||||
|
||||
if order_status == 'expired' or order_status == 'rejected':
|
||||
@ -571,13 +571,13 @@ class FreqtradeBot(LoggingMixin):
|
||||
)
|
||||
stake_amount = order['cost']
|
||||
amount = safe_value_fallback(order, 'filled', 'amount')
|
||||
buy_limit_filled_price = safe_value_fallback(order, 'average', 'price')
|
||||
enter_limit_filled_price = safe_value_fallback(order, 'average', 'price')
|
||||
|
||||
# in case of FOK the order may be filled immediately and fully
|
||||
elif order_status == 'closed':
|
||||
stake_amount = order['cost']
|
||||
amount = safe_value_fallback(order, 'filled', 'amount')
|
||||
buy_limit_filled_price = safe_value_fallback(order, 'average', 'price')
|
||||
enter_limit_filled_price = safe_value_fallback(order, 'average', 'price')
|
||||
|
||||
# Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
|
||||
fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
|
||||
@ -589,8 +589,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
amount_requested=amount_requested,
|
||||
fee_open=fee,
|
||||
fee_close=fee,
|
||||
open_rate=buy_limit_filled_price,
|
||||
open_rate_requested=buy_limit_requested,
|
||||
open_rate=enter_limit_filled_price,
|
||||
open_rate_requested=enter_limit_requested,
|
||||
open_date=datetime.utcnow(),
|
||||
exchange=self.exchange.id,
|
||||
open_order_id=order_id,
|
||||
@ -613,11 +613,11 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Updating wallets
|
||||
self.wallets.update()
|
||||
|
||||
self._notify_buy(trade, order_type)
|
||||
self._notify_enter(trade, order_type)
|
||||
|
||||
return True
|
||||
|
||||
def _notify_buy(self, trade: Trade, order_type: str) -> None:
|
||||
def _notify_enter(self, trade: Trade, order_type: str) -> None:
|
||||
"""
|
||||
Sends rpc notification when a buy occurred.
|
||||
"""
|
||||
@ -640,7 +640,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Send the message
|
||||
self.rpc.send_msg(msg)
|
||||
|
||||
def _notify_buy_cancel(self, trade: Trade, order_type: str, reason: str) -> None:
|
||||
def _notify_enter_cancel(self, trade: Trade, order_type: str, reason: str) -> None:
|
||||
"""
|
||||
Sends rpc notification when a buy cancel occurred.
|
||||
"""
|
||||
@ -666,7 +666,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Send the message
|
||||
self.rpc.send_msg(msg)
|
||||
|
||||
def _notify_buy_fill(self, trade: Trade) -> None:
|
||||
def _notify_enter_fill(self, trade: Trade) -> None:
|
||||
msg = {
|
||||
'trade_id': trade.id,
|
||||
'type': RPCMessageType.BUY_FILL,
|
||||
@ -736,8 +736,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
)
|
||||
|
||||
logger.debug('checking sell')
|
||||
sell_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell")
|
||||
if self._check_and_execute_sell(trade, sell_rate, buy, sell):
|
||||
exit_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell")
|
||||
if self._check_and_execute_exit(trade, exit_rate, buy, sell):
|
||||
return True
|
||||
|
||||
logger.debug('Found no sell signal for %s.', trade)
|
||||
@ -767,8 +767,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
except InvalidOrderException as e:
|
||||
trade.stoploss_order_id = None
|
||||
logger.error(f'Unable to place a stoploss order on exchange. {e}')
|
||||
logger.warning('Selling the trade forcefully')
|
||||
self.execute_sell(trade, trade.stop_loss, sell_reason=SellCheckTuple(
|
||||
logger.warning('Exiting the trade forcefully')
|
||||
self.execute_trade_exit(trade, trade.stop_loss, sell_reason=SellCheckTuple(
|
||||
sell_type=SellType.EMERGENCY_SELL))
|
||||
|
||||
except ExchangeError:
|
||||
@ -805,7 +805,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Lock pair for one candle to prevent immediate rebuys
|
||||
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
|
||||
reason='Auto lock')
|
||||
self._notify_sell(trade, "stoploss")
|
||||
self._notify_exit(trade, "stoploss")
|
||||
return True
|
||||
|
||||
if trade.open_order_id or not trade.is_open:
|
||||
@ -874,19 +874,19 @@ class FreqtradeBot(LoggingMixin):
|
||||
logger.warning(f"Could not create trailing stoploss order "
|
||||
f"for pair {trade.pair}.")
|
||||
|
||||
def _check_and_execute_sell(self, trade: Trade, sell_rate: float,
|
||||
def _check_and_execute_exit(self, trade: Trade, exit_rate: float,
|
||||
buy: bool, sell: bool) -> bool:
|
||||
"""
|
||||
Check and execute sell
|
||||
Check and execute exit
|
||||
"""
|
||||
should_sell = self.strategy.should_sell(
|
||||
trade, sell_rate, datetime.now(timezone.utc), buy, sell,
|
||||
trade, exit_rate, datetime.now(timezone.utc), buy, sell,
|
||||
force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0
|
||||
)
|
||||
|
||||
if should_sell.sell_flag:
|
||||
logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}')
|
||||
self.execute_sell(trade, sell_rate, should_sell)
|
||||
self.execute_trade_exit(trade, exit_rate, should_sell)
|
||||
return True
|
||||
return False
|
||||
|
||||
@ -929,7 +929,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
default_retval=False)(pair=trade.pair,
|
||||
trade=trade,
|
||||
order=order))):
|
||||
self.handle_cancel_buy(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||
self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||
|
||||
elif (order['side'] == 'sell' and (order['status'] == 'open' or fully_cancelled) and (
|
||||
fully_cancelled
|
||||
@ -938,7 +938,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
default_retval=False)(pair=trade.pair,
|
||||
trade=trade,
|
||||
order=order))):
|
||||
self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||
self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||
|
||||
def cancel_all_open_orders(self) -> None:
|
||||
"""
|
||||
@ -954,13 +954,13 @@ class FreqtradeBot(LoggingMixin):
|
||||
continue
|
||||
|
||||
if order['side'] == 'buy':
|
||||
self.handle_cancel_buy(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
|
||||
self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
|
||||
|
||||
elif order['side'] == 'sell':
|
||||
self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
|
||||
self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
|
||||
Trade.commit()
|
||||
|
||||
def handle_cancel_buy(self, trade: Trade, order: Dict, reason: str) -> bool:
|
||||
def handle_cancel_enter(self, trade: Trade, order: Dict, reason: str) -> bool:
|
||||
"""
|
||||
Buy cancel - cancel order
|
||||
:return: True if order was fully cancelled
|
||||
@ -968,7 +968,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
was_trade_fully_canceled = False
|
||||
|
||||
# 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_stake = filled_val * trade.open_rate
|
||||
minstake = self.exchange.get_min_pair_stake_amount(
|
||||
@ -984,7 +984,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
# 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
|
||||
# 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.")
|
||||
return False
|
||||
else:
|
||||
@ -1017,11 +1017,11 @@ class FreqtradeBot(LoggingMixin):
|
||||
reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}"
|
||||
|
||||
self.wallets.update()
|
||||
self._notify_buy_cancel(trade, order_type=self.strategy.order_types['buy'],
|
||||
reason=reason)
|
||||
self._notify_enter_cancel(trade, order_type=self.strategy.order_types['buy'],
|
||||
reason=reason)
|
||||
return was_trade_fully_canceled
|
||||
|
||||
def handle_cancel_sell(self, trade: Trade, order: Dict, reason: str) -> str:
|
||||
def handle_cancel_exit(self, trade: Trade, order: Dict, reason: str) -> str:
|
||||
"""
|
||||
Sell cancel - cancel order and update trade
|
||||
:return: Reason for cancel
|
||||
@ -1055,14 +1055,14 @@ class FreqtradeBot(LoggingMixin):
|
||||
reason = constants.CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
|
||||
|
||||
self.wallets.update()
|
||||
self._notify_sell_cancel(
|
||||
self._notify_exit_cancel(
|
||||
trade,
|
||||
order_type=self.strategy.order_types['sell'],
|
||||
reason=reason
|
||||
)
|
||||
return reason
|
||||
|
||||
def _safe_sell_amount(self, pair: str, amount: float) -> float:
|
||||
def _safe_exit_amount(self, pair: str, amount: float) -> float:
|
||||
"""
|
||||
Get sellable amount.
|
||||
Should be trade.amount - but will fall back to the available amount if necessary.
|
||||
@ -1087,9 +1087,9 @@ class FreqtradeBot(LoggingMixin):
|
||||
raise DependencyException(
|
||||
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 limit: limit rate for the sell order
|
||||
:param sell_reason: Reason the sell was triggered
|
||||
@ -1134,7 +1134,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
# but we allow this value to be changed)
|
||||
order_type = self.strategy.order_types.get("forcesell", order_type)
|
||||
|
||||
amount = self._safe_sell_amount(trade.pair, trade.amount)
|
||||
amount = self._safe_exit_amount(trade.pair, trade.amount)
|
||||
time_in_force = self.strategy.order_time_in_force['sell']
|
||||
|
||||
if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)(
|
||||
@ -1165,7 +1165,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
trade.close_rate_requested = limit
|
||||
trade.sell_reason = sell_reason.sell_reason
|
||||
# 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)
|
||||
Trade.commit()
|
||||
|
||||
@ -1173,7 +1173,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
|
||||
reason='Auto lock')
|
||||
|
||||
self._notify_sell(trade, order_type)
|
||||
self._notify_exit(trade, order_type)
|
||||
self._remove_maintenance_trade(trade)
|
||||
|
||||
return True
|
||||
@ -1186,7 +1186,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
if self.collateral == Collateral.CROSS:
|
||||
self.maintenance_margin.remove_trade(trade)
|
||||
|
||||
def _notify_sell(self, trade: Trade, order_type: str, fill: bool = False) -> None:
|
||||
def _notify_exit(self, trade: Trade, order_type: str, fill: bool = False) -> None:
|
||||
"""
|
||||
Sends rpc notification when a sell occurred.
|
||||
"""
|
||||
@ -1228,7 +1228,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Send the message
|
||||
self.rpc.send_msg(msg)
|
||||
|
||||
def _notify_sell_cancel(self, trade: Trade, order_type: str, reason: str) -> None:
|
||||
def _notify_exit_cancel(self, trade: Trade, order_type: str, reason: str) -> None:
|
||||
"""
|
||||
Sends rpc notification when a sell cancel occurred.
|
||||
"""
|
||||
@ -1323,13 +1323,13 @@ class FreqtradeBot(LoggingMixin):
|
||||
# Updating wallets when order is closed
|
||||
if not trade.is_open:
|
||||
if not stoploss_order and not trade.open_order_id:
|
||||
self._notify_sell(trade, '', True)
|
||||
self._notify_exit(trade, '', True)
|
||||
self.protections.stop_per_pair(trade.pair)
|
||||
self.protections.global_stop()
|
||||
self.wallets.update()
|
||||
elif not trade.open_order_id:
|
||||
# Buy fill
|
||||
self._notify_buy_fill(trade)
|
||||
self._notify_enter_fill(trade)
|
||||
|
||||
return False
|
||||
|
||||
|
@ -87,7 +87,7 @@ def setup_logging(config: Dict[str, Any]) -> None:
|
||||
# syslog config. The messages should be equal for this.
|
||||
handler_sl.setFormatter(Formatter('%(name)s - %(levelname)s - %(message)s'))
|
||||
logging.root.addHandler(handler_sl)
|
||||
elif s[0] == 'journald':
|
||||
elif s[0] == 'journald': # pragma: no cover
|
||||
try:
|
||||
from systemd.journal import JournaldLogHandler
|
||||
except ImportError:
|
||||
|
@ -9,7 +9,7 @@ from typing import Any, List
|
||||
|
||||
|
||||
# check min. python version
|
||||
if sys.version_info < (3, 7):
|
||||
if sys.version_info < (3, 7): # pragma: no cover
|
||||
sys.exit("Freqtrade requires Python version >= 3.7")
|
||||
|
||||
from freqtrade.commands import Arguments
|
||||
@ -46,7 +46,7 @@ def main(sysargv: List[str] = None) -> None:
|
||||
"`freqtrade --help` or `freqtrade <command> --help`."
|
||||
)
|
||||
|
||||
except SystemExit as e:
|
||||
except SystemExit as e: # pragma: no cover
|
||||
return_code = e
|
||||
except KeyboardInterrupt:
|
||||
logger.info('SIGINT received, aborting ...')
|
||||
@ -60,5 +60,5 @@ def main(sysargv: List[str] = None) -> None:
|
||||
sys.exit(return_code)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == '__main__': # pragma: no cover
|
||||
main()
|
||||
|
@ -86,6 +86,17 @@ class Backtesting:
|
||||
"configuration or as cli argument `--timeframe 5m`")
|
||||
self.timeframe = str(self.config.get('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)
|
||||
if 'VolumePairList' in self.pairlists.name_list:
|
||||
@ -188,6 +199,23 @@ class Backtesting:
|
||||
self.progress.set_new_value(1)
|
||||
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):
|
||||
"""
|
||||
Backtesting setup method - called once for every call to "backtest()".
|
||||
@ -320,7 +348,8 @@ class Backtesting:
|
||||
else:
|
||||
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 = self.strategy.should_sell(trade, sell_row[OPEN_IDX], # type: ignore
|
||||
sell_candle_time, sell_row[BUY_IDX],
|
||||
@ -348,6 +377,32 @@ class Backtesting:
|
||||
|
||||
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['buy'] = sell_row[BUY_IDX]
|
||||
detail_data['sell'] = sell_row[SELL_IDX]
|
||||
headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high']
|
||||
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) -> Optional[LocalTrade]:
|
||||
try:
|
||||
stake_amount = self.wallets.get_trade_stake_amount(pair, None)
|
||||
@ -594,6 +649,7 @@ class Backtesting:
|
||||
data: Dict[str, Any] = {}
|
||||
|
||||
data, timerange = self.load_bt_data()
|
||||
self.load_bt_data_detail()
|
||||
logger.info("Dataload complete. Calculating indicators")
|
||||
|
||||
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']
|
||||
if config['max_open_trades'] != float('inf') else -1),
|
||||
'timeframe': config['timeframe'],
|
||||
'timeframe_detail': config.get('timeframe_detail', ''),
|
||||
'timerange': config.get('timerange', ''),
|
||||
'enable_protections': config.get('enable_protections', False),
|
||||
'strategy_name': strategy,
|
||||
|
@ -13,7 +13,7 @@ from sqlalchemy.orm import Query, declarative_base, relationship, scoped_session
|
||||
from sqlalchemy.pool import StaticPool
|
||||
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 Collateral, SellType, TradingMode
|
||||
from freqtrade.exceptions import DependencyException, OperationalException
|
||||
from freqtrade.leverage import interest, liquidation_price
|
||||
@ -164,7 +164,7 @@ class Order(_DECL_BASE):
|
||||
self.order_date = datetime.fromtimestamp(order['timestamp'] / 1000, tz=timezone.utc)
|
||||
|
||||
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
|
||||
if (order.get('filled', 0.0) or 0.0) > 0:
|
||||
self.order_filled_date = datetime.now(timezone.utc)
|
||||
|
@ -17,7 +17,7 @@ def expand_pairlist(wildcardpl: List[str], available_pairs: List[str],
|
||||
if keep_invalid:
|
||||
for pair_wc in wildcardpl:
|
||||
try:
|
||||
comp = re.compile(pair_wc)
|
||||
comp = re.compile(pair_wc, re.IGNORECASE)
|
||||
result_partial = [
|
||||
pair for pair in available_pairs if re.fullmatch(comp, pair)
|
||||
]
|
||||
@ -33,7 +33,7 @@ def expand_pairlist(wildcardpl: List[str], available_pairs: List[str],
|
||||
else:
|
||||
for pair_wc in wildcardpl:
|
||||
try:
|
||||
comp = re.compile(pair_wc)
|
||||
comp = re.compile(pair_wc, re.IGNORECASE)
|
||||
result += [
|
||||
pair for pair in available_pairs if re.fullmatch(comp, pair)
|
||||
]
|
||||
|
@ -46,11 +46,14 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac
|
||||
if (
|
||||
not ApiServer._bt
|
||||
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('timerange') != btconfig['timerange']
|
||||
):
|
||||
from freqtrade.optimize.backtesting import Backtesting
|
||||
ApiServer._bt = Backtesting(btconfig)
|
||||
if ApiServer._bt.timeframe_detail:
|
||||
ApiServer._bt.load_bt_data_detail()
|
||||
|
||||
# Only reload data if timeframe changed.
|
||||
if (
|
||||
|
@ -324,6 +324,7 @@ class PairHistory(BaseModel):
|
||||
class BacktestRequest(BaseModel):
|
||||
strategy: str
|
||||
timeframe: Optional[str]
|
||||
timeframe_detail: Optional[str]
|
||||
timerange: Optional[str]
|
||||
max_open_trades: Optional[int]
|
||||
stake_amount: Optional[Union[float, str]]
|
||||
|
@ -5,6 +5,20 @@ import time
|
||||
import uvicorn
|
||||
|
||||
|
||||
def asyncio_setup() -> None: # pragma: no cover
|
||||
# Set eventloop for win32 setups
|
||||
# Reverts a change done in uvicorn 0.15.0 - which now sets the eventloop
|
||||
# via policy.
|
||||
import sys
|
||||
|
||||
if sys.version_info >= (3, 8) and sys.platform == "win32":
|
||||
import asyncio
|
||||
import selectors
|
||||
selector = selectors.SelectSelector()
|
||||
loop = asyncio.SelectorEventLoop(selector)
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
|
||||
class UvicornServer(uvicorn.Server):
|
||||
"""
|
||||
Multithreaded server - as found in https://github.com/encode/uvicorn/issues/742
|
||||
@ -28,7 +42,7 @@ class UvicornServer(uvicorn.Server):
|
||||
try:
|
||||
import uvloop # noqa
|
||||
except ImportError: # pragma: no cover
|
||||
from uvicorn.loops.asyncio import asyncio_setup
|
||||
|
||||
asyncio_setup()
|
||||
else:
|
||||
asyncio.set_event_loop(uvloop.new_event_loop())
|
||||
|
@ -403,8 +403,11 @@ class RPC:
|
||||
# Doing the sum is not right - overall profit needs to be based on initial capital
|
||||
profit_all_ratio_sum = sum(profit_all_ratio) if profit_all_ratio else 0.0
|
||||
starting_balance = self._freqtrade.wallets.get_starting_balance()
|
||||
profit_closed_ratio_fromstart = profit_closed_coin_sum / starting_balance
|
||||
profit_all_ratio_fromstart = profit_all_coin_sum / starting_balance
|
||||
profit_closed_ratio_fromstart = 0
|
||||
profit_all_ratio_fromstart = 0
|
||||
if starting_balance:
|
||||
profit_closed_ratio_fromstart = profit_closed_coin_sum / starting_balance
|
||||
profit_all_ratio_fromstart = profit_all_coin_sum / starting_balance
|
||||
|
||||
profit_all_fiat = self._fiat_converter.convert_amount(
|
||||
profit_all_coin_sum,
|
||||
@ -545,25 +548,25 @@ class RPC:
|
||||
order = self._freqtrade.exchange.fetch_order(trade.open_order_id, trade.pair)
|
||||
|
||||
if order['side'] == 'buy':
|
||||
fully_canceled = self._freqtrade.handle_cancel_buy(
|
||||
fully_canceled = self._freqtrade.handle_cancel_enter(
|
||||
trade, order, CANCEL_REASON['FORCE_SELL'])
|
||||
|
||||
if order['side'] == 'sell':
|
||||
# Cancel order - so it is placed anew with a fresh price.
|
||||
self._freqtrade.handle_cancel_sell(trade, order, CANCEL_REASON['FORCE_SELL'])
|
||||
self._freqtrade.handle_cancel_exit(trade, order, CANCEL_REASON['FORCE_SELL'])
|
||||
|
||||
if not fully_canceled:
|
||||
# Get current rate and execute sell
|
||||
current_rate = self._freqtrade.exchange.get_rate(
|
||||
trade.pair, refresh=False, side="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 ----
|
||||
|
||||
if self._freqtrade.state != State.RUNNING:
|
||||
raise RPCException('trader is not running')
|
||||
|
||||
with self._freqtrade._sell_lock:
|
||||
with self._freqtrade._exit_lock:
|
||||
if trade_id == 'all':
|
||||
# Execute sell for all open orders
|
||||
for trade in Trade.get_open_trades():
|
||||
@ -613,7 +616,7 @@ class RPC:
|
||||
stakeamount = self._freqtrade.wallets.get_trade_stake_amount(pair)
|
||||
|
||||
# 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 = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first()
|
||||
return trade
|
||||
@ -625,7 +628,7 @@ class RPC:
|
||||
Handler for delete <id>.
|
||||
Delete the given trade and close eventually existing open orders.
|
||||
"""
|
||||
with self._freqtrade._sell_lock:
|
||||
with self._freqtrade._exit_lock:
|
||||
c_count = 0
|
||||
trade = Trade.get_trades(trade_filter=[Trade.id == trade_id]).first()
|
||||
if not trade:
|
||||
|
@ -120,6 +120,8 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
# and wallets - access to the current balance.
|
||||
dp: Optional[DataProvider] = None
|
||||
wallets: Optional[Wallets] = None
|
||||
# Filled from configuration
|
||||
stake_currency: str
|
||||
# container variable for strategy source code
|
||||
__source__: str = ''
|
||||
|
||||
|
@ -1,3 +1,10 @@
|
||||
{%set volume_pairlist = '{
|
||||
"method": "VolumePairList",
|
||||
"number_assets": 20,
|
||||
"sort_key": "quoteVolume",
|
||||
"min_value": 0,
|
||||
"refresh_period": 1800
|
||||
}' %}
|
||||
{
|
||||
"max_open_trades": {{ max_open_trades }},
|
||||
"stake_currency": "{{ stake_currency }}",
|
||||
@ -29,7 +36,7 @@
|
||||
},
|
||||
{{ exchange | indent(4) }},
|
||||
"pairlists": [
|
||||
{"method": "StaticPairList"}
|
||||
{{ '{"method": "StaticPairList"}' if exchange_name == 'bittrex' else volume_pairlist }}
|
||||
],
|
||||
"edge": {
|
||||
"enabled": false,
|
||||
|
@ -8,34 +8,8 @@
|
||||
"rateLimit": 200
|
||||
},
|
||||
"pair_whitelist": [
|
||||
"ALGO/BTC",
|
||||
"ATOM/BTC",
|
||||
"BAT/BTC",
|
||||
"BCH/BTC",
|
||||
"BRD/BTC",
|
||||
"EOS/BTC",
|
||||
"ETH/BTC",
|
||||
"IOTA/BTC",
|
||||
"LINK/BTC",
|
||||
"LTC/BTC",
|
||||
"NEO/BTC",
|
||||
"NXS/BTC",
|
||||
"XMR/BTC",
|
||||
"XRP/BTC",
|
||||
"XTZ/BTC"
|
||||
],
|
||||
"pair_blacklist": [
|
||||
"BNB/BTC",
|
||||
"BNB/BUSD",
|
||||
"BNB/ETH",
|
||||
"BNB/EUR",
|
||||
"BNB/NGN",
|
||||
"BNB/PAX",
|
||||
"BNB/RUB",
|
||||
"BNB/TRY",
|
||||
"BNB/TUSD",
|
||||
"BNB/USDC",
|
||||
"BNB/USDS",
|
||||
"BNB/USDT",
|
||||
"BNB/.*"
|
||||
]
|
||||
}
|
||||
|
@ -15,16 +15,6 @@
|
||||
"rateLimit": 500
|
||||
},
|
||||
"pair_whitelist": [
|
||||
"ETH/BTC",
|
||||
"LTC/BTC",
|
||||
"ETC/BTC",
|
||||
"DASH/BTC",
|
||||
"ZEC/BTC",
|
||||
"XLM/BTC",
|
||||
"XRP/BTC",
|
||||
"TRX/BTC",
|
||||
"ADA/BTC",
|
||||
"XMR/BTC"
|
||||
],
|
||||
"pair_blacklist": [
|
||||
]
|
||||
|
@ -7,28 +7,10 @@
|
||||
"ccxt_async_config": {
|
||||
"enableRateLimit": true,
|
||||
"rateLimit": 1000
|
||||
// Enable the below for downoading data.
|
||||
//"rateLimit": 3100
|
||||
},
|
||||
"pair_whitelist": [
|
||||
"ADA/EUR",
|
||||
"ATOM/EUR",
|
||||
"BAT/EUR",
|
||||
"BCH/EUR",
|
||||
"BTC/EUR",
|
||||
"DAI/EUR",
|
||||
"DASH/EUR",
|
||||
"EOS/EUR",
|
||||
"ETC/EUR",
|
||||
"ETH/EUR",
|
||||
"LINK/EUR",
|
||||
"LTC/EUR",
|
||||
"QTUM/EUR",
|
||||
"REP/EUR",
|
||||
"WAVES/EUR",
|
||||
"XLM/EUR",
|
||||
"XMR/EUR",
|
||||
"XRP/EUR",
|
||||
"XTZ/EUR",
|
||||
"ZEC/EUR"
|
||||
],
|
||||
"pair_blacklist": [
|
||||
|
||||
|
18
freqtrade/templates/subtemplates/exchange_kucoin.j2
Normal file
18
freqtrade/templates/subtemplates/exchange_kucoin.j2
Normal file
@ -0,0 +1,18 @@
|
||||
"exchange": {
|
||||
"name": "{{ exchange_name | lower }}",
|
||||
"key": "{{ exchange_key }}",
|
||||
"secret": "{{ exchange_secret }}",
|
||||
"password": "{{ exchange_key_password }}",
|
||||
"ccxt_config": {
|
||||
"enableRateLimit": true
|
||||
"rateLimit": 200
|
||||
},
|
||||
"ccxt_async_config": {
|
||||
"enableRateLimit": true,
|
||||
"rateLimit": 200
|
||||
},
|
||||
"pair_whitelist": [
|
||||
],
|
||||
"pair_blacklist": [
|
||||
]
|
||||
}
|
72
mkdocs.yml
72
mkdocs.yml
@ -3,42 +3,42 @@ site_url: https://www.freqtrade.io/
|
||||
repo_url: https://github.com/freqtrade/freqtrade
|
||||
use_directory_urls: True
|
||||
nav:
|
||||
- Home: index.md
|
||||
- Quickstart with Docker: docker_quickstart.md
|
||||
- Installation:
|
||||
- Linux/MacOS/Raspberry: installation.md
|
||||
- Windows: windows_installation.md
|
||||
- Freqtrade Basics: bot-basics.md
|
||||
- Configuration: configuration.md
|
||||
- Strategy Customization: strategy-customization.md
|
||||
- Plugins: plugins.md
|
||||
- Stoploss: stoploss.md
|
||||
- Start the bot: bot-usage.md
|
||||
- Control the bot:
|
||||
- Telegram: telegram-usage.md
|
||||
- REST API & FreqUI: rest-api.md
|
||||
- Web Hook: webhook-config.md
|
||||
- Data Downloading: data-download.md
|
||||
- Backtesting: backtesting.md
|
||||
- Leverage: leverage.md
|
||||
- Hyperopt: hyperopt.md
|
||||
- Utility Sub-commands: utils.md
|
||||
- Plotting: plotting.md
|
||||
- Data Analysis:
|
||||
- Jupyter Notebooks: data-analysis.md
|
||||
- Strategy analysis: strategy_analysis_example.md
|
||||
- Exchange-specific Notes: exchanges.md
|
||||
- Advanced Topics:
|
||||
- Advanced Post-installation Tasks: advanced-setup.md
|
||||
- Edge Positioning: edge.md
|
||||
- Advanced Strategy: strategy-advanced.md
|
||||
- Advanced Hyperopt: advanced-hyperopt.md
|
||||
- Sandbox Testing: sandbox-testing.md
|
||||
- FAQ: faq.md
|
||||
- SQL Cheat-sheet: sql_cheatsheet.md
|
||||
- Updating Freqtrade: updating.md
|
||||
- Deprecated Features: deprecated.md
|
||||
- Contributors Guide: developer.md
|
||||
- Home: index.md
|
||||
- Quickstart with Docker: docker_quickstart.md
|
||||
- Installation:
|
||||
- Linux/MacOS/Raspberry: installation.md
|
||||
- Windows: windows_installation.md
|
||||
- Freqtrade Basics: bot-basics.md
|
||||
- Configuration: configuration.md
|
||||
- Strategy Customization: strategy-customization.md
|
||||
- Plugins: plugins.md
|
||||
- Stoploss: stoploss.md
|
||||
- Start the bot: bot-usage.md
|
||||
- Control the bot:
|
||||
- Telegram: telegram-usage.md
|
||||
- REST API & FreqUI: rest-api.md
|
||||
- Web Hook: webhook-config.md
|
||||
- Data Downloading: data-download.md
|
||||
- Backtesting: backtesting.md
|
||||
- Hyperopt: hyperopt.md
|
||||
- Leverage: leverage.md
|
||||
- Utility Sub-commands: utils.md
|
||||
- Plotting: plotting.md
|
||||
- Exchange-specific Notes: exchanges.md
|
||||
- Data Analysis:
|
||||
- Jupyter Notebooks: data-analysis.md
|
||||
- Strategy analysis: strategy_analysis_example.md
|
||||
- Advanced Topics:
|
||||
- Advanced Post-installation Tasks: advanced-setup.md
|
||||
- Edge Positioning: edge.md
|
||||
- Advanced Strategy: strategy-advanced.md
|
||||
- Advanced Hyperopt: advanced-hyperopt.md
|
||||
- Sandbox Testing: sandbox-testing.md
|
||||
- FAQ: faq.md
|
||||
- SQL Cheat-sheet: sql_cheatsheet.md
|
||||
- Updating Freqtrade: updating.md
|
||||
- Deprecated Features: deprecated.md
|
||||
- Contributors Guide: developer.md
|
||||
theme:
|
||||
name: material
|
||||
logo: "images/logo.png"
|
||||
|
@ -8,7 +8,7 @@ flake8==3.9.2
|
||||
flake8-type-annotations==0.1.0
|
||||
flake8-tidy-imports==4.4.1
|
||||
mypy==0.910
|
||||
pytest==6.2.4
|
||||
pytest==6.2.5
|
||||
pytest-asyncio==0.15.1
|
||||
pytest-cov==2.12.1
|
||||
pytest-mock==3.6.1
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Include all requirements to run the bot.
|
||||
-r requirements.txt
|
||||
|
||||
plotly==5.2.1
|
||||
plotly==5.3.1
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
numpy==1.21.2
|
||||
pandas==1.3.2
|
||||
|
||||
ccxt==1.55.28
|
||||
ccxt==1.55.83
|
||||
# Pin cryptography for now due to rust build errors with piwheels
|
||||
cryptography==3.4.7
|
||||
cryptography==3.4.8
|
||||
aiohttp==3.7.4.post0
|
||||
SQLAlchemy==1.4.23
|
||||
python-telegram-bot==13.7
|
||||
@ -31,7 +31,7 @@ python-rapidjson==1.4
|
||||
sdnotify==0.3.2
|
||||
|
||||
# API Server
|
||||
fastapi==0.68.0
|
||||
fastapi==0.68.1
|
||||
uvicorn==0.15.0
|
||||
pyjwt==2.1.0
|
||||
aiofiles==0.7.0
|
||||
|
16
setup.sh
16
setup.sh
@ -95,19 +95,7 @@ function install_talib() {
|
||||
return
|
||||
fi
|
||||
|
||||
cd build_helpers
|
||||
tar zxvf ta-lib-0.4.0-src.tar.gz
|
||||
cd ta-lib
|
||||
sed -i.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h
|
||||
./configure --prefix=/usr/local
|
||||
make
|
||||
sudo make install
|
||||
if [ -x "$(command -v apt-get)" ]; then
|
||||
echo "Updating library path using ldconfig"
|
||||
sudo ldconfig
|
||||
fi
|
||||
cd .. && rm -rf ./ta-lib/
|
||||
cd ..
|
||||
cd build_helpers && ./install_ta-lib.sh && cd ..
|
||||
}
|
||||
|
||||
function install_mac_newer_python_dependencies() {
|
||||
@ -119,6 +107,7 @@ function install_mac_newer_python_dependencies() {
|
||||
echo "-------------------------"
|
||||
brew install hdf5
|
||||
fi
|
||||
export HDF5_DIR=$(brew --prefix)
|
||||
|
||||
if [ ! $(brew --prefix --installed c-blosc 2>/dev/null) ]
|
||||
then
|
||||
@ -127,6 +116,7 @@ function install_mac_newer_python_dependencies() {
|
||||
echo "-------------------------"
|
||||
brew install c-blosc
|
||||
fi
|
||||
export CBLOSC_DIR=$(brew --prefix)
|
||||
}
|
||||
|
||||
# Install bot MacOS
|
||||
|
@ -510,17 +510,6 @@ def test_start_new_strategy(mocker, caplog):
|
||||
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):
|
||||
args = [
|
||||
"new-strategy",
|
||||
@ -552,17 +541,6 @@ def test_start_new_hyperopt(mocker, caplog):
|
||||
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):
|
||||
args = [
|
||||
"new-hyperopt",
|
||||
@ -827,9 +805,9 @@ def test_start_list_strategies(mocker, caplog, capsys):
|
||||
# pargs['config'] = None
|
||||
start_list_strategies(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert "TestStrategyLegacy" in captured.out
|
||||
assert "legacy_strategy.py" not in captured.out
|
||||
assert "DefaultStrategy" in captured.out
|
||||
assert "TestStrategyLegacyV1" in captured.out
|
||||
assert "legacy_strategy_v1.py" not in captured.out
|
||||
assert "StrategyTestV2" in captured.out
|
||||
|
||||
# Test regular output
|
||||
args = [
|
||||
@ -842,9 +820,9 @@ def test_start_list_strategies(mocker, caplog, capsys):
|
||||
# pargs['config'] = None
|
||||
start_list_strategies(pargs)
|
||||
captured = capsys.readouterr()
|
||||
assert "TestStrategyLegacy" in captured.out
|
||||
assert "legacy_strategy.py" in captured.out
|
||||
assert "DefaultStrategy" in captured.out
|
||||
assert "TestStrategyLegacyV1" in captured.out
|
||||
assert "legacy_strategy_v1.py" in captured.out
|
||||
assert "StrategyTestV2" in captured.out
|
||||
|
||||
|
||||
def test_start_list_hyperopts(mocker, caplog, capsys):
|
||||
@ -861,7 +839,7 @@ def test_start_list_hyperopts(mocker, caplog, capsys):
|
||||
captured = capsys.readouterr()
|
||||
assert "TestHyperoptLegacy" 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
|
||||
|
||||
# Test regular output
|
||||
@ -876,7 +854,7 @@ def test_start_list_hyperopts(mocker, caplog, capsys):
|
||||
captured = capsys.readouterr()
|
||||
assert "TestHyperoptLegacy" 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):
|
||||
|
@ -360,7 +360,7 @@ def get_default_conf(testdatadir):
|
||||
"user_data_dir": Path("user_data"),
|
||||
"verbosity": 3,
|
||||
"strategy_path": str(Path(__file__).parent / "strategy" / "strats"),
|
||||
"strategy": "DefaultStrategy",
|
||||
"strategy": "StrategyTestV2",
|
||||
"disableparamexport": True,
|
||||
"internals": {},
|
||||
"export": "none",
|
||||
|
@ -33,7 +33,7 @@ def mock_trade_1(fee):
|
||||
open_rate=0.123,
|
||||
exchange='binance',
|
||||
open_order_id='dry_run_buy_12345',
|
||||
strategy='DefaultStrategy',
|
||||
strategy='StrategyTestV2',
|
||||
timeframe=5,
|
||||
)
|
||||
o = Order.parse_from_ccxt_object(mock_order_1(), 'ETH/BTC', 'buy')
|
||||
@ -87,7 +87,7 @@ def mock_trade_2(fee):
|
||||
exchange='binance',
|
||||
is_open=False,
|
||||
open_order_id='dry_run_sell_12345',
|
||||
strategy='DefaultStrategy',
|
||||
strategy='StrategyTestV2',
|
||||
timeframe=5,
|
||||
sell_reason='sell_signal',
|
||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
||||
@ -146,7 +146,7 @@ def mock_trade_3(fee):
|
||||
close_profit_abs=0.000155,
|
||||
exchange='binance',
|
||||
is_open=False,
|
||||
strategy='DefaultStrategy',
|
||||
strategy='StrategyTestV2',
|
||||
timeframe=5,
|
||||
sell_reason='roi',
|
||||
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
|
||||
@ -189,7 +189,7 @@ def mock_trade_4(fee):
|
||||
open_rate=0.123,
|
||||
exchange='binance',
|
||||
open_order_id='prod_buy_12345',
|
||||
strategy='DefaultStrategy',
|
||||
strategy='StrategyTestV2',
|
||||
timeframe=5,
|
||||
)
|
||||
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):
|
||||
|
||||
filename = testdatadir / "backtest-result_multistrat.json"
|
||||
for strategy in ('DefaultStrategy', 'TestStrategy'):
|
||||
for strategy in ('StrategyTestV2', 'TestStrategy'):
|
||||
bt_data = load_backtest_data(filename, strategy=strategy)
|
||||
assert isinstance(bt_data, DataFrame)
|
||||
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:
|
||||
if col not in ['index', 'open_at_end']:
|
||||
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
|
||||
trades = load_trades_from_db(db_url=default_conf['db_url'], strategy='NoneStrategy')
|
||||
assert len(trades) == 0
|
||||
@ -186,7 +186,7 @@ def test_load_trades(default_conf, mocker):
|
||||
db_url=default_conf.get('db_url'),
|
||||
exportfilename=default_conf.get('exportfilename'),
|
||||
no_trades=False,
|
||||
strategy="DefaultStrategy",
|
||||
strategy="StrategyTestV2",
|
||||
)
|
||||
|
||||
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:
|
||||
patch_exchange(mocker)
|
||||
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
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:
|
||||
patch_exchange(mocker)
|
||||
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
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:
|
||||
patch_exchange(mocker)
|
||||
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
timerange = TimeRange('index', 'index', 200, 250)
|
||||
|
@ -108,6 +108,13 @@ def test_init_ccxt_kwargs(default_conf, mocker, caplog):
|
||||
assert hasattr(ex._api_async, 'TestKWARG')
|
||||
assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog)
|
||||
assert log_has(asynclogmsg, caplog)
|
||||
# Test additional headers case
|
||||
Exchange._headers = {'hello': 'world'}
|
||||
ex = Exchange(conf)
|
||||
|
||||
assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog)
|
||||
assert ex._api.headers == {'hello': 'world'}
|
||||
Exchange._headers = {}
|
||||
|
||||
|
||||
def test_destroy(default_conf, mocker, caplog):
|
||||
@ -557,7 +564,7 @@ def test_reload_markets_exception(default_conf, mocker, caplog):
|
||||
|
||||
|
||||
@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
|
||||
api_mock = MagicMock()
|
||||
type(api_mock).load_markets = MagicMock(return_value={
|
||||
@ -571,7 +578,7 @@ def test_validate_stake_currency(default_conf, stake_currency, mocker, caplog):
|
||||
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'
|
||||
api_mock = MagicMock()
|
||||
type(api_mock).load_markets = MagicMock(return_value={
|
||||
@ -587,6 +594,13 @@ def test_validate_stake_currency_error(default_conf, mocker, caplog):
|
||||
'Available currencies are: BTC, ETH, USDT'):
|
||||
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):
|
||||
ex = get_patched_exchange(mocker, default_conf)
|
||||
|
@ -16,7 +16,7 @@ def hyperopt_conf(default_conf):
|
||||
hyperconf.update({
|
||||
'datadir': Path(default_conf['datadir']),
|
||||
'runmode': RunMode.HYPEROPT,
|
||||
'hyperopt': 'DefaultHyperOpt',
|
||||
'hyperopt': 'HyperoptTestSepFile',
|
||||
'hyperopt_loss': 'ShortTradeDurHyperOptLoss',
|
||||
'hyperopt_path': str(Path(__file__).parent / 'hyperopts'),
|
||||
'epochs': 1,
|
||||
|
@ -11,7 +11,7 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
from freqtrade.optimize.hyperopt_interface import IHyperOpt
|
||||
|
||||
|
||||
class DefaultHyperOpt(IHyperOpt):
|
||||
class HyperoptTestSepFile(IHyperOpt):
|
||||
"""
|
||||
Default hyperopt provided by the Freqtrade bot.
|
||||
You can override it with your own Hyperopt
|
@ -1,7 +1,7 @@
|
||||
# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, unused-argument
|
||||
|
||||
import random
|
||||
from datetime import timedelta
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, PropertyMock
|
||||
|
||||
@ -155,7 +155,7 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca
|
||||
args = [
|
||||
'backtesting',
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'--strategy', 'StrategyTestV2',
|
||||
'--export', 'none'
|
||||
]
|
||||
|
||||
@ -190,7 +190,7 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
|
||||
args = [
|
||||
'backtesting',
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'--strategy', 'StrategyTestV2',
|
||||
'--datadir', '/foo/bar',
|
||||
'--timeframe', '1m',
|
||||
'--enable-position-stacking',
|
||||
@ -240,7 +240,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog)
|
||||
args = [
|
||||
'backtesting',
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'--strategy', 'StrategyTestV2',
|
||||
'--stake-amount', '1',
|
||||
'--starting-balance', '2'
|
||||
]
|
||||
@ -251,7 +251,7 @@ def test_setup_optimize_configuration_stake_amount(mocker, default_conf, caplog)
|
||||
args = [
|
||||
'backtesting',
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'--strategy', 'StrategyTestV2',
|
||||
'--stake-amount', '1',
|
||||
'--starting-balance', '0.5'
|
||||
]
|
||||
@ -269,7 +269,7 @@ def test_start(mocker, fee, default_conf, caplog) -> None:
|
||||
args = [
|
||||
'backtesting',
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'--strategy', 'StrategyTestV2',
|
||||
]
|
||||
pargs = get_args(args)
|
||||
start_backtesting(pargs)
|
||||
@ -302,7 +302,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None:
|
||||
def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None:
|
||||
patch_exchange(mocker)
|
||||
del default_conf['timeframe']
|
||||
default_conf['strategy_list'] = ['DefaultStrategy',
|
||||
default_conf['strategy_list'] = ['StrategyTestV2',
|
||||
'SampleStrategy']
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
|
||||
@ -340,7 +340,7 @@ def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None:
|
||||
assert len(processed['UNITTEST/BTC']) == 102
|
||||
|
||||
# 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)
|
||||
|
||||
processed2 = strategy.advise_all_indicators(data)
|
||||
@ -441,6 +441,15 @@ def test_backtesting_no_pair_left(default_conf, mocker, caplog, testdatadir) ->
|
||||
with pytest.raises(OperationalException, match='VolumePairList not allowed for backtesting.'):
|
||||
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:
|
||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
||||
@ -473,7 +482,7 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti
|
||||
Backtesting(default_conf)
|
||||
|
||||
# Multiple strategies
|
||||
default_conf['strategy_list'] = ['DefaultStrategy', 'TestStrategyLegacy']
|
||||
default_conf['strategy_list'] = ['StrategyTestV2', 'TestStrategyLegacyV1']
|
||||
with pytest.raises(OperationalException,
|
||||
match='PrecisionFilter not allowed for backtesting multiple strategies.'):
|
||||
Backtesting(default_conf)
|
||||
@ -491,7 +500,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
|
||||
pair = 'UNITTEST/BTC'
|
||||
row = [
|
||||
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0),
|
||||
1, # Sell
|
||||
1, # Buy
|
||||
0.001, # Open
|
||||
0.0011, # Close
|
||||
0, # Sell
|
||||
@ -539,6 +548,88 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
|
||||
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:
|
||||
default_conf['use_sell_signal'] = False
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
|
||||
@ -694,7 +785,7 @@ def test_backtest_pricecontours(default_conf, fee, mocker, 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):
|
||||
buy_value = 1
|
||||
sell_value = 1
|
||||
@ -710,7 +801,7 @@ def test_backtest_clash_buy_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):
|
||||
buy_value = 0
|
||||
sell_value = 1
|
||||
@ -837,7 +928,7 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
|
||||
args = [
|
||||
'backtesting',
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'--strategy', 'StrategyTestV2',
|
||||
'--datadir', str(testdatadir),
|
||||
'--timeframe', '1m',
|
||||
'--timerange', '1510694220-1510700340',
|
||||
@ -908,8 +999,8 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||
'--enable-position-stacking',
|
||||
'--disable-max-market-positions',
|
||||
'--strategy-list',
|
||||
'DefaultStrategy',
|
||||
'TestStrategyLegacy',
|
||||
'StrategyTestV2',
|
||||
'TestStrategyLegacyV1',
|
||||
]
|
||||
args = get_args(args)
|
||||
start_backtesting(args)
|
||||
@ -931,8 +1022,8 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
|
||||
'Backtesting with data from 2017-11-14 21:17:00 '
|
||||
'up to 2017-11-14 22:58:00 (0 days).',
|
||||
'Parameter --enable-position-stacking detected ...',
|
||||
'Running backtesting for Strategy DefaultStrategy',
|
||||
'Running backtesting for Strategy TestStrategyLegacy',
|
||||
'Running backtesting for Strategy StrategyTestV2',
|
||||
'Running backtesting for Strategy TestStrategyLegacyV1',
|
||||
]
|
||||
|
||||
for line in exists:
|
||||
@ -1012,8 +1103,8 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
|
||||
'--enable-position-stacking',
|
||||
'--disable-max-market-positions',
|
||||
'--strategy-list',
|
||||
'DefaultStrategy',
|
||||
'TestStrategyLegacy',
|
||||
'StrategyTestV2',
|
||||
'TestStrategyLegacyV1',
|
||||
]
|
||||
args = get_args(args)
|
||||
start_backtesting(args)
|
||||
@ -1029,8 +1120,8 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
|
||||
'Backtesting with data from 2017-11-14 21:17:00 '
|
||||
'up to 2017-11-14 22:58:00 (0 days).',
|
||||
'Parameter --enable-position-stacking detected ...',
|
||||
'Running backtesting for Strategy DefaultStrategy',
|
||||
'Running backtesting for Strategy TestStrategyLegacy',
|
||||
'Running backtesting for Strategy StrategyTestV2',
|
||||
'Running backtesting for Strategy TestStrategyLegacyV1',
|
||||
]
|
||||
|
||||
for line in exists:
|
||||
@ -1042,3 +1133,102 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
|
||||
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 '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 = [
|
||||
'edge',
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'--strategy', 'StrategyTestV2',
|
||||
]
|
||||
|
||||
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 = [
|
||||
'edge',
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'--strategy', 'StrategyTestV2',
|
||||
'--datadir', '/foo/bar',
|
||||
'--timeframe', '1m',
|
||||
'--timerange', ':100',
|
||||
@ -80,7 +80,7 @@ def test_start(mocker, fee, edge_conf, caplog) -> None:
|
||||
args = [
|
||||
'edge',
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'--strategy', 'StrategyTestV2',
|
||||
]
|
||||
pargs = get_args(args)
|
||||
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,
|
||||
patched_configuration_load_config_file)
|
||||
|
||||
from .hyperopts.default_hyperopt import DefaultHyperOpt
|
||||
from .hyperopts.hyperopt_test_sep_file import HyperoptTestSepFile
|
||||
|
||||
|
||||
def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None:
|
||||
@ -31,7 +31,7 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca
|
||||
args = [
|
||||
'hyperopt',
|
||||
'--config', 'config.json',
|
||||
'--hyperopt', 'DefaultHyperOpt',
|
||||
'--hyperopt', 'HyperoptTestSepFile',
|
||||
]
|
||||
|
||||
config = setup_optimize_configuration(get_args(args), RunMode.HYPEROPT)
|
||||
@ -63,7 +63,7 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo
|
||||
args = [
|
||||
'hyperopt',
|
||||
'--config', 'config.json',
|
||||
'--hyperopt', 'DefaultHyperOpt',
|
||||
'--hyperopt', 'HyperoptTestSepFile',
|
||||
'--datadir', '/foo/bar',
|
||||
'--timeframe', '1m',
|
||||
'--timerange', ':100',
|
||||
@ -115,7 +115,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None
|
||||
args = [
|
||||
'hyperopt',
|
||||
'--config', 'config.json',
|
||||
'--hyperopt', 'DefaultHyperOpt',
|
||||
'--hyperopt', 'HyperoptTestSepFile',
|
||||
'--stake-amount', '1',
|
||||
'--starting-balance', '2'
|
||||
]
|
||||
@ -125,7 +125,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None
|
||||
args = [
|
||||
'hyperopt',
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'--strategy', 'StrategyTestV2',
|
||||
'--stake-amount', '1',
|
||||
'--starting-balance', '0.5'
|
||||
]
|
||||
@ -136,7 +136,7 @@ def test_setup_hyperopt_configuration_stake_amount(mocker, default_conf) -> None
|
||||
def test_hyperoptresolver(mocker, default_conf, caplog) -> None:
|
||||
patched_configuration_load_config_file(mocker, default_conf)
|
||||
|
||||
hyperopt = DefaultHyperOpt
|
||||
hyperopt = HyperoptTestSepFile
|
||||
delattr(hyperopt, 'populate_indicators')
|
||||
delattr(hyperopt, 'populate_buy_trend')
|
||||
delattr(hyperopt, 'populate_sell_trend')
|
||||
@ -144,7 +144,7 @@ def test_hyperoptresolver(mocker, default_conf, caplog) -> None:
|
||||
'freqtrade.resolvers.hyperopt_resolver.HyperOptResolver.load_object',
|
||||
MagicMock(return_value=hyperopt(default_conf))
|
||||
)
|
||||
default_conf.update({'hyperopt': 'DefaultHyperOpt'})
|
||||
default_conf.update({'hyperopt': 'HyperoptTestSepFile'})
|
||||
x = HyperOptResolver.load_hyperopt(default_conf)
|
||||
assert not hasattr(x, 'populate_indicators')
|
||||
assert not hasattr(x, 'populate_buy_trend')
|
||||
@ -184,7 +184,7 @@ def test_start_not_installed(mocker, default_conf, import_fails) -> None:
|
||||
args = [
|
||||
'hyperopt',
|
||||
'--config', 'config.json',
|
||||
'--hyperopt', 'DefaultHyperOpt',
|
||||
'--hyperopt', 'HyperoptTestSepFile',
|
||||
'--hyperopt-path',
|
||||
str(Path(__file__).parent / "hyperopts"),
|
||||
'--epochs', '5',
|
||||
@ -205,7 +205,7 @@ def test_start(mocker, hyperopt_conf, caplog) -> None:
|
||||
args = [
|
||||
'hyperopt',
|
||||
'--config', 'config.json',
|
||||
'--hyperopt', 'DefaultHyperOpt',
|
||||
'--hyperopt', 'HyperoptTestSepFile',
|
||||
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
|
||||
'--epochs', '5'
|
||||
]
|
||||
@ -229,7 +229,7 @@ def test_start_no_data(mocker, hyperopt_conf) -> None:
|
||||
args = [
|
||||
'hyperopt',
|
||||
'--config', 'config.json',
|
||||
'--hyperopt', 'DefaultHyperOpt',
|
||||
'--hyperopt', 'HyperoptTestSepFile',
|
||||
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
|
||||
'--epochs', '5'
|
||||
]
|
||||
@ -247,7 +247,7 @@ def test_start_filelock(mocker, hyperopt_conf, caplog) -> None:
|
||||
args = [
|
||||
'hyperopt',
|
||||
'--config', 'config.json',
|
||||
'--hyperopt', 'DefaultHyperOpt',
|
||||
'--hyperopt', 'HyperoptTestSepFile',
|
||||
'--hyperopt-loss', 'SharpeHyperOptLossDaily',
|
||||
'--epochs', '5'
|
||||
]
|
||||
|
@ -167,9 +167,9 @@ def test__pprint_dict():
|
||||
|
||||
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 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')
|
||||
assert x is None
|
||||
@ -177,7 +177,7 @@ def test_get_strategy_filename(default_conf):
|
||||
|
||||
def test_export_params(tmpdir):
|
||||
|
||||
filename = Path(tmpdir) / "DefaultStrategy.json"
|
||||
filename = Path(tmpdir) / "StrategyTestV2.json"
|
||||
assert not filename.is_file()
|
||||
params = {
|
||||
"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()
|
||||
|
||||
content = rapidjson.load(filename.open('r'))
|
||||
assert content['strategy_name'] == 'DefaultStrategy'
|
||||
assert content['strategy_name'] == 'StrategyTestV2'
|
||||
assert 'params' in content
|
||||
assert "buy" 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
|
||||
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()
|
||||
params = {
|
||||
"params_details": {
|
||||
@ -252,17 +252,17 @@ def test_try_export_params(default_conf, tmpdir, caplog, mocker):
|
||||
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 export_mock.call_count == 0
|
||||
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_args_list[0][0][1] == 'DefaultStrategy'
|
||||
assert export_mock.call_args_list[0][0][2].name == 'default_strategy.json'
|
||||
assert export_mock.call_args_list[0][0][1] == 'StrategyTestV2'
|
||||
assert export_mock.call_args_list[0][0][2].name == 'strategy_test_v2.json'
|
||||
|
||||
|
||||
def test_params_print(capsys):
|
||||
|
@ -4,7 +4,7 @@ from unittest.mock import MagicMock
|
||||
import pytest
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
@ -52,7 +52,7 @@ def test_text_table_bt_results():
|
||||
|
||||
|
||||
def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||
StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
results = {'DefStrat': {
|
||||
|
@ -879,7 +879,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
|
||||
'open_trade_value': 15.1668225,
|
||||
'sell_reason': None,
|
||||
'sell_order_status': None,
|
||||
'strategy': 'DefaultStrategy',
|
||||
'strategy': 'StrategyTestV2',
|
||||
'buy_tag': None,
|
||||
'timeframe': 5,
|
||||
'exchange': 'binance',
|
||||
@ -984,7 +984,7 @@ def test_api_forcebuy(botclient, mocker, fee):
|
||||
close_rate=0.265441,
|
||||
id=22,
|
||||
timeframe=5,
|
||||
strategy="DefaultStrategy"
|
||||
strategy="StrategyTestV2"
|
||||
))
|
||||
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,
|
||||
'sell_reason': None,
|
||||
'sell_order_status': None,
|
||||
'strategy': 'DefaultStrategy',
|
||||
'strategy': 'StrategyTestV2',
|
||||
'buy_tag': None,
|
||||
'timeframe': 5,
|
||||
'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}")
|
||||
assert_response(rc)
|
||||
assert 'strategy' in rc.json()
|
||||
assert rc.json()['strategy'] == 'DefaultStrategy'
|
||||
assert rc.json()['strategy'] == 'StrategyTestV2'
|
||||
assert 'columns' in rc.json()
|
||||
assert 'data_start_ts' in rc.json()
|
||||
assert 'data_start' in rc.json()
|
||||
@ -1139,19 +1139,19 @@ def test_api_pair_history(botclient, ohlcv_history):
|
||||
# No pair
|
||||
rc = client_get(client,
|
||||
f"{BASE_URI}/pair_history?timeframe={timeframe}"
|
||||
"&timerange=20180111-20180112&strategy=DefaultStrategy")
|
||||
"&timerange=20180111-20180112&strategy=StrategyTestV2")
|
||||
assert_response(rc, 422)
|
||||
|
||||
# No Timeframe
|
||||
rc = client_get(client,
|
||||
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC"
|
||||
"&timerange=20180111-20180112&strategy=DefaultStrategy")
|
||||
"&timerange=20180111-20180112&strategy=StrategyTestV2")
|
||||
assert_response(rc, 422)
|
||||
|
||||
# No timerange
|
||||
rc = client_get(client,
|
||||
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
|
||||
"&strategy=DefaultStrategy")
|
||||
"&strategy=StrategyTestV2")
|
||||
assert_response(rc, 422)
|
||||
|
||||
# No strategy
|
||||
@ -1163,14 +1163,14 @@ def test_api_pair_history(botclient, ohlcv_history):
|
||||
# Working
|
||||
rc = client_get(client,
|
||||
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 rc.json()['length'] == 289
|
||||
assert len(rc.json()['data']) == rc.json()['length']
|
||||
assert 'columns' in rc.json()
|
||||
assert 'data' in rc.json()
|
||||
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_ts'] == 1515628800000
|
||||
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
|
||||
rc = client_get(client,
|
||||
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 rc.json()['error'] == ("Error querying /api/v1/pair_history: "
|
||||
"No data for UNITTEST/BTC, 5m in 20200111-20200112 found.")
|
||||
@ -1217,21 +1217,21 @@ def test_api_strategies(botclient):
|
||||
|
||||
assert_response(rc)
|
||||
assert rc.json() == {'strategies': [
|
||||
'DefaultStrategy',
|
||||
'HyperoptableStrategy',
|
||||
'TestStrategyLegacy'
|
||||
'StrategyTestV2',
|
||||
'TestStrategyLegacyV1'
|
||||
]}
|
||||
|
||||
|
||||
def test_api_strategy(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 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
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/strategy/NoStrat")
|
||||
@ -1288,7 +1288,7 @@ def test_api_backtesting(botclient, mocker, fee, caplog):
|
||||
|
||||
# start backtesting
|
||||
data = {
|
||||
"strategy": "DefaultStrategy",
|
||||
"strategy": "StrategyTestV2",
|
||||
"timeframe": "5m",
|
||||
"timerange": "20180110-20180111",
|
||||
"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 '*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 '*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]
|
||||
|
||||
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 '*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 '*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]
|
||||
|
||||
|
||||
|
@ -5,5 +5,5 @@ import nonexiting_module # noqa
|
||||
from freqtrade.strategy.interface import IStrategy
|
||||
|
||||
|
||||
class TestStrategyLegacy(IStrategy):
|
||||
class TestStrategyLegacyV1(IStrategy):
|
||||
pass
|
||||
|
@ -10,7 +10,7 @@ from freqtrade.strategy.interface import IStrategy
|
||||
# --------------------------------
|
||||
|
||||
# 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
|
||||
removed in a future update.
|
@ -7,9 +7,9 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
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 look at the SampleStrategy in the user_data/strategy directory
|
||||
or strategy repository https://github.com/freqtrade/freqtrade-strategies
|
@ -4,20 +4,20 @@ from pandas import DataFrame
|
||||
|
||||
from freqtrade.persistence.models import Trade
|
||||
|
||||
from .strats.default_strategy import DefaultStrategy
|
||||
from .strats.strategy_test_v2 import StrategyTestV2
|
||||
|
||||
|
||||
def test_default_strategy_structure():
|
||||
assert hasattr(DefaultStrategy, 'minimal_roi')
|
||||
assert hasattr(DefaultStrategy, 'stoploss')
|
||||
assert hasattr(DefaultStrategy, 'timeframe')
|
||||
assert hasattr(DefaultStrategy, 'populate_indicators')
|
||||
assert hasattr(DefaultStrategy, 'populate_buy_trend')
|
||||
assert hasattr(DefaultStrategy, 'populate_sell_trend')
|
||||
def test_strategy_test_v2_structure():
|
||||
assert hasattr(StrategyTestV2, 'minimal_roi')
|
||||
assert hasattr(StrategyTestV2, 'stoploss')
|
||||
assert hasattr(StrategyTestV2, 'timeframe')
|
||||
assert hasattr(StrategyTestV2, 'populate_indicators')
|
||||
assert hasattr(StrategyTestV2, 'populate_buy_trend')
|
||||
assert hasattr(StrategyTestV2, 'populate_sell_trend')
|
||||
|
||||
|
||||
def test_default_strategy(result, fee):
|
||||
strategy = DefaultStrategy({})
|
||||
def test_strategy_test_v2(result, fee):
|
||||
strategy = StrategyTestV2({})
|
||||
|
||||
metadata = {'pair': 'ETH/BTC'}
|
||||
assert type(strategy.minimal_roi) is dict
|
||||
|
@ -22,11 +22,11 @@ from freqtrade.strategy.interface import SellCheckTuple
|
||||
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
||||
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
|
||||
_STRATEGY = DefaultStrategy(config={})
|
||||
_STRATEGY = StrategyTestV2(config={})
|
||||
_STRATEGY.dp = DataProvider({}, None, None)
|
||||
|
||||
|
||||
@ -148,7 +148,7 @@ def test_get_signal_no_sell_column(default_conf, mocker, caplog, ohlcv_history):
|
||||
|
||||
|
||||
def test_ignore_expired_candle(default_conf):
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
strategy.ignore_buying_expired_candle_after = 60
|
||||
|
||||
@ -233,7 +233,7 @@ def test_assert_df(ohlcv_history, caplog):
|
||||
|
||||
|
||||
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)
|
||||
|
||||
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
||||
@ -244,7 +244,7 @@ def test_advise_all_indicators(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)
|
||||
aimock = mocker.patch('freqtrade.strategy.interface.IStrategy.advise_indicators')
|
||||
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
||||
@ -262,7 +262,7 @@ def test_min_roi_reached(default_conf, fee) -> None:
|
||||
min_roi_list = [{20: 0.05, 55: 0.01, 0: 0.1},
|
||||
{0: 0.1, 20: 0.05, 55: 0.01}]
|
||||
for roi in min_roi_list:
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
strategy.minimal_roi = roi
|
||||
trade = Trade(
|
||||
@ -301,7 +301,7 @@ def test_min_roi_reached2(default_conf, fee) -> None:
|
||||
},
|
||||
]
|
||||
for roi in min_roi_list:
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
strategy.minimal_roi = roi
|
||||
trade = Trade(
|
||||
@ -336,7 +336,7 @@ def test_min_roi_reached3(default_conf, fee) -> None:
|
||||
30: 0.05,
|
||||
55: 0.30,
|
||||
}
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
strategy.minimal_roi = min_roi
|
||||
trade = Trade(
|
||||
@ -389,7 +389,7 @@ def test_min_roi_reached3(default_conf, fee) -> None:
|
||||
def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, trailing, custom,
|
||||
profit2, adjusted2, expected2, custom_stop) -> None:
|
||||
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
trade = Trade(
|
||||
@ -437,7 +437,7 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili
|
||||
|
||||
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)
|
||||
trade = Trade(
|
||||
@ -491,7 +491,7 @@ def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None:
|
||||
advise_sell=sell_mock,
|
||||
|
||||
)
|
||||
strategy = DefaultStrategy({})
|
||||
strategy = StrategyTestV2({})
|
||||
strategy.analyze_ticker(ohlcv_history, {'pair': 'ETH/BTC'})
|
||||
assert ind_mock.call_count == 1
|
||||
assert buy_mock.call_count == 1
|
||||
@ -522,7 +522,7 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) ->
|
||||
advise_sell=sell_mock,
|
||||
|
||||
)
|
||||
strategy = DefaultStrategy({})
|
||||
strategy = StrategyTestV2({})
|
||||
strategy.dp = DataProvider({}, None, None)
|
||||
strategy.process_only_new_candles = True
|
||||
|
||||
@ -554,7 +554,7 @@ def test__analyze_ticker_internal_skip_analyze(ohlcv_history, mocker, caplog) ->
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_is_pair_locked(default_conf):
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||
PairLocks.timeframe = default_conf['timeframe']
|
||||
PairLocks.use_db = True
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
@ -607,7 +607,7 @@ def test_is_pair_locked(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)
|
||||
# Should return empty
|
||||
# Uses fallback to base implementation
|
||||
@ -739,11 +739,16 @@ def test_auto_hyperopt_interface(default_conf):
|
||||
PairLocks.timeframe = default_conf['timeframe']
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
next(strategy.enumerate_parameters('deadBeef'))
|
||||
|
||||
assert strategy.buy_rsi.value == strategy.buy_params['buy_rsi']
|
||||
# PlusDI is NOT in the buy-params, so default should be used
|
||||
assert strategy.buy_plusdi.value == 0.5
|
||||
assert strategy.sell_rsi.value == strategy.sell_params['sell_rsi']
|
||||
|
||||
assert repr(strategy.sell_rsi) == 'IntParameter(74)'
|
||||
|
||||
# Parameter is disabled - so value from sell_param dict will NOT be used.
|
||||
assert strategy.sell_minusdi.value == 0.5
|
||||
all_params = strategy.detect_all_parameters()
|
||||
|
@ -18,7 +18,7 @@ def test_search_strategy():
|
||||
|
||||
s, _ = StrategyResolver._search_object(
|
||||
directory=default_location,
|
||||
object_name='DefaultStrategy',
|
||||
object_name='StrategyTestV2',
|
||||
add_source=True,
|
||||
)
|
||||
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):
|
||||
default_conf['strategy'] = 'DefaultStrategy'
|
||||
default_conf['strategy'] = 'StrategyTestV2'
|
||||
extra_dir = Path.cwd() / 'some/path'
|
||||
with pytest.raises(OperationalException):
|
||||
StrategyResolver._load_strategy('DefaultStrategy', config=default_conf,
|
||||
StrategyResolver._load_strategy('StrategyTestV2', config=default_conf,
|
||||
extra_dir=extra_dir)
|
||||
|
||||
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):
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
metadata = {'pair': 'ETH/BTC'}
|
||||
@ -127,7 +127,7 @@ def test_strategy(result, default_conf):
|
||||
def test_strategy_override_minimal_roi(caplog, default_conf):
|
||||
caplog.set_level(logging.INFO)
|
||||
default_conf.update({
|
||||
'strategy': 'DefaultStrategy',
|
||||
'strategy': 'StrategyTestV2',
|
||||
'minimal_roi': {
|
||||
"20": 0.1,
|
||||
"0": 0.5
|
||||
@ -144,7 +144,7 @@ def test_strategy_override_minimal_roi(caplog, default_conf):
|
||||
def test_strategy_override_stoploss(caplog, default_conf):
|
||||
caplog.set_level(logging.INFO)
|
||||
default_conf.update({
|
||||
'strategy': 'DefaultStrategy',
|
||||
'strategy': 'StrategyTestV2',
|
||||
'stoploss': -0.5
|
||||
})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
@ -156,7 +156,7 @@ def test_strategy_override_stoploss(caplog, default_conf):
|
||||
def test_strategy_override_trailing_stop(caplog, default_conf):
|
||||
caplog.set_level(logging.INFO)
|
||||
default_conf.update({
|
||||
'strategy': 'DefaultStrategy',
|
||||
'strategy': 'StrategyTestV2',
|
||||
'trailing_stop': True
|
||||
})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
@ -169,7 +169,7 @@ def test_strategy_override_trailing_stop(caplog, default_conf):
|
||||
def test_strategy_override_trailing_stop_positive(caplog, default_conf):
|
||||
caplog.set_level(logging.INFO)
|
||||
default_conf.update({
|
||||
'strategy': 'DefaultStrategy',
|
||||
'strategy': 'StrategyTestV2',
|
||||
'trailing_stop_positive': -0.1,
|
||||
'trailing_stop_positive_offset': -0.2
|
||||
|
||||
@ -189,7 +189,7 @@ def test_strategy_override_timeframe(caplog, default_conf):
|
||||
caplog.set_level(logging.INFO)
|
||||
|
||||
default_conf.update({
|
||||
'strategy': 'DefaultStrategy',
|
||||
'strategy': 'StrategyTestV2',
|
||||
'timeframe': 60,
|
||||
'stake_currency': 'ETH'
|
||||
})
|
||||
@ -205,7 +205,7 @@ def test_strategy_override_process_only_new_candles(caplog, default_conf):
|
||||
caplog.set_level(logging.INFO)
|
||||
|
||||
default_conf.update({
|
||||
'strategy': 'DefaultStrategy',
|
||||
'strategy': 'StrategyTestV2',
|
||||
'process_only_new_candles': True
|
||||
})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
@ -225,7 +225,7 @@ def test_strategy_override_order_types(caplog, default_conf):
|
||||
'stoploss_on_exchange': True,
|
||||
}
|
||||
default_conf.update({
|
||||
'strategy': 'DefaultStrategy',
|
||||
'strategy': 'StrategyTestV2',
|
||||
'order_types': order_types
|
||||
})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
@ -239,12 +239,12 @@ def test_strategy_override_order_types(caplog, default_conf):
|
||||
" 'stoploss_on_exchange': True}.", caplog)
|
||||
|
||||
default_conf.update({
|
||||
'strategy': 'DefaultStrategy',
|
||||
'strategy': 'StrategyTestV2',
|
||||
'order_types': {'buy': 'market'}
|
||||
})
|
||||
# Raise error for invalid configuration
|
||||
with pytest.raises(ImportError,
|
||||
match=r"Impossible to load Strategy 'DefaultStrategy'. "
|
||||
match=r"Impossible to load Strategy 'StrategyTestV2'. "
|
||||
r"Order-types mapping is incomplete."):
|
||||
StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
@ -258,7 +258,7 @@ def test_strategy_override_order_tif(caplog, default_conf):
|
||||
}
|
||||
|
||||
default_conf.update({
|
||||
'strategy': 'DefaultStrategy',
|
||||
'strategy': 'StrategyTestV2',
|
||||
'order_time_in_force': order_time_in_force
|
||||
})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
@ -271,12 +271,12 @@ def test_strategy_override_order_tif(caplog, default_conf):
|
||||
" {'buy': 'fok', 'sell': 'gtc'}.", caplog)
|
||||
|
||||
default_conf.update({
|
||||
'strategy': 'DefaultStrategy',
|
||||
'strategy': 'StrategyTestV2',
|
||||
'order_time_in_force': {'buy': 'fok'}
|
||||
})
|
||||
# Raise error for invalid configuration
|
||||
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."):
|
||||
StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
@ -284,7 +284,7 @@ def test_strategy_override_order_tif(caplog, default_conf):
|
||||
def test_strategy_override_use_sell_signal(caplog, default_conf):
|
||||
caplog.set_level(logging.INFO)
|
||||
default_conf.update({
|
||||
'strategy': 'DefaultStrategy',
|
||||
'strategy': 'StrategyTestV2',
|
||||
})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
assert strategy.use_sell_signal
|
||||
@ -294,7 +294,7 @@ def test_strategy_override_use_sell_signal(caplog, default_conf):
|
||||
assert default_conf['use_sell_signal']
|
||||
|
||||
default_conf.update({
|
||||
'strategy': 'DefaultStrategy',
|
||||
'strategy': 'StrategyTestV2',
|
||||
'use_sell_signal': False,
|
||||
})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
@ -307,7 +307,7 @@ def test_strategy_override_use_sell_signal(caplog, default_conf):
|
||||
def test_strategy_override_use_sell_profit_only(caplog, default_conf):
|
||||
caplog.set_level(logging.INFO)
|
||||
default_conf.update({
|
||||
'strategy': 'DefaultStrategy',
|
||||
'strategy': 'StrategyTestV2',
|
||||
})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
assert not strategy.sell_profit_only
|
||||
@ -317,7 +317,7 @@ def test_strategy_override_use_sell_profit_only(caplog, default_conf):
|
||||
assert not default_conf['sell_profit_only']
|
||||
|
||||
default_conf.update({
|
||||
'strategy': 'DefaultStrategy',
|
||||
'strategy': 'StrategyTestV2',
|
||||
'sell_profit_only': True,
|
||||
})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
@ -330,7 +330,7 @@ def test_strategy_override_use_sell_profit_only(caplog, default_conf):
|
||||
@pytest.mark.filterwarnings("ignore:deprecated")
|
||||
def test_deprecate_populate_indicators(result, default_conf):
|
||||
default_location = Path(__file__).parent / "strats"
|
||||
default_conf.update({'strategy': 'TestStrategyLegacy',
|
||||
default_conf.update({'strategy': 'TestStrategyLegacyV1',
|
||||
'strategy_path': default_location})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
@ -365,7 +365,7 @@ def test_deprecate_populate_indicators(result, default_conf):
|
||||
def test_call_deprecated_function(result, monkeypatch, default_conf, caplog):
|
||||
default_location = Path(__file__).parent / "strats"
|
||||
del default_conf['timeframe']
|
||||
default_conf.update({'strategy': 'TestStrategyLegacy',
|
||||
default_conf.update({'strategy': 'TestStrategyLegacyV1',
|
||||
'strategy_path': default_location})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
metadata = {'pair': 'ETH/BTC'}
|
||||
@ -395,7 +395,7 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog):
|
||||
|
||||
|
||||
def test_strategy_interface_versioning(result, monkeypatch, default_conf):
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
default_conf.update({'strategy': 'StrategyTestV2'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
metadata = {'pair': 'ETH/BTC'}
|
||||
|
||||
|
@ -123,7 +123,7 @@ def test_parse_args_backtesting_custom() -> None:
|
||||
'-c', 'test_conf.json',
|
||||
'--ticker-interval', '1m',
|
||||
'--strategy-list',
|
||||
'DefaultStrategy',
|
||||
'StrategyTestV2',
|
||||
'SampleStrategy'
|
||||
]
|
||||
call_args = Arguments(args).get_parsed_arg()
|
||||
|
@ -404,7 +404,7 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
|
||||
arglist = [
|
||||
'backtesting',
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'--strategy', 'StrategyTestV2',
|
||||
]
|
||||
|
||||
args = Arguments(arglist).get_parsed_arg()
|
||||
@ -441,7 +441,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
|
||||
arglist = [
|
||||
'backtesting',
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'DefaultStrategy',
|
||||
'--strategy', 'StrategyTestV2',
|
||||
'--datadir', '/foo/bar',
|
||||
'--userdir', "/tmp/freqtrade",
|
||||
'--ticker-interval', '1m',
|
||||
@ -498,7 +498,7 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non
|
||||
'--ticker-interval', '1m',
|
||||
'--export', 'trades',
|
||||
'--strategy-list',
|
||||
'DefaultStrategy',
|
||||
'StrategyTestV2',
|
||||
'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)
|
||||
result = freqtrade.wallets.get_trade_stake_amount('ETH/BTC')
|
||||
assert pytest.approx(result) == expected[i]
|
||||
freqtrade.execute_buy('ETH/BTC', result)
|
||||
freqtrade.execute_entry('ETH/BTC', result)
|
||||
else:
|
||||
with pytest.raises(DependencyException):
|
||||
freqtrade.wallets.get_trade_stake_amount('ETH/BTC')
|
||||
@ -518,6 +518,7 @@ def test_enter_positions_global_pairlock(default_conf, ticker, limit_buy_order,
|
||||
# 0 trades, but it's not because of pairlock.
|
||||
assert n == 0
|
||||
assert not log_has_re(message, caplog)
|
||||
caplog.clear()
|
||||
|
||||
PairLocks.lock_pair('*', arrow.utcnow().shift(minutes=20).datetime, 'Just because')
|
||||
n = freqtrade.enter_positions()
|
||||
@ -584,8 +585,8 @@ def test_create_trades_preopen(default_conf, ticker, fee, mocker, limit_buy_orde
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
# Create 2 existing trades
|
||||
freqtrade.execute_buy('ETH/BTC', default_conf['stake_amount'])
|
||||
freqtrade.execute_buy('NEO/BTC', default_conf['stake_amount'])
|
||||
freqtrade.execute_entry('ETH/BTC', default_conf['stake_amount'])
|
||||
freqtrade.execute_entry('NEO/BTC', default_conf['stake_amount'])
|
||||
|
||||
assert len(Trade.get_open_trades()) == 2
|
||||
# Change order_id for new orders
|
||||
@ -776,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]
|
||||
|
||||
|
||||
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_exchange(mocker)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
@ -799,7 +800,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||
)
|
||||
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_mm.call_count == 0
|
||||
assert freqtrade.strategy.confirm_trade_entry.call_count == 1
|
||||
@ -807,7 +808,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||
|
||||
limit_buy_order_open['id'] = '22'
|
||||
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_mm.call_count == 1
|
||||
call_args = buy_mm.call_args_list[0][1]
|
||||
@ -826,7 +827,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||
# Test calling with price
|
||||
limit_buy_order_open['id'] = '33'
|
||||
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
|
||||
assert buy_rate_mock.call_count == 0
|
||||
|
||||
@ -844,7 +845,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.create_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]
|
||||
assert trade
|
||||
assert trade.open_order_id is None
|
||||
@ -861,7 +862,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||
limit_buy_order['id'] = '555'
|
||||
mocker.patch('freqtrade.exchange.Exchange.create_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]
|
||||
assert trade
|
||||
assert trade.open_order_id == '555'
|
||||
@ -873,7 +874,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||
limit_buy_order['id'] = '556'
|
||||
|
||||
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]
|
||||
assert trade
|
||||
assert trade.stake_amount == 150
|
||||
@ -881,7 +882,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||
# Exception case
|
||||
limit_buy_order['id'] = '557'
|
||||
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]
|
||||
assert trade
|
||||
assert trade.stake_amount == 2.0
|
||||
@ -896,20 +897,20 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||
limit_buy_order['id'] = '66'
|
||||
mocker.patch('freqtrade.exchange.Exchange.create_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...
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0))
|
||||
|
||||
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
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_rate', return_value=0.50)
|
||||
limit_buy_order['status'] = 'open'
|
||||
limit_buy_order['id'] = '5566'
|
||||
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]
|
||||
assert trade
|
||||
assert trade.open_rate_requested == 0.508
|
||||
@ -924,7 +925,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||
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]
|
||||
assert trade
|
||||
assert trade.open_rate_requested == 10
|
||||
@ -933,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['id'] = '5568'
|
||||
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]
|
||||
assert trade
|
||||
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)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
@ -957,18 +958,18 @@ def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) -
|
||||
pair = 'ETH/BTC'
|
||||
|
||||
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'
|
||||
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'
|
||||
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)
|
||||
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:
|
||||
@ -1086,6 +1087,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
|
||||
assert log_has_re(r'STOP_LOSS_LIMIT is hit for Trade\(id=1, .*\)\.', caplog)
|
||||
assert trade.stoploss_order_id is None
|
||||
assert trade.is_open is False
|
||||
caplog.clear()
|
||||
|
||||
mocker.patch(
|
||||
'freqtrade.exchange.Binance.stoploss',
|
||||
@ -1190,7 +1192,7 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf, caplog, fee,
|
||||
assert trade.stoploss_order_id is None
|
||||
assert trade.sell_reason == SellType.EMERGENCY_SELL.value
|
||||
assert log_has("Unable to place a stoploss order on exchange. ", caplog)
|
||||
assert log_has("Selling the trade forcefully", caplog)
|
||||
assert log_has("Exiting the trade forcefully", caplog)
|
||||
|
||||
# Should call a market sell
|
||||
assert create_order_mock.call_count == 2
|
||||
@ -1743,10 +1745,12 @@ def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> No
|
||||
)
|
||||
assert not freqtrade.update_trade_state(trade, None)
|
||||
assert log_has_re(r'Orderid for trade .* is empty.', caplog)
|
||||
caplog.clear()
|
||||
# Add datetime explicitly since sqlalchemy defaults apply only once written to database
|
||||
freqtrade.update_trade_state(trade, '123')
|
||||
# Test amount not modified by fee-logic
|
||||
assert not log_has_re(r'Applying fee to .*', caplog)
|
||||
caplog.clear()
|
||||
assert trade.open_order_id is None
|
||||
assert trade.amount == limit_buy_order['amount']
|
||||
|
||||
@ -2007,7 +2011,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open,
|
||||
trade = Trade.query.first()
|
||||
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(),
|
||||
# we might just want to check if we are in a sell condition without
|
||||
# executing
|
||||
@ -2453,8 +2457,8 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke
|
||||
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.FreqtradeBot',
|
||||
handle_cancel_buy=MagicMock(),
|
||||
handle_cancel_sell=MagicMock(),
|
||||
handle_cancel_enter=MagicMock(),
|
||||
handle_cancel_exit=MagicMock(),
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
@ -2475,7 +2479,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke
|
||||
caplog)
|
||||
|
||||
|
||||
def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> None:
|
||||
def test_handle_cancel_enter(mocker, caplog, default_conf, limit_buy_order) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_buy_order = deepcopy(limit_buy_order)
|
||||
@ -2486,7 +2490,7 @@ def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> Non
|
||||
mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
freqtrade._notify_buy_cancel = MagicMock()
|
||||
freqtrade._notify_enter_cancel = MagicMock()
|
||||
|
||||
trade = MagicMock()
|
||||
trade.pair = 'LTC/USDT'
|
||||
@ -2494,46 +2498,46 @@ def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> Non
|
||||
limit_buy_order['filled'] = 0.0
|
||||
limit_buy_order['status'] = 'open'
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
assert freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
cancel_order_mock.reset_mock()
|
||||
caplog.clear()
|
||||
limit_buy_order['filled'] = 0.01
|
||||
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert not freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert log_has_re("Order .* for .* not cancelled, as the filled amount.* unsellable.*", caplog)
|
||||
|
||||
caplog.clear()
|
||||
cancel_order_mock.reset_mock()
|
||||
limit_buy_order['filled'] = 2
|
||||
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert not freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
# Order remained open for some reason (cancel failed)
|
||||
cancel_buy_order['status'] = 'open'
|
||||
cancel_order_mock = MagicMock(return_value=cancel_buy_order)
|
||||
mocker.patch('freqtrade.exchange.Exchange.cancel_order_with_result', cancel_order_mock)
|
||||
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert not freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
|
||||
assert log_has_re(r"Order .* for .* not cancelled.", caplog)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("limit_buy_order_canceled_empty", ['binance', 'ftx', 'kraken', 'bittrex'],
|
||||
indirect=['limit_buy_order_canceled_empty'])
|
||||
def test_handle_cancel_buy_exchanges(mocker, caplog, default_conf,
|
||||
limit_buy_order_canceled_empty) -> None:
|
||||
def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf,
|
||||
limit_buy_order_canceled_empty) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_order_mock = mocker.patch(
|
||||
'freqtrade.exchange.Exchange.cancel_order_with_result',
|
||||
return_value=limit_buy_order_canceled_empty)
|
||||
nofiy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_buy_cancel')
|
||||
nofiy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot._notify_enter_cancel')
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
trade = MagicMock()
|
||||
trade.pair = 'LTC/ETH'
|
||||
assert freqtrade.handle_cancel_buy(trade, limit_buy_order_canceled_empty, reason)
|
||||
assert freqtrade.handle_cancel_enter(trade, limit_buy_order_canceled_empty, reason)
|
||||
assert cancel_order_mock.call_count == 0
|
||||
assert log_has_re(r'Buy order fully cancelled. Removing .* from database\.', caplog)
|
||||
assert nofiy_mock.call_count == 1
|
||||
@ -2545,8 +2549,8 @@ def test_handle_cancel_buy_exchanges(mocker, caplog, default_conf,
|
||||
'String Return value',
|
||||
123
|
||||
])
|
||||
def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order,
|
||||
cancelorder) -> None:
|
||||
def test_handle_cancel_enter_corder_empty(mocker, default_conf, limit_buy_order,
|
||||
cancelorder) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_order_mock = MagicMock(return_value=cancelorder)
|
||||
@ -2556,7 +2560,7 @@ def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order,
|
||||
)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
freqtrade._notify_buy_cancel = MagicMock()
|
||||
freqtrade._notify_enter_cancel = MagicMock()
|
||||
|
||||
trade = MagicMock()
|
||||
trade.pair = 'LTC/USDT'
|
||||
@ -2564,16 +2568,16 @@ def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order,
|
||||
limit_buy_order['filled'] = 0.0
|
||||
limit_buy_order['status'] = 'open'
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
assert freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
cancel_order_mock.reset_mock()
|
||||
limit_buy_order['filled'] = 1.0
|
||||
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
|
||||
assert not freqtrade.handle_cancel_enter(trade, limit_buy_order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
|
||||
def test_handle_cancel_sell_limit(mocker, default_conf, fee) -> None:
|
||||
def test_handle_cancel_exit_limit(mocker, default_conf, fee) -> None:
|
||||
send_msg_mock = patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
cancel_order_mock = MagicMock()
|
||||
@ -2599,26 +2603,26 @@ def test_handle_cancel_sell_limit(mocker, default_conf, fee) -> None:
|
||||
'amount': 1,
|
||||
'status': "open"}
|
||||
reason = CANCEL_REASON['TIMEOUT']
|
||||
assert freqtrade.handle_cancel_sell(trade, order, reason)
|
||||
assert freqtrade.handle_cancel_exit(trade, order, reason)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert send_msg_mock.call_count == 1
|
||||
|
||||
send_msg_mock.reset_mock()
|
||||
|
||||
order['amount'] = 2
|
||||
assert freqtrade.handle_cancel_sell(trade, order, reason
|
||||
assert freqtrade.handle_cancel_exit(trade, order, reason
|
||||
) == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
|
||||
# Assert cancel_order was not called (callcount remains unchanged)
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert send_msg_mock.call_count == 1
|
||||
assert freqtrade.handle_cancel_sell(trade, order, reason
|
||||
assert freqtrade.handle_cancel_exit(trade, order, reason
|
||||
) == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
|
||||
# Message should not be iterated again
|
||||
assert trade.sell_order_status == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
|
||||
assert send_msg_mock.call_count == 1
|
||||
|
||||
|
||||
def test_handle_cancel_sell_cancel_exception(mocker, default_conf) -> None:
|
||||
def test_handle_cancel_exit_cancel_exception(mocker, default_conf) -> None:
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch(
|
||||
@ -2631,10 +2635,10 @@ def test_handle_cancel_sell_cancel_exception(mocker, default_conf) -> None:
|
||||
order = {'remaining': 1,
|
||||
'amount': 1,
|
||||
'status': "open"}
|
||||
assert freqtrade.handle_cancel_sell(trade, order, reason) == 'error cancelling order'
|
||||
assert freqtrade.handle_cancel_exit(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)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
@ -2662,16 +2666,16 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
|
||||
fetch_ticker=ticker_sell_up
|
||||
)
|
||||
# Prevented sell ...
|
||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
||||
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
||||
assert rpc_mock.call_count == 0
|
||||
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
|
||||
|
||||
# Repatch with true
|
||||
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
|
||||
|
||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
||||
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
||||
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
|
||||
|
||||
assert rpc_mock.call_count == 1
|
||||
@ -2698,7 +2702,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
|
||||
} == 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)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
@ -2723,8 +2727,8 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker)
|
||||
fetch_ticker=ticker_sell_down
|
||||
)
|
||||
|
||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_down()['bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||
|
||||
assert rpc_mock.call_count == 2
|
||||
last_msg = rpc_mock.call_args_list[-1][0][0]
|
||||
@ -2750,7 +2754,8 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker)
|
||||
} == 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)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
@ -2783,8 +2788,8 @@ def test_execute_sell_custom_exit_price(default_conf, ticker, fee, ticker_sell_u
|
||||
# Set a custom exit price
|
||||
freqtrade.strategy.custom_exit_price = lambda **kwargs: 1.170e-05
|
||||
|
||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL))
|
||||
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL))
|
||||
|
||||
# Sell price must be different to default bid price
|
||||
|
||||
@ -2814,8 +2819,8 @@ def test_execute_sell_custom_exit_price(default_conf, ticker, fee, ticker_sell_u
|
||||
} == last_msg
|
||||
|
||||
|
||||
def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fee,
|
||||
ticker_sell_down, mocker) -> None:
|
||||
def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(default_conf, ticker, fee,
|
||||
ticker_sell_down, mocker) -> None:
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
@ -2845,8 +2850,8 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
|
||||
# Setting trade stoploss to 0.01
|
||||
|
||||
trade.stop_loss = 0.00001099 * 0.99
|
||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_down()['bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||
|
||||
assert rpc_mock.call_count == 2
|
||||
last_msg = rpc_mock.call_args_list[-1][0][0]
|
||||
@ -2873,7 +2878,8 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
|
||||
} == 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)
|
||||
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
|
||||
side_effect=InvalidOrderException())
|
||||
@ -2900,14 +2906,14 @@ def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, c
|
||||
freqtrade.config['dry_run'] = False
|
||||
trade.stoploss_order_id = "abcd"
|
||||
|
||||
freqtrade.execute_sell(trade=trade, limit=1234,
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||
freqtrade.execute_trade_exit(trade=trade, limit=1234,
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||
assert create_order_mock.call_count == 2
|
||||
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,
|
||||
mocker) -> None:
|
||||
def test_execute_trade_exit_with_stoploss_on_exchange(default_conf, ticker, fee, ticker_sell_up,
|
||||
mocker) -> None:
|
||||
|
||||
default_conf['exchange']['name'] = 'binance'
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
@ -2951,8 +2957,8 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke
|
||||
fetch_ticker=ticker_sell_up
|
||||
)
|
||||
|
||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||
|
||||
trade = Trade.query.first()
|
||||
assert trade
|
||||
@ -2960,8 +2966,8 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke
|
||||
assert rpc_mock.call_count == 3
|
||||
|
||||
|
||||
def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, fee,
|
||||
mocker) -> None:
|
||||
def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf, ticker, fee,
|
||||
mocker) -> None:
|
||||
default_conf['exchange']['name'] = 'binance'
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
@ -3032,8 +3038,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
|
||||
|
||||
|
||||
def test_execute_sell_market_order(default_conf, ticker, fee,
|
||||
ticker_sell_up, mocker) -> None:
|
||||
def test_execute_trade_exit_market_order(default_conf, ticker, fee,
|
||||
ticker_sell_up, mocker) -> None:
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
@ -3059,8 +3065,8 @@ def test_execute_sell_market_order(default_conf, ticker, fee,
|
||||
)
|
||||
freqtrade.config['order_types']['sell'] = 'market'
|
||||
|
||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
||||
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
||||
|
||||
assert not trade.is_open
|
||||
assert trade.close_profit == 0.0620716
|
||||
@ -3090,8 +3096,8 @@ def test_execute_sell_market_order(default_conf, ticker, fee,
|
||||
} == last_msg
|
||||
|
||||
|
||||
def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee,
|
||||
ticker_sell_up, mocker) -> None:
|
||||
def test_execute_trade_exit_insufficient_funds_error(default_conf, ticker, fee,
|
||||
ticker_sell_up, mocker) -> None:
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds')
|
||||
mocker.patch.multiple(
|
||||
@ -3118,8 +3124,8 @@ def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee,
|
||||
)
|
||||
|
||||
sell_reason = SellCheckTuple(sell_type=SellType.ROI)
|
||||
assert not freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
||||
sell_reason=sell_reason)
|
||||
assert not freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_up()['bid'],
|
||||
sell_reason=sell_reason)
|
||||
assert mock_insuf.call_count == 1
|
||||
|
||||
|
||||
@ -3301,7 +3307,7 @@ def test_sell_not_enough_balance(default_conf, limit_buy_order, limit_buy_order_
|
||||
assert trade.amount != amnt
|
||||
|
||||
|
||||
def test__safe_sell_amount(default_conf, fee, caplog, mocker):
|
||||
def test__safe_exit_amount(default_conf, fee, caplog, mocker):
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
amount = 95.33
|
||||
@ -3321,17 +3327,17 @@ def test__safe_sell_amount(default_conf, fee, caplog, mocker):
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
wallet_update.reset_mock()
|
||||
assert freqtrade._safe_sell_amount(trade.pair, trade.amount) == amount_wallet
|
||||
assert freqtrade._safe_exit_amount(trade.pair, trade.amount) == amount_wallet
|
||||
assert log_has_re(r'.*Falling back to wallet-amount.', caplog)
|
||||
assert wallet_update.call_count == 1
|
||||
caplog.clear()
|
||||
wallet_update.reset_mock()
|
||||
assert freqtrade._safe_sell_amount(trade.pair, amount_wallet) == amount_wallet
|
||||
assert freqtrade._safe_exit_amount(trade.pair, amount_wallet) == amount_wallet
|
||||
assert not log_has_re(r'.*Falling back to wallet-amount.', caplog)
|
||||
assert wallet_update.call_count == 1
|
||||
|
||||
|
||||
def test__safe_sell_amount_error(default_conf, fee, caplog, mocker):
|
||||
def test__safe_exit_amount_error(default_conf, fee, caplog, mocker):
|
||||
patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
amount = 95.33
|
||||
@ -3349,7 +3355,7 @@ def test__safe_sell_amount_error(default_conf, fee, caplog, mocker):
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade)
|
||||
with pytest.raises(DependencyException, match=r"Not enough amount to sell."):
|
||||
assert freqtrade._safe_sell_amount(trade.pair, trade.amount)
|
||||
assert freqtrade._safe_exit_amount(trade.pair, trade.amount)
|
||||
|
||||
|
||||
def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplog) -> None:
|
||||
@ -3375,8 +3381,8 @@ def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplo
|
||||
fetch_ticker=ticker_sell_down
|
||||
)
|
||||
|
||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||
freqtrade.execute_trade_exit(trade=trade, limit=ticker_sell_down()['bid'],
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||
trade.close(ticker_sell_down()['bid'])
|
||||
assert freqtrade.strategy.is_pair_locked(trade.pair)
|
||||
|
||||
@ -3523,6 +3529,7 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, limit_buy_or
|
||||
assert log_has("ETH/BTC - Using positive stoploss: 0.01 offset: 0 profit: 0.2666%", caplog)
|
||||
assert log_has("ETH/BTC - Adjusting stoploss...", caplog)
|
||||
assert trade.stop_loss == 0.0000138501
|
||||
caplog.clear()
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||
MagicMock(return_value={
|
||||
@ -3583,6 +3590,7 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, limit_buy_orde
|
||||
assert log_has("ETH/BTC - Using positive stoploss: 0.01 offset: 0.011 profit: 0.2666%", caplog)
|
||||
assert log_has("ETH/BTC - Adjusting stoploss...", caplog)
|
||||
assert trade.stop_loss == 0.0000138501
|
||||
caplog.clear()
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||
MagicMock(return_value={
|
||||
@ -3647,6 +3655,7 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, limit_buy_order_
|
||||
|
||||
assert not log_has("ETH/BTC - Adjusting stoploss...", caplog)
|
||||
assert trade.stop_loss == 0.0000098910
|
||||
caplog.clear()
|
||||
|
||||
# price rises above the offset (rises 12% when the offset is 5.5%)
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||
@ -4314,8 +4323,8 @@ def test_cancel_all_open_orders(mocker, default_conf, fee, limit_buy_order, limi
|
||||
mocker.patch('freqtrade.exchange.Exchange.fetch_order',
|
||||
side_effect=[
|
||||
ExchangeError(), limit_sell_order, limit_buy_order, limit_sell_order])
|
||||
buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_buy')
|
||||
sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_sell')
|
||||
buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_enter')
|
||||
sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_exit')
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
create_mock_trades(fee)
|
||||
@ -4349,6 +4358,7 @@ def test_update_open_orders(mocker, default_conf, fee, caplog):
|
||||
|
||||
freqtrade.update_open_orders()
|
||||
assert not log_has_re(r"Error updating Order .*", caplog)
|
||||
caplog.clear()
|
||||
|
||||
freqtrade.config['dry_run'] = False
|
||||
freqtrade.update_open_orders()
|
||||
@ -4430,14 +4440,14 @@ def test_update_closed_trades_without_assigned_fees(mocker, default_conf, fee):
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_reupdate_buy_order_fees(mocker, default_conf, fee, caplog):
|
||||
def test_reupdate_enter_order_fees(mocker, default_conf, fee, caplog):
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
mock_uts = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.update_trade_state')
|
||||
|
||||
create_mock_trades(fee)
|
||||
trades = Trade.get_trades().all()
|
||||
|
||||
freqtrade.reupdate_buy_order_fees(trades[0])
|
||||
freqtrade.reupdate_enter_order_fees(trades[0])
|
||||
assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
|
||||
assert mock_uts.call_count == 1
|
||||
assert mock_uts.call_args_list[0][0][0] == trades[0]
|
||||
@ -4460,7 +4470,7 @@ def test_reupdate_buy_order_fees(mocker, default_conf, fee, caplog):
|
||||
)
|
||||
Trade.query.session.add(trade)
|
||||
|
||||
freqtrade.reupdate_buy_order_fees(trade)
|
||||
freqtrade.reupdate_enter_order_fees(trade)
|
||||
assert log_has_re(r"Trying to reupdate buy fees for .*", caplog)
|
||||
assert mock_uts.call_count == 0
|
||||
assert not log_has_re(r"Updating buy-fee on trade .* for order .*\.", caplog)
|
||||
@ -4470,7 +4480,7 @@ def test_reupdate_buy_order_fees(mocker, default_conf, fee, caplog):
|
||||
def test_handle_insufficient_funds(mocker, default_conf, fee):
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
mock_rlo = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.refind_lost_order')
|
||||
mock_bof = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.reupdate_buy_order_fees')
|
||||
mock_bof = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.reupdate_enter_order_fees')
|
||||
create_mock_trades(fee)
|
||||
trades = Trade.get_trades().all()
|
||||
|
||||
|
@ -9,7 +9,7 @@ from freqtrade.strategy.interface import SellCheckTuple
|
||||
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:
|
||||
"""
|
||||
Tests workflow of selling stoploss_on_exchange.
|
||||
@ -70,7 +70,7 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.FreqtradeBot',
|
||||
create_stoploss_order=MagicMock(return_value=True),
|
||||
_notify_sell=MagicMock(),
|
||||
_notify_exit=MagicMock(),
|
||||
)
|
||||
mocker.patch("freqtrade.strategy.interface.IStrategy.should_sell", should_sell_mock)
|
||||
wallets_mock = mocker.patch("freqtrade.wallets.Wallets.update", MagicMock())
|
||||
@ -154,7 +154,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.FreqtradeBot',
|
||||
create_stoploss_order=MagicMock(return_value=True),
|
||||
_notify_sell=MagicMock(),
|
||||
_notify_exit=MagicMock(),
|
||||
)
|
||||
should_sell_mock = MagicMock(side_effect=[
|
||||
SellCheckTuple(sell_type=SellType.NONE),
|
||||
|
@ -70,7 +70,6 @@ def test_add_indicators(default_conf, testdatadir, caplog):
|
||||
indicators1 = {"ema10": {}}
|
||||
indicators2 = {"macd": {"color": "red"}}
|
||||
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
# Generate buy/sell signals and indicators
|
||||
@ -112,7 +111,6 @@ def test_add_areas(default_conf, testdatadir, caplog):
|
||||
"fill_to": "macdhist"}}
|
||||
|
||||
ind_plain = {"macd": {"fill_to": "macdhist"}}
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
# 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',
|
||||
datadir=testdatadir, timerange=timerange)
|
||||
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
# 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
|
||||
|
||||
# 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')
|
||||
assert result == result1
|
||||
|
||||
# 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')
|
||||
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