Merge branch 'freqtrade:develop' into strategy_utils

This commit is contained in:
hippocritical 2023-02-17 21:07:23 +01:00 committed by GitHub
commit 08ca0f7c0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 3620 additions and 1598 deletions

View File

@ -15,7 +15,7 @@ repos:
additional_dependencies: additional_dependencies:
- types-cachetools==5.3.0.0 - types-cachetools==5.3.0.0
- types-filelock==3.2.7 - types-filelock==3.2.7
- types-requests==2.28.11.8 - types-requests==2.28.11.12
- types-tabulate==0.9.0.0 - types-tabulate==0.9.0.0
- types-python-dateutil==2.8.19.6 - types-python-dateutil==2.8.19.6
# stages: [push] # stages: [push]

View File

@ -1,4 +1,4 @@
FROM python:3.10.7-slim-bullseye as base FROM python:3.10.10-slim-bullseye as base
# Setup env # Setup env
ENV LANG C.UTF-8 ENV LANG C.UTF-8

View File

@ -1,4 +1,4 @@
FROM python:3.9.12-slim-bullseye as base FROM python:3.9.16-slim-bullseye as base
# Setup env # Setup env
ENV LANG C.UTF-8 ENV LANG C.UTF-8

View File

@ -363,7 +363,7 @@ from pathlib import Path
exchange = ccxt.binance({ exchange = ccxt.binance({
'apiKey': '<apikey>', 'apiKey': '<apikey>',
'secret': '<secret>' 'secret': '<secret>'
'options': {'defaultType': 'future'} 'options': {'defaultType': 'swap'}
}) })
_ = exchange.load_markets() _ = exchange.load_markets()

View File

