mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-10 10:21:59 +00:00
Merge branch 'freqtrade:develop' into strategy_utils
This commit is contained in:
commit
08ca0f7c0f
|
@ -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]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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."
|
||||||
|
|
|
@ -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`.
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
@ -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 "
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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?,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user