Merge branch 'develop' into margin-db
This commit is contained in:
commit
d88e2ae603
@ -37,7 +37,7 @@ fi
|
||||
# Tag image for upload and next build step
|
||||
docker tag freqtrade:$TAG_ARM ${CACHE_IMAGE}:$TAG_ARM
|
||||
|
||||
docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${TAG_ARM} -t freqtrade:${TAG_PLOT_ARM} -f docker/Dockerfile.plot .
|
||||
docker build --cache-from freqtrade:${TAG_ARM} --build-arg sourceimage=${CACHE_IMAGE} --build-arg sourcetag=${TAG_ARM} -t freqtrade:${TAG_PLOT_ARM} -f docker/Dockerfile.plot .
|
||||
|
||||
docker tag freqtrade:$TAG_PLOT_ARM ${CACHE_IMAGE}:$TAG_PLOT_ARM
|
||||
|
||||
@ -63,13 +63,13 @@ echo "create manifests"
|
||||
docker manifest create --amend ${IMAGE_NAME}:${TAG} ${CACHE_IMAGE}:${TAG_ARM} ${IMAGE_NAME}:${TAG_PI} ${CACHE_IMAGE}:${TAG}
|
||||
docker manifest push -p ${IMAGE_NAME}:${TAG}
|
||||
|
||||
docker manifest create --amend ${IMAGE_NAME}:${TAG_PLOT} ${CACHE_IMAGE}:${TAG_PLOT_ARM} ${CACHE_IMAGE}:${TAG_PLOT}
|
||||
docker manifest create ${IMAGE_NAME}:${TAG_PLOT} ${CACHE_IMAGE}:${TAG_PLOT_ARM} ${CACHE_IMAGE}:${TAG_PLOT}
|
||||
docker manifest push -p ${IMAGE_NAME}:${TAG_PLOT}
|
||||
|
||||
Tag as latest for develop builds
|
||||
# Tag as latest for develop builds
|
||||
if [ "${TAG}" = "develop" ]; then
|
||||
docker tag ${IMAGE_NAME}:develop ${IMAGE_NAME}:latest
|
||||
docker push ${IMAGE_NAME}:latest
|
||||
docker manifest create ${IMAGE_NAME}:latest ${CACHE_IMAGE}:${TAG_ARM} ${IMAGE_NAME}:${TAG_PI} ${CACHE_IMAGE}:${TAG}
|
||||
docker manifest push -p ${IMAGE_NAME}:latest
|
||||
fi
|
||||
|
||||
docker images
|
||||
|
@ -48,7 +48,7 @@ fi
|
||||
# Tag image for upload and next build step
|
||||
docker tag freqtrade:$TAG ${CACHE_IMAGE}:$TAG
|
||||
|
||||
docker build --cache-from freqtrade:${TAG} --build-arg sourceimage=${TAG} -t freqtrade:${TAG_PLOT} -f docker/Dockerfile.plot .
|
||||
docker build --cache-from freqtrade:${TAG} --build-arg sourceimage=${CACHE_IMAGE} --build-arg sourcetag=${TAG} -t freqtrade:${TAG_PLOT} -f docker/Dockerfile.plot .
|
||||
|
||||
docker tag freqtrade:$TAG_PLOT ${CACHE_IMAGE}:$TAG_PLOT
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
ARG sourceimage=develop
|
||||
FROM freqtradeorg/freqtrade:${sourceimage}
|
||||
ARG sourceimage=freqtradeorg/freqtrade
|
||||
ARG sourcetag=develop
|
||||
FROM ${sourceimage}:${sourcetag}
|
||||
|
||||
# Install dependencies
|
||||
COPY requirements-plot.txt /freqtrade/
|
||||
|
@ -11,6 +11,37 @@ Per default, the bot loads the configuration from the `config.json` file, locate
|
||||
|
||||
You can specify a different configuration file used by the bot with the `-c/--config` command-line option.
|
||||
|
||||
If you used the [Quick start](installation.md/#quick-start) method for installing
|
||||
the bot, the installation script should have already created the default configuration file (`config.json`) for you.
|
||||
|
||||
If the default configuration file is not created we recommend to use `freqtrade new-config --config config.json` to generate a basic configuration file.
|
||||
|
||||
The Freqtrade configuration file is to be written in JSON format.
|
||||
|
||||
Additionally to the standard JSON syntax, you may use one-line `// ...` and multi-line `/* ... */` comments in your configuration files and trailing commas in the lists of parameters.
|
||||
|
||||
Do not worry if you are not familiar with JSON format -- simply open the configuration file with an editor of your choice, make some changes to the parameters you need, save your changes and, finally, restart the bot or, if it was previously stopped, run it again with the changes you made to the configuration. The bot validates the syntax of the configuration file at startup and will warn you if you made any errors editing it, pointing out problematic lines.
|
||||
|
||||
### Environment variables
|
||||
|
||||
Set options in the Freqtrade configuration via environment variables.
|
||||
This takes priority over the corresponding value in configuration or strategy.
|
||||
|
||||
Environment variables must be prefixed with `FREQTRADE__` to be loaded to the freqtrade configuration.
|
||||
|
||||
`__` serves as level separator, so the format used should correspond to `FREQTRADE__{section}__{key}`.
|
||||
As such - an environment variable defined as `export FREQTRADE__STAKE_AMOUNT=200` would result in `{stake_amount: 200}`.
|
||||
|
||||
A more complex example might be `export FREQTRADE__EXCHANGE__KEY=<yourExchangeKey>` to keep your exchange key secret. This will move the value to the `exchange.key` section of the configuration.
|
||||
Using this scheme, all configuration settings will also be available as environment variables.
|
||||
|
||||
Please note that Environment variables will overwrite corresponding settings in your configuration, but command line Arguments will always win.
|
||||
|
||||
!!! Note
|
||||
Environment variables detected are logged at startup - so if you can't find why a value is not what you think it should be based on the configuration, make sure it's not loaded from an environment variable.
|
||||
|
||||
### Multiple configuration files
|
||||
|
||||
Multiple configuration files can be specified and used by the bot or the bot can read its configuration parameters from the process standard input stream.
|
||||
|
||||
!!! Tip "Use multiple configuration files to keep secrets secret"
|
||||
@ -22,17 +53,6 @@ Multiple configuration files can be specified and used by the bot or the bot can
|
||||
The 2nd file should only specify what you intend to override.
|
||||
If a key is in more than one of the configurations, then the "last specified configuration" wins (in the above example, `config-private.json`).
|
||||
|
||||
If you used the [Quick start](installation.md/#quick-start) method for installing
|
||||
the bot, the installation script should have already created the default configuration file (`config.json`) for you.
|
||||
|
||||
If the default configuration file is not created we recommend you to use `freqtrade new-config --config config.json` to generate a basic configuration file.
|
||||
|
||||
The Freqtrade configuration file is to be written in JSON format.
|
||||
|
||||
Additionally to the standard JSON syntax, you may use one-line `// ...` and multi-line `/* ... */` comments in your configuration files and trailing commas in the lists of parameters.
|
||||
|
||||
Do not worry if you are not familiar with JSON format -- simply open the configuration file with an editor of your choice, make some changes to the parameters you need, save your changes and, finally, restart the bot or, if it was previously stopped, run it again with the changes you made to the configuration. The bot validates the syntax of the configuration file at startup and will warn you if you made any errors editing it, pointing out problematic lines.
|
||||
|
||||
## Configuration parameters
|
||||
|
||||
The table below will list all configuration parameters available.
|
||||
@ -41,6 +61,7 @@ Freqtrade can also load many options via command line (CLI) arguments (check out
|
||||
The prevalence for all Options is as follows:
|
||||
|
||||
- CLI arguments override any other option
|
||||
- [Environment Variables](#environment-variables)
|
||||
- Configuration files are used in sequence (the last file wins) and override Strategy configurations.
|
||||
- Strategy configurations are only used if they are not set via configuration or command-line arguments. These options are marked with [Strategy Override](#parameters-in-the-strategy) in the below table.
|
||||
|
||||
@ -526,9 +547,10 @@ Once you will be happy with your bot performance running in the Dry-run mode, yo
|
||||
|
||||
## Switch to production mode
|
||||
|
||||
In production mode, the bot will engage your money. Be careful, since a wrong
|
||||
strategy can lose all your money. Be aware of what you are doing when
|
||||
you run it in production mode.
|
||||
In production mode, the bot will engage your money. Be careful, since a wrong strategy can lose all your money.
|
||||
Be aware of what you are doing when you run it in production mode.
|
||||
|
||||
When switching to Production mode, please make sure to use a different / fresh database to avoid dry-run trades messing with your exchange money and eventually tainting your statistics.
|
||||
|
||||
### Setup your exchange account
|
||||
|
||||
|
@ -24,7 +24,6 @@ Freqtrade provides an official Docker image on [Dockerhub](https://hub.docker.co
|
||||
|
||||
Create a new directory and place the [docker-compose file](https://raw.githubusercontent.com/freqtrade/freqtrade/stable/docker-compose.yml) in this directory.
|
||||
|
||||
=== "PC/MAC/Linux"
|
||||
``` bash
|
||||
mkdir ft_userdata
|
||||
cd ft_userdata/
|
||||
@ -41,66 +40,6 @@ Create a new directory and place the [docker-compose file](https://raw.githubuse
|
||||
docker-compose run --rm freqtrade new-config --config user_data/config.json
|
||||
```
|
||||
|
||||
=== "RaspberryPi"
|
||||
``` bash
|
||||
mkdir ft_userdata
|
||||
cd ft_userdata/
|
||||
# Download the docker-compose file from the repository
|
||||
curl https://raw.githubusercontent.com/freqtrade/freqtrade/stable/docker-compose.yml -o docker-compose.yml
|
||||
|
||||
# Edit the compose file to use an image named `*_pi` (stable_pi or develop_pi)
|
||||
|
||||
# Pull the freqtrade image
|
||||
docker-compose pull
|
||||
|
||||
# Create user directory structure
|
||||
docker-compose run --rm freqtrade create-userdir --userdir user_data
|
||||
|
||||
# Create configuration - Requires answering interactive questions
|
||||
docker-compose run --rm freqtrade new-config --config user_data/config.json
|
||||
```
|
||||
|
||||
!!! Note "Change your docker Image"
|
||||
You have to change the docker image in the docker-compose file for your Raspberry build to work properly.
|
||||
``` yml
|
||||
image: freqtradeorg/freqtrade:stable_pi
|
||||
# image: freqtradeorg/freqtrade:develop_pi
|
||||
```
|
||||
|
||||
=== "ARM 64 Systenms (Mac M1, Raspberry Pi 4, Jetson Nano)"
|
||||
In case of a Mac M1, make sure that your docker installation is running in native mode
|
||||
Arm64 images are not yet provided via Docker Hub and need to be build locally first.
|
||||
Depending on the device, this may take a few minutes (Apple M1) or multiple hours (Raspberry Pi)
|
||||
|
||||
``` bash
|
||||
# Clone Freqtrade repository
|
||||
git clone https://github.com/freqtrade/freqtrade.git
|
||||
cd freqtrade
|
||||
# Optionally switch to the stable version
|
||||
git checkout stable
|
||||
|
||||
# Modify your docker-compose file to enable building and change the image name
|
||||
# (see the Note Box below for necessary changes)
|
||||
|
||||
# Build image
|
||||
docker-compose build
|
||||
|
||||
# Create user directory structure
|
||||
docker-compose run --rm freqtrade create-userdir --userdir user_data
|
||||
|
||||
# Create configuration - Requires answering interactive questions
|
||||
docker-compose run --rm freqtrade new-config --config user_data/config.json
|
||||
```
|
||||
|
||||
!!! Note "Change your docker Image"
|
||||
You have to change the docker image in the docker-compose file for your arm64 build to work properly.
|
||||
``` yml
|
||||
image: freqtradeorg/freqtrade:custom_arm64
|
||||
build:
|
||||
context: .
|
||||
dockerfile: "Dockerfile"
|
||||
```
|
||||
|
||||
The above snippet creates a new directory called `ft_userdata`, downloads the latest compose file and pulls the freqtrade image.
|
||||
The last 2 steps in the snippet create the directory with `user_data`, as well as (interactively) the default configuration based on your selections.
|
||||
|
||||
@ -117,7 +56,7 @@ The last 2 steps in the snippet create the directory with `user_data`, as well a
|
||||
|
||||
The `SampleStrategy` is run by default.
|
||||
|
||||
!!! Warning "`SampleStrategy` is just a demo!"
|
||||
!!! Danger "`SampleStrategy` is just a demo!"
|
||||
The `SampleStrategy` is there for your reference and give you ideas for your own strategy.
|
||||
Please always backtest your strategy and use dry-run for some time before risking real money!
|
||||
You will find more information about Strategy development in the [Strategy documentation](strategy-customization.md).
|
||||
@ -167,6 +106,10 @@ Advanced users may edit the docker-compose file further to include all possible
|
||||
|
||||
All freqtrade arguments will be available by running `docker-compose run --rm freqtrade <command> <optional arguments>`.
|
||||
|
||||
!!! Warning "`docker-compose` for trade commands"
|
||||
Trade commands (`freqtrade trade <...>`) should not be ran via `docker-compose run` - but should use `docker-compose up -d` instead.
|
||||
This makes sure that the container is properly started (including port forwardings) and will make sure that the container will restart after a system reboot.
|
||||
|
||||
!!! Note "`docker-compose run --rm`"
|
||||
Including `--rm` will remove the container after completion, and is highly recommended for all modes except trading mode (running with `freqtrade trade` command).
|
||||
|
||||
|
@ -77,8 +77,9 @@ You can get a list of restricted markets by using the following snippet:
|
||||
``` python
|
||||
import ccxt
|
||||
ct = ccxt.bittrex()
|
||||
_ = ct.load_markets()
|
||||
res = [ f"{x['MarketCurrency']}/{x['BaseCurrency']}" for x in ct.publicGetMarkets()['result'] if x['IsRestricted']]
|
||||
lm = ct.load_markets()
|
||||
|
||||
res = [p for p, x in lm.items() if 'US' in x['info']['prohibitedIn']]
|
||||
print(res)
|
||||
```
|
||||
|
||||
|
@ -221,10 +221,10 @@ If `DOGE/BTC` maximum bid is 0.00000026 and minimum ask is 0.00000027, the ratio
|
||||
|
||||
#### RangeStabilityFilter
|
||||
|
||||
Removes pairs where the difference between lowest low and highest high over `lookback_days` days is below `min_rate_of_change`. Since this is a filter that requires additional data, the results are cached for `refresh_period`.
|
||||
Removes pairs where the difference between lowest low and highest high over `lookback_days` days is below `min_rate_of_change` or above `max_rate_of_change`. Since this is a filter that requires additional data, the results are cached for `refresh_period`.
|
||||
|
||||
In the below example:
|
||||
If the trading range over the last 10 days is <1%, remove the pair from the whitelist.
|
||||
If the trading range over the last 10 days is <1% or >99%, remove the pair from the whitelist.
|
||||
|
||||
```json
|
||||
"pairlists": [
|
||||
@ -232,6 +232,7 @@ If the trading range over the last 10 days is <1%, remove the pair from the whit
|
||||
"method": "RangeStabilityFilter",
|
||||
"lookback_days": 10,
|
||||
"min_rate_of_change": 0.01,
|
||||
"max_rate_of_change": 0.99,
|
||||
"refresh_period": 1440
|
||||
}
|
||||
]
|
||||
@ -239,6 +240,7 @@ If the trading range over the last 10 days is <1%, remove the pair from the whit
|
||||
|
||||
!!! Tip
|
||||
This Filter can be used to automatically remove stable coin pairs, which have a very low trading range, and are therefore extremely difficult to trade with profit.
|
||||
Additionally, it can also be used to automatically remove pairs with extreme high/low variance over a given amount of time.
|
||||
|
||||
#### VolatilityFilter
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
mkdocs==1.2.2
|
||||
mkdocs-material==7.2.1
|
||||
mkdocs-material==7.2.2
|
||||
mdx_truly_sane_lists==1.2
|
||||
pymdown-extensions==8.2
|
||||
|
@ -110,7 +110,7 @@ DELETE FROM trades WHERE id = 31;
|
||||
Freqtrade supports PostgreSQL by using SQLAlchemy, which supports multiple different database systems.
|
||||
|
||||
Installation:
|
||||
`pip install psycopg2`
|
||||
`pip install psycopg2-binary`
|
||||
|
||||
Usage:
|
||||
`... --db-url postgresql+psycopg2://<username>:<password>@localhost:5432/<database>`
|
||||
|
@ -114,6 +114,36 @@ class AwesomeStrategy(IStrategy):
|
||||
|
||||
See [Dataframe access](#dataframe-access) for more information about dataframe use in strategy callbacks.
|
||||
|
||||
## Buy Tag
|
||||
|
||||
When your strategy has multiple buy signals, you can name the signal that triggered.
|
||||
Then you can access you buy signal on `custom_sell`
|
||||
|
||||
```python
|
||||
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
dataframe.loc[
|
||||
(
|
||||
(dataframe['rsi'] < 35) &
|
||||
(dataframe['volume'] > 0)
|
||||
),
|
||||
['buy', 'buy_tag']] = (1, 'buy_signal_rsi')
|
||||
|
||||
return dataframe
|
||||
|
||||
def custom_sell(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
|
||||
current_profit: float, **kwargs):
|
||||
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||
last_candle = dataframe.iloc[-1].squeeze()
|
||||
if trade.buy_tag == 'buy_signal_rsi' and last_candle['rsi'] > 80:
|
||||
return 'sell_signal_rsi'
|
||||
return None
|
||||
|
||||
```
|
||||
|
||||
!!! Note
|
||||
`buy_tag` is limited to 100 characters, remaining data will be truncated.
|
||||
|
||||
|
||||
## Custom stoploss
|
||||
|
||||
The stoploss price can only ever move upwards - if the stoploss value returned from `custom_stoploss` would result in a lower stoploss price than was previously set, it will be ignored. The traditional `stoploss` value serves as an absolute lower level and will be instated as the initial stoploss.
|
||||
|
@ -627,7 +627,7 @@ FreqUI will also show the backtesting results.
|
||||
|
||||
```
|
||||
usage: freqtrade webserver [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
|
||||
[--userdir PATH] [-s NAME] [--strategy-path PATH]
|
||||
[--userdir PATH]
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
@ -648,12 +648,6 @@ Common arguments:
|
||||
--userdir PATH, --user-data-dir PATH
|
||||
Path to userdata directory.
|
||||
|
||||
Strategy arguments:
|
||||
-s NAME, --strategy NAME
|
||||
Specify strategy class name which will be used by the
|
||||
bot.
|
||||
--strategy-path PATH Specify additional strategy lookup path.
|
||||
|
||||
```
|
||||
|
||||
## List Hyperopt results
|
||||
|
@ -83,6 +83,7 @@ Possible parameters are:
|
||||
* `fiat_currency`
|
||||
* `order_type`
|
||||
* `current_rate`
|
||||
* `buy_tag`
|
||||
|
||||
### Webhookbuycancel
|
||||
|
||||
@ -100,6 +101,7 @@ Possible parameters are:
|
||||
* `fiat_currency`
|
||||
* `order_type`
|
||||
* `current_rate`
|
||||
* `buy_tag`
|
||||
|
||||
### Webhookbuyfill
|
||||
|
||||
@ -115,6 +117,7 @@ Possible parameters are:
|
||||
* `stake_amount`
|
||||
* `stake_currency`
|
||||
* `fiat_currency`
|
||||
* `buy_tag`
|
||||
|
||||
### Webhooksell
|
||||
|
||||
|
@ -11,6 +11,7 @@ from freqtrade import constants
|
||||
from freqtrade.configuration.check_exchange import check_exchange
|
||||
from freqtrade.configuration.deprecated_settings import process_temporary_deprecated_settings
|
||||
from freqtrade.configuration.directory_operations import create_datadir, create_userdata_dir
|
||||
from freqtrade.configuration.environment_vars import enironment_vars_to_dict
|
||||
from freqtrade.configuration.load_config import load_config_file, load_file
|
||||
from freqtrade.enums import NON_UTIL_MODES, TRADING_MODES, RunMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
@ -71,6 +72,11 @@ class Configuration:
|
||||
|
||||
# Merge config options, overwriting old values
|
||||
config = deep_merge_dicts(load_config_file(path), config)
|
||||
|
||||
# Load environment variables
|
||||
env_data = enironment_vars_to_dict()
|
||||
config = deep_merge_dicts(env_data, config)
|
||||
|
||||
config['config_files'] = files
|
||||
# Normalize config
|
||||
if 'internals' not in config:
|
||||
|
54
freqtrade/configuration/environment_vars.py
Normal file
54
freqtrade/configuration/environment_vars.py
Normal file
@ -0,0 +1,54 @@
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, Dict
|
||||
|
||||
from freqtrade.constants import ENV_VAR_PREFIX
|
||||
from freqtrade.misc import deep_merge_dicts
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_var_typed(val):
|
||||
try:
|
||||
return int(val)
|
||||
except ValueError:
|
||||
try:
|
||||
return float(val)
|
||||
except ValueError:
|
||||
if val.lower() in ('t', 'true'):
|
||||
return True
|
||||
elif val.lower() in ('f', 'false'):
|
||||
return False
|
||||
# keep as string
|
||||
return val
|
||||
|
||||
|
||||
def flat_vars_to_nested_dict(env_dict: Dict[str, Any], prefix: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Environment variables must be prefixed with FREQTRADE.
|
||||
FREQTRADE__{section}__{key}
|
||||
:param env_dict: Dictionary to validate - usually os.environ
|
||||
:param prefix: Prefix to consider (usually FREQTRADE__)
|
||||
:return: Nested dict based on available and relevant variables.
|
||||
"""
|
||||
relevant_vars: Dict[str, Any] = {}
|
||||
|
||||
for env_var, val in sorted(env_dict.items()):
|
||||
if env_var.startswith(prefix):
|
||||
logger.info(f"Loading variable '{env_var}'")
|
||||
key = env_var.replace(prefix, '')
|
||||
for k in reversed(key.split('__')):
|
||||
val = {k.lower(): get_var_typed(val) if type(val) != dict else val}
|
||||
relevant_vars = deep_merge_dicts(val, relevant_vars)
|
||||
|
||||
return relevant_vars
|
||||
|
||||
|
||||
def enironment_vars_to_dict() -> Dict[str, Any]:
|
||||
"""
|
||||
Read environment variables and return a nested dict for relevant variables
|
||||
Relevant variables must follow the FREQTRADE__{section}__{key} pattern
|
||||
:return: Nested dict based on available and relevant variables.
|
||||
"""
|
||||
return flat_vars_to_nested_dict(os.environ.copy(), ENV_VAR_PREFIX)
|
@ -47,6 +47,7 @@ USERPATH_STRATEGIES = 'strategies'
|
||||
USERPATH_NOTEBOOKS = 'notebooks'
|
||||
|
||||
TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent']
|
||||
ENV_VAR_PREFIX = 'FREQTRADE__'
|
||||
|
||||
|
||||
# Define decimals per coin for outputs
|
||||
|
@ -30,7 +30,7 @@ BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date',
|
||||
'fee_open', 'fee_close', 'trade_duration',
|
||||
'profit_ratio', 'profit_abs', 'sell_reason',
|
||||
'initial_stop_loss_abs', 'initial_stop_loss_ratio', 'stop_loss_abs',
|
||||
'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', ]
|
||||
'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'buy_tag']
|
||||
|
||||
|
||||
def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str:
|
||||
|
@ -10,11 +10,12 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.configuration import TimeRange
|
||||
from freqtrade.constants import ListPairsWithTimeframes, PairWithTimeframe
|
||||
from freqtrade.data.history import load_pair_history
|
||||
from freqtrade.enums import RunMode
|
||||
from freqtrade.exceptions import ExchangeError, OperationalException
|
||||
from freqtrade.exchange import Exchange
|
||||
from freqtrade.exchange import Exchange, timeframe_to_seconds
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -31,6 +32,7 @@ class DataProvider:
|
||||
self._pairlists = pairlists
|
||||
self.__cached_pairs: Dict[PairWithTimeframe, Tuple[DataFrame, datetime]] = {}
|
||||
self.__slice_index: Optional[int] = None
|
||||
self.__cached_pairs_backtesting: Dict[PairWithTimeframe, DataFrame] = {}
|
||||
|
||||
def _set_dataframe_max_index(self, limit_index: int):
|
||||
"""
|
||||
@ -62,11 +64,22 @@ class DataProvider:
|
||||
:param pair: pair to get the data for
|
||||
:param timeframe: timeframe to get data for
|
||||
"""
|
||||
return load_pair_history(pair=pair,
|
||||
saved_pair = (pair, str(timeframe))
|
||||
if saved_pair not in self.__cached_pairs_backtesting:
|
||||
timerange = TimeRange.parse_timerange(None if self._config.get(
|
||||
'timerange') is None else str(self._config.get('timerange')))
|
||||
# Move informative start time respecting startup_candle_count
|
||||
timerange.subtract_start(
|
||||
timeframe_to_seconds(str(timeframe)) * self._config.get('startup_candle_count', 0)
|
||||
)
|
||||
self.__cached_pairs_backtesting[saved_pair] = load_pair_history(
|
||||
pair=pair,
|
||||
timeframe=timeframe or self._config['timeframe'],
|
||||
datadir=self._config['datadir'],
|
||||
timerange=timerange,
|
||||
data_format=self._config.get('dataformat_ohlcv', 'json')
|
||||
)
|
||||
return self.__cached_pairs_backtesting[saved_pair].copy()
|
||||
|
||||
def get_pair_dataframe(self, pair: str, timeframe: str = None) -> DataFrame:
|
||||
"""
|
||||
|
@ -62,7 +62,7 @@ class JsonDataHandler(IDataHandler):
|
||||
filename = self._pair_data_filename(self._datadir, pair, timeframe)
|
||||
_data = data.copy()
|
||||
# Convert date to int
|
||||
_data['date'] = _data['date'].astype(np.int64) // 1000 // 1000
|
||||
_data['date'] = _data['date'].view(np.int64) // 1000 // 1000
|
||||
|
||||
# Reset index, select only appropriate columns and save as json
|
||||
_data.reset_index(drop=True).loc[:, self._columns].to_json(
|
||||
|
@ -4,5 +4,5 @@ from freqtrade.enums.interestmode import InterestMode
|
||||
from freqtrade.enums.rpcmessagetype import RPCMessageType
|
||||
from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode
|
||||
from freqtrade.enums.selltype import SellType
|
||||
from freqtrade.enums.signaltype import SignalType
|
||||
from freqtrade.enums.signaltype import SignalTagType, SignalType
|
||||
from freqtrade.enums.state import State
|
||||
|
@ -7,3 +7,10 @@ class SignalType(Enum):
|
||||
"""
|
||||
BUY = "buy"
|
||||
SELL = "sell"
|
||||
|
||||
|
||||
class SignalTagType(Enum):
|
||||
"""
|
||||
Enum for signal columns
|
||||
"""
|
||||
BUY_TAG = "buy_tag"
|
||||
|
@ -387,7 +387,7 @@ class Exchange:
|
||||
# its contents depend on the exchange.
|
||||
# It can also be a string or similar ... so we need to verify that first.
|
||||
elif (isinstance(self.markets[pair].get('info', None), dict)
|
||||
and self.markets[pair].get('info', {}).get('IsRestricted', False)):
|
||||
and self.markets[pair].get('info', {}).get('prohibitedIn', False)):
|
||||
# Warn users about restricted pairs in whitelist.
|
||||
# We cannot determine reliably if Users are affected.
|
||||
logger.warning(f"Pair {pair} is restricted for some users on this exchange."
|
||||
@ -689,7 +689,16 @@ class Exchange:
|
||||
# Order handling
|
||||
|
||||
def create_order(self, pair: str, ordertype: str, side: str, amount: float,
|
||||
rate: float, params: Dict = {}) -> Dict:
|
||||
rate: float, time_in_force: str = 'gtc') -> Dict:
|
||||
|
||||
if self._config['dry_run']:
|
||||
dry_order = self.create_dry_run_order(pair, ordertype, side, amount, rate)
|
||||
return dry_order
|
||||
|
||||
params = self._params.copy()
|
||||
if time_in_force != 'gtc' and ordertype != 'market':
|
||||
params.update({'timeInForce': time_in_force})
|
||||
|
||||
try:
|
||||
# Set the precision for amount and price(rate) as accepted by the exchange
|
||||
amount = self.amount_to_precision(pair, amount)
|
||||
@ -720,32 +729,6 @@ class Exchange:
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException(e) from e
|
||||
|
||||
def buy(self, pair: str, ordertype: str, amount: float,
|
||||
rate: float, time_in_force: str) -> Dict:
|
||||
|
||||
if self._config['dry_run']:
|
||||
dry_order = self.create_dry_run_order(pair, ordertype, "buy", amount, rate)
|
||||
return dry_order
|
||||
|
||||
params = self._params.copy()
|
||||
if time_in_force != 'gtc' and ordertype != 'market':
|
||||
params.update({'timeInForce': time_in_force})
|
||||
|
||||
return self.create_order(pair, ordertype, 'buy', amount, rate, params)
|
||||
|
||||
def sell(self, pair: str, ordertype: str, amount: float,
|
||||
rate: float, time_in_force: str = 'gtc') -> Dict:
|
||||
|
||||
if self._config['dry_run']:
|
||||
dry_order = self.create_dry_run_order(pair, ordertype, "sell", amount, rate)
|
||||
return dry_order
|
||||
|
||||
params = self._params.copy()
|
||||
if time_in_force != 'gtc' and ordertype != 'market':
|
||||
params.update({'timeInForce': time_in_force})
|
||||
|
||||
return self.create_order(pair, ordertype, 'sell', amount, rate, params)
|
||||
|
||||
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
|
||||
"""
|
||||
Verify stop_loss against stoploss-order value (limit or price)
|
||||
|
@ -420,7 +420,11 @@ class FreqtradeBot(LoggingMixin):
|
||||
return False
|
||||
|
||||
# running get_signal on historical data fetched
|
||||
(buy, sell) = self.strategy.get_signal(pair, self.strategy.timeframe, analyzed_df)
|
||||
(buy, sell, buy_tag) = self.strategy.get_signal(
|
||||
pair,
|
||||
self.strategy.timeframe,
|
||||
analyzed_df
|
||||
)
|
||||
|
||||
if buy and not sell:
|
||||
stake_amount = self.wallets.get_trade_stake_amount(pair, self.edge)
|
||||
@ -429,11 +433,11 @@ class FreqtradeBot(LoggingMixin):
|
||||
if ((bid_check_dom.get('enabled', False)) and
|
||||
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
|
||||
if self._check_depth_of_market_buy(pair, bid_check_dom):
|
||||
return self.execute_buy(pair, stake_amount)
|
||||
return self.execute_buy(pair, stake_amount, buy_tag=buy_tag)
|
||||
else:
|
||||
return False
|
||||
|
||||
return self.execute_buy(pair, stake_amount)
|
||||
return self.execute_buy(pair, stake_amount, buy_tag=buy_tag)
|
||||
else:
|
||||
return False
|
||||
|
||||
@ -462,7 +466,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
return False
|
||||
|
||||
def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = None,
|
||||
forcebuy: bool = False) -> bool:
|
||||
forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool:
|
||||
"""
|
||||
Executes a limit buy for the given pair
|
||||
:param pair: pair for which we want to create a LIMIT_BUY
|
||||
@ -510,7 +514,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
logger.info(f"User requested abortion of buying {pair}")
|
||||
return False
|
||||
amount = self.exchange.amount_to_precision(pair, amount)
|
||||
order = self.exchange.buy(pair=pair, ordertype=order_type,
|
||||
order = self.exchange.create_order(pair=pair, ordertype=order_type, side="buy",
|
||||
amount=amount, rate=buy_limit_requested,
|
||||
time_in_force=time_in_force)
|
||||
order_obj = Order.parse_from_ccxt_object(order, pair, 'buy')
|
||||
@ -565,6 +569,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
exchange=self.exchange.id,
|
||||
open_order_id=order_id,
|
||||
strategy=self.strategy.get_strategy_name(),
|
||||
buy_tag=buy_tag,
|
||||
timeframe=timeframe_to_minutes(self.config['timeframe'])
|
||||
)
|
||||
trade.orders.append(order_obj)
|
||||
@ -590,6 +595,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
msg = {
|
||||
'trade_id': trade.id,
|
||||
'type': RPCMessageType.BUY,
|
||||
'buy_tag': trade.buy_tag,
|
||||
'exchange': self.exchange.name.capitalize(),
|
||||
'pair': trade.pair,
|
||||
'limit': trade.open_rate,
|
||||
@ -614,6 +620,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
msg = {
|
||||
'trade_id': trade.id,
|
||||
'type': RPCMessageType.BUY_CANCEL,
|
||||
'buy_tag': trade.buy_tag,
|
||||
'exchange': self.exchange.name.capitalize(),
|
||||
'pair': trade.pair,
|
||||
'limit': trade.open_rate,
|
||||
@ -634,6 +641,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
msg = {
|
||||
'trade_id': trade.id,
|
||||
'type': RPCMessageType.BUY_FILL,
|
||||
'buy_tag': trade.buy_tag,
|
||||
'exchange': self.exchange.name.capitalize(),
|
||||
'pair': trade.pair,
|
||||
'open_rate': trade.open_rate,
|
||||
@ -692,7 +700,11 @@ class FreqtradeBot(LoggingMixin):
|
||||
analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair,
|
||||
self.strategy.timeframe)
|
||||
|
||||
(buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.timeframe, analyzed_df)
|
||||
(buy, sell, _) = self.strategy.get_signal(
|
||||
trade.pair,
|
||||
self.strategy.timeframe,
|
||||
analyzed_df
|
||||
)
|
||||
|
||||
logger.debug('checking sell')
|
||||
sell_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell")
|
||||
@ -1094,8 +1106,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
try:
|
||||
# Execute sell and update trade record
|
||||
order = self.exchange.sell(pair=trade.pair,
|
||||
ordertype=order_type,
|
||||
order = self.exchange.create_order(pair=trade.pair,
|
||||
ordertype=order_type, side="sell",
|
||||
amount=amount, rate=limit,
|
||||
time_in_force=time_in_force
|
||||
)
|
||||
|
@ -15,7 +15,7 @@ from freqtrade.configuration import TimeRange, remove_credentials, validate_conf
|
||||
from freqtrade.constants import DATETIME_PRINT_FORMAT
|
||||
from freqtrade.data import history
|
||||
from freqtrade.data.btanalysis import trade_list_to_dataframe
|
||||
from freqtrade.data.converter import trim_dataframes
|
||||
from freqtrade.data.converter import trim_dataframe, trim_dataframes
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.enums import BacktestState, SellType
|
||||
from freqtrade.exceptions import DependencyException, OperationalException
|
||||
@ -43,6 +43,7 @@ CLOSE_IDX = 3
|
||||
SELL_IDX = 4
|
||||
LOW_IDX = 5
|
||||
HIGH_IDX = 6
|
||||
BUY_TAG_IDX = 7
|
||||
|
||||
|
||||
class Backtesting:
|
||||
@ -116,8 +117,13 @@ class Backtesting:
|
||||
|
||||
self.wallets = Wallets(self.config, self.exchange, log=False)
|
||||
|
||||
self.timerange = TimeRange.parse_timerange(
|
||||
None if self.config.get('timerange') is None else str(self.config.get('timerange')))
|
||||
|
||||
# Get maximum required startup period
|
||||
self.required_startup = max([strat.startup_candle_count for strat in self.strategylist])
|
||||
# Add maximum startup candle count to configuration for informative pairs support
|
||||
self.config['startup_candle_count'] = self.required_startup
|
||||
self.exchange.validate_required_startup_candles(self.required_startup, self.timeframe)
|
||||
|
||||
self.progress = BTProgress()
|
||||
@ -154,14 +160,11 @@ class Backtesting:
|
||||
"""
|
||||
self.progress.init_step(BacktestState.DATALOAD, 1)
|
||||
|
||||
timerange = TimeRange.parse_timerange(None if self.config.get(
|
||||
'timerange') is None else str(self.config.get('timerange')))
|
||||
|
||||
data = history.load_data(
|
||||
datadir=self.config['datadir'],
|
||||
pairs=self.pairlists.whitelist,
|
||||
timeframe=self.timeframe,
|
||||
timerange=timerange,
|
||||
timerange=self.timerange,
|
||||
startup_candles=self.required_startup,
|
||||
fail_without_data=True,
|
||||
data_format=self.config.get('dataformat_ohlcv', 'json'),
|
||||
@ -174,11 +177,11 @@ class Backtesting:
|
||||
f'({(max_date - min_date).days} days).')
|
||||
|
||||
# Adjust startts forward if not enough data is available
|
||||
timerange.adjust_start_if_necessary(timeframe_to_seconds(self.timeframe),
|
||||
self.timerange.adjust_start_if_necessary(timeframe_to_seconds(self.timeframe),
|
||||
self.required_startup, min_date)
|
||||
|
||||
self.progress.set_new_value(1)
|
||||
return data, timerange
|
||||
return data, self.timerange
|
||||
|
||||
def prepare_backtest(self, enable_protections):
|
||||
"""
|
||||
@ -217,23 +220,34 @@ class Backtesting:
|
||||
for pair, pair_data in processed.items():
|
||||
self.check_abort()
|
||||
self.progress.increment()
|
||||
has_buy_tag = 'buy_tag' in pair_data
|
||||
headers = headers + ['buy_tag'] if has_buy_tag else headers
|
||||
if not pair_data.empty:
|
||||
pair_data.loc[:, 'buy'] = 0 # cleanup if buy_signal is exist
|
||||
pair_data.loc[:, 'sell'] = 0 # cleanup if sell_signal is exist
|
||||
if has_buy_tag:
|
||||
pair_data.loc[:, 'buy_tag'] = None # cleanup if buy_tag is exist
|
||||
|
||||
df_analyzed = self.strategy.advise_sell(
|
||||
self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy()
|
||||
|
||||
self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair}).copy()
|
||||
# Trim startup period from analyzed dataframe
|
||||
df_analyzed = trim_dataframe(df_analyzed, self.timerange,
|
||||
startup_candles=self.required_startup)
|
||||
# To avoid using data from future, we use buy/sell signals shifted
|
||||
# from the previous candle
|
||||
df_analyzed.loc[:, 'buy'] = df_analyzed.loc[:, 'buy'].shift(1)
|
||||
df_analyzed.loc[:, 'sell'] = df_analyzed.loc[:, 'sell'].shift(1)
|
||||
if has_buy_tag:
|
||||
df_analyzed.loc[:, 'buy_tag'] = df_analyzed.loc[:, 'buy_tag'].shift(1)
|
||||
|
||||
df_analyzed.drop(df_analyzed.head(1).index, inplace=True)
|
||||
|
||||
# Update dataprovider cache
|
||||
self.dataprovider._set_cached_df(pair, self.timeframe, df_analyzed)
|
||||
|
||||
# Convert from Pandas to list for performance reasons
|
||||
# (Looping Pandas is slow.)
|
||||
data[pair] = df_analyzed.values.tolist()
|
||||
data[pair] = df_analyzed[headers].values.tolist()
|
||||
return data
|
||||
|
||||
def _get_close_rate(self, sell_row: Tuple, trade: LocalTrade, sell: SellCheckTuple,
|
||||
@ -358,6 +372,7 @@ class Backtesting:
|
||||
|
||||
if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount):
|
||||
# Enter trade
|
||||
has_buy_tag = len(row) >= BUY_TAG_IDX + 1
|
||||
trade = LocalTrade(
|
||||
pair=pair,
|
||||
open_rate=row[OPEN_IDX],
|
||||
@ -367,6 +382,7 @@ class Backtesting:
|
||||
fee_open=self.fee,
|
||||
fee_close=self.fee,
|
||||
is_open=True,
|
||||
buy_tag=row[BUY_TAG_IDX] if has_buy_tag else None,
|
||||
exchange='backtesting',
|
||||
)
|
||||
return trade
|
||||
@ -423,10 +439,6 @@ class Backtesting:
|
||||
trades: List[LocalTrade] = []
|
||||
self.prepare_backtest(enable_protections)
|
||||
|
||||
# Update dataprovider cache
|
||||
for pair, dataframe in processed.items():
|
||||
self.dataprovider._set_cached_df(pair, self.timeframe, dataframe)
|
||||
|
||||
# Use dict of lists with data for performance
|
||||
# (looping lists is a lot faster than pandas DataFrames)
|
||||
data: Dict = self._get_ohlcv_as_lists(processed)
|
||||
@ -537,14 +549,15 @@ class Backtesting:
|
||||
preprocessed = self.strategy.ohlcvdata_to_dataframe(data)
|
||||
|
||||
# Trim startup period from analyzed dataframe
|
||||
preprocessed = trim_dataframes(preprocessed, timerange, self.required_startup)
|
||||
preprocessed_tmp = trim_dataframes(preprocessed, timerange, self.required_startup)
|
||||
|
||||
if not preprocessed:
|
||||
if not preprocessed_tmp:
|
||||
raise OperationalException(
|
||||
"No data left after adjusting for startup candles.")
|
||||
|
||||
min_date, max_date = history.get_timerange(preprocessed)
|
||||
|
||||
# Use preprocessed_tmp for date generation (the trimmed dataframe).
|
||||
# Backtesting will re-trim the dataframes after buy/sell signal generation.
|
||||
min_date, max_date = history.get_timerange(preprocessed_tmp)
|
||||
logger.info(f'Backtesting with data from {min_date.strftime(DATETIME_PRINT_FORMAT)} '
|
||||
f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} '
|
||||
f'({(max_date - min_date).days} days).')
|
||||
|
@ -102,7 +102,9 @@ class Hyperopt:
|
||||
self.num_epochs_saved = 0
|
||||
self.current_best_epoch: Optional[Dict[str, Any]] = None
|
||||
|
||||
# Populate functions here (hasattr is slow so should not be run during "regular" operations)
|
||||
if not self.auto_hyperopt:
|
||||
# Populate "fallback" functions here
|
||||
# (hasattr is slow so should not be run during "regular" operations)
|
||||
if hasattr(self.custom_hyperopt, 'populate_indicators'):
|
||||
self.backtesting.strategy.advise_indicators = ( # type: ignore
|
||||
self.custom_hyperopt.populate_indicators) # type: ignore
|
||||
@ -264,17 +266,14 @@ class Hyperopt:
|
||||
|
||||
def generate_optimizer(self, raw_params: List[Any], iteration=None) -> Dict:
|
||||
"""
|
||||
Used Optimize function. Called once per epoch to optimize whatever is configured.
|
||||
Used Optimize function.
|
||||
Called once per epoch to optimize whatever is configured.
|
||||
Keep this function as optimized as possible!
|
||||
"""
|
||||
backtest_start_time = datetime.now(timezone.utc)
|
||||
params_dict = self._get_params_dict(self.dimensions, raw_params)
|
||||
|
||||
# Apply parameters
|
||||
if HyperoptTools.has_space(self.config, 'roi'):
|
||||
self.backtesting.strategy.minimal_roi = ( # type: ignore
|
||||
self.custom_hyperopt.generate_roi_table(params_dict))
|
||||
|
||||
if HyperoptTools.has_space(self.config, 'buy'):
|
||||
self.backtesting.strategy.advise_buy = ( # type: ignore
|
||||
self.custom_hyperopt.buy_strategy_generator(params_dict))
|
||||
@ -283,6 +282,10 @@ class Hyperopt:
|
||||
self.backtesting.strategy.advise_sell = ( # type: ignore
|
||||
self.custom_hyperopt.sell_strategy_generator(params_dict))
|
||||
|
||||
if HyperoptTools.has_space(self.config, 'roi'):
|
||||
self.backtesting.strategy.minimal_roi = ( # type: ignore
|
||||
self.custom_hyperopt.generate_roi_table(params_dict))
|
||||
|
||||
if HyperoptTools.has_space(self.config, 'stoploss'):
|
||||
self.backtesting.strategy.stoploss = params_dict['stoploss']
|
||||
|
||||
@ -378,16 +381,15 @@ class Hyperopt:
|
||||
|
||||
preprocessed = self.backtesting.strategy.ohlcvdata_to_dataframe(data)
|
||||
|
||||
# Trim startup period from analyzed dataframe
|
||||
# Trim startup period from analyzed dataframe to get correct dates for output.
|
||||
processed = trim_dataframes(preprocessed, timerange, self.backtesting.required_startup)
|
||||
|
||||
self.min_date, self.max_date = get_timerange(processed)
|
||||
|
||||
logger.info(f'Hyperopting with data from {self.min_date.strftime(DATETIME_PRINT_FORMAT)} '
|
||||
f'up to {self.max_date.strftime(DATETIME_PRINT_FORMAT)} '
|
||||
f'({(self.max_date - self.min_date).days} days)..')
|
||||
|
||||
dump(processed, self.data_pickle_file)
|
||||
# Store non-trimmed data - will be trimmed after signal generation.
|
||||
dump(preprocessed, self.data_pickle_file)
|
||||
|
||||
def start(self) -> None:
|
||||
self.random_state = self._set_random_state(self.config.get('hyperopt_random_state', None))
|
||||
|
@ -54,6 +54,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
|
||||
# sqlite does not support literals for booleans
|
||||
is_short = get_column_def(cols, 'is_short', '0')
|
||||
interest_mode = get_column_def(cols, 'interest_mode', 'null')
|
||||
buy_tag = get_column_def(cols, 'buy_tag', 'null')
|
||||
# If ticker-interval existed use that, else null.
|
||||
if has_column(cols, 'ticker_interval'):
|
||||
timeframe = get_column_def(cols, 'timeframe', 'ticker_interval')
|
||||
@ -72,7 +73,8 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
|
||||
# Schema migration necessary
|
||||
with engine.begin() as connection:
|
||||
connection.execute(text(f"alter table trades rename to {table_back_name}"))
|
||||
# drop indexes on backup table
|
||||
with engine.begin() as connection:
|
||||
# drop indexes on backup table in new session
|
||||
for index in inspector.get_indexes(table_back_name):
|
||||
connection.execute(text(f"drop index {index['name']}"))
|
||||
# let SQLAlchemy create the schema as required
|
||||
@ -83,23 +85,16 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
|
||||
connection.execute(text(f"""insert into trades
|
||||
(id, exchange, pair, is_open,
|
||||
fee_open, fee_open_cost, fee_open_currency,
|
||||
fee_close, fee_close_cost, fee_open_currency, open_rate,
|
||||
fee_close, fee_close_cost, fee_close_currency, open_rate,
|
||||
open_rate_requested, close_rate, close_rate_requested, close_profit,
|
||||
stake_amount, amount, amount_requested, open_date, close_date, open_order_id,
|
||||
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
|
||||
stoploss_order_id, stoploss_last_update,
|
||||
max_rate, min_rate, sell_reason, sell_order_status, strategy,
|
||||
max_rate, min_rate, sell_reason, sell_order_status, strategy, buy_tag,
|
||||
timeframe, open_trade_value, close_profit_abs,
|
||||
leverage, interest_rate, isolated_liq, is_short, interest_mode
|
||||
)
|
||||
select id, lower(exchange),
|
||||
case
|
||||
when instr(pair, '_') != 0 then
|
||||
substr(pair, instr(pair, '_') + 1) || '/' ||
|
||||
substr(pair, 1, instr(pair, '_') - 1)
|
||||
else pair
|
||||
end
|
||||
pair,
|
||||
select id, lower(exchange), pair,
|
||||
is_open, {fee_open} fee_open, {fee_open_cost} fee_open_cost,
|
||||
{fee_open_currency} fee_open_currency, {fee_close} fee_close,
|
||||
{fee_close_cost} fee_close_cost, {fee_close_currency} fee_close_currency,
|
||||
@ -112,7 +107,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
|
||||
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
|
||||
{max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason,
|
||||
{sell_order_status} sell_order_status,
|
||||
{strategy} strategy, {timeframe} timeframe,
|
||||
{strategy} strategy, {buy_tag} buy_tag, {timeframe} timeframe,
|
||||
{open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs,
|
||||
{leverage} leverage, {interest_rate} interest_rate,
|
||||
{isolated_liq} isolated_liq, {is_short} is_short,
|
||||
@ -143,7 +138,9 @@ def migrate_orders_table(decl_base, inspector, engine, table_back_name: str, col
|
||||
|
||||
with engine.begin() as connection:
|
||||
connection.execute(text(f"alter table orders rename to {table_back_name}"))
|
||||
# drop indexes on backup table
|
||||
|
||||
with engine.begin() as connection:
|
||||
# drop indexes on backup table in new session
|
||||
for index in inspector.get_indexes(table_back_name):
|
||||
connection.execute(text(f"drop index {index['name']}"))
|
||||
|
||||
@ -174,7 +171,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
|
||||
table_back_name = get_backup_name(tabs, 'trades_bak')
|
||||
|
||||
# Check for latest column
|
||||
if not has_column(cols, 'open_trade_value'):
|
||||
if not has_column(cols, 'buy_tag'):
|
||||
logger.info(f'Running database migration for trades - backup: {table_back_name}')
|
||||
migrate_trades_table(decl_base, inspector, engine, table_back_name, cols)
|
||||
# Reread columns - the above recreated the table!
|
||||
|
@ -261,6 +261,7 @@ class LocalTrade():
|
||||
sell_reason: str = ''
|
||||
sell_order_status: str = ''
|
||||
strategy: str = ''
|
||||
buy_tag: Optional[str] = None
|
||||
timeframe: Optional[int] = None
|
||||
|
||||
# Margin trading properties
|
||||
@ -378,6 +379,7 @@ class LocalTrade():
|
||||
'amount_requested': round(self.amount_requested, 8) if self.amount_requested else None,
|
||||
'stake_amount': round(self.stake_amount, 8),
|
||||
'strategy': self.strategy,
|
||||
'buy_tag': self.buy_tag,
|
||||
'timeframe': self.timeframe,
|
||||
|
||||
'fee_open': self.fee_open,
|
||||
@ -884,6 +886,7 @@ class Trade(_DECL_BASE, LocalTrade):
|
||||
sell_reason = Column(String(100), nullable=True) # TODO-mg: Change to close_reason
|
||||
sell_order_status = Column(String(100), nullable=True) # TODO-mg: Change to close_order_status
|
||||
strategy = Column(String(100), nullable=True)
|
||||
buy_tag = Column(String(100), nullable=True)
|
||||
timeframe = Column(Integer, nullable=True)
|
||||
|
||||
# Margin trading properties
|
||||
|
@ -26,6 +26,7 @@ class RangeStabilityFilter(IPairList):
|
||||
|
||||
self._days = pairlistconfig.get('lookback_days', 10)
|
||||
self._min_rate_of_change = pairlistconfig.get('min_rate_of_change', 0.01)
|
||||
self._max_rate_of_change = pairlistconfig.get('max_rate_of_change', None)
|
||||
self._refresh_period = pairlistconfig.get('refresh_period', 1440)
|
||||
|
||||
self._pair_cache: TTLCache = TTLCache(maxsize=1000, ttl=self._refresh_period)
|
||||
@ -50,8 +51,12 @@ class RangeStabilityFilter(IPairList):
|
||||
"""
|
||||
Short whitelist method description - used for startup-messages
|
||||
"""
|
||||
max_rate_desc = ""
|
||||
if self._max_rate_of_change:
|
||||
max_rate_desc = (f" and above {self._max_rate_of_change}")
|
||||
return (f"{self.name} - Filtering pairs with rate of change below "
|
||||
f"{self._min_rate_of_change} over the last {plural(self._days, 'day')}.")
|
||||
f"{self._min_rate_of_change}{max_rate_desc} over the "
|
||||
f"last {plural(self._days, 'day')}.")
|
||||
|
||||
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
|
||||
"""
|
||||
@ -104,6 +109,16 @@ class RangeStabilityFilter(IPairList):
|
||||
f"which is below the threshold of {self._min_rate_of_change}.",
|
||||
logger.info)
|
||||
result = False
|
||||
if self._max_rate_of_change:
|
||||
if pct_change <= self._max_rate_of_change:
|
||||
result = True
|
||||
else:
|
||||
self.log_once(
|
||||
f"Removed {pair} from whitelist, because rate of change "
|
||||
f"over {self._days} {plural(self._days, 'day')} is {pct_change:.3f}, "
|
||||
f"which is above the threshold of {self._max_rate_of_change}.",
|
||||
logger.info)
|
||||
result = False
|
||||
self._pair_cache[pair] = result
|
||||
|
||||
return result
|
||||
|
@ -47,15 +47,15 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac
|
||||
not ApiServer._bt
|
||||
or lastconfig.get('timeframe') != strat.timeframe
|
||||
or lastconfig.get('dry_run_wallet') != btconfig.get('dry_run_wallet', 0)
|
||||
or lastconfig.get('timerange') != btconfig['timerange']
|
||||
):
|
||||
from freqtrade.optimize.backtesting import Backtesting
|
||||
ApiServer._bt = Backtesting(btconfig)
|
||||
|
||||
# Only reload data if timeframe or timerange changed.
|
||||
# Only reload data if timeframe changed.
|
||||
if (
|
||||
not ApiServer._bt_data
|
||||
or not ApiServer._bt_timerange
|
||||
or lastconfig.get('timerange') != btconfig['timerange']
|
||||
or lastconfig.get('stake_amount') != btconfig.get('stake_amount')
|
||||
or lastconfig.get('enable_protections') != btconfig.get('enable_protections')
|
||||
or lastconfig.get('protections') != btconfig.get('protections', [])
|
||||
|
@ -151,6 +151,7 @@ class TradeSchema(BaseModel):
|
||||
amount_requested: float
|
||||
stake_amount: float
|
||||
strategy: str
|
||||
buy_tag: Optional[str]
|
||||
timeframe: int
|
||||
fee_open: Optional[float]
|
||||
fee_open_cost: Optional[float]
|
||||
|
@ -208,15 +208,25 @@ class Telegram(RPCHandler):
|
||||
else:
|
||||
msg['stake_amount_fiat'] = 0
|
||||
|
||||
message = (f"\N{LARGE BLUE CIRCLE} *{msg['exchange']}:* Buying {msg['pair']}"
|
||||
content = []
|
||||
content.append(
|
||||
f"\N{LARGE BLUE CIRCLE} *{msg['exchange']}:* Buying {msg['pair']}"
|
||||
f" (#{msg['trade_id']})\n"
|
||||
f"*Amount:* `{msg['amount']:.8f}`\n"
|
||||
f"*Open Rate:* `{msg['limit']:.8f}`\n"
|
||||
f"*Current Rate:* `{msg['current_rate']:.8f}`\n"
|
||||
f"*Total:* `({round_coin_value(msg['stake_amount'], msg['stake_currency'])}")
|
||||
|
||||
)
|
||||
if msg.get('buy_tag', None):
|
||||
content.append(f"*Buy Tag:* `{msg['buy_tag']}`\n")
|
||||
content.append(f"*Amount:* `{msg['amount']:.8f}`\n")
|
||||
content.append(f"*Open Rate:* `{msg['limit']:.8f}`\n")
|
||||
content.append(f"*Current Rate:* `{msg['current_rate']:.8f}`\n")
|
||||
content.append(
|
||||
f"*Total:* `({round_coin_value(msg['stake_amount'], msg['stake_currency'])}"
|
||||
)
|
||||
if msg.get('fiat_currency', None):
|
||||
message += f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}"
|
||||
content.append(
|
||||
f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}"
|
||||
)
|
||||
|
||||
message = ''.join(content)
|
||||
message += ")`"
|
||||
return message
|
||||
|
||||
@ -354,6 +364,7 @@ class Telegram(RPCHandler):
|
||||
"*Trade ID:* `{trade_id}` `(since {open_date_hum})`",
|
||||
"*Current Pair:* {pair}",
|
||||
"*Amount:* `{amount} ({stake_amount} {base_currency})`",
|
||||
"*Buy Tag:* `{buy_tag}`" if r['buy_tag'] else "",
|
||||
"*Open Rate:* `{open_rate:.8f}`",
|
||||
"*Close Rate:* `{close_rate}`" if r['close_rate'] else "",
|
||||
"*Current Rate:* `{current_rate:.8f}`",
|
||||
|
@ -13,7 +13,7 @@ from pandas import DataFrame
|
||||
|
||||
from freqtrade.constants import ListPairsWithTimeframes
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.enums import SellType, SignalType
|
||||
from freqtrade.enums import SellType, SignalTagType, SignalType
|
||||
from freqtrade.exceptions import OperationalException, StrategyError
|
||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
|
||||
from freqtrade.exchange.exchange import timeframe_to_next_date
|
||||
@ -422,6 +422,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
logger.debug("Skipping TA Analysis for already analyzed candle")
|
||||
dataframe['buy'] = 0
|
||||
dataframe['sell'] = 0
|
||||
dataframe['buy_tag'] = None
|
||||
|
||||
# Other Defs in strategy that want to be called every loop here
|
||||
# twitter_sell = self.watch_twitter_feed(dataframe, metadata)
|
||||
@ -482,8 +483,6 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
message = "No dataframe returned (return statement missing?)."
|
||||
elif 'buy' not in dataframe:
|
||||
message = "Buy column not set."
|
||||
elif 'sell' not in dataframe:
|
||||
message = "Sell column not set."
|
||||
elif df_len != len(dataframe):
|
||||
message = message_template.format("length")
|
||||
elif df_close != dataframe["close"].iloc[-1]:
|
||||
@ -496,7 +495,12 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
else:
|
||||
raise StrategyError(message)
|
||||
|
||||
def get_signal(self, pair: str, timeframe: str, dataframe: DataFrame) -> Tuple[bool, bool]:
|
||||
def get_signal(
|
||||
self,
|
||||
pair: str,
|
||||
timeframe: str,
|
||||
dataframe: DataFrame
|
||||
) -> Tuple[bool, bool, Optional[str]]:
|
||||
"""
|
||||
Calculates current signal based based on the buy / sell columns of the dataframe.
|
||||
Used by Bot to get the signal to buy or sell
|
||||
@ -507,7 +511,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
"""
|
||||
if not isinstance(dataframe, DataFrame) or dataframe.empty:
|
||||
logger.warning(f'Empty candle (OHLCV) data for pair {pair}')
|
||||
return False, False
|
||||
return False, False, None
|
||||
|
||||
latest_date = dataframe['date'].max()
|
||||
latest = dataframe.loc[dataframe['date'] == latest_date].iloc[-1]
|
||||
@ -522,9 +526,16 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
'Outdated history for pair %s. Last tick is %s minutes old',
|
||||
pair, int((arrow.utcnow() - latest_date).total_seconds() // 60)
|
||||
)
|
||||
return False, False
|
||||
return False, False, None
|
||||
|
||||
buy = latest[SignalType.BUY.value] == 1
|
||||
|
||||
sell = False
|
||||
if SignalType.SELL.value in latest:
|
||||
sell = latest[SignalType.SELL.value] == 1
|
||||
|
||||
buy_tag = latest.get(SignalTagType.BUY_TAG.value, None)
|
||||
|
||||
(buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1
|
||||
logger.debug('trigger: %s (pair=%s) buy=%s sell=%s',
|
||||
latest['date'], pair, str(buy), str(sell))
|
||||
timeframe_seconds = timeframe_to_seconds(timeframe)
|
||||
@ -532,8 +543,8 @@ class IStrategy(ABC, HyperStrategyMixin):
|
||||
current_time=datetime.now(timezone.utc),
|
||||
timeframe_seconds=timeframe_seconds,
|
||||
buy=buy):
|
||||
return False, sell
|
||||
return buy, sell
|
||||
return False, sell, buy_tag
|
||||
return buy, sell, buy_tag
|
||||
|
||||
def ignore_expired_candle(self, latest_date: datetime, current_time: datetime,
|
||||
timeframe_seconds: int, buy: bool):
|
||||
|
@ -13,7 +13,7 @@ pytest-asyncio==0.15.1
|
||||
pytest-cov==2.12.1
|
||||
pytest-mock==3.6.1
|
||||
pytest-random-order==1.0.4
|
||||
isort==5.9.2
|
||||
isort==5.9.3
|
||||
|
||||
# Convert jupyter notebooks to markdown documents
|
||||
nbconvert==6.1.0
|
||||
@ -21,5 +21,5 @@ nbconvert==6.1.0
|
||||
# mypy types
|
||||
types-cachetools==0.1.9
|
||||
types-filelock==0.1.4
|
||||
types-requests==2.25.0
|
||||
types-requests==2.25.1
|
||||
types-tabulate==0.1.1
|
||||
|
@ -2,7 +2,7 @@
|
||||
-r requirements.txt
|
||||
|
||||
# Required for hyperopt
|
||||
scipy==1.7.0
|
||||
scipy==1.7.1
|
||||
scikit-learn==0.24.2
|
||||
scikit-optimize==0.8.1
|
||||
filelock==3.0.12
|
||||
|
@ -1,7 +1,7 @@
|
||||
numpy==1.21.1
|
||||
pandas==1.3.1
|
||||
|
||||
ccxt==1.53.72
|
||||
ccxt==1.54.24
|
||||
# Pin cryptography for now due to rust build errors with piwheels
|
||||
cryptography==3.4.7
|
||||
aiohttp==3.7.4.post0
|
||||
@ -31,7 +31,7 @@ python-rapidjson==1.4
|
||||
sdnotify==0.3.2
|
||||
|
||||
# API Server
|
||||
fastapi==0.67.0
|
||||
fastapi==0.68.0
|
||||
uvicorn==0.14.0
|
||||
pyjwt==2.1.0
|
||||
aiofiles==0.7.0
|
||||
|
@ -182,7 +182,7 @@ def get_patched_worker(mocker, config) -> Worker:
|
||||
return Worker(args=None, config=config)
|
||||
|
||||
|
||||
def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False)) -> None:
|
||||
def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False, None)) -> None:
|
||||
"""
|
||||
:param mocker: mocker to patch IStrategy class
|
||||
:param value: which value IStrategy.get_signal() must return
|
||||
|
@ -29,7 +29,6 @@ from tests.optimize import (BTContainer, BTrade, _build_backtest_dataframe,
|
||||
|
||||
tests_start_time = arrow.get(2018, 10, 3)
|
||||
timeframe_in_minute = 60
|
||||
_ohlc = {'date': 0, 'buy': 1, 'open': 2, 'high': 3, 'low': 4, 'close': 5, 'sell': 6, 'volume': 7}
|
||||
|
||||
# Helpers for this test file
|
||||
|
||||
|
@ -673,7 +673,7 @@ def test_validate_pairs_restricted(default_conf, mocker, caplog):
|
||||
api_mock = MagicMock()
|
||||
type(api_mock).load_markets = MagicMock(return_value={
|
||||
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
|
||||
'XRP/BTC': {'quote': 'BTC', 'info': {'IsRestricted': True}},
|
||||
'XRP/BTC': {'quote': 'BTC', 'info': {'prohibitedIn': ['US']}},
|
||||
'NEO/BTC': {'quote': 'BTC', 'info': 'TestString'}, # info can also be a string ...
|
||||
})
|
||||
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||
@ -1056,7 +1056,7 @@ def test_buy_dry_run(default_conf, mocker):
|
||||
default_conf['dry_run'] = True
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
|
||||
order = exchange.buy(pair='ETH/BTC', ordertype='limit',
|
||||
order = exchange.create_order(pair='ETH/BTC', ordertype='limit', side="buy",
|
||||
amount=1, rate=200, time_in_force='gtc')
|
||||
assert 'id' in order
|
||||
assert 'dry_run_buy_' in order['id']
|
||||
@ -1080,7 +1080,7 @@ def test_buy_prod(default_conf, mocker, exchange_name):
|
||||
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
|
||||
order = exchange.buy(pair='ETH/BTC', ordertype=order_type,
|
||||
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
|
||||
amount=1, rate=200, time_in_force=time_in_force)
|
||||
|
||||
assert 'id' in order
|
||||
@ -1094,9 +1094,10 @@ def test_buy_prod(default_conf, mocker, exchange_name):
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
order_type = 'limit'
|
||||
order = exchange.buy(
|
||||
order = exchange.create_order(
|
||||
pair='ETH/BTC',
|
||||
ordertype=order_type,
|
||||
side="buy",
|
||||
amount=1,
|
||||
rate=200,
|
||||
time_in_force=time_in_force)
|
||||
@ -1110,31 +1111,31 @@ def test_buy_prod(default_conf, mocker, exchange_name):
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("Not enough funds"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
exchange.buy(pair='ETH/BTC', ordertype=order_type,
|
||||
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
|
||||
amount=1, rate=200, time_in_force=time_in_force)
|
||||
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
exchange.buy(pair='ETH/BTC', ordertype='limit',
|
||||
exchange.create_order(pair='ETH/BTC', ordertype='limit', side="buy",
|
||||
amount=1, rate=200, time_in_force=time_in_force)
|
||||
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
exchange.buy(pair='ETH/BTC', ordertype='market',
|
||||
exchange.create_order(pair='ETH/BTC', ordertype='market', side="buy",
|
||||
amount=1, rate=200, time_in_force=time_in_force)
|
||||
|
||||
with pytest.raises(TemporaryError):
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("Network disconnect"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
exchange.buy(pair='ETH/BTC', ordertype=order_type,
|
||||
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
|
||||
amount=1, rate=200, time_in_force=time_in_force)
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
exchange.buy(pair='ETH/BTC', ordertype=order_type,
|
||||
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
|
||||
amount=1, rate=200, time_in_force=time_in_force)
|
||||
|
||||
|
||||
@ -1157,7 +1158,7 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name):
|
||||
order_type = 'limit'
|
||||
time_in_force = 'ioc'
|
||||
|
||||
order = exchange.buy(pair='ETH/BTC', ordertype=order_type,
|
||||
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
|
||||
amount=1, rate=200, time_in_force=time_in_force)
|
||||
|
||||
assert 'id' in order
|
||||
@ -1174,7 +1175,7 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name):
|
||||
order_type = 'market'
|
||||
time_in_force = 'ioc'
|
||||
|
||||
order = exchange.buy(pair='ETH/BTC', ordertype=order_type,
|
||||
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
|
||||
amount=1, rate=200, time_in_force=time_in_force)
|
||||
|
||||
assert 'id' in order
|
||||
@ -1193,7 +1194,8 @@ def test_sell_dry_run(default_conf, mocker):
|
||||
default_conf['dry_run'] = True
|
||||
exchange = get_patched_exchange(mocker, default_conf)
|
||||
|
||||
order = exchange.sell(pair='ETH/BTC', ordertype='limit', amount=1, rate=200)
|
||||
order = exchange.create_order(pair='ETH/BTC', ordertype='limit',
|
||||
side="sell", amount=1, rate=200)
|
||||
assert 'id' in order
|
||||
assert 'dry_run_sell_' in order['id']
|
||||
|
||||
@ -1216,7 +1218,8 @@ def test_sell_prod(default_conf, mocker, exchange_name):
|
||||
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
|
||||
order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type,
|
||||
side="sell", amount=1, rate=200)
|
||||
|
||||
assert 'id' in order
|
||||
assert 'info' in order
|
||||
@ -1229,7 +1232,8 @@ def test_sell_prod(default_conf, mocker, exchange_name):
|
||||
|
||||
api_mock.create_order.reset_mock()
|
||||
order_type = 'limit'
|
||||
order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type,
|
||||
side="sell", amount=1, rate=200)
|
||||
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
|
||||
assert api_mock.create_order.call_args[0][1] == order_type
|
||||
assert api_mock.create_order.call_args[0][2] == 'sell'
|
||||
@ -1240,28 +1244,28 @@ def test_sell_prod(default_conf, mocker, exchange_name):
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200)
|
||||
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
exchange.sell(pair='ETH/BTC', ordertype='limit', amount=1, rate=200)
|
||||
exchange.create_order(pair='ETH/BTC', ordertype='limit', side="sell", amount=1, rate=200)
|
||||
|
||||
# Market orders don't require price, so the behaviour is slightly different
|
||||
with pytest.raises(DependencyException):
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
exchange.sell(pair='ETH/BTC', ordertype='market', amount=1, rate=200)
|
||||
exchange.create_order(pair='ETH/BTC', ordertype='market', side="sell", amount=1, rate=200)
|
||||
|
||||
with pytest.raises(TemporaryError):
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No Connection"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200)
|
||||
|
||||
with pytest.raises(OperationalException):
|
||||
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
|
||||
exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
@ -1283,7 +1287,7 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name):
|
||||
order_type = 'limit'
|
||||
time_in_force = 'ioc'
|
||||
|
||||
order = exchange.sell(pair='ETH/BTC', ordertype=order_type,
|
||||
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell",
|
||||
amount=1, rate=200, time_in_force=time_in_force)
|
||||
|
||||
assert 'id' in order
|
||||
@ -1299,7 +1303,7 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name):
|
||||
|
||||
order_type = 'market'
|
||||
time_in_force = 'ioc'
|
||||
order = exchange.sell(pair='ETH/BTC', ordertype=order_type,
|
||||
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell",
|
||||
amount=1, rate=200, time_in_force=time_in_force)
|
||||
|
||||
assert 'id' in order
|
||||
@ -2186,7 +2190,7 @@ def test_cancel_order_dry_run(default_conf, mocker, exchange_name):
|
||||
assert exchange.cancel_order(order_id='123', pair='TKN/BTC') == {}
|
||||
assert exchange.cancel_stoploss_order(order_id='123', pair='TKN/BTC') == {}
|
||||
|
||||
order = exchange.buy('ETH/BTC', 'limit', 5, 0.55, 'gtc')
|
||||
order = exchange.create_order('ETH/BTC', 'limit', "buy", 5, 0.55, 'gtc')
|
||||
|
||||
cancel_order = exchange.cancel_order(order_id=order['id'], pair='ETH/BTC')
|
||||
assert order['id'] == cancel_order['id']
|
||||
|
@ -31,7 +31,7 @@ def test_buy_kraken_trading_agreement(default_conf, mocker):
|
||||
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
|
||||
|
||||
order = exchange.buy(pair='ETH/BTC', ordertype=order_type,
|
||||
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy",
|
||||
amount=1, rate=200, time_in_force=time_in_force)
|
||||
|
||||
assert 'id' in order
|
||||
@ -63,7 +63,8 @@ def test_sell_kraken_trading_agreement(default_conf, mocker):
|
||||
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
|
||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
|
||||
|
||||
order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
|
||||
order = exchange.create_order(pair='ETH/BTC', ordertype=order_type,
|
||||
side="sell", amount=1, rate=200)
|
||||
|
||||
assert 'id' in order
|
||||
assert 'info' in order
|
||||
|
@ -18,6 +18,7 @@ class BTrade(NamedTuple):
|
||||
sell_reason: SellType
|
||||
open_tick: int
|
||||
close_tick: int
|
||||
buy_tag: Optional[str] = None
|
||||
|
||||
|
||||
class BTContainer(NamedTuple):
|
||||
@ -44,6 +45,7 @@ def _get_frame_time_from_offset(offset):
|
||||
|
||||
def _build_backtest_dataframe(data):
|
||||
columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'buy', 'sell']
|
||||
columns = columns + ['buy_tag'] if len(data[0]) == 9 else columns
|
||||
|
||||
frame = DataFrame.from_records(data, columns=columns)
|
||||
frame['date'] = frame['date'].apply(_get_frame_time_from_offset)
|
||||
|
@ -516,6 +516,26 @@ tc32 = BTContainer(data=[
|
||||
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
|
||||
)
|
||||
|
||||
# Test 33: trailing_stop should be triggered immediately on trade open candle.
|
||||
# stop-loss: 1%, ROI: 10% (should not apply)
|
||||
tc33 = BTContainer(data=[
|
||||
# D O H L C V B S BT
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 1, 0, 'buy_signal_01'],
|
||||
[1, 5000, 5500, 5000, 4900, 6172, 0, 0, None], # enter trade (signal on last candle) and stop
|
||||
[2, 4900, 5250, 4500, 5100, 6172, 0, 0, None],
|
||||
[3, 5100, 5100, 4650, 4750, 6172, 0, 0, None],
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0, None]],
|
||||
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True,
|
||||
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
|
||||
trailing_stop_positive=0.01, use_custom_stoploss=True,
|
||||
trades=[BTrade(
|
||||
sell_reason=SellType.TRAILING_STOP_LOSS,
|
||||
open_tick=1,
|
||||
close_tick=1,
|
||||
buy_tag='buy_signal_01'
|
||||
)]
|
||||
)
|
||||
|
||||
TESTS = [
|
||||
tc0,
|
||||
tc1,
|
||||
@ -550,6 +570,7 @@ TESTS = [
|
||||
tc30,
|
||||
tc31,
|
||||
tc32,
|
||||
tc33,
|
||||
]
|
||||
|
||||
|
||||
@ -575,6 +596,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
|
||||
frame = _build_backtest_dataframe(data.data)
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
backtesting.required_startup = 0
|
||||
backtesting.strategy.advise_buy = lambda a, m: frame
|
||||
backtesting.strategy.advise_sell = lambda a, m: frame
|
||||
backtesting.strategy.use_custom_stoploss = data.use_custom_stoploss
|
||||
@ -598,5 +620,6 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
|
||||
for c, trade in enumerate(data.trades):
|
||||
res = results.iloc[c]
|
||||
assert res.sell_reason == trade.sell_reason.value
|
||||
assert res.buy_tag == trade.buy_tag
|
||||
assert res.open_date == _get_frame_time_from_offset(trade.open_tick)
|
||||
assert res.close_date == _get_frame_time_from_offset(trade.close_tick)
|
||||
|
@ -496,6 +496,7 @@ def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
|
||||
0, # Sell
|
||||
0.00099, # Low
|
||||
0.0012, # High
|
||||
'', # Buy Signal Name
|
||||
]
|
||||
trade = backtesting._enter_trade(pair, row=row)
|
||||
assert isinstance(trade, LocalTrade)
|
||||
@ -583,6 +584,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
|
||||
'min_rate': [0.1038, 0.10302485],
|
||||
'max_rate': [0.10501, 0.1038888],
|
||||
'is_open': [False, False],
|
||||
'buy_tag': [None, None],
|
||||
})
|
||||
pd.testing.assert_frame_equal(results, expected)
|
||||
data_pair = processed[pair]
|
||||
@ -727,6 +729,7 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
|
||||
pair='UNITTEST/BTC', datadir=testdatadir)
|
||||
default_conf['timeframe'] = '1m'
|
||||
backtesting = Backtesting(default_conf)
|
||||
backtesting.required_startup = 0
|
||||
backtesting._set_strategy(backtesting.strategylist[0])
|
||||
backtesting.strategy.advise_buy = _trend_alternate # Override
|
||||
backtesting.strategy.advise_sell = _trend_alternate # Override
|
||||
@ -736,6 +739,9 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
|
||||
# 100 buys signals
|
||||
results = result['results']
|
||||
assert len(results) == 100
|
||||
# Cached data should be 200 (no change since required_startup is 0)
|
||||
assert len(backtesting.dataprovider.get_analyzed_dataframe('UNITTEST/BTC', '1m')[0]) == 200
|
||||
|
||||
# One trade was force-closed at the end
|
||||
assert len(results.loc[results['is_open']]) == 0
|
||||
|
||||
@ -791,6 +797,10 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
|
||||
# make sure we don't have trades with more than configured max_open_trades
|
||||
assert len(evaluate_result_multi(results['results'], '5m', 3)) == 0
|
||||
|
||||
# Cached data correctly removed amounts
|
||||
removed_candles = len(data[pair]) - 1 - backtesting.strategy.startup_candle_count
|
||||
assert len(backtesting.dataprovider.get_analyzed_dataframe(pair, '5m')[0]) == removed_candles
|
||||
|
||||
backtest_conf = {
|
||||
'processed': processed,
|
||||
'start_date': min_date,
|
||||
|
@ -427,6 +427,10 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
|
||||
{"method": "RangeStabilityFilter", "lookback_days": 10,
|
||||
"min_rate_of_change": 0.01, "refresh_period": 1440}],
|
||||
"BTC", ['ETH/BTC', 'TKN/BTC', 'HOT/BTC']),
|
||||
([{"method": "StaticPairList"},
|
||||
{"method": "RangeStabilityFilter", "lookback_days": 10,
|
||||
"max_rate_of_change": 0.01, "refresh_period": 1440}],
|
||||
"BTC", []), # All removed because of max_rate_of_change being 0.017
|
||||
([{"method": "StaticPairList"},
|
||||
{"method": "VolatilityFilter", "lookback_days": 3,
|
||||
"min_volatility": 0.002, "max_volatility": 0.004, "refresh_period": 1440}],
|
||||
@ -874,15 +878,16 @@ def test_rangestabilityfilter_checks(mocker, default_conf, markets, tickers):
|
||||
get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('min_rate_of_change,expected_length', [
|
||||
(0.01, 5),
|
||||
(0.05, 0), # Setting rate_of_change to 5% removes all pairs from the whitelist.
|
||||
@pytest.mark.parametrize('min_rate_of_change,max_rate_of_change,expected_length', [
|
||||
(0.01, 0.99, 5),
|
||||
(0.05, 0.0, 0), # Setting min rate_of_change to 5% removes all pairs from the whitelist.
|
||||
])
|
||||
def test_rangestabilityfilter_caching(mocker, markets, default_conf, tickers, ohlcv_history,
|
||||
min_rate_of_change, expected_length):
|
||||
min_rate_of_change, max_rate_of_change, expected_length):
|
||||
default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10},
|
||||
{'method': 'RangeStabilityFilter', 'lookback_days': 2,
|
||||
'min_rate_of_change': min_rate_of_change}]
|
||||
'min_rate_of_change': min_rate_of_change,
|
||||
"max_rate_of_change": max_rate_of_change}]
|
||||
|
||||
mocker.patch.multiple('freqtrade.exchange.Exchange',
|
||||
markets=PropertyMock(return_value=markets),
|
||||
@ -984,11 +989,18 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo
|
||||
None,
|
||||
"PriceFilter requires max_value to be >= 0"
|
||||
), # OperationalException expected
|
||||
({"method": "RangeStabilityFilter", "lookback_days": 10, "min_rate_of_change": 0.01},
|
||||
({"method": "RangeStabilityFilter", "lookback_days": 10,
|
||||
"min_rate_of_change": 0.01},
|
||||
"[{'RangeStabilityFilter': 'RangeStabilityFilter - Filtering pairs with rate of change below "
|
||||
"0.01 over the last days.'}]",
|
||||
None
|
||||
),
|
||||
({"method": "RangeStabilityFilter", "lookback_days": 10,
|
||||
"min_rate_of_change": 0.01, "max_rate_of_change": 0.99},
|
||||
"[{'RangeStabilityFilter': 'RangeStabilityFilter - Filtering pairs with rate of change below "
|
||||
"0.01 and above 0.99 over the last days.'}]",
|
||||
None
|
||||
),
|
||||
])
|
||||
def test_pricefilter_desc(mocker, whitelist_conf, markets, pairlistconfig,
|
||||
desc_expected, exception_expected):
|
||||
|
@ -35,7 +35,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
)
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
rpc = RPC(freqtradebot)
|
||||
|
||||
freqtradebot.state = State.RUNNING
|
||||
@ -69,6 +69,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'min_rate': ANY,
|
||||
'max_rate': ANY,
|
||||
'strategy': ANY,
|
||||
'buy_tag': ANY,
|
||||
'timeframe': 5,
|
||||
'open_order_id': ANY,
|
||||
'close_date': None,
|
||||
@ -139,6 +140,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'min_rate': ANY,
|
||||
'max_rate': ANY,
|
||||
'strategy': ANY,
|
||||
'buy_tag': ANY,
|
||||
'timeframe': ANY,
|
||||
'open_order_id': ANY,
|
||||
'close_date': None,
|
||||
@ -198,7 +200,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||
)
|
||||
del default_conf['fiat_display_currency']
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
rpc = RPC(freqtradebot)
|
||||
|
||||
freqtradebot.state = State.RUNNING
|
||||
@ -245,7 +247,7 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
|
||||
)
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
stake_currency = default_conf['stake_currency']
|
||||
fiat_display_currency = default_conf['fiat_display_currency']
|
||||
|
||||
@ -377,7 +379,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
||||
)
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
stake_currency = default_conf['stake_currency']
|
||||
fiat_display_currency = default_conf['fiat_display_currency']
|
||||
|
||||
@ -465,7 +467,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee,
|
||||
)
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
stake_currency = default_conf['stake_currency']
|
||||
fiat_display_currency = default_conf['fiat_display_currency']
|
||||
|
||||
@ -532,7 +534,7 @@ def test_rpc_balance_handle_error(default_conf, mocker):
|
||||
)
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
rpc = RPC(freqtradebot)
|
||||
rpc._fiat_converter = CryptoToFiatConverter()
|
||||
with pytest.raises(RPCException, match="Error getting current tickers."):
|
||||
@ -573,7 +575,7 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
|
||||
)
|
||||
default_conf['dry_run'] = False
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
rpc = RPC(freqtradebot)
|
||||
rpc._fiat_converter = CryptoToFiatConverter()
|
||||
|
||||
@ -618,7 +620,7 @@ def test_rpc_start(mocker, default_conf) -> None:
|
||||
)
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
rpc = RPC(freqtradebot)
|
||||
freqtradebot.state = State.STOPPED
|
||||
|
||||
@ -639,7 +641,7 @@ def test_rpc_stop(mocker, default_conf) -> None:
|
||||
)
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
rpc = RPC(freqtradebot)
|
||||
freqtradebot.state = State.RUNNING
|
||||
|
||||
@ -661,7 +663,7 @@ def test_rpc_stopbuy(mocker, default_conf) -> None:
|
||||
)
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
rpc = RPC(freqtradebot)
|
||||
freqtradebot.state = State.RUNNING
|
||||
|
||||
@ -693,7 +695,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
|
||||
mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=1000)
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
rpc = RPC(freqtradebot)
|
||||
|
||||
freqtradebot.state = State.STOPPED
|
||||
@ -811,7 +813,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
|
||||
)
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
rpc = RPC(freqtradebot)
|
||||
|
||||
# Create some test data
|
||||
@ -844,7 +846,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
|
||||
)
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
rpc = RPC(freqtradebot)
|
||||
|
||||
counts = rpc._rpc_count()
|
||||
@ -865,11 +867,11 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
fetch_ticker=ticker,
|
||||
get_fee=fee,
|
||||
buy=buy_mm
|
||||
create_order=buy_mm
|
||||
)
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
rpc = RPC(freqtradebot)
|
||||
pair = 'ETH/BTC'
|
||||
trade = rpc._rpc_forcebuy(pair, None)
|
||||
@ -895,7 +897,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
|
||||
# Test not buying
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
freqtradebot.config['stake_amount'] = 0
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
rpc = RPC(freqtradebot)
|
||||
pair = 'TKN/BTC'
|
||||
trade = rpc._rpc_forcebuy(pair, None)
|
||||
@ -908,7 +910,7 @@ def test_rpcforcebuy_stopped(mocker, default_conf) -> None:
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
rpc = RPC(freqtradebot)
|
||||
pair = 'ETH/BTC'
|
||||
with pytest.raises(RPCException, match=r'trader is not running'):
|
||||
@ -919,7 +921,7 @@ def test_rpcforcebuy_disabled(mocker, default_conf) -> None:
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
rpc = RPC(freqtradebot)
|
||||
pair = 'ETH/BTC'
|
||||
with pytest.raises(RPCException, match=r'Forcebuy not enabled.'):
|
||||
|
@ -442,7 +442,7 @@ def test_api_balance(botclient, mocker, rpc_balance):
|
||||
|
||||
def test_api_count(botclient, mocker, ticker, fee, markets):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
patch_get_signal(ftbot, (True, False, None))
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
@ -504,7 +504,7 @@ def test_api_locks(botclient):
|
||||
|
||||
def test_api_show_config(botclient, mocker):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
patch_get_signal(ftbot, (True, False, None))
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/show_config")
|
||||
assert_response(rc)
|
||||
@ -522,7 +522,7 @@ def test_api_show_config(botclient, mocker):
|
||||
|
||||
def test_api_daily(botclient, mocker, ticker, fee, markets):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
patch_get_signal(ftbot, (True, False, None))
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
@ -540,7 +540,7 @@ def test_api_daily(botclient, mocker, ticker, fee, markets):
|
||||
|
||||
def test_api_trades(botclient, mocker, fee, markets):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
patch_get_signal(ftbot, (True, False, None))
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
markets=PropertyMock(return_value=markets)
|
||||
@ -568,7 +568,7 @@ def test_api_trades(botclient, mocker, fee, markets):
|
||||
|
||||
def test_api_trade_single(botclient, mocker, fee, ticker, markets):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
patch_get_signal(ftbot, (True, False, None))
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
markets=PropertyMock(return_value=markets),
|
||||
@ -588,7 +588,7 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets):
|
||||
|
||||
def test_api_delete_trade(botclient, mocker, fee, markets):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
patch_get_signal(ftbot, (True, False, None))
|
||||
stoploss_mock = MagicMock()
|
||||
cancel_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
@ -656,13 +656,13 @@ def test_api_logs(botclient):
|
||||
# Help debugging random test failure
|
||||
print(f"rc={rc.json()}")
|
||||
print(f"rc1={rc1.json()}")
|
||||
assert rc1.json()['log_count'] == 5
|
||||
assert rc1.json()['log_count'] > 2
|
||||
assert len(rc1.json()['logs']) == rc1.json()['log_count']
|
||||
|
||||
|
||||
def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
patch_get_signal(ftbot, (True, False, None))
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
@ -678,7 +678,7 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_api_profit(botclient, mocker, ticker, fee, markets):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
patch_get_signal(ftbot, (True, False, None))
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
@ -729,7 +729,7 @@ def test_api_profit(botclient, mocker, ticker, fee, markets):
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_api_stats(botclient, mocker, ticker, fee, markets,):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
patch_get_signal(ftbot, (True, False, None))
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
@ -757,7 +757,7 @@ def test_api_stats(botclient, mocker, ticker, fee, markets,):
|
||||
|
||||
def test_api_performance(botclient, fee):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
patch_get_signal(ftbot, (True, False, None))
|
||||
|
||||
trade = Trade(
|
||||
pair='LTC/ETH',
|
||||
@ -803,7 +803,7 @@ def test_api_performance(botclient, fee):
|
||||
|
||||
def test_api_status(botclient, mocker, ticker, fee, markets):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
patch_get_signal(ftbot, (True, False, None))
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
@ -875,6 +875,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
|
||||
'sell_reason': None,
|
||||
'sell_order_status': None,
|
||||
'strategy': 'DefaultStrategy',
|
||||
'buy_tag': None,
|
||||
'timeframe': 5,
|
||||
'exchange': 'binance',
|
||||
}
|
||||
@ -1029,6 +1030,7 @@ def test_api_forcebuy(botclient, mocker, fee):
|
||||
'sell_reason': None,
|
||||
'sell_order_status': None,
|
||||
'strategy': 'DefaultStrategy',
|
||||
'buy_tag': None,
|
||||
'timeframe': 5,
|
||||
'exchange': 'binance',
|
||||
}
|
||||
@ -1044,7 +1046,7 @@ def test_api_forcesell(botclient, mocker, ticker, fee, markets):
|
||||
markets=PropertyMock(return_value=markets),
|
||||
_is_dry_limit_order_filled=MagicMock(return_value=False),
|
||||
)
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
patch_get_signal(ftbot, (True, False, None))
|
||||
|
||||
rc = client_post(client, f"{BASE_URI}/forcesell",
|
||||
data='{"tradeid": "1"}')
|
||||
@ -1185,8 +1187,10 @@ def test_api_plot_config(botclient):
|
||||
assert_response(rc)
|
||||
assert rc.json() == {}
|
||||
|
||||
ftbot.strategy.plot_config = {'main_plot': {'sma': {}},
|
||||
'subplots': {'RSI': {'rsi': {'color': 'red'}}}}
|
||||
ftbot.strategy.plot_config = {
|
||||
'main_plot': {'sma': {}},
|
||||
'subplots': {'RSI': {'rsi': {'color': 'red'}}}
|
||||
}
|
||||
rc = client_get(client, f"{BASE_URI}/plot_config")
|
||||
assert_response(rc)
|
||||
assert rc.json() == ftbot.strategy.plot_config
|
||||
|
@ -119,7 +119,7 @@ def test_authorized_only(default_conf, mocker, caplog, update) -> None:
|
||||
rpc = RPC(bot)
|
||||
dummy = DummyCls(rpc, default_conf)
|
||||
|
||||
patch_get_signal(bot, (True, False))
|
||||
patch_get_signal(bot, (True, False, None))
|
||||
dummy.dummy_handler(update=update, context=MagicMock())
|
||||
assert dummy.state['called'] is True
|
||||
assert log_has('Executing handler: dummy_handler for chat_id: 0', caplog)
|
||||
@ -139,7 +139,7 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
|
||||
rpc = RPC(bot)
|
||||
dummy = DummyCls(rpc, default_conf)
|
||||
|
||||
patch_get_signal(bot, (True, False))
|
||||
patch_get_signal(bot, (True, False, None))
|
||||
dummy.dummy_handler(update=update, context=MagicMock())
|
||||
assert dummy.state['called'] is False
|
||||
assert not log_has('Executing handler: dummy_handler for chat_id: 3735928559', caplog)
|
||||
@ -155,7 +155,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog, update) -> None
|
||||
bot = FreqtradeBot(default_conf)
|
||||
rpc = RPC(bot)
|
||||
dummy = DummyCls(rpc, default_conf)
|
||||
patch_get_signal(bot, (True, False))
|
||||
patch_get_signal(bot, (True, False, None))
|
||||
|
||||
dummy.dummy_exception(update=update, context=MagicMock())
|
||||
assert dummy.state['called'] is False
|
||||
@ -185,6 +185,7 @@ def test_telegram_status(default_conf, update, mocker) -> None:
|
||||
'current_rate': 1.098e-05,
|
||||
'amount': 90.99181074,
|
||||
'stake_amount': 90.99181074,
|
||||
'buy_tag': None,
|
||||
'close_profit_pct': None,
|
||||
'profit': -0.0059,
|
||||
'profit_pct': -0.59,
|
||||
@ -228,7 +229,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
|
||||
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
|
||||
freqtradebot.state = State.STOPPED
|
||||
# Status is also enabled when stopped
|
||||
@ -285,7 +286,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
|
||||
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
|
||||
freqtradebot.state = State.STOPPED
|
||||
# Status table is also enabled when stopped
|
||||
@ -329,7 +330,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
|
||||
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
|
||||
# Create some test data
|
||||
freqtradebot.enter_positions()
|
||||
@ -400,7 +401,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
|
||||
)
|
||||
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
|
||||
# Try invalid data
|
||||
msg_mock.reset_mock()
|
||||
@ -432,7 +433,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
|
||||
)
|
||||
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
|
||||
telegram._profit(update=update, context=MagicMock())
|
||||
assert msg_mock.call_count == 1
|
||||
@ -487,7 +488,7 @@ def test_telegram_stats(default_conf, update, ticker, ticker_sell_up, fee,
|
||||
get_fee=fee,
|
||||
)
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
|
||||
telegram._stats(update=update, context=MagicMock())
|
||||
assert msg_mock.call_count == 1
|
||||
@ -513,7 +514,7 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tick
|
||||
side_effect=lambda a, b: f"{a}/{b}")
|
||||
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
|
||||
telegram._balance(update=update, context=MagicMock())
|
||||
result = msg_mock.call_args_list[0][0][0]
|
||||
@ -536,7 +537,7 @@ def test_balance_handle_empty_response(default_conf, update, mocker) -> None:
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={})
|
||||
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
|
||||
freqtradebot.config['dry_run'] = False
|
||||
telegram._balance(update=update, context=MagicMock())
|
||||
@ -549,7 +550,7 @@ def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value={})
|
||||
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
|
||||
telegram._balance(update=update, context=MagicMock())
|
||||
result = msg_mock.call_args_list[0][0][0]
|
||||
@ -578,7 +579,7 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None
|
||||
})
|
||||
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
|
||||
telegram._balance(update=update, context=MagicMock())
|
||||
assert msg_mock.call_count > 1
|
||||
@ -677,7 +678,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
rpc = RPC(freqtradebot)
|
||||
telegram = Telegram(rpc, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
|
||||
# Create some test data
|
||||
freqtradebot.enter_positions()
|
||||
@ -736,7 +737,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
rpc = RPC(freqtradebot)
|
||||
telegram = Telegram(rpc, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
|
||||
# Create some test data
|
||||
freqtradebot.enter_positions()
|
||||
@ -797,7 +798,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
rpc = RPC(freqtradebot)
|
||||
telegram = Telegram(rpc, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
|
||||
# Create some test data
|
||||
freqtradebot.enter_positions()
|
||||
@ -838,7 +839,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
|
||||
return_value=15000.0)
|
||||
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
|
||||
# Trader is not running
|
||||
freqtradebot.state = State.STOPPED
|
||||
@ -876,7 +877,7 @@ def test_forcebuy_handle(default_conf, update, mocker) -> None:
|
||||
mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock)
|
||||
|
||||
telegram, freqtradebot, _ = get_telegram_testobject(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
|
||||
# /forcebuy ETH/BTC
|
||||
context = MagicMock()
|
||||
@ -905,7 +906,7 @@ def test_forcebuy_handle_exception(default_conf, update, mocker) -> None:
|
||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
|
||||
update.message.text = '/forcebuy ETH/Nonepair'
|
||||
telegram._forcebuy(update=update, context=MagicMock())
|
||||
@ -922,7 +923,7 @@ def test_forcebuy_no_pair(default_conf, update, mocker) -> None:
|
||||
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
|
||||
context = MagicMock()
|
||||
context.args = []
|
||||
@ -950,7 +951,7 @@ def test_performance_handle(default_conf, update, ticker, fee,
|
||||
get_fee=fee,
|
||||
)
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
|
||||
# Create some test data
|
||||
freqtradebot.enter_positions()
|
||||
@ -978,7 +979,7 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
|
||||
get_fee=fee,
|
||||
)
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
|
||||
freqtradebot.state = State.STOPPED
|
||||
telegram._count(update=update, context=MagicMock())
|
||||
@ -1007,7 +1008,7 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None
|
||||
get_fee=fee,
|
||||
)
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
patch_get_signal(freqtradebot, (True, False, None))
|
||||
telegram._locks(update=update, context=MagicMock())
|
||||
assert msg_mock.call_count == 1
|
||||
assert 'No active locks.' in msg_mock.call_args_list[0][0][0]
|
||||
@ -1253,6 +1254,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
|
||||
msg = {
|
||||
'type': RPCMessageType.BUY,
|
||||
'trade_id': 1,
|
||||
'buy_tag': 'buy_signal_01',
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'limit': 1.099e-05,
|
||||
@ -1270,6 +1272,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
|
||||
telegram.send_msg(msg)
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== '\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n' \
|
||||
'*Buy Tag:* `buy_signal_01`\n' \
|
||||
'*Amount:* `1333.33333333`\n' \
|
||||
'*Open Rate:* `0.00001099`\n' \
|
||||
'*Current Rate:* `0.00001099`\n' \
|
||||
@ -1297,6 +1300,7 @@ def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None:
|
||||
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.BUY_CANCEL,
|
||||
'buy_tag': 'buy_signal_01',
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
@ -1314,6 +1318,7 @@ def test_send_msg_buy_fill_notification(default_conf, mocker) -> None:
|
||||
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.BUY_FILL,
|
||||
'buy_tag': 'buy_signal_01',
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/USDT',
|
||||
@ -1498,6 +1503,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
|
||||
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.BUY,
|
||||
'buy_tag': 'buy_signal_01',
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
@ -1512,6 +1518,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
|
||||
'open_date': arrow.utcnow().shift(hours=-1)
|
||||
})
|
||||
assert msg_mock.call_args[0][0] == ('\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n'
|
||||
'*Buy Tag:* `buy_signal_01`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00001099`\n'
|
||||
'*Current Rate:* `0.00001099`\n'
|
||||
|
@ -38,15 +38,20 @@ def test_returns_latest_signal(mocker, default_conf, ohlcv_history):
|
||||
mocked_history['buy'] = 0
|
||||
mocked_history.loc[1, 'sell'] = 1
|
||||
|
||||
assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, True)
|
||||
assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, True, None)
|
||||
mocked_history.loc[1, 'sell'] = 0
|
||||
mocked_history.loc[1, 'buy'] = 1
|
||||
|
||||
assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False)
|
||||
assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, None)
|
||||
mocked_history.loc[1, 'sell'] = 0
|
||||
mocked_history.loc[1, 'buy'] = 0
|
||||
|
||||
assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False)
|
||||
assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False, None)
|
||||
mocked_history.loc[1, 'sell'] = 0
|
||||
mocked_history.loc[1, 'buy'] = 1
|
||||
mocked_history.loc[1, 'buy_tag'] = 'buy_signal_01'
|
||||
|
||||
assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, 'buy_signal_01')
|
||||
|
||||
|
||||
def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history):
|
||||
@ -63,15 +68,21 @@ def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history):
|
||||
|
||||
|
||||
def test_get_signal_empty(default_conf, mocker, caplog):
|
||||
assert (False, False) == _STRATEGY.get_signal('foo', default_conf['timeframe'], DataFrame())
|
||||
assert (False, False, None) == _STRATEGY.get_signal(
|
||||
'foo', default_conf['timeframe'], DataFrame()
|
||||
)
|
||||
assert log_has('Empty candle (OHLCV) data for pair foo', caplog)
|
||||
caplog.clear()
|
||||
|
||||
assert (False, False) == _STRATEGY.get_signal('bar', default_conf['timeframe'], None)
|
||||
assert (False, False, None) == _STRATEGY.get_signal('bar', default_conf['timeframe'], None)
|
||||
assert log_has('Empty candle (OHLCV) data for pair bar', caplog)
|
||||
caplog.clear()
|
||||
|
||||
assert (False, False) == _STRATEGY.get_signal('baz', default_conf['timeframe'], DataFrame([]))
|
||||
assert (False, False, None) == _STRATEGY.get_signal(
|
||||
'baz',
|
||||
default_conf['timeframe'],
|
||||
DataFrame([])
|
||||
)
|
||||
assert log_has('Empty candle (OHLCV) data for pair baz', caplog)
|
||||
|
||||
|
||||
@ -107,10 +118,35 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history):
|
||||
caplog.set_level(logging.INFO)
|
||||
mocker.patch.object(_STRATEGY, 'assert_df')
|
||||
|
||||
assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['timeframe'], mocked_history)
|
||||
assert (False, False, None) == _STRATEGY.get_signal(
|
||||
'xyz',
|
||||
default_conf['timeframe'],
|
||||
mocked_history
|
||||
)
|
||||
assert log_has('Outdated history for pair xyz. Last tick is 16 minutes old', caplog)
|
||||
|
||||
|
||||
def test_get_signal_no_sell_column(default_conf, mocker, caplog, ohlcv_history):
|
||||
# default_conf defines a 5m interval. we check interval * 2 + 5m
|
||||
# this is necessary as the last candle is removed (partial candles) by default
|
||||
ohlcv_history.loc[1, 'date'] = arrow.utcnow()
|
||||
# Take a copy to correctly modify the call
|
||||
mocked_history = ohlcv_history.copy()
|
||||
# Intentionally don't set sell column
|
||||
# mocked_history['sell'] = 0
|
||||
mocked_history['buy'] = 0
|
||||
mocked_history.loc[1, 'buy'] = 1
|
||||
|
||||
caplog.set_level(logging.INFO)
|
||||
mocker.patch.object(_STRATEGY, 'assert_df')
|
||||
|
||||
assert (True, False, None) == _STRATEGY.get_signal(
|
||||
'xyz',
|
||||
default_conf['timeframe'],
|
||||
mocked_history
|
||||
)
|
||||
|
||||
|
||||
def test_ignore_expired_candle(default_conf):
|
||||
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
@ -182,10 +218,6 @@ def test_assert_df(ohlcv_history, caplog):
|
||||
match="Buy column not set"):
|
||||
_STRATEGY.assert_df(ohlcv_history.drop('buy', axis=1), len(ohlcv_history),
|
||||
ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[0, 'date'])
|
||||
with pytest.raises(StrategyError,
|
||||
match="Sell column not set"):
|
||||
_STRATEGY.assert_df(ohlcv_history.drop('sell', axis=1), len(ohlcv_history),
|
||||
ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[0, 'date'])
|
||||
|
||||
_STRATEGY.disable_dataframe_checks = True
|
||||
caplog.clear()
|
||||
|
@ -18,8 +18,9 @@ from freqtrade.configuration.deprecated_settings import (check_conflicting_setti
|
||||
process_deprecated_setting,
|
||||
process_removed_setting,
|
||||
process_temporary_deprecated_settings)
|
||||
from freqtrade.configuration.environment_vars import flat_vars_to_nested_dict
|
||||
from freqtrade.configuration.load_config import load_config_file, load_file, log_config_error_range
|
||||
from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL
|
||||
from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL, ENV_VAR_PREFIX
|
||||
from freqtrade.enums import RunMode
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.loggers import _set_loggers, setup_logging, setup_logging_pre
|
||||
@ -1349,3 +1350,35 @@ def test_process_deprecated_ticker_interval(mocker, default_conf, caplog):
|
||||
with pytest.raises(OperationalException,
|
||||
match=r"Both 'timeframe' and 'ticker_interval' detected."):
|
||||
process_temporary_deprecated_settings(config)
|
||||
|
||||
|
||||
def test_flat_vars_to_nested_dict(caplog):
|
||||
|
||||
test_args = {
|
||||
'FREQTRADE__EXCHANGE__SOME_SETTING': 'true',
|
||||
'FREQTRADE__EXCHANGE__SOME_FALSE_SETTING': 'false',
|
||||
'FREQTRADE__EXCHANGE__CONFIG__whatever': 'sometime',
|
||||
'FREQTRADE__ASK_STRATEGY__PRICE_SIDE': 'bid',
|
||||
'FREQTRADE__ASK_STRATEGY__cccc': '500',
|
||||
'FREQTRADE__STAKE_AMOUNT': '200.05',
|
||||
'NOT_RELEVANT': '200.0', # Will be ignored
|
||||
}
|
||||
expected = {
|
||||
'stake_amount': 200.05,
|
||||
'ask_strategy': {
|
||||
'price_side': 'bid',
|
||||
'cccc': 500,
|
||||
},
|
||||
'exchange': {
|
||||
'config': {
|
||||
'whatever': 'sometime',
|
||||
},
|
||||
'some_setting': True,
|
||||
'some_false_setting': False,
|
||||
}
|
||||
}
|
||||
res = flat_vars_to_nested_dict(test_args, ENV_VAR_PREFIX)
|
||||
assert res == expected
|
||||
|
||||
assert log_has("Loading variable 'FREQTRADE__EXCHANGE__SOME_SETTING'", caplog)
|
||||
assert not log_has("Loading variable 'NOT_RELEVANT'", caplog)
|
||||
|
@ -169,7 +169,7 @@ def test_check_available_stake_amount(default_conf, ticker, mocker, fee, limit_b
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(return_value=limit_buy_order_open),
|
||||
get_fee=fee
|
||||
)
|
||||
default_conf['dry_run_wallet'] = wallet
|
||||
@ -384,7 +384,7 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order_open,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=buy_mock,
|
||||
create_order=buy_mock,
|
||||
get_fee=fee,
|
||||
)
|
||||
default_conf['stake_amount'] = 0.0005
|
||||
@ -404,7 +404,7 @@ def test_create_trade_too_small_stake_amount(default_conf, ticker, limit_buy_ord
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=buy_mock,
|
||||
create_order=buy_mock,
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
@ -425,7 +425,7 @@ def test_create_trade_zero_stake_amount(default_conf, ticker, limit_buy_order_op
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=buy_mock,
|
||||
create_order=buy_mock,
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
@ -444,7 +444,7 @@ def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order_open,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(return_value=limit_buy_order_open),
|
||||
get_fee=fee,
|
||||
)
|
||||
default_conf['max_open_trades'] = 0
|
||||
@ -464,7 +464,7 @@ def test_enter_positions_no_pairs_left(default_conf, ticker, limit_buy_order_ope
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(return_value=limit_buy_order_open),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
@ -487,7 +487,7 @@ def test_enter_positions_no_pairs_in_whitelist(default_conf, ticker, limit_buy_o
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
create_order=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
default_conf['exchange']['pair_whitelist'] = []
|
||||
@ -507,7 +507,7 @@ def test_enter_positions_global_pairlock(default_conf, ticker, limit_buy_order,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
create_order=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
@ -536,7 +536,7 @@ def test_create_trade_no_signal(default_conf, fee, mocker) -> None:
|
||||
)
|
||||
default_conf['stake_amount'] = 10
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade, value=(False, False))
|
||||
patch_get_signal(freqtrade, value=(False, False, None))
|
||||
|
||||
Trade.query = MagicMock()
|
||||
Trade.query.filter = MagicMock()
|
||||
@ -556,7 +556,7 @@ def test_create_trades_multiple_trades(default_conf, ticker, fee, mocker, limit_
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(return_value=limit_buy_order_open),
|
||||
get_fee=fee,
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
@ -577,7 +577,7 @@ def test_create_trades_preopen(default_conf, ticker, fee, mocker, limit_buy_orde
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(return_value=limit_buy_order_open),
|
||||
get_fee=fee,
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
@ -606,7 +606,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order, limit_buy
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(return_value=limit_buy_order_open),
|
||||
fetch_order=MagicMock(return_value=limit_buy_order),
|
||||
get_fee=fee,
|
||||
)
|
||||
@ -641,7 +641,7 @@ def test_process_exchange_failures(default_conf, ticker, mocker) -> None:
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(side_effect=TemporaryError)
|
||||
create_order=MagicMock(side_effect=TemporaryError)
|
||||
)
|
||||
sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None)
|
||||
|
||||
@ -658,7 +658,7 @@ def test_process_operational_exception(default_conf, ticker, mocker) -> None:
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(side_effect=OperationalException)
|
||||
create_order=MagicMock(side_effect=OperationalException)
|
||||
)
|
||||
worker = Worker(args=None, config=default_conf)
|
||||
patch_get_signal(worker.freqtrade)
|
||||
@ -676,7 +676,7 @@ def test_process_trade_handling(default_conf, ticker, limit_buy_order_open, fee,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(return_value=limit_buy_order_open),
|
||||
fetch_order=MagicMock(return_value=limit_buy_order_open),
|
||||
get_fee=fee,
|
||||
)
|
||||
@ -703,7 +703,7 @@ def test_process_trade_no_whitelist_pair(default_conf, ticker, limit_buy_order,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
create_order=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
fetch_order=MagicMock(return_value=limit_buy_order),
|
||||
get_fee=fee,
|
||||
)
|
||||
@ -753,11 +753,14 @@ def test_process_informative_pairs_added(default_conf, ticker, mocker) -> None:
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(side_effect=TemporaryError),
|
||||
create_order=MagicMock(side_effect=TemporaryError),
|
||||
refresh_latest_ohlcv=refresh_mock,
|
||||
)
|
||||
inf_pairs = MagicMock(return_value=[("BTC/ETH", '1m'), ("ETH/USDT", "1h")])
|
||||
mocker.patch('freqtrade.strategy.interface.IStrategy.get_signal', return_value=(False, False))
|
||||
mocker.patch(
|
||||
'freqtrade.strategy.interface.IStrategy.get_signal',
|
||||
return_value=(False, False, '')
|
||||
)
|
||||
mocker.patch('time.sleep', return_value=None)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
@ -790,7 +793,7 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||
'ask': 0.00001173,
|
||||
'last': 0.00001172
|
||||
}),
|
||||
buy=buy_mm,
|
||||
create_order=buy_mm,
|
||||
get_min_pair_stake_amount=MagicMock(return_value=1),
|
||||
get_fee=fee,
|
||||
)
|
||||
@ -839,7 +842,8 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||
limit_buy_order['cost'] = 100
|
||||
limit_buy_order['id'] = '444'
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.buy', MagicMock(return_value=limit_buy_order))
|
||||
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||
MagicMock(return_value=limit_buy_order))
|
||||
assert freqtrade.execute_buy(pair, stake_amount)
|
||||
trade = Trade.query.all()[2]
|
||||
assert trade
|
||||
@ -855,7 +859,8 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||
limit_buy_order['price'] = 0.5
|
||||
limit_buy_order['cost'] = 40.495905365
|
||||
limit_buy_order['id'] = '555'
|
||||
mocker.patch('freqtrade.exchange.Exchange.buy', MagicMock(return_value=limit_buy_order))
|
||||
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||
MagicMock(return_value=limit_buy_order))
|
||||
assert freqtrade.execute_buy(pair, stake_amount)
|
||||
trade = Trade.query.all()[3]
|
||||
assert trade
|
||||
@ -889,7 +894,8 @@ def test_execute_buy(mocker, default_conf, fee, limit_buy_order, limit_buy_order
|
||||
limit_buy_order['price'] = 0.5
|
||||
limit_buy_order['cost'] = 0.0
|
||||
limit_buy_order['id'] = '66'
|
||||
mocker.patch('freqtrade.exchange.Exchange.buy', MagicMock(return_value=limit_buy_order))
|
||||
mocker.patch('freqtrade.exchange.Exchange.create_order',
|
||||
MagicMock(return_value=limit_buy_order))
|
||||
assert not freqtrade.execute_buy(pair, stake_amount)
|
||||
|
||||
# Fail to get price...
|
||||
@ -908,7 +914,7 @@ def test_execute_buy_confirm_error(mocker, default_conf, fee, limit_buy_order) -
|
||||
'ask': 0.00001173,
|
||||
'last': 0.00001172
|
||||
}),
|
||||
buy=MagicMock(return_value=limit_buy_order),
|
||||
create_order=MagicMock(return_value=limit_buy_order),
|
||||
get_rate=MagicMock(return_value=0.11),
|
||||
get_min_pair_stake_amount=MagicMock(return_value=1),
|
||||
get_fee=fee,
|
||||
@ -970,8 +976,10 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
|
||||
'ask': 0.00001173,
|
||||
'last': 0.00001172
|
||||
}),
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
|
||||
create_order=MagicMock(side_effect=[
|
||||
{'id': limit_buy_order['id']},
|
||||
{'id': limit_sell_order['id']},
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
@ -1087,8 +1095,10 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf, fee, caplog,
|
||||
'ask': 0.00001173,
|
||||
'last': 0.00001172
|
||||
}),
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
|
||||
create_order=MagicMock(side_effect=[
|
||||
{'id': limit_buy_order['id']},
|
||||
{'id': limit_sell_order['id']},
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
@ -1116,7 +1126,10 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf, caplog, fee,
|
||||
limit_buy_order_open, limit_sell_order):
|
||||
rpc_mock = patch_RPCManager(mocker)
|
||||
patch_exchange(mocker)
|
||||
sell_mock = MagicMock(return_value={'id': limit_sell_order['id']})
|
||||
create_order_mock = MagicMock(side_effect=[
|
||||
limit_buy_order_open,
|
||||
{'id': limit_sell_order['id']}
|
||||
])
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=MagicMock(return_value={
|
||||
@ -1124,8 +1137,7 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf, caplog, fee,
|
||||
'ask': 0.00001173,
|
||||
'last': 0.00001172
|
||||
}),
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
sell=sell_mock,
|
||||
create_order=create_order_mock,
|
||||
get_fee=fee,
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
@ -1147,10 +1159,10 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf, caplog, fee,
|
||||
assert log_has("Selling the trade forcefully", caplog)
|
||||
|
||||
# Should call a market sell
|
||||
assert sell_mock.call_count == 1
|
||||
assert sell_mock.call_args[1]['ordertype'] == 'market'
|
||||
assert sell_mock.call_args[1]['pair'] == trade.pair
|
||||
assert sell_mock.call_args[1]['amount'] == trade.amount
|
||||
assert create_order_mock.call_count == 2
|
||||
assert create_order_mock.call_args[1]['ordertype'] == 'market'
|
||||
assert create_order_mock.call_args[1]['pair'] == trade.pair
|
||||
assert create_order_mock.call_args[1]['amount'] == trade.amount
|
||||
|
||||
# Rpc is sending first buy, then sell
|
||||
assert rpc_mock.call_count == 2
|
||||
@ -1171,8 +1183,10 @@ def test_create_stoploss_order_insufficient_funds(mocker, default_conf, caplog,
|
||||
'ask': 0.00001173,
|
||||
'last': 0.00001172
|
||||
}),
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
sell=sell_mock,
|
||||
create_order=MagicMock(side_effect=[
|
||||
limit_buy_order_open,
|
||||
sell_mock,
|
||||
]),
|
||||
get_fee=fee,
|
||||
fetch_order=MagicMock(return_value={'status': 'canceled'}),
|
||||
)
|
||||
@ -1212,8 +1226,10 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee,
|
||||
'ask': 0.00001173,
|
||||
'last': 0.00001172
|
||||
}),
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
|
||||
create_order=MagicMock(side_effect=[
|
||||
{'id': limit_buy_order['id']},
|
||||
{'id': limit_sell_order['id']},
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
@ -1318,8 +1334,10 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c
|
||||
'ask': 0.00001173,
|
||||
'last': 0.00001172
|
||||
}),
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
|
||||
create_order=MagicMock(side_effect=[
|
||||
{'id': limit_buy_order['id']},
|
||||
{'id': limit_sell_order['id']},
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
@ -1391,8 +1409,10 @@ def test_handle_stoploss_on_exchange_custom_stop(mocker, default_conf, fee,
|
||||
'ask': 0.00001173,
|
||||
'last': 0.00001172
|
||||
}),
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
|
||||
create_order=MagicMock(side_effect=[
|
||||
{'id': limit_buy_order['id']},
|
||||
{'id': limit_sell_order['id']},
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
@ -1502,8 +1522,10 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
|
||||
'ask': 0.00001173,
|
||||
'last': 0.00001172
|
||||
}),
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
|
||||
create_order=MagicMock(side_effect=[
|
||||
{'id': limit_buy_order['id']},
|
||||
{'id': limit_sell_order['id']},
|
||||
]),
|
||||
get_fee=fee,
|
||||
stoploss=stoploss,
|
||||
)
|
||||
@ -1840,8 +1862,10 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order_open, limi
|
||||
'ask': 0.00001173,
|
||||
'last': 0.00001172
|
||||
}),
|
||||
buy=MagicMock(return_value=limit_buy_order),
|
||||
sell=MagicMock(return_value=limit_sell_order_open),
|
||||
create_order=MagicMock(side_effect=[
|
||||
limit_buy_order,
|
||||
limit_sell_order_open,
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
@ -1857,7 +1881,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order_open, limi
|
||||
assert trade.is_open is True
|
||||
freqtrade.wallets.update()
|
||||
|
||||
patch_get_signal(freqtrade, value=(False, True))
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert trade.open_order_id == limit_sell_order['id']
|
||||
|
||||
@ -1877,12 +1901,15 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(side_effect=[
|
||||
limit_buy_order_open,
|
||||
{'id': 1234553382},
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtrade, value=(True, True))
|
||||
patch_get_signal(freqtrade, value=(True, True, None))
|
||||
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
|
||||
|
||||
freqtrade.enter_positions()
|
||||
@ -1893,7 +1920,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open,
|
||||
assert nb_trades == 0
|
||||
|
||||
# Buy is triggering, so buying ...
|
||||
patch_get_signal(freqtrade, value=(True, False))
|
||||
patch_get_signal(freqtrade, value=(True, False, None))
|
||||
freqtrade.enter_positions()
|
||||
trades = Trade.query.all()
|
||||
nb_trades = len(trades)
|
||||
@ -1901,7 +1928,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open,
|
||||
assert trades[0].is_open is True
|
||||
|
||||
# Buy and Sell are not triggering, so doing nothing ...
|
||||
patch_get_signal(freqtrade, value=(False, False))
|
||||
patch_get_signal(freqtrade, value=(False, False, None))
|
||||
assert freqtrade.handle_trade(trades[0]) is False
|
||||
trades = Trade.query.all()
|
||||
nb_trades = len(trades)
|
||||
@ -1909,7 +1936,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open,
|
||||
assert trades[0].is_open is True
|
||||
|
||||
# Buy and Sell are triggering, so doing nothing ...
|
||||
patch_get_signal(freqtrade, value=(True, True))
|
||||
patch_get_signal(freqtrade, value=(True, True, None))
|
||||
assert freqtrade.handle_trade(trades[0]) is False
|
||||
trades = Trade.query.all()
|
||||
nb_trades = len(trades)
|
||||
@ -1917,7 +1944,7 @@ def test_handle_overlapping_signals(default_conf, ticker, limit_buy_order_open,
|
||||
assert trades[0].is_open is True
|
||||
|
||||
# Sell is triggering, guess what : we are Selling!
|
||||
patch_get_signal(freqtrade, value=(False, True))
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
trades = Trade.query.all()
|
||||
assert freqtrade.handle_trade(trades[0]) is True
|
||||
|
||||
@ -1930,12 +1957,15 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(side_effect=[
|
||||
limit_buy_order_open,
|
||||
{'id': 1234553382},
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
patch_get_signal(freqtrade, value=(True, False))
|
||||
patch_get_signal(freqtrade, value=(True, False, None))
|
||||
freqtrade.strategy.min_roi_reached = MagicMock(return_value=True)
|
||||
|
||||
freqtrade.enter_positions()
|
||||
@ -1948,21 +1978,24 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open,
|
||||
# we might just want to check if we are in a sell condition without
|
||||
# executing
|
||||
# if ROI is reached we must sell
|
||||
patch_get_signal(freqtrade, value=(False, True))
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
assert freqtrade.handle_trade(trade)
|
||||
assert log_has("ETH/BTC - Required profit reached. sell_type=SellType.ROI",
|
||||
caplog)
|
||||
|
||||
|
||||
def test_handle_trade_use_sell_signal(
|
||||
default_conf, ticker, limit_buy_order_open, fee, mocker, caplog) -> None:
|
||||
def test_handle_trade_use_sell_signal(default_conf, ticker, limit_buy_order_open,
|
||||
limit_sell_order_open, fee, mocker, caplog) -> None:
|
||||
# use_sell_signal is True buy default
|
||||
caplog.set_level(logging.DEBUG)
|
||||
patch_RPCManager(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(side_effect=[
|
||||
limit_buy_order_open,
|
||||
limit_sell_order_open,
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
@ -1974,10 +2007,10 @@ def test_handle_trade_use_sell_signal(
|
||||
trade = Trade.query.first()
|
||||
trade.is_open = True
|
||||
|
||||
patch_get_signal(freqtrade, value=(False, False))
|
||||
patch_get_signal(freqtrade, value=(False, False, None))
|
||||
assert not freqtrade.handle_trade(trade)
|
||||
|
||||
patch_get_signal(freqtrade, value=(False, True))
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
assert freqtrade.handle_trade(trade)
|
||||
assert log_has("ETH/BTC - Sell signal received. sell_type=SellType.SELL_SIGNAL",
|
||||
caplog)
|
||||
@ -1990,7 +2023,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_buy_order_open
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(return_value=limit_buy_order_open),
|
||||
get_fee=fee,
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
@ -2747,13 +2780,16 @@ def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, c
|
||||
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
|
||||
side_effect=InvalidOrderException())
|
||||
mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=300))
|
||||
sellmock = MagicMock(return_value={'id': '12345555'})
|
||||
create_order_mock = MagicMock(side_effect=[
|
||||
{'id': '12345554'},
|
||||
{'id': '12345555'},
|
||||
])
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
get_fee=fee,
|
||||
sell=sellmock
|
||||
create_order=create_order_mock,
|
||||
)
|
||||
|
||||
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
|
||||
@ -2768,7 +2804,7 @@ def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, c
|
||||
|
||||
freqtrade.execute_sell(trade=trade, limit=1234,
|
||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||
assert sellmock.call_count == 1
|
||||
assert create_order_mock.call_count == 2
|
||||
assert log_has('Could not cancel stoploss order abcd', caplog)
|
||||
|
||||
|
||||
@ -2964,7 +3000,10 @@ def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee,
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
get_fee=fee,
|
||||
sell=MagicMock(side_effect=InsufficientFundsError())
|
||||
create_order=MagicMock(side_effect=[
|
||||
{'id': 1234553382},
|
||||
InsufficientFundsError(),
|
||||
]),
|
||||
)
|
||||
patch_get_signal(freqtrade)
|
||||
|
||||
@ -2997,7 +3036,10 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, limit_buy
|
||||
'ask': 0.00001173,
|
||||
'last': 0.00001172
|
||||
}),
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(side_effect=[
|
||||
limit_buy_order_open,
|
||||
{'id': 1234553382},
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
default_conf.update({
|
||||
@ -3014,7 +3056,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, limit_buy
|
||||
trade = Trade.query.first()
|
||||
trade.update(limit_buy_order)
|
||||
freqtrade.wallets.update()
|
||||
patch_get_signal(freqtrade, value=(False, True))
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
|
||||
freqtrade.strategy.sell_profit_offset = 0.0
|
||||
@ -3034,7 +3076,10 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, limit_bu
|
||||
'ask': 0.00002173,
|
||||
'last': 0.00002172
|
||||
}),
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(side_effect=[
|
||||
limit_buy_order_open,
|
||||
{'id': 1234553382},
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
default_conf.update({
|
||||
@ -3049,7 +3094,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, limit_bu
|
||||
trade = Trade.query.first()
|
||||
trade.update(limit_buy_order)
|
||||
freqtrade.wallets.update()
|
||||
patch_get_signal(freqtrade, value=(False, True))
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert trade.sell_reason == SellType.SELL_SIGNAL.value
|
||||
|
||||
@ -3065,7 +3110,10 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, limit_buy_o
|
||||
'ask': 0.00000173,
|
||||
'last': 0.00000172
|
||||
}),
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(side_effect=[
|
||||
limit_buy_order_open,
|
||||
{'id': 1234553382},
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
default_conf.update({
|
||||
@ -3080,7 +3128,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, limit_buy_o
|
||||
|
||||
trade = Trade.query.first()
|
||||
trade.update(limit_buy_order)
|
||||
patch_get_signal(freqtrade, value=(False, True))
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
|
||||
|
||||
@ -3095,7 +3143,10 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, limit_buy_
|
||||
'ask': 0.0000173,
|
||||
'last': 0.0000172
|
||||
}),
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(side_effect=[
|
||||
limit_buy_order_open,
|
||||
{'id': 1234553382},
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
default_conf.update({
|
||||
@ -3112,7 +3163,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, limit_buy_
|
||||
trade = Trade.query.first()
|
||||
trade.update(limit_buy_order)
|
||||
freqtrade.wallets.update()
|
||||
patch_get_signal(freqtrade, value=(False, True))
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert trade.sell_reason == SellType.SELL_SIGNAL.value
|
||||
|
||||
@ -3128,7 +3179,10 @@ def test_sell_not_enough_balance(default_conf, limit_buy_order, limit_buy_order_
|
||||
'ask': 0.00002173,
|
||||
'last': 0.00002172
|
||||
}),
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(side_effect=[
|
||||
limit_buy_order_open,
|
||||
{'id': 1234553382},
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
@ -3141,7 +3195,7 @@ def test_sell_not_enough_balance(default_conf, limit_buy_order, limit_buy_order_
|
||||
trade = Trade.query.first()
|
||||
amnt = trade.amount
|
||||
trade.update(limit_buy_order)
|
||||
patch_get_signal(freqtrade, value=(False, True))
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=trade.amount * 0.985))
|
||||
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
@ -3246,7 +3300,10 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, limit_buy_order
|
||||
'ask': 0.0000173,
|
||||
'last': 0.0000172
|
||||
}),
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(side_effect=[
|
||||
limit_buy_order_open,
|
||||
{'id': 1234553382},
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
default_conf['ignore_roi_if_buy_signal'] = True
|
||||
@ -3260,11 +3317,11 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, limit_buy_order
|
||||
trade = Trade.query.first()
|
||||
trade.update(limit_buy_order)
|
||||
freqtrade.wallets.update()
|
||||
patch_get_signal(freqtrade, value=(True, True))
|
||||
patch_get_signal(freqtrade, value=(True, True, None))
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
|
||||
# Test if buy-signal is absent (should sell due to roi = true)
|
||||
patch_get_signal(freqtrade, value=(False, True))
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert trade.sell_reason == SellType.ROI.value
|
||||
|
||||
@ -3280,7 +3337,10 @@ def test_trailing_stop_loss(default_conf, limit_buy_order_open, limit_buy_order,
|
||||
'ask': 0.00001099,
|
||||
'last': 0.00001099
|
||||
}),
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(side_effect=[
|
||||
limit_buy_order_open,
|
||||
{'id': 1234553382},
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
default_conf['trailing_stop'] = True
|
||||
@ -3332,7 +3392,10 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, limit_buy_or
|
||||
'ask': buy_price - 0.000001,
|
||||
'last': buy_price - 0.000001
|
||||
}),
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(side_effect=[
|
||||
limit_buy_order_open,
|
||||
{'id': 1234553382},
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
default_conf['trailing_stop'] = True
|
||||
@ -3389,7 +3452,10 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, limit_buy_orde
|
||||
'ask': buy_price - 0.000001,
|
||||
'last': buy_price - 0.000001
|
||||
}),
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(side_effect=[
|
||||
limit_buy_order_open,
|
||||
{'id': 1234553382},
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
patch_whitelist(mocker, default_conf)
|
||||
@ -3449,7 +3515,7 @@ def test_tsl_only_offset_reached(default_conf, limit_buy_order, limit_buy_order_
|
||||
'ask': buy_price,
|
||||
'last': buy_price
|
||||
}),
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(return_value=limit_buy_order_open),
|
||||
get_fee=fee,
|
||||
)
|
||||
patch_whitelist(mocker, default_conf)
|
||||
@ -3509,7 +3575,11 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, limit_b
|
||||
'ask': 0.00000173,
|
||||
'last': 0.00000172
|
||||
}),
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(side_effect=[
|
||||
limit_buy_order_open,
|
||||
{'id': 1234553382},
|
||||
{'id': 1234553383}
|
||||
]),
|
||||
get_fee=fee,
|
||||
_is_dry_limit_order_filled=MagicMock(return_value=False),
|
||||
)
|
||||
@ -3525,11 +3595,11 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, limit_b
|
||||
trade = Trade.query.first()
|
||||
trade.update(limit_buy_order)
|
||||
# Sell due to min_roi_reached
|
||||
patch_get_signal(freqtrade, value=(True, True))
|
||||
patch_get_signal(freqtrade, value=(True, True, None))
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
|
||||
# Test if buy-signal is absent
|
||||
patch_get_signal(freqtrade, value=(False, True))
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert trade.sell_reason == SellType.SELL_SIGNAL.value
|
||||
|
||||
@ -3917,7 +3987,7 @@ def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order_open,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(return_value=limit_buy_order_open),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
@ -3954,7 +4024,7 @@ def test_order_book_depth_of_market_high_delta(default_conf, ticker, limit_buy_o
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
create_order=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_fee=fee,
|
||||
)
|
||||
# Save state of current whitelist
|
||||
@ -4051,8 +4121,10 @@ def test_order_book_ask_strategy(default_conf, limit_buy_order_open, limit_buy_o
|
||||
'ask': 0.00001173,
|
||||
'last': 0.00001172
|
||||
}),
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
sell=MagicMock(return_value=limit_sell_order_open),
|
||||
create_order=MagicMock(side_effect=[
|
||||
limit_buy_order_open,
|
||||
limit_sell_order_open,
|
||||
]),
|
||||
get_fee=fee,
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf)
|
||||
@ -4068,7 +4140,7 @@ def test_order_book_ask_strategy(default_conf, limit_buy_order_open, limit_buy_o
|
||||
freqtrade.wallets.update()
|
||||
assert trade.is_open is True
|
||||
|
||||
patch_get_signal(freqtrade, value=(False, True))
|
||||
patch_get_signal(freqtrade, value=(False, True, None))
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert trade.close_rate_requested == order_book_l2.return_value['asks'][0][0]
|
||||
|
||||
@ -4117,7 +4189,7 @@ def test_sync_wallet_dry_run(mocker, default_conf, ticker, fee, limit_buy_order_
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(return_value=limit_buy_order_open),
|
||||
get_fee=fee,
|
||||
)
|
||||
|
||||
|
@ -69,7 +69,7 @@ def test_init_dryrun_db(default_conf, tmpdir):
|
||||
def test_enter_exit_side(fee):
|
||||
trade = Trade(
|
||||
id=2,
|
||||
pair='ETH/BTC',
|
||||
pair='ADA/USDT',
|
||||
stake_amount=0.001,
|
||||
open_rate=0.01,
|
||||
amount=5,
|
||||
@ -94,10 +94,10 @@ def test_enter_exit_side(fee):
|
||||
def test__set_stop_loss_isolated_liq(fee):
|
||||
trade = Trade(
|
||||
id=2,
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
open_rate=0.01,
|
||||
amount=5,
|
||||
pair='ADA/USDT',
|
||||
stake_amount=60.0,
|
||||
open_rate=2.0,
|
||||
amount=30.0,
|
||||
is_open=True,
|
||||
open_date=arrow.utcnow().datetime,
|
||||
fee_open=fee.return_value,
|
||||
@ -227,7 +227,7 @@ def test_interest(market_buy_order_usdt, fee):
|
||||
"""
|
||||
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
pair='ADA/USDT',
|
||||
stake_amount=20.0,
|
||||
amount=30.0,
|
||||
open_rate=2.0,
|
||||
@ -404,7 +404,7 @@ def test_borrowed(limit_buy_order_usdt, limit_sell_order_usdt, fee, caplog):
|
||||
|
||||
trade = Trade(
|
||||
id=2,
|
||||
pair='ETH/BTC',
|
||||
pair='ADA/USDT',
|
||||
stake_amount=60.0,
|
||||
open_rate=2.0,
|
||||
amount=30.0,
|
||||
@ -498,7 +498,7 @@ def test_update_limit_order(limit_buy_order_usdt, limit_sell_order_usdt, fee, ca
|
||||
|
||||
trade = Trade(
|
||||
id=2,
|
||||
pair='ETH/BTC',
|
||||
pair='ADA/USDT',
|
||||
stake_amount=60.0,
|
||||
open_rate=2.0,
|
||||
amount=30.0,
|
||||
@ -519,7 +519,7 @@ def test_update_limit_order(limit_buy_order_usdt, limit_sell_order_usdt, fee, ca
|
||||
assert trade.close_profit is None
|
||||
assert trade.close_date is None
|
||||
assert log_has_re(r"LIMIT_BUY has been fulfilled for Trade\(id=2, "
|
||||
r'pair=ETH/BTC, amount=30.00000000, '
|
||||
r'pair=ADA/USDT, amount=30.00000000, '
|
||||
r"is_short=False, leverage=1.0, open_rate=2.00000000, open_since=.*\).",
|
||||
caplog)
|
||||
|
||||
@ -531,14 +531,14 @@ def test_update_limit_order(limit_buy_order_usdt, limit_sell_order_usdt, fee, ca
|
||||
assert trade.close_profit == round(0.0945137157107232, 8)
|
||||
assert trade.close_date is not None
|
||||
assert log_has_re(r"LIMIT_SELL has been fulfilled for Trade\(id=2, "
|
||||
r"pair=ETH/BTC, amount=30.00000000, "
|
||||
r"pair=ADA/USDT, amount=30.00000000, "
|
||||
r"is_short=False, leverage=1.0, open_rate=2.00000000, open_since=.*\).",
|
||||
caplog)
|
||||
caplog.clear()
|
||||
|
||||
trade = Trade(
|
||||
id=226531,
|
||||
pair='ETH/BTC',
|
||||
pair='ADA/USDT',
|
||||
stake_amount=20.0,
|
||||
open_rate=2.0,
|
||||
amount=30.0,
|
||||
@ -561,7 +561,7 @@ def test_update_limit_order(limit_buy_order_usdt, limit_sell_order_usdt, fee, ca
|
||||
assert trade.close_date is None
|
||||
|
||||
assert log_has_re(r"LIMIT_SELL has been fulfilled for Trade\(id=226531, "
|
||||
r"pair=ETH/BTC, amount=30.00000000, "
|
||||
r"pair=ADA/USDT, amount=30.00000000, "
|
||||
r"is_short=True, leverage=3.0, open_rate=2.20000000, open_since=.*\).",
|
||||
caplog)
|
||||
caplog.clear()
|
||||
@ -573,7 +573,7 @@ def test_update_limit_order(limit_buy_order_usdt, limit_sell_order_usdt, fee, ca
|
||||
assert trade.close_profit == round(0.2589996297562085, 8)
|
||||
assert trade.close_date is not None
|
||||
assert log_has_re(r"LIMIT_BUY has been fulfilled for Trade\(id=226531, "
|
||||
r"pair=ETH/BTC, amount=30.00000000, "
|
||||
r"pair=ADA/USDT, amount=30.00000000, "
|
||||
r"is_short=True, leverage=3.0, open_rate=2.20000000, open_since=.*\).",
|
||||
caplog)
|
||||
caplog.clear()
|
||||
@ -583,7 +583,7 @@ def test_update_limit_order(limit_buy_order_usdt, limit_sell_order_usdt, fee, ca
|
||||
def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee, caplog):
|
||||
trade = Trade(
|
||||
id=1,
|
||||
pair='ETH/BTC',
|
||||
pair='ADA/USDT',
|
||||
stake_amount=60.0,
|
||||
open_rate=2.0,
|
||||
amount=30.0,
|
||||
@ -601,7 +601,7 @@ def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee,
|
||||
assert trade.close_profit is None
|
||||
assert trade.close_date is None
|
||||
assert log_has_re(r"MARKET_BUY has been fulfilled for Trade\(id=1, "
|
||||
r"pair=ETH/BTC, amount=30.00000000, is_short=False, leverage=1.0, "
|
||||
r"pair=ADA/USDT, amount=30.00000000, is_short=False, leverage=1.0, "
|
||||
r"open_rate=2.00000000, open_since=.*\).",
|
||||
caplog)
|
||||
|
||||
@ -614,7 +614,7 @@ def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee,
|
||||
assert trade.close_profit == round(0.0945137157107232, 8)
|
||||
assert trade.close_date is not None
|
||||
assert log_has_re(r"MARKET_SELL has been fulfilled for Trade\(id=1, "
|
||||
r"pair=ETH/BTC, amount=30.00000000, is_short=False, leverage=1.0, "
|
||||
r"pair=ADA/USDT, amount=30.00000000, is_short=False, leverage=1.0, "
|
||||
r"open_rate=2.00000000, open_since=.*\).",
|
||||
caplog)
|
||||
|
||||
@ -622,7 +622,7 @@ def test_update_market_order(market_buy_order_usdt, market_sell_order_usdt, fee,
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_open_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt, fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
pair='ADA/USDT',
|
||||
stake_amount=60.0,
|
||||
open_rate=2.0,
|
||||
amount=30.0,
|
||||
@ -685,7 +685,7 @@ def test_calc_open_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_trade_close(limit_buy_order_usdt, limit_sell_order_usdt, fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
pair='ADA/USDT',
|
||||
stake_amount=60.0,
|
||||
open_rate=2.0,
|
||||
amount=30.0,
|
||||
@ -710,14 +710,14 @@ def test_trade_close(limit_buy_order_usdt, limit_sell_order_usdt, fee):
|
||||
# Close should NOT update close_date if the trade has been closed already
|
||||
assert trade.is_open is False
|
||||
trade.close_date = new_date
|
||||
trade.close(0.02)
|
||||
trade.close(2.2)
|
||||
assert trade.close_date == new_date
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_close_trade_price_exception(limit_buy_order_usdt, fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
pair='ADA/USDT',
|
||||
stake_amount=60.0,
|
||||
open_rate=2.0,
|
||||
amount=30.0,
|
||||
@ -734,7 +734,7 @@ def test_calc_close_trade_price_exception(limit_buy_order_usdt, fee):
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_update_open_order(limit_buy_order_usdt):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
pair='ADA/USDT',
|
||||
stake_amount=60.0,
|
||||
open_rate=2.0,
|
||||
amount=30.0,
|
||||
@ -758,7 +758,7 @@ def test_update_open_order(limit_buy_order_usdt):
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_update_invalid_order(limit_buy_order_usdt):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
pair='ADA/USDT',
|
||||
stake_amount=60.0,
|
||||
amount=30.0,
|
||||
open_rate=2.0,
|
||||
@ -788,7 +788,7 @@ def test_calc_open_trade_value(limit_buy_order_usdt, fee):
|
||||
# 1x, 3x: 30 * 2 + 30 * 2 * 0.003 = 60.18 quote
|
||||
# -1x,-3x: 30 * 2 - 30 * 2 * 0.003 = 59.82 quote
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
pair='ADA/USDT',
|
||||
stake_amount=60.0,
|
||||
amount=30.0,
|
||||
open_rate=2.0,
|
||||
@ -823,7 +823,7 @@ def test_calc_open_trade_value(limit_buy_order_usdt, fee):
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_close_trade_price(limit_buy_order_usdt, limit_sell_order_usdt, fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
pair='ADA/USDT',
|
||||
stake_amount=60.0,
|
||||
amount=30.0,
|
||||
open_rate=2.0,
|
||||
@ -1007,7 +1007,7 @@ def test_calc_profit(limit_buy_order_usdt, limit_sell_order_usdt, fee):
|
||||
2.2 quote: (65.802 / 60.15) - 1 = 0.09396508728179565
|
||||
"""
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
pair='ADA/USDT',
|
||||
stake_amount=60.0,
|
||||
amount=30.0,
|
||||
open_rate=2.0,
|
||||
@ -1109,7 +1109,7 @@ def test_calc_profit(limit_buy_order_usdt, limit_sell_order_usdt, fee):
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_calc_profit_ratio(limit_buy_order_usdt, limit_sell_order_usdt, fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
pair='ADA/USDT',
|
||||
stake_amount=60.0,
|
||||
amount=30.0,
|
||||
open_rate=2.0,
|
||||
@ -1214,7 +1214,7 @@ def test_clean_dry_run_db(default_conf, fee):
|
||||
|
||||
# Simulate dry_run entries
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
pair='ADA/USDT',
|
||||
stake_amount=0.001,
|
||||
amount=123.0,
|
||||
fee_open=fee.return_value,
|
||||
@ -1259,96 +1259,6 @@ def test_clean_dry_run_db(default_conf, fee):
|
||||
assert len(Trade.query.filter(Trade.open_order_id.isnot(None)).all()) == 1
|
||||
|
||||
|
||||
def test_migrate_old(mocker, default_conf, fee):
|
||||
"""
|
||||
Test Database migration(starting with old pairformat)
|
||||
"""
|
||||
amount = 103.223
|
||||
create_table_old = """CREATE TABLE IF NOT EXISTS "trades" (
|
||||
id INTEGER NOT NULL,
|
||||
exchange VARCHAR NOT NULL,
|
||||
pair VARCHAR NOT NULL,
|
||||
is_open BOOLEAN NOT NULL,
|
||||
fee FLOAT NOT NULL,
|
||||
open_rate FLOAT,
|
||||
close_rate FLOAT,
|
||||
close_profit FLOAT,
|
||||
stake_amount FLOAT NOT NULL,
|
||||
amount FLOAT,
|
||||
open_date DATETIME NOT NULL,
|
||||
close_date DATETIME,
|
||||
open_order_id VARCHAR,
|
||||
PRIMARY KEY (id),
|
||||
CHECK (is_open IN (0, 1))
|
||||
);"""
|
||||
insert_table_old = """INSERT INTO trades (exchange, pair, is_open, open_order_id, fee,
|
||||
open_rate, stake_amount, amount, open_date)
|
||||
VALUES ('binance', 'BTC_ETC', 1, '123123', {fee},
|
||||
0.00258580, {stake}, {amount},
|
||||
'2017-11-28 12:44:24.000000')
|
||||
""".format(fee=fee.return_value,
|
||||
stake=default_conf.get("stake_amount"),
|
||||
amount=amount
|
||||
)
|
||||
insert_table_old2 = """INSERT INTO trades (exchange, pair, is_open, fee,
|
||||
open_rate, close_rate, stake_amount, amount, open_date)
|
||||
VALUES ('binance', 'BTC_ETC', 0, {fee},
|
||||
0.00258580, 0.00268580, {stake}, {amount},
|
||||
'2017-11-28 12:44:24.000000')
|
||||
""".format(fee=fee.return_value,
|
||||
stake=default_conf.get("stake_amount"),
|
||||
amount=amount
|
||||
)
|
||||
engine = create_engine('sqlite://')
|
||||
mocker.patch('freqtrade.persistence.models.create_engine', lambda *args, **kwargs: engine)
|
||||
|
||||
# Create table using the old format
|
||||
with engine.begin() as connection:
|
||||
connection.execute(text(create_table_old))
|
||||
connection.execute(text(insert_table_old))
|
||||
connection.execute(text(insert_table_old2))
|
||||
# Run init to test migration
|
||||
init_db(default_conf['db_url'], default_conf['dry_run'])
|
||||
|
||||
assert len(Trade.query.filter(Trade.id == 1).all()) == 1
|
||||
trade = Trade.query.filter(Trade.id == 1).first()
|
||||
assert trade.fee_open == fee.return_value
|
||||
assert trade.fee_close == fee.return_value
|
||||
assert trade.open_rate_requested is None
|
||||
assert trade.close_rate_requested is None
|
||||
assert trade.is_open == 1
|
||||
assert trade.amount == amount
|
||||
assert trade.amount_requested == amount
|
||||
assert trade.stake_amount == default_conf.get("stake_amount")
|
||||
assert trade.pair == "ETC/BTC"
|
||||
assert trade.exchange == "binance"
|
||||
assert trade.max_rate == 0.0
|
||||
assert trade.stop_loss == 0.0
|
||||
assert trade.initial_stop_loss == 0.0
|
||||
assert trade.open_trade_value == trade._calc_open_trade_value()
|
||||
assert trade.close_profit_abs is None
|
||||
assert trade.fee_open_cost is None
|
||||
assert trade.fee_open_currency is None
|
||||
assert trade.fee_close_cost is None
|
||||
assert trade.fee_close_currency is None
|
||||
assert trade.timeframe is None
|
||||
|
||||
trade = Trade.query.filter(Trade.id == 2).first()
|
||||
assert trade.close_rate is not None
|
||||
assert trade.is_open == 0
|
||||
assert trade.open_rate_requested is None
|
||||
assert trade.close_rate_requested is None
|
||||
assert trade.close_rate is not None
|
||||
assert pytest.approx(trade.close_profit_abs) == trade.calc_profit()
|
||||
assert trade.sell_order_status is None
|
||||
|
||||
# Should've created one order
|
||||
assert len(Order.query.all()) == 1
|
||||
order = Order.query.first()
|
||||
assert order.order_id == '123123'
|
||||
assert order.ft_order_side == 'buy'
|
||||
|
||||
|
||||
def test_migrate_new(mocker, default_conf, fee, caplog):
|
||||
"""
|
||||
Test Database migration (starting with new pairformat)
|
||||
@ -1571,9 +1481,9 @@ def test_migrate_mid_state(mocker, default_conf, fee, caplog):
|
||||
|
||||
def test_adjust_stop_loss(fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
amount=5,
|
||||
pair='ADA/USDT',
|
||||
stake_amount=30.0,
|
||||
amount=30,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance',
|
||||
@ -1623,7 +1533,7 @@ def test_adjust_stop_loss(fee):
|
||||
|
||||
def test_adjust_stop_loss_short(fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
pair='ADA/USDT',
|
||||
stake_amount=0.001,
|
||||
amount=5,
|
||||
fee_open=fee.return_value,
|
||||
@ -1677,9 +1587,9 @@ def test_adjust_stop_loss_short(fee):
|
||||
|
||||
def test_adjust_min_max_rates(fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
amount=5,
|
||||
pair='ADA/USDT',
|
||||
stake_amount=30.0,
|
||||
amount=30.0,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance',
|
||||
@ -1735,7 +1645,7 @@ def test_to_json(default_conf, fee):
|
||||
|
||||
# Simulate dry_run entries
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
pair='ADA/USDT',
|
||||
stake_amount=0.001,
|
||||
amount=123.0,
|
||||
amount_requested=123.0,
|
||||
@ -1744,13 +1654,14 @@ def test_to_json(default_conf, fee):
|
||||
open_date=arrow.utcnow().shift(hours=-2).datetime,
|
||||
open_rate=0.123,
|
||||
exchange='binance',
|
||||
buy_tag=None,
|
||||
open_order_id='dry_run_buy_12345'
|
||||
)
|
||||
result = trade.to_json()
|
||||
assert isinstance(result, dict)
|
||||
|
||||
assert result == {'trade_id': None,
|
||||
'pair': 'ETH/BTC',
|
||||
'pair': 'ADA/USDT',
|
||||
'is_open': None,
|
||||
'open_date': trade.open_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
'open_timestamp': int(trade.open_date.timestamp() * 1000),
|
||||
@ -1793,6 +1704,7 @@ def test_to_json(default_conf, fee):
|
||||
'min_rate': None,
|
||||
'max_rate': None,
|
||||
'strategy': None,
|
||||
'buy_tag': None,
|
||||
'timeframe': None,
|
||||
'exchange': 'binance',
|
||||
'leverage': None,
|
||||
@ -1813,6 +1725,7 @@ def test_to_json(default_conf, fee):
|
||||
close_date=arrow.utcnow().shift(hours=-1).datetime,
|
||||
open_rate=0.123,
|
||||
close_rate=0.125,
|
||||
buy_tag='buys_signal_001',
|
||||
exchange='binance',
|
||||
)
|
||||
result = trade.to_json()
|
||||
@ -1862,6 +1775,7 @@ def test_to_json(default_conf, fee):
|
||||
'sell_reason': None,
|
||||
'sell_order_status': None,
|
||||
'strategy': None,
|
||||
'buy_tag': 'buys_signal_001',
|
||||
'timeframe': None,
|
||||
'exchange': 'binance',
|
||||
'leverage': None,
|
||||
@ -1874,11 +1788,11 @@ def test_to_json(default_conf, fee):
|
||||
def test_stoploss_reinitialization(default_conf, fee):
|
||||
init_db(default_conf['db_url'])
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
pair='ADA/USDT',
|
||||
stake_amount=30.0,
|
||||
fee_open=fee.return_value,
|
||||
open_date=arrow.utcnow().shift(hours=-2).datetime,
|
||||
amount=10,
|
||||
amount=30.0,
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance',
|
||||
open_rate=1,
|
||||
@ -1934,7 +1848,7 @@ def test_stoploss_reinitialization(default_conf, fee):
|
||||
def test_stoploss_reinitialization_short(default_conf, fee):
|
||||
init_db(default_conf['db_url'])
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
pair='ADA/USDT',
|
||||
stake_amount=0.001,
|
||||
fee_open=fee.return_value,
|
||||
open_date=arrow.utcnow().shift(hours=-2).datetime,
|
||||
@ -1993,11 +1907,11 @@ def test_stoploss_reinitialization_short(default_conf, fee):
|
||||
|
||||
def test_update_fee(fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
pair='ADA/USDT',
|
||||
stake_amount=30.0,
|
||||
fee_open=fee.return_value,
|
||||
open_date=arrow.utcnow().shift(hours=-2).datetime,
|
||||
amount=10,
|
||||
amount=30.0,
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance',
|
||||
open_rate=1,
|
||||
@ -2032,11 +1946,11 @@ def test_update_fee(fee):
|
||||
|
||||
def test_fee_updated(fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
pair='ADA/USDT',
|
||||
stake_amount=30.0,
|
||||
fee_open=fee.return_value,
|
||||
open_date=arrow.utcnow().shift(hours=-2).datetime,
|
||||
amount=10,
|
||||
amount=30.0,
|
||||
fee_close=fee.return_value,
|
||||
exchange='binance',
|
||||
open_rate=1,
|
||||
@ -2164,16 +2078,16 @@ def test_get_best_pair_lev(fee):
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_update_order_from_ccxt(caplog):
|
||||
# Most basic order return (only has orderid)
|
||||
o = Order.parse_from_ccxt_object({'id': '1234'}, 'ETH/BTC', 'buy')
|
||||
o = Order.parse_from_ccxt_object({'id': '1234'}, 'ADA/USDT', 'buy')
|
||||
assert isinstance(o, Order)
|
||||
assert o.ft_pair == 'ETH/BTC'
|
||||
assert o.ft_pair == 'ADA/USDT'
|
||||
assert o.ft_order_side == 'buy'
|
||||
assert o.order_id == '1234'
|
||||
assert o.ft_is_open
|
||||
ccxt_order = {
|
||||
'id': '1234',
|
||||
'side': 'buy',
|
||||
'symbol': 'ETH/BTC',
|
||||
'symbol': 'ADA/USDT',
|
||||
'type': 'limit',
|
||||
'price': 1234.5,
|
||||
'amount': 20.0,
|
||||
@ -2182,9 +2096,9 @@ def test_update_order_from_ccxt(caplog):
|
||||
'status': 'open',
|
||||
'timestamp': 1599394315123
|
||||
}
|
||||
o = Order.parse_from_ccxt_object(ccxt_order, 'ETH/BTC', 'buy')
|
||||
o = Order.parse_from_ccxt_object(ccxt_order, 'ADA/USDT', 'buy')
|
||||
assert isinstance(o, Order)
|
||||
assert o.ft_pair == 'ETH/BTC'
|
||||
assert o.ft_pair == 'ADA/USDT'
|
||||
assert o.ft_order_side == 'buy'
|
||||
assert o.order_id == '1234'
|
||||
assert o.order_type == 'limit'
|
||||
|
@ -138,7 +138,7 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_r
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
fetch_ticker=ticker,
|
||||
buy=MagicMock(return_value=limit_buy_order_open),
|
||||
create_order=MagicMock(return_value=limit_buy_order_open),
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user