Merge branch 'freqtrade:develop' into reject_report
This commit is contained in:
		
							
								
								
									
										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 | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -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.5.1 |       uses: pypa/gh-action-pypi-publish@v1.6.1 | ||||||
|       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.5.1 |       uses: pypa/gh-action-pypi-publish@v1.6.1 | ||||||
|       if: (github.event_name == 'release') |       if: (github.event_name == 'release') | ||||||
|       with: |       with: | ||||||
|         user: __token__ |         user: __token__ | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| markdown==3.3.7 | markdown==3.3.7 | ||||||
| mkdocs==1.4.2 | mkdocs==1.4.2 | ||||||
| mkdocs-material==8.5.10 | mkdocs-material==8.5.11 | ||||||
| mdx_truly_sane_lists==1.3 | mdx_truly_sane_lists==1.3 | ||||||
| pymdown-extensions==9.8 | pymdown-extensions==9.9 | ||||||
| jinja2==3.1.2 | jinja2==3.1.2 | ||||||
|   | |||||||
| @@ -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 | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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 | ||||||
|   | |||||||
| @@ -104,13 +104,15 @@ class DataProvider: | |||||||
|     def _emit_df( |     def _emit_df( | ||||||
|         self, |         self, | ||||||
|         pair_key: PairWithTimeframe, |         pair_key: PairWithTimeframe, | ||||||
|         dataframe: DataFrame |         dataframe: DataFrame, | ||||||
|  |         new_candle: bool | ||||||
|     ) -> None: |     ) -> None: | ||||||
|         """ |         """ | ||||||
|         Send this dataframe as an ANALYZED_DF message to RPC |         Send this dataframe as an ANALYZED_DF message to RPC | ||||||
|  |  | ||||||
|         :param pair_key: PairWithTimeframe tuple |         :param pair_key: PairWithTimeframe tuple | ||||||
|         :param data: Tuple containing the DataFrame and the datetime it was cached |         :param dataframe: Dataframe to emit | ||||||
|  |         :param new_candle: This is a new candle | ||||||
|         """ |         """ | ||||||
|         if self.__rpc: |         if self.__rpc: | ||||||
|             self.__rpc.send_msg( |             self.__rpc.send_msg( | ||||||
| @@ -123,6 +125,11 @@ class DataProvider: | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             ) |             ) | ||||||
|  |             if new_candle: | ||||||
|  |                 self.__rpc.send_msg({ | ||||||
|  |                         'type': RPCMessageType.NEW_CANDLE, | ||||||
|  |                         'data': pair_key, | ||||||
|  |                     }) | ||||||
|  |  | ||||||
|     def _add_external_df( |     def _add_external_df( | ||||||
|         self, |         self, | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ from freqtrade.enums.exittype import ExitType | |||||||
| from freqtrade.enums.hyperoptstate import HyperoptState | from freqtrade.enums.hyperoptstate import HyperoptState | ||||||
| from freqtrade.enums.marginmode import MarginMode | from freqtrade.enums.marginmode import MarginMode | ||||||
| from freqtrade.enums.ordertypevalue import OrderTypeValues | from freqtrade.enums.ordertypevalue import OrderTypeValues | ||||||
| from freqtrade.enums.rpcmessagetype import RPCMessageType, RPCRequestType | from freqtrade.enums.rpcmessagetype import NO_ECHO_MESSAGES, RPCMessageType, RPCRequestType | ||||||
| from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode | from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode | ||||||
| from freqtrade.enums.signaltype import SignalDirection, SignalTagType, SignalType | from freqtrade.enums.signaltype import SignalDirection, SignalTagType, SignalType | ||||||
| from freqtrade.enums.state import State | from freqtrade.enums.state import State | ||||||
|   | |||||||
| @@ -21,6 +21,7 @@ class RPCMessageType(str, Enum): | |||||||
|  |  | ||||||
|     WHITELIST = 'whitelist' |     WHITELIST = 'whitelist' | ||||||
|     ANALYZED_DF = 'analyzed_df' |     ANALYZED_DF = 'analyzed_df' | ||||||
|  |     NEW_CANDLE = 'new_candle' | ||||||
|  |  | ||||||
|     def __repr__(self): |     def __repr__(self): | ||||||
|         return self.value |         return self.value | ||||||
| @@ -35,3 +36,6 @@ class RPCRequestType(str, Enum): | |||||||
|  |  | ||||||
|     WHITELIST = 'whitelist' |     WHITELIST = 'whitelist' | ||||||
|     ANALYZED_DF = 'analyzed_df' |     ANALYZED_DF = 'analyzed_df' | ||||||
|  |  | ||||||
|  |  | ||||||
|  | NO_ECHO_MESSAGES = (RPCMessageType.ANALYZED_DF, RPCMessageType.WHITELIST, RPCMessageType.NEW_CANDLE) | ||||||
|   | |||||||
| @@ -20,6 +20,9 @@ class Base4ActionRLEnv(BaseEnvironment): | |||||||
|     """ |     """ | ||||||
|     Base class for a 4 action environment |     Base class for a 4 action environment | ||||||
|     """ |     """ | ||||||
|  |     def __init__(self, **kwargs): | ||||||
|  |         super().__init__(**kwargs) | ||||||
|  |         self.actions = Actions | ||||||
|  |  | ||||||
|     def set_action_space(self): |     def set_action_space(self): | ||||||
|         self.action_space = spaces.Discrete(len(Actions)) |         self.action_space = spaces.Discrete(len(Actions)) | ||||||
| @@ -92,9 +95,12 @@ class Base4ActionRLEnv(BaseEnvironment): | |||||||
|  |  | ||||||
|         info = dict( |         info = dict( | ||||||
|             tick=self._current_tick, |             tick=self._current_tick, | ||||||
|  |             action=action, | ||||||
|             total_reward=self.total_reward, |             total_reward=self.total_reward, | ||||||
|             total_profit=self._total_profit, |             total_profit=self._total_profit, | ||||||
|             position=self._position.value |             position=self._position.value, | ||||||
|  |             trade_duration=self.get_trade_duration(), | ||||||
|  |             current_profit_pct=self.get_unrealized_profit() | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         observation = self._get_observation() |         observation = self._get_observation() | ||||||
|   | |||||||
| @@ -21,6 +21,9 @@ class Base5ActionRLEnv(BaseEnvironment): | |||||||
|     """ |     """ | ||||||
|     Base class for a 5 action environment |     Base class for a 5 action environment | ||||||
|     """ |     """ | ||||||
|  |     def __init__(self, **kwargs): | ||||||
|  |         super().__init__(**kwargs) | ||||||
|  |         self.actions = Actions | ||||||
|  |  | ||||||
|     def set_action_space(self): |     def set_action_space(self): | ||||||
|         self.action_space = spaces.Discrete(len(Actions)) |         self.action_space = spaces.Discrete(len(Actions)) | ||||||
| @@ -98,9 +101,12 @@ class Base5ActionRLEnv(BaseEnvironment): | |||||||
|  |  | ||||||
|         info = dict( |         info = dict( | ||||||
|             tick=self._current_tick, |             tick=self._current_tick, | ||||||
|  |             action=action, | ||||||
|             total_reward=self.total_reward, |             total_reward=self.total_reward, | ||||||
|             total_profit=self._total_profit, |             total_profit=self._total_profit, | ||||||
|             position=self._position.value |             position=self._position.value, | ||||||
|  |             trade_duration=self.get_trade_duration(), | ||||||
|  |             current_profit_pct=self.get_unrealized_profit() | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         observation = self._get_observation() |         observation = self._get_observation() | ||||||
|   | |||||||
| @@ -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 | from typing import Optional, Type | ||||||
|  |  | ||||||
| import gym | import gym | ||||||
| import numpy as np | import numpy as np | ||||||
| @@ -12,11 +12,23 @@ 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__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class BaseActions(Enum): | ||||||
|  |     """ | ||||||
|  |     Default action space, mostly used for type handling. | ||||||
|  |     """ | ||||||
|  |     Neutral = 0 | ||||||
|  |     Long_enter = 1 | ||||||
|  |     Long_exit = 2 | ||||||
|  |     Short_enter = 3 | ||||||
|  |     Short_exit = 4 | ||||||
|  |  | ||||||
|  |  | ||||||
| class Positions(Enum): | class Positions(Enum): | ||||||
|     Short = 0 |     Short = 0 | ||||||
|     Long = 1 |     Long = 1 | ||||||
| @@ -64,6 +76,16 @@ class BaseEnvironment(gym.Env): | |||||||
|         else: |         else: | ||||||
|             self.fee = 0.0015 |             self.fee = 0.0015 | ||||||
|  |  | ||||||
|  |         # set here to default 5Ac, but all children envs can override this | ||||||
|  |         self.actions: Type[Enum] = BaseActions | ||||||
|  |         self.custom_info: 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): | ||||||
|         """ |         """ | ||||||
| @@ -118,6 +140,19 @@ class BaseEnvironment(gym.Env): | |||||||
|         return [seed] |         return [seed] | ||||||
|  |  | ||||||
|     def reset(self): |     def reset(self): | ||||||
|  |         """ | ||||||
|  |         Reset is called at the beginning of every episode | ||||||
|  |         """ | ||||||
|  |         # custom_info is used for episodic reports and tensorboard logging | ||||||
|  |         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 | ||||||
|  |  | ||||||
| @@ -160,7 +195,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', | ||||||
| @@ -271,6 +306,13 @@ class BaseEnvironment(gym.Env): | |||||||
|     def current_price(self) -> float: |     def current_price(self) -> float: | ||||||
|         return self.prices.iloc[self._current_tick].open |         return self.prices.iloc[self._current_tick].open | ||||||
|  |  | ||||||
|  |     def get_actions(self) -> Type[Enum]: | ||||||
|  |         """ | ||||||
|  |         Used by SubprocVecEnv to get actions from | ||||||
|  |         initialized env for tensorboard callback | ||||||
|  |         """ | ||||||
|  |         return self.actions | ||||||
|  |  | ||||||
|     # Keeping around incase we want to start building more complex environment |     # Keeping around incase we want to start building more complex environment | ||||||
|     # templates in the future. |     # templates in the future. | ||||||
|     # def most_recent_return(self): |     # def most_recent_return(self): | ||||||
|   | |||||||
| @@ -21,7 +21,8 @@ from freqtrade.exceptions import OperationalException | |||||||
| from freqtrade.freqai.data_kitchen import FreqaiDataKitchen | from freqtrade.freqai.data_kitchen import FreqaiDataKitchen | ||||||
| from freqtrade.freqai.freqai_interface import IFreqaiModel | from freqtrade.freqai.freqai_interface import IFreqaiModel | ||||||
| from freqtrade.freqai.RL.Base5ActionRLEnv import Actions, Base5ActionRLEnv | from freqtrade.freqai.RL.Base5ActionRLEnv import Actions, Base5ActionRLEnv | ||||||
| from freqtrade.freqai.RL.BaseEnvironment import Positions | from freqtrade.freqai.RL.BaseEnvironment import BaseActions, Positions | ||||||
|  | from freqtrade.freqai.RL.TensorboardCallback import TensorboardCallback | ||||||
| from freqtrade.persistence import Trade | from freqtrade.persistence import Trade | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -44,8 +45,8 @@ class BaseReinforcementLearningModel(IFreqaiModel): | |||||||
|             'cpu_count', 1), max(int(self.max_system_threads / 2), 1)) |             'cpu_count', 1), max(int(self.max_system_threads / 2), 1)) | ||||||
|         th.set_num_threads(self.max_threads) |         th.set_num_threads(self.max_threads) | ||||||
|         self.reward_params = self.freqai_info['rl_config']['model_reward_parameters'] |         self.reward_params = self.freqai_info['rl_config']['model_reward_parameters'] | ||||||
|         self.train_env: Union[SubprocVecEnv, gym.Env] = None |         self.train_env: Union[SubprocVecEnv, Type[gym.Env]] = gym.Env() | ||||||
|         self.eval_env: Union[SubprocVecEnv, gym.Env] = None |         self.eval_env: Union[SubprocVecEnv, Type[gym.Env]] = gym.Env() | ||||||
|         self.eval_callback: Optional[EvalCallback] = None |         self.eval_callback: Optional[EvalCallback] = None | ||||||
|         self.model_type = self.freqai_info['rl_config']['model_type'] |         self.model_type = self.freqai_info['rl_config']['model_type'] | ||||||
|         self.rl_config = self.freqai_info['rl_config'] |         self.rl_config = self.freqai_info['rl_config'] | ||||||
| @@ -65,6 +66,8 @@ class BaseReinforcementLearningModel(IFreqaiModel): | |||||||
|         self.unset_outlier_removal() |         self.unset_outlier_removal() | ||||||
|         self.net_arch = self.rl_config.get('net_arch', [128, 128]) |         self.net_arch = self.rl_config.get('net_arch', [128, 128]) | ||||||
|         self.dd.model_type = import_str |         self.dd.model_type = import_str | ||||||
|  |         self.tensorboard_callback: TensorboardCallback = \ | ||||||
|  |             TensorboardCallback(verbose=1, actions=BaseActions) | ||||||
|  |  | ||||||
|     def unset_outlier_removal(self): |     def unset_outlier_removal(self): | ||||||
|         """ |         """ | ||||||
| @@ -156,6 +159,9 @@ class BaseReinforcementLearningModel(IFreqaiModel): | |||||||
|                                           render=False, eval_freq=len(train_df), |                                           render=False, eval_freq=len(train_df), | ||||||
|                                           best_model_save_path=str(dk.data_path)) |                                           best_model_save_path=str(dk.data_path)) | ||||||
|  |  | ||||||
|  |         actions = self.train_env.get_actions() | ||||||
|  |         self.tensorboard_callback = TensorboardCallback(verbose=1, actions=actions) | ||||||
|  |  | ||||||
|     @abstractmethod |     @abstractmethod | ||||||
|     def fit(self, data_dictionary: Dict[str, Any], dk: FreqaiDataKitchen, **kwargs): |     def fit(self, data_dictionary: Dict[str, Any], dk: FreqaiDataKitchen, **kwargs): | ||||||
|         """ |         """ | ||||||
|   | |||||||
							
								
								
									
										60
									
								
								freqtrade/freqai/RL/TensorboardCallback.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								freqtrade/freqai/RL/TensorboardCallback.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | |||||||