@ -165,10 +165,10 @@ Below are the values you can expect to include/use inside a typical strategy dat
## Setting the `startup_candle_count` ## Setting the `startup_candle_count`
The `startup_candle_count` in the FreqAI strategy needs to be set up in the same way as in the standard Freqtrade strategy (see details [here](strategy-customization.md#strategy-startup-period)). This value is used by Freqtrade to ensure that a sufficient amount of data is provided when calling the `dataprovider`, to avoid any NaNs at the beginning of the first training. You can easily set this value by identifying the longest period (in candle units) which is passed to the indicator creation functions (e.g., Ta-Lib functions). In the presented example, `startup_candle_count` is 20 since this is the maximum value in `indicators_periods_candles`. The `startup_candle_count` in the FreqAI strategy needs to be set up in the same way as in the standard Freqtrade strategy (see details [here](strategy-customization.md#strategy-startup-period)). This value is used by Freqtrade to ensure that a sufficient amount of data is provided when calling the `dataprovider`, to avoid any NaNs at the beginning of the first training. You can easily set this value by identifying the longest period (in candle units) which is passed to the indicator creation functions (e.g., TA-Lib functions). In the presented example, `startup_candle_count` is 20 since this is the maximum value in `indicators_periods_candles`.
!!! Note !!! Note
There are instances where the Ta-Lib functions actually require more data than just the passed `period` or else the feature dataset gets populated with NaNs. Anecdotally, multiplying the `startup_candle_count` by 2 always leads to a fully NaN free training dataset. Hence, it is typically safest to multiply the expected `startup_candle_count` by 2. Look out for this log message to confirm that the data is clean: There are instances where the TA-Lib functions actually require more data than just the passed `period` or else the feature dataset gets populated with NaNs. Anecdotally, multiplying the `startup_candle_count` by 2 always leads to a fully NaN free training dataset. Hence, it is typically safest to multiply the expected `startup_candle_count` by 2. Look out for this log message to confirm that the data is clean:
``` ```
2022-08-31 15:14:04 - freqtrade.freqai.data_kitchen - INFO - dropped 0 training points due to NaNs in populated dataset 4319. 2022-08-31 15:14:04 - freqtrade.freqai.data_kitchen - INFO - dropped 0 training points due to NaNs in populated dataset 4319.
@ -205,7 +205,7 @@ All of the aforementioned model libraries implement gradient boosted decision tr
* LightGBM: https://lightgbm.readthedocs.io/en/v3.3.2/# * LightGBM: https://lightgbm.readthedocs.io/en/v3.3.2/#
* XGBoost: https://xgboost.readthedocs.io/en/stable/# * XGBoost: https://xgboost.readthedocs.io/en/stable/#
There are also numerous online articles describing and comparing the algorithms. Some relatively light-weight examples would be [CatBoost vs. LightGBM vs. XGBoost — Which is the best algorithm?](https://towardsdatascience.com/catboost-vs-lightgbm-vs-xgboost-c80f40662924#:~:text=In%20CatBoost%2C%20symmetric%20trees%2C%20or,the%20same%20depth%20can%20differ.) and [XGBoost, LightGBM or CatBoost — which boosting algorithm should I use?](https://medium.com/riskified-technology/xgboost-lightgbm-or-catboost-which-boosting-algorithm-should-i-use-e7fda7bb36bc). Keep in mind that the performance of each model is highly dependent on the application and so any reported metrics might not be true for your particular use of the model. There are also numerous online articles describing and comparing the algorithms. Some relatively lightweight examples would be [CatBoost vs. LightGBM vs. XGBoost — Which is the best algorithm?](https://towardsdatascience.com/catboost-vs-lightgbm-vs-xgboost-c80f40662924#:~:text=In%20CatBoost%2C%20symmetric%20trees%2C%20or,the%20same%20depth%20can%20differ.) and [XGBoost, LightGBM or CatBoost — which boosting algorithm should I use?](https://medium.com/riskified-technology/xgboost-lightgbm-or-catboost-which-boosting-algorithm-should-i-use-e7fda7bb36bc). Keep in mind that the performance of each model is highly dependent on the application and so any reported metrics might not be true for your particular use of the model.
Apart from the models already available in FreqAI, it is also possible to customize and create your own prediction models using the `IFreqaiModel` class. You are encouraged to inherit `fit()`, `train()`, and `predict()` to customize various aspects of the training procedures. You can place custom FreqAI models in `user_data/freqaimodels` - and freqtrade will pick them up from there based on the provided `--freqaimodel` name - which has to correspond to the class name of your custom model. Apart from the models already available in FreqAI, it is also possible to customize and create your own prediction models using the `IFreqaiModel` class. You are encouraged to inherit `fit()`, `train()`, and `predict()` to customize various aspects of the training procedures. You can place custom FreqAI models in `user_data/freqaimodels` - and freqtrade will pick them up from there based on the provided `--freqaimodel` name - which has to correspond to the class name of your custom model.
Make sure to use unique names to avoid overriding built-in models. Make sure to use unique names to avoid overriding built-in models.

View File

@ -8,7 +8,7 @@ Low level feature engineering is performed in the user strategy within a set of
|---------------|-------------| |---------------|-------------|
| `feature_engineering__expand_all()` | This optional function will automatically expand the defined features on the config defined `indicator_periods_candles`, `include_timeframes`, `include_shifted_candles`, and `include_corr_pairs`. | `feature_engineering__expand_all()` | This optional function will automatically expand the defined features on the config defined `indicator_periods_candles`, `include_timeframes`, `include_shifted_candles`, and `include_corr_pairs`.
| `feature_engineering__expand_basic()` | This optional function will automatically expand the defined features on the config defined `include_timeframes`, `include_shifted_candles`, and `include_corr_pairs`. Note: this function does *not* expand across `include_periods_candles`. | `feature_engineering__expand_basic()` | This optional function will automatically expand the defined features on the config defined `include_timeframes`, `include_shifted_candles`, and `include_corr_pairs`. Note: this function does *not* expand across `include_periods_candles`.
| `feature_engineering_standard()` | This optional function will be called once with the dataframe of the base timeframe. This is the final function to be called, which means that the dataframe entering this function will contain all the features and columns from the base asset created by the other `feature_engineering_expand` functions. This function is a good place to do custom exotic feature extractions (e.g. tsfresh). This function is also a good place for any feature that should not be auto-expanded upon (e.g. day of the week). | `feature_engineering_standard()` | This optional function will be called once with the dataframe of the base timeframe. This is the final function to be called, which means that the dataframe entering this function will contain all the features and columns from the base asset created by the other `feature_engineering_expand` functions. This function is a good place to do custom exotic feature extractions (e.g. tsfresh). This function is also a good place for any feature that should not be auto-expanded upon (e.g., day of the week).
| `set_freqai_targets()` | Required function to set the targets for the model. All targets must be prepended with `&` to be recognized by the FreqAI internals. | `set_freqai_targets()` | Required function to set the targets for the model. All targets must be prepended with `&` to be recognized by the FreqAI internals.
Meanwhile, high level feature engineering is handled within `"feature_parameters":{}` in the FreqAI config. Within this file, it is possible to decide large scale feature expansions on top of the `base_features` such as "including correlated pairs" or "including informative timeframes" or even "including recent candles." Meanwhile, high level feature engineering is handled within `"feature_parameters":{}` in the FreqAI config. Within this file, it is possible to decide large scale feature expansions on top of the `base_features` such as "including correlated pairs" or "including informative timeframes" or even "including recent candles."

View File

@ -51,7 +51,7 @@ Mandatory parameters are marked as **Required** and have to be set in one of the
| Parameter | Description | | Parameter | Description |
|------------|-------------| |------------|-------------|
| | **Data split parameters within the `freqai.data_split_parameters` sub dictionary** | | **Data split parameters within the `freqai.data_split_parameters` sub dictionary**
| `data_split_parameters` | Include any additional parameters available from Scikit-learn `test_train_split()`, which are shown [here](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html) (external website). <br> **Datatype:** Dictionary. | `data_split_parameters` | Include any additional parameters available from scikit-learn `test_train_split()`, which are shown [here](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html) (external website). <br> **Datatype:** Dictionary.
| `test_size` | The fraction of data that should be used for testing instead of training. <br> **Datatype:** Positive float < 1. | `test_size` | The fraction of data that should be used for testing instead of training. <br> **Datatype:** Positive float < 1.
| `shuffle` | Shuffle the training data points during training. Typically, to not remove the chronological order of data in time-series forecasting, this is set to `False`. <br> **Datatype:** Boolean. <br> Defaut: `False`. | `shuffle` | Shuffle the training data points during training. Typically, to not remove the chronological order of data in time-series forecasting, this is set to `False`. <br> **Datatype:** Boolean. <br> Defaut: `False`.
@ -88,6 +88,6 @@ Mandatory parameters are marked as **Required** and have to be set in one of the
| Parameter | Description | | Parameter | Description |
|------------|-------------| |------------|-------------|
| | **Extraneous parameters** | | **Extraneous parameters**
| `freqai.keras` | If the selected model makes use of Keras (typical for Tensorflow-based prediction models), this flag needs to be activated so that the model save/loading follows Keras standards. <br> **Datatype:** Boolean. <br> Default: `False`. | `freqai.keras` | If the selected model makes use of Keras (typical for TensorFlow-based prediction models), this flag needs to be activated so that the model save/loading follows Keras standards. <br> **Datatype:** Boolean. <br> Default: `False`.
| `freqai.conv_width` | The width of a convolutional neural network input tensor. This replaces the need for shifting candles (`include_shifted_candles`) by feeding in historical data points as the second dimension of the tensor. Technically, this parameter can also be used for regressors, but it only adds computational overhead and does not change the model training/prediction. <br> **Datatype:** Integer. <br> Default: `2`. | `freqai.conv_width` | The width of a convolutional neural network input tensor. This replaces the need for shifting candles (`include_shifted_candles`) by feeding in historical data points as the second dimension of the tensor. Technically, this parameter can also be used for regressors, but it only adds computational overhead and does not change the model training/prediction. <br> **Datatype:** Integer. <br> Default: `2`.
| `freqai.reduce_df_footprint` | Recast all numeric columns to float32/int32, with the objective of reducing ram/disk usage and decreasing train/inference timing. This parameter is set in the main level of the Freqtrade configuration file (not inside FreqAI). <br> **Datatype:** Boolean. <br> Default: `False`. | `freqai.reduce_df_footprint` | Recast all numeric columns to float32/int32, with the objective of reducing ram/disk usage and decreasing train/inference timing. This parameter is set in the main level of the Freqtrade configuration file (not inside FreqAI). <br> **Datatype:** Boolean. <br> Default: `False`.

View File

@ -24,7 +24,7 @@ The framework is built on stable_baselines3 (torch) and OpenAI gym for the base
### Important considerations ### Important considerations
As explained above, the agent is "trained" in an artificial trading "environment". In our case, that environment may seem quite similar to a real Freqtrade backtesting environment, but it is *NOT*. In fact, the RL training environment is much more simplified. It does not incorporate any of the complicated strategy logic, such as callbacks like `custom_exit`, `custom_stoploss`, leverage controls, etc. The RL environment is instead a very "raw" representation of the true market, where the agent has free-will to learn the policy (read: stoploss, take profit, etc.) which is enforced by the `calculate_reward()`. Thus, it is important to consider that the agent training environment is not identical to the real world. As explained above, the agent is "trained" in an artificial trading "environment". In our case, that environment may seem quite similar to a real Freqtrade backtesting environment, but it is *NOT*. In fact, the RL training environment is much more simplified. It does not incorporate any of the complicated strategy logic, such as callbacks like `custom_exit`, `custom_stoploss`, leverage controls, etc. The RL environment is instead a very "raw" representation of the true market, where the agent has free will to learn the policy (read: stoploss, take profit, etc.) which is enforced by the `calculate_reward()`. Thus, it is important to consider that the agent training environment is not identical to the real world.
## Running Reinforcement Learning ## Running Reinforcement Learning

View File

@ -120,7 +120,7 @@ In the presented example config, the user will only allow predictions on models
Model training parameters are unique to the selected machine learning library. FreqAI allows you to set any parameter for any library using the `model_training_parameters` dictionary in the config. The example config (found in `config_examples/config_freqai.example.json`) shows some of the example parameters associated with `Catboost` and `LightGBM`, but you can add any parameters available in those libraries or any other machine learning library you choose to implement. Model training parameters are unique to the selected machine learning library. FreqAI allows you to set any parameter for any library using the `model_training_parameters` dictionary in the config. The example config (found in `config_examples/config_freqai.example.json`) shows some of the example parameters associated with `Catboost` and `LightGBM`, but you can add any parameters available in those libraries or any other machine learning library you choose to implement.
Data split parameters are defined in `data_split_parameters` which can be any parameters associated with Scikit-learn's `train_test_split()` function. `train_test_split()` has a parameters called `shuffle` which allows to shuffle the data or keep it unshuffled. This is particularly useful to avoid biasing training with temporally auto-correlated data. More details about these parameters can be found the [Scikit-learn website](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html) (external website). Data split parameters are defined in `data_split_parameters` which can be any parameters associated with scikit-learn's `train_test_split()` function. `train_test_split()` has a parameters called `shuffle` which allows to shuffle the data or keep it unshuffled. This is particularly useful to avoid biasing training with temporally auto-correlated data. More details about these parameters can be found the [scikit-learn website](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html) (external website).
The FreqAI specific parameter `label_period_candles` defines the offset (number of candles into the future) used for the `labels`. In the presented [example config](freqai-configuration.md#setting-up-the-configuration-file), the user is asking for `labels` that are 24 candles in the future. The FreqAI specific parameter `label_period_candles` defines the offset (number of candles into the future) used for the `labels`. In the presented [example config](freqai-configuration.md#setting-up-the-configuration-file), the user is asking for `labels` that are 24 candles in the future.

View File

@ -4,7 +4,7 @@
## Introduction ## Introduction
FreqAI is a software designed to automate a variety of tasks associated with training a predictive machine learning model to generate market forecasts given a set of input signals. In general, the FreqAI aims to be a sand-box for easily deploying robust machine-learning libraries on real-time data ([details])(#freqai-position-in-open-source-machine-learning-landscape). FreqAI is a software designed to automate a variety of tasks associated with training a predictive machine learning model to generate market forecasts given a set of input signals. In general, FreqAI aims to be a sandbox for easily deploying robust machine learning libraries on real-time data ([details](#freqai-position-in-open-source-machine-learning-landscape)).
Features include: Features include:
@ -70,11 +70,11 @@ pip install -r requirements-freqai.txt
### Usage with docker ### Usage with docker
If you are using docker, a dedicated tag with FreqAI dependencies is available as `:freqai`. As such - you can replace the image line in your docker-compose file with `image: freqtradeorg/freqtrade:develop_freqai`. This image contains the regular FreqAI dependencies. Similar to native installs, Catboost will not be available on ARM based devices. If you are using docker, a dedicated tag with FreqAI dependencies is available as `:freqai`. As such - you can replace the image line in your docker compose file with `image: freqtradeorg/freqtrade:develop_freqai`. This image contains the regular FreqAI dependencies. Similar to native installs, Catboost will not be available on ARM based devices.
### FreqAI position in open-source machine learning landscape ### FreqAI position in open-source machine learning landscape
Forecasting chaotic time-series based systems, such as equity/cryptocurrency markets, requires a broad set of tools geared toward testing a wide range of hypotheses. Fortunately, a recent maturation of robust machine learning libraries (e.g. `scikit-learn`) has opened up a wide range of research possibilities. Scientists from a diverse range of fields can now easily prototype their studies on an abundance of established machine learning algorithms. Similarly, these user-friendly libraries enable "citzen scientists" to use their basic Python skills for data-exploration. However, leveraging these machine learning libraries on historical and live chaotic data sources can be logistically difficult and expensive. Additionally, robust data-collection, storage, and handling presents a disparate challenge. [`FreqAI`](#freqai) aims to provide a generalized and extensible open-sourced framework geared toward live deployments of adaptive modeling for market forecasting. The `FreqAI` framework is effectively a sandbox for the rich world of open-source machine learning libraries. Inside the `FreqAI` sandbox, users find they can combine a wide variety of third-party libraries to test creative hypotheses on a free live 24/7 chaotic data source - cryptocurrency exchange data. Forecasting chaotic time-series based systems, such as equity/cryptocurrency markets, requires a broad set of tools geared toward testing a wide range of hypotheses. Fortunately, a recent maturation of robust machine learning libraries (e.g. `scikit-learn`) has opened up a wide range of research possibilities. Scientists from a diverse range of fields can now easily prototype their studies on an abundance of established machine learning algorithms. Similarly, these user-friendly libraries enable "citzen scientists" to use their basic Python skills for data exploration. However, leveraging these machine learning libraries on historical and live chaotic data sources can be logistically difficult and expensive. Additionally, robust data collection, storage, and handling presents a disparate challenge. [`FreqAI`](#freqai) aims to provide a generalized and extensible open-sourced framework geared toward live deployments of adaptive modeling for market forecasting. The `FreqAI` framework is effectively a sandbox for the rich world of open-source machine learning libraries. Inside the `FreqAI` sandbox, users find they can combine a wide variety of third-party libraries to test creative hypotheses on a free live 24/7 chaotic data source - cryptocurrency exchange data.
### Citing FreqAI ### Citing FreqAI

View File

@ -1,6 +1,6 @@
markdown==3.3.7 markdown==3.3.7
mkdocs==1.4.2 mkdocs==1.4.2
mkdocs-material==9.0.11 mkdocs-material==9.0.12
mdx_truly_sane_lists==1.3 mdx_truly_sane_lists==1.3
pymdown-extensions==9.9.2 pymdown-extensions==9.9.2
jinja2==3.1.2 jinja2==3.1.2

View File

@ -1,4 +1,5 @@
import logging import logging
import signal
from typing import Any, Dict from typing import Any, Dict
@ -12,15 +13,20 @@ def start_trading(args: Dict[str, Any]) -> int:
# Import here to avoid loading worker module when it's not used # Import here to avoid loading worker module when it's not used
from freqtrade.worker import Worker from freqtrade.worker import Worker
def term_handler(signum, frame):
# Raise KeyboardInterrupt - so we can handle it in the same way as Ctrl-C
raise KeyboardInterrupt()
# Create and run worker # Create and run worker
worker = None worker = None
try: try:
signal.signal(signal.SIGTERM, term_handler)
worker = Worker(args) worker = Worker(args)
worker.run() worker.run()
except Exception as e: except Exception as e:
logger.error(str(e)) logger.error(str(e))
logger.exception("Fatal exception!") logger.exception("Fatal exception!")
except KeyboardInterrupt: except (KeyboardInterrupt):
logger.info('SIGINT received, aborting ...') logger.info('SIGINT received, aborting ...')
finally: finally:
if worker: if worker:

View File

@ -681,6 +681,7 @@ EntryExit = Literal['entry', 'exit']
BuySell = Literal['buy', 'sell'] BuySell = Literal['buy', 'sell']
MakerTaker = Literal['maker', 'taker'] MakerTaker = Literal['maker', 'taker']
BidAsk = Literal['bid', 'ask'] BidAsk = Literal['bid', 'ask']
OBLiteral = Literal['asks', 'bids']
Config = Dict[str, Any] Config = Dict[str, Any]
IntOrInf = float IntOrInf = float

View File

@ -18,6 +18,7 @@ from freqtrade.data.history import load_pair_history
from freqtrade.enums import CandleType, RPCMessageType, RunMode from freqtrade.enums import CandleType, RPCMessageType, RunMode
from freqtrade.exceptions import ExchangeError, OperationalException from freqtrade.exceptions import ExchangeError, OperationalException
from freqtrade.exchange import Exchange, timeframe_to_seconds from freqtrade.exchange import Exchange, timeframe_to_seconds
from freqtrade.exchange.types import OrderBook
from freqtrade.misc import append_candles_to_dataframe from freqtrade.misc import append_candles_to_dataframe
from freqtrade.rpc import RPCManager from freqtrade.rpc import RPCManager
from freqtrade.util import PeriodicCache from freqtrade.util import PeriodicCache
@ -489,7 +490,7 @@ class DataProvider:
except ExchangeError: except ExchangeError:
return {} return {}
def orderbook(self, pair: str, maximum: int) -> Dict[str, List]: def orderbook(self, pair: str, maximum: int) -> OrderBook:
""" """
Fetch latest l2 orderbook data Fetch latest l2 orderbook data
Warning: Does a network request - so use with common sense. Warning: Does a network request - so use with common sense.

View File

@ -195,7 +195,7 @@ class Edge:
def stake_amount(self, pair: str, free_capital: float, def stake_amount(self, pair: str, free_capital: float,
total_capital: float, capital_in_trade: float) -> float: total_capital: float, capital_in_trade: float) -> float:
stoploss = self.stoploss(pair) stoploss = self.get_stoploss(pair)
available_capital = (total_capital + capital_in_trade) * self._capital_ratio available_capital = (total_capital + capital_in_trade) * self._capital_ratio
allowed_capital_at_risk = available_capital * self._allowed_risk allowed_capital_at_risk = available_capital * self._allowed_risk
max_position_size = abs(allowed_capital_at_risk / stoploss) max_position_size = abs(allowed_capital_at_risk / stoploss)
@ -214,7 +214,7 @@ class Edge:
) )
return round(position_size, 15) return round(position_size, 15)
def stoploss(self, pair: str) -> float: def get_stoploss(self, pair: str) -> float:
if pair in self._cached_pairs: if pair in self._cached_pairs:
return self._cached_pairs[pair].stoploss return self._cached_pairs[pair].stoploss
else: else:

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@ from pandas import DataFrame, concat
from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, BidAsk, from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, BidAsk,
BuySell, Config, EntryExit, ListPairsWithTimeframes, MakerTaker, BuySell, Config, EntryExit, ListPairsWithTimeframes, MakerTaker,
PairWithTimeframe) OBLiteral, PairWithTimeframe)
from freqtrade.data.converter import clean_ohlcv_dataframe, ohlcv_to_dataframe, trades_dict_to_list from freqtrade.data.converter import clean_ohlcv_dataframe, ohlcv_to_dataframe, trades_dict_to_list
from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, TradingMode from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, TradingMode
from freqtrade.enums.pricetype import PriceType from freqtrade.enums.pricetype import PriceType
@ -37,7 +37,7 @@ from freqtrade.exchange.exchange_utils import (CcxtModuleType, amount_to_contrac
price_to_precision, timeframe_to_minutes, price_to_precision, timeframe_to_minutes,
timeframe_to_msecs, timeframe_to_next_date, timeframe_to_msecs, timeframe_to_next_date,
timeframe_to_prev_date, timeframe_to_seconds) timeframe_to_prev_date, timeframe_to_seconds)
from freqtrade.exchange.types import OHLCVResponse, Ticker, Tickers from freqtrade.exchange.types import OHLCVResponse, OrderBook, Ticker, Tickers
from freqtrade.misc import (chunks, deep_merge_dicts, file_dump_json, file_load_json, from freqtrade.misc import (chunks, deep_merge_dicts, file_dump_json, file_load_json,
safe_value_fallback2) safe_value_fallback2)
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
@ -850,7 +850,7 @@ class Exchange:
'remaining': _amount, 'remaining': _amount,
'datetime': arrow.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'), 'datetime': arrow.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'),
'timestamp': arrow.utcnow().int_timestamp * 1000, 'timestamp': arrow.utcnow().int_timestamp * 1000,
'status': "closed" if ordertype == "market" and not stop_loss else "open", 'status': "open",
'fee': None, 'fee': None,
'info': {}, 'info': {},
'leverage': leverage 'leverage': leverage
@ -860,20 +860,33 @@ class Exchange:
dry_order["stopPrice"] = dry_order["price"] dry_order["stopPrice"] = dry_order["price"]
# Workaround to avoid filling stoploss orders immediately # Workaround to avoid filling stoploss orders immediately
dry_order["ft_order_type"] = "stoploss" dry_order["ft_order_type"] = "stoploss"
orderbook: Optional[OrderBook] = None
if self.exchange_has('fetchL2OrderBook'):
orderbook = self.fetch_l2_order_book(pair, 20)
if ordertype == "limit" and orderbook:
# Allow a 3% price difference
allowed_diff = 0.03
if self._dry_is_price_crossed(pair, side, rate, orderbook, allowed_diff):
logger.info(
f"Converted order {pair} to market order due to price {rate} crossing spread "
f"by more than {allowed_diff:.2%}.")
dry_order["type"] = "market"
if dry_order["type"] == "market" and not dry_order.get("ft_order_type"): if dry_order["type"] == "market" and not dry_order.get("ft_order_type"):
# Update market order pricing # Update market order pricing
average = self.get_dry_market_fill_price(pair, side, amount, rate) average = self.get_dry_market_fill_price(pair, side, amount, rate, orderbook)
dry_order.update({ dry_order.update({
'average': average, 'average': average,
'filled': _amount, 'filled': _amount,
'remaining': 0.0, 'remaining': 0.0,
'status': "closed",
'cost': (dry_order['amount'] * average) / leverage 'cost': (dry_order['amount'] * average) / leverage
}) })
# market orders will always incurr taker fees # market orders will always incurr taker fees
dry_order = self.add_dry_order_fee(pair, dry_order, 'taker') dry_order = self.add_dry_order_fee(pair, dry_order, 'taker')
dry_order = self.check_dry_limit_order_filled(dry_order, immediate=True) dry_order = self.check_dry_limit_order_filled(
dry_order, immediate=True, orderbook=orderbook)
self._dry_run_open_orders[dry_order["id"]] = dry_order self._dry_run_open_orders[dry_order["id"]] = dry_order
# Copy order and close it - so the returned order is open unless it's a market order # Copy order and close it - so the returned order is open unless it's a market order
@ -895,20 +908,22 @@ class Exchange:
}) })
return dry_order return dry_order
def get_dry_market_fill_price(self, pair: str, side: str, amount: float, rate: float) -> float: def get_dry_market_fill_price(self, pair: str, side: str, amount: float, rate: float,
orderbook: Optional[OrderBook]) -> float:
""" """
Get the market order fill price based on orderbook interpolation Get the market order fill price based on orderbook interpolation
""" """
if self.exchange_has('fetchL2OrderBook'): if self.exchange_has('fetchL2OrderBook'):
ob = self.fetch_l2_order_book(pair, 20) if not orderbook:
ob_type = 'asks' if side == 'buy' else 'bids' orderbook = self.fetch_l2_order_book(pair, 20)
ob_type: OBLiteral = 'asks' if side == 'buy' else 'bids'
slippage = 0.05 slippage = 0.05
max_slippage_val = rate * ((1 + slippage) if side == 'buy' else (1 - slippage)) max_slippage_val = rate * ((1 + slippage) if side == 'buy' else (1 - slippage))
remaining_amount = amount remaining_amount = amount
filled_amount = 0.0 filled_amount = 0.0
book_entry_price = 0.0 book_entry_price = 0.0
for book_entry in ob[ob_type]: for book_entry in orderbook[ob_type]:
book_entry_price = book_entry[0] book_entry_price = book_entry[0]
book_entry_coin_volume = book_entry[1] book_entry_coin_volume = book_entry[1]
if remaining_amount > 0: if remaining_amount > 0:
@ -936,20 +951,20 @@ class Exchange:
return rate return rate
def _is_dry_limit_order_filled(self, pair: str, side: str, limit: float) -> bool: def _dry_is_price_crossed(self, pair: str, side: str, limit: float,
orderbook: Optional[OrderBook] = None, offset: float = 0.0) -> bool:
if not self.exchange_has('fetchL2OrderBook'): if not self.exchange_has('fetchL2OrderBook'):
return True return True
ob = self.fetch_l2_order_book(pair, 1) if not orderbook:
orderbook = self.fetch_l2_order_book(pair, 1)
try: try:
if side == 'buy': if side == 'buy':
price = ob['asks'][0][0] price = orderbook['asks'][0][0]
logger.debug(f"{pair} checking dry buy-order: price={price}, limit={limit}") if limit * (1 - offset) >= price:
if limit >= price:
return True return True
else: else:
price = ob['bids'][0][0] price = orderbook['bids'][0][0]
logger.debug(f"{pair} checking dry sell-order: price={price}, limit={limit}") if limit * (1 + offset) <= price:
if limit <= price:
return True return True
except IndexError: except IndexError:
# Ignore empty orderbooks when filling - can be filled with the next iteration. # Ignore empty orderbooks when filling - can be filled with the next iteration.
@ -957,7 +972,8 @@ class Exchange:
return False return False
def check_dry_limit_order_filled( def check_dry_limit_order_filled(
self, order: Dict[str, Any], immediate: bool = False) -> Dict[str, Any]: self, order: Dict[str, Any], immediate: bool = False,
orderbook: Optional[OrderBook] = None) -> Dict[str, Any]:
""" """
Check dry-run limit order fill and update fee (if it filled). Check dry-run limit order fill and update fee (if it filled).
""" """
@ -965,7 +981,7 @@ class Exchange:
and order['type'] in ["limit"] and order['type'] in ["limit"]
and not order.get('ft_order_type')): and not order.get('ft_order_type')):
pair = order['symbol'] pair = order['symbol']
if self._is_dry_limit_order_filled(pair, order['side'], order['price']): if self._dry_is_price_crossed(pair, order['side'], order['price'], orderbook):
order.update({ order.update({
'status': 'closed', 'status': 'closed',
'filled': order['amount'], 'filled': order['amount'],
@ -1131,8 +1147,8 @@ class Exchange:
return params return params
@retrier(retries=0) @retrier(retries=0)
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict, def create_stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict,
side: BuySell, leverage: float) -> Dict: side: BuySell, leverage: float) -> Dict:
""" """
creates a stoploss order. creates a stoploss order.
requires `_ft_has['stoploss_order_types']` to be set as a dict mapping limit and market requires `_ft_has['stoploss_order_types']` to be set as a dict mapping limit and market
@ -1511,7 +1527,7 @@ class Exchange:
return result return result
@retrier @retrier
def fetch_l2_order_book(self, pair: str, limit: int = 100) -> dict: def fetch_l2_order_book(self, pair: str, limit: int = 100) -> OrderBook:
""" """
Get L2 order book from exchange. Get L2 order book from exchange.
Can be limited to a certain amount (if supported). Can be limited to a certain amount (if supported).
@ -1554,7 +1570,7 @@ class Exchange:
def get_rate(self, pair: str, refresh: bool, def get_rate(self, pair: str, refresh: bool,
side: EntryExit, is_short: bool, side: EntryExit, is_short: bool,
order_book: Optional[dict] = None, ticker: Optional[Ticker] = None) -> float: order_book: Optional[OrderBook] = None, ticker: Optional[Ticker] = None) -> float:
""" """
Calculates bid/ask target Calculates bid/ask target
bid rate - between current ask price and last price bid rate - between current ask price and last price
@ -1592,7 +1608,8 @@ class Exchange:
logger.debug('order_book %s', order_book) logger.debug('order_book %s', order_book)
# top 1 = index 0 # top 1 = index 0
try: try:
rate = order_book[f"{price_side}s"][order_book_top - 1][0] obside: OBLiteral = 'bids' if price_side == 'bid' else 'asks'
rate = order_book[obside][order_book_top - 1][0]
except (IndexError, KeyError) as e: except (IndexError, KeyError) as e:
logger.warning( logger.warning(
f"{pair} - {name} Price at location {order_book_top} from orderbook " f"{pair} - {name} Price at location {order_book_top} from orderbook "

View File

@ -97,8 +97,8 @@ class Kraken(Exchange):
)) ))
@retrier(retries=0) @retrier(retries=0)
def stoploss(self, pair: str, amount: float, stop_price: float, def create_stoploss(self, pair: str, amount: float, stop_price: float,
order_types: Dict, side: BuySell, leverage: float) -> Dict: order_types: Dict, side: BuySell, leverage: float) -> Dict:
""" """
Creates a stoploss market order. Creates a stoploss market order.
Stoploss market orders is the only stoploss type supported by kraken. Stoploss market orders is the only stoploss type supported by kraken.

