mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-10 10:21:59 +00:00
Merge branch 'develop' into sortino_hyperopt_loss
This commit is contained in:
commit
3fb6818bd8
|
@ -48,7 +48,7 @@ pytest tests/test_<file_name>.py::test_<method_name>
|
||||||
#### Run Flake8
|
#### Run Flake8
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
flake8 freqtrade
|
flake8 freqtrade tests scripts
|
||||||
```
|
```
|
||||||
|
|
||||||
We receive a lot of code that fails the `flake8` checks.
|
We receive a lot of code that fails the `flake8` checks.
|
||||||
|
@ -109,11 +109,11 @@ Exceptions:
|
||||||
|
|
||||||
Contributors may be given commit privileges. Preference will be given to those with:
|
Contributors may be given commit privileges. Preference will be given to those with:
|
||||||
|
|
||||||
1. Past contributions to FreqTrade and other related open-source projects. Contributions to FreqTrade include both code (both accepted and pending) and friendly participation in the issue tracker and Pull request reviews. Quantity and quality are considered.
|
1. Past contributions to Freqtrade and other related open-source projects. Contributions to Freqtrade include both code (both accepted and pending) and friendly participation in the issue tracker and Pull request reviews. Quantity and quality are considered.
|
||||||
1. A coding style that the other core committers find simple, minimal, and clean.
|
1. A coding style that the other core committers find simple, minimal, and clean.
|
||||||
1. Access to resources for cross-platform development and testing.
|
1. Access to resources for cross-platform development and testing.
|
||||||
1. Time to devote to the project regularly.
|
1. Time to devote to the project regularly.
|
||||||
|
|
||||||
Being a Committer does not grant write permission on `develop` or `master` for security reasons (Users trust FreqTrade with their Exchange API keys).
|
Being a Committer does not grant write permission on `develop` or `master` for security reasons (Users trust Freqtrade with their Exchange API keys).
|
||||||
|
|
||||||
After being Committer for some time, a Committer may be named Core Committer and given full repository access.
|
After being Committer for some time, a Committer may be named Core Committer and given full repository access.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Freqtrade
|
# Freqtrade
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/freqtrade/freqtrade.svg?branch=develop)](https://travis-ci.org/freqtrade/freqtrade)
|
[![Freqtrade CI](https://github.com/freqtrade/freqtrade/workflows/Freqtrade%20CI/badge.svg)](https://github.com/freqtrade/freqtrade/actions/)
|
||||||
[![Coverage Status](https://coveralls.io/repos/github/freqtrade/freqtrade/badge.svg?branch=develop&service=github)](https://coveralls.io/github/freqtrade/freqtrade?branch=develop)
|
[![Coverage Status](https://coveralls.io/repos/github/freqtrade/freqtrade/badge.svg?branch=develop&service=github)](https://coveralls.io/github/freqtrade/freqtrade?branch=develop)
|
||||||
[![Documentation](https://readthedocs.org/projects/freqtrade/badge/)](https://www.freqtrade.io)
|
[![Documentation](https://readthedocs.org/projects/freqtrade/badge/)](https://www.freqtrade.io)
|
||||||
[![Maintainability](https://api.codeclimate.com/v1/badges/5737e6d668200b7518ff/maintainability)](https://codeclimate.com/github/freqtrade/freqtrade/maintainability)
|
[![Maintainability](https://api.codeclimate.com/v1/badges/5737e6d668200b7518ff/maintainability)](https://codeclimate.com/github/freqtrade/freqtrade/maintainability)
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import logging
|
||||||
|
|
||||||
from freqtrade.main import main
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
warnings.warn(
|
|
||||||
"Deprecated - To continue to run the bot like this, please run `pip install -e .` again.",
|
logger.error("DEPRECATED installation detected, please run `pip install -e .` again.")
|
||||||
DeprecationWarning)
|
|
||||||
main(sys.argv[1:])
|
sys.exit(2)
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"stake_amount": 0.05,
|
"stake_amount": 0.05,
|
||||||
"tradable_balance_ratio": 0.99,
|
"tradable_balance_ratio": 0.99,
|
||||||
"fiat_display_currency": "USD",
|
"fiat_display_currency": "USD",
|
||||||
"ticker_interval" : "5m",
|
"ticker_interval": "5m",
|
||||||
"dry_run": false,
|
"dry_run": false,
|
||||||
"trailing_stop": false,
|
"trailing_stop": false,
|
||||||
"unfilledtimeout": {
|
"unfilledtimeout": {
|
||||||
|
@ -44,7 +44,7 @@
|
||||||
"DASH/BTC",
|
"DASH/BTC",
|
||||||
"ZEC/BTC",
|
"ZEC/BTC",
|
||||||
"XLM/BTC",
|
"XLM/BTC",
|
||||||
"NXT/BTC",
|
"XRP/BTC",
|
||||||
"TRX/BTC",
|
"TRX/BTC",
|
||||||
"ADA/BTC",
|
"ADA/BTC",
|
||||||
"XMR/BTC"
|
"XMR/BTC"
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"stake_amount": 0.05,
|
"stake_amount": 0.05,
|
||||||
"tradable_balance_ratio": 0.99,
|
"tradable_balance_ratio": 0.99,
|
||||||
"fiat_display_currency": "USD",
|
"fiat_display_currency": "USD",
|
||||||
"ticker_interval" : "5m",
|
"ticker_interval": "5m",
|
||||||
"dry_run": true,
|
"dry_run": true,
|
||||||
"trailing_stop": false,
|
"trailing_stop": false,
|
||||||
"unfilledtimeout": {
|
"unfilledtimeout": {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"stake_amount": 0.05,
|
"stake_amount": 0.05,
|
||||||
"tradable_balance_ratio": 0.99,
|
"tradable_balance_ratio": 0.99,
|
||||||
"fiat_display_currency": "USD",
|
"fiat_display_currency": "USD",
|
||||||
"amount_reserve_percent" : 0.05,
|
"amount_reserve_percent": 0.05,
|
||||||
"amend_last_stake_amount": false,
|
"amend_last_stake_amount": false,
|
||||||
"last_stake_amount_min_ratio": 0.5,
|
"last_stake_amount_min_ratio": 0.5,
|
||||||
"dry_run": false,
|
"dry_run": false,
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"stake_amount": 10,
|
"stake_amount": 10,
|
||||||
"tradable_balance_ratio": 0.99,
|
"tradable_balance_ratio": 0.99,
|
||||||
"fiat_display_currency": "EUR",
|
"fiat_display_currency": "EUR",
|
||||||
"ticker_interval" : "5m",
|
"ticker_interval": "5m",
|
||||||
"dry_run": true,
|
"dry_run": true,
|
||||||
"trailing_stop": false,
|
"trailing_stop": false,
|
||||||
"unfilledtimeout": {
|
"unfilledtimeout": {
|
||||||
|
|
|
@ -3,6 +3,18 @@ version: '3'
|
||||||
services:
|
services:
|
||||||
freqtrade:
|
freqtrade:
|
||||||
image: freqtradeorg/freqtrade:master
|
image: freqtradeorg/freqtrade:master
|
||||||
|
# Build step - only needed when additional dependencies are needed
|
||||||
|
# build:
|
||||||
|
# context: .
|
||||||
|
# dockerfile: "./Dockerfile.technical"
|
||||||
|
restart: unless-stopped
|
||||||
|
container_name: freqtrade
|
||||||
volumes:
|
volumes:
|
||||||
- "./user_data:/freqtrade/user_data"
|
- "./user_data:/freqtrade/user_data"
|
||||||
- "./config.json:/freqtrade/config.json"
|
# Default command used when running `docker compose up`
|
||||||
|
command: >
|
||||||
|
trade
|
||||||
|
--logfile /freqtrade/user_data/freqtrade.log
|
||||||
|
--db-url sqlite:////freqtrade/user_data/tradesv3.sqlite
|
||||||
|
--config /freqtrade/user_data/config.json
|
||||||
|
--strategy SampleStrategy
|
||||||
|
|
|
@ -4,6 +4,34 @@ This page explains some advanced Hyperopt topics that may require higher
|
||||||
coding skills and Python knowledge than creation of an ordinal hyperoptimization
|
coding skills and Python knowledge than creation of an ordinal hyperoptimization
|
||||||
class.
|
class.
|
||||||
|
|
||||||
|
## Derived hyperopt classes
|
||||||
|
|
||||||
|
Custom hyperop classes can be derived in the same way [it can be done for strategies](strategy-customization.md#derived-strategies).
|
||||||
|
|
||||||
|
Applying to hyperoptimization, as an example, you may override how dimensions are defined in your optimization hyperspace:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class MyAwesomeHyperOpt(IHyperOpt):
|
||||||
|
...
|
||||||
|
# Uses default stoploss dimension
|
||||||
|
|
||||||
|
class MyAwesomeHyperOpt2(MyAwesomeHyperOpt):
|
||||||
|
@staticmethod
|
||||||
|
def stoploss_space() -> List[Dimension]:
|
||||||
|
# Override boundaries for stoploss
|
||||||
|
return [
|
||||||
|
Real(-0.33, -0.01, name='stoploss'),
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
and then quickly switch between hyperopt classes, running optimization process with hyperopt class you need in each particular case:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ freqtrade hyperopt --hyperopt MyAwesomeHyperOpt ...
|
||||||
|
or
|
||||||
|
$ freqtrade hyperopt --hyperopt MyAwesomeHyperOpt2 ...
|
||||||
|
```
|
||||||
|
|
||||||
## Creating and using a custom loss function
|
## Creating and using a custom loss function
|
||||||
|
|
||||||
To use a custom loss function class, make sure that the function `hyperopt_loss_function` is defined in your custom hyperopt loss class.
|
To use a custom loss function class, make sure that the function `hyperopt_loss_function` is defined in your custom hyperopt loss class.
|
||||||
|
|
|
@ -119,40 +119,40 @@ A backtesting result will look like that:
|
||||||
|
|
||||||
```
|
```
|
||||||
========================================================= BACKTESTING REPORT ========================================================
|
========================================================= BACKTESTING REPORT ========================================================
|
||||||
| pair | buy count | avg profit % | cum profit % | tot profit BTC | tot profit % | avg duration | profit | loss |
|
| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses |
|
||||||
|:---------|------------:|---------------:|---------------:|-----------------:|---------------:|:---------------|---------:|-------:|
|
|:---------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|--------:|
|
||||||
| ADA/BTC | 35 | -0.11 | -3.88 | -0.00019428 | -1.94 | 4:35:00 | 14 | 21 |
|
| ADA/BTC | 35 | -0.11 | -3.88 | -0.00019428 | -1.94 | 4:35:00 | 14 | 0 | 21 |
|
||||||
| ARK/BTC | 11 | -0.41 | -4.52 | -0.00022647 | -2.26 | 2:03:00 | 3 | 8 |
|
| ARK/BTC | 11 | -0.41 | -4.52 | -0.00022647 | -2.26 | 2:03:00 | 3 | 0 | 8 |
|
||||||
| BTS/BTC | 32 | 0.31 | 9.78 | 0.00048938 | 4.89 | 5:05:00 | 18 | 14 |
|
| BTS/BTC | 32 | 0.31 | 9.78 | 0.00048938 | 4.89 | 5:05:00 | 18 | 0 | 14 |
|
||||||
| DASH/BTC | 13 | -0.08 | -1.07 | -0.00005343 | -0.53 | 4:39:00 | 6 | 7 |
|
| DASH/BTC | 13 | -0.08 | -1.07 | -0.00005343 | -0.53 | 4:39:00 | 6 | 0 | 7 |
|
||||||
| ENG/BTC | 18 | 1.36 | 24.54 | 0.00122807 | 12.27 | 2:50:00 | 8 | 10 |
|
| ENG/BTC | 18 | 1.36 | 24.54 | 0.00122807 | 12.27 | 2:50:00 | 8 | 0 | 10 |
|
||||||
| EOS/BTC | 36 | 0.08 | 3.06 | 0.00015304 | 1.53 | 3:34:00 | 16 | 20 |
|
| EOS/BTC | 36 | 0.08 | 3.06 | 0.00015304 | 1.53 | 3:34:00 | 16 | 0 | 20 |
|
||||||
| ETC/BTC | 26 | 0.37 | 9.51 | 0.00047576 | 4.75 | 6:14:00 | 11 | 15 |
|
| ETC/BTC | 26 | 0.37 | 9.51 | 0.00047576 | 4.75 | 6:14:00 | 11 | 0 | 15 |
|
||||||
| ETH/BTC | 33 | 0.30 | 9.96 | 0.00049856 | 4.98 | 7:31:00 | 16 | 17 |
|
| ETH/BTC | 33 | 0.30 | 9.96 | 0.00049856 | 4.98 | 7:31:00 | 16 | 0 | 17 |
|
||||||
| IOTA/BTC | 32 | 0.03 | 1.09 | 0.00005444 | 0.54 | 3:12:00 | 14 | 18 |
|
| IOTA/BTC | 32 | 0.03 | 1.09 | 0.00005444 | 0.54 | 3:12:00 | 14 | 0 | 18 |
|
||||||
| LSK/BTC | 15 | 1.75 | 26.26 | 0.00131413 | 13.13 | 2:58:00 | 6 | 9 |
|
| LSK/BTC | 15 | 1.75 | 26.26 | 0.00131413 | 13.13 | 2:58:00 | 6 | 0 | 9 |
|
||||||
| LTC/BTC | 32 | -0.04 | -1.38 | -0.00006886 | -0.69 | 4:49:00 | 11 | 21 |
|
| LTC/BTC | 32 | -0.04 | -1.38 | -0.00006886 | -0.69 | 4:49:00 | 11 | 0 | 21 |
|
||||||
| NANO/BTC | 17 | 1.26 | 21.39 | 0.00107058 | 10.70 | 1:55:00 | 10 | 7 |
|
| NANO/BTC | 17 | 1.26 | 21.39 | 0.00107058 | 10.70 | 1:55:00 | 10 | 0 | 7 |
|
||||||
| NEO/BTC | 23 | 0.82 | 18.97 | 0.00094936 | 9.48 | 2:59:00 | 10 | 13 |
|
| NEO/BTC | 23 | 0.82 | 18.97 | 0.00094936 | 9.48 | 2:59:00 | 10 | 0 | 13 |
|
||||||
| REQ/BTC | 9 | 1.17 | 10.54 | 0.00052734 | 5.27 | 3:47:00 | 4 | 5 |
|
| REQ/BTC | 9 | 1.17 | 10.54 | 0.00052734 | 5.27 | 3:47:00 | 4 | 0 | 5 |
|
||||||
| XLM/BTC | 16 | 1.22 | 19.54 | 0.00097800 | 9.77 | 3:15:00 | 7 | 9 |
|
| XLM/BTC | 16 | 1.22 | 19.54 | 0.00097800 | 9.77 | 3:15:00 | 7 | 0 | 9 |
|
||||||
| XMR/BTC | 23 | -0.18 | -4.13 | -0.00020696 | -2.07 | 5:30:00 | 12 | 11 |
|
| XMR/BTC | 23 | -0.18 | -4.13 | -0.00020696 | -2.07 | 5:30:00 | 12 | 0 | 11 |
|
||||||
| XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 | 23 |
|
| XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 | 0 | 23 |
|
||||||
| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 | 15 |
|
| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 | 0 | 15 |
|
||||||
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 243 |
|
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 0 | 243 |
|
||||||
========================================================= SELL REASON STATS =========================================================
|
========================================================= SELL REASON STATS =========================================================
|
||||||
| Sell Reason | Count | Profit | Loss |
|
| Sell Reason | Sells | Wins | Draws | Losses |
|
||||||
|:-------------------|--------:|---------:|-------:|
|
|:-------------------|--------:|------:|-------:|--------:|
|
||||||
| trailing_stop_loss | 205 | 150 | 55 |
|
| trailing_stop_loss | 205 | 150 | 0 | 55 |
|
||||||
| stop_loss | 166 | 0 | 166 |
|
| stop_loss | 166 | 0 | 0 | 166 |
|
||||||
| sell_signal | 56 | 36 | 20 |
|
| sell_signal | 56 | 36 | 0 | 20 |
|
||||||
| force_sell | 2 | 0 | 2 |
|
| force_sell | 2 | 0 | 0 | 2 |
|
||||||
====================================================== LEFT OPEN TRADES REPORT ======================================================
|
====================================================== LEFT OPEN TRADES REPORT ======================================================
|
||||||
| pair | buy count | avg profit % | cum profit % | tot profit BTC | tot profit % | avg duration | profit | loss |
|
| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses |
|
||||||
|:---------|------------:|---------------:|---------------:|-----------------:|---------------:|:---------------|---------:|-------:|
|
|:---------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|--------:|
|
||||||
| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 | 0 |
|
| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 | 0 | 0 |
|
||||||
| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 | 0 |
|
| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 | 0 | 0 |
|
||||||
| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 | 0 |
|
| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 | 0 | 0 |
|
||||||
```
|
```
|
||||||
|
|
||||||
The 1st table contains all trades the bot made, including "left open trades".
|
The 1st table contains all trades the bot made, including "left open trades".
|
||||||
|
@ -238,10 +238,10 @@ Detailed output for all strategies one after the other will be available, so mak
|
||||||
|
|
||||||
```
|
```
|
||||||
=========================================================== STRATEGY SUMMARY ===========================================================
|
=========================================================== STRATEGY SUMMARY ===========================================================
|
||||||
| Strategy | Buy Count | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Losses |
|
| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses |
|
||||||
|:------------|------------:|---------------:|---------------:|-----------------:|---------------:|:---------------|---------:|-------:|
|
|:------------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|-------:|
|
||||||
| Strategy1 | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 243 |
|
| Strategy1 | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 0 | 243 |
|
||||||
| Strategy2 | 1487 | -0.13 | -197.58 | -0.00988917 | -98.79 | 4:43:00 | 662 | 825 |
|
| Strategy2 | 1487 | -0.13 | -197.58 | -0.00988917 | -98.79 | 4:43:00 | 662 | 0 | 825 |
|
||||||
```
|
```
|
||||||
|
|
||||||
## Next step
|
## Next step
|
||||||
|
|
|
@ -58,9 +58,10 @@ Common arguments:
|
||||||
details.
|
details.
|
||||||
-V, --version show program's version number and exit
|
-V, --version show program's version number and exit
|
||||||
-c PATH, --config PATH
|
-c PATH, --config PATH
|
||||||
Specify configuration file (default: `config.json`).
|
Specify configuration file (default:
|
||||||
Multiple --config options may be used. Can be set to
|
`userdir/config.json` or `config.json` whichever
|
||||||
`-` to read config from stdin.
|
exists). Multiple --config options may be used. Can be
|
||||||
|
set to `-` to read config from stdin.
|
||||||
-d PATH, --datadir PATH
|
-d PATH, --datadir PATH
|
||||||
Path to directory with historical backtesting data.
|
Path to directory with historical backtesting data.
|
||||||
--userdir PATH, --user-data-dir PATH
|
--userdir PATH, --user-data-dir PATH
|
||||||
|
@ -71,6 +72,7 @@ Strategy arguments:
|
||||||
Specify strategy class name which will be used by the
|
Specify strategy class name which will be used by the
|
||||||
bot.
|
bot.
|
||||||
--strategy-path PATH Specify additional strategy lookup path.
|
--strategy-path PATH Specify additional strategy lookup path.
|
||||||
|
.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -242,12 +244,15 @@ optional arguments:
|
||||||
|
|
||||||
Common arguments:
|
Common arguments:
|
||||||
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
--logfile FILE Log to the file specified.
|
--logfile FILE Log to the file specified. Special values are:
|
||||||
|
'syslog', 'journald'. See the documentation for more
|
||||||
|
details.
|
||||||
-V, --version show program's version number and exit
|
-V, --version show program's version number and exit
|
||||||
-c PATH, --config PATH
|
-c PATH, --config PATH
|
||||||
Specify configuration file (default: `config.json`).
|
Specify configuration file (default:
|
||||||
Multiple --config options may be used. Can be set to
|
`userdir/config.json` or `config.json` whichever
|
||||||
`-` to read config from stdin.
|
exists). Multiple --config options may be used. Can be
|
||||||
|
set to `-` to read config from stdin.
|
||||||
-d PATH, --datadir PATH
|
-d PATH, --datadir PATH
|
||||||
Path to directory with historical backtesting data.
|
Path to directory with historical backtesting data.
|
||||||
--userdir PATH, --user-data-dir PATH
|
--userdir PATH, --user-data-dir PATH
|
||||||
|
@ -280,7 +285,7 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
|
||||||
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
|
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
|
||||||
[--hyperopt NAME] [--hyperopt-path PATH] [--eps]
|
[--hyperopt NAME] [--hyperopt-path PATH] [--eps]
|
||||||
[-e INT]
|
[-e INT]
|
||||||
[--spaces {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]]
|
[--spaces {all,buy,sell,roi,stoploss,trailing,default} [{all,buy,sell,roi,stoploss,trailing,default} ...]]
|
||||||
[--dmmp] [--print-all] [--no-color] [--print-json]
|
[--dmmp] [--print-all] [--no-color] [--print-json]
|
||||||
[-j JOBS] [--random-state INT] [--min-trades INT]
|
[-j JOBS] [--random-state INT] [--min-trades INT]
|
||||||
[--continue] [--hyperopt-loss NAME]
|
[--continue] [--hyperopt-loss NAME]
|
||||||
|
@ -308,9 +313,9 @@ optional arguments:
|
||||||
Allow buying the same pair multiple times (position
|
Allow buying the same pair multiple times (position
|
||||||
stacking).
|
stacking).
|
||||||
-e INT, --epochs INT Specify number of epochs (default: 100).
|
-e INT, --epochs INT Specify number of epochs (default: 100).
|
||||||
--spaces {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]
|
--spaces {all,buy,sell,roi,stoploss,trailing,default} [{all,buy,sell,roi,stoploss,trailing,default} ...]
|
||||||
Specify which parameters to hyperopt. Space-separated
|
Specify which parameters to hyperopt. Space-separated
|
||||||
list. Default: `all`.
|
list.
|
||||||
--dmmp, --disable-max-market-positions
|
--dmmp, --disable-max-market-positions
|
||||||
Disable applying `max_open_trades` during backtest
|
Disable applying `max_open_trades` during backtest
|
||||||
(same as setting `max_open_trades` to a very high
|
(same as setting `max_open_trades` to a very high
|
||||||
|
@ -344,12 +349,15 @@ optional arguments:
|
||||||
|
|
||||||
Common arguments:
|
Common arguments:
|
||||||
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
--logfile FILE Log to the file specified.
|
--logfile FILE Log to the file specified. Special values are:
|
||||||
|
'syslog', 'journald'. See the documentation for more
|
||||||
|
details.
|
||||||
-V, --version show program's version number and exit
|
-V, --version show program's version number and exit
|
||||||
-c PATH, --config PATH
|
-c PATH, --config PATH
|
||||||
Specify configuration file (default: `config.json`).
|
Specify configuration file (default:
|
||||||
Multiple --config options may be used. Can be set to
|
`userdir/config.json` or `config.json` whichever
|
||||||
`-` to read config from stdin.
|
exists). Multiple --config options may be used. Can be
|
||||||
|
set to `-` to read config from stdin.
|
||||||
-d PATH, --datadir PATH
|
-d PATH, --datadir PATH
|
||||||
Path to directory with historical backtesting data.
|
Path to directory with historical backtesting data.
|
||||||
--userdir PATH, --user-data-dir PATH
|
--userdir PATH, --user-data-dir PATH
|
||||||
|
@ -360,6 +368,7 @@ Strategy arguments:
|
||||||
Specify strategy class name which will be used by the
|
Specify strategy class name which will be used by the
|
||||||
bot.
|
bot.
|
||||||
--strategy-path PATH Specify additional strategy lookup path.
|
--strategy-path PATH Specify additional strategy lookup path.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Edge commands
|
## Edge commands
|
||||||
|
@ -396,12 +405,15 @@ optional arguments:
|
||||||
|
|
||||||
Common arguments:
|
Common arguments:
|
||||||
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
--logfile FILE Log to the file specified.
|
--logfile FILE Log to the file specified. Special values are:
|
||||||
|
'syslog', 'journald'. See the documentation for more
|
||||||
|
details.
|
||||||
-V, --version show program's version number and exit
|
-V, --version show program's version number and exit
|
||||||
-c PATH, --config PATH
|
-c PATH, --config PATH
|
||||||
Specify configuration file (default: `config.json`).
|
Specify configuration file (default:
|
||||||
Multiple --config options may be used. Can be set to
|
`userdir/config.json` or `config.json` whichever
|
||||||
`-` to read config from stdin.
|
exists). Multiple --config options may be used. Can be
|
||||||
|
set to `-` to read config from stdin.
|
||||||
-d PATH, --datadir PATH
|
-d PATH, --datadir PATH
|
||||||
Path to directory with historical backtesting data.
|
Path to directory with historical backtesting data.
|
||||||
--userdir PATH, --user-data-dir PATH
|
--userdir PATH, --user-data-dir PATH
|
||||||
|
@ -412,6 +424,7 @@ Strategy arguments:
|
||||||
Specify strategy class name which will be used by the
|
Specify strategy class name which will be used by the
|
||||||
bot.
|
bot.
|
||||||
--strategy-path PATH Specify additional strategy lookup path.
|
--strategy-path PATH Specify additional strategy lookup path.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
To understand edge and how to read the results, please read the [edge documentation](edge.md).
|
To understand edge and how to read the results, please read the [edge documentation](edge.md).
|
||||||
|
|
|
@ -40,75 +40,77 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
||||||
|
|
||||||
| Parameter | Description |
|
| Parameter | Description |
|
||||||
|------------|-------------|
|
|------------|-------------|
|
||||||
| `max_open_trades` | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades). [More information below](#configuring-amount-per-trade).<br> ***Datatype:*** *Positive integer or -1.*
|
| `max_open_trades` | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades). [More information below](#configuring-amount-per-trade).<br> **Datatype:** Positive integer or -1.
|
||||||
| `stake_currency` | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *String*
|
| `stake_currency` | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** String
|
||||||
| `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Set it to `"unlimited"` to allow the bot to use all available balance. [More information below](#configuring-amount-per-trade). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *Positive float or `"unlimited"`.*
|
| `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Set it to `"unlimited"` to allow the bot to use all available balance. [More information below](#configuring-amount-per-trade). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Positive float or `"unlimited"`.
|
||||||
| `tradable_balance_ratio` | Ratio of the total account balance the bot is allowed to trade. [More information below](#configuring-amount-per-trade). <br>*Defaults to `0.99` 99%).*<br> ***Datatype:*** *Positive float between `0.1` and `1.0`.*
|
| `tradable_balance_ratio` | Ratio of the total account balance the bot is allowed to trade. [More information below](#configuring-amount-per-trade). <br>*Defaults to `0.99` 99%).*<br> **Datatype:** Positive float between `0.1` and `1.0`.
|
||||||
| `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade). <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
|
| `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||||
| `last_stake_amount_min_ratio` | Defines minimum stake amount that has to be left and executed. Applies only to the last stake amount when it's amended to a reduced value (i.e. if `amend_last_stake_amount` is set to `true`). [More information below](#configuring-amount-per-trade). <br>*Defaults to `0.5`.* <br> ***Datatype:*** *Float (as ratio)*
|
| `last_stake_amount_min_ratio` | Defines minimum stake amount that has to be left and executed. Applies only to the last stake amount when it's amended to a reduced value (i.e. if `amend_last_stake_amount` is set to `true`). [More information below](#configuring-amount-per-trade). <br>*Defaults to `0.5`.* <br> **Datatype:** Float (as ratio)
|
||||||
| `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals. <br>*Defaults to `0.05` (5%).* <br> ***Datatype:*** *Positive Float as ratio.*
|
| `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals. <br>*Defaults to `0.05` (5%).* <br> **Datatype:** Positive Float as ratio.
|
||||||
| `ticker_interval` | The ticker interval to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *String*
|
| `ticker_interval` | The ticker interval to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** String
|
||||||
| `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency). <br> ***Datatype:*** *String*
|
| `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency). <br> **Datatype:** String
|
||||||
| `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode. <br>*Defaults to `true`.* <br> ***Datatype:*** *Boolean*
|
| `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode. <br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
||||||
| `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in the Dry Run mode.<br>*Defaults to `1000`.* <br> ***Datatype:*** *Float*
|
| `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in the Dry Run mode.<br>*Defaults to `1000`.* <br> **Datatype:** Float
|
||||||
| `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
|
| `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||||
| `minimal_roi` | **Required.** Set the threshold in percent the bot will use to sell a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *Dict*
|
| `minimal_roi` | **Required.** Set the threshold in percent the bot will use to sell a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Dict
|
||||||
| `stoploss` | **Required.** Value of the stoploss in percent used by the bot. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *Float (as ratio)*
|
| `stoploss` | **Required.** Value of the stoploss in percent used by the bot. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Float (as ratio)
|
||||||
| `trailing_stop` | Enables trailing stoploss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *Boolean*
|
| `trailing_stop` | Enables trailing stoploss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Boolean
|
||||||
| `trailing_stop_positive` | Changes stoploss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *Float*
|
| `trailing_stop_positive` | Changes stoploss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Float
|
||||||
| `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0` (no offset).* <br> ***Datatype:*** *Float*
|
| `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0` (no offset).* <br> **Datatype:** Float
|
||||||
| `trailing_only_offset_is_reached` | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
|
| `trailing_only_offset_is_reached` | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||||
| `unfilledtimeout.buy` | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).<br> ***Datatype:*** *Integer*
|
| `unfilledtimeout.buy` | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
|
||||||
| `unfilledtimeout.sell` | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).<br> ***Datatype:*** *Integer*
|
| `unfilledtimeout.sell` | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
|
||||||
| `bid_strategy.ask_last_balance` | **Required.** Set the bidding price. More information [below](#buy-price-without-orderbook).
|
| `bid_strategy.ask_last_balance` | **Required.** Set the bidding price. More information [below](#buy-price-without-orderbook).
|
||||||
| `bid_strategy.use_order_book` | Enable buying using the rates in [Order Book Bids](#buy-price-with-orderbook-enabled). <br> ***Datatype:*** *Boolean*
|
| `bid_strategy.use_order_book` | Enable buying using the rates in [Order Book Bids](#buy-price-with-orderbook-enabled). <br> **Datatype:** Boolean
|
||||||
| `bid_strategy.order_book_top` | Bot will use the top N rate in Order Book Bids to buy. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in [Order Book Bids](#buy-price-with-orderbook-enabled). <br>*Defaults to `1`.* <br> ***Datatype:*** *Positive Integer*
|
| `bid_strategy.order_book_top` | Bot will use the top N rate in Order Book Bids to buy. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in [Order Book Bids](#buy-price-with-orderbook-enabled). <br>*Defaults to `1`.* <br> **Datatype:** Positive Integer
|
||||||
| `bid_strategy. check_depth_of_market.enabled` | Do not buy if the difference of buy orders and sell orders is met in Order Book. [Check market depth](#check-depth-of-market). <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
|
| `bid_strategy. check_depth_of_market.enabled` | Do not buy if the difference of buy orders and sell orders is met in Order Book. [Check market depth](#check-depth-of-market). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||||
| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | The difference ratio of buy orders and sell orders found in Order Book. A value below 1 means sell order size is greater, while value greater than 1 means buy order size is higher. [Check market depth](#check-depth-of-market) <br> *Defaults to `0`.* <br> ***Datatype:*** *Float (as ratio)*
|
| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | The difference ratio of buy orders and sell orders found in Order Book. A value below 1 means sell order size is greater, while value greater than 1 means buy order size is higher. [Check market depth](#check-depth-of-market) <br> *Defaults to `0`.* <br> **Datatype:** Float (as ratio)
|
||||||
| `ask_strategy.use_order_book` | Enable selling of open trades using [Order Book Asks](#sell-price-with-orderbook-enabled). <br> ***Datatype:*** *Boolean*
|
| `ask_strategy.use_order_book` | Enable selling of open trades using [Order Book Asks](#sell-price-with-orderbook-enabled). <br> **Datatype:** Boolean
|
||||||
| `ask_strategy.order_book_min` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. <br>*Defaults to `1`.* <br> ***Datatype:*** *Positive Integer*
|
| `ask_strategy.order_book_min` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. <br>*Defaults to `1`.* <br> **Datatype:** Positive Integer
|
||||||
| `ask_strategy.order_book_max` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. <br>*Defaults to `1`.* <br> ***Datatype:*** *Positive Integer*
|
| `ask_strategy.order_book_max` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. <br>*Defaults to `1`.* <br> **Datatype:** Positive Integer
|
||||||
| `ask_strategy.use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `true`.* <br> ***Datatype:*** *Boolean*
|
| `ask_strategy.use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
||||||
| `ask_strategy.sell_profit_only` | Wait until the bot makes a positive profit before taking a sell decision. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
|
| `ask_strategy.sell_profit_only` | Wait until the bot makes a positive profit before taking a sell decision. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||||
| `ask_strategy.ignore_roi_if_buy_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
|
| `ask_strategy.ignore_roi_if_buy_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||||
| `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).<br> ***Datatype:*** *Dict*
|
| `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Dict
|
||||||
| `order_time_in_force` | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *Dict*
|
| `order_time_in_force` | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Dict
|
||||||
| `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). <br> ***Datatype:*** *String*
|
| `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). <br> **Datatype:** String
|
||||||
| `exchange.sandbox` | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details.<br> ***Datatype:*** *Boolean*
|
| `exchange.sandbox` | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details.<br> **Datatype:** Boolean
|
||||||
| `exchange.key` | API key to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> ***Datatype:*** *String*
|
| `exchange.key` | API key to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
||||||
| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> ***Datatype:*** *String*
|
| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
||||||
| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests.<br>**Keep it in secret, do not disclose publicly.** <br> ***Datatype:*** *String*
|
| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
||||||
| `exchange.pair_whitelist` | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Not used by VolumePairList (see [below](#dynamic-pairlists)). <br> ***Datatype:*** *List*
|
| `exchange.pair_whitelist` | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Not used by VolumePairList (see [below](#dynamic-pairlists)). <br> **Datatype:** List
|
||||||
| `exchange.pair_blacklist` | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#dynamic-pairlists)). <br> ***Datatype:*** *List*
|
| `exchange.pair_blacklist` | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#dynamic-pairlists)). <br> **Datatype:** List
|
||||||
| `exchange.ccxt_config` | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) <br> ***Datatype:*** *Dict*
|
| `exchange.ccxt_config` | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) <br> **Datatype:** Dict
|
||||||
| `exchange.ccxt_async_config` | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) <br> ***Datatype:*** *Dict*
|
| `exchange.ccxt_async_config` | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) <br> **Datatype:** Dict
|
||||||
| `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded. <br>*Defaults to `60` minutes.* <br> ***Datatype:*** *Positive Integer*
|
| `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded. <br>*Defaults to `60` minutes.* <br> **Datatype:** Positive Integer
|
||||||
| `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation.
|
| `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation.
|
||||||
| `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now. <br>*Defaults to `true`.* <br> ***Datatype:*** *Boolean*
|
| `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now. <br>*Defaults to `true`.* <br> **Datatype:** Boolean
|
||||||
| `pairlists` | Define one or more pairlists to be used. [More information below](#dynamic-pairlists). <br>*Defaults to `StaticPairList`.* <br> ***Datatype:*** *List of Dicts*
|
| `pairlists` | Define one or more pairlists to be used. [More information below](#dynamic-pairlists). <br>*Defaults to `StaticPairList`.* <br> **Datatype:** List of Dicts
|
||||||
| `telegram.enabled` | Enable the usage of Telegram. <br> ***Datatype:*** *Boolean*
|
| `telegram.enabled` | Enable the usage of Telegram. <br> **Datatype:** Boolean
|
||||||
| `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> ***Datatype:*** *String*
|
| `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
||||||
| `telegram.chat_id` | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> ***Datatype:*** *String*
|
| `telegram.chat_id` | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
||||||
| `webhook.enabled` | Enable usage of Webhook notifications <br> ***Datatype:*** *Boolean*
|
| `webhook.enabled` | Enable usage of Webhook notifications <br> **Datatype:** Boolean
|
||||||
| `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> ***Datatype:*** *String*
|
| `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
||||||
| `webhook.webhookbuy` | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> ***Datatype:*** *String*
|
| `webhook.webhookbuy` | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
||||||
| `webhook.webhooksell` | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> ***Datatype:*** *String*
|
| `webhook.webhookbuycancel` | Payload to send on buy order cancel. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
||||||
| `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> ***Datatype:*** *String*
|
| `webhook.webhooksell` | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
||||||
| `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details. <br> ***Datatype:*** *Boolean*
|
| `webhook.webhooksellcancel` | Payload to send on sell order cancel. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
||||||
| `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details. <br> ***Datatype:*** *IPv4*
|
| `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
|
||||||
| `api_server.listen_port` | Bind Port. See the [API Server documentation](rest-api.md) for more details. <br>***Datatype:*** *Integer between 1024 and 65535*
|
| `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** Boolean
|
||||||
| `api_server.username` | Username for API server. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.**<br> ***Datatype:*** *String*
|
| `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** IPv4
|
||||||
| `api_server.password` | Password for API server. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.**<br> ***Datatype:*** *String*
|
| `api_server.listen_port` | Bind Port. See the [API Server documentation](rest-api.md) for more details. <br>**Datatype:** Integer between 1024 and 65535
|
||||||
| `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite:///tradesv3.dryrun.sqlite` if `dry_run` is `true`, and to `sqlite:///tradesv3.sqlite` for production instances. <br> ***Datatype:*** *String, SQLAlchemy connect string*
|
| `api_server.username` | Username for API server. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.**<br> **Datatype:** String
|
||||||
| `initial_state` | Defines the initial application state. More information below. <br>*Defaults to `stopped`.* <br> ***Datatype:*** *Enum, either `stopped` or `running`*
|
| `api_server.password` | Password for API server. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.**<br> **Datatype:** String
|
||||||
| `forcebuy_enable` | Enables the RPC Commands to force a buy. More information below. <br> ***Datatype:*** *Boolean*
|
| `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite:///tradesv3.dryrun.sqlite` if `dry_run` is `true`, and to `sqlite:///tradesv3.sqlite` for production instances. <br> **Datatype:** String, SQLAlchemy connect string
|
||||||
| `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`. <br> ***Datatype:*** *ClassName*
|
| `initial_state` | Defines the initial application state. More information below. <br>*Defaults to `stopped`.* <br> **Datatype:** Enum, either `stopped` or `running`
|
||||||
| `strategy_path` | Adds an additional strategy lookup path (must be a directory). <br> ***Datatype:*** *String*
|
| `forcebuy_enable` | Enables the RPC Commands to force a buy. More information below. <br> **Datatype:** Boolean
|
||||||
| `internals.process_throttle_secs` | Set the process throttle. Value in second. <br>*Defaults to `5` seconds.* <br> ***Datatype:*** *Positive Integer*
|
| `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`. <br> **Datatype:** ClassName
|
||||||
| `internals.heartbeat_interval` | Print heartbeat message every N seconds. Set to 0 to disable heartbeat messages. <br>*Defaults to `60` seconds.* <br> ***Datatype:*** *Positive Integer or 0*
|
| `strategy_path` | Adds an additional strategy lookup path (must be a directory). <br> **Datatype:** String
|
||||||
| `internals.sd_notify` | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details. <br> ***Datatype:*** *Boolean*
|
| `internals.process_throttle_secs` | Set the process throttle. Value in second. <br>*Defaults to `5` seconds.* <br> **Datatype:** Positive Intege
|
||||||
| `logfile` | Specifies logfile name. Uses a rolling strategy for log file rotation for 10 files with the 1MB limit per file. <br> ***Datatype:*** *String*
|
| `internals.heartbeat_interval` | Print heartbeat message every N seconds. Set to 0 to disable heartbeat messages. <br>*Defaults to `60` seconds.* <br> **Datatype:** Positive Integer or 0
|
||||||
| `user_data_dir` | Directory containing user data. <br> *Defaults to `./user_data/`*. <br> ***Datatype:*** *String*
|
| `internals.sd_notify` | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details. <br> **Datatype:** Boolean
|
||||||
|
| `logfile` | Specifies logfile name. Uses a rolling strategy for log file rotation for 10 files with the 1MB limit per file. <br> **Datatype:** String
|
||||||
|
| `user_data_dir` | Directory containing user data. <br> *Defaults to `./user_data/`*. <br> **Datatype:** String
|
||||||
|
|
||||||
### Parameters in the strategy
|
### Parameters in the strategy
|
||||||
|
|
||||||
|
@ -608,6 +610,14 @@ Once you will be happy with your bot performance running in the Dry-run mode, yo
|
||||||
!!! Note
|
!!! Note
|
||||||
A simulated wallet is available during dry-run mode, and will assume a starting capital of `dry_run_wallet` (defaults to 1000).
|
A simulated wallet is available during dry-run mode, and will assume a starting capital of `dry_run_wallet` (defaults to 1000).
|
||||||
|
|
||||||
|
### Considerations for dry-run
|
||||||
|
|
||||||
|
* API-keys may or may not be provided. Only Read-Only operations (i.e. operations that do not alter account state) on the exchange are performed in the dry-run mode.
|
||||||
|
* Wallets (`/balance`) are simulated.
|
||||||
|
* Orders are simulated, and will not be posted to the exchange.
|
||||||
|
* In combination with `stoploss_on_exchange`, the stop_loss price is assumed to be filled.
|
||||||
|
* Open orders (not trades, which are stored in the database) are reset on bot restart.
|
||||||
|
|
||||||
## Switch to production mode
|
## Switch to production mode
|
||||||
|
|
||||||
In production mode, the bot will engage your money. Be careful, since a wrong
|
In production mode, the bot will engage your money. Be careful, since a wrong
|
||||||
|
@ -662,7 +672,7 @@ freqtrade
|
||||||
|
|
||||||
## Embedding Strategies
|
## Embedding Strategies
|
||||||
|
|
||||||
FreqTrade provides you with with an easy way to embed the strategy into your configuration file.
|
Freqtrade provides you with with an easy way to embed the strategy into your configuration file.
|
||||||
This is done by utilizing BASE64 encoding and providing this string at the strategy configuration field,
|
This is done by utilizing BASE64 encoding and providing this string at the strategy configuration field,
|
||||||
in your chosen config file.
|
in your chosen config file.
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Development Help
|
# Development Help
|
||||||
|
|
||||||
This page is intended for developers of FreqTrade, people who want to contribute to the FreqTrade codebase or documentation, or people who want to understand the source code of the application they're running.
|
This page is intended for developers of Freqtrade, people who want to contribute to the Freqtrade codebase or documentation, or people who want to understand the source code of the application they're running.
|
||||||
|
|
||||||
All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. We [track issues](https://github.com/freqtrade/freqtrade/issues) on [GitHub](https://github.com) and also have a dev channel in [slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE) where you can ask questions.
|
All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. We [track issues](https://github.com/freqtrade/freqtrade/issues) on [GitHub](https://github.com) and also have a dev channel in [slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE) where you can ask questions.
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ In VolumePairList, this implements different methods of sorting, does early vali
|
||||||
## Implement a new Exchange (WIP)
|
## Implement a new Exchange (WIP)
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
This section is a Work in Progress and is not a complete guide on how to test a new exchange with FreqTrade.
|
This section is a Work in Progress and is not a complete guide on how to test a new exchange with Freqtrade.
|
||||||
|
|
||||||
Most exchanges supported by CCXT should work out of the box.
|
Most exchanges supported by CCXT should work out of the box.
|
||||||
|
|
||||||
|
|
134
docs/docker.md
134
docs/docker.md
|
@ -1,4 +1,4 @@
|
||||||
# Using FreqTrade with Docker
|
# Using Freqtrade with Docker
|
||||||
|
|
||||||
## Install Docker
|
## Install Docker
|
||||||
|
|
||||||
|
@ -8,13 +8,141 @@ Start by downloading and installing Docker CE for your platform:
|
||||||
* [Windows](https://docs.docker.com/docker-for-windows/install/)
|
* [Windows](https://docs.docker.com/docker-for-windows/install/)
|
||||||
* [Linux](https://docs.docker.com/install/)
|
* [Linux](https://docs.docker.com/install/)
|
||||||
|
|
||||||
|
Optionally, [docker-compose](https://docs.docker.com/compose/install/) should be installed and available to follow the [docker quick start guide](#docker-quick-start).
|
||||||
|
|
||||||
Once you have Docker installed, simply prepare the config file (e.g. `config.json`) and run the image for `freqtrade` as explained below.
|
Once you have Docker installed, simply prepare the config file (e.g. `config.json`) and run the image for `freqtrade` as explained below.
|
||||||
|
|
||||||
## Download the official FreqTrade docker image
|
## Freqtrade with docker-compose
|
||||||
|
|
||||||
|
Freqtrade provides an official Docker image on [Dockerhub](https://hub.docker.com/r/freqtradeorg/freqtrade/), as well as a [docker-compose file](https://github.com/freqtrade/freqtrade/blob/develop/docker-compose.yml) ready for usage.
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
The following section assumes that docker and docker-compose is installed and available to the logged in user.
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
All below comands use relative directories and will have to be executed from the directory containing the `docker-compose.yml` file.
|
||||||
|
|
||||||
|
### Docker quick start
|
||||||
|
|
||||||
|
Create a new directory and place the [docker-compose file](https://github.com/freqtrade/freqtrade/blob/develop/docker-compose.yml) in this directory.
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
mkdir ft_userdata
|
||||||
|
cd ft_userdata/
|
||||||
|
# Download the docker-compose file from the repository
|
||||||
|
curl https://raw.githubusercontent.com/freqtrade/freqtrade/develop/docker-compose.yml -o docker-compose.yml
|
||||||
|
|
||||||
|
# Pull the freqtrade image
|
||||||
|
docker-compose pull
|
||||||
|
|
||||||
|
# Create user directory structure
|
||||||
|
docker-compose run --rm freqtrade create-userdir --userdir user_data
|
||||||
|
|
||||||
|
# Create configuration - Requires answering interactive questions
|
||||||
|
docker-compose run --rm freqtrade new-config --config user_data/config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The above snippet creates a new directory called "ft_userdata", downloads the latest compose file and pulls the freqtrade image.
|
||||||
|
The last 2 steps in the snippet create the directory with user-data, as well as (interactively) the default configuration based on your selections.
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
You can edit the configuration at any time, which is available as `user_data/config.json` (within the directory `ft_userdata`) when using the above configuration.
|
||||||
|
|
||||||
|
#### Adding your strategy
|
||||||
|
|
||||||
|
The configuration is now available as `user_data/config.json`.
|
||||||
|
You should now copy your strategy to `user_data/strategies/` - and add the Strategy class name to the `docker-compose.yml` file, replacing `SampleStrategy`. If you wish to run the bot with the SampleStrategy, just leave it as it is.
|
||||||
|
|
||||||
|
!!! Warning
|
||||||
|
The `SampleStrategy` is there for your reference and give you ideas for your own strategy.
|
||||||
|
Please always backtest the strategy and use dry-run for some time before risking real money!
|
||||||
|
|
||||||
|
Once this is done, you're ready to launch the bot in trading mode (Dry-run or Live-trading, depending on your answer to the corresponding question you made above).
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Docker-compose logs
|
||||||
|
|
||||||
|
Logs will be written to `user_data/freqtrade.log`.
|
||||||
|
Alternatively, you can check the latest logs using `docker-compose logs -f`.
|
||||||
|
|
||||||
|
#### Database
|
||||||
|
|
||||||
|
The database will be in the user_data directory as well, and will be called `user_data/tradesv3.sqlite`.
|
||||||
|
|
||||||
|
#### Updating freqtrade with docker-compose
|
||||||
|
|
||||||
|
To update freqtrade when using docker-compose is as simple as running the following 2 commands:
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
# Download the latest image
|
||||||
|
docker-compose pull
|
||||||
|
# Restart the image
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
This will first pull the latest image, and will then restart the container with the just pulled version.
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
You should always check the changelog for breaking changes / manual interventions required and make sure the bot starts correctly after the update.
|
||||||
|
|
||||||
|
#### Going from here
|
||||||
|
|
||||||
|
Advanced users may edit the docker-compose file further to include all possible options or arguments.
|
||||||
|
|
||||||
|
All possible freqtrade arguments will be available by running `docker-compose run --rm freqtrade <command> <optional arguments>`.
|
||||||
|
|
||||||
|
!!! Note "`docker-compose run --rm`"
|
||||||
|
Including `--rm` will clean up the container after completion, and is highly recommended for all modes except trading mode (running with `freqtrade trade` command).
|
||||||
|
|
||||||
|
##### Example: Download data with docker-compose
|
||||||
|
|
||||||
|
Download backtesting data for 5 days for the pair ETH/BTC and 1h timeframe from Binance. The data will be stored in the directory `user_data/data/` on the host.
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
docker-compose run --rm freqtrade download-data --pairs ETH/BTC --exchange binance --days 5 -t 1h
|
||||||
|
```
|
||||||
|
|
||||||
|
Head over to the [Data Downloading Documentation](data-download.md) for more details on downloading data.
|
||||||
|
|
||||||
|
##### Example: Backtest with docker-compose
|
||||||
|
|
||||||
|
Run backtesting in docker-containers for SampleStrategy and specified timerange of historical data, on 5m timeframe:
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
docker-compose run --rm freqtrade backtesting --config user_data/config.json --strategy SampleStrategy --timerange 20190801-20191001 -i 5m
|
||||||
|
```
|
||||||
|
|
||||||
|
Head over to the [Backtesting Documentation](backtesting.md) to learn more.
|
||||||
|
|
||||||
|
#### Additional dependencies with docker-compose
|
||||||
|
|
||||||
|
If your strategy requires dependencies not included in the default image (like [technical](https://github.com/freqtrade/technical)) - it will be necessary to build the image on your host.
|
||||||
|
For this, please create a Dockerfile containing installation steps for the additional dependencies (have a look at [Dockerfile.technical](https://github.com/freqtrade/freqtrade/blob/develop/Dockerfile.technical) for an example).
|
||||||
|
|
||||||
|
You'll then also need to modify the `docker-compose.yml` file and uncomment the build step, as well as rename the image to avoid naming collisions.
|
||||||
|
|
||||||
|
``` yaml
|
||||||
|
image: freqtrade_custom
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: "./Dockerfile.<yourextension>"
|
||||||
|
```
|
||||||
|
|
||||||
|
You can then run `docker-compose build` to build the docker image, and run it using the commands described above.
|
||||||
|
|
||||||
|
## Freqtrade with docker without docker-compose
|
||||||
|
|
||||||
|
!!! Warning
|
||||||
|
The below documentation is provided for completeness and assumes that you are somewhat familiar with running docker containers. If you're just starting out with docker, we recommend to follow the [Freqtrade with docker-compose](#freqtrade-with-docker-compose) instructions.
|
||||||
|
|
||||||
|
### Download the official Freqtrade docker image
|
||||||
|
|
||||||
Pull the image from docker hub.
|
Pull the image from docker hub.
|
||||||
|
|
||||||
Branches / tags available can be checked out on [Dockerhub](https://hub.docker.com/r/freqtradeorg/freqtrade/tags/).
|
Branches / tags available can be checked out on [Dockerhub tags page](https://hub.docker.com/r/freqtradeorg/freqtrade/tags/).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull freqtradeorg/freqtrade:develop
|
docker pull freqtradeorg/freqtrade:develop
|
||||||
|
|
26
docs/edge.md
26
docs/edge.md
|
@ -145,19 +145,19 @@ Edge module has following configuration options:
|
||||||
|
|
||||||
| Parameter | Description |
|
| Parameter | Description |
|
||||||
|------------|-------------|
|
|------------|-------------|
|
||||||
| `enabled` | If true, then Edge will run periodically. <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
|
| `enabled` | If true, then Edge will run periodically. <br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||||
| `process_throttle_secs` | How often should Edge run in seconds. <br>*Defaults to `3600` (once per hour).* <br> ***Datatype:*** *Integer*
|
| `process_throttle_secs` | How often should Edge run in seconds. <br>*Defaults to `3600` (once per hour).* <br> **Datatype:** Integer
|
||||||
| `calculate_since_number_of_days` | Number of days of data against which Edge calculates Win Rate, Risk Reward and Expectancy. <br> **Note** that it downloads historical data so increasing this number would lead to slowing down the bot. <br>*Defaults to `7`.* <br> ***Datatype:*** *Integer*
|
| `calculate_since_number_of_days` | Number of days of data against which Edge calculates Win Rate, Risk Reward and Expectancy. <br> **Note** that it downloads historical data so increasing this number would lead to slowing down the bot. <br>*Defaults to `7`.* <br> **Datatype:** Integer
|
||||||
| `capital_available_percentage` | **DEPRECATED - [replaced with `tradable_balance_ratio`](configuration.md#Available balance)** This is the percentage of the total capital on exchange in stake currency. <br>As an example if you have 10 ETH available in your wallet on the exchange and this value is 0.5 (which is 50%), then the bot will use a maximum amount of 5 ETH for trading and considers it as available capital. <br>*Defaults to `0.5`.* <br> ***Datatype:*** *Float*
|
| `capital_available_percentage` | **DEPRECATED - [replaced with `tradable_balance_ratio`](configuration.md#Available balance)** This is the percentage of the total capital on exchange in stake currency. <br>As an example if you have 10 ETH available in your wallet on the exchange and this value is 0.5 (which is 50%), then the bot will use a maximum amount of 5 ETH for trading and considers it as available capital. <br>*Defaults to `0.5`.* <br> **Datatype:** Float
|
||||||
| `allowed_risk` | Ratio of allowed risk per trade. <br>*Defaults to `0.01` (1%)).* <br> ***Datatype:*** *Float*
|
| `allowed_risk` | Ratio of allowed risk per trade. <br>*Defaults to `0.01` (1%)).* <br> **Datatype:** Float
|
||||||
| `stoploss_range_min` | Minimum stoploss. <br>*Defaults to `-0.01`.* <br> ***Datatype:*** *Float*
|
| `stoploss_range_min` | Minimum stoploss. <br>*Defaults to `-0.01`.* <br> **Datatype:** Float
|
||||||
| `stoploss_range_max` | Maximum stoploss. <br>*Defaults to `-0.10`.* <br> ***Datatype:*** *Float*
|
| `stoploss_range_max` | Maximum stoploss. <br>*Defaults to `-0.10`.* <br> **Datatype:** Float
|
||||||
| `stoploss_range_step` | As an example if this is set to -0.01 then Edge will test the strategy for `[-0.01, -0,02, -0,03 ..., -0.09, -0.10]` ranges. <br> **Note** than having a smaller step means having a bigger range which could lead to slow calculation. <br> If you set this parameter to -0.001, you then slow down the Edge calculation by a factor of 10. <br>*Defaults to `-0.001`.* <br> ***Datatype:*** *Float*
|
| `stoploss_range_step` | As an example if this is set to -0.01 then Edge will test the strategy for `[-0.01, -0,02, -0,03 ..., -0.09, -0.10]` ranges. <br> **Note** than having a smaller step means having a bigger range which could lead to slow calculation. <br> If you set this parameter to -0.001, you then slow down the Edge calculation by a factor of 10. <br>*Defaults to `-0.001`.* <br> **Datatype:** Float
|
||||||
| `minimum_winrate` | It filters out pairs which don't have at least minimum_winrate. <br>This comes handy if you want to be conservative and don't comprise win rate in favour of risk reward ratio. <br>*Defaults to `0.60`.* <br> ***Datatype:*** *Float*
|
| `minimum_winrate` | It filters out pairs which don't have at least minimum_winrate. <br>This comes handy if you want to be conservative and don't comprise win rate in favour of risk reward ratio. <br>*Defaults to `0.60`.* <br> **Datatype:** Float
|
||||||
| `minimum_expectancy` | It filters out pairs which have the expectancy lower than this number. <br>Having an expectancy of 0.20 means if you put 10$ on a trade you expect a 12$ return. <br>*Defaults to `0.20`.* <br> ***Datatype:*** *Float*
|
| `minimum_expectancy` | It filters out pairs which have the expectancy lower than this number. <br>Having an expectancy of 0.20 means if you put 10$ on a trade you expect a 12$ return. <br>*Defaults to `0.20`.* <br> **Datatype:** Float
|
||||||
| `min_trade_number` | When calculating *W*, *R* and *E* (expectancy) against historical data, you always want to have a minimum number of trades. The more this number is the more Edge is reliable. <br>Having a win rate of 100% on a single trade doesn't mean anything at all. But having a win rate of 70% over past 100 trades means clearly something. <br>*Defaults to `10` (it is highly recommended not to decrease this number).* <br> ***Datatype:*** *Integer*
|
| `min_trade_number` | When calculating *W*, *R* and *E* (expectancy) against historical data, you always want to have a minimum number of trades. The more this number is the more Edge is reliable. <br>Having a win rate of 100% on a single trade doesn't mean anything at all. But having a win rate of 70% over past 100 trades means clearly something. <br>*Defaults to `10` (it is highly recommended not to decrease this number).* <br> **Datatype:** Integer
|
||||||
| `max_trade_duration_minute` | Edge will filter out trades with long duration. If a trade is profitable after 1 month, it is hard to evaluate the strategy based on it. But if most of trades are profitable and they have maximum duration of 30 minutes, then it is clearly a good sign.<br>**NOTICE:** While configuring this value, you should take into consideration your ticker interval. As an example filtering out trades having duration less than one day for a strategy which has 4h interval does not make sense. Default value is set assuming your strategy interval is relatively small (1m or 5m, etc.).<br>*Defaults to `1440` (one day).* <br> ***Datatype:*** *Integer*
|
| `max_trade_duration_minute` | Edge will filter out trades with long duration. If a trade is profitable after 1 month, it is hard to evaluate the strategy based on it. But if most of trades are profitable and they have maximum duration of 30 minutes, then it is clearly a good sign.<br>**NOTICE:** While configuring this value, you should take into consideration your ticker interval. As an example filtering out trades having duration less than one day for a strategy which has 4h interval does not make sense. Default value is set assuming your strategy interval is relatively small (1m or 5m, etc.).<br>*Defaults to `1440` (one day).* <br> **Datatype:** Integer
|
||||||
| `remove_pumps` | Edge will remove sudden pumps in a given market while going through historical data. However, given that pumps happen very often in crypto markets, we recommend you keep this off.<br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
|
| `remove_pumps` | Edge will remove sudden pumps in a given market while going through historical data. However, given that pumps happen very often in crypto markets, we recommend you keep this off.<br>*Defaults to `false`.* <br> **Datatype:** Boolean
|
||||||
|
|
||||||
## Running Edge independently
|
## Running Edge independently
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,10 @@ To download data for the Kraken exchange, using `--dl-trades` is mandatory, othe
|
||||||
|
|
||||||
## Bittrex
|
## Bittrex
|
||||||
|
|
||||||
|
### Order types
|
||||||
|
|
||||||
|
Bittrex does not support market orders. If you have a message at the bot startup about this, you should change order type values set in your configuration and/or in the strategy from `"market"` to `"limit"`. See some more details on this [here in the FAQ](faq.md#im-getting-the-exchange-bittrex-does-not-support-market-orders-message-and-cannot-run-my-strategy).
|
||||||
|
|
||||||
### Restricted markets
|
### Restricted markets
|
||||||
|
|
||||||
Bittrex split its exchange into US and International versions.
|
Bittrex split its exchange into US and International versions.
|
||||||
|
|
18
docs/faq.md
18
docs/faq.md
|
@ -45,12 +45,28 @@ the tutorial [here|Testing-new-strategies-with-Hyperopt](bot-usage.md#hyperopt-c
|
||||||
|
|
||||||
You can use the `/forcesell all` command from Telegram.
|
You can use the `/forcesell all` command from Telegram.
|
||||||
|
|
||||||
### I get the message "RESTRICTED_MARKET"
|
### I'm getting the "RESTRICTED_MARKET" message in the log
|
||||||
|
|
||||||
Currently known to happen for US Bittrex users.
|
Currently known to happen for US Bittrex users.
|
||||||
|
|
||||||
Read [the Bittrex section about restricted markets](exchanges.md#restricted-markets) for more information.
|
Read [the Bittrex section about restricted markets](exchanges.md#restricted-markets) for more information.
|
||||||
|
|
||||||
|
### I'm getting the "Exchange Bittrex does not support market orders." message and cannot run my strategy
|
||||||
|
|
||||||
|
As the message says, Bittrex does not support market orders and you have one of the [order types](configuration.md/#understand-order_types) set to "market". Probably your strategy was written with other exchanges in mind and sets "market" orders for "stoploss" orders, which is correct and preferable for most of the exchanges supporting market orders (but not for Bittrex).
|
||||||
|
|
||||||
|
To fix it for Bittrex, redefine order types in the strategy to use "limit" instead of "market":
|
||||||
|
|
||||||
|
```
|
||||||
|
order_types = {
|
||||||
|
...
|
||||||
|
'stoploss': 'limit',
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Same fix should be done in the configuration file, if order types are defined in your custom config rather than in the strategy.
|
||||||
|
|
||||||
### How do I search the bot logs for something?
|
### How do I search the bot logs for something?
|
||||||
|
|
||||||
By default, the bot writes its log into stderr stream. This is implemented this way so that you can easily separate the bot's diagnostics messages from Backtesting, Edge and Hyperopt results, output from other various Freqtrade utility subcommands, as well as from the output of your custom `print()`'s you may have inserted into your strategy. So if you need to search the log messages with the grep utility, you need to redirect stderr to stdout and disregard stdout.
|
By default, the bot writes its log into stderr stream. This is implemented this way so that you can easily separate the bot's diagnostics messages from Backtesting, Edge and Hyperopt results, output from other various Freqtrade utility subcommands, as well as from the output of your custom `print()`'s you may have inserted into your strategy. So if you need to search the log messages with the grep utility, you need to redirect stderr to stdout and disregard stdout.
|
||||||
|
|
|
@ -182,7 +182,7 @@ add it to the `populate_indicators()` method in your custom hyperopt file.
|
||||||
|
|
||||||
Each hyperparameter tuning requires a target. This is usually defined as a loss function (sometimes also called objective function), which should decrease for more desirable results, and increase for bad results.
|
Each hyperparameter tuning requires a target. This is usually defined as a loss function (sometimes also called objective function), which should decrease for more desirable results, and increase for bad results.
|
||||||
|
|
||||||
By default, FreqTrade uses a loss function, which has been with freqtrade since the beginning and optimizes mostly for short trade duration and avoiding losses.
|
By default, Freqtrade uses a loss function, which has been with freqtrade since the beginning and optimizes mostly for short trade duration and avoiding losses.
|
||||||
|
|
||||||
A different loss function can be specified by using the `--hyperopt-loss <Class-name>` argument.
|
A different loss function can be specified by using the `--hyperopt-loss <Class-name>` argument.
|
||||||
This class should be in its own file within the `user_data/hyperopts/` directory.
|
This class should be in its own file within the `user_data/hyperopts/` directory.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# Freqtrade
|
# Freqtrade
|
||||||
[![Build Status](https://travis-ci.org/freqtrade/freqtrade.svg?branch=develop)](https://travis-ci.org/freqtrade/freqtrade)
|
[![Freqtrade CI](https://github.com/freqtrade/freqtrade/workflows/Freqtrade%20CI/badge.svg)](https://github.com/freqtrade/freqtrade/actions/)
|
||||||
[![Coverage Status](https://coveralls.io/repos/github/freqtrade/freqtrade/badge.svg?branch=develop&service=github)](https://coveralls.io/github/freqtrade/freqtrade?branch=develop)
|
[![Coverage Status](https://coveralls.io/repos/github/freqtrade/freqtrade/badge.svg?branch=develop&service=github)](https://coveralls.io/github/freqtrade/freqtrade?branch=develop)
|
||||||
[![Maintainability](https://api.codeclimate.com/v1/badges/5737e6d668200b7518ff/maintainability)](https://codeclimate.com/github/freqtrade/freqtrade/maintainability)
|
[![Maintainability](https://api.codeclimate.com/v1/badges/5737e6d668200b7518ff/maintainability)](https://codeclimate.com/github/freqtrade/freqtrade/maintainability)
|
||||||
|
|
||||||
|
@ -51,12 +51,15 @@ To run this bot we recommend you a cloud instance with a minimum of:
|
||||||
|
|
||||||
### Software requirements
|
### Software requirements
|
||||||
|
|
||||||
|
- Docker (Recommended)
|
||||||
|
|
||||||
|
Alternatively
|
||||||
|
|
||||||
- Python 3.6.x
|
- Python 3.6.x
|
||||||
- pip (pip3)
|
- pip (pip3)
|
||||||
- git
|
- git
|
||||||
- TA-Lib
|
- TA-Lib
|
||||||
- virtualenv (Recommended)
|
- virtualenv (Recommended)
|
||||||
- Docker (Recommended)
|
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
|
|
||||||
|
@ -67,4 +70,4 @@ Click [here](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODc
|
||||||
|
|
||||||
## Ready to try?
|
## Ready to try?
|
||||||
|
|
||||||
Begin by reading our installation guide [here](installation).
|
Begin by reading our installation guide [for docker](docker.md), or for [installation without docker](installation.md).
|
||||||
|
|
|
@ -31,7 +31,7 @@ Freqtrade provides the Linux/MacOS Easy Installation script to install all depen
|
||||||
!!! Note
|
!!! Note
|
||||||
Windows installation is explained [here](#windows).
|
Windows installation is explained [here](#windows).
|
||||||
|
|
||||||
The easiest way to install and run Freqtrade is to clone the bot GitHub repository and then run the Easy Installation script, if it's available for your platform.
|
The easiest way to install and run Freqtrade is to clone the bot Github repository and then run the Easy Installation script, if it's available for your platform.
|
||||||
|
|
||||||
!!! Note "Version considerations"
|
!!! Note "Version considerations"
|
||||||
When cloning the repository the default working branch has the name `develop`. This branch contains all last features (can be considered as relatively stable, thanks to automated tests). The `master` branch contains the code of the last release (done usually once per month on an approximately one week old snapshot of the `develop` branch to prevent packaging bugs, so potentially it's more stable).
|
When cloning the repository the default working branch has the name `develop`. This branch contains all last features (can be considered as relatively stable, thanks to automated tests). The `master` branch contains the code of the last release (done usually once per month on an approximately one week old snapshot of the `develop` branch to prevent packaging bugs, so potentially it's more stable).
|
||||||
|
@ -47,6 +47,7 @@ cd freqtrade
|
||||||
git checkout master # Optional, see (1)
|
git checkout master # Optional, see (1)
|
||||||
./setup.sh --install
|
./setup.sh --install
|
||||||
```
|
```
|
||||||
|
|
||||||
(1) This command switches the cloned repository to the use of the `master` branch. It's not needed if you wish to stay on the `develop` branch. You may later switch between branches at any time with the `git checkout master`/`git checkout develop` commands.
|
(1) This command switches the cloned repository to the use of the `master` branch. It's not needed if you wish to stay on the `develop` branch. You may later switch between branches at any time with the `git checkout master`/`git checkout develop` commands.
|
||||||
|
|
||||||
## Easy Installation Script (Linux/MacOS)
|
## Easy Installation Script (Linux/MacOS)
|
||||||
|
@ -129,6 +130,17 @@ bash setup.sh -i
|
||||||
|
|
||||||
#### 1. Install TA-Lib
|
#### 1. Install TA-Lib
|
||||||
|
|
||||||
|
Use the provided ta-lib installation script
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./build_helpers/install_ta-lib.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
This will use the ta-lib tar.gz included in this repository.
|
||||||
|
|
||||||
|
##### TA-Lib manual installation
|
||||||
|
|
||||||
Official webpage: https://mrjbq7.github.io/ta-lib/install.html
|
Official webpage: https://mrjbq7.github.io/ta-lib/install.html
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@ -184,7 +196,8 @@ python3 -m pip install -e .
|
||||||
# Initialize the user_directory
|
# Initialize the user_directory
|
||||||
freqtrade create-userdir --userdir user_data/
|
freqtrade create-userdir --userdir user_data/
|
||||||
|
|
||||||
cp config.json.example config.json
|
# Create a new configuration file
|
||||||
|
freqtrade new-config --config config.json
|
||||||
```
|
```
|
||||||
|
|
||||||
> *To edit the config please refer to [Bot Configuration](configuration.md).*
|
> *To edit the config please refer to [Bot Configuration](configuration.md).*
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
mkdocs-material==4.6.0
|
mkdocs-material==4.6.3
|
||||||
mdx_truly_sane_lists==1.2
|
mdx_truly_sane_lists==1.2
|
||||||
|
|
|
@ -74,7 +74,7 @@ docker run -d \
|
||||||
## Consuming the API
|
## Consuming the API
|
||||||
|
|
||||||
You can consume the API by using the script `scripts/rest_client.py`.
|
You can consume the API by using the script `scripts/rest_client.py`.
|
||||||
The client script only requires the `requests` module, so FreqTrade does not need to be installed on the system.
|
The client script only requires the `requests` module, so Freqtrade does not need to be installed on the system.
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
python3 scripts/rest_client.py <command> [optional parameters]
|
python3 scripts/rest_client.py <command> [optional parameters]
|
||||||
|
|
|
@ -346,7 +346,7 @@ if self.dp:
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
if self.dp:
|
if self.dp:
|
||||||
if self.dp.runmode in ('live', 'dry_run'):
|
if self.dp.runmode.value in ('live', 'dry_run'):
|
||||||
ob = self.dp.orderbook(metadata['pair'], 1)
|
ob = self.dp.orderbook(metadata['pair'], 1)
|
||||||
dataframe['best_bid'] = ob['bids'][0][0]
|
dataframe['best_bid'] = ob['bids'][0][0]
|
||||||
dataframe['best_ask'] = ob['asks'][0][0]
|
dataframe['best_ask'] = ob['asks'][0][0]
|
||||||
|
@ -422,7 +422,7 @@ from freqtrade.persistence import Trade
|
||||||
The following example queries for the current pair and trades from today, however other filters can easily be added.
|
The following example queries for the current pair and trades from today, however other filters can easily be added.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
if self.config['runmode'] in ('live', 'dry_run'):
|
if self.config['runmode'].value in ('live', 'dry_run'):
|
||||||
trades = Trade.get_trades([Trade.pair == metadata['pair'],
|
trades = Trade.get_trades([Trade.pair == metadata['pair'],
|
||||||
Trade.open_date > datetime.utcnow() - timedelta(days=1),
|
Trade.open_date > datetime.utcnow() - timedelta(days=1),
|
||||||
Trade.is_open == False,
|
Trade.is_open == False,
|
||||||
|
@ -434,7 +434,7 @@ if self.config['runmode'] in ('live', 'dry_run'):
|
||||||
Get amount of stake_currency currently invested in Trades:
|
Get amount of stake_currency currently invested in Trades:
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
if self.config['runmode'] in ('live', 'dry_run'):
|
if self.config['runmode'].value in ('live', 'dry_run'):
|
||||||
total_stakes = Trade.total_open_trades_stakes()
|
total_stakes = Trade.total_open_trades_stakes()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -442,7 +442,7 @@ Retrieve performance per pair.
|
||||||
Returns a List of dicts per pair.
|
Returns a List of dicts per pair.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
if self.config['runmode'] in ('live', 'dry_run'):
|
if self.config['runmode'].value in ('live', 'dry_run'):
|
||||||
performance = Trade.get_overall_performance()
|
performance = Trade.get_overall_performance()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -487,7 +487,7 @@ from datetime import timedelta, datetime, timezone
|
||||||
# --------
|
# --------
|
||||||
|
|
||||||
# Within populate indicators (or populate_buy):
|
# Within populate indicators (or populate_buy):
|
||||||
if self.config['runmode'] in ('live', 'dry_run'):
|
if self.config['runmode'].value in ('live', 'dry_run'):
|
||||||
# fetch closed trades for the last 2 days
|
# fetch closed trades for the last 2 days
|
||||||
trades = Trade.get_trades([Trade.pair == metadata['pair'],
|
trades = Trade.get_trades([Trade.pair == metadata['pair'],
|
||||||
Trade.open_date > datetime.utcnow() - timedelta(days=2),
|
Trade.open_date > datetime.utcnow() - timedelta(days=2),
|
||||||
|
@ -532,6 +532,27 @@ If you want to use a strategy from a different directory you can pass `--strateg
|
||||||
freqtrade trade --strategy AwesomeStrategy --strategy-path /some/directory
|
freqtrade trade --strategy AwesomeStrategy --strategy-path /some/directory
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Derived strategies
|
||||||
|
|
||||||
|
The strategies can be derived from other strategies. This avoids duplication of your custom strategy code. You can use this technique to override small parts of your main strategy, leaving the rest untouched:
|
||||||
|
|
||||||
|
``` python
|
||||||
|
class MyAwesomeStrategy(IStrategy):
|
||||||
|
...
|
||||||
|
stoploss = 0.13
|
||||||
|
trailing_stop = False
|
||||||
|
# All other attributes and methods are here as they
|
||||||
|
# should be in any custom strategy...
|
||||||
|
...
|
||||||
|
|
||||||
|
class MyAwesomeStrategy2(MyAwesomeStrategy):
|
||||||
|
# Override something
|
||||||
|
stoploss = 0.08
|
||||||
|
trailing_stop = True
|
||||||
|
```
|
||||||
|
|
||||||
|
Both attributes and methods may be overriden, altering behavior of the original strategy in a way you need.
|
||||||
|
|
||||||
### Common mistakes when developing strategies
|
### Common mistakes when developing strategies
|
||||||
|
|
||||||
Backtesting analyzes the whole time-range at once for performance reasons. Because of this, strategy authors need to make sure that strategies do not look-ahead into the future.
|
Backtesting analyzes the whole time-range at once for performance reasons. Because of this, strategy authors need to make sure that strategies do not look-ahead into the future.
|
||||||
|
|
|
@ -1,24 +1,28 @@
|
||||||
# Strategy analysis example
|
# Strategy analysis example
|
||||||
|
|
||||||
Debugging a strategy can be time-consuming. FreqTrade offers helper functions to visualize raw data.
|
Debugging a strategy can be time-consuming. Freqtrade offers helper functions to visualize raw data.
|
||||||
|
The following assumes you work with SampleStrategy, data for 5m timeframe from Binance and have downloaded them into the data directory in the default location.
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from freqtrade.configuration import Configuration
|
||||||
|
|
||||||
# Customize these according to your needs.
|
# Customize these according to your needs.
|
||||||
|
|
||||||
|
# Initialize empty configuration object
|
||||||
|
config = Configuration.from_files([])
|
||||||
|
# Optionally, use existing configuration file
|
||||||
|
# config = Configuration.from_files(["config.json"])
|
||||||
|
|
||||||
# Define some constants
|
# Define some constants
|
||||||
timeframe = "5m"
|
config["ticker_interval"] = "5m"
|
||||||
# Name of the strategy class
|
# Name of the strategy class
|
||||||
strategy_name = 'SampleStrategy'
|
config["strategy"] = "SampleStrategy"
|
||||||
# Path to user data
|
|
||||||
user_data_dir = Path('user_data')
|
|
||||||
# Location of the strategy
|
|
||||||
strategy_location = user_data_dir / 'strategies'
|
|
||||||
# Location of the data
|
# Location of the data
|
||||||
data_location = Path(user_data_dir, 'data', 'binance')
|
data_location = Path(config['user_data_dir'], 'data', 'binance')
|
||||||
# Pair to analyze - Only use one pair here
|
# Pair to analyze - Only use one pair here
|
||||||
pair = "BTC_USDT"
|
pair = "BTC_USDT"
|
||||||
```
|
```
|
||||||
|
@ -29,7 +33,7 @@ pair = "BTC_USDT"
|
||||||
from freqtrade.data.history import load_pair_history
|
from freqtrade.data.history import load_pair_history
|
||||||
|
|
||||||
candles = load_pair_history(datadir=data_location,
|
candles = load_pair_history(datadir=data_location,
|
||||||
timeframe=timeframe,
|
timeframe=config["ticker_interval"],
|
||||||
pair=pair)
|
pair=pair)
|
||||||
|
|
||||||
# Confirm success
|
# Confirm success
|
||||||
|
@ -44,9 +48,7 @@ candles.head()
|
||||||
```python
|
```python
|
||||||
# Load strategy using values set above
|
# Load strategy using values set above
|
||||||
from freqtrade.resolvers import StrategyResolver
|
from freqtrade.resolvers import StrategyResolver
|
||||||
strategy = StrategyResolver.load_strategy({'strategy': strategy_name,
|
strategy = StrategyResolver.load_strategy(config)
|
||||||
'user_data_dir': user_data_dir,
|
|
||||||
'strategy_path': strategy_location})
|
|
||||||
|
|
||||||
# Generate buy/sell signals using strategy
|
# Generate buy/sell signals using strategy
|
||||||
df = strategy.analyze_ticker(candles, {'pair': pair})
|
df = strategy.analyze_ticker(candles, {'pair': pair})
|
||||||
|
@ -86,7 +88,7 @@ Analyze a trades dataframe (also used below for plotting)
|
||||||
from freqtrade.data.btanalysis import load_backtest_data
|
from freqtrade.data.btanalysis import load_backtest_data
|
||||||
|
|
||||||
# Load backtest results
|
# Load backtest results
|
||||||
trades = load_backtest_data(user_data_dir / "backtest_results/backtest-result.json")
|
trades = load_backtest_data(config["user_data_dir"] / "backtest_results/backtest-result.json")
|
||||||
|
|
||||||
# Show value-counts per pair
|
# Show value-counts per pair
|
||||||
trades.groupby("pair")["sell_reason"].value_counts()
|
trades.groupby("pair")["sell_reason"].value_counts()
|
||||||
|
|
|
@ -55,7 +55,7 @@ official commands. You can ask at any moment for help with `/help`.
|
||||||
| `/reload_conf` | | Reloads the configuration file
|
| `/reload_conf` | | Reloads the configuration file
|
||||||
| `/show_config` | | Shows part of the current configuration with relevant settings to operation
|
| `/show_config` | | Shows part of the current configuration with relevant settings to operation
|
||||||
| `/status` | | Lists all open trades
|
| `/status` | | Lists all open trades
|
||||||
| `/status table` | | List all open trades in a table format
|
| `/status table` | | List all open trades in a table format. Pending buy orders are marked with an asterisk (*) Pending sell orders are marked with a double asterisk (**)
|
||||||
| `/count` | | Displays number of trades used and available
|
| `/count` | | Displays number of trades used and available
|
||||||
| `/profit` | | Display a summary of your profit/loss from close trades and some stats about your performance
|
| `/profit` | | Display a summary of your profit/loss from close trades and some stats about your performance
|
||||||
| `/forcesell <trade_id>` | | Instantly sells the given trade (Ignoring `minimum_roi`).
|
| `/forcesell <trade_id>` | | Instantly sells the given trade (Ignoring `minimum_roi`).
|
||||||
|
|
142
docs/utils.md
142
docs/utils.md
|
@ -36,6 +36,38 @@ optional arguments:
|
||||||
└── sample_strategy.py
|
└── sample_strategy.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Create new config
|
||||||
|
|
||||||
|
Creates a new configuration file, asking some questions which are important selections for a configuration.
|
||||||
|
|
||||||
|
```
|
||||||
|
usage: freqtrade new-config [-h] [-c PATH]
|
||||||
|
|
||||||
|
optional arguments:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
-c PATH, --config PATH
|
||||||
|
Specify configuration file (default: `config.json`). Multiple --config options may be used. Can be set to `-`
|
||||||
|
to read config from stdin.
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! Warning
|
||||||
|
Only vital questions are asked. Freqtrade offers a lot more configuration possibilities, which are listed in the [Configuration documentation](configuration.md#configuration-parameters)
|
||||||
|
|
||||||
|
### Create config examples
|
||||||
|
|
||||||
|
```
|
||||||
|
$ freqtrade new-config --config config_binance.json
|
||||||
|
|
||||||
|
? Do you want to enable Dry-run (simulated trades)? Yes
|
||||||
|
? Please insert your stake currency: BTC
|
||||||
|
? Please insert your stake amount: 0.05
|
||||||
|
? Please insert max_open_trades (Integer or 'unlimited'): 5
|
||||||
|
? Please insert your ticker interval: 15m
|
||||||
|
? Please insert your display Currency (for reporting): USD
|
||||||
|
? Select exchange binance
|
||||||
|
? Do you want to enable Telegram? No
|
||||||
|
```
|
||||||
|
|
||||||
## Create new strategy
|
## Create new strategy
|
||||||
|
|
||||||
Creates a new strategy from a template similar to SampleStrategy.
|
Creates a new strategy from a template similar to SampleStrategy.
|
||||||
|
@ -112,38 +144,47 @@ freqtrade new-hyperopt --userdir ~/.freqtrade/ --hyperopt AwesomeHyperopt
|
||||||
|
|
||||||
Use the `list-strategies` subcommand to see all strategies in one particular directory and the `list-hyperopts` subcommand to list custom Hyperopts.
|
Use the `list-strategies` subcommand to see all strategies in one particular directory and the `list-hyperopts` subcommand to list custom Hyperopts.
|
||||||
|
|
||||||
|
These subcommands are useful for finding problems in your environment with loading strategies or hyperopt classes: modules with strategies or hyperopt classes that contain errors and failed to load are printed in red (LOAD FAILED), while strategies or hyperopt classes with duplicate names are printed in yellow (DUPLICATE NAME).
|
||||||
|
|
||||||
```
|
```
|
||||||
freqtrade list-strategies --help
|
usage: freqtrade list-strategies [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
||||||
usage: freqtrade list-strategies [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--strategy-path PATH] [-1]
|
[-d PATH] [--userdir PATH]
|
||||||
|
[--strategy-path PATH] [-1] [--no-color]
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
--strategy-path PATH Specify additional strategy lookup path.
|
--strategy-path PATH Specify additional strategy lookup path.
|
||||||
-1, --one-column Print output in one column.
|
-1, --one-column Print output in one column.
|
||||||
|
--no-color Disable colorization of hyperopt results. May be
|
||||||
|
useful if you are redirecting output to a file.
|
||||||
|
|
||||||
Common arguments:
|
Common arguments:
|
||||||
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
--logfile FILE Log to the file specified. Special values are: 'syslog', 'journald'. See the documentation for more details.
|
--logfile FILE Log to the file specified. Special values are:
|
||||||
|
'syslog', 'journald'. See the documentation for more
|
||||||
|
details.
|
||||||
-V, --version show program's version number and exit
|
-V, --version show program's version number and exit
|
||||||
-c PATH, --config PATH
|
-c PATH, --config PATH
|
||||||
Specify configuration file (default: `config.json`). Multiple --config options may be used. Can be set to `-`
|
Specify configuration file (default: `config.json`).
|
||||||
to read config from stdin.
|
Multiple --config options may be used. Can be set to
|
||||||
|
`-` to read config from stdin.
|
||||||
-d PATH, --datadir PATH
|
-d PATH, --datadir PATH
|
||||||
Path to directory with historical backtesting data.
|
Path to directory with historical backtesting data.
|
||||||
--userdir PATH, --user-data-dir PATH
|
--userdir PATH, --user-data-dir PATH
|
||||||
Path to userdata directory.
|
Path to userdata directory.
|
||||||
```
|
```
|
||||||
```
|
```
|
||||||
freqtrade list-hyperopts --help
|
|
||||||
usage: freqtrade list-hyperopts [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
usage: freqtrade list-hyperopts [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
||||||
[-d PATH] [--userdir PATH]
|
[-d PATH] [--userdir PATH]
|
||||||
[--hyperopt-path PATH] [-1]
|
[--hyperopt-path PATH] [-1] [--no-color]
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
--hyperopt-path PATH Specify additional lookup path for Hyperopt and
|
--hyperopt-path PATH Specify additional lookup path for Hyperopt and
|
||||||
Hyperopt Loss functions.
|
Hyperopt Loss functions.
|
||||||
-1, --one-column Print output in one column.
|
-1, --one-column Print output in one column.
|
||||||
|
--no-color Disable colorization of hyperopt results. May be
|
||||||
|
useful if you are redirecting output to a file.
|
||||||
|
|
||||||
Common arguments:
|
Common arguments:
|
||||||
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
|
@ -220,20 +261,31 @@ All exchanges supported by the ccxt library: _1btcxe, acx, adara, allcoin, anxpr
|
||||||
Use the `list-timeframes` subcommand to see the list of ticker intervals (timeframes) available for the exchange.
|
Use the `list-timeframes` subcommand to see the list of ticker intervals (timeframes) available for the exchange.
|
||||||
|
|
||||||
```
|
```
|
||||||
usage: freqtrade list-timeframes [-h] [--exchange EXCHANGE] [-1]
|
usage: freqtrade list-timeframes [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--exchange EXCHANGE] [-1]
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
--exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no
|
--exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no config is provided.
|
||||||
config is provided.
|
-1, --one-column Print output in one column.
|
||||||
-1, --one-column Print output in one column.
|
|
||||||
|
Common arguments:
|
||||||
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
|
--logfile FILE Log to the file specified. Special values are: 'syslog', 'journald'. See the documentation for more details.
|
||||||
|
-V, --version show program's version number and exit
|
||||||
|
-c PATH, --config PATH
|
||||||
|
Specify configuration file (default: `config.json`). Multiple --config options may be used. Can be set to `-`
|
||||||
|
to read config from stdin.
|
||||||
|
-d PATH, --datadir PATH
|
||||||
|
Path to directory with historical backtesting data.
|
||||||
|
--userdir PATH, --user-data-dir PATH
|
||||||
|
Path to userdata directory.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
* Example: see the timeframes for the 'binance' exchange, set in the configuration file:
|
* Example: see the timeframes for the 'binance' exchange, set in the configuration file:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ freqtrade -c config_binance.json list-timeframes
|
$ freqtrade list-timeframes -c config_binance.json
|
||||||
...
|
...
|
||||||
Timeframes available for the exchange `binance`: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M
|
Timeframes available for the exchange `binance`: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M
|
||||||
```
|
```
|
||||||
|
@ -257,14 +309,16 @@ You can print info about any pair/market with these subcommands - and you can fi
|
||||||
These subcommands have same usage and same set of available options:
|
These subcommands have same usage and same set of available options:
|
||||||
|
|
||||||
```
|
```
|
||||||
usage: freqtrade list-markets [-h] [--exchange EXCHANGE] [--print-list]
|
usage: freqtrade list-markets [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
||||||
[--print-json] [-1] [--print-csv]
|
[-d PATH] [--userdir PATH] [--exchange EXCHANGE]
|
||||||
|
[--print-list] [--print-json] [-1] [--print-csv]
|
||||||
[--base BASE_CURRENCY [BASE_CURRENCY ...]]
|
[--base BASE_CURRENCY [BASE_CURRENCY ...]]
|
||||||
[--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]]
|
[--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]]
|
||||||
[-a]
|
[-a]
|
||||||
|
|
||||||
usage: freqtrade list-pairs [-h] [--exchange EXCHANGE] [--print-list]
|
usage: freqtrade list-pairs [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
||||||
[--print-json] [-1] [--print-csv]
|
[-d PATH] [--userdir PATH] [--exchange EXCHANGE]
|
||||||
|
[--print-list] [--print-json] [-1] [--print-csv]
|
||||||
[--base BASE_CURRENCY [BASE_CURRENCY ...]]
|
[--base BASE_CURRENCY [BASE_CURRENCY ...]]
|
||||||
[--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]] [-a]
|
[--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]] [-a]
|
||||||
|
|
||||||
|
@ -283,6 +337,22 @@ optional arguments:
|
||||||
Specify quote currency(-ies). Space-separated list.
|
Specify quote currency(-ies). Space-separated list.
|
||||||
-a, --all Print all pairs or market symbols. By default only
|
-a, --all Print all pairs or market symbols. By default only
|
||||||
active ones are shown.
|
active ones are shown.
|
||||||
|
|
||||||
|
Common arguments:
|
||||||
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
|
--logfile FILE Log to the file specified. Special values are:
|
||||||
|
'syslog', 'journald'. See the documentation for more
|
||||||
|
details.
|
||||||
|
-V, --version show program's version number and exit
|
||||||
|
-c PATH, --config PATH
|
||||||
|
Specify configuration file (default: `config.json`).
|
||||||
|
Multiple --config options may be used. Can be set to
|
||||||
|
`-` to read config from stdin.
|
||||||
|
-d PATH, --datadir PATH
|
||||||
|
Path to directory with historical backtesting data.
|
||||||
|
--userdir PATH, --user-data-dir PATH
|
||||||
|
Path to userdata directory.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, only active pairs/markets are shown. Active pairs/markets are those that can currently be traded
|
By default, only active pairs/markets are shown. Active pairs/markets are those that can currently be traded
|
||||||
|
@ -304,7 +374,7 @@ $ freqtrade list-pairs --quote USD --print-json
|
||||||
human-readable list with summary:
|
human-readable list with summary:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ freqtrade -c config_binance.json list-pairs --all --base BTC ETH --quote USDT USD --print-list
|
$ freqtrade list-pairs -c config_binance.json --all --base BTC ETH --quote USDT USD --print-list
|
||||||
```
|
```
|
||||||
|
|
||||||
* Print all markets on exchange "Kraken", in the tabular format:
|
* Print all markets on exchange "Kraken", in the tabular format:
|
||||||
|
@ -352,17 +422,49 @@ You can list the hyperoptimization epochs the Hyperopt module evaluated previous
|
||||||
```
|
```
|
||||||
usage: freqtrade hyperopt-list [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
usage: freqtrade hyperopt-list [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
||||||
[-d PATH] [--userdir PATH] [--best]
|
[-d PATH] [--userdir PATH] [--best]
|
||||||
[--profitable] [--no-color] [--print-json]
|
[--profitable] [--min-trades INT]
|
||||||
[--no-details]
|
[--max-trades INT] [--min-avg-time FLOAT]
|
||||||
|
[--max-avg-time FLOAT] [--min-avg-profit FLOAT]
|
||||||
|
[--max-avg-profit FLOAT]
|
||||||
|
[--min-total-profit FLOAT]
|
||||||
|
[--max-total-profit FLOAT] [--no-color]
|
||||||
|
[--print-json] [--no-details]
|
||||||
|
|
||||||
optional arguments:
|
optional arguments:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
--best Select only best epochs.
|
--best Select only best epochs.
|
||||||
--profitable Select only profitable epochs.
|
--profitable Select only profitable epochs.
|
||||||
|
--min-trades INT Select epochs with more than INT trades.
|
||||||
|
--max-trades INT Select epochs with less than INT trades.
|
||||||
|
--min-avg-time FLOAT Select epochs on above average time.
|
||||||
|
--max-avg-time FLOAT Select epochs on under average time.
|
||||||
|
--min-avg-profit FLOAT
|
||||||
|
Select epochs on above average profit.
|
||||||
|
--max-avg-profit FLOAT
|
||||||
|
Select epochs on below average profit.
|
||||||
|
--min-total-profit FLOAT
|
||||||
|
Select epochs on above total profit.
|
||||||
|
--max-total-profit FLOAT
|
||||||
|
Select epochs on below total profit.
|
||||||
--no-color Disable colorization of hyperopt results. May be
|
--no-color Disable colorization of hyperopt results. May be
|
||||||
useful if you are redirecting output to a file.
|
useful if you are redirecting output to a file.
|
||||||
--print-json Print best result detailization in JSON format.
|
--print-json Print best result detailization in JSON format.
|
||||||
--no-details Do not print best epoch details.
|
--no-details Do not print best epoch details.
|
||||||
|
|
||||||
|
Common arguments:
|
||||||
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
|
--logfile FILE Log to the file specified. Special values are:
|
||||||
|
'syslog', 'journald'. See the documentation for more
|
||||||
|
details.
|
||||||
|
-V, --version show program's version number and exit
|
||||||
|
-c PATH, --config PATH
|
||||||
|
Specify configuration file (default: `config.json`).
|
||||||
|
Multiple --config options may be used. Can be set to
|
||||||
|
`-` to read config from stdin.
|
||||||
|
-d PATH, --datadir PATH
|
||||||
|
Path to directory with historical backtesting data.
|
||||||
|
--userdir PATH, --user-data-dir PATH
|
||||||
|
Path to userdata directory.
|
||||||
```
|
```
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
|
@ -15,11 +15,21 @@ Sample configuration (tested using IFTTT).
|
||||||
"value2": "limit {limit:8f}",
|
"value2": "limit {limit:8f}",
|
||||||
"value3": "{stake_amount:8f} {stake_currency}"
|
"value3": "{stake_amount:8f} {stake_currency}"
|
||||||
},
|
},
|
||||||
|
"webhookbuycancel": {
|
||||||
|
"value1": "Cancelling Open Buy Order for {pair}",
|
||||||
|
"value2": "limit {limit:8f}",
|
||||||
|
"value3": "{stake_amount:8f} {stake_currency}"
|
||||||
|
},
|
||||||
"webhooksell": {
|
"webhooksell": {
|
||||||
"value1": "Selling {pair}",
|
"value1": "Selling {pair}",
|
||||||
"value2": "limit {limit:8f}",
|
"value2": "limit {limit:8f}",
|
||||||
"value3": "profit: {profit_amount:8f} {stake_currency}"
|
"value3": "profit: {profit_amount:8f} {stake_currency}"
|
||||||
},
|
},
|
||||||
|
"webhooksellcancel": {
|
||||||
|
"value1": "Cancelling Open Sell Order for {pair}",
|
||||||
|
"value2": "limit {limit:8f}",
|
||||||
|
"value3": "profit: {profit_amount:8f} {stake_currency}"
|
||||||
|
},
|
||||||
"webhookstatus": {
|
"webhookstatus": {
|
||||||
"value1": "Status: {status}",
|
"value1": "Status: {status}",
|
||||||
"value2": "",
|
"value2": "",
|
||||||
|
@ -40,10 +50,29 @@ Possible parameters are:
|
||||||
* `exchange`
|
* `exchange`
|
||||||
* `pair`
|
* `pair`
|
||||||
* `limit`
|
* `limit`
|
||||||
|
* `amount`
|
||||||
|
* `open_date`
|
||||||
* `stake_amount`
|
* `stake_amount`
|
||||||
* `stake_currency`
|
* `stake_currency`
|
||||||
* `fiat_currency`
|
* `fiat_currency`
|
||||||
* `order_type`
|
* `order_type`
|
||||||
|
* `current_rate`
|
||||||
|
|
||||||
|
### Webhookbuycancel
|
||||||
|
|
||||||
|
The fields in `webhook.webhookbuycancel` are filled when the bot cancels a buy order. Parameters are filled using string.format.
|
||||||
|
Possible parameters are:
|
||||||
|
|
||||||
|
* `exchange`
|
||||||
|
* `pair`
|
||||||
|
* `limit`
|
||||||
|
* `amount`
|
||||||
|
* `open_date`
|
||||||
|
* `stake_amount`
|
||||||
|
* `stake_currency`
|
||||||
|
* `fiat_currency`
|
||||||
|
* `order_type`
|
||||||
|
* `current_rate`
|
||||||
|
|
||||||
### Webhooksell
|
### Webhooksell
|
||||||
|
|
||||||
|
@ -66,6 +95,27 @@ Possible parameters are:
|
||||||
* `open_date`
|
* `open_date`
|
||||||
* `close_date`
|
* `close_date`
|
||||||
|
|
||||||
|
### Webhooksellcancel
|
||||||
|
|
||||||
|
The fields in `webhook.webhooksellcancel` are filled when the bot cancels a sell order. Parameters are filled using string.format.
|
||||||
|
Possible parameters are:
|
||||||
|
|
||||||
|
* `exchange`
|
||||||
|
* `pair`
|
||||||
|
* `gain`
|
||||||
|
* `limit`
|
||||||
|
* `amount`
|
||||||
|
* `open_rate`
|
||||||
|
* `current_rate`
|
||||||
|
* `profit_amount`
|
||||||
|
* `profit_percent`
|
||||||
|
* `stake_currency`
|
||||||
|
* `fiat_currency`
|
||||||
|
* `sell_reason`
|
||||||
|
* `order_type`
|
||||||
|
* `open_date`
|
||||||
|
* `close_date`
|
||||||
|
|
||||||
### Webhookstatus
|
### Webhookstatus
|
||||||
|
|
||||||
The fields in `webhook.webhookstatus` are used for regular status messages (Started / Stopped / ...). Parameters are filled using string.format.
|
The fields in `webhook.webhookstatus` are used for regular status messages (Started / Stopped / ...). Parameters are filled using string.format.
|
||||||
|
|
|
@ -7,6 +7,7 @@ Note: Be careful with file-scoped imports in these subfiles.
|
||||||
as they are parsed on startup, nothing containing optional modules should be loaded.
|
as they are parsed on startup, nothing containing optional modules should be loaded.
|
||||||
"""
|
"""
|
||||||
from freqtrade.commands.arguments import Arguments
|
from freqtrade.commands.arguments import Arguments
|
||||||
|
from freqtrade.commands.build_config_commands import start_new_config
|
||||||
from freqtrade.commands.data_commands import start_download_data
|
from freqtrade.commands.data_commands import start_download_data
|
||||||
from freqtrade.commands.deploy_commands import (start_create_userdir,
|
from freqtrade.commands.deploy_commands import (start_create_userdir,
|
||||||
start_new_hyperopt,
|
start_new_hyperopt,
|
||||||
|
|
|
@ -6,8 +6,8 @@ from functools import partial
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from freqtrade import constants
|
|
||||||
from freqtrade.commands.cli_options import AVAILABLE_CLI_OPTIONS
|
from freqtrade.commands.cli_options import AVAILABLE_CLI_OPTIONS
|
||||||
|
from freqtrade.constants import DEFAULT_CONFIG
|
||||||
|
|
||||||
ARGS_COMMON = ["verbosity", "logfile", "version", "config", "datadir", "user_data_dir"]
|
ARGS_COMMON = ["verbosity", "logfile", "version", "config", "datadir", "user_data_dir"]
|
||||||
|
|
||||||
|
@ -30,9 +30,9 @@ ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
|
||||||
|
|
||||||
ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"]
|
ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"]
|
||||||
|
|
||||||
ARGS_LIST_STRATEGIES = ["strategy_path", "print_one_column"]
|
ARGS_LIST_STRATEGIES = ["strategy_path", "print_one_column", "print_colorized"]
|
||||||
|
|
||||||
ARGS_LIST_HYPEROPTS = ["hyperopt_path", "print_one_column"]
|
ARGS_LIST_HYPEROPTS = ["hyperopt_path", "print_one_column", "print_colorized"]
|
||||||
|
|
||||||
ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all"]
|
ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all"]
|
||||||
|
|
||||||
|
@ -45,6 +45,8 @@ ARGS_TEST_PAIRLIST = ["config", "quote_currencies", "print_one_column", "list_pa
|
||||||
|
|
||||||
ARGS_CREATE_USERDIR = ["user_data_dir", "reset"]
|
ARGS_CREATE_USERDIR = ["user_data_dir", "reset"]
|
||||||
|
|
||||||
|
ARGS_BUILD_CONFIG = ["config"]
|
||||||
|
|
||||||
ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"]
|
ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"]
|
||||||
|
|
||||||
ARGS_BUILD_HYPEROPT = ["user_data_dir", "hyperopt", "template"]
|
ARGS_BUILD_HYPEROPT = ["user_data_dir", "hyperopt", "template"]
|
||||||
|
@ -59,8 +61,12 @@ ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit",
|
||||||
ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url",
|
ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url",
|
||||||
"trade_source", "ticker_interval"]
|
"trade_source", "ticker_interval"]
|
||||||
|
|
||||||
ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable", "print_colorized",
|
ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable",
|
||||||
"print_json", "hyperopt_list_no_details"]
|
"hyperopt_list_min_trades", "hyperopt_list_max_trades",
|
||||||
|
"hyperopt_list_min_avg_time", "hyperopt_list_max_avg_time",
|
||||||
|
"hyperopt_list_min_avg_profit", "hyperopt_list_max_avg_profit",
|
||||||
|
"hyperopt_list_min_total_profit", "hyperopt_list_max_total_profit",
|
||||||
|
"print_colorized", "print_json", "hyperopt_list_no_details"]
|
||||||
|
|
||||||
ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index",
|
ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index",
|
||||||
"print_json", "hyperopt_show_no_header"]
|
"print_json", "hyperopt_show_no_header"]
|
||||||
|
@ -101,10 +107,23 @@ class Arguments:
|
||||||
# Workaround issue in argparse with action='append' and default value
|
# Workaround issue in argparse with action='append' and default value
|
||||||
# (see https://bugs.python.org/issue16399)
|
# (see https://bugs.python.org/issue16399)
|
||||||
# Allow no-config for certain commands (like downloading / plotting)
|
# Allow no-config for certain commands (like downloading / plotting)
|
||||||
if ('config' in parsed_arg and parsed_arg.config is None and
|
if ('config' in parsed_arg and parsed_arg.config is None):
|
||||||
((Path.cwd() / constants.DEFAULT_CONFIG).is_file() or
|
conf_required = ('command' in parsed_arg and parsed_arg.command in NO_CONF_REQURIED)
|
||||||
not ('command' in parsed_arg and parsed_arg.command in NO_CONF_REQURIED))):
|
|
||||||
parsed_arg.config = [constants.DEFAULT_CONFIG]
|
if 'user_data_dir' in parsed_arg and parsed_arg.user_data_dir is not None:
|
||||||
|
user_dir = parsed_arg.user_data_dir
|
||||||
|
else:
|
||||||
|
# Default case
|
||||||
|
user_dir = 'user_data'
|
||||||
|
# Try loading from "user_data/config.json"
|
||||||
|
cfgfile = Path(user_dir) / DEFAULT_CONFIG
|
||||||
|
if cfgfile.is_file():
|
||||||
|
parsed_arg.config = [str(cfgfile)]
|
||||||
|
else:
|
||||||
|
# Else use "config.json".
|
||||||
|
cfgfile = Path.cwd() / DEFAULT_CONFIG
|
||||||
|
if cfgfile.is_file() or not conf_required:
|
||||||
|
parsed_arg.config = [DEFAULT_CONFIG]
|
||||||
|
|
||||||
return parsed_arg
|
return parsed_arg
|
||||||
|
|
||||||
|
@ -136,7 +155,7 @@ class Arguments:
|
||||||
start_hyperopt_list, start_hyperopt_show,
|
start_hyperopt_list, start_hyperopt_show,
|
||||||
start_list_exchanges, start_list_hyperopts,
|
start_list_exchanges, start_list_hyperopts,
|
||||||
start_list_markets, start_list_strategies,
|
start_list_markets, start_list_strategies,
|
||||||
start_list_timeframes,
|
start_list_timeframes, start_new_config,
|
||||||
start_new_hyperopt, start_new_strategy,
|
start_new_hyperopt, start_new_strategy,
|
||||||
start_plot_dataframe, start_plot_profit,
|
start_plot_dataframe, start_plot_profit,
|
||||||
start_backtesting, start_hyperopt, start_edge,
|
start_backtesting, start_hyperopt, start_edge,
|
||||||
|
@ -180,6 +199,12 @@ class Arguments:
|
||||||
create_userdir_cmd.set_defaults(func=start_create_userdir)
|
create_userdir_cmd.set_defaults(func=start_create_userdir)
|
||||||
self._build_args(optionlist=ARGS_CREATE_USERDIR, parser=create_userdir_cmd)
|
self._build_args(optionlist=ARGS_CREATE_USERDIR, parser=create_userdir_cmd)
|
||||||
|
|
||||||
|
# add new-config subcommand
|
||||||
|
build_config_cmd = subparsers.add_parser('new-config',
|
||||||
|
help="Create new config")
|
||||||
|
build_config_cmd.set_defaults(func=start_new_config)
|
||||||
|
self._build_args(optionlist=ARGS_BUILD_CONFIG, parser=build_config_cmd)
|
||||||
|
|
||||||
# add new-strategy subcommand
|
# add new-strategy subcommand
|
||||||
build_strategy_cmd = subparsers.add_parser('new-strategy',
|
build_strategy_cmd = subparsers.add_parser('new-strategy',
|
||||||
help="Create new strategy")
|
help="Create new strategy")
|
||||||
|
|
193
freqtrade/commands/build_config_commands.py
Normal file
193
freqtrade/commands/build_config_commands.py
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from questionary import Separator, prompt
|
||||||
|
|
||||||
|
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
|
||||||
|
from freqtrade.exchange import available_exchanges, MAP_EXCHANGE_CHILDCLASS
|
||||||
|
from freqtrade.misc import render_template
|
||||||
|
from freqtrade.exceptions import OperationalException
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_is_int(val):
|
||||||
|
try:
|
||||||
|
_ = int(val)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def validate_is_float(val):
|
||||||
|
try:
|
||||||
|
_ = float(val)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def ask_user_overwrite(config_path: Path) -> bool:
|
||||||
|
questions = [
|
||||||
|
{
|
||||||
|
"type": "confirm",
|
||||||
|
"name": "overwrite",
|
||||||
|
"message": f"File {config_path} already exists. Overwrite?",
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
answers = prompt(questions)
|
||||||
|
return answers['overwrite']
|
||||||
|
|
||||||
|
|
||||||
|
def ask_user_config() -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Ask user a few questions to build the configuration.
|
||||||
|
Interactive questions built using https://github.com/tmbo/questionary
|
||||||
|
:returns: Dict with keys to put into template
|
||||||
|
"""
|
||||||
|
questions = [
|
||||||
|
{
|
||||||
|
"type": "confirm",
|
||||||
|
"name": "dry_run",
|
||||||
|
"message": "Do you want to enable Dry-run (simulated trades)?",
|
||||||
|
"default": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"name": "stake_currency",
|
||||||
|
"message": "Please insert your stake currency:",
|
||||||
|
"default": 'BTC',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"name": "stake_amount",
|
||||||
|
"message": "Please insert your stake amount:",
|
||||||
|
"default": "0.01",
|
||||||
|
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"name": "max_open_trades",
|
||||||
|
"message": f"Please insert max_open_trades (Integer or '{UNLIMITED_STAKE_AMOUNT}'):",
|
||||||
|
"default": "3",
|
||||||
|
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_int(val)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"name": "ticker_interval",
|
||||||
|
"message": "Please insert your ticker interval:",
|
||||||
|
"default": "5m",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"name": "fiat_display_currency",
|
||||||
|
"message": "Please insert your display Currency (for reporting):",
|
||||||
|
"default": 'USD',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "select",
|
||||||
|
"name": "exchange_name",
|
||||||
|
"message": "Select exchange",
|
||||||
|
"choices": [
|
||||||
|
"binance",
|
||||||
|
"binanceje",
|
||||||
|
"binanceus",
|
||||||
|
"bittrex",
|
||||||
|
"kraken",
|
||||||
|
Separator(),
|
||||||
|
"other",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "autocomplete",
|
||||||
|
"name": "exchange_name",
|
||||||
|
"message": "Type your exchange name (Must be supported by ccxt)",
|
||||||
|
"choices": available_exchanges(),
|
||||||
|
"when": lambda x: x["exchange_name"] == 'other'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "password",
|
||||||
|
"name": "exchange_key",
|
||||||
|
"message": "Insert Exchange Key",
|
||||||
|
"when": lambda x: not x['dry_run']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "password",
|
||||||
|
"name": "exchange_secret",
|
||||||
|
"message": "Insert Exchange Secret",
|
||||||
|
"when": lambda x: not x['dry_run']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "confirm",
|
||||||
|
"name": "telegram",
|
||||||
|
"message": "Do you want to enable Telegram?",
|
||||||
|
"default": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "password",
|
||||||
|
"name": "telegram_token",
|
||||||
|
"message": "Insert Telegram token",
|
||||||
|
"when": lambda x: x['telegram']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"name": "telegram_chat_id",
|
||||||
|
"message": "Insert Telegram chat id",
|
||||||
|
"when": lambda x: x['telegram']
|
||||||
|
},
|
||||||
|
]
|
||||||
|
answers = prompt(questions)
|
||||||
|
|
||||||
|
if not answers:
|
||||||
|
# Interrupted questionary sessions return an empty dict.
|
||||||
|
raise OperationalException("User interrupted interactive questions.")
|
||||||
|
|
||||||
|
return answers
|
||||||
|
|
||||||
|
|
||||||
|
def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None:
|
||||||
|
"""
|
||||||
|
Applies selections to the template and writes the result to config_path
|
||||||
|
:param config_path: Path object for new config file. Should not exist yet
|
||||||
|
:param selecions: Dict containing selections taken by the user.
|
||||||
|
"""
|
||||||
|
from jinja2.exceptions import TemplateNotFound
|
||||||
|
try:
|
||||||
|
exchange_template = MAP_EXCHANGE_CHILDCLASS.get(
|
||||||
|
selections['exchange_name'], selections['exchange_name'])
|
||||||
|
|
||||||
|
selections['exchange'] = render_template(
|
||||||
|
templatefile=f"subtemplates/exchange_{exchange_template}.j2",
|
||||||
|
arguments=selections
|
||||||
|
)
|
||||||
|
except TemplateNotFound:
|
||||||
|
selections['exchange'] = render_template(
|
||||||
|
templatefile=f"subtemplates/exchange_generic.j2",
|
||||||
|
arguments=selections
|
||||||
|
)
|
||||||
|
|
||||||
|
config_text = render_template(templatefile='base_config.json.j2',
|
||||||
|
arguments=selections)
|
||||||
|
|
||||||
|
logger.info(f"Writing config to `{config_path}`.")
|
||||||
|
config_path.write_text(config_text)
|
||||||
|
|
||||||
|
|
||||||
|
def start_new_config(args: Dict[str, Any]) -> None:
|
||||||
|
"""
|
||||||
|
Create a new strategy from a template
|
||||||
|
Asking the user questions to fill out the templateaccordingly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
config_path = Path(args['config'][0])
|
||||||
|
if config_path.exists():
|
||||||
|
overwrite = ask_user_overwrite(config_path)
|
||||||
|
if overwrite:
|
||||||
|
config_path.unlink()
|
||||||
|
else:
|
||||||
|
raise OperationalException(
|
||||||
|
f"Configuration file `{config_path}` already exists. "
|
||||||
|
"Please delete it or use a different configuration file name.")
|
||||||
|
selections = ask_user_config()
|
||||||
|
deploy_new_config(config_path, selections)
|
|
@ -59,7 +59,8 @@ AVAILABLE_CLI_OPTIONS = {
|
||||||
),
|
),
|
||||||
"config": Arg(
|
"config": Arg(
|
||||||
'-c', '--config',
|
'-c', '--config',
|
||||||
help=f'Specify configuration file (default: `{constants.DEFAULT_CONFIG}`). '
|
help=f'Specify configuration file (default: `userdir/{constants.DEFAULT_CONFIG}` '
|
||||||
|
f'or `config.json` whichever exists). '
|
||||||
f'Multiple --config options may be used. '
|
f'Multiple --config options may be used. '
|
||||||
f'Can be set to `-` to read config from stdin.',
|
f'Can be set to `-` to read config from stdin.',
|
||||||
action='append',
|
action='append',
|
||||||
|
@ -399,6 +400,54 @@ AVAILABLE_CLI_OPTIONS = {
|
||||||
help='Select only best epochs.',
|
help='Select only best epochs.',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
),
|
),
|
||||||
|
"hyperopt_list_min_trades": Arg(
|
||||||
|
'--min-trades',
|
||||||
|
help='Select epochs with more than INT trades.',
|
||||||
|
type=check_int_positive,
|
||||||
|
metavar='INT',
|
||||||
|
),
|
||||||
|
"hyperopt_list_max_trades": Arg(
|
||||||
|
'--max-trades',
|
||||||
|
help='Select epochs with less than INT trades.',
|
||||||
|
type=check_int_positive,
|
||||||
|
metavar='INT',
|
||||||
|
),
|
||||||
|
"hyperopt_list_min_avg_time": Arg(
|
||||||
|
'--min-avg-time',
|
||||||
|
help='Select epochs on above average time.',
|
||||||
|
type=float,
|
||||||
|
metavar='FLOAT',
|
||||||
|
),
|
||||||
|
"hyperopt_list_max_avg_time": Arg(
|
||||||
|
'--max-avg-time',
|
||||||
|
help='Select epochs on under average time.',
|
||||||
|
type=float,
|
||||||
|
metavar='FLOAT',
|
||||||
|
),
|
||||||
|
"hyperopt_list_min_avg_profit": Arg(
|
||||||
|
'--min-avg-profit',
|
||||||
|
help='Select epochs on above average profit.',
|
||||||
|
type=float,
|
||||||
|
metavar='FLOAT',
|
||||||
|
),
|
||||||
|
"hyperopt_list_max_avg_profit": Arg(
|
||||||
|
'--max-avg-profit',
|
||||||
|
help='Select epochs on below average profit.',
|
||||||
|
type=float,
|
||||||
|
metavar='FLOAT',
|
||||||
|
),
|
||||||
|
"hyperopt_list_min_total_profit": Arg(
|
||||||
|
'--min-total-profit',
|
||||||
|
help='Select epochs on above total profit.',
|
||||||
|
type=float,
|
||||||
|
metavar='FLOAT',
|
||||||
|
),
|
||||||
|
"hyperopt_list_max_total_profit": Arg(
|
||||||
|
'--max-total-profit',
|
||||||
|
help='Select epochs on below total profit.',
|
||||||
|
type=float,
|
||||||
|
metavar='FLOAT',
|
||||||
|
),
|
||||||
"hyperopt_list_no_details": Arg(
|
"hyperopt_list_no_details": Arg(
|
||||||
'--no-details',
|
'--no-details',
|
||||||
help='Do not print best epoch details.',
|
help='Do not print best epoch details.',
|
||||||
|
|
|
@ -37,7 +37,12 @@ def start_download_data(args: Dict[str, Any]) -> None:
|
||||||
pairs_not_available: List[str] = []
|
pairs_not_available: List[str] = []
|
||||||
|
|
||||||
# Init exchange
|
# Init exchange
|
||||||
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config)
|
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False)
|
||||||
|
# Manual validations of relevant settings
|
||||||
|
exchange.validate_pairs(config['pairs'])
|
||||||
|
for timeframe in config['timeframes']:
|
||||||
|
exchange.validate_timeframes(timeframe)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
if config.get('download_trades'):
|
if config.get('download_trades'):
|
||||||
|
|
94
freqtrade/commands/hyperopt_commands.py
Normal file → Executable file
94
freqtrade/commands/hyperopt_commands.py
Normal file → Executable file
|
@ -19,13 +19,24 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
|
||||||
|
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|
||||||
only_best = config.get('hyperopt_list_best', False)
|
|
||||||
only_profitable = config.get('hyperopt_list_profitable', False)
|
|
||||||
print_colorized = config.get('print_colorized', False)
|
print_colorized = config.get('print_colorized', False)
|
||||||
print_json = config.get('print_json', False)
|
print_json = config.get('print_json', False)
|
||||||
no_details = config.get('hyperopt_list_no_details', False)
|
no_details = config.get('hyperopt_list_no_details', False)
|
||||||
no_header = False
|
no_header = False
|
||||||
|
|
||||||
|
filteroptions = {
|
||||||
|
'only_best': config.get('hyperopt_list_best', False),
|
||||||
|
'only_profitable': config.get('hyperopt_list_profitable', False),
|
||||||
|
'filter_min_trades': config.get('hyperopt_list_min_trades', 0),
|
||||||
|
'filter_max_trades': config.get('hyperopt_list_max_trades', 0),
|
||||||
|
'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', None),
|
||||||
|
'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', None),
|
||||||
|
'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None),
|
||||||
|
'filter_max_avg_profit': config.get('hyperopt_list_max_avg_profit', None),
|
||||||
|
'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', None),
|
||||||
|
'filter_max_total_profit': config.get('hyperopt_list_max_total_profit', None)
|
||||||
|
}
|
||||||
|
|
||||||
trials_file = (config['user_data_dir'] /
|
trials_file = (config['user_data_dir'] /
|
||||||
'hyperopt_results' / 'hyperopt_results.pickle')
|
'hyperopt_results' / 'hyperopt_results.pickle')
|
||||||
|
|
||||||
|
@ -33,7 +44,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
|
||||||
trials = Hyperopt.load_previous_results(trials_file)
|
trials = Hyperopt.load_previous_results(trials_file)
|
||||||
total_epochs = len(trials)
|
total_epochs = len(trials)
|
||||||
|
|
||||||
trials = _hyperopt_filter_trials(trials, only_best, only_profitable)
|
trials = _hyperopt_filter_trials(trials, filteroptions)
|
||||||
|
|
||||||
# TODO: fetch the interval for epochs to print from the cli option
|
# TODO: fetch the interval for epochs to print from the cli option
|
||||||
epoch_start, epoch_stop = 0, None
|
epoch_start, epoch_stop = 0, None
|
||||||
|
@ -44,7 +55,8 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
|
||||||
try:
|
try:
|
||||||
# Human-friendly indexes used here (starting from 1)
|
# Human-friendly indexes used here (starting from 1)
|
||||||
for val in trials[epoch_start:epoch_stop]:
|
for val in trials[epoch_start:epoch_stop]:
|
||||||
Hyperopt.print_results_explanation(val, total_epochs, not only_best, print_colorized)
|
Hyperopt.print_results_explanation(val, total_epochs,
|
||||||
|
not filteroptions['only_best'], print_colorized)
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print('User interrupted..')
|
print('User interrupted..')
|
||||||
|
@ -63,8 +75,18 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None:
|
||||||
|
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|
||||||
only_best = config.get('hyperopt_list_best', False)
|
filteroptions = {
|
||||||
only_profitable = config.get('hyperopt_list_profitable', False)
|
'only_best': config.get('hyperopt_list_best', False),
|
||||||
|
'only_profitable': config.get('hyperopt_list_profitable', False),
|
||||||
|
'filter_min_trades': config.get('hyperopt_list_min_trades', 0),
|
||||||
|
'filter_max_trades': config.get('hyperopt_list_max_trades', 0),
|
||||||
|
'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', None),
|
||||||
|
'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', None),
|
||||||
|
'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None),
|
||||||
|
'filter_max_avg_profit': config.get('hyperopt_list_max_avg_profit', None),
|
||||||
|
'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', None),
|
||||||
|
'filter_max_total_profit': config.get('hyperopt_list_max_total_profit', None)
|
||||||
|
}
|
||||||
no_header = config.get('hyperopt_show_no_header', False)
|
no_header = config.get('hyperopt_show_no_header', False)
|
||||||
|
|
||||||
trials_file = (config['user_data_dir'] /
|
trials_file = (config['user_data_dir'] /
|
||||||
|
@ -74,7 +96,7 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None:
|
||||||
trials = Hyperopt.load_previous_results(trials_file)
|
trials = Hyperopt.load_previous_results(trials_file)
|
||||||
total_epochs = len(trials)
|
total_epochs = len(trials)
|
||||||
|
|
||||||
trials = _hyperopt_filter_trials(trials, only_best, only_profitable)
|
trials = _hyperopt_filter_trials(trials, filteroptions)
|
||||||
trials_epochs = len(trials)
|
trials_epochs = len(trials)
|
||||||
|
|
||||||
n = config.get('hyperopt_show_index', -1)
|
n = config.get('hyperopt_show_index', -1)
|
||||||
|
@ -97,18 +119,66 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None:
|
||||||
header_str="Epoch details")
|
header_str="Epoch details")
|
||||||
|
|
||||||
|
|
||||||
def _hyperopt_filter_trials(trials: List, only_best: bool, only_profitable: bool) -> List:
|
def _hyperopt_filter_trials(trials: List, filteroptions: dict) -> List:
|
||||||
"""
|
"""
|
||||||
Filter our items from the list of hyperopt results
|
Filter our items from the list of hyperopt results
|
||||||
"""
|
"""
|
||||||
if only_best:
|
if filteroptions['only_best']:
|
||||||
trials = [x for x in trials if x['is_best']]
|
trials = [x for x in trials if x['is_best']]
|
||||||
if only_profitable:
|
if filteroptions['only_profitable']:
|
||||||
trials = [x for x in trials if x['results_metrics']['profit'] > 0]
|
trials = [x for x in trials if x['results_metrics']['profit'] > 0]
|
||||||
|
if filteroptions['filter_min_trades'] > 0:
|
||||||
|
trials = [
|
||||||
|
x for x in trials
|
||||||
|
if x['results_metrics']['trade_count'] > filteroptions['filter_min_trades']
|
||||||
|
]
|
||||||
|
if filteroptions['filter_max_trades'] > 0:
|
||||||
|
trials = [
|
||||||
|
x for x in trials
|
||||||
|
if x['results_metrics']['trade_count'] < filteroptions['filter_max_trades']
|
||||||
|
]
|
||||||
|
if filteroptions['filter_min_avg_time'] is not None:
|
||||||
|
trials = [x for x in trials if x['results_metrics']['trade_count'] > 0]
|
||||||
|
trials = [
|
||||||
|
x for x in trials
|
||||||
|
if x['results_metrics']['duration'] > filteroptions['filter_min_avg_time']
|
||||||
|
]
|
||||||
|
if filteroptions['filter_max_avg_time'] is not None:
|
||||||
|
trials = [x for x in trials if x['results_metrics']['trade_count'] > 0]
|
||||||
|
trials = [
|
||||||
|
x for x in trials
|
||||||
|
if x['results_metrics']['duration'] < filteroptions['filter_max_avg_time']
|
||||||
|
]
|
||||||
|
if filteroptions['filter_min_avg_profit'] is not None:
|
||||||
|
trials = [x for x in trials if x['results_metrics']['trade_count'] > 0]
|
||||||
|
trials = [
|
||||||
|
x for x in trials
|
||||||
|
if x['results_metrics']['avg_profit']
|
||||||
|
> filteroptions['filter_min_avg_profit']
|
||||||
|
]
|
||||||
|
if filteroptions['filter_max_avg_profit'] is not None:
|
||||||
|
trials = [x for x in trials if x['results_metrics']['trade_count'] > 0]
|
||||||
|
trials = [
|
||||||
|
x for x in trials
|
||||||
|
if x['results_metrics']['avg_profit']
|
||||||
|
< filteroptions['filter_max_avg_profit']
|
||||||
|
]
|
||||||
|
if filteroptions['filter_min_total_profit'] is not None:
|
||||||
|
trials = [x for x in trials if x['results_metrics']['trade_count'] > 0]
|
||||||
|
trials = [
|
||||||
|
x for x in trials
|
||||||
|
if x['results_metrics']['profit'] > filteroptions['filter_min_total_profit']
|
||||||
|
]
|
||||||
|
if filteroptions['filter_max_total_profit'] is not None:
|
||||||
|
trials = [x for x in trials if x['results_metrics']['trade_count'] > 0]
|
||||||
|
trials = [
|
||||||
|
x for x in trials
|
||||||
|
if x['results_metrics']['profit'] < filteroptions['filter_max_total_profit']
|
||||||
|
]
|
||||||
|
|
||||||
logger.info(f"{len(trials)} " +
|
logger.info(f"{len(trials)} " +
|
||||||
("best " if only_best else "") +
|
("best " if filteroptions['only_best'] else "") +
|
||||||
("profitable " if only_profitable else "") +
|
("profitable " if filteroptions['only_profitable'] else "") +
|
||||||
"epochs found.")
|
"epochs found.")
|
||||||
|
|
||||||
return trials
|
return trials
|
||||||
|
|
|
@ -3,8 +3,10 @@ import logging
|
||||||
import sys
|
import sys
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
|
from colorama import init as colorama_init
|
||||||
|
from colorama import Fore, Style
|
||||||
import rapidjson
|
import rapidjson
|
||||||
from tabulate import tabulate
|
from tabulate import tabulate
|
||||||
|
|
||||||
|
@ -36,6 +38,29 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
|
||||||
print(f"Exchanges available for Freqtrade: {', '.join(exchanges)}")
|
print(f"Exchanges available for Freqtrade: {', '.join(exchanges)}")
|
||||||
|
|
||||||
|
|
||||||
|
def _print_objs_tabular(objs: List, print_colorized: bool) -> None:
|
||||||
|
if print_colorized:
|
||||||
|
colorama_init(autoreset=True)
|
||||||
|
red = Fore.RED
|
||||||
|
yellow = Fore.YELLOW
|
||||||
|
reset = Style.RESET_ALL
|
||||||
|
else:
|
||||||
|
red = ''
|
||||||
|
yellow = ''
|
||||||
|
reset = ''
|
||||||
|
|
||||||
|
names = [s['name'] for s in objs]
|
||||||
|
objss_to_print = [{
|
||||||
|
'name': s['name'] if s['name'] else "--",
|
||||||
|
'location': s['location'].name,
|
||||||
|
'status': (red + "LOAD FAILED" + reset if s['class'] is None
|
||||||
|
else "OK" if names.count(s['name']) == 1
|
||||||
|
else yellow + "DUPLICATE NAME" + reset)
|
||||||
|
} for s in objs]
|
||||||
|
|
||||||
|
print(tabulate(objss_to_print, headers='keys', tablefmt='pipe'))
|
||||||
|
|
||||||
|
|
||||||
def start_list_strategies(args: Dict[str, Any]) -> None:
|
def start_list_strategies(args: Dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Print files with Strategy custom classes available in the directory
|
Print files with Strategy custom classes available in the directory
|
||||||
|
@ -43,15 +68,14 @@ def start_list_strategies(args: Dict[str, Any]) -> None:
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|
||||||
directory = Path(config.get('strategy_path', config['user_data_dir'] / USERPATH_STRATEGIES))
|
directory = Path(config.get('strategy_path', config['user_data_dir'] / USERPATH_STRATEGIES))
|
||||||
strategies = StrategyResolver.search_all_objects(directory)
|
strategy_objs = StrategyResolver.search_all_objects(directory, not args['print_one_column'])
|
||||||
# Sort alphabetically
|
# Sort alphabetically
|
||||||
strategies = sorted(strategies, key=lambda x: x['name'])
|
strategy_objs = sorted(strategy_objs, key=lambda x: x['name'])
|
||||||
strats_to_print = [{'name': s['name'], 'location': s['location'].name} for s in strategies]
|
|
||||||
|
|
||||||
if args['print_one_column']:
|
if args['print_one_column']:
|
||||||
print('\n'.join([s['name'] for s in strategies]))
|
print('\n'.join([s['name'] for s in strategy_objs]))
|
||||||
else:
|
else:
|
||||||
print(tabulate(strats_to_print, headers='keys', tablefmt='pipe'))
|
_print_objs_tabular(strategy_objs, config.get('print_colorized', False))
|
||||||
|
|
||||||
|
|
||||||
def start_list_hyperopts(args: Dict[str, Any]) -> None:
|
def start_list_hyperopts(args: Dict[str, Any]) -> None:
|
||||||
|
@ -63,15 +87,14 @@ def start_list_hyperopts(args: Dict[str, Any]) -> None:
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|
||||||
directory = Path(config.get('hyperopt_path', config['user_data_dir'] / USERPATH_HYPEROPTS))
|
directory = Path(config.get('hyperopt_path', config['user_data_dir'] / USERPATH_HYPEROPTS))
|
||||||
hyperopts = HyperOptResolver.search_all_objects(directory)
|
hyperopt_objs = HyperOptResolver.search_all_objects(directory, not args['print_one_column'])
|
||||||
# Sort alphabetically
|
# Sort alphabetically
|
||||||
hyperopts = sorted(hyperopts, key=lambda x: x['name'])
|
hyperopt_objs = sorted(hyperopt_objs, key=lambda x: x['name'])
|
||||||
hyperopts_to_print = [{'name': s['name'], 'location': s['location'].name} for s in hyperopts]
|
|
||||||
|
|
||||||
if args['print_one_column']:
|
if args['print_one_column']:
|
||||||
print('\n'.join([s['name'] for s in hyperopts]))
|
print('\n'.join([s['name'] for s in hyperopt_objs]))
|
||||||
else:
|
else:
|
||||||
print(tabulate(hyperopts_to_print, headers='keys', tablefmt='pipe'))
|
_print_objs_tabular(hyperopt_objs, config.get('print_colorized', False))
|
||||||
|
|
||||||
|
|
||||||
def start_list_timeframes(args: Dict[str, Any]) -> None:
|
def start_list_timeframes(args: Dict[str, Any]) -> None:
|
||||||
|
|
|
@ -310,6 +310,30 @@ class Configuration:
|
||||||
self._args_to_config(config, argname='hyperopt_list_profitable',
|
self._args_to_config(config, argname='hyperopt_list_profitable',
|
||||||
logstring='Parameter --profitable detected: {}')
|
logstring='Parameter --profitable detected: {}')
|
||||||
|
|
||||||
|
self._args_to_config(config, argname='hyperopt_list_min_trades',
|
||||||
|
logstring='Parameter --min-trades detected: {}')
|
||||||
|
|
||||||
|
self._args_to_config(config, argname='hyperopt_list_max_trades',
|
||||||
|
logstring='Parameter --max-trades detected: {}')
|
||||||
|
|
||||||
|
self._args_to_config(config, argname='hyperopt_list_min_avg_time',
|
||||||
|
logstring='Parameter --min-avg-time detected: {}')
|
||||||
|
|
||||||
|
self._args_to_config(config, argname='hyperopt_list_max_avg_time',
|
||||||
|
logstring='Parameter --max-avg-time detected: {}')
|
||||||
|
|
||||||
|
self._args_to_config(config, argname='hyperopt_list_min_avg_profit',
|
||||||
|
logstring='Parameter --min-avg-profit detected: {}')
|
||||||
|
|
||||||
|
self._args_to_config(config, argname='hyperopt_list_max_avg_profit',
|
||||||
|
logstring='Parameter --max-avg-profit detected: {}')
|
||||||
|
|
||||||
|
self._args_to_config(config, argname='hyperopt_list_min_total_profit',
|
||||||
|
logstring='Parameter --min-total-profit detected: {}')
|
||||||
|
|
||||||
|
self._args_to_config(config, argname='hyperopt_list_max_total_profit',
|
||||||
|
logstring='Parameter --max-total-profit detected: {}')
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_list_no_details',
|
self._args_to_config(config, argname='hyperopt_list_no_details',
|
||||||
logstring='Parameter --no-details detected: {}')
|
logstring='Parameter --no-details detected: {}')
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,7 @@ CONF_SCHEMA = {
|
||||||
'amend_last_stake_amount': {'type': 'boolean', 'default': False},
|
'amend_last_stake_amount': {'type': 'boolean', 'default': False},
|
||||||
'last_stake_amount_min_ratio': {
|
'last_stake_amount_min_ratio': {
|
||||||
'type': 'number', 'minimum': 0.0, 'maximum': 1.0, 'default': 0.5
|
'type': 'number', 'minimum': 0.0, 'maximum': 1.0, 'default': 0.5
|
||||||
},
|
},
|
||||||
'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT},
|
'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT},
|
||||||
'dry_run': {'type': 'boolean'},
|
'dry_run': {'type': 'boolean'},
|
||||||
'dry_run_wallet': {'type': 'number', 'default': DRY_RUN_WALLET},
|
'dry_run_wallet': {'type': 'number', 'default': DRY_RUN_WALLET},
|
||||||
|
@ -191,7 +191,9 @@ CONF_SCHEMA = {
|
||||||
'properties': {
|
'properties': {
|
||||||
'enabled': {'type': 'boolean'},
|
'enabled': {'type': 'boolean'},
|
||||||
'webhookbuy': {'type': 'object'},
|
'webhookbuy': {'type': 'object'},
|
||||||
|
'webhookbuycancel': {'type': 'object'},
|
||||||
'webhooksell': {'type': 'object'},
|
'webhooksell': {'type': 'object'},
|
||||||
|
'webhooksellcancel': {'type': 'object'},
|
||||||
'webhookstatus': {'type': 'object'},
|
'webhookstatus': {'type': 'object'},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
from freqtrade.exchange.common import MAP_EXCHANGE_CHILDCLASS # noqa: F401
|
# flake8: noqa: F401
|
||||||
from freqtrade.exchange.exchange import Exchange # noqa: F401
|
from freqtrade.exchange.common import MAP_EXCHANGE_CHILDCLASS
|
||||||
from freqtrade.exchange.exchange import (get_exchange_bad_reason, # noqa: F401
|
from freqtrade.exchange.exchange import Exchange
|
||||||
|
from freqtrade.exchange.exchange import (get_exchange_bad_reason,
|
||||||
is_exchange_bad,
|
is_exchange_bad,
|
||||||
is_exchange_known_ccxt,
|
is_exchange_known_ccxt,
|
||||||
is_exchange_officially_supported,
|
is_exchange_officially_supported,
|
||||||
ccxt_exchanges,
|
ccxt_exchanges,
|
||||||
available_exchanges)
|
available_exchanges)
|
||||||
from freqtrade.exchange.exchange import (timeframe_to_seconds, # noqa: F401
|
from freqtrade.exchange.exchange import (timeframe_to_seconds,
|
||||||
timeframe_to_minutes,
|
timeframe_to_minutes,
|
||||||
timeframe_to_msecs,
|
timeframe_to_msecs,
|
||||||
timeframe_to_next_date,
|
timeframe_to_next_date,
|
||||||
timeframe_to_prev_date)
|
timeframe_to_prev_date)
|
||||||
from freqtrade.exchange.exchange import (market_is_active, # noqa: F401
|
from freqtrade.exchange.exchange import (market_is_active,
|
||||||
symbol_is_pair)
|
symbol_is_pair)
|
||||||
from freqtrade.exchange.kraken import Kraken # noqa: F401
|
from freqtrade.exchange.kraken import Kraken
|
||||||
from freqtrade.exchange.binance import Binance # noqa: F401
|
from freqtrade.exchange.binance import Binance
|
||||||
from freqtrade.exchange.bibox import Bibox # noqa: F401
|
from freqtrade.exchange.bibox import Bibox
|
||||||
|
from freqtrade.exchange.ftx import Ftx
|
||||||
|
|
14
freqtrade/exchange/ftx.py
Normal file
14
freqtrade/exchange/ftx.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
""" FTX exchange subclass """
|
||||||
|
import logging
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from freqtrade.exchange import Exchange
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Ftx(Exchange):
|
||||||
|
|
||||||
|
_ft_has: Dict = {
|
||||||
|
"ohlcv_candle_limit": 1500,
|
||||||
|
}
|
|
@ -234,7 +234,7 @@ class FreqtradeBot:
|
||||||
|
|
||||||
return trades_created
|
return trades_created
|
||||||
|
|
||||||
def get_buy_rate(self, pair: str, tick: Dict = None) -> float:
|
def get_buy_rate(self, pair: str, refresh: bool, tick: Dict = None) -> float:
|
||||||
"""
|
"""
|
||||||
Calculates bid target between current ask price and last price
|
Calculates bid target between current ask price and last price
|
||||||
:return: float: Price
|
:return: float: Price
|
||||||
|
@ -253,7 +253,7 @@ class FreqtradeBot:
|
||||||
else:
|
else:
|
||||||
if not tick:
|
if not tick:
|
||||||
logger.info('Using Last Ask / Last Price')
|
logger.info('Using Last Ask / Last Price')
|
||||||
ticker = self.exchange.fetch_ticker(pair)
|
ticker = self.exchange.fetch_ticker(pair, refresh)
|
||||||
else:
|
else:
|
||||||
ticker = tick
|
ticker = tick
|
||||||
if ticker['ask'] < ticker['last']:
|
if ticker['ask'] < ticker['last']:
|
||||||
|
@ -404,7 +404,7 @@ class FreqtradeBot:
|
||||||
|
|
||||||
stake_amount = self.get_trade_stake_amount(pair)
|
stake_amount = self.get_trade_stake_amount(pair)
|
||||||
if not stake_amount:
|
if not stake_amount:
|
||||||
logger.debug("Stake amount is 0, ignoring possible trade for {pair}.")
|
logger.debug(f"Stake amount is 0, ignoring possible trade for {pair}.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
logger.info(f"Buy signal found: about create a new trade with stake_amount: "
|
logger.info(f"Buy signal found: about create a new trade with stake_amount: "
|
||||||
|
@ -414,10 +414,12 @@ class FreqtradeBot:
|
||||||
if ((bid_check_dom.get('enabled', False)) and
|
if ((bid_check_dom.get('enabled', False)) and
|
||||||
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
|
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
|
||||||
if self._check_depth_of_market_buy(pair, bid_check_dom):
|
if self._check_depth_of_market_buy(pair, bid_check_dom):
|
||||||
|
logger.info(f'Executing Buy for {pair}.')
|
||||||
return self.execute_buy(pair, stake_amount)
|
return self.execute_buy(pair, stake_amount)
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
logger.info(f'Executing Buy for {pair}')
|
||||||
return self.execute_buy(pair, stake_amount)
|
return self.execute_buy(pair, stake_amount)
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
@ -450,7 +452,7 @@ class FreqtradeBot:
|
||||||
"""
|
"""
|
||||||
Executes a limit buy for the given pair
|
Executes a limit buy for the given pair
|
||||||
:param pair: pair for which we want to create a LIMIT_BUY
|
:param pair: pair for which we want to create a LIMIT_BUY
|
||||||
:return: None
|
:return: True if a buy order is created, false if it fails.
|
||||||
"""
|
"""
|
||||||
time_in_force = self.strategy.order_time_in_force['buy']
|
time_in_force = self.strategy.order_time_in_force['buy']
|
||||||
|
|
||||||
|
@ -458,7 +460,7 @@ class FreqtradeBot:
|
||||||
buy_limit_requested = price
|
buy_limit_requested = price
|
||||||
else:
|
else:
|
||||||
# Calculate price
|
# Calculate price
|
||||||
buy_limit_requested = self.get_buy_rate(pair)
|
buy_limit_requested = self.get_buy_rate(pair, True)
|
||||||
|
|
||||||
min_stake_amount = self._get_min_pair_stake_amount(pair, buy_limit_requested)
|
min_stake_amount = self._get_min_pair_stake_amount(pair, buy_limit_requested)
|
||||||
if min_stake_amount is not None and min_stake_amount > stake_amount:
|
if min_stake_amount is not None and min_stake_amount > stake_amount:
|
||||||
|
@ -525,8 +527,6 @@ class FreqtradeBot:
|
||||||
ticker_interval=timeframe_to_minutes(self.config['ticker_interval'])
|
ticker_interval=timeframe_to_minutes(self.config['ticker_interval'])
|
||||||
)
|
)
|
||||||
|
|
||||||
self._notify_buy(trade, order_type)
|
|
||||||
|
|
||||||
# Update fees if order is closed
|
# Update fees if order is closed
|
||||||
if order_status == 'closed':
|
if order_status == 'closed':
|
||||||
self.update_trade_state(trade, order)
|
self.update_trade_state(trade, order)
|
||||||
|
@ -537,6 +537,8 @@ class FreqtradeBot:
|
||||||
# Updating wallets
|
# Updating wallets
|
||||||
self.wallets.update()
|
self.wallets.update()
|
||||||
|
|
||||||
|
self._notify_buy(trade, order_type)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _notify_buy(self, trade: Trade, order_type: str) -> None:
|
def _notify_buy(self, trade: Trade, order_type: str) -> None:
|
||||||
|
@ -552,6 +554,32 @@ class FreqtradeBot:
|
||||||
'stake_amount': trade.stake_amount,
|
'stake_amount': trade.stake_amount,
|
||||||
'stake_currency': self.config['stake_currency'],
|
'stake_currency': self.config['stake_currency'],
|
||||||
'fiat_currency': self.config.get('fiat_display_currency', None),
|
'fiat_currency': self.config.get('fiat_display_currency', None),
|
||||||
|
'amount': trade.amount,
|
||||||
|
'open_date': trade.open_date or datetime.utcnow(),
|
||||||
|
'current_rate': trade.open_rate_requested,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Send the message
|
||||||
|
self.rpc.send_msg(msg)
|
||||||
|
|
||||||
|
def _notify_buy_cancel(self, trade: Trade, order_type: str) -> None:
|
||||||
|
"""
|
||||||
|
Sends rpc notification when a buy cancel occured.
|
||||||
|
"""
|
||||||
|
current_rate = self.get_buy_rate(trade.pair, True)
|
||||||
|
|
||||||
|
msg = {
|
||||||
|
'type': RPCMessageType.BUY_CANCEL_NOTIFICATION,
|
||||||
|
'exchange': self.exchange.name.capitalize(),
|
||||||
|
'pair': trade.pair,
|
||||||
|
'limit': trade.open_rate,
|
||||||
|
'order_type': order_type,
|
||||||
|
'stake_amount': trade.stake_amount,
|
||||||
|
'stake_currency': self.config['stake_currency'],
|
||||||
|
'fiat_currency': self.config.get('fiat_display_currency', None),
|
||||||
|
'amount': trade.amount,
|
||||||
|
'open_date': trade.open_date,
|
||||||
|
'current_rate': current_rate,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Send the message
|
# Send the message
|
||||||
|
@ -751,7 +779,7 @@ class FreqtradeBot:
|
||||||
update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60)
|
update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60)
|
||||||
if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() >= update_beat:
|
if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() >= update_beat:
|
||||||
# cancelling the current stoploss on exchange first
|
# cancelling the current stoploss on exchange first
|
||||||
logger.info('Trailing stoploss: cancelling current stoploss on exchange (id:{%s})'
|
logger.info('Trailing stoploss: cancelling current stoploss on exchange (id:{%s}) '
|
||||||
'in order to add another one ...', order['id'])
|
'in order to add another one ...', order['id'])
|
||||||
try:
|
try:
|
||||||
self.exchange.cancel_order(order['id'], trade.pair)
|
self.exchange.cancel_order(order['id'], trade.pair)
|
||||||
|
@ -776,8 +804,8 @@ class FreqtradeBot:
|
||||||
)
|
)
|
||||||
|
|
||||||
if should_sell.sell_flag:
|
if should_sell.sell_flag:
|
||||||
|
logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}')
|
||||||
self.execute_sell(trade, sell_rate, should_sell.sell_type)
|
self.execute_sell(trade, sell_rate, should_sell.sell_type)
|
||||||
logger.info('executed sell, reason: %s', should_sell.sell_type)
|
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -820,41 +848,37 @@ class FreqtradeBot:
|
||||||
|
|
||||||
if ((order['side'] == 'buy' and order['status'] == 'canceled')
|
if ((order['side'] == 'buy' and order['status'] == 'canceled')
|
||||||
or (self._check_timed_out('buy', order))):
|
or (self._check_timed_out('buy', order))):
|
||||||
|
|
||||||
self.handle_timedout_limit_buy(trade, order)
|
self.handle_timedout_limit_buy(trade, order)
|
||||||
self.wallets.update()
|
self.wallets.update()
|
||||||
|
order_type = self.strategy.order_types['buy']
|
||||||
|
self._notify_buy_cancel(trade, order_type)
|
||||||
|
|
||||||
elif ((order['side'] == 'sell' and order['status'] == 'canceled')
|
elif ((order['side'] == 'sell' and order['status'] == 'canceled')
|
||||||
or (self._check_timed_out('sell', order))):
|
or (self._check_timed_out('sell', order))):
|
||||||
self.handle_timedout_limit_sell(trade, order)
|
self.handle_timedout_limit_sell(trade, order)
|
||||||
self.wallets.update()
|
self.wallets.update()
|
||||||
|
order_type = self.strategy.order_types['sell']
|
||||||
def handle_buy_order_full_cancel(self, trade: Trade, reason: str) -> None:
|
self._notify_sell_cancel(trade, order_type)
|
||||||
"""Close trade in database and send message"""
|
|
||||||
Trade.session.delete(trade)
|
|
||||||
Trade.session.flush()
|
|
||||||
logger.info('Buy order %s for %s.', reason, trade)
|
|
||||||
self.rpc.send_msg({
|
|
||||||
'type': RPCMessageType.STATUS_NOTIFICATION,
|
|
||||||
'status': f'Unfilled buy order for {trade.pair} {reason}'
|
|
||||||
})
|
|
||||||
|
|
||||||
def handle_timedout_limit_buy(self, trade: Trade, order: Dict) -> bool:
|
def handle_timedout_limit_buy(self, trade: Trade, order: Dict) -> bool:
|
||||||
"""
|
"""
|
||||||
Buy timeout - cancel order
|
Buy timeout - cancel order
|
||||||
:return: True if order was fully cancelled
|
:return: True if order was fully cancelled
|
||||||
"""
|
"""
|
||||||
reason = "cancelled due to timeout"
|
|
||||||
if order['status'] != 'canceled':
|
if order['status'] != 'canceled':
|
||||||
|
reason = "cancelled due to timeout"
|
||||||
corder = self.exchange.cancel_order(trade.open_order_id, trade.pair)
|
corder = self.exchange.cancel_order(trade.open_order_id, trade.pair)
|
||||||
|
logger.info('Buy order %s for %s.', reason, trade)
|
||||||
else:
|
else:
|
||||||
# Order was cancelled already, so we can reuse the existing dict
|
# Order was cancelled already, so we can reuse the existing dict
|
||||||
corder = order
|
corder = order
|
||||||
reason = "canceled on Exchange"
|
reason = "cancelled on exchange"
|
||||||
|
logger.info('Buy order %s for %s.', reason, trade)
|
||||||
|
|
||||||
if corder.get('remaining', order['remaining']) == order['amount']:
|
if corder.get('remaining', order['remaining']) == order['amount']:
|
||||||
# if trade is not partially completed, just delete the trade
|
# if trade is not partially completed, just delete the trade
|
||||||
self.handle_buy_order_full_cancel(trade, reason)
|
Trade.session.delete(trade)
|
||||||
|
Trade.session.flush()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# if trade is partially complete, edit the stake details for the trade
|
# if trade is partially complete, edit the stake details for the trade
|
||||||
|
@ -889,24 +913,22 @@ class FreqtradeBot:
|
||||||
Sell timeout - cancel order and update trade
|
Sell timeout - cancel order and update trade
|
||||||
:return: True if order was fully cancelled
|
:return: True if order was fully cancelled
|
||||||
"""
|
"""
|
||||||
|
# if trade is not partially completed, just cancel the trade
|
||||||
if order['remaining'] == order['amount']:
|
if order['remaining'] == order['amount']:
|
||||||
# if trade is not partially completed, just cancel the trade
|
|
||||||
if order["status"] != "canceled":
|
if order["status"] != "canceled":
|
||||||
reason = "due to timeout"
|
reason = "cancelled due to timeout"
|
||||||
|
# if trade is not partially completed, just delete the trade
|
||||||
self.exchange.cancel_order(trade.open_order_id, trade.pair)
|
self.exchange.cancel_order(trade.open_order_id, trade.pair)
|
||||||
logger.info('Sell order timeout for %s.', trade)
|
logger.info('Sell order %s for %s.', reason, trade)
|
||||||
else:
|
else:
|
||||||
reason = "on exchange"
|
reason = "cancelled on exchange"
|
||||||
logger.info('Sell order canceled on exchange for %s.', trade)
|
logger.info('Sell order %s for %s.', reason, trade)
|
||||||
|
|
||||||
trade.close_rate = None
|
trade.close_rate = None
|
||||||
trade.close_profit = None
|
trade.close_profit = None
|
||||||
trade.close_date = None
|
trade.close_date = None
|
||||||
trade.is_open = True
|
trade.is_open = True
|
||||||
trade.open_order_id = None
|
trade.open_order_id = None
|
||||||
self.rpc.send_msg({
|
|
||||||
'type': RPCMessageType.STATUS_NOTIFICATION,
|
|
||||||
'status': f'Unfilled sell order for {trade.pair} cancelled {reason}'
|
|
||||||
})
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -938,13 +960,13 @@ class FreqtradeBot:
|
||||||
raise DependencyException(
|
raise DependencyException(
|
||||||
f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}")
|
f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}")
|
||||||
|
|
||||||
def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType) -> None:
|
def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType) -> bool:
|
||||||
"""
|
"""
|
||||||
Executes a limit sell for the given trade and limit
|
Executes a limit sell for the given trade and limit
|
||||||
:param trade: Trade instance
|
:param trade: Trade instance
|
||||||
:param limit: limit rate for the sell order
|
:param limit: limit rate for the sell order
|
||||||
:param sellreason: Reason the sell was triggered
|
:param sellreason: Reason the sell was triggered
|
||||||
:return: None
|
:return: True if it succeeds (supported) False (not supported)
|
||||||
"""
|
"""
|
||||||
sell_type = 'sell'
|
sell_type = 'sell'
|
||||||
if sell_reason in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
|
if sell_reason in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
|
||||||
|
@ -965,7 +987,7 @@ class FreqtradeBot:
|
||||||
|
|
||||||
order_type = self.strategy.order_types[sell_type]
|
order_type = self.strategy.order_types[sell_type]
|
||||||
if sell_reason == SellType.EMERGENCY_SELL:
|
if sell_reason == SellType.EMERGENCY_SELL:
|
||||||
# Emergencysells (default to market!)
|
# Emergency sells (default to market!)
|
||||||
order_type = self.strategy.order_types.get("emergencysell", "market")
|
order_type = self.strategy.order_types.get("emergencysell", "market")
|
||||||
|
|
||||||
amount = self._safe_sell_amount(trade.pair, trade.amount)
|
amount = self._safe_sell_amount(trade.pair, trade.amount)
|
||||||
|
@ -990,6 +1012,8 @@ class FreqtradeBot:
|
||||||
|
|
||||||
self._notify_sell(trade, order_type)
|
self._notify_sell(trade, order_type)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
def _notify_sell(self, trade: Trade, order_type: str) -> None:
|
def _notify_sell(self, trade: Trade, order_type: str) -> None:
|
||||||
"""
|
"""
|
||||||
Sends rpc notification when a sell occured.
|
Sends rpc notification when a sell occured.
|
||||||
|
@ -1006,7 +1030,7 @@ class FreqtradeBot:
|
||||||
'exchange': trade.exchange.capitalize(),
|
'exchange': trade.exchange.capitalize(),
|
||||||
'pair': trade.pair,
|
'pair': trade.pair,
|
||||||
'gain': gain,
|
'gain': gain,
|
||||||
'limit': trade.close_rate_requested,
|
'limit': profit_rate,
|
||||||
'order_type': order_type,
|
'order_type': order_type,
|
||||||
'amount': trade.amount,
|
'amount': trade.amount,
|
||||||
'open_rate': trade.open_rate,
|
'open_rate': trade.open_rate,
|
||||||
|
@ -1017,6 +1041,44 @@ class FreqtradeBot:
|
||||||
'open_date': trade.open_date,
|
'open_date': trade.open_date,
|
||||||
'close_date': trade.close_date or datetime.utcnow(),
|
'close_date': trade.close_date or datetime.utcnow(),
|
||||||
'stake_currency': self.config['stake_currency'],
|
'stake_currency': self.config['stake_currency'],
|
||||||
|
'fiat_currency': self.config.get('fiat_display_currency', None),
|
||||||
|
}
|
||||||
|
|
||||||
|
if 'fiat_display_currency' in self.config:
|
||||||
|
msg.update({
|
||||||
|
'fiat_currency': self.config['fiat_display_currency'],
|
||||||
|
})
|
||||||
|
|
||||||
|
# Send the message
|
||||||
|
self.rpc.send_msg(msg)
|
||||||
|
|
||||||
|
def _notify_sell_cancel(self, trade: Trade, order_type: str) -> None:
|
||||||
|
"""
|
||||||
|
Sends rpc notification when a sell cancel occured.
|
||||||
|
"""
|
||||||
|
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
|
||||||
|
profit_trade = trade.calc_profit(rate=profit_rate)
|
||||||
|
current_rate = self.get_sell_rate(trade.pair, True)
|
||||||
|
profit_percent = trade.calc_profit_ratio(profit_rate)
|
||||||
|
gain = "profit" if profit_percent > 0 else "loss"
|
||||||
|
|
||||||
|
msg = {
|
||||||
|
'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
|
||||||
|
'exchange': trade.exchange.capitalize(),
|
||||||
|
'pair': trade.pair,
|
||||||
|
'gain': gain,
|
||||||
|
'limit': profit_rate,
|
||||||
|
'order_type': order_type,
|
||||||
|
'amount': trade.amount,
|
||||||
|
'open_rate': trade.open_rate,
|
||||||
|
'current_rate': current_rate,
|
||||||
|
'profit_amount': profit_trade,
|
||||||
|
'profit_percent': profit_percent,
|
||||||
|
'sell_reason': trade.sell_reason,
|
||||||
|
'open_date': trade.open_date,
|
||||||
|
'close_date': trade.close_date,
|
||||||
|
'stake_currency': self.config['stake_currency'],
|
||||||
|
'fiat_currency': self.config.get('fiat_display_currency', None),
|
||||||
}
|
}
|
||||||
|
|
||||||
if 'fiat_display_currency' in self.config:
|
if 'fiat_display_currency' in self.config:
|
||||||
|
|
|
@ -38,8 +38,8 @@ def main(sysargv: List[str] = None) -> None:
|
||||||
# No subcommand was issued.
|
# No subcommand was issued.
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
"Usage of Freqtrade requires a subcommand to be specified.\n"
|
"Usage of Freqtrade requires a subcommand to be specified.\n"
|
||||||
"To have the previous behavior (bot executing trades in live/dry-run modes, "
|
"To have the bot executing trades in live/dry-run modes, "
|
||||||
"depending on the value of the `dry_run` setting in the config), run freqtrade "
|
"depending on the value of the `dry_run` setting in the config, run Freqtrade "
|
||||||
"as `freqtrade trade [options...]`.\n"
|
"as `freqtrade trade [options...]`.\n"
|
||||||
"To see the full list of options available, please use "
|
"To see the full list of options available, please use "
|
||||||
"`freqtrade --help` or `freqtrade <command> --help`."
|
"`freqtrade --help` or `freqtrade <command> --help`."
|
||||||
|
|
|
@ -139,5 +139,4 @@ def render_template(templatefile: str, arguments: dict = {}) -> str:
|
||||||
autoescape=select_autoescape(['html', 'xml'])
|
autoescape=select_autoescape(['html', 'xml'])
|
||||||
)
|
)
|
||||||
template = env.get_template(templatefile)
|
template = env.get_template(templatefile)
|
||||||
|
|
||||||
return template.render(**arguments)
|
return template.render(**arguments)
|
||||||
|
|
|
@ -207,7 +207,7 @@ class IHyperOpt(ABC):
|
||||||
# so this intermediate parameter is used as the value of the difference between
|
# so this intermediate parameter is used as the value of the difference between
|
||||||
# them. The value of the 'trailing_stop_positive_offset' is constructed in the
|
# them. The value of the 'trailing_stop_positive_offset' is constructed in the
|
||||||
# generate_trailing_params() method.
|
# generate_trailing_params() method.
|
||||||
# # This is similar to the hyperspace dimensions used for constructing the ROI tables.
|
# This is similar to the hyperspace dimensions used for constructing the ROI tables.
|
||||||
Real(0.001, 0.1, name='trailing_stop_positive_offset_p1'),
|
Real(0.001, 0.1, name='trailing_stop_positive_offset_p1'),
|
||||||
|
|
||||||
Categorical([True, False], name='trailing_only_offset_is_reached'),
|
Categorical([True, False], name='trailing_only_offset_is_reached'),
|
||||||
|
|
|
@ -39,7 +39,8 @@ class SharpeHyperOptLossDaily(IHyperOptLoss):
|
||||||
results['profit_percent'] - slippage_per_trade_ratio
|
results['profit_percent'] - slippage_per_trade_ratio
|
||||||
|
|
||||||
# create the index within the min_date and end max_date
|
# create the index within the min_date and end max_date
|
||||||
t_index = date_range(start=min_date, end=max_date, freq=resample_freq)
|
t_index = date_range(start=min_date, end=max_date, freq=resample_freq,
|
||||||
|
normalize=True)
|
||||||
|
|
||||||
sum_daily = (
|
sum_daily = (
|
||||||
results.resample(resample_freq, on='close_time').agg(
|
results.resample(resample_freq, on='close_time').agg(
|
||||||
|
|
|
@ -21,13 +21,14 @@ def generate_text_table(data: Dict[str, Dict], stake_currency: str, max_open_tra
|
||||||
tabular_data = []
|
tabular_data = []
|
||||||
headers = [
|
headers = [
|
||||||
'Pair',
|
'Pair',
|
||||||
'Buy Count',
|
'Buys',
|
||||||
'Avg Profit %',
|
'Avg Profit %',
|
||||||
'Cum Profit %',
|
'Cum Profit %',
|
||||||
f'Tot Profit {stake_currency}',
|
f'Tot Profit {stake_currency}',
|
||||||
'Tot Profit %',
|
'Tot Profit %',
|
||||||
'Avg Duration',
|
'Avg Duration',
|
||||||
'Wins',
|
'Wins',
|
||||||
|
'Draws',
|
||||||
'Losses'
|
'Losses'
|
||||||
]
|
]
|
||||||
for pair in data:
|
for pair in data:
|
||||||
|
@ -45,6 +46,7 @@ def generate_text_table(data: Dict[str, Dict], stake_currency: str, max_open_tra
|
||||||
str(timedelta(
|
str(timedelta(
|
||||||
minutes=round(result.trade_duration.mean()))) if not result.empty else '0:00',
|
minutes=round(result.trade_duration.mean()))) if not result.empty else '0:00',
|
||||||
len(result[result.profit_abs > 0]),
|
len(result[result.profit_abs > 0]),
|
||||||
|
len(result[result.profit_abs == 0]),
|
||||||
len(result[result.profit_abs < 0])
|
len(result[result.profit_abs < 0])
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -59,6 +61,7 @@ def generate_text_table(data: Dict[str, Dict], stake_currency: str, max_open_tra
|
||||||
str(timedelta(
|
str(timedelta(
|
||||||
minutes=round(results.trade_duration.mean()))) if not results.empty else '0:00',
|
minutes=round(results.trade_duration.mean()))) if not results.empty else '0:00',
|
||||||
len(results[results.profit_abs > 0]),
|
len(results[results.profit_abs > 0]),
|
||||||
|
len(results[results.profit_abs == 0]),
|
||||||
len(results[results.profit_abs < 0])
|
len(results[results.profit_abs < 0])
|
||||||
])
|
])
|
||||||
# Ignore type as floatfmt does allow tuples but mypy does not know that
|
# Ignore type as floatfmt does allow tuples but mypy does not know that
|
||||||
|
@ -78,8 +81,9 @@ def generate_text_table_sell_reason(
|
||||||
tabular_data = []
|
tabular_data = []
|
||||||
headers = [
|
headers = [
|
||||||
"Sell Reason",
|
"Sell Reason",
|
||||||
"Sell Count",
|
"Sells",
|
||||||
"Wins",
|
"Wins",
|
||||||
|
"Draws",
|
||||||
"Losses",
|
"Losses",
|
||||||
"Avg Profit %",
|
"Avg Profit %",
|
||||||
"Cum Profit %",
|
"Cum Profit %",
|
||||||
|
@ -88,7 +92,8 @@ def generate_text_table_sell_reason(
|
||||||
]
|
]
|
||||||
for reason, count in results['sell_reason'].value_counts().iteritems():
|
for reason, count in results['sell_reason'].value_counts().iteritems():
|
||||||
result = results.loc[results['sell_reason'] == reason]
|
result = results.loc[results['sell_reason'] == reason]
|
||||||
profit = len(result[result['profit_abs'] >= 0])
|
wins = len(result[result['profit_abs'] > 0])
|
||||||
|
draws = len(result[result['profit_abs'] == 0])
|
||||||
loss = len(result[result['profit_abs'] < 0])
|
loss = len(result[result['profit_abs'] < 0])
|
||||||
profit_mean = round(result['profit_percent'].mean() * 100.0, 2)
|
profit_mean = round(result['profit_percent'].mean() * 100.0, 2)
|
||||||
profit_sum = round(result["profit_percent"].sum() * 100.0, 2)
|
profit_sum = round(result["profit_percent"].sum() * 100.0, 2)
|
||||||
|
@ -98,7 +103,8 @@ def generate_text_table_sell_reason(
|
||||||
[
|
[
|
||||||
reason.value,
|
reason.value,
|
||||||
count,
|
count,
|
||||||
profit,
|
wins,
|
||||||
|
draws,
|
||||||
loss,
|
loss,
|
||||||
profit_mean,
|
profit_mean,
|
||||||
profit_sum,
|
profit_sum,
|
||||||
|
@ -121,9 +127,9 @@ def generate_text_table_strategy(stake_currency: str, max_open_trades: str,
|
||||||
|
|
||||||
floatfmt = ('s', 'd', '.2f', '.2f', '.8f', '.2f', 'd', '.1f', '.1f')
|
floatfmt = ('s', 'd', '.2f', '.2f', '.8f', '.2f', 'd', '.1f', '.1f')
|
||||||
tabular_data = []
|
tabular_data = []
|
||||||
headers = ['Strategy', 'Buy Count', 'Avg Profit %', 'Cum Profit %',
|
headers = ['Strategy', 'Buys', 'Avg Profit %', 'Cum Profit %',
|
||||||
f'Tot Profit {stake_currency}', 'Tot Profit %', 'Avg Duration',
|
f'Tot Profit {stake_currency}', 'Tot Profit %', 'Avg Duration',
|
||||||
'Wins', 'Losses']
|
'Wins', 'Draws', 'Losses']
|
||||||
for strategy, results in all_results.items():
|
for strategy, results in all_results.items():
|
||||||
tabular_data.append([
|
tabular_data.append([
|
||||||
strategy,
|
strategy,
|
||||||
|
@ -135,6 +141,7 @@ def generate_text_table_strategy(stake_currency: str, max_open_trades: str,
|
||||||
str(timedelta(
|
str(timedelta(
|
||||||
minutes=round(results.trade_duration.mean()))) if not results.empty else '0:00',
|
minutes=round(results.trade_duration.mean()))) if not results.empty else '0:00',
|
||||||
len(results[results.profit_abs > 0]),
|
len(results[results.profit_abs > 0]),
|
||||||
|
len(results[results.profit_abs == 0]),
|
||||||
len(results[results.profit_abs < 0])
|
len(results[results.profit_abs < 0])
|
||||||
])
|
])
|
||||||
# Ignore type as floatfmt does allow tuples but mypy does not know that
|
# Ignore type as floatfmt does allow tuples but mypy does not know that
|
||||||
|
@ -146,9 +153,9 @@ def generate_edge_table(results: dict) -> str:
|
||||||
|
|
||||||
floatfmt = ('s', '.10g', '.2f', '.2f', '.2f', '.2f', 'd', '.d')
|
floatfmt = ('s', '.10g', '.2f', '.2f', '.2f', '.2f', 'd', '.d')
|
||||||
tabular_data = []
|
tabular_data = []
|
||||||
headers = ['pair', 'stoploss', 'win rate', 'risk reward ratio',
|
headers = ['Pair', 'Stoploss', 'Win Rate', 'Risk Reward Ratio',
|
||||||
'required risk reward', 'expectancy', 'total number of trades',
|
'Required Risk Reward', 'Expectancy', 'Total Number of Trades',
|
||||||
'average duration (min)']
|
'Average Duration (min)']
|
||||||
|
|
||||||
for result in results.items():
|
for result in results.items():
|
||||||
if result[1].nb_trades > 0:
|
if result[1].nb_trades > 0:
|
||||||
|
|
|
@ -7,7 +7,7 @@ import importlib.util
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, Generator, List, Optional, Tuple, Type, Union
|
from typing import Any, Dict, Iterator, List, Optional, Tuple, Type, Union
|
||||||
|
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
|
|
||||||
|
@ -40,12 +40,14 @@ class IResolver:
|
||||||
return abs_paths
|
return abs_paths
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_valid_object(cls, module_path: Path,
|
def _get_valid_object(cls, module_path: Path, object_name: Optional[str],
|
||||||
object_name: Optional[str]) -> Generator[Any, None, None]:
|
enum_failed: bool = False) -> Iterator[Any]:
|
||||||
"""
|
"""
|
||||||
Generator returning objects with matching object_type and object_name in the path given.
|
Generator returning objects with matching object_type and object_name in the path given.
|
||||||
:param module_path: absolute path to the module
|
:param module_path: absolute path to the module
|
||||||
:param object_name: Class name of the object
|
:param object_name: Class name of the object
|
||||||
|
:param enum_failed: If True, will return None for modules which fail.
|
||||||
|
Otherwise, failing modules are skipped.
|
||||||
:return: generator containing matching objects
|
:return: generator containing matching objects
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -58,10 +60,13 @@ class IResolver:
|
||||||
except (ModuleNotFoundError, SyntaxError) as err:
|
except (ModuleNotFoundError, SyntaxError) as err:
|
||||||
# Catch errors in case a specific module is not installed
|
# Catch errors in case a specific module is not installed
|
||||||
logger.warning(f"Could not import {module_path} due to '{err}'")
|
logger.warning(f"Could not import {module_path} due to '{err}'")
|
||||||
|
if enum_failed:
|
||||||
|
return iter([None])
|
||||||
|
|
||||||
valid_objects_gen = (
|
valid_objects_gen = (
|
||||||
obj for name, obj in inspect.getmembers(module, inspect.isclass)
|
obj for name, obj in inspect.getmembers(module, inspect.isclass)
|
||||||
if (object_name is None or object_name == name) and cls.object_type in obj.__bases__
|
if ((object_name is None or object_name == name) and
|
||||||
|
issubclass(obj, cls.object_type) and obj is not cls.object_type)
|
||||||
)
|
)
|
||||||
return valid_objects_gen
|
return valid_objects_gen
|
||||||
|
|
||||||
|
@ -135,10 +140,13 @@ class IResolver:
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def search_all_objects(cls, directory: Path) -> List[Dict[str, Any]]:
|
def search_all_objects(cls, directory: Path,
|
||||||
|
enum_failed: bool) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Searches a directory for valid objects
|
Searches a directory for valid objects
|
||||||
:param directory: Path to search
|
:param directory: Path to search
|
||||||
|
:param enum_failed: If True, will return None for modules which fail.
|
||||||
|
Otherwise, failing modules are skipped.
|
||||||
:return: List of dicts containing 'name', 'class' and 'location' entires
|
:return: List of dicts containing 'name', 'class' and 'location' entires
|
||||||
"""
|
"""
|
||||||
logger.debug(f"Searching for {cls.object_type.__name__} '{directory}'")
|
logger.debug(f"Searching for {cls.object_type.__name__} '{directory}'")
|
||||||
|
@ -150,9 +158,10 @@ class IResolver:
|
||||||
continue
|
continue
|
||||||
module_path = entry.resolve()
|
module_path = entry.resolve()
|
||||||
logger.debug(f"Path {module_path}")
|
logger.debug(f"Path {module_path}")
|
||||||
for obj in cls._get_valid_object(module_path, object_name=None):
|
for obj in cls._get_valid_object(module_path, object_name=None,
|
||||||
|
enum_failed=enum_failed):
|
||||||
objects.append(
|
objects.append(
|
||||||
{'name': obj.__name__,
|
{'name': obj.__name__ if obj is not None else '',
|
||||||
'class': obj,
|
'class': obj,
|
||||||
'location': entry,
|
'location': entry,
|
||||||
})
|
})
|
||||||
|
|
|
@ -26,7 +26,9 @@ class RPCMessageType(Enum):
|
||||||
WARNING_NOTIFICATION = 'warning'
|
WARNING_NOTIFICATION = 'warning'
|
||||||
CUSTOM_NOTIFICATION = 'custom'
|
CUSTOM_NOTIFICATION = 'custom'
|
||||||
BUY_NOTIFICATION = 'buy'
|
BUY_NOTIFICATION = 'buy'
|
||||||
|
BUY_CANCEL_NOTIFICATION = 'buy_cancel'
|
||||||
SELL_NOTIFICATION = 'sell'
|
SELL_NOTIFICATION = 'sell'
|
||||||
|
SELL_CANCEL_NOTIFICATION = 'sell_cancel'
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.value
|
return self.value
|
||||||
|
@ -39,6 +41,7 @@ class RPCException(Exception):
|
||||||
|
|
||||||
raise RPCException('*Status:* `no active trade`')
|
raise RPCException('*Status:* `no active trade`')
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, message: str) -> None:
|
def __init__(self, message: str) -> None:
|
||||||
super().__init__(self)
|
super().__init__(self)
|
||||||
self.message = message
|
self.message = message
|
||||||
|
@ -157,15 +160,17 @@ class RPC:
|
||||||
profit_str = f'{trade_perc:.2f}%'
|
profit_str = f'{trade_perc:.2f}%'
|
||||||
if self._fiat_converter:
|
if self._fiat_converter:
|
||||||
fiat_profit = self._fiat_converter.convert_amount(
|
fiat_profit = self._fiat_converter.convert_amount(
|
||||||
trade_profit,
|
trade_profit,
|
||||||
stake_currency,
|
stake_currency,
|
||||||
fiat_display_currency
|
fiat_display_currency
|
||||||
)
|
)
|
||||||
if fiat_profit and not isnan(fiat_profit):
|
if fiat_profit and not isnan(fiat_profit):
|
||||||
profit_str += f" ({fiat_profit:.2f})"
|
profit_str += f" ({fiat_profit:.2f})"
|
||||||
trades_list.append([
|
trades_list.append([
|
||||||
trade.id,
|
trade.id,
|
||||||
trade.pair,
|
trade.pair + ('*' if (trade.open_order_id is not None
|
||||||
|
and trade.close_rate_requested is None) else '')
|
||||||
|
+ ('**' if (trade.close_rate_requested is not None) else ''),
|
||||||
shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)),
|
shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)),
|
||||||
profit_str
|
profit_str
|
||||||
])
|
])
|
||||||
|
|
|
@ -134,13 +134,18 @@ class Telegram(RPC):
|
||||||
msg['stake_amount_fiat'] = 0
|
msg['stake_amount_fiat'] = 0
|
||||||
|
|
||||||
message = ("*{exchange}:* Buying {pair}\n"
|
message = ("*{exchange}:* Buying {pair}\n"
|
||||||
"at rate `{limit:.8f}\n"
|
"*Amount:* `{amount:.8f}`\n"
|
||||||
"({stake_amount:.6f} {stake_currency}").format(**msg)
|
"*Open Rate:* `{limit:.8f}`\n"
|
||||||
|
"*Current Rate:* `{current_rate:.8f}`\n"
|
||||||
|
"*Total:* `({stake_amount:.6f} {stake_currency}").format(**msg)
|
||||||
|
|
||||||
if msg.get('fiat_currency', None):
|
if msg.get('fiat_currency', None):
|
||||||
message += ",{stake_amount_fiat:.3f} {fiat_currency}".format(**msg)
|
message += ", {stake_amount_fiat:.3f} {fiat_currency}".format(**msg)
|
||||||
message += ")`"
|
message += ")`"
|
||||||
|
|
||||||
|
elif msg['type'] == RPCMessageType.BUY_CANCEL_NOTIFICATION:
|
||||||
|
message = "*{exchange}:* Cancelling Open Buy Order for {pair}".format(**msg)
|
||||||
|
|
||||||
elif msg['type'] == RPCMessageType.SELL_NOTIFICATION:
|
elif msg['type'] == RPCMessageType.SELL_NOTIFICATION:
|
||||||
msg['amount'] = round(msg['amount'], 8)
|
msg['amount'] = round(msg['amount'], 8)
|
||||||
msg['profit_percent'] = round(msg['profit_percent'] * 100, 2)
|
msg['profit_percent'] = round(msg['profit_percent'] * 100, 2)
|
||||||
|
@ -149,10 +154,10 @@ class Telegram(RPC):
|
||||||
msg['duration_min'] = msg['duration'].total_seconds() / 60
|
msg['duration_min'] = msg['duration'].total_seconds() / 60
|
||||||
|
|
||||||
message = ("*{exchange}:* Selling {pair}\n"
|
message = ("*{exchange}:* Selling {pair}\n"
|
||||||
"*Rate:* `{limit:.8f}`\n"
|
|
||||||
"*Amount:* `{amount:.8f}`\n"
|
"*Amount:* `{amount:.8f}`\n"
|
||||||
"*Open Rate:* `{open_rate:.8f}`\n"
|
"*Open Rate:* `{open_rate:.8f}`\n"
|
||||||
"*Current Rate:* `{current_rate:.8f}`\n"
|
"*Current Rate:* `{current_rate:.8f}`\n"
|
||||||
|
"*Close Rate:* `{limit:.8f}`\n"
|
||||||
"*Sell Reason:* `{sell_reason}`\n"
|
"*Sell Reason:* `{sell_reason}`\n"
|
||||||
"*Duration:* `{duration} ({duration_min:.1f} min)`\n"
|
"*Duration:* `{duration} ({duration_min:.1f} min)`\n"
|
||||||
"*Profit:* `{profit_percent:.2f}%`").format(**msg)
|
"*Profit:* `{profit_percent:.2f}%`").format(**msg)
|
||||||
|
@ -163,8 +168,11 @@ class Telegram(RPC):
|
||||||
and self._fiat_converter):
|
and self._fiat_converter):
|
||||||
msg['profit_fiat'] = self._fiat_converter.convert_amount(
|
msg['profit_fiat'] = self._fiat_converter.convert_amount(
|
||||||
msg['profit_amount'], msg['stake_currency'], msg['fiat_currency'])
|
msg['profit_amount'], msg['stake_currency'], msg['fiat_currency'])
|
||||||
message += ('` ({gain}: {profit_amount:.8f} {stake_currency}`'
|
message += (' `({gain}: {profit_amount:.8f} {stake_currency}'
|
||||||
'` / {profit_fiat:.3f} {fiat_currency})`').format(**msg)
|
' / {profit_fiat:.3f} {fiat_currency})`').format(**msg)
|
||||||
|
|
||||||
|
elif msg['type'] == RPCMessageType.SELL_CANCEL_NOTIFICATION:
|
||||||
|
message = "*{exchange}:* Cancelling Open Sell Order for {pair}".format(**msg)
|
||||||
|
|
||||||
elif msg['type'] == RPCMessageType.STATUS_NOTIFICATION:
|
elif msg['type'] == RPCMessageType.STATUS_NOTIFICATION:
|
||||||
message = '*Status:* `{status}`'.format(**msg)
|
message = '*Status:* `{status}`'.format(**msg)
|
||||||
|
@ -553,6 +561,8 @@ class Telegram(RPC):
|
||||||
"*/stop:* `Stops the trader`\n" \
|
"*/stop:* `Stops the trader`\n" \
|
||||||
"*/status [table]:* `Lists all open trades`\n" \
|
"*/status [table]:* `Lists all open trades`\n" \
|
||||||
" *table :* `will display trades in a table`\n" \
|
" *table :* `will display trades in a table`\n" \
|
||||||
|
" `pending buy orders are marked with an asterisk (*)`\n" \
|
||||||
|
" `pending sell orders are marked with a double asterisk (**)`\n" \
|
||||||
"*/profit:* `Lists cumulative profit from all finished trades`\n" \
|
"*/profit:* `Lists cumulative profit from all finished trades`\n" \
|
||||||
"*/forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, " \
|
"*/forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, " \
|
||||||
"regardless of profit`\n" \
|
"regardless of profit`\n" \
|
||||||
|
|
|
@ -41,8 +41,12 @@ class Webhook(RPC):
|
||||||
|
|
||||||
if msg['type'] == RPCMessageType.BUY_NOTIFICATION:
|
if msg['type'] == RPCMessageType.BUY_NOTIFICATION:
|
||||||
valuedict = self._config['webhook'].get('webhookbuy', None)
|
valuedict = self._config['webhook'].get('webhookbuy', None)
|
||||||
|
elif msg['type'] == RPCMessageType.BUY_CANCEL_NOTIFICATION:
|
||||||
|
valuedict = self._config['webhook'].get('webhookbuycancel', None)
|
||||||
elif msg['type'] == RPCMessageType.SELL_NOTIFICATION:
|
elif msg['type'] == RPCMessageType.SELL_NOTIFICATION:
|
||||||
valuedict = self._config['webhook'].get('webhooksell', None)
|
valuedict = self._config['webhook'].get('webhooksell', None)
|
||||||
|
elif msg['type'] == RPCMessageType.SELL_CANCEL_NOTIFICATION:
|
||||||
|
valuedict = self._config['webhook'].get('webhooksellcancel', None)
|
||||||
elif msg['type'] in(RPCMessageType.STATUS_NOTIFICATION,
|
elif msg['type'] in(RPCMessageType.STATUS_NOTIFICATION,
|
||||||
RPCMessageType.CUSTOM_NOTIFICATION,
|
RPCMessageType.CUSTOM_NOTIFICATION,
|
||||||
RPCMessageType.WARNING_NOTIFICATION):
|
RPCMessageType.WARNING_NOTIFICATION):
|
||||||
|
|
58
freqtrade/templates/base_config.json.j2
Normal file
58
freqtrade/templates/base_config.json.j2
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
{
|
||||||
|
"max_open_trades": {{ max_open_trades }},
|
||||||
|
"stake_currency": "{{ stake_currency }}",
|
||||||
|
"stake_amount": {{ stake_amount }},
|
||||||
|
"tradable_balance_ratio": 0.99,
|
||||||
|
"fiat_display_currency": "{{ fiat_display_currency }}",
|
||||||
|
"ticker_interval": "{{ ticker_interval }}",
|
||||||
|
"dry_run": {{ dry_run | lower }},
|
||||||
|
"unfilledtimeout": {
|
||||||
|
"buy": 10,
|
||||||
|
"sell": 30
|
||||||
|
},
|
||||||
|
"bid_strategy": {
|
||||||
|
"ask_last_balance": 0.0,
|
||||||
|
"use_order_book": false,
|
||||||
|
"order_book_top": 1,
|
||||||
|
"check_depth_of_market": {
|
||||||
|
"enabled": false,
|
||||||
|
"bids_to_ask_delta": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ask_strategy": {
|
||||||
|
"use_order_book": false,
|
||||||
|
"order_book_min": 1,
|
||||||
|
"order_book_max": 9,
|
||||||
|
"use_sell_signal": true,
|
||||||
|
"sell_profit_only": false,
|
||||||
|
"ignore_roi_if_buy_signal": false
|
||||||
|
},
|
||||||
|
{{ exchange | indent(4) }},
|
||||||
|
"pairlists": [
|
||||||
|
{"method": "StaticPairList"}
|
||||||
|
],
|
||||||
|
"edge": {
|
||||||
|
"enabled": false,
|
||||||
|
"process_throttle_secs": 3600,
|
||||||
|
"calculate_since_number_of_days": 7,
|
||||||
|
"allowed_risk": 0.01,
|
||||||
|
"stoploss_range_min": -0.01,
|
||||||
|
"stoploss_range_max": -0.1,
|
||||||
|
"stoploss_range_step": -0.01,
|
||||||
|
"minimum_winrate": 0.60,
|
||||||
|
"minimum_expectancy": 0.20,
|
||||||
|
"min_trade_number": 10,
|
||||||
|
"max_trade_duration_minute": 1440,
|
||||||
|
"remove_pumps": false
|
||||||
|
},
|
||||||
|
"telegram": {
|
||||||
|
"enabled": {{ telegram | lower }},
|
||||||
|
"token": "{{ telegram_token }}",
|
||||||
|
"chat_id": "{{ telegram_chat_id }}"
|
||||||
|
},
|
||||||
|
"initial_state": "running",
|
||||||
|
"forcebuy_enable": false,
|
||||||
|
"internals": {
|
||||||
|
"process_throttle_secs": 5
|
||||||
|
}
|
||||||
|
}
|
|
@ -230,7 +230,7 @@ class AdvancedSampleHyperOpt(IHyperOpt):
|
||||||
'stoploss' optimization hyperspace.
|
'stoploss' optimization hyperspace.
|
||||||
"""
|
"""
|
||||||
return [
|
return [
|
||||||
Real(-0.5, -0.02, name='stoploss'),
|
Real(-0.35, -0.02, name='stoploss'),
|
||||||
]
|
]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -249,8 +249,15 @@ class AdvancedSampleHyperOpt(IHyperOpt):
|
||||||
# other 'trailing' hyperspace parameters.
|
# other 'trailing' hyperspace parameters.
|
||||||
Categorical([True], name='trailing_stop'),
|
Categorical([True], name='trailing_stop'),
|
||||||
|
|
||||||
Real(0.02, 0.35, name='trailing_stop_positive'),
|
Real(0.01, 0.35, name='trailing_stop_positive'),
|
||||||
Real(0.01, 0.1, name='trailing_stop_positive_offset'),
|
|
||||||
|
# 'trailing_stop_positive_offset' should be greater than 'trailing_stop_positive',
|
||||||
|
# so this intermediate parameter is used as the value of the difference between
|
||||||
|
# them. The value of the 'trailing_stop_positive_offset' is constructed in the
|
||||||
|
# generate_trailing_params() method.
|
||||||
|
# This is similar to the hyperspace dimensions used for constructing the ROI tables.
|
||||||
|
Real(0.001, 0.1, name='trailing_stop_positive_offset_p1'),
|
||||||
|
|
||||||
Categorical([True, False], name='trailing_only_offset_is_reached'),
|
Categorical([True, False], name='trailing_only_offset_is_reached'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
"source": [
|
"source": [
|
||||||
"# Strategy analysis example\n",
|
"# Strategy analysis example\n",
|
||||||
"\n",
|
"\n",
|
||||||
"Debugging a strategy can be time-consuming. FreqTrade offers helper functions to visualize raw data."
|
"Debugging a strategy can be time-consuming. Freqtrade offers helper functions to visualize raw data.\n",
|
||||||
|
"The following assumes you work with SampleStrategy, data for 5m timeframe from Binance and have downloaded them into the data directory in the default location."
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -23,18 +24,21 @@
|
||||||
"outputs": [],
|
"outputs": [],
|
||||||
"source": [
|
"source": [
|
||||||
"from pathlib import Path\n",
|
"from pathlib import Path\n",
|
||||||
|
"from freqtrade.configuration import Configuration\n",
|
||||||
|
"\n",
|
||||||
"# Customize these according to your needs.\n",
|
"# Customize these according to your needs.\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
"# Initialize empty configuration object\n",
|
||||||
|
"config = Configuration.from_files([])\n",
|
||||||
|
"# Optionally, use existing configuration file\n",
|
||||||
|
"# config = Configuration.from_files([\"config.json\"])\n",
|
||||||
|
"\n",
|
||||||
"# Define some constants\n",
|
"# Define some constants\n",
|
||||||
"timeframe = \"5m\"\n",
|
"config[\"ticker_interval\"] = \"5m\"\n",
|
||||||
"# Name of the strategy class\n",
|
"# Name of the strategy class\n",
|
||||||
"strategy_name = 'SampleStrategy'\n",
|
"config[\"strategy\"] = \"SampleStrategy\"\n",
|
||||||
"# Path to user data\n",
|
|
||||||
"user_data_dir = Path('user_data')\n",
|
|
||||||
"# Location of the strategy\n",
|
|
||||||
"strategy_location = user_data_dir / 'strategies'\n",
|
|
||||||
"# Location of the data\n",
|
"# Location of the data\n",
|
||||||
"data_location = Path(user_data_dir, 'data', 'binance')\n",
|
"data_location = Path(config['user_data_dir'], 'data', 'binance')\n",
|
||||||
"# Pair to analyze - Only use one pair here\n",
|
"# Pair to analyze - Only use one pair here\n",
|
||||||
"pair = \"BTC_USDT\""
|
"pair = \"BTC_USDT\""
|
||||||
]
|
]
|
||||||
|
@ -49,7 +53,7 @@
|
||||||
"from freqtrade.data.history import load_pair_history\n",
|
"from freqtrade.data.history import load_pair_history\n",
|
||||||
"\n",
|
"\n",
|
||||||
"candles = load_pair_history(datadir=data_location,\n",
|
"candles = load_pair_history(datadir=data_location,\n",
|
||||||
" timeframe=timeframe,\n",
|
" timeframe=config[\"ticker_interval\"],\n",
|
||||||
" pair=pair)\n",
|
" pair=pair)\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Confirm success\n",
|
"# Confirm success\n",
|
||||||
|
@ -73,9 +77,7 @@
|
||||||
"source": [
|
"source": [
|
||||||
"# Load strategy using values set above\n",
|
"# Load strategy using values set above\n",
|
||||||
"from freqtrade.resolvers import StrategyResolver\n",
|
"from freqtrade.resolvers import StrategyResolver\n",
|
||||||
"strategy = StrategyResolver.load_strategy({'strategy': strategy_name,\n",
|
"strategy = StrategyResolver.load_strategy(config)\n",
|
||||||
" 'user_data_dir': user_data_dir,\n",
|
|
||||||
" 'strategy_path': strategy_location})\n",
|
|
||||||
"\n",
|
"\n",
|
||||||
"# Generate buy/sell signals using strategy\n",
|
"# Generate buy/sell signals using strategy\n",
|
||||||
"df = strategy.analyze_ticker(candles, {'pair': pair})\n",
|
"df = strategy.analyze_ticker(candles, {'pair': pair})\n",
|
||||||
|
@ -137,7 +139,7 @@
|
||||||
"from freqtrade.data.btanalysis import load_backtest_data\n",
|
"from freqtrade.data.btanalysis import load_backtest_data\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Load backtest results\n",
|
"# Load backtest results\n",
|
||||||
"trades = load_backtest_data(user_data_dir / \"backtest_results/backtest-result.json\")\n",
|
"trades = load_backtest_data(config[\"user_data_dir\"] / \"backtest_results/backtest-result.json\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
"# Show value-counts per pair\n",
|
"# Show value-counts per pair\n",
|
||||||
"trades.groupby(\"pair\")[\"sell_reason\"].value_counts()"
|
"trades.groupby(\"pair\")[\"sell_reason\"].value_counts()"
|
||||||
|
|
41
freqtrade/templates/subtemplates/exchange_binance.j2
Normal file
41
freqtrade/templates/subtemplates/exchange_binance.j2
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
"exchange": {
|
||||||
|
"name": "{{ exchange_name | lower }}",
|
||||||
|
"key": "{{ exchange_key }}",
|
||||||
|
"secret": "{{ exchange_secret }}",
|
||||||
|
"ccxt_config": {"enableRateLimit": true},
|
||||||
|
"ccxt_async_config": {
|
||||||
|
"enableRateLimit": true,
|
||||||
|
"rateLimit": 200
|
||||||
|
},
|
||||||
|
"pair_whitelist": [
|
||||||
|
"ALGO/BTC",
|
||||||
|
"ATOM/BTC",
|
||||||
|
"BAT/BTC",
|
||||||
|
"BCH/BTC",
|
||||||
|
"BRD/BTC",
|
||||||
|
"EOS/BTC",
|
||||||
|
"ETH/BTC",
|
||||||
|
"IOTA/BTC",
|
||||||
|
"LINK/BTC",
|
||||||
|
"LTC/BTC",
|
||||||
|
"NEO/BTC",
|
||||||
|
"NXS/BTC",
|
||||||
|
"XMR/BTC",
|
||||||
|
"XRP/BTC",
|
||||||
|
"XTZ/BTC"
|
||||||
|
],
|
||||||
|
"pair_blacklist": [
|
||||||
|
"BNB/BTC",
|
||||||
|
"BNB/BUSD",
|
||||||
|
"BNB/ETH",
|
||||||
|
"BNB/EUR",
|
||||||
|
"BNB/NGN",
|
||||||
|
"BNB/PAX",
|
||||||
|
"BNB/RUB",
|
||||||
|
"BNB/TRY",
|
||||||
|
"BNB/TUSD",
|
||||||
|
"BNB/USDC",
|
||||||
|
"BNB/USDS",
|
||||||
|
"BNB/USDT",
|
||||||
|
]
|
||||||
|
}
|
31
freqtrade/templates/subtemplates/exchange_bittrex.j2
Normal file
31
freqtrade/templates/subtemplates/exchange_bittrex.j2
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
"order_types": {
|
||||||
|
"buy": "limit",
|
||||||
|
"sell": "limit",
|
||||||
|
"emergencysell": "limit",
|
||||||
|
"stoploss": "limit",
|
||||||
|
"stoploss_on_exchange": false
|
||||||
|
},
|
||||||
|
"exchange": {
|
||||||
|
"name": "{{ exchange_name | lower }}",
|
||||||
|
"key": "{{ exchange_key }}",
|
||||||
|
"secret": "{{ exchange_secret }}",
|
||||||
|
"ccxt_config": {"enableRateLimit": true},
|
||||||
|
"ccxt_async_config": {
|
||||||
|
"enableRateLimit": true,
|
||||||
|
"rateLimit": 500
|
||||||
|
},
|
||||||
|
"pair_whitelist": [
|
||||||
|
"ETH/BTC",
|
||||||
|
"LTC/BTC",
|
||||||
|
"ETC/BTC",
|
||||||
|
"DASH/BTC",
|
||||||
|
"ZEC/BTC",
|
||||||
|
"XLM/BTC",
|
||||||
|
"XRP/BTC",
|
||||||
|
"TRX/BTC",
|
||||||
|
"ADA/BTC",
|
||||||
|
"XMR/BTC"
|
||||||
|
],
|
||||||
|
"pair_blacklist": [
|
||||||
|
]
|
||||||
|
}
|
15
freqtrade/templates/subtemplates/exchange_generic.j2
Normal file
15
freqtrade/templates/subtemplates/exchange_generic.j2
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
"exchange": {
|
||||||
|
"name": "{{ exchange_name | lower }}",
|
||||||
|
"key": "{{ exchange_key }}",
|
||||||
|
"secret": "{{ exchange_secret }}",
|
||||||
|
"ccxt_config": {"enableRateLimit": true},
|
||||||
|
"ccxt_async_config": {
|
||||||
|
"enableRateLimit": true
|
||||||
|
},
|
||||||
|
"pair_whitelist": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"pair_blacklist": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
36
freqtrade/templates/subtemplates/exchange_kraken.j2
Normal file
36
freqtrade/templates/subtemplates/exchange_kraken.j2
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
"download_trades": true,
|
||||||
|
"exchange": {
|
||||||
|
"name": "kraken",
|
||||||
|
"key": "{{ exchange_key }}",
|
||||||
|
"secret": "{{ exchange_secret }}",
|
||||||
|
"ccxt_config": {"enableRateLimit": true},
|
||||||
|
"ccxt_async_config": {
|
||||||
|
"enableRateLimit": true,
|
||||||
|
"rateLimit": 1000
|
||||||
|
},
|
||||||
|
"pair_whitelist": [
|
||||||
|
"ADA/EUR",
|
||||||
|
"ATOM/EUR",
|
||||||
|
"BAT/EUR",
|
||||||
|
"BCH/EUR",
|
||||||
|
"BTC/EUR",
|
||||||
|
"DAI/EUR",
|
||||||
|
"DASH/EUR",
|
||||||
|
"EOS/EUR",
|
||||||
|
"ETC/EUR",
|
||||||
|
"ETH/EUR",
|
||||||
|
"LINK/EUR",
|
||||||
|
"LTC/EUR",
|
||||||
|
"QTUM/EUR",
|
||||||
|
"REP/EUR",
|
||||||
|
"WAVES/EUR",
|
||||||
|
"XLM/EUR",
|
||||||
|
"XMR/EUR",
|
||||||
|
"XRP/EUR",
|
||||||
|
"XTZ/EUR",
|
||||||
|
"ZEC/EUR"
|
||||||
|
],
|
||||||
|
"pair_blacklist": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,13 +1,13 @@
|
||||||
# requirements without requirements installable via conda
|
# requirements without requirements installable via conda
|
||||||
# mainly used for Raspberry pi installs
|
# mainly used for Raspberry pi installs
|
||||||
ccxt==1.22.30
|
ccxt==1.22.61
|
||||||
SQLAlchemy==1.3.13
|
SQLAlchemy==1.3.13
|
||||||
python-telegram-bot==12.3.0
|
python-telegram-bot==12.4.2
|
||||||
arrow==0.15.5
|
arrow==0.15.5
|
||||||
cachetools==4.0.0
|
cachetools==4.0.0
|
||||||
requests==2.22.0
|
requests==2.22.0
|
||||||
urllib3==1.25.8
|
urllib3==1.25.8
|
||||||
wrapt==1.11.2
|
wrapt==1.12.0
|
||||||
jsonschema==3.2.0
|
jsonschema==3.2.0
|
||||||
TA-Lib==0.4.17
|
TA-Lib==0.4.17
|
||||||
tabulate==0.8.6
|
tabulate==0.8.6
|
||||||
|
@ -28,3 +28,6 @@ flask==1.1.1
|
||||||
|
|
||||||
# Support for colorized terminal output
|
# Support for colorized terminal output
|
||||||
colorama==0.4.3
|
colorama==0.4.3
|
||||||
|
# Building config files interactively
|
||||||
|
questionary==1.5.1
|
||||||
|
prompt-toolkit==3.0.3
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
-r requirements-plot.txt
|
-r requirements-plot.txt
|
||||||
-r requirements-hyperopt.txt
|
-r requirements-hyperopt.txt
|
||||||
|
|
||||||
coveralls==1.10.0
|
coveralls==1.11.1
|
||||||
flake8==3.7.9
|
flake8==3.7.9
|
||||||
flake8-type-annotations==0.1.0
|
flake8-type-annotations==0.1.0
|
||||||
flake8-tidy-imports==4.0.0
|
flake8-tidy-imports==4.0.0
|
||||||
|
|
|
@ -4,6 +4,6 @@
|
||||||
# Required for hyperopt
|
# Required for hyperopt
|
||||||
scipy==1.4.1
|
scipy==1.4.1
|
||||||
scikit-learn==0.22.1
|
scikit-learn==0.22.1
|
||||||
scikit-optimize==0.7.1
|
scikit-optimize==0.7.2
|
||||||
filelock==3.0.12
|
filelock==3.0.12
|
||||||
joblib==0.14.1
|
joblib==0.14.1
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
-r requirements-common.txt
|
-r requirements-common.txt
|
||||||
|
|
||||||
numpy==1.18.1
|
numpy==1.18.1
|
||||||
pandas==1.0.0
|
pandas==1.0.1
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -79,6 +79,8 @@ setup(name='freqtrade',
|
||||||
'sdnotify',
|
'sdnotify',
|
||||||
'colorama',
|
'colorama',
|
||||||
'jinja2',
|
'jinja2',
|
||||||
|
'questionary',
|
||||||
|
'prompt-toolkit',
|
||||||
# from requirements.txt
|
# from requirements.txt
|
||||||
'numpy',
|
'numpy',
|
||||||
'pandas',
|
'pandas',
|
||||||
|
|
29
setup.sh
29
setup.sh
|
@ -17,6 +17,14 @@ function check_installed_python() {
|
||||||
exit 2
|
exit 2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
which python3.8
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "using Python 3.8"
|
||||||
|
PYTHON=python3.8
|
||||||
|
check_installed_pip
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
which python3.7
|
which python3.7
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
echo "using Python 3.7"
|
echo "using Python 3.7"
|
||||||
|
@ -215,27 +223,8 @@ function config_generator() {
|
||||||
function config() {
|
function config() {
|
||||||
|
|
||||||
echo "-------------------------"
|
echo "-------------------------"
|
||||||
echo "Generating config file"
|
echo "Please use 'freqtrade new-config -c config.json' to generate a new configuration file."
|
||||||
echo "-------------------------"
|
echo "-------------------------"
|
||||||
if [ -f config.json ]
|
|
||||||
then
|
|
||||||
read -p "A config file already exist, do you want to override it [y/N]? "
|
|
||||||
if [[ $REPLY =~ ^[Yy]$ ]]
|
|
||||||
then
|
|
||||||
config_generator
|
|
||||||
else
|
|
||||||
echo "Configuration of config.json ignored."
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
config_generator
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo "-------------------------"
|
|
||||||
echo "Config file generated"
|
|
||||||
echo "-------------------------"
|
|
||||||
echo "Edit ./config.json to modify Pair and other configurations."
|
|
||||||
echo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function install() {
|
function install() {
|
||||||
|
|
116
tests/commands/test_build_config.py
Normal file
116
tests/commands/test_build_config.py
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
from pathlib import Path
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import rapidjson
|
||||||
|
|
||||||
|
from freqtrade.commands.build_config_commands import (ask_user_config,
|
||||||
|
ask_user_overwrite,
|
||||||
|
start_new_config,
|
||||||
|
validate_is_float,
|
||||||
|
validate_is_int)
|
||||||
|
from freqtrade.exceptions import OperationalException
|
||||||
|
from tests.conftest import get_args, log_has_re
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_is_float():
|
||||||
|
assert validate_is_float('2.0')
|
||||||
|
assert validate_is_float('2.1')
|
||||||
|
assert validate_is_float('0.1')
|
||||||
|
assert validate_is_float('-0.5')
|
||||||
|
assert not validate_is_float('-0.5e')
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_is_int():
|
||||||
|
assert validate_is_int('2')
|
||||||
|
assert validate_is_int('6')
|
||||||
|
assert validate_is_int('-1')
|
||||||
|
assert validate_is_int('500')
|
||||||
|
assert not validate_is_int('2.0')
|
||||||
|
assert not validate_is_int('2.1')
|
||||||
|
assert not validate_is_int('-2.1')
|
||||||
|
assert not validate_is_int('-ee')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('exchange', ['bittrex', 'binance', 'kraken', 'ftx'])
|
||||||
|
def test_start_new_config(mocker, caplog, exchange):
|
||||||
|
wt_mock = mocker.patch.object(Path, "write_text", MagicMock())
|
||||||
|
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
|
||||||
|
unlink_mock = mocker.patch.object(Path, "unlink", MagicMock())
|
||||||
|
mocker.patch('freqtrade.commands.build_config_commands.ask_user_overwrite', return_value=True)
|
||||||
|
|
||||||
|
sample_selections = {
|
||||||
|
'max_open_trades': 3,
|
||||||
|
'stake_currency': 'USDT',
|
||||||
|
'stake_amount': 100,
|
||||||
|
'fiat_display_currency': 'EUR',
|
||||||
|
'ticker_interval': '15m',
|
||||||
|
'dry_run': True,
|
||||||
|
'exchange_name': exchange,
|
||||||
|
'exchange_key': 'sampleKey',
|
||||||
|
'exchange_secret': 'Samplesecret',
|
||||||
|
'telegram': False,
|
||||||
|
'telegram_token': 'asdf1244',
|
||||||
|
'telegram_chat_id': '1144444',
|
||||||
|
}
|
||||||
|
mocker.patch('freqtrade.commands.build_config_commands.ask_user_config',
|
||||||
|
return_value=sample_selections)
|
||||||
|
args = [
|
||||||
|
"new-config",
|
||||||
|
"--config",
|
||||||
|
"coolconfig.json"
|
||||||
|
]
|
||||||
|
start_new_config(get_args(args))
|
||||||
|
|
||||||
|
assert log_has_re("Writing config to .*", caplog)
|
||||||
|
assert wt_mock.call_count == 1
|
||||||
|
assert unlink_mock.call_count == 1
|
||||||
|
result = rapidjson.loads(wt_mock.call_args_list[0][0][0],
|
||||||
|
parse_mode=rapidjson.PM_COMMENTS | rapidjson.PM_TRAILING_COMMAS)
|
||||||
|
assert result['exchange']['name'] == exchange
|
||||||
|
assert result['ticker_interval'] == '15m'
|
||||||
|
|
||||||
|
|
||||||
|
def test_start_new_config_exists(mocker, caplog):
|
||||||
|
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
|
||||||
|
mocker.patch('freqtrade.commands.build_config_commands.ask_user_overwrite', return_value=False)
|
||||||
|
args = [
|
||||||
|
"new-config",
|
||||||
|
"--config",
|
||||||
|
"coolconfig.json"
|
||||||
|
]
|
||||||
|
with pytest.raises(OperationalException, match=r"Configuration .* already exists\."):
|
||||||
|
start_new_config(get_args(args))
|
||||||
|
|
||||||
|
|
||||||
|
def test_ask_user_overwrite(mocker):
|
||||||
|
"""
|
||||||
|
Once https://github.com/tmbo/questionary/issues/35 is implemented, improve this test.
|
||||||
|
"""
|
||||||
|
prompt_mock = mocker.patch('freqtrade.commands.build_config_commands.prompt',
|
||||||
|
return_value={'overwrite': False})
|
||||||
|
assert not ask_user_overwrite(Path('test.json'))
|
||||||
|
assert prompt_mock.call_count == 1
|
||||||
|
|
||||||
|
prompt_mock.reset_mock()
|
||||||
|
prompt_mock = mocker.patch('freqtrade.commands.build_config_commands.prompt',
|
||||||
|
return_value={'overwrite': True})
|
||||||
|
assert ask_user_overwrite(Path('test.json'))
|
||||||
|
assert prompt_mock.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_ask_user_config(mocker):
|
||||||
|
"""
|
||||||
|
Once https://github.com/tmbo/questionary/issues/35 is implemented, improve this test.
|
||||||
|
"""
|
||||||
|
prompt_mock = mocker.patch('freqtrade.commands.build_config_commands.prompt',
|
||||||
|
return_value={'overwrite': False})
|
||||||
|
answers = ask_user_config()
|
||||||
|
assert isinstance(answers, dict)
|
||||||
|
assert prompt_mock.call_count == 1
|
||||||
|
|
||||||
|
prompt_mock = mocker.patch('freqtrade.commands.build_config_commands.prompt',
|
||||||
|
return_value={})
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException, match=r"User interrupted interactive questions\."):
|
||||||
|
ask_user_config()
|
|
@ -778,6 +778,121 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results):
|
||||||
assert all(x not in captured.out
|
assert all(x not in captured.out
|
||||||
for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
|
for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
|
||||||
" 11/12", " 12/12"])
|
" 11/12", " 12/12"])
|
||||||
|
args = [
|
||||||
|
"hyperopt-list",
|
||||||
|
"--no-details",
|
||||||
|
"--no-color",
|
||||||
|
"--min-trades", "20"
|
||||||
|
]
|
||||||
|
pargs = get_args(args)
|
||||||
|
pargs['config'] = None
|
||||||
|
start_hyperopt_list(pargs)
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert all(x in captured.out
|
||||||
|
for x in [" 3/12", " 6/12", " 7/12", " 9/12", " 11/12"])
|
||||||
|
assert all(x not in captured.out
|
||||||
|
for x in [" 1/12", " 2/12", " 4/12", " 5/12", " 8/12", " 10/12", " 12/12"])
|
||||||
|
args = [
|
||||||
|
"hyperopt-list",
|
||||||
|
"--profitable",
|
||||||
|
"--no-details",
|
||||||
|
"--max-trades", "20"
|
||||||
|
]
|
||||||
|
pargs = get_args(args)
|
||||||
|
pargs['config'] = None
|
||||||
|
start_hyperopt_list(pargs)
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert all(x in captured.out
|
||||||
|
for x in [" 2/12", " 10/12"])
|
||||||
|
assert all(x not in captured.out
|
||||||
|
for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
|
||||||
|
" 11/12", " 12/12"])
|
||||||
|
args = [
|
||||||
|
"hyperopt-list",
|
||||||
|
"--profitable",
|
||||||
|
"--no-details",
|
||||||
|
"--min-avg-profit", "0.11"
|
||||||
|
]
|
||||||
|
pargs = get_args(args)
|
||||||
|
pargs['config'] = None
|
||||||
|
start_hyperopt_list(pargs)
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert all(x in captured.out
|
||||||
|
for x in [" 2/12"])
|
||||||
|
assert all(x not in captured.out
|
||||||
|
for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
|
||||||
|
" 10/12", " 11/12", " 12/12"])
|
||||||
|
args = [
|
||||||
|
"hyperopt-list",
|
||||||
|
"--no-details",
|
||||||
|
"--max-avg-profit", "0.10"
|
||||||
|
]
|
||||||
|
pargs = get_args(args)
|
||||||
|
pargs['config'] = None
|
||||||
|
start_hyperopt_list(pargs)
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert all(x in captured.out
|
||||||
|
for x in [" 1/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12", " 9/12",
|
||||||
|
" 11/12"])
|
||||||
|
assert all(x not in captured.out
|
||||||
|
for x in [" 2/12", " 4/12", " 10/12", " 12/12"])
|
||||||
|
args = [
|
||||||
|
"hyperopt-list",
|
||||||
|
"--no-details",
|
||||||
|
"--min-total-profit", "0.4"
|
||||||
|
]
|
||||||
|
pargs = get_args(args)
|
||||||
|
pargs['config'] = None
|
||||||
|
start_hyperopt_list(pargs)
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert all(x in captured.out
|
||||||
|
for x in [" 10/12"])
|
||||||
|
assert all(x not in captured.out
|
||||||
|
for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12", " 8/12",
|
||||||
|
" 9/12", " 11/12", " 12/12"])
|
||||||
|
args = [
|
||||||
|
"hyperopt-list",
|
||||||
|
"--no-details",
|
||||||
|
"--max-total-profit", "0.4"
|
||||||
|
]
|
||||||
|
pargs = get_args(args)
|
||||||
|
pargs['config'] = None
|
||||||
|
start_hyperopt_list(pargs)
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert all(x in captured.out
|
||||||
|
for x in [" 1/12", " 2/12", " 3/12", " 5/12", " 6/12", " 7/12", " 8/12",
|
||||||
|
" 9/12", " 11/12"])
|
||||||
|
assert all(x not in captured.out
|
||||||
|
for x in [" 4/12", " 10/12", " 12/12"])
|
||||||
|
args = [
|
||||||
|
"hyperopt-list",
|
||||||
|
"--profitable",
|
||||||
|
"--no-details",
|
||||||
|
"--min-avg-time", "2000"
|
||||||
|
]
|
||||||
|
pargs = get_args(args)
|
||||||
|
pargs['config'] = None
|
||||||
|
start_hyperopt_list(pargs)
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert all(x in captured.out
|
||||||
|
for x in [" 10/12"])
|
||||||
|
assert all(x not in captured.out
|
||||||
|
for x in [" 1/12", " 2/12", " 3/12", " 4/12", " 5/12", " 6/12", " 7/12",
|
||||||
|
" 8/12", " 9/12", " 11/12", " 12/12"])
|
||||||
|
args = [
|
||||||
|
"hyperopt-list",
|
||||||
|
"--no-details",
|
||||||
|
"--max-avg-time", "1500"
|
||||||
|
]
|
||||||
|
pargs = get_args(args)
|
||||||
|
pargs['config'] = None
|
||||||
|
start_hyperopt_list(pargs)
|
||||||
|
captured = capsys.readouterr()
|
||||||
|
assert all(x in captured.out
|
||||||
|
for x in [" 2/12", " 6/12"])
|
||||||
|
assert all(x not in captured.out
|
||||||
|
for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 7/12", " 8/12"
|
||||||
|
" 9/12", " 10/12", " 11/12", " 12/12"])
|
||||||
|
|
||||||
|
|
||||||
def test_hyperopt_show(mocker, capsys, hyperopt_results):
|
def test_hyperopt_show(mocker, capsys, hyperopt_results):
|
||||||
|
|
|
@ -24,7 +24,7 @@ from freqtrade.data.history import (_download_pair_history,
|
||||||
validate_backtest_data)
|
validate_backtest_data)
|
||||||
from freqtrade.exchange import timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
from freqtrade.misc import file_dump_json
|
from freqtrade.misc import file_dump_json
|
||||||
from freqtrade.strategy.default_strategy import DefaultStrategy
|
from freqtrade.resolvers import StrategyResolver
|
||||||
from tests.conftest import (get_patched_exchange, log_has, log_has_re,
|
from tests.conftest import (get_patched_exchange, log_has, log_has_re,
|
||||||
patch_exchange)
|
patch_exchange)
|
||||||
|
|
||||||
|
@ -370,7 +370,7 @@ def test_load_partial_missing(testdatadir, caplog) -> None:
|
||||||
|
|
||||||
def test_init(default_conf, mocker) -> None:
|
def test_init(default_conf, mocker) -> None:
|
||||||
assert {} == load_data(
|
assert {} == load_data(
|
||||||
datadir='',
|
datadir=Path(''),
|
||||||
pairs=[],
|
pairs=[],
|
||||||
timeframe=default_conf['ticker_interval']
|
timeframe=default_conf['ticker_interval']
|
||||||
)
|
)
|
||||||
|
@ -379,13 +379,13 @@ def test_init(default_conf, mocker) -> None:
|
||||||
def test_init_with_refresh(default_conf, mocker) -> None:
|
def test_init_with_refresh(default_conf, mocker) -> None:
|
||||||
exchange = get_patched_exchange(mocker, default_conf)
|
exchange = get_patched_exchange(mocker, default_conf)
|
||||||
refresh_data(
|
refresh_data(
|
||||||
datadir='',
|
datadir=Path(''),
|
||||||
pairs=[],
|
pairs=[],
|
||||||
timeframe=default_conf['ticker_interval'],
|
timeframe=default_conf['ticker_interval'],
|
||||||
exchange=exchange
|
exchange=exchange
|
||||||
)
|
)
|
||||||
assert {} == load_data(
|
assert {} == load_data(
|
||||||
datadir='',
|
datadir=Path(''),
|
||||||
pairs=[],
|
pairs=[],
|
||||||
timeframe=default_conf['ticker_interval']
|
timeframe=default_conf['ticker_interval']
|
||||||
)
|
)
|
||||||
|
@ -420,7 +420,7 @@ def test_trim_tickerlist(testdatadir) -> None:
|
||||||
|
|
||||||
# Test the pattern ^(\d{8})-$
|
# Test the pattern ^(\d{8})-$
|
||||||
# This pattern extracts elements from the date to now
|
# This pattern extracts elements from the date to now
|
||||||
timerange = TimeRange('date', None, ticker_list[10][0] / 1000 - 1, None)
|
timerange = TimeRange('date', None, ticker_list[10][0] / 1000 - 1, 0)
|
||||||
ticker = trim_tickerlist(ticker_list, timerange)
|
ticker = trim_tickerlist(ticker_list, timerange)
|
||||||
ticker_len = len(ticker)
|
ticker_len = len(ticker)
|
||||||
|
|
||||||
|
@ -430,14 +430,14 @@ def test_trim_tickerlist(testdatadir) -> None:
|
||||||
|
|
||||||
# Test a wrong pattern
|
# Test a wrong pattern
|
||||||
# This pattern must return the list unchanged
|
# This pattern must return the list unchanged
|
||||||
timerange = TimeRange(None, None, None, 5)
|
timerange = TimeRange(None, None, 0, 5)
|
||||||
ticker = trim_tickerlist(ticker_list, timerange)
|
ticker = trim_tickerlist(ticker_list, timerange)
|
||||||
ticker_len = len(ticker)
|
ticker_len = len(ticker)
|
||||||
|
|
||||||
assert ticker_list_len == ticker_len
|
assert ticker_list_len == ticker_len
|
||||||
|
|
||||||
# passing empty list
|
# passing empty list
|
||||||
timerange = TimeRange(None, None, None, 5)
|
timerange = TimeRange(None, None, 0, 5)
|
||||||
ticker = trim_tickerlist([], timerange)
|
ticker = trim_tickerlist([], timerange)
|
||||||
assert 0 == len(ticker)
|
assert 0 == len(ticker)
|
||||||
assert not ticker
|
assert not ticker
|
||||||
|
@ -509,7 +509,9 @@ def test_file_dump_json_tofile(testdatadir) -> None:
|
||||||
|
|
||||||
def test_get_timerange(default_conf, mocker, testdatadir) -> None:
|
def test_get_timerange(default_conf, mocker, testdatadir) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
strategy = DefaultStrategy(default_conf)
|
|
||||||
|
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||||
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
data = strategy.tickerdata_to_dataframe(
|
data = strategy.tickerdata_to_dataframe(
|
||||||
load_data(
|
load_data(
|
||||||
|
@ -525,7 +527,9 @@ def test_get_timerange(default_conf, mocker, testdatadir) -> None:
|
||||||
|
|
||||||
def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) -> None:
|
def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
strategy = DefaultStrategy(default_conf)
|
|
||||||
|
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||||
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
data = strategy.tickerdata_to_dataframe(
|
data = strategy.tickerdata_to_dataframe(
|
||||||
load_data(
|
load_data(
|
||||||
|
@ -547,7 +551,9 @@ def test_validate_backtest_data_warn(default_conf, mocker, caplog, testdatadir)
|
||||||
|
|
||||||
def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> None:
|
def test_validate_backtest_data(default_conf, mocker, caplog, testdatadir) -> None:
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
strategy = DefaultStrategy(default_conf)
|
|
||||||
|
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||||
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
timerange = TimeRange('index', 'index', 200, 250)
|
timerange = TimeRange('index', 'index', 200, 250)
|
||||||
data = strategy.tickerdata_to_dataframe(
|
data = strategy.tickerdata_to_dataframe(
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import Dict, List, NamedTuple
|
from typing import Dict, List, NamedTuple, Optional
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
@ -23,14 +23,14 @@ class BTContainer(NamedTuple):
|
||||||
"""
|
"""
|
||||||
Minimal BacktestContainer defining Backtest inputs and results.
|
Minimal BacktestContainer defining Backtest inputs and results.
|
||||||
"""
|
"""
|
||||||
data: List[float]
|
data: List[List[float]]
|
||||||
stop_loss: float
|
stop_loss: float
|
||||||
roi: Dict[str, float]
|
roi: Dict[str, float]
|
||||||
trades: List[BTrade]
|
trades: List[BTrade]
|
||||||
profit_perc: float
|
profit_perc: float
|
||||||
trailing_stop: bool = False
|
trailing_stop: bool = False
|
||||||
trailing_only_offset_is_reached: bool = False
|
trailing_only_offset_is_reached: bool = False
|
||||||
trailing_stop_positive: float = None
|
trailing_stop_positive: Optional[float] = None
|
||||||
trailing_stop_positive_offset: float = 0.0
|
trailing_stop_positive_offset: float = 0.0
|
||||||
use_sell_signal: bool = False
|
use_sell_signal: bool = False
|
||||||
|
|
||||||
|
|
|
@ -364,7 +364,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
|
||||||
default_conf["trailing_stop"] = data.trailing_stop
|
default_conf["trailing_stop"] = data.trailing_stop
|
||||||
default_conf["trailing_only_offset_is_reached"] = data.trailing_only_offset_is_reached
|
default_conf["trailing_only_offset_is_reached"] = data.trailing_only_offset_is_reached
|
||||||
# Only add this to configuration If it's necessary
|
# Only add this to configuration If it's necessary
|
||||||
if data.trailing_stop_positive:
|
if data.trailing_stop_positive is not None:
|
||||||
default_conf["trailing_stop_positive"] = data.trailing_stop_positive
|
default_conf["trailing_stop_positive"] = data.trailing_stop_positive
|
||||||
default_conf["trailing_stop_positive_offset"] = data.trailing_stop_positive_offset
|
default_conf["trailing_stop_positive_offset"] = data.trailing_stop_positive_offset
|
||||||
default_conf["ask_strategy"] = {"use_sell_signal": data.use_sell_signal}
|
default_conf["ask_strategy"] = {"use_sell_signal": data.use_sell_signal}
|
||||||
|
|
|
@ -20,8 +20,8 @@ from freqtrade.data.dataprovider import DataProvider
|
||||||
from freqtrade.data.history import get_timerange
|
from freqtrade.data.history import get_timerange
|
||||||
from freqtrade.exceptions import DependencyException, OperationalException
|
from freqtrade.exceptions import DependencyException, OperationalException
|
||||||
from freqtrade.optimize.backtesting import Backtesting
|
from freqtrade.optimize.backtesting import Backtesting
|
||||||
|
from freqtrade.resolvers import StrategyResolver
|
||||||
from freqtrade.state import RunMode
|
from freqtrade.state import RunMode
|
||||||
from freqtrade.strategy.default_strategy import DefaultStrategy
|
|
||||||
from freqtrade.strategy.interface import SellType
|
from freqtrade.strategy.interface import SellType
|
||||||
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
|
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
|
||||||
patched_configuration_load_config_file)
|
patched_configuration_load_config_file)
|
||||||
|
@ -287,8 +287,8 @@ def test_start(mocker, fee, default_conf, caplog) -> None:
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'DefaultStrategy',
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
pargs = get_args(args)
|
||||||
start_backtesting(args)
|
start_backtesting(pargs)
|
||||||
assert log_has('Starting freqtrade in Backtesting mode', caplog)
|
assert log_has('Starting freqtrade in Backtesting mode', caplog)
|
||||||
assert start_mock.call_count == 1
|
assert start_mock.call_count == 1
|
||||||
|
|
||||||
|
@ -350,7 +350,9 @@ def test_tickerdata_to_dataframe_bt(default_conf, mocker, testdatadir) -> None:
|
||||||
assert len(data['UNITTEST/BTC']) == 102
|
assert len(data['UNITTEST/BTC']) == 102
|
||||||
|
|
||||||
# Load strategy to compare the result between Backtesting function and strategy are the same
|
# Load strategy to compare the result between Backtesting function and strategy are the same
|
||||||
strategy = DefaultStrategy(default_conf)
|
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||||
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
data2 = strategy.tickerdata_to_dataframe(tickerlist)
|
data2 = strategy.tickerdata_to_dataframe(tickerlist)
|
||||||
assert data['UNITTEST/BTC'].equals(data2['UNITTEST/BTC'])
|
assert data['UNITTEST/BTC'].equals(data2['UNITTEST/BTC'])
|
||||||
|
|
||||||
|
|
|
@ -82,8 +82,8 @@ def test_start(mocker, fee, edge_conf, caplog) -> None:
|
||||||
'--config', 'config.json',
|
'--config', 'config.json',
|
||||||
'--strategy', 'DefaultStrategy',
|
'--strategy', 'DefaultStrategy',
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
pargs = get_args(args)
|
||||||
start_edge(args)
|
start_edge(pargs)
|
||||||
assert log_has('Starting freqtrade in Edge mode', caplog)
|
assert log_has('Starting freqtrade in Edge mode', caplog)
|
||||||
assert start_mock.call_count == 1
|
assert start_mock.call_count == 1
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import locale
|
import locale
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Dict, List
|
||||||
from unittest.mock import MagicMock, PropertyMock
|
from unittest.mock import MagicMock, PropertyMock
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
@ -9,7 +10,8 @@ import pytest
|
||||||
from arrow import Arrow
|
from arrow import Arrow
|
||||||
from filelock import Timeout
|
from filelock import Timeout
|
||||||
|
|
||||||
from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_hyperopt
|
from freqtrade.commands.optimize_commands import (setup_optimize_configuration,
|
||||||
|
start_hyperopt)
|
||||||
from freqtrade.data.converter import parse_ticker_dataframe
|
from freqtrade.data.converter import parse_ticker_dataframe
|
||||||
from freqtrade.data.history import load_tickerdata_file
|
from freqtrade.data.history import load_tickerdata_file
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
|
@ -54,7 +56,7 @@ def hyperopt_results():
|
||||||
|
|
||||||
|
|
||||||
# Functions for recurrent object patching
|
# Functions for recurrent object patching
|
||||||
def create_trials(mocker, hyperopt, testdatadir) -> None:
|
def create_trials(mocker, hyperopt, testdatadir) -> List[Dict]:
|
||||||
"""
|
"""
|
||||||
When creating trials, mock the hyperopt Trials so that *by default*
|
When creating trials, mock the hyperopt Trials so that *by default*
|
||||||
- we don't create any pickle'd files in the filesystem
|
- we don't create any pickle'd files in the filesystem
|
||||||
|
@ -228,10 +230,10 @@ def test_start_not_installed(mocker, default_conf, caplog, import_fails) -> None
|
||||||
'--hyperopt', 'DefaultHyperOpt',
|
'--hyperopt', 'DefaultHyperOpt',
|
||||||
'--epochs', '5'
|
'--epochs', '5'
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
pargs = get_args(args)
|
||||||
|
|
||||||
with pytest.raises(OperationalException, match=r"Please ensure that the hyperopt dependencies"):
|
with pytest.raises(OperationalException, match=r"Please ensure that the hyperopt dependencies"):
|
||||||
start_hyperopt(args)
|
start_hyperopt(pargs)
|
||||||
|
|
||||||
|
|
||||||
def test_start(mocker, default_conf, caplog) -> None:
|
def test_start(mocker, default_conf, caplog) -> None:
|
||||||
|
@ -246,8 +248,8 @@ def test_start(mocker, default_conf, caplog) -> None:
|
||||||
'--hyperopt', 'DefaultHyperOpt',
|
'--hyperopt', 'DefaultHyperOpt',
|
||||||
'--epochs', '5'
|
'--epochs', '5'
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
pargs = get_args(args)
|
||||||
start_hyperopt(args)
|
start_hyperopt(pargs)
|
||||||
|
|
||||||
assert log_has('Starting freqtrade in Hyperopt mode', caplog)
|
assert log_has('Starting freqtrade in Hyperopt mode', caplog)
|
||||||
assert start_mock.call_count == 1
|
assert start_mock.call_count == 1
|
||||||
|
@ -269,9 +271,9 @@ def test_start_no_data(mocker, default_conf, caplog) -> None:
|
||||||
'--hyperopt', 'DefaultHyperOpt',
|
'--hyperopt', 'DefaultHyperOpt',
|
||||||
'--epochs', '5'
|
'--epochs', '5'
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
pargs = get_args(args)
|
||||||
with pytest.raises(OperationalException, match='No data found. Terminating.'):
|
with pytest.raises(OperationalException, match='No data found. Terminating.'):
|
||||||
start_hyperopt(args)
|
start_hyperopt(pargs)
|
||||||
|
|
||||||
|
|
||||||
def test_start_filelock(mocker, default_conf, caplog) -> None:
|
def test_start_filelock(mocker, default_conf, caplog) -> None:
|
||||||
|
@ -286,16 +288,19 @@ def test_start_filelock(mocker, default_conf, caplog) -> None:
|
||||||
'--hyperopt', 'DefaultHyperOpt',
|
'--hyperopt', 'DefaultHyperOpt',
|
||||||
'--epochs', '5'
|
'--epochs', '5'
|
||||||
]
|
]
|
||||||
args = get_args(args)
|
pargs = get_args(args)
|
||||||
start_hyperopt(args)
|
start_hyperopt(pargs)
|
||||||
assert log_has("Another running instance of freqtrade Hyperopt detected.", caplog)
|
assert log_has("Another running instance of freqtrade Hyperopt detected.", caplog)
|
||||||
|
|
||||||
|
|
||||||
def test_loss_calculation_prefer_correct_trade_count(default_conf, hyperopt_results) -> None:
|
def test_loss_calculation_prefer_correct_trade_count(default_conf, hyperopt_results) -> None:
|
||||||
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
||||||
correct = hl.hyperopt_loss_function(hyperopt_results, 600)
|
correct = hl.hyperopt_loss_function(hyperopt_results, 600,
|
||||||
over = hl.hyperopt_loss_function(hyperopt_results, 600 + 100)
|
datetime(2019, 1, 1), datetime(2019, 5, 1))
|
||||||
under = hl.hyperopt_loss_function(hyperopt_results, 600 - 100)
|
over = hl.hyperopt_loss_function(hyperopt_results, 600 + 100,
|
||||||
|
datetime(2019, 1, 1), datetime(2019, 5, 1))
|
||||||
|
under = hl.hyperopt_loss_function(hyperopt_results, 600 - 100,
|
||||||
|
datetime(2019, 1, 1), datetime(2019, 5, 1))
|
||||||
assert over > correct
|
assert over > correct
|
||||||
assert under > correct
|
assert under > correct
|
||||||
|
|
||||||
|
@ -305,8 +310,10 @@ def test_loss_calculation_prefer_shorter_trades(default_conf, hyperopt_results)
|
||||||
resultsb.loc[1, 'trade_duration'] = 20
|
resultsb.loc[1, 'trade_duration'] = 20
|
||||||
|
|
||||||
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
||||||
longer = hl.hyperopt_loss_function(hyperopt_results, 100)
|
longer = hl.hyperopt_loss_function(hyperopt_results, 100,
|
||||||
shorter = hl.hyperopt_loss_function(resultsb, 100)
|
datetime(2019, 1, 1), datetime(2019, 5, 1))
|
||||||
|
shorter = hl.hyperopt_loss_function(resultsb, 100,
|
||||||
|
datetime(2019, 1, 1), datetime(2019, 5, 1))
|
||||||
assert shorter < longer
|
assert shorter < longer
|
||||||
|
|
||||||
|
|
||||||
|
@ -317,9 +324,12 @@ def test_loss_calculation_has_limited_profit(default_conf, hyperopt_results) ->
|
||||||
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
|
results_under['profit_percent'] = hyperopt_results['profit_percent'] / 2
|
||||||
|
|
||||||
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
hl = HyperOptLossResolver.load_hyperoptloss(default_conf)
|
||||||
correct = hl.hyperopt_loss_function(hyperopt_results, 600)
|
correct = hl.hyperopt_loss_function(hyperopt_results, 600,
|
||||||
over = hl.hyperopt_loss_function(results_over, 600)
|
datetime(2019, 1, 1), datetime(2019, 5, 1))
|
||||||
under = hl.hyperopt_loss_function(results_under, 600)
|
over = hl.hyperopt_loss_function(results_over, 600,
|
||||||
|
datetime(2019, 1, 1), datetime(2019, 5, 1))
|
||||||
|
under = hl.hyperopt_loss_function(results_under, 600,
|
||||||
|
datetime(2019, 1, 1), datetime(2019, 5, 1))
|
||||||
assert over < correct
|
assert over < correct
|
||||||
assert under > correct
|
assert under > correct
|
||||||
|
|
||||||
|
|
|
@ -15,20 +15,21 @@ def test_generate_text_table(default_conf, mocker):
|
||||||
'profit_percent': [0.1, 0.2],
|
'profit_percent': [0.1, 0.2],
|
||||||
'profit_abs': [0.2, 0.4],
|
'profit_abs': [0.2, 0.4],
|
||||||
'trade_duration': [10, 30],
|
'trade_duration': [10, 30],
|
||||||
'profit': [2, 0],
|
'wins': [2, 0],
|
||||||
'loss': [0, 0]
|
'draws': [0, 0],
|
||||||
|
'losses': [0, 0]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
result_str = (
|
result_str = (
|
||||||
'| Pair | Buy Count | Avg Profit % | Cum Profit % | Tot Profit BTC '
|
'| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC |'
|
||||||
'| Tot Profit % | Avg Duration | Wins | Losses |\n'
|
' Tot Profit % | Avg Duration | Wins | Draws | Losses |\n'
|
||||||
'|:--------|------------:|---------------:|---------------:|-----------------:'
|
'|:--------|-------:|---------------:|---------------:|-----------------:|'
|
||||||
'|---------------:|:---------------|-------:|---------:|\n'
|
'---------------:|:---------------|-------:|--------:|---------:|\n'
|
||||||
'| ETH/BTC | 2 | 15.00 | 30.00 | 0.60000000 '
|
'| ETH/BTC | 2 | 15.00 | 30.00 | 0.60000000 |'
|
||||||
'| 15.00 | 0:20:00 | 2 | 0 |\n'
|
' 15.00 | 0:20:00 | 2 | 0 | 0 |\n'
|
||||||
'| TOTAL | 2 | 15.00 | 30.00 | 0.60000000 '
|
'| TOTAL | 2 | 15.00 | 30.00 | 0.60000000 |'
|
||||||
'| 15.00 | 0:20:00 | 2 | 0 |'
|
' 15.00 | 0:20:00 | 2 | 0 | 0 |'
|
||||||
)
|
)
|
||||||
assert generate_text_table(data={'ETH/BTC': {}},
|
assert generate_text_table(data={'ETH/BTC': {}},
|
||||||
stake_currency='BTC', max_open_trades=2,
|
stake_currency='BTC', max_open_trades=2,
|
||||||
|
@ -43,21 +44,22 @@ def test_generate_text_table_sell_reason(default_conf, mocker):
|
||||||
'profit_percent': [0.1, 0.2, -0.1],
|
'profit_percent': [0.1, 0.2, -0.1],
|
||||||
'profit_abs': [0.2, 0.4, -0.2],
|
'profit_abs': [0.2, 0.4, -0.2],
|
||||||
'trade_duration': [10, 30, 10],
|
'trade_duration': [10, 30, 10],
|
||||||
'profit': [2, 0, 0],
|
'wins': [2, 0, 0],
|
||||||
'loss': [0, 0, 1],
|
'draws': [0, 0, 0],
|
||||||
|
'losses': [0, 0, 1],
|
||||||
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
result_str = (
|
result_str = (
|
||||||
'| Sell Reason | Sell Count | Wins | Losses | Avg Profit % |'
|
'| Sell Reason | Sells | Wins | Draws | Losses |'
|
||||||
' Cum Profit % | Tot Profit BTC | Tot Profit % |\n'
|
' Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % |\n'
|
||||||
'|:--------------|-------------:|-------:|---------:|---------------:|'
|
'|:--------------|--------:|-------:|--------:|---------:|'
|
||||||
'---------------:|-----------------:|---------------:|\n'
|
'---------------:|---------------:|-----------------:|---------------:|\n'
|
||||||
'| roi | 2 | 2 | 0 | 15 |'
|
'| roi | 2 | 2 | 0 | 0 |'
|
||||||
' 30 | 0.6 | 15 |\n'
|
' 15 | 30 | 0.6 | 15 |\n'
|
||||||
'| stop_loss | 1 | 0 | 1 | -10 |'
|
'| stop_loss | 1 | 0 | 0 | 1 |'
|
||||||
' -10 | -0.2 | -5 |'
|
' -10 | -10 | -0.2 | -5 |'
|
||||||
)
|
)
|
||||||
assert generate_text_table_sell_reason(
|
assert generate_text_table_sell_reason(
|
||||||
data={'ETH/BTC': {}},
|
data={'ETH/BTC': {}},
|
||||||
|
@ -67,38 +69,40 @@ def test_generate_text_table_sell_reason(default_conf, mocker):
|
||||||
|
|
||||||
def test_generate_text_table_strategy(default_conf, mocker):
|
def test_generate_text_table_strategy(default_conf, mocker):
|
||||||
results = {}
|
results = {}
|
||||||
results['ETH/BTC'] = pd.DataFrame(
|
results['TestStrategy1'] = pd.DataFrame(
|
||||||
{
|
{
|
||||||
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
|
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
|
||||||
'profit_percent': [0.1, 0.2, 0.3],
|
'profit_percent': [0.1, 0.2, 0.3],
|
||||||
'profit_abs': [0.2, 0.4, 0.5],
|
'profit_abs': [0.2, 0.4, 0.5],
|
||||||
'trade_duration': [10, 30, 10],
|
'trade_duration': [10, 30, 10],
|
||||||
'profit': [2, 0, 0],
|
'wins': [2, 0, 0],
|
||||||
'loss': [0, 0, 1],
|
'draws': [0, 0, 0],
|
||||||
|
'losses': [0, 0, 1],
|
||||||
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
results['LTC/BTC'] = pd.DataFrame(
|
results['TestStrategy2'] = pd.DataFrame(
|
||||||
{
|
{
|
||||||
'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'],
|
'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'],
|
||||||
'profit_percent': [0.4, 0.2, 0.3],
|
'profit_percent': [0.4, 0.2, 0.3],
|
||||||
'profit_abs': [0.4, 0.4, 0.5],
|
'profit_abs': [0.4, 0.4, 0.5],
|
||||||
'trade_duration': [15, 30, 15],
|
'trade_duration': [15, 30, 15],
|
||||||
'profit': [4, 1, 0],
|
'wins': [4, 1, 0],
|
||||||
'loss': [0, 0, 1],
|
'draws': [0, 0, 0],
|
||||||
|
'losses': [0, 0, 1],
|
||||||
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
result_str = (
|
result_str = (
|
||||||
'| Strategy | Buy Count | Avg Profit % | Cum Profit % | Tot Profit BTC '
|
'| Strategy | Buys | Avg Profit % | Cum Profit % | Tot'
|
||||||
'| Tot Profit % | Avg Duration | Wins | Losses |\n'
|
' Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses |\n'
|
||||||
'|:-----------|------------:|---------------:|---------------:|-----------------:'
|
'|:--------------|-------:|---------------:|---------------:|------'
|
||||||
'|---------------:|:---------------|-------:|---------:|\n'
|
'-----------:|---------------:|:---------------|-------:|--------:|---------:|\n'
|
||||||
'| ETH/BTC | 3 | 20.00 | 60.00 '
|
'| TestStrategy1 | 3 | 20.00 | 60.00 | '
|
||||||
'| 1.10000000 | 30.00 | 0:17:00 | 3 | 0 |\n'
|
' 1.10000000 | 30.00 | 0:17:00 | 3 | 0 | 0 |\n'
|
||||||
'| LTC/BTC | 3 | 30.00 | 90.00 '
|
'| TestStrategy2 | 3 | 30.00 | 90.00 | '
|
||||||
'| 1.30000000 | 45.00 | 0:20:00 | 3 | 0 |'
|
' 1.30000000 | 45.00 | 0:20:00 | 3 | 0 | 0 |'
|
||||||
)
|
)
|
||||||
assert generate_text_table_strategy('BTC', 2, all_results=results) == result_str
|
assert generate_text_table_strategy('BTC', 2, all_results=results) == result_str
|
||||||
|
|
||||||
|
@ -111,4 +115,4 @@ def test_generate_edge_table(edge_conf, mocker):
|
||||||
assert generate_edge_table(results).count(':|') == 7
|
assert generate_edge_table(results).count(':|') == 7
|
||||||
assert generate_edge_table(results).count('| ETH/BTC |') == 1
|
assert generate_edge_table(results).count('| ETH/BTC |') == 1
|
||||||
assert generate_edge_table(results).count(
|
assert generate_edge_table(results).count(
|
||||||
'| risk reward ratio | required risk reward | expectancy |') == 1
|
'| Risk Reward Ratio | Required Risk Reward | Expectancy |') == 1
|
||||||
|
|
|
@ -122,7 +122,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||||
assert "Since" in headers
|
assert "Since" in headers
|
||||||
assert "Pair" in headers
|
assert "Pair" in headers
|
||||||
assert 'instantly' == result[0][2]
|
assert 'instantly' == result[0][2]
|
||||||
assert 'ETH/BTC' == result[0][1]
|
assert 'ETH/BTC' in result[0][1]
|
||||||
assert '-0.59%' == result[0][3]
|
assert '-0.59%' == result[0][3]
|
||||||
# Test with fiatconvert
|
# Test with fiatconvert
|
||||||
|
|
||||||
|
@ -131,7 +131,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||||
assert "Since" in headers
|
assert "Since" in headers
|
||||||
assert "Pair" in headers
|
assert "Pair" in headers
|
||||||
assert 'instantly' == result[0][2]
|
assert 'instantly' == result[0][2]
|
||||||
assert 'ETH/BTC' == result[0][1]
|
assert 'ETH/BTC' in result[0][1]
|
||||||
assert '-0.59% (-0.09)' == result[0][3]
|
assert '-0.59% (-0.09)' == result[0][3]
|
||||||
|
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||||
|
@ -140,7 +140,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||||
rpc._freqtrade.exchange._cached_ticker = {}
|
rpc._freqtrade.exchange._cached_ticker = {}
|
||||||
result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||||
assert 'instantly' == result[0][2]
|
assert 'instantly' == result[0][2]
|
||||||
assert 'ETH/BTC' == result[0][1]
|
assert 'ETH/BTC' in result[0][1]
|
||||||
assert 'nan%' == result[0][3]
|
assert 'nan%' == result[0][3]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -284,7 +284,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
|
||||||
fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ')
|
fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ')
|
||||||
|
|
||||||
assert int(fields[0]) == 1
|
assert int(fields[0]) == 1
|
||||||
assert fields[1] == 'ETH/BTC'
|
assert 'ETH/BTC' in fields[1]
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
@ -1200,12 +1200,35 @@ def test_send_msg_buy_notification(default_conf, mocker) -> None:
|
||||||
'stake_amount': 0.001,
|
'stake_amount': 0.001,
|
||||||
'stake_amount_fiat': 0.0,
|
'stake_amount_fiat': 0.0,
|
||||||
'stake_currency': 'BTC',
|
'stake_currency': 'BTC',
|
||||||
'fiat_currency': 'USD'
|
'fiat_currency': 'USD',
|
||||||
|
'current_rate': 1.099e-05,
|
||||||
|
'amount': 1333.3333333333335,
|
||||||
|
'open_date': arrow.utcnow().shift(hours=-1)
|
||||||
})
|
})
|
||||||
assert msg_mock.call_args[0][0] \
|
assert msg_mock.call_args[0][0] \
|
||||||
== '*Bittrex:* Buying ETH/BTC\n' \
|
== '*Bittrex:* Buying ETH/BTC\n' \
|
||||||
'at rate `0.00001099\n' \
|
'*Amount:* `1333.33333333`\n' \
|
||||||
'(0.001000 BTC,0.000 USD)`'
|
'*Open Rate:* `0.00001099`\n' \
|
||||||
|
'*Current Rate:* `0.00001099`\n' \
|
||||||
|
'*Total:* `(0.001000 BTC, 0.000 USD)`'
|
||||||
|
|
||||||
|
|
||||||
|
def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None:
|
||||||
|
msg_mock = MagicMock()
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.rpc.telegram.Telegram',
|
||||||
|
_init=MagicMock(),
|
||||||
|
_send_msg=msg_mock
|
||||||
|
)
|
||||||
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
|
telegram = Telegram(freqtradebot)
|
||||||
|
telegram.send_msg({
|
||||||
|
'type': RPCMessageType.BUY_CANCEL_NOTIFICATION,
|
||||||
|
'exchange': 'Bittrex',
|
||||||
|
'pair': 'ETH/BTC',
|
||||||
|
})
|
||||||
|
assert msg_mock.call_args[0][0] \
|
||||||
|
== ('*Bittrex:* Cancelling Open Buy Order for ETH/BTC')
|
||||||
|
|
||||||
|
|
||||||
def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||||
|
@ -1239,13 +1262,13 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||||
})
|
})
|
||||||
assert msg_mock.call_args[0][0] \
|
assert msg_mock.call_args[0][0] \
|
||||||
== ('*Binance:* Selling KEY/ETH\n'
|
== ('*Binance:* Selling KEY/ETH\n'
|
||||||
'*Rate:* `0.00003201`\n'
|
|
||||||
'*Amount:* `1333.33333333`\n'
|
'*Amount:* `1333.33333333`\n'
|
||||||
'*Open Rate:* `0.00007500`\n'
|
'*Open Rate:* `0.00007500`\n'
|
||||||
'*Current Rate:* `0.00003201`\n'
|
'*Current Rate:* `0.00003201`\n'
|
||||||
|
'*Close Rate:* `0.00003201`\n'
|
||||||
'*Sell Reason:* `stop_loss`\n'
|
'*Sell Reason:* `stop_loss`\n'
|
||||||
'*Duration:* `1:00:00 (60.0 min)`\n'
|
'*Duration:* `1:00:00 (60.0 min)`\n'
|
||||||
'*Profit:* `-57.41%`` (loss: -0.05746268 ETH`` / -24.812 USD)`')
|
'*Profit:* `-57.41%` `(loss: -0.05746268 ETH / -24.812 USD)`')
|
||||||
|
|
||||||
msg_mock.reset_mock()
|
msg_mock.reset_mock()
|
||||||
telegram.send_msg({
|
telegram.send_msg({
|
||||||
|
@ -1267,10 +1290,10 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||||
})
|
})
|
||||||
assert msg_mock.call_args[0][0] \
|
assert msg_mock.call_args[0][0] \
|
||||||
== ('*Binance:* Selling KEY/ETH\n'
|
== ('*Binance:* Selling KEY/ETH\n'
|
||||||
'*Rate:* `0.00003201`\n'
|
|
||||||
'*Amount:* `1333.33333333`\n'
|
'*Amount:* `1333.33333333`\n'
|
||||||
'*Open Rate:* `0.00007500`\n'
|
'*Open Rate:* `0.00007500`\n'
|
||||||
'*Current Rate:* `0.00003201`\n'
|
'*Current Rate:* `0.00003201`\n'
|
||||||
|
'*Close Rate:* `0.00003201`\n'
|
||||||
'*Sell Reason:* `stop_loss`\n'
|
'*Sell Reason:* `stop_loss`\n'
|
||||||
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
|
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
|
||||||
'*Profit:* `-57.41%`')
|
'*Profit:* `-57.41%`')
|
||||||
|
@ -1278,6 +1301,37 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||||
telegram._fiat_converter.convert_amount = old_convamount
|
telegram._fiat_converter.convert_amount = old_convamount
|
||||||
|
|
||||||
|
|
||||||
|
def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
|
||||||
|
msg_mock = MagicMock()
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.rpc.telegram.Telegram',
|
||||||
|
_init=MagicMock(),
|
||||||
|
_send_msg=msg_mock
|
||||||
|
)
|
||||||
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
|
telegram = Telegram(freqtradebot)
|
||||||
|
old_convamount = telegram._fiat_converter.convert_amount
|
||||||
|
telegram._fiat_converter.convert_amount = lambda a, b, c: -24.812
|
||||||
|
telegram.send_msg({
|
||||||
|
'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
|
||||||
|
'exchange': 'Binance',
|
||||||
|
'pair': 'KEY/ETH',
|
||||||
|
})
|
||||||
|
assert msg_mock.call_args[0][0] \
|
||||||
|
== ('*Binance:* Cancelling Open Sell Order for KEY/ETH')
|
||||||
|
|
||||||
|
msg_mock.reset_mock()
|
||||||
|
telegram.send_msg({
|
||||||
|
'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
|
||||||
|
'exchange': 'Binance',
|
||||||
|
'pair': 'KEY/ETH',
|
||||||
|
})
|
||||||
|
assert msg_mock.call_args[0][0] \
|
||||||
|
== ('*Binance:* Cancelling Open Sell Order for KEY/ETH')
|
||||||
|
# Reset singleton function to avoid random breaks
|
||||||
|
telegram._fiat_converter.convert_amount = old_convamount
|
||||||
|
|
||||||
|
|
||||||
def test_send_msg_status_notification(default_conf, mocker) -> None:
|
def test_send_msg_status_notification(default_conf, mocker) -> None:
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
|
@ -1360,12 +1414,17 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
|
||||||
'stake_amount': 0.001,
|
'stake_amount': 0.001,
|
||||||
'stake_amount_fiat': 0.0,
|
'stake_amount_fiat': 0.0,
|
||||||
'stake_currency': 'BTC',
|
'stake_currency': 'BTC',
|
||||||
'fiat_currency': None
|
'fiat_currency': None,
|
||||||
|
'current_rate': 1.099e-05,
|
||||||
|
'amount': 1333.3333333333335,
|
||||||
|
'open_date': arrow.utcnow().shift(hours=-1)
|
||||||
})
|
})
|
||||||
assert msg_mock.call_args[0][0] \
|
assert msg_mock.call_args[0][0] \
|
||||||
== '*Bittrex:* Buying ETH/BTC\n' \
|
== '*Bittrex:* Buying ETH/BTC\n' \
|
||||||
'at rate `0.00001099\n' \
|
'*Amount:* `1333.33333333`\n' \
|
||||||
'(0.001000 BTC)`'
|
'*Open Rate:* `0.00001099`\n' \
|
||||||
|
'*Current Rate:* `0.00001099`\n' \
|
||||||
|
'*Total:* `(0.001000 BTC)`'
|
||||||
|
|
||||||
|
|
||||||
def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
|
def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
|
||||||
|
@ -1398,10 +1457,10 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
|
||||||
})
|
})
|
||||||
assert msg_mock.call_args[0][0] \
|
assert msg_mock.call_args[0][0] \
|
||||||
== '*Binance:* Selling KEY/ETH\n' \
|
== '*Binance:* Selling KEY/ETH\n' \
|
||||||
'*Rate:* `0.00003201`\n' \
|
|
||||||
'*Amount:* `1333.33333333`\n' \
|
'*Amount:* `1333.33333333`\n' \
|
||||||
'*Open Rate:* `0.00007500`\n' \
|
'*Open Rate:* `0.00007500`\n' \
|
||||||
'*Current Rate:* `0.00003201`\n' \
|
'*Current Rate:* `0.00003201`\n' \
|
||||||
|
'*Close Rate:* `0.00003201`\n' \
|
||||||
'*Sell Reason:* `stop_loss`\n' \
|
'*Sell Reason:* `stop_loss`\n' \
|
||||||
'*Duration:* `2:35:03 (155.1 min)`\n' \
|
'*Duration:* `2:35:03 (155.1 min)`\n' \
|
||||||
'*Profit:* `-57.41%`'
|
'*Profit:* `-57.41%`'
|
||||||
|
|
|
@ -13,24 +13,34 @@ from tests.conftest import get_patched_freqtradebot, log_has
|
||||||
|
|
||||||
def get_webhook_dict() -> dict:
|
def get_webhook_dict() -> dict:
|
||||||
return {
|
return {
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
"url": "https://maker.ifttt.com/trigger/freqtrade_test/with/key/c764udvJ5jfSlswVRukZZ2/",
|
"url": "https://maker.ifttt.com/trigger/freqtrade_test/with/key/c764udvJ5jfSlswVRukZZ2/",
|
||||||
"webhookbuy": {
|
"webhookbuy": {
|
||||||
"value1": "Buying {pair}",
|
"value1": "Buying {pair}",
|
||||||
"value2": "limit {limit:8f}",
|
"value2": "limit {limit:8f}",
|
||||||
"value3": "{stake_amount:8f} {stake_currency}"
|
"value3": "{stake_amount:8f} {stake_currency}"
|
||||||
},
|
},
|
||||||
"webhooksell": {
|
"webhookbuycancel": {
|
||||||
"value1": "Selling {pair}",
|
"value1": "Cancelling Open Buy Order for {pair}",
|
||||||
"value2": "limit {limit:8f}",
|
"value2": "limit {limit:8f}",
|
||||||
"value3": "profit: {profit_amount:8f} {stake_currency}"
|
"value3": "{stake_amount:8f} {stake_currency}"
|
||||||
},
|
},
|
||||||
"webhookstatus": {
|
"webhooksell": {
|
||||||
"value1": "Status: {status}",
|
"value1": "Selling {pair}",
|
||||||
"value2": "",
|
"value2": "limit {limit:8f}",
|
||||||
"value3": ""
|
"value3": "profit: {profit_amount:8f} {stake_currency}"
|
||||||
}
|
},
|
||||||
}
|
"webhooksellcancel": {
|
||||||
|
"value1": "Cancelling Open Sell Order for {pair}",
|
||||||
|
"value2": "limit {limit:8f}",
|
||||||
|
"value3": "profit: {profit_amount:8f} {stake_currency}"
|
||||||
|
},
|
||||||
|
"webhookstatus": {
|
||||||
|
"value1": "Status: {status}",
|
||||||
|
"value2": "",
|
||||||
|
"value3": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def test__init__(mocker, default_conf):
|
def test__init__(mocker, default_conf):
|
||||||
|
@ -44,6 +54,9 @@ def test_send_msg(default_conf, mocker):
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||||
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf))
|
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf))
|
||||||
|
# Test buy
|
||||||
|
msg_mock = MagicMock()
|
||||||
|
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||||
msg = {
|
msg = {
|
||||||
'type': RPCMessageType.BUY_NOTIFICATION,
|
'type': RPCMessageType.BUY_NOTIFICATION,
|
||||||
'exchange': 'Bittrex',
|
'exchange': 'Bittrex',
|
||||||
|
@ -54,8 +67,6 @@ def test_send_msg(default_conf, mocker):
|
||||||
'stake_currency': 'BTC',
|
'stake_currency': 'BTC',
|
||||||
'fiat_currency': 'EUR'
|
'fiat_currency': 'EUR'
|
||||||
}
|
}
|
||||||
msg_mock = MagicMock()
|
|
||||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
|
||||||
webhook.send_msg(msg=msg)
|
webhook.send_msg(msg=msg)
|
||||||
assert msg_mock.call_count == 1
|
assert msg_mock.call_count == 1
|
||||||
assert (msg_mock.call_args[0][0]["value1"] ==
|
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||||
|
@ -64,6 +75,27 @@ def test_send_msg(default_conf, mocker):
|
||||||
default_conf["webhook"]["webhookbuy"]["value2"].format(**msg))
|
default_conf["webhook"]["webhookbuy"]["value2"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||||
default_conf["webhook"]["webhookbuy"]["value3"].format(**msg))
|
default_conf["webhook"]["webhookbuy"]["value3"].format(**msg))
|
||||||
|
# Test buy cancel
|
||||||
|
msg_mock = MagicMock()
|
||||||
|
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||||
|
msg = {
|
||||||
|
'type': RPCMessageType.BUY_CANCEL_NOTIFICATION,
|
||||||
|
'exchange': 'Bittrex',
|
||||||
|
'pair': 'ETH/BTC',
|
||||||
|
'limit': 0.005,
|
||||||
|
'stake_amount': 0.8,
|
||||||
|
'stake_amount_fiat': 500,
|
||||||
|
'stake_currency': 'BTC',
|
||||||
|
'fiat_currency': 'EUR'
|
||||||
|
}
|
||||||
|
webhook.send_msg(msg=msg)
|
||||||
|
assert msg_mock.call_count == 1
|
||||||
|
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||||
|
default_conf["webhook"]["webhookbuycancel"]["value1"].format(**msg))
|
||||||
|
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||||
|
default_conf["webhook"]["webhookbuycancel"]["value2"].format(**msg))
|
||||||
|
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||||
|
default_conf["webhook"]["webhookbuycancel"]["value3"].format(**msg))
|
||||||
# Test sell
|
# Test sell
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||||
|
@ -90,7 +122,32 @@ def test_send_msg(default_conf, mocker):
|
||||||
default_conf["webhook"]["webhooksell"]["value2"].format(**msg))
|
default_conf["webhook"]["webhooksell"]["value2"].format(**msg))
|
||||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||||
default_conf["webhook"]["webhooksell"]["value3"].format(**msg))
|
default_conf["webhook"]["webhooksell"]["value3"].format(**msg))
|
||||||
|
# Test sell cancel
|
||||||
|
msg_mock = MagicMock()
|
||||||
|
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||||
|
msg = {
|
||||||
|
'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
|
||||||
|
'exchange': 'Bittrex',
|
||||||
|
'pair': 'ETH/BTC',
|
||||||
|
'gain': "profit",
|
||||||
|
'limit': 0.005,
|
||||||
|
'amount': 0.8,
|
||||||
|
'order_type': 'limit',
|
||||||
|
'open_rate': 0.004,
|
||||||
|
'current_rate': 0.005,
|
||||||
|
'profit_amount': 0.001,
|
||||||
|
'profit_percent': 0.20,
|
||||||
|
'stake_currency': 'BTC',
|
||||||
|
'sell_reason': SellType.STOP_LOSS.value
|
||||||
|
}
|
||||||
|
webhook.send_msg(msg=msg)
|
||||||
|
assert msg_mock.call_count == 1
|
||||||
|
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||||
|
default_conf["webhook"]["webhooksellcancel"]["value1"].format(**msg))
|
||||||
|
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||||
|
default_conf["webhook"]["webhooksellcancel"]["value2"].format(**msg))
|
||||||
|
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||||
|
default_conf["webhook"]["webhooksellcancel"]["value3"].format(**msg))
|
||||||
for msgtype in [RPCMessageType.STATUS_NOTIFICATION,
|
for msgtype in [RPCMessageType.STATUS_NOTIFICATION,
|
||||||
RPCMessageType.WARNING_NOTIFICATION,
|
RPCMessageType.WARNING_NOTIFICATION,
|
||||||
RPCMessageType.CUSTOM_NOTIFICATION]:
|
RPCMessageType.CUSTOM_NOTIFICATION]:
|
||||||
|
|
9
tests/strategy/failing_strategy.py
Normal file
9
tests/strategy/failing_strategy.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# The strategy which fails to load due to non-existent dependency
|
||||||
|
|
||||||
|
import nonexiting_module # noqa
|
||||||
|
|
||||||
|
from freqtrade.strategy.interface import IStrategy
|
||||||
|
|
||||||
|
|
||||||
|
class TestStrategyLegacy(IStrategy):
|
||||||
|
pass
|
|
@ -10,8 +10,9 @@ from freqtrade.configuration import TimeRange
|
||||||
from freqtrade.data.converter import parse_ticker_dataframe
|
from freqtrade.data.converter import parse_ticker_dataframe
|
||||||
from freqtrade.data.history import load_tickerdata_file
|
from freqtrade.data.history import load_tickerdata_file
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
from tests.conftest import get_patched_exchange, log_has
|
from freqtrade.resolvers import StrategyResolver
|
||||||
from freqtrade.strategy.default_strategy import DefaultStrategy
|
from freqtrade.strategy.default_strategy import DefaultStrategy
|
||||||
|
from tests.conftest import get_patched_exchange, log_has
|
||||||
|
|
||||||
# Avoid to reinit the same object again and again
|
# Avoid to reinit the same object again and again
|
||||||
_STRATEGY = DefaultStrategy(config={})
|
_STRATEGY = DefaultStrategy(config={})
|
||||||
|
@ -104,7 +105,8 @@ def test_get_signal_handles_exceptions(mocker, default_conf):
|
||||||
|
|
||||||
|
|
||||||
def test_tickerdata_to_dataframe(default_conf, testdatadir) -> None:
|
def test_tickerdata_to_dataframe(default_conf, testdatadir) -> None:
|
||||||
strategy = DefaultStrategy(default_conf)
|
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||||
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
timerange = TimeRange.parse_timerange('1510694220-1510700340')
|
||||||
tick = load_tickerdata_file(testdatadir, 'UNITTEST/BTC', '1m', timerange=timerange)
|
tick = load_tickerdata_file(testdatadir, 'UNITTEST/BTC', '1m', timerange=timerange)
|
||||||
|
@ -120,7 +122,8 @@ def test_min_roi_reached(default_conf, fee) -> None:
|
||||||
min_roi_list = [{20: 0.05, 55: 0.01, 0: 0.1},
|
min_roi_list = [{20: 0.05, 55: 0.01, 0: 0.1},
|
||||||
{0: 0.1, 20: 0.05, 55: 0.01}]
|
{0: 0.1, 20: 0.05, 55: 0.01}]
|
||||||
for roi in min_roi_list:
|
for roi in min_roi_list:
|
||||||
strategy = DefaultStrategy(default_conf)
|
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||||
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
strategy.minimal_roi = roi
|
strategy.minimal_roi = roi
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='ETH/BTC',
|
pair='ETH/BTC',
|
||||||
|
@ -158,7 +161,8 @@ def test_min_roi_reached2(default_conf, fee) -> None:
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
for roi in min_roi_list:
|
for roi in min_roi_list:
|
||||||
strategy = DefaultStrategy(default_conf)
|
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||||
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
strategy.minimal_roi = roi
|
strategy.minimal_roi = roi
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='ETH/BTC',
|
pair='ETH/BTC',
|
||||||
|
@ -192,7 +196,8 @@ def test_min_roi_reached3(default_conf, fee) -> None:
|
||||||
30: 0.05,
|
30: 0.05,
|
||||||
55: 0.30,
|
55: 0.30,
|
||||||
}
|
}
|
||||||
strategy = DefaultStrategy(default_conf)
|
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||||
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
strategy.minimal_roi = min_roi
|
strategy.minimal_roi = min_roi
|
||||||
trade = Trade(
|
trade = Trade(
|
||||||
pair='ETH/BTC',
|
pair='ETH/BTC',
|
||||||
|
@ -292,7 +297,8 @@ def test__analyze_ticker_internal_skip_analyze(ticker_history, mocker, caplog) -
|
||||||
|
|
||||||
|
|
||||||
def test_is_pair_locked(default_conf):
|
def test_is_pair_locked(default_conf):
|
||||||
strategy = DefaultStrategy(default_conf)
|
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||||
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
# dict should be empty
|
# dict should be empty
|
||||||
assert not strategy._pair_locked_until
|
assert not strategy._pair_locked_until
|
||||||
|
|
||||||
|
|
|
@ -30,14 +30,25 @@ def test_search_strategy():
|
||||||
assert s is None
|
assert s is None
|
||||||
|
|
||||||
|
|
||||||
def test_search_all_strategies():
|
def test_search_all_strategies_no_failed():
|
||||||
directory = Path(__file__).parent
|
directory = Path(__file__).parent
|
||||||
strategies = StrategyResolver.search_all_objects(directory)
|
strategies = StrategyResolver.search_all_objects(directory, enum_failed=False)
|
||||||
assert isinstance(strategies, list)
|
assert isinstance(strategies, list)
|
||||||
assert len(strategies) == 3
|
assert len(strategies) == 3
|
||||||
assert isinstance(strategies[0], dict)
|
assert isinstance(strategies[0], dict)
|
||||||
|
|
||||||
|
|
||||||
|
def test_search_all_strategies_with_failed():
|
||||||
|
directory = Path(__file__).parent
|
||||||
|
strategies = StrategyResolver.search_all_objects(directory, enum_failed=True)
|
||||||
|
assert isinstance(strategies, list)
|
||||||
|
assert len(strategies) == 4
|
||||||
|
# with enum_failed=True search_all_objects() shall find 3 good strategies
|
||||||
|
# and 1 which fails to load
|
||||||
|
assert len([x for x in strategies if x['class'] is not None]) == 3
|
||||||
|
assert len([x for x in strategies if x['class'] is None]) == 1
|
||||||
|
|
||||||
|
|
||||||
def test_load_strategy(default_conf, result):
|
def test_load_strategy(default_conf, result):
|
||||||
default_conf.update({'strategy': 'SampleStrategy',
|
default_conf.update({'strategy': 'SampleStrategy',
|
||||||
'strategy_path': str(Path(__file__).parents[2] / 'freqtrade/templates')
|
'strategy_path': str(Path(__file__).parents[2] / 'freqtrade/templates')
|
||||||
|
|
|
@ -18,7 +18,8 @@ def test_parse_args_none() -> None:
|
||||||
assert isinstance(arguments.parser, argparse.ArgumentParser)
|
assert isinstance(arguments.parser, argparse.ArgumentParser)
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_defaults() -> None:
|
def test_parse_args_defaults(mocker) -> None:
|
||||||
|
mocker.patch.object(Path, "is_file", MagicMock(side_effect=[False, True]))
|
||||||
args = Arguments(['trade']).get_parsed_arg()
|
args = Arguments(['trade']).get_parsed_arg()
|
||||||
assert args["config"] == ['config.json']
|
assert args["config"] == ['config.json']
|
||||||
assert args["strategy_path"] is None
|
assert args["strategy_path"] is None
|
||||||
|
@ -26,6 +27,26 @@ def test_parse_args_defaults() -> None:
|
||||||
assert args["verbosity"] == 0
|
assert args["verbosity"] == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_args_default_userdatadir(mocker) -> None:
|
||||||
|
mocker.patch.object(Path, "is_file", MagicMock(return_value=True))
|
||||||
|
args = Arguments(['trade']).get_parsed_arg()
|
||||||
|
# configuration defaults to user_data if that is available.
|
||||||
|
assert args["config"] == [str(Path('user_data/config.json'))]
|
||||||
|
assert args["strategy_path"] is None
|
||||||
|
assert args["datadir"] is None
|
||||||
|
assert args["verbosity"] == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_args_userdatadir(mocker) -> None:
|
||||||
|
mocker.patch.object(Path, "is_file", MagicMock(return_value=True))
|
||||||
|
args = Arguments(['trade', '--user-data-dir', 'user_data']).get_parsed_arg()
|
||||||
|
# configuration defaults to user_data if that is available.
|
||||||
|
assert args["config"] == [str(Path('user_data/config.json'))]
|
||||||
|
assert args["strategy_path"] is None
|
||||||
|
assert args["datadir"] is None
|
||||||
|
assert args["verbosity"] == 0
|
||||||
|
|
||||||
|
|
||||||
def test_parse_args_config() -> None:
|
def test_parse_args_config() -> None:
|
||||||
args = Arguments(['trade', '-c', '/dev/null']).get_parsed_arg()
|
args = Arguments(['trade', '-c', '/dev/null']).get_parsed_arg()
|
||||||
assert args["config"] == ['/dev/null']
|
assert args["config"] == ['/dev/null']
|
||||||
|
@ -208,7 +229,7 @@ def test_config_notrequired(mocker) -> None:
|
||||||
assert pargs["config"] is None
|
assert pargs["config"] is None
|
||||||
|
|
||||||
# When file exists:
|
# When file exists:
|
||||||
mocker.patch.object(Path, "is_file", MagicMock(return_value=True))
|
mocker.patch.object(Path, "is_file", MagicMock(side_effect=[False, True]))
|
||||||
args = [
|
args = [
|
||||||
'download-data',
|
'download-data',
|
||||||
]
|
]
|
||||||
|
|
|
@ -300,7 +300,7 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, caplog, mocker, edge_conf
|
||||||
|
|
||||||
# stoploss shoud be hit
|
# stoploss shoud be hit
|
||||||
assert freqtrade.handle_trade(trade) is True
|
assert freqtrade.handle_trade(trade) is True
|
||||||
assert log_has('executed sell, reason: SellType.STOP_LOSS', caplog)
|
assert log_has('Executing Sell for NEO/BTC. Reason: SellType.STOP_LOSS', caplog)
|
||||||
assert trade.sell_reason == SellType.STOP_LOSS.value
|
assert trade.sell_reason == SellType.STOP_LOSS.value
|
||||||
|
|
||||||
|
|
||||||
|
@ -921,7 +921,7 @@ def test_get_buy_rate(mocker, default_conf, ask, last, last_ab, expected) -> Non
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
|
||||||
MagicMock(return_value={'ask': ask, 'last': last}))
|
MagicMock(return_value={'ask': ask, 'last': last}))
|
||||||
|
|
||||||
assert freqtrade.get_buy_rate('ETH/BTC') == expected
|
assert freqtrade.get_buy_rate('ETH/BTC', True) == expected
|
||||||
|
|
||||||
|
|
||||||
def test_execute_buy(mocker, default_conf, fee, limit_buy_order) -> None:
|
def test_execute_buy(mocker, default_conf, fee, limit_buy_order) -> None:
|
||||||
|
@ -1964,7 +1964,7 @@ def test_check_handle_cancelled_buy(default_conf, ticker, limit_buy_order_old, o
|
||||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||||
nb_trades = len(trades)
|
nb_trades = len(trades)
|
||||||
assert nb_trades == 0
|
assert nb_trades == 0
|
||||||
assert log_has_re("Buy order canceled on Exchange for Trade.*", caplog)
|
assert log_has_re("Buy order cancelled on exchange for Trade.*", caplog)
|
||||||
|
|
||||||
|
|
||||||
def test_check_handle_timedout_buy_exception(default_conf, ticker, limit_buy_order_old, open_trade,
|
def test_check_handle_timedout_buy_exception(default_conf, ticker, limit_buy_order_old, open_trade,
|
||||||
|
@ -2045,7 +2045,7 @@ def test_check_handle_cancelled_sell(default_conf, ticker, limit_sell_order_old,
|
||||||
assert cancel_order_mock.call_count == 0
|
assert cancel_order_mock.call_count == 0
|
||||||
assert rpc_mock.call_count == 1
|
assert rpc_mock.call_count == 1
|
||||||
assert open_trade.is_open is True
|
assert open_trade.is_open is True
|
||||||
assert log_has_re("Sell order canceled on exchange for Trade.*", caplog)
|
assert log_has_re("Sell order cancelled on exchange for Trade.*", caplog)
|
||||||
|
|
||||||
|
|
||||||
def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old_partial,
|
def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old_partial,
|
||||||
|
@ -2067,7 +2067,7 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
|
||||||
# note this is for a partially-complete buy order
|
# note this is for a partially-complete buy order
|
||||||
freqtrade.check_handle_timedout()
|
freqtrade.check_handle_timedout()
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
assert rpc_mock.call_count == 1
|
assert rpc_mock.call_count == 2
|
||||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||||
assert len(trades) == 1
|
assert len(trades) == 1
|
||||||
assert trades[0].amount == 23.0
|
assert trades[0].amount == 23.0
|
||||||
|
@ -2101,7 +2101,7 @@ def test_check_handle_timedout_partial_fee(default_conf, ticker, open_trade, cap
|
||||||
assert log_has_re(r"Applying fee on amount for Trade.* Order", caplog)
|
assert log_has_re(r"Applying fee on amount for Trade.* Order", caplog)
|
||||||
|
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
assert rpc_mock.call_count == 1
|
assert rpc_mock.call_count == 2
|
||||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||||
assert len(trades) == 1
|
assert len(trades) == 1
|
||||||
# Verify that tradehas been updated
|
# Verify that tradehas been updated
|
||||||
|
@ -2140,7 +2140,7 @@ def test_check_handle_timedout_partial_except(default_conf, ticker, open_trade,
|
||||||
assert log_has_re(r"Could not update trade amount: .*", caplog)
|
assert log_has_re(r"Could not update trade amount: .*", caplog)
|
||||||
|
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
assert rpc_mock.call_count == 1
|
assert rpc_mock.call_count == 2
|
||||||
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
|
||||||
assert len(trades) == 1
|
assert len(trades) == 1
|
||||||
# Verify that tradehas been updated
|
# Verify that tradehas been updated
|
||||||
|
@ -3524,7 +3524,7 @@ def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2) -> None:
|
||||||
default_conf['telegram']['enabled'] = False
|
default_conf['telegram']['enabled'] = False
|
||||||
|
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
assert freqtrade.get_buy_rate('ETH/BTC') == 0.043935
|
assert freqtrade.get_buy_rate('ETH/BTC', True) == 0.043935
|
||||||
assert ticker_mock.call_count == 0
|
assert ticker_mock.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -3549,7 +3549,7 @@ def test_order_book_bid_strategy2(mocker, default_conf, order_book_l2) -> None:
|
||||||
|
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
# orderbook shall be used even if tickers would be lower.
|
# orderbook shall be used even if tickers would be lower.
|
||||||
assert freqtrade.get_buy_rate('ETH/BTC') != 0.042
|
assert freqtrade.get_buy_rate('ETH/BTC', True) != 0.042
|
||||||
assert ticker_mock.call_count == 0
|
assert ticker_mock.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,9 @@ from unittest.mock import MagicMock, PropertyMock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
from freqtrade.commands import Arguments
|
from freqtrade.commands import Arguments
|
||||||
from freqtrade.exceptions import OperationalException, FreqtradeException
|
from freqtrade.exceptions import FreqtradeException, OperationalException
|
||||||
from freqtrade.freqtradebot import FreqtradeBot
|
from freqtrade.freqtradebot import FreqtradeBot
|
||||||
from freqtrade.main import main
|
from freqtrade.main import main
|
||||||
from freqtrade.state import State
|
from freqtrade.state import State
|
||||||
|
@ -26,6 +27,7 @@ def test_parse_args_backtesting(mocker) -> None:
|
||||||
Test that main() can start backtesting and also ensure we can pass some specific arguments
|
Test that main() can start backtesting and also ensure we can pass some specific arguments
|
||||||
further argument parsing is done in test_arguments.py
|
further argument parsing is done in test_arguments.py
|
||||||
"""
|
"""
|
||||||
|
mocker.patch.object(Path, "is_file", MagicMock(side_effect=[False, True]))
|
||||||
backtesting_mock = mocker.patch('freqtrade.commands.start_backtesting')
|
backtesting_mock = mocker.patch('freqtrade.commands.start_backtesting')
|
||||||
backtesting_mock.__name__ = PropertyMock("start_backtesting")
|
backtesting_mock.__name__ = PropertyMock("start_backtesting")
|
||||||
# it's sys.exit(0) at the end of backtesting
|
# it's sys.exit(0) at the end of backtesting
|
||||||
|
@ -42,6 +44,7 @@ def test_parse_args_backtesting(mocker) -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_main_start_hyperopt(mocker) -> None:
|
def test_main_start_hyperopt(mocker) -> None:
|
||||||
|
mocker.patch.object(Path, "is_file", MagicMock(side_effect=[False, True]))
|
||||||
hyperopt_mock = mocker.patch('freqtrade.commands.start_hyperopt', MagicMock())
|
hyperopt_mock = mocker.patch('freqtrade.commands.start_hyperopt', MagicMock())
|
||||||
hyperopt_mock.__name__ = PropertyMock("start_hyperopt")
|
hyperopt_mock.__name__ = PropertyMock("start_hyperopt")
|
||||||
# it's sys.exit(0) at the end of hyperopt
|
# it's sys.exit(0) at the end of hyperopt
|
||||||
|
|
|
@ -19,7 +19,7 @@ from freqtrade.plot.plotting import (add_indicators, add_profit,
|
||||||
generate_profit_graph, init_plotscript,
|
generate_profit_graph, init_plotscript,
|
||||||
load_and_plot_trades, plot_profit,
|
load_and_plot_trades, plot_profit,
|
||||||
plot_trades, store_plot_file)
|
plot_trades, store_plot_file)
|
||||||
from freqtrade.strategy.default_strategy import DefaultStrategy
|
from freqtrade.resolvers import StrategyResolver
|
||||||
from tests.conftest import get_args, log_has, log_has_re
|
from tests.conftest import get_args, log_has, log_has_re
|
||||||
|
|
||||||
|
|
||||||
|
@ -70,9 +70,11 @@ def test_add_indicators(default_conf, testdatadir, caplog):
|
||||||
indicators1 = {"ema10": {}}
|
indicators1 = {"ema10": {}}
|
||||||
indicators2 = {"macd": {"color": "red"}}
|
indicators2 = {"macd": {"color": "red"}}
|
||||||
|
|
||||||
|
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||||
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
# Generate buy/sell signals and indicators
|
# Generate buy/sell signals and indicators
|
||||||
strat = DefaultStrategy(default_conf)
|
data = strategy.analyze_ticker(data, {'pair': pair})
|
||||||
data = strat.analyze_ticker(data, {'pair': pair})
|
|
||||||
fig = generate_empty_figure()
|
fig = generate_empty_figure()
|
||||||
|
|
||||||
# Row 1
|
# Row 1
|
||||||
|
@ -181,9 +183,11 @@ def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir)
|
||||||
data = history.load_pair_history(pair=pair, timeframe='1m',
|
data = history.load_pair_history(pair=pair, timeframe='1m',
|
||||||
datadir=testdatadir, timerange=timerange)
|
datadir=testdatadir, timerange=timerange)
|
||||||
|
|
||||||
|
default_conf.update({'strategy': 'DefaultStrategy'})
|
||||||
|
strategy = StrategyResolver.load_strategy(default_conf)
|
||||||
|
|
||||||
# Generate buy/sell signals and indicators
|
# Generate buy/sell signals and indicators
|
||||||
strat = DefaultStrategy(default_conf)
|
data = strategy.analyze_ticker(data, {'pair': pair})
|
||||||
data = strat.analyze_ticker(data, {'pair': pair})
|
|
||||||
|
|
||||||
indicators1 = []
|
indicators1 = []
|
||||||
indicators2 = []
|
indicators2 = []
|
||||||
|
|
Loading…
Reference in New Issue
Block a user