Merge branch 'develop' into python-version
This commit is contained in:
commit
912b06b34b
@ -11,7 +11,10 @@ update: all
|
|||||||
# allowed: True, False
|
# allowed: True, False
|
||||||
pin: True
|
pin: True
|
||||||
|
|
||||||
schedule: "every day"
|
# update schedule
|
||||||
|
# default: empty
|
||||||
|
# allowed: "every day", "every week", ..
|
||||||
|
schedule: "every week"
|
||||||
|
|
||||||
|
|
||||||
search: False
|
search: False
|
||||||
|
@ -3,4 +3,4 @@ FROM freqtradeorg/freqtrade:develop
|
|||||||
RUN apt-get update \
|
RUN apt-get update \
|
||||||
&& apt-get -y install git \
|
&& apt-get -y install git \
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& pip install git+https://github.com/berlinguyinca/technical
|
&& pip install git+https://github.com/freqtrade/technical
|
||||||
|
@ -129,7 +129,6 @@ The project is currently setup in two main branches:
|
|||||||
- `master` - This branch contains the latest stable release. The bot 'should' be stable on this branch, and is generally well tested.
|
- `master` - This branch contains the latest stable release. The bot 'should' be stable on this branch, and is generally well tested.
|
||||||
- `feat/*` - These are feature branches, which are being worked on heavily. Please don't use these unless you want to test a specific feature.
|
- `feat/*` - These are feature branches, which are being worked on heavily. Please don't use these unless you want to test a specific feature.
|
||||||
|
|
||||||
|
|
||||||
## A note on Binance
|
## A note on Binance
|
||||||
|
|
||||||
For Binance, please add `"BNB/<STAKE>"` to your blacklist to avoid issues.
|
For Binance, please add `"BNB/<STAKE>"` to your blacklist to avoid issues.
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
"sell": 30
|
"sell": 30
|
||||||
},
|
},
|
||||||
"bid_strategy": {
|
"bid_strategy": {
|
||||||
"ask_last_balance": 0.0,
|
|
||||||
"use_order_book": false,
|
"use_order_book": false,
|
||||||
|
"ask_last_balance": 0.0,
|
||||||
"order_book_top": 1,
|
"order_book_top": 1,
|
||||||
"check_depth_of_market": {
|
"check_depth_of_market": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
@ -22,8 +22,8 @@
|
|||||||
"sell": 30
|
"sell": 30
|
||||||
},
|
},
|
||||||
"bid_strategy": {
|
"bid_strategy": {
|
||||||
"ask_last_balance": 0.0,
|
|
||||||
"use_order_book": false,
|
"use_order_book": false,
|
||||||
|
"ask_last_balance": 0.0,
|
||||||
"order_book_top": 1,
|
"order_book_top": 1,
|
||||||
"check_depth_of_market": {
|
"check_depth_of_market": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
@ -5,15 +5,14 @@
|
|||||||
"fiat_display_currency": "EUR",
|
"fiat_display_currency": "EUR",
|
||||||
"ticker_interval" : "5m",
|
"ticker_interval" : "5m",
|
||||||
"dry_run": true,
|
"dry_run": true,
|
||||||
"db_url": "sqlite:///tradesv3.dryrun.sqlite",
|
|
||||||
"trailing_stop": false,
|
"trailing_stop": false,
|
||||||
"unfilledtimeout": {
|
"unfilledtimeout": {
|
||||||
"buy": 10,
|
"buy": 10,
|
||||||
"sell": 30
|
"sell": 30
|
||||||
},
|
},
|
||||||
"bid_strategy": {
|
"bid_strategy": {
|
||||||
"ask_last_balance": 0.0,
|
|
||||||
"use_order_book": false,
|
"use_order_book": false,
|
||||||
|
"ask_last_balance": 0.0,
|
||||||
"order_book_top": 1,
|
"order_book_top": 1,
|
||||||
"check_depth_of_market": {
|
"check_depth_of_market": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
@ -60,8 +59,8 @@
|
|||||||
},
|
},
|
||||||
"telegram": {
|
"telegram": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"token": "",
|
"token": "your_telegram_token",
|
||||||
"chat_id": ""
|
"chat_id": "your_telegram_chat_id"
|
||||||
},
|
},
|
||||||
"initial_state": "running",
|
"initial_state": "running",
|
||||||
"forcebuy_enable": false,
|
"forcebuy_enable": false,
|
||||||
|
@ -103,7 +103,7 @@ If the bot does not find your strategy file, it will display in an error
|
|||||||
message the reason (File not found, or errors in your code).
|
message the reason (File not found, or errors in your code).
|
||||||
|
|
||||||
Learn more about strategy file in
|
Learn more about strategy file in
|
||||||
[optimize your bot](bot-optimization.md).
|
[Strategy Customization](strategy-customization.md).
|
||||||
|
|
||||||
### How to use **--strategy-path**?
|
### How to use **--strategy-path**?
|
||||||
|
|
||||||
@ -296,4 +296,4 @@ in [misc.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/misc.
|
|||||||
## Next step
|
## Next step
|
||||||
|
|
||||||
The optimal strategy of the bot will change with time depending of the market trends. The next step is to
|
The optimal strategy of the bot will change with time depending of the market trends. The next step is to
|
||||||
[optimize your bot](bot-optimization.md).
|
[Strategy Customization](strategy-customization.md).
|
||||||
|
@ -191,14 +191,28 @@ If this is configured, all 4 values (`buy`, `sell`, `stoploss` and
|
|||||||
`stoploss_on_exchange`) need to be present, otherwise the bot will warn about it and fail to start.
|
`stoploss_on_exchange`) need to be present, otherwise the bot will warn about it and fail to start.
|
||||||
The below is the default which is used if this is not configured in either strategy or configuration file.
|
The below is the default which is used if this is not configured in either strategy or configuration file.
|
||||||
|
|
||||||
|
Syntax for Strategy:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
"order_types": {
|
order_types = {
|
||||||
"buy": "limit",
|
"buy": "limit",
|
||||||
"sell": "limit",
|
"sell": "limit",
|
||||||
"stoploss": "market",
|
"stoploss": "market",
|
||||||
"stoploss_on_exchange": False,
|
"stoploss_on_exchange": False,
|
||||||
"stoploss_on_exchange_interval": 60
|
"stoploss_on_exchange_interval": 60
|
||||||
},
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"order_types": {
|
||||||
|
"buy": "limit",
|
||||||
|
"sell": "limit",
|
||||||
|
"stoploss": "market",
|
||||||
|
"stoploss_on_exchange": false,
|
||||||
|
"stoploss_on_exchange_interval": 60
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
|
204
docs/docker.md
Normal file
204
docs/docker.md
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
# Using FreqTrade with Docker
|
||||||
|
|
||||||
|
## Install Docker
|
||||||
|
|
||||||
|
Start by downloading and installing Docker CE for your platform:
|
||||||
|
|
||||||
|
* [Mac](https://docs.docker.com/docker-for-mac/install/)
|
||||||
|
* [Windows](https://docs.docker.com/docker-for-windows/install/)
|
||||||
|
* [Linux](https://docs.docker.com/install/)
|
||||||
|
|
||||||
|
Once you have Docker installed, simply prepare the config file (e.g. `config.json`) and run the image for `freqtrade` as explained below.
|
||||||
|
|
||||||
|
## Download the official FreqTrade docker image
|
||||||
|
|
||||||
|
Pull the image from docker hub.
|
||||||
|
|
||||||
|
Branches / tags available can be checked out on [Dockerhub](https://hub.docker.com/r/freqtradeorg/freqtrade/tags/).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker pull freqtradeorg/freqtrade:develop
|
||||||
|
# Optionally tag the repository so the run-commands remain shorter
|
||||||
|
docker tag freqtradeorg/freqtrade:develop freqtrade
|
||||||
|
```
|
||||||
|
|
||||||
|
To update the image, simply run the above commands again and restart your running container.
|
||||||
|
|
||||||
|
Should you require additional libraries, please [build the image yourself](#build-your-own-docker-image).
|
||||||
|
|
||||||
|
### Prepare the configuration files
|
||||||
|
|
||||||
|
Even though you will use docker, you'll still need some files from the github repository.
|
||||||
|
|
||||||
|
#### Clone the git repository
|
||||||
|
|
||||||
|
Linux/Mac/Windows with WSL
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/freqtrade/freqtrade.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Windows with docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone --config core.autocrlf=input https://github.com/freqtrade/freqtrade.git
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Copy `config.json.example` to `config.json`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd freqtrade
|
||||||
|
cp -n config.json.example config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
> To understand the configuration options, please refer to the [Bot Configuration](configuration.md) page.
|
||||||
|
|
||||||
|
#### Create your database file
|
||||||
|
|
||||||
|
Production
|
||||||
|
|
||||||
|
```bash
|
||||||
|
touch tradesv3.sqlite
|
||||||
|
````
|
||||||
|
|
||||||
|
Dry-Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
touch tradesv3.dryrun.sqlite
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
Make sure to use the path to this file when starting the bot in docker.
|
||||||
|
|
||||||
|
### Build your own Docker image
|
||||||
|
|
||||||
|
Best start by pulling the official docker image from dockerhub as explained [here](#download-the-official-docker-image) to speed up building.
|
||||||
|
|
||||||
|
To add additional libraries to your docker image, best check out [Dockerfile.technical](https://github.com/freqtrade/freqtrade/blob/develop/Dockerfile.technical) which adds the [technical](https://github.com/freqtrade/technical) module to the image.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t freqtrade -f Dockerfile.technical .
|
||||||
|
```
|
||||||
|
|
||||||
|
If you are developing using Docker, use `Dockerfile.develop` to build a dev Docker image, which will also set up develop dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -f Dockerfile.develop -t freqtrade-dev .
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
For security reasons, your configuration file will not be included in the image, you will need to bind mount it. It is also advised to bind mount an SQLite database file (see the "5. Run a restartable docker image" section) to keep it between updates.
|
||||||
|
|
||||||
|
#### Verify the Docker image
|
||||||
|
|
||||||
|
After the build process you can verify that the image was created with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker images
|
||||||
|
```
|
||||||
|
|
||||||
|
The output should contain the freqtrade image.
|
||||||
|
|
||||||
|
### Run the Docker image
|
||||||
|
|
||||||
|
You can run a one-off container that is immediately deleted upon exiting with the following command (`config.json` must be in the current working directory):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --rm -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! Warning
|
||||||
|
In this example, the database will be created inside the docker instance and will be lost when you will refresh your image.
|
||||||
|
|
||||||
|
#### Adjust timezone
|
||||||
|
|
||||||
|
By default, the container will use UTC timezone.
|
||||||
|
Should you find this irritating please add the following to your docker commands:
|
||||||
|
|
||||||
|
##### Linux
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
-v /etc/timezone:/etc/timezone:ro
|
||||||
|
|
||||||
|
# Complete command:
|
||||||
|
docker run --rm -v /etc/timezone:/etc/timezone:ro -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
|
||||||
|
```
|
||||||
|
|
||||||
|
##### MacOS
|
||||||
|
|
||||||
|
There is known issue in OSX Docker versions after 17.09.1, whereby `/etc/localtime` cannot be shared causing Docker to not start. A work-around for this is to start with the following cmd.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --rm -e TZ=`ls -la /etc/localtime | cut -d/ -f8-9` -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
|
||||||
|
```
|
||||||
|
|
||||||
|
More information on this docker issue and work-around can be read [here](https://github.com/docker/for-mac/issues/2396).
|
||||||
|
|
||||||
|
### Run a restartable docker image
|
||||||
|
|
||||||
|
To run a restartable instance in the background (feel free to place your configuration and database files wherever it feels comfortable on your filesystem).
|
||||||
|
|
||||||
|
#### Move your config file and database
|
||||||
|
|
||||||
|
The following will assume that you place your configuration / database files to `~/.freqtrade`, which is a hidden folder in your home directory. Feel free to use a different folder and replace the folder in the upcomming commands.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir ~/.freqtrade
|
||||||
|
mv config.json ~/.freqtrade
|
||||||
|
mv tradesv3.sqlite ~/.freqtrade
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Run the docker image
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name freqtrade \
|
||||||
|
-v ~/.freqtrade/config.json:/freqtrade/config.json \
|
||||||
|
-v ~/.freqtrade/user_data/:/freqtrade/user_data \
|
||||||
|
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \
|
||||||
|
freqtrade --db-url sqlite:///tradesv3.sqlite --strategy MyAwesomeStrategy
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
db-url defaults to `sqlite:///tradesv3.sqlite` but it defaults to `sqlite://` if `dry_run=True` is being used.
|
||||||
|
To override this behaviour use a custom db-url value: i.e.: `--db-url sqlite:///tradesv3.dryrun.sqlite`
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
All available bot command line parameters can be added to the end of the `docker run` command.
|
||||||
|
|
||||||
|
### Monitor your Docker instance
|
||||||
|
|
||||||
|
You can use the following commands to monitor and manage your container:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker logs freqtrade
|
||||||
|
docker logs -f freqtrade
|
||||||
|
docker restart freqtrade
|
||||||
|
docker stop freqtrade
|
||||||
|
docker start freqtrade
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information on how to operate Docker, please refer to the [official Docker documentation](https://docs.docker.com/).
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
You do not need to rebuild the image for configuration changes, it will suffice to edit `config.json` and restart the container.
|
||||||
|
|
||||||
|
### Backtest with docker
|
||||||
|
|
||||||
|
The following assumes that the download/setup of the docker image have been completed successfully.
|
||||||
|
Also, backtest-data should be available at `~/.freqtrade/user_data/`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name freqtrade \
|
||||||
|
-v /etc/localtime:/etc/localtime:ro \
|
||||||
|
-v ~/.freqtrade/config.json:/freqtrade/config.json \
|
||||||
|
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \
|
||||||
|
-v ~/.freqtrade/user_data/:/freqtrade/user_data/ \
|
||||||
|
freqtrade --strategy AwsomelyProfitableStrategy backtesting
|
||||||
|
```
|
||||||
|
|
||||||
|
Head over to the [Backtesting Documentation](backtesting.md) for more details.
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
Additional bot command line parameters can be appended after the image name (`freqtrade` in the above example).
|
@ -122,9 +122,10 @@ So let's write the buy strategy using these values:
|
|||||||
dataframe['macd'], dataframe['macdsignal']
|
dataframe['macd'], dataframe['macdsignal']
|
||||||
))
|
))
|
||||||
|
|
||||||
dataframe.loc[
|
if conditions:
|
||||||
reduce(lambda x, y: x & y, conditions),
|
dataframe.loc[
|
||||||
'buy'] = 1
|
reduce(lambda x, y: x & y, conditions),
|
||||||
|
'buy'] = 1
|
||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
|
@ -36,12 +36,14 @@ Freqtrade is a cryptocurrency trading bot written in Python.
|
|||||||
- Daily summary of profit/loss: Receive the daily summary of your profit/loss.
|
- Daily summary of profit/loss: Receive the daily summary of your profit/loss.
|
||||||
- Performance status report: Receive the performance status of your current trades.
|
- Performance status report: Receive the performance status of your current trades.
|
||||||
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
### Up to date clock
|
### Up to date clock
|
||||||
|
|
||||||
The clock on the system running the bot must be accurate, synchronized to a NTP server frequently enough to avoid problems with communication to the exchanges.
|
The clock on the system running the bot must be accurate, synchronized to a NTP server frequently enough to avoid problems with communication to the exchanges.
|
||||||
|
|
||||||
### Hardware requirements
|
### Hardware requirements
|
||||||
|
|
||||||
To run this bot we recommend you a cloud instance with a minimum of:
|
To run this bot we recommend you a cloud instance with a minimum of:
|
||||||
|
|
||||||
- 2GB RAM
|
- 2GB RAM
|
||||||
@ -49,6 +51,7 @@ To run this bot we recommend you a cloud instance with a minimum of:
|
|||||||
- 2vCPU
|
- 2vCPU
|
||||||
|
|
||||||
### Software requirements
|
### Software requirements
|
||||||
|
|
||||||
- Python 3.6.x
|
- Python 3.6.x
|
||||||
- pip (pip3)
|
- pip (pip3)
|
||||||
- git
|
- git
|
||||||
@ -58,10 +61,12 @@ To run this bot we recommend you a cloud instance with a minimum of:
|
|||||||
|
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
Help / Slack
|
Help / Slack
|
||||||
For any questions not covered by the documentation or for further information about the bot, we encourage you to join our Slack channel.
|
For any questions not covered by the documentation or for further information about the bot, we encourage you to join our Slack channel.
|
||||||
|
|
||||||
Click [here](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE) to join Slack channel.
|
Click [here](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE) to join Slack channel.
|
||||||
|
|
||||||
## Ready to try?
|
## Ready to try?
|
||||||
|
|
||||||
Begin by reading our installation guide [here](installation).
|
Begin by reading our installation guide [here](installation).
|
||||||
|
@ -1,58 +1,21 @@
|
|||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
This page explains how to prepare your environment for running the bot.
|
This page explains how to prepare your environment for running the bot.
|
||||||
|
|
||||||
## Prerequisite
|
## Prerequisite
|
||||||
|
|
||||||
Before running your bot in production you will need to setup few
|
Before running your bot in production you will need to setup few
|
||||||
external API. In production mode, the bot required valid Bittrex API
|
external API. In production mode, the bot will require valid Exchange API
|
||||||
credentials and a Telegram bot (optional but recommended).
|
credentials. We also reccomend a [Telegram bot](telegram-usage.md#setup-your-telegram-bot) (optional but recommended).
|
||||||
|
|
||||||
- [Setup your exchange account](#setup-your-exchange-account)
|
- [Setup your exchange account](#setup-your-exchange-account)
|
||||||
- [Backtesting commands](#setup-your-telegram-bot)
|
|
||||||
|
|
||||||
### Setup your exchange account
|
### Setup your exchange account
|
||||||
*To be completed, please feel free to complete this section.*
|
|
||||||
|
|
||||||
### Setup your Telegram bot
|
You will need to create API Keys (Usually you get `key` and `secret`) from the Exchange website and insert this into the appropriate fields in the configuration or when asked by the installation script.
|
||||||
The only things you need is a working Telegram bot and its API token.
|
|
||||||
Below we explain how to create your Telegram Bot, and how to get your
|
|
||||||
Telegram user id.
|
|
||||||
|
|
||||||
### 1. Create your Telegram bot
|
|
||||||
|
|
||||||
**1.1. Start a chat with https://telegram.me/BotFather**
|
|
||||||
|
|
||||||
**1.2. Send the message `/newbot`. ** *BotFather response:*
|
|
||||||
```
|
|
||||||
Alright, a new bot. How are we going to call it? Please choose a name for your bot.
|
|
||||||
```
|
|
||||||
|
|
||||||
**1.3. Choose the public name of your bot (e.x. `Freqtrade bot`)**
|
|
||||||
*BotFather response:*
|
|
||||||
```
|
|
||||||
Good. Now let's choose a username for your bot. It must end in `bot`. Like this, for example: TetrisBot or tetris_bot.
|
|
||||||
```
|
|
||||||
**1.4. Choose the name id of your bot (e.x "`My_own_freqtrade_bot`")**
|
|
||||||
|
|
||||||
**1.5. Father bot will return you the token (API key)**<br/>
|
|
||||||
Copy it and keep it you will use it for the config parameter `token`.
|
|
||||||
*BotFather response:*
|
|
||||||
```hl_lines="4"
|
|
||||||
Done! Congratulations on your new bot. You will find it at t.me/My_own_freqtrade_bot. You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this.
|
|
||||||
|
|
||||||
Use this token to access the HTTP API:
|
|
||||||
521095879:AAEcEZEL7ADJ56FtG_qD0bQJSKETbXCBCi0
|
|
||||||
|
|
||||||
For a description of the Bot API, see this page: https://core.telegram.org/bots/api
|
|
||||||
```
|
|
||||||
**1.6. Don't forget to start the conversation with your bot, by clicking /START button**
|
|
||||||
|
|
||||||
### 2. Get your user id
|
|
||||||
**2.1. Talk to https://telegram.me/userinfobot**
|
|
||||||
|
|
||||||
**2.2. Get your "Id", you will use it for the config parameter
|
|
||||||
`chat_id`.**
|
|
||||||
<hr/>
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
Freqtrade provides a Linux/MacOS script to install all dependencies and help you to configure the bot.
|
Freqtrade provides a Linux/MacOS script to install all dependencies and help you to configure the bot.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -61,9 +24,10 @@ cd freqtrade
|
|||||||
git checkout develop
|
git checkout develop
|
||||||
./setup.sh --install
|
./setup.sh --install
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
Windows installation is explained [here](#windows).
|
Windows installation is explained [here](#windows).
|
||||||
<hr/>
|
|
||||||
## Easy Installation - Linux Script
|
## Easy Installation - Linux Script
|
||||||
|
|
||||||
If you are on Debian, Ubuntu or MacOS a freqtrade provides a script to Install, Update, Configure, and Reset your bot.
|
If you are on Debian, Ubuntu or MacOS a freqtrade provides a script to Install, Update, Configure, and Reset your bot.
|
||||||
@ -101,189 +65,6 @@ Config parameter is a `config.json` configurator. This script will ask you quest
|
|||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## Automatic Installation - Docker
|
|
||||||
|
|
||||||
Start by downloading Docker for your platform:
|
|
||||||
|
|
||||||
* [Mac](https://www.docker.com/products/docker#/mac)
|
|
||||||
* [Windows](https://www.docker.com/products/docker#/windows)
|
|
||||||
* [Linux](https://www.docker.com/products/docker#/linux)
|
|
||||||
|
|
||||||
Once you have Docker installed, simply create the config file (e.g. `config.json`) and then create a Docker image for `freqtrade` using the Dockerfile in this repo.
|
|
||||||
|
|
||||||
### 1. Prepare the Bot
|
|
||||||
|
|
||||||
**1.1. Clone the git repository**
|
|
||||||
|
|
||||||
Linux/Mac/Windows with WSL
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/freqtrade/freqtrade.git
|
|
||||||
```
|
|
||||||
|
|
||||||
Windows with docker
|
|
||||||
```bash
|
|
||||||
git clone --config core.autocrlf=input https://github.com/freqtrade/freqtrade.git
|
|
||||||
```
|
|
||||||
|
|
||||||
**1.2. (Optional) Checkout the develop branch**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git checkout develop
|
|
||||||
```
|
|
||||||
|
|
||||||
**1.3. Go into the new directory**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd freqtrade
|
|
||||||
```
|
|
||||||
|
|
||||||
**1.4. Copy `config.json.example` to `config.json`**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp -n config.json.example config.json
|
|
||||||
```
|
|
||||||
|
|
||||||
> To edit the config please refer to the [Bot Configuration](configuration.md) page.
|
|
||||||
|
|
||||||
**1.5. Create your database file *(optional - the bot will create it if it is missing)**
|
|
||||||
|
|
||||||
Production
|
|
||||||
|
|
||||||
```bash
|
|
||||||
touch tradesv3.sqlite
|
|
||||||
````
|
|
||||||
|
|
||||||
Dry-Run
|
|
||||||
|
|
||||||
```bash
|
|
||||||
touch tradesv3.dryrun.sqlite
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Download or build the docker image
|
|
||||||
|
|
||||||
Either use the prebuilt image from docker hub - or build the image yourself if you would like more control on which version is used.
|
|
||||||
|
|
||||||
Branches / tags available can be checked out on [Dockerhub](https://hub.docker.com/r/freqtradeorg/freqtrade/tags/).
|
|
||||||
|
|
||||||
**2.1. Download the docker image**
|
|
||||||
|
|
||||||
Pull the image from docker hub and (optionally) change the name of the image
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker pull freqtradeorg/freqtrade:develop
|
|
||||||
# Optionally tag the repository so the run-commands remain shorter
|
|
||||||
docker tag freqtradeorg/freqtrade:develop freqtrade
|
|
||||||
```
|
|
||||||
|
|
||||||
To update the image, simply run the above commands again and restart your running container.
|
|
||||||
|
|
||||||
**2.2. Build the Docker image**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd freqtrade
|
|
||||||
docker build -t freqtrade .
|
|
||||||
```
|
|
||||||
|
|
||||||
If you are developing using Docker, use `Dockerfile.develop` to build a dev Docker image, which will also set up develop dependencies:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker build -f ./Dockerfile.develop -t freqtrade-dev .
|
|
||||||
```
|
|
||||||
|
|
||||||
For security reasons, your configuration file will not be included in the image, you will need to bind mount it. It is also advised to bind mount an SQLite database file (see the "5. Run a restartable docker image" section) to keep it between updates.
|
|
||||||
|
|
||||||
### 3. Verify the Docker image
|
|
||||||
|
|
||||||
After the build process you can verify that the image was created with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker images
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Run the Docker image
|
|
||||||
|
|
||||||
You can run a one-off container that is immediately deleted upon exiting with the following command (`config.json` must be in the current working directory):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run --rm -v /etc/localtime:/etc/localtime:ro -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
|
|
||||||
```
|
|
||||||
|
|
||||||
There is known issue in OSX Docker versions after 17.09.1, whereby /etc/localtime cannot be shared causing Docker to not start. A work-around for this is to start with the following cmd.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run --rm -e TZ=`ls -la /etc/localtime | cut -d/ -f8-9` -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
|
|
||||||
```
|
|
||||||
|
|
||||||
More information on this docker issue and work-around can be read [here](https://github.com/docker/for-mac/issues/2396).
|
|
||||||
|
|
||||||
In this example, the database will be created inside the docker instance and will be lost when you will refresh your image.
|
|
||||||
|
|
||||||
### 5. Run a restartable docker image
|
|
||||||
|
|
||||||
To run a restartable instance in the background (feel free to place your configuration and database files wherever it feels comfortable on your filesystem).
|
|
||||||
|
|
||||||
**5.1. Move your config file and database**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir ~/.freqtrade
|
|
||||||
mv config.json ~/.freqtrade
|
|
||||||
mv tradesv3.sqlite ~/.freqtrade
|
|
||||||
```
|
|
||||||
|
|
||||||
**5.2. Run the docker image**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run -d \
|
|
||||||
--name freqtrade \
|
|
||||||
-v /etc/localtime:/etc/localtime:ro \
|
|
||||||
-v ~/.freqtrade/config.json:/freqtrade/config.json \
|
|
||||||
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \
|
|
||||||
freqtrade --db-url sqlite:///tradesv3.sqlite
|
|
||||||
```
|
|
||||||
|
|
||||||
!!! Note
|
|
||||||
db-url defaults to `sqlite:///tradesv3.sqlite` but it defaults to `sqlite://` if `dry_run=True` is being used.
|
|
||||||
To override this behaviour use a custom db-url value: i.e.: `--db-url sqlite:///tradesv3.dryrun.sqlite`
|
|
||||||
|
|
||||||
### 6. Monitor your Docker instance
|
|
||||||
|
|
||||||
You can then use the following commands to monitor and manage your container:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker logs freqtrade
|
|
||||||
docker logs -f freqtrade
|
|
||||||
docker restart freqtrade
|
|
||||||
docker stop freqtrade
|
|
||||||
docker start freqtrade
|
|
||||||
```
|
|
||||||
|
|
||||||
For more information on how to operate Docker, please refer to the [official Docker documentation](https://docs.docker.com/).
|
|
||||||
|
|
||||||
!!! Note
|
|
||||||
You do not need to rebuild the image for configuration changes, it will suffice to edit `config.json` and restart the container.
|
|
||||||
|
|
||||||
### 7. Backtest with docker
|
|
||||||
|
|
||||||
The following assumes that the above steps (1-4) have been completed successfully.
|
|
||||||
Also, backtest-data should be available at `~/.freqtrade/user_data/`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run -d \
|
|
||||||
--name freqtrade \
|
|
||||||
-v /etc/localtime:/etc/localtime:ro \
|
|
||||||
-v ~/.freqtrade/config.json:/freqtrade/config.json \
|
|
||||||
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \
|
|
||||||
-v ~/.freqtrade/user_data/:/freqtrade/user_data/ \
|
|
||||||
freqtrade --strategy AwsomelyProfitableStrategy backtesting
|
|
||||||
```
|
|
||||||
|
|
||||||
Head over to the [Backtesting Documentation](backtesting.md) for more details.
|
|
||||||
|
|
||||||
!!! Note
|
|
||||||
Additional parameters can be appended after the image name (`freqtrade` in the above example).
|
|
||||||
|
|
||||||
------
|
|
||||||
|
|
||||||
## Custom Installation
|
## Custom Installation
|
||||||
|
|
||||||
We've included/collected install instructions for Ubuntu 16.04, MacOS, and Windows. These are guidelines and your success may vary with other distros.
|
We've included/collected install instructions for Ubuntu 16.04, MacOS, and Windows. These are guidelines and your success may vary with other distros.
|
||||||
@ -409,7 +190,7 @@ If this is the first time you run the bot, ensure you are running it in Dry-run
|
|||||||
python3.6 freqtrade -c config.json
|
python3.6 freqtrade -c config.json
|
||||||
```
|
```
|
||||||
|
|
||||||
*Note*: If you run the bot on a server, you should consider using [Docker](#automatic-installation---docker) a terminal multiplexer like `screen` or [`tmux`](https://en.wikipedia.org/wiki/Tmux) to avoid that the bot is stopped on logout.
|
*Note*: If you run the bot on a server, you should consider using [Docker](docker.md) or a terminal multiplexer like `screen` or [`tmux`](https://en.wikipedia.org/wiki/Tmux) to avoid that the bot is stopped on logout.
|
||||||
|
|
||||||
#### 7. [Optional] Configure `freqtrade` as a `systemd` service
|
#### 7. [Optional] Configure `freqtrade` as a `systemd` service
|
||||||
|
|
||||||
@ -437,14 +218,13 @@ The `freqtrade.service.watchdog` file contains an example of the service unit co
|
|||||||
as the watchdog.
|
as the watchdog.
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
The sd_notify communication between the bot and the systemd service manager will not work if the bot runs in a
|
The sd_notify communication between the bot and the systemd service manager will not work if the bot runs in a Docker container.
|
||||||
Docker container.
|
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## Windows
|
## Windows
|
||||||
|
|
||||||
We recommend that Windows users use [Docker](#docker) as this will work much easier and smoother (also more secure).
|
We recommend that Windows users use [Docker](docker.md) as this will work much easier and smoother (also more secure).
|
||||||
|
|
||||||
If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work.
|
If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work.
|
||||||
If that is not available on your system, feel free to try the instructions below, which led to success for some.
|
If that is not available on your system, feel free to try the instructions below, which led to success for some.
|
||||||
@ -488,7 +268,7 @@ error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++
|
|||||||
|
|
||||||
Unfortunately, many packages requiring compilation don't provide a pre-build wheel. It is therefore mandatory to have a C/C++ compiler installed and available for your python environment to use.
|
Unfortunately, many packages requiring compilation don't provide a pre-build wheel. It is therefore mandatory to have a C/C++ compiler installed and available for your python environment to use.
|
||||||
|
|
||||||
The easiest way is to download install Microsoft Visual Studio Community [here](https://visualstudio.microsoft.com/downloads/) and make sure to install "Common Tools for Visual C++" to enable building c code on Windows. Unfortunately, this is a heavy download / dependency (~4Gb) so you might want to consider WSL or docker first.
|
The easiest way is to download install Microsoft Visual Studio Community [here](https://visualstudio.microsoft.com/downloads/) and make sure to install "Common Tools for Visual C++" to enable building c code on Windows. Unfortunately, this is a heavy download / dependency (~4Gb) so you might want to consider WSL or [docker](docker.md) first.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -1,63 +1,83 @@
|
|||||||
# Plotting
|
# Plotting
|
||||||
This page explains how to plot prices, indicator, profits.
|
|
||||||
|
This page explains how to plot prices, indicators and profits.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Plotting scripts use Plotly library. Install/upgrade it with:
|
Plotting scripts use Plotly library. Install/upgrade it with:
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
pip install -U -r requirements-plot.txt
|
||||||
```
|
```
|
||||||
pip install --upgrade plotly
|
|
||||||
```
|
|
||||||
|
|
||||||
At least version 2.3.0 is required.
|
|
||||||
|
|
||||||
## Plot price and indicators
|
## Plot price and indicators
|
||||||
|
|
||||||
Usage for the price plotter:
|
Usage for the price plotter:
|
||||||
|
|
||||||
```
|
``` bash
|
||||||
script/plot_dataframe.py [-h] [-p pairs] [--live]
|
python3 script/plot_dataframe.py [-h] [-p pairs] [--live]
|
||||||
```
|
```
|
||||||
|
|
||||||
Example
|
Example
|
||||||
```
|
|
||||||
python scripts/plot_dataframe.py -p BTC/ETH
|
``` bash
|
||||||
|
python3 scripts/plot_dataframe.py -p BTC/ETH
|
||||||
```
|
```
|
||||||
|
|
||||||
The `-p` pairs argument, can be used to specify
|
The `-p` pairs argument can be used to specify pairs you would like to plot.
|
||||||
pairs you would like to plot.
|
|
||||||
|
|
||||||
**Advanced use**
|
Specify custom indicators.
|
||||||
|
Use `--indicators1` for the main plot and `--indicators2` for the subplot below (if values are in a different range than prices).
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
python3 scripts/plot_dataframe.py -p BTC/ETH --indicators1 sma,ema --indicators2 macd
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced use
|
||||||
|
|
||||||
To plot multiple pairs, separate them with a comma:
|
To plot multiple pairs, separate them with a comma:
|
||||||
```
|
|
||||||
python scripts/plot_dataframe.py -p BTC/ETH,XRP/ETH
|
``` bash
|
||||||
|
python3 scripts/plot_dataframe.py -p BTC/ETH,XRP/ETH
|
||||||
```
|
```
|
||||||
|
|
||||||
To plot the current live price use the `--live` flag:
|
To plot the current live price use the `--live` flag:
|
||||||
```
|
|
||||||
python scripts/plot_dataframe.py -p BTC/ETH --live
|
``` bash
|
||||||
|
python3 scripts/plot_dataframe.py -p BTC/ETH --live
|
||||||
```
|
```
|
||||||
|
|
||||||
To plot a timerange (to zoom in):
|
To plot a timerange (to zoom in):
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
python3 scripts/plot_dataframe.py -p BTC/ETH --timerange=100-200
|
||||||
```
|
```
|
||||||
python scripts/plot_dataframe.py -p BTC/ETH --timerange=100-200
|
|
||||||
```
|
|
||||||
Timerange doesn't work with live data.
|
Timerange doesn't work with live data.
|
||||||
|
|
||||||
To plot trades stored in a database use `--db-url` argument:
|
To plot trades stored in a database use `--db-url` argument:
|
||||||
```
|
|
||||||
python scripts/plot_dataframe.py --db-url sqlite:///tradesv3.dry_run.sqlite -p BTC/ETH
|
``` bash
|
||||||
|
python3 scripts/plot_dataframe.py --db-url sqlite:///tradesv3.dry_run.sqlite -p BTC/ETH
|
||||||
```
|
```
|
||||||
|
|
||||||
To plot a test strategy the strategy should have first be backtested.
|
To plot trades from a backtesting result, use `--export-filename <filename>`
|
||||||
The results may then be plotted with the -s argument:
|
|
||||||
|
``` bash
|
||||||
|
python3 scripts/plot_dataframe.py --export-filename user_data/backtest_data/backtest-result.json -p BTC/ETH
|
||||||
```
|
```
|
||||||
python scripts/plot_dataframe.py -s Strategy_Name -p BTC/ETH --datadir user_data/data/<exchange_name>/
|
|
||||||
|
To plot a custom strategy the strategy should have first be backtested.
|
||||||
|
The results may then be plotted with the -s argument:
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
python3 scripts/plot_dataframe.py -s Strategy_Name -p BTC/ETH --datadir user_data/data/<exchange_name>/
|
||||||
```
|
```
|
||||||
|
|
||||||
## Plot profit
|
## Plot profit
|
||||||
|
|
||||||
The profit plotter show a picture with three plots:
|
The profit plotter shows a picture with three plots:
|
||||||
|
|
||||||
1) Average closing price for all pairs
|
1) Average closing price for all pairs
|
||||||
2) The summarized profit made by backtesting.
|
2) The summarized profit made by backtesting.
|
||||||
Note that this is not the real-world profit, but
|
Note that this is not the real-world profit, but
|
||||||
@ -67,7 +87,7 @@ The profit plotter show a picture with three plots:
|
|||||||
The first graph is good to get a grip of how the overall market
|
The first graph is good to get a grip of how the overall market
|
||||||
progresses.
|
progresses.
|
||||||
|
|
||||||
The second graph will show how you algorithm works or doesnt.
|
The second graph will show how your algorithm works or doesn't.
|
||||||
Perhaps you want an algorithm that steadily makes small profits,
|
Perhaps you want an algorithm that steadily makes small profits,
|
||||||
or one that acts less seldom, but makes big swings.
|
or one that acts less seldom, but makes big swings.
|
||||||
|
|
||||||
@ -76,13 +96,14 @@ that makes profit spikes.
|
|||||||
|
|
||||||
Usage for the profit plotter:
|
Usage for the profit plotter:
|
||||||
|
|
||||||
```
|
``` bash
|
||||||
script/plot_profit.py [-h] [-p pair] [--datadir directory] [--ticker_interval num]
|
python3 script/plot_profit.py [-h] [-p pair] [--datadir directory] [--ticker_interval num]
|
||||||
```
|
```
|
||||||
|
|
||||||
The `-p` pair argument, can be used to plot a single pair
|
The `-p` pair argument, can be used to plot a single pair
|
||||||
|
|
||||||
Example
|
Example
|
||||||
```
|
|
||||||
|
``` bash
|
||||||
python3 scripts/plot_profit.py --datadir ../freqtrade/freqtrade/tests/testdata-20171221/ -p LTC/BTC
|
python3 scripts/plot_profit.py --datadir ../freqtrade/freqtrade/tests/testdata-20171221/ -p LTC/BTC
|
||||||
```
|
```
|
||||||
|
@ -53,6 +53,12 @@ file as reference.**
|
|||||||
It is therefore best to use vectorized operations (across the whole dataframe, not loops) and
|
It is therefore best to use vectorized operations (across the whole dataframe, not loops) and
|
||||||
avoid index referencing (`df.iloc[-1]`), but instead use `df.shift()` to get to the previous candle.
|
avoid index referencing (`df.iloc[-1]`), but instead use `df.shift()` to get to the previous candle.
|
||||||
|
|
||||||
|
!!! Warning Using future data
|
||||||
|
Since backtesting passes the full time interval to the `populate_*()` methods, the strategy author
|
||||||
|
needs to take care to avoid having the strategy utilize data from the future.
|
||||||
|
Samples for usage of future data are `dataframe.shift(-1)`, `dataframe.resample("1h")` (this uses the left border of the interval, so moves data from an hour to the start of the hour).
|
||||||
|
They all use data which is not available during regular operations, so these strategies will perform well during backtesting, but will fail / perform badly in dry-runs.
|
||||||
|
|
||||||
### Customize Indicators
|
### Customize Indicators
|
||||||
|
|
||||||
Buy and sell strategies need indicators. You can add more indicators by extending the list contained in the method `populate_indicators()` from your strategy file.
|
Buy and sell strategies need indicators. You can add more indicators by extending the list contained in the method `populate_indicators()` from your strategy file.
|
||||||
@ -345,6 +351,30 @@ if self.wallets:
|
|||||||
- `get_used(asset)` - currently tied up balance (open orders)
|
- `get_used(asset)` - currently tied up balance (open orders)
|
||||||
- `get_total(asset)` - total available balance - sum of the 2 above
|
- `get_total(asset)` - total available balance - sum of the 2 above
|
||||||
|
|
||||||
|
### Print created dataframe
|
||||||
|
|
||||||
|
To inspect the created dataframe, you can issue a print-statement in either `populate_buy_trend()` or `populate_sell_trend()`.
|
||||||
|
You may also want to print the pair so it's clear what data is currently shown.
|
||||||
|
|
||||||
|
``` python
|
||||||
|
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
#>> whatever condition<<<
|
||||||
|
),
|
||||||
|
'buy'] = 1
|
||||||
|
|
||||||
|
# Print the Analyzed pair
|
||||||
|
print(f"result for {metadata['pair']}")
|
||||||
|
|
||||||
|
# Inspect the last 5 rows
|
||||||
|
print(dataframe.tail())
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
```
|
||||||
|
|
||||||
|
Printing more than a few rows is also possible (simply use `print(dataframe)` instead of `print(dataframe.tail())`), however not recommended, as that will be very verbose (~500 lines per pair every 5 seconds).
|
||||||
|
|
||||||
### Where is the default strategy?
|
### Where is the default strategy?
|
||||||
|
|
||||||
The default buy strategy is located in the file
|
The default buy strategy is located in the file
|
@ -1,10 +1,45 @@
|
|||||||
# Telegram usage
|
# Telegram usage
|
||||||
|
|
||||||
## Prerequisite
|
## Setup your Telegram bot
|
||||||
|
|
||||||
To control your bot with Telegram, you need first to
|
Below we explain how to create your Telegram Bot, and how to get your
|
||||||
[set up a Telegram bot](installation.md)
|
Telegram user id.
|
||||||
and add your Telegram API keys into your config file.
|
|
||||||
|
### 1. Create your Telegram bot
|
||||||
|
|
||||||
|
Start a chat with the [Telegram BotFather](https://telegram.me/BotFather)
|
||||||
|
|
||||||
|
Send the message `/newbot`.
|
||||||
|
|
||||||
|
*BotFather response:*
|
||||||
|
|
||||||
|
> Alright, a new bot. How are we going to call it? Please choose a name for your bot.
|
||||||
|
|
||||||
|
Choose the public name of your bot (e.x. `Freqtrade bot`)
|
||||||
|
|
||||||
|
*BotFather response:*
|
||||||
|
|
||||||
|
> Good. Now let's choose a username for your bot. It must end in `bot`. Like this, for example: TetrisBot or tetris_bot.
|
||||||
|
|
||||||
|
Choose the name id of your bot and send it to the BotFather (e.g. "`My_own_freqtrade_bot`")
|
||||||
|
|
||||||
|
*BotFather response:*
|
||||||
|
|
||||||
|
> Done! Congratulations on your new bot. You will find it at `t.me/yourbots_name_bot`. You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this.
|
||||||
|
|
||||||
|
> Use this token to access the HTTP API: `22222222:APITOKEN`
|
||||||
|
|
||||||
|
> For a description of the Bot API, see this page: https://core.telegram.org/bots/api Father bot will return you the token (API key)
|
||||||
|
|
||||||
|
Copy the API Token (`22222222:APITOKEN` in the above example) and keep use it for the config parameter `token`.
|
||||||
|
|
||||||
|
Don't forget to start the conversation with your bot, by clicking `/START` button
|
||||||
|
|
||||||
|
### 2. Get your user id
|
||||||
|
|
||||||
|
Talk to the [userinfobot](https://telegram.me/userinfobot)
|
||||||
|
|
||||||
|
Get your "Id", you will use it for the config parameter `chat_id`.
|
||||||
|
|
||||||
## Telegram commands
|
## Telegram commands
|
||||||
|
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
__version__ = '0.18.5-dev'
|
__version__ = '0.18.5-dev'
|
||||||
|
|
||||||
|
|
||||||
class DependencyException(BaseException):
|
class DependencyException(Exception):
|
||||||
"""
|
"""
|
||||||
Indicates that a assumed dependency is not met.
|
Indicates that an assumed dependency is not met.
|
||||||
This could happen when there is currently not enough money on the account.
|
This could happen when there is currently not enough money on the account.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class OperationalException(BaseException):
|
class OperationalException(Exception):
|
||||||
"""
|
"""
|
||||||
Requires manual intervention.
|
Requires manual intervention.
|
||||||
This happens when an exchange returns an unexpected error during runtime
|
This happens when an exchange returns an unexpected error during runtime
|
||||||
@ -17,7 +17,7 @@ class OperationalException(BaseException):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class InvalidOrderException(BaseException):
|
class InvalidOrderException(Exception):
|
||||||
"""
|
"""
|
||||||
This is returned when the order is not valid. Example:
|
This is returned when the order is not valid. Example:
|
||||||
If stoploss on exchange order is hit, then trying to cancel the order
|
If stoploss on exchange order is hit, then trying to cancel the order
|
||||||
@ -25,7 +25,7 @@ class InvalidOrderException(BaseException):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class TemporaryError(BaseException):
|
class TemporaryError(Exception):
|
||||||
"""
|
"""
|
||||||
Temporary network or exchange related error.
|
Temporary network or exchange related error.
|
||||||
This could happen when an exchange is congested, unavailable, or the user
|
This could happen when an exchange is congested, unavailable, or the user
|
||||||
|
@ -340,25 +340,25 @@ class Arguments(object):
|
|||||||
Builds and attaches all subcommands
|
Builds and attaches all subcommands
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
from freqtrade.optimize import backtesting, hyperopt, edge_cli
|
from freqtrade.optimize import start_backtesting, start_hyperopt, start_edge
|
||||||
|
|
||||||
subparsers = self.parser.add_subparsers(dest='subparser')
|
subparsers = self.parser.add_subparsers(dest='subparser')
|
||||||
|
|
||||||
# Add backtesting subcommand
|
# Add backtesting subcommand
|
||||||
backtesting_cmd = subparsers.add_parser('backtesting', help='Backtesting module.')
|
backtesting_cmd = subparsers.add_parser('backtesting', help='Backtesting module.')
|
||||||
backtesting_cmd.set_defaults(func=backtesting.start)
|
backtesting_cmd.set_defaults(func=start_backtesting)
|
||||||
self.optimizer_shared_options(backtesting_cmd)
|
self.optimizer_shared_options(backtesting_cmd)
|
||||||
self.backtesting_options(backtesting_cmd)
|
self.backtesting_options(backtesting_cmd)
|
||||||
|
|
||||||
# Add edge subcommand
|
# Add edge subcommand
|
||||||
edge_cmd = subparsers.add_parser('edge', help='Edge module.')
|
edge_cmd = subparsers.add_parser('edge', help='Edge module.')
|
||||||
edge_cmd.set_defaults(func=edge_cli.start)
|
edge_cmd.set_defaults(func=start_edge)
|
||||||
self.optimizer_shared_options(edge_cmd)
|
self.optimizer_shared_options(edge_cmd)
|
||||||
self.edge_options(edge_cmd)
|
self.edge_options(edge_cmd)
|
||||||
|
|
||||||
# Add hyperopt subcommand
|
# Add hyperopt subcommand
|
||||||
hyperopt_cmd = subparsers.add_parser('hyperopt', help='Hyperopt module.')
|
hyperopt_cmd = subparsers.add_parser('hyperopt', help='Hyperopt module.')
|
||||||
hyperopt_cmd.set_defaults(func=hyperopt.start)
|
hyperopt_cmd.set_defaults(func=start_hyperopt)
|
||||||
self.optimizer_shared_options(hyperopt_cmd)
|
self.optimizer_shared_options(hyperopt_cmd)
|
||||||
self.hyperopt_options(hyperopt_cmd)
|
self.hyperopt_options(hyperopt_cmd)
|
||||||
|
|
||||||
@ -405,7 +405,7 @@ class Arguments(object):
|
|||||||
raise Exception('Incorrect syntax for timerange "%s"' % text)
|
raise Exception('Incorrect syntax for timerange "%s"' % text)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_int_positive(value) -> int:
|
def check_int_positive(value: str) -> int:
|
||||||
try:
|
try:
|
||||||
uint = int(value)
|
uint = int(value)
|
||||||
if uint <= 0:
|
if uint <= 0:
|
||||||
|
@ -5,19 +5,21 @@ Includes:
|
|||||||
* load data for a pair (or a list of pairs) from disk
|
* load data for a pair (or a list of pairs) from disk
|
||||||
* download data from exchange and store to disk
|
* download data from exchange and store to disk
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import operator
|
||||||
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, List, Dict, Tuple, Any
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade import misc, OperationalException
|
from freqtrade import OperationalException, misc
|
||||||
from freqtrade.arguments import TimeRange
|
from freqtrade.arguments import TimeRange
|
||||||
from freqtrade.data.converter import parse_ticker_dataframe
|
from freqtrade.data.converter import parse_ticker_dataframe
|
||||||
from freqtrade.exchange import Exchange, timeframe_to_minutes
|
from freqtrade.exchange import Exchange, timeframe_to_minutes
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -63,12 +65,8 @@ def load_tickerdata_file(
|
|||||||
Load a pair from file, either .json.gz or .json
|
Load a pair from file, either .json.gz or .json
|
||||||
:return tickerlist or None if unsuccesful
|
:return tickerlist or None if unsuccesful
|
||||||
"""
|
"""
|
||||||
path = make_testdata_path(datadir)
|
filename = pair_data_filename(datadir, pair, ticker_interval)
|
||||||
pair_s = pair.replace('/', '_')
|
pairdata = misc.file_load_json(filename)
|
||||||
file = path.joinpath(f'{pair_s}-{ticker_interval}.json')
|
|
||||||
|
|
||||||
pairdata = misc.file_load_json(file)
|
|
||||||
|
|
||||||
if not pairdata:
|
if not pairdata:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -90,13 +88,8 @@ def load_pair_history(pair: str,
|
|||||||
:return: DataFrame with ohlcv data
|
:return: DataFrame with ohlcv data
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# If the user force the refresh of pairs
|
# The user forced the refresh of pairs
|
||||||
if refresh_pairs:
|
if refresh_pairs:
|
||||||
if not exchange:
|
|
||||||
raise OperationalException("Exchange needs to be initialized when "
|
|
||||||
"calling load_data with refresh_pairs=True")
|
|
||||||
|
|
||||||
logger.info('Download data for pair and store them in %s', datadir)
|
|
||||||
download_pair_history(datadir=datadir,
|
download_pair_history(datadir=datadir,
|
||||||
exchange=exchange,
|
exchange=exchange,
|
||||||
pair=pair,
|
pair=pair,
|
||||||
@ -115,10 +108,11 @@ def load_pair_history(pair: str,
|
|||||||
arrow.get(pairdata[-1][0] // 1000).strftime('%Y-%m-%d %H:%M:%S'))
|
arrow.get(pairdata[-1][0] // 1000).strftime('%Y-%m-%d %H:%M:%S'))
|
||||||
return parse_ticker_dataframe(pairdata, ticker_interval, fill_up_missing)
|
return parse_ticker_dataframe(pairdata, ticker_interval, fill_up_missing)
|
||||||
else:
|
else:
|
||||||
logger.warning('No data for pair: "%s", Interval: %s. '
|
logger.warning(
|
||||||
'Use --refresh-pairs-cached option or download_backtest_data.py '
|
f'No history data for pair: "{pair}", interval: {ticker_interval}. '
|
||||||
'script to download the data',
|
'Use --refresh-pairs-cached option or download_backtest_data.py '
|
||||||
pair, ticker_interval)
|
'script to download the data'
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@ -151,6 +145,13 @@ def make_testdata_path(datadir: Optional[Path]) -> Path:
|
|||||||
return datadir or (Path(__file__).parent.parent / "tests" / "testdata").resolve()
|
return datadir or (Path(__file__).parent.parent / "tests" / "testdata").resolve()
|
||||||
|
|
||||||
|
|
||||||
|
def pair_data_filename(datadir: Optional[Path], pair: str, ticker_interval: str) -> Path:
|
||||||
|
path = make_testdata_path(datadir)
|
||||||
|
pair_s = pair.replace("/", "_")
|
||||||
|
filename = path.joinpath(f'{pair_s}-{ticker_interval}.json')
|
||||||
|
return filename
|
||||||
|
|
||||||
|
|
||||||
def load_cached_data_for_updating(filename: Path, ticker_interval: str,
|
def load_cached_data_for_updating(filename: Path, ticker_interval: str,
|
||||||
timerange: Optional[TimeRange]) -> Tuple[List[Any],
|
timerange: Optional[TimeRange]) -> Tuple[List[Any],
|
||||||
Optional[int]]:
|
Optional[int]]:
|
||||||
@ -190,7 +191,7 @@ def load_cached_data_for_updating(filename: Path, ticker_interval: str,
|
|||||||
|
|
||||||
|
|
||||||
def download_pair_history(datadir: Optional[Path],
|
def download_pair_history(datadir: Optional[Path],
|
||||||
exchange: Exchange,
|
exchange: Optional[Exchange],
|
||||||
pair: str,
|
pair: str,
|
||||||
ticker_interval: str = '5m',
|
ticker_interval: str = '5m',
|
||||||
timerange: Optional[TimeRange] = None) -> bool:
|
timerange: Optional[TimeRange] = None) -> bool:
|
||||||
@ -201,18 +202,24 @@ def download_pair_history(datadir: Optional[Path],
|
|||||||
the full data will be redownloaded
|
the full data will be redownloaded
|
||||||
|
|
||||||
Based on @Rybolov work: https://github.com/rybolov/freqtrade-data
|
Based on @Rybolov work: https://github.com/rybolov/freqtrade-data
|
||||||
|
|
||||||
:param pair: pair to download
|
:param pair: pair to download
|
||||||
:param ticker_interval: ticker interval
|
:param ticker_interval: ticker interval
|
||||||
:param timerange: range of time to download
|
:param timerange: range of time to download
|
||||||
:return: bool with success state
|
:return: bool with success state
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
if not exchange:
|
||||||
path = make_testdata_path(datadir)
|
raise OperationalException(
|
||||||
filepair = pair.replace("/", "_")
|
"Exchange needs to be initialized when downloading pair history data"
|
||||||
filename = path.joinpath(f'{filepair}-{ticker_interval}.json')
|
)
|
||||||
|
|
||||||
logger.info('Download the pair: "%s", Interval: %s', pair, ticker_interval)
|
try:
|
||||||
|
filename = pair_data_filename(datadir, pair, ticker_interval)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f'Download history data for pair: "{pair}", interval: {ticker_interval} '
|
||||||
|
f'and store in {datadir}.'
|
||||||
|
)
|
||||||
|
|
||||||
data, since_ms = load_cached_data_for_updating(filename, ticker_interval, timerange)
|
data, since_ms = load_cached_data_for_updating(filename, ticker_interval, timerange)
|
||||||
|
|
||||||
@ -231,7 +238,46 @@ def download_pair_history(datadir: Optional[Path],
|
|||||||
|
|
||||||
misc.file_dump_json(filename, data)
|
misc.file_dump_json(filename, data)
|
||||||
return True
|
return True
|
||||||
except BaseException:
|
|
||||||
logger.info('Failed to download the pair: "%s", Interval: %s',
|
except Exception as e:
|
||||||
pair, ticker_interval)
|
logger.error(
|
||||||
|
f'Failed to download history data for pair: "{pair}", interval: {ticker_interval}. '
|
||||||
|
f'Error: {e}'
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_timeframe(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]:
|
||||||
|
"""
|
||||||
|
Get the maximum timeframe for the given backtest data
|
||||||
|
:param data: dictionary with preprocessed backtesting data
|
||||||
|
:return: tuple containing min_date, max_date
|
||||||
|
"""
|
||||||
|
timeframe = [
|
||||||
|
(arrow.get(frame['date'].min()), arrow.get(frame['date'].max()))
|
||||||
|
for frame in data.values()
|
||||||
|
]
|
||||||
|
return min(timeframe, key=operator.itemgetter(0))[0], \
|
||||||
|
max(timeframe, key=operator.itemgetter(1))[1]
|
||||||
|
|
||||||
|
|
||||||
|
def validate_backtest_data(data: Dict[str, DataFrame], min_date: datetime,
|
||||||
|
max_date: datetime, ticker_interval_mins: int) -> bool:
|
||||||
|
"""
|
||||||
|
Validates preprocessed backtesting data for missing values and shows warnings about it that.
|
||||||
|
|
||||||
|
:param data: dictionary with preprocessed backtesting data
|
||||||
|
:param min_date: start-date of the data
|
||||||
|
:param max_date: end-date of the data
|
||||||
|
:param ticker_interval_mins: ticker interval in minutes
|
||||||
|
"""
|
||||||
|
# total difference in minutes / interval-minutes
|
||||||
|
expected_frames = int((max_date - min_date).total_seconds() // 60 // ticker_interval_mins)
|
||||||
|
found_missing = False
|
||||||
|
for pair, df in data.items():
|
||||||
|
dflen = len(df)
|
||||||
|
if dflen < expected_frames:
|
||||||
|
found_missing = True
|
||||||
|
logger.warning("%s has missing frames: expected %s, got %s, that's %s missing values",
|
||||||
|
pair, expected_frames, dflen, expected_frames - dflen)
|
||||||
|
return found_missing
|
||||||
|
@ -13,7 +13,6 @@ from freqtrade import constants, OperationalException
|
|||||||
from freqtrade.arguments import Arguments
|
from freqtrade.arguments import Arguments
|
||||||
from freqtrade.arguments import TimeRange
|
from freqtrade.arguments import TimeRange
|
||||||
from freqtrade.data import history
|
from freqtrade.data import history
|
||||||
from freqtrade.optimize import get_timeframe
|
|
||||||
from freqtrade.strategy.interface import SellType
|
from freqtrade.strategy.interface import SellType
|
||||||
|
|
||||||
|
|
||||||
@ -49,7 +48,6 @@ class Edge():
|
|||||||
self.strategy = strategy
|
self.strategy = strategy
|
||||||
self.ticker_interval = self.strategy.ticker_interval
|
self.ticker_interval = self.strategy.ticker_interval
|
||||||
self.tickerdata_to_dataframe = self.strategy.tickerdata_to_dataframe
|
self.tickerdata_to_dataframe = self.strategy.tickerdata_to_dataframe
|
||||||
self.get_timeframe = get_timeframe
|
|
||||||
self.advise_sell = self.strategy.advise_sell
|
self.advise_sell = self.strategy.advise_sell
|
||||||
self.advise_buy = self.strategy.advise_buy
|
self.advise_buy = self.strategy.advise_buy
|
||||||
|
|
||||||
@ -117,7 +115,7 @@ class Edge():
|
|||||||
preprocessed = self.tickerdata_to_dataframe(data)
|
preprocessed = self.tickerdata_to_dataframe(data)
|
||||||
|
|
||||||
# Print timeframe
|
# Print timeframe
|
||||||
min_date, max_date = self.get_timeframe(preprocessed)
|
min_date, max_date = history.get_timeframe(preprocessed)
|
||||||
logger.info(
|
logger.info(
|
||||||
'Measuring data from %s up to %s (%s days) ...',
|
'Measuring data from %s up to %s (%s days) ...',
|
||||||
min_date.isoformat(),
|
min_date.isoformat(),
|
||||||
@ -139,6 +137,7 @@ class Edge():
|
|||||||
|
|
||||||
# If no trade found then exit
|
# If no trade found then exit
|
||||||
if len(trades) == 0:
|
if len(trades) == 0:
|
||||||
|
logger.info("No trades found.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Fill missing, calculable columns, profit, duration , abs etc.
|
# Fill missing, calculable columns, profit, duration , abs etc.
|
||||||
|
@ -510,7 +510,11 @@ class Exchange(object):
|
|||||||
_LIMIT = 500
|
_LIMIT = 500
|
||||||
|
|
||||||
one_call = timeframe_to_msecs(ticker_interval) * _LIMIT
|
one_call = timeframe_to_msecs(ticker_interval) * _LIMIT
|
||||||
logger.debug("one_call: %s msecs", one_call)
|
logger.debug(
|
||||||
|
"one_call: %s msecs (%s)",
|
||||||
|
one_call,
|
||||||
|
arrow.utcnow().shift(seconds=one_call // 1000).humanize(only_distance=True)
|
||||||
|
)
|
||||||
input_coroutines = [self._async_get_candle_history(
|
input_coroutines = [self._async_get_candle_history(
|
||||||
pair, ticker_interval, since) for since in
|
pair, ticker_interval, since) for since in
|
||||||
range(since_ms, arrow.utcnow().timestamp * 1000, one_call)]
|
range(since_ms, arrow.utcnow().timestamp * 1000, one_call)]
|
||||||
@ -541,7 +545,10 @@ class Exchange(object):
|
|||||||
or self._now_is_time_to_refresh(pair, ticker_interval)):
|
or self._now_is_time_to_refresh(pair, ticker_interval)):
|
||||||
input_coroutines.append(self._async_get_candle_history(pair, ticker_interval))
|
input_coroutines.append(self._async_get_candle_history(pair, ticker_interval))
|
||||||
else:
|
else:
|
||||||
logger.debug("Using cached ohlcv data for %s, %s ...", pair, ticker_interval)
|
logger.debug(
|
||||||
|
"Using cached ohlcv data for pair %s, interval %s ...",
|
||||||
|
pair, ticker_interval
|
||||||
|
)
|
||||||
|
|
||||||
tickers = asyncio.get_event_loop().run_until_complete(
|
tickers = asyncio.get_event_loop().run_until_complete(
|
||||||
asyncio.gather(*input_coroutines, return_exceptions=True))
|
asyncio.gather(*input_coroutines, return_exceptions=True))
|
||||||
@ -578,7 +585,11 @@ class Exchange(object):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# fetch ohlcv asynchronously
|
# fetch ohlcv asynchronously
|
||||||
logger.debug("fetching %s, %s since %s ...", pair, ticker_interval, since_ms)
|
s = '(' + arrow.get(since_ms // 1000).isoformat() + ') ' if since_ms is not None else ''
|
||||||
|
logger.debug(
|
||||||
|
"Fetching pair %s, interval %s, since %s %s...",
|
||||||
|
pair, ticker_interval, since_ms, s
|
||||||
|
)
|
||||||
|
|
||||||
data = await self._api_async.fetch_ohlcv(pair, timeframe=ticker_interval,
|
data = await self._api_async.fetch_ohlcv(pair, timeframe=ticker_interval,
|
||||||
since=since_ms)
|
since=since_ms)
|
||||||
@ -593,7 +604,7 @@ class Exchange(object):
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
logger.exception("Error loading %s. Result was %s.", pair, data)
|
logger.exception("Error loading %s. Result was %s.", pair, data)
|
||||||
return pair, ticker_interval, []
|
return pair, ticker_interval, []
|
||||||
logger.debug("done fetching %s, %s ...", pair, ticker_interval)
|
logger.debug("Done fetching pair %s, interval %s ...", pair, ticker_interval)
|
||||||
return pair, ticker_interval, data
|
return pair, ticker_interval, data
|
||||||
|
|
||||||
except ccxt.NotSupported as e:
|
except ccxt.NotSupported as e:
|
||||||
|
@ -1,49 +1,115 @@
|
|||||||
# pragma pylint: disable=missing-docstring
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from argparse import Namespace
|
||||||
from typing import Dict, Tuple
|
from typing import Any, Dict
|
||||||
import operator
|
|
||||||
|
|
||||||
import arrow
|
from filelock import FileLock, Timeout
|
||||||
from pandas import DataFrame
|
|
||||||
|
|
||||||
from freqtrade.optimize.default_hyperopt import DefaultHyperOpts # noqa: F401
|
from freqtrade import DependencyException, constants
|
||||||
|
from freqtrade.configuration import Configuration
|
||||||
|
from freqtrade.state import RunMode
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_timeframe(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]:
|
def setup_configuration(args: Namespace, method: RunMode) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Get the maximum timeframe for the given backtest data
|
Prepare the configuration for the Hyperopt module
|
||||||
:param data: dictionary with preprocessed backtesting data
|
:param args: Cli args from Arguments()
|
||||||
:return: tuple containing min_date, max_date
|
:return: Configuration
|
||||||
"""
|
"""
|
||||||
timeframe = [
|
configuration = Configuration(args, method)
|
||||||
(arrow.get(frame['date'].min()), arrow.get(frame['date'].max()))
|
config = configuration.load_config()
|
||||||
for frame in data.values()
|
|
||||||
]
|
# Ensure we do not use Exchange credentials
|
||||||
return min(timeframe, key=operator.itemgetter(0))[0], \
|
config['exchange']['key'] = ''
|
||||||
max(timeframe, key=operator.itemgetter(1))[1]
|
config['exchange']['secret'] = ''
|
||||||
|
|
||||||
|
if method == RunMode.BACKTEST:
|
||||||
|
if config['stake_amount'] == constants.UNLIMITED_STAKE_AMOUNT:
|
||||||
|
raise DependencyException('stake amount could not be "%s" for backtesting' %
|
||||||
|
constants.UNLIMITED_STAKE_AMOUNT)
|
||||||
|
|
||||||
|
if method == RunMode.HYPEROPT:
|
||||||
|
# Special cases for Hyperopt
|
||||||
|
if config.get('strategy') and config.get('strategy') != 'DefaultStrategy':
|
||||||
|
logger.error("Please don't use --strategy for hyperopt.")
|
||||||
|
logger.error(
|
||||||
|
"Read the documentation at "
|
||||||
|
"https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md "
|
||||||
|
"to understand how to configure hyperopt.")
|
||||||
|
raise DependencyException("--strategy configured but not supported for hyperopt")
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
def validate_backtest_data(data: Dict[str, DataFrame], min_date: datetime,
|
def start_backtesting(args: Namespace) -> None:
|
||||||
max_date: datetime, ticker_interval_mins: int) -> bool:
|
|
||||||
"""
|
"""
|
||||||
Validates preprocessed backtesting data for missing values and shows warnings about it that.
|
Start Backtesting script
|
||||||
|
:param args: Cli args from Arguments()
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
# Import here to avoid loading backtesting module when it's not used
|
||||||
|
from freqtrade.optimize.backtesting import Backtesting
|
||||||
|
|
||||||
:param data: dictionary with preprocessed backtesting data
|
# Initialize configuration
|
||||||
:param min_date: start-date of the data
|
config = setup_configuration(args, RunMode.BACKTEST)
|
||||||
:param max_date: end-date of the data
|
|
||||||
:param ticker_interval_mins: ticker interval in minutes
|
logger.info('Starting freqtrade in Backtesting mode')
|
||||||
|
|
||||||
|
# Initialize backtesting object
|
||||||
|
backtesting = Backtesting(config)
|
||||||
|
backtesting.start()
|
||||||
|
|
||||||
|
|
||||||
|
def start_hyperopt(args: Namespace) -> None:
|
||||||
"""
|
"""
|
||||||
# total difference in minutes / interval-minutes
|
Start hyperopt script
|
||||||
expected_frames = int((max_date - min_date).total_seconds() // 60 // ticker_interval_mins)
|
:param args: Cli args from Arguments()
|
||||||
found_missing = False
|
:return: None
|
||||||
for pair, df in data.items():
|
"""
|
||||||
dflen = len(df)
|
# Import here to avoid loading hyperopt module when it's not used
|
||||||
if dflen < expected_frames:
|
from freqtrade.optimize.hyperopt import Hyperopt, HYPEROPT_LOCKFILE
|
||||||
found_missing = True
|
|
||||||
logger.warning("%s has missing frames: expected %s, got %s, that's %s missing values",
|
# Initialize configuration
|
||||||
pair, expected_frames, dflen, expected_frames - dflen)
|
config = setup_configuration(args, RunMode.HYPEROPT)
|
||||||
return found_missing
|
|
||||||
|
logger.info('Starting freqtrade in Hyperopt mode')
|
||||||
|
|
||||||
|
lock = FileLock(HYPEROPT_LOCKFILE)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with lock.acquire(timeout=1):
|
||||||
|
|
||||||
|
# Remove noisy log messages
|
||||||
|
logging.getLogger('hyperopt.tpe').setLevel(logging.WARNING)
|
||||||
|
logging.getLogger('filelock').setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
# Initialize backtesting object
|
||||||
|
hyperopt = Hyperopt(config)
|
||||||
|
hyperopt.start()
|
||||||
|
|
||||||
|
except Timeout:
|
||||||
|
logger.info("Another running instance of freqtrade Hyperopt detected.")
|
||||||
|
logger.info("Simultaneous execution of multiple Hyperopt commands is not supported. "
|
||||||
|
"Hyperopt module is resource hungry. Please run your Hyperopts sequentially "
|
||||||
|
"or on separate machines.")
|
||||||
|
logger.info("Quitting now.")
|
||||||
|
# TODO: return False here in order to help freqtrade to exit
|
||||||
|
# with non-zero exit code...
|
||||||
|
# Same in Edge and Backtesting start() functions.
|
||||||
|
|
||||||
|
|
||||||
|
def start_edge(args: Namespace) -> None:
|
||||||
|
"""
|
||||||
|
Start Edge script
|
||||||
|
:param args: Cli args from Arguments()
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
from freqtrade.optimize.edge_cli import EdgeCli
|
||||||
|
# Initialize configuration
|
||||||
|
config = setup_configuration(args, RunMode.EDGE)
|
||||||
|
logger.info('Starting freqtrade in Edge mode')
|
||||||
|
|
||||||
|
# Initialize Edge object
|
||||||
|
edge_cli = EdgeCli(config)
|
||||||
|
edge_cli.start()
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
This module contains the backtesting logic
|
This module contains the backtesting logic
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from argparse import Namespace
|
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -13,10 +12,7 @@ from typing import Any, Dict, List, NamedTuple, Optional
|
|||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
from tabulate import tabulate
|
from tabulate import tabulate
|
||||||
|
|
||||||
from freqtrade import optimize
|
|
||||||
from freqtrade import DependencyException, constants
|
|
||||||
from freqtrade.arguments import Arguments
|
from freqtrade.arguments import Arguments
|
||||||
from freqtrade.configuration import Configuration
|
|
||||||
from freqtrade.data import history
|
from freqtrade.data import history
|
||||||
from freqtrade.data.dataprovider import DataProvider
|
from freqtrade.data.dataprovider import DataProvider
|
||||||
from freqtrade.exchange import timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
@ -24,8 +20,7 @@ from freqtrade.misc import file_dump_json
|
|||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
||||||
from freqtrade.state import RunMode
|
from freqtrade.state import RunMode
|
||||||
from freqtrade.strategy.interface import SellType, IStrategy
|
from freqtrade.strategy.interface import IStrategy, SellType
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -440,12 +435,12 @@ class Backtesting(object):
|
|||||||
logger.info("Running backtesting for Strategy %s", strat.get_strategy_name())
|
logger.info("Running backtesting for Strategy %s", strat.get_strategy_name())
|
||||||
self._set_strategy(strat)
|
self._set_strategy(strat)
|
||||||
|
|
||||||
min_date, max_date = optimize.get_timeframe(data)
|
min_date, max_date = history.get_timeframe(data)
|
||||||
# Validate dataframe for missing values (mainly at start and end, as fillup is called)
|
# Validate dataframe for missing values (mainly at start and end, as fillup is called)
|
||||||
optimize.validate_backtest_data(data, min_date, max_date,
|
history.validate_backtest_data(data, min_date, max_date,
|
||||||
timeframe_to_minutes(self.ticker_interval))
|
timeframe_to_minutes(self.ticker_interval))
|
||||||
logger.info(
|
logger.info(
|
||||||
'Measuring data from %s up to %s (%s days)..',
|
'Backtesting with data from %s up to %s (%s days)..',
|
||||||
min_date.isoformat(),
|
min_date.isoformat(),
|
||||||
max_date.isoformat(),
|
max_date.isoformat(),
|
||||||
(max_date - min_date).days
|
(max_date - min_date).days
|
||||||
@ -486,39 +481,3 @@ class Backtesting(object):
|
|||||||
print(' Strategy Summary '.center(133, '='))
|
print(' Strategy Summary '.center(133, '='))
|
||||||
print(self._generate_text_table_strategy(all_results))
|
print(self._generate_text_table_strategy(all_results))
|
||||||
print('\nFor more details, please look at the detail tables above')
|
print('\nFor more details, please look at the detail tables above')
|
||||||
|
|
||||||
|
|
||||||
def setup_configuration(args: Namespace) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Prepare the configuration for the backtesting
|
|
||||||
:param args: Cli args from Arguments()
|
|
||||||
:return: Configuration
|
|
||||||
"""
|
|
||||||
configuration = Configuration(args, RunMode.BACKTEST)
|
|
||||||
config = configuration.get_config()
|
|
||||||
|
|
||||||
# Ensure we do not use Exchange credentials
|
|
||||||
config['exchange']['key'] = ''
|
|
||||||
config['exchange']['secret'] = ''
|
|
||||||
|
|
||||||
if config['stake_amount'] == constants.UNLIMITED_STAKE_AMOUNT:
|
|
||||||
raise DependencyException('stake amount could not be "%s" for backtesting' %
|
|
||||||
constants.UNLIMITED_STAKE_AMOUNT)
|
|
||||||
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
def start(args: Namespace) -> None:
|
|
||||||
"""
|
|
||||||
Start Backtesting script
|
|
||||||
:param args: Cli args from Arguments()
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
# Initialize configuration
|
|
||||||
config = setup_configuration(args)
|
|
||||||
|
|
||||||
logger.info('Starting freqtrade in Backtesting mode')
|
|
||||||
|
|
||||||
# Initialize backtesting object
|
|
||||||
backtesting = Backtesting(config)
|
|
||||||
backtesting.start()
|
|
||||||
|
@ -70,9 +70,10 @@ class DefaultHyperOpts(IHyperOpt):
|
|||||||
dataframe['close'], dataframe['sar']
|
dataframe['close'], dataframe['sar']
|
||||||
))
|
))
|
||||||
|
|
||||||
dataframe.loc[
|
if conditions:
|
||||||
reduce(lambda x, y: x & y, conditions),
|
dataframe.loc[
|
||||||
'buy'] = 1
|
reduce(lambda x, y: x & y, conditions),
|
||||||
|
'buy'] = 1
|
||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
@ -129,9 +130,10 @@ class DefaultHyperOpts(IHyperOpt):
|
|||||||
dataframe['sar'], dataframe['close']
|
dataframe['sar'], dataframe['close']
|
||||||
))
|
))
|
||||||
|
|
||||||
dataframe.loc[
|
if conditions:
|
||||||
reduce(lambda x, y: x & y, conditions),
|
dataframe.loc[
|
||||||
'sell'] = 1
|
reduce(lambda x, y: x & y, conditions),
|
||||||
|
'sell'] = 1
|
||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
|
@ -4,16 +4,13 @@
|
|||||||
This module contains the edge backtesting interface
|
This module contains the edge backtesting interface
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
from argparse import Namespace
|
|
||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
from tabulate import tabulate
|
from tabulate import tabulate
|
||||||
from freqtrade.edge import Edge
|
from freqtrade.edge import Edge
|
||||||
|
|
||||||
from freqtrade.arguments import Arguments
|
from freqtrade.arguments import Arguments
|
||||||
from freqtrade.configuration import Configuration
|
|
||||||
from freqtrade.exchange import Exchange
|
from freqtrade.exchange import Exchange
|
||||||
from freqtrade.resolvers import StrategyResolver
|
from freqtrade.resolvers import StrategyResolver
|
||||||
from freqtrade.state import RunMode
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -73,37 +70,7 @@ class EdgeCli(object):
|
|||||||
floatfmt=floatfmt, tablefmt="pipe")
|
floatfmt=floatfmt, tablefmt="pipe")
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
self.edge.calculate()
|
result = self.edge.calculate()
|
||||||
print('') # blank like for readability
|
if result:
|
||||||
print(self._generate_edge_table(self.edge._cached_pairs))
|
print('') # blank line for readability
|
||||||
|
print(self._generate_edge_table(self.edge._cached_pairs))
|
||||||
|
|
||||||
def setup_configuration(args: Namespace) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Prepare the configuration for edge backtesting
|
|
||||||
:param args: Cli args from Arguments()
|
|
||||||
:return: Configuration
|
|
||||||
"""
|
|
||||||
configuration = Configuration(args, RunMode.EDGECLI)
|
|
||||||
config = configuration.get_config()
|
|
||||||
|
|
||||||
# Ensure we do not use Exchange credentials
|
|
||||||
config['exchange']['key'] = ''
|
|
||||||
config['exchange']['secret'] = ''
|
|
||||||
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
def start(args: Namespace) -> None:
|
|
||||||
"""
|
|
||||||
Start Edge script
|
|
||||||
:param args: Cli args from Arguments()
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
# Initialize configuration
|
|
||||||
config = setup_configuration(args)
|
|
||||||
logger.info('Starting freqtrade in Edge mode')
|
|
||||||
|
|
||||||
# Initialize Edge object
|
|
||||||
edge_cli = EdgeCli(config)
|
|
||||||
edge_cli.start()
|
|
||||||
|
@ -7,32 +7,28 @@ This module contains the hyperopt logic
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from argparse import Namespace
|
|
||||||
from math import exp
|
from math import exp
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from filelock import Timeout, FileLock
|
|
||||||
from joblib import Parallel, delayed, dump, load, wrap_non_picklable_objects, cpu_count
|
from joblib import Parallel, delayed, dump, load, wrap_non_picklable_objects, cpu_count
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
from skopt import Optimizer
|
from skopt import Optimizer
|
||||||
from skopt.space import Dimension
|
from skopt.space import Dimension
|
||||||
|
|
||||||
from freqtrade import DependencyException
|
|
||||||
from freqtrade.arguments import Arguments
|
from freqtrade.arguments import Arguments
|
||||||
from freqtrade.configuration import Configuration
|
from freqtrade.data.history import load_data, get_timeframe, validate_backtest_data
|
||||||
from freqtrade.data.history import load_data
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
from freqtrade.optimize import get_timeframe
|
|
||||||
from freqtrade.optimize.backtesting import Backtesting
|
from freqtrade.optimize.backtesting import Backtesting
|
||||||
from freqtrade.state import RunMode
|
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver
|
||||||
from freqtrade.resolvers import HyperOptResolver
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
INITIAL_POINTS = 30
|
||||||
MAX_LOSS = 100000 # just a big enough number to be bad result in loss optimization
|
MAX_LOSS = 100000 # just a big enough number to be bad result in loss optimization
|
||||||
TICKERDATA_PICKLE = os.path.join('user_data', 'hyperopt_tickerdata.pkl')
|
TICKERDATA_PICKLE = os.path.join('user_data', 'hyperopt_tickerdata.pkl')
|
||||||
TRIALSDATA_PICKLE = os.path.join('user_data', 'hyperopt_results.pickle')
|
TRIALSDATA_PICKLE = os.path.join('user_data', 'hyperopt_results.pickle')
|
||||||
@ -62,9 +58,11 @@ class Hyperopt(Backtesting):
|
|||||||
# if eval ends with higher value, we consider it a failed eval
|
# if eval ends with higher value, we consider it a failed eval
|
||||||
self.max_accepted_trade_duration = 300
|
self.max_accepted_trade_duration = 300
|
||||||
|
|
||||||
# this is expexted avg profit * expected trade count
|
# This is assumed to be expected avg profit * expected trade count.
|
||||||
# for example 3.5%, 1100 trades, self.expected_max_profit = 3.85
|
# For example, for 0.35% avg per trade (or 0.0035 as ratio) and 1100 trades,
|
||||||
# check that the reported Σ% values do not exceed this!
|
# self.expected_max_profit = 3.85
|
||||||
|
# Check that the reported Σ% values do not exceed this!
|
||||||
|
# Note, this is ratio. 3.85 stated above means 385Σ%.
|
||||||
self.expected_max_profit = 3.0
|
self.expected_max_profit = 3.0
|
||||||
|
|
||||||
# Previous evaluations
|
# Previous evaluations
|
||||||
@ -120,14 +118,20 @@ class Hyperopt(Backtesting):
|
|||||||
"""
|
"""
|
||||||
Log results if it is better than any previous evaluation
|
Log results if it is better than any previous evaluation
|
||||||
"""
|
"""
|
||||||
if self.config.get('print_all', False) or results['loss'] < self.current_best_loss:
|
print_all = self.config.get('print_all', False)
|
||||||
current = results['current_tries']
|
if print_all or results['loss'] < self.current_best_loss:
|
||||||
|
# Output human-friendly index here (starting from 1)
|
||||||
|
current = results['current_tries'] + 1
|
||||||
total = results['total_tries']
|
total = results['total_tries']
|
||||||
res = results['result']
|
res = results['result']
|
||||||
loss = results['loss']
|
loss = results['loss']
|
||||||
self.current_best_loss = results['loss']
|
self.current_best_loss = results['loss']
|
||||||
log_msg = f'\n{current:5d}/{total}: {res}. Loss {loss:.5f}'
|
log_msg = f'{current:5d}/{total}: {res} Objective: {loss:.5f}'
|
||||||
print(log_msg)
|
log_msg = f'*{log_msg}' if results['initial_point'] else f' {log_msg}'
|
||||||
|
if print_all:
|
||||||
|
print(log_msg)
|
||||||
|
else:
|
||||||
|
print('\n' + log_msg)
|
||||||
else:
|
else:
|
||||||
print('.', end='')
|
print('.', end='')
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
@ -204,8 +208,8 @@ class Hyperopt(Backtesting):
|
|||||||
trade_count = len(results.index)
|
trade_count = len(results.index)
|
||||||
trade_duration = results.trade_duration.mean()
|
trade_duration = results.trade_duration.mean()
|
||||||
|
|
||||||
# If this evaluation contains too short small amount of trades
|
# If this evaluation contains too short amount of trades to be
|
||||||
# to be interesting -- consider it as 'bad' (assign max. loss value)
|
# interesting -- consider it as 'bad' (assigned max. loss value)
|
||||||
# in order to cast this hyperspace point away from optimization
|
# in order to cast this hyperspace point away from optimization
|
||||||
# path. We do not want to optimize 'hodl' strategies.
|
# path. We do not want to optimize 'hodl' strategies.
|
||||||
if trade_count < self.config['hyperopt_min_trades']:
|
if trade_count < self.config['hyperopt_min_trades']:
|
||||||
@ -231,19 +235,19 @@ class Hyperopt(Backtesting):
|
|||||||
avg_profit = results.profit_percent.mean() * 100.0
|
avg_profit = results.profit_percent.mean() * 100.0
|
||||||
total_profit = results.profit_abs.sum()
|
total_profit = results.profit_abs.sum()
|
||||||
stake_cur = self.config['stake_currency']
|
stake_cur = self.config['stake_currency']
|
||||||
profit = results.profit_percent.sum()
|
profit = results.profit_percent.sum() * 100.0
|
||||||
duration = results.trade_duration.mean()
|
duration = results.trade_duration.mean()
|
||||||
|
|
||||||
return (f'{trades:6d} trades. Avg profit {avg_profit: 5.2f}%. '
|
return (f'{trades:6d} trades. Avg profit {avg_profit: 5.2f}%. '
|
||||||
f'Total profit {total_profit: 11.8f} {stake_cur} '
|
f'Total profit {total_profit: 11.8f} {stake_cur} '
|
||||||
f'({profit:.4f}Σ%). Avg duration {duration:5.1f} mins.')
|
f'({profit: 7.2f}Σ%). Avg duration {duration:5.1f} mins.')
|
||||||
|
|
||||||
def get_optimizer(self, cpu_count) -> Optimizer:
|
def get_optimizer(self, cpu_count) -> Optimizer:
|
||||||
return Optimizer(
|
return Optimizer(
|
||||||
self.hyperopt_space(),
|
self.hyperopt_space(),
|
||||||
base_estimator="ET",
|
base_estimator="ET",
|
||||||
acq_optimizer="auto",
|
acq_optimizer="auto",
|
||||||
n_initial_points=30,
|
n_initial_points=INITIAL_POINTS,
|
||||||
acq_optimizer_kwargs={'n_jobs': cpu_count},
|
acq_optimizer_kwargs={'n_jobs': cpu_count},
|
||||||
random_state=self.config.get('hyperopt_random_state', None)
|
random_state=self.config.get('hyperopt_random_state', None)
|
||||||
)
|
)
|
||||||
@ -273,9 +277,25 @@ class Hyperopt(Backtesting):
|
|||||||
timerange=timerange
|
timerange=timerange
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
logger.critical("No data found. Terminating.")
|
||||||
|
return
|
||||||
|
|
||||||
|
min_date, max_date = get_timeframe(data)
|
||||||
|
# Validate dataframe for missing values (mainly at start and end, as fillup is called)
|
||||||
|
validate_backtest_data(data, min_date, max_date,
|
||||||
|
timeframe_to_minutes(self.ticker_interval))
|
||||||
|
logger.info(
|
||||||
|
'Hyperopting with data from %s up to %s (%s days)..',
|
||||||
|
min_date.isoformat(),
|
||||||
|
max_date.isoformat(),
|
||||||
|
(max_date - min_date).days
|
||||||
|
)
|
||||||
|
|
||||||
if self.has_space('buy') or self.has_space('sell'):
|
if self.has_space('buy') or self.has_space('sell'):
|
||||||
self.strategy.advise_indicators = \
|
self.strategy.advise_indicators = \
|
||||||
self.custom_hyperopt.populate_indicators # type: ignore
|
self.custom_hyperopt.populate_indicators # type: ignore
|
||||||
|
|
||||||
dump(self.strategy.tickerdata_to_dataframe(data), TICKERDATA_PICKLE)
|
dump(self.strategy.tickerdata_to_dataframe(data), TICKERDATA_PICKLE)
|
||||||
|
|
||||||
# We don't need exchange instance anymore while running hyperopt
|
# We don't need exchange instance anymore while running hyperopt
|
||||||
@ -301,76 +321,19 @@ class Hyperopt(Backtesting):
|
|||||||
|
|
||||||
self.trials += f_val
|
self.trials += f_val
|
||||||
for j in range(jobs):
|
for j in range(jobs):
|
||||||
|
current = i * jobs + j
|
||||||
self.log_results({
|
self.log_results({
|
||||||
'loss': f_val[j]['loss'],
|
'loss': f_val[j]['loss'],
|
||||||
'current_tries': i * jobs + j,
|
'current_tries': current,
|
||||||
|
'initial_point': current < INITIAL_POINTS,
|
||||||
'total_tries': self.total_tries,
|
'total_tries': self.total_tries,
|
||||||
'result': f_val[j]['result'],
|
'result': f_val[j]['result'],
|
||||||
})
|
})
|
||||||
logger.debug(f"Optimizer params: {f_val[j]['params']}")
|
logger.debug(f"Optimizer params: {f_val[j]['params']}")
|
||||||
for j in range(jobs):
|
for j in range(jobs):
|
||||||
logger.debug(f"Opimizer state: Xi: {opt.Xi[-j-1]}, yi: {opt.yi[-j-1]}")
|
logger.debug(f"Optimizer state: Xi: {opt.Xi[-j-1]}, yi: {opt.yi[-j-1]}")
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print('User interrupted..')
|
print('User interrupted..')
|
||||||
|
|
||||||
self.save_trials()
|
self.save_trials()
|
||||||
self.log_trials_result()
|
self.log_trials_result()
|
||||||
|
|
||||||
|
|
||||||
def setup_configuration(args: Namespace) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
Prepare the configuration for the Hyperopt module
|
|
||||||
:param args: Cli args from Arguments()
|
|
||||||
:return: Configuration
|
|
||||||
"""
|
|
||||||
configuration = Configuration(args, RunMode.HYPEROPT)
|
|
||||||
config = configuration.load_config()
|
|
||||||
|
|
||||||
# Ensure we do not use Exchange credentials
|
|
||||||
config['exchange']['key'] = ''
|
|
||||||
config['exchange']['secret'] = ''
|
|
||||||
|
|
||||||
if config.get('strategy') and config.get('strategy') != 'DefaultStrategy':
|
|
||||||
logger.error("Please don't use --strategy for hyperopt.")
|
|
||||||
logger.error(
|
|
||||||
"Read the documentation at "
|
|
||||||
"https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md "
|
|
||||||
"to understand how to configure hyperopt.")
|
|
||||||
raise DependencyException("--strategy configured but not supported for hyperopt")
|
|
||||||
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
def start(args: Namespace) -> None:
|
|
||||||
"""
|
|
||||||
Start Backtesting script
|
|
||||||
:param args: Cli args from Arguments()
|
|
||||||
:return: None
|
|
||||||
"""
|
|
||||||
# Initialize configuration
|
|
||||||
config = setup_configuration(args)
|
|
||||||
|
|
||||||
logger.info('Starting freqtrade in Hyperopt mode')
|
|
||||||
|
|
||||||
lock = FileLock(HYPEROPT_LOCKFILE)
|
|
||||||
|
|
||||||
try:
|
|
||||||
with lock.acquire(timeout=1):
|
|
||||||
|
|
||||||
# Remove noisy log messages
|
|
||||||
logging.getLogger('hyperopt.tpe').setLevel(logging.WARNING)
|
|
||||||
logging.getLogger('filelock').setLevel(logging.WARNING)
|
|
||||||
|
|
||||||
# Initialize backtesting object
|
|
||||||
hyperopt = Hyperopt(config)
|
|
||||||
hyperopt.start()
|
|
||||||
|
|
||||||
except Timeout:
|
|
||||||
logger.info("Another running instance of freqtrade Hyperopt detected.")
|
|
||||||
logger.info("Simultaneous execution of multiple Hyperopt commands is not supported. "
|
|
||||||
"Hyperopt module is resource hungry. Please run your Hyperopts sequentially "
|
|
||||||
"or on separate machines.")
|
|
||||||
logger.info("Quitting now.")
|
|
||||||
# TODO: return False here in order to help freqtrade to exit
|
|
||||||
# with non-zero exit code...
|
|
||||||
# Same in Edge and Backtesting start() functions.
|
|
||||||
|
@ -213,11 +213,31 @@ class Trade(_DECL_BASE):
|
|||||||
return (f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, '
|
return (f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, '
|
||||||
f'open_rate={self.open_rate:.8f}, open_since={open_since})')
|
f'open_rate={self.open_rate:.8f}, open_since={open_since})')
|
||||||
|
|
||||||
|
def to_json(self) -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
'trade_id': self.id,
|
||||||
|
'pair': self.pair,
|
||||||
|
'open_date_hum': arrow.get(self.open_date).humanize(),
|
||||||
|
'open_date': self.open_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
'close_date_hum': (arrow.get(self.close_date).humanize()
|
||||||
|
if self.close_date else None),
|
||||||
|
'close_date': (self.close_date.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
if self.close_date else None),
|
||||||
|
'open_rate': self.open_rate,
|
||||||
|
'close_rate': self.close_rate,
|
||||||
|
'amount': round(self.amount, 8),
|
||||||
|
'stake_amount': round(self.stake_amount, 8),
|
||||||
|
'stop_loss': self.stop_loss,
|
||||||
|
'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None,
|
||||||
|
'initial_stop_loss': self.initial_stop_loss,
|
||||||
|
'initial_stop_loss_pct': (self.initial_stop_loss_pct * 100
|
||||||
|
if self.initial_stop_loss_pct else None),
|
||||||
|
}
|
||||||
|
|
||||||
def adjust_min_max_rates(self, current_price: float):
|
def adjust_min_max_rates(self, current_price: float):
|
||||||
"""
|
"""
|
||||||
Adjust the max_rate and min_rate.
|
Adjust the max_rate and min_rate.
|
||||||
"""
|
"""
|
||||||
logger.debug("Adjusting min/max rates")
|
|
||||||
self.max_rate = max(current_price, self.max_rate or self.open_rate)
|
self.max_rate = max(current_price, self.max_rate or self.open_rate)
|
||||||
self.min_rate = min(current_price, self.min_rate or self.open_rate)
|
self.min_rate = min(current_price, self.min_rate or self.open_rate)
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from freqtrade.resolvers.iresolver import IResolver # noqa: F401
|
from freqtrade.resolvers.iresolver import IResolver # noqa: F401
|
||||||
from freqtrade.resolvers.exchange_resolver import ExchangeResolver # noqa: F401
|
from freqtrade.resolvers.exchange_resolver import ExchangeResolver # noqa: F401
|
||||||
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver # noqa: F401
|
# Don't import HyperoptResolver to avoid loading the whole Optimize tree
|
||||||
|
# from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver # noqa: F401
|
||||||
from freqtrade.resolvers.pairlist_resolver import PairListResolver # noqa: F401
|
from freqtrade.resolvers.pairlist_resolver import PairListResolver # noqa: F401
|
||||||
from freqtrade.resolvers.strategy_resolver import StrategyResolver # noqa: F401
|
from freqtrade.resolvers.strategy_resolver import StrategyResolver # noqa: F401
|
||||||
|
@ -100,28 +100,17 @@ class RPC(object):
|
|||||||
current_profit = trade.calc_profit_percent(current_rate)
|
current_profit = trade.calc_profit_percent(current_rate)
|
||||||
fmt_close_profit = (f'{round(trade.close_profit * 100, 2):.2f}%'
|
fmt_close_profit = (f'{round(trade.close_profit * 100, 2):.2f}%'
|
||||||
if trade.close_profit else None)
|
if trade.close_profit else None)
|
||||||
results.append(dict(
|
trade_dict = trade.to_json()
|
||||||
trade_id=trade.id,
|
trade_dict.update(dict(
|
||||||
pair=trade.pair,
|
|
||||||
base_currency=self._freqtrade.config['stake_currency'],
|
base_currency=self._freqtrade.config['stake_currency'],
|
||||||
date=arrow.get(trade.open_date),
|
|
||||||
open_rate=trade.open_rate,
|
|
||||||
close_rate=trade.close_rate,
|
|
||||||
current_rate=current_rate,
|
|
||||||
amount=round(trade.amount, 8),
|
|
||||||
stake_amount=round(trade.stake_amount, 8),
|
|
||||||
close_profit=fmt_close_profit,
|
close_profit=fmt_close_profit,
|
||||||
|
current_rate=current_rate,
|
||||||
current_profit=round(current_profit * 100, 2),
|
current_profit=round(current_profit * 100, 2),
|
||||||
stop_loss=trade.stop_loss,
|
|
||||||
stop_loss_pct=(trade.stop_loss_pct * 100)
|
|
||||||
if trade.stop_loss_pct else None,
|
|
||||||
initial_stop_loss=trade.initial_stop_loss,
|
|
||||||
initial_stop_loss_pct=(trade.initial_stop_loss_pct * 100)
|
|
||||||
if trade.initial_stop_loss_pct else None,
|
|
||||||
open_order='({} {} rem={:.8f})'.format(
|
open_order='({} {} rem={:.8f})'.format(
|
||||||
order['type'], order['side'], order['remaining']
|
order['type'], order['side'], order['remaining']
|
||||||
) if order else None,
|
) if order else None,
|
||||||
))
|
))
|
||||||
|
results.append(trade_dict)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def _rpc_status_table(self) -> DataFrame:
|
def _rpc_status_table(self) -> DataFrame:
|
||||||
@ -287,11 +276,12 @@ class RPC(object):
|
|||||||
rate = 1.0
|
rate = 1.0
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
if coin == 'USDT':
|
if coin in('USDT', 'USD', 'EUR'):
|
||||||
rate = 1.0 / self._freqtrade.get_sell_rate('BTC/USDT', False)
|
rate = 1.0 / self._freqtrade.get_sell_rate('BTC/' + coin, False)
|
||||||
else:
|
else:
|
||||||
rate = self._freqtrade.get_sell_rate(coin + '/BTC', False)
|
rate = self._freqtrade.get_sell_rate(coin + '/BTC', False)
|
||||||
except (TemporaryError, DependencyException):
|
except (TemporaryError, DependencyException):
|
||||||
|
logger.warning(f" Could not get rate for pair {coin}.")
|
||||||
continue
|
continue
|
||||||
est_btc: float = rate * balance['total']
|
est_btc: float = rate * balance['total']
|
||||||
total = total + est_btc
|
total = total + est_btc
|
||||||
|
@ -193,14 +193,11 @@ class Telegram(RPC):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
results = self._rpc_trade_status()
|
results = self._rpc_trade_status()
|
||||||
# pre format data
|
|
||||||
for result in results:
|
|
||||||
result['date'] = result['date'].humanize()
|
|
||||||
|
|
||||||
messages = []
|
messages = []
|
||||||
for r in results:
|
for r in results:
|
||||||
lines = [
|
lines = [
|
||||||
"*Trade ID:* `{trade_id}` `(since {date})`",
|
"*Trade ID:* `{trade_id}` `(since {open_date_hum})`",
|
||||||
"*Current Pair:* {pair}",
|
"*Current Pair:* {pair}",
|
||||||
"*Amount:* `{amount} ({stake_amount} {base_currency})`",
|
"*Amount:* `{amount} ({stake_amount} {base_currency})`",
|
||||||
"*Open Rate:* `{open_rate:.8f}`",
|
"*Open Rate:* `{open_rate:.8f}`",
|
||||||
|
@ -18,11 +18,11 @@ class State(Enum):
|
|||||||
class RunMode(Enum):
|
class RunMode(Enum):
|
||||||
"""
|
"""
|
||||||
Bot running mode (backtest, hyperopt, ...)
|
Bot running mode (backtest, hyperopt, ...)
|
||||||
can be "live", "dry-run", "backtest", "edgecli", "hyperopt".
|
can be "live", "dry-run", "backtest", "edge", "hyperopt".
|
||||||
"""
|
"""
|
||||||
LIVE = "live"
|
LIVE = "live"
|
||||||
DRY_RUN = "dry_run"
|
DRY_RUN = "dry_run"
|
||||||
BACKTEST = "backtest"
|
BACKTEST = "backtest"
|
||||||
EDGECLI = "edgecli"
|
EDGE = "edge"
|
||||||
HYPEROPT = "hyperopt"
|
HYPEROPT = "hyperopt"
|
||||||
OTHER = "other" # Used for plotting scripts and test
|
OTHER = "other" # Used for plotting scripts and test
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from freqtrade.data.converter import parse_ticker_dataframe, ohlcv_fill_up_missing_data
|
from freqtrade.data.converter import parse_ticker_dataframe, ohlcv_fill_up_missing_data
|
||||||
from freqtrade.data.history import load_pair_history
|
from freqtrade.data.history import load_pair_history, validate_backtest_data, get_timeframe
|
||||||
from freqtrade.optimize import validate_backtest_data, get_timeframe
|
|
||||||
from freqtrade.tests.conftest import log_has
|
from freqtrade.tests.conftest import log_has
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,24 +2,25 @@
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
|
||||||
import uuid
|
import uuid
|
||||||
|
from pathlib import Path
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
from pandas import DataFrame
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade import OperationalException
|
from freqtrade import OperationalException
|
||||||
from freqtrade.arguments import TimeRange
|
from freqtrade.arguments import TimeRange
|
||||||
from freqtrade.data import history
|
from freqtrade.data import history
|
||||||
from freqtrade.data.history import (download_pair_history,
|
from freqtrade.data.history import (download_pair_history,
|
||||||
load_cached_data_for_updating,
|
load_cached_data_for_updating,
|
||||||
load_tickerdata_file,
|
load_tickerdata_file, make_testdata_path,
|
||||||
make_testdata_path,
|
|
||||||
trim_tickerlist)
|
trim_tickerlist)
|
||||||
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
from freqtrade.misc import file_dump_json
|
from freqtrade.misc import file_dump_json
|
||||||
from freqtrade.tests.conftest import get_patched_exchange, log_has
|
from freqtrade.strategy.default_strategy import DefaultStrategy
|
||||||
|
from freqtrade.tests.conftest import get_patched_exchange, log_has, patch_exchange
|
||||||
|
|
||||||
# Change this if modifying UNITTEST/BTC testdatafile
|
# Change this if modifying UNITTEST/BTC testdatafile
|
||||||
_BTC_UNITTEST_LENGTH = 13681
|
_BTC_UNITTEST_LENGTH = 13681
|
||||||
@ -59,7 +60,11 @@ def _clean_test_file(file: str) -> None:
|
|||||||
def test_load_data_30min_ticker(mocker, caplog, default_conf) -> None:
|
def test_load_data_30min_ticker(mocker, caplog, default_conf) -> None:
|
||||||
ld = history.load_pair_history(pair='UNITTEST/BTC', ticker_interval='30m', datadir=None)
|
ld = history.load_pair_history(pair='UNITTEST/BTC', ticker_interval='30m', datadir=None)
|
||||||
assert isinstance(ld, DataFrame)
|
assert isinstance(ld, DataFrame)
|
||||||
assert not log_has('Download the pair: "UNITTEST/BTC", Interval: 30m', caplog.record_tuples)
|
assert not log_has(
|
||||||
|
'Download history data for pair: "UNITTEST/BTC", interval: 30m '
|
||||||
|
'and store in None.',
|
||||||
|
caplog.record_tuples
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_load_data_7min_ticker(mocker, caplog, default_conf) -> None:
|
def test_load_data_7min_ticker(mocker, caplog, default_conf) -> None:
|
||||||
@ -67,7 +72,7 @@ def test_load_data_7min_ticker(mocker, caplog, default_conf) -> None:
|
|||||||
assert not isinstance(ld, DataFrame)
|
assert not isinstance(ld, DataFrame)
|
||||||
assert ld is None
|
assert ld is None
|
||||||
assert log_has(
|
assert log_has(
|
||||||
'No data for pair: "UNITTEST/BTC", Interval: 7m. '
|
'No history data for pair: "UNITTEST/BTC", interval: 7m. '
|
||||||
'Use --refresh-pairs-cached option or download_backtest_data.py '
|
'Use --refresh-pairs-cached option or download_backtest_data.py '
|
||||||
'script to download the data',
|
'script to download the data',
|
||||||
caplog.record_tuples
|
caplog.record_tuples
|
||||||
@ -80,7 +85,11 @@ def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None:
|
|||||||
_backup_file(file, copy_file=True)
|
_backup_file(file, copy_file=True)
|
||||||
history.load_data(datadir=None, ticker_interval='1m', pairs=['UNITTEST/BTC'])
|
history.load_data(datadir=None, ticker_interval='1m', pairs=['UNITTEST/BTC'])
|
||||||
assert os.path.isfile(file) is True
|
assert os.path.isfile(file) is True
|
||||||
assert not log_has('Download the pair: "UNITTEST/BTC", Interval: 1m', caplog.record_tuples)
|
assert not log_has(
|
||||||
|
'Download history data for pair: "UNITTEST/BTC", interval: 1m '
|
||||||
|
'and store in None.',
|
||||||
|
caplog.record_tuples
|
||||||
|
)
|
||||||
_clean_test_file(file)
|
_clean_test_file(file)
|
||||||
|
|
||||||
|
|
||||||
@ -100,7 +109,7 @@ def test_load_data_with_new_pair_1min(ticker_history_list, mocker, caplog, defau
|
|||||||
pair='MEME/BTC')
|
pair='MEME/BTC')
|
||||||
assert os.path.isfile(file) is False
|
assert os.path.isfile(file) is False
|
||||||
assert log_has(
|
assert log_has(
|
||||||
'No data for pair: "MEME/BTC", Interval: 1m. '
|
'No history data for pair: "MEME/BTC", interval: 1m. '
|
||||||
'Use --refresh-pairs-cached option or download_backtest_data.py '
|
'Use --refresh-pairs-cached option or download_backtest_data.py '
|
||||||
'script to download the data',
|
'script to download the data',
|
||||||
caplog.record_tuples
|
caplog.record_tuples
|
||||||
@ -113,7 +122,11 @@ def test_load_data_with_new_pair_1min(ticker_history_list, mocker, caplog, defau
|
|||||||
exchange=exchange,
|
exchange=exchange,
|
||||||
pair='MEME/BTC')
|
pair='MEME/BTC')
|
||||||
assert os.path.isfile(file) is True
|
assert os.path.isfile(file) is True
|
||||||
assert log_has('Download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples)
|
assert log_has(
|
||||||
|
'Download history data for pair: "MEME/BTC", interval: 1m '
|
||||||
|
'and store in None.',
|
||||||
|
caplog.record_tuples
|
||||||
|
)
|
||||||
with pytest.raises(OperationalException, match=r'Exchange needs to be initialized when.*'):
|
with pytest.raises(OperationalException, match=r'Exchange needs to be initialized when.*'):
|
||||||
history.load_pair_history(datadir=None,
|
history.load_pair_history(datadir=None,
|
||||||
ticker_interval='1m',
|
ticker_interval='1m',
|
||||||
@ -293,7 +306,7 @@ def test_download_pair_history2(mocker, default_conf) -> None:
|
|||||||
|
|
||||||
def test_download_backtesting_data_exception(ticker_history, mocker, caplog, default_conf) -> None:
|
def test_download_backtesting_data_exception(ticker_history, mocker, caplog, default_conf) -> None:
|
||||||
mocker.patch('freqtrade.exchange.Exchange.get_history',
|
mocker.patch('freqtrade.exchange.Exchange.get_history',
|
||||||
side_effect=BaseException('File Error'))
|
side_effect=Exception('File Error'))
|
||||||
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
|
|
||||||
@ -308,7 +321,11 @@ def test_download_backtesting_data_exception(ticker_history, mocker, caplog, def
|
|||||||
# clean files freshly downloaded
|
# clean files freshly downloaded
|
||||||
_clean_test_file(file1_1)
|
_clean_test_file(file1_1)
|
||||||
_clean_test_file(file1_5)
|
_clean_test_file(file1_5)
|
||||||
assert log_has('Failed to download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples)
|
assert log_has(
|
||||||
|
'Failed to download history data for pair: "MEME/BTC", interval: 1m. '
|
||||||
|
'Error: File Error',
|
||||||
|
caplog.record_tuples
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_load_tickerdata_file() -> None:
|
def test_load_tickerdata_file() -> None:
|
||||||
@ -479,3 +496,62 @@ def test_file_dump_json_tofile() -> None:
|
|||||||
|
|
||||||
# Remove the file
|
# Remove the file
|
||||||
_clean_test_file(file)
|
_clean_test_file(file)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_timeframe(default_conf, mocker) -> None:
|
||||||
|
patch_exchange(mocker)
|
||||||
|
strategy = DefaultStrategy(default_conf)
|
||||||
|
|
||||||
|
data = strategy.tickerdata_to_dataframe(
|
||||||
|
history.load_data(
|
||||||
|
datadir=None,
|
||||||
|
ticker_interval='1m',
|
||||||
|
pairs=['UNITTEST/BTC']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
min_date, max_date = history.get_timeframe(data)
|
||||||
|
assert min_date.isoformat() == '2017-11-04T23:02:00+00:00'
|
||||||
|
assert max_date.isoformat() == '2017-11-14T22:58:00+00:00'
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_backtest_data_warn(default_conf, mocker, caplog) -> None:
|
||||||
|
patch_exchange(mocker)
|
||||||
|
strategy = DefaultStrategy(default_conf)
|
||||||
|
|
||||||
|
data = strategy.tickerdata_to_dataframe(
|
||||||
|
history.load_data(
|
||||||
|
datadir=None,
|
||||||
|
ticker_interval='1m',
|
||||||
|
pairs=['UNITTEST/BTC'],
|
||||||
|
fill_up_missing=False
|
||||||
|
)
|
||||||
|
)
|
||||||
|
min_date, max_date = history.get_timeframe(data)
|
||||||
|
caplog.clear()
|
||||||
|
assert history.validate_backtest_data(data, min_date, max_date,
|
||||||
|
timeframe_to_minutes('1m'))
|
||||||
|
assert len(caplog.record_tuples) == 1
|
||||||
|
assert log_has(
|
||||||
|
"UNITTEST/BTC has missing frames: expected 14396, got 13680, that's 716 missing values",
|
||||||
|
caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_backtest_data(default_conf, mocker, caplog) -> None:
|
||||||
|
patch_exchange(mocker)
|
||||||
|
strategy = DefaultStrategy(default_conf)
|
||||||
|
|
||||||
|
timerange = TimeRange('index', 'index', 200, 250)
|
||||||
|
data = strategy.tickerdata_to_dataframe(
|
||||||
|
history.load_data(
|
||||||
|
datadir=None,
|
||||||
|
ticker_interval='5m',
|
||||||
|
pairs=['UNITTEST/BTC'],
|
||||||
|
timerange=timerange
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
min_date, max_date = history.get_timeframe(data)
|
||||||
|
caplog.clear()
|
||||||
|
assert not history.validate_backtest_data(data, min_date, max_date,
|
||||||
|
timeframe_to_minutes('5m'))
|
||||||
|
assert len(caplog.record_tuples) == 0
|
||||||
|
@ -10,10 +10,11 @@ import numpy as np
|
|||||||
import pytest
|
import pytest
|
||||||
from pandas import DataFrame, to_datetime
|
from pandas import DataFrame, to_datetime
|
||||||
|
|
||||||
|
from freqtrade import OperationalException
|
||||||
from freqtrade.data.converter import parse_ticker_dataframe
|
from freqtrade.data.converter import parse_ticker_dataframe
|
||||||
from freqtrade.edge import Edge, PairInfo
|
from freqtrade.edge import Edge, PairInfo
|
||||||
from freqtrade.strategy.interface import SellType
|
from freqtrade.strategy.interface import SellType
|
||||||
from freqtrade.tests.conftest import get_patched_freqtradebot
|
from freqtrade.tests.conftest import get_patched_freqtradebot, log_has
|
||||||
from freqtrade.tests.optimize import (BTContainer, BTrade,
|
from freqtrade.tests.optimize import (BTContainer, BTrade,
|
||||||
_build_backtest_dataframe,
|
_build_backtest_dataframe,
|
||||||
_get_frame_time_from_offset)
|
_get_frame_time_from_offset)
|
||||||
@ -30,7 +31,50 @@ ticker_start_time = arrow.get(2018, 10, 3)
|
|||||||
ticker_interval_in_minute = 60
|
ticker_interval_in_minute = 60
|
||||||
_ohlc = {'date': 0, 'buy': 1, 'open': 2, 'high': 3, 'low': 4, 'close': 5, 'sell': 6, 'volume': 7}
|
_ohlc = {'date': 0, 'buy': 1, 'open': 2, 'high': 3, 'low': 4, 'close': 5, 'sell': 6, 'volume': 7}
|
||||||
|
|
||||||
|
# Helpers for this test file
|
||||||
|
|
||||||
|
|
||||||
|
def _validate_ohlc(buy_ohlc_sell_matrice):
|
||||||
|
for index, ohlc in enumerate(buy_ohlc_sell_matrice):
|
||||||
|
# if not high < open < low or not high < close < low
|
||||||
|
if not ohlc[3] >= ohlc[2] >= ohlc[4] or not ohlc[3] >= ohlc[5] >= ohlc[4]:
|
||||||
|
raise Exception('Line ' + str(index + 1) + ' of ohlc has invalid values!')
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _build_dataframe(buy_ohlc_sell_matrice):
|
||||||
|
_validate_ohlc(buy_ohlc_sell_matrice)
|
||||||
|
tickers = []
|
||||||
|
for ohlc in buy_ohlc_sell_matrice:
|
||||||
|
ticker = {
|
||||||
|
'date': ticker_start_time.shift(
|
||||||
|
minutes=(
|
||||||
|
ohlc[0] *
|
||||||
|
ticker_interval_in_minute)).timestamp *
|
||||||
|
1000,
|
||||||
|
'buy': ohlc[1],
|
||||||
|
'open': ohlc[2],
|
||||||
|
'high': ohlc[3],
|
||||||
|
'low': ohlc[4],
|
||||||
|
'close': ohlc[5],
|
||||||
|
'sell': ohlc[6]}
|
||||||
|
tickers.append(ticker)
|
||||||
|
|
||||||
|
frame = DataFrame(tickers)
|
||||||
|
frame['date'] = to_datetime(frame['date'],
|
||||||
|
unit='ms',
|
||||||
|
utc=True,
|
||||||
|
infer_datetime_format=True)
|
||||||
|
|
||||||
|
return frame
|
||||||
|
|
||||||
|
|
||||||
|
def _time_on_candle(number):
|
||||||
|
return np.datetime64(ticker_start_time.shift(
|
||||||
|
minutes=(number * ticker_interval_in_minute)).timestamp * 1000, 'ms')
|
||||||
|
|
||||||
|
|
||||||
|
# End helper functions
|
||||||
# Open trade should be removed from the end
|
# Open trade should be removed from the end
|
||||||
tc0 = BTContainer(data=[
|
tc0 = BTContainer(data=[
|
||||||
# D O H L C V B S
|
# D O H L C V B S
|
||||||
@ -203,46 +247,6 @@ def test_nonexisting_stake_amount(mocker, edge_conf):
|
|||||||
assert edge.stake_amount('N/O', 1, 2, 1) == 0.15
|
assert edge.stake_amount('N/O', 1, 2, 1) == 0.15
|
||||||
|
|
||||||
|
|
||||||
def _validate_ohlc(buy_ohlc_sell_matrice):
|
|
||||||
for index, ohlc in enumerate(buy_ohlc_sell_matrice):
|
|
||||||
# if not high < open < low or not high < close < low
|
|
||||||
if not ohlc[3] >= ohlc[2] >= ohlc[4] or not ohlc[3] >= ohlc[5] >= ohlc[4]:
|
|
||||||
raise Exception('Line ' + str(index + 1) + ' of ohlc has invalid values!')
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def _build_dataframe(buy_ohlc_sell_matrice):
|
|
||||||
_validate_ohlc(buy_ohlc_sell_matrice)
|
|
||||||
tickers = []
|
|
||||||
for ohlc in buy_ohlc_sell_matrice:
|
|
||||||
ticker = {
|
|
||||||
'date': ticker_start_time.shift(
|
|
||||||
minutes=(
|
|
||||||
ohlc[0] *
|
|
||||||
ticker_interval_in_minute)).timestamp *
|
|
||||||
1000,
|
|
||||||
'buy': ohlc[1],
|
|
||||||
'open': ohlc[2],
|
|
||||||
'high': ohlc[3],
|
|
||||||
'low': ohlc[4],
|
|
||||||
'close': ohlc[5],
|
|
||||||
'sell': ohlc[6]}
|
|
||||||
tickers.append(ticker)
|
|
||||||
|
|
||||||
frame = DataFrame(tickers)
|
|
||||||
frame['date'] = to_datetime(frame['date'],
|
|
||||||
unit='ms',
|
|
||||||
utc=True,
|
|
||||||
infer_datetime_format=True)
|
|
||||||
|
|
||||||
return frame
|
|
||||||
|
|
||||||
|
|
||||||
def _time_on_candle(number):
|
|
||||||
return np.datetime64(ticker_start_time.shift(
|
|
||||||
minutes=(number * ticker_interval_in_minute)).timestamp * 1000, 'ms')
|
|
||||||
|
|
||||||
|
|
||||||
def test_edge_heartbeat_calculate(mocker, edge_conf):
|
def test_edge_heartbeat_calculate(mocker, edge_conf):
|
||||||
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||||
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||||
@ -298,6 +302,40 @@ def test_edge_process_downloaded_data(mocker, edge_conf):
|
|||||||
assert edge._last_updated <= arrow.utcnow().timestamp + 2
|
assert edge._last_updated <= arrow.utcnow().timestamp + 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_process_no_data(mocker, edge_conf, caplog):
|
||||||
|
edge_conf['datadir'] = None
|
||||||
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001))
|
||||||
|
mocker.patch('freqtrade.data.history.load_data', MagicMock(return_value={}))
|
||||||
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||||
|
|
||||||
|
assert not edge.calculate()
|
||||||
|
assert len(edge._cached_pairs) == 0
|
||||||
|
assert log_has("No data found. Edge is stopped ...", caplog.record_tuples)
|
||||||
|
assert edge._last_updated == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_process_no_trades(mocker, edge_conf, caplog):
|
||||||
|
edge_conf['datadir'] = None
|
||||||
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001))
|
||||||
|
mocker.patch('freqtrade.data.history.load_data', mocked_load_data)
|
||||||
|
# Return empty
|
||||||
|
mocker.patch('freqtrade.edge.Edge._find_trades_for_stoploss_range', MagicMock(return_value=[]))
|
||||||
|
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
|
||||||
|
|
||||||
|
assert not edge.calculate()
|
||||||
|
assert len(edge._cached_pairs) == 0
|
||||||
|
assert log_has("No trades found.", caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
|
def test_edge_init_error(mocker, edge_conf,):
|
||||||
|
edge_conf['stake_amount'] = 0.5
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001))
|
||||||
|
with pytest.raises(OperationalException, match='Edge works only with unlimited stake amount'):
|
||||||
|
get_patched_freqtradebot(mocker, edge_conf)
|
||||||
|
|
||||||
|
|
||||||
def test_process_expectancy(mocker, edge_conf):
|
def test_process_expectancy(mocker, edge_conf):
|
||||||
edge_conf['edge']['min_trade_number'] = 2
|
edge_conf['edge']['min_trade_number'] = 2
|
||||||
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
|
||||||
@ -360,3 +398,11 @@ def test_process_expectancy(mocker, edge_conf):
|
|||||||
assert round(final['TEST/BTC'].risk_reward_ratio, 10) == 306.5384615384
|
assert round(final['TEST/BTC'].risk_reward_ratio, 10) == 306.5384615384
|
||||||
assert round(final['TEST/BTC'].required_risk_reward, 10) == 2.0
|
assert round(final['TEST/BTC'].required_risk_reward, 10) == 2.0
|
||||||
assert round(final['TEST/BTC'].expectancy, 10) == 101.5128205128
|
assert round(final['TEST/BTC'].expectancy, 10) == 101.5128205128
|
||||||
|
|
||||||
|
# Pop last item so no trade is profitable
|
||||||
|
trades.pop()
|
||||||
|
trades_df = DataFrame(trades)
|
||||||
|
trades_df = edge._fill_calculable_fields(trades_df)
|
||||||
|
final = edge._process_expectancy(trades_df)
|
||||||
|
assert len(final) == 0
|
||||||
|
assert isinstance(final, dict)
|
||||||
|
@ -1016,7 +1016,7 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None:
|
|||||||
exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m')])
|
exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m')])
|
||||||
|
|
||||||
assert exchange._api_async.fetch_ohlcv.call_count == 2
|
assert exchange._api_async.fetch_ohlcv.call_count == 2
|
||||||
assert log_has(f"Using cached ohlcv data for {pairs[0][0]}, {pairs[0][1]} ...",
|
assert log_has(f"Using cached ohlcv data for pair {pairs[0][0]}, interval {pairs[0][1]} ...",
|
||||||
caplog.record_tuples)
|
caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,17 +2,17 @@
|
|||||||
import logging
|
import logging
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from pandas import DataFrame
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from pandas import DataFrame
|
||||||
|
|
||||||
|
from freqtrade.data.history import get_timeframe
|
||||||
from freqtrade.optimize import get_timeframe
|
|
||||||
from freqtrade.optimize.backtesting import Backtesting
|
from freqtrade.optimize.backtesting import Backtesting
|
||||||
from freqtrade.strategy.interface import SellType
|
from freqtrade.strategy.interface import SellType
|
||||||
from freqtrade.tests.optimize import (BTrade, BTContainer, _build_backtest_dataframe,
|
|
||||||
_get_frame_time_from_offset, tests_ticker_interval)
|
|
||||||
from freqtrade.tests.conftest import patch_exchange
|
from freqtrade.tests.conftest import patch_exchange
|
||||||
|
from freqtrade.tests.optimize import (BTContainer, BTrade,
|
||||||
|
_build_backtest_dataframe,
|
||||||
|
_get_frame_time_from_offset,
|
||||||
|
tests_ticker_interval)
|
||||||
|
|
||||||
# Test 1 Minus 8% Close
|
# Test 1 Minus 8% Close
|
||||||
# Test with Stop-loss at 1%
|
# Test with Stop-loss at 1%
|
||||||
|
@ -17,9 +17,9 @@ from freqtrade.data import history
|
|||||||
from freqtrade.data.btanalysis import evaluate_result_multi
|
from freqtrade.data.btanalysis import evaluate_result_multi
|
||||||
from freqtrade.data.converter import parse_ticker_dataframe
|
from freqtrade.data.converter import parse_ticker_dataframe
|
||||||
from freqtrade.data.dataprovider import DataProvider
|
from freqtrade.data.dataprovider import DataProvider
|
||||||
from freqtrade.optimize import get_timeframe
|
from freqtrade.data.history import get_timeframe
|
||||||
from freqtrade.optimize.backtesting import (Backtesting, setup_configuration,
|
from freqtrade.optimize import setup_configuration, start_backtesting
|
||||||
start)
|
from freqtrade.optimize.backtesting import Backtesting
|
||||||
from freqtrade.state import RunMode
|
from freqtrade.state import RunMode
|
||||||
from freqtrade.strategy.default_strategy import DefaultStrategy
|
from freqtrade.strategy.default_strategy import DefaultStrategy
|
||||||
from freqtrade.strategy.interface import SellType
|
from freqtrade.strategy.interface import SellType
|
||||||
@ -33,7 +33,7 @@ def get_args(args) -> List[str]:
|
|||||||
def trim_dictlist(dict_list, num):
|
def trim_dictlist(dict_list, num):
|
||||||
new = {}
|
new = {}
|
||||||
for pair, pair_data in dict_list.items():
|
for pair, pair_data in dict_list.items():
|
||||||
new[pair] = pair_data[num:]
|
new[pair] = pair_data[num:].reset_index()
|
||||||
return new
|
return new
|
||||||
|
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
|
|||||||
'backtesting'
|
'backtesting'
|
||||||
]
|
]
|
||||||
|
|
||||||
config = setup_configuration(get_args(args))
|
config = setup_configuration(get_args(args), RunMode.BACKTEST)
|
||||||
assert 'max_open_trades' in config
|
assert 'max_open_trades' in config
|
||||||
assert 'stake_currency' in config
|
assert 'stake_currency' in config
|
||||||
assert 'stake_amount' in config
|
assert 'stake_amount' in config
|
||||||
@ -228,7 +228,7 @@ def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) ->
|
|||||||
'--export-filename', 'foo_bar.json'
|
'--export-filename', 'foo_bar.json'
|
||||||
]
|
]
|
||||||
|
|
||||||
config = setup_configuration(get_args(args))
|
config = setup_configuration(get_args(args), RunMode.BACKTEST)
|
||||||
assert 'max_open_trades' in config
|
assert 'max_open_trades' in config
|
||||||
assert 'stake_currency' in config
|
assert 'stake_currency' in config
|
||||||
assert 'stake_amount' in config
|
assert 'stake_amount' in config
|
||||||
@ -290,7 +290,7 @@ def test_setup_configuration_unlimited_stake_amount(mocker, default_conf, caplog
|
|||||||
]
|
]
|
||||||
|
|
||||||
with pytest.raises(DependencyException, match=r'.*stake amount.*'):
|
with pytest.raises(DependencyException, match=r'.*stake amount.*'):
|
||||||
setup_configuration(get_args(args))
|
setup_configuration(get_args(args), RunMode.BACKTEST)
|
||||||
|
|
||||||
|
|
||||||
def test_start(mocker, fee, default_conf, caplog) -> None:
|
def test_start(mocker, fee, default_conf, caplog) -> None:
|
||||||
@ -307,7 +307,7 @@ def test_start(mocker, fee, default_conf, caplog) -> None:
|
|||||||
'backtesting'
|
'backtesting'
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
args = get_args(args)
|
||||||
start(args)
|
start_backtesting(args)
|
||||||
assert log_has(
|
assert log_has(
|
||||||
'Starting freqtrade in Backtesting mode',
|
'Starting freqtrade in Backtesting mode',
|
||||||
caplog.record_tuples
|
caplog.record_tuples
|
||||||
@ -472,7 +472,7 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
|
|||||||
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
|
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
|
||||||
|
|
||||||
mocker.patch('freqtrade.data.history.load_data', mocked_load_data)
|
mocker.patch('freqtrade.data.history.load_data', mocked_load_data)
|
||||||
mocker.patch('freqtrade.optimize.get_timeframe', get_timeframe)
|
mocker.patch('freqtrade.data.history.get_timeframe', get_timeframe)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', MagicMock())
|
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', MagicMock())
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -495,7 +495,7 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
|
|||||||
'Using local backtesting data (using whitelist in given config) ...',
|
'Using local backtesting data (using whitelist in given config) ...',
|
||||||
'Using stake_currency: BTC ...',
|
'Using stake_currency: BTC ...',
|
||||||
'Using stake_amount: 0.001 ...',
|
'Using stake_amount: 0.001 ...',
|
||||||
'Measuring data from 2017-11-14T21:17:00+00:00 '
|
'Backtesting with data from 2017-11-14T21:17:00+00:00 '
|
||||||
'up to 2017-11-14T22:59:00+00:00 (0 days)..'
|
'up to 2017-11-14T22:59:00+00:00 (0 days)..'
|
||||||
]
|
]
|
||||||
for line in exists:
|
for line in exists:
|
||||||
@ -507,7 +507,7 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog) -> None:
|
|||||||
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
|
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
|
||||||
|
|
||||||
mocker.patch('freqtrade.data.history.load_data', MagicMock(return_value={}))
|
mocker.patch('freqtrade.data.history.load_data', MagicMock(return_value={}))
|
||||||
mocker.patch('freqtrade.optimize.get_timeframe', get_timeframe)
|
mocker.patch('freqtrade.data.history.get_timeframe', get_timeframe)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', MagicMock())
|
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', MagicMock())
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -708,7 +708,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair):
|
|||||||
data = trim_dictlist(data, -500)
|
data = trim_dictlist(data, -500)
|
||||||
|
|
||||||
# Remove data for one pair from the beginning of the data
|
# Remove data for one pair from the beginning of the data
|
||||||
data[pair] = data[pair][tres:]
|
data[pair] = data[pair][tres:].reset_index()
|
||||||
# We need to enable sell-signal - otherwise it sells on ROI!!
|
# We need to enable sell-signal - otherwise it sells on ROI!!
|
||||||
default_conf['experimental'] = {"use_sell_signal": True}
|
default_conf['experimental'] = {"use_sell_signal": True}
|
||||||
default_conf['ticker_interval'] = '5m'
|
default_conf['ticker_interval'] = '5m'
|
||||||
@ -847,7 +847,7 @@ def test_backtest_start_live(default_conf, mocker, caplog):
|
|||||||
'--disable-max-market-positions'
|
'--disable-max-market-positions'
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
args = get_args(args)
|
||||||
start(args)
|
start_backtesting(args)
|
||||||
# check the logs, that will contain the backtest result
|
# check the logs, that will contain the backtest result
|
||||||
exists = [
|
exists = [
|
||||||
'Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...',
|
'Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...',
|
||||||
@ -858,7 +858,8 @@ def test_backtest_start_live(default_conf, mocker, caplog):
|
|||||||
'Using stake_currency: BTC ...',
|
'Using stake_currency: BTC ...',
|
||||||
'Using stake_amount: 0.001 ...',
|
'Using stake_amount: 0.001 ...',
|
||||||
'Downloading data for all pairs in whitelist ...',
|
'Downloading data for all pairs in whitelist ...',
|
||||||
'Measuring data from 2017-11-14T19:31:00+00:00 up to 2017-11-14T22:58:00+00:00 (0 days)..',
|
'Backtesting with data from 2017-11-14T19:31:00+00:00 '
|
||||||
|
'up to 2017-11-14T22:58:00+00:00 (0 days)..',
|
||||||
'Parameter --enable-position-stacking detected ...'
|
'Parameter --enable-position-stacking detected ...'
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -900,7 +901,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog):
|
|||||||
'TestStrategy',
|
'TestStrategy',
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
args = get_args(args)
|
||||||
start(args)
|
start_backtesting(args)
|
||||||
# 2 backtests, 4 tables
|
# 2 backtests, 4 tables
|
||||||
assert backtestmock.call_count == 2
|
assert backtestmock.call_count == 2
|
||||||
assert gen_table_mock.call_count == 4
|
assert gen_table_mock.call_count == 4
|
||||||
@ -916,7 +917,8 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog):
|
|||||||
'Using stake_currency: BTC ...',
|
'Using stake_currency: BTC ...',
|
||||||
'Using stake_amount: 0.001 ...',
|
'Using stake_amount: 0.001 ...',
|
||||||
'Downloading data for all pairs in whitelist ...',
|
'Downloading data for all pairs in whitelist ...',
|
||||||
'Measuring data from 2017-11-14T19:31:00+00:00 up to 2017-11-14T22:58:00+00:00 (0 days)..',
|
'Backtesting with data from 2017-11-14T19:31:00+00:00 '
|
||||||
|
'up to 2017-11-14T22:58:00+00:00 (0 days)..',
|
||||||
'Parameter --enable-position-stacking detected ...',
|
'Parameter --enable-position-stacking detected ...',
|
||||||
'Running backtesting for Strategy DefaultStrategy',
|
'Running backtesting for Strategy DefaultStrategy',
|
||||||
'Running backtesting for Strategy TestStrategy',
|
'Running backtesting for Strategy TestStrategy',
|
||||||
|
@ -7,7 +7,8 @@ from unittest.mock import MagicMock
|
|||||||
|
|
||||||
from freqtrade.arguments import Arguments
|
from freqtrade.arguments import Arguments
|
||||||
from freqtrade.edge import PairInfo
|
from freqtrade.edge import PairInfo
|
||||||
from freqtrade.optimize.edge_cli import EdgeCli, setup_configuration, start
|
from freqtrade.optimize import start_edge, setup_configuration
|
||||||
|
from freqtrade.optimize.edge_cli import EdgeCli
|
||||||
from freqtrade.state import RunMode
|
from freqtrade.state import RunMode
|
||||||
from freqtrade.tests.conftest import log_has, log_has_re, patch_exchange
|
from freqtrade.tests.conftest import log_has, log_has_re, patch_exchange
|
||||||
|
|
||||||
@ -27,8 +28,8 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
|
|||||||
'edge'
|
'edge'
|
||||||
]
|
]
|
||||||
|
|
||||||
config = setup_configuration(get_args(args))
|
config = setup_configuration(get_args(args), RunMode.EDGE)
|
||||||
assert config['runmode'] == RunMode.EDGECLI
|
assert config['runmode'] == RunMode.EDGE
|
||||||
|
|
||||||
assert 'max_open_trades' in config
|
assert 'max_open_trades' in config
|
||||||
assert 'stake_currency' in config
|
assert 'stake_currency' in config
|
||||||
@ -67,14 +68,14 @@ def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> N
|
|||||||
'--stoplosses=-0.01,-0.10,-0.001'
|
'--stoplosses=-0.01,-0.10,-0.001'
|
||||||
]
|
]
|
||||||
|
|
||||||
config = setup_configuration(get_args(args))
|
config = setup_configuration(get_args(args), RunMode.EDGE)
|
||||||
assert 'max_open_trades' in config
|
assert 'max_open_trades' in config
|
||||||
assert 'stake_currency' in config
|
assert 'stake_currency' in config
|
||||||
assert 'stake_amount' in config
|
assert 'stake_amount' in config
|
||||||
assert 'exchange' in config
|
assert 'exchange' in config
|
||||||
assert 'pair_whitelist' in config['exchange']
|
assert 'pair_whitelist' in config['exchange']
|
||||||
assert 'datadir' in config
|
assert 'datadir' in config
|
||||||
assert config['runmode'] == RunMode.EDGECLI
|
assert config['runmode'] == RunMode.EDGE
|
||||||
assert log_has(
|
assert log_has(
|
||||||
'Using data folder: {} ...'.format(config['datadir']),
|
'Using data folder: {} ...'.format(config['datadir']),
|
||||||
caplog.record_tuples
|
caplog.record_tuples
|
||||||
@ -106,7 +107,7 @@ def test_start(mocker, fee, edge_conf, caplog) -> None:
|
|||||||
'edge'
|
'edge'
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
args = get_args(args)
|
||||||
start(args)
|
start_edge(args)
|
||||||
assert log_has(
|
assert log_has(
|
||||||
'Starting freqtrade in Edge mode',
|
'Starting freqtrade in Edge mode',
|
||||||
caplog.record_tuples
|
caplog.record_tuples
|
||||||
|
@ -3,6 +3,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
from filelock import Timeout
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import pytest
|
import pytest
|
||||||
@ -11,8 +12,9 @@ from freqtrade import DependencyException
|
|||||||
from freqtrade.data.converter import parse_ticker_dataframe
|
from freqtrade.data.converter import parse_ticker_dataframe
|
||||||
from freqtrade.data.history import load_tickerdata_file
|
from freqtrade.data.history import load_tickerdata_file
|
||||||
from freqtrade.optimize.default_hyperopt import DefaultHyperOpts
|
from freqtrade.optimize.default_hyperopt import DefaultHyperOpts
|
||||||
from freqtrade.optimize.hyperopt import Hyperopt, setup_configuration, start
|
from freqtrade.optimize.hyperopt import Hyperopt, HYPEROPT_LOCKFILE
|
||||||
from freqtrade.resolvers import HyperOptResolver
|
from freqtrade.optimize import setup_configuration, start_hyperopt
|
||||||
|
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver
|
||||||
from freqtrade.state import RunMode
|
from freqtrade.state import RunMode
|
||||||
from freqtrade.tests.conftest import log_has, log_has_re, patch_exchange
|
from freqtrade.tests.conftest import log_has, log_has_re, patch_exchange
|
||||||
from freqtrade.tests.optimize.test_backtesting import get_args
|
from freqtrade.tests.optimize.test_backtesting import get_args
|
||||||
@ -52,7 +54,7 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca
|
|||||||
'hyperopt'
|
'hyperopt'
|
||||||
]
|
]
|
||||||
|
|
||||||
config = setup_configuration(get_args(args))
|
config = setup_configuration(get_args(args), RunMode.HYPEROPT)
|
||||||
assert 'max_open_trades' in config
|
assert 'max_open_trades' in config
|
||||||
assert 'stake_currency' in config
|
assert 'stake_currency' in config
|
||||||
assert 'stake_amount' in config
|
assert 'stake_amount' in config
|
||||||
@ -100,7 +102,7 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo
|
|||||||
'--print-all'
|
'--print-all'
|
||||||
]
|
]
|
||||||
|
|
||||||
config = setup_configuration(get_args(args))
|
config = setup_configuration(get_args(args), RunMode.HYPEROPT)
|
||||||
assert 'max_open_trades' in config
|
assert 'max_open_trades' in config
|
||||||
assert 'stake_currency' in config
|
assert 'stake_currency' in config
|
||||||
assert 'stake_amount' in config
|
assert 'stake_amount' in config
|
||||||
@ -183,7 +185,7 @@ def test_start(mocker, default_conf, caplog) -> None:
|
|||||||
'--epochs', '5'
|
'--epochs', '5'
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
args = get_args(args)
|
||||||
start(args)
|
start_hyperopt(args)
|
||||||
|
|
||||||
import pprint
|
import pprint
|
||||||
pprint.pprint(caplog.record_tuples)
|
pprint.pprint(caplog.record_tuples)
|
||||||
@ -195,6 +197,33 @@ def test_start(mocker, default_conf, caplog) -> None:
|
|||||||
assert start_mock.call_count == 1
|
assert start_mock.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_start_no_data(mocker, default_conf, caplog) -> None:
|
||||||
|
mocker.patch(
|
||||||
|
'freqtrade.configuration.Configuration._load_config_file',
|
||||||
|
lambda *args, **kwargs: default_conf
|
||||||
|
)
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock(return_value={}))
|
||||||
|
mocker.patch(
|
||||||
|
'freqtrade.optimize.hyperopt.get_timeframe',
|
||||||
|
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13)))
|
||||||
|
)
|
||||||
|
|
||||||
|
patch_exchange(mocker)
|
||||||
|
|
||||||
|
args = [
|
||||||
|
'--config', 'config.json',
|
||||||
|
'hyperopt',
|
||||||
|
'--epochs', '5'
|
||||||
|
]
|
||||||
|
args = get_args(args)
|
||||||
|
start_hyperopt(args)
|
||||||
|
|
||||||
|
import pprint
|
||||||
|
pprint.pprint(caplog.record_tuples)
|
||||||
|
|
||||||
|
assert log_has('No data found. Terminating.', caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
def test_start_failure(mocker, default_conf, caplog) -> None:
|
def test_start_failure(mocker, default_conf, caplog) -> None:
|
||||||
start_mock = MagicMock()
|
start_mock = MagicMock()
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
@ -212,13 +241,35 @@ def test_start_failure(mocker, default_conf, caplog) -> None:
|
|||||||
]
|
]
|
||||||
args = get_args(args)
|
args = get_args(args)
|
||||||
with pytest.raises(DependencyException):
|
with pytest.raises(DependencyException):
|
||||||
start(args)
|
start_hyperopt(args)
|
||||||
assert log_has(
|
assert log_has(
|
||||||
"Please don't use --strategy for hyperopt.",
|
"Please don't use --strategy for hyperopt.",
|
||||||
caplog.record_tuples
|
caplog.record_tuples
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_start_filelock(mocker, default_conf, caplog) -> None:
|
||||||
|
start_mock = MagicMock(side_effect=Timeout(HYPEROPT_LOCKFILE))
|
||||||
|
mocker.patch(
|
||||||
|
'freqtrade.configuration.Configuration._load_config_file',
|
||||||
|
lambda *args, **kwargs: default_conf
|
||||||
|
)
|
||||||
|
mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.start', start_mock)
|
||||||
|
patch_exchange(mocker)
|
||||||
|
|
||||||
|
args = [
|
||||||
|
'--config', 'config.json',
|
||||||
|
'hyperopt',
|
||||||
|
'--epochs', '5'
|
||||||
|
]
|
||||||
|
args = get_args(args)
|
||||||
|
start_hyperopt(args)
|
||||||
|
assert log_has(
|
||||||
|
"Another running instance of freqtrade Hyperopt detected.",
|
||||||
|
caplog.record_tuples
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_loss_calculation_prefer_correct_trade_count(hyperopt) -> None:
|
def test_loss_calculation_prefer_correct_trade_count(hyperopt) -> None:
|
||||||
|
|
||||||
correct = hyperopt.calculate_loss(1, hyperopt.target_trades, 20)
|
correct = hyperopt.calculate_loss(1, hyperopt.target_trades, 20)
|
||||||
@ -249,11 +300,12 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None:
|
|||||||
'loss': 1,
|
'loss': 1,
|
||||||
'current_tries': 1,
|
'current_tries': 1,
|
||||||
'total_tries': 2,
|
'total_tries': 2,
|
||||||
'result': 'foo'
|
'result': 'foo.',
|
||||||
|
'initial_point': False
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
out, err = capsys.readouterr()
|
out, err = capsys.readouterr()
|
||||||
assert ' 1/2: foo. Loss 1.00000' in out
|
assert ' 2/2: foo. Objective: 1.00000' in out
|
||||||
|
|
||||||
|
|
||||||
def test_no_log_if_loss_does_not_improve(hyperopt, caplog) -> None:
|
def test_no_log_if_loss_does_not_improve(hyperopt, caplog) -> None:
|
||||||
@ -309,6 +361,11 @@ def test_roi_table_generation(hyperopt) -> None:
|
|||||||
def test_start_calls_optimizer(mocker, default_conf, caplog) -> None:
|
def test_start_calls_optimizer(mocker, default_conf, caplog) -> None:
|
||||||
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
|
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||||
|
mocker.patch(
|
||||||
|
'freqtrade.optimize.hyperopt.get_timeframe',
|
||||||
|
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13)))
|
||||||
|
)
|
||||||
|
|
||||||
parallel = mocker.patch(
|
parallel = mocker.patch(
|
||||||
'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel',
|
'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel',
|
||||||
MagicMock(return_value=[{'loss': 1, 'result': 'foo result', 'params': {}}])
|
MagicMock(return_value=[{'loss': 1, 'result': 'foo result', 'params': {}}])
|
||||||
@ -459,7 +516,7 @@ def test_generate_optimizer(mocker, default_conf) -> None:
|
|||||||
response_expected = {
|
response_expected = {
|
||||||
'loss': 1.9840569076926293,
|
'loss': 1.9840569076926293,
|
||||||
'result': ' 1 trades. Avg profit 2.31%. Total profit 0.00023300 BTC '
|
'result': ' 1 trades. Avg profit 2.31%. Total profit 0.00023300 BTC '
|
||||||
'(0.0231Σ%). Avg duration 100.0 mins.',
|
'( 2.31Σ%). Avg duration 100.0 mins.',
|
||||||
'params': optimizer_param
|
'params': optimizer_param
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
# pragma pylint: disable=missing-docstring, protected-access, C0103
|
|
||||||
from freqtrade import optimize
|
|
||||||
from freqtrade.arguments import TimeRange
|
|
||||||
from freqtrade.data import history
|
|
||||||
from freqtrade.exchange import timeframe_to_minutes
|
|
||||||
from freqtrade.strategy.default_strategy import DefaultStrategy
|
|
||||||
from freqtrade.tests.conftest import log_has, patch_exchange
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_timeframe(default_conf, mocker) -> None:
|
|
||||||
patch_exchange(mocker)
|
|
||||||
strategy = DefaultStrategy(default_conf)
|
|
||||||
|
|
||||||
data = strategy.tickerdata_to_dataframe(
|
|
||||||
history.load_data(
|
|
||||||
datadir=None,
|
|
||||||
ticker_interval='1m',
|
|
||||||
pairs=['UNITTEST/BTC']
|
|
||||||
)
|
|
||||||
)
|
|
||||||
min_date, max_date = optimize.get_timeframe(data)
|
|
||||||
assert min_date.isoformat() == '2017-11-04T23:02:00+00:00'
|
|
||||||
assert max_date.isoformat() == '2017-11-14T22:58:00+00:00'
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_backtest_data_warn(default_conf, mocker, caplog) -> None:
|
|
||||||
patch_exchange(mocker)
|
|
||||||
strategy = DefaultStrategy(default_conf)
|
|
||||||
|
|
||||||
data = strategy.tickerdata_to_dataframe(
|
|
||||||
history.load_data(
|
|
||||||
datadir=None,
|
|
||||||
ticker_interval='1m',
|
|
||||||
pairs=['UNITTEST/BTC'],
|
|
||||||
fill_up_missing=False
|
|
||||||
)
|
|
||||||
)
|
|
||||||
min_date, max_date = optimize.get_timeframe(data)
|
|
||||||
caplog.clear()
|
|
||||||
assert optimize.validate_backtest_data(data, min_date, max_date,
|
|
||||||
timeframe_to_minutes('1m'))
|
|
||||||
assert len(caplog.record_tuples) == 1
|
|
||||||
assert log_has(
|
|
||||||
"UNITTEST/BTC has missing frames: expected 14396, got 13680, that's 716 missing values",
|
|
||||||
caplog.record_tuples)
|
|
||||||
|
|
||||||
|
|
||||||
def test_validate_backtest_data(default_conf, mocker, caplog) -> None:
|
|
||||||
patch_exchange(mocker)
|
|
||||||
strategy = DefaultStrategy(default_conf)
|
|
||||||
|
|
||||||
timerange = TimeRange('index', 'index', 200, 250)
|
|
||||||
data = strategy.tickerdata_to_dataframe(
|
|
||||||
history.load_data(
|
|
||||||
datadir=None,
|
|
||||||
ticker_interval='5m',
|
|
||||||
pairs=['UNITTEST/BTC'],
|
|
||||||
timerange=timerange
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
min_date, max_date = optimize.get_timeframe(data)
|
|
||||||
caplog.clear()
|
|
||||||
assert not optimize.validate_backtest_data(data, min_date, max_date,
|
|
||||||
timeframe_to_minutes('5m'))
|
|
||||||
assert len(caplog.record_tuples) == 0
|
|
@ -47,12 +47,14 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
|
|||||||
|
|
||||||
freqtradebot.create_trade()
|
freqtradebot.create_trade()
|
||||||
results = rpc._rpc_trade_status()
|
results = rpc._rpc_trade_status()
|
||||||
|
|
||||||
assert {
|
assert {
|
||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
'base_currency': 'BTC',
|
'base_currency': 'BTC',
|
||||||
'date': ANY,
|
'open_date': ANY,
|
||||||
|
'open_date_hum': ANY,
|
||||||
|
'close_date': None,
|
||||||
|
'close_date_hum': None,
|
||||||
'open_rate': 1.099e-05,
|
'open_rate': 1.099e-05,
|
||||||
'close_rate': None,
|
'close_rate': None,
|
||||||
'current_rate': 1.098e-05,
|
'current_rate': 1.098e-05,
|
||||||
@ -78,7 +80,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
|
|||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
'base_currency': 'BTC',
|
'base_currency': 'BTC',
|
||||||
'date': ANY,
|
'open_date': ANY,
|
||||||
|
'open_date_hum': ANY,
|
||||||
|
'close_date': None,
|
||||||
|
'close_date_hum': None,
|
||||||
'open_rate': 1.099e-05,
|
'open_rate': 1.099e-05,
|
||||||
'close_rate': None,
|
'close_rate': None,
|
||||||
'current_rate': ANY,
|
'current_rate': ANY,
|
||||||
@ -114,7 +119,7 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
|
|||||||
|
|
||||||
freqtradebot.create_trade()
|
freqtradebot.create_trade()
|
||||||
result = rpc._rpc_status_table()
|
result = rpc._rpc_status_table()
|
||||||
assert 'just now' in result['Since'].all()
|
assert 'instantly' in result['Since'].all()
|
||||||
assert 'ETH/BTC' in result['Pair'].all()
|
assert 'ETH/BTC' in result['Pair'].all()
|
||||||
assert '-0.59%' in result['Profit'].all()
|
assert '-0.59%' in result['Profit'].all()
|
||||||
|
|
||||||
@ -123,7 +128,7 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
|
|||||||
# invalidate ticker cache
|
# invalidate ticker cache
|
||||||
rpc._freqtrade.exchange._cached_ticker = {}
|
rpc._freqtrade.exchange._cached_ticker = {}
|
||||||
result = rpc._rpc_status_table()
|
result = rpc._rpc_status_table()
|
||||||
assert 'just now' in result['Since'].all()
|
assert 'instantly' in result['Since'].all()
|
||||||
assert 'ETH/BTC' in result['Pair'].all()
|
assert 'ETH/BTC' in result['Pair'].all()
|
||||||
assert 'nan%' in result['Profit'].all()
|
assert 'nan%' in result['Profit'].all()
|
||||||
|
|
||||||
|
@ -192,7 +192,10 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
|
|||||||
'trade_id': 1,
|
'trade_id': 1,
|
||||||
'pair': 'ETH/BTC',
|
'pair': 'ETH/BTC',
|
||||||
'base_currency': 'BTC',
|
'base_currency': 'BTC',
|
||||||
'date': arrow.utcnow(),
|
'open_date': arrow.utcnow(),
|
||||||
|
'open_date_hum': arrow.utcnow().humanize,
|
||||||
|
'close_date': None,
|
||||||
|
'close_date_hum': None,
|
||||||
'open_rate': 1.099e-05,
|
'open_rate': 1.099e-05,
|
||||||
'close_rate': None,
|
'close_rate': None,
|
||||||
'current_rate': 1.098e-05,
|
'current_rate': 1.098e-05,
|
||||||
@ -519,6 +522,11 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
|
|||||||
'total': 1.0,
|
'total': 1.0,
|
||||||
'free': 1.0,
|
'free': 1.0,
|
||||||
'used': 0.0
|
'used': 0.0
|
||||||
|
},
|
||||||
|
'EUR': {
|
||||||
|
'total': 10.0,
|
||||||
|
'free': 10.0,
|
||||||
|
'used': 0.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -562,6 +570,7 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
|
|||||||
assert '*BTC:*' in result
|
assert '*BTC:*' in result
|
||||||
assert '*ETH:*' not in result
|
assert '*ETH:*' not in result
|
||||||
assert '*USDT:*' in result
|
assert '*USDT:*' in result
|
||||||
|
assert '*EUR:*' in result
|
||||||
assert 'Balance:' in result
|
assert 'Balance:' in result
|
||||||
assert 'Est. BTC:' in result
|
assert 'Est. BTC:' in result
|
||||||
assert 'BTC: 12.00000000' in result
|
assert 'BTC: 12.00000000' in result
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
# pragma pylint: disable=missing-docstring, C0103
|
# pragma pylint: disable=missing-docstring, C0103
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -185,3 +184,22 @@ def test_testdata_dl_options() -> None:
|
|||||||
assert args.export == 'export/folder'
|
assert args.export == 'export/folder'
|
||||||
assert args.days == 30
|
assert args.days == 30
|
||||||
assert args.exchange == 'binance'
|
assert args.exchange == 'binance'
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_int_positive() -> None:
|
||||||
|
|
||||||
|
assert Arguments.check_int_positive("3") == 3
|
||||||
|
assert Arguments.check_int_positive("1") == 1
|
||||||
|
assert Arguments.check_int_positive("100") == 100
|
||||||
|
|
||||||
|
with pytest.raises(argparse.ArgumentTypeError):
|
||||||
|
Arguments.check_int_positive("-2")
|
||||||
|
|
||||||
|
with pytest.raises(argparse.ArgumentTypeError):
|
||||||
|
Arguments.check_int_positive("0")
|
||||||
|
|
||||||
|
with pytest.raises(argparse.ArgumentTypeError):
|
||||||
|
Arguments.check_int_positive("3.5")
|
||||||
|
|
||||||
|
with pytest.raises(argparse.ArgumentTypeError):
|
||||||
|
Arguments.check_int_positive("DeadBeef")
|
||||||
|
@ -19,7 +19,7 @@ def test_parse_args_backtesting(mocker) -> None:
|
|||||||
Test that main() can start backtesting and also ensure we can pass some specific arguments
|
Test that main() can start backtesting and also ensure we can pass some specific arguments
|
||||||
further argument parsing is done in test_arguments.py
|
further argument parsing is done in test_arguments.py
|
||||||
"""
|
"""
|
||||||
backtesting_mock = mocker.patch('freqtrade.optimize.backtesting.start', MagicMock())
|
backtesting_mock = mocker.patch('freqtrade.optimize.start_backtesting', MagicMock())
|
||||||
# it's sys.exit(0) at the end of backtesting
|
# it's sys.exit(0) at the end of backtesting
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
main(['backtesting'])
|
main(['backtesting'])
|
||||||
@ -34,7 +34,7 @@ def test_parse_args_backtesting(mocker) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_main_start_hyperopt(mocker) -> None:
|
def test_main_start_hyperopt(mocker) -> None:
|
||||||
hyperopt_mock = mocker.patch('freqtrade.optimize.hyperopt.start', MagicMock())
|
hyperopt_mock = mocker.patch('freqtrade.optimize.start_hyperopt', MagicMock())
|
||||||
# it's sys.exit(0) at the end of hyperopt
|
# it's sys.exit(0) at the end of hyperopt
|
||||||
with pytest.raises(SystemExit):
|
with pytest.raises(SystemExit):
|
||||||
main(['hyperopt'])
|
main(['hyperopt'])
|
||||||
|
@ -6,7 +6,7 @@ from unittest.mock import MagicMock
|
|||||||
from freqtrade.data.converter import parse_ticker_dataframe
|
from freqtrade.data.converter import parse_ticker_dataframe
|
||||||
from freqtrade.misc import (common_datearray, datesarray_to_datetimearray,
|
from freqtrade.misc import (common_datearray, datesarray_to_datetimearray,
|
||||||
file_dump_json, file_load_json, format_ms_time, shorten_date)
|
file_dump_json, file_load_json, format_ms_time, shorten_date)
|
||||||
from freqtrade.data.history import load_tickerdata_file, make_testdata_path
|
from freqtrade.data.history import load_tickerdata_file, pair_data_filename
|
||||||
from freqtrade.strategy.default_strategy import DefaultStrategy
|
from freqtrade.strategy.default_strategy import DefaultStrategy
|
||||||
|
|
||||||
|
|
||||||
@ -60,13 +60,13 @@ def test_file_dump_json(mocker) -> None:
|
|||||||
def test_file_load_json(mocker) -> None:
|
def test_file_load_json(mocker) -> None:
|
||||||
|
|
||||||
# 7m .json does not exist
|
# 7m .json does not exist
|
||||||
ret = file_load_json(make_testdata_path(None).joinpath('UNITTEST_BTC-7m.json'))
|
ret = file_load_json(pair_data_filename(None, 'UNITTEST/BTC', '7m'))
|
||||||
assert not ret
|
assert not ret
|
||||||
# 1m json exists (but no .gz exists)
|
# 1m json exists (but no .gz exists)
|
||||||
ret = file_load_json(make_testdata_path(None).joinpath('UNITTEST_BTC-1m.json'))
|
ret = file_load_json(pair_data_filename(None, 'UNITTEST/BTC', '1m'))
|
||||||
assert ret
|
assert ret
|
||||||
# 8 .json is empty and will fail if it's loaded. .json.gz is a copy of 1.json
|
# 8 .json is empty and will fail if it's loaded. .json.gz is a copy of 1.json
|
||||||
ret = file_load_json(make_testdata_path(None).joinpath('UNITTEST_BTC-8m.json'))
|
ret = file_load_json(pair_data_filename(None, 'UNITTEST/BTC', '8m'))
|
||||||
assert ret
|
assert ret
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
# pragma pylint: disable=missing-docstring, C0103
|
# pragma pylint: disable=missing-docstring, C0103
|
||||||
from unittest.mock import MagicMock
|
|
||||||
import logging
|
import logging
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
import arrow
|
||||||
import pytest
|
import pytest
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
|
|
||||||
@ -710,3 +711,69 @@ def test_get_open(default_conf, fee):
|
|||||||
Trade.session.add(trade)
|
Trade.session.add(trade)
|
||||||
|
|
||||||
assert len(Trade.get_open_trades()) == 2
|
assert len(Trade.get_open_trades()) == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_to_json(default_conf, fee):
|
||||||
|
init(default_conf)
|
||||||
|
|
||||||
|
# Simulate dry_run entries
|
||||||
|
trade = Trade(
|
||||||
|
pair='ETH/BTC',
|
||||||
|
stake_amount=0.001,
|
||||||
|
amount=123.0,
|
||||||
|
fee_open=fee.return_value,
|
||||||
|
fee_close=fee.return_value,
|
||||||
|
open_date=arrow.utcnow().shift(hours=-2).datetime,
|
||||||
|
open_rate=0.123,
|
||||||
|
exchange='bittrex',
|
||||||
|
open_order_id='dry_run_buy_12345'
|
||||||
|
)
|
||||||
|
result = trade.to_json()
|
||||||
|
assert isinstance(result, dict)
|
||||||
|
print(result)
|
||||||
|
|
||||||
|
assert result == {'trade_id': None,
|
||||||
|
'pair': 'ETH/BTC',
|
||||||
|
'open_date_hum': '2 hours ago',
|
||||||
|
'open_date': trade.open_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
'close_date_hum': None,
|
||||||
|
'close_date': None,
|
||||||
|
'open_rate': 0.123,
|
||||||
|
'close_rate': None,
|
||||||
|
'amount': 123.0,
|
||||||
|
'stake_amount': 0.001,
|
||||||
|
'stop_loss': None,
|
||||||
|
'stop_loss_pct': None,
|
||||||
|
'initial_stop_loss': None,
|
||||||
|
'initial_stop_loss_pct': None}
|
||||||
|
|
||||||
|
# Simulate dry_run entries
|
||||||
|
trade = Trade(
|
||||||
|
pair='XRP/BTC',
|
||||||
|
stake_amount=0.001,
|
||||||
|
amount=100.0,
|
||||||
|
fee_open=fee.return_value,
|
||||||
|
fee_close=fee.return_value,
|
||||||
|
open_date=arrow.utcnow().shift(hours=-2).datetime,
|
||||||
|
close_date=arrow.utcnow().shift(hours=-1).datetime,
|
||||||
|
open_rate=0.123,
|
||||||
|
close_rate=0.125,
|
||||||
|
exchange='bittrex',
|
||||||
|
)
|
||||||
|
result = trade.to_json()
|
||||||
|
assert isinstance(result, dict)
|
||||||
|
|
||||||
|
assert result == {'trade_id': None,
|
||||||
|
'pair': 'XRP/BTC',
|
||||||
|
'open_date_hum': '2 hours ago',
|
||||||
|
'open_date': trade.open_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
'close_date_hum': 'an hour ago',
|
||||||
|
'close_date': trade.close_date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
'open_rate': 0.123,
|
||||||
|
'close_rate': 0.125,
|
||||||
|
'amount': 100.0,
|
||||||
|
'stake_amount': 0.001,
|
||||||
|
'stop_loss': None,
|
||||||
|
'stop_loss_pct': None,
|
||||||
|
'initial_stop_loss': None,
|
||||||
|
'initial_stop_loss_pct': None}
|
||||||
|
202
freqtrade/vendor/qtpylib/indicators.py
vendored
202
freqtrade/vendor/qtpylib/indicators.py
vendored
@ -4,13 +4,13 @@
|
|||||||
# QTPyLib: Quantitative Trading Python Library
|
# QTPyLib: Quantitative Trading Python Library
|
||||||
# https://github.com/ranaroussi/qtpylib
|
# https://github.com/ranaroussi/qtpylib
|
||||||
#
|
#
|
||||||
# Copyright 2016 Ran Aroussi
|
# Copyright 2016-2018 Ran Aroussi
|
||||||
#
|
#
|
||||||
# Licensed under the GNU Lesser General Public License, v3.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with the License.
|
# you may not use this file except in compliance with the License.
|
||||||
# You may obtain a copy of the License at
|
# You may obtain a copy of the License at
|
||||||
#
|
#
|
||||||
# https://www.gnu.org/licenses/lgpl-3.0.en.html
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
#
|
#
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
@ -19,8 +19,8 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
import sys
|
|
||||||
import warnings
|
import warnings
|
||||||
|
import sys
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
@ -62,19 +62,20 @@ def numpy_rolling_series(func):
|
|||||||
|
|
||||||
@numpy_rolling_series
|
@numpy_rolling_series
|
||||||
def numpy_rolling_mean(data, window, as_source=False):
|
def numpy_rolling_mean(data, window, as_source=False):
|
||||||
return np.mean(numpy_rolling_window(data, window), -1)
|
return np.mean(numpy_rolling_window(data, window), axis=-1)
|
||||||
|
|
||||||
|
|
||||||
@numpy_rolling_series
|
@numpy_rolling_series
|
||||||
def numpy_rolling_std(data, window, as_source=False):
|
def numpy_rolling_std(data, window, as_source=False):
|
||||||
return np.std(numpy_rolling_window(data, window), -1)
|
return np.std(numpy_rolling_window(data, window), axis=-1, ddof=1)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def session(df, start='17:00', end='16:00'):
|
def session(df, start='17:00', end='16:00'):
|
||||||
""" remove previous globex day from df """
|
""" remove previous globex day from df """
|
||||||
if len(df) == 0:
|
if df.empty:
|
||||||
return df
|
return df
|
||||||
|
|
||||||
# get start/end/now as decimals
|
# get start/end/now as decimals
|
||||||
@ -103,47 +104,47 @@ def session(df, start='17:00', end='16:00'):
|
|||||||
|
|
||||||
return df.copy()
|
return df.copy()
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def heikinashi(bars):
|
def heikinashi(bars):
|
||||||
bars = bars.copy()
|
bars = bars.copy()
|
||||||
bars['ha_close'] = (bars['open'] + bars['high'] +
|
bars['ha_close'] = (bars['open'] + bars['high'] +
|
||||||
bars['low'] + bars['close']) / 4
|
bars['low'] + bars['close']) / 4
|
||||||
|
|
||||||
bars['ha_open'] = (bars['open'].shift(1) + bars['close'].shift(1)) / 2
|
# ha open
|
||||||
bars.loc[:1, 'ha_open'] = bars['open'].values[0]
|
bars.at[0, 'ha_open'] = (bars.at[0, 'open'] + bars.at[0, 'close']) / 2
|
||||||
for x in range(2):
|
for i in range(1, len(bars)):
|
||||||
bars.loc[1:, 'ha_open'] = (
|
bars.at[i, 'ha_open'] = (bars.at[i - 1, 'ha_open'] + bars.at[i - 1, 'ha_close']) / 2
|
||||||
(bars['ha_open'].shift(1) + bars['ha_close'].shift(1)) / 2)[1:]
|
|
||||||
|
|
||||||
bars['ha_high'] = bars.loc[:, ['high', 'ha_open', 'ha_close']].max(axis=1)
|
bars['ha_high'] = bars.loc[:, ['high', 'ha_open', 'ha_close']].max(axis=1)
|
||||||
bars['ha_low'] = bars.loc[:, ['low', 'ha_open', 'ha_close']].min(axis=1)
|
bars['ha_low'] = bars.loc[:, ['low', 'ha_open', 'ha_close']].min(axis=1)
|
||||||
|
|
||||||
return pd.DataFrame(
|
return pd.DataFrame(index=bars.index,
|
||||||
index=bars.index,
|
data={'open': bars['ha_open'],
|
||||||
data={
|
'high': bars['ha_high'],
|
||||||
'open': bars['ha_open'],
|
'low': bars['ha_low'],
|
||||||
'high': bars['ha_high'],
|
'close': bars['ha_close']})
|
||||||
'low': bars['ha_low'],
|
|
||||||
'close': bars['ha_close']})
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
|
|
||||||
def tdi(series, rsi_len=13, bollinger_len=34, rsi_smoothing=2,
|
|
||||||
rsi_signal_len=7, bollinger_std=1.6185):
|
def tdi(series, rsi_lookback=13, rsi_smooth_len=2,
|
||||||
rsi_series = rsi(series, rsi_len)
|
rsi_signal_len=7, bb_lookback=34, bb_std=1.6185):
|
||||||
bb_series = bollinger_bands(rsi_series, bollinger_len, bollinger_std)
|
|
||||||
signal = sma(rsi_series, rsi_signal_len)
|
rsi_data = rsi(series, rsi_lookback)
|
||||||
rsi_series = sma(rsi_series, rsi_smoothing)
|
rsi_smooth = sma(rsi_data, rsi_smooth_len)
|
||||||
|
rsi_signal = sma(rsi_data, rsi_signal_len)
|
||||||
|
|
||||||
|
bb_series = bollinger_bands(rsi_data, bb_lookback, bb_std)
|
||||||
|
|
||||||
return pd.DataFrame(index=series.index, data={
|
return pd.DataFrame(index=series.index, data={
|
||||||
"rsi": rsi_series,
|
"rsi": rsi_data,
|
||||||
"signal": signal,
|
"rsi_signal": rsi_signal,
|
||||||
"bbupper": bb_series['upper'],
|
"rsi_smooth": rsi_smooth,
|
||||||
"bblower": bb_series['lower'],
|
"rsi_bb_upper": bb_series['upper'],
|
||||||
"bbmid": bb_series['mid']
|
"rsi_bb_lower": bb_series['lower'],
|
||||||
|
"rsi_bb_mid": bb_series['mid']
|
||||||
})
|
})
|
||||||
|
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
@ -163,8 +164,8 @@ def awesome_oscillator(df, weighted=False, fast=5, slow=34):
|
|||||||
|
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
|
|
||||||
def nans(len=1):
|
def nans(length=1):
|
||||||
mtx = np.empty(len)
|
mtx = np.empty(length)
|
||||||
mtx[:] = np.nan
|
mtx[:] = np.nan
|
||||||
return mtx
|
return mtx
|
||||||
|
|
||||||
@ -222,7 +223,7 @@ def crossed(series1, series2, direction=None):
|
|||||||
if isinstance(series1, np.ndarray):
|
if isinstance(series1, np.ndarray):
|
||||||
series1 = pd.Series(series1)
|
series1 = pd.Series(series1)
|
||||||
|
|
||||||
if isinstance(series2, int) or isinstance(series2, float) or isinstance(series2, np.ndarray):
|
if isinstance(series2, (float, int, np.ndarray)):
|
||||||
series2 = pd.Series(index=series1.index, data=series2)
|
series2 = pd.Series(index=series1.index, data=series2)
|
||||||
|
|
||||||
if direction is None or direction == "above":
|
if direction is None or direction == "above":
|
||||||
@ -256,7 +257,7 @@ def rolling_std(series, window=200, min_periods=None):
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
return series.rolling(window=window, min_periods=min_periods).std()
|
return series.rolling(window=window, min_periods=min_periods).std()
|
||||||
except BaseException:
|
except Exception as e: # noqa: F841
|
||||||
return pd.Series(series).rolling(window=window, min_periods=min_periods).std()
|
return pd.Series(series).rolling(window=window, min_periods=min_periods).std()
|
||||||
|
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
@ -269,7 +270,7 @@ def rolling_mean(series, window=200, min_periods=None):
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
return series.rolling(window=window, min_periods=min_periods).mean()
|
return series.rolling(window=window, min_periods=min_periods).mean()
|
||||||
except BaseException:
|
except Exception as e: # noqa: F841
|
||||||
return pd.Series(series).rolling(window=window, min_periods=min_periods).mean()
|
return pd.Series(series).rolling(window=window, min_periods=min_periods).mean()
|
||||||
|
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
@ -279,7 +280,7 @@ def rolling_min(series, window=14, min_periods=None):
|
|||||||
min_periods = window if min_periods is None else min_periods
|
min_periods = window if min_periods is None else min_periods
|
||||||
try:
|
try:
|
||||||
return series.rolling(window=window, min_periods=min_periods).min()
|
return series.rolling(window=window, min_periods=min_periods).min()
|
||||||
except BaseException:
|
except Exception as e: # noqa: F841
|
||||||
return pd.Series(series).rolling(window=window, min_periods=min_periods).min()
|
return pd.Series(series).rolling(window=window, min_periods=min_periods).min()
|
||||||
|
|
||||||
|
|
||||||
@ -289,7 +290,7 @@ def rolling_max(series, window=14, min_periods=None):
|
|||||||
min_periods = window if min_periods is None else min_periods
|
min_periods = window if min_periods is None else min_periods
|
||||||
try:
|
try:
|
||||||
return series.rolling(window=window, min_periods=min_periods).min()
|
return series.rolling(window=window, min_periods=min_periods).min()
|
||||||
except BaseException:
|
except Exception as e: # noqa: F841
|
||||||
return pd.Series(series).rolling(window=window, min_periods=min_periods).min()
|
return pd.Series(series).rolling(window=window, min_periods=min_periods).min()
|
||||||
|
|
||||||
|
|
||||||
@ -299,16 +300,17 @@ def rolling_weighted_mean(series, window=200, min_periods=None):
|
|||||||
min_periods = window if min_periods is None else min_periods
|
min_periods = window if min_periods is None else min_periods
|
||||||
try:
|
try:
|
||||||
return series.ewm(span=window, min_periods=min_periods).mean()
|
return series.ewm(span=window, min_periods=min_periods).mean()
|
||||||
except BaseException:
|
except Exception as e: # noqa: F841
|
||||||
return pd.ewma(series, span=window, min_periods=min_periods)
|
return pd.ewma(series, span=window, min_periods=min_periods)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
|
|
||||||
def hull_moving_average(series, window=200):
|
def hull_moving_average(series, window=200, min_periods=None):
|
||||||
wma = (2 * rolling_weighted_mean(series, window=window / 2)) - \
|
min_periods = window if min_periods is None else min_periods
|
||||||
rolling_weighted_mean(series, window=window)
|
ma = (2 * rolling_weighted_mean(series, window / 2, min_periods)) - \
|
||||||
return rolling_weighted_mean(wma, window=np.sqrt(window))
|
rolling_weighted_mean(series, window, min_periods)
|
||||||
|
return rolling_weighted_mean(ma, np.sqrt(window), min_periods)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
@ -325,8 +327,8 @@ def wma(series, window=200, min_periods=None):
|
|||||||
|
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
|
|
||||||
def hma(series, window=200):
|
def hma(series, window=200, min_periods=None):
|
||||||
return hull_moving_average(series, window=window)
|
return hull_moving_average(series, window=window, min_periods=min_periods)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
@ -361,7 +363,8 @@ def rolling_vwap(bars, window=200, min_periods=None):
|
|||||||
min_periods=min_periods).sum()
|
min_periods=min_periods).sum()
|
||||||
right = volume.rolling(window=window, min_periods=min_periods).sum()
|
right = volume.rolling(window=window, min_periods=min_periods).sum()
|
||||||
|
|
||||||
return pd.Series(index=bars.index, data=(left / right))
|
return pd.Series(index=bars.index, data=(left / right)
|
||||||
|
).replace([np.inf, -np.inf], float('NaN')).ffill()
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
@ -370,6 +373,7 @@ def rsi(series, window=14):
|
|||||||
"""
|
"""
|
||||||
compute the n period relative strength indicator
|
compute the n period relative strength indicator
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 100-(100/relative_strength)
|
# 100-(100/relative_strength)
|
||||||
deltas = np.diff(series)
|
deltas = np.diff(series)
|
||||||
seed = deltas[:window + 1]
|
seed = deltas[:window + 1]
|
||||||
@ -406,13 +410,13 @@ def macd(series, fast=3, slow=10, smooth=16):
|
|||||||
using a fast and slow exponential moving avg'
|
using a fast and slow exponential moving avg'
|
||||||
return value is emaslow, emafast, macd which are len(x) arrays
|
return value is emaslow, emafast, macd which are len(x) arrays
|
||||||
"""
|
"""
|
||||||
macd = rolling_weighted_mean(series, window=fast) - \
|
macd_line = rolling_weighted_mean(series, window=fast) - \
|
||||||
rolling_weighted_mean(series, window=slow)
|
rolling_weighted_mean(series, window=slow)
|
||||||
signal = rolling_weighted_mean(macd, window=smooth)
|
signal = rolling_weighted_mean(macd_line, window=smooth)
|
||||||
histogram = macd - signal
|
histogram = macd_line - signal
|
||||||
# return macd, signal, histogram
|
# return macd_line, signal, histogram
|
||||||
return pd.DataFrame(index=series.index, data={
|
return pd.DataFrame(index=series.index, data={
|
||||||
'macd': macd.values,
|
'macd': macd_line.values,
|
||||||
'signal': signal.values,
|
'signal': signal.values,
|
||||||
'histogram': histogram.values
|
'histogram': histogram.values
|
||||||
})
|
})
|
||||||
@ -421,14 +425,14 @@ def macd(series, fast=3, slow=10, smooth=16):
|
|||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
|
|
||||||
def bollinger_bands(series, window=20, stds=2):
|
def bollinger_bands(series, window=20, stds=2):
|
||||||
sma = rolling_mean(series, window=window)
|
ma = rolling_mean(series, window=window, min_periods=1)
|
||||||
std = rolling_std(series, window=window)
|
std = rolling_std(series, window=window, min_periods=1)
|
||||||
upper = sma + std * stds
|
upper = ma + std * stds
|
||||||
lower = sma - std * stds
|
lower = ma - std * stds
|
||||||
|
|
||||||
return pd.DataFrame(index=series.index, data={
|
return pd.DataFrame(index=series.index, data={
|
||||||
'upper': upper,
|
'upper': upper,
|
||||||
'mid': sma,
|
'mid': ma,
|
||||||
'lower': lower
|
'lower': lower
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -454,7 +458,7 @@ def returns(series):
|
|||||||
try:
|
try:
|
||||||
res = (series / series.shift(1) -
|
res = (series / series.shift(1) -
|
||||||
1).replace([np.inf, -np.inf], float('NaN'))
|
1).replace([np.inf, -np.inf], float('NaN'))
|
||||||
except BaseException:
|
except Exception as e: # noqa: F841
|
||||||
res = nans(len(series))
|
res = nans(len(series))
|
||||||
|
|
||||||
return pd.Series(index=series.index, data=res)
|
return pd.Series(index=series.index, data=res)
|
||||||
@ -466,7 +470,7 @@ def log_returns(series):
|
|||||||
try:
|
try:
|
||||||
res = np.log(series / series.shift(1)
|
res = np.log(series / series.shift(1)
|
||||||
).replace([np.inf, -np.inf], float('NaN'))
|
).replace([np.inf, -np.inf], float('NaN'))
|
||||||
except BaseException:
|
except Exception as e: # noqa: F841
|
||||||
res = nans(len(series))
|
res = nans(len(series))
|
||||||
|
|
||||||
return pd.Series(index=series.index, data=res)
|
return pd.Series(index=series.index, data=res)
|
||||||
@ -479,7 +483,7 @@ def implied_volatility(series, window=252):
|
|||||||
logret = np.log(series / series.shift(1)
|
logret = np.log(series / series.shift(1)
|
||||||
).replace([np.inf, -np.inf], float('NaN'))
|
).replace([np.inf, -np.inf], float('NaN'))
|
||||||
res = numpy_rolling_std(logret, window) * np.sqrt(window)
|
res = numpy_rolling_std(logret, window) * np.sqrt(window)
|
||||||
except BaseException:
|
except Exception as e: # noqa: F841
|
||||||
res = nans(len(series))
|
res = nans(len(series))
|
||||||
|
|
||||||
return pd.Series(index=series.index, data=res)
|
return pd.Series(index=series.index, data=res)
|
||||||
@ -530,32 +534,55 @@ def stoch(df, window=14, d=3, k=3, fast=False):
|
|||||||
compute the n period relative strength indicator
|
compute the n period relative strength indicator
|
||||||
http://excelta.blogspot.co.il/2013/09/stochastic-oscillator-technical.html
|
http://excelta.blogspot.co.il/2013/09/stochastic-oscillator-technical.html
|
||||||
"""
|
"""
|
||||||
highs_ma = pd.concat([df['high'].shift(i)
|
|
||||||
for i in np.arange(window)], 1).apply(list, 1)
|
|
||||||
highs_ma = highs_ma.T.max().T
|
|
||||||
|
|
||||||
lows_ma = pd.concat([df['low'].shift(i)
|
my_df = pd.DataFrame(index=df.index)
|
||||||
for i in np.arange(window)], 1).apply(list, 1)
|
|
||||||
lows_ma = lows_ma.T.min().T
|
|
||||||
|
|
||||||
fast_k = ((df['close'] - lows_ma) / (highs_ma - lows_ma)) * 100
|
my_df['rolling_max'] = df['high'].rolling(window).max()
|
||||||
fast_d = numpy_rolling_mean(fast_k, d)
|
my_df['rolling_min'] = df['low'].rolling(window).min()
|
||||||
|
|
||||||
|
my_df['fast_k'] = (
|
||||||
|
100 * (df['close'] - my_df['rolling_min']) /
|
||||||
|
(my_df['rolling_max'] - my_df['rolling_min'])
|
||||||
|
)
|
||||||
|
my_df['fast_d'] = my_df['fast_k'].rolling(d).mean()
|
||||||
|
|
||||||
if fast:
|
if fast:
|
||||||
data = {
|
return my_df.loc[:, ['fast_k', 'fast_d']]
|
||||||
'k': fast_k,
|
|
||||||
'd': fast_d
|
|
||||||
}
|
|
||||||
|
|
||||||
else:
|
my_df['slow_k'] = my_df['fast_k'].rolling(k).mean()
|
||||||
slow_k = numpy_rolling_mean(fast_k, k)
|
my_df['slow_d'] = my_df['slow_k'].rolling(d).mean()
|
||||||
slow_d = numpy_rolling_mean(slow_k, d)
|
|
||||||
data = {
|
|
||||||
'k': slow_k,
|
|
||||||
'd': slow_d
|
|
||||||
}
|
|
||||||
|
|
||||||
return pd.DataFrame(index=df.index, data=data)
|
return my_df.loc[:, ['slow_k', 'slow_d']]
|
||||||
|
|
||||||
|
# ---------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def zlma(series, window=20, min_periods=None, kind="ema"):
|
||||||
|
"""
|
||||||
|
John Ehlers' Zero lag (exponential) moving average
|
||||||
|
https://en.wikipedia.org/wiki/Zero_lag_exponential_moving_average
|
||||||
|
"""
|
||||||
|
min_periods = window if min_periods is None else min_periods
|
||||||
|
|
||||||
|
lag = (window - 1) // 2
|
||||||
|
series = 2 * series - series.shift(lag)
|
||||||
|
if kind in ['ewm', 'ema']:
|
||||||
|
return wma(series, lag, min_periods)
|
||||||
|
elif kind == "hma":
|
||||||
|
return hma(series, lag, min_periods)
|
||||||
|
return sma(series, lag, min_periods)
|
||||||
|
|
||||||
|
|
||||||
|
def zlema(series, window, min_periods=None):
|
||||||
|
return zlma(series, window, min_periods, kind="ema")
|
||||||
|
|
||||||
|
|
||||||
|
def zlsma(series, window, min_periods=None):
|
||||||
|
return zlma(series, window, min_periods, kind="sma")
|
||||||
|
|
||||||
|
|
||||||
|
def zlhma(series, window, min_periods=None):
|
||||||
|
return zlma(series, window, min_periods, kind="hma")
|
||||||
|
|
||||||
# ---------------------------------------------
|
# ---------------------------------------------
|
||||||
|
|
||||||
@ -571,13 +598,13 @@ def zscore(bars, window=20, stds=1, col='close'):
|
|||||||
|
|
||||||
def pvt(bars):
|
def pvt(bars):
|
||||||
""" Price Volume Trend """
|
""" Price Volume Trend """
|
||||||
pvt = ((bars['close'] - bars['close'].shift(1)) /
|
trend = ((bars['close'] - bars['close'].shift(1)) /
|
||||||
bars['close'].shift(1)) * bars['volume']
|
bars['close'].shift(1)) * bars['volume']
|
||||||
return pvt.cumsum()
|
return trend.cumsum()
|
||||||
|
|
||||||
|
|
||||||
# =============================================
|
# =============================================
|
||||||
|
|
||||||
|
|
||||||
PandasObject.session = session
|
PandasObject.session = session
|
||||||
PandasObject.atr = atr
|
PandasObject.atr = atr
|
||||||
PandasObject.bollinger_bands = bollinger_bands
|
PandasObject.bollinger_bands = bollinger_bands
|
||||||
@ -613,4 +640,11 @@ PandasObject.rolling_weighted_mean = rolling_weighted_mean
|
|||||||
|
|
||||||
PandasObject.sma = sma
|
PandasObject.sma = sma
|
||||||
PandasObject.wma = wma
|
PandasObject.wma = wma
|
||||||
|
PandasObject.ema = wma
|
||||||
PandasObject.hma = hma
|
PandasObject.hma = hma
|
||||||
|
|
||||||
|
PandasObject.zlsma = zlsma
|
||||||
|
PandasObject.zlwma = zlema
|
||||||
|
PandasObject.zlema = zlema
|
||||||
|
PandasObject.zlhma = zlhma
|
||||||
|
PandasObject.zlma = zlma
|
||||||
|
@ -2,8 +2,9 @@ site_name: Freqtrade
|
|||||||
nav:
|
nav:
|
||||||
- About: index.md
|
- About: index.md
|
||||||
- Installation: installation.md
|
- Installation: installation.md
|
||||||
|
- Installation Docker: docker.md
|
||||||
- Configuration: configuration.md
|
- Configuration: configuration.md
|
||||||
- Custom Strategy: bot-optimization.md
|
- Strategy Customization: strategy-customization.md
|
||||||
- Stoploss: stoploss.md
|
- Stoploss: stoploss.md
|
||||||
- Start the bot: bot-usage.md
|
- Start the bot: bot-usage.md
|
||||||
- Control the bot:
|
- Control the bot:
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
# requirements without requirements installable via conda
|
# requirements without requirements installable via conda
|
||||||
# mainly used for Raspberry pi installs
|
# mainly used for Raspberry pi installs
|
||||||
ccxt==1.18.508
|
ccxt==1.18.578
|
||||||
SQLAlchemy==1.3.3
|
SQLAlchemy==1.3.3
|
||||||
python-telegram-bot==11.1.0
|
python-telegram-bot==11.1.0
|
||||||
arrow==0.13.1
|
arrow==0.13.2
|
||||||
cachetools==3.1.0
|
cachetools==3.1.1
|
||||||
requests==2.21.0
|
requests==2.22.0
|
||||||
urllib3==1.24.2 # pyup: ignore
|
urllib3==1.24.2 # pyup: ignore
|
||||||
wrapt==1.11.1
|
wrapt==1.11.1
|
||||||
scikit-learn==0.20.3
|
scikit-learn==0.21.2
|
||||||
joblib==0.13.2
|
joblib==0.13.2
|
||||||
jsonschema==3.0.1
|
jsonschema==3.0.1
|
||||||
TA-Lib==0.4.17
|
TA-Lib==0.4.17
|
||||||
@ -17,13 +17,13 @@ coinmarketcap==5.0.3
|
|||||||
|
|
||||||
# Required for hyperopt
|
# Required for hyperopt
|
||||||
scikit-optimize==0.5.2
|
scikit-optimize==0.5.2
|
||||||
filelock==3.0.10
|
filelock==3.0.12
|
||||||
|
|
||||||
# find first, C search in arrays
|
# find first, C search in arrays
|
||||||
py_find_1st==1.1.3
|
py_find_1st==1.1.3
|
||||||
|
|
||||||
#Load ticker files 30% faster
|
#Load ticker files 30% faster
|
||||||
python-rapidjson==0.7.0
|
python-rapidjson==0.7.1
|
||||||
|
|
||||||
# Notify systemd
|
# Notify systemd
|
||||||
sdnotify==0.3.2
|
sdnotify==0.3.2
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
flake8==3.7.7
|
flake8==3.7.7
|
||||||
flake8-type-annotations==0.1.0
|
flake8-type-annotations==0.1.0
|
||||||
flake8-tidy-imports==2.0.0
|
flake8-tidy-imports==2.0.0
|
||||||
pytest==4.4.1
|
pytest==4.5.0
|
||||||
pytest-mock==1.10.4
|
pytest-mock==1.10.4
|
||||||
pytest-asyncio==0.10.0
|
pytest-asyncio==0.10.0
|
||||||
pytest-cov==2.7.1
|
pytest-cov==2.7.1
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Include all requirements to run the bot.
|
# Include all requirements to run the bot.
|
||||||
-r requirements.txt
|
-r requirements.txt
|
||||||
|
|
||||||
plotly==3.8.1
|
plotly==3.9.0
|
||||||
|
|
||||||
|
@ -3,4 +3,4 @@
|
|||||||
|
|
||||||
numpy==1.16.3
|
numpy==1.16.3
|
||||||
pandas==0.24.2
|
pandas==0.24.2
|
||||||
scipy==1.2.1
|
scipy==1.3.0
|
||||||
|
@ -41,9 +41,10 @@ from freqtrade.arguments import Arguments, TimeRange
|
|||||||
from freqtrade.data import history
|
from freqtrade.data import history
|
||||||
from freqtrade.data.btanalysis import BT_DATA_COLUMNS, load_backtest_data
|
from freqtrade.data.btanalysis import BT_DATA_COLUMNS, load_backtest_data
|
||||||
from freqtrade.exchange import Exchange
|
from freqtrade.exchange import Exchange
|
||||||
from freqtrade.optimize.backtesting import setup_configuration
|
from freqtrade.optimize import setup_configuration
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
from freqtrade.resolvers import StrategyResolver
|
from freqtrade.resolvers import StrategyResolver
|
||||||
|
from freqtrade.state import RunMode
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
_CONF: Dict[str, Any] = {}
|
_CONF: Dict[str, Any] = {}
|
||||||
@ -74,7 +75,7 @@ def load_trades(args: Namespace, pair: str, timerange: TimeRange) -> pd.DataFram
|
|||||||
|
|
||||||
file = Path(args.exportfilename)
|
file = Path(args.exportfilename)
|
||||||
if file.exists():
|
if file.exists():
|
||||||
load_backtest_data(file)
|
trades = load_backtest_data(file)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
trades = pd.DataFrame([], columns=BT_DATA_COLUMNS)
|
trades = pd.DataFrame([], columns=BT_DATA_COLUMNS)
|
||||||
@ -107,7 +108,7 @@ def get_trading_env(args: Namespace):
|
|||||||
global _CONF
|
global _CONF
|
||||||
|
|
||||||
# Load the configuration
|
# Load the configuration
|
||||||
_CONF.update(setup_configuration(args))
|
_CONF.update(setup_configuration(args, RunMode.BACKTEST))
|
||||||
print(_CONF)
|
print(_CONF)
|
||||||
|
|
||||||
pairs = args.pairs.split(',')
|
pairs = args.pairs.split(',')
|
||||||
|
@ -79,9 +79,10 @@ class SampleHyperOpts(IHyperOpt):
|
|||||||
dataframe['close'], dataframe['sar']
|
dataframe['close'], dataframe['sar']
|
||||||
))
|
))
|
||||||
|
|
||||||
dataframe.loc[
|
if conditions:
|
||||||
reduce(lambda x, y: x & y, conditions),
|
dataframe.loc[
|
||||||
'buy'] = 1
|
reduce(lambda x, y: x & y, conditions),
|
||||||
|
'buy'] = 1
|
||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
@ -138,9 +139,10 @@ class SampleHyperOpts(IHyperOpt):
|
|||||||
dataframe['sar'], dataframe['close']
|
dataframe['sar'], dataframe['close']
|
||||||
))
|
))
|
||||||
|
|
||||||
dataframe.loc[
|
if conditions:
|
||||||
reduce(lambda x, y: x & y, conditions),
|
dataframe.loc[
|
||||||
'sell'] = 1
|
reduce(lambda x, y: x & y, conditions),
|
||||||
|
'sell'] = 1
|
||||||
|
|
||||||
return dataframe
|
return dataframe
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ class TestStrategy(IStrategy):
|
|||||||
ticker_interval = '5m'
|
ticker_interval = '5m'
|
||||||
|
|
||||||
# run "populate_indicators" only for new candle
|
# run "populate_indicators" only for new candle
|
||||||
ta_on_candle = False
|
process_only_new_candles = False
|
||||||
|
|
||||||
# Experimental settings (configuration will overide these if set)
|
# Experimental settings (configuration will overide these if set)
|
||||||
use_sell_signal = False
|
use_sell_signal = False
|
||||||
|
Loading…
Reference in New Issue
Block a user