View File

@ -15,6 +15,15 @@ class Ticker(TypedDict):
# Several more - only listing required. # Several more - only listing required.
class OrderBook(TypedDict):
symbol: str
bids: List[Tuple[float, float]]
asks: List[Tuple[float, float]]
timestamp: Optional[int]
datetime: Optional[str]
nonce: Optional[int]
Tickers = Dict[str, Ticker] Tickers = Dict[str, Ticker]
# pair, timeframe, candleType, OHLCV, drop last?, # pair, timeframe, candleType, OHLCV, drop last?,

View File

@ -563,7 +563,13 @@ class IFreqaiModel(ABC):
:return: :return:
:boolean: whether the model file exists or not. :boolean: whether the model file exists or not.
""" """
path_to_modelfile = Path(dk.data_path / f"{dk.model_filename}_model.joblib") if self.dd.model_type == 'joblib':
file_type = ".joblib"
elif self.dd.model_type == 'keras':
file_type = ".h5"
elif 'stable_baselines' in self.dd.model_type or 'sb3_contrib' == self.dd.model_type:
file_type = ".zip"
path_to_modelfile = Path(dk.data_path / f"{dk.model_filename}_model.{file_type}")
file_exists = path_to_modelfile.is_file() file_exists = path_to_modelfile.is_file()
if file_exists: if file_exists:
logger.info("Found model at %s", dk.data_path / dk.model_filename) logger.info("Found model at %s", dk.data_path / dk.model_filename)

