Merge branch 'develop' into test_coverage

This commit is contained in:
kryofly 2018-01-19 08:05:52 +01:00
commit e3088647fc
28 changed files with 743 additions and 308 deletions

View File

@ -136,8 +136,8 @@ to understand the requirements before sending your pull-requests.
### Bot commands ### Bot commands
```bash ```bash
usage: main.py [-h] [-c PATH] [-v] [--version] [--dynamic-whitelist [INT]] usage: main.py [-h] [-v] [--version] [-c PATH] [--dry-run-db] [--datadir PATH]
[--dry-run-db] [--dynamic-whitelist [INT]]
{backtesting,hyperopt} ... {backtesting,hyperopt} ...
Simple High Frequency Trading Bot for crypto currencies Simple High Frequency Trading Bot for crypto currencies
@ -149,16 +149,17 @@ positional arguments:
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
-c PATH, --config PATH
specify configuration file (default: config.json)
-v, --verbose be verbose -v, --verbose be verbose
--version show program's version number and exit --version show program's version number and exit
--dynamic-whitelist [INT] -c PATH, --config PATH
dynamically generate and update whitelist based on 24h specify configuration file (default: config.json)
BaseVolume (Default 20 currencies)
--dry-run-db Force dry run to use a local DB --dry-run-db Force dry run to use a local DB
"tradesv3.dry_run.sqlite" instead of memory DB. Work "tradesv3.dry_run.sqlite" instead of memory DB. Work
only if dry_run is enabled. only if dry_run is enabled.
--datadir PATH path to backtest data (default freqdata/tests/testdata
--dynamic-whitelist [INT]
dynamically generate and update whitelist based on 24h
BaseVolume (Default 20 currencies)
``` ```
More details on: More details on:
- [How to run the bot](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-usage.md#bot-commands) - [How to run the bot](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-usage.md#bot-commands)

View File

@ -51,6 +51,50 @@ python3 ./freqtrade/main.py backtesting --realistic-simulation --live
python3 ./freqtrade/main.py backtesting --datadir freqtrade/tests/testdata-20180101 python3 ./freqtrade/main.py backtesting --datadir freqtrade/tests/testdata-20180101
``` ```
**Running backtest with smaller testset**
Use the `--timerange` argument to change how much of the testset
you want to use. The last N ticks/timeframes will be used.
Example:
```bash
python3 ./freqtrade/main.py backtesting --timerange=-200
```
***Advanced use of timerange***
Doing `--timerange=-200` will get the last 200 timeframes
from your inputdata. You can also specify specific dates,
or a range span indexed by start and stop.
The full timerange specification:
- Use last 123 tickframes of data: `--timerange=-123`
- Use first 123 tickframes of data: `--timerange=123-`
- Use tickframes from line 123 through 456: `--timerange=123-456`
Incoming feature, not implemented yet:
- `--timerange=-20180131`
- `--timerange=20180101-`
- `--timerange=20180101-20181231`
**Update testdata directory**
To update your testdata directory, or download into another testdata directory:
```bash
mkdir -p user_data/data/testdata-20180113
cp freqtrade/tests/testdata/pairs.json user_data/data-20180113
cd user_data/data-20180113
```
Possibly edit pairs.json file to include/exclude pairs
```bash
python freqtrade/tests/testdata/download_backtest_data.py -p pairs.json
```
The script will read your pairs.json file, and download ticker data
into the current working directory.
For help about backtesting usage, please refer to For help about backtesting usage, please refer to
[Backtesting commands](#backtesting-commands). [Backtesting commands](#backtesting-commands).

View File

@ -29,6 +29,7 @@ The table below will list all configuration parameters.
| `exchange.pair_whitelist` | [] | No | List of currency to use by the bot. Can be overrided with `--dynamic-whitelist` param. | `exchange.pair_whitelist` | [] | No | List of currency to use by the bot. Can be overrided with `--dynamic-whitelist` param.
| `exchange.pair_blacklist` | [] | No | List of currency the bot must avoid. Useful when using `--dynamic-whitelist` param. | `exchange.pair_blacklist` | [] | No | List of currency the bot must avoid. Useful when using `--dynamic-whitelist` param.
| `experimental.use_sell_signal` | false | No | Use your sell strategy in addition of the `minimal_roi`. | `experimental.use_sell_signal` | false | No | Use your sell strategy in addition of the `minimal_roi`.
| `experimental.sell_profit_only` | false | No | waits until you have made a positive profit before taking a sell decision.
| `telegram.enabled` | true | Yes | Enable or not the usage of Telegram. | `telegram.enabled` | true | Yes | Enable or not the usage of Telegram.
| `telegram.token` | token | No | Your Telegram bot token. Only required is `enable` is `true`. | `telegram.token` | token | No | Your Telegram bot token. Only required is `enable` is `true`.
| `telegram.chat_id` | chat_id | No | Your personal Telegram account id. Only required is `enable` is `true`. | `telegram.chat_id` | chat_id | No | Your personal Telegram account id. Only required is `enable` is `true`.

View File

@ -2,20 +2,70 @@
#### I have waited 5 minutes, why hasn't the bot made any trades yet?! #### I have waited 5 minutes, why hasn't the bot made any trades yet?!
Depending on the buy strategy, the amount of whitelisted coins, the situation of the market etc, it can take up to hours to find good entry position for a trade. Be patient! Depending on the buy strategy, the amount of whitelisted coins, the
situation of the market etc, it can take up to hours to find good entry
position for a trade. Be patient!
#### I have made 12 trades already, why is my total profit negative?! #### I have made 12 trades already, why is my total profit negative?!
I understand your disappointment but unfortunately 12 trades is just not enough to say anything. If you run backtesting, you can see that our current algorithm does leave you on the plus side, but that is after thousands of trades and even there, you will be left with losses on specific coins that you have traded tens if not hundreds of times. We of course constantly aim to improve the bot but it will _always_ be a gamble, which should leave you with modest wins on monthly basis but you can't say much from few trades. I understand your disappointment but unfortunately 12 trades is just
not enough to say anything. If you run backtesting, you can see that our
current algorithm does leave you on the plus side, but that is after
thousands of trades and even there, you will be left with losses on
specific coins that you have traded tens if not hundreds of times. We
of course constantly aim to improve the bot but it will _always_ be a
gamble, which should leave you with modest wins on monthly basis but
you can't say much from few trades.
#### Id like to change the stake amount. Can I just stop the bot with /stop and then change the config.json and run it again? #### Id like to change the stake amount. Can I just stop the bot with
/stop and then change the config.json and run it again?
Not quite. Trades are persisted to a database but the configuration is currently only read when the bot is killed and restarted. `/stop` more like pauses. You can stop your bot, adjust settings and start it again. Not quite. Trades are persisted to a database but the configuration is
currently only read when the bot is killed and restarted. `/stop` more
like pauses. You can stop your bot, adjust settings and start it again.
#### I want to improve the bot with a new strategy #### I want to improve the bot with a new strategy
That's great. We have a nice backtesting and hyperoptimizing setup. See the tutorial [[here|Testing-new-strategies-with-Hyperopt]]. That's great. We have a nice backtesting and hyperoptimizing setup. See
the tutorial [here|Testing-new-strategies-with-Hyperopt](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-usage.md#hyperopt-commands).
#### Is there a setting to only SELL the coins being held and not perform anymore BUYS? #### Is there a setting to only SELL the coins being held and not
perform anymore BUYS?
You can use the `/forcesell all` command from Telegram. You can use the `/forcesell all` command from Telegram.
### How many epoch do I need to get a good Hyperopt result?
Per default Hyperopts without `-e` or `--epochs` parameter will only
run 100 epochs, means 100 evals of your triggers, guards, .... Too few
to find a great result (unless if you are very lucky), so you probably
have to run it for 10.000 or more. But it will take an eternity to
compute.
We recommend you to run it at least 10.000 epochs:
```bash
python3 ./freqtrade/main.py hyperopt -e 10000
```
or if you want intermediate result to see
```bash
for i in {1..100}; do python3 ./freqtrade/main.py hyperopt -e 100; done
```
#### Why it is so long to run hyperopt?
Finding a great Hyperopt results takes time.
If you wonder why it takes a while to find great hyperopt results
This answer was written during the under the release 0.15.1, when we had
:
- 8 triggers
- 9 guards: let's say we evaluate even 10 values from each
- 1 stoploss calculation: let's say we want 10 values from that too to
be evaluated
The following calculation is still very rough and not very precise
but it will give the idea. With only these triggers and guards there is
already 8*10^9*10 evaluations. A roughly total of 80 billion evals.
Did you run 100 000 evals? Congrats, you've done roughly 1 / 100 000 th
of the search space.

View File

@ -168,6 +168,16 @@ If you would like to learn parameters using an alternate ticke-data that
you have on-disk, use the --datadir PATH option. Default hyperopt will you have on-disk, use the --datadir PATH option. Default hyperopt will
use data from directory freqtrade/tests/testdata. use data from directory freqtrade/tests/testdata.
### Running hyperopt with smaller testset
Use the --timeperiod argument to change how much of the testset
you want to use. The last N ticks/timeframes will be used.
Example:
```bash
python3 ./freqtrade/main.py hyperopt --timeperiod -200
```
### Hyperopt with MongoDB ### Hyperopt with MongoDB
Hyperopt with MongoDB, is like Hyperopt under steroids. As you saw by Hyperopt with MongoDB, is like Hyperopt under steroids. As you saw by
executing the previous command is the execution takes a long time. executing the previous command is the execution takes a long time.

View File

@ -1,98 +1,119 @@
# Install the bot # Installation
This page explains how to prepare your environment for running the bot. This page explains how to prepare your environment for running the bot.
To understand how to set up the bot please read the Bot
[Bot configuration](https://github.com/gcarq/freqtrade/blob/develop/docs/configuration.md) To understand how to set up the bot please read the [Bot Configuration](https://github.com/gcarq/freqtrade/blob/develop/docs/configuration.md) page.
page.
## Table of Contents ## Table of Contents
- [Docker Automatic Installation](#docker)
- [Linux or Mac manual Installation](#linux--mac)
- [Linux - Ubuntu 16.04](#21-linux---ubuntu-1604)
- [Linux - Other distro](#22-linux---other-distro)
- [MacOS installation](#23-macos-installation)
- [Advanced Linux ](#advanced-linux)
- [Windows manual Installation](#windows)
# Docker * [Table of Contents](#table-of-contents)
* [Automatic Installation - Docker](#automatic-installation-docker)
* [Custom Installation](#custom-installation)
- [Requirements](#requirements)
- [Linux - Ubuntu 16.04](#linux-ubuntu-1604)
- [MacOS](#macos)
- [Windows](#windows)
* [First Steps](#first-step)
<!-- /TOC -->
------
## Automatic Installation - Docker
## Easy installation
Start by downloading Docker for your platform: 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 * [Mac](https://www.docker.com/products/docker#/mac)
(e.g. `config.json`) and then create a Docker image for `freqtrade` * [Windows](https://www.docker.com/products/docker#/windows)
using the Dockerfile in this repo. * [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
### 1. Prepare the bot
1. Clone the git
```bash ```bash
git clone https://github.com/gcarq/freqtrade.git git clone https://github.com/gcarq/freqtrade.git
``` ```
2. (Optional) Checkout the develop branch
#### 1.2. (Optional) Checkout the develop branch
```bash ```bash
git checkout develop git checkout develop
``` ```
3. Go into the new directory
#### 1.3. Go into the new directory
```bash ```bash
cd freqtrade cd freqtrade
``` ```
4. Copy `config.sample` to `config.json`
```bash
cp config.json.example config.json
```
To edit the config please refer to the [Bot Configuration](https://github.com/gcarq/freqtrade/blob/develop/docs/configuration.md) page
5. Create your DB file (Optional, the bot will create it if it is missing)
```bash
# For Production
touch tradesv3.sqlite
# For Dry-run #### 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](https://github.com/gcarq/freqtrade/blob/develop/docs/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 touch tradesv3.dryrun.sqlite
``` ```
### 2. Build the docker image
### 2. Build the Docker image
```bash ```bash
cd freqtrade cd freqtrade
docker build -t freqtrade . docker build -t freqtrade .
``` ```
For security reasons, your configuration file will not be included in the 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.
image, you will need to bind mount it. It is also advised to bind mount
a sqlite database file (see the "5. Run a restartable docker image"
section) to keep it between updates.
### 3. Verify the docker image
After build process you can verify that the image was created with: ### 3. Verify the Docker image
```
After the build process you can verify that the image was created with:
```bash
docker images 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):
``` ### 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 `pwd`/config.json:/freqtrade/config.json -it freqtrade docker run --rm -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
``` ```
In this example, the database will be created inside the docker instance In this example, the database will be created inside the docker instance and will be lost when you will refresh your image.
and will be lost when you will refresh your image.
### 5. Run a restartable docker 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** 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 ```bash
mkdir ~/.freqtrade mkdir ~/.freqtrade
mv config.json ~/.freqtrade mv config.json ~/.freqtrade
mv tradesv3.sqlite ~/.freqtrade mv tradesv3.sqlite ~/.freqtrade
``` ```
**5.2. Run the docker image** #### 5.2. Run the docker image
```bash ```bash
docker run -d \ docker run -d \
--name freqtrade \ --name freqtrade \
@ -100,12 +121,11 @@ docker run -d \
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \ -v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \
freqtrade freqtrade
``` ```
If you are using `dry_run=True` it's not necessary to mount
`tradesv3.sqlite`, but you can mount `tradesv3.dryrun.sqlite` if you
plan to use the dry run mode with the param `--dry-run-db`.
If you are using `dry_run=True` it's not necessary to mount `tradesv3.sqlite`, but you can mount `tradesv3.dryrun.sqlite` if you plan to use the dry run mode with the param `--dry-run-db`.
### 6. Monitor your Docker instance ### 6. Monitor your Docker instance
You can then use the following commands to monitor and manage your container: You can then use the following commands to monitor and manage your container:
```bash ```bash
@ -116,35 +136,39 @@ docker stop freqtrade
docker start freqtrade docker start freqtrade
``` ```
You do not need to rebuild the image for configuration changes, it will You do not need to rebuild the image for configuration changes, it will suffice to edit `config.json` and restart the container.
suffice to edit `config.json` and restart the container.
------
## 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.
### Requirements
# Linux / MacOS
## 1. Requirements
Click each one for install guide: Click each one for install guide:
- [Python 3.6.x](http://docs.python-guide.org/en/latest/starting/installation/), * [Python 3.6.x](http://docs.python-guide.org/en/latest/starting/installation/), note the bot was not tested on Python >= 3.7.x
note the bot was not tested on Python >= 3.7.x * [pip](https://pip.pypa.io/en/stable/installing/)
- [pip](https://pip.pypa.io/en/stable/installing/) * [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) * [virtualenv](https://virtualenv.pypa.io/en/stable/installation/) (Recommended)
- [virtualenv](https://virtualenv.pypa.io/en/stable/installation/) (Recommended) * [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html)
- [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html)
## 2. First install required packages
This bot require Python 3.6 and TA-LIB
### 2.1 Linux - Ubuntu 16.04 ### Linux - Ubuntu 16.04
#### 1. Install Python 3.6, Git, and wget
**2.1.1. Install Python 3.6, Git, and wget**
```bash ```bash
sudo add-apt-repository ppa:jonathonf/python-3.6 sudo add-apt-repository ppa:jonathonf/python-3.6
sudo apt-get update sudo apt-get update
sudo apt-get install python3.6 python3.6-venv python3.6-dev build-essential autoconf libtool pkg-config make wget git sudo apt-get install python3.6 python3.6-venv python3.6-dev build-essential autoconf libtool pkg-config make wget git
``` ```
**2.1.2. Install TA-LIB** #### 2. Install TA-Lib
Official webpage: https://mrjbq7.github.io/ta-lib/install.html Official webpage: https://mrjbq7.github.io/ta-lib/install.html
```
```bash
wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz
tar xvzf ta-lib-0.4.0-src.tar.gz tar xvzf ta-lib-0.4.0-src.tar.gz
cd ta-lib cd ta-lib
@ -155,29 +179,58 @@ cd ..
rm -rf ./ta-lib* rm -rf ./ta-lib*
``` ```
**2.1.3. [Optional] Install MongoDB** #### 3. [Optional] Install MongoDB
Install MongoDB if you plan to optimize your strategy with Hyperopt. Install MongoDB if you plan to optimize your strategy with Hyperopt.
```bash ```bash
sudo apt-get install mongodb-org sudo apt-get install mongodb-org
``` ```
Complete tutorial on [Digital Ocean: How to Install MongoDB on Ubuntu 16.04](https://www.digitalocean.com/community/tutorials/how-to-install-mongodb-on-ubuntu-16-04)
### 2.2. Linux - Other distro > Complete tutorial from Digital Ocean: [How to Install MongoDB on Ubuntu 16.04](https://www.digitalocean.com/community/tutorials/how-to-install-mongodb-on-ubuntu-16-04).
If you are on a different Linux OS you maybe have to adapt things like:
- package manager (for example yum instead of apt-get) #### 4. Install FreqTrade
- package names
### 2.3. MacOS installation Clone the git repository:
**2.3.1. Install Python 3.6, git and wget**
```bash ```bash
brew install python3 git wget git clone https://github.com/gcarq/freqtrade.git
``` ```
**2.3.2. [Optional] Install MongoDB** Optionally checkout the develop branch:
```bash
git checkout develop
```
#### 5. Configure `freqtrade` as a `systemd` service
From the freqtrade repo... copy `freqtrade.service` to your systemd user directory (usually `~/.config/systemd/user`) and update `WorkingDirectory` and `ExecStart` to match your setup.
After that you can start the daemon with:
```bash
systemctl --user start freqtrade
```
For this to be persistent (run when user is logged out) you'll need to enable `linger` for your freqtrade user.
```bash
sudo loginctl enable-linger "$USER"
```
### MacOS
#### 1. Install Python 3.6, git, wget and ta-lib
```bash
brew install python3 git wget ta-lib
```
#### 2. [Optional] Install MongoDB
Install MongoDB if you plan to optimize your strategy with Hyperopt. Install MongoDB if you plan to optimize your strategy with Hyperopt.
```bash ```bash
curl -O https://fastdl.mongodb.org/osx/mongodb-osx-ssl-x86_64-3.4.10.tgz curl -O https://fastdl.mongodb.org/osx/mongodb-osx-ssl-x86_64-3.4.10.tgz
tar -zxvf mongodb-osx-ssl-x86_64-3.4.10.tgz tar -zxvf mongodb-osx-ssl-x86_64-3.4.10.tgz
@ -186,49 +239,30 @@ cp -R -n mongodb-osx-x86_64-3.4.10/ <path_freqtrade>/env/mongodb
export PATH=<path_freqtrade>/env/mongodb/bin:$PATH export PATH=<path_freqtrade>/env/mongodb/bin:$PATH
``` ```
## 3. Clone the repo #### 3. Install FreqTrade
The following steps are made for Linux/mac environment
1. Clone the git `git clone https://github.com/gcarq/freqtrade.git`
2. (Optional) Checkout the develop branch `git checkout develop`
## 4. Prepare the bot Clone the git repository:
```bash
cd freqtrade
cp config.json.example config.json
```
To edit the config please refer to [Bot Configuration](https://github.com/gcarq/freqtrade/blob/develop/docs/configuration.md)
## 5. Setup your virtual env
```bash
python3.6 -m venv .env
source .env/bin/activate
pip3.6 install -r requirements.txt
pip3.6 install -e .
```
## 6. Run the bot
If this is the first time you run the bot, ensure you are running it
in Dry-run `"dry_run": true,` otherwise it will start to buy and sell coins.
```bash ```bash
python3.6 ./freqtrade/main.py -c config.json git clone https://github.com/gcarq/freqtrade.git
``` ```
### Advanced Linux Optionally checkout the develop branch:
**systemd service file**
Copy `./freqtrade.service` to your systemd user directory (usually `~/.config/systemd/user`)
and update `WorkingDirectory` and `ExecStart` to match your setup.
After that you can start the daemon with:
```bash ```bash
systemctl --user start freqtrade git checkout develop
``` ```
# Windows ### Windows
We do recommend Windows users to use [Docker](#docker) this will work
much easier and smoother (also safer). We recommend that Windows users use [Docker](#docker) as this will work
much easier and smoother (also more secure).
#### 1. Install freqtrade
copy paste `config.json` to ``\path\freqtrade-develop\freqtrade`
```cmd ```cmd
#copy paste config.json to \path\freqtrade-develop\freqtrade
>cd \path\freqtrade-develop >cd \path\freqtrade-develop
>python -m venv .env >python -m venv .env
>cd .env\Scripts >cd .env\Scripts
@ -239,8 +273,41 @@ much easier and smoother (also safer).
>cd freqtrade >cd freqtrade
>python main.py >python main.py
``` ```
*Thanks [Owdr](https://github.com/Owdr) for the commands. Source: [Issue #222](https://github.com/gcarq/freqtrade/issues/222)*
## Next step > Thanks [Owdr](https://github.com/Owdr) for the commands. Source: [Issue #222](https://github.com/gcarq/freqtrade/issues/222)
Now you have an environment ready, the next step is to
[configure your bot](https://github.com/gcarq/freqtrade/blob/develop/docs/configuration.md).
------
## First Steps
### 1. Initialize the configuration
```bash
cd freqtrade
cp config.json.example config.json
```
> *To edit the config please refer to [Bot Configuration](https://github.com/gcarq/freqtrade/blob/develop/docs/configuration.md).*
### 2. Setup your Python virtual environment (virtualenv)
```bash
python3.6 -m venv .env
source .env/bin/activate
pip3.6 install -r requirements.txt
pip3.6 install -e .
```
### 3. Run the Bot
If this is the first time you run the bot, ensure you are running it in Dry-run `"dry_run": true,` otherwise it will start to buy and sell coins.
```bash
python3.6 ./freqtrade/main.py -c config.json
```
Now you have an environment ready, the next step is
[Bot Configuration](https://github.com/gcarq/freqtrade/blob/develop/docs/configuration.md)...

View File

@ -67,6 +67,18 @@ SET is_open=0, close_date='2017-12-20 03:08:45.103418', close_rate=0.19638016, c
WHERE id=31; WHERE id=31;
``` ```
## Insert manually a new trade
```sql
INSERT
INTO trades (exchange, pair, is_open, fee, open_rate, stake_amount, amount, open_date)
VALUES ('BITTREX', 'BTC_<COIN>', 1, 0.0025, <open_rate>, <stake_amount>, <amount>, '<datetime>')
```
**Example:**
```sql
INSERT INTO trades (exchange, pair, is_open, fee, open_rate, stake_amount, amount, open_date) VALUES ('BITTREX', 'BTC_ETC', 1, 0.0025, 0.00258580, 0.002, 0.7715262081, '2017-11-28 12:44:24.000000')
```
## Fix wrong fees in the table ## Fix wrong fees in the table
If your DB was created before If your DB was created before

View File

@ -1,5 +1,5 @@
""" FreqTrade bot """ """ FreqTrade bot """
__version__ = '0.14.3' __version__ = '0.15.1'
class DependencyException(BaseException): class DependencyException(BaseException):

View File

@ -74,6 +74,8 @@ def populate_indicators(dataframe: DataFrame) -> DataFrame:
# Plus Directional Indicator / Movement # Plus Directional Indicator / Movement
dataframe['plus_dm'] = ta.PLUS_DM(dataframe) dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
dataframe['plus_di'] = ta.PLUS_DI(dataframe) dataframe['plus_di'] = ta.PLUS_DI(dataframe)
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
""" """
# ROC # ROC
dataframe['roc'] = ta.ROC(dataframe) dataframe['roc'] = ta.ROC(dataframe)
@ -114,13 +116,14 @@ def populate_indicators(dataframe: DataFrame) -> DataFrame:
dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband'] dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband']
""" """
# Bollinger bands # Bollinger bands
"""
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower'] dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_middleband'] = bollinger['mid'] dataframe['bb_middleband'] = bollinger['mid']
dataframe['bb_upperband'] = bollinger['upper'] dataframe['bb_upperband'] = bollinger['upper']
"""
# EMA - Exponential Moving Average # EMA - Exponential Moving Average
dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
@ -210,14 +213,12 @@ def populate_indicators(dataframe: DataFrame) -> DataFrame:
# Chart type # Chart type
# ------------------------------------ # ------------------------------------
"""
# Heikinashi stategy # Heikinashi stategy
heikinashi = qtpylib.heikinashi(dataframe) heikinashi = qtpylib.heikinashi(dataframe)
dataframe['ha_open'] = heikinashi['open'] dataframe['ha_open'] = heikinashi['open']
dataframe['ha_close'] = heikinashi['close'] dataframe['ha_close'] = heikinashi['close']
dataframe['ha_high'] = heikinashi['high'] dataframe['ha_high'] = heikinashi['high']
dataframe['ha_low'] = heikinashi['low'] dataframe['ha_low'] = heikinashi['low']
"""
return dataframe return dataframe
@ -280,29 +281,38 @@ def analyze_ticker(ticker_history: List[Dict]) -> DataFrame:
return dataframe return dataframe
def get_signal(pair: str, signal: SignalType) -> bool: # FIX: 20180109, there could be some confusion because we will make a
# boolean result (execute the action or not depending on the signal).
# But the above checks can also return False, and we hide that.
# 20180119 Update to above fix, after an code update we now return
# a tuple (buy, sell). We could take advantage of this
# To distinguish an error from an non-signal situation (False, False)
# by just returning False.
# In short, if we return False it is error, If a tuple we
# get the signal situation.
def get_signal(pair: str) -> (bool, bool):
""" """
Calculates current signal based several technical analysis indicators Calculates current signal based several technical analysis indicators
:param pair: pair in format BTC_ANT or BTC-ANT :param pair: pair in format BTC_ANT or BTC-ANT
:return: True if pair is good for buying, False otherwise :return: (True, False) if pair is good for buying and not for selling
""" """
ticker_hist = get_ticker_history(pair) ticker_hist = get_ticker_history(pair)
if not ticker_hist: if not ticker_hist:
logger.warning('Empty ticker history for pair %s', pair) logger.warning('Empty ticker history for pair %s', pair)
return False return (False, False)
try: try:
dataframe = analyze_ticker(ticker_hist) dataframe = analyze_ticker(ticker_hist)
except ValueError as ex: except ValueError as ex:
logger.warning('Unable to analyze ticker for pair %s: %s', pair, str(ex)) logger.warning('Unable to analyze ticker for pair %s: %s', pair, str(ex))
return False return (False, False)
except Exception as ex: except Exception as ex:
logger.exception('Unexpected error when analyzing ticker for pair %s: %s', pair, str(ex)) logger.exception('Unexpected error when analyzing ticker for pair %s: %s', pair, str(ex))
return False return (False, False)
if dataframe.empty: if dataframe.empty:
logger.warning('Empty dataframe for pair %s', pair) logger.warning('Empty dataframe for pair %s', pair)
return False return (False, False)
latest = dataframe.iloc[-1] latest = dataframe.iloc[-1]
@ -310,11 +320,8 @@ def get_signal(pair: str, signal: SignalType) -> bool:
signal_date = arrow.get(latest['date']) signal_date = arrow.get(latest['date'])
if signal_date < arrow.now() - timedelta(minutes=10): if signal_date < arrow.now() - timedelta(minutes=10):
logger.warning('Too old dataframe for pair %s', pair) logger.warning('Too old dataframe for pair %s', pair)
return False return (False, False)
# FIX: 20180109, there could be some confusion because we will make a (buy, sell) = latest[SignalType.BUY.value] == 1, latest[SignalType.SELL.value] == 1
# boolean result (execute the action or not depending on the signal). logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', latest['date'], pair, str(buy), str(sell))
# But the above checks can also return False, and we hide that. return (buy, sell)
result = latest[signal.value] == 1
logger.debug('%s_trigger: %s (pair=%s, signal=%s)', signal.value, latest['date'], pair, result)
return result

View File

@ -123,10 +123,8 @@ class Bittrex(Exchange):
message=data['message'], message=data['message'],
pair=pair)) pair=pair))
if not data.get('result') \ if not data.get('result') or\
or not data['result'].get('Bid') \ not all(key in data.get('result', {}) for key in ['Bid', 'Ask', 'Last']):
or not data['result'].get('Ask') \
or not data['result'].get('Last'):
raise ContentDecodingError('{message} params=({pair})'.format( raise ContentDecodingError('{message} params=({pair})'.format(
message='Got invalid response from bittrex', message='Got invalid response from bittrex',
pair=pair)) pair=pair))

View File

@ -14,7 +14,7 @@ from cachetools import cached, TTLCache
from freqtrade import (DependencyException, OperationalException, __version__, from freqtrade import (DependencyException, OperationalException, __version__,
exchange, persistence, rpc) exchange, persistence, rpc)
from freqtrade.analyze import SignalType, get_signal from freqtrade.analyze import get_signal
from freqtrade.fiat_convert import CryptoToFiatConverter from freqtrade.fiat_convert import CryptoToFiatConverter
from freqtrade.misc import (State, get_state, load_config, parse_args, from freqtrade.misc import (State, get_state, load_config, parse_args,
throttle, update_state) throttle, update_state)
@ -155,6 +155,8 @@ def handle_timedout_limit_buy(trade, order):
# check_handle_timedout will flush afterwards # check_handle_timedout will flush afterwards
Trade.session.flush() Trade.session.flush()
logger.info('Buy order timeout for %s.', trade) logger.info('Buy order timeout for %s.', trade)
rpc.send_msg('*Timeout:* Unfilled buy order for {} cancelled'.format(
trade.pair.replace('_', '/')))
return True return True
else: else:
# if trade is partially complete, edit the stake details for the trade # if trade is partially complete, edit the stake details for the trade
@ -163,6 +165,8 @@ def handle_timedout_limit_buy(trade, order):
trade.stake_amount = trade.amount * trade.open_rate trade.stake_amount = trade.amount * trade.open_rate
trade.open_order_id = None trade.open_order_id = None
logger.info('Partial buy order timeout for %s.', trade) logger.info('Partial buy order timeout for %s.', trade)
rpc.send_msg('*Timeout:* Remaining buy order for {} cancelled'.format(
trade.pair.replace('_', '/')))
return False return False
@ -180,6 +184,8 @@ def handle_timedout_limit_sell(trade, order):
trade.close_date = None trade.close_date = None
trade.is_open = True trade.is_open = True
trade.open_order_id = None trade.open_order_id = None
rpc.send_msg('*Timeout:* Unfilled sell order for {} cancelled'.format(
trade.pair.replace('_', '/')))
logger.info('Sell order timeout for %s.', trade) logger.info('Sell order timeout for %s.', trade)
return True return True
else: else:
@ -196,20 +202,21 @@ def check_handle_timedout(timeoutvalue: int) -> None:
timeoutthreashold = arrow.utcnow().shift(minutes=-timeoutvalue).datetime timeoutthreashold = arrow.utcnow().shift(minutes=-timeoutvalue).datetime
for trade in Trade.query.filter(Trade.open_order_id.isnot(None)).all(): for trade in Trade.query.filter(Trade.open_order_id.isnot(None)).all():
try:
order = exchange.get_order(trade.open_order_id) order = exchange.get_order(trade.open_order_id)
except requests.exceptions.RequestException:
logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc())
continue
ordertime = arrow.get(order['opened']) ordertime = arrow.get(order['opened'])
# Check if trade is still actually open
if int(order['remaining']) == 0:
continue
if order['type'] == "LIMIT_BUY" and ordertime < timeoutthreashold: if order['type'] == "LIMIT_BUY" and ordertime < timeoutthreashold:
handle_timedout_limit_buy(trade, order) handle_timedout_limit_buy(trade, order)
elif order['type'] == "LIMIT_SELL" and ordertime < timeoutthreashold: elif order['type'] == "LIMIT_SELL" and ordertime < timeoutthreashold:
if handle_timedout_limit_sell(trade, order): handle_timedout_limit_sell(trade, order)
# BUG? if there is more trades that are
# timed out, shouldn't we collect and
# then return all of them?
# Also the function signature is return None.
# But we return True here.
return True
def execute_sell(trade: Trade, limit: float) -> None: def execute_sell(trade: Trade, limit: float) -> None:
@ -295,21 +302,25 @@ def handle_trade(trade: Trade) -> bool:
logger.debug('Handling %s ...', trade) logger.debug('Handling %s ...', trade)
current_rate = exchange.get_ticker(trade.pair)['bid'] current_rate = exchange.get_ticker(trade.pair)['bid']
# Check if minimal roi has been reached (buy, sell) = (False, False)
if min_roi_reached(trade, current_rate, datetime.utcnow()):
if _CONF.get('experimental', {}).get('use_sell_signal'):
(buy, sell) = get_signal(trade.pair)
# Check if minimal roi has been reached and no longer in buy conditions (avoiding a fee)
if not buy and min_roi_reached(trade, current_rate, datetime.utcnow()):
logger.debug('Executing sell due to ROI ...') logger.debug('Executing sell due to ROI ...')
execute_sell(trade, current_rate) execute_sell(trade, current_rate)
return True return True
# Experimental: Check if sell signal has been enabled and triggered
if _CONF.get('experimental', {}).get('use_sell_signal'):
# Experimental: Check if the trade is profitable before selling it (avoid selling at loss) # Experimental: Check if the trade is profitable before selling it (avoid selling at loss)
if _CONF.get('experimental', {}).get('sell_profit_only'): if _CONF.get('experimental', {}).get('sell_profit_only', False):
logger.debug('Checking if trade is profitable ...') logger.debug('Checking if trade is profitable ...')
if trade.calc_profit(rate=current_rate) <= 0: if not buy and trade.calc_profit(rate=current_rate) <= 0:
return False return False
logger.debug('Checking sell_signal ...')
if get_signal(trade.pair, SignalType.SELL): # Experimental: Check if sell signal has been enabled and triggered
if sell and not buy:
logger.debug('Executing sell due to sell signal ...') logger.debug('Executing sell due to sell signal ...')
execute_sell(trade, current_rate) execute_sell(trade, current_rate)
return True return True
@ -353,7 +364,8 @@ def create_trade(stake_amount: float) -> bool:
# Pick pair based on StochRSI buy signals # Pick pair based on StochRSI buy signals
for _pair in whitelist: for _pair in whitelist:
if get_signal(_pair, SignalType.BUY): (buy, sell) = get_signal(_pair)
if buy and not sell:
pair = _pair pair = _pair
break break
else: else:

View File

@ -4,6 +4,7 @@ import json
import logging import logging
import time import time
import os import os
import re
from typing import Any, Callable, Dict, List from typing import Any, Callable, Dict, List
from jsonschema import Draft4Validator, validate from jsonschema import Draft4Validator, validate
@ -15,6 +16,11 @@ from freqtrade import __version__
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def file_dump_json(filename, data):
with open(filename, 'w') as fp:
json.dump(data, fp)
class State(enum.Enum): class State(enum.Enum):
RUNNING = 0 RUNNING = 0
STOPPED = 1 STOPPED = 1
@ -127,7 +133,7 @@ def parse_args(args: List[str], description: str):
dest='dry_run_db', dest='dry_run_db',
) )
parser.add_argument( parser.add_argument(
'-dd', '--datadir', '--datadir',
help='path to backtest data (default freqdata/tests/testdata', help='path to backtest data (default freqdata/tests/testdata',
dest='datadir', dest='datadir',
default=os.path.join('freqtrade', 'tests', 'testdata'), default=os.path.join('freqtrade', 'tests', 'testdata'),
@ -185,6 +191,13 @@ def build_subcommands(parser: argparse.ArgumentParser) -> None:
action='store_true', action='store_true',
dest='refresh_pairs', dest='refresh_pairs',
) )
backtesting_cmd.add_argument(
'--timerange',
help='Specify what timerange of data to use.',
default=None,
type=str,
dest='timerange',
)
# Add hyperopt subcommand # Add hyperopt subcommand
hyperopt_cmd = subparsers.add_parser('hyperopt', help='hyperopt module') hyperopt_cmd = subparsers.add_parser('hyperopt', help='hyperopt module')
@ -211,6 +224,43 @@ def build_subcommands(parser: argparse.ArgumentParser) -> None:
type=int, type=int,
metavar='INT', metavar='INT',
) )
hyperopt_cmd.add_argument(
'--timerange',
help='Specify what timerange of data to use.',
default=None,
type=str,
dest='timerange',
)
def parse_timerange(text):
if text is None:
return None
syntax = [('^-(\d{8})$', (None, 'date')),
('^(\d{8})-$', ('date', None)),
('^(\d{8})-(\d{8})$', ('date', 'date')),
('^(-\d+)$', (None, 'line')),
('^(\d+)-$', ('line', None)),
('^(\d+)-(\d+)$', ('index', 'index'))]
for rex, stype in syntax:
# Apply the regular expression to text
m = re.match(rex, text)
if m: # Regex has matched
rvals = m.groups()
n = 0
start = None
stop = None
if stype[0]:
start = rvals[n]
if stype[0] != 'date':
start = int(start)
n += 1
if stype[1]:
stop = rvals[n]
if stype[1] != 'date':
stop = int(stop)
return (stype, start, stop)
raise Exception('Incorrect syntax for timerange "%s"' % text)
# Required json-schema for user specified config # Required json-schema for user specified config

View File

@ -12,7 +12,20 @@ from freqtrade.analyze import populate_indicators, parse_ticker_dataframe
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def load_tickerdata_file(datadir, pair, ticker_interval): def trim_tickerlist(tickerlist, timerange):
(stype, start, stop) = timerange
if stype == (None, 'line'):
return tickerlist[stop:]
elif stype == ('line', None):
return tickerlist[0:start]
elif stype == ('index', 'index'):
return tickerlist[start:stop]
else:
return tickerlist
def load_tickerdata_file(datadir, pair, ticker_interval,
timerange=None):
""" """
Load a pair from file, Load a pair from file,
:return dict OR empty if unsuccesful :return dict OR empty if unsuccesful
@ -30,11 +43,15 @@ def load_tickerdata_file(datadir, pair, ticker_interval):
# Read the file, load the json # Read the file, load the json
with open(file) as tickerdata: with open(file) as tickerdata:
pairdata = json.load(tickerdata) pairdata = json.load(tickerdata)
if timerange:
pairdata = trim_tickerlist(pairdata, timerange)
return pairdata return pairdata
def load_data(datadir: str, ticker_interval: int = 5, pairs: Optional[List[str]] = None, def load_data(datadir: str, ticker_interval: int = 5,
refresh_pairs: Optional[bool] = False) -> Dict[str, List]: pairs: Optional[List[str]] = None,
refresh_pairs: Optional[bool] = False,
timerange=None) -> Dict[str, List]:
""" """
Loads ticker history data for the given parameters Loads ticker history data for the given parameters
:param ticker_interval: ticker interval in minutes :param ticker_interval: ticker interval in minutes
@ -51,16 +68,21 @@ def load_data(datadir: str, ticker_interval: int = 5, pairs: Optional[List[str]]
download_pairs(datadir, _pairs) download_pairs(datadir, _pairs)
for pair in _pairs: for pair in _pairs:
pairdata = load_tickerdata_file(datadir, pair, ticker_interval) pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange)
if not pairdata: if not pairdata:
# download the tickerdata from exchange # download the tickerdata from exchange
download_backtesting_testdata(datadir, pair=pair, interval=ticker_interval) download_backtesting_testdata(datadir, pair=pair, interval=ticker_interval)
# and retry reading the pair # and retry reading the pair
pairdata = load_tickerdata_file(datadir, pair, ticker_interval) pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange)
result[pair] = pairdata result[pair] = pairdata
return result return result
def tickerdata_to_dataframe(data):
preprocessed = preprocess(data)
return preprocessed
def preprocess(tickerdata: Dict[str, List]) -> Dict[str, DataFrame]: def preprocess(tickerdata: Dict[str, List]) -> Dict[str, DataFrame]:
"""Creates a dataframe and populates indicators for given ticker data""" """Creates a dataframe and populates indicators for given ticker data"""
return {pair: populate_indicators(parse_ticker_dataframe(pair_data)) return {pair: populate_indicators(parse_ticker_dataframe(pair_data))

View File

@ -13,7 +13,6 @@ from freqtrade import exchange
from freqtrade.analyze import populate_buy_trend, populate_sell_trend from freqtrade.analyze import populate_buy_trend, populate_sell_trend
from freqtrade.exchange import Bittrex from freqtrade.exchange import Bittrex
from freqtrade.main import min_roi_reached from freqtrade.main import min_roi_reached
from freqtrade.optimize import preprocess
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -161,12 +160,13 @@ def start(args):
data[pair] = exchange.get_ticker_history(pair, args.ticker_interval) data[pair] = exchange.get_ticker_history(pair, args.ticker_interval)
else: else:
logger.info('Using local backtesting data (using whitelist in given config) ...') logger.info('Using local backtesting data (using whitelist in given config) ...')
data = optimize.load_data(args.datadir, pairs=pairs, ticker_interval=args.ticker_interval,
refresh_pairs=args.refresh_pairs)
logger.info('Using stake_currency: %s ...', config['stake_currency']) logger.info('Using stake_currency: %s ...', config['stake_currency'])
logger.info('Using stake_amount: %s ...', config['stake_amount']) logger.info('Using stake_amount: %s ...', config['stake_amount'])
timerange = misc.parse_timerange(args.timerange)
data = optimize.load_data(args.datadir, pairs=pairs, ticker_interval=args.ticker_interval,
refresh_pairs=args.refresh_pairs,
timerange=timerange)
max_open_trades = 0 max_open_trades = 0
if args.realistic_simulation: if args.realistic_simulation:
logger.info('Using max_open_trades: %s ...', config['max_open_trades']) logger.info('Using max_open_trades: %s ...', config['max_open_trades'])
@ -176,7 +176,7 @@ def start(args):
from freqtrade import main from freqtrade import main
main._CONF = config main._CONF = config
preprocessed = preprocess(data) preprocessed = optimize.tickerdata_to_dataframe(data)
# Print timeframe # Print timeframe
min_date, max_date = get_timeframe(preprocessed) min_date, max_date = get_timeframe(preprocessed)
logger.info('Measuring data from %s up to %s ...', min_date.isoformat(), max_date.isoformat()) logger.info('Measuring data from %s up to %s ...', min_date.isoformat(), max_date.isoformat())

View File

@ -15,7 +15,7 @@ from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, hp, space_eval, tpe
from hyperopt.mongoexp import MongoTrials from hyperopt.mongoexp import MongoTrials
from pandas import DataFrame from pandas import DataFrame
from freqtrade import main # noqa from freqtrade import main, misc # noqa
from freqtrade import exchange, optimize from freqtrade import exchange, optimize
from freqtrade.exchange import Bittrex from freqtrade.exchange import Bittrex
from freqtrade.misc import load_config from freqtrade.misc import load_config
@ -30,18 +30,19 @@ logging.getLogger('hyperopt.tpe').setLevel(logging.WARNING)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# set TARGET_TRADES to suit your number concurrent trades so its realistic to 20days of data # set TARGET_TRADES to suit your number concurrent trades so its realistic to 20days of data
TARGET_TRADES = 1100 TARGET_TRADES = 600
TOTAL_TRIES = 0 TOTAL_TRIES = 0
_CURRENT_TRIES = 0 _CURRENT_TRIES = 0
CURRENT_BEST_LOSS = 100 CURRENT_BEST_LOSS = 100
# max average trade duration in minutes # max average trade duration in minutes
# if eval ends with higher value, we consider it a failed eval # if eval ends with higher value, we consider it a failed eval
MAX_ACCEPTED_TRADE_DURATION = 240 MAX_ACCEPTED_TRADE_DURATION = 300
# this is expexted avg profit * expected trade count # this is expexted avg profit * expected trade count
# for example 3.5%, 1100 trades, EXPECTED_MAX_PROFIT = 3.85 # for example 3.5%, 1100 trades, EXPECTED_MAX_PROFIT = 3.85
EXPECTED_MAX_PROFIT = 3.85 # check that the reported Σ% values do not exceed this!
EXPECTED_MAX_PROFIT = 3.0
# Configuration and data used by hyperopt # Configuration and data used by hyperopt
PROCESSED = None # optimize.preprocess(optimize.load_data()) PROCESSED = None # optimize.preprocess(optimize.load_data())
@ -57,6 +58,10 @@ main._CONF = OPTIMIZE_CONFIG
SPACE = { SPACE = {
'macd_below_zero': hp.choice('macd_below_zero', [
{'enabled': False},
{'enabled': True}
]),
'mfi': hp.choice('mfi', [ 'mfi': hp.choice('mfi', [
{'enabled': False}, {'enabled': False},
{'enabled': True, 'value': hp.quniform('mfi-value', 5, 25, 1)} {'enabled': True, 'value': hp.quniform('mfi-value', 5, 25, 1)}
@ -95,13 +100,15 @@ SPACE = {
]), ]),
'trigger': hp.choice('trigger', [ 'trigger': hp.choice('trigger', [
{'type': 'lower_bb'}, {'type': 'lower_bb'},
{'type': 'lower_bb_tema'},
{'type': 'faststoch10'}, {'type': 'faststoch10'},
{'type': 'ao_cross_zero'}, {'type': 'ao_cross_zero'},
{'type': 'ema5_cross_ema10'}, {'type': 'ema3_cross_ema10'},
{'type': 'macd_cross_signal'}, {'type': 'macd_cross_signal'},
{'type': 'sar_reversal'}, {'type': 'sar_reversal'},
{'type': 'stochf_cross'},
{'type': 'ht_sine'}, {'type': 'ht_sine'},
{'type': 'heiken_reversal_bull'},
{'type': 'di_cross'},
]), ]),
'stoploss': hp.uniform('stoploss', -0.5, -0.02), 'stoploss': hp.uniform('stoploss', -0.5, -0.02),
} }
@ -133,10 +140,11 @@ def log_results(results):
if results['loss'] < CURRENT_BEST_LOSS: if results['loss'] < CURRENT_BEST_LOSS:
CURRENT_BEST_LOSS = results['loss'] CURRENT_BEST_LOSS = results['loss']
logger.info('{:5d}/{}: {}'.format( logger.info('{:5d}/{}: {}. Loss {:.5f}'.format(
results['current_tries'], results['current_tries'],
results['total_tries'], results['total_tries'],
results['result'])) results['result'],
results['loss']))
else: else:
print('.', end='') print('.', end='')
sys.stdout.flush() sys.stdout.flush()
@ -144,9 +152,9 @@ def log_results(results):
def calculate_loss(total_profit: float, trade_count: int, trade_duration: float): def calculate_loss(total_profit: float, trade_count: int, trade_duration: float):
""" objective function, returns smaller number for more optimal results """ """ objective function, returns smaller number for more optimal results """
trade_loss = 1 - 0.35 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.2) trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8)
profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT) profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT)
duration_loss = min(trade_duration / MAX_ACCEPTED_TRADE_DURATION, 1) duration_loss = 0.7 + 0.3 * min(trade_duration / MAX_ACCEPTED_TRADE_DURATION, 1)
return trade_loss + profit_loss + duration_loss return trade_loss + profit_loss + duration_loss
@ -190,10 +198,11 @@ def optimizer(params):
def format_results(results: DataFrame): def format_results(results: DataFrame):
return ('{:6d} trades. Avg profit {: 5.2f}%. ' return ('{:6d} trades. Avg profit {: 5.2f}%. '
'Total profit {: 11.8f} BTC. Avg duration {:5.1f} mins.').format( 'Total profit {: 11.8f} BTC ({:.4f}Σ%). Avg duration {:5.1f} mins.').format(
len(results.index), len(results.index),
results.profit_percent.mean() * 100.0, results.profit_percent.mean() * 100.0,
results.profit_BTC.sum(), results.profit_BTC.sum(),
results.profit_percent.sum(),
results.duration.mean() * 5, results.duration.mean() * 5,
) )
@ -204,6 +213,8 @@ def buy_strategy_generator(params):
# GUARDS AND TRENDS # GUARDS AND TRENDS
if params['uptrend_long_ema']['enabled']: if params['uptrend_long_ema']['enabled']:
conditions.append(dataframe['ema50'] > dataframe['ema100']) conditions.append(dataframe['ema50'] > dataframe['ema100'])
if params['macd_below_zero']['enabled']:
conditions.append(dataframe['macd'] < 0)
if params['uptrend_short_ema']['enabled']: if params['uptrend_short_ema']['enabled']:
conditions.append(dataframe['ema5'] > dataframe['ema10']) conditions.append(dataframe['ema5'] > dataframe['ema10'])
if params['mfi']['enabled']: if params['mfi']['enabled']:
@ -224,14 +235,17 @@ def buy_strategy_generator(params):
# TRIGGERS # TRIGGERS
triggers = { triggers = {
'lower_bb': dataframe['tema'] <= dataframe['blower'], 'lower_bb': (dataframe['close'] < dataframe['bb_lowerband']),
'lower_bb_tema': (dataframe['tema'] < dataframe['bb_lowerband']),
'faststoch10': (crossed_above(dataframe['fastd'], 10.0)), 'faststoch10': (crossed_above(dataframe['fastd'], 10.0)),
'ao_cross_zero': (crossed_above(dataframe['ao'], 0.0)), 'ao_cross_zero': (crossed_above(dataframe['ao'], 0.0)),
'ema5_cross_ema10': (crossed_above(dataframe['ema5'], dataframe['ema10'])), 'ema3_cross_ema10': (crossed_above(dataframe['ema3'], dataframe['ema10'])),
'macd_cross_signal': (crossed_above(dataframe['macd'], dataframe['macdsignal'])), 'macd_cross_signal': (crossed_above(dataframe['macd'], dataframe['macdsignal'])),
'sar_reversal': (crossed_above(dataframe['close'], dataframe['sar'])), 'sar_reversal': (crossed_above(dataframe['close'], dataframe['sar'])),
'stochf_cross': (crossed_above(dataframe['fastk'], dataframe['fastd'])),
'ht_sine': (crossed_above(dataframe['htleadsine'], dataframe['htsine'])), 'ht_sine': (crossed_above(dataframe['htleadsine'], dataframe['htsine'])),
'heiken_reversal_bull': (crossed_above(dataframe['ha_close'], dataframe['ha_open'])) &
(dataframe['ha_low'] == dataframe['ha_open']),
'di_cross': (crossed_above(dataframe['plus_di'], dataframe['minus_di'])),
} }
conditions.append(triggers.get(params['trigger']['type'])) conditions.append(triggers.get(params['trigger']['type']))
@ -259,8 +273,11 @@ def start(args):
logger.info('Using config: %s ...', args.config) logger.info('Using config: %s ...', args.config)
config = load_config(args.config) config = load_config(args.config)
pairs = config['exchange']['pair_whitelist'] pairs = config['exchange']['pair_whitelist']
PROCESSED = optimize.preprocess(optimize.load_data( timerange = misc.parse_timerange(args.timerange)
args.datadir, pairs=pairs, ticker_interval=args.ticker_interval)) data = optimize.load_data(args.datadir, pairs=pairs,
ticker_interval=args.ticker_interval,
timerange=timerange)
PROCESSED = optimize.tickerdata_to_dataframe(data)
if args.mongodb: if args.mongodb:
logger.info('Using mongodb ...') logger.info('Using mongodb ...')

View File

@ -382,13 +382,31 @@ def _balance(bot: Bot, update: Update) -> None:
if not balances: if not balances:
output = '`All balances are zero.`' output = '`All balances are zero.`'
total = 0.0
for currency in balances: for currency in balances:
coin = currency['Currency']
if coin == 'BTC':
currency["Rate"] = 1.0
else:
currency["Rate"] = exchange.get_ticker('BTC_' + coin, False)['bid']
currency['BTC'] = currency["Rate"] * currency["Balance"]
total = total + currency['BTC']
output += """*Currency*: {Currency} output += """*Currency*: {Currency}
*Available*: {Available} *Available*: {Available}
*Balance*: {Balance} *Balance*: {Balance}
*Pending*: {Pending} *Pending*: {Pending}
*Est. BTC*: {BTC: .8f}
""".format(**currency) """.format(**currency)
symbol = _CONF['fiat_display_currency']
value = _FIAT_CONVERT.convert_amount(
total, 'BTC', symbol
)
output += """*Estimated Value*:
*BTC*: {0: .8f}
*{1}*: {2: .2f}
""".format(total, symbol, value)
send_msg(output) send_msg(output)

View File

@ -212,7 +212,18 @@ def test_exchange_bittrex_get_ticker_bad():
wb = make_wrap_bittrex() wb = make_wrap_bittrex()
fb = FakeBittrex() fb = FakeBittrex()
fb.result = {'success': True, fb.result = {'success': True,
'result': {'Bid': 1}} # incomplete result 'result': {'Bid': 1, 'Ask': 0}} # incomplete result
with pytest.raises(ContentDecodingError, match=r'.*Got invalid response from bittrex params.*'):
wb.get_ticker('BTC_ETH')
fb.result = {'success': False,
'message': 'gone bad'
}
with pytest.raises(btx.OperationalException, match=r'.*gone bad.*'):
wb.get_ticker('BTC_ETH')
fb.result = {'success': True,
'result': {}} # incomplete result
with pytest.raises(ContentDecodingError, match=r'.*Got invalid response from bittrex params.*'): with pytest.raises(ContentDecodingError, match=r'.*Got invalid response from bittrex params.*'):
wb.get_ticker('BTC_ETH') wb.get_ticker('BTC_ETH')
fb.result = {'success': False, fb.result = {'success': False,

View File

@ -11,6 +11,13 @@ from freqtrade.optimize.backtesting import backtest, generate_text_table, get_ti
import freqtrade.optimize.backtesting as backtesting import freqtrade.optimize.backtesting as backtesting
def trim_dictlist(dl, num):
new = {}
for pair, pair_data in dl.items():
new[pair] = pair_data[num:]
return new
def test_generate_text_table(): def test_generate_text_table():
results = pd.DataFrame( results = pd.DataFrame(
{ {
@ -43,6 +50,7 @@ def test_backtest(default_conf, mocker):
exchange._API = Bittrex({'key': '', 'secret': ''}) exchange._API = Bittrex({'key': '', 'secret': ''})
data = optimize.load_data(None, ticker_interval=5, pairs=['BTC_ETH']) data = optimize.load_data(None, ticker_interval=5, pairs=['BTC_ETH'])
data = trim_dictlist(data, -200)
results = backtest(default_conf['stake_amount'], results = backtest(default_conf['stake_amount'],
optimize.preprocess(data), 10, True) optimize.preprocess(data), 10, True)
assert not results.empty assert not results.empty
@ -54,21 +62,15 @@ def test_backtest_1min_ticker_interval(default_conf, mocker):
# Run a backtesting for an exiting 5min ticker_interval # Run a backtesting for an exiting 5min ticker_interval
data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST']) data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST'])
data = trim_dictlist(data, -200)
results = backtest(default_conf['stake_amount'], results = backtest(default_conf['stake_amount'],
optimize.preprocess(data), 1, True) optimize.preprocess(data), 1, True)
assert not results.empty assert not results.empty
def trim_dictlist(dl, num):
new = {}
for pair, pair_data in dl.items():
new[pair] = pair_data[num:]
return new
def load_data_test(what): def load_data_test(what):
data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST']) timerange = ((None, 'line'), None, -100)
data = trim_dictlist(data, -100) data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST'], timerange=timerange)
pair = data['BTC_UNITEST'] pair = data['BTC_UNITEST']
datalen = len(pair) datalen = len(pair)
# Depending on the what parameter we now adjust the # Depending on the what parameter we now adjust the
@ -125,6 +127,7 @@ def simple_backtest(config, contour, num_results):
def test_backtest2(default_conf, mocker): def test_backtest2(default_conf, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
data = optimize.load_data(None, ticker_interval=5, pairs=['BTC_ETH']) data = optimize.load_data(None, ticker_interval=5, pairs=['BTC_ETH'])
data = trim_dictlist(data, -200)
results = backtest(default_conf['stake_amount'], results = backtest(default_conf['stake_amount'],
optimize.preprocess(data), 10, True) optimize.preprocess(data), 10, True)
assert not results.empty assert not results.empty
@ -149,10 +152,10 @@ def test_backtest_pricecontours(default_conf, mocker):
simple_backtest(default_conf, contour, numres) simple_backtest(default_conf, contour, numres)
def mocked_load_data(datadir, pairs=[], ticker_interval=0, refresh_pairs=False): def mocked_load_data(datadir, pairs=[], ticker_interval=0, refresh_pairs=False, timerange=None):
tickerdata = optimize.load_tickerdata_file(datadir, 'BTC_UNITEST', 1) tickerdata = optimize.load_tickerdata_file(datadir, 'BTC_UNITEST', 1, timerange=timerange)
pairdata = {'BTC_UNITEST': tickerdata} pairdata = {'BTC_UNITEST': tickerdata}
return trim_dictlist(pairdata, -100) return pairdata
def test_backtest_start(default_conf, mocker, caplog): def test_backtest_start(default_conf, mocker, caplog):
@ -166,6 +169,7 @@ def test_backtest_start(default_conf, mocker, caplog):
args.level = 10 args.level = 10
args.live = False args.live = False
args.datadir = None args.datadir = None
args.timerange = '-100' # needed due to MagicMock malleability
backtesting.start(args) backtesting.start(args)
# check the logs, that will contain the backtest result # check the logs, that will contain the backtest result
exists = ['Using max_open_trades: 1 ...', exists = ['Using max_open_trades: 1 ...',

View File

@ -54,6 +54,7 @@ def create_trials(mocker):
def test_start_calls_fmin(mocker): def test_start_calls_fmin(mocker):
trials = create_trials(mocker) trials = create_trials(mocker)
mocker.patch('freqtrade.optimize.tickerdata_to_dataframe')
mocker.patch('freqtrade.optimize.hyperopt.TRIALS', return_value=trials) mocker.patch('freqtrade.optimize.hyperopt.TRIALS', return_value=trials)
mocker.patch('freqtrade.optimize.hyperopt.sorted', mocker.patch('freqtrade.optimize.hyperopt.sorted',
return_value=trials.results) return_value=trials.results)
@ -61,7 +62,8 @@ def test_start_calls_fmin(mocker):
mocker.patch('freqtrade.optimize.load_data') mocker.patch('freqtrade.optimize.load_data')
mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={}) mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
args = mocker.Mock(epochs=1, config='config.json.example', mongodb=False) args = mocker.Mock(epochs=1, config='config.json.example', mongodb=False,
timerange=None)
start(args) start(args)
mock_fmin.assert_called_once() mock_fmin.assert_called_once()
@ -70,11 +72,12 @@ def test_start_calls_fmin(mocker):
def test_start_uses_mongotrials(mocker): def test_start_uses_mongotrials(mocker):
mock_mongotrials = mocker.patch('freqtrade.optimize.hyperopt.MongoTrials', mock_mongotrials = mocker.patch('freqtrade.optimize.hyperopt.MongoTrials',
return_value=create_trials(mocker)) return_value=create_trials(mocker))
mocker.patch('freqtrade.optimize.preprocess') mocker.patch('freqtrade.optimize.tickerdata_to_dataframe')
mocker.patch('freqtrade.optimize.load_data') mocker.patch('freqtrade.optimize.load_data')
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={}) mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
args = mocker.Mock(epochs=1, config='config.json.example', mongodb=True) args = mocker.Mock(epochs=1, config='config.json.example', mongodb=True,
timerange=None)
start(args) start(args)
mock_mongotrials.assert_called_once() mock_mongotrials.assert_called_once()
@ -107,6 +110,7 @@ def test_no_log_if_loss_does_not_improve(mocker):
def test_fmin_best_results(mocker, caplog): def test_fmin_best_results(mocker, caplog):
fmin_result = { fmin_result = {
"macd_below_zero": 0,
"adx": 1, "adx": 1,
"adx-value": 15.0, "adx-value": 15.0,
"fastd": 1, "fastd": 1,
@ -124,11 +128,12 @@ def test_fmin_best_results(mocker, caplog):
} }
mocker.patch('freqtrade.optimize.hyperopt.MongoTrials', return_value=create_trials(mocker)) mocker.patch('freqtrade.optimize.hyperopt.MongoTrials', return_value=create_trials(mocker))
mocker.patch('freqtrade.optimize.preprocess') mocker.patch('freqtrade.optimize.tickerdata_to_dataframe')
mocker.patch('freqtrade.optimize.load_data') mocker.patch('freqtrade.optimize.load_data')
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result) mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result)
args = mocker.Mock(epochs=1, config='config.json.example') args = mocker.Mock(epochs=1, config='config.json.example',
timerange=None)
start(args) start(args)
exists = [ exists = [
@ -136,7 +141,7 @@ def test_fmin_best_results(mocker, caplog):
'"adx": {\n "enabled": true,\n "value": 15.0\n },', '"adx": {\n "enabled": true,\n "value": 15.0\n },',
'"green_candle": {\n "enabled": true\n },', '"green_candle": {\n "enabled": true\n },',
'"mfi": {\n "enabled": false\n },', '"mfi": {\n "enabled": false\n },',
'"trigger": {\n "type": "ao_cross_zero"\n },', '"trigger": {\n "type": "faststoch10"\n },',
'"stoploss": -0.1', '"stoploss": -0.1',
] ]
@ -146,11 +151,12 @@ def test_fmin_best_results(mocker, caplog):
def test_fmin_throw_value_error(mocker, caplog): def test_fmin_throw_value_error(mocker, caplog):
mocker.patch('freqtrade.optimize.hyperopt.MongoTrials', return_value=create_trials(mocker)) mocker.patch('freqtrade.optimize.hyperopt.MongoTrials', return_value=create_trials(mocker))
mocker.patch('freqtrade.optimize.preprocess') mocker.patch('freqtrade.optimize.tickerdata_to_dataframe')
mocker.patch('freqtrade.optimize.load_data') mocker.patch('freqtrade.optimize.load_data')
mocker.patch('freqtrade.optimize.hyperopt.fmin', side_effect=ValueError()) mocker.patch('freqtrade.optimize.hyperopt.fmin', side_effect=ValueError())
args = mocker.Mock(epochs=1, config='config.json.example') args = mocker.Mock(epochs=1, config='config.json.example',
timerange=None)
start(args) start(args)
exists = [ exists = [
@ -184,7 +190,8 @@ def test_resuming_previous_hyperopt_results_succeeds(mocker):
return_value={}) return_value={})
args = mocker.Mock(epochs=1, args = mocker.Mock(epochs=1,
config='config.json.example', config='config.json.example',
mongodb=False) mongodb=False,
timerange=None)
start(args) start(args)

View File

@ -189,3 +189,11 @@ def test_init(mocker):
conf = {'exchange': {'pair_whitelist': []}} conf = {'exchange': {'pair_whitelist': []}}
mocker.patch('freqtrade.optimize.hyperopt_optimize_conf', return_value=conf) mocker.patch('freqtrade.optimize.hyperopt_optimize_conf', return_value=conf)
assert {} == optimize.load_data('', pairs=[], refresh_pairs=True) assert {} == optimize.load_data('', pairs=[], refresh_pairs=True)
def test_tickerdata_to_dataframe():
timerange = ((None, 'line'), None, -100)
tick = load_tickerdata_file(None, 'BTC_UNITEST', 1, timerange=timerange)
tickerlist = {'BTC_UNITEST': tick}
data = optimize.tickerdata_to_dataframe(tickerlist)
assert 100 == len(data['BTC_UNITEST'])

View File

@ -77,7 +77,7 @@ def test_authorized_only_exception(default_conf, mocker):
def test_status_handle(default_conf, update, ticker, mocker): def test_status_handle(default_conf, update, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.rpc.telegram', mocker.patch.multiple('freqtrade.rpc.telegram',
@ -112,7 +112,7 @@ def test_status_handle(default_conf, update, ticker, mocker):
def test_status_table_handle(default_conf, update, ticker, mocker): def test_status_table_handle(default_conf, update, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
@ -154,7 +154,7 @@ def test_status_table_handle(default_conf, update, ticker, mocker):
def test_profit_handle( def test_profit_handle(
default_conf, update, ticker, ticker_sell_up, limit_buy_order, limit_sell_order, mocker): default_conf, update, ticker, ticker_sell_up, limit_buy_order, limit_sell_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.rpc.telegram', mocker.patch.multiple('freqtrade.rpc.telegram',
@ -210,7 +210,7 @@ def test_profit_handle(
def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker): def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.rpc.telegram', mocker.patch.multiple('freqtrade.rpc.telegram',
_CONF=default_conf, _CONF=default_conf,
@ -247,7 +247,7 @@ def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker):
def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, mocker): def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.rpc.telegram', mocker.patch.multiple('freqtrade.rpc.telegram',
_CONF=default_conf, _CONF=default_conf,
@ -308,7 +308,7 @@ def test_exec_forcesell_open_orders(default_conf, ticker, mocker):
def test_forcesell_all_handle(default_conf, update, ticker, mocker): def test_forcesell_all_handle(default_conf, update, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.rpc.telegram', mocker.patch.multiple('freqtrade.rpc.telegram',
_CONF=default_conf, _CONF=default_conf,
@ -339,7 +339,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, mocker):
def test_forcesell_handle_invalid(default_conf, update, mocker): def test_forcesell_handle_invalid(default_conf, update, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, True))
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple('freqtrade.rpc.telegram', mocker.patch.multiple('freqtrade.rpc.telegram',
_CONF=default_conf, _CONF=default_conf,
@ -376,7 +376,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker):
def test_performance_handle( def test_performance_handle(
default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker): default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.rpc.telegram', mocker.patch.multiple('freqtrade.rpc.telegram',
@ -410,7 +410,7 @@ def test_performance_handle(
def test_daily_handle( def test_daily_handle(
default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker): default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.rpc.telegram', mocker.patch.multiple('freqtrade.rpc.telegram',
@ -460,7 +460,7 @@ def test_daily_handle(
def test_count_handle(default_conf, update, ticker, mocker): def test_count_handle(default_conf, update, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.rpc.telegram', 'freqtrade.rpc.telegram',
@ -492,7 +492,7 @@ def test_count_handle(default_conf, update, ticker, mocker):
def test_performance_handle_invalid(default_conf, update, mocker): def test_performance_handle_invalid(default_conf, update, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, True))
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.multiple('freqtrade.rpc.telegram', mocker.patch.multiple('freqtrade.rpc.telegram',
_CONF=default_conf, _CONF=default_conf,
@ -606,11 +606,15 @@ def test_balance_handle(default_conf, update, mocker):
send_msg=msg_mock) send_msg=msg_mock)
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
get_balances=MagicMock(return_value=mock_balance)) get_balances=MagicMock(return_value=mock_balance))
mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap',
ticker=MagicMock(return_value={'price_usd': 15000.0}),
_cache_symbols=MagicMock(return_value={'BTC': 1}))
_balance(bot=MagicMock(), update=update) _balance(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert '*Currency*: BTC' in msg_mock.call_args_list[0][0][0] assert '*Currency*: BTC' in msg_mock.call_args_list[0][0][0]
assert 'Balance' in msg_mock.call_args_list[0][0][0] assert 'Balance' in msg_mock.call_args_list[0][0][0]
assert 'Est. BTC' in msg_mock.call_args_list[0][0][0]
def test_help_handle(default_conf, update, mocker): def test_help_handle(default_conf, update, mocker):

View File

@ -8,7 +8,7 @@ import datetime
import pytest import pytest
from pandas import DataFrame from pandas import DataFrame
from freqtrade.analyze import (SignalType, get_signal, parse_ticker_dataframe, from freqtrade.analyze import (get_signal, parse_ticker_dataframe,
populate_buy_trend, populate_indicators, populate_buy_trend, populate_indicators,
populate_sell_trend) populate_sell_trend)
@ -42,35 +42,35 @@ def test_returns_latest_buy_signal(mocker):
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock()) mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock())
mocker.patch( mocker.patch(
'freqtrade.analyze.analyze_ticker', 'freqtrade.analyze.analyze_ticker',
return_value=DataFrame([{'buy': 1, 'date': arrow.utcnow()}]) return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}])
) )
assert get_signal('BTC-ETH', SignalType.BUY) assert get_signal('BTC-ETH') == (True, False)
mocker.patch( mocker.patch(
'freqtrade.analyze.analyze_ticker', 'freqtrade.analyze.analyze_ticker',
return_value=DataFrame([{'buy': 0, 'date': arrow.utcnow()}]) return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}])
) )
assert not get_signal('BTC-ETH', SignalType.BUY) assert get_signal('BTC-ETH') == (False, True)
def test_returns_latest_sell_signal(mocker): def test_returns_latest_sell_signal(mocker):
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock()) mocker.patch('freqtrade.analyze.get_ticker_history', return_value=MagicMock())
mocker.patch( mocker.patch(
'freqtrade.analyze.analyze_ticker', 'freqtrade.analyze.analyze_ticker',
return_value=DataFrame([{'sell': 1, 'date': arrow.utcnow()}]) return_value=DataFrame([{'sell': 1, 'buy': 0, 'date': arrow.utcnow()}])
) )
assert get_signal('BTC-ETH', SignalType.SELL) assert get_signal('BTC-ETH') == (False, True)
mocker.patch( mocker.patch(
'freqtrade.analyze.analyze_ticker', 'freqtrade.analyze.analyze_ticker',
return_value=DataFrame([{'sell': 0, 'date': arrow.utcnow()}]) return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}])
) )
assert not get_signal('BTC-ETH', SignalType.SELL) assert get_signal('BTC-ETH') == (True, False)
def test_get_signal_empty(mocker, caplog): def test_get_signal_empty(mocker, caplog):
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=None) mocker.patch('freqtrade.analyze.get_ticker_history', return_value=None)
assert not get_signal('foo', SignalType.BUY) assert (False, False) == get_signal('foo')
assert tt.log_has('Empty ticker history for pair foo', assert tt.log_has('Empty ticker history for pair foo',
caplog.record_tuples) caplog.record_tuples)
@ -79,17 +79,15 @@ def test_get_signal_execption_valueerror(mocker, caplog):
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1) mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1)
mocker.patch('freqtrade.analyze.analyze_ticker', mocker.patch('freqtrade.analyze.analyze_ticker',
side_effect=ValueError('xyz')) side_effect=ValueError('xyz'))
assert not get_signal('foo', SignalType.BUY) assert (False, False) == get_signal('foo')
assert tt.log_has('Unable to analyze ticker for pair foo: xyz', assert tt.log_has('Unable to analyze ticker for pair foo: xyz',
caplog.record_tuples) caplog.record_tuples)
# This error should never occur becase analyze_ticker is run first,
# and that function can only add columns, it cant delete all rows from the dataframe
def test_get_signal_empty_dataframe(mocker, caplog): def test_get_signal_empty_dataframe(mocker, caplog):
mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1) mocker.patch('freqtrade.analyze.get_ticker_history', return_value=1)
mocker.patch('freqtrade.analyze.analyze_ticker', return_value=DataFrame([])) mocker.patch('freqtrade.analyze.analyze_ticker', return_value=DataFrame([]))
assert not get_signal('xyz', SignalType.BUY) assert (False, False) == get_signal('xyz')
assert tt.log_has('Empty dataframe for pair xyz', assert tt.log_has('Empty dataframe for pair xyz',
caplog.record_tuples) caplog.record_tuples)
@ -100,7 +98,7 @@ def test_get_signal_old_dataframe(mocker, caplog):
oldtime = arrow.utcnow() - datetime.timedelta(minutes=11) oldtime = arrow.utcnow() - datetime.timedelta(minutes=11)
ticks = DataFrame([{'buy': 1, 'date': oldtime}]) ticks = DataFrame([{'buy': 1, 'date': oldtime}])
mocker.patch('freqtrade.analyze.analyze_ticker', return_value=DataFrame(ticks)) mocker.patch('freqtrade.analyze.analyze_ticker', return_value=DataFrame(ticks))
assert not get_signal('xyz', SignalType.BUY) assert (False, False) == get_signal('xyz')
assert tt.log_has('Too old dataframe for pair xyz', assert tt.log_has('Too old dataframe for pair xyz',
caplog.record_tuples) caplog.record_tuples)
@ -110,4 +108,4 @@ def test_get_signal_handles_exceptions(mocker):
mocker.patch('freqtrade.analyze.analyze_ticker', mocker.patch('freqtrade.analyze.analyze_ticker',
side_effect=Exception('invalid ticker history ')) side_effect=Exception('invalid ticker history '))
assert not get_signal('BTC-ETH', SignalType.BUY) assert get_signal('BTC-ETH') == (False, False)

View File

@ -11,7 +11,6 @@ from sqlalchemy import create_engine
import freqtrade.main as main import freqtrade.main as main
from freqtrade import DependencyException, OperationalException from freqtrade import DependencyException, OperationalException
from freqtrade.analyze import SignalType
from freqtrade.exchange import Exchanges from freqtrade.exchange import Exchanges
from freqtrade.main import (_process, check_handle_timedout, create_trade, from freqtrade.main import (_process, check_handle_timedout, create_trade,
execute_sell, get_target_bid, handle_trade, init) execute_sell, get_target_bid, handle_trade, init)
@ -91,7 +90,7 @@ def test_process_maybe_execute_buy_exception(default_conf, mocker, caplog):
def test_process_trade_creation(default_conf, ticker, limit_buy_order, health, mocker): def test_process_trade_creation(default_conf, ticker, limit_buy_order, health, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
@ -121,7 +120,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order, health, m
def test_process_exchange_failures(default_conf, ticker, health, mocker): def test_process_exchange_failures(default_conf, ticker, health, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None) sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None)
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
@ -138,7 +137,7 @@ def test_process_operational_exception(default_conf, ticker, health, mocker):
msg_mock = MagicMock() msg_mock = MagicMock()
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=msg_mock) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=msg_mock)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
@ -156,8 +155,7 @@ def test_process_operational_exception(default_conf, ticker, health, mocker):
def test_process_trade_handling(default_conf, ticker, limit_buy_order, health, mocker): def test_process_trade_handling(default_conf, ticker, limit_buy_order, health, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch('freqtrade.main.get_signal', mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
side_effect=lambda *args: False if args[1] == SignalType.SELL else True)
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
@ -179,7 +177,7 @@ def test_process_trade_handling(default_conf, ticker, limit_buy_order, health, m
def test_create_trade(default_conf, ticker, limit_buy_order, mocker): def test_create_trade(default_conf, ticker, limit_buy_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
@ -210,7 +208,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, mocker):
def test_create_trade_minimal_amount(default_conf, ticker, mocker): def test_create_trade_minimal_amount(default_conf, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
buy_mock = mocker.patch( buy_mock = mocker.patch(
'freqtrade.main.exchange.buy', MagicMock(return_value='mocked_limit_buy') 'freqtrade.main.exchange.buy', MagicMock(return_value='mocked_limit_buy')
) )
@ -226,7 +224,7 @@ def test_create_trade_minimal_amount(default_conf, ticker, mocker):
def test_create_trade_no_stake_amount(default_conf, ticker, mocker): def test_create_trade_no_stake_amount(default_conf, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
@ -239,7 +237,7 @@ def test_create_trade_no_stake_amount(default_conf, ticker, mocker):
def test_create_trade_no_pairs(default_conf, ticker, mocker): def test_create_trade_no_pairs(default_conf, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
@ -255,7 +253,7 @@ def test_create_trade_no_pairs(default_conf, ticker, mocker):
def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker): def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
@ -273,7 +271,7 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker):
def test_create_trade_no_signal(default_conf, ticker, mocker): def test_create_trade_no_signal(default_conf, ticker, mocker):
default_conf['dry_run'] = True default_conf['dry_run'] = True
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', MagicMock(return_value=False)) mocker.patch('freqtrade.main.get_signal', MagicMock(return_value=(False, False)))
mocker.patch.multiple('freqtrade.exchange', mocker.patch.multiple('freqtrade.exchange',
get_ticker_history=MagicMock(return_value=20)) get_ticker_history=MagicMock(return_value=20))
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
@ -286,7 +284,7 @@ def test_create_trade_no_signal(default_conf, ticker, mocker):
def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker): def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
@ -309,6 +307,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker):
trade.update(limit_buy_order) trade.update(limit_buy_order)
assert trade.is_open is True assert trade.is_open is True
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True))
handle_trade(trade) handle_trade(trade)
assert trade.open_order_id == 'mocked_limit_sell' assert trade.open_order_id == 'mocked_limit_sell'
@ -321,11 +320,57 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker):
assert trade.close_date is not None assert trade.close_date is not None
def test_handle_overlpapping_signals(default_conf, ticker, mocker, caplog):
default_conf.update({'experimental': {'use_sell_signal': True}})
mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, True))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value='mocked_limit_buy'))
mocker.patch('freqtrade.main.min_roi_reached', return_value=False)
init(default_conf, create_engine('sqlite://'))
create_trade(0.001)
# Buy and Sell triggering, so doing nothing ...
trades = Trade.query.all()
assert len(trades) == 0
# Buy is triggering, so buying ...
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
create_trade(0.001)
trades = Trade.query.all()
assert len(trades) == 1
assert trades[0].is_open is True
# Buy and Sell are not triggering, so doing nothing ...
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, False))
assert handle_trade(trades[0]) is False
trades = Trade.query.all()
assert len(trades) == 1
assert trades[0].is_open is True
# Buy and Sell are triggering, so doing nothing ...
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, True))
assert handle_trade(trades[0]) is False
trades = Trade.query.all()
assert len(trades) == 1
assert trades[0].is_open is True
# Sell is triggering, guess what : we are Selling!
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True))
trades = Trade.query.all()
assert handle_trade(trades[0]) is True
def test_handle_trade_roi(default_conf, ticker, mocker, caplog): def test_handle_trade_roi(default_conf, ticker, mocker, caplog):
default_conf.update({'experimental': {'use_sell_signal': True}}) default_conf.update({'experimental': {'use_sell_signal': True}})
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
@ -344,11 +389,11 @@ def test_handle_trade_roi(default_conf, ticker, mocker, caplog):
# we might just want to check if we are in a sell condition without # we might just want to check if we are in a sell condition without
# executing # executing
# if ROI is reached we must sell # if ROI is reached we must sell
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: False) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True))
assert handle_trade(trade) assert handle_trade(trade)
assert ('freqtrade', logging.DEBUG, 'Executing sell due to ROI ...') in caplog.record_tuples assert ('freqtrade', logging.DEBUG, 'Executing sell due to ROI ...') in caplog.record_tuples
# if ROI is reached we must sell even if sell-signal is not signalled # if ROI is reached we must sell even if sell-signal is not signalled
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True))
assert handle_trade(trade) assert handle_trade(trade)
assert ('freqtrade', logging.DEBUG, 'Executing sell due to ROI ...') in caplog.record_tuples assert ('freqtrade', logging.DEBUG, 'Executing sell due to ROI ...') in caplog.record_tuples
@ -357,7 +402,7 @@ def test_handle_trade_experimental(default_conf, ticker, mocker, caplog):
default_conf.update({'experimental': {'use_sell_signal': True}}) default_conf.update({'experimental': {'use_sell_signal': True}})
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
@ -371,11 +416,10 @@ def test_handle_trade_experimental(default_conf, ticker, mocker, caplog):
trade = Trade.query.first() trade = Trade.query.first()
trade.is_open = True trade.is_open = True
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: False) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, False))
value_returned = handle_trade(trade) value_returned = handle_trade(trade)
assert ('freqtrade', logging.DEBUG, 'Checking sell_signal ...') in caplog.record_tuples
assert value_returned is False assert value_returned is False
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True))
assert handle_trade(trade) assert handle_trade(trade)
s = 'Executing sell due to sell signal ...' s = 'Executing sell due to sell signal ...'
assert ('freqtrade', logging.DEBUG, s) in caplog.record_tuples assert ('freqtrade', logging.DEBUG, s) in caplog.record_tuples
@ -383,7 +427,7 @@ def test_handle_trade_experimental(default_conf, ticker, mocker, caplog):
def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mocker): def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
@ -408,7 +452,8 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mo
def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, mocker): def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch('freqtrade.rpc.init', MagicMock())
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
@ -433,6 +478,7 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, mo
# check it does cancel buy orders over the time limit # check it does cancel buy orders over the time limit
check_handle_timedout(600) check_handle_timedout(600)
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
assert rpc_mock.call_count == 1
trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all() trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
assert len(trades) == 0 assert len(trades) == 0
@ -454,7 +500,8 @@ def test_handle_timedout_limit_buy(default_conf, mocker):
def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old, mocker): def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch('freqtrade.rpc.init', MagicMock())
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
@ -480,6 +527,7 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old,
# check it does cancel sell orders over the time limit # check it does cancel sell orders over the time limit
check_handle_timedout(600) check_handle_timedout(600)
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
assert rpc_mock.call_count == 1
assert trade_sell.is_open is True assert trade_sell.is_open is True
@ -501,7 +549,8 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
mocker): mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch('freqtrade.rpc.init', MagicMock())
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
get_ticker=ticker, get_ticker=ticker,
@ -527,6 +576,7 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
# note this is for a partially-complete buy order # note this is for a partially-complete buy order
check_handle_timedout(600) check_handle_timedout(600)
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
assert rpc_mock.call_count == 1
trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all() trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
assert len(trades) == 1 assert len(trades) == 1
assert trades[0].amount == 23.0 assert trades[0].amount == 23.0
@ -550,7 +600,7 @@ def test_balance_bigger_last_ask(mocker):
def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker): def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch('freqtrade.rpc.init', MagicMock()) mocker.patch('freqtrade.rpc.init', MagicMock())
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
@ -583,7 +633,7 @@ def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker):
def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker): def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch('freqtrade.rpc.init', MagicMock()) mocker.patch('freqtrade.rpc.init', MagicMock())
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.rpc.telegram', mocker.patch.multiple('freqtrade.rpc.telegram',
@ -620,7 +670,7 @@ def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker):
def test_execute_sell_without_conf(default_conf, ticker, ticker_sell_up, mocker): def test_execute_sell_without_conf(default_conf, ticker, ticker_sell_up, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch('freqtrade.rpc.init', MagicMock()) mocker.patch('freqtrade.rpc.init', MagicMock())
rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock()) rpc_mock = mocker.patch('freqtrade.main.rpc.send_msg', MagicMock())
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
@ -657,7 +707,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.min_roi_reached', return_value=False) mocker.patch('freqtrade.main.min_roi_reached', return_value=False)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
@ -673,6 +723,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker):
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True))
assert handle_trade(trade) is True assert handle_trade(trade) is True
@ -684,7 +735,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.min_roi_reached', return_value=False) mocker.patch('freqtrade.main.min_roi_reached', return_value=False)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
@ -700,6 +751,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker):
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True))
assert handle_trade(trade) is True assert handle_trade(trade) is True
@ -711,7 +763,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.min_roi_reached', return_value=False) mocker.patch('freqtrade.main.min_roi_reached', return_value=False)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
@ -727,6 +779,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker):
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True))
assert handle_trade(trade) is False assert handle_trade(trade) is False
@ -738,7 +791,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker):
mocker.patch.dict('freqtrade.main._CONF', default_conf) mocker.patch.dict('freqtrade.main._CONF', default_conf)
mocker.patch('freqtrade.main.min_roi_reached', return_value=False) mocker.patch('freqtrade.main.min_roi_reached', return_value=False)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s, t: True) mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (True, False))
mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock()) mocker.patch.multiple('freqtrade.rpc', init=MagicMock(), send_msg=MagicMock())
mocker.patch.multiple('freqtrade.main.exchange', mocker.patch.multiple('freqtrade.main.exchange',
validate_pairs=MagicMock(), validate_pairs=MagicMock(),
@ -754,4 +807,6 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker):
trade = Trade.query.first() trade = Trade.query.first()
trade.update(limit_buy_order) trade.update(limit_buy_order)
mocker.patch('freqtrade.main.get_signal', side_effect=lambda s: (False, True))
assert handle_trade(trade) is True assert handle_trade(trade) is True

View File

@ -8,7 +8,7 @@ import pytest
from jsonschema import ValidationError from jsonschema import ValidationError
from freqtrade.misc import (common_args_parser, load_config, parse_args, from freqtrade.misc import (common_args_parser, load_config, parse_args,
throttle) throttle, parse_timerange)
def test_throttle(): def test_throttle():
@ -133,6 +133,13 @@ def test_parse_args_hyperopt_custom(mocker):
assert call_args.func is not None assert call_args.func is not None
def test_parse_timerange_incorrect():
assert ((None, 'line'), None, -200) == parse_timerange('-200')
assert (('line', None), 200, None) == parse_timerange('200-')
with pytest.raises(Exception, match=r'Incorrect syntax.*'):
parse_timerange('-')
def test_load_config(default_conf, mocker): def test_load_config(default_conf, mocker):
file_mock = mocker.patch('freqtrade.misc.open', mocker.mock_open( file_mock = mocker.patch('freqtrade.misc.open', mocker.mock_open(
read_data=json.dumps(default_conf) read_data=json.dumps(default_conf)

View File

@ -1,29 +1,38 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""This script generate json data from bittrex""" """This script generate json data from bittrex"""
import sys
import json import json
from os import path
from freqtrade import exchange from freqtrade import exchange
from freqtrade.exchange import Bittrex from freqtrade.exchange import Bittrex
from freqtrade import misc
PAIRS = [ parser = misc.common_args_parser('download utility')
'BTC_BCC', 'BTC_ETH', 'BTC_MER', 'BTC_POWR', 'BTC_ETC', parser.add_argument(
'BTC_OK', 'BTC_NEO', 'BTC_EMC2', 'BTC_DASH', 'BTC_LSK', '-p', '--pair',
'BTC_LTC', 'BTC_XZC', 'BTC_OMG', 'BTC_STRAT', 'BTC_XRP', help='JSON file containing pairs to download',
'BTC_QTUM', 'BTC_WAVES', 'BTC_VTC', 'BTC_XLM', 'BTC_MCO' dest='pair',
] default=None
TICKER_INTERVAL = 5 # ticker interval in minutes (currently implemented: 1 and 5) )
OUTPUT_DIR = path.dirname(path.realpath(__file__)) args = parser.parse_args(sys.argv[1:])
TICKER_INTERVALS = [1, 5] # ticker interval in minutes (currently implemented: 1 and 5)
PAIRS = []
if args.pair:
with open(args.pair) as file:
PAIRS = json.load(file)
PAIRS = list(set(PAIRS))
print('About to download pairs:', PAIRS)
# Init Bittrex exchange # Init Bittrex exchange
exchange._API = Bittrex({'key': '', 'secret': ''}) exchange._API = Bittrex({'key': '', 'secret': ''})
for pair in PAIRS: for pair in PAIRS:
data = exchange.get_ticker_history(pair, TICKER_INTERVAL) for tick_interval in TICKER_INTERVALS:
filename = path.join(OUTPUT_DIR, '{}-{}.json'.format( print('downloading pair %s, interval %s' % (pair, tick_interval))
pair, data = exchange.get_ticker_history(pair, tick_interval)
TICKER_INTERVAL, filename = '{}-{}.json'.format(pair, tick_interval)
)) misc.file_dump_json(filename, data)
with open(filename, 'w') as fp:
json.dump(data, fp)

23
freqtrade/tests/testdata/pairs.json vendored Normal file
View File

@ -0,0 +1,23 @@
[
"BTC_ADA",
"BTC_BAT",
"BTC_DASH",
"BTC_ETC",
"BTC_ETH",
"BTC_GBYTE",
"BTC_LSK",
"BTC_LTC",
"BTC_NEO",
"BTC_NXT",
"BTC_POWR",
"BTC_STORJ",
"BTC_QTUM",
"BTC_WAVES",
"BTC_VTC",
"BTC_XLM",
"BTC_XMR",
"BTC_XVG",
"BTC_XRP",
"BTC_ZEC"
]

View File

@ -1,7 +1,7 @@
python-bittrex==0.2.2 python-bittrex==0.2.2
SQLAlchemy==1.2.0 SQLAlchemy==1.2.1
python-telegram-bot==9.0.0 python-telegram-bot==9.0.0
arrow==0.12.0 arrow==0.12.1
cachetools==2.0.1 cachetools==2.0.1
requests==2.18.4 requests==2.18.4
urllib3==1.22 urllib3==1.22
@ -11,7 +11,7 @@ scikit-learn==0.19.1
scipy==1.0.0 scipy==1.0.0
jsonschema==2.6.0 jsonschema==2.6.0
numpy==1.14.0 numpy==1.14.0
TA-Lib==0.4.14 TA-Lib==0.4.16
pytest==3.3.2 pytest==3.3.2
pytest-mock==1.6.3 pytest-mock==1.6.3
pytest-cov==2.5.1 pytest-cov==2.5.1
@ -19,7 +19,7 @@ hyperopt==0.1
# do not upgrade networkx before this is fixed https://github.com/hyperopt/hyperopt/issues/325 # do not upgrade networkx before this is fixed https://github.com/hyperopt/hyperopt/issues/325
networkx==1.11 networkx==1.11
tabulate==0.8.2 tabulate==0.8.2
pymarketcap==3.3.145 pymarketcap==3.3.148
# Required for plotting data # Required for plotting data
#matplotlib==2.1.0 #matplotlib==2.1.0

View File

@ -10,7 +10,7 @@ from freqtrade.misc import common_args_parser
def plot_parse_args(args ): def plot_parse_args(args ):
parser = common_args_parser(args, 'Graph utility') parser = common_args_parser(description='Graph utility')
parser.add_argument( parser.add_argument(
'-p', '--pair', '-p', '--pair',
help = 'What currency pair', help = 'What currency pair',