Merge branch 'freqtrade:develop' into remotepairlist
This commit is contained in:
commit
9e20d13e50
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -20,7 +20,7 @@ Please do not use bug reports to request new features.
|
|||||||
* Operating system: ____
|
* Operating system: ____
|
||||||
* Python Version: _____ (`python -V`)
|
* Python Version: _____ (`python -V`)
|
||||||
* CCXT version: _____ (`pip freeze | grep ccxt`)
|
* CCXT version: _____ (`pip freeze | grep ccxt`)
|
||||||
* Freqtrade Version: ____ (`freqtrade -V` or `docker-compose run --rm freqtrade -V` for Freqtrade running in docker)
|
* Freqtrade Version: ____ (`freqtrade -V` or `docker compose run --rm freqtrade -V` for Freqtrade running in docker)
|
||||||
|
|
||||||
Note: All issues other than enhancement requests will be closed without further comment if the above template is deleted or not filled out.
|
Note: All issues other than enhancement requests will be closed without further comment if the above template is deleted or not filled out.
|
||||||
|
|
||||||
|
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -18,7 +18,7 @@ Have you search for this feature before requesting it? It's highly likely that a
|
|||||||
* Operating system: ____
|
* Operating system: ____
|
||||||
* Python Version: _____ (`python -V`)
|
* Python Version: _____ (`python -V`)
|
||||||
* CCXT version: _____ (`pip freeze | grep ccxt`)
|
* CCXT version: _____ (`pip freeze | grep ccxt`)
|
||||||
* Freqtrade Version: ____ (`freqtrade -V` or `docker-compose run --rm freqtrade -V` for Freqtrade running in docker)
|
* Freqtrade Version: ____ (`freqtrade -V` or `docker compose run --rm freqtrade -V` for Freqtrade running in docker)
|
||||||
|
|
||||||
|
|
||||||
## Describe the enhancement
|
## Describe the enhancement
|
||||||
|
2
.github/ISSUE_TEMPLATE/question.md
vendored
2
.github/ISSUE_TEMPLATE/question.md
vendored
@ -18,7 +18,7 @@ Please do not use the question template to report bugs or to request new feature
|
|||||||
* Operating system: ____
|
* Operating system: ____
|
||||||
* Python Version: _____ (`python -V`)
|
* Python Version: _____ (`python -V`)
|
||||||
* CCXT version: _____ (`pip freeze | grep ccxt`)
|
* CCXT version: _____ (`pip freeze | grep ccxt`)
|
||||||
* Freqtrade Version: ____ (`freqtrade -V` or `docker-compose run --rm freqtrade -V` for Freqtrade running in docker)
|
* Freqtrade Version: ____ (`freqtrade -V` or `docker compose run --rm freqtrade -V` for Freqtrade running in docker)
|
||||||
|
|
||||||
## Your question
|
## Your question
|
||||||
|
|
||||||
|
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -88,7 +88,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cp config_examples/config_bittrex.example.json config.json
|
cp config_examples/config_bittrex.example.json config.json
|
||||||
freqtrade create-userdir --userdir user_data
|
freqtrade create-userdir --userdir user_data
|
||||||
freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt-loss SharpeHyperOptLossDaily --print-all
|
freqtrade hyperopt --datadir tests/testdata -e 6 --strategy SampleStrategy --hyperopt-loss SharpeHyperOptLossDaily --print-all
|
||||||
|
|
||||||
- name: Flake8
|
- name: Flake8
|
||||||
run: |
|
run: |
|
||||||
@ -410,7 +410,7 @@ jobs:
|
|||||||
python setup.py sdist bdist_wheel
|
python setup.py sdist bdist_wheel
|
||||||
|
|
||||||
- name: Publish to PyPI (Test)
|
- name: Publish to PyPI (Test)
|
||||||
uses: pypa/gh-action-pypi-publish@v1.6.1
|
uses: pypa/gh-action-pypi-publish@v1.6.4
|
||||||
if: (github.event_name == 'release')
|
if: (github.event_name == 'release')
|
||||||
with:
|
with:
|
||||||
user: __token__
|
user: __token__
|
||||||
@ -418,7 +418,7 @@ jobs:
|
|||||||
repository_url: https://test.pypi.org/legacy/
|
repository_url: https://test.pypi.org/legacy/
|
||||||
|
|
||||||
- name: Publish to PyPI
|
- name: Publish to PyPI
|
||||||
uses: pypa/gh-action-pypi-publish@v1.6.1
|
uses: pypa/gh-action-pypi-publish@v1.6.4
|
||||||
if: (github.event_name == 'release')
|
if: (github.event_name == 'release')
|
||||||
with:
|
with:
|
||||||
user: __token__
|
user: __token__
|
||||||
|
@ -79,9 +79,7 @@
|
|||||||
"test_size": 0.33,
|
"test_size": 0.33,
|
||||||
"random_state": 1
|
"random_state": 1
|
||||||
},
|
},
|
||||||
"model_training_parameters": {
|
"model_training_parameters": {}
|
||||||
"n_estimators": 1000
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"bot_name": "",
|
"bot_name": "",
|
||||||
"force_entry_enable": true,
|
"force_entry_enable": true,
|
||||||
|
@ -5,7 +5,7 @@ You can analyze the results of backtests and trading history easily using Jupyte
|
|||||||
## Quick start with docker
|
## Quick start with docker
|
||||||
|
|
||||||
Freqtrade provides a docker-compose file which starts up a jupyter lab server.
|
Freqtrade provides a docker-compose file which starts up a jupyter lab server.
|
||||||
You can run this server using the following command: `docker-compose -f docker/docker-compose-jupyter.yml up`
|
You can run this server using the following command: `docker compose -f docker/docker-compose-jupyter.yml up`
|
||||||
|
|
||||||
This will create a dockercontainer running jupyter lab, which will be accessible using `https://127.0.0.1:8888/lab`.
|
This will create a dockercontainer running jupyter lab, which will be accessible using `https://127.0.0.1:8888/lab`.
|
||||||
Please use the link that's printed in the console after startup for simplified login.
|
Please use the link that's printed in the console after startup for simplified login.
|
||||||
|
@ -4,20 +4,22 @@ This page explains how to run the bot with Docker. It is not meant to work out o
|
|||||||
|
|
||||||
## Install Docker
|
## Install Docker
|
||||||
|
|
||||||
Start by downloading and installing Docker CE for your platform:
|
Start by downloading and installing Docker / Docker Desktop for your platform:
|
||||||
|
|
||||||
* [Mac](https://docs.docker.com/docker-for-mac/install/)
|
* [Mac](https://docs.docker.com/docker-for-mac/install/)
|
||||||
* [Windows](https://docs.docker.com/docker-for-windows/install/)
|
* [Windows](https://docs.docker.com/docker-for-windows/install/)
|
||||||
* [Linux](https://docs.docker.com/install/)
|
* [Linux](https://docs.docker.com/install/)
|
||||||
|
|
||||||
To simplify running freqtrade, [`docker-compose`](https://docs.docker.com/compose/install/) should be installed and available to follow the below [docker quick start guide](#docker-quick-start).
|
!!! Info "Docker compose install"
|
||||||
|
Freqtrade documentation assumes the use of Docker desktop (or the docker compose plugin).
|
||||||
|
While the docker-compose standalone installation still works, it will require changing all `docker compose` commands from `docker compose` to `docker-compose` to work (e.g. `docker compose up -d` will become `docker-compose up -d`).
|
||||||
|
|
||||||
## Freqtrade with docker-compose
|
## Freqtrade with docker
|
||||||
|
|
||||||
Freqtrade provides an official Docker image on [Dockerhub](https://hub.docker.com/r/freqtradeorg/freqtrade/), as well as a [docker-compose file](https://github.com/freqtrade/freqtrade/blob/stable/docker-compose.yml) ready for usage.
|
Freqtrade provides an official Docker image on [Dockerhub](https://hub.docker.com/r/freqtradeorg/freqtrade/), as well as a [docker compose file](https://github.com/freqtrade/freqtrade/blob/stable/docker-compose.yml) ready for usage.
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
- The following section assumes that `docker` and `docker-compose` are installed and available to the logged in user.
|
- The following section assumes that `docker` is installed and available to the logged in user.
|
||||||
- All below commands use relative directories and will have to be executed from the directory containing the `docker-compose.yml` file.
|
- All below commands use relative directories and will have to be executed from the directory containing the `docker-compose.yml` file.
|
||||||
|
|
||||||
### Docker quick start
|
### Docker quick start
|
||||||
@ -31,13 +33,13 @@ cd ft_userdata/
|
|||||||
curl https://raw.githubusercontent.com/freqtrade/freqtrade/stable/docker-compose.yml -o docker-compose.yml
|
curl https://raw.githubusercontent.com/freqtrade/freqtrade/stable/docker-compose.yml -o docker-compose.yml
|
||||||
|
|
||||||
# Pull the freqtrade image
|
# Pull the freqtrade image
|
||||||
docker-compose pull
|
docker compose pull
|
||||||
|
|
||||||
# Create user directory structure
|
# Create user directory structure
|
||||||
docker-compose run --rm freqtrade create-userdir --userdir user_data
|
docker compose run --rm freqtrade create-userdir --userdir user_data
|
||||||
|
|
||||||
# Create configuration - Requires answering interactive questions
|
# Create configuration - Requires answering interactive questions
|
||||||
docker-compose run --rm freqtrade new-config --config user_data/config.json
|
docker compose run --rm freqtrade new-config --config user_data/config.json
|
||||||
```
|
```
|
||||||
|
|
||||||
The above snippet creates a new directory called `ft_userdata`, downloads the latest compose file and pulls the freqtrade image.
|
The above snippet creates a new directory called `ft_userdata`, downloads the latest compose file and pulls the freqtrade image.
|
||||||
@ -64,7 +66,7 @@ The `SampleStrategy` is run by default.
|
|||||||
Once this is done, you're ready to launch the bot in trading mode (Dry-run or Live-trading, depending on your answer to the corresponding question you made above).
|
Once this is done, you're ready to launch the bot in trading mode (Dry-run or Live-trading, depending on your answer to the corresponding question you made above).
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
docker-compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! Warning "Default configuration"
|
!!! Warning "Default configuration"
|
||||||
@ -84,27 +86,27 @@ You can now access the UI by typing localhost:8080 in your browser.
|
|||||||
|
|
||||||
#### Monitoring the bot
|
#### Monitoring the bot
|
||||||
|
|
||||||
You can check for running instances with `docker-compose ps`.
|
You can check for running instances with `docker compose ps`.
|
||||||
This should list the service `freqtrade` as `running`. If that's not the case, best check the logs (see next point).
|
This should list the service `freqtrade` as `running`. If that's not the case, best check the logs (see next point).
|
||||||
|
|
||||||
#### Docker-compose logs
|
#### Docker compose logs
|
||||||
|
|
||||||
Logs will be written to: `user_data/logs/freqtrade.log`.
|
Logs will be written to: `user_data/logs/freqtrade.log`.
|
||||||
You can also check the latest log with the command `docker-compose logs -f`.
|
You can also check the latest log with the command `docker compose logs -f`.
|
||||||
|
|
||||||
#### Database
|
#### Database
|
||||||
|
|
||||||
The database will be located at: `user_data/tradesv3.sqlite`
|
The database will be located at: `user_data/tradesv3.sqlite`
|
||||||
|
|
||||||
#### Updating freqtrade with docker-compose
|
#### Updating freqtrade with docker
|
||||||
|
|
||||||
Updating freqtrade when using `docker-compose` is as simple as running the following 2 commands:
|
Updating freqtrade when using `docker` is as simple as running the following 2 commands:
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
# Download the latest image
|
# Download the latest image
|
||||||
docker-compose pull
|
docker compose pull
|
||||||
# Restart the image
|
# Restart the image
|
||||||
docker-compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
This will first pull the latest image, and will then restart the container with the just pulled version.
|
This will first pull the latest image, and will then restart the container with the just pulled version.
|
||||||
@ -116,43 +118,43 @@ This will first pull the latest image, and will then restart the container with
|
|||||||
|
|
||||||
Advanced users may edit the docker-compose file further to include all possible options or arguments.
|
Advanced users may edit the docker-compose file further to include all possible options or arguments.
|
||||||
|
|
||||||
All freqtrade arguments will be available by running `docker-compose run --rm freqtrade <command> <optional arguments>`.
|
All freqtrade arguments will be available by running `docker compose run --rm freqtrade <command> <optional arguments>`.
|
||||||
|
|
||||||
!!! Warning "`docker-compose` for trade commands"
|
!!! 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.
|
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.
|
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.
|
||||||
If you intend to use freqUI, please also ensure to adjust the [configuration accordingly](rest-api.md#configuration-with-docker), otherwise the UI will not be available.
|
If you intend to use freqUI, please also ensure to adjust the [configuration accordingly](rest-api.md#configuration-with-docker), otherwise the UI will not be available.
|
||||||
|
|
||||||
!!! Note "`docker-compose run --rm`"
|
!!! 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).
|
Including `--rm` will remove the container after completion, and is highly recommended for all modes except trading mode (running with `freqtrade trade` command).
|
||||||
|
|
||||||
??? Note "Using docker without docker-compose"
|
??? Note "Using docker without docker"
|
||||||
"`docker-compose run --rm`" will require a compose file to be provided.
|
"`docker compose run --rm`" will require a compose file to be provided.
|
||||||
Some freqtrade commands that don't require authentication such as `list-pairs` can be run with "`docker run --rm`" instead.
|
Some freqtrade commands that don't require authentication such as `list-pairs` can be run with "`docker run --rm`" instead.
|
||||||
For example `docker run --rm freqtradeorg/freqtrade:stable list-pairs --exchange binance --quote BTC --print-json`.
|
For example `docker run --rm freqtradeorg/freqtrade:stable list-pairs --exchange binance --quote BTC --print-json`.
|
||||||
This can be useful for fetching exchange information to add to your `config.json` without affecting your running containers.
|
This can be useful for fetching exchange information to add to your `config.json` without affecting your running containers.
|
||||||
|
|
||||||
#### Example: Download data with docker-compose
|
#### Example: Download data with docker
|
||||||
|
|
||||||
Download backtesting data for 5 days for the pair ETH/BTC and 1h timeframe from Binance. The data will be stored in the directory `user_data/data/` on the host.
|
Download backtesting data for 5 days for the pair ETH/BTC and 1h timeframe from Binance. The data will be stored in the directory `user_data/data/` on the host.
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
docker-compose run --rm freqtrade download-data --pairs ETH/BTC --exchange binance --days 5 -t 1h
|
docker compose run --rm freqtrade download-data --pairs ETH/BTC --exchange binance --days 5 -t 1h
|
||||||
```
|
```
|
||||||
|
|
||||||
Head over to the [Data Downloading Documentation](data-download.md) for more details on downloading data.
|
Head over to the [Data Downloading Documentation](data-download.md) for more details on downloading data.
|
||||||
|
|
||||||
#### Example: Backtest with docker-compose
|
#### Example: Backtest with docker
|
||||||
|
|
||||||
Run backtesting in docker-containers for SampleStrategy and specified timerange of historical data, on 5m timeframe:
|
Run backtesting in docker-containers for SampleStrategy and specified timerange of historical data, on 5m timeframe:
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
docker-compose run --rm freqtrade backtesting --config user_data/config.json --strategy SampleStrategy --timerange 20190801-20191001 -i 5m
|
docker compose run --rm freqtrade backtesting --config user_data/config.json --strategy SampleStrategy --timerange 20190801-20191001 -i 5m
|
||||||
```
|
```
|
||||||
|
|
||||||
Head over to the [Backtesting Documentation](backtesting.md) to learn more.
|
Head over to the [Backtesting Documentation](backtesting.md) to learn more.
|
||||||
|
|
||||||
### Additional dependencies with docker-compose
|
### Additional dependencies with docker
|
||||||
|
|
||||||
If your strategy requires dependencies not included in the default image - it will be necessary to build the image on your host.
|
If your strategy requires dependencies not included in the default image - it will be necessary to build the image on your host.
|
||||||
For this, please create a Dockerfile containing installation steps for the additional dependencies (have a look at [docker/Dockerfile.custom](https://github.com/freqtrade/freqtrade/blob/develop/docker/Dockerfile.custom) for an example).
|
For this, please create a Dockerfile containing installation steps for the additional dependencies (have a look at [docker/Dockerfile.custom](https://github.com/freqtrade/freqtrade/blob/develop/docker/Dockerfile.custom) for an example).
|
||||||
@ -166,15 +168,15 @@ You'll then also need to modify the `docker-compose.yml` file and uncomment the
|
|||||||
dockerfile: "./Dockerfile.<yourextension>"
|
dockerfile: "./Dockerfile.<yourextension>"
|
||||||
```
|
```
|
||||||
|
|
||||||
You can then run `docker-compose build --pull` to build the docker image, and run it using the commands described above.
|
You can then run `docker compose build --pull` to build the docker image, and run it using the commands described above.
|
||||||
|
|
||||||
### Plotting with docker-compose
|
### Plotting with docker
|
||||||
|
|
||||||
Commands `freqtrade plot-profit` and `freqtrade plot-dataframe` ([Documentation](plotting.md)) are available by changing the image to `*_plot` in your docker-compose.yml file.
|
Commands `freqtrade plot-profit` and `freqtrade plot-dataframe` ([Documentation](plotting.md)) are available by changing the image to `*_plot` in your docker-compose.yml file.
|
||||||
You can then use these commands as follows:
|
You can then use these commands as follows:
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
docker-compose run --rm freqtrade plot-dataframe --strategy AwesomeStrategy -p BTC/ETH --timerange=20180801-20180805
|
docker compose run --rm freqtrade plot-dataframe --strategy AwesomeStrategy -p BTC/ETH --timerange=20180801-20180805
|
||||||
```
|
```
|
||||||
|
|
||||||
The output will be stored in the `user_data/plot` directory, and can be opened with any modern browser.
|
The output will be stored in the `user_data/plot` directory, and can be opened with any modern browser.
|
||||||
@ -185,7 +187,7 @@ Freqtrade provides a docker-compose file which starts up a jupyter lab server.
|
|||||||
You can run this server using the following command:
|
You can run this server using the following command:
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
docker-compose -f docker/docker-compose-jupyter.yml up
|
docker compose -f docker/docker-compose-jupyter.yml up
|
||||||
```
|
```
|
||||||
|
|
||||||
This will create a docker-container running jupyter lab, which will be accessible using `https://127.0.0.1:8888/lab`.
|
This will create a docker-container running jupyter lab, which will be accessible using `https://127.0.0.1:8888/lab`.
|
||||||
@ -194,7 +196,7 @@ Please use the link that's printed in the console after startup for simplified l
|
|||||||
Since part of this image is built on your machine, it is recommended to rebuild the image from time to time to keep freqtrade (and dependencies) up-to-date.
|
Since part of this image is built on your machine, it is recommended to rebuild the image from time to time to keep freqtrade (and dependencies) up-to-date.
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
docker-compose -f docker/docker-compose-jupyter.yml build --no-cache
|
docker compose -f docker/docker-compose-jupyter.yml build --no-cache
|
||||||
```
|
```
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
@ -26,10 +26,7 @@ FreqAI is configured through the typical [Freqtrade config file](configuration.m
|
|||||||
},
|
},
|
||||||
"data_split_parameters" : {
|
"data_split_parameters" : {
|
||||||
"test_size": 0.25
|
"test_size": 0.25
|
||||||
},
|
}
|
||||||
"model_training_parameters" : {
|
|
||||||
"n_estimators": 100
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -247,6 +247,32 @@ where `unique-id` is the `identifier` set in the `freqai` configuration file. Th
|
|||||||
|
|
||||||
![tensorboard](assets/tensorboard.jpg)
|
![tensorboard](assets/tensorboard.jpg)
|
||||||
|
|
||||||
|
|
||||||
|
### Custom logging
|
||||||
|
|
||||||
|
FreqAI also provides a built in episodic summary logger called `self.tensorboard_log` for adding custom information to the Tensorboard log. By default, this function is already called once per step inside the environment to record the agent actions. All values accumulated for all steps in a single episode are reported at the conclusion of each episode, followed by a full reset of all metrics to 0 in preparation for the subsequent episode.
|
||||||
|
|
||||||
|
|
||||||
|
`self.tensorboard_log` can also be used anywhere inside the environment, for example, it can be added to the `calculate_reward` function to collect more detailed information about how often various parts of the reward were called:
|
||||||
|
|
||||||
|
```py
|
||||||
|
class MyRLEnv(Base5ActionRLEnv):
|
||||||
|
"""
|
||||||
|
User made custom environment. This class inherits from BaseEnvironment and gym.env.
|
||||||
|
Users can override any functions from those parent classes. Here is an example
|
||||||
|
of a user customized `calculate_reward()` function.
|
||||||
|
"""
|
||||||
|
def calculate_reward(self, action: int) -> float:
|
||||||
|
if not self._is_valid(action):
|
||||||
|
self.tensorboard_log("is_valid")
|
||||||
|
return -2
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
The `self.tensorboard_log()` function is designed for tracking incremented objects only i.e. events, actions inside the training environment. If the event of interest is a float, the float can be passed as the second argument e.g. `self.tensorboard_log("float_metric1", 0.23)` would add 0.23 to `float_metric`. In this case you can also disable incrementing using `inc=False` parameter.
|
||||||
|
|
||||||
|
|
||||||
### Choosing a base environment
|
### Choosing a base environment
|
||||||
|
|
||||||
FreqAI provides two base environments, `Base4ActionEnvironment` and `Base5ActionEnvironment`. As the names imply, the environments are customized for agents that can select from 4 or 5 actions. In the `Base4ActionEnvironment`, the agent can enter long, enter short, hold neutral, or exit position. Meanwhile, in the `Base5ActionEnvironment`, the agent has the same actions as Base4, but instead of a single exit action, it separates exit long and exit short. The main changes stemming from the environment selection include:
|
FreqAI provides two base environments, `Base4ActionEnvironment` and `Base5ActionEnvironment`. As the names imply, the environments are customized for agents that can select from 4 or 5 actions. In the `Base4ActionEnvironment`, the agent can enter long, enter short, hold neutral, or exit position. Meanwhile, in the `Base5ActionEnvironment`, the agent has the same actions as Base4, but instead of a single exit action, it separates exit long and exit short. The main changes stemming from the environment selection include:
|
||||||
|
@ -13,12 +13,12 @@ Feel free to use a visual Database editor like SqliteBrowser if you feel more co
|
|||||||
sudo apt-get install sqlite3
|
sudo apt-get install sqlite3
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using sqlite3 via docker-compose
|
### Using sqlite3 via docker
|
||||||
|
|
||||||
The freqtrade docker image does contain sqlite3, so you can edit the database without having to install anything on the host system.
|
The freqtrade docker image does contain sqlite3, so you can edit the database without having to install anything on the host system.
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
docker-compose exec freqtrade /bin/bash
|
docker compose exec freqtrade /bin/bash
|
||||||
sqlite3 <database-file>.sqlite
|
sqlite3 <database-file>.sqlite
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -773,7 +773,7 @@ class DigDeeperStrategy(IStrategy):
|
|||||||
* Sell 100@10\$ -> Avg price: 8.5\$, realized profit 150\$, 17.65%
|
* Sell 100@10\$ -> Avg price: 8.5\$, realized profit 150\$, 17.65%
|
||||||
* Buy 150@11\$ -> Avg price: 10\$, realized profit 150\$, 17.65%
|
* Buy 150@11\$ -> Avg price: 10\$, realized profit 150\$, 17.65%
|
||||||
* Sell 100@12\$ -> Avg price: 10\$, total realized profit 350\$, 20%
|
* Sell 100@12\$ -> Avg price: 10\$, total realized profit 350\$, 20%
|
||||||
* Sell 150@14\$ -> Avg price: 10\$, total realized profit 950\$, 40%
|
* Sell 150@14\$ -> Avg price: 10\$, total realized profit 950\$, 40% <- *This will be the last "Exit" message*
|
||||||
|
|
||||||
The total profit for this trade was 950$ on a 3350$ investment (`100@8$ + 100@9$ + 150@11$`). As such - the final relative profit is 28.35% (`950 / 3350`).
|
The total profit for this trade was 950$ on a 3350$ investment (`100@8$ + 100@9$ + 150@11$`). As such - the final relative profit is 28.35% (`950 / 3350`).
|
||||||
|
|
||||||
|
@ -363,9 +363,9 @@ class AwesomeStrategy(IStrategy):
|
|||||||
timeframe = "1d"
|
timeframe = "1d"
|
||||||
timeframe_mins = timeframe_to_minutes(timeframe)
|
timeframe_mins = timeframe_to_minutes(timeframe)
|
||||||
minimal_roi = {
|
minimal_roi = {
|
||||||
"0": 0.05, # 5% for the first 3 candles
|
"0": 0.05, # 5% for the first 3 candles
|
||||||
str(timeframe_mins * 3)): 0.02, # 2% after 3 candles
|
str(timeframe_mins * 3): 0.02, # 2% after 3 candles
|
||||||
str(timeframe_mins * 6)): 0.01, # 1% After 6 candles
|
str(timeframe_mins * 6): 0.01, # 1% After 6 candles
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -2,12 +2,37 @@
|
|||||||
|
|
||||||
Debugging a strategy can be time-consuming. Freqtrade offers helper functions to visualize raw data.
|
Debugging a strategy can be time-consuming. Freqtrade offers helper functions to visualize raw data.
|
||||||
The following assumes you work with SampleStrategy, data for 5m timeframe from Binance and have downloaded them into the data directory in the default location.
|
The following assumes you work with SampleStrategy, data for 5m timeframe from Binance and have downloaded them into the data directory in the default location.
|
||||||
|
Please follow the [documentation](https://www.freqtrade.io/en/stable/data-download/) for more details.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
|
### Change Working directory to repository root
|
||||||
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Change directory
|
||||||
|
# Modify this cell to insure that the output shows the correct path.
|
||||||
|
# Define all paths relative to the project root shown in the cell output
|
||||||
|
project_root = "somedir/freqtrade"
|
||||||
|
i=0
|
||||||
|
try:
|
||||||
|
os.chdirdir(project_root)
|
||||||
|
assert Path('LICENSE').is_file()
|
||||||
|
except:
|
||||||
|
while i<4 and (not Path('LICENSE').is_file()):
|
||||||
|
os.chdir(Path(Path.cwd(), '../'))
|
||||||
|
i+=1
|
||||||
|
project_root = Path.cwd()
|
||||||
|
print(Path.cwd())
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configure Freqtrade environment
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
from freqtrade.configuration import Configuration
|
from freqtrade.configuration import Configuration
|
||||||
|
|
||||||
# Customize these according to your needs.
|
# Customize these according to your needs.
|
||||||
@ -15,14 +40,14 @@ from freqtrade.configuration import Configuration
|
|||||||
# Initialize empty configuration object
|
# Initialize empty configuration object
|
||||||
config = Configuration.from_files([])
|
config = Configuration.from_files([])
|
||||||
# Optionally (recommended), use existing configuration file
|
# Optionally (recommended), use existing configuration file
|
||||||
# config = Configuration.from_files(["config.json"])
|
# config = Configuration.from_files(["user_data/config.json"])
|
||||||
|
|
||||||
# Define some constants
|
# Define some constants
|
||||||
config["timeframe"] = "5m"
|
config["timeframe"] = "5m"
|
||||||
# Name of the strategy class
|
# Name of the strategy class
|
||||||
config["strategy"] = "SampleStrategy"
|
config["strategy"] = "SampleStrategy"
|
||||||
# Location of the data
|
# Location of the data
|
||||||
data_location = config['datadir']
|
data_location = config["datadir"]
|
||||||
# Pair to analyze - Only use one pair here
|
# Pair to analyze - Only use one pair here
|
||||||
pair = "BTC/USDT"
|
pair = "BTC/USDT"
|
||||||
```
|
```
|
||||||
@ -36,12 +61,12 @@ from freqtrade.enums import CandleType
|
|||||||
candles = load_pair_history(datadir=data_location,
|
candles = load_pair_history(datadir=data_location,
|
||||||
timeframe=config["timeframe"],
|
timeframe=config["timeframe"],
|
||||||
pair=pair,
|
pair=pair,
|
||||||
data_format = "hdf5",
|
data_format = "json", # Make sure to update this to your data
|
||||||
candle_type=CandleType.SPOT,
|
candle_type=CandleType.SPOT,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Confirm success
|
# Confirm success
|
||||||
print("Loaded " + str(len(candles)) + f" rows of data for {pair} from {data_location}")
|
print(f"Loaded {len(candles)} rows of data for {pair} from {data_location}")
|
||||||
candles.head()
|
candles.head()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -6,14 +6,14 @@ To update your freqtrade installation, please use one of the below methods, corr
|
|||||||
Breaking changes / changed behavior will be documented in the changelog that is posted alongside every release.
|
Breaking changes / changed behavior will be documented in the changelog that is posted alongside every release.
|
||||||
For the develop branch, please follow PR's to avoid being surprised by changes.
|
For the develop branch, please follow PR's to avoid being surprised by changes.
|
||||||
|
|
||||||
## docker-compose
|
## docker
|
||||||
|
|
||||||
!!! Note "Legacy installations using the `master` image"
|
!!! Note "Legacy installations using the `master` image"
|
||||||
We're switching from master to stable for the release Images - please adjust your docker-file and replace `freqtradeorg/freqtrade:master` with `freqtradeorg/freqtrade:stable`
|
We're switching from master to stable for the release Images - please adjust your docker-file and replace `freqtradeorg/freqtrade:master` with `freqtradeorg/freqtrade:stable`
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
docker-compose pull
|
docker compose pull
|
||||||
docker-compose up -d
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
## Installation via setup script
|
## Installation via setup script
|
||||||
|
@ -652,7 +652,7 @@ Common arguments:
|
|||||||
|
|
||||||
You can also use webserver mode via docker.
|
You can also use webserver mode via docker.
|
||||||
Starting a one-off container requires the configuration of the port explicitly, as ports are not exposed by default.
|
Starting a one-off container requires the configuration of the port explicitly, as ports are not exposed by default.
|
||||||
You can use `docker-compose run --rm -p 127.0.0.1:8080:8080 freqtrade webserver` to start a one-off container that'll be removed once you stop it. This assumes that port 8080 is still available and no other bot is running on that port.
|
You can use `docker compose run --rm -p 127.0.0.1:8080:8080 freqtrade webserver` to start a one-off container that'll be removed once you stop it. This assumes that port 8080 is still available and no other bot is running on that port.
|
||||||
|
|
||||||
Alternatively, you can reconfigure the docker-compose file to have the command updated:
|
Alternatively, you can reconfigure the docker-compose file to have the command updated:
|
||||||
|
|
||||||
@ -662,7 +662,7 @@ Alternatively, you can reconfigure the docker-compose file to have the command u
|
|||||||
--config /freqtrade/user_data/config.json
|
--config /freqtrade/user_data/config.json
|
||||||
```
|
```
|
||||||
|
|
||||||
You can now use `docker-compose up` to start the webserver.
|
You can now use `docker compose up` to start the webserver.
|
||||||
This assumes that the configuration has a webserver enabled and configured for docker (listening port = `0.0.0.0`).
|
This assumes that the configuration has a webserver enabled and configured for docker (listening port = `0.0.0.0`).
|
||||||
|
|
||||||
!!! Tip
|
!!! Tip
|
||||||
|
@ -355,6 +355,13 @@ def _validate_freqai_include_timeframes(conf: Dict[str, Any]) -> None:
|
|||||||
f"Main timeframe of {main_tf} must be smaller or equal to FreqAI "
|
f"Main timeframe of {main_tf} must be smaller or equal to FreqAI "
|
||||||
f"`include_timeframes`.Offending include-timeframes: {', '.join(offending_lines)}")
|
f"`include_timeframes`.Offending include-timeframes: {', '.join(offending_lines)}")
|
||||||
|
|
||||||
|
# Ensure that the base timeframe is included in the include_timeframes list
|
||||||
|
if main_tf not in freqai_include_timeframes:
|
||||||
|
feature_parameters = conf.get('freqai', {}).get('feature_parameters', {})
|
||||||
|
include_timeframes = [main_tf] + freqai_include_timeframes
|
||||||
|
conf.get('freqai', {}).get('feature_parameters', {}) \
|
||||||
|
.update({**feature_parameters, 'include_timeframes': include_timeframes})
|
||||||
|
|
||||||
|
|
||||||
def _validate_freqai_backtest(conf: Dict[str, Any]) -> None:
|
def _validate_freqai_backtest(conf: Dict[str, Any]) -> None:
|
||||||
if conf.get('runmode', RunMode.OTHER) == RunMode.BACKTEST:
|
if conf.get('runmode', RunMode.OTHER) == RunMode.BACKTEST:
|
||||||
|
@ -608,9 +608,8 @@ CONF_SCHEMA = {
|
|||||||
"backtest_period_days",
|
"backtest_period_days",
|
||||||
"identifier",
|
"identifier",
|
||||||
"feature_parameters",
|
"feature_parameters",
|
||||||
"data_split_parameters",
|
"data_split_parameters"
|
||||||
"model_training_parameters"
|
]
|
||||||
]
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -46,9 +46,9 @@ class Base4ActionRLEnv(BaseEnvironment):
|
|||||||
self._done = True
|
self._done = True
|
||||||
|
|
||||||
self._update_unrealized_total_profit()
|
self._update_unrealized_total_profit()
|
||||||
|
|
||||||
step_reward = self.calculate_reward(action)
|
step_reward = self.calculate_reward(action)
|
||||||
self.total_reward += step_reward
|
self.total_reward += step_reward
|
||||||
|
self.tensorboard_log(self.actions._member_names_[action])
|
||||||
|
|
||||||
trade_type = None
|
trade_type = None
|
||||||
if self.is_tradesignal(action):
|
if self.is_tradesignal(action):
|
||||||
|
@ -49,6 +49,7 @@ class Base5ActionRLEnv(BaseEnvironment):
|
|||||||
self._update_unrealized_total_profit()
|
self._update_unrealized_total_profit()
|
||||||
step_reward = self.calculate_reward(action)
|
step_reward = self.calculate_reward(action)
|
||||||
self.total_reward += step_reward
|
self.total_reward += step_reward
|
||||||
|
self.tensorboard_log(self.actions._member_names_[action])
|
||||||
|
|
||||||
trade_type = None
|
trade_type = None
|
||||||
if self.is_tradesignal(action):
|
if self.is_tradesignal(action):
|
||||||
|
@ -2,7 +2,7 @@ import logging
|
|||||||
import random
|
import random
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional, Type
|
from typing import Optional, Type, Union
|
||||||
|
|
||||||
import gym
|
import gym
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@ -12,6 +12,7 @@ from gym.utils import seeding
|
|||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade.data.dataprovider import DataProvider
|
from freqtrade.data.dataprovider import DataProvider
|
||||||
|
from freqtrade.enums import RunMode
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -77,7 +78,13 @@ class BaseEnvironment(gym.Env):
|
|||||||
|
|
||||||
# set here to default 5Ac, but all children envs can override this
|
# set here to default 5Ac, but all children envs can override this
|
||||||
self.actions: Type[Enum] = BaseActions
|
self.actions: Type[Enum] = BaseActions
|
||||||
self.custom_info: dict = {}
|
self.tensorboard_metrics: dict = {}
|
||||||
|
self.live: bool = False
|
||||||
|
if dp:
|
||||||
|
self.live = dp.runmode in (RunMode.DRY_RUN, RunMode.LIVE)
|
||||||
|
if not self.live and self.add_state_info:
|
||||||
|
self.add_state_info = False
|
||||||
|
logger.warning("add_state_info is not available in backtesting. Deactivating.")
|
||||||
|
|
||||||
def reset_env(self, df: DataFrame, prices: DataFrame, window_size: int,
|
def reset_env(self, df: DataFrame, prices: DataFrame, window_size: int,
|
||||||
reward_kwargs: dict, starting_point=True):
|
reward_kwargs: dict, starting_point=True):
|
||||||
@ -132,20 +139,38 @@ class BaseEnvironment(gym.Env):
|
|||||||
self.np_random, seed = seeding.np_random(seed)
|
self.np_random, seed = seeding.np_random(seed)
|
||||||
return [seed]
|
return [seed]
|
||||||
|
|
||||||
|
def tensorboard_log(self, metric: str, value: Union[int, float] = 1, inc: bool = True):
|
||||||
|
"""
|
||||||
|
Function builds the tensorboard_metrics dictionary
|
||||||
|
to be parsed by the TensorboardCallback. This
|
||||||
|
function is designed for tracking incremented objects,
|
||||||
|
events, actions inside the training environment.
|
||||||
|
For example, a user can call this to track the
|
||||||
|
frequency of occurence of an `is_valid` call in
|
||||||
|
their `calculate_reward()`:
|
||||||
|
|
||||||
|
def calculate_reward(self, action: int) -> float:
|
||||||
|
if not self._is_valid(action):
|
||||||
|
self.tensorboard_log("is_valid")
|
||||||
|
return -2
|
||||||
|
|
||||||
|
:param metric: metric to be tracked and incremented
|
||||||
|
:param value: value to increment `metric` by
|
||||||
|
:param inc: sets whether the `value` is incremented or not
|
||||||
|
"""
|
||||||
|
if not inc or metric not in self.tensorboard_metrics:
|
||||||
|
self.tensorboard_metrics[metric] = value
|
||||||
|
else:
|
||||||
|
self.tensorboard_metrics[metric] += value
|
||||||
|
|
||||||
|
def reset_tensorboard_log(self):
|
||||||
|
self.tensorboard_metrics = {}
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"""
|
"""
|
||||||
Reset is called at the beginning of every episode
|
Reset is called at the beginning of every episode
|
||||||
"""
|
"""
|
||||||
# custom_info is used for episodic reports and tensorboard logging
|
self.reset_tensorboard_log()
|
||||||
self.custom_info["Invalid"] = 0
|
|
||||||
self.custom_info["Hold"] = 0
|
|
||||||
self.custom_info["Unknown"] = 0
|
|
||||||
self.custom_info["pnl_factor"] = 0
|
|
||||||
self.custom_info["duration_factor"] = 0
|
|
||||||
self.custom_info["reward_exit"] = 0
|
|
||||||
self.custom_info["reward_hold"] = 0
|
|
||||||
for action in self.actions:
|
|
||||||
self.custom_info[f"{action.name}"] = 0
|
|
||||||
|
|
||||||
self._done = False
|
self._done = False
|
||||||
|
|
||||||
@ -188,7 +213,7 @@ class BaseEnvironment(gym.Env):
|
|||||||
"""
|
"""
|
||||||
features_window = self.signal_features[(
|
features_window = self.signal_features[(
|
||||||
self._current_tick - self.window_size):self._current_tick]
|
self._current_tick - self.window_size):self._current_tick]
|
||||||
if self.add_state_info:
|
if self.add_state_info and self.live:
|
||||||
features_and_state = DataFrame(np.zeros((len(features_window), 3)),
|
features_and_state = DataFrame(np.zeros((len(features_window), 3)),
|
||||||
columns=['current_profit_pct',
|
columns=['current_profit_pct',
|
||||||
'position',
|
'position',
|
||||||
|
@ -42,19 +42,18 @@ class TensorboardCallback(BaseCallback):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _on_step(self) -> bool:
|
def _on_step(self) -> bool:
|
||||||
custom_info = self.training_env.get_attr("custom_info")[0]
|
|
||||||
self.logger.record("_state/position", self.locals["infos"][0]["position"])
|
local_info = self.locals["infos"][0]
|
||||||
self.logger.record("_state/trade_duration", self.locals["infos"][0]["trade_duration"])
|
tensorboard_metrics = self.training_env.get_attr("tensorboard_metrics")[0]
|
||||||
self.logger.record("_state/current_profit_pct", self.locals["infos"]
|
|
||||||
[0]["current_profit_pct"])
|
for info in local_info:
|
||||||
self.logger.record("_reward/total_profit", self.locals["infos"][0]["total_profit"])
|
if info not in ["episode", "terminal_observation"]:
|
||||||
self.logger.record("_reward/total_reward", self.locals["infos"][0]["total_reward"])
|
self.logger.record(f"_info/{info}", local_info[info])
|
||||||
self.logger.record_mean("_reward/mean_trade_duration", self.locals["infos"]
|
|
||||||
[0]["trade_duration"])
|
for info in tensorboard_metrics:
|
||||||
self.logger.record("_actions/action", self.locals["infos"][0]["action"])
|
if info in [action.name for action in self.actions]:
|
||||||
self.logger.record("_actions/_Invalid", custom_info["Invalid"])
|
self.logger.record(f"_actions/{info}", tensorboard_metrics[info])
|
||||||
self.logger.record("_actions/_Unknown", custom_info["Unknown"])
|
else:
|
||||||
self.logger.record("_actions/Hold", custom_info["Hold"])
|
self.logger.record(f"_custom/{info}", tensorboard_metrics[info])
|
||||||
for action in self.actions:
|
|
||||||
self.logger.record(f"_actions/{action.name}", custom_info[action.name])
|
|
||||||
return True
|
return True
|
||||||
|
@ -95,9 +95,14 @@ class BaseClassifierModel(IFreqaiModel):
|
|||||||
self.data_cleaning_predict(dk)
|
self.data_cleaning_predict(dk)
|
||||||
|
|
||||||
predictions = self.model.predict(dk.data_dictionary["prediction_features"])
|
predictions = self.model.predict(dk.data_dictionary["prediction_features"])
|
||||||
|
if self.CONV_WIDTH == 1:
|
||||||
|
predictions = np.reshape(predictions, (-1, len(dk.label_list)))
|
||||||
|
|
||||||
pred_df = DataFrame(predictions, columns=dk.label_list)
|
pred_df = DataFrame(predictions, columns=dk.label_list)
|
||||||
|
|
||||||
predictions_prob = self.model.predict_proba(dk.data_dictionary["prediction_features"])
|
predictions_prob = self.model.predict_proba(dk.data_dictionary["prediction_features"])
|
||||||
|
if self.CONV_WIDTH == 1:
|
||||||
|
predictions_prob = np.reshape(predictions_prob, (-1, len(self.model.classes_)))
|
||||||
pred_df_prob = DataFrame(predictions_prob, columns=self.model.classes_)
|
pred_df_prob = DataFrame(predictions_prob, columns=self.model.classes_)
|
||||||
|
|
||||||
pred_df = pd.concat([pred_df, pred_df_prob], axis=1)
|
pred_df = pd.concat([pred_df, pred_df_prob], axis=1)
|
||||||
|
@ -95,6 +95,9 @@ class BaseRegressionModel(IFreqaiModel):
|
|||||||
self.data_cleaning_predict(dk)
|
self.data_cleaning_predict(dk)
|
||||||
|
|
||||||
predictions = self.model.predict(dk.data_dictionary["prediction_features"])
|
predictions = self.model.predict(dk.data_dictionary["prediction_features"])
|
||||||
|
if self.CONV_WIDTH == 1:
|
||||||
|
predictions = np.reshape(predictions, (-1, len(dk.label_list)))
|
||||||
|
|
||||||
pred_df = DataFrame(predictions, columns=dk.label_list)
|
pred_df = DataFrame(predictions, columns=dk.label_list)
|
||||||
|
|
||||||
pred_df = dk.denormalize_labels_from_metadata(pred_df)
|
pred_df = dk.denormalize_labels_from_metadata(pred_df)
|
||||||
|
@ -61,7 +61,7 @@ class ReinforcementLearner(BaseReinforcementLearningModel):
|
|||||||
model = self.MODELCLASS(self.policy_type, self.train_env, policy_kwargs=policy_kwargs,
|
model = self.MODELCLASS(self.policy_type, self.train_env, policy_kwargs=policy_kwargs,
|
||||||
tensorboard_log=Path(
|
tensorboard_log=Path(
|
||||||
dk.full_path / "tensorboard" / dk.pair.split('/')[0]),
|
dk.full_path / "tensorboard" / dk.pair.split('/')[0]),
|
||||||
**self.freqai_info['model_training_parameters']
|
**self.freqai_info.get('model_training_parameters', {})
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.info('Continual training activated - starting training from previously '
|
logger.info('Continual training activated - starting training from previously '
|
||||||
@ -100,7 +100,7 @@ class ReinforcementLearner(BaseReinforcementLearningModel):
|
|||||||
"""
|
"""
|
||||||
# first, penalize if the action is not valid
|
# first, penalize if the action is not valid
|
||||||
if not self._is_valid(action):
|
if not self._is_valid(action):
|
||||||
self.custom_info["Invalid"] += 1
|
self.tensorboard_log("is_valid")
|
||||||
return -2
|
return -2
|
||||||
|
|
||||||
pnl = self.get_unrealized_profit()
|
pnl = self.get_unrealized_profit()
|
||||||
@ -109,15 +109,12 @@ class ReinforcementLearner(BaseReinforcementLearningModel):
|
|||||||
# reward agent for entering trades
|
# reward agent for entering trades
|
||||||
if (action == Actions.Long_enter.value
|
if (action == Actions.Long_enter.value
|
||||||
and self._position == Positions.Neutral):
|
and self._position == Positions.Neutral):
|
||||||
self.custom_info[f"{Actions.Long_enter.name}"] += 1
|
|
||||||
return 25
|
return 25
|
||||||
if (action == Actions.Short_enter.value
|
if (action == Actions.Short_enter.value
|
||||||
and self._position == Positions.Neutral):
|
and self._position == Positions.Neutral):
|
||||||
self.custom_info[f"{Actions.Short_enter.name}"] += 1
|
|
||||||
return 25
|
return 25
|
||||||
# discourage agent from not entering trades
|
# discourage agent from not entering trades
|
||||||
if action == Actions.Neutral.value and self._position == Positions.Neutral:
|
if action == Actions.Neutral.value and self._position == Positions.Neutral:
|
||||||
self.custom_info[f"{Actions.Neutral.name}"] += 1
|
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
max_trade_duration = self.rl_config.get('max_trade_duration_candles', 300)
|
max_trade_duration = self.rl_config.get('max_trade_duration_candles', 300)
|
||||||
@ -131,22 +128,18 @@ class ReinforcementLearner(BaseReinforcementLearningModel):
|
|||||||
# discourage sitting in position
|
# discourage sitting in position
|
||||||
if (self._position in (Positions.Short, Positions.Long) and
|
if (self._position in (Positions.Short, Positions.Long) and
|
||||||
action == Actions.Neutral.value):
|
action == Actions.Neutral.value):
|
||||||
self.custom_info["Hold"] += 1
|
|
||||||
return -1 * trade_duration / max_trade_duration
|
return -1 * trade_duration / max_trade_duration
|
||||||
|
|
||||||
# close long
|
# close long
|
||||||
if action == Actions.Long_exit.value and self._position == Positions.Long:
|
if action == Actions.Long_exit.value and self._position == Positions.Long:
|
||||||
if pnl > self.profit_aim * self.rr:
|
if pnl > self.profit_aim * self.rr:
|
||||||
factor *= self.rl_config['model_reward_parameters'].get('win_reward_factor', 2)
|
factor *= self.rl_config['model_reward_parameters'].get('win_reward_factor', 2)
|
||||||
self.custom_info[f"{Actions.Long_exit.name}"] += 1
|
|
||||||
return float(pnl * factor)
|
return float(pnl * factor)
|
||||||
|
|
||||||
# close short
|
# close short
|
||||||
if action == Actions.Short_exit.value and self._position == Positions.Short:
|
if action == Actions.Short_exit.value and self._position == Positions.Short:
|
||||||
if pnl > self.profit_aim * self.rr:
|
if pnl > self.profit_aim * self.rr:
|
||||||
factor *= self.rl_config['model_reward_parameters'].get('win_reward_factor', 2)
|
factor *= self.rl_config['model_reward_parameters'].get('win_reward_factor', 2)
|
||||||
self.custom_info[f"{Actions.Short_exit.name}"] += 1
|
|
||||||
return float(pnl * factor)
|
return float(pnl * factor)
|
||||||
|
|
||||||
self.custom_info["Unknown"] += 1
|
|
||||||
return 0.
|
return 0.
|
||||||
|
@ -155,6 +155,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
self.cancel_all_open_orders()
|
self.cancel_all_open_orders()
|
||||||
|
|
||||||
self.check_for_open_trades()
|
self.check_for_open_trades()
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f'Exception during cleanup: {e.__class__.__name__} {e}')
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self.strategy.ft_bot_cleanup()
|
self.strategy.ft_bot_cleanup()
|
||||||
@ -162,8 +164,13 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
self.rpc.cleanup()
|
self.rpc.cleanup()
|
||||||
if self.emc:
|
if self.emc:
|
||||||
self.emc.shutdown()
|
self.emc.shutdown()
|
||||||
Trade.commit()
|
|
||||||
self.exchange.close()
|
self.exchange.close()
|
||||||
|
try:
|
||||||
|
Trade.commit()
|
||||||
|
except Exception:
|
||||||
|
# Exeptions here will be happening if the db disappeared.
|
||||||
|
# At which point we can no longer commit anyway.
|
||||||
|
pass
|
||||||
|
|
||||||
def startup(self) -> None:
|
def startup(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -218,7 +218,7 @@ class VolumePairList(IPairList):
|
|||||||
else:
|
else:
|
||||||
filtered_tickers[i]['quoteVolume'] = 0
|
filtered_tickers[i]['quoteVolume'] = 0
|
||||||
else:
|
else:
|
||||||
# Tickers mode - filter based on incomming pairlist.
|
# Tickers mode - filter based on incoming pairlist.
|
||||||
filtered_tickers = [v for k, v in tickers.items() if k in pairlist]
|
filtered_tickers = [v for k, v in tickers.items() if k in pairlist]
|
||||||
|
|
||||||
if self._min_value > 0:
|
if self._min_value > 0:
|
||||||
|
@ -167,6 +167,7 @@ class RPC:
|
|||||||
results = []
|
results = []
|
||||||
for trade in trades:
|
for trade in trades:
|
||||||
order: Optional[Order] = None
|
order: Optional[Order] = None
|
||||||
|
current_profit_fiat: Optional[float] = None
|
||||||
if trade.open_order_id:
|
if trade.open_order_id:
|
||||||
order = trade.select_order_by_order_id(trade.open_order_id)
|
order = trade.select_order_by_order_id(trade.open_order_id)
|
||||||
# calculate profit and send message to user
|
# calculate profit and send message to user
|
||||||
@ -176,23 +177,26 @@ class RPC:
|
|||||||
trade.pair, side='exit', is_short=trade.is_short, refresh=False)
|
trade.pair, side='exit', is_short=trade.is_short, refresh=False)
|
||||||
except (ExchangeError, PricingError):
|
except (ExchangeError, PricingError):
|
||||||
current_rate = NAN
|
current_rate = NAN
|
||||||
|
if len(trade.select_filled_orders(trade.entry_side)) > 0:
|
||||||
|
current_profit = trade.calc_profit_ratio(
|
||||||
|
current_rate) if not isnan(current_rate) else NAN
|
||||||
|
current_profit_abs = trade.calc_profit(
|
||||||
|
current_rate) if not isnan(current_rate) else NAN
|
||||||
|
else:
|
||||||
|
current_profit = current_profit_abs = current_profit_fiat = 0.0
|
||||||
else:
|
else:
|
||||||
|
# Closed trade ...
|
||||||
current_rate = trade.close_rate
|
current_rate = trade.close_rate
|
||||||
if len(trade.select_filled_orders(trade.entry_side)) > 0:
|
current_profit = trade.close_profit
|
||||||
current_profit = trade.calc_profit_ratio(
|
current_profit_abs = trade.close_profit_abs
|
||||||
current_rate) if not isnan(current_rate) else NAN
|
|
||||||
current_profit_abs = trade.calc_profit(
|
# Calculate fiat profit
|
||||||
current_rate) if not isnan(current_rate) else NAN
|
if not isnan(current_profit_abs) and self._fiat_converter:
|
||||||
current_profit_fiat: Optional[float] = None
|
current_profit_fiat = self._fiat_converter.convert_amount(
|
||||||
# Calculate fiat profit
|
current_profit_abs,
|
||||||
if self._fiat_converter:
|
self._freqtrade.config['stake_currency'],
|
||||||
current_profit_fiat = self._fiat_converter.convert_amount(
|
self._freqtrade.config['fiat_display_currency']
|
||||||
current_profit_abs,
|
)
|
||||||
self._freqtrade.config['stake_currency'],
|
|
||||||
self._freqtrade.config['fiat_display_currency']
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
current_profit = current_profit_abs = current_profit_fiat = 0.0
|
|
||||||
|
|
||||||
# Calculate guaranteed profit (in case of trailing stop)
|
# Calculate guaranteed profit (in case of trailing stop)
|
||||||
stoploss_entry_dist = trade.calc_profit(trade.stop_loss)
|
stoploss_entry_dist = trade.calc_profit(trade.stop_loss)
|
||||||
|
@ -7,14 +7,17 @@
|
|||||||
"# Strategy analysis example\n",
|
"# Strategy analysis example\n",
|
||||||
"\n",
|
"\n",
|
||||||
"Debugging a strategy can be time-consuming. Freqtrade offers helper functions to visualize raw data.\n",
|
"Debugging a strategy can be time-consuming. Freqtrade offers helper functions to visualize raw data.\n",
|
||||||
"The following assumes you work with SampleStrategy, data for 5m timeframe from Binance and have downloaded them into the data directory in the default location."
|
"The following assumes you work with SampleStrategy, data for 5m timeframe from Binance and have downloaded them into the data directory in the default location.\n",
|
||||||
|
"Please follow the [documentation](https://www.freqtrade.io/en/stable/data-download/) for more details."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"cell_type": "markdown",
|
"cell_type": "markdown",
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"source": [
|
"source": [
|
||||||
"## Setup"
|
"## Setup\n",
|
||||||
|
"\n",
|
||||||
|
"### Change Working directory to repository root"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -23,7 +26,38 @@
|
|||||||
"metadata": {},
|
"metadata": {},
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
|
"import os\n",
|
||||||
"from pathlib import Path\n",
|
"from pathlib import Path\n",
|
||||||
|
"\n",
|
||||||
|
"# Change directory\n",
|
||||||
|
"# Modify this cell to insure that the output shows the correct path.\n",
|
||||||
|
"# Define all paths relative to the project root shown in the cell output\n",
|
||||||
|
"project_root = \"somedir/freqtrade\"\n",
|
||||||
|
"i=0\n",
|
||||||
|
"try:\n",
|
||||||
|
" os.chdirdir(project_root)\n",
|
||||||
|
" assert Path('LICENSE').is_file()\n",
|
||||||
|
"except:\n",
|
||||||
|
" while i<4 and (not Path('LICENSE').is_file()):\n",
|
||||||
|
" os.chdir(Path(Path.cwd(), '../'))\n",
|
||||||
|
" i+=1\n",
|
||||||
|
" project_root = Path.cwd()\n",
|
||||||
|
"print(Path.cwd())"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### Configure Freqtrade environment"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
"from freqtrade.configuration import Configuration\n",
|
"from freqtrade.configuration import Configuration\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Customize these according to your needs.\n",
|
"# Customize these according to your needs.\n",
|
||||||
@ -31,14 +65,14 @@
|
|||||||
"# Initialize empty configuration object\n",
|
"# Initialize empty configuration object\n",
|
||||||
"config = Configuration.from_files([])\n",
|
"config = Configuration.from_files([])\n",
|
||||||
"# Optionally (recommended), use existing configuration file\n",
|
"# Optionally (recommended), use existing configuration file\n",
|
||||||
"# config = Configuration.from_files([\"config.json\"])\n",
|
"# config = Configuration.from_files([\"user_data/config.json\"])\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Define some constants\n",
|
"# Define some constants\n",
|
||||||
"config[\"timeframe\"] = \"5m\"\n",
|
"config[\"timeframe\"] = \"5m\"\n",
|
||||||
"# Name of the strategy class\n",
|
"# Name of the strategy class\n",
|
||||||
"config[\"strategy\"] = \"SampleStrategy\"\n",
|
"config[\"strategy\"] = \"SampleStrategy\"\n",
|
||||||
"# Location of the data\n",
|
"# Location of the data\n",
|
||||||
"data_location = config['datadir']\n",
|
"data_location = config[\"datadir\"]\n",
|
||||||
"# Pair to analyze - Only use one pair here\n",
|
"# Pair to analyze - Only use one pair here\n",
|
||||||
"pair = \"BTC/USDT\""
|
"pair = \"BTC/USDT\""
|
||||||
]
|
]
|
||||||
@ -56,12 +90,12 @@
|
|||||||
"candles = load_pair_history(datadir=data_location,\n",
|
"candles = load_pair_history(datadir=data_location,\n",
|
||||||
" timeframe=config[\"timeframe\"],\n",
|
" timeframe=config[\"timeframe\"],\n",
|
||||||
" pair=pair,\n",
|
" pair=pair,\n",
|
||||||
" data_format = \"hdf5\",\n",
|
" data_format = \"json\", # Make sure to update this to your data\n",
|
||||||
" candle_type=CandleType.SPOT,\n",
|
" candle_type=CandleType.SPOT,\n",
|
||||||
" )\n",
|
" )\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Confirm success\n",
|
"# Confirm success\n",
|
||||||
"print(\"Loaded \" + str(len(candles)) + f\" rows of data for {pair} from {data_location}\")\n",
|
"print(f\"Loaded {len(candles)} rows of data for {pair} from {data_location}\")\n",
|
||||||
"candles.head()"
|
"candles.head()"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -365,7 +399,7 @@
|
|||||||
"metadata": {
|
"metadata": {
|
||||||
"file_extension": ".py",
|
"file_extension": ".py",
|
||||||
"kernelspec": {
|
"kernelspec": {
|
||||||
"display_name": "Python 3.9.7 64-bit ('trade_397')",
|
"display_name": "Python 3.9.7 64-bit",
|
||||||
"language": "python",
|
"language": "python",
|
||||||
"name": "python3"
|
"name": "python3"
|
||||||
},
|
},
|
||||||
|
@ -12,7 +12,7 @@ flake8-tidy-imports==4.8.0
|
|||||||
mypy==0.991
|
mypy==0.991
|
||||||
pre-commit==2.20.0
|
pre-commit==2.20.0
|
||||||
pytest==7.2.0
|
pytest==7.2.0
|
||||||
pytest-asyncio==0.20.2
|
pytest-asyncio==0.20.3
|
||||||
pytest-cov==4.0.0
|
pytest-cov==4.0.0
|
||||||
pytest-mock==3.10.0
|
pytest-mock==3.10.0
|
||||||
pytest-random-order==1.1.0
|
pytest-random-order==1.1.0
|
||||||
@ -23,7 +23,7 @@ time-machine==2.8.2
|
|||||||
httpx==0.23.1
|
httpx==0.23.1
|
||||||
|
|
||||||
# Convert jupyter notebooks to markdown documents
|
# Convert jupyter notebooks to markdown documents
|
||||||
nbconvert==7.2.5
|
nbconvert==7.2.6
|
||||||
|
|
||||||
# mypy types
|
# mypy types
|
||||||
types-cachetools==5.2.1
|
types-cachetools==5.2.1
|
||||||
|
@ -7,5 +7,5 @@ scikit-learn==1.1.3
|
|||||||
joblib==1.2.0
|
joblib==1.2.0
|
||||||
catboost==1.1.1; platform_machine != 'aarch64'
|
catboost==1.1.1; platform_machine != 'aarch64'
|
||||||
lightgbm==3.3.3
|
lightgbm==3.3.3
|
||||||
xgboost==1.7.1
|
xgboost==1.7.2
|
||||||
tensorboard==2.11.0
|
tensorboard==2.11.0
|
||||||
|
@ -5,5 +5,5 @@
|
|||||||
scipy==1.9.3
|
scipy==1.9.3
|
||||||
scikit-learn==1.1.3
|
scikit-learn==1.1.3
|
||||||
scikit-optimize==0.9.0
|
scikit-optimize==0.9.0
|
||||||
filelock==3.8.0
|
filelock==3.8.2
|
||||||
progressbar2==4.2.0
|
progressbar2==4.2.0
|
||||||
|
@ -2,13 +2,13 @@ numpy==1.23.5
|
|||||||
pandas==1.5.2
|
pandas==1.5.2
|
||||||
pandas-ta==0.3.14b
|
pandas-ta==0.3.14b
|
||||||
|
|
||||||
ccxt==2.2.67
|
ccxt==2.2.92
|
||||||
# Pin cryptography for now due to rust build errors with piwheels
|
# Pin cryptography for now due to rust build errors with piwheels
|
||||||
cryptography==38.0.1; platform_machine == 'armv7l'
|
cryptography==38.0.1; platform_machine == 'armv7l'
|
||||||
cryptography==38.0.4; platform_machine != 'armv7l'
|
cryptography==38.0.4; platform_machine != 'armv7l'
|
||||||
aiohttp==3.8.3
|
aiohttp==3.8.3
|
||||||
SQLAlchemy==1.4.44
|
SQLAlchemy==1.4.45
|
||||||
python-telegram-bot==13.14
|
python-telegram-bot==13.15
|
||||||
arrow==1.2.3
|
arrow==1.2.3
|
||||||
cachetools==4.2.2
|
cachetools==4.2.2
|
||||||
requests==2.28.1
|
requests==2.28.1
|
||||||
@ -20,7 +20,8 @@ tabulate==0.9.0
|
|||||||
pycoingecko==3.1.0
|
pycoingecko==3.1.0
|
||||||
jinja2==3.1.2
|
jinja2==3.1.2
|
||||||
tables==3.7.0
|
tables==3.7.0
|
||||||
blosc==1.10.6
|
blosc==1.10.6; platform_machine == 'arm64'
|
||||||
|
blosc==1.11.0; platform_machine != 'arm64'
|
||||||
joblib==1.2.0
|
joblib==1.2.0
|
||||||
pyarrow==10.0.1; platform_machine != 'armv7l'
|
pyarrow==10.0.1; platform_machine != 'armv7l'
|
||||||
|
|
||||||
@ -47,7 +48,7 @@ psutil==5.9.4
|
|||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
# Building config files interactively
|
# Building config files interactively
|
||||||
questionary==1.10.0
|
questionary==1.10.0
|
||||||
prompt-toolkit==3.0.33
|
prompt-toolkit==3.0.36
|
||||||
# Extensions to datetime library
|
# Extensions to datetime library
|
||||||
python-dateutil==2.8.2
|
python-dateutil==2.8.2
|
||||||
|
|
||||||
|
@ -408,6 +408,11 @@ def create_mock_trades_usdt(fee, is_short: Optional[bool] = False, use_db: bool
|
|||||||
Trade.commit()
|
Trade.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def patch_gc(mocker) -> None:
|
||||||
|
mocker.patch("freqtrade.main.gc_set_threshold")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def patch_coingekko(mocker) -> None:
|
def patch_coingekko(mocker) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -224,8 +224,13 @@ class TestCCXTExchange():
|
|||||||
for val in [1, 2, 5, 25, 100]:
|
for val in [1, 2, 5, 25, 100]:
|
||||||
l2 = exchange.fetch_l2_order_book(pair, val)
|
l2 = exchange.fetch_l2_order_book(pair, val)
|
||||||
if not l2_limit_range or val in l2_limit_range:
|
if not l2_limit_range or val in l2_limit_range:
|
||||||
assert len(l2['asks']) == val
|
if val > 50:
|
||||||
assert len(l2['bids']) == val
|
# Orderbooks are not always this deep.
|
||||||
|
assert val - 5 < len(l2['asks']) <= val
|
||||||
|
assert val - 5 < len(l2['bids']) <= val
|
||||||
|
else:
|
||||||
|
assert len(l2['asks']) == val
|
||||||
|
assert len(l2['bids']) == val
|
||||||
else:
|
else:
|
||||||
next_limit = exchange.get_next_limit_in_list(
|
next_limit = exchange.get_next_limit_in_list(
|
||||||
val, l2_limit_range, l2_limit_range_required)
|
val, l2_limit_range, l2_limit_range_required)
|
||||||
|
@ -12,6 +12,7 @@ from unittest.mock import ANY, MagicMock
|
|||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
import pytest
|
import pytest
|
||||||
|
import time_machine
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
from telegram import Chat, Message, ReplyKeyboardMarkup, Update
|
from telegram import Chat, Message, ReplyKeyboardMarkup, Update
|
||||||
from telegram.error import BadRequest, NetworkError, TelegramError
|
from telegram.error import BadRequest, NetworkError, TelegramError
|
||||||
@ -1906,119 +1907,120 @@ def test_send_msg_entry_fill_notification(default_conf, mocker, message_type, en
|
|||||||
|
|
||||||
def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||||
|
|
||||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
with time_machine.travel("2022-09-01 05:00:00 +00:00", tick=False):
|
||||||
|
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
|
|
||||||
old_convamount = telegram._rpc._fiat_converter.convert_amount
|
old_convamount = telegram._rpc._fiat_converter.convert_amount
|
||||||
telegram._rpc._fiat_converter.convert_amount = lambda a, b, c: -24.812
|
telegram._rpc._fiat_converter.convert_amount = lambda a, b, c: -24.812
|
||||||
telegram.send_msg({
|
telegram.send_msg({
|
||||||
'type': RPCMessageType.EXIT,
|
'type': RPCMessageType.EXIT,
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'KEY/ETH',
|
'pair': 'KEY/ETH',
|
||||||
'leverage': 1.0,
|
'leverage': 1.0,
|
||||||
'direction': 'Long',
|
'direction': 'Long',
|
||||||
'gain': 'loss',
|
'gain': 'loss',
|
||||||
'order_rate': 3.201e-05,
|
'order_rate': 3.201e-05,
|
||||||
'amount': 1333.3333333333335,
|
'amount': 1333.3333333333335,
|
||||||
'order_type': 'market',
|
'order_type': 'market',
|
||||||
'open_rate': 7.5e-05,
|
'open_rate': 7.5e-05,
|
||||||
'current_rate': 3.201e-05,
|
'current_rate': 3.201e-05,
|
||||||
'profit_amount': -0.05746268,
|
'profit_amount': -0.05746268,
|
||||||
'profit_ratio': -0.57405275,
|
'profit_ratio': -0.57405275,
|
||||||
'stake_currency': 'ETH',
|
'stake_currency': 'ETH',
|
||||||
'fiat_currency': 'USD',
|
'fiat_currency': 'USD',
|
||||||
'enter_tag': 'buy_signal1',
|
'enter_tag': 'buy_signal1',
|
||||||
'exit_reason': ExitType.STOP_LOSS.value,
|
'exit_reason': ExitType.STOP_LOSS.value,
|
||||||
'open_date': arrow.utcnow().shift(hours=-1),
|
'open_date': arrow.utcnow().shift(hours=-1),
|
||||||
'close_date': arrow.utcnow(),
|
'close_date': arrow.utcnow(),
|
||||||
})
|
})
|
||||||
assert msg_mock.call_args[0][0] == (
|
assert msg_mock.call_args[0][0] == (
|
||||||
'\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
|
'\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
|
||||||
'*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
|
'*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
|
||||||
'*Enter Tag:* `buy_signal1`\n'
|
'*Enter Tag:* `buy_signal1`\n'
|
||||||
'*Exit Reason:* `stop_loss`\n'
|
'*Exit Reason:* `stop_loss`\n'
|
||||||
'*Direction:* `Long`\n'
|
'*Direction:* `Long`\n'
|
||||||
'*Amount:* `1333.33333333`\n'
|
'*Amount:* `1333.33333333`\n'
|
||||||
'*Open Rate:* `0.00007500`\n'
|
'*Open Rate:* `0.00007500`\n'
|
||||||
'*Current Rate:* `0.00003201`\n'
|
'*Current Rate:* `0.00003201`\n'
|
||||||
'*Exit Rate:* `0.00003201`\n'
|
'*Exit Rate:* `0.00003201`\n'
|
||||||
'*Duration:* `1:00:00 (60.0 min)`'
|
'*Duration:* `1:00:00 (60.0 min)`'
|
||||||
)
|
|
||||||
|
|
||||||
msg_mock.reset_mock()
|
|
||||||
telegram.send_msg({
|
|
||||||
'type': RPCMessageType.EXIT,
|
|
||||||
'trade_id': 1,
|
|
||||||
'exchange': 'Binance',
|
|
||||||
'pair': 'KEY/ETH',
|
|
||||||
'direction': 'Long',
|
|
||||||
'gain': 'loss',
|
|
||||||
'order_rate': 3.201e-05,
|
|
||||||
'amount': 1333.3333333333335,
|
|
||||||
'order_type': 'market',
|
|
||||||
'open_rate': 7.5e-05,
|
|
||||||
'current_rate': 3.201e-05,
|
|
||||||
'cumulative_profit': -0.15746268,
|
|
||||||
'profit_amount': -0.05746268,
|
|
||||||
'profit_ratio': -0.57405275,
|
|
||||||
'stake_currency': 'ETH',
|
|
||||||
'fiat_currency': 'USD',
|
|
||||||
'enter_tag': 'buy_signal1',
|
|
||||||
'exit_reason': ExitType.STOP_LOSS.value,
|
|
||||||
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
|
|
||||||
'close_date': arrow.utcnow(),
|
|
||||||
'stake_amount': 0.01,
|
|
||||||
'sub_trade': True,
|
|
||||||
})
|
|
||||||
assert msg_mock.call_args[0][0] == (
|
|
||||||
'\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
|
|
||||||
'*Unrealized Sub Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
|
|
||||||
'*Cumulative Profit:* (`-0.15746268 ETH / -24.812 USD`)\n'
|
|
||||||
'*Enter Tag:* `buy_signal1`\n'
|
|
||||||
'*Exit Reason:* `stop_loss`\n'
|
|
||||||
'*Direction:* `Long`\n'
|
|
||||||
'*Amount:* `1333.33333333`\n'
|
|
||||||
'*Open Rate:* `0.00007500`\n'
|
|
||||||
'*Current Rate:* `0.00003201`\n'
|
|
||||||
'*Exit Rate:* `0.00003201`\n'
|
|
||||||
'*Remaining:* `(0.01 ETH, -24.812 USD)`'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
telegram.send_msg({
|
telegram.send_msg({
|
||||||
'type': RPCMessageType.EXIT,
|
'type': RPCMessageType.EXIT,
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'exchange': 'Binance',
|
'exchange': 'Binance',
|
||||||
'pair': 'KEY/ETH',
|
'pair': 'KEY/ETH',
|
||||||
'direction': 'Long',
|
'direction': 'Long',
|
||||||
'gain': 'loss',
|
'gain': 'loss',
|
||||||
'order_rate': 3.201e-05,
|
'order_rate': 3.201e-05,
|
||||||
'amount': 1333.3333333333335,
|
'amount': 1333.3333333333335,
|
||||||
'order_type': 'market',
|
'order_type': 'market',
|
||||||
'open_rate': 7.5e-05,
|
'open_rate': 7.5e-05,
|
||||||
'current_rate': 3.201e-05,
|
'current_rate': 3.201e-05,
|
||||||
'profit_amount': -0.05746268,
|
'cumulative_profit': -0.15746268,
|
||||||
'profit_ratio': -0.57405275,
|
'profit_amount': -0.05746268,
|
||||||
'stake_currency': 'ETH',
|
'profit_ratio': -0.57405275,
|
||||||
'enter_tag': 'buy_signal1',
|
'stake_currency': 'ETH',
|
||||||
'exit_reason': ExitType.STOP_LOSS.value,
|
'fiat_currency': 'USD',
|
||||||
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
|
'enter_tag': 'buy_signal1',
|
||||||
'close_date': arrow.utcnow(),
|
'exit_reason': ExitType.STOP_LOSS.value,
|
||||||
})
|
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
|
||||||
assert msg_mock.call_args[0][0] == (
|
'close_date': arrow.utcnow(),
|
||||||
'\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
|
'stake_amount': 0.01,
|
||||||
'*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH)`\n'
|
'sub_trade': True,
|
||||||
'*Enter Tag:* `buy_signal1`\n'
|
})
|
||||||
'*Exit Reason:* `stop_loss`\n'
|
assert msg_mock.call_args[0][0] == (
|
||||||
'*Direction:* `Long`\n'
|
'\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
|
||||||
'*Amount:* `1333.33333333`\n'
|
'*Unrealized Sub Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
|
||||||
'*Open Rate:* `0.00007500`\n'
|
'*Cumulative Profit:* (`-0.15746268 ETH / -24.812 USD`)\n'
|
||||||
'*Current Rate:* `0.00003201`\n'
|
'*Enter Tag:* `buy_signal1`\n'
|
||||||
'*Exit Rate:* `0.00003201`\n'
|
'*Exit Reason:* `stop_loss`\n'
|
||||||
'*Duration:* `1 day, 2:30:00 (1590.0 min)`'
|
'*Direction:* `Long`\n'
|
||||||
)
|
'*Amount:* `1333.33333333`\n'
|
||||||
# Reset singleton function to avoid random breaks
|
'*Open Rate:* `0.00007500`\n'
|
||||||
telegram._rpc._fiat_converter.convert_amount = old_convamount
|
'*Current Rate:* `0.00003201`\n'
|
||||||
|
'*Exit Rate:* `0.00003201`\n'
|
||||||
|
'*Remaining:* `(0.01 ETH, -24.812 USD)`'
|
||||||
|
)
|
||||||
|
|
||||||
|
msg_mock.reset_mock()
|
||||||
|
telegram.send_msg({
|
||||||
|
'type': RPCMessageType.EXIT,
|
||||||
|
'trade_id': 1,
|
||||||
|
'exchange': 'Binance',
|
||||||
|
'pair': 'KEY/ETH',
|
||||||
|
'direction': 'Long',
|
||||||
|
'gain': 'loss',
|
||||||
|
'order_rate': 3.201e-05,
|
||||||
|
'amount': 1333.3333333333335,
|
||||||
|
'order_type': 'market',
|
||||||
|
'open_rate': 7.5e-05,
|
||||||
|
'current_rate': 3.201e-05,
|
||||||
|
'profit_amount': -0.05746268,
|
||||||
|
'profit_ratio': -0.57405275,
|
||||||
|
'stake_currency': 'ETH',
|
||||||
|
'enter_tag': 'buy_signal1',
|
||||||
|
'exit_reason': ExitType.STOP_LOSS.value,
|
||||||
|
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
|
||||||
|
'close_date': arrow.utcnow(),
|
||||||
|
})
|
||||||
|
assert msg_mock.call_args[0][0] == (
|
||||||
|
'\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
|
||||||
|
'*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH)`\n'
|
||||||
|
'*Enter Tag:* `buy_signal1`\n'
|
||||||
|
'*Exit Reason:* `stop_loss`\n'
|
||||||
|
'*Direction:* `Long`\n'
|
||||||
|
'*Amount:* `1333.33333333`\n'
|
||||||
|
'*Open Rate:* `0.00007500`\n'
|
||||||
|
'*Current Rate:* `0.00003201`\n'
|
||||||
|
'*Exit Rate:* `0.00003201`\n'
|
||||||
|
'*Duration:* `1 day, 2:30:00 (1590.0 min)`'
|
||||||
|
)
|
||||||
|
# Reset singleton function to avoid random breaks
|
||||||
|
telegram._rpc._fiat_converter.convert_amount = old_convamount
|
||||||
|
|
||||||
|
|
||||||
def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
|
def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
|
||||||
@ -2065,41 +2067,42 @@ def test_send_msg_sell_fill_notification(default_conf, mocker, direction,
|
|||||||
default_conf['telegram']['notification_settings']['exit_fill'] = 'on'
|
default_conf['telegram']['notification_settings']['exit_fill'] = 'on'
|
||||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||||
|
|
||||||
telegram.send_msg({
|
with time_machine.travel("2022-09-01 05:00:00 +00:00", tick=False):
|
||||||
'type': RPCMessageType.EXIT_FILL,
|
telegram.send_msg({
|
||||||
'trade_id': 1,
|
'type': RPCMessageType.EXIT_FILL,
|
||||||
'exchange': 'Binance',
|
'trade_id': 1,
|
||||||
'pair': 'KEY/ETH',
|
'exchange': 'Binance',
|
||||||
'leverage': leverage,
|
'pair': 'KEY/ETH',
|
||||||
'direction': direction,
|
'leverage': leverage,
|
||||||
'gain': 'loss',
|
'direction': direction,
|
||||||
'limit': 3.201e-05,
|
'gain': 'loss',
|
||||||
'amount': 1333.3333333333335,
|
'limit': 3.201e-05,
|
||||||
'order_type': 'market',
|
'amount': 1333.3333333333335,
|
||||||
'open_rate': 7.5e-05,
|
'order_type': 'market',
|
||||||
'close_rate': 3.201e-05,
|
'open_rate': 7.5e-05,
|
||||||
'profit_amount': -0.05746268,
|
'close_rate': 3.201e-05,
|
||||||
'profit_ratio': -0.57405275,
|
'profit_amount': -0.05746268,
|
||||||
'stake_currency': 'ETH',
|
'profit_ratio': -0.57405275,
|
||||||
'enter_tag': enter_signal,
|
'stake_currency': 'ETH',
|
||||||
'exit_reason': ExitType.STOP_LOSS.value,
|
'enter_tag': enter_signal,
|
||||||
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
|
'exit_reason': ExitType.STOP_LOSS.value,
|
||||||
'close_date': arrow.utcnow(),
|
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
|
||||||
})
|
'close_date': arrow.utcnow(),
|
||||||
|
})
|
||||||
|
|
||||||
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
|
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
|
||||||
assert msg_mock.call_args[0][0] == (
|
assert msg_mock.call_args[0][0] == (
|
||||||
'\N{WARNING SIGN} *Binance (dry):* Exited KEY/ETH (#1)\n'
|
'\N{WARNING SIGN} *Binance (dry):* Exited KEY/ETH (#1)\n'
|
||||||
'*Profit:* `-57.41% (loss: -0.05746268 ETH)`\n'
|
'*Profit:* `-57.41% (loss: -0.05746268 ETH)`\n'
|
||||||
f'*Enter Tag:* `{enter_signal}`\n'
|
f'*Enter Tag:* `{enter_signal}`\n'
|
||||||
'*Exit Reason:* `stop_loss`\n'
|
'*Exit Reason:* `stop_loss`\n'
|
||||||
f"*Direction:* `{direction}`\n"
|
f"*Direction:* `{direction}`\n"
|
||||||
f"{leverage_text}"
|
f"{leverage_text}"
|
||||||
'*Amount:* `1333.33333333`\n'
|
'*Amount:* `1333.33333333`\n'
|
||||||
'*Open Rate:* `0.00007500`\n'
|
'*Open Rate:* `0.00007500`\n'
|
||||||
'*Exit Rate:* `0.00003201`\n'
|
'*Exit Rate:* `0.00003201`\n'
|
||||||
'*Duration:* `1 day, 2:30:00 (1590.0 min)`'
|
'*Duration:* `1 day, 2:30:00 (1590.0 min)`'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_send_msg_status_notification(default_conf, mocker) -> None:
|
def test_send_msg_status_notification(default_conf, mocker) -> None:
|
||||||
|
@ -1046,8 +1046,13 @@ def test__validate_freqai_include_timeframes(default_conf, caplog) -> None:
|
|||||||
# Validation pass
|
# Validation pass
|
||||||
conf.update({'timeframe': '1m'})
|
conf.update({'timeframe': '1m'})
|
||||||
validate_config_consistency(conf)
|
validate_config_consistency(conf)
|
||||||
conf.update({'analyze_per_epoch': True})
|
|
||||||
|
|
||||||
|
# Ensure base timeframe is in include_timeframes
|
||||||
|
conf['freqai']['feature_parameters']['include_timeframes'] = ["5m", "15m"]
|
||||||
|
validate_config_consistency(conf)
|
||||||
|
assert conf['freqai']['feature_parameters']['include_timeframes'] == ["1m", "5m", "15m"]
|
||||||
|
|
||||||
|
conf.update({'analyze_per_epoch': True})
|
||||||
with pytest.raises(OperationalException,
|
with pytest.raises(OperationalException,
|
||||||
match=r"Using analyze-per-epoch .* not supported with a FreqAI strategy."):
|
match=r"Using analyze-per-epoch .* not supported with a FreqAI strategy."):
|
||||||
validate_config_consistency(conf)
|
validate_config_consistency(conf)
|
||||||
|
@ -88,6 +88,18 @@ def test_bot_cleanup(mocker, default_conf_usdt, caplog) -> None:
|
|||||||
assert coo_mock.call_count == 1
|
assert coo_mock.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_bot_cleanup_db_errors(mocker, default_conf_usdt, caplog) -> None:
|
||||||
|
mocker.patch('freqtrade.freqtradebot.Trade.commit',
|
||||||
|
side_effect=OperationalException())
|
||||||
|
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.check_for_open_trades',
|
||||||
|
side_effect=OperationalException())
|
||||||
|
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||||
|
freqtrade.emc = MagicMock()
|
||||||
|
freqtrade.emc.shutdown = MagicMock()
|
||||||
|
freqtrade.cleanup()
|
||||||
|
assert freqtrade.emc.shutdown.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('runmode', [
|
@pytest.mark.parametrize('runmode', [
|
||||||
RunMode.DRY_RUN,
|
RunMode.DRY_RUN,
|
||||||
RunMode.LIVE
|
RunMode.LIVE
|
||||||
|
Loading…
Reference in New Issue
Block a user