View File

@ -1078,7 +1078,7 @@ class FreqtradeBot(LoggingMixin):
datetime.now(timezone.utc), datetime.now(timezone.utc),
enter=enter, enter=enter,
exit_=exit_, exit_=exit_,
force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0 force_stoploss=self.edge.get_stoploss(trade.pair) if self.edge else 0
) )
for should_exit in exits: for should_exit in exits:
if should_exit.exit_flag: if should_exit.exit_flag:
@ -1098,7 +1098,7 @@ class FreqtradeBot(LoggingMixin):
:return: True if the order succeeded, and False in case of problems. :return: True if the order succeeded, and False in case of problems.
""" """
try: try:
stoploss_order = self.exchange.stoploss( stoploss_order = self.exchange.create_stoploss(
pair=trade.pair, pair=trade.pair,
amount=trade.amount, amount=trade.amount,
stop_price=stop_price, stop_price=stop_price,
@ -1172,7 +1172,7 @@ class FreqtradeBot(LoggingMixin):
if not stoploss_order: if not stoploss_order:
stop_price = trade.stoploss_or_liquidation stop_price = trade.stoploss_or_liquidation
if self.edge: if self.edge:
stoploss = self.edge.stoploss(pair=trade.pair) stoploss = self.edge.get_stoploss(pair=trade.pair)
stop_price = ( stop_price = (
trade.open_rate * (1 - stoploss) if trade.is_short trade.open_rate * (1 - stoploss) if trade.is_short
else trade.open_rate * (1 + stoploss) else trade.open_rate * (1 + stoploss)

View File

@ -163,7 +163,7 @@ class HyperStrategyMixin:
else: else:
logger.info(f'Strategy Parameter(default): {attr_name} = {attr.value}') logger.info(f'Strategy Parameter(default): {attr_name} = {attr.value}')
def get_no_optimize_params(self): def get_no_optimize_params(self) -> Dict[str, Dict]:
""" """
Returns list of Parameters that are not part of the current optimize job Returns list of Parameters that are not part of the current optimize job
""" """
@ -173,7 +173,7 @@ class HyperStrategyMixin:
'protection': {}, 'protection': {},
} }
for name, p in self.enumerate_parameters(): for name, p in self.enumerate_parameters():
if not p.optimize or not p.in_space: if p.category and (not p.optimize or not p.in_space):
params[p.category][name] = p.value params[p.category][name] = p.value
return params return params

View File

@ -1083,10 +1083,10 @@ class IStrategy(ABC, HyperStrategyMixin):
trade.adjust_min_max_rates(high or current_rate, low or current_rate) trade.adjust_min_max_rates(high or current_rate, low or current_rate)
stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade, stoplossflag = self.ft_stoploss_reached(current_rate=current_rate, trade=trade,
current_time=current_time, current_time=current_time,
current_profit=current_profit, current_profit=current_profit,
force_stoploss=force_stoploss, low=low, high=high) force_stoploss=force_stoploss, low=low, high=high)
# Set current rate to high for backtesting exits # Set current rate to high for backtesting exits
current_rate = (low if trade.is_short else high) or rate current_rate = (low if trade.is_short else high) or rate
@ -1153,13 +1153,12 @@ class IStrategy(ABC, HyperStrategyMixin):
return exits return exits
def stop_loss_reached(self, current_rate: float, trade: Trade, def ft_stoploss_adjust(self, current_rate: float, trade: Trade,
current_time: datetime, current_profit: float, current_time: datetime, current_profit: float,
force_stoploss: float, low: Optional[float] = None, force_stoploss: float, low: Optional[float] = None,
high: Optional[float] = None) -> ExitCheckTuple: high: Optional[float] = None) -> None:
""" """
Based on current profit of the trade and configured (trailing) stoploss, Adjust stop-loss dynamically if configured to do so.
decides to exit or not
:param current_profit: current profit as ratio :param current_profit: current profit as ratio
:param low: Low value of this candle, only set in backtesting :param low: Low value of this candle, only set in backtesting
:param high: High value of this candle, only set in backtesting :param high: High value of this candle, only set in backtesting
@ -1205,6 +1204,20 @@ class IStrategy(ABC, HyperStrategyMixin):
trade.adjust_stop_loss(bound or current_rate, stop_loss_value) trade.adjust_stop_loss(bound or current_rate, stop_loss_value)
def ft_stoploss_reached(self, current_rate: float, trade: Trade,
current_time: datetime, current_profit: float,
force_stoploss: float, low: Optional[float] = None,
high: Optional[float] = None) -> ExitCheckTuple:
"""
Based on current profit of the trade and configured (trailing) stoploss,
decides to exit or not
:param current_profit: current profit as ratio
:param low: Low value of this candle, only set in backtesting
:param high: High value of this candle, only set in backtesting
"""
self.ft_stoploss_adjust(current_rate, trade, current_time, current_profit,
force_stoploss, low, high)
sl_higher_long = (trade.stop_loss >= (low or current_rate) and not trade.is_short) sl_higher_long = (trade.stop_loss >= (low or current_rate) and not trade.is_short)
sl_lower_short = (trade.stop_loss <= (high or current_rate) and trade.is_short) sl_lower_short = (trade.stop_loss <= (high or current_rate) and trade.is_short)
liq_higher_long = (trade.liquidation_price liq_higher_long = (trade.liquidation_price

View File

@ -9,7 +9,7 @@
coveralls==3.3.1 coveralls==3.3.1
flake8==6.0.0 flake8==6.0.0
flake8-tidy-imports==4.8.0 flake8-tidy-imports==4.8.0
mypy==0.991 mypy==1.0.0
pre-commit==3.0.4 pre-commit==3.0.4
pytest==7.2.1 pytest==7.2.1
pytest-asyncio==0.20.3 pytest-asyncio==0.20.3
@ -28,6 +28,6 @@ nbconvert==7.2.9
# mypy types # mypy types
types-cachetools==5.3.0.0 types-cachetools==5.3.0.0
types-filelock==3.2.7 types-filelock==3.2.7
types-requests==2.28.11.8 types-requests==2.28.11.12
types-tabulate==0.9.0.0 types-tabulate==0.9.0.0
types-python-dateutil==2.8.19.6 types-python-dateutil==2.8.19.6

View File

@ -8,4 +8,4 @@ joblib==1.2.0
catboost==1.1.1; platform_machine != 'aarch64' catboost==1.1.1; platform_machine != 'aarch64'
lightgbm==3.3.5 lightgbm==3.3.5
xgboost==1.7.3 xgboost==1.7.3
tensorboard==2.11.2 tensorboard==2.12.0

View File

@ -2,11 +2,11 @@ numpy==1.24.2
pandas==1.5.3 pandas==1.5.3
pandas-ta==0.3.14b pandas-ta==0.3.14b
ccxt==2.7.80 ccxt==2.7.93
# Pin cryptography for now due to rust build errors with piwheels # Pin cryptography for now due to rust build errors with piwheels
cryptography==38.0.1; platform_machine == 'armv7l' cryptography==38.0.1; platform_machine == 'armv7l'
cryptography==39.0.1; platform_machine != 'armv7l' cryptography==39.0.1; platform_machine != 'armv7l'
aiohttp==3.8.3 aiohttp==3.8.4
SQLAlchemy==1.4.46 SQLAlchemy==1.4.46
python-telegram-bot==13.15 python-telegram-bot==13.15
arrow==1.2.3 arrow==1.2.3
@ -30,17 +30,17 @@ py_find_1st==1.1.5
# Load ticker files 30% faster # Load ticker files 30% faster
python-rapidjson==1.9 python-rapidjson==1.9
# Properly format api responses # Properly format api responses
orjson==3.8.5 orjson==3.8.6
# Notify systemd # Notify systemd
sdnotify==0.3.2 sdnotify==0.3.2
# API Server # API Server
fastapi==0.89.1 fastapi==0.91.0
pydantic==1.10.4 pydantic==1.10.4
uvicorn==0.20.0 uvicorn==0.20.0
pyjwt==2.6.0 pyjwt==2.6.0
aiofiles==22.1.0 aiofiles==23.1.0
psutil==5.9.4 psutil==5.9.4
# Support for colorized terminal output # Support for colorized terminal output

View File

@ -139,7 +139,7 @@ def test_adjust(mocker, edge_conf):
assert (edge.adjust(pairs) == ['E/F', 'C/D']) assert (edge.adjust(pairs) == ['E/F', 'C/D'])
def test_stoploss(mocker, edge_conf): def test_edge_get_stoploss(mocker, edge_conf):
freqtrade = get_patched_freqtradebot(mocker, edge_conf) freqtrade = get_patched_freqtradebot(mocker, edge_conf)
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy) edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
@ -150,10 +150,10 @@ def test_stoploss(mocker, edge_conf):
} }
)) ))
assert edge.stoploss('E/F') == -0.01 assert edge.get_stoploss('E/F') == -0.01
def test_nonexisting_stoploss(mocker, edge_conf): def test_nonexisting_get_stoploss(mocker, edge_conf):
freqtrade = get_patched_freqtradebot(mocker, edge_conf) freqtrade = get_patched_freqtradebot(mocker, edge_conf)
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy) edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
@ -162,7 +162,7 @@ def test_nonexisting_stoploss(mocker, edge_conf):
} }
)) ))
assert edge.stoploss('N/O') == -0.1 assert edge.get_stoploss('N/O') == -0.1
def test_edge_stake_amount(mocker, edge_conf): def test_edge_stake_amount(mocker, edge_conf):

