Merge branch 'develop' into python-version

This commit is contained in:
hroff-1902 2019-05-29 20:07:46 +03:00 committed by GitHub
commit 912b06b34b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 1199 additions and 835 deletions

View File

@ -11,7 +11,10 @@ update: all
# allowed: True, False
pin: True
schedule: "every day"
# update schedule
# default: empty
# allowed: "every day", "every week", ..
schedule: "every week"
search: False

View File

@ -3,4 +3,4 @@ FROM freqtradeorg/freqtrade:develop
RUN apt-get update \
&& apt-get -y install git \
&& apt-get clean \
&& pip install git+https://github.com/berlinguyinca/technical
&& pip install git+https://github.com/freqtrade/technical

View File

@ -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.
- `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
For Binance, please add `"BNB/<STAKE>"` to your blacklist to avoid issues.

View File

@ -11,8 +11,8 @@
"sell": 30
},
"bid_strategy": {
"ask_last_balance": 0.0,
"use_order_book": false,
"ask_last_balance": 0.0,
"order_book_top": 1,
"check_depth_of_market": {
"enabled": false,

View File

@ -22,8 +22,8 @@
"sell": 30
},
"bid_strategy": {
"ask_last_balance": 0.0,
"use_order_book": false,
"ask_last_balance": 0.0,
"order_book_top": 1,
"check_depth_of_market": {
"enabled": false,

View File

@ -5,15 +5,14 @@
"fiat_display_currency": "EUR",
"ticker_interval" : "5m",
"dry_run": true,
"db_url": "sqlite:///tradesv3.dryrun.sqlite",
"trailing_stop": false,
"unfilledtimeout": {
"buy": 10,
"sell": 30
},
"bid_strategy": {
"ask_last_balance": 0.0,
"use_order_book": false,
"ask_last_balance": 0.0,
"order_book_top": 1,
"check_depth_of_market": {
"enabled": false,
@ -60,8 +59,8 @@
},
"telegram": {
"enabled": false,
"token": "",
"chat_id": ""
"token": "your_telegram_token",
"chat_id": "your_telegram_chat_id"
},
"initial_state": "running",
"forcebuy_enable": false,

View File

@ -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).
Learn more about strategy file in
[optimize your bot](bot-optimization.md).
[Strategy Customization](strategy-customization.md).
### How to use **--strategy-path**?
@ -296,4 +296,4 @@ in [misc.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/misc.
## Next step
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).

View File

@ -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.
The below is the default which is used if this is not configured in either strategy or configuration file.
Syntax for Strategy:
```python
"order_types": {
order_types = {
"buy": "limit",
"sell": "limit",
"stoploss": "market",
"stoploss_on_exchange": False,
"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

204
docs/docker.md Normal file
View 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).

View File

@ -122,9 +122,10 @@ So let's write the buy strategy using these values:
dataframe['macd'], dataframe['macdsignal']
))
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'buy'] = 1
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'buy'] = 1
return dataframe

View File

@ -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.
- Performance status report: Receive the performance status of your current trades.
## Requirements
### 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.
### Hardware requirements
To run this bot we recommend you a cloud instance with a minimum of:
- 2GB RAM
@ -49,6 +51,7 @@ To run this bot we recommend you a cloud instance with a minimum of:
- 2vCPU
### Software requirements
- Python 3.6.x
- pip (pip3)
- git
@ -58,10 +61,12 @@ To run this bot we recommend you a cloud instance with a minimum of:
## Support
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.
Click [here](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE) to join Slack channel.
## Ready to try?
Begin by reading our installation guide [here](installation).

View File

