Merge branch 'freqtrade-develop' into wohlgemuth
This commit is contained in:
commit
844761db35
@ -3,3 +3,4 @@ omit =
|
|||||||
scripts/*
|
scripts/*
|
||||||
freqtrade/tests/*
|
freqtrade/tests/*
|
||||||
freqtrade/vendor/*
|
freqtrade/vendor/*
|
||||||
|
freqtrade/__main__.py
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
# Backtesting
|
# Backtesting
|
||||||
|
|
||||||
This page explains how to validate your strategy performance by using
|
This page explains how to validate your strategy performance by using
|
||||||
Backtesting.
|
Backtesting.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [Test your strategy with Backtesting](#test-your-strategy-with-backtesting)
|
- [Test your strategy with Backtesting](#test-your-strategy-with-backtesting)
|
||||||
- [Understand the backtesting result](#understand-the-backtesting-result)
|
- [Understand the backtesting result](#understand-the-backtesting-result)
|
||||||
|
|
||||||
## Test your strategy with Backtesting
|
## Test your strategy with Backtesting
|
||||||
|
|
||||||
Now you have good Buy and Sell strategies, you want to test it against
|
Now you have good Buy and Sell strategies, you want to test it against
|
||||||
real data. This is what we call
|
real data. This is what we call
|
||||||
[backtesting](https://en.wikipedia.org/wiki/Backtesting).
|
[backtesting](https://en.wikipedia.org/wiki/Backtesting).
|
||||||
|
|
||||||
|
|
||||||
Backtesting will use the crypto-currencies (pair) from your config file
|
Backtesting will use the crypto-currencies (pair) from your config file
|
||||||
and load static tickers located in
|
and load static tickers located in
|
||||||
[/freqtrade/tests/testdata](https://github.com/freqtrade/freqtrade/tree/develop/freqtrade/tests/testdata).
|
[/freqtrade/tests/testdata](https://github.com/freqtrade/freqtrade/tree/develop/freqtrade/tests/testdata).
|
||||||
@ -19,70 +21,80 @@ If the 5 min and 1 min ticker for the crypto-currencies to test is not
|
|||||||
already in the `testdata` folder, backtesting will download them
|
already in the `testdata` folder, backtesting will download them
|
||||||
automatically. Testdata files will not be updated until you specify it.
|
automatically. Testdata files will not be updated until you specify it.
|
||||||
|
|
||||||
The result of backtesting will confirm you if your bot as more chance to
|
The result of backtesting will confirm you if your bot has better odds of making a profit than a loss.
|
||||||
make a profit than a loss.
|
|
||||||
|
|
||||||
|
|
||||||
The backtesting is very easy with freqtrade.
|
The backtesting is very easy with freqtrade.
|
||||||
|
|
||||||
### Run a backtesting against the currencies listed in your config file
|
### Run a backtesting against the currencies listed in your config file
|
||||||
**With 5 min tickers (Per default)**
|
#### With 5 min tickers (Per default)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 ./freqtrade/main.py backtesting --realistic-simulation
|
python3 ./freqtrade/main.py backtesting --realistic-simulation
|
||||||
```
|
```
|
||||||
|
|
||||||
**With 1 min tickers**
|
#### With 1 min tickers
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 ./freqtrade/main.py backtesting --realistic-simulation --ticker-interval 1m
|
python3 ./freqtrade/main.py backtesting --realistic-simulation --ticker-interval 1m
|
||||||
```
|
```
|
||||||
|
|
||||||
**Update cached pairs with the latest data**
|
#### Update cached pairs with the latest data
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 ./freqtrade/main.py backtesting --realistic-simulation --refresh-pairs-cached
|
python3 ./freqtrade/main.py backtesting --realistic-simulation --refresh-pairs-cached
|
||||||
```
|
```
|
||||||
|
|
||||||
**With live data (do not alter your testdata files)**
|
#### With live data (do not alter your testdata files)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 ./freqtrade/main.py backtesting --realistic-simulation --live
|
python3 ./freqtrade/main.py backtesting --realistic-simulation --live
|
||||||
```
|
```
|
||||||
|
|
||||||
**Using a different on-disk ticker-data source**
|
#### Using a different on-disk ticker-data source
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 ./freqtrade/main.py backtesting --datadir freqtrade/tests/testdata-20180101
|
python3 ./freqtrade/main.py backtesting --datadir freqtrade/tests/testdata-20180101
|
||||||
```
|
```
|
||||||
|
|
||||||
**With a (custom) strategy file**
|
#### With a (custom) strategy file
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 ./freqtrade/main.py -s TestStrategy backtesting
|
python3 ./freqtrade/main.py -s TestStrategy backtesting
|
||||||
```
|
```
|
||||||
|
|
||||||
Where `-s TestStrategy` refers to the class name within the strategy file `test_strategy.py` found in the `freqtrade/user_data/strategies` directory
|
Where `-s TestStrategy` refers to the class name within the strategy file `test_strategy.py` found in the `freqtrade/user_data/strategies` directory
|
||||||
|
|
||||||
**Exporting trades to file**
|
#### Exporting trades to file
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 ./freqtrade/main.py backtesting --export trades
|
python3 ./freqtrade/main.py backtesting --export trades
|
||||||
```
|
```
|
||||||
|
|
||||||
**Exporting trades to file specifying a custom filename**
|
#### Exporting trades to file specifying a custom filename
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 ./freqtrade/main.py backtesting --export trades --export-filename=backtest_teststrategy.json
|
python3 ./freqtrade/main.py backtesting --export trades --export-filename=backtest_teststrategy.json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Running backtest with smaller testset
|
||||||
|
|
||||||
**Running backtest with smaller testset**
|
|
||||||
Use the `--timerange` argument to change how much of the testset
|
Use the `--timerange` argument to change how much of the testset
|
||||||
you want to use. The last N ticks/timeframes will be used.
|
you want to use. The last N ticks/timeframes will be used.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 ./freqtrade/main.py backtesting --timerange=-200
|
python3 ./freqtrade/main.py backtesting --timerange=-200
|
||||||
```
|
```
|
||||||
|
|
||||||
***Advanced use of timerange***
|
#### Advanced use of timerange
|
||||||
|
|
||||||
Doing `--timerange=-200` will get the last 200 timeframes
|
Doing `--timerange=-200` will get the last 200 timeframes
|
||||||
from your inputdata. You can also specify specific dates,
|
from your inputdata. You can also specify specific dates,
|
||||||
or a range span indexed by start and stop.
|
or a range span indexed by start and stop.
|
||||||
|
|
||||||
The full timerange specification:
|
The full timerange specification:
|
||||||
|
|
||||||
- Use last 123 tickframes of data: `--timerange=-123`
|
- Use last 123 tickframes of data: `--timerange=-123`
|
||||||
- Use first 123 tickframes of data: `--timerange=123-`
|
- Use first 123 tickframes of data: `--timerange=123-`
|
||||||
- Use tickframes from line 123 through 456: `--timerange=123-456`
|
- Use tickframes from line 123 through 456: `--timerange=123-456`
|
||||||
@ -92,11 +104,12 @@ The full timerange specification:
|
|||||||
- Use tickframes between POSIX timestamps 1527595200 1527618600:
|
- Use tickframes between POSIX timestamps 1527595200 1527618600:
|
||||||
`--timerange=1527595200-1527618600`
|
`--timerange=1527595200-1527618600`
|
||||||
|
|
||||||
|
#### Downloading new set of ticker data
|
||||||
|
|
||||||
**Downloading new set of ticker data**
|
|
||||||
To download new set of backtesting ticker data, you can use a download script.
|
To download new set of backtesting ticker data, you can use a download script.
|
||||||
|
|
||||||
If you are using Binance for example:
|
If you are using Binance for example:
|
||||||
|
|
||||||
- create a folder `user_data/data/binance` and copy `pairs.json` in that folder.
|
- create a folder `user_data/data/binance` and copy `pairs.json` in that folder.
|
||||||
- update the `pairs.json` to contain the currency pairs you are interested in.
|
- update the `pairs.json` to contain the currency pairs you are interested in.
|
||||||
|
|
||||||
@ -119,33 +132,55 @@ This will download ticker data for all the currency pairs you defined in `pairs.
|
|||||||
- To download ticker data for only 10 days, use `--days 10`.
|
- To download ticker data for only 10 days, use `--days 10`.
|
||||||
- Use `--timeframes` to specify which tickers to download. Default is `--timeframes 1m 5m` which will download 1-minute and 5-minute tickers.
|
- Use `--timeframes` to specify which tickers to download. Default is `--timeframes 1m 5m` which will download 1-minute and 5-minute tickers.
|
||||||
|
|
||||||
|
For help about backtesting usage, please refer to [Backtesting commands](#backtesting-commands).
|
||||||
For help about backtesting usage, please refer to
|
|
||||||
[Backtesting commands](#backtesting-commands).
|
|
||||||
|
|
||||||
## Understand the backtesting result
|
## Understand the backtesting result
|
||||||
|
|
||||||
The most important in the backtesting is to understand the result.
|
The most important in the backtesting is to understand the result.
|
||||||
|
|
||||||
A backtesting result will look like that:
|
A backtesting result will look like that:
|
||||||
|
|
||||||
```
|
```
|
||||||
====================== BACKTESTING REPORT ================================
|
======================================== BACKTESTING REPORT =========================================
|
||||||
pair buy count avg profit % total profit BTC avg duration
|
| pair | buy count | avg profit % | total profit BTC | avg duration | profit | loss |
|
||||||
-------- ----------- -------------- ------------------ --------------
|
|:---------|------------:|---------------:|-------------------:|---------------:|---------:|-------:|
|
||||||
ETH/BTC 56 -0.67 -0.00075455 62.3
|
| ETH/BTC | 44 | 0.18 | 0.00159118 | 50.9 | 44 | 0 |
|
||||||
LTC/BTC 38 -0.48 -0.00036315 57.9
|
| LTC/BTC | 27 | 0.10 | 0.00051931 | 103.1 | 26 | 1 |
|
||||||
ETC/BTC 42 -1.15 -0.00096469 67.0
|
| ETC/BTC | 24 | 0.05 | 0.00022434 | 166.0 | 22 | 2 |
|
||||||
DASH/BTC 72 -0.62 -0.00089368 39.9
|
| DASH/BTC | 29 | 0.18 | 0.00103223 | 192.2 | 29 | 0 |
|
||||||
ZEC/BTC 45 -0.46 -0.00041387 63.2
|
| ZEC/BTC | 65 | -0.02 | -0.00020621 | 202.7 | 62 | 3 |
|
||||||
XLM/BTC 24 -0.88 -0.00041846 47.7
|
| XLM/BTC | 35 | 0.02 | 0.00012877 | 242.4 | 32 | 3 |
|
||||||
NXT/BTC 24 0.68 0.00031833 40.2
|
| BCH/BTC | 12 | 0.62 | 0.00149284 | 50.0 | 12 | 0 |
|
||||||
POWR/BTC 35 0.98 0.00064887 45.3
|
| POWR/BTC | 21 | 0.26 | 0.00108215 | 134.8 | 21 | 0 |
|
||||||
ADA/BTC 43 -0.39 -0.00032292 55.0
|
| ADA/BTC | 54 | -0.19 | -0.00205202 | 191.3 | 47 | 7 |
|
||||||
XMR/BTC 40 -0.40 -0.00032181 47.4
|
| XMR/BTC | 24 | -0.43 | -0.00206013 | 120.6 | 20 | 4 |
|
||||||
TOTAL 419 -0.41 -0.00348593 52.9
|
| TOTAL | 335 | 0.03 | 0.00175246 | 157.9 | 315 | 20 |
|
||||||
|
2018-06-13 06:57:27,347 - freqtrade.optimize.backtesting - INFO -
|
||||||
|
====================================== LEFT OPEN TRADES REPORT ======================================
|
||||||
|
| pair | buy count | avg profit % | total profit BTC | avg duration | profit | loss |
|
||||||
|
|:---------|------------:|---------------:|-------------------:|---------------:|---------:|-------:|
|
||||||
|
| ETH/BTC | 3 | 0.16 | 0.00009619 | 25.0 | 3 | 0 |
|
||||||
|
| LTC/BTC | 1 | -1.00 | -0.00020118 | 1085.0 | 0 | 1 |
|
||||||
|
| ETC/BTC | 2 | -1.80 | -0.00071933 | 1092.5 | 0 | 2 |
|
||||||
|
| DASH/BTC | 0 | nan | 0.00000000 | nan | 0 | 0 |
|
||||||
|
| ZEC/BTC | 3 | -4.27 | -0.00256826 | 1301.7 | 0 | 3 |
|
||||||
|
| XLM/BTC | 3 | -1.11 | -0.00066744 | 965.0 | 0 | 3 |
|
||||||
|
| BCH/BTC | 0 | nan | 0.00000000 | nan | 0 | 0 |
|
||||||
|
| POWR/BTC | 0 | nan | 0.00000000 | nan | 0 | 0 |
|
||||||
|
| ADA/BTC | 7 | -3.58 | -0.00503604 | 850.0 | 0 | 7 |
|
||||||
|
| XMR/BTC | 4 | -3.79 | -0.00303456 | 291.2 | 0 | 4 |
|
||||||
|
| TOTAL | 23 | -2.63 | -0.01213062 | 750.4 | 3 | 20 |
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The 1st table will contain all trades the bot made.
|
||||||
|
|
||||||
|
The 2nd table will contain all trades the bot had to `forcesell` at the end of the backtest period to prsent a full picture.
|
||||||
|
These trades are also included in the first table, but are extracted separately for clarity.
|
||||||
|
|
||||||
The last line will give you the overall performance of your strategy,
|
The last line will give you the overall performance of your strategy,
|
||||||
here:
|
here:
|
||||||
|
|
||||||
```
|
```
|
||||||
TOTAL 419 -0.41 -0.00348593 52.9
|
TOTAL 419 -0.41 -0.00348593 52.9
|
||||||
```
|
```
|
||||||
@ -161,6 +196,7 @@ strategy, your sell strategy, and also by the `minimal_roi` and
|
|||||||
As for an example if your minimal_roi is only `"0": 0.01`. You cannot
|
As for an example if your minimal_roi is only `"0": 0.01`. You cannot
|
||||||
expect the bot to make more profit than 1% (because it will sell every
|
expect the bot to make more profit than 1% (because it will sell every
|
||||||
time a trade will reach 1%).
|
time a trade will reach 1%).
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"minimal_roi": {
|
"minimal_roi": {
|
||||||
"0": 0.01
|
"0": 0.01
|
||||||
@ -173,6 +209,7 @@ profit. Hence, keep in mind that your performance is a mix of your
|
|||||||
strategies, your configuration, and the crypto-currency you have set up.
|
strategies, your configuration, and the crypto-currency you have set up.
|
||||||
|
|
||||||
## Next step
|
## Next step
|
||||||
|
|
||||||
Great, your strategy is profitable. What if the bot can give your the
|
Great, your strategy is profitable. What if the bot can give your the
|
||||||
optimal parameters to use for your strategy?
|
optimal parameters to use for your strategy?
|
||||||
Your next step is to learn [how to find optimal parameters with Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md)
|
Your next step is to learn [how to find optimal parameters with Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md)
|
||||||
|
@ -160,13 +160,12 @@ the parameter `-l` or `--live`.
|
|||||||
|
|
||||||
## Hyperopt commands
|
## Hyperopt commands
|
||||||
|
|
||||||
It is possible to use hyperopt for trading strategy optimization.
|
To optimize your strategy, you can use hyperopt parameter hyperoptimization
|
||||||
Hyperopt uses an internal json config return by `hyperopt_optimize_conf()`
|
to find optimal parameter values for your stategy.
|
||||||
located in `freqtrade/optimize/hyperopt_conf.py`.
|
|
||||||
|
|
||||||
```
|
```
|
||||||
usage: main.py hyperopt [-h] [-i TICKER_INTERVAL] [--realistic-simulation]
|
usage: main.py hyperopt [-h] [-i TICKER_INTERVAL] [--realistic-simulation]
|
||||||
[--timerange TIMERANGE] [-e INT] [--use-mongodb]
|
[--timerange TIMERANGE] [-e INT]
|
||||||
[-s {all,buy,roi,stoploss} [{all,buy,roi,stoploss} ...]]
|
[-s {all,buy,roi,stoploss} [{all,buy,roi,stoploss} ...]]
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
@ -176,11 +175,8 @@ optional arguments:
|
|||||||
--realistic-simulation
|
--realistic-simulation
|
||||||
uses max_open_trades from config to simulate real
|
uses max_open_trades from config to simulate real
|
||||||
world limitations
|
world limitations
|
||||||
--timerange TIMERANGE
|
--timerange TIMERANGE specify what timerange of data to use.
|
||||||
specify what timerange of data to use.
|
|
||||||
-e INT, --epochs INT specify number of epochs (default: 100)
|
-e INT, --epochs INT specify number of epochs (default: 100)
|
||||||
--use-mongodb parallelize evaluations with mongodb (requires mongod
|
|
||||||
in PATH)
|
|
||||||
-s {all,buy,roi,stoploss} [{all,buy,roi,stoploss} ...], --spaces {all,buy,roi,stoploss} [{all,buy,roi,stoploss} ...]
|
-s {all,buy,roi,stoploss} [{all,buy,roi,stoploss} ...], --spaces {all,buy,roi,stoploss} [{all,buy,roi,stoploss} ...]
|
||||||
Specify which parameters to hyperopt. Space separate
|
Specify which parameters to hyperopt. Space separate
|
||||||
list. Default: all
|
list. Default: all
|
||||||
|
@ -9,7 +9,6 @@ parameters with Hyperopt.
|
|||||||
- [Advanced Hyperopt notions](#advanced-notions)
|
- [Advanced Hyperopt notions](#advanced-notions)
|
||||||
- [Understand the Guards and Triggers](#understand-the-guards-and-triggers)
|
- [Understand the Guards and Triggers](#understand-the-guards-and-triggers)
|
||||||
- [Execute Hyperopt](#execute-hyperopt)
|
- [Execute Hyperopt](#execute-hyperopt)
|
||||||
- [Hyperopt with MongoDB](#hyperopt-with-mongoDB)
|
|
||||||
- [Understand the hyperopts result](#understand-the-backtesting-result)
|
- [Understand the hyperopts result](#understand-the-backtesting-result)
|
||||||
|
|
||||||
## Prepare Hyperopt
|
## Prepare Hyperopt
|
||||||
@ -194,41 +193,6 @@ Legal values are:
|
|||||||
- `stoploss`: search for the best stoploss value
|
- `stoploss`: search for the best stoploss value
|
||||||
- space-separated list of any of the above values for example `--spaces roi stoploss`
|
- space-separated list of any of the above values for example `--spaces roi stoploss`
|
||||||
|
|
||||||
### Hyperopt with MongoDB
|
|
||||||
Hyperopt with MongoDB, is like Hyperopt under steroids. As you saw by
|
|
||||||
executing the previous command is the execution takes a long time.
|
|
||||||
To accelerate it you can use hyperopt with MongoDB.
|
|
||||||
|
|
||||||
To run hyperopt with MongoDb you will need 3 terminals.
|
|
||||||
|
|
||||||
**Terminal 1: Start MongoDB**
|
|
||||||
```bash
|
|
||||||
cd <freqtrade>
|
|
||||||
source .env/bin/activate
|
|
||||||
python3 scripts/start-mongodb.py
|
|
||||||
```
|
|
||||||
|
|
||||||
**Terminal 2: Start Hyperopt worker**
|
|
||||||
```bash
|
|
||||||
cd <freqtrade>
|
|
||||||
source .env/bin/activate
|
|
||||||
python3 scripts/start-hyperopt-worker.py
|
|
||||||
```
|
|
||||||
|
|
||||||
**Terminal 3: Start Hyperopt with MongoDB**
|
|
||||||
```bash
|
|
||||||
cd <freqtrade>
|
|
||||||
source .env/bin/activate
|
|
||||||
python3 ./freqtrade/main.py -c config.json hyperopt --use-mongodb
|
|
||||||
```
|
|
||||||
|
|
||||||
**Re-run an Hyperopt**
|
|
||||||
To re-run Hyperopt you have to delete the existing MongoDB table.
|
|
||||||
```bash
|
|
||||||
cd <freqtrade>
|
|
||||||
rm -rf .hyperopt/mongodb/
|
|
||||||
```
|
|
||||||
|
|
||||||
## Understand the hyperopts result
|
## Understand the hyperopts result
|
||||||
Once Hyperopt is completed you can use the result to adding new buy
|
Once Hyperopt is completed you can use the result to adding new buy
|
||||||
signal. Given following result from hyperopt:
|
signal. Given following result from hyperopt:
|
||||||
|
@ -184,6 +184,26 @@ docker start freqtrade
|
|||||||
|
|
||||||
You do not need to rebuild the image for configuration changes, it will suffice to edit `config.json` and restart the container.
|
You do not need to rebuild the image for configuration changes, it will suffice to edit `config.json` and restart the container.
|
||||||
|
|
||||||
|
### 7. Backtest with docker
|
||||||
|
|
||||||
|
The following assumes that the above steps (1-4) have been completed successfully.
|
||||||
|
Also, backtest-data should be available at `~/.freqtrade/user_data/`.
|
||||||
|
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
docker run -d \
|
||||||
|
--name freqtrade \
|
||||||
|
-v /etc/localtime:/etc/localtime:ro \
|
||||||
|
-v ~/.freqtrade/config.json:/freqtrade/config.json \
|
||||||
|
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \
|
||||||
|
-v ~/.freqtrade/user_data/:/freqtrade/user_data/ \
|
||||||
|
freqtrade --strategy AwsomelyProfitableStrategy backtesting
|
||||||
|
```
|
||||||
|
|
||||||
|
Head over to the [Backtesting Documentation](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md) for more details.
|
||||||
|
|
||||||
|
*Note*: Additional parameters can be appended after the image name (`freqtrade` in the above example).
|
||||||
|
|
||||||
------
|
------
|
||||||
|
|
||||||
## Custom Installation
|
## Custom Installation
|
||||||
@ -225,17 +245,7 @@ cd ..
|
|||||||
rm -rf ./ta-lib*
|
rm -rf ./ta-lib*
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 3. [Optional] Install MongoDB
|
#### 3. Install FreqTrade
|
||||||
|
|
||||||
Install MongoDB if you plan to optimize your strategy with Hyperopt.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt-get install mongodb-org
|
|
||||||
```
|
|
||||||
|
|
||||||
> 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).
|
|
||||||
|
|
||||||
#### 4. Install FreqTrade
|
|
||||||
|
|
||||||
Clone the git repository:
|
Clone the git repository:
|
||||||
|
|
||||||
@ -243,7 +253,7 @@ Clone the git repository:
|
|||||||
git clone https://github.com/freqtrade/freqtrade.git
|
git clone https://github.com/freqtrade/freqtrade.git
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 5. Configure `freqtrade` as a `systemd` service
|
#### 4. 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.
|
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.
|
||||||
|
|
||||||
@ -267,19 +277,7 @@ sudo loginctl enable-linger "$USER"
|
|||||||
brew install python3 git wget ta-lib
|
brew install python3 git wget ta-lib
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 2. [Optional] Install MongoDB
|
#### 2. Install FreqTrade
|
||||||
|
|
||||||
Install MongoDB if you plan to optimize your strategy with Hyperopt.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
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
|
|
||||||
mkdir -p <path_freqtrade>/env/mongodb
|
|
||||||
cp -R -n mongodb-osx-x86_64-3.4.10/ <path_freqtrade>/env/mongodb
|
|
||||||
export PATH=<path_freqtrade>/env/mongodb/bin:$PATH
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. Install FreqTrade
|
|
||||||
|
|
||||||
Clone the git repository:
|
Clone the git repository:
|
||||||
|
|
||||||
|
@ -203,12 +203,6 @@ class Arguments(object):
|
|||||||
type=int,
|
type=int,
|
||||||
metavar='INT',
|
metavar='INT',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
'--use-mongodb',
|
|
||||||
help='parallelize evaluations with mongodb (requires mongod in PATH)',
|
|
||||||
dest='mongodb',
|
|
||||||
action='store_true',
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-s', '--spaces',
|
'-s', '--spaces',
|
||||||
help='Specify which parameters to hyperopt. Space separate list. \
|
help='Specify which parameters to hyperopt. Space separate list. \
|
||||||
|
@ -188,11 +188,6 @@ class Configuration(object):
|
|||||||
logger.info('Parameter --epochs detected ...')
|
logger.info('Parameter --epochs detected ...')
|
||||||
logger.info('Will run Hyperopt with for %s epochs ...', config.get('epochs'))
|
logger.info('Will run Hyperopt with for %s epochs ...', config.get('epochs'))
|
||||||
|
|
||||||
# If --mongodb is used we add it to the configuration
|
|
||||||
if 'mongodb' in self.args and self.args.mongodb:
|
|
||||||
config.update({'mongodb': self.args.mongodb})
|
|
||||||
logger.info('Parameter --use-mongodb detected ...')
|
|
||||||
|
|
||||||
# If --spaces is used we add it to the configuration
|
# If --spaces is used we add it to the configuration
|
||||||
if 'spaces' in self.args and self.args.spaces:
|
if 'spaces' in self.args and self.args.spaces:
|
||||||
config.update({'spaces': self.args.spaces})
|
config.update({'spaces': self.args.spaces})
|
||||||
|
@ -11,8 +11,6 @@ from freqtrade import misc, constants
|
|||||||
from freqtrade.exchange import get_ticker_history
|
from freqtrade.exchange import get_ticker_history
|
||||||
from freqtrade.arguments import TimeRange
|
from freqtrade.arguments import TimeRange
|
||||||
|
|
||||||
from user_data.hyperopt_conf import hyperopt_optimize_conf
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -83,7 +81,7 @@ def load_tickerdata_file(
|
|||||||
|
|
||||||
def load_data(datadir: str,
|
def load_data(datadir: str,
|
||||||
ticker_interval: str,
|
ticker_interval: str,
|
||||||
pairs: Optional[List[str]] = None,
|
pairs: List[str],
|
||||||
refresh_pairs: Optional[bool] = False,
|
refresh_pairs: Optional[bool] = False,
|
||||||
timerange: TimeRange = TimeRange(None, None, 0, 0)) -> Dict[str, List]:
|
timerange: TimeRange = TimeRange(None, None, 0, 0)) -> Dict[str, List]:
|
||||||
"""
|
"""
|
||||||
@ -92,14 +90,12 @@ def load_data(datadir: str,
|
|||||||
"""
|
"""
|
||||||
result = {}
|
result = {}
|
||||||
|
|
||||||
_pairs = pairs or hyperopt_optimize_conf()['exchange']['pair_whitelist']
|
|
||||||
|
|
||||||
# If the user force the refresh of pairs
|
# If the user force the refresh of pairs
|
||||||
if refresh_pairs:
|
if refresh_pairs:
|
||||||
logger.info('Download data for all pairs and store them in %s', datadir)
|
logger.info('Download data for all pairs and store them in %s', datadir)
|
||||||
download_pairs(datadir, _pairs, ticker_interval, timerange=timerange)
|
download_pairs(datadir, pairs, ticker_interval, timerange=timerange)
|
||||||
|
|
||||||
for pair in _pairs:
|
for pair in pairs:
|
||||||
pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange)
|
pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange)
|
||||||
if pairdata:
|
if pairdata:
|
||||||
result[pair] = pairdata
|
result[pair] = pairdata
|
||||||
|
@ -6,7 +6,8 @@ This module contains the backtesting logic
|
|||||||
import logging
|
import logging
|
||||||
import operator
|
import operator
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from typing import Dict, Tuple, Any, List, Optional
|
from datetime import datetime
|
||||||
|
from typing import Dict, Tuple, Any, List, Optional, NamedTuple
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
@ -23,6 +24,21 @@ from freqtrade.persistence import Trade
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class BacktestResult(NamedTuple):
|
||||||
|
"""
|
||||||
|
NamedTuple Defining BacktestResults inputs.
|
||||||
|
"""
|
||||||
|
pair: str
|
||||||
|
profit_percent: float
|
||||||
|
profit_abs: float
|
||||||
|
open_time: datetime
|
||||||
|
close_time: datetime
|
||||||
|
open_index: int
|
||||||
|
close_index: int
|
||||||
|
trade_duration: float
|
||||||
|
open_at_end: bool
|
||||||
|
|
||||||
|
|
||||||
class Backtesting(object):
|
class Backtesting(object):
|
||||||
"""
|
"""
|
||||||
Backtesting class, this class contains all the logic to run a backtest
|
Backtesting class, this class contains all the logic to run a backtest
|
||||||
@ -78,17 +94,16 @@ class Backtesting(object):
|
|||||||
headers = ['pair', 'buy count', 'avg profit %', 'cum profit %',
|
headers = ['pair', 'buy count', 'avg profit %', 'cum profit %',
|
||||||
'total profit ' + stake_currency, 'avg duration', 'profit', 'loss']
|
'total profit ' + stake_currency, 'avg duration', 'profit', 'loss']
|
||||||
for pair in data:
|
for pair in data:
|
||||||
result = results[results.currency == pair]
|
result = results[results.pair == pair]
|
||||||
|
|
||||||
tabular_data.append([
|
tabular_data.append([
|
||||||
pair,
|
pair,
|
||||||
len(result.index),
|
len(result.index),
|
||||||
result.profit_percent.mean() * 100.0,
|
result.profit_percent.mean() * 100.0,
|
||||||
result.profit_percent.sum() * 100.0,
|
result.profit_percent.sum() * 100.0,
|
||||||
result.profit_BTC.sum(),
|
result.profit_abs.sum(),
|
||||||
result.duration.mean(),
|
result.trade_duration.mean(),
|
||||||
len(result[result.profit_BTC > 0]),
|
len(result[result.profit_abs > 0]),
|
||||||
len(result[result.profit_BTC < 0])
|
len(result[result.profit_abs < 0])
|
||||||
])
|
])
|
||||||
# Append Total
|
# Append Total
|
||||||
tabular_data.append([
|
tabular_data.append([
|
||||||
@ -96,16 +111,28 @@ class Backtesting(object):
|
|||||||
len(results.index),
|
len(results.index),
|
||||||
results.profit_percent.mean() * 100.0,
|
results.profit_percent.mean() * 100.0,
|
||||||
results.profit_percent.sum() * 100.0,
|
results.profit_percent.sum() * 100.0,
|
||||||
results.profit_BTC.sum(),
|
results.profit_abs.sum(),
|
||||||
results.duration.mean(),
|
results.trade_duration.mean(),
|
||||||
len(results[results.profit_BTC > 0]),
|
len(results[results.profit_abs > 0]),
|
||||||
len(results[results.profit_BTC < 0])
|
len(results[results.profit_abs < 0])
|
||||||
])
|
])
|
||||||
return floatfmt, headers, tabular_data
|
return floatfmt, headers, tabular_data
|
||||||
|
|
||||||
|
def _store_backtest_result(self, recordfilename: Optional[str], results: DataFrame) -> None:
|
||||||
|
|
||||||
|
records = [(trade_entry.pair, trade_entry.profit_percent,
|
||||||
|
trade_entry.open_time.timestamp(),
|
||||||
|
trade_entry.close_time.timestamp(),
|
||||||
|
trade_entry.open_index - 1, trade_entry.trade_duration)
|
||||||
|
for index, trade_entry in results.iterrows()]
|
||||||
|
|
||||||
|
if records:
|
||||||
|
logger.info('Dumping backtest results to %s', recordfilename)
|
||||||
|
file_dump_json(recordfilename, records)
|
||||||
|
|
||||||
def _get_sell_trade_entry(
|
def _get_sell_trade_entry(
|
||||||
self, pair: str, buy_row: DataFrame,
|
self, pair: str, buy_row: DataFrame,
|
||||||
partial_ticker: List, trade_count_lock: Dict, args: Dict) -> Optional[Tuple]:
|
partial_ticker: List, trade_count_lock: Dict, args: Dict) -> Optional[BacktestResult]:
|
||||||
|
|
||||||
stake_amount = args['stake_amount']
|
stake_amount = args['stake_amount']
|
||||||
max_open_trades = args.get('max_open_trades', 0)
|
max_open_trades = args.get('max_open_trades', 0)
|
||||||
@ -128,17 +155,32 @@ class Backtesting(object):
|
|||||||
buy_signal = sell_row.buy
|
buy_signal = sell_row.buy
|
||||||
if self.analyze.should_sell(trade, sell_row.close, sell_row.date, buy_signal,
|
if self.analyze.should_sell(trade, sell_row.close, sell_row.date, buy_signal,
|
||||||
sell_row.sell):
|
sell_row.sell):
|
||||||
return \
|
return BacktestResult(pair=pair,
|
||||||
sell_row, \
|
profit_percent=trade.calc_profit_percent(rate=sell_row.close),
|
||||||
(
|
profit_abs=trade.calc_profit(rate=sell_row.close),
|
||||||
pair,
|
open_time=buy_row.date,
|
||||||
trade.calc_profit_percent(rate=sell_row.close),
|
close_time=sell_row.date,
|
||||||
trade.calc_profit(rate=sell_row.close),
|
trade_duration=(sell_row.date - buy_row.date).seconds // 60,
|
||||||
(sell_row.date - buy_row.date).seconds // 60,
|
open_index=buy_row.Index,
|
||||||
buy_row.date,
|
close_index=sell_row.Index,
|
||||||
sell_row.date
|
open_at_end=False
|
||||||
), \
|
)
|
||||||
sell_row.date
|
if partial_ticker:
|
||||||
|
# no sell condition found - trade stil open at end of backtest period
|
||||||
|
sell_row = partial_ticker[-1]
|
||||||
|
btr = BacktestResult(pair=pair,
|
||||||
|
profit_percent=trade.calc_profit_percent(rate=sell_row.close),
|
||||||
|
profit_abs=trade.calc_profit(rate=sell_row.close),
|
||||||
|
open_time=buy_row.date,
|
||||||
|
close_time=sell_row.date,
|
||||||
|
trade_duration=(sell_row.date - buy_row.date).seconds // 60,
|
||||||
|
open_index=buy_row.Index,
|
||||||
|
close_index=sell_row.Index,
|
||||||
|
open_at_end=True
|
||||||
|
)
|
||||||
|
logger.debug('Force_selling still open trade %s with %s perc - %s', btr.pair,
|
||||||
|
btr.profit_percent, btr.profit_abs)
|
||||||
|
return btr
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def backtest(self, args: Dict) -> DataFrame:
|
def backtest(self, args: Dict) -> DataFrame:
|
||||||
@ -154,17 +196,12 @@ class Backtesting(object):
|
|||||||
processed: a processed dictionary with format {pair, data}
|
processed: a processed dictionary with format {pair, data}
|
||||||
max_open_trades: maximum number of concurrent trades (default: 0, disabled)
|
max_open_trades: maximum number of concurrent trades (default: 0, disabled)
|
||||||
realistic: do we try to simulate realistic trades? (default: True)
|
realistic: do we try to simulate realistic trades? (default: True)
|
||||||
sell_profit_only: sell if profit only
|
|
||||||
use_sell_signal: act on sell-signal
|
|
||||||
:return: DataFrame
|
:return: DataFrame
|
||||||
"""
|
"""
|
||||||
headers = ['date', 'buy', 'open', 'close', 'sell']
|
headers = ['date', 'buy', 'open', 'close', 'sell']
|
||||||
processed = args['processed']
|
processed = args['processed']
|
||||||
max_open_trades = args.get('max_open_trades', 0)
|
max_open_trades = args.get('max_open_trades', 0)
|
||||||
realistic = args.get('realistic', False)
|
realistic = args.get('realistic', False)
|
||||||
record = args.get('record', None)
|
|
||||||
recordfilename = args.get('recordfn', 'backtest-result.json')
|
|
||||||
records = []
|
|
||||||
trades = []
|
trades = []
|
||||||
trade_count_lock: Dict = {}
|
trade_count_lock: Dict = {}
|
||||||
for pair, pair_data in processed.items():
|
for pair, pair_data in processed.items():
|
||||||
@ -179,6 +216,8 @@ class Backtesting(object):
|
|||||||
|
|
||||||
ticker_data.drop(ticker_data.head(1).index, inplace=True)
|
ticker_data.drop(ticker_data.head(1).index, inplace=True)
|
||||||
|
|
||||||
|
# Convert from Pandas to list for performance reasons
|
||||||
|
# (Looping Pandas is slow.)
|
||||||
ticker = [x for x in ticker_data.itertuples()]
|
ticker = [x for x in ticker_data.itertuples()]
|
||||||
|
|
||||||
lock_pair_until = None
|
lock_pair_until = None
|
||||||
@ -196,30 +235,18 @@ class Backtesting(object):
|
|||||||
|
|
||||||
trade_count_lock[row.date] = trade_count_lock.get(row.date, 0) + 1
|
trade_count_lock[row.date] = trade_count_lock.get(row.date, 0) + 1
|
||||||
|
|
||||||
ret = self._get_sell_trade_entry(pair, row, ticker[index + 1:],
|
trade_entry = self._get_sell_trade_entry(pair, row, ticker[index + 1:],
|
||||||
trade_count_lock, args)
|
trade_count_lock, args)
|
||||||
|
|
||||||
if ret:
|
|
||||||
row2, trade_entry, next_date = ret
|
|
||||||
lock_pair_until = next_date
|
|
||||||
|
|
||||||
|
if trade_entry:
|
||||||
|
lock_pair_until = trade_entry.close_time
|
||||||
trades.append(trade_entry)
|
trades.append(trade_entry)
|
||||||
if record:
|
else:
|
||||||
# Note, need to be json.dump friendly
|
# Set lock_pair_until to end of testing period if trade could not be closed
|
||||||
# record a tuple of pair, current_profit_percent,
|
# This happens only if the buy-signal was with the last candle
|
||||||
# entry-date, duration
|
lock_pair_until = ticker_data.iloc[-1].date
|
||||||
records.append((pair, trade_entry[1],
|
|
||||||
row.date.strftime('%s'),
|
|
||||||
row2.date.strftime('%s'),
|
|
||||||
index, trade_entry[3]))
|
|
||||||
# For now export inside backtest(), maybe change so that backtest()
|
|
||||||
# returns a tuple like: (dataframe, records, logs, etc)
|
|
||||||
if record and record.find('trades') >= 0:
|
|
||||||
logger.info('Dumping backtest results to %s', recordfilename)
|
|
||||||
file_dump_json(recordfilename, records)
|
|
||||||
labels = ['currency', 'profit_percent', 'profit_BTC', 'duration', 'entry', 'exit']
|
|
||||||
|
|
||||||
return DataFrame.from_records(trades, columns=labels)
|
return DataFrame.from_records(trades, columns=BacktestResult._fields)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""
|
"""
|
||||||
@ -270,24 +297,22 @@ class Backtesting(object):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Execute backtest and print results
|
# Execute backtest and print results
|
||||||
sell_profit_only = self.config.get('experimental', {}).get('sell_profit_only', False)
|
|
||||||
use_sell_signal = self.config.get('experimental', {}).get('use_sell_signal', False)
|
|
||||||
results = self.backtest(
|
results = self.backtest(
|
||||||
{
|
{
|
||||||
'stake_amount': self.config.get('stake_amount'),
|
'stake_amount': self.config.get('stake_amount'),
|
||||||
'processed': preprocessed,
|
'processed': preprocessed,
|
||||||
'max_open_trades': max_open_trades,
|
'max_open_trades': max_open_trades,
|
||||||
'realistic': self.config.get('realistic_simulation', False),
|
'realistic': self.config.get('realistic_simulation', False),
|
||||||
'sell_profit_only': sell_profit_only,
|
|
||||||
'use_sell_signal': use_sell_signal,
|
|
||||||
'record': self.config.get('export'),
|
|
||||||
'recordfn': self.config.get('exportfilename'),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.config.get('export', False):
|
||||||
|
self._store_backtest_result(self.config.get('exportfilename'), results)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
'\n==================================== '
|
'\n======================================== '
|
||||||
'BACKTESTING REPORT'
|
'BACKTESTING REPORT'
|
||||||
' ====================================\n'
|
' =========================================\n'
|
||||||
'%s',
|
'%s',
|
||||||
self._generate_text_table(
|
self._generate_text_table(
|
||||||
data,
|
data,
|
||||||
@ -295,7 +320,17 @@ class Backtesting(object):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# return date for data storage
|
logger.info(
|
||||||
|
'\n====================================== '
|
||||||
|
'LEFT OPEN TRADES REPORT'
|
||||||
|
' ======================================\n'
|
||||||
|
'%s',
|
||||||
|
self._generate_text_table(
|
||||||
|
data,
|
||||||
|
results.loc[results.open_at_end]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
table = self.aggregate(data, results)
|
table = self.aggregate(data, results)
|
||||||
return results, table
|
return results, table
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ from typing import Dict, Any, Callable, Optional
|
|||||||
import numpy
|
import numpy
|
||||||
import talib.abstract as ta
|
import talib.abstract as ta
|
||||||
from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, hp, space_eval, tpe
|
from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, hp, space_eval, tpe
|
||||||
from hyperopt.mongoexp import MongoTrials
|
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||||
@ -27,7 +26,6 @@ from freqtrade.arguments import Arguments
|
|||||||
from freqtrade.configuration import Configuration
|
from freqtrade.configuration import Configuration
|
||||||
from freqtrade.optimize import load_data
|
from freqtrade.optimize import load_data
|
||||||
from freqtrade.optimize.backtesting import Backtesting
|
from freqtrade.optimize.backtesting import Backtesting
|
||||||
from user_data.hyperopt_conf import hyperopt_optimize_conf
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -449,7 +447,7 @@ class Hyperopt(Backtesting):
|
|||||||
|
|
||||||
total_profit = results.profit_percent.sum()
|
total_profit = results.profit_percent.sum()
|
||||||
trade_count = len(results.index)
|
trade_count = len(results.index)
|
||||||
trade_duration = results.duration.mean()
|
trade_duration = results.trade_duration.mean()
|
||||||
|
|
||||||
if trade_count == 0 or trade_duration > self.max_accepted_trade_duration:
|
if trade_count == 0 or trade_duration > self.max_accepted_trade_duration:
|
||||||
print('.', end='')
|
print('.', end='')
|
||||||
@ -486,10 +484,10 @@ class Hyperopt(Backtesting):
|
|||||||
'Total profit {: 11.8f} {} ({:.4f}Σ%). Avg duration {:5.1f} mins.').format(
|
'Total profit {: 11.8f} {} ({:.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_abs.sum(),
|
||||||
self.config['stake_currency'],
|
self.config['stake_currency'],
|
||||||
results.profit_percent.sum(),
|
results.profit_percent.sum(),
|
||||||
results.duration.mean(),
|
results.trade_duration.mean(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
@ -506,32 +504,20 @@ class Hyperopt(Backtesting):
|
|||||||
self.analyze.populate_indicators = Hyperopt.populate_indicators # type: ignore
|
self.analyze.populate_indicators = Hyperopt.populate_indicators # type: ignore
|
||||||
self.processed = self.tickerdata_to_dataframe(data)
|
self.processed = self.tickerdata_to_dataframe(data)
|
||||||
|
|
||||||
if self.config.get('mongodb'):
|
logger.info('Preparing Trials..')
|
||||||
logger.info('Using mongodb ...')
|
signal.signal(signal.SIGINT, self.signal_handler)
|
||||||
|
# read trials file if we have one
|
||||||
|
if os.path.exists(self.trials_file) and os.path.getsize(self.trials_file) > 0:
|
||||||
|
self.trials = self.read_trials()
|
||||||
|
|
||||||
|
self.current_tries = len(self.trials.results)
|
||||||
|
self.total_tries += self.current_tries
|
||||||
logger.info(
|
logger.info(
|
||||||
'Start scripts/start-mongodb.sh and start-hyperopt-worker.sh manually!'
|
'Continuing with trials. Current: %d, Total: %d',
|
||||||
|
self.current_tries,
|
||||||
|
self.total_tries
|
||||||
)
|
)
|
||||||
|
|
||||||
db_name = 'freqtrade_hyperopt'
|
|
||||||
self.trials = MongoTrials(
|
|
||||||
arg='mongo://127.0.0.1:1234/{}/jobs'.format(db_name),
|
|
||||||
exp_key='exp1'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logger.info('Preparing Trials..')
|
|
||||||
signal.signal(signal.SIGINT, self.signal_handler)
|
|
||||||
# read trials file if we have one
|
|
||||||
if os.path.exists(self.trials_file) and os.path.getsize(self.trials_file) > 0:
|
|
||||||
self.trials = self.read_trials()
|
|
||||||
|
|
||||||
self.current_tries = len(self.trials.results)
|
|
||||||
self.total_tries += self.current_tries
|
|
||||||
logger.info(
|
|
||||||
'Continuing with trials. Current: %d, Total: %d',
|
|
||||||
self.current_tries,
|
|
||||||
self.total_tries
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
best_parameters = fmin(
|
best_parameters = fmin(
|
||||||
fn=self.generate_optimizer,
|
fn=self.generate_optimizer,
|
||||||
@ -587,18 +573,14 @@ def start(args: Namespace) -> None:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Remove noisy log messages
|
# Remove noisy log messages
|
||||||
logging.getLogger('hyperopt.mongoexp').setLevel(logging.WARNING)
|
|
||||||
logging.getLogger('hyperopt.tpe').setLevel(logging.WARNING)
|
logging.getLogger('hyperopt.tpe').setLevel(logging.WARNING)
|
||||||
|
|
||||||
# Initialize configuration
|
# Initialize configuration
|
||||||
# Monkey patch the configuration with hyperopt_conf.py
|
# Monkey patch the configuration with hyperopt_conf.py
|
||||||
configuration = Configuration(args)
|
configuration = Configuration(args)
|
||||||
logger.info('Starting freqtrade in Hyperopt mode')
|
logger.info('Starting freqtrade in Hyperopt mode')
|
||||||
|
config = configuration.load_config()
|
||||||
|
|
||||||
optimize_config = hyperopt_optimize_conf()
|
|
||||||
config = configuration._load_common_config(optimize_config)
|
|
||||||
config = configuration._load_backtesting_config(config)
|
|
||||||
config = configuration._load_hyperopt_config(config)
|
|
||||||
config['exchange']['key'] = ''
|
config['exchange']['key'] = ''
|
||||||
config['exchange']['secret'] = ''
|
config['exchange']['secret'] = ''
|
||||||
|
|
||||||
|
@ -356,21 +356,31 @@ def test_generate_text_table(default_conf, mocker):
|
|||||||
|
|
||||||
results = pd.DataFrame(
|
results = pd.DataFrame(
|
||||||
{
|
{
|
||||||
'currency': ['ETH/BTC', 'ETH/BTC'],
|
'pair': ['ETH/BTC', 'ETH/BTC'],
|
||||||
'profit_percent': [0.1, 0.2],
|
'profit_percent': [0.1, 0.2],
|
||||||
'profit_BTC': [0.2, 0.4],
|
'profit_abs': [0.2, 0.4],
|
||||||
'duration': [10, 30],
|
'cum profit %': [30, 30],
|
||||||
|
'total profit BTC': [0.6, 0.6],
|
||||||
|
'trade_duration': [10, 30],
|
||||||
'profit': [2, 0],
|
'profit': [2, 0],
|
||||||
'loss': [0, 0]
|
'loss': [0, 0]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
result_str = (
|
result_str = (
|
||||||
"""| pair | buy count | avg profit % | cum profit % | total profit BTC | avg duration | profit | loss |
|
"""| pair | buy count | avg profit % | cum profit % | total profit BTC | avg duration | profit | loss |
|
||||||
|:--------|------------:|---------------:|---------------:|-------------------:|---------------:|---------:|-------:|
|
|:--------|------------:|---------------:|---------------:|-------------------:|---------------:|---------:|-------:|
|
||||||
| ETH/BTC | 2 | 15.00 | 30.00 | 0.60000000 | 20.0 | 2 | 0 |
|
| ETH/BTC | 2 | 15.00 | 30.00 | 0.60000000 | 20.0 | 2 | 0 |
|
||||||
| TOTAL | 2 | 15.00 | 30.00 | 0.60000000 | 20.0 | 2 | 0 |"""
|
| TOTAL | 2 | 15.00 | 30.00 | 0.60000000 | 20.0 | 2 | 0 |"""
|
||||||
)
|
)
|
||||||
|
#
|
||||||
|
# print()
|
||||||
|
# print(backtesting._generate_text_table(data={'ETH/BTC': {}}, results=results))
|
||||||
|
#
|
||||||
|
# print()
|
||||||
|
# print()
|
||||||
|
# print(result_str)
|
||||||
|
|
||||||
assert backtesting._generate_text_table(data={'ETH/BTC': {}}, results=results) == result_str
|
assert backtesting._generate_text_table(data={'ETH/BTC': {}}, results=results) == result_str
|
||||||
|
|
||||||
|
|
||||||
@ -469,6 +479,7 @@ def test_backtest(default_conf, fee, mocker) -> None:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
assert not results.empty
|
assert not results.empty
|
||||||
|
assert len(results) == 2
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_1min_ticker_interval(default_conf, fee, mocker) -> None:
|
def test_backtest_1min_ticker_interval(default_conf, fee, mocker) -> None:
|
||||||
@ -491,6 +502,7 @@ def test_backtest_1min_ticker_interval(default_conf, fee, mocker) -> None:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
assert not results.empty
|
assert not results.empty
|
||||||
|
assert len(results) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_processed(default_conf, mocker) -> None:
|
def test_processed(default_conf, mocker) -> None:
|
||||||
@ -512,7 +524,7 @@ def test_processed(default_conf, mocker) -> None:
|
|||||||
|
|
||||||
def test_backtest_pricecontours(default_conf, fee, mocker) -> None:
|
def test_backtest_pricecontours(default_conf, fee, mocker) -> None:
|
||||||
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
|
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
|
||||||
tests = [['raise', 17], ['lower', 0], ['sine', 16]]
|
tests = [['raise', 18], ['lower', 0], ['sine', 16]]
|
||||||
for [contour, numres] in tests:
|
for [contour, numres] in tests:
|
||||||
simple_backtest(default_conf, contour, numres, mocker)
|
simple_backtest(default_conf, contour, numres, mocker)
|
||||||
|
|
||||||
@ -572,7 +584,10 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker):
|
|||||||
backtesting.populate_buy_trend = _trend_alternate # Override
|
backtesting.populate_buy_trend = _trend_alternate # Override
|
||||||
backtesting.populate_sell_trend = _trend_alternate # Override
|
backtesting.populate_sell_trend = _trend_alternate # Override
|
||||||
results = backtesting.backtest(backtest_conf)
|
results = backtesting.backtest(backtest_conf)
|
||||||
assert len(results) == 3
|
backtesting._store_backtest_result("test_.json", results)
|
||||||
|
assert len(results) == 4
|
||||||
|
# One trade was force-closed at the end
|
||||||
|
assert len(results.loc[results.open_at_end]) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_backtest_record(default_conf, fee, mocker):
|
def test_backtest_record(default_conf, fee, mocker):
|
||||||
@ -584,22 +599,30 @@ def test_backtest_record(default_conf, fee, mocker):
|
|||||||
'freqtrade.optimize.backtesting.file_dump_json',
|
'freqtrade.optimize.backtesting.file_dump_json',
|
||||||
new=lambda n, r: (names.append(n), records.append(r))
|
new=lambda n, r: (names.append(n), records.append(r))
|
||||||
)
|
)
|
||||||
backtest_conf = _make_backtest_conf(
|
|
||||||
mocker,
|
|
||||||
conf=default_conf,
|
|
||||||
pair='UNITTEST/BTC',
|
|
||||||
record="trades"
|
|
||||||
)
|
|
||||||
backtesting = Backtesting(default_conf)
|
backtesting = Backtesting(default_conf)
|
||||||
backtesting.populate_buy_trend = _trend_alternate # Override
|
results = pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC",
|
||||||
backtesting.populate_sell_trend = _trend_alternate # Override
|
"UNITTEST/BTC", "UNITTEST/BTC"],
|
||||||
results = backtesting.backtest(backtest_conf)
|
"profit_percent": [0.003312, 0.010801, 0.013803, 0.002780],
|
||||||
assert len(results) == 3
|
"profit_abs": [0.000003, 0.000011, 0.000014, 0.000003],
|
||||||
|
"open_time": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
|
||||||
|
Arrow(2017, 11, 14, 21, 36, 00).datetime,
|
||||||
|
Arrow(2017, 11, 14, 22, 12, 00).datetime,
|
||||||
|
Arrow(2017, 11, 14, 22, 44, 00).datetime],
|
||||||
|
"close_time": [Arrow(2017, 11, 14, 21, 35, 00).datetime,
|
||||||
|
Arrow(2017, 11, 14, 22, 10, 00).datetime,
|
||||||
|
Arrow(2017, 11, 14, 22, 43, 00).datetime,
|
||||||
|
Arrow(2017, 11, 14, 22, 58, 00).datetime],
|
||||||
|
"open_index": [1, 119, 153, 185],
|
||||||
|
"close_index": [118, 151, 184, 199],
|
||||||
|
"trade_duration": [123, 34, 31, 14]})
|
||||||
|
backtesting._store_backtest_result("backtest-result.json", results)
|
||||||
|
assert len(results) == 4
|
||||||
# Assert file_dump_json was only called once
|
# Assert file_dump_json was only called once
|
||||||
assert names == ['backtest-result.json']
|
assert names == ['backtest-result.json']
|
||||||
records = records[0]
|
records = records[0]
|
||||||
# Ensure records are of correct type
|
# Ensure records are of correct type
|
||||||
assert len(records) == 3
|
assert len(records) == 4
|
||||||
# ('UNITTEST/BTC', 0.00331158, '1510684320', '1510691700', 0, 117)
|
# ('UNITTEST/BTC', 0.00331158, '1510684320', '1510691700', 0, 117)
|
||||||
# Below follows just a typecheck of the schema/type of trade-records
|
# Below follows just a typecheck of the schema/type of trade-records
|
||||||
oix = None
|
oix = None
|
||||||
|
@ -23,8 +23,6 @@ def init_hyperopt(default_conf, mocker):
|
|||||||
global _HYPEROPT_INITIALIZED, _HYPEROPT
|
global _HYPEROPT_INITIALIZED, _HYPEROPT
|
||||||
if not _HYPEROPT_INITIALIZED:
|
if not _HYPEROPT_INITIALIZED:
|
||||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf',
|
|
||||||
MagicMock(return_value=default_conf))
|
|
||||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock())
|
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock())
|
||||||
_HYPEROPT = Hyperopt(default_conf)
|
_HYPEROPT = Hyperopt(default_conf)
|
||||||
_HYPEROPT_INITIALIZED = True
|
_HYPEROPT_INITIALIZED = True
|
||||||
@ -63,9 +61,11 @@ def test_start(mocker, default_conf, caplog) -> None:
|
|||||||
Test start() function
|
Test start() function
|
||||||
"""
|
"""
|
||||||
start_mock = MagicMock()
|
start_mock = MagicMock()
|
||||||
|
mocker.patch(
|
||||||
|
'freqtrade.configuration.Configuration._load_config_file',
|
||||||
|
lambda *args, **kwargs: default_conf
|
||||||
|
)
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.start', start_mock)
|
mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.start', start_mock)
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf',
|
|
||||||
MagicMock(return_value=default_conf))
|
|
||||||
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
|
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
|
||||||
|
|
||||||
args = [
|
args = [
|
||||||
@ -123,6 +123,7 @@ def test_loss_calculation_has_limited_profit(init_hyperopt) -> None:
|
|||||||
assert under > correct
|
assert under > correct
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="no way of currently testing this")
|
||||||
def test_log_results_if_loss_improves(init_hyperopt, capsys) -> None:
|
def test_log_results_if_loss_improves(init_hyperopt, capsys) -> None:
|
||||||
hyperopt = _HYPEROPT
|
hyperopt = _HYPEROPT
|
||||||
hyperopt.current_best_loss = 2
|
hyperopt.current_best_loss = 2
|
||||||
@ -135,7 +136,9 @@ def test_log_results_if_loss_improves(init_hyperopt, capsys) -> None:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
out, err = capsys.readouterr()
|
out, err = capsys.readouterr()
|
||||||
assert ' 1/2: foo. Loss 1.00000'in out
|
with capsys.disabled():
|
||||||
|
print("out is: {}".format(out))
|
||||||
|
assert ' 1/2: foo. Loss 1.00000'in out
|
||||||
|
|
||||||
|
|
||||||
def test_no_log_if_loss_does_not_improve(init_hyperopt, caplog) -> None:
|
def test_no_log_if_loss_does_not_improve(init_hyperopt, caplog) -> None:
|
||||||
@ -182,7 +185,6 @@ def test_fmin_best_results(mocker, init_hyperopt, default_conf, caplog) -> None:
|
|||||||
|
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result)
|
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result)
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
|
|
||||||
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
|
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
|
||||||
|
|
||||||
StrategyResolver({'strategy': 'DefaultStrategy'})
|
StrategyResolver({'strategy': 'DefaultStrategy'})
|
||||||
@ -227,7 +229,6 @@ def test_fmin_throw_value_error(mocker, init_hyperopt, default_conf, caplog) ->
|
|||||||
conf.update({'epochs': 1})
|
conf.update({'epochs': 1})
|
||||||
conf.update({'timerange': None})
|
conf.update({'timerange': None})
|
||||||
conf.update({'spaces': 'all'})
|
conf.update({'spaces': 'all'})
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
|
|
||||||
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
|
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
|
||||||
|
|
||||||
StrategyResolver({'strategy': 'DefaultStrategy'})
|
StrategyResolver({'strategy': 'DefaultStrategy'})
|
||||||
@ -253,7 +254,6 @@ def test_resuming_previous_hyperopt_results_succeeds(mocker, init_hyperopt, defa
|
|||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf.update({'config': 'config.json.example'})
|
conf.update({'config': 'config.json.example'})
|
||||||
conf.update({'epochs': 1})
|
conf.update({'epochs': 1})
|
||||||
conf.update({'mongodb': False})
|
|
||||||
conf.update({'timerange': None})
|
conf.update({'timerange': None})
|
||||||
conf.update({'spaces': 'all'})
|
conf.update({'spaces': 'all'})
|
||||||
|
|
||||||
@ -270,7 +270,6 @@ def test_resuming_previous_hyperopt_results_succeeds(mocker, init_hyperopt, defa
|
|||||||
mocker.patch('freqtrade.optimize.hyperopt.sorted', return_value=trials.results)
|
mocker.patch('freqtrade.optimize.hyperopt.sorted', return_value=trials.results)
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
|
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
|
|
||||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock())
|
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock())
|
||||||
|
|
||||||
StrategyResolver({'strategy': 'DefaultStrategy'})
|
StrategyResolver({'strategy': 'DefaultStrategy'})
|
||||||
@ -348,7 +347,6 @@ def test_start_calls_fmin(mocker, init_hyperopt, default_conf) -> None:
|
|||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf.update({'config': 'config.json.example'})
|
conf.update({'config': 'config.json.example'})
|
||||||
conf.update({'epochs': 1})
|
conf.update({'epochs': 1})
|
||||||
conf.update({'mongodb': False})
|
|
||||||
conf.update({'timerange': None})
|
conf.update({'timerange': None})
|
||||||
conf.update({'spaces': 'all'})
|
conf.update({'spaces': 'all'})
|
||||||
|
|
||||||
@ -360,35 +358,6 @@ def test_start_calls_fmin(mocker, init_hyperopt, default_conf) -> None:
|
|||||||
mock_fmin.assert_called_once()
|
mock_fmin.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
def test_start_uses_mongotrials(mocker, init_hyperopt, default_conf) -> None:
|
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
|
||||||
mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
|
|
||||||
mock_mongotrials = mocker.patch(
|
|
||||||
'freqtrade.optimize.hyperopt.MongoTrials',
|
|
||||||
return_value=create_trials(mocker)
|
|
||||||
)
|
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
|
||||||
conf.update({'config': 'config.json.example'})
|
|
||||||
conf.update({'epochs': 1})
|
|
||||||
conf.update({'mongodb': True})
|
|
||||||
conf.update({'timerange': None})
|
|
||||||
conf.update({'spaces': 'all'})
|
|
||||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
|
|
||||||
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
|
|
||||||
|
|
||||||
hyperopt = Hyperopt(conf)
|
|
||||||
hyperopt.tickerdata_to_dataframe = MagicMock()
|
|
||||||
|
|
||||||
hyperopt.start()
|
|
||||||
mock_mongotrials.assert_called_once()
|
|
||||||
mock_fmin.assert_called_once()
|
|
||||||
|
|
||||||
|
|
||||||
# test log_trials_result
|
|
||||||
# test buy_strategy_generator def populate_buy_trend
|
|
||||||
# test optimizer if 'ro_t1' in params
|
|
||||||
|
|
||||||
def test_format_results(init_hyperopt):
|
def test_format_results(init_hyperopt):
|
||||||
"""
|
"""
|
||||||
Test Hyperopt.format_results()
|
Test Hyperopt.format_results()
|
||||||
@ -400,7 +369,7 @@ def test_format_results(init_hyperopt):
|
|||||||
('LTC/BTC', 1, 1, 123),
|
('LTC/BTC', 1, 1, 123),
|
||||||
('XPR/BTC', -1, -2, -246)
|
('XPR/BTC', -1, -2, -246)
|
||||||
]
|
]
|
||||||
labels = ['currency', 'profit_percent', 'profit_BTC', 'duration']
|
labels = ['currency', 'profit_percent', 'profit_abs', 'trade_duration']
|
||||||
df = pd.DataFrame.from_records(trades, columns=labels)
|
df = pd.DataFrame.from_records(trades, columns=labels)
|
||||||
|
|
||||||
result = _HYPEROPT.format_results(df)
|
result = _HYPEROPT.format_results(df)
|
||||||
@ -530,7 +499,7 @@ def test_generate_optimizer(mocker, init_hyperopt, default_conf) -> None:
|
|||||||
trades = [
|
trades = [
|
||||||
('POWR/BTC', 0.023117, 0.000233, 100)
|
('POWR/BTC', 0.023117, 0.000233, 100)
|
||||||
]
|
]
|
||||||
labels = ['currency', 'profit_percent', 'profit_BTC', 'duration']
|
labels = ['currency', 'profit_percent', 'profit_abs', 'trade_duration']
|
||||||
backtest_result = pd.DataFrame.from_records(trades, columns=labels)
|
backtest_result = pd.DataFrame.from_records(trades, columns=labels)
|
||||||
|
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
# pragma pylint: disable=missing-docstring,W0212
|
|
||||||
|
|
||||||
from user_data.hyperopt_conf import hyperopt_optimize_conf
|
|
||||||
|
|
||||||
|
|
||||||
def test_hyperopt_optimize_conf():
|
|
||||||
hyperopt_conf = hyperopt_optimize_conf()
|
|
||||||
|
|
||||||
assert "max_open_trades" in hyperopt_conf
|
|
||||||
assert "stake_currency" in hyperopt_conf
|
|
||||||
assert "stake_amount" in hyperopt_conf
|
|
||||||
assert "minimal_roi" in hyperopt_conf
|
|
||||||
assert "stoploss" in hyperopt_conf
|
|
||||||
assert "bid_strategy" in hyperopt_conf
|
|
||||||
assert "exchange" in hyperopt_conf
|
|
||||||
assert "pair_whitelist" in hyperopt_conf['exchange']
|
|
@ -326,8 +326,6 @@ def test_load_tickerdata_file() -> None:
|
|||||||
|
|
||||||
|
|
||||||
def test_init(default_conf, mocker) -> None:
|
def test_init(default_conf, mocker) -> None:
|
||||||
conf = {'exchange': {'pair_whitelist': []}}
|
|
||||||
mocker.patch('freqtrade.optimize.hyperopt_optimize_conf', return_value=conf)
|
|
||||||
assert {} == optimize.load_data(
|
assert {} == optimize.load_data(
|
||||||
'',
|
'',
|
||||||
pairs=[],
|
pairs=[],
|
||||||
|
@ -13,6 +13,7 @@ from jsonschema import ValidationError
|
|||||||
|
|
||||||
from freqtrade.arguments import Arguments
|
from freqtrade.arguments import Arguments
|
||||||
from freqtrade.configuration import Configuration
|
from freqtrade.configuration import Configuration
|
||||||
|
from freqtrade.constants import DEFAULT_DB_PROD_URL, DEFAULT_DB_DRYRUN_URL
|
||||||
from freqtrade.tests.conftest import log_has
|
from freqtrade.tests.conftest import log_has
|
||||||
from freqtrade import OperationalException
|
from freqtrade import OperationalException
|
||||||
|
|
||||||
@ -140,6 +141,43 @@ def test_load_config_with_params(default_conf, mocker) -> None:
|
|||||||
assert validated_conf.get('strategy_path') == '/some/path'
|
assert validated_conf.get('strategy_path') == '/some/path'
|
||||||
assert validated_conf.get('db_url') == 'sqlite:///someurl'
|
assert validated_conf.get('db_url') == 'sqlite:///someurl'
|
||||||
|
|
||||||
|
conf = default_conf.copy()
|
||||||
|
conf["dry_run"] = False
|
||||||
|
del conf["db_url"]
|
||||||
|
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||||
|
read_data=json.dumps(conf)
|
||||||
|
))
|
||||||
|
|
||||||
|
arglist = [
|
||||||
|
'--dynamic-whitelist', '10',
|
||||||
|
'--strategy', 'TestStrategy',
|
||||||
|
'--strategy-path', '/some/path'
|
||||||
|
]
|
||||||
|
args = Arguments(arglist, '').get_parsed_arg()
|
||||||
|
|
||||||
|
configuration = Configuration(args)
|
||||||
|
validated_conf = configuration.load_config()
|
||||||
|
assert validated_conf.get('db_url') == DEFAULT_DB_PROD_URL
|
||||||
|
|
||||||
|
# Test dry=run with ProdURL
|
||||||
|
conf = default_conf.copy()
|
||||||
|
conf["dry_run"] = True
|
||||||
|
conf["db_url"] = DEFAULT_DB_PROD_URL
|
||||||
|
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||||
|
read_data=json.dumps(conf)
|
||||||
|
))
|
||||||
|
|
||||||
|
arglist = [
|
||||||
|
'--dynamic-whitelist', '10',
|
||||||
|
'--strategy', 'TestStrategy',
|
||||||
|
'--strategy-path', '/some/path'
|
||||||
|
]
|
||||||
|
args = Arguments(arglist, '').get_parsed_arg()
|
||||||
|
|
||||||
|
configuration = Configuration(args)
|
||||||
|
validated_conf = configuration.load_config()
|
||||||
|
assert validated_conf.get('db_url') == DEFAULT_DB_DRYRUN_URL
|
||||||
|
|
||||||
|
|
||||||
def test_load_custom_strategy(default_conf, mocker) -> None:
|
def test_load_custom_strategy(default_conf, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
@ -310,7 +348,6 @@ def test_hyperopt_with_arguments(mocker, default_conf, caplog) -> None:
|
|||||||
arglist = [
|
arglist = [
|
||||||
'hyperopt',
|
'hyperopt',
|
||||||
'--epochs', '10',
|
'--epochs', '10',
|
||||||
'--use-mongodb',
|
|
||||||
'--spaces', 'all',
|
'--spaces', 'all',
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -324,10 +361,6 @@ def test_hyperopt_with_arguments(mocker, default_conf, caplog) -> None:
|
|||||||
assert log_has('Parameter --epochs detected ...', caplog.record_tuples)
|
assert log_has('Parameter --epochs detected ...', caplog.record_tuples)
|
||||||
assert log_has('Will run Hyperopt with for 10 epochs ...', caplog.record_tuples)
|
assert log_has('Will run Hyperopt with for 10 epochs ...', caplog.record_tuples)
|
||||||
|
|
||||||
assert 'mongodb' in config
|
|
||||||
assert config['mongodb'] is True
|
|
||||||
assert log_has('Parameter --use-mongodb detected ...', caplog.record_tuples)
|
|
||||||
|
|
||||||
assert 'spaces' in config
|
assert 'spaces' in config
|
||||||
assert config['spaces'] == ['all']
|
assert config['spaces'] == ['all']
|
||||||
assert log_has('Parameter -s/--spaces detected: [\'all\']', caplog.record_tuples)
|
assert log_has('Parameter -s/--spaces detected: [\'all\']', caplog.record_tuples)
|
||||||
|
@ -40,7 +40,8 @@ def test_pair_convertion_object():
|
|||||||
assert pair_convertion.price == 30000.123
|
assert pair_convertion.price == 30000.123
|
||||||
|
|
||||||
|
|
||||||
def test_fiat_convert_is_supported():
|
def test_fiat_convert_is_supported(mocker):
|
||||||
|
patch_coinmarketcap(mocker)
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
assert fiat_convert._is_supported_fiat(fiat='USD') is True
|
assert fiat_convert._is_supported_fiat(fiat='USD') is True
|
||||||
assert fiat_convert._is_supported_fiat(fiat='usd') is True
|
assert fiat_convert._is_supported_fiat(fiat='usd') is True
|
||||||
@ -48,7 +49,9 @@ def test_fiat_convert_is_supported():
|
|||||||
assert fiat_convert._is_supported_fiat(fiat='ABC') is False
|
assert fiat_convert._is_supported_fiat(fiat='ABC') is False
|
||||||
|
|
||||||
|
|
||||||
def test_fiat_convert_add_pair():
|
def test_fiat_convert_add_pair(mocker):
|
||||||
|
patch_coinmarketcap(mocker)
|
||||||
|
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
|
|
||||||
pair_len = len(fiat_convert._pairs)
|
pair_len = len(fiat_convert._pairs)
|
||||||
@ -70,11 +73,8 @@ def test_fiat_convert_add_pair():
|
|||||||
|
|
||||||
|
|
||||||
def test_fiat_convert_find_price(mocker):
|
def test_fiat_convert_find_price(mocker):
|
||||||
api_mock = MagicMock(return_value={
|
patch_coinmarketcap(mocker)
|
||||||
'price_usd': 12345.0,
|
|
||||||
'price_eur': 13000.2
|
|
||||||
})
|
|
||||||
mocker.patch('freqtrade.fiat_convert.Market.ticker', api_mock)
|
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
|
|
||||||
with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'):
|
with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'):
|
||||||
@ -92,17 +92,15 @@ def test_fiat_convert_find_price(mocker):
|
|||||||
|
|
||||||
def test_fiat_convert_unsupported_crypto(mocker, caplog):
|
def test_fiat_convert_unsupported_crypto(mocker, caplog):
|
||||||
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[])
|
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[])
|
||||||
|
patch_coinmarketcap(mocker)
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0
|
assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0
|
||||||
assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog.record_tuples)
|
assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog.record_tuples)
|
||||||
|
|
||||||
|
|
||||||
def test_fiat_convert_get_price(mocker):
|
def test_fiat_convert_get_price(mocker):
|
||||||
api_mock = MagicMock(return_value={
|
patch_coinmarketcap(mocker)
|
||||||
'price_usd': 28000.0,
|
|
||||||
'price_eur': 15000.0
|
|
||||||
})
|
|
||||||
mocker.patch('freqtrade.fiat_convert.Market.ticker', api_mock)
|
|
||||||
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=28000.0)
|
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=28000.0)
|
||||||
|
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
@ -172,8 +170,9 @@ def test_fiat_init_network_exception(mocker):
|
|||||||
assert length_cryptomap == 0
|
assert length_cryptomap == 0
|
||||||
|
|
||||||
|
|
||||||
def test_fiat_convert_without_network():
|
def test_fiat_convert_without_network(mocker):
|
||||||
# Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap
|
# Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap
|
||||||
|
patch_coinmarketcap(mocker)
|
||||||
|
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
|
|
||||||
@ -186,6 +185,7 @@ def test_fiat_convert_without_network():
|
|||||||
|
|
||||||
|
|
||||||
def test_convert_amount(mocker):
|
def test_convert_amount(mocker):
|
||||||
|
patch_coinmarketcap(mocker)
|
||||||
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter.get_price', return_value=12345.0)
|
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter.get_price', return_value=12345.0)
|
||||||
|
|
||||||
fiat_convert = CryptoToFiatConverter()
|
fiat_convert = CryptoToFiatConverter()
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
ccxt==1.14.186
|
ccxt==1.14.224
|
||||||
SQLAlchemy==1.2.8
|
SQLAlchemy==1.2.8
|
||||||
python-telegram-bot==10.1.0
|
python-telegram-bot==10.1.0
|
||||||
arrow==0.12.1
|
arrow==0.12.1
|
||||||
cachetools==2.1.0
|
cachetools==2.1.0
|
||||||
requests==2.19.0
|
requests==2.19.1
|
||||||
urllib3==1.22
|
urllib3==1.22
|
||||||
wrapt==1.10.11
|
wrapt==1.10.11
|
||||||
pandas==0.23.1
|
pandas==0.23.1
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import multiprocessing
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
PROC_COUNT = multiprocessing.cpu_count() - 1
|
|
||||||
DB_NAME = 'freqtrade_hyperopt'
|
|
||||||
WORK_DIR = os.path.join(
|
|
||||||
os.path.sep,
|
|
||||||
os.path.abspath(os.path.dirname(__file__)),
|
|
||||||
'..', '.hyperopt', 'worker'
|
|
||||||
)
|
|
||||||
if not os.path.exists(WORK_DIR):
|
|
||||||
os.makedirs(WORK_DIR)
|
|
||||||
|
|
||||||
# Spawn workers
|
|
||||||
command = [
|
|
||||||
'hyperopt-mongo-worker',
|
|
||||||
'--mongo=127.0.0.1:1234/{}'.format(DB_NAME),
|
|
||||||
'--poll-interval=0.1',
|
|
||||||
'--workdir={}'.format(WORK_DIR),
|
|
||||||
]
|
|
||||||
processes = [subprocess.Popen(command) for i in range(PROC_COUNT)]
|
|
||||||
|
|
||||||
# Join all workers
|
|
||||||
for proc in processes:
|
|
||||||
proc.wait()
|
|
@ -1,21 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
|
|
||||||
DB_PATH = os.path.join(
|
|
||||||
os.path.sep,
|
|
||||||
os.path.abspath(os.path.dirname(__file__)),
|
|
||||||
'..', '.hyperopt', 'mongodb'
|
|
||||||
)
|
|
||||||
if not os.path.exists(DB_PATH):
|
|
||||||
os.makedirs(DB_PATH)
|
|
||||||
|
|
||||||
subprocess.Popen([
|
|
||||||
'mongod',
|
|
||||||
'--bind_ip=127.0.0.1',
|
|
||||||
'--port=1234',
|
|
||||||
'--nohttpinterface',
|
|
||||||
'--dbpath={}'.format(DB_PATH),
|
|
||||||
]).wait()
|
|
@ -1,42 +0,0 @@
|
|||||||
"""
|
|
||||||
File that contains the configuration for Hyperopt
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def hyperopt_optimize_conf() -> dict:
|
|
||||||
"""
|
|
||||||
This function is used to define which parameters Hyperopt must used.
|
|
||||||
The "pair_whitelist" is only used is your are using Hyperopt with MongoDB,
|
|
||||||
without MongoDB, Hyperopt will use the pair your have set in your config file.
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
'max_open_trades': 3,
|
|
||||||
'stake_currency': 'BTC',
|
|
||||||
'stake_amount': 0.01,
|
|
||||||
"minimal_roi": {
|
|
||||||
'40': 0.0,
|
|
||||||
'30': 0.01,
|
|
||||||
'20': 0.02,
|
|
||||||
'0': 0.04,
|
|
||||||
},
|
|
||||||
'stoploss': -0.10,
|
|
||||||
"bid_strategy": {
|
|
||||||
"ask_last_balance": 0.0
|
|
||||||
},
|
|
||||||
"exchange": {
|
|
||||||
"name": "bittrex",
|
|
||||||
"pair_whitelist": [
|
|
||||||
"ETH/BTC",
|
|
||||||
"LTC/BTC",
|
|
||||||
"ETC/BTC",
|
|
||||||
"DASH/BTC",
|
|
||||||
"ZEC/BTC",
|
|
||||||
"XLM/BTC",
|
|
||||||
"NXT/BTC",
|
|
||||||
"POWR/BTC",
|
|
||||||
"ADA/BTC",
|
|
||||||
"XMR/BTC"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user