|  | from enum import Enum | ||||||
|  | from typing import Any, Dict, Type, Union | ||||||
|  |  | ||||||
|  | from stable_baselines3.common.callbacks import BaseCallback | ||||||
|  | from stable_baselines3.common.logger import HParam | ||||||
|  |  | ||||||
|  | from freqtrade.freqai.RL.BaseEnvironment import BaseActions, BaseEnvironment | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TensorboardCallback(BaseCallback): | ||||||
|  |     """ | ||||||
|  |     Custom callback for plotting additional values in tensorboard and | ||||||
|  |     episodic summary reports. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, verbose=1, actions: Type[Enum] = BaseActions): | ||||||
|  |         super(TensorboardCallback, self).__init__(verbose) | ||||||
|  |         self.model: Any = None | ||||||
|  |         self.logger = None  # type: Any | ||||||
|  |         self.training_env: BaseEnvironment = None  # type: ignore | ||||||
|  |         self.actions: Type[Enum] = actions | ||||||
|  |  | ||||||
|  |     def _on_training_start(self) -> None: | ||||||
|  |         hparam_dict = { | ||||||
|  |             "algorithm": self.model.__class__.__name__, | ||||||
|  |             "learning_rate": self.model.learning_rate, | ||||||
|  |             # "gamma": self.model.gamma, | ||||||
|  |             # "gae_lambda": self.model.gae_lambda, | ||||||
|  |             # "batch_size": self.model.batch_size, | ||||||
|  |             # "n_steps": self.model.n_steps, | ||||||
|  |         } | ||||||
|  |         metric_dict: Dict[str, Union[float, int]] = { | ||||||
|  |             "eval/mean_reward": 0, | ||||||
|  |             "rollout/ep_rew_mean": 0, | ||||||
|  |             "rollout/ep_len_mean": 0, | ||||||
|  |             "train/value_loss": 0, | ||||||
|  |             "train/explained_variance": 0, | ||||||
|  |         } | ||||||
|  |         self.logger.record( | ||||||
|  |             "hparams", | ||||||
|  |             HParam(hparam_dict, metric_dict), | ||||||
|  |             exclude=("stdout", "log", "json", "csv"), | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |     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"]) | ||||||
|  |         self.logger.record("_state/trade_duration", self.locals["infos"][0]["trade_duration"]) | ||||||
|  |         self.logger.record("_state/current_profit_pct", self.locals["infos"] | ||||||
|  |                            [0]["current_profit_pct"]) | ||||||
|  |         self.logger.record("_reward/total_profit", self.locals["infos"][0]["total_profit"]) | ||||||
|  |         self.logger.record("_reward/total_reward", self.locals["infos"][0]["total_reward"]) | ||||||
|  |         self.logger.record_mean("_reward/mean_trade_duration", self.locals["infos"] | ||||||
|  |                                 [0]["trade_duration"]) | ||||||
|  |         self.logger.record("_actions/action", self.locals["infos"][0]["action"]) | ||||||
|  |         self.logger.record("_actions/_Invalid", custom_info["Invalid"]) | ||||||
|  |         self.logger.record("_actions/_Unknown", custom_info["Unknown"]) | ||||||
|  |         self.logger.record("_actions/Hold", custom_info["Hold"]) | ||||||
|  |         for action in self.actions: | ||||||
|  |             self.logger.record(f"_actions/{action.name}", custom_info[action.name]) | ||||||
|  |         return True | ||||||
| @@ -462,10 +462,10 @@ class FreqaiDataKitchen: | |||||||
|         :param df: Dataframe containing all candles to run the entire backtest. Here |         :param df: Dataframe containing all candles to run the entire backtest. Here | ||||||
|                    it is sliced down to just the present training period. |                    it is sliced down to just the present training period. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         df = df.loc[df["date"] >= timerange.startdt, :] |  | ||||||
|         if not self.live: |         if not self.live: | ||||||
|             df = df.loc[df["date"] < timerange.stopdt, :] |             df = df.loc[(df["date"] >= timerange.startdt) & (df["date"] < timerange.stopdt), :] | ||||||
|  |         else: | ||||||
|  |             df = df.loc[df["date"] >= timerange.startdt, :] | ||||||
|  |  | ||||||
|         return df |         return df | ||||||
|  |  | ||||||
|   | |||||||
| @@ -282,10 +282,10 @@ class IFreqaiModel(ABC): | |||||||
|             train_it += 1 |             train_it += 1 | ||||||
|             total_trains = len(dk.backtesting_timeranges) |             total_trains = len(dk.backtesting_timeranges) | ||||||
|             self.training_timerange = tr_train |             self.training_timerange = tr_train | ||||||
|             dataframe_train = dk.slice_dataframe(tr_train, dataframe) |             len_backtest_df = len(dataframe.loc[(dataframe["date"] >= tr_backtest.startdt) & ( | ||||||
|             dataframe_backtest = dk.slice_dataframe(tr_backtest, dataframe) |                                   dataframe["date"] < tr_backtest.stopdt), :]) | ||||||
|  |  | ||||||
|             if not self.ensure_data_exists(dataframe_backtest, tr_backtest, pair): |             if not self.ensure_data_exists(len_backtest_df, tr_backtest, pair): | ||||||
|                 continue |                 continue | ||||||
|  |  | ||||||
|             self.log_backtesting_progress(tr_train, pair, train_it, total_trains) |             self.log_backtesting_progress(tr_train, pair, train_it, total_trains) | ||||||
| @@ -298,13 +298,15 @@ class IFreqaiModel(ABC): | |||||||
|  |  | ||||||
|             dk.set_new_model_names(pair, timestamp_model_id) |             dk.set_new_model_names(pair, timestamp_model_id) | ||||||
|  |  | ||||||
|             if dk.check_if_backtest_prediction_is_valid(len(dataframe_backtest)): |             if dk.check_if_backtest_prediction_is_valid(len_backtest_df): | ||||||
|                 self.dd.load_metadata(dk) |                 self.dd.load_metadata(dk) | ||||||
|                 dk.find_features(dataframe_train) |                 dk.find_features(dataframe) | ||||||
|                 self.check_if_feature_list_matches_strategy(dk) |                 self.check_if_feature_list_matches_strategy(dk) | ||||||
|                 append_df = dk.get_backtesting_prediction() |                 append_df = dk.get_backtesting_prediction() | ||||||
|                 dk.append_predictions(append_df) |                 dk.append_predictions(append_df) | ||||||
|             else: |             else: | ||||||
|  |                 dataframe_train = dk.slice_dataframe(tr_train, dataframe) | ||||||
|  |                 dataframe_backtest = dk.slice_dataframe(tr_backtest, dataframe) | ||||||
|                 if not self.model_exists(dk): |                 if not self.model_exists(dk): | ||||||
|                     dk.find_features(dataframe_train) |                     dk.find_features(dataframe_train) | ||||||
|                     dk.find_labels(dataframe_train) |                     dk.find_labels(dataframe_train) | ||||||
| @@ -804,16 +806,16 @@ class IFreqaiModel(ABC): | |||||||
|             self.pair_it = 1 |             self.pair_it = 1 | ||||||
|             self.current_candle = self.dd.current_candle |             self.current_candle = self.dd.current_candle | ||||||
|  |  | ||||||
|     def ensure_data_exists(self, dataframe_backtest: DataFrame, |     def ensure_data_exists(self, len_dataframe_backtest: int, | ||||||
|                            tr_backtest: TimeRange, pair: str) -> bool: |                            tr_backtest: TimeRange, pair: str) -> bool: | ||||||
|         """ |         """ | ||||||
|         Check if the dataframe is empty, if not, report useful information to user. |         Check if the dataframe is empty, if not, report useful information to user. | ||||||
|         :param dataframe_backtest: the backtesting dataframe, maybe empty. |         :param len_dataframe_backtest: the len of backtesting dataframe | ||||||
|         :param tr_backtest: current backtesting timerange. |         :param tr_backtest: current backtesting timerange. | ||||||
|         :param pair: current pair |         :param pair: current pair | ||||||
|         :return: if the data exists or not |         :return: if the data exists or not | ||||||
|         """ |         """ | ||||||
|         if self.config.get("freqai_backtest_live_models", False) and len(dataframe_backtest) == 0: |         if self.config.get("freqai_backtest_live_models", False) and len_dataframe_backtest == 0: | ||||||
|             logger.info(f"No data found for pair {pair} from " |             logger.info(f"No data found for pair {pair} from " | ||||||
|                         f"from { tr_backtest.start_fmt} to {tr_backtest.stop_fmt}. " |                         f"from { tr_backtest.start_fmt} to {tr_backtest.stop_fmt}. " | ||||||
|                         "Probably more than one training within the same candle period.") |                         "Probably more than one training within the same candle period.") | ||||||
|   | |||||||
| @@ -71,7 +71,7 @@ class ReinforcementLearner(BaseReinforcementLearningModel): | |||||||
|  |  | ||||||
|         model.learn( |         model.learn( | ||||||
|             total_timesteps=int(total_timesteps), |             total_timesteps=int(total_timesteps), | ||||||
|             callback=self.eval_callback |             callback=[self.eval_callback, self.tensorboard_callback] | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|         if Path(dk.data_path / "best_model.zip").is_file(): |         if Path(dk.data_path / "best_model.zip").is_file(): | ||||||
| @@ -100,17 +100,24 @@ 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 | ||||||
|                 return -2 |                 return -2 | ||||||
|  |  | ||||||
|             pnl = self.get_unrealized_profit() |             pnl = self.get_unrealized_profit() | ||||||
|             factor = 100. |             factor = 100. | ||||||
|  |  | ||||||
|             # reward agent for entering trades |             # reward agent for entering trades | ||||||
|             if (action in (Actions.Long_enter.value, Actions.Short_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 | ||||||
|  |             if (action == Actions.Short_enter.value | ||||||
|  |                     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) | ||||||
| @@ -124,18 +131,22 @@ 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. | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| import logging | import logging | ||||||
| from typing import Any, Dict  # , Tuple | from typing import Any, Dict | ||||||
|  |  | ||||||
| # import numpy.typing as npt |  | ||||||
| from pandas import DataFrame | from pandas import DataFrame | ||||||
| from stable_baselines3.common.callbacks import EvalCallback | from stable_baselines3.common.callbacks import EvalCallback | ||||||
| from stable_baselines3.common.vec_env import SubprocVecEnv | from stable_baselines3.common.vec_env import SubprocVecEnv | ||||||
| @@ -9,6 +8,7 @@ from stable_baselines3.common.vec_env import SubprocVecEnv | |||||||
| from freqtrade.freqai.data_kitchen import FreqaiDataKitchen | from freqtrade.freqai.data_kitchen import FreqaiDataKitchen | ||||||
| from freqtrade.freqai.prediction_models.ReinforcementLearner import ReinforcementLearner | from freqtrade.freqai.prediction_models.ReinforcementLearner import ReinforcementLearner | ||||||
| from freqtrade.freqai.RL.BaseReinforcementLearningModel import make_env | from freqtrade.freqai.RL.BaseReinforcementLearningModel import make_env | ||||||
|  | from freqtrade.freqai.RL.TensorboardCallback import TensorboardCallback | ||||||
|  |  | ||||||
|  |  | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| @@ -49,3 +49,6 @@ class ReinforcementLearner_multiproc(ReinforcementLearner): | |||||||
|         self.eval_callback = EvalCallback(self.eval_env, deterministic=True, |         self.eval_callback = EvalCallback(self.eval_env, deterministic=True, | ||||||
|                                           render=False, eval_freq=len(train_df), |                                           render=False, eval_freq=len(train_df), | ||||||
|                                           best_model_save_path=str(dk.data_path)) |                                           best_model_save_path=str(dk.data_path)) | ||||||
|  |  | ||||||
|  |         actions = self.train_env.env_method("get_actions")[0] | ||||||
|  |         self.tensorboard_callback = TensorboardCallback(verbose=1, actions=actions) | ||||||
|   | |||||||
| @@ -37,7 +37,8 @@ logger = logging.getLogger(__name__) | |||||||
| # 2.16: Additional daily metrics | # 2.16: Additional daily metrics | ||||||
| # 2.17: Forceentry - leverage, partial force_exit | # 2.17: Forceentry - leverage, partial force_exit | ||||||
| # 2.20: Add websocket endpoints | # 2.20: Add websocket endpoints | ||||||
| API_VERSION = 2.20 | # 2.21: Add new_candle messagetype | ||||||
|  | API_VERSION = 2.21 | ||||||
|  |  | ||||||
| # Public API, requires no auth. | # Public API, requires no auth. | ||||||
| router_public = APIRouter() | router_public = APIRouter() | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ from collections import deque | |||||||
| from typing import Any, Dict, List | from typing import Any, Dict, List | ||||||
|  |  | ||||||
| from freqtrade.constants import Config | from freqtrade.constants import Config | ||||||
| from freqtrade.enums import RPCMessageType | from freqtrade.enums import NO_ECHO_MESSAGES, RPCMessageType | ||||||
| from freqtrade.rpc import RPC, RPCHandler | from freqtrade.rpc import RPC, RPCHandler | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -67,7 +67,7 @@ class RPCManager: | |||||||
|             'status': 'stopping bot' |             'status': 'stopping bot' | ||||||
|         } |         } | ||||||
|         """ |         """ | ||||||
|         if msg.get('type') not in (RPCMessageType.ANALYZED_DF, RPCMessageType.WHITELIST): |         if msg.get('type') not in NO_ECHO_MESSAGES: | ||||||
|             logger.info('Sending rpc message: %s', msg) |             logger.info('Sending rpc message: %s', msg) | ||||||
|         if 'pair' in msg: |         if 'pair' in msg: | ||||||
|             msg.update({ |             msg.update({ | ||||||
|   | |||||||
| @@ -68,6 +68,7 @@ class Webhook(RPCHandler): | |||||||
|                 RPCMessageType.PROTECTION_TRIGGER_GLOBAL, |                 RPCMessageType.PROTECTION_TRIGGER_GLOBAL, | ||||||
|                 RPCMessageType.WHITELIST, |                 RPCMessageType.WHITELIST, | ||||||
|                 RPCMessageType.ANALYZED_DF, |                 RPCMessageType.ANALYZED_DF, | ||||||
|  |                 RPCMessageType.NEW_CANDLE, | ||||||
|                 RPCMessageType.STRATEGY_MSG): |                 RPCMessageType.STRATEGY_MSG): | ||||||
|             # Don't fail for non-implemented types |             # Don't fail for non-implemented types | ||||||
|             return None |             return None | ||||||
|   | |||||||
| @@ -739,10 +739,10 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|         """ |         """ | ||||||
|         pair = str(metadata.get('pair')) |         pair = str(metadata.get('pair')) | ||||||
|  |  | ||||||
|  |         new_candle = self._last_candle_seen_per_pair.get(pair, None) != dataframe.iloc[-1]['date'] | ||||||
|         # Test if seen this pair and last candle before. |         # Test if seen this pair and last candle before. | ||||||
|         # always run if process_only_new_candles is set to false |         # always run if process_only_new_candles is set to false | ||||||
|         if (not self.process_only_new_candles or |         if not self.process_only_new_candles or new_candle: | ||||||
|                 self._last_candle_seen_per_pair.get(pair, None) != dataframe.iloc[-1]['date']): |  | ||||||
|  |  | ||||||
|             # Defs that only make change on new candle data. |             # Defs that only make change on new candle data. | ||||||
|             dataframe = self.analyze_ticker(dataframe, metadata) |             dataframe = self.analyze_ticker(dataframe, metadata) | ||||||
| @@ -751,7 +751,7 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|  |  | ||||||
|             candle_type = self.config.get('candle_type_def', CandleType.SPOT) |             candle_type = self.config.get('candle_type_def', CandleType.SPOT) | ||||||
|             self.dp._set_cached_df(pair, self.timeframe, dataframe, candle_type=candle_type) |             self.dp._set_cached_df(pair, self.timeframe, dataframe, candle_type=candle_type) | ||||||
|             self.dp._emit_df((pair, self.timeframe, candle_type), dataframe) |             self.dp._emit_df((pair, self.timeframe, candle_type), dataframe, new_candle) | ||||||
|  |  | ||||||
|         else: |         else: | ||||||
|             logger.debug("Skipping TA Analysis for already analyzed candle") |             logger.debug("Skipping TA Analysis for already analyzed candle") | ||||||
|   | |||||||
| @@ -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" | ||||||
|   }, |   }, | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ pytest==7.2.0 | |||||||
| pytest-asyncio==0.20.2 | pytest-asyncio==0.20.2 | ||||||
| pytest-cov==4.0.0 | pytest-cov==4.0.0 | ||||||
| pytest-mock==3.10.0 | pytest-mock==3.10.0 | ||||||
| pytest-random-order==1.0.4 | pytest-random-order==1.1.0 | ||||||
| isort==5.10.1 | isort==5.10.1 | ||||||
| # For datetime mocking | # For datetime mocking | ||||||
| time-machine==2.8.2 | time-machine==2.8.2 | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| numpy==1.23.5 | numpy==1.23.5 | ||||||
| pandas==1.5.1 | pandas==1.5.2 | ||||||
| pandas-ta==0.3.14b | pandas-ta==0.3.14b | ||||||
|  |  | ||||||
| ccxt==2.2.36 | ccxt==2.2.67 | ||||||
| # 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' | ||||||
| @@ -13,7 +13,7 @@ arrow==1.2.3 | |||||||
| cachetools==4.2.2 | cachetools==4.2.2 | ||||||
| requests==2.28.1 | requests==2.28.1 | ||||||
| urllib3==1.26.13 | urllib3==1.26.13 | ||||||
| jsonschema==4.17.1 | jsonschema==4.17.3 | ||||||
| TA-Lib==0.4.25 | TA-Lib==0.4.25 | ||||||
| technical==1.3.0 | technical==1.3.0 | ||||||
| tabulate==0.9.0 | tabulate==0.9.0 | ||||||
| @@ -30,13 +30,13 @@ py_find_1st==1.1.5 | |||||||
| # Load ticker files 30% faster | # Load ticker files 30% faster | ||||||
| python-rapidjson==1.9 | python-rapidjson==1.9 | ||||||
| # Properly format api responses | # Properly format api responses | ||||||
| orjson==3.8.2 | orjson==3.8.3 | ||||||
|  |  | ||||||
| # Notify systemd | # Notify systemd | ||||||
| sdnotify==0.3.2 | sdnotify==0.3.2 | ||||||
|  |  | ||||||
| # API Server | # API Server | ||||||
| fastapi==0.87.0 | fastapi==0.88.0 | ||||||
| pydantic==1.10.2 | pydantic==1.10.2 | ||||||
| uvicorn==0.20.0 | uvicorn==0.20.0 | ||||||
| pyjwt==2.6.0 | pyjwt==2.6.0 | ||||||
|   | |||||||
| @@ -207,12 +207,18 @@ def test_emit_df(mocker, default_conf, ohlcv_history): | |||||||
|     assert send_mock.call_count == 0 |     assert send_mock.call_count == 0 | ||||||
|  |  | ||||||
|     # Rpc is added, we call emit, should call send_msg |     # Rpc is added, we call emit, should call send_msg | ||||||
|     dataprovider._emit_df(pair, ohlcv_history) |     dataprovider._emit_df(pair, ohlcv_history, False) | ||||||
|     assert send_mock.call_count == 1 |     assert send_mock.call_count == 1 | ||||||
|  |  | ||||||
|  |     send_mock.reset_mock() | ||||||
|  |     dataprovider._emit_df(pair, ohlcv_history, True) | ||||||
|  |     assert send_mock.call_count == 2 | ||||||
|  |  | ||||||
|  |     send_mock.reset_mock() | ||||||
|  |  | ||||||
|     # No rpc added, emit called, should not call send_msg |     # No rpc added, emit called, should not call send_msg | ||||||
|     dataprovider_no_rpc._emit_df(pair, ohlcv_history) |     dataprovider_no_rpc._emit_df(pair, ohlcv_history, False) | ||||||
|     assert send_mock.call_count == 1 |     assert send_mock.call_count == 0 | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_refresh(mocker, default_conf, ohlcv_history): | def test_refresh(mocker, default_conf, ohlcv_history): | ||||||
|   | |||||||
| @@ -224,6 +224,11 @@ 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: | ||||||
|  |                 if val > 50: | ||||||
|  |                     # 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['asks']) == val | ||||||
|                     assert len(l2['bids']) == val |                     assert len(l2['bids']) == val | ||||||
|             else: |             else: | ||||||
|   | |||||||
| @@ -237,7 +237,6 @@ def test_start_backtesting(mocker, freqai_conf, model, num_files, strat, caplog) | |||||||
|     df = freqai.cache_corr_pairlist_dfs(df, freqai.dk) |     df = freqai.cache_corr_pairlist_dfs(df, freqai.dk) | ||||||
|     for i in range(5): |     for i in range(5): | ||||||
|         df[f'%-constant_{i}'] = i |         df[f'%-constant_{i}'] = i | ||||||
|         # df.loc[:, f'%-constant_{i}'] = i |  | ||||||
|  |  | ||||||
|     metadata = {"pair": "LTC/BTC"} |     metadata = {"pair": "LTC/BTC"} | ||||||
|     freqai.start_backtesting(df, metadata, freqai.dk) |     freqai.start_backtesting(df, metadata, freqai.dk) | ||||||
|   | |||||||
| @@ -588,7 +588,7 @@ def test_api_show_config(botclient): | |||||||
|     assert 'unfilledtimeout' in response |     assert 'unfilledtimeout' in response | ||||||
|     assert 'version' in response |     assert 'version' in response | ||||||
|     assert 'api_version' in response |     assert 'api_version' in response | ||||||
|     assert 2.1 <= response['api_version'] <= 2.2 |     assert 2.1 <= response['api_version'] < 3.0 | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_api_daily(botclient, mocker, ticker, fee, markets): | def test_api_daily(botclient, mocker, ticker, fee, markets): | ||||||
|   | |||||||
| @@ -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,6 +1907,7 @@ 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: | ||||||
|  |  | ||||||
|  |     with time_machine.travel("2022-09-01 05:00:00 +00:00", tick=False): | ||||||
|         telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) |         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 | ||||||
| @@ -2065,6 +2067,7 @@ 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) | ||||||
|  |  | ||||||
|  |     with time_machine.travel("2022-09-01 05:00:00 +00:00", tick=False): | ||||||
|         telegram.send_msg({ |         telegram.send_msg({ | ||||||
|             'type': RPCMessageType.EXIT_FILL, |             'type': RPCMessageType.EXIT_FILL, | ||||||
|             'trade_id': 1, |             'trade_id': 1, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user