@ -1,58 +1,21 @@
# Installation
This page explains how to prepare your environment for running the bot.
## Prerequisite
Before running your bot in production you will need to setup few
external API. In production mode, the bot required valid Bittrex API
credentials and a Telegram bot (optional but recommended).
external API. In production mode, the bot will require valid Exchange API
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)
- [Backtesting commands](#setup-your-telegram-bot)
### Setup your exchange account
*To be completed, please feel free to complete this section.*
### Setup your Telegram bot
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.
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.
### 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
Freqtrade provides a Linux/MacOS script to install all dependencies and help you to configure the bot.
```bash
@ -61,9 +24,10 @@ cd freqtrade
git checkout develop
./setup.sh --install
```
!!! Note
Windows installation is explained [here](#windows).
<hr/>
## 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.
@ -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
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
```
*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
@ -437,14 +218,13 @@ The `freqtrade.service.watchdog` file contains an example of the service unit co
as the watchdog.
!!! Note
The sd_notify communication between the bot and the systemd service manager will not work if the bot runs in a
Docker container.
The sd_notify communication between the bot and the systemd service manager will not work if the bot runs in a Docker container.
------
## 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 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.
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.
---

View File

@ -1,63 +1,83 @@
# Plotting
This page explains how to plot prices, indicator, profits.
This page explains how to plot prices, indicators and profits.
## Installation
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
Usage for the price plotter:
```
script/plot_dataframe.py [-h] [-p pairs] [--live]
``` bash
python3 script/plot_dataframe.py [-h] [-p pairs] [--live]
```
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
pairs you would like to plot.
The `-p` pairs argument can be used to specify 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:
```
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:
```
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):
``` 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.
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.
The results may then be plotted with the -s argument:
To plot trades from a backtesting result, use `--export-filename <filename>`
``` 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
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
2) The summarized profit made by backtesting.
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
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,
or one that acts less seldom, but makes big swings.
@ -76,13 +96,14 @@ that makes profit spikes.
Usage for the profit plotter:
```
script/plot_profit.py [-h] [-p pair] [--datadir directory] [--ticker_interval num]
``` bash
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
Example
```
``` bash
python3 scripts/plot_profit.py --datadir ../freqtrade/freqtrade/tests/testdata-20171221/ -p LTC/BTC
```

View File

@ -53,6 +53,12 @@ file as reference.**
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.
!!! 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
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_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?
The default buy strategy is located in the file

View File

@ -1,10 +1,45 @@
# Telegram usage
## Prerequisite
## Setup your Telegram bot
To control your bot with Telegram, you need first to
[set up a Telegram bot](installation.md)
and add your Telegram API keys into your config file.
Below we explain how to create your Telegram Bot, and how to get your
Telegram user id.
### 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

View File

@ -2,14 +2,14 @@
__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.
"""
class OperationalException(BaseException):
class OperationalException(Exception):
"""
Requires manual intervention.
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:
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.
This could happen when an exchange is congested, unavailable, or the user

View File

@ -340,25 +340,25 @@ class Arguments(object):
Builds and attaches all subcommands
: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')
# Add backtesting subcommand
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.backtesting_options(backtesting_cmd)
# Add edge subcommand
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.edge_options(edge_cmd)
# Add hyperopt subcommand
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.hyperopt_options(hyperopt_cmd)
@ -405,7 +405,7 @@ class Arguments(object):
raise Exception('Incorrect syntax for timerange "%s"' % text)
@staticmethod
def check_int_positive(value) -> int:
def check_int_positive(value: str) -> int:
try:
uint = int(value)
if uint <= 0:

View File

@ -5,19 +5,21 @@ Includes:
* load data for a pair (or a list of pairs) from disk
* download data from exchange and store to disk
"""
import logging
import operator
from datetime import datetime
from pathlib import Path
from typing import Optional, List, Dict, Tuple, Any
from typing import Any, Dict, List, Optional, Tuple
import arrow
from pandas import DataFrame
from freqtrade import misc, OperationalException
from freqtrade import OperationalException, misc
from freqtrade.arguments import TimeRange
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.exchange import Exchange, timeframe_to_minutes
logger = logging.getLogger(__name__)
@ -63,12 +65,8 @@ def load_tickerdata_file(
Load a pair from file, either .json.gz or .json
:return tickerlist or None if unsuccesful
"""
path = make_testdata_path(datadir)
pair_s = pair.replace('/', '_')
file = path.joinpath(f'{pair_s}-{ticker_interval}.json')
pairdata = misc.file_load_json(file)
filename = pair_data_filename(datadir, pair, ticker_interval)
pairdata = misc.file_load_json(filename)
if not pairdata:
return None
@ -90,13 +88,8 @@ def load_pair_history(pair: str,
:return: DataFrame with ohlcv data
"""
# If the user force the refresh of pairs
# The user forced the refresh of 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,
exchange=exchange,
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'))
return parse_ticker_dataframe(pairdata, ticker_interval, fill_up_missing)
else:
logger.warning('No data for pair: "%s", Interval: %s. '
'Use --refresh-pairs-cached option or download_backtest_data.py '
'script to download the data',
pair, ticker_interval)
logger.warning(
f'No history data for pair: "{pair}", interval: {ticker_interval}. '
'Use --refresh-pairs-cached option or download_backtest_data.py '
'script to download the data'
)
return None
@ -151,6 +145,13 @@ def make_testdata_path(datadir: Optional[Path]) -> Path:
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,
timerange: Optional[TimeRange]) -> Tuple[List[Any],
Optional[int]]:
@ -190,7 +191,7 @@ def load_cached_data_for_updating(filename: Path, ticker_interval: str,
def download_pair_history(datadir: Optional[Path],
exchange: Exchange,
exchange: Optional[Exchange],
pair: str,
ticker_interval: str = '5m',
timerange: Optional[TimeRange] = None) -> bool:
@ -201,18 +202,24 @@ def download_pair_history(datadir: Optional[Path],
the full data will be redownloaded
Based on @Rybolov work: https://github.com/rybolov/freqtrade-data
:param pair: pair to download
:param ticker_interval: ticker interval
:param timerange: range of time to download
:return: bool with success state
"""
try:
path = make_testdata_path(datadir)
filepair = pair.replace("/", "_")
filename = path.joinpath(f'{filepair}-{ticker_interval}.json')
if not exchange:
raise OperationalException(
"Exchange needs to be initialized when downloading pair history data"
)
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)
@ -231,7 +238,46 @@ def download_pair_history(datadir: Optional[Path],
misc.file_dump_json(filename, data)
return True
except BaseException:
logger.info('Failed to download the pair: "%s", Interval: %s',
pair, ticker_interval)
except Exception as e:
logger.error(
f'Failed to download history data for pair: "{pair}", interval: {ticker_interval}. '
f'Error: {e}'
)
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

View File

@ -13,7 +13,6 @@ from freqtrade import constants, OperationalException
from freqtrade.arguments import Arguments
from freqtrade.arguments import TimeRange
from freqtrade.data import history
from freqtrade.optimize import get_timeframe
from freqtrade.strategy.interface import SellType
@ -49,7 +48,6 @@ class Edge():
self.strategy = strategy
self.ticker_interval = self.strategy.ticker_interval
self.tickerdata_to_dataframe = self.strategy.tickerdata_to_dataframe
self.get_timeframe = get_timeframe
self.advise_sell = self.strategy.advise_sell
self.advise_buy = self.strategy.advise_buy
@ -117,7 +115,7 @@ class Edge():
preprocessed = self.tickerdata_to_dataframe(data)
# Print timeframe
min_date, max_date = self.get_timeframe(preprocessed)
min_date, max_date = history.get_timeframe(preprocessed)
logger.info(
'Measuring data from %s up to %s (%s days) ...',
min_date.isoformat(),
@ -139,6 +137,7 @@ class Edge():
# If no trade found then exit
if len(trades) == 0:
logger.info("No trades found.")
return False
# Fill missing, calculable columns, profit, duration , abs etc.

View File

@ -510,7 +510,11 @@ class Exchange(object):
_LIMIT = 500
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(
pair, ticker_interval, since) for since in
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)):
input_coroutines.append(self._async_get_candle_history(pair, ticker_interval))
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(
asyncio.gather(*input_coroutines, return_exceptions=True))
@ -578,7 +585,11 @@ class Exchange(object):
"""
try:
# 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,
since=since_ms)
@ -593,7 +604,7 @@ class Exchange(object):
except IndexError:
logger.exception("Error loading %s. Result was %s.", pair, data)
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
except ccxt.NotSupported as e:

View File

@ -1,49 +1,115 @@
# pragma pylint: disable=missing-docstring
import logging
from datetime import datetime
from typing import Dict, Tuple
import operator
from argparse import Namespace
from typing import Any, Dict
import arrow
from pandas import DataFrame
from filelock import FileLock, Timeout
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__)
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
:param data: dictionary with preprocessed backtesting data
:return: tuple containing min_date, max_date
Prepare the configuration for the Hyperopt module
:param args: Cli args from Arguments()
:return: Configuration
"""
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]
configuration = Configuration(args, method)
config = configuration.load_config()
# Ensure we do not use Exchange credentials
config['exchange']['key'] = ''
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,
max_date: datetime, ticker_interval_mins: int) -> bool:
def start_backtesting(args: Namespace) -> None:
"""
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
:param min_date: start-date of the data
:param max_date: end-date of the data
:param ticker_interval_mins: ticker interval in minutes
# Initialize configuration
config = setup_configuration(args, RunMode.BACKTEST)
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
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
Start hyperopt script
:param args: Cli args from Arguments()
:return: None
"""
# Import here to avoid loading hyperopt module when it's not used
from freqtrade.optimize.hyperopt import Hyperopt, HYPEROPT_LOCKFILE
# Initialize configuration
config = setup_configuration(args, RunMode.HYPEROPT)
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()

View File

@ -4,7 +4,6 @@
This module contains the backtesting logic
"""
import logging
from argparse import Namespace
from copy import deepcopy
from datetime import datetime, timedelta
from pathlib import Path
@ -13,10 +12,7 @@ from typing import Any, Dict, List, NamedTuple, Optional
from pandas import DataFrame
from tabulate import tabulate
from freqtrade import optimize
from freqtrade import DependencyException, constants
from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration
from freqtrade.data import history
from freqtrade.data.dataprovider import DataProvider
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.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.state import RunMode
from freqtrade.strategy.interface import SellType, IStrategy
from freqtrade.strategy.interface import IStrategy, SellType
logger = logging.getLogger(__name__)
@ -440,12 +435,12 @@ class Backtesting(object):
logger.info("Running backtesting for Strategy %s", strat.get_strategy_name())
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)
optimize.validate_backtest_data(data, min_date, max_date,
timeframe_to_minutes(self.ticker_interval))
history.validate_backtest_data(data, min_date, max_date,
timeframe_to_minutes(self.ticker_interval))
logger.info(
'Measuring data from %s up to %s (%s days)..',
'Backtesting with data from %s up to %s (%s days)..',
min_date.isoformat(),
max_date.isoformat(),
(max_date - min_date).days
@ -486,39 +481,3 @@ class Backtesting(object):
print(' Strategy Summary '.center(133, '='))
print(self._generate_text_table_strategy(all_results))
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()

View File

@ -70,9 +70,10 @@ class DefaultHyperOpts(IHyperOpt):
dataframe['close'], dataframe['sar']
))
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'buy'] = 1
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'buy'] = 1
return dataframe
@ -129,9 +130,10 @@ class DefaultHyperOpts(IHyperOpt):
dataframe['sar'], dataframe['close']
))
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'sell'] = 1
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'sell'] = 1
return dataframe

View File

@ -4,16 +4,13 @@
This module contains the edge backtesting interface
"""
import logging
from argparse import Namespace
from typing import Dict, Any
from tabulate import tabulate
from freqtrade.edge import Edge
from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration
from freqtrade.exchange import Exchange
from freqtrade.resolvers import StrategyResolver
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
@ -73,37 +70,7 @@ class EdgeCli(object):
floatfmt=floatfmt, tablefmt="pipe")
def start(self) -> None:
self.edge.calculate()
print('') # blank like 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()
result = self.edge.calculate()
if result:
print('') # blank line for readability
print(self._generate_edge_table(self.edge._cached_pairs))

View File

@ -7,32 +7,28 @@ This module contains the hyperopt logic
import logging
import os
import sys
from argparse import Namespace
from math import exp
from operator import itemgetter
from pathlib import Path
from pprint import pprint
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 pandas import DataFrame
from skopt import Optimizer
from skopt.space import Dimension
from freqtrade import DependencyException
from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration
from freqtrade.data.history import load_data
from freqtrade.optimize import get_timeframe
from freqtrade.data.history import load_data, get_timeframe, validate_backtest_data
from freqtrade.exchange import timeframe_to_minutes
from freqtrade.optimize.backtesting import Backtesting
from freqtrade.state import RunMode
from freqtrade.resolvers import HyperOptResolver
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver
logger = logging.getLogger(__name__)
INITIAL_POINTS = 30
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')
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
self.max_accepted_trade_duration = 300
# this is expexted avg profit * expected trade count
# for example 3.5%, 1100 trades, self.expected_max_profit = 3.85
# check that the reported Σ% values do not exceed this!
# This is assumed to be expected avg profit * expected trade count.
# For example, for 0.35% avg per trade (or 0.0035 as ratio) and 1100 trades,
# 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
# Previous evaluations
@ -120,14 +118,20 @@ class Hyperopt(Backtesting):
"""
Log results if it is better than any previous evaluation
"""
if self.config.get('print_all', False) or results['loss'] < self.current_best_loss:
current = results['current_tries']
print_all = self.config.get('print_all', False)
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']
res = results['result']
loss = results['loss']
self.current_best_loss = results['loss']
log_msg = f'\n{current:5d}/{total}: {res}. Loss {loss:.5f}'
print(log_msg)
log_msg = f'{current:5d}/{total}: {res} Objective: {loss:.5f}'
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:
print('.', end='')
sys.stdout.flush()
@ -204,8 +208,8 @@ class Hyperopt(Backtesting):
trade_count = len(results.index)
trade_duration = results.trade_duration.mean()
# If this evaluation contains too short small amount of trades
# to be interesting -- consider it as 'bad' (assign max. loss value)
# If this evaluation contains too short amount of trades to be
# interesting -- consider it as 'bad' (assigned max. loss value)
# in order to cast this hyperspace point away from optimization
# path. We do not want to optimize 'hodl' strategies.
if trade_count < self.config['hyperopt_min_trades']:
@ -231,19 +235,19 @@ class Hyperopt(Backtesting):
avg_profit = results.profit_percent.mean() * 100.0
total_profit = results.profit_abs.sum()
stake_cur = self.config['stake_currency']
profit = results.profit_percent.sum()
profit = results.profit_percent.sum() * 100.0
duration = results.trade_duration.mean()
return (f'{trades:6d} trades. Avg profit {avg_profit: 5.2f}%. '
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:
return Optimizer(
self.hyperopt_space(),
base_estimator="ET",
acq_optimizer="auto",
n_initial_points=30,
n_initial_points=INITIAL_POINTS,
acq_optimizer_kwargs={'n_jobs': cpu_count},
random_state=self.config.get('hyperopt_random_state', None)
)
@ -273,9 +277,25 @@ class Hyperopt(Backtesting):
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'):
self.strategy.advise_indicators = \
self.custom_hyperopt.populate_indicators # type: ignore
dump(self.strategy.tickerdata_to_dataframe(data), TICKERDATA_PICKLE)
# We don't need exchange instance anymore while running hyperopt
@ -301,76 +321,19 @@ class Hyperopt(Backtesting):
self.trials += f_val
for j in range(jobs):
current = i * jobs + j
self.log_results({
'loss': f_val[j]['loss'],
'current_tries': i * jobs + j,
'current_tries': current,
'initial_point': current < INITIAL_POINTS,
'total_tries': self.total_tries,
'result': f_val[j]['result'],
})
logger.debug(f"Optimizer params: {f_val[j]['params']}")
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:
print('User interrupted..')
self.save_trials()
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.

View File

@ -213,11 +213,31 @@ class Trade(_DECL_BASE):
return (f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, '
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):
"""
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.min_rate = min(current_price, self.min_rate or self.open_rate)

View File

@ -1,5 +1,6 @@
from freqtrade.resolvers.iresolver import IResolver # 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.strategy_resolver import StrategyResolver # noqa: F401

View File

@ -100,28 +100,17 @@ class RPC(object):
current_profit = trade.calc_profit_percent(current_rate)
fmt_close_profit = (f'{round(trade.close_profit * 100, 2):.2f}%'
if trade.close_profit else None)
results.append(dict(
trade_id=trade.id,
pair=trade.pair,
trade_dict = trade.to_json()
trade_dict.update(dict(
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,
current_rate=current_rate,
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(
order['type'], order['side'], order['remaining']
) if order else None,
))
results.append(trade_dict)
return results
def _rpc_status_table(self) -> DataFrame:
@ -287,11 +276,12 @@ class RPC(object):
rate = 1.0
else:
try:
if coin == 'USDT':
rate = 1.0 / self._freqtrade.get_sell_rate('BTC/USDT', False)
if coin in('USDT', 'USD', 'EUR'):
rate = 1.0 / self._freqtrade.get_sell_rate('BTC/' + coin, False)
else:
rate = self._freqtrade.get_sell_rate(coin + '/BTC', False)
except (TemporaryError, DependencyException):
logger.warning(f" Could not get rate for pair {coin}.")
continue
est_btc: float = rate * balance['total']
total = total + est_btc

View File

@ -193,14 +193,11 @@ class Telegram(RPC):
try:
results = self._rpc_trade_status()
# pre format data
for result in results:
result['date'] = result['date'].humanize()
messages = []
for r in results:
lines = [
"*Trade ID:* `{trade_id}` `(since {date})`",
"*Trade ID:* `{trade_id}` `(since {open_date_hum})`",
"*Current Pair:* {pair}",
"*Amount:* `{amount} ({stake_amount} {base_currency})`",
"*Open Rate:* `{open_rate:.8f}`",

View File

@ -18,11 +18,11 @@ class State(Enum):
class RunMode(Enum):
"""
Bot running mode (backtest, hyperopt, ...)
can be "live", "dry-run", "backtest", "edgecli", "hyperopt".
can be "live", "dry-run", "backtest", "edge", "hyperopt".
"""
LIVE = "live"
DRY_RUN = "dry_run"
BACKTEST = "backtest"
EDGECLI = "edgecli"
EDGE = "edge"
HYPEROPT = "hyperopt"
OTHER = "other" # Used for plotting scripts and test

View File

@ -2,8 +2,7 @@
import logging
from freqtrade.data.converter import parse_ticker_dataframe, ohlcv_fill_up_missing_data
from freqtrade.data.history import load_pair_history
from freqtrade.optimize import validate_backtest_data, get_timeframe
from freqtrade.data.history import load_pair_history, validate_backtest_data, get_timeframe
from freqtrade.tests.conftest import log_has

View File

@ -2,24 +2,25 @@
import json
import os
from pathlib import Path
import uuid
from pathlib import Path
from shutil import copyfile
import arrow
from pandas import DataFrame
import pytest
from pandas import DataFrame
from freqtrade import OperationalException
from freqtrade.arguments import TimeRange
from freqtrade.data import history
from freqtrade.data.history import (download_pair_history,
load_cached_data_for_updating,
load_tickerdata_file,
make_testdata_path,
load_tickerdata_file, make_testdata_path,
trim_tickerlist)
from freqtrade.exchange import timeframe_to_minutes
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
_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:
ld = history.load_pair_history(pair='UNITTEST/BTC', ticker_interval='30m', datadir=None)
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:
@ -67,7 +72,7 @@ def test_load_data_7min_ticker(mocker, caplog, default_conf) -> None:
assert not isinstance(ld, DataFrame)
assert ld is None
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 '
'script to download the data',
caplog.record_tuples
@ -80,7 +85,11 @@ def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None:
_backup_file(file, copy_file=True)
history.load_data(datadir=None, ticker_interval='1m', pairs=['UNITTEST/BTC'])
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)
@ -100,7 +109,7 @@ def test_load_data_with_new_pair_1min(ticker_history_list, mocker, caplog, defau
pair='MEME/BTC')
assert os.path.isfile(file) is False
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 '
'script to download the data',
caplog.record_tuples
@ -113,7 +122,11 @@ def test_load_data_with_new_pair_1min(ticker_history_list, mocker, caplog, defau
exchange=exchange,
pair='MEME/BTC')
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.*'):
history.load_pair_history(datadir=None,
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:
mocker.patch('freqtrade.exchange.Exchange.get_history',
side_effect=BaseException('File Error'))
side_effect=Exception('File Error'))
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_test_file(file1_1)
_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:
@ -479,3 +496,62 @@ def test_file_dump_json_tofile() -> None:
# Remove the 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

View File

@ -10,10 +10,11 @@ import numpy as np
import pytest
from pandas import DataFrame, to_datetime
from freqtrade import OperationalException
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.edge import Edge, PairInfo
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,
_build_backtest_dataframe,
_get_frame_time_from_offset)
@ -30,7 +31,50 @@ ticker_start_time = arrow.get(2018, 10, 3)
ticker_interval_in_minute = 60
_ohlc = {'date': 0, 'buy': 1, 'open': 2, 'high': 3, 'low': 4, 'close': 5, 'sell': 6, 'volume': 7}
# Helpers for this test file
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
tc0 = BTContainer(data=[
# 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
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):
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
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
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):
edge_conf['edge']['min_trade_number'] = 2
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'].required_risk_reward, 10) == 2.0
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)

View File

@ -1016,7 +1016,7 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None:
exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m')])
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)

View File

@ -2,17 +2,17 @@
import logging
from unittest.mock import MagicMock
from pandas import DataFrame
import pytest
from pandas import DataFrame
from freqtrade.optimize import get_timeframe
from freqtrade.data.history import get_timeframe
from freqtrade.optimize.backtesting import Backtesting
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.optimize import (BTContainer, BTrade,
_build_backtest_dataframe,
_get_frame_time_from_offset,
tests_ticker_interval)
# Test 1 Minus 8% Close
# Test with Stop-loss at 1%

View File

@ -17,9 +17,9 @@ from freqtrade.data import history
from freqtrade.data.btanalysis import evaluate_result_multi
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.data.dataprovider import DataProvider
from freqtrade.optimize import get_timeframe
from freqtrade.optimize.backtesting import (Backtesting, setup_configuration,
start)
from freqtrade.data.history import get_timeframe
from freqtrade.optimize import setup_configuration, start_backtesting
from freqtrade.optimize.backtesting import Backtesting
from freqtrade.state import RunMode
from freqtrade.strategy.default_strategy import DefaultStrategy
from freqtrade.strategy.interface import SellType
@ -33,7 +33,7 @@ def get_args(args) -> List[str]:
def trim_dictlist(dict_list, num):
new = {}
for pair, pair_data in dict_list.items():
new[pair] = pair_data[num:]
new[pair] = pair_data[num:].reset_index()
return new
@ -178,7 +178,7 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
'backtesting'
]
config = setup_configuration(get_args(args))
config = setup_configuration(get_args(args), RunMode.BACKTEST)
assert 'max_open_trades' in config
assert 'stake_currency' 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'
]
config = setup_configuration(get_args(args))
config = setup_configuration(get_args(args), RunMode.BACKTEST)
assert 'max_open_trades' in config
assert 'stake_currency' 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.*'):
setup_configuration(get_args(args))
setup_configuration(get_args(args), RunMode.BACKTEST)
def test_start(mocker, fee, default_conf, caplog) -> None:
@ -307,7 +307,7 @@ def test_start(mocker, fee, default_conf, caplog) -> None:
'backtesting'
]
args = get_args(args)
start(args)
start_backtesting(args)
assert log_has(
'Starting freqtrade in Backtesting mode',
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)
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())
patch_exchange(mocker)
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 stake_currency: BTC ...',
'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)..'
]
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)
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())
patch_exchange(mocker)
mocker.patch.multiple(
@ -708,7 +708,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair):
data = trim_dictlist(data, -500)
# 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!!
default_conf['experimental'] = {"use_sell_signal": True}
default_conf['ticker_interval'] = '5m'
@ -847,7 +847,7 @@ def test_backtest_start_live(default_conf, mocker, caplog):
'--disable-max-market-positions'
]
args = get_args(args)
start(args)
start_backtesting(args)
# check the logs, that will contain the backtest result
exists = [
'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_amount: 0.001 ...',
'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 ...'
]
@ -900,7 +901,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog):
'TestStrategy',
]
args = get_args(args)
start(args)
start_backtesting(args)
# 2 backtests, 4 tables
assert backtestmock.call_count == 2
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_amount: 0.001 ...',
'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 ...',
'Running backtesting for Strategy DefaultStrategy',
'Running backtesting for Strategy TestStrategy',

View File

@ -7,7 +7,8 @@ from unittest.mock import MagicMock
from freqtrade.arguments import Arguments
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.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'
]
config = setup_configuration(get_args(args))
assert config['runmode'] == RunMode.EDGECLI
config = setup_configuration(get_args(args), RunMode.EDGE)
assert config['runmode'] == RunMode.EDGE
assert 'max_open_trades' 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'
]
config = setup_configuration(get_args(args))
config = setup_configuration(get_args(args), RunMode.EDGE)
assert 'max_open_trades' in config
assert 'stake_currency' in config
assert 'stake_amount' in config
assert 'exchange' in config
assert 'pair_whitelist' in config['exchange']
assert 'datadir' in config
assert config['runmode'] == RunMode.EDGECLI
assert config['runmode'] == RunMode.EDGE
assert log_has(
'Using data folder: {} ...'.format(config['datadir']),
caplog.record_tuples
@ -106,7 +107,7 @@ def test_start(mocker, fee, edge_conf, caplog) -> None:
'edge'
]
args = get_args(args)
start(args)
start_edge(args)
assert log_has(
'Starting freqtrade in Edge mode',
caplog.record_tuples

View File

@ -3,6 +3,7 @@ import json
import os
from datetime import datetime
from unittest.mock import MagicMock
from filelock import Timeout
import pandas as pd
import pytest
@ -11,8 +12,9 @@ from freqtrade import DependencyException
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.data.history import load_tickerdata_file
from freqtrade.optimize.default_hyperopt import DefaultHyperOpts
from freqtrade.optimize.hyperopt import Hyperopt, setup_configuration, start
from freqtrade.resolvers import HyperOptResolver
from freqtrade.optimize.hyperopt import Hyperopt, HYPEROPT_LOCKFILE
from freqtrade.optimize import setup_configuration, start_hyperopt
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver
from freqtrade.state import RunMode
from freqtrade.tests.conftest import log_has, log_has_re, patch_exchange
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'
]
config = setup_configuration(get_args(args))
config = setup_configuration(get_args(args), RunMode.HYPEROPT)
assert 'max_open_trades' in config
assert 'stake_currency' in config
assert 'stake_amount' in config
@ -100,7 +102,7 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo
'--print-all'
]
config = setup_configuration(get_args(args))
config = setup_configuration(get_args(args), RunMode.HYPEROPT)
assert 'max_open_trades' in config
assert 'stake_currency' in config
assert 'stake_amount' in config
@ -183,7 +185,7 @@ def test_start(mocker, default_conf, caplog) -> None:
'--epochs', '5'
]
args = get_args(args)
start(args)
start_hyperopt(args)
import pprint
pprint.pprint(caplog.record_tuples)
@ -195,6 +197,33 @@ def test_start(mocker, default_conf, caplog) -> None:
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:
start_mock = MagicMock()
mocker.patch(
@ -212,13 +241,35 @@ def test_start_failure(mocker, default_conf, caplog) -> None:
]
args = get_args(args)
with pytest.raises(DependencyException):
start(args)
start_hyperopt(args)
assert log_has(
"Please don't use --strategy for hyperopt.",
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:
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,
'current_tries': 1,
'total_tries': 2,
'result': 'foo'
'result': 'foo.',
'initial_point': False
}
)
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:
@ -309,6 +361,11 @@ def test_roi_table_generation(hyperopt) -> None:
def test_start_calls_optimizer(mocker, default_conf, caplog) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', 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(
'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel',
MagicMock(return_value=[{'loss': 1, 'result': 'foo result', 'params': {}}])
@ -459,7 +516,7 @@ def test_generate_optimizer(mocker, default_conf) -> None:
response_expected = {
'loss': 1.9840569076926293,
'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
}

View File

@ -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

View File

@ -47,12 +47,14 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
freqtradebot.create_trade()
results = rpc._rpc_trade_status()
assert {
'trade_id': 1,
'pair': 'ETH/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,
'close_rate': None,
'current_rate': 1.098e-05,
@ -78,7 +80,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
'trade_id': 1,
'pair': 'ETH/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,
'close_rate': None,
'current_rate': ANY,
@ -114,7 +119,7 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
freqtradebot.create_trade()
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 '-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
rpc._freqtrade.exchange._cached_ticker = {}
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 'nan%' in result['Profit'].all()

View File

@ -192,7 +192,10 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
'trade_id': 1,
'pair': 'ETH/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,
'close_rate': None,
'current_rate': 1.098e-05,
@ -519,6 +522,11 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
'total': 1.0,
'free': 1.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 '*ETH:*' not in result
assert '*USDT:*' in result
assert '*EUR:*' in result
assert 'Balance:' in result
assert 'Est. BTC:' in result
assert 'BTC: 12.00000000' in result

View File

@ -1,5 +1,4 @@
# pragma pylint: disable=missing-docstring, C0103
import argparse
import pytest
@ -185,3 +184,22 @@ def test_testdata_dl_options() -> None:
assert args.export == 'export/folder'
assert args.days == 30
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")

View File

@ -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
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
with pytest.raises(SystemExit):
main(['backtesting'])
@ -34,7 +34,7 @@ def test_parse_args_backtesting(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
with pytest.raises(SystemExit):
main(['hyperopt'])

View File

@ -6,7 +6,7 @@ from unittest.mock import MagicMock
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.misc import (common_datearray, datesarray_to_datetimearray,
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
@ -60,13 +60,13 @@ def test_file_dump_json(mocker) -> None:
def test_file_load_json(mocker) -> None:
# 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
# 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
# 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

View File

@ -1,7 +1,8 @@
# pragma pylint: disable=missing-docstring, C0103
from unittest.mock import MagicMock
import logging
from unittest.mock import MagicMock
import arrow
import pytest
from sqlalchemy import create_engine
@ -710,3 +711,69 @@ def test_get_open(default_conf, fee):
Trade.session.add(trade)
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}

View File

@ -4,13 +4,13 @@
# QTPyLib: Quantitative Trading Python Library
# 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 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
# distributed under the License is distributed on an "AS IS" BASIS,
@ -19,8 +19,8 @@
# limitations under the License.
#
import sys
import warnings
import sys
from datetime import datetime, timedelta
import numpy as np
@ -62,19 +62,20 @@ def numpy_rolling_series(func):
@numpy_rolling_series
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
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'):
""" remove previous globex day from df """
if len(df) == 0:
if df.empty:
return df
# get start/end/now as decimals
@ -103,47 +104,47 @@ def session(df, start='17:00', end='16:00'):
return df.copy()
# ---------------------------------------------
def heikinashi(bars):
bars = bars.copy()
bars['ha_close'] = (bars['open'] + bars['high'] +
bars['low'] + bars['close']) / 4
bars['ha_open'] = (bars['open'].shift(1) + bars['close'].shift(1)) / 2
bars.loc[:1, 'ha_open'] = bars['open'].values[0]
for x in range(2):
bars.loc[1:, 'ha_open'] = (
(bars['ha_open'].shift(1) + bars['ha_close'].shift(1)) / 2)[1:]
# ha open
bars.at[0, 'ha_open'] = (bars.at[0, 'open'] + bars.at[0, 'close']) / 2
for i in range(1, len(bars)):
bars.at[i, 'ha_open'] = (bars.at[i - 1, 'ha_open'] + bars.at[i - 1, 'ha_close']) / 2
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)
return pd.DataFrame(
index=bars.index,
data={
'open': bars['ha_open'],
'high': bars['ha_high'],
'low': bars['ha_low'],
'close': bars['ha_close']})
return pd.DataFrame(index=bars.index,
data={'open': bars['ha_open'],
'high': bars['ha_high'],
'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):
rsi_series = rsi(series, rsi_len)
bb_series = bollinger_bands(rsi_series, bollinger_len, bollinger_std)
signal = sma(rsi_series, rsi_signal_len)
rsi_series = sma(rsi_series, rsi_smoothing)
def tdi(series, rsi_lookback=13, rsi_smooth_len=2,
rsi_signal_len=7, bb_lookback=34, bb_std=1.6185):
rsi_data = rsi(series, rsi_lookback)
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={
"rsi": rsi_series,
"signal": signal,
"bbupper": bb_series['upper'],
"bblower": bb_series['lower'],
"bbmid": bb_series['mid']
"rsi": rsi_data,
"rsi_signal": rsi_signal,
"rsi_smooth": rsi_smooth,
"rsi_bb_upper": bb_series['upper'],
"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):
mtx = np.empty(len)
def nans(length=1):
mtx = np.empty(length)
mtx[:] = np.nan
return mtx
@ -222,7 +223,7 @@ def crossed(series1, series2, direction=None):
if isinstance(series1, np.ndarray):
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)
if direction is None or direction == "above":
@ -256,7 +257,7 @@ def rolling_std(series, window=200, min_periods=None):
else:
try:
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()
# ---------------------------------------------
@ -269,7 +270,7 @@ def rolling_mean(series, window=200, min_periods=None):
else:
try:
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()
# ---------------------------------------------
@ -279,7 +280,7 @@ def rolling_min(series, window=14, min_periods=None):
min_periods = window if min_periods is None else min_periods
try:
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()
@ -289,7 +290,7 @@ def rolling_max(series, window=14, min_periods=None):
min_periods = window if min_periods is None else min_periods
try:
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()
@ -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
try:
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)
# ---------------------------------------------
def hull_moving_average(series, window=200):
wma = (2 * rolling_weighted_mean(series, window=window / 2)) - \
rolling_weighted_mean(series, window=window)
return rolling_weighted_mean(wma, window=np.sqrt(window))
def hull_moving_average(series, window=200, min_periods=None):
min_periods = window if min_periods is None else min_periods
ma = (2 * rolling_weighted_mean(series, window / 2, min_periods)) - \
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):
return hull_moving_average(series, window=window)
def hma(series, window=200, min_periods=None):
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()
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
"""
# 100-(100/relative_strength)
deltas = np.diff(series)
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'
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)
signal = rolling_weighted_mean(macd, window=smooth)
histogram = macd - signal
# return macd, signal, histogram
signal = rolling_weighted_mean(macd_line, window=smooth)
histogram = macd_line - signal
# return macd_line, signal, histogram
return pd.DataFrame(index=series.index, data={
'macd': macd.values,
'macd': macd_line.values,
'signal': signal.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):
sma = rolling_mean(series, window=window)
std = rolling_std(series, window=window)
upper = sma + std * stds
lower = sma - std * stds
ma = rolling_mean(series, window=window, min_periods=1)
std = rolling_std(series, window=window, min_periods=1)
upper = ma + std * stds
lower = ma - std * stds
return pd.DataFrame(index=series.index, data={
'upper': upper,
'mid': sma,
'mid': ma,
'lower': lower
})
@ -454,7 +458,7 @@ def returns(series):
try:
res = (series / series.shift(1) -
1).replace([np.inf, -np.inf], float('NaN'))
except BaseException:
except Exception as e: # noqa: F841
res = nans(len(series))
return pd.Series(index=series.index, data=res)
@ -466,7 +470,7 @@ def log_returns(series):
try:
res = np.log(series / series.shift(1)
).replace([np.inf, -np.inf], float('NaN'))
except BaseException:
except Exception as e: # noqa: F841
res = nans(len(series))
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)
).replace([np.inf, -np.inf], float('NaN'))
res = numpy_rolling_std(logret, window) * np.sqrt(window)
except BaseException:
except Exception as e: # noqa: F841
res = nans(len(series))
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
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)
for i in np.arange(window)], 1).apply(list, 1)
lows_ma = lows_ma.T.min().T
my_df = pd.DataFrame(index=df.index)
fast_k = ((df['close'] - lows_ma) / (highs_ma - lows_ma)) * 100
fast_d = numpy_rolling_mean(fast_k, d)
my_df['rolling_max'] = df['high'].rolling(window).max()
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:
data = {
'k': fast_k,
'd': fast_d
}
return my_df.loc[:, ['fast_k', 'fast_d']]
else:
slow_k = numpy_rolling_mean(fast_k, k)
slow_d = numpy_rolling_mean(slow_k, d)
data = {
'k': slow_k,
'd': slow_d
}
my_df['slow_k'] = my_df['fast_k'].rolling(k).mean()
my_df['slow_d'] = my_df['slow_k'].rolling(d).mean()
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):
""" Price Volume Trend """
pvt = ((bars['close'] - bars['close'].shift(1)) /
bars['close'].shift(1)) * bars['volume']
return pvt.cumsum()
trend = ((bars['close'] - bars['close'].shift(1)) /
bars['close'].shift(1)) * bars['volume']
return trend.cumsum()
# =============================================
PandasObject.session = session
PandasObject.atr = atr
PandasObject.bollinger_bands = bollinger_bands
@ -613,4 +640,11 @@ PandasObject.rolling_weighted_mean = rolling_weighted_mean
PandasObject.sma = sma
PandasObject.wma = wma
PandasObject.ema = wma
PandasObject.hma = hma
PandasObject.zlsma = zlsma
PandasObject.zlwma = zlema
PandasObject.zlema = zlema
PandasObject.zlhma = zlhma
PandasObject.zlma = zlma

View File

@ -2,8 +2,9 @@ site_name: Freqtrade
nav:
- About: index.md
- Installation: installation.md
- Installation Docker: docker.md
- Configuration: configuration.md
- Custom Strategy: bot-optimization.md
- Strategy Customization: strategy-customization.md
- Stoploss: stoploss.md
- Start the bot: bot-usage.md
- Control the bot:

View File

@ -1,14 +1,14 @@
# requirements without requirements installable via conda
# mainly used for Raspberry pi installs
ccxt==1.18.508
ccxt==1.18.578
SQLAlchemy==1.3.3
python-telegram-bot==11.1.0
arrow==0.13.1
cachetools==3.1.0
requests==2.21.0
arrow==0.13.2
cachetools==3.1.1
requests==2.22.0
urllib3==1.24.2 # pyup: ignore
wrapt==1.11.1
scikit-learn==0.20.3
scikit-learn==0.21.2
joblib==0.13.2
jsonschema==3.0.1
TA-Lib==0.4.17
@ -17,13 +17,13 @@ coinmarketcap==5.0.3
# Required for hyperopt
scikit-optimize==0.5.2
filelock==3.0.10
filelock==3.0.12
# find first, C search in arrays
py_find_1st==1.1.3
#Load ticker files 30% faster
python-rapidjson==0.7.0
python-rapidjson==0.7.1
# Notify systemd
sdnotify==0.3.2

View File

@ -4,7 +4,7 @@
flake8==3.7.7
flake8-type-annotations==0.1.0
flake8-tidy-imports==2.0.0
pytest==4.4.1
pytest==4.5.0
pytest-mock==1.10.4
pytest-asyncio==0.10.0
pytest-cov==2.7.1

View File

@ -1,5 +1,5 @@
# Include all requirements to run the bot.
-r requirements.txt
plotly==3.8.1
plotly==3.9.0

View File

@ -3,4 +3,4 @@
numpy==1.16.3
pandas==0.24.2
scipy==1.2.1
scipy==1.3.0

View File

@ -41,9 +41,10 @@ from freqtrade.arguments import Arguments, TimeRange
from freqtrade.data import history
from freqtrade.data.btanalysis import BT_DATA_COLUMNS, load_backtest_data
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.resolvers import StrategyResolver
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
_CONF: Dict[str, Any] = {}
@ -74,7 +75,7 @@ def load_trades(args: Namespace, pair: str, timerange: TimeRange) -> pd.DataFram
file = Path(args.exportfilename)
if file.exists():
load_backtest_data(file)
trades = load_backtest_data(file)
else:
trades = pd.DataFrame([], columns=BT_DATA_COLUMNS)
@ -107,7 +108,7 @@ def get_trading_env(args: Namespace):
global _CONF
# Load the configuration
_CONF.update(setup_configuration(args))
_CONF.update(setup_configuration(args, RunMode.BACKTEST))
print(_CONF)
pairs = args.pairs.split(',')

View File

@ -79,9 +79,10 @@ class SampleHyperOpts(IHyperOpt):
dataframe['close'], dataframe['sar']
))
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'buy'] = 1
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'buy'] = 1
return dataframe
@ -138,9 +139,10 @@ class SampleHyperOpts(IHyperOpt):
dataframe['sar'], dataframe['close']
))
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'sell'] = 1
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'sell'] = 1
return dataframe

View File

@ -51,7 +51,7 @@ class TestStrategy(IStrategy):
ticker_interval = '5m'
# run "populate_indicators" only for new candle
ta_on_candle = False
process_only_new_candles = False
# Experimental settings (configuration will overide these if set)
use_sell_signal = False