View File

@ -20,7 +20,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers
(0.99, 220 * 1.01, "buy"), (0.99, 220 * 1.01, "buy"),
(0.98, 220 * 1.02, "buy"), (0.98, 220 * 1.02, "buy"),
]) ])
def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side, trademode): def test_create_stoploss_order_binance(default_conf, mocker, limitratio, expected, side, trademode):
api_mock = MagicMock() api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_type = 'stop_loss_limit' if trademode == TradingMode.SPOT else 'stop' order_type = 'stop_loss_limit' if trademode == TradingMode.SPOT else 'stop'
@ -40,7 +40,7 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
order = exchange.stoploss( order = exchange.create_stoploss(
pair='ETH/BTC', pair='ETH/BTC',
amount=1, amount=1,
stop_price=190, stop_price=190,
@ -54,7 +54,7 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side
if limitratio is not None: if limitratio is not None:
order_types.update({'stoploss_on_exchange_limit_ratio': limitratio}) order_types.update({'stoploss_on_exchange_limit_ratio': limitratio})
order = exchange.stoploss( order = exchange.create_stoploss(
pair='ETH/BTC', pair='ETH/BTC',
amount=1, amount=1,
stop_price=220, stop_price=220,
@ -82,7 +82,7 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
exchange.stoploss( exchange.create_stoploss(
pair='ETH/BTC', pair='ETH/BTC',
amount=1, amount=1,
stop_price=220, stop_price=220,
@ -94,7 +94,7 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side
api_mock.create_order = MagicMock( api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("binance Order would trigger immediately.")) side_effect=ccxt.InvalidOrder("binance Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
exchange.stoploss( exchange.create_stoploss(
pair='ETH/BTC', pair='ETH/BTC',
amount=1, amount=1,
stop_price=220, stop_price=220,
@ -104,12 +104,12 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected, side
) )
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance", ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance",
"stoploss", "create_order", retries=1, "create_stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={}, pair='ETH/BTC', amount=1, stop_price=220, order_types={},
side=side, leverage=1.0) side=side, leverage=1.0)
def test_stoploss_order_dry_run_binance(default_conf, mocker): def test_create_stoploss_order_dry_run_binance(default_conf, mocker):
api_mock = MagicMock() api_mock = MagicMock()
order_type = 'stop_loss_limit' order_type = 'stop_loss_limit'
default_conf['dry_run'] = True default_conf['dry_run'] = True
@ -119,7 +119,7 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
order = exchange.stoploss( order = exchange.create_stoploss(
pair='ETH/BTC', pair='ETH/BTC',
amount=1, amount=1,
stop_price=190, stop_price=190,
@ -130,7 +130,7 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker):
api_mock.create_order.reset_mock() api_mock.create_order.reset_mock()
order = exchange.stoploss( order = exchange.create_stoploss(
pair='ETH/BTC', pair='ETH/BTC',
amount=1, amount=1,
stop_price=220, stop_price=220,
@ -495,7 +495,8 @@ def test_fill_leverage_tiers_binance_dryrun(default_conf, mocker, leverage_tiers
for key, value in leverage_tiers.items(): for key, value in leverage_tiers.items():
v = exchange._leverage_tiers[key] v = exchange._leverage_tiers[key]
assert isinstance(v, list) assert isinstance(v, list)
assert len(v) == len(value) # Assert if conftest leverage tiers have less or equal tiers than the exchange
assert len(v) >= len(value)
def test_additional_exchange_init_binance(default_conf, mocker): def test_additional_exchange_init_binance(default_conf, mocker):

View File

@ -534,8 +534,7 @@ class TestCCXTExchange():
def test_ccxt__async_get_candle_history(self, exchange: EXCHANGE_FIXTURE_TYPE): def test_ccxt__async_get_candle_history(self, exchange: EXCHANGE_FIXTURE_TYPE):
exc, exchangename = exchange exc, exchangename = exchange
if exchangename in ('binanceus', 'bittrex'): if exchangename in ('bittrex'):
# TODO: reenable binanceus test once downtime "ages out" (2023-02-06)
# For some weired reason, this test returns random lengths for bittrex. # For some weired reason, this test returns random lengths for bittrex.
pytest.skip("Exchange doesn't provide stable ohlcv history") pytest.skip("Exchange doesn't provide stable ohlcv history")

View File

@ -1223,7 +1223,7 @@ def test_create_dry_run_order_fees(
'freqtrade.exchange.Exchange.get_fee', 'freqtrade.exchange.Exchange.get_fee',
side_effect=lambda symbol, taker_or_maker: 2.0 if taker_or_maker == 'taker' else 1.0 side_effect=lambda symbol, taker_or_maker: 2.0 if taker_or_maker == 'taker' else 1.0
) )
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed',
return_value=price_side == 'other') return_value=price_side == 'other')
exchange = get_patched_exchange(mocker, default_conf) exchange = get_patched_exchange(mocker, default_conf)
@ -1241,25 +1241,27 @@ def test_create_dry_run_order_fees(
else: else:
assert order['fee'] is None assert order['fee'] is None
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed',
return_value=price_side != 'other') return_value=price_side != 'other')
order1 = exchange.fetch_dry_run_order(order['id']) order1 = exchange.fetch_dry_run_order(order['id'])
assert order1['fee']['rate'] == fee assert order1['fee']['rate'] == fee
@pytest.mark.parametrize("side,price,filled", [ @pytest.mark.parametrize("side,price,filled,converted", [
# order_book_l2_usd spread: # order_book_l2_usd spread:
# best ask: 25.566 # best ask: 25.566
# best bid: 25.563 # best bid: 25.563
("buy", 25.563, False), ("buy", 25.563, False, False),
("buy", 25.566, True), ("buy", 25.566, True, False),
("sell", 25.566, False), ("sell", 25.566, False, False),
("sell", 25.563, True), ("sell", 25.563, True, False),
("buy", 29.563, True, True),
("sell", 21.563, True, True),
]) ])
@pytest.mark.parametrize("exchange_name", EXCHANGES) @pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, filled, def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, filled, caplog,
exchange_name, order_book_l2_usd): exchange_name, order_book_l2_usd, converted):
default_conf['dry_run'] = True default_conf['dry_run'] = True
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
mocker.patch.multiple('freqtrade.exchange.Exchange', mocker.patch.multiple('freqtrade.exchange.Exchange',
@ -1279,9 +1281,16 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, price, fill
assert 'id' in order assert 'id' in order
assert f'dry_run_{side}_' in order["id"] assert f'dry_run_{side}_' in order["id"]
assert order["side"] == side assert order["side"] == side
assert order["type"] == "limit" if not converted:
assert order["average"] == price
assert order["type"] == "limit"
else:
# Converted to market order
assert order["type"] == "market"
assert 25.5 < order["average"] < 25.6
assert log_has_re(r"Converted .* to market order.*", caplog)
assert order["symbol"] == "LTC/USDT" assert order["symbol"] == "LTC/USDT"
assert order["average"] == price
assert order['status'] == 'open' if not filled else 'closed' assert order['status'] == 'open' if not filled else 'closed'
order_book_l2_usd.reset_mock() order_book_l2_usd.reset_mock()
@ -3018,7 +3027,7 @@ def test_get_historic_trades_notsupported(default_conf, mocker, caplog, exchange
def test_cancel_order_dry_run(default_conf, mocker, exchange_name): def test_cancel_order_dry_run(default_conf, mocker, exchange_name):
default_conf['dry_run'] = True default_conf['dry_run'] = True
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=True) mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=True)
assert exchange.cancel_order(order_id='123', pair='TKN/BTC') == {} assert exchange.cancel_order(order_id='123', pair='TKN/BTC') == {}
assert exchange.cancel_stoploss_order(order_id='123', pair='TKN/BTC') == {} assert exchange.cancel_stoploss_order(order_id='123', pair='TKN/BTC') == {}
@ -3380,7 +3389,7 @@ def test_get_fee(default_conf, mocker, exchange_name):
def test_stoploss_order_unsupported_exchange(default_conf, mocker): def test_stoploss_order_unsupported_exchange(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, id='bittrex') exchange = get_patched_exchange(mocker, default_conf, id='bittrex')
with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"):
exchange.stoploss( exchange.create_stoploss(
pair='ETH/BTC', pair='ETH/BTC',
amount=1, amount=1,
stop_price=220, stop_price=220,
@ -5318,7 +5327,7 @@ def test_stoploss_contract_size(mocker, default_conf, contract_size, order_amoun
exchange.get_contract_size = MagicMock(return_value=contract_size) exchange.get_contract_size = MagicMock(return_value=contract_size)
api_mock.create_order.reset_mock() api_mock.create_order.reset_mock()
order = exchange.stoploss( order = exchange.create_stoploss(
pair='ETH/BTC', pair='ETH/BTC',
amount=100, amount=100,
stop_price=220, stop_price=220,

View File

@ -14,7 +14,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers
(0.99, 220 * 0.99, "sell"), (0.99, 220 * 0.99, "sell"),
(0.98, 220 * 0.98, "sell"), (0.98, 220 * 0.98, "sell"),
]) ])
def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected, side): def test_create_stoploss_order_huobi(default_conf, mocker, limitratio, expected, side):
api_mock = MagicMock() api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_type = 'stop-limit' order_type = 'stop-limit'
@ -32,15 +32,15 @@ def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected, side):
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss_on_exchange_limit_ratio': 1.05}, order_types={'stoploss_on_exchange_limit_ratio': 1.05},
side=side, side=side,
leverage=1.0) leverage=1.0)
api_mock.create_order.reset_mock() api_mock.create_order.reset_mock()
order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio} order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio}
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types, order = exchange.create_stoploss(
side=side, leverage=1.0) pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types, side=side, leverage=1.0)
assert 'id' in order assert 'id' in order
assert 'info' in order assert 'info' in order
@ -59,23 +59,23 @@ def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected, side):
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={}, side=side, leverage=1.0) order_types={}, side=side, leverage=1.0)
with pytest.raises(InvalidOrderException): with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock( api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("binance Order would trigger immediately.")) side_effect=ccxt.InvalidOrder("binance Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={}, side=side, leverage=1.0) order_types={}, side=side, leverage=1.0)
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "huobi", ccxt_exceptionhandlers(mocker, default_conf, api_mock, "huobi",
"stoploss", "create_order", retries=1, "create_stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={}, pair='ETH/BTC', amount=1, stop_price=220, order_types={},
side=side, leverage=1.0) side=side, leverage=1.0)
def test_stoploss_order_dry_run_huobi(default_conf, mocker): def test_create_stoploss_order_dry_run_huobi(default_conf, mocker):
api_mock = MagicMock() api_mock = MagicMock()
order_type = 'stop-limit' order_type = 'stop-limit'
default_conf['dry_run'] = True default_conf['dry_run'] = True
@ -85,14 +85,14 @@ def test_stoploss_order_dry_run_huobi(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi')
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss_on_exchange_limit_ratio': 1.05}, order_types={'stoploss_on_exchange_limit_ratio': 1.05},
side='sell', leverage=1.0) side='sell', leverage=1.0)
api_mock.create_order.reset_mock() api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={}, side='sell', leverage=1.0) order_types={}, side='sell', leverage=1.0)
assert 'id' in order assert 'id' in order
assert 'info' in order assert 'info' in order

