This commit is contained in:
Gert Wohlgemuth 2018-06-19 09:56:04 -07:00
commit c2e83e97c3
20 changed files with 312 additions and 397 deletions

View File

@ -3,3 +3,4 @@ omit =
scripts/* scripts/*
freqtrade/tests/* freqtrade/tests/*
freqtrade/vendor/* freqtrade/vendor/*
freqtrade/__main__.py

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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: if trade_entry:
row2, trade_entry, next_date = ret lock_pair_until = trade_entry.close_time
lock_pair_until = next_date
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

View File

@ -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,18 +504,6 @@ 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('Using mongodb ...')
logger.info(
'Start scripts/start-mongodb.sh and start-hyperopt-worker.sh manually!'
)
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..') logger.info('Preparing Trials..')
signal.signal(signal.SIGINT, self.signal_handler) signal.signal(signal.SIGINT, self.signal_handler)
# read trials file if we have one # read trials file if we have one
@ -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'] = ''

View File

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

View File

@ -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,6 +136,8 @@ def test_log_results_if_loss_improves(init_hyperopt, capsys) -> None:
} }
) )
out, err = capsys.readouterr() out, err = capsys.readouterr()
with capsys.disabled():
print("out is: {}".format(out))
assert ' 1/2: foo. Loss 1.00000'in out assert ' 1/2: foo. Loss 1.00000'in out
@ -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(

View File

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

View File

@ -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=[],

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"
]
}
}