View File

@ -179,7 +179,7 @@ def test_get_balances_prod(default_conf, mocker):
("sell", 217.8), ("sell", 217.8),
("buy", 222.2), ("buy", 222.2),
]) ])
def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedprice): def test_create_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedprice):
api_mock = MagicMock() api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
@ -196,7 +196,7 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedpr
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
order = exchange.stoploss( order = exchange.create_stoploss(
pair='ETH/BTC', pair='ETH/BTC',
amount=1, amount=1,
stop_price=220, stop_price=220,
@ -230,7 +230,7 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedpr
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
exchange.stoploss( exchange.create_stoploss(
pair='ETH/BTC', pair='ETH/BTC',
amount=1, amount=1,
stop_price=220, stop_price=220,
@ -243,7 +243,7 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedpr
api_mock.create_order = MagicMock( api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("kraken Order would trigger immediately.")) side_effect=ccxt.InvalidOrder("kraken Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
exchange.stoploss( exchange.create_stoploss(
pair='ETH/BTC', pair='ETH/BTC',
amount=1, amount=1,
stop_price=220, stop_price=220,
@ -253,13 +253,13 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedpr
) )
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken", ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken",
"stoploss", "create_order", retries=1, "create_stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={}, pair='ETH/BTC', amount=1, stop_price=220, order_types={},
side=side, leverage=1.0) side=side, leverage=1.0)
@pytest.mark.parametrize('side', ['buy', 'sell']) @pytest.mark.parametrize('side', ['buy', 'sell'])
def test_stoploss_order_dry_run_kraken(default_conf, mocker, side): def test_create_stoploss_order_dry_run_kraken(default_conf, mocker, side):
api_mock = MagicMock() api_mock = MagicMock()
default_conf['dry_run'] = True default_conf['dry_run'] = True
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
@ -269,7 +269,7 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker, side):
api_mock.create_order.reset_mock() api_mock.create_order.reset_mock()
order = exchange.stoploss( order = exchange.create_stoploss(
pair='ETH/BTC', pair='ETH/BTC',
amount=1, amount=1,
stop_price=220, stop_price=220,

View File

@ -15,7 +15,7 @@ from tests.exchange.test_exchange import ccxt_exceptionhandlers
(0.99, 220 * 0.99, "sell"), (0.99, 220 * 0.99, "sell"),
(0.98, 220 * 0.98, "sell"), (0.98, 220 * 0.98, "sell"),
]) ])
def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, side, order_type): def test_create_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, side, order_type):
api_mock = MagicMock() api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
@ -32,18 +32,18 @@ def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, side,
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
if order_type == 'limit': if order_type == 'limit':
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={ order_types={
'stoploss': order_type, 'stoploss': order_type,
'stoploss_on_exchange_limit_ratio': 1.05}, 'stoploss_on_exchange_limit_ratio': 1.05},
side=side, leverage=1.0) side=side, leverage=1.0)
api_mock.create_order.reset_mock() api_mock.create_order.reset_mock()
order_types = {'stoploss': order_type} order_types = {'stoploss': order_type}
if limitratio is not None: if limitratio is not None:
order_types.update({'stoploss_on_exchange_limit_ratio': limitratio}) order_types.update({'stoploss_on_exchange_limit_ratio': limitratio})
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types=order_types, side=side, leverage=1.0) order_types=order_types, side=side, leverage=1.0)
assert 'id' in order assert 'id' in order
assert 'info' in order assert 'info' in order
@ -67,18 +67,18 @@ def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, side,
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={}, side=side, leverage=1.0) order_types={}, side=side, leverage=1.0)
with pytest.raises(InvalidOrderException): with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock( api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("kucoin Order would trigger immediately.")) side_effect=ccxt.InvalidOrder("kucoin Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={}, side=side, leverage=1.0) order_types={}, side=side, leverage=1.0)
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kucoin", ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kucoin",
"stoploss", "create_order", retries=1, "create_stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={}, pair='ETH/BTC', amount=1, stop_price=220, order_types={},
side=side, leverage=1.0) side=side, leverage=1.0)
@ -93,15 +93,15 @@ def test_stoploss_order_dry_run_kucoin(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin')
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=190,
order_types={'stoploss': 'limit', order_types={'stoploss': 'limit',
'stoploss_on_exchange_limit_ratio': 1.05}, 'stoploss_on_exchange_limit_ratio': 1.05},
side='sell', leverage=1.0) side='sell', leverage=1.0)
api_mock.create_order.reset_mock() api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order = exchange.create_stoploss(pair='ETH/BTC', amount=1, stop_price=220,
order_types={}, side='sell', leverage=1.0) order_types={}, side='sell', leverage=1.0)
assert 'id' in order assert 'id' in order
assert 'info' in order assert 'info' in order

View File

@ -112,7 +112,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker, fetch_ticker=ticker,
get_fee=fee, get_fee=fee,
_is_dry_limit_order_filled=MagicMock(side_effect=[False, True]), _dry_is_price_crossed=MagicMock(side_effect=[False, True]),
) )
freqtradebot = get_patched_freqtradebot(mocker, default_conf) freqtradebot = get_patched_freqtradebot(mocker, default_conf)
@ -226,7 +226,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
with pytest.raises(RPCException, match=r'.*no active trade*'): with pytest.raises(RPCException, match=r'.*no active trade*'):
rpc._rpc_status_table(default_conf['stake_currency'], 'USD') rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=False) mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=False)
freqtradebot.enter_positions() freqtradebot.enter_positions()
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
@ -237,7 +237,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
assert '0.00' == result[0][3] assert '0.00' == result[0][3]
assert isnan(fiat_profit_sum) assert isnan(fiat_profit_sum)
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=True) mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=True)
freqtradebot.process() freqtradebot.process()
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
@ -688,7 +688,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
'filled': 0.0, 'filled': 0.0,
} }
), ),
_is_dry_limit_order_filled=MagicMock(return_value=True), _dry_is_price_crossed=MagicMock(return_value=True),
get_fee=fee, get_fee=fee,
) )
mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=1000) mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=1000)
@ -726,7 +726,7 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None:
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
assert cancel_order_mock.call_count == 0 assert cancel_order_mock.call_count == 0
mocker.patch( mocker.patch(
'freqtrade.exchange.Exchange._is_dry_limit_order_filled', MagicMock(return_value=False)) 'freqtrade.exchange.Exchange._dry_is_price_crossed', MagicMock(return_value=False))
freqtradebot.enter_positions() freqtradebot.enter_positions()
# make an limit-buy open trade # make an limit-buy open trade
trade = Trade.query.filter(Trade.id == '3').first() trade = Trade.query.filter(Trade.id == '3').first()

View File

@ -1280,7 +1280,7 @@ def test_api_forceexit(botclient, mocker, ticker, fee, markets):
fetch_ticker=ticker, fetch_ticker=ticker,
get_fee=fee, get_fee=fee,
markets=PropertyMock(return_value=markets), markets=PropertyMock(return_value=markets),
_is_dry_limit_order_filled=MagicMock(return_value=True), _dry_is_price_crossed=MagicMock(return_value=True),
) )
patch_get_signal(ftbot) patch_get_signal(ftbot)

View File

@ -313,7 +313,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker, fetch_ticker=ticker,
get_fee=fee, get_fee=fee,
_is_dry_limit_order_filled=MagicMock(return_value=True), _dry_is_price_crossed=MagicMock(return_value=True),
) )
status_table = MagicMock() status_table = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
@ -930,7 +930,7 @@ def test_telegram_forceexit_handle(default_conf, update, ticker, fee,
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker, fetch_ticker=ticker,
get_fee=fee, get_fee=fee,
_is_dry_limit_order_filled=MagicMock(return_value=True), _dry_is_price_crossed=MagicMock(return_value=True),
) )
freqtradebot = FreqtradeBot(default_conf) freqtradebot = FreqtradeBot(default_conf)
@ -999,7 +999,7 @@ def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee,
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker, fetch_ticker=ticker,
get_fee=fee, get_fee=fee,
_is_dry_limit_order_filled=MagicMock(return_value=True), _dry_is_price_crossed=MagicMock(return_value=True),
) )
freqtradebot = FreqtradeBot(default_conf) freqtradebot = FreqtradeBot(default_conf)
@ -1070,7 +1070,7 @@ def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) -> None
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker, fetch_ticker=ticker,
get_fee=fee, get_fee=fee,
_is_dry_limit_order_filled=MagicMock(return_value=True), _dry_is_price_crossed=MagicMock(return_value=True),
) )
default_conf['max_open_trades'] = 4 default_conf['max_open_trades'] = 4
freqtradebot = FreqtradeBot(default_conf) freqtradebot = FreqtradeBot(default_conf)
@ -1155,7 +1155,7 @@ def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> None:
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker, fetch_ticker=ticker,
get_fee=fee, get_fee=fee,
_is_dry_limit_order_filled=MagicMock(return_value=True), _dry_is_price_crossed=MagicMock(return_value=True),
) )
femock = mocker.patch('freqtrade.rpc.rpc.RPC._rpc_force_exit') femock = mocker.patch('freqtrade.rpc.rpc.RPC._rpc_force_exit')
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)

View File

@ -452,8 +452,8 @@ def test_min_roi_reached3(default_conf, fee) -> None:
(0.05, 0.9, ExitType.NONE, None, False, True, 0.09, 0.9, ExitType.NONE, (0.05, 0.9, ExitType.NONE, None, False, True, 0.09, 0.9, ExitType.NONE,
lambda **kwargs: None), lambda **kwargs: None),
]) ])
def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, liq, trailing, custom, def test_ft_stoploss_reached(default_conf, fee, profit, adjusted, expected, liq, trailing, custom,
profit2, adjusted2, expected2, custom_stop) -> None: profit2, adjusted2, expected2, custom_stop) -> None:
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
trade = Trade( trade = Trade(
@ -477,9 +477,9 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, liq, t
now = arrow.utcnow().datetime now = arrow.utcnow().datetime
current_rate = trade.open_rate * (1 + profit) current_rate = trade.open_rate * (1 + profit)
sl_flag = strategy.stop_loss_reached(current_rate=current_rate, trade=trade, sl_flag = strategy.ft_stoploss_reached(current_rate=current_rate, trade=trade,
current_time=now, current_profit=profit, current_time=now, current_profit=profit,
force_stoploss=0, high=None) force_stoploss=0, high=None)
assert isinstance(sl_flag, ExitCheckTuple) assert isinstance(sl_flag, ExitCheckTuple)
assert sl_flag.exit_type == expected assert sl_flag.exit_type == expected
if expected == ExitType.NONE: if expected == ExitType.NONE:
@ -489,9 +489,9 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, liq, t
assert round(trade.stop_loss, 2) == adjusted assert round(trade.stop_loss, 2) == adjusted
current_rate2 = trade.open_rate * (1 + profit2) current_rate2 = trade.open_rate * (1 + profit2)
sl_flag = strategy.stop_loss_reached(current_rate=current_rate2, trade=trade, sl_flag = strategy.ft_stoploss_reached(current_rate=current_rate2, trade=trade,
current_time=now, current_profit=profit2, current_time=now, current_profit=profit2,
force_stoploss=0, high=None) force_stoploss=0, high=None)
assert sl_flag.exit_type == expected2 assert sl_flag.exit_type == expected2
if expected2 == ExitType.NONE: if expected2 == ExitType.NONE:
assert sl_flag.exit_flag is False assert sl_flag.exit_flag is False
@ -579,7 +579,7 @@ def test_should_sell(default_conf, fee) -> None:
assert res == [ExitCheckTuple(exit_type=ExitType.ROI)] assert res == [ExitCheckTuple(exit_type=ExitType.ROI)]
strategy.min_roi_reached = MagicMock(return_value=True) strategy.min_roi_reached = MagicMock(return_value=True)
strategy.stop_loss_reached = MagicMock( strategy.ft_stoploss_reached = MagicMock(
return_value=ExitCheckTuple(exit_type=ExitType.STOP_LOSS)) return_value=ExitCheckTuple(exit_type=ExitType.STOP_LOSS))
res = strategy.should_exit(trade, 1, now, res = strategy.should_exit(trade, 1, now,
@ -603,7 +603,7 @@ def test_should_sell(default_conf, fee) -> None:
ExitCheckTuple(exit_type=ExitType.ROI), ExitCheckTuple(exit_type=ExitType.ROI),
] ]
strategy.stop_loss_reached = MagicMock( strategy.ft_stoploss_reached = MagicMock(
return_value=ExitCheckTuple(exit_type=ExitType.TRAILING_STOP_LOSS)) return_value=ExitCheckTuple(exit_type=ExitType.TRAILING_STOP_LOSS))
# Regular exit signal # Regular exit signal
res = strategy.should_exit(trade, 1, now, res = strategy.should_exit(trade, 1, now,

View File

@ -272,7 +272,7 @@ def test_total_open_trades_stakes(mocker, default_conf_usdt, ticker_usdt, fee) -
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt, fetch_ticker=ticker_usdt,
get_fee=fee, get_fee=fee,
_is_dry_limit_order_filled=MagicMock(return_value=False), _dry_is_price_crossed=MagicMock(return_value=False),
) )
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
@ -307,7 +307,7 @@ def test_create_trade(default_conf_usdt, ticker_usdt, limit_order,
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt, fetch_ticker=ticker_usdt,
get_fee=fee, get_fee=fee,
_is_dry_limit_order_filled=MagicMock(return_value=False), _dry_is_price_crossed=MagicMock(return_value=False),
) )
# Save state of current whitelist # Save state of current whitelist
@ -1070,7 +1070,7 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_sho
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[]) mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
stoploss = MagicMock(return_value={'id': 13434334}) stoploss = MagicMock(return_value={'id': 13434334})
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss) mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss)
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True freqtrade.strategy.order_types['stoploss_on_exchange'] = True
@ -1109,7 +1109,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
exit_order, exit_order,
]), ]),
get_fee=fee, get_fee=fee,
stoploss=stoploss create_stoploss=stoploss
) )
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
@ -1191,7 +1191,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
caplog.clear() caplog.clear()
mocker.patch( mocker.patch(
'freqtrade.exchange.Exchange.stoploss', 'freqtrade.exchange.Exchange.create_stoploss',
side_effect=ExchangeError() side_effect=ExchangeError()
) )
trade.is_open = True trade.is_open = True
@ -1205,7 +1205,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
stoploss.reset_mock() stoploss.reset_mock()
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order',
side_effect=InvalidOrderException()) side_effect=InvalidOrderException())
mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss) mocker.patch('freqtrade.exchange.Exchange.create_stoploss', stoploss)
freqtrade.handle_stoploss_on_exchange(trade) freqtrade.handle_stoploss_on_exchange(trade)
assert stoploss.call_count == 1 assert stoploss.call_count == 1
@ -1215,7 +1215,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
trade.is_open = False trade.is_open = False
stoploss.reset_mock() stoploss.reset_mock()
mocker.patch('freqtrade.exchange.Exchange.fetch_order') mocker.patch('freqtrade.exchange.Exchange.fetch_order')
mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss) mocker.patch('freqtrade.exchange.Exchange.create_stoploss', stoploss)
assert freqtrade.handle_stoploss_on_exchange(trade) is False assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert stoploss.call_count == 0 assert stoploss.call_count == 0
@ -1240,7 +1240,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order_with_result', mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order_with_result',
side_effect=InvalidOrderException()) side_effect=InvalidOrderException())
mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_cancelled) mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_cancelled)
mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss) mocker.patch('freqtrade.exchange.Exchange.create_stoploss', stoploss)
assert freqtrade.handle_stoploss_on_exchange(trade) is False assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert trade.stoploss_order_id is None assert trade.stoploss_order_id is None
assert trade.is_open is False assert trade.is_open is False
@ -1271,7 +1271,7 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Binance', 'freqtrade.exchange.Binance',
fetch_stoploss_order=MagicMock(return_value={'status': 'canceled', 'id': 100}), fetch_stoploss_order=MagicMock(return_value={'status': 'canceled', 'id': 100}),
stoploss=MagicMock(side_effect=ExchangeError()), create_stoploss=MagicMock(side_effect=ExchangeError()),
) )
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
@ -1315,7 +1315,7 @@ def test_create_stoploss_order_invalid_order(
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Binance', 'freqtrade.exchange.Binance',
fetch_order=MagicMock(return_value={'status': 'canceled'}), fetch_order=MagicMock(return_value={'status': 'canceled'}),
stoploss=MagicMock(side_effect=InvalidOrderException()), create_stoploss=MagicMock(side_effect=InvalidOrderException()),
) )
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
@ -1367,7 +1367,7 @@ def test_create_stoploss_order_insufficient_funds(
) )
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Binance', 'freqtrade.exchange.Binance',
stoploss=MagicMock(side_effect=InsufficientFundsError()), create_stoploss=MagicMock(side_effect=InsufficientFundsError()),
) )
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short) patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True freqtrade.strategy.order_types['stoploss_on_exchange'] = True
@ -1417,7 +1417,7 @@ def test_handle_stoploss_on_exchange_trailing(
) )
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Binance', 'freqtrade.exchange.Binance',
stoploss=stoploss, create_stoploss=stoploss,
stoploss_adjust=MagicMock(return_value=True), stoploss_adjust=MagicMock(return_value=True),
) )
@ -1478,7 +1478,7 @@ def test_handle_stoploss_on_exchange_trailing(
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
stoploss_order_mock = MagicMock(return_value={'id': 'so1'}) stoploss_order_mock = MagicMock(return_value={'id': 'so1'})
mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', cancel_order_mock) mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', cancel_order_mock)
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss_order_mock) mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss_order_mock)
# stoploss should not be updated as the interval is 60 seconds # stoploss should not be updated as the interval is 60 seconds
assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_trade(trade) is False
@ -1542,7 +1542,7 @@ def test_handle_stoploss_on_exchange_trailing_error(
) )
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Binance', 'freqtrade.exchange.Binance',
stoploss=stoploss, create_stoploss=stoploss,
stoploss_adjust=MagicMock(return_value=True), stoploss_adjust=MagicMock(return_value=True),
) )
@ -1593,7 +1593,7 @@ def test_handle_stoploss_on_exchange_trailing_error(
trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime
caplog.clear() caplog.clear()
cancel_mock = mocker.patch("freqtrade.exchange.Binance.cancel_stoploss_order", MagicMock()) cancel_mock = mocker.patch("freqtrade.exchange.Binance.cancel_stoploss_order", MagicMock())
mocker.patch("freqtrade.exchange.Binance.stoploss", side_effect=ExchangeError()) mocker.patch("freqtrade.exchange.Binance.create_stoploss", side_effect=ExchangeError())
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging) freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
assert cancel_mock.call_count == 1 assert cancel_mock.call_count == 1
assert log_has_re(r"Could not create trailing stoploss order for pair ETH/USDT\..*", caplog) assert log_has_re(r"Could not create trailing stoploss order for pair ETH/USDT\..*", caplog)
@ -1611,7 +1611,7 @@ def test_stoploss_on_exchange_price_rounding(
adjust_mock = MagicMock(return_value=False) adjust_mock = MagicMock(return_value=False)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Binance', 'freqtrade.exchange.Binance',
stoploss=stoploss_mock, create_stoploss=stoploss_mock,
stoploss_adjust=adjust_mock, stoploss_adjust=adjust_mock,
price_to_precision=price_mock, price_to_precision=price_mock,
) )
@ -1650,7 +1650,7 @@ def test_handle_stoploss_on_exchange_custom_stop(
) )
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Binance', 'freqtrade.exchange.Binance',
stoploss=stoploss, create_stoploss=stoploss,
stoploss_adjust=MagicMock(return_value=True), stoploss_adjust=MagicMock(return_value=True),
) )
@ -1710,7 +1710,7 @@ def test_handle_stoploss_on_exchange_custom_stop(
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
stoploss_order_mock = MagicMock(return_value={'id': 'so1'}) stoploss_order_mock = MagicMock(return_value={'id': 'so1'})
mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', cancel_order_mock) mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', cancel_order_mock)
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss_order_mock) mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss_order_mock)
# stoploss should not be updated as the interval is 60 seconds # stoploss should not be updated as the interval is 60 seconds
assert freqtrade.handle_trade(trade) is False assert freqtrade.handle_trade(trade) is False
@ -1775,7 +1775,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde
{'id': exit_order['id']}, {'id': exit_order['id']},
]), ]),
get_fee=fee, get_fee=fee,
stoploss=stoploss, create_stoploss=stoploss,
) )
# enabling TSL # enabling TSL
@ -1827,7 +1827,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
stoploss_order_mock = MagicMock() stoploss_order_mock = MagicMock()
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', cancel_order_mock) mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', cancel_order_mock)
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss_order_mock) mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss_order_mock)
# price goes down 5% # price goes down 5%
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={ mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={
@ -3257,7 +3257,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt, fetch_ticker=ticker_usdt,
get_fee=fee, get_fee=fee,
_is_dry_limit_order_filled=MagicMock(return_value=False), _dry_is_price_crossed=MagicMock(return_value=False),
) )
patch_whitelist(mocker, default_conf_usdt) patch_whitelist(mocker, default_conf_usdt)
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
@ -3340,7 +3340,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt, fetch_ticker=ticker_usdt,
get_fee=fee, get_fee=fee,
_is_dry_limit_order_filled=MagicMock(return_value=False), _dry_is_price_crossed=MagicMock(return_value=False),
) )
patch_whitelist(mocker, default_conf_usdt) patch_whitelist(mocker, default_conf_usdt)
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
@ -3409,7 +3409,7 @@ def test_execute_trade_exit_custom_exit_price(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt, fetch_ticker=ticker_usdt,
get_fee=fee, get_fee=fee,
_is_dry_limit_order_filled=MagicMock(return_value=False), _dry_is_price_crossed=MagicMock(return_value=False),
) )
config = deepcopy(default_conf_usdt) config = deepcopy(default_conf_usdt)
config['custom_price_max_distance_ratio'] = 0.1 config['custom_price_max_distance_ratio'] = 0.1
@ -3490,7 +3490,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt, fetch_ticker=ticker_usdt,
get_fee=fee, get_fee=fee,
_is_dry_limit_order_filled=MagicMock(return_value=False), _dry_is_price_crossed=MagicMock(return_value=False),
) )
patch_whitelist(mocker, default_conf_usdt) patch_whitelist(mocker, default_conf_usdt)
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
@ -3607,9 +3607,9 @@ def test_execute_trade_exit_with_stoploss_on_exchange(
get_fee=fee, get_fee=fee,
amount_to_precision=lambda s, x, y: y, amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y, price_to_precision=lambda s, x, y: y,
stoploss=stoploss, create_stoploss=stoploss,
cancel_stoploss_order=cancel_order, cancel_stoploss_order=cancel_order,
_is_dry_limit_order_filled=MagicMock(side_effect=[True, False]), _dry_is_price_crossed=MagicMock(side_effect=[True, False]),
) )
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
@ -3658,7 +3658,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(
get_fee=fee, get_fee=fee,
amount_to_precision=lambda s, x, y: y, amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y, price_to_precision=lambda s, x, y: y,
_is_dry_limit_order_filled=MagicMock(side_effect=[False, True]), _dry_is_price_crossed=MagicMock(side_effect=[False, True]),
) )
stoploss = MagicMock(return_value={ stoploss = MagicMock(return_value={
@ -3668,7 +3668,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(
} }
}) })
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss) mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss)
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True freqtrade.strategy.order_types['stoploss_on_exchange'] = True
@ -3753,7 +3753,7 @@ def test_execute_trade_exit_market_order(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt, fetch_ticker=ticker_usdt,
get_fee=fee, get_fee=fee,
_is_dry_limit_order_filled=MagicMock(return_value=True), _dry_is_price_crossed=MagicMock(return_value=True),
get_funding_fees=MagicMock(side_effect=ExchangeError()), get_funding_fees=MagicMock(side_effect=ExchangeError()),
) )
patch_whitelist(mocker, default_conf_usdt) patch_whitelist(mocker, default_conf_usdt)
@ -3771,7 +3771,7 @@ def test_execute_trade_exit_market_order(
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker_usdt_sell_up, fetch_ticker=ticker_usdt_sell_up,
_is_dry_limit_order_filled=MagicMock(return_value=False), _dry_is_price_crossed=MagicMock(return_value=False),
) )
freqtrade.config['order_types']['exit'] = 'market' freqtrade.config['order_types']['exit'] = 'market'
@ -3902,7 +3902,7 @@ def test_exit_profit_only(
if exit_type == ExitType.EXIT_SIGNAL.value: if exit_type == ExitType.EXIT_SIGNAL.value:
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
else: else:
freqtrade.strategy.stop_loss_reached = MagicMock(return_value=ExitCheckTuple( freqtrade.strategy.ft_stoploss_reached = MagicMock(return_value=ExitCheckTuple(
exit_type=ExitType.NONE)) exit_type=ExitType.NONE))
freqtrade.enter_positions() freqtrade.enter_positions()
@ -4283,7 +4283,7 @@ def test_disable_ignore_roi_if_entry_signal(default_conf_usdt, limit_order, limi
{'id': 1234553383} {'id': 1234553383}
]), ]),
get_fee=fee, get_fee=fee,
_is_dry_limit_order_filled=MagicMock(return_value=False), _dry_is_price_crossed=MagicMock(return_value=False),
) )
default_conf_usdt['exit_pricing'] = { default_conf_usdt['exit_pricing'] = {
'ignore_roi_if_entry_signal': False 'ignore_roi_if_entry_signal': False

View File

@ -56,7 +56,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
[ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)]] [ExitCheckTuple(exit_type=ExitType.EXIT_SIGNAL)]]
) )
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss) mocker.patch('freqtrade.exchange.Binance.create_stoploss', stoploss)
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker, fetch_ticker=ticker,
@ -367,7 +367,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
amount_to_precision=lambda s, x, y: y, amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y, price_to_precision=lambda s, x, y: y,
) )
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=False) mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=False)
mocker.patch("freqtrade.exchange.Exchange.get_max_leverage", return_value=10) mocker.patch("freqtrade.exchange.Exchange.get_max_leverage", return_value=10)
mocker.patch("freqtrade.exchange.Exchange.get_funding_fees", return_value=0) mocker.patch("freqtrade.exchange.Exchange.get_funding_fees", return_value=0)
mocker.patch("freqtrade.exchange.Exchange.get_maintenance_ratio_and_amt", return_value=(0, 0)) mocker.patch("freqtrade.exchange.Exchange.get_maintenance_ratio_and_amt", return_value=(0, 0))
@ -413,7 +413,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
assert trade.initial_stop_loss_pct is None assert trade.initial_stop_loss_pct is None
# Fill order # Fill order
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=True) mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=True)
freqtrade.process() freqtrade.process()
trade = Trade.get_trades().first() trade = Trade.get_trades().first()
assert len(trade.orders) == 2 assert len(trade.orders) == 2
@ -428,7 +428,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
# 2nd order - not filling # 2nd order - not filling
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=120) freqtrade.strategy.adjust_trade_position = MagicMock(return_value=120)
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=False) mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=False)
freqtrade.process() freqtrade.process()
trade = Trade.get_trades().first() trade = Trade.get_trades().first()
@ -452,7 +452,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
# Fill DCA order # Fill DCA order
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=None) freqtrade.strategy.adjust_trade_position = MagicMock(return_value=None)
mocker.patch('freqtrade.exchange.Exchange._is_dry_limit_order_filled', return_value=True) mocker.patch('freqtrade.exchange.Exchange._dry_is_price_crossed', return_value=True)
freqtrade.strategy.adjust_entry_price = MagicMock(side_effect=ValueError) freqtrade.strategy.adjust_entry_price = MagicMock(side_effect=ValueError)
freqtrade.process() freqtrade.process()