logs. No limit to get the entire log.
:return: json object
"""
return self._get("logs", params={"limit": limit} if limit else 0)
- def trades(self, limit=None):
- """Return trades history.
+ def trades(self, limit=None, offset=None):
+ """Return trades history, sorted by id
- :param limit: Limits trades to the X last trades. No limit to get all the trades.
+ :param limit: Limits trades to the X last trades. Max 500 trades.
+ :param offset: Offset by this amount of trades.
:return: json object
"""
- return self._get("trades", params={"limit": limit} if limit else 0)
+ params = {}
+ if limit:
+ params['limit'] = limit
+ if offset:
+ params['offset'] = offset
+ return self._get("trades", params)
+
+ def trade(self, trade_id):
+ """Return specific trade
+
+ :param trade_id: Specify which trade to get.
+ :return: json object
+ """
+ return self._get("trade/{}".format(trade_id))
def delete_trade(self, trade_id):
"""Delete trade from the database.
@@ -382,7 +396,7 @@ def main(args):
sys.exit()
config = load_config(args['config'])
- url = config.get('api_server', {}).get('server_url', '127.0.0.1')
+ url = config.get('api_server', {}).get('listen_ip_address', '127.0.0.1')
port = config.get('api_server', {}).get('listen_port', '8080')
username = config.get('api_server', {}).get('username')
password = config.get('api_server', {}).get('password')
diff --git a/setup.sh b/setup.sh
index d0ca1f643..631c31df2 100755
--- a/setup.sh
+++ b/setup.sh
@@ -138,7 +138,7 @@ function install_macos() {
# Install bot Debian_ubuntu
function install_debian() {
sudo apt-get update
- sudo apt-get install -y build-essential autoconf libtool pkg-config make wget git
+ sudo apt-get install -y build-essential autoconf libtool pkg-config make wget git libpython3-dev
install_talib
}
diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py
index 232fc4e2c..4d3937d87 100644
--- a/tests/commands/test_commands.py
+++ b/tests/commands/test_commands.py
@@ -116,7 +116,7 @@ def test_list_timeframes(mocker, capsys):
'1h': 'hour',
'1d': 'day',
}
- patch_exchange(mocker, api_mock=api_mock)
+ patch_exchange(mocker, api_mock=api_mock, id='bittrex')
args = [
"list-timeframes",
]
@@ -201,7 +201,7 @@ def test_list_markets(mocker, markets, capsys):
api_mock = MagicMock()
api_mock.markets = markets
- patch_exchange(mocker, api_mock=api_mock)
+ patch_exchange(mocker, api_mock=api_mock, id='bittrex')
# Test with no --config
args = [
@@ -918,242 +918,244 @@ def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys):
captured.out)
-def test_hyperopt_list(mocker, capsys, caplog, hyperopt_results):
+def test_hyperopt_list(mocker, capsys, caplog, saved_hyperopt_results,
+ saved_hyperopt_results_legacy):
+ for _ in (saved_hyperopt_results, saved_hyperopt_results_legacy):
+ mocker.patch(
+ 'freqtrade.optimize.hyperopt_tools.HyperoptTools.load_previous_results',
+ MagicMock(return_value=saved_hyperopt_results_legacy)
+ )
+
+ args = [
+ "hyperopt-list",
+ "--no-details",
+ "--no-color",
+ ]
+ 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", " 4/12", " 5/12",
+ " 6/12", " 7/12", " 8/12", " 9/12", " 10/12",
+ " 11/12", " 12/12"])
+ args = [
+ "hyperopt-list",
+ "--best",
+ "--no-details",
+ "--no-color",
+ ]
+ 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", " 5/12", " 10/12"])
+ assert all(x not in captured.out
+ for x in [" 2/12", " 3/12", " 4/12", " 6/12", " 7/12", " 8/12", " 9/12",
+ " 11/12", " 12/12"])
+ args = [
+ "hyperopt-list",
+ "--profitable",
+ "--no-details",
+ "--no-color",
+ ]
+ 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-color",
+ ]
+ 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", "Best result:", "Buy hyperspace params",
+ "Sell hyperspace params", "ROI table", "Stoploss"])
+ 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",
+ "--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",
+ "--no-color",
+ "--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",
+ "--no-color",
+ "--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",
+ "--no-color",
+ "--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",
+ "--no-color",
+ "--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",
+ "--no-color",
+ "--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",
+ "--no-details",
+ "--no-color",
+ "--min-objective", "0.1",
+ ]
+ 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-objective", "0.1",
+ ]
+ 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",
+ "--no-color",
+ "--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",
+ "--no-color",
+ "--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"])
+ args = [
+ "hyperopt-list",
+ "--no-details",
+ "--no-color",
+ "--export-csv", "test_file.csv",
+ ]
+ pargs = get_args(args)
+ pargs['config'] = None
+ start_hyperopt_list(pargs)
+ captured = capsys.readouterr()
+ log_has("CSV file created: test_file.csv", caplog)
+ f = Path("test_file.csv")
+ assert 'Best,1,2,-1.25%,-1.2222,-0.00125625,,-2.51,"3,930.0 m",0.43662' in f.read_text()
+ assert f.is_file()
+ f.unlink()
+
+
+def test_hyperopt_show(mocker, capsys, saved_hyperopt_results):
mocker.patch(
'freqtrade.optimize.hyperopt_tools.HyperoptTools.load_previous_results',
- MagicMock(return_value=hyperopt_results)
- )
-
- args = [
- "hyperopt-list",
- "--no-details",
- "--no-color",
- ]
- 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", " 4/12", " 5/12",
- " 6/12", " 7/12", " 8/12", " 9/12", " 10/12",
- " 11/12", " 12/12"])
- args = [
- "hyperopt-list",
- "--best",
- "--no-details",
- "--no-color",
- ]
- 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", " 5/12", " 10/12"])
- assert all(x not in captured.out
- for x in [" 2/12", " 3/12", " 4/12", " 6/12", " 7/12", " 8/12", " 9/12",
- " 11/12", " 12/12"])
- args = [
- "hyperopt-list",
- "--profitable",
- "--no-details",
- "--no-color",
- ]
- 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-color",
- ]
- 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", "Best result:", "Buy hyperspace params",
- "Sell hyperspace params", "ROI table", "Stoploss"])
- 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",
- "--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",
- "--no-color",
- "--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",
- "--no-color",
- "--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",
- "--no-color",
- "--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",
- "--no-color",
- "--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",
- "--no-color",
- "--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",
- "--no-details",
- "--no-color",
- "--min-objective", "0.1",
- ]
- 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-objective", "0.1",
- ]
- 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",
- "--no-color",
- "--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",
- "--no-color",
- "--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"])
- args = [
- "hyperopt-list",
- "--no-details",
- "--no-color",
- "--export-csv", "test_file.csv",
- ]
- pargs = get_args(args)
- pargs['config'] = None
- start_hyperopt_list(pargs)
- captured = capsys.readouterr()
- log_has("CSV file created: test_file.csv", caplog)
- f = Path("test_file.csv")
- assert 'Best,1,2,-1.25%,-1.2222,-0.00125625,,-2.51,"3,930.0 m",0.43662' in f.read_text()
- assert f.is_file()
- f.unlink()
-
-
-def test_hyperopt_show(mocker, capsys, hyperopt_results):
- mocker.patch(
- 'freqtrade.optimize.hyperopt_tools.HyperoptTools.load_previous_results',
- MagicMock(return_value=hyperopt_results)
+ MagicMock(return_value=saved_hyperopt_results)
)
args = [
diff --git a/tests/config_test_comments.json b/tests/config_test_comments.json
index 4f201f86c..48a087dec 100644
--- a/tests/config_test_comments.json
+++ b/tests/config_test_comments.json
@@ -59,7 +59,7 @@
}
},
"exchange": {
- "name": "bittrex",
+ "name": "binance",
"sandbox": false,
"key": "your_exchange_key",
"secret": "your_exchange_secret",
diff --git a/tests/conftest.py b/tests/conftest.py
index 4a2106a4d..ef2bd0613 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -3,7 +3,7 @@ import json
import logging
import re
from copy import deepcopy
-from datetime import datetime
+from datetime import datetime, timedelta
from functools import reduce
from pathlib import Path
from unittest.mock import MagicMock, Mock, PropertyMock
@@ -21,6 +21,7 @@ from freqtrade.exchange import Exchange
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import LocalTrade, Trade, init_db
from freqtrade.resolvers import ExchangeResolver
+from freqtrade.state import RunMode
from freqtrade.worker import Worker
from tests.conftest_trades import (mock_trade_1, mock_trade_2, mock_trade_3, mock_trade_4,
mock_trade_5, mock_trade_6)
@@ -79,7 +80,7 @@ def patched_configuration_load_config_file(mocker, config) -> None:
)
-def patch_exchange(mocker, api_mock=None, id='bittrex', mock_markets=True) -> None:
+def patch_exchange(mocker, api_mock=None, id='binance', mock_markets=True) -> None:
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
@@ -98,7 +99,7 @@ def patch_exchange(mocker, api_mock=None, id='bittrex', mock_markets=True) -> No
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock())
-def get_patched_exchange(mocker, config, api_mock=None, id='bittrex',
+def get_patched_exchange(mocker, config, api_mock=None, id='binance',
mock_markets=True) -> Exchange:
patch_exchange(mocker, api_mock, id, mock_markets)
config['exchange']['name'] = id
@@ -293,7 +294,7 @@ def get_default_conf(testdatadir):
"order_book_max": 1
},
"exchange": {
- "name": "bittrex",
+ "name": "binance",
"enabled": True,
"key": "key",
"secret": "secret",
@@ -314,7 +315,8 @@ def get_default_conf(testdatadir):
"telegram": {
"enabled": True,
"token": "token",
- "chat_id": "0"
+ "chat_id": "0",
+ "notification_settings": {},
},
"datadir": str(testdatadir),
"initial_state": "running",
@@ -1676,6 +1678,7 @@ def buy_order_fee():
@pytest.fixture(scope="function")
def edge_conf(default_conf):
conf = deepcopy(default_conf)
+ conf['runmode'] = RunMode.DRY_RUN
conf['max_open_trades'] = -1
conf['tradable_balance_ratio'] = 0.5
conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
@@ -1765,7 +1768,7 @@ def open_trade():
return Trade(
pair='ETH/BTC',
open_rate=0.00001099,
- exchange='bittrex',
+ exchange='binance',
open_order_id='123456789',
amount=90.99181073,
fee_open=0.0,
@@ -1777,7 +1780,7 @@ def open_trade():
@pytest.fixture
-def hyperopt_results():
+def saved_hyperopt_results_legacy():
return [
{
'loss': 0.4366182531161519,
@@ -1906,3 +1909,136 @@ def hyperopt_results():
'is_best': False
}
]
+
+
+@pytest.fixture
+def saved_hyperopt_results():
+ return [
+ {
+ 'loss': 0.4366182531161519,
+ 'params_dict': {
+ 'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1190, 'roi_t2': 541, 'roi_t3': 408, 'roi_p1': 0.026035863879169705, 'roi_p2': 0.12508730043628782, 'roi_p3': 0.27766427921605896, 'stoploss': -0.2562930402099556}, # noqa: E501
+ 'params_details': {'buy': {'mfi-value': 15, 'fastd-value': 20, 'adx-value': 25, 'rsi-value': 28, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 88, 'sell-fastd-value': 97, 'sell-adx-value': 51, 'sell-rsi-value': 67, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.4287874435315165, 408: 0.15112316431545753, 949: 0.026035863879169705, 2139: 0}, 'stoploss': {'stoploss': -0.2562930402099556}}, # noqa: E501
+ 'results_metrics': {'total_trades': 2, 'wins': 0, 'draws': 0, 'losses': 2, 'profit_mean': -0.01254995, 'profit_median': -0.012222, 'profit_total': -0.00125625, 'profit_total_abs': -2.50999, 'holding_avg': timedelta(minutes=3930.0)}, # noqa: E501
+ 'results_explanation': ' 2 trades. Avg profit -1.25%. Total profit -0.00125625 BTC ( -2.51Σ%). Avg duration 3930.0 min.', # noqa: E501
+ 'total_profit': -0.00125625,
+ 'current_epoch': 1,
+ 'is_initial_point': True,
+ 'is_best': True
+ }, {
+ 'loss': 20.0,
+ 'params_dict': {
+ 'mfi-value': 17, 'fastd-value': 38, 'adx-value': 48, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 96, 'sell-fastd-value': 68, 'sell-adx-value': 63, 'sell-rsi-value': 81, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 334, 'roi_t2': 683, 'roi_t3': 140, 'roi_p1': 0.06403981740598495, 'roi_p2': 0.055519840060645045, 'roi_p3': 0.3253712811342459, 'stoploss': -0.338070047333259}, # noqa: E501
+ 'params_details': {
+ 'buy': {'mfi-value': 17, 'fastd-value': 38, 'adx-value': 48, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, # noqa: E501
+ 'sell': {'sell-mfi-value': 96, 'sell-fastd-value': 68, 'sell-adx-value': 63, 'sell-rsi-value': 81, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, # noqa: E501
+ 'roi': {0: 0.4449309386008759, 140: 0.11955965746663, 823: 0.06403981740598495, 1157: 0}, # noqa: E501
+ 'stoploss': {'stoploss': -0.338070047333259}},
+ 'results_metrics': {'total_trades': 1, 'wins': 0, 'draws': 0, 'losses': 1, 'profit_mean': 0.012357, 'profit_median': -0.012222, 'profit_total': 6.185e-05, 'profit_total_abs': 0.12357, 'holding_avg': timedelta(minutes=1200.0)}, # noqa: E501
+ 'results_explanation': ' 1 trades. Avg profit 0.12%. Total profit 0.00006185 BTC ( 0.12Σ%). Avg duration 1200.0 min.', # noqa: E501
+ 'total_profit': 6.185e-05,
+ 'current_epoch': 2,
+ 'is_initial_point': True,
+ 'is_best': False
+ }, {
+ 'loss': 14.241196856510731,
+ 'params_dict': {'mfi-value': 25, 'fastd-value': 16, 'adx-value': 29, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 98, 'sell-fastd-value': 72, 'sell-adx-value': 51, 'sell-rsi-value': 82, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 889, 'roi_t2': 533, 'roi_t3': 263, 'roi_p1': 0.04759065393663096, 'roi_p2': 0.1488819964638463, 'roi_p3': 0.4102801822104605, 'stoploss': -0.05394588767607611}, # noqa: E501
+ 'params_details': {'buy': {'mfi-value': 25, 'fastd-value': 16, 'adx-value': 29, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 98, 'sell-fastd-value': 72, 'sell-adx-value': 51, 'sell-rsi-value': 82, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.6067528326109377, 263: 0.19647265040047726, 796: 0.04759065393663096, 1685: 0}, 'stoploss': {'stoploss': -0.05394588767607611}}, # noqa: E501
+ 'results_metrics': {'total_trades': 621, 'wins': 320, 'draws': 0, 'losses': 301, 'profit_mean': -0.043883302093397747, 'profit_median': -0.012222, 'profit_total': -0.13639474, 'profit_total_abs': -272.515306, 'holding_avg': timedelta(minutes=1691.207729468599)}, # noqa: E501
+ 'results_explanation': ' 621 trades. Avg profit -0.44%. Total profit -0.13639474 BTC (-272.52Σ%). Avg duration 1691.2 min.', # noqa: E501
+ 'total_profit': -0.13639474,
+ 'current_epoch': 3,
+ 'is_initial_point': True,
+ 'is_best': False
+ }, {
+ 'loss': 100000,
+ 'params_dict': {'mfi-value': 13, 'fastd-value': 35, 'adx-value': 39, 'rsi-value': 29, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 87, 'sell-fastd-value': 54, 'sell-adx-value': 63, 'sell-rsi-value': 93, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1402, 'roi_t2': 676, 'roi_t3': 215, 'roi_p1': 0.06264755784937427, 'roi_p2': 0.14258587851894644, 'roi_p3': 0.20671291201040828, 'stoploss': -0.11818343570194478}, # noqa: E501
+ 'params_details': {'buy': {'mfi-value': 13, 'fastd-value': 35, 'adx-value': 39, 'rsi-value': 29, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 54, 'sell-adx-value': 63, 'sell-rsi-value': 93, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.411946348378729, 215: 0.2052334363683207, 891: 0.06264755784937427, 2293: 0}, 'stoploss': {'stoploss': -0.11818343570194478}}, # noqa: E501
+ 'results_metrics': {'total_trades': 0, 'wins': 0, 'draws': 0, 'losses': 0, 'profit_mean': None, 'profit_median': None, 'profit_total': 0, 'profit': 0.0, 'holding_avg': timedelta()}, # noqa: E501
+ 'results_explanation': ' 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.', # noqa: E501
+ 'total_profit': 0, 'current_epoch': 4, 'is_initial_point': True, 'is_best': False
+ }, {
+ 'loss': 0.22195522184191518,
+ 'params_dict': {'mfi-value': 17, 'fastd-value': 21, 'adx-value': 38, 'rsi-value': 33, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 87, 'sell-fastd-value': 82, 'sell-adx-value': 78, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 1269, 'roi_t2': 601, 'roi_t3': 444, 'roi_p1': 0.07280999507931168, 'roi_p2': 0.08946698095898986, 'roi_p3': 0.1454876733325284, 'stoploss': -0.18181041180901014}, # noqa: E501
+ 'params_details': {'buy': {'mfi-value': 17, 'fastd-value': 21, 'adx-value': 38, 'rsi-value': 33, 'mfi-enabled': True, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 82, 'sell-adx-value': 78, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.3077646493708299, 444: 0.16227697603830155, 1045: 0.07280999507931168, 2314: 0}, 'stoploss': {'stoploss': -0.18181041180901014}}, # noqa: E501
+ 'results_metrics': {'total_trades': 14, 'wins': 6, 'draws': 0, 'losses': 8, 'profit_mean': -0.003539515, 'profit_median': -0.012222, 'profit_total': -0.002480140000000001, 'profit_total_abs': -4.955321, 'holding_avg': timedelta(minutes=3402.8571428571427)}, # noqa: E501
+ 'results_explanation': ' 14 trades. Avg profit -0.35%. Total profit -0.00248014 BTC ( -4.96Σ%). Avg duration 3402.9 min.', # noqa: E501
+ 'total_profit': -0.002480140000000001,
+ 'current_epoch': 5,
+ 'is_initial_point': True,
+ 'is_best': True
+ }, {
+ 'loss': 0.545315889154162,
+ 'params_dict': {'mfi-value': 22, 'fastd-value': 43, 'adx-value': 46, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'bb_lower', 'sell-mfi-value': 87, 'sell-fastd-value': 65, 'sell-adx-value': 94, 'sell-rsi-value': 63, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 319, 'roi_t2': 556, 'roi_t3': 216, 'roi_p1': 0.06251955472249589, 'roi_p2': 0.11659519602202795, 'roi_p3': 0.0953744132197762, 'stoploss': -0.024551752215582423}, # noqa: E501
+ 'params_details': {'buy': {'mfi-value': 22, 'fastd-value': 43, 'adx-value': 46, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 87, 'sell-fastd-value': 65, 'sell-adx-value': 94, 'sell-rsi-value': 63, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.2744891639643, 216: 0.17911475074452382, 772: 0.06251955472249589, 1091: 0}, 'stoploss': {'stoploss': -0.024551752215582423}}, # noqa: E501
+ 'results_metrics': {'total_trades': 39, 'wins': 20, 'draws': 0, 'losses': 19, 'profit_mean': -0.0021400679487179478, 'profit_median': -0.012222, 'profit_total': -0.0041773, 'profit_total_abs': -8.346264999999997, 'holding_avg': timedelta(minutes=636.9230769230769)}, # noqa: E501
+ 'results_explanation': ' 39 trades. Avg profit -0.21%. Total profit -0.00417730 BTC ( -8.35Σ%). Avg duration 636.9 min.', # noqa: E501
+ 'total_profit': -0.0041773,
+ 'current_epoch': 6,
+ 'is_initial_point': True,
+ 'is_best': False
+ }, {
+ 'loss': 4.713497421432944,
+ 'params_dict': {'mfi-value': 13, 'fastd-value': 41, 'adx-value': 21, 'rsi-value': 29, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower', 'sell-mfi-value': 99, 'sell-fastd-value': 60, 'sell-adx-value': 81, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 771, 'roi_t2': 620, 'roi_t3': 145, 'roi_p1': 0.0586919200378493, 'roi_p2': 0.04984118697312542, 'roi_p3': 0.37521058680247044, 'stoploss': -0.14613268022709905}, # noqa: E501
+ 'params_details': {
+ 'buy': {'mfi-value': 13, 'fastd-value': 41, 'adx-value': 21, 'rsi-value': 29, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 99, 'sell-fastd-value': 60, 'sell-adx-value': 81, 'sell-rsi-value': 69, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': False, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.4837436938134452, 145: 0.10853310701097472, 765: 0.0586919200378493, 1536: 0}, # noqa: E501
+ 'stoploss': {'stoploss': -0.14613268022709905}}, # noqa: E501
+ 'results_metrics': {'total_trades': 318, 'wins': 100, 'draws': 0, 'losses': 218, 'profit_mean': -0.0039833954716981146, 'profit_median': -0.012222, 'profit_total': -0.06339929, 'profit_total_abs': -126.67197600000004, 'holding_avg': timedelta(minutes=3140.377358490566)}, # noqa: E501
+ 'results_explanation': ' 318 trades. Avg profit -0.40%. Total profit -0.06339929 BTC (-126.67Σ%). Avg duration 3140.4 min.', # noqa: E501
+ 'total_profit': -0.06339929,
+ 'current_epoch': 7,
+ 'is_initial_point': True,
+ 'is_best': False
+ }, {
+ 'loss': 20.0, # noqa: E501
+ 'params_dict': {'mfi-value': 24, 'fastd-value': 43, 'adx-value': 33, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'sar_reversal', 'sell-mfi-value': 89, 'sell-fastd-value': 74, 'sell-adx-value': 70, 'sell-rsi-value': 70, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': False, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 1149, 'roi_t2': 375, 'roi_t3': 289, 'roi_p1': 0.05571820757172588, 'roi_p2': 0.0606240398618907, 'roi_p3': 0.1729012220156157, 'stoploss': -0.1588514289110401}, # noqa: E501
+ 'params_details': {'buy': {'mfi-value': 24, 'fastd-value': 43, 'adx-value': 33, 'rsi-value': 20, 'mfi-enabled': False, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': True, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 89, 'sell-fastd-value': 74, 'sell-adx-value': 70, 'sell-rsi-value': 70, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': False, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, 'roi': {0: 0.2892434694492323, 289: 0.11634224743361658, 664: 0.05571820757172588, 1813: 0}, 'stoploss': {'stoploss': -0.1588514289110401}}, # noqa: E501
+ 'results_metrics': {'total_trades': 1, 'wins': 0, 'draws': 1, 'losses': 0, 'profit_mean': 0.0, 'profit_median': 0.0, 'profit_total': 0.0, 'profit_total_abs': 0.0, 'holding_avg': timedelta(minutes=5340.0)}, # noqa: E501
+ 'results_explanation': ' 1 trades. Avg profit 0.00%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration 5340.0 min.', # noqa: E501
+ 'total_profit': 0.0,
+ 'current_epoch': 8,
+ 'is_initial_point': True,
+ 'is_best': False
+ }, {
+ 'loss': 2.4731817780991223,
+ 'params_dict': {'mfi-value': 22, 'fastd-value': 20, 'adx-value': 29, 'rsi-value': 40, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'sar_reversal', 'sell-mfi-value': 97, 'sell-fastd-value': 65, 'sell-adx-value': 81, 'sell-rsi-value': 64, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1012, 'roi_t2': 584, 'roi_t3': 422, 'roi_p1': 0.036764323603472565, 'roi_p2': 0.10335480573205287, 'roi_p3': 0.10322347377503042, 'stoploss': -0.2780610808108503}, # noqa: E501
+ 'params_details': {'buy': {'mfi-value': 22, 'fastd-value': 20, 'adx-value': 29, 'rsi-value': 40, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 97, 'sell-fastd-value': 65, 'sell-adx-value': 81, 'sell-rsi-value': 64, 'sell-mfi-enabled': True, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.2433426031105559, 422: 0.14011912933552545, 1006: 0.036764323603472565, 2018: 0}, 'stoploss': {'stoploss': -0.2780610808108503}}, # noqa: E501
+ 'results_metrics': {'total_trades': 229, 'wins': 150, 'draws': 0, 'losses': 79, 'profit_mean': -0.0038433433624454144, 'profit_median': -0.012222, 'profit_total': -0.044050070000000004, 'profit_total_abs': -88.01256299999999, 'holding_avg': timedelta(minutes=6505.676855895196)}, # noqa: E501
+ 'results_explanation': ' 229 trades. Avg profit -0.38%. Total profit -0.04405007 BTC ( -88.01Σ%). Avg duration 6505.7 min.', # noqa: E501
+ 'total_profit': -0.044050070000000004, # noqa: E501
+ 'current_epoch': 9,
+ 'is_initial_point': True,
+ 'is_best': False
+ }, {
+ 'loss': -0.2604606005845212, # noqa: E501
+ 'params_dict': {'mfi-value': 23, 'fastd-value': 24, 'adx-value': 22, 'rsi-value': 24, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal', 'sell-mfi-value': 97, 'sell-fastd-value': 70, 'sell-adx-value': 64, 'sell-rsi-value': 80, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal', 'roi_t1': 792, 'roi_t2': 464, 'roi_t3': 215, 'roi_p1': 0.04594053535385903, 'roi_p2': 0.09623192684243963, 'roi_p3': 0.04428219070850663, 'stoploss': -0.16992287161634415}, # noqa: E501
+ 'params_details': {'buy': {'mfi-value': 23, 'fastd-value': 24, 'adx-value': 22, 'rsi-value': 24, 'mfi-enabled': False, 'fastd-enabled': False, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'macd_cross_signal'}, 'sell': {'sell-mfi-value': 97, 'sell-fastd-value': 70, 'sell-adx-value': 64, 'sell-rsi-value': 80, 'sell-mfi-enabled': False, 'sell-fastd-enabled': True, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-sar_reversal'}, 'roi': {0: 0.18645465290480528, 215: 0.14217246219629864, 679: 0.04594053535385903, 1471: 0}, 'stoploss': {'stoploss': -0.16992287161634415}}, # noqa: E501
+ 'results_metrics': {'total_trades': 4, 'wins': 0, 'draws': 0, 'losses': 4, 'profit_mean': 0.001080385, 'profit_median': -0.012222, 'profit_total': 0.00021629, 'profit_total_abs': 0.432154, 'holding_avg': timedelta(minutes=2850.0)}, # noqa: E501
+ 'results_explanation': ' 4 trades. Avg profit 0.11%. Total profit 0.00021629 BTC ( 0.43Σ%). Avg duration 2850.0 min.', # noqa: E501
+ 'total_profit': 0.00021629,
+ 'current_epoch': 10,
+ 'is_initial_point': True,
+ 'is_best': True
+ }, {
+ 'loss': 4.876465945994304, # noqa: E501
+ 'params_dict': {'mfi-value': 20, 'fastd-value': 32, 'adx-value': 49, 'rsi-value': 23, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower', 'sell-mfi-value': 75, 'sell-fastd-value': 56, 'sell-adx-value': 61, 'sell-rsi-value': 62, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal', 'roi_t1': 579, 'roi_t2': 614, 'roi_t3': 273, 'roi_p1': 0.05307643172744114, 'roi_p2': 0.1352282078262871, 'roi_p3': 0.1913307406325751, 'stoploss': -0.25728526022513887}, # noqa: E501
+ 'params_details': {'buy': {'mfi-value': 20, 'fastd-value': 32, 'adx-value': 49, 'rsi-value': 23, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': False, 'rsi-enabled': False, 'trigger': 'bb_lower'}, 'sell': {'sell-mfi-value': 75, 'sell-fastd-value': 56, 'sell-adx-value': 61, 'sell-rsi-value': 62, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-macd_cross_signal'}, 'roi': {0: 0.3796353801863034, 273: 0.18830463955372825, 887: 0.05307643172744114, 1466: 0}, 'stoploss': {'stoploss': -0.25728526022513887}}, # noqa: E501
+ # New Hyperopt mode!
+ 'results_metrics': {'total_trades': 117, 'wins': 67, 'draws': 0, 'losses': 50, 'profit_mean': -0.012698609145299145, 'profit_median': -0.012222, 'profit_total': -0.07436117, 'profit_total_abs': -148.573727, 'holding_avg': timedelta(minutes=4282.5641025641025)}, # noqa: E501
+ 'results_explanation': ' 117 trades. Avg profit -1.27%. Total profit -0.07436117 BTC (-148.57Σ%). Avg duration 4282.6 min.', # noqa: E501
+ 'total_profit': -0.07436117,
+ 'current_epoch': 11,
+ 'is_initial_point': True,
+ 'is_best': False
+ }, {
+ 'loss': 100000,
+ 'params_dict': {'mfi-value': 10, 'fastd-value': 36, 'adx-value': 31, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'sar_reversal', 'sell-mfi-value': 80, 'sell-fastd-value': 71, 'sell-adx-value': 60, 'sell-rsi-value': 85, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper', 'roi_t1': 1156, 'roi_t2': 581, 'roi_t3': 408, 'roi_p1': 0.06860454019988212, 'roi_p2': 0.12473718444931989, 'roi_p3': 0.2896360635226823, 'stoploss': -0.30889015124682806}, # noqa: E501
+ 'params_details': {'buy': {'mfi-value': 10, 'fastd-value': 36, 'adx-value': 31, 'rsi-value': 22, 'mfi-enabled': True, 'fastd-enabled': True, 'adx-enabled': True, 'rsi-enabled': False, 'trigger': 'sar_reversal'}, 'sell': {'sell-mfi-value': 80, 'sell-fastd-value': 71, 'sell-adx-value': 60, 'sell-rsi-value': 85, 'sell-mfi-enabled': False, 'sell-fastd-enabled': False, 'sell-adx-enabled': True, 'sell-rsi-enabled': True, 'sell-trigger': 'sell-bb_upper'}, 'roi': {0: 0.4829777881718843, 408: 0.19334172464920202, 989: 0.06860454019988212, 2145: 0}, 'stoploss': {'stoploss': -0.30889015124682806}}, # noqa: E501
+ 'results_metrics': {'total_trades': 0, 'wins': 0, 'draws': 0, 'losses': 0, 'profit_mean': None, 'profit_median': None, 'profit_total': 0, 'profit_total_abs': 0.0, 'holding_avg': timedelta()}, # noqa: E501
+ 'results_explanation': ' 0 trades. Avg profit nan%. Total profit 0.00000000 BTC ( 0.00Σ%). Avg duration nan min.', # noqa: E501
+ 'total_profit': 0,
+ 'current_epoch': 12,
+ 'is_initial_point': True,
+ 'is_best': False
+ }
+ ]
diff --git a/tests/conftest_trades.py b/tests/conftest_trades.py
index 34fc58aee..b92b51144 100644
--- a/tests/conftest_trades.py
+++ b/tests/conftest_trades.py
@@ -31,7 +31,7 @@ def mock_trade_1(fee):
is_open=True,
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=17),
open_rate=0.123,
- exchange='bittrex',
+ exchange='binance',
open_order_id='dry_run_buy_12345',
strategy='DefaultStrategy',
timeframe=5,
@@ -84,7 +84,7 @@ def mock_trade_2(fee):
close_rate=0.128,
close_profit=0.005,
close_profit_abs=0.000584127,
- exchange='bittrex',
+ exchange='binance',
is_open=False,
open_order_id='dry_run_sell_12345',
strategy='DefaultStrategy',
@@ -144,7 +144,7 @@ def mock_trade_3(fee):
close_rate=0.06,
close_profit=0.01,
close_profit_abs=0.000155,
- exchange='bittrex',
+ exchange='binance',
is_open=False,
strategy='DefaultStrategy',
timeframe=5,
@@ -187,7 +187,7 @@ def mock_trade_4(fee):
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=14),
is_open=True,
open_rate=0.123,
- exchange='bittrex',
+ exchange='binance',
open_order_id='prod_buy_12345',
strategy='DefaultStrategy',
timeframe=5,
@@ -239,7 +239,7 @@ def mock_trade_5(fee):
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=12),
is_open=True,
open_rate=0.123,
- exchange='bittrex',
+ exchange='binance',
strategy='SampleStrategy',
stoploss_order_id='prod_stoploss_3455',
timeframe=5,
@@ -293,7 +293,7 @@ def mock_trade_6(fee):
fee_close=fee.return_value,
is_open=True,
open_rate=0.15,
- exchange='bittrex',
+ exchange='binance',
strategy='SampleStrategy',
open_order_id="prod_sell_6",
timeframe=5,
diff --git a/tests/data/test_btanalysis.py b/tests/data/test_btanalysis.py
index e42c13e18..6bde60926 100644
--- a/tests/data/test_btanalysis.py
+++ b/tests/data/test_btanalysis.py
@@ -1,3 +1,4 @@
+from math import isclose
from pathlib import Path
from unittest.mock import MagicMock
@@ -246,7 +247,7 @@ def test_create_cum_profit(testdatadir):
"cum_profits", timeframe="5m")
assert "cum_profits" in cum_profits.columns
assert cum_profits.iloc[0]['cum_profits'] == 0
- assert cum_profits.iloc[-1]['cum_profits'] == 0.0798005
+ assert isclose(cum_profits.iloc[-1]['cum_profits'], 8.723007518796964e-06)
def test_create_cum_profit1(testdatadir):
@@ -264,7 +265,7 @@ def test_create_cum_profit1(testdatadir):
"cum_profits", timeframe="5m")
assert "cum_profits" in cum_profits.columns
assert cum_profits.iloc[0]['cum_profits'] == 0
- assert cum_profits.iloc[-1]['cum_profits'] == 0.0798005
+ assert isclose(cum_profits.iloc[-1]['cum_profits'], 8.723007518796964e-06)
with pytest.raises(ValueError, match='Trade dataframe empty.'):
create_cum_profit(df.set_index('date'), bt_data[bt_data["pair"] == 'NOTAPAIR'],
diff --git a/tests/data/test_dataprovider.py b/tests/data/test_dataprovider.py
index ee2e551b6..b87258c64 100644
--- a/tests/data/test_dataprovider.py
+++ b/tests/data/test_dataprovider.py
@@ -214,8 +214,8 @@ def test_current_whitelist(mocker, default_conf, tickers):
pairlist.refresh_pairlist()
assert dp.current_whitelist() == pairlist._whitelist
- # The identity of the 2 lists should be identical
- assert dp.current_whitelist() is pairlist._whitelist
+ # The identity of the 2 lists should not be identical, but a copy
+ assert dp.current_whitelist() is not pairlist._whitelist
with pytest.raises(OperationalException):
dp = DataProvider(default_conf, exchange)
@@ -246,3 +246,46 @@ def test_get_analyzed_dataframe(mocker, default_conf, ohlcv_history):
assert dataframe.empty
assert isinstance(time, datetime)
assert time == datetime(1970, 1, 1, tzinfo=timezone.utc)
+
+ # Test backtest mode
+ default_conf["runmode"] = RunMode.BACKTEST
+ dp._set_dataframe_max_index(1)
+ dataframe, time = dp.get_analyzed_dataframe("XRP/BTC", timeframe)
+
+ assert len(dataframe) == 1
+
+ dp._set_dataframe_max_index(2)
+ dataframe, time = dp.get_analyzed_dataframe("XRP/BTC", timeframe)
+ assert len(dataframe) == 2
+
+ dp._set_dataframe_max_index(3)
+ dataframe, time = dp.get_analyzed_dataframe("XRP/BTC", timeframe)
+ assert len(dataframe) == 3
+
+ dp._set_dataframe_max_index(500)
+ dataframe, time = dp.get_analyzed_dataframe("XRP/BTC", timeframe)
+ assert len(dataframe) == len(ohlcv_history)
+
+
+def test_no_exchange_mode(default_conf):
+ dp = DataProvider(default_conf, None)
+
+ message = "Exchange is not available to DataProvider."
+
+ with pytest.raises(OperationalException, match=message):
+ dp.refresh([()])
+
+ with pytest.raises(OperationalException, match=message):
+ dp.ohlcv('XRP/USDT', '5m')
+
+ with pytest.raises(OperationalException, match=message):
+ dp.market('XRP/USDT')
+
+ with pytest.raises(OperationalException, match=message):
+ dp.ticker('XRP/USDT')
+
+ with pytest.raises(OperationalException, match=message):
+ dp.orderbook('XRP/USDT', 20)
+
+ with pytest.raises(OperationalException, match=message):
+ dp.available_pairs()
diff --git a/tests/edge/test_edge.py b/tests/edge/test_edge.py
index 5142dd985..c4620e1c7 100644
--- a/tests/edge/test_edge.py
+++ b/tests/edge/test_edge.py
@@ -330,11 +330,11 @@ def test_edge_process_no_data(mocker, edge_conf, caplog):
def test_edge_process_no_trades(mocker, edge_conf, caplog):
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
- mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001))
- mocker.patch('freqtrade.edge.edge_positioning.refresh_data', MagicMock())
+ mocker.patch('freqtrade.exchange.Exchange.get_fee', return_value=0.001)
+ mocker.patch('freqtrade.edge.edge_positioning.refresh_data', )
mocker.patch('freqtrade.edge.edge_positioning.load_data', mocked_load_data)
# Return empty
- mocker.patch('freqtrade.edge.Edge._find_trades_for_stoploss_range', MagicMock(return_value=[]))
+ mocker.patch('freqtrade.edge.Edge._find_trades_for_stoploss_range', return_value=[])
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
assert not edge.calculate(edge_conf['exchange']['pair_whitelist'])
@@ -342,6 +342,25 @@ def test_edge_process_no_trades(mocker, edge_conf, caplog):
assert log_has("No trades found.", caplog)
+def test_edge_process_no_pairs(mocker, edge_conf, caplog):
+ edge_conf['exchange']['pair_whitelist'] = []
+ mocker.patch('freqtrade.freqtradebot.validate_config_consistency')
+
+ freqtrade = get_patched_freqtradebot(mocker, edge_conf)
+ fee_mock = mocker.patch('freqtrade.exchange.Exchange.get_fee', return_value=0.001)
+ mocker.patch('freqtrade.edge.edge_positioning.refresh_data')
+ mocker.patch('freqtrade.edge.edge_positioning.load_data', mocked_load_data)
+ # Return empty
+ mocker.patch('freqtrade.edge.Edge._find_trades_for_stoploss_range', return_value=[])
+ edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
+ assert fee_mock.call_count == 0
+ assert edge.fee is None
+
+ assert not edge.calculate(['XRP/USDT'])
+ assert fee_mock.call_count == 1
+ assert edge.fee == 0.001
+
+
def test_edge_init_error(mocker, edge_conf,):
edge_conf['stake_amount'] = 0.5
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001))
diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py
index 870e6cabd..dce10da84 100644
--- a/tests/exchange/test_ccxt_compat.py
+++ b/tests/exchange/test_ccxt_compat.py
@@ -36,7 +36,12 @@ EXCHANGES = {
'pair': 'BTC/USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
- }
+ },
+ 'kucoin': {
+ 'pair': 'BTC/USDT',
+ 'hasQuoteVolume': True,
+ 'timeframe': '5m',
+ },
}
@@ -100,14 +105,16 @@ class TestCCXTExchange():
assert 'asks' in l2
assert 'bids' in l2
l2_limit_range = exchange._ft_has['l2_limit_range']
+ l2_limit_range_required = exchange._ft_has['l2_limit_range_required']
for val in [1, 2, 5, 25, 100]:
l2 = exchange.fetch_l2_order_book(pair, val)
if not l2_limit_range or val in l2_limit_range:
assert len(l2['asks']) == val
assert len(l2['bids']) == val
else:
- next_limit = exchange.get_next_limit_in_list(val, l2_limit_range)
- if next_limit > 200:
+ next_limit = exchange.get_next_limit_in_list(
+ val, l2_limit_range, l2_limit_range_required)
+ if next_limit is None or next_limit > 200:
# Large orderbook sizes can be a problem for some exchanges (bitrex ...)
assert len(l2['asks']) > 200
assert len(l2['asks']) > 200
diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py
index 27a19bc61..95a11cd85 100644
--- a/tests/exchange/test_exchange.py
+++ b/tests/exchange/test_exchange.py
@@ -379,7 +379,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
PropertyMock(return_value=markets)
)
result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss)
- assert isclose(result, 2 * 1.1)
+ assert isclose(result, 2 * (1+0.05) / (1-abs(stoploss)))
# min amount is set
markets["ETH/BTC"]["limits"] = {
@@ -391,7 +391,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
PropertyMock(return_value=markets)
)
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
- assert isclose(result, 2 * 2 * 1.1)
+ assert isclose(result, 2 * 2 * (1+0.05) / (1-abs(stoploss)))
# min amount and cost are set (cost is minimal)
markets["ETH/BTC"]["limits"] = {
@@ -403,7 +403,7 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
PropertyMock(return_value=markets)
)
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
- assert isclose(result, max(2, 2 * 2) * 1.1)
+ assert isclose(result, max(2, 2 * 2) * (1+0.05) / (1-abs(stoploss)))
# min amount and cost are set (amount is minial)
markets["ETH/BTC"]["limits"] = {
@@ -415,10 +415,10 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
PropertyMock(return_value=markets)
)
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss)
- assert isclose(result, max(8, 2 * 2) * 1.1)
+ assert isclose(result, max(8, 2 * 2) * (1+0.05) / (1-abs(stoploss)))
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4)
- assert isclose(result, max(8, 2 * 2) * 1.45)
+ assert isclose(result, max(8, 2 * 2) * 1.5)
# Really big stoploss
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1)
@@ -440,7 +440,10 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None:
PropertyMock(return_value=markets)
)
result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss)
- assert round(result, 8) == round(max(0.0001, 0.001 * 0.020405) * 1.1, 8)
+ assert round(result, 8) == round(
+ max(0.0001, 0.001 * 0.020405) * (1+0.05) / (1-abs(stoploss)),
+ 8
+ )
def test_set_sandbox(default_conf, mocker):
@@ -1253,29 +1256,6 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name):
assert "timeInForce" not in api_mock.create_order.call_args[0][5]
-@pytest.mark.parametrize("exchange_name", EXCHANGES)
-def test_get_balance_prod(default_conf, mocker, exchange_name):
- api_mock = MagicMock()
- api_mock.fetch_balance = MagicMock(return_value={'BTC': {'free': 123.4, 'total': 123.4}})
- default_conf['dry_run'] = False
-
- exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
-
- assert exchange.get_balance(currency='BTC') == 123.4
-
- with pytest.raises(OperationalException):
- api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
- exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
-
- exchange.get_balance(currency='BTC')
-
- with pytest.raises(TemporaryError, match=r'.*balance due to malformed exchange response:.*'):
- exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
- mocker.patch('freqtrade.exchange.Exchange.get_balances', MagicMock(return_value={}))
- mocker.patch('freqtrade.exchange.Kraken.get_balances', MagicMock(return_value={}))
- exchange.get_balance(currency='BTC')
-
-
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_get_balances_prod(default_conf, mocker, exchange_name):
balance_item = {
@@ -1327,6 +1307,16 @@ def test_get_tickers(default_conf, mocker, exchange_name):
assert tickers['ETH/BTC']['ask'] == 1
assert tickers['BCH/BTC']['bid'] == 0.6
assert tickers['BCH/BTC']['ask'] == 0.5
+ assert api_mock.fetch_tickers.call_count == 1
+
+ api_mock.fetch_tickers.reset_mock()
+
+ # Cached ticker should not call api again
+ tickers2 = exchange.get_tickers(cached=True)
+ assert tickers2 == tickers
+ assert api_mock.fetch_tickers.call_count == 0
+ tickers2 = exchange.get_tickers(cached=False)
+ assert api_mock.fetch_tickers.call_count == 1
ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
"get_tickers", "fetch_tickers")
@@ -1649,6 +1639,9 @@ def test_get_next_limit_in_list():
# Going over the limit ...
assert Exchange.get_next_limit_in_list(1001, limit_range) == 1000
assert Exchange.get_next_limit_in_list(2000, limit_range) == 1000
+ # Without required range
+ assert Exchange.get_next_limit_in_list(2000, limit_range, False) is None
+ assert Exchange.get_next_limit_in_list(15, limit_range, False) == 20
assert Exchange.get_next_limit_in_list(21, None) == 21
assert Exchange.get_next_limit_in_list(100, None) == 100
@@ -2099,6 +2092,46 @@ def test_cancel_stoploss_order(default_conf, mocker, exchange_name):
order_id='_', pair='TKN/BTC')
+@pytest.mark.parametrize("exchange_name", EXCHANGES)
+def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name):
+ default_conf['dry_run'] = False
+ mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', return_value={'for': 123})
+ mocker.patch('freqtrade.exchange.Ftx.fetch_stoploss_order', return_value={'for': 123})
+ exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
+
+ mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
+ return_value={'fee': {}, 'status': 'canceled', 'amount': 1234})
+ mocker.patch('freqtrade.exchange.Ftx.cancel_stoploss_order',
+ return_value={'fee': {}, 'status': 'canceled', 'amount': 1234})
+ co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555)
+ assert co == {'fee': {}, 'status': 'canceled', 'amount': 1234}
+
+ mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
+ return_value='canceled')
+ mocker.patch('freqtrade.exchange.Ftx.cancel_stoploss_order',
+ return_value='canceled')
+ # Fall back to fetch_stoploss_order
+ co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555)
+ assert co == {'for': 123}
+
+ mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order',
+ side_effect=InvalidOrderException(""))
+ mocker.patch('freqtrade.exchange.Ftx.fetch_stoploss_order',
+ side_effect=InvalidOrderException(""))
+
+ co = exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=555)
+ assert co['amount'] == 555
+ assert co == {'fee': {}, 'status': 'canceled', 'amount': 555, 'info': {}}
+
+ with pytest.raises(InvalidOrderException):
+ mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
+ side_effect=InvalidOrderException("Did not find order"))
+ mocker.patch('freqtrade.exchange.Ftx.cancel_stoploss_order',
+ side_effect=InvalidOrderException("Did not find order"))
+ exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
+ exchange.cancel_stoploss_order_with_result(order_id='_', pair='TKN/BTC', amount=123)
+
+
@pytest.mark.parametrize("exchange_name", EXCHANGES)
def test_fetch_order(default_conf, mocker, exchange_name):
default_conf['dry_run'] = True
diff --git a/tests/exchange/test_ftx.py b/tests/exchange/test_ftx.py
index 17cfb26fa..63d99acdf 100644
--- a/tests/exchange/test_ftx.py
+++ b/tests/exchange/test_ftx.py
@@ -39,8 +39,9 @@ def test_stoploss_order_ftx(default_conf, mocker):
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
- assert api_mock.create_order.call_args_list[0][1]['price'] == 190
assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params']
+ assert 'stopPrice' in api_mock.create_order.call_args_list[0][1]['params']
+ assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 190
assert api_mock.create_order.call_count == 1
@@ -55,8 +56,8 @@ def test_stoploss_order_ftx(default_conf, mocker):
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
- assert api_mock.create_order.call_args_list[0][1]['price'] == 220
assert 'orderPrice' not in api_mock.create_order.call_args_list[0][1]['params']
+ assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 220
api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
@@ -69,9 +70,9 @@ def test_stoploss_order_ftx(default_conf, mocker):
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell'
assert api_mock.create_order.call_args_list[0][1]['amount'] == 1
- assert api_mock.create_order.call_args_list[0][1]['price'] == 220
assert 'orderPrice' in api_mock.create_order.call_args_list[0][1]['params']
assert api_mock.create_order.call_args_list[0][1]['params']['orderPrice'] == 217.8
+ assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 220
# test exception handling
with pytest.raises(DependencyException):
@@ -156,3 +157,26 @@ def test_fetch_stoploss_order(default_conf, mocker):
'fetch_stoploss_order', 'fetch_orders',
retries=API_FETCH_ORDER_RETRY_COUNT + 1,
order_id='_', pair='TKN/BTC')
+
+
+def test_get_order_id(mocker, default_conf):
+ exchange = get_patched_exchange(mocker, default_conf, id='ftx')
+ order = {
+ 'type': STOPLOSS_ORDERTYPE,
+ 'price': 1500,
+ 'id': '1111',
+ 'info': {
+ 'orderId': '1234'
+ }
+ }
+ assert exchange.get_order_id_conditional(order) == '1234'
+
+ order = {
+ 'type': 'limit',
+ 'price': 1500,
+ 'id': '1111',
+ 'info': {
+ 'orderId': '1234'
+ }
+ }
+ assert exchange.get_order_id_conditional(order) == '1111'
diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py
index 97f428e2f..ed22cde92 100644
--- a/tests/exchange/test_kraken.py
+++ b/tests/exchange/test_kraken.py
@@ -90,6 +90,7 @@ def test_get_balances_prod(default_conf, mocker):
'3ST': balance_item.copy(),
'4ST': balance_item.copy(),
'EUR': balance_item.copy(),
+ 'timestamp': 123123
})
kraken_open_orders = [{'symbol': '1ST/EUR',
'type': 'limit',
@@ -138,7 +139,7 @@ def test_get_balances_prod(default_conf, mocker):
default_conf['dry_run'] = False
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
balances = exchange.get_balances()
- assert len(balances) == 5
+ assert len(balances) == 6
assert balances['1ST']['free'] == 9.0
assert balances['1ST']['total'] == 10.0
diff --git a/tests/optimize/conftest.py b/tests/optimize/conftest.py
index 5c789ec1e..11b4674f3 100644
--- a/tests/optimize/conftest.py
+++ b/tests/optimize/conftest.py
@@ -6,6 +6,7 @@ import pandas as pd
import pytest
from freqtrade.optimize.hyperopt import Hyperopt
+from freqtrade.state import RunMode
from freqtrade.strategy.interface import SellType
from tests.conftest import patch_exchange
@@ -15,6 +16,7 @@ def hyperopt_conf(default_conf):
hyperconf = deepcopy(default_conf)
hyperconf.update({
'datadir': Path(default_conf['datadir']),
+ 'runmode': RunMode.HYPEROPT,
'hyperopt': 'DefaultHyperOpt',
'hyperopt_loss': 'ShortTradeDurHyperOptLoss',
'hyperopt_path': str(Path(__file__).parent / 'hyperopts'),
diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py
index 3655b941d..c35a083ec 100644
--- a/tests/optimize/test_backtest_detail.py
+++ b/tests/optimize/test_backtest_detail.py
@@ -185,7 +185,7 @@ tc11 = BTContainer(data=[
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5050, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 5100, 5100, 6172, 0, 0],
- [3, 4850, 5050, 4650, 4750, 6172, 0, 0],
+ [3, 5000, 5150, 4650, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
@@ -440,6 +440,23 @@ tc27 = BTContainer(data=[
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=4)]
)
+# Test 28: trailing_stop should raise so candle 3 causes a stoploss
+# Same case than tc11 - but candle 3 "gaps down" - the stoploss will be above the candle,
+# therefore "open" will be used
+# stop-loss: 10%, ROI: 10% (should not apply), stoploss adjusted candle 2
+tc28 = BTContainer(data=[
+ # D O H L C V B S
+ [0, 5000, 5050, 4950, 5000, 6172, 1, 0],
+ [1, 5000, 5050, 4950, 5100, 6172, 0, 0],
+ [2, 5100, 5251, 5100, 5100, 6172, 0, 0],
+ [3, 4850, 5050, 4650, 4750, 6172, 0, 0],
+ [4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
+ stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.03, trailing_stop=True,
+ trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
+ trailing_stop_positive=0.03,
+ trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
+)
+
TESTS = [
tc0,
tc1,
@@ -469,6 +486,7 @@ TESTS = [
tc25,
tc26,
tc27,
+ tc28,
]
@@ -493,6 +511,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
patch_exchange(mocker)
frame = _build_backtest_dataframe(data.data)
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = lambda a, m: frame
backtesting.strategy.advise_sell = lambda a, m: frame
caplog.set_level(logging.DEBUG)
@@ -501,13 +520,14 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
# Dummy data as we mock the analyze functions
data_processed = {pair: frame.copy()}
min_date, max_date = get_timerange({pair: frame})
- results = backtesting.backtest(
+ result = backtesting.backtest(
processed=data_processed,
start_date=min_date,
end_date=max_date,
max_open_trades=10,
)
+ results = result['results']
assert len(results) == len(data.trades)
assert round(results["profit_ratio"].sum(), 3) == round(data.profit_perc, 3)
diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py
index 4bbfe8a78..632d458ce 100644
--- a/tests/optimize/test_backtesting.py
+++ b/tests/optimize/test_backtesting.py
@@ -83,6 +83,7 @@ def simple_backtest(config, contour, mocker, testdatadir) -> None:
patch_exchange(mocker)
config['timeframe'] = '1m'
backtesting = Backtesting(config)
+ backtesting._set_strategy(backtesting.strategylist[0])
data = load_data_test(contour, testdatadir)
processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
@@ -106,6 +107,7 @@ def _make_backtest_conf(mocker, datadir, conf=None, pair='UNITTEST/BTC'):
data = trim_dictlist(data, -201)
patch_exchange(mocker)
backtesting = Backtesting(conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
min_date, max_date = get_timerange(processed)
return {
@@ -285,6 +287,7 @@ def test_backtesting_init(mocker, default_conf, order_types) -> None:
patch_exchange(mocker)
get_fee = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
assert backtesting.config == default_conf
assert backtesting.timeframe == '5m'
assert callable(backtesting.strategy.ohlcvdata_to_dataframe)
@@ -315,11 +318,13 @@ def test_data_with_fee(default_conf, mocker, testdatadir) -> None:
fee_mock = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
assert backtesting.fee == 0.1234
assert fee_mock.call_count == 0
default_conf['fee'] = 0.0
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
assert backtesting.fee == 0.0
assert fee_mock.call_count == 0
@@ -330,6 +335,7 @@ def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None:
data = history.load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange,
fill_up_missing=True)
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
assert len(processed['UNITTEST/BTC']) == 102
@@ -361,12 +367,13 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
default_conf['timerange'] = '-1510694220'
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.bot_loop_start = MagicMock()
backtesting.start()
# check the logs, that will contain the backtest result
exists = [
'Backtesting with data from 2017-11-14 21:17:00 '
- 'up to 2017-11-14 22:59:00 (0 days)..'
+ 'up to 2017-11-14 22:59:00 (0 days).'
]
for line in exists:
assert log_has(line, caplog)
@@ -393,6 +400,7 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) ->
default_conf['timerange'] = '20180101-20180102'
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
with pytest.raises(OperationalException, match='No data found. Terminating.'):
backtesting.start()
@@ -457,13 +465,15 @@ def test_backtesting_pairlist_list(default_conf, mocker, caplog, testdatadir, ti
Backtesting(default_conf)
-def test_backtest__enter_trade(default_conf, fee, mocker, testdatadir) -> None:
+def test_backtest__enter_trade(default_conf, fee, mocker) -> None:
default_conf['ask_strategy']['use_sell_signal'] = False
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
patch_exchange(mocker)
default_conf['stake_amount'] = 'unlimited'
+ default_conf['max_open_trades'] = 2
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
pair = 'UNITTEST/BTC'
row = [
pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0),
@@ -474,24 +484,30 @@ def test_backtest__enter_trade(default_conf, fee, mocker, testdatadir) -> None:
0.00099, # Low
0.0012, # High
]
- trade = backtesting._enter_trade(pair, row=row, max_open_trades=2, open_trade_count=0)
+ trade = backtesting._enter_trade(pair, row=row)
assert isinstance(trade, LocalTrade)
assert trade.stake_amount == 495
- trade = backtesting._enter_trade(pair, row=row, max_open_trades=2, open_trade_count=2)
+ # Fake 2 trades, so there's not enough amount for the next trade left.
+ LocalTrade.trades_open.append(trade)
+ LocalTrade.trades_open.append(trade)
+ trade = backtesting._enter_trade(pair, row=row)
assert trade is None
+ LocalTrade.trades_open.pop()
+ trade = backtesting._enter_trade(pair, row=row)
+ assert trade is not None
# Stake-amount too high!
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=600.0)
- trade = backtesting._enter_trade(pair, row=row, max_open_trades=2, open_trade_count=0)
+ trade = backtesting._enter_trade(pair, row=row)
assert trade is None
- # Stake-amount too high!
+ # Stake-amount throwing error
mocker.patch("freqtrade.wallets.Wallets.get_trade_stake_amount",
side_effect=DependencyException)
- trade = backtesting._enter_trade(pair, row=row, max_open_trades=2, open_trade_count=0)
+ trade = backtesting._enter_trade(pair, row=row)
assert trade is None
@@ -501,19 +517,21 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
pair = 'UNITTEST/BTC'
timerange = TimeRange('date', None, 1517227800, 0)
data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'],
timerange=timerange)
processed = backtesting.strategy.ohlcvdata_to_dataframe(data)
min_date, max_date = get_timerange(processed)
- results = backtesting.backtest(
+ result = backtesting.backtest(
processed=processed,
start_date=min_date,
end_date=max_date,
max_open_trades=10,
position_stacking=False,
)
+ results = result['results']
assert not results.empty
assert len(results) == 2
@@ -562,6 +580,7 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None
mocker.patch("freqtrade.exchange.Exchange.get_min_pair_stake_amount", return_value=0.00001)
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
# Run a backtesting for an exiting 1min timeframe
timerange = TimeRange.parse_timerange('1510688220-1510700340')
@@ -576,13 +595,14 @@ def test_backtest_1min_timeframe(default_conf, fee, mocker, testdatadir) -> None
max_open_trades=1,
position_stacking=False,
)
- assert not results.empty
- assert len(results) == 1
+ assert not results['results'].empty
+ assert len(results['results']) == 1
def test_processed(default_conf, mocker, testdatadir) -> None:
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
dict_of_tickerrows = load_data_test('raise', testdatadir)
dataframes = backtesting.strategy.ohlcvdata_to_dataframe(dict_of_tickerrows)
@@ -616,7 +636,7 @@ def test_backtest_pricecontours_protections(default_conf, fee, mocker, testdatad
# While buy-signals are unrealistic, running backtesting
# over and over again should not cause different results
for [contour, numres] in tests:
- assert len(simple_backtest(default_conf, contour, mocker, testdatadir)) == numres
+ assert len(simple_backtest(default_conf, contour, mocker, testdatadir)['results']) == numres
@pytest.mark.parametrize('protections,contour,expected', [
@@ -641,7 +661,7 @@ def test_backtest_pricecontours(default_conf, fee, mocker, testdatadir,
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
# While buy-signals are unrealistic, running backtesting
# over and over again should not cause different results
- assert len(simple_backtest(default_conf, contour, mocker, testdatadir)) == expected
+ assert len(simple_backtest(default_conf, contour, mocker, testdatadir)['results']) == expected
def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
@@ -653,10 +673,11 @@ def test_backtest_clash_buy_sell(mocker, default_conf, testdatadir):
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir)
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = fun # Override
backtesting.strategy.advise_sell = fun # Override
- results = backtesting.backtest(**backtest_conf)
- assert results.empty
+ result = backtesting.backtest(**backtest_conf)
+ assert result['results'].empty
def test_backtest_only_sell(mocker, default_conf, testdatadir):
@@ -668,10 +689,11 @@ def test_backtest_only_sell(mocker, default_conf, testdatadir):
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, datadir=testdatadir)
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = fun # Override
backtesting.strategy.advise_sell = fun # Override
- results = backtesting.backtest(**backtest_conf)
- assert results.empty
+ result = backtesting.backtest(**backtest_conf)
+ assert result['results'].empty
def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
@@ -681,12 +703,14 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker, testdatadir):
pair='UNITTEST/BTC', datadir=testdatadir)
default_conf['timeframe'] = '1m'
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = _trend_alternate # Override
backtesting.strategy.advise_sell = _trend_alternate # Override
- results = backtesting.backtest(**backtest_conf)
+ result = backtesting.backtest(**backtest_conf)
# 200 candles in backtest data
# won't buy on first (shifted by 1)
# 100 buys signals
+ results = result['results']
assert len(results) == 100
# One trade was force-closed at the end
assert len(results.loc[results['is_open']]) == 0
@@ -722,6 +746,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
default_conf['timeframe'] = '5m'
backtesting = Backtesting(default_conf)
+ backtesting._set_strategy(backtesting.strategylist[0])
backtesting.strategy.advise_buy = _trend_alternate_hold # Override
backtesting.strategy.advise_sell = _trend_alternate_hold # Override
@@ -738,9 +763,9 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
results = backtesting.backtest(**backtest_conf)
# Make sure we have parallel trades
- assert len(evaluate_result_multi(results, '5m', 2)) > 0
+ assert len(evaluate_result_multi(results['results'], '5m', 2)) > 0
# make sure we don't have trades with more than configured max_open_trades
- assert len(evaluate_result_multi(results, '5m', 3)) == 0
+ assert len(evaluate_result_multi(results['results'], '5m', 3)) == 0
backtest_conf = {
'processed': processed,
@@ -750,7 +775,7 @@ def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair, testdatadir)
'position_stacking': False,
}
results = backtesting.backtest(**backtest_conf)
- assert len(evaluate_result_multi(results, '5m', 1)) == 0
+ assert len(evaluate_result_multi(results['results'], '5m', 1)) == 0
def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
@@ -782,9 +807,9 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
'Parameter --timerange detected: 1510694220-1510700340 ...',
f'Using data directory: {testdatadir} ...',
'Loading data from 2017-11-14 20:57:00 '
- 'up to 2017-11-14 22:58:00 (0 days)..',
+ 'up to 2017-11-14 22:58:00 (0 days).',
'Backtesting with data from 2017-11-14 21:17:00 '
- 'up to 2017-11-14 22:58:00 (0 days)..',
+ 'up to 2017-11-14 22:58:00 (0 days).',
'Parameter --enable-position-stacking detected ...'
]
@@ -795,8 +820,20 @@ def test_backtest_start_timerange(default_conf, mocker, caplog, testdatadir):
@pytest.mark.filterwarnings("ignore:deprecated")
def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
+ default_conf['ask_strategy'].update({
+ "use_sell_signal": True,
+ "sell_profit_only": False,
+ "sell_profit_offset": 0.0,
+ "ignore_roi_if_buy_signal": False,
+ })
patch_exchange(mocker)
- backtestmock = MagicMock(return_value=pd.DataFrame(columns=BT_DATA_COLUMNS))
+ backtestmock = MagicMock(return_value={
+ 'results': pd.DataFrame(columns=BT_DATA_COLUMNS),
+ 'config': default_conf,
+ 'locks': [],
+ 'rejected_signals': 20,
+ 'final_balance': 1000,
+ })
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['UNITTEST/BTC']))
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
@@ -810,7 +847,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
text_table_strategy=strattable_mock,
generate_pair_metrics=MagicMock(),
generate_sell_reason_stats=sell_reason_mock,
- generate_strategy_metrics=strat_summary,
+ generate_strategy_comparison=strat_summary,
generate_daily_stats=MagicMock(),
)
patched_configuration_load_config_file(mocker, default_conf)
@@ -844,9 +881,9 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
'Parameter --timerange detected: 1510694220-1510700340 ...',
f'Using data directory: {testdatadir} ...',
'Loading data from 2017-11-14 20:57:00 '
- 'up to 2017-11-14 22:58:00 (0 days)..',
+ 'up to 2017-11-14 22:58:00 (0 days).',
'Backtesting with data from 2017-11-14 21:17:00 '
- 'up to 2017-11-14 22:58:00 (0 days)..',
+ 'up to 2017-11-14 22:58:00 (0 days).',
'Parameter --enable-position-stacking detected ...',
'Running backtesting for Strategy DefaultStrategy',
'Running backtesting for Strategy TestStrategyLegacy',
@@ -858,41 +895,60 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
@pytest.mark.filterwarnings("ignore:deprecated")
def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdatadir, capsys):
-
+ default_conf['ask_strategy'].update({
+ "use_sell_signal": True,
+ "sell_profit_only": False,
+ "sell_profit_offset": 0.0,
+ "ignore_roi_if_buy_signal": False,
+ })
patch_exchange(mocker)
+ result1 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'],
+ 'profit_ratio': [0.0, 0.0],
+ 'profit_abs': [0.0, 0.0],
+ 'open_date': pd.to_datetime(['2018-01-29 18:40:00',
+ '2018-01-30 03:30:00', ], utc=True
+ ),
+ 'close_date': pd.to_datetime(['2018-01-29 20:45:00',
+ '2018-01-30 05:35:00', ], utc=True),
+ 'trade_duration': [235, 40],
+ 'is_open': [False, False],
+ 'stake_amount': [0.01, 0.01],
+ 'open_rate': [0.104445, 0.10302485],
+ 'close_rate': [0.104969, 0.103541],
+ 'sell_reason': [SellType.ROI, SellType.ROI]
+ })
+ result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
+ 'profit_ratio': [0.03, 0.01, 0.1],
+ 'profit_abs': [0.01, 0.02, 0.2],
+ 'open_date': pd.to_datetime(['2018-01-29 18:40:00',
+ '2018-01-30 03:30:00',
+ '2018-01-30 05:30:00'], utc=True
+ ),
+ 'close_date': pd.to_datetime(['2018-01-29 20:45:00',
+ '2018-01-30 05:35:00',
+ '2018-01-30 08:30:00'], utc=True),
+ 'trade_duration': [47, 40, 20],
+ 'is_open': [False, False, False],
+ 'stake_amount': [0.01, 0.01, 0.01],
+ 'open_rate': [0.104445, 0.10302485, 0.122541],
+ 'close_rate': [0.104969, 0.103541, 0.123541],
+ 'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
+ })
backtestmock = MagicMock(side_effect=[
- pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC'],
- 'profit_ratio': [0.0, 0.0],
- 'profit_abs': [0.0, 0.0],
- 'open_date': pd.to_datetime(['2018-01-29 18:40:00',
- '2018-01-30 03:30:00', ], utc=True
- ),
- 'close_date': pd.to_datetime(['2018-01-29 20:45:00',
- '2018-01-30 05:35:00', ], utc=True),
- 'trade_duration': [235, 40],
- 'is_open': [False, False],
- 'stake_amount': [0.01, 0.01],
- 'open_rate': [0.104445, 0.10302485],
- 'close_rate': [0.104969, 0.103541],
- 'sell_reason': [SellType.ROI, SellType.ROI]
- }),
- pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
- 'profit_ratio': [0.03, 0.01, 0.1],
- 'profit_abs': [0.01, 0.02, 0.2],
- 'open_date': pd.to_datetime(['2018-01-29 18:40:00',
- '2018-01-30 03:30:00',
- '2018-01-30 05:30:00'], utc=True
- ),
- 'close_date': pd.to_datetime(['2018-01-29 20:45:00',
- '2018-01-30 05:35:00',
- '2018-01-30 08:30:00'], utc=True),
- 'trade_duration': [47, 40, 20],
- 'is_open': [False, False, False],
- 'stake_amount': [0.01, 0.01, 0.01],
- 'open_rate': [0.104445, 0.10302485, 0.122541],
- 'close_rate': [0.104969, 0.103541, 0.123541],
- 'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
- }),
+ {
+ 'results': result1,
+ 'config': default_conf,
+ 'locks': [],
+ 'rejected_signals': 20,
+ 'final_balance': 1000,
+ },
+ {
+ 'results': result2,
+ 'config': default_conf,
+ 'locks': [],
+ 'rejected_signals': 20,
+ 'final_balance': 1000,
+ }
])
mocker.patch('freqtrade.plugins.pairlistmanager.PairListManager.whitelist',
PropertyMock(return_value=['UNITTEST/BTC']))
@@ -923,9 +979,9 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'Parameter --timerange detected: 1510694220-1510700340 ...',
f'Using data directory: {testdatadir} ...',
'Loading data from 2017-11-14 20:57:00 '
- 'up to 2017-11-14 22:58:00 (0 days)..',
+ 'up to 2017-11-14 22:58:00 (0 days).',
'Backtesting with data from 2017-11-14 21:17:00 '
- 'up to 2017-11-14 22:58:00 (0 days)..',
+ 'up to 2017-11-14 22:58:00 (0 days).',
'Parameter --enable-position-stacking detected ...',
'Running backtesting for Strategy DefaultStrategy',
'Running backtesting for Strategy TestStrategyLegacy',
diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py
index c13da0d76..d7eb8bf67 100644
--- a/tests/optimize/test_hyperopt.py
+++ b/tests/optimize/test_hyperopt.py
@@ -5,7 +5,7 @@ import re
from datetime import datetime
from pathlib import Path
from typing import Dict, List
-from unittest.mock import MagicMock
+from unittest.mock import ANY, MagicMock
import pandas as pd
import pytest
@@ -18,8 +18,12 @@ from freqtrade.exceptions import OperationalException
from freqtrade.optimize.hyperopt import Hyperopt
from freqtrade.optimize.hyperopt_auto import HyperOptAuto
from freqtrade.optimize.hyperopt_tools import HyperoptTools
+from freqtrade.optimize.optimize_reports import generate_strategy_stats
+from freqtrade.optimize.space import SKDecimal
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver
from freqtrade.state import RunMode
+from freqtrade.strategy.hyper import IntParameter
+from freqtrade.strategy.interface import SellType
from tests.conftest import (get_args, log_has, log_has_re, patch_exchange,
patched_configuration_load_config_file)
@@ -27,23 +31,7 @@ from .hyperopts.default_hyperopt import DefaultHyperOpt
# Functions for recurrent object patching
-def create_results(mocker, hyperopt, testdatadir) -> List[Dict]:
- """
- When creating results, mock the hyperopt so that *by default*
- - we don't create any pickle'd files in the filesystem
- - we might have a pickle'd file so make sure that we return
- false when looking for it
- """
- hyperopt.results_file = testdatadir / 'optimize/ut_results.pickle'
-
- mocker.patch.object(Path, "is_file", MagicMock(return_value=False))
- stat_mock = MagicMock()
- stat_mock.st_size = 1
- mocker.patch.object(Path, "stat", MagicMock(return_value=stat_mock))
-
- mocker.patch.object(Path, "unlink", MagicMock(return_value=True))
- mocker.patch('freqtrade.optimize.hyperopt.dump', return_value=None)
- mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
+def create_results() -> List[Dict]:
return [{'loss': 1, 'result': 'foo', 'params': {}, 'is_best': True}]
@@ -317,54 +305,49 @@ def test_no_log_if_loss_does_not_improve(hyperopt, caplog) -> None:
assert caplog.record_tuples == []
-def test_save_results_saves_epochs(mocker, hyperopt, testdatadir, caplog) -> None:
- epochs = create_results(mocker, hyperopt, testdatadir)
- mock_dump = mocker.patch('freqtrade.optimize.hyperopt.dump', return_value=None)
- mock_dump_json = mocker.patch('freqtrade.optimize.hyperopt.file_dump_json', return_value=None)
- results_file = testdatadir / 'optimize' / 'ut_results.pickle'
+def test_save_results_saves_epochs(mocker, hyperopt, tmpdir, caplog) -> None:
+ # Test writing to temp dir and reading again
+ epochs = create_results()
+ hyperopt.results_file = Path(tmpdir / 'ut_results.fthypt')
caplog.set_level(logging.DEBUG)
- hyperopt.epochs = epochs
- hyperopt._save_results()
- assert log_has(f"1 epoch saved to '{results_file}'.", caplog)
- mock_dump.assert_called_once()
- mock_dump_json.assert_called_once()
+ for epoch in epochs:
+ hyperopt._save_result(epoch)
+ assert log_has(f"1 epoch saved to '{hyperopt.results_file}'.", caplog)
- hyperopt.epochs = epochs + epochs
- hyperopt._save_results()
- assert log_has(f"2 epochs saved to '{results_file}'.", caplog)
+ hyperopt._save_result(epochs[0])
+ assert log_has(f"2 epochs saved to '{hyperopt.results_file}'.", caplog)
+
+ hyperopt_epochs = HyperoptTools.load_previous_results(hyperopt.results_file)
+ assert len(hyperopt_epochs) == 2
-def test_read_results_returns_epochs(mocker, hyperopt, testdatadir, caplog) -> None:
- epochs = create_results(mocker, hyperopt, testdatadir)
- mock_load = mocker.patch('freqtrade.optimize.hyperopt_tools.load', return_value=epochs)
- results_file = testdatadir / 'optimize' / 'ut_results.pickle'
- hyperopt_epochs = HyperoptTools._read_results(results_file)
- assert log_has(f"Reading epochs from '{results_file}'", caplog)
- assert hyperopt_epochs == epochs
- mock_load.assert_called_once()
+def test_load_previous_results(testdatadir, caplog) -> None:
-
-def test_load_previous_results(mocker, hyperopt, testdatadir, caplog) -> None:
- epochs = create_results(mocker, hyperopt, testdatadir)
- mock_load = mocker.patch('freqtrade.optimize.hyperopt_tools.load', return_value=epochs)
- mocker.patch.object(Path, 'is_file', MagicMock(return_value=True))
- statmock = MagicMock()
- statmock.st_size = 5
- # mocker.patch.object(Path, 'stat', MagicMock(return_value=statmock))
-
- results_file = testdatadir / 'optimize' / 'ut_results.pickle'
+ results_file = testdatadir / 'hyperopt_results_SampleStrategy.pickle'
hyperopt_epochs = HyperoptTools.load_previous_results(results_file)
- assert hyperopt_epochs == epochs
- mock_load.assert_called_once()
+ assert len(hyperopt_epochs) == 5
+ assert log_has_re(r"Reading pickled epochs from .*", caplog)
- del epochs[0]['is_best']
- mock_load = mocker.patch('freqtrade.optimize.hyperopt_tools.load', return_value=epochs)
+ caplog.clear()
- with pytest.raises(OperationalException):
+ # Modern version
+ results_file = testdatadir / 'strategy_SampleStrategy.fthypt'
+
+ hyperopt_epochs = HyperoptTools.load_previous_results(results_file)
+
+ assert len(hyperopt_epochs) == 5
+ assert log_has_re(r"Reading epochs from .*", caplog)
+
+
+def test_load_previous_results2(mocker, testdatadir, caplog) -> None:
+ mocker.patch('freqtrade.optimize.hyperopt_tools.HyperoptTools._read_results_pickle',
+ return_value=[{'asdf': '222'}])
+ results_file = testdatadir / 'hyperopt_results_SampleStrategy.pickle'
+ with pytest.raises(OperationalException, match=r"The file .* incompatible.*"):
HyperoptTools.load_previous_results(results_file)
@@ -382,7 +365,8 @@ def test_roi_table_generation(hyperopt) -> None:
def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
- dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
+ dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
+ dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
@@ -421,9 +405,9 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
out, err = capsys.readouterr()
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
- assert dumper.called
- # Should be called twice, once for historical candle data, once to save evaluations
- assert dumper.call_count == 2
+ # Should be called for historical candle data
+ assert dumper.call_count == 1
+ assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt, "max_open_trades")
@@ -431,18 +415,42 @@ def test_start_calls_optimizer(mocker, hyperopt_conf, capsys) -> None:
assert hasattr(hyperopt, "position_stacking")
-def test_format_results(hyperopt):
- # Test with BTC as stake_currency
- trades = [
- ('ETH/BTC', 2, 2, 123),
- ('LTC/BTC', 1, 1, 123),
- ('XPR/BTC', -1, -2, -246)
- ]
- labels = ['currency', 'profit_ratio', 'profit_abs', 'trade_duration']
- df = pd.DataFrame.from_records(trades, columns=labels)
- results_metrics = hyperopt._calculate_results_metrics(df)
- results_explanation = hyperopt._format_results_explanation_string(results_metrics)
- total_profit = results_metrics['total_profit']
+def test_hyperopt_format_results(hyperopt):
+
+ bt_result = {
+ 'results': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC",
+ "UNITTEST/BTC", "UNITTEST/BTC"],
+ "profit_ratio": [0.003312, 0.010801, 0.013803, 0.002780],
+ "profit_abs": [0.000003, 0.000011, 0.000014, 0.000003],
+ "open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
+ Arrow(2017, 11, 14, 21, 36, 00).datetime,
+ Arrow(2017, 11, 14, 22, 12, 00).datetime,
+ Arrow(2017, 11, 14, 22, 44, 00).datetime],
+ "close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime,
+ Arrow(2017, 11, 14, 22, 10, 00).datetime,
+ Arrow(2017, 11, 14, 22, 43, 00).datetime,
+ Arrow(2017, 11, 14, 22, 58, 00).datetime],
+ "open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
+ "close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
+ "trade_duration": [123, 34, 31, 14],
+ "is_open": [False, False, False, True],
+ "stake_amount": [0.01, 0.01, 0.01, 0.01],
+ "sell_reason": [SellType.ROI, SellType.STOP_LOSS,
+ SellType.ROI, SellType.FORCE_SELL]
+ }),
+ 'config': hyperopt.config,
+ 'locks': [],
+ 'final_balance': 0.02,
+ 'rejected_signals': 2,
+ 'backtest_start_time': 1619718665,
+ 'backtest_end_time': 1619718665,
+ }
+ results_metrics = generate_strategy_stats({'XRP/BTC': None}, '', bt_result,
+ Arrow(2017, 11, 14, 19, 32, 00),
+ Arrow(2017, 12, 14, 19, 32, 00), market_change=0)
+
+ results_explanation = HyperoptTools.format_results_explanation_string(results_metrics, 'BTC')
+ total_profit = results_metrics['profit_total_abs']
results = {
'loss': 0.0,
@@ -456,21 +464,9 @@ def test_format_results(hyperopt):
}
result = HyperoptTools._format_explanation_string(results, 1)
- assert result.find(' 66.67%')
- assert result.find('Total profit 1.00000000 BTC')
- assert result.find('2.0000Σ %')
-
- # Test with EUR as stake_currency
- trades = [
- ('ETH/EUR', 2, 2, 123),
- ('LTC/EUR', 1, 1, 123),
- ('XPR/EUR', -1, -2, -246)
- ]
- df = pd.DataFrame.from_records(trades, columns=labels)
- results_metrics = hyperopt._calculate_results_metrics(df)
- results['total_profit'] = results_metrics['total_profit']
- result = HyperoptTools._format_explanation_string(results, 1)
- assert result.find('Total profit 1.00000000 EUR')
+ assert ' 0.71%' in result
+ assert 'Total profit 0.00003100 BTC' in result
+ assert '0:50:00 min' in result
@pytest.mark.parametrize("spaces, expected_results", [
@@ -501,10 +497,10 @@ def test_format_results(hyperopt):
(['default', 'buy'],
{'buy': True, 'sell': True, 'roi': True, 'stoploss': True, 'trailing': False}),
])
-def test_has_space(hyperopt, spaces, expected_results):
+def test_has_space(hyperopt_conf, spaces, expected_results):
for s in ['buy', 'sell', 'roi', 'stoploss', 'trailing']:
- hyperopt.config.update({'spaces': spaces})
- assert hyperopt.has_space(s) == expected_results[s]
+ hyperopt_conf.update({'spaces': spaces})
+ assert HyperoptTools.has_space(hyperopt_conf, s) == expected_results[s]
def test_populate_indicators(hyperopt, testdatadir) -> None:
@@ -575,22 +571,39 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
'hyperopt_min_trades': 1,
})
- trades = [
- ('TRX/BTC', 0.023117, 0.000233, 100)
- ]
- labels = ['currency', 'profit_ratio', 'profit_abs', 'trade_duration']
- backtest_result = pd.DataFrame.from_records(trades, columns=labels)
+ backtest_result = {
+ 'results': pd.DataFrame({"pair": ["UNITTEST/BTC", "UNITTEST/BTC",
+ "UNITTEST/BTC", "UNITTEST/BTC"],
+ "profit_ratio": [0.003312, 0.010801, 0.013803, 0.002780],
+ "profit_abs": [0.000003, 0.000011, 0.000014, 0.000003],
+ "open_date": [Arrow(2017, 11, 14, 19, 32, 00).datetime,
+ Arrow(2017, 11, 14, 21, 36, 00).datetime,
+ Arrow(2017, 11, 14, 22, 12, 00).datetime,
+ Arrow(2017, 11, 14, 22, 44, 00).datetime],
+ "close_date": [Arrow(2017, 11, 14, 21, 35, 00).datetime,
+ Arrow(2017, 11, 14, 22, 10, 00).datetime,
+ Arrow(2017, 11, 14, 22, 43, 00).datetime,
+ Arrow(2017, 11, 14, 22, 58, 00).datetime],
+ "open_rate": [0.002543, 0.003003, 0.003089, 0.003214],
+ "close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
+ "trade_duration": [123, 34, 31, 14],
+ "is_open": [False, False, False, True],
+ "stake_amount": [0.01, 0.01, 0.01, 0.01],
+ "sell_reason": [SellType.ROI, SellType.STOP_LOSS,
+ SellType.ROI, SellType.FORCE_SELL]
+ }),
+ 'config': hyperopt_conf,
+ 'locks': [],
+ 'rejected_signals': 20,
+ 'final_balance': 1000,
+ }
- mocker.patch(
- 'freqtrade.optimize.hyperopt.Backtesting.backtest',
- MagicMock(return_value=backtest_result)
- )
- mocker.patch(
- 'freqtrade.optimize.hyperopt.get_timerange',
- MagicMock(return_value=(Arrow(2017, 12, 10), Arrow(2017, 12, 13)))
- )
+ mocker.patch('freqtrade.optimize.hyperopt.Backtesting.backtest', return_value=backtest_result)
+ mocker.patch('freqtrade.optimize.hyperopt.get_timerange',
+ return_value=(Arrow(2017, 12, 10), Arrow(2017, 12, 13)))
patch_exchange(mocker)
- mocker.patch('freqtrade.optimize.hyperopt.load', MagicMock())
+ mocker.patch.object(Path, 'open')
+ mocker.patch('freqtrade.optimize.hyperopt.load', return_value={'XRP/BTC': None})
optimizer_param = {
'adx-value': 0,
@@ -624,11 +637,11 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
'trailing_only_offset_is_reached': False,
}
response_expected = {
- 'loss': 1.9840569076926293,
- 'results_explanation': (' 1 trades. 1/0/0 Wins/Draws/Losses. '
- 'Avg profit 2.31%. Median profit 2.31%. Total profit '
- '0.00023300 BTC ( 2.31\N{GREEK CAPITAL LETTER SIGMA}%). '
- 'Avg duration 100.0 min.'
+ 'loss': 1.9147239021396234,
+ 'results_explanation': (' 4 trades. 4/0/0 Wins/Draws/Losses. '
+ 'Avg profit 0.77%. Median profit 0.71%. Total profit '
+ '0.00003100 BTC ( 0.00\N{GREEK CAPITAL LETTER SIGMA}%). '
+ 'Avg duration 0:50:00 min.'
).encode(locale.getpreferredencoding(), 'replace').decode('utf-8'),
'params_details': {'buy': {'adx-enabled': False,
'adx-value': 0,
@@ -639,10 +652,10 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
'rsi-enabled': False,
'rsi-value': 0,
'trigger': 'macd_cross_signal'},
- 'roi': {0: 0.12000000000000001,
- 20.0: 0.02,
- 50.0: 0.01,
- 110.0: 0},
+ 'roi': {"0": 0.12000000000000001,
+ "20.0": 0.02,
+ "50.0": 0.01,
+ "110.0": 0},
'sell': {'sell-adx-enabled': False,
'sell-adx-value': 0,
'sell-fastd-enabled': True,
@@ -658,21 +671,16 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
'trailing_stop_positive': 0.02,
'trailing_stop_positive_offset': 0.07}},
'params_dict': optimizer_param,
- 'results_metrics': {'avg_profit': 2.3117,
- 'draws': 0,
- 'duration': 100.0,
- 'losses': 0,
- 'winsdrawslosses': ' 1 0 0',
- 'median_profit': 2.3117,
- 'profit': 2.3117,
- 'total_profit': 0.000233,
- 'trade_count': 1,
- 'wins': 1},
- 'total_profit': 0.00023300
+ 'params_not_optimized': {'buy': {}, 'sell': {}},
+ 'results_metrics': ANY,
+ 'total_profit': 3.1e-08
}
hyperopt = Hyperopt(hyperopt_conf)
- hyperopt.dimensions = hyperopt.hyperopt_space()
+ hyperopt.min_date = Arrow(2017, 12, 10)
+ hyperopt.max_date = Arrow(2017, 12, 13)
+ hyperopt.init_spaces()
+ hyperopt.dimensions = hyperopt.dimensions
generate_optimizer_value = hyperopt.generate_optimizer(list(optimizer_param.values()))
assert generate_optimizer_value == response_expected
@@ -689,7 +697,8 @@ def test_clean_hyperopt(mocker, hyperopt_conf, caplog):
def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
- dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
+ dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
+ dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
@@ -740,13 +749,14 @@ def test_print_json_spaces_all(mocker, hyperopt_conf, capsys) -> None:
':{},"stoploss":null,"trailing_stop":null}'
)
assert result_str in out # noqa: E501
- assert dumper.called
- # Should be called twice, once for historical candle data, once to save evaluations
- assert dumper.call_count == 2
+ # Should be called for historical candle data
+ assert dumper.call_count == 1
+ assert dumper2.call_count == 1
def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
- dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
+ dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
+ dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
@@ -788,13 +798,14 @@ def test_print_json_spaces_default(mocker, hyperopt_conf, capsys) -> None:
out, err = capsys.readouterr()
assert '{"params":{"mfi-value":null,"sell-mfi-value":null},"minimal_roi":{},"stoploss":null}' in out # noqa: E501
- assert dumper.called
- # Should be called twice, once for historical candle data, once to save evaluations
- assert dumper.call_count == 2
+ # Should be called for historical candle data
+ assert dumper.call_count == 1
+ assert dumper2.call_count == 1
def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
- dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
+ dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
+ dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
@@ -835,13 +846,14 @@ def test_print_json_spaces_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
out, err = capsys.readouterr()
assert '{"minimal_roi":{},"stoploss":null}' in out
- assert dumper.called
- # Should be called twice, once for historical candle data, once to save evaluations
- assert dumper.call_count == 2
+
+ assert dumper.call_count == 1
+ assert dumper2.call_count == 1
def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> None:
- dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
+ dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
+ dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
@@ -883,9 +895,9 @@ def test_simplified_interface_roi_stoploss(mocker, hyperopt_conf, capsys) -> Non
out, err = capsys.readouterr()
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
- assert dumper.called
- # Should be called twice, once for historical candle data, once to save evaluations
- assert dumper.call_count == 2
+ assert dumper.call_count == 1
+ assert dumper2.call_count == 1
+
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt, "max_open_trades")
@@ -921,7 +933,8 @@ def test_simplified_interface_all_failed(mocker, hyperopt_conf) -> None:
def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
- dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
+ dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
+ dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
@@ -964,8 +977,8 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
out, err = capsys.readouterr()
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
assert dumper.called
- # Should be called twice, once for historical candle data, once to save evaluations
- assert dumper.call_count == 2
+ assert dumper.call_count == 1
+ assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt, "max_open_trades")
@@ -974,7 +987,8 @@ def test_simplified_interface_buy(mocker, hyperopt_conf, capsys) -> None:
def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
- dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
+ dumper = mocker.patch('freqtrade.optimize.hyperopt.dump')
+ dumper2 = mocker.patch('freqtrade.optimize.hyperopt.Hyperopt._save_result')
mocker.patch('freqtrade.optimize.hyperopt.file_dump_json')
mocker.patch('freqtrade.optimize.backtesting.Backtesting.load_bt_data',
MagicMock(return_value=(MagicMock(), None)))
@@ -1017,8 +1031,8 @@ def test_simplified_interface_sell(mocker, hyperopt_conf, capsys) -> None:
out, err = capsys.readouterr()
assert 'Best result:\n\n* 1/1: foo result Objective: 1.00000\n' in out
assert dumper.called
- # Should be called twice, once for historical candle data, once to save evaluations
- assert dumper.call_count == 2
+ assert dumper.call_count == 1
+ assert dumper2.call_count == 1
assert hasattr(hyperopt.backtesting.strategy, "advise_sell")
assert hasattr(hyperopt.backtesting.strategy, "advise_buy")
assert hasattr(hyperopt, "max_open_trades")
@@ -1092,7 +1106,9 @@ def test_print_epoch_details(capsys):
assert re.search(r'^\s+\"90\"\:\s0.14,\s*$', captured.out, re.MULTILINE)
-def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir) -> None:
+def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None:
+ patch_exchange(mocker)
+ mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
(Path(tmpdir) / 'hyperopt_results').mkdir(parents=True)
# No hyperopt needed
del hyperopt_conf['hyperopt']
@@ -1102,5 +1118,44 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir) -> None:
})
hyperopt = Hyperopt(hyperopt_conf)
assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto)
+ assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter)
+
+ assert hyperopt.backtesting.strategy.buy_rsi.in_space is True
+ assert hyperopt.backtesting.strategy.buy_rsi.value == 35
+ buy_rsi_range = hyperopt.backtesting.strategy.buy_rsi.range
+ assert isinstance(buy_rsi_range, range)
+ # Range from 0 - 50 (inclusive)
+ assert len(list(buy_rsi_range)) == 51
hyperopt.start()
+
+
+def test_SKDecimal():
+ space = SKDecimal(1, 2, decimals=2)
+ assert 1.5 in space
+ assert 2.5 not in space
+ assert space.low == 100
+ assert space.high == 200
+
+ assert space.inverse_transform([200]) == [2.0]
+ assert space.inverse_transform([100]) == [1.0]
+ assert space.inverse_transform([150, 160]) == [1.5, 1.6]
+
+ assert space.transform([1.5]) == [150]
+ assert space.transform([2.0]) == [200]
+ assert space.transform([1.0]) == [100]
+ assert space.transform([1.5, 1.6]) == [150, 160]
+
+
+def test___pprint():
+ params = {'buy_std': 1.2, 'buy_rsi': 31, 'buy_enable': True, 'buy_what': 'asdf'}
+ non_params = {'buy_notoptimied': 55}
+
+ x = HyperoptTools._pprint(params, non_params)
+ assert x == """{
+ "buy_std": 1.2,
+ "buy_rsi": 31,
+ "buy_enable": True,
+ "buy_what": "asdf",
+ "buy_notoptimied": 55, # value loaded from strategy
+}"""
diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py
index 8119c732b..f9dac3397 100644
--- a/tests/optimize/test_optimize_reports.py
+++ b/tests/optimize/test_optimize_reports.py
@@ -1,5 +1,6 @@
+import datetime
import re
-from datetime import datetime, timedelta, timezone
+from datetime import timedelta
from pathlib import Path
import pandas as pd
@@ -7,14 +8,15 @@ import pytest
from arrow import Arrow
from freqtrade.configuration import TimeRange
-from freqtrade.constants import LAST_BT_RESULT_FN
+from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN
from freqtrade.data import history
from freqtrade.data.btanalysis import get_latest_backtest_filename, load_backtest_data
from freqtrade.edge import PairInfo
from freqtrade.optimize.optimize_reports import (generate_backtest_stats, generate_daily_stats,
generate_edge_table, generate_pair_metrics,
generate_sell_reason_stats,
- generate_strategy_metrics, store_backtest_stats,
+ generate_strategy_comparison,
+ generate_trading_stats, store_backtest_stats,
text_table_bt_results, text_table_sell_reason,
text_table_strategy)
from freqtrade.resolvers.strategy_resolver import StrategyResolver
@@ -26,25 +28,22 @@ def test_text_table_bt_results():
results = pd.DataFrame(
{
- 'pair': ['ETH/BTC', 'ETH/BTC'],
- 'profit_ratio': [0.1, 0.2],
- 'profit_abs': [0.2, 0.4],
- 'trade_duration': [10, 30],
- 'wins': [2, 0],
- 'draws': [0, 0],
- 'losses': [0, 0]
+ 'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
+ 'profit_ratio': [0.1, 0.2, -0.05],
+ 'profit_abs': [0.2, 0.4, -0.1],
+ 'trade_duration': [10, 30, 20],
}
)
result_str = (
- '| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC |'
- ' Tot Profit % | Avg Duration | Wins | Draws | Losses |\n'
- '|---------+--------+----------------+----------------+------------------+'
- '----------------+----------------+--------+---------+----------|\n'
- '| ETH/BTC | 2 | 15.00 | 30.00 | 0.60000000 |'
- ' 15.00 | 0:20:00 | 2 | 0 | 0 |\n'
- '| TOTAL | 2 | 15.00 | 30.00 | 0.60000000 |'
- ' 15.00 | 0:20:00 | 2 | 0 | 0 |'
+ '| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % |'
+ ' Avg Duration | Win Draw Loss Win% |\n'
+ '|---------+--------+----------------+----------------+------------------+----------------+'
+ '----------------+-------------------------|\n'
+ '| ETH/BTC | 3 | 8.33 | 25.00 | 0.50000000 | 12.50 |'
+ ' 0:20:00 | 2 0 1 66.7 |\n'
+ '| TOTAL | 3 | 8.33 | 25.00 | 0.50000000 | 12.50 |'
+ ' 0:20:00 | 2 0 1 66.7 |'
)
pair_results = generate_pair_metrics(data={'ETH/BTC': {}}, stake_currency='BTC',
@@ -80,6 +79,7 @@ def test_generate_backtest_stats(default_conf, testdatadir):
'config': default_conf,
'locks': [],
'final_balance': 1000.02,
+ 'rejected_signals': 20,
'backtest_start_time': Arrow.utcnow().int_timestamp,
'backtest_end_time': Arrow.utcnow().int_timestamp,
}
@@ -96,8 +96,8 @@ def test_generate_backtest_stats(default_conf, testdatadir):
assert 'DefStrat' in stats['strategy']
assert 'strategy_comparison' in stats
strat_stats = stats['strategy']['DefStrat']
- assert strat_stats['backtest_start'] == min_date.datetime
- assert strat_stats['backtest_end'] == max_date.datetime
+ assert strat_stats['backtest_start'] == min_date.strftime(DATETIME_PRINT_FORMAT)
+ assert strat_stats['backtest_end'] == max_date.strftime(DATETIME_PRINT_FORMAT)
assert strat_stats['total_trades'] == len(results['DefStrat']['results'])
# Above sample had no loosing trade
assert strat_stats['max_drawdown'] == 0.0
@@ -127,6 +127,7 @@ def test_generate_backtest_stats(default_conf, testdatadir):
'config': default_conf,
'locks': [],
'final_balance': 1000.02,
+ 'rejected_signals': 20,
'backtest_start_time': Arrow.utcnow().int_timestamp,
'backtest_end_time': Arrow.utcnow().int_timestamp,
}
@@ -140,8 +141,8 @@ def test_generate_backtest_stats(default_conf, testdatadir):
strat_stats = stats['strategy']['DefStrat']
assert strat_stats['max_drawdown'] == 0.013803
- assert strat_stats['drawdown_start'] == datetime(2017, 11, 14, 22, 10, tzinfo=timezone.utc)
- assert strat_stats['drawdown_end'] == datetime(2017, 11, 14, 22, 43, tzinfo=timezone.utc)
+ assert strat_stats['drawdown_start'] == '2017-11-14 22:10:00'
+ assert strat_stats['drawdown_end'] == '2017-11-14 22:43:00'
assert strat_stats['drawdown_end_ts'] == 1510699380000
assert strat_stats['drawdown_start_ts'] == 1510697400000
assert strat_stats['pairlist'] == ['UNITTEST/BTC']
@@ -226,8 +227,6 @@ def test_generate_daily_stats(testdatadir):
assert res['winning_days'] == 14
assert res['draw_days'] == 4
assert res['losing_days'] == 3
- assert res['winner_holding_avg'] == timedelta(seconds=1440)
- assert res['loser_holding_avg'] == timedelta(days=1, seconds=21420)
# Select empty dataframe!
res = generate_daily_stats(bt_data.loc[bt_data['open_date'] == '2000-01-01', :])
@@ -238,6 +237,23 @@ def test_generate_daily_stats(testdatadir):
assert res['losing_days'] == 0
+def test_generate_trading_stats(testdatadir):
+ filename = testdatadir / "backtest-result_new.json"
+ bt_data = load_backtest_data(filename)
+ res = generate_trading_stats(bt_data)
+ assert isinstance(res, dict)
+ assert res['winner_holding_avg'] == timedelta(seconds=1440)
+ assert res['loser_holding_avg'] == timedelta(days=1, seconds=21420)
+ assert 'wins' in res
+ assert 'losses' in res
+ assert 'draws' in res
+
+ # Select empty dataframe!
+ res = generate_trading_stats(bt_data.loc[bt_data['open_date'] == '2000-01-01', :])
+ assert res['wins'] == 0
+ assert res['losses'] == 0
+
+
def test_text_table_sell_reason():
results = pd.DataFrame(
@@ -254,14 +270,14 @@ def test_text_table_sell_reason():
)
result_str = (
- '| Sell Reason | Sells | Wins | Draws | Losses |'
- ' Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % |\n'
- '|---------------+---------+--------+---------+----------+'
- '----------------+----------------+------------------+----------------|\n'
- '| roi | 2 | 2 | 0 | 0 |'
- ' 15 | 30 | 0.6 | 15 |\n'
- '| stop_loss | 1 | 0 | 0 | 1 |'
- ' -10 | -10 | -0.2 | -5 |'
+ '| Sell Reason | Sells | Win Draws Loss Win% | Avg Profit % | Cum Profit % |'
+ ' Tot Profit BTC | Tot Profit % |\n'
+ '|---------------+---------+--------------------------+----------------+----------------+'
+ '------------------+----------------|\n'
+ '| roi | 2 | 2 0 0 100 | 15 | 30 |'
+ ' 0.6 | 15 |\n'
+ '| stop_loss | 1 | 0 0 1 0 | -10 | -10 |'
+ ' -0.2 | -5 |'
)
sell_reason_stats = generate_sell_reason_stats(max_open_trades=2,
@@ -309,9 +325,12 @@ def test_text_table_strategy(default_conf):
default_conf['max_open_trades'] = 2
default_conf['dry_run_wallet'] = 3
results = {}
+ date = datetime.datetime(year=2020, month=1, day=1, hour=12, minute=30)
+ delta = datetime.timedelta(days=1)
results['TestStrategy1'] = {'results': pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
+ 'close_date': [date, date + delta, date + delta * 2],
'profit_ratio': [0.1, 0.2, 0.3],
'profit_abs': [0.2, 0.4, 0.5],
'trade_duration': [10, 30, 10],
@@ -324,6 +343,7 @@ def test_text_table_strategy(default_conf):
results['TestStrategy2'] = {'results': pd.DataFrame(
{
'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'],
+ 'close_date': [date, date + delta, date + delta * 2],
'profit_ratio': [0.4, 0.2, 0.3],
'profit_abs': [0.4, 0.4, 0.5],
'trade_duration': [15, 30, 15],
@@ -335,18 +355,17 @@ def test_text_table_strategy(default_conf):
), 'config': default_conf}
result_str = (
- '| Strategy | Buys | Avg Profit % | Cum Profit % | Tot'
- ' Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses |\n'
+ '| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC |'
+ ' Tot Profit % | Avg Duration | Win Draw Loss Win% | Drawdown |\n'
'|---------------+--------+----------------+----------------+------------------+'
- '----------------+----------------+--------+---------+----------|\n'
+ '----------------+----------------+-------------------------+-----------------------|\n'
'| TestStrategy1 | 3 | 20.00 | 60.00 | 1.10000000 |'
- ' 36.67 | 0:17:00 | 3 | 0 | 0 |\n'
+ ' 36.67 | 0:17:00 | 3 0 0 100 | 0.00000000 BTC 0.00% |\n'
'| TestStrategy2 | 3 | 30.00 | 90.00 | 1.30000000 |'
- ' 43.33 | 0:20:00 | 3 | 0 | 0 |'
+ ' 43.33 | 0:20:00 | 3 0 0 100 | 0.00000000 BTC 0.00% |'
)
- strategy_results = generate_strategy_metrics(all_results=results)
-
+ strategy_results = generate_strategy_comparison(all_results=results)
assert text_table_strategy(strategy_results, 'BTC') == result_str
diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py
index bf225271f..5e2274ce3 100644
--- a/tests/plugins/test_pairlist.py
+++ b/tests/plugins/test_pairlist.py
@@ -1,15 +1,17 @@
# pragma pylint: disable=missing-docstring,C0103,protected-access
+import time
from unittest.mock import MagicMock, PropertyMock
import pytest
from freqtrade.constants import AVAILABLE_PAIRLISTS
from freqtrade.exceptions import OperationalException
+from freqtrade.persistence import Trade
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.plugins.pairlistmanager import PairListManager
from freqtrade.resolvers import PairListResolver
-from tests.conftest import get_patched_freqtradebot, log_has, log_has_re
+from tests.conftest import get_patched_exchange, get_patched_freqtradebot, log_has, log_has_re
@pytest.fixture(scope="function")
@@ -260,6 +262,8 @@ def test_refresh_pairlist_dynamic_2(mocker, shitcoinmarkets, tickers, whitelist_
freqtrade.pairlists.refresh_pairlist()
assert whitelist == freqtrade.pairlists.whitelist
+ # Delay to allow 0 TTL cache to expire...
+ time.sleep(1)
whitelist = ['FUEL/BTC', 'ETH/BTC', 'TKN/BTC', 'LTC/BTC', 'XRP/BTC']
tickers_dict['FUEL/BTC']['quoteVolume'] = 10000.0
freqtrade.pairlists.refresh_pairlist()
@@ -403,6 +407,9 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
([{"method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume"},
{"method": "PriceFilter", "low_price_ratio": 0.02}],
"USDT", ['ETH/USDT', 'NANO/USDT']),
+ ([{"method": "VolumePairList", "number_assets": 20, "sort_key": "quoteVolume"},
+ {"method": "PriceFilter", "max_value": 0.000001}],
+ "USDT", ['NANO/USDT']),
([{"method": "StaticPairList"},
{"method": "RangeStabilityFilter", "lookback_days": 10,
"min_rate_of_change": 0.01, "refresh_period": 1440}],
@@ -485,6 +492,8 @@ def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, t
r'because last price < .*%$', caplog) or
log_has_re(r'^Removed .* from whitelist, '
r'because last price > .*%$', caplog) or
+ log_has_re(r'^Removed .* from whitelist, '
+ r'because min value change of .*', caplog) or
log_has_re(r"^Removed .* from whitelist, because ticker\['last'\] "
r"is empty.*", caplog))
if pairlist['method'] == 'VolumePairList':
@@ -509,6 +518,18 @@ def test_PrecisionFilter_error(mocker, whitelist_conf) -> None:
PairListManager(MagicMock, whitelist_conf)
+def test_PerformanceFilter_error(mocker, whitelist_conf, caplog) -> None:
+ whitelist_conf['pairlists'] = [{"method": "StaticPairList"}, {"method": "PerformanceFilter"}]
+ if hasattr(Trade, 'query'):
+ del Trade.query
+ mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
+ exchange = get_patched_exchange(mocker, whitelist_conf)
+ pm = PairListManager(exchange, whitelist_conf)
+ pm.refresh_pairlist()
+
+ assert log_has("PerformanceFilter is not available in this mode.", caplog)
+
+
def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None:
default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}]
@@ -604,17 +625,14 @@ def test_volumepairlist_caching(mocker, markets, whitelist_conf, tickers):
get_tickers=tickers
)
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
- assert freqtrade.pairlists._pairlist_handlers[0]._last_refresh == 0
+ assert len(freqtrade.pairlists._pairlist_handlers[0]._pair_cache) == 0
assert tickers.call_count == 0
freqtrade.pairlists.refresh_pairlist()
assert tickers.call_count == 1
- assert freqtrade.pairlists._pairlist_handlers[0]._last_refresh != 0
- lrf = freqtrade.pairlists._pairlist_handlers[0]._last_refresh
+ assert len(freqtrade.pairlists._pairlist_handlers[0]._pair_cache) == 1
freqtrade.pairlists.refresh_pairlist()
assert tickers.call_count == 1
- # Time should not be updated.
- assert freqtrade.pairlists._pairlist_handlers[0]._last_refresh == lrf
def test_agefilter_min_days_listed_too_small(mocker, default_conf, markets, tickers):
@@ -787,6 +805,10 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo
"[{'PriceFilter': 'PriceFilter - Filtering pairs priced below 0.00002000.'}]",
None
),
+ ({"method": "PriceFilter", "max_value": 0.00002000},
+ "[{'PriceFilter': 'PriceFilter - Filtering pairs priced Value above 0.00002000.'}]",
+ None
+ ),
({"method": "PriceFilter"},
"[{'PriceFilter': 'PriceFilter - No price filters configured.'}]",
None
@@ -803,6 +825,10 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo
None,
"PriceFilter requires max_price to be >= 0"
), # OperationalException expected
+ ({"method": "PriceFilter", "max_value": -1.00010000},
+ None,
+ "PriceFilter requires max_value to be >= 0"
+ ), # OperationalException expected
({"method": "RangeStabilityFilter", "lookback_days": 10, "min_rate_of_change": 0.01},
"[{'RangeStabilityFilter': 'RangeStabilityFilter - Filtering pairs with rate of change below "
"0.01 over the last days.'}]",
diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py
index 545387eaa..a39301145 100644
--- a/tests/plugins/test_protections.py
+++ b/tests/plugins/test_protections.py
@@ -27,7 +27,7 @@ def generate_mock_trade(pair: str, fee: float, is_open: bool,
open_rate=open_rate,
is_open=is_open,
amount=0.01 / open_rate,
- exchange='bittrex',
+ exchange='binance',
)
trade.recalc_open_trade_value()
if not is_open:
diff --git a/tests/rpc/test_fiat_convert.py b/tests/rpc/test_fiat_convert.py
index 2d43addff..5174f9416 100644
--- a/tests/rpc/test_fiat_convert.py
+++ b/tests/rpc/test_fiat_convert.py
@@ -1,6 +1,7 @@
# pragma pylint: disable=missing-docstring, too-many-arguments, too-many-ancestors,
# pragma pylint: disable=protected-access, C0103
+import datetime
from unittest.mock import MagicMock
import pytest
@@ -21,6 +22,12 @@ def test_fiat_convert_is_supported(mocker):
def test_fiat_convert_find_price(mocker):
fiat_convert = CryptoToFiatConverter()
+ fiat_convert._cryptomap = {}
+ fiat_convert._backoff = 0
+ mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._load_cryptomap',
+ return_value=None)
+ assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='EUR') == 0.0
+
with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'):
fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='ABC')
@@ -115,6 +122,28 @@ def test_fiat_convert_without_network(mocker):
CryptoToFiatConverter._coingekko = cmc_temp
+def test_fiat_too_many_requests_response(mocker, caplog):
+ # Because CryptoToFiatConverter is a Singleton we reset the listings
+ req_exception = "429 Too Many Requests"
+ listmock = MagicMock(return_value="{}", side_effect=RequestException(req_exception))
+ mocker.patch.multiple(
+ 'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
+ get_coins_list=listmock,
+ )
+ # with pytest.raises(RequestEsxception):
+ fiat_convert = CryptoToFiatConverter()
+ fiat_convert._cryptomap = {}
+ fiat_convert._load_cryptomap()
+
+ length_cryptomap = len(fiat_convert._cryptomap)
+ assert length_cryptomap == 0
+ assert fiat_convert._backoff > datetime.datetime.now().timestamp()
+ assert log_has(
+ 'Too many requests for Coingecko API, backing off and trying again later.',
+ caplog
+ )
+
+
def test_fiat_invalid_response(mocker, caplog):
# Because CryptoToFiatConverter is a Singleton we reset the listings
listmock = MagicMock(return_value="{'novalidjson':DEADBEEFf}")
diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py
index 64918ed47..b005fb105 100644
--- a/tests/rpc/test_rpc.py
+++ b/tests/rpc/test_rpc.py
@@ -53,7 +53,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'pair': 'ETH/BTC',
'base_currency': 'BTC',
'open_date': ANY,
- 'open_date_hum': ANY,
'open_timestamp': ANY,
'is_open': ANY,
'fee_open': ANY,
@@ -73,7 +72,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'timeframe': 5,
'open_order_id': ANY,
'close_date': None,
- 'close_date_hum': None,
'close_timestamp': None,
'open_rate': 1.098e-05,
'close_rate': None,
@@ -108,7 +106,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'stoploss_entry_dist': -0.00010475,
'stoploss_entry_dist_ratio': -0.10448878,
'open_order': None,
- 'exchange': 'bittrex',
+ 'exchange': 'binance',
}
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
@@ -121,7 +119,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'pair': 'ETH/BTC',
'base_currency': 'BTC',
'open_date': ANY,
- 'open_date_hum': ANY,
'open_timestamp': ANY,
'is_open': ANY,
'fee_open': ANY,
@@ -141,7 +138,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'timeframe': ANY,
'open_order_id': ANY,
'close_date': None,
- 'close_date_hum': None,
'close_timestamp': None,
'open_rate': 1.098e-05,
'close_rate': None,
@@ -176,7 +172,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'stoploss_entry_dist': -0.00010475,
'stoploss_entry_dist_ratio': -0.10448878,
'open_order': None,
- 'exchange': 'bittrex',
+ 'exchange': 'binance',
}
@@ -203,28 +199,31 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
freqtradebot.enter_positions()
- result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
+ result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
assert "Since" in headers
assert "Pair" in headers
assert 'instantly' == result[0][2]
assert 'ETH/BTC' in result[0][1]
assert '-0.41%' == result[0][3]
+ assert isnan(fiat_profit_sum)
# Test with fiatconvert
rpc._fiat_converter = CryptoToFiatConverter()
- result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
+ result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
assert "Since" in headers
assert "Pair" in headers
assert 'instantly' == result[0][2]
assert 'ETH/BTC' in result[0][1]
assert '-0.41% (-0.06)' == result[0][3]
+ assert '-0.06' == f'{fiat_profit_sum:.2f}'
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
- result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
+ result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
assert 'instantly' == result[0][2]
assert 'ETH/BTC' in result[0][1]
assert 'nan%' == result[0][3]
+ assert isnan(fiat_profit_sum)
def test_rpc_daily_profit(default_conf, update, ticker, fee,
@@ -573,6 +572,8 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
result = rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency'])
assert prec_satoshi(result['total'], 12.309096315)
assert prec_satoshi(result['value'], 184636.44472997)
+ assert tickers.call_count == 1
+ assert tickers.call_args_list[0][1]['cached'] is True
assert 'USD' == result['symbol']
assert result['currencies'] == [
{'currency': 'BTC',
diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py
index db9da8a82..a23750460 100644
--- a/tests/rpc/test_rpc_apiserver.py
+++ b/tests/rpc/test_rpc_apiserver.py
@@ -468,7 +468,7 @@ def test_api_show_config(botclient, mocker):
rc = client_get(client, f"{BASE_URI}/show_config")
assert_response(rc)
assert 'dry_run' in rc.json()
- assert rc.json()['exchange'] == 'bittrex'
+ assert rc.json()['exchange'] == 'binance'
assert rc.json()['timeframe'] == '5m'
assert rc.json()['timeframe_ms'] == 300000
assert rc.json()['timeframe_min'] == 5
@@ -506,8 +506,9 @@ def test_api_trades(botclient, mocker, fee, markets):
)
rc = client_get(client, f"{BASE_URI}/trades")
assert_response(rc)
- assert len(rc.json()) == 2
+ assert len(rc.json()) == 3
assert rc.json()['trades_count'] == 0
+ assert rc.json()['total_trades'] == 0
create_mock_trades(fee)
Trade.query.session.flush()
@@ -516,10 +517,32 @@ def test_api_trades(botclient, mocker, fee, markets):
assert_response(rc)
assert len(rc.json()['trades']) == 2
assert rc.json()['trades_count'] == 2
+ assert rc.json()['total_trades'] == 2
rc = client_get(client, f"{BASE_URI}/trades?limit=1")
assert_response(rc)
assert len(rc.json()['trades']) == 1
assert rc.json()['trades_count'] == 1
+ assert rc.json()['total_trades'] == 2
+
+
+def test_api_trade_single(botclient, mocker, fee, ticker, markets):
+ ftbot, client = botclient
+ patch_get_signal(ftbot, (True, False))
+ mocker.patch.multiple(
+ 'freqtrade.exchange.Exchange',
+ markets=PropertyMock(return_value=markets),
+ fetch_ticker=ticker,
+ )
+ rc = client_get(client, f"{BASE_URI}/trade/3")
+ assert_response(rc, 404)
+ assert rc.json()['detail'] == 'Trade not found.'
+
+ create_mock_trades(fee)
+ Trade.query.session.flush()
+
+ rc = client_get(client, f"{BASE_URI}/trade/3")
+ assert_response(rc)
+ assert rc.json()['trade_id'] == 3
def test_api_delete_trade(botclient, mocker, fee, markets):
@@ -687,7 +710,7 @@ def test_api_stats(botclient, mocker, ticker, fee, markets,):
assert 'draws' in rc.json()['durations']
-def test_api_performance(botclient, mocker, ticker, fee):
+def test_api_performance(botclient, fee):
ftbot, client = botclient
patch_get_signal(ftbot, (True, False))
@@ -705,6 +728,7 @@ def test_api_performance(botclient, mocker, ticker, fee):
)
trade.close_profit = trade.calc_profit_ratio()
+ trade.close_profit_abs = trade.calc_profit()
Trade.query.session.add(trade)
trade = Trade(
@@ -720,14 +744,16 @@ def test_api_performance(botclient, mocker, ticker, fee):
close_rate=0.391
)
trade.close_profit = trade.calc_profit_ratio()
+ trade.close_profit_abs = trade.calc_profit()
+
Trade.query.session.add(trade)
Trade.query.session.flush()
rc = client_get(client, f"{BASE_URI}/performance")
assert_response(rc)
assert len(rc.json()) == 2
- assert rc.json() == [{'count': 1, 'pair': 'LTC/ETH', 'profit': 7.61},
- {'count': 1, 'pair': 'XRP/ETH', 'profit': -5.57}]
+ assert rc.json() == [{'count': 1, 'pair': 'LTC/ETH', 'profit': 7.61, 'profit_abs': 0.01872279},
+ {'count': 1, 'pair': 'XRP/ETH', 'profit': -5.57, 'profit_abs': -0.1150375}]
def test_api_status(botclient, mocker, ticker, fee, markets):
@@ -753,9 +779,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
assert rc.json()[0] == {
'amount': 123.0,
'amount_requested': 123.0,
- 'base_currency': 'BTC',
'close_date': None,
- 'close_date_hum': None,
'close_timestamp': None,
'close_profit': None,
'close_profit_pct': None,
@@ -770,7 +794,6 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
'profit_fiat': ANY,
'current_rate': 1.099e-05,
'open_date': ANY,
- 'open_date_hum': ANY,
'open_timestamp': ANY,
'open_order': None,
'open_rate': 0.123,
@@ -808,7 +831,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
'sell_order_status': None,
'strategy': 'DefaultStrategy',
'timeframe': 5,
- 'exchange': 'bittrex',
+ 'exchange': 'binance',
}
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
@@ -899,7 +922,7 @@ def test_api_forcebuy(botclient, mocker, fee):
pair='ETH/ETH',
amount=1,
amount_requested=1,
- exchange='bittrex',
+ exchange='binance',
stake_amount=1,
open_rate=0.245441,
open_order_id="123456",
@@ -922,11 +945,9 @@ def test_api_forcebuy(botclient, mocker, fee):
'amount_requested': 1,
'trade_id': 22,
'close_date': None,
- 'close_date_hum': None,
'close_timestamp': None,
'close_rate': 0.265441,
'open_date': ANY,
- 'open_date_hum': 'just now',
'open_timestamp': ANY,
'open_rate': 0.245441,
'pair': 'ETH/ETH',
@@ -964,9 +985,8 @@ def test_api_forcebuy(botclient, mocker, fee):
'sell_order_status': None,
'strategy': 'DefaultStrategy',
'timeframe': 5,
- 'exchange': 'bittrex',
- }
-
+ 'exchange': 'binance',
+ }
def test_api_forcesell(botclient, mocker, ticker, fee, markets):
ftbot, client = botclient
@@ -1124,6 +1144,14 @@ def test_api_plot_config(botclient):
assert_response(rc)
assert rc.json() == ftbot.strategy.plot_config
assert isinstance(rc.json()['main_plot'], dict)
+ assert isinstance(rc.json()['subplots'], dict)
+
+ ftbot.strategy.plot_config = {'main_plot': {'sma': {}}}
+ rc = client_get(client, f"{BASE_URI}/plot_config")
+ assert_response(rc)
+
+ assert isinstance(rc.json()['main_plot'], dict)
+ assert isinstance(rc.json()['subplots'], dict)
def test_api_strategies(botclient):
diff --git a/tests/rpc/test_rpc_manager.py b/tests/rpc/test_rpc_manager.py
index 3068e9764..69a757fcf 100644
--- a/tests/rpc/test_rpc_manager.py
+++ b/tests/rpc/test_rpc_manager.py
@@ -71,7 +71,7 @@ def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None:
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
rpc_manager = RPCManager(freqtradebot)
rpc_manager.send_msg({
- 'type': RPCMessageType.STATUS_NOTIFICATION,
+ 'type': RPCMessageType.STATUS,
'status': 'test'
})
@@ -86,7 +86,7 @@ def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None:
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
rpc_manager = RPCManager(freqtradebot)
rpc_manager.send_msg({
- 'type': RPCMessageType.STATUS_NOTIFICATION,
+ 'type': RPCMessageType.STATUS,
'status': 'test'
})
@@ -124,7 +124,7 @@ def test_send_msg_webhook_CustomMessagetype(mocker, default_conf, caplog) -> Non
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
assert 'webhook' in [mod.name for mod in rpc_manager.registered_modules]
- rpc_manager.send_msg({'type': RPCMessageType.STARTUP_NOTIFICATION,
+ rpc_manager.send_msg({'type': RPCMessageType.STARTUP,
'status': 'TestMessage'})
assert log_has(
"Message type 'startup' not implemented by handler webhook.",
@@ -140,7 +140,7 @@ def test_startupmessages_telegram_enabled(mocker, default_conf, caplog) -> None:
rpc_manager.startup_messages(default_conf, freqtradebot.pairlists, freqtradebot.protections)
assert telegram_mock.call_count == 3
- assert "*Exchange:* `bittrex`" in telegram_mock.call_args_list[1][0][0]['status']
+ assert "*Exchange:* `binance`" in telegram_mock.call_args_list[1][0][0]['status']
telegram_mock.reset_mock()
default_conf['dry_run'] = True
diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index 27babb1b7..e640f2dff 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -4,6 +4,7 @@
import re
from datetime import datetime
+from functools import reduce
from random import choice, randint
from string import ascii_uppercase
from unittest.mock import ANY, MagicMock
@@ -54,6 +55,14 @@ class DummyCls(Telegram):
raise Exception('test')
+def get_telegram_testobject_with_inline(mocker, default_conf, mock=True, ftbot=None):
+ inline_msg_mock = MagicMock()
+ telegram, ftbot, msg_mock = get_telegram_testobject(mocker, default_conf)
+ mocker.patch('freqtrade.rpc.telegram.Telegram._send_inline_msg', inline_msg_mock)
+
+ return telegram, ftbot, msg_mock, inline_msg_mock
+
+
def get_telegram_testobject(mocker, default_conf, mock=True, ftbot=None):
msg_mock = MagicMock()
if mock:
@@ -177,9 +186,7 @@ def test_telegram_status(default_conf, update, mocker) -> None:
'pair': 'ETH/BTC',
'base_currency': 'BTC',
'open_date': arrow.utcnow(),
- 'open_date_hum': arrow.utcnow().humanize,
'close_date': None,
- 'close_date_hum': None,
'open_rate': 1.099e-05,
'close_rate': None,
'current_rate': 1.098e-05,
@@ -685,12 +692,12 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
context.args = ["1"]
telegram._forcesell(update=update, context=context)
- assert msg_mock.call_count == 3
+ assert msg_mock.call_count == 4
last_msg = msg_mock.call_args_list[-1][0][0]
assert {
- 'type': RPCMessageType.SELL_NOTIFICATION,
+ 'type': RPCMessageType.SELL,
'trade_id': 1,
- 'exchange': 'Bittrex',
+ 'exchange': 'Binance',
'pair': 'ETH/BTC',
'gain': 'profit',
'limit': 1.173e-05,
@@ -705,6 +712,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
'sell_reason': SellType.FORCE_SELL.value,
'open_date': ANY,
'close_date': ANY,
+ 'close_rate': ANY,
} == last_msg
@@ -745,13 +753,13 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
context.args = ["1"]
telegram._forcesell(update=update, context=context)
- assert msg_mock.call_count == 3
+ assert msg_mock.call_count == 4
last_msg = msg_mock.call_args_list[-1][0][0]
assert {
- 'type': RPCMessageType.SELL_NOTIFICATION,
+ 'type': RPCMessageType.SELL,
'trade_id': 1,
- 'exchange': 'Bittrex',
+ 'exchange': 'Binance',
'pair': 'ETH/BTC',
'gain': 'loss',
'limit': 1.043e-05,
@@ -766,6 +774,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
'sell_reason': SellType.FORCE_SELL.value,
'open_date': ANY,
'close_date': ANY,
+ 'close_rate': ANY,
} == last_msg
@@ -796,13 +805,13 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
context.args = ["all"]
telegram._forcesell(update=update, context=context)
- # Called for each trade 3 times
- assert msg_mock.call_count == 8
- msg = msg_mock.call_args_list[1][0][0]
+ # Called for each trade 4 times
+ assert msg_mock.call_count == 12
+ msg = msg_mock.call_args_list[2][0][0]
assert {
- 'type': RPCMessageType.SELL_NOTIFICATION,
+ 'type': RPCMessageType.SELL,
'trade_id': 1,
- 'exchange': 'Bittrex',
+ 'exchange': 'Binance',
'pair': 'ETH/BTC',
'gain': 'loss',
'limit': 1.099e-05,
@@ -817,6 +826,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
'sell_reason': SellType.FORCE_SELL.value,
'open_date': ANY,
'close_date': ANY,
+ 'close_rate': ANY,
} == msg
@@ -901,6 +911,33 @@ def test_forcebuy_handle_exception(default_conf, update, mocker) -> None:
assert msg_mock.call_args_list[0][0][0] == 'Forcebuy not enabled.'
+def test_forcebuy_no_pair(default_conf, update, mocker) -> None:
+ mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
+
+ fbuy_mock = MagicMock(return_value=None)
+ mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock)
+
+ telegram, freqtradebot, _, inline_msg_mock = get_telegram_testobject_with_inline(mocker,
+ default_conf)
+ patch_get_signal(freqtradebot, (True, False))
+
+ context = MagicMock()
+ context.args = []
+ telegram._forcebuy(update=update, context=context)
+
+ assert fbuy_mock.call_count == 0
+ assert inline_msg_mock.call_count == 1
+ assert inline_msg_mock.call_args_list[0][0][0] == 'Which pair?'
+ assert inline_msg_mock.call_args_list[0][1]['callback_query_handler'] == 'forcebuy'
+ keyboard = inline_msg_mock.call_args_list[0][1]['keyboard']
+ assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 4
+ update = MagicMock()
+ update.callback_query = MagicMock()
+ update.callback_query.data = 'XRP/USDT'
+ telegram._forcebuy_inline(update, None)
+ assert fbuy_mock.call_count == 1
+
+
def test_performance_handle(default_conf, update, ticker, fee,
limit_buy_order, limit_sell_order, mocker) -> None:
@@ -928,7 +965,7 @@ def test_performance_handle(default_conf, update, ticker, fee,
telegram._performance(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert 'Performance' in msg_mock.call_args_list[0][0][0]
- assert 'ETH/BTC\t6.20% (1)
' in msg_mock.call_args_list[0][0][0]
+ assert 'ETH/BTC\t0.00006217 BTC (6.20%) (1)
' in msg_mock.call_args_list[0][0][0]
def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
@@ -968,6 +1005,11 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None
)
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False))
+ telegram._locks(update=update, context=MagicMock())
+ assert msg_mock.call_count == 1
+ assert 'No active locks.' in msg_mock.call_args_list[0][0][0]
+
+ msg_mock.reset_mock()
PairLocks.lock_pair('ETH/BTC', arrow.utcnow().shift(minutes=4).datetime, 'randreason')
PairLocks.lock_pair('XRP/BTC', arrow.utcnow().shift(minutes=20).datetime, 'deadbeef')
@@ -1101,6 +1143,15 @@ def test_edge_enabled(edge_conf, update, mocker) -> None:
assert 'Edge only validated following pairs:\n' in msg_mock.call_args_list[0][0][0]
assert 'Pair Winrate Expectancy Stoploss' in msg_mock.call_args_list[0][0][0]
+ msg_mock.reset_mock()
+
+ mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
+ return_value={}))
+ telegram._edge(update=update, context=MagicMock())
+ assert msg_mock.call_count == 1
+ assert 'Edge only validated following pairs:' in msg_mock.call_args_list[0][0][0]
+ assert 'Winrate' not in msg_mock.call_args_list[0][0][0]
+
def test_telegram_trades(mocker, update, default_conf, fee):
@@ -1180,7 +1231,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
telegram._show_config(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
- assert '*Exchange:* `bittrex`' in msg_mock.call_args_list[0][0][0]
+ assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
assert '*Strategy:* `DefaultStrategy`' in msg_mock.call_args_list[0][0][0]
assert '*Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
@@ -1189,7 +1240,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
telegram._show_config(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
- assert '*Exchange:* `bittrex`' in msg_mock.call_args_list[0][0][0]
+ assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
assert '*Strategy:* `DefaultStrategy`' in msg_mock.call_args_list[0][0][0]
assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
@@ -1197,9 +1248,9 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
msg = {
- 'type': RPCMessageType.BUY_NOTIFICATION,
+ 'type': RPCMessageType.BUY,
'trade_id': 1,
- 'exchange': 'Bittrex',
+ 'exchange': 'Binance',
'pair': 'ETH/BTC',
'limit': 1.099e-05,
'order_type': 'limit',
@@ -1215,7 +1266,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
telegram.send_msg(msg)
assert msg_mock.call_args[0][0] \
- == '\N{LARGE BLUE CIRCLE} *Bittrex:* Buying ETH/BTC (#1)\n' \
+ == '\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n' \
'*Amount:* `1333.33333333`\n' \
'*Open Rate:* `0.00001099`\n' \
'*Current Rate:* `0.00001099`\n' \
@@ -1242,17 +1293,36 @@ def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg({
- 'type': RPCMessageType.BUY_CANCEL_NOTIFICATION,
+ 'type': RPCMessageType.BUY_CANCEL,
'trade_id': 1,
- 'exchange': 'Bittrex',
+ 'exchange': 'Binance',
'pair': 'ETH/BTC',
'reason': CANCEL_REASON['TIMEOUT']
})
- assert (msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Bittrex:* '
+ assert (msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Binance:* '
'Cancelling open buy Order for ETH/BTC (#1). '
'Reason: cancelled due to timeout.')
+def test_send_msg_buy_fill_notification(default_conf, mocker) -> None:
+
+ default_conf['telegram']['notification_settings']['buy_fill'] = 'on'
+ telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
+
+ telegram.send_msg({
+ 'type': RPCMessageType.BUY_FILL,
+ 'trade_id': 1,
+ 'exchange': 'Binance',
+ 'pair': 'ETH/USDT',
+ 'open_rate': 200,
+ 'stake_amount': 100,
+ 'amount': 0.5,
+ 'open_date': arrow.utcnow().datetime
+ })
+ assert (msg_mock.call_args[0][0] == '\N{LARGE CIRCLE} *Binance:* '
+ 'Buy order for ETH/USDT (#1) filled for 200.')
+
+
def test_send_msg_sell_notification(default_conf, mocker) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
@@ -1260,7 +1330,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
old_convamount = telegram._rpc._fiat_converter.convert_amount
telegram._rpc._fiat_converter.convert_amount = lambda a, b, c: -24.812
telegram.send_msg({
- 'type': RPCMessageType.SELL_NOTIFICATION,
+ 'type': RPCMessageType.SELL,
'trade_id': 1,
'exchange': 'Binance',
'pair': 'KEY/ETH',
@@ -1290,7 +1360,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
msg_mock.reset_mock()
telegram.send_msg({
- 'type': RPCMessageType.SELL_NOTIFICATION,
+ 'type': RPCMessageType.SELL,
'trade_id': 1,
'exchange': 'Binance',
'pair': 'KEY/ETH',
@@ -1327,36 +1397,65 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
old_convamount = telegram._rpc._fiat_converter.convert_amount
telegram._rpc._fiat_converter.convert_amount = lambda a, b, c: -24.812
telegram.send_msg({
- 'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
+ 'type': RPCMessageType.SELL_CANCEL,
'trade_id': 1,
'exchange': 'Binance',
'pair': 'KEY/ETH',
'reason': 'Cancelled on exchange'
})
assert msg_mock.call_args[0][0] \
- == ('\N{WARNING SIGN} *Binance:* Cancelling Open Sell Order for KEY/ETH (#1).'
- ' Reason: Cancelled on exchange')
+ == ('\N{WARNING SIGN} *Binance:* Cancelling open sell Order for KEY/ETH (#1).'
+ ' Reason: Cancelled on exchange.')
msg_mock.reset_mock()
telegram.send_msg({
- 'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
+ 'type': RPCMessageType.SELL_CANCEL,
'trade_id': 1,
'exchange': 'Binance',
'pair': 'KEY/ETH',
'reason': 'timeout'
})
assert msg_mock.call_args[0][0] \
- == ('\N{WARNING SIGN} *Binance:* Cancelling Open Sell Order for KEY/ETH (#1).'
- ' Reason: timeout')
+ == ('\N{WARNING SIGN} *Binance:* Cancelling open sell Order for KEY/ETH (#1).'
+ ' Reason: timeout.')
# Reset singleton function to avoid random breaks
telegram._rpc._fiat_converter.convert_amount = old_convamount
+def test_send_msg_sell_fill_notification(default_conf, mocker) -> None:
+
+ default_conf['telegram']['notification_settings']['sell_fill'] = 'on'
+ telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
+
+ telegram.send_msg({
+ 'type': RPCMessageType.SELL_FILL,
+ 'trade_id': 1,
+ 'exchange': 'Binance',
+ 'pair': 'ETH/USDT',
+ 'gain': 'loss',
+ 'limit': 3.201e-05,
+ 'amount': 0.1,
+ 'order_type': 'market',
+ 'open_rate': 500,
+ 'close_rate': 550,
+ 'current_rate': 3.201e-05,
+ 'profit_amount': -0.05746268,
+ 'profit_ratio': -0.57405275,
+ 'stake_currency': 'ETH',
+ 'fiat_currency': 'USD',
+ 'sell_reason': SellType.STOP_LOSS.value,
+ 'open_date': arrow.utcnow().shift(hours=-1),
+ 'close_date': arrow.utcnow(),
+ })
+ assert msg_mock.call_args[0][0] \
+ == ('\N{LARGE CIRCLE} *Binance:* Sell order for ETH/USDT (#1) filled for 550.')
+
+
def test_send_msg_status_notification(default_conf, mocker) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg({
- 'type': RPCMessageType.STATUS_NOTIFICATION,
+ 'type': RPCMessageType.STATUS,
'status': 'running'
})
assert msg_mock.call_args[0][0] == '*Status:* `running`'
@@ -1365,7 +1464,7 @@ def test_send_msg_status_notification(default_conf, mocker) -> None:
def test_warning_notification(default_conf, mocker) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg({
- 'type': RPCMessageType.WARNING_NOTIFICATION,
+ 'type': RPCMessageType.WARNING,
'status': 'message'
})
assert msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Warning:* `message`'
@@ -1374,7 +1473,7 @@ def test_warning_notification(default_conf, mocker) -> None:
def test_startup_notification(default_conf, mocker) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg({
- 'type': RPCMessageType.STARTUP_NOTIFICATION,
+ 'type': RPCMessageType.STARTUP,
'status': '*Custom:* `Hello World`'
})
assert msg_mock.call_args[0][0] == '*Custom:* `Hello World`'
@@ -1393,9 +1492,9 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg({
- 'type': RPCMessageType.BUY_NOTIFICATION,
+ 'type': RPCMessageType.BUY,
'trade_id': 1,
- 'exchange': 'Bittrex',
+ 'exchange': 'Binance',
'pair': 'ETH/BTC',
'limit': 1.099e-05,
'order_type': 'limit',
@@ -1407,7 +1506,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
'amount': 1333.3333333333335,
'open_date': arrow.utcnow().shift(hours=-1)
})
- assert msg_mock.call_args[0][0] == ('\N{LARGE BLUE CIRCLE} *Bittrex:* Buying ETH/BTC (#1)\n'
+ assert msg_mock.call_args[0][0] == ('\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n'
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00001099`\n'
'*Current Rate:* `0.00001099`\n'
@@ -1419,7 +1518,7 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg({
- 'type': RPCMessageType.SELL_NOTIFICATION,
+ 'type': RPCMessageType.SELL,
'trade_id': 1,
'exchange': 'Binance',
'pair': 'KEY/ETH',
diff --git a/tests/rpc/test_rpc_webhook.py b/tests/rpc/test_rpc_webhook.py
index 5361cd947..0560f8d53 100644
--- a/tests/rpc/test_rpc_webhook.py
+++ b/tests/rpc/test_rpc_webhook.py
@@ -25,6 +25,11 @@ def get_webhook_dict() -> dict:
"value2": "limit {limit:8f}",
"value3": "{stake_amount:8f} {stake_currency}"
},
+ "webhookbuyfill": {
+ "value1": "Buy Order for {pair} filled",
+ "value2": "at {open_rate:8f}",
+ "value3": "{stake_amount:8f} {stake_currency}"
+ },
"webhooksell": {
"value1": "Selling {pair}",
"value2": "limit {limit:8f}",
@@ -35,6 +40,11 @@ def get_webhook_dict() -> dict:
"value2": "limit {limit:8f}",
"value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})"
},
+ "webhooksellfill": {
+ "value1": "Sell Order for {pair} filled",
+ "value2": "at {close_rate:8f}",
+ "value3": ""
+ },
"webhookstatus": {
"value1": "Status: {status}",
"value2": "",
@@ -49,7 +59,7 @@ def test__init__(mocker, default_conf):
assert webhook._config == default_conf
-def test_send_msg(default_conf, mocker):
+def test_send_msg_webhook(default_conf, mocker):
default_conf["webhook"] = get_webhook_dict()
msg_mock = MagicMock()
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
@@ -58,8 +68,8 @@ def test_send_msg(default_conf, mocker):
msg_mock = MagicMock()
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
msg = {
- 'type': RPCMessageType.BUY_NOTIFICATION,
- 'exchange': 'Bittrex',
+ 'type': RPCMessageType.BUY,
+ 'exchange': 'Binance',
'pair': 'ETH/BTC',
'limit': 0.005,
'stake_amount': 0.8,
@@ -76,11 +86,11 @@ def test_send_msg(default_conf, mocker):
assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhookbuy"]["value3"].format(**msg))
# Test buy cancel
- msg_mock = MagicMock()
- mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
+ msg_mock.reset_mock()
+
msg = {
- 'type': RPCMessageType.BUY_CANCEL_NOTIFICATION,
- 'exchange': 'Bittrex',
+ 'type': RPCMessageType.BUY_CANCEL,
+ 'exchange': 'Binance',
'pair': 'ETH/BTC',
'limit': 0.005,
'stake_amount': 0.8,
@@ -96,12 +106,32 @@ def test_send_msg(default_conf, mocker):
default_conf["webhook"]["webhookbuycancel"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhookbuycancel"]["value3"].format(**msg))
- # Test sell
- msg_mock = MagicMock()
- mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
+ # Test buy fill
+ msg_mock.reset_mock()
+
msg = {
- 'type': RPCMessageType.SELL_NOTIFICATION,
- 'exchange': 'Bittrex',
+ 'type': RPCMessageType.BUY_FILL,
+ 'exchange': 'Binance',
+ 'pair': 'ETH/BTC',
+ 'open_rate': 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"]["webhookbuyfill"]["value1"].format(**msg))
+ assert (msg_mock.call_args[0][0]["value2"] ==
+ default_conf["webhook"]["webhookbuyfill"]["value2"].format(**msg))
+ assert (msg_mock.call_args[0][0]["value3"] ==
+ default_conf["webhook"]["webhookbuyfill"]["value3"].format(**msg))
+ # Test sell
+ msg_mock.reset_mock()
+ msg = {
+ 'type': RPCMessageType.SELL,
+ 'exchange': 'Binance',
'pair': 'ETH/BTC',
'gain': "profit",
'limit': 0.005,
@@ -123,11 +153,10 @@ def test_send_msg(default_conf, mocker):
assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhooksell"]["value3"].format(**msg))
# Test sell cancel
- msg_mock = MagicMock()
- mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
+ msg_mock.reset_mock()
msg = {
- 'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
- 'exchange': 'Bittrex',
+ 'type': RPCMessageType.SELL_CANCEL,
+ 'exchange': 'Binance',
'pair': 'ETH/BTC',
'gain': "profit",
'limit': 0.005,
@@ -148,9 +177,35 @@ def test_send_msg(default_conf, mocker):
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,
- RPCMessageType.WARNING_NOTIFICATION,
- RPCMessageType.STARTUP_NOTIFICATION]:
+ # Test Sell fill
+ msg_mock.reset_mock()
+ msg = {
+ 'type': RPCMessageType.SELL_FILL,
+ 'exchange': 'Binance',
+ 'pair': 'ETH/BTC',
+ 'gain': "profit",
+ 'close_rate': 0.005,
+ 'amount': 0.8,
+ 'order_type': 'limit',
+ 'open_rate': 0.004,
+ 'current_rate': 0.005,
+ 'profit_amount': 0.001,
+ 'profit_ratio': 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"]["webhooksellfill"]["value1"].format(**msg))
+ assert (msg_mock.call_args[0][0]["value2"] ==
+ default_conf["webhook"]["webhooksellfill"]["value2"].format(**msg))
+ assert (msg_mock.call_args[0][0]["value3"] ==
+ default_conf["webhook"]["webhooksellfill"]["value3"].format(**msg))
+
+ for msgtype in [RPCMessageType.STATUS,
+ RPCMessageType.WARNING,
+ RPCMessageType.STARTUP]:
# Test notification
msg = {
'type': msgtype,
@@ -173,8 +228,8 @@ def test_exception_send_msg(default_conf, mocker, caplog):
del default_conf["webhook"]["webhookbuy"]
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
- webhook.send_msg({'type': RPCMessageType.BUY_NOTIFICATION})
- assert log_has(f"Message type '{RPCMessageType.BUY_NOTIFICATION}' not configured for webhooks",
+ webhook.send_msg({'type': RPCMessageType.BUY})
+ assert log_has(f"Message type '{RPCMessageType.BUY}' not configured for webhooks",
caplog)
default_conf["webhook"] = get_webhook_dict()
@@ -183,8 +238,8 @@ def test_exception_send_msg(default_conf, mocker, caplog):
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
msg = {
- 'type': RPCMessageType.BUY_NOTIFICATION,
- 'exchange': 'Bittrex',
+ 'type': RPCMessageType.BUY,
+ 'exchange': 'Binance',
'pair': 'ETH/BTC',
'limit': 0.005,
'order_type': 'limit',
diff --git a/tests/strategy/test_default_strategy.py b/tests/strategy/test_default_strategy.py
index ec7b3c33d..92ac9f63a 100644
--- a/tests/strategy/test_default_strategy.py
+++ b/tests/strategy/test_default_strategy.py
@@ -36,9 +36,11 @@ def test_default_strategy(result, fee):
)
assert strategy.confirm_trade_entry(pair='ETH/BTC', order_type='limit', amount=0.1,
- rate=20000, time_in_force='gtc') is True
+ rate=20000, time_in_force='gtc',
+ current_time=datetime.utcnow()) is True
assert strategy.confirm_trade_exit(pair='ETH/BTC', trade=trade, order_type='limit', amount=0.1,
- rate=20000, time_in_force='gtc', sell_reason='roi') is True
+ rate=20000, time_in_force='gtc', sell_reason='roi',
+ current_time=datetime.utcnow()) is True
assert strategy.custom_stoploss(pair='ETH/BTC', trade=trade, current_time=datetime.now(),
current_rate=20_000, current_profit=0.05) == strategy.stoploss
diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py
index 3bfa691b4..ded396779 100644
--- a/tests/strategy/test_interface.py
+++ b/tests/strategy/test_interface.py
@@ -219,7 +219,7 @@ def test_min_roi_reached(default_conf, fee) -> None:
open_date=arrow.utcnow().shift(hours=-1).datetime,
fee_open=fee.return_value,
fee_close=fee.return_value,
- exchange='bittrex',
+ exchange='binance',
open_rate=1,
)
@@ -258,7 +258,7 @@ def test_min_roi_reached2(default_conf, fee) -> None:
open_date=arrow.utcnow().shift(hours=-1).datetime,
fee_open=fee.return_value,
fee_close=fee.return_value,
- exchange='bittrex',
+ exchange='binance',
open_rate=1,
)
@@ -293,7 +293,7 @@ def test_min_roi_reached3(default_conf, fee) -> None:
open_date=arrow.utcnow().shift(hours=-1).datetime,
fee_open=fee.return_value,
fee_close=fee.return_value,
- exchange='bittrex',
+ exchange='binance',
open_rate=1,
)
@@ -346,7 +346,7 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili
open_date=arrow.utcnow().shift(hours=-1).datetime,
fee_open=fee.return_value,
fee_close=fee.return_value,
- exchange='bittrex',
+ exchange='binance',
open_rate=1,
)
trade.adjust_min_max_rates(trade.open_rate)
@@ -382,6 +382,50 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili
strategy.custom_stoploss = original_stopvalue
+def test_custom_sell(default_conf, fee, caplog) -> None:
+
+ default_conf.update({'strategy': 'DefaultStrategy'})
+
+ strategy = StrategyResolver.load_strategy(default_conf)
+ trade = Trade(
+ pair='ETH/BTC',
+ stake_amount=0.01,
+ amount=1,
+ open_date=arrow.utcnow().shift(hours=-1).datetime,
+ fee_open=fee.return_value,
+ fee_close=fee.return_value,
+ exchange='binance',
+ open_rate=1,
+ )
+
+ now = arrow.utcnow().datetime
+ res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
+
+ assert res.sell_flag is False
+ assert res.sell_type == SellType.NONE
+
+ strategy.custom_sell = MagicMock(return_value=True)
+ res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
+ assert res.sell_flag is True
+ assert res.sell_type == SellType.CUSTOM_SELL
+ assert res.sell_reason == 'custom_sell'
+
+ strategy.custom_sell = MagicMock(return_value='hello world')
+
+ res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
+ assert res.sell_type == SellType.CUSTOM_SELL
+ assert res.sell_flag is True
+ assert res.sell_reason == 'hello world'
+
+ caplog.clear()
+ strategy.custom_sell = MagicMock(return_value='h' * 100)
+ res = strategy.should_sell(trade, 1, now, False, False, None, None, 0)
+ assert res.sell_type == SellType.CUSTOM_SELL
+ assert res.sell_flag is True
+ assert res.sell_reason == 'h' * 64
+ assert log_has_re('Custom sell reason returned from custom_sell is too long.*', caplog)
+
+
def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None:
caplog.set_level(logging.DEBUG)
ind_mock = MagicMock(side_effect=lambda x, meta: x)
@@ -588,6 +632,14 @@ def test_hyperopt_parameters():
intpar = IntParameter(low=0, high=5, default=1, space='buy')
assert intpar.value == 1
assert isinstance(intpar.get_space(''), Integer)
+ assert isinstance(intpar.range, range)
+ assert len(list(intpar.range)) == 1
+ # Range contains ONLY the default / value.
+ assert list(intpar.range) == [intpar.value]
+ intpar.in_space = True
+
+ assert len(list(intpar.range)) == 6
+ assert list(intpar.range) == [0, 1, 2, 3, 4, 5]
fltpar = RealParameter(low=0.0, high=5.5, default=1.0, space='buy')
assert isinstance(fltpar.get_space(''), Real)
@@ -596,8 +648,6 @@ def test_hyperopt_parameters():
fltpar = DecimalParameter(low=0.0, high=5.5, default=1.0004, decimals=3, space='buy')
assert isinstance(fltpar.get_space(''), Integer)
assert fltpar.value == 1
- fltpar._set_value(2222)
- assert fltpar.value == 2.222
catpar = CategoricalParameter(['buy_rsi', 'buy_macd', 'buy_none'],
default='buy_macd', space='buy')
@@ -621,4 +671,4 @@ def test_auto_hyperopt_interface(default_conf):
strategy.sell_rsi = IntParameter([0, 10], default=5, space='buy')
with pytest.raises(OperationalException, match=r"Inconclusive parameter.*"):
- [x for x in strategy.enumerate_parameters('sell')]
+ [x for x in strategy._detect_parameters('sell')]
diff --git a/tests/test_configuration.py b/tests/test_configuration.py
index a512bf58a..b2c883108 100644
--- a/tests/test_configuration.py
+++ b/tests/test_configuration.py
@@ -1002,6 +1002,7 @@ def test_pairlist_resolving():
config = configuration.get_config()
assert config['pairs'] == ['ETH/BTC', 'XRP/BTC']
+ assert config['exchange']['pair_whitelist'] == ['ETH/BTC', 'XRP/BTC']
assert config['exchange']['name'] == 'binance'
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
index c91015766..4d9284a2f 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -82,10 +82,6 @@ def test_bot_cleanup(mocker, default_conf, caplog) -> None:
def test_order_dict_dry_run(default_conf, mocker, caplog) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
- mocker.patch.multiple(
- 'freqtrade.exchange.Exchange',
- get_balance=MagicMock(return_value=default_conf['stake_amount'] * 2)
- )
conf = default_conf.copy()
conf['runmode'] = RunMode.DRY_RUN
conf['order_types'] = {
@@ -117,10 +113,7 @@ def test_order_dict_dry_run(default_conf, mocker, caplog) -> None:
def test_order_dict_live(default_conf, mocker, caplog) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
- mocker.patch.multiple(
- 'freqtrade.exchange.Exchange',
- get_balance=MagicMock(return_value=default_conf['stake_amount'] * 2)
- )
+
conf = default_conf.copy()
conf['runmode'] = RunMode.LIVE
conf['order_types'] = {
@@ -153,15 +146,10 @@ def test_order_dict_live(default_conf, mocker, caplog) -> None:
def test_get_trade_stake_amount(default_conf, ticker, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
- mocker.patch.multiple(
- 'freqtrade.exchange.Exchange',
- get_balance=MagicMock(return_value=default_conf['stake_amount'] * 2)
- )
freqtrade = FreqtradeBot(default_conf)
- result = freqtrade.wallets.get_trade_stake_amount(
- 'ETH/BTC', freqtrade.get_free_open_trades())
+ result = freqtrade.wallets.get_trade_stake_amount('ETH/BTC')
assert result == default_conf['stake_amount']
@@ -182,7 +170,6 @@ def test_check_available_stake_amount(default_conf, ticker, mocker, fee, limit_b
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker,
- get_balance=MagicMock(return_value=default_conf['stake_amount'] * 2),
buy=MagicMock(return_value=limit_buy_order_open),
get_fee=fee
)
@@ -197,73 +184,12 @@ def test_check_available_stake_amount(default_conf, ticker, mocker, fee, limit_b
if expected[i] is not None:
limit_buy_order_open['id'] = str(i)
- result = freqtrade.wallets.get_trade_stake_amount('ETH/BTC',
- freqtrade.get_free_open_trades())
+ result = freqtrade.wallets.get_trade_stake_amount('ETH/BTC')
assert pytest.approx(result) == expected[i]
freqtrade.execute_buy('ETH/BTC', result)
else:
with pytest.raises(DependencyException):
- freqtrade.wallets.get_trade_stake_amount('ETH/BTC',
- freqtrade.get_free_open_trades())
-
-
-def test_get_trade_stake_amount_no_stake_amount(default_conf, mocker) -> None:
- patch_RPCManager(mocker)
- patch_exchange(mocker)
- patch_wallet(mocker, free=default_conf['stake_amount'] * 0.5)
- freqtrade = FreqtradeBot(default_conf)
- patch_get_signal(freqtrade)
-
- with pytest.raises(DependencyException, match=r'.*stake amount.*'):
- freqtrade.wallets.get_trade_stake_amount('ETH/BTC', freqtrade.get_free_open_trades())
-
-
-@pytest.mark.parametrize("balance_ratio,result1", [
- (1, 0.005),
- (0.99, 0.00495),
- (0.50, 0.0025),
- ])
-def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_ratio, result1,
- limit_buy_order_open, fee, mocker) -> None:
- patch_RPCManager(mocker)
- patch_exchange(mocker)
- mocker.patch.multiple(
- 'freqtrade.exchange.Exchange',
- fetch_ticker=ticker,
- buy=MagicMock(return_value=limit_buy_order_open),
- get_fee=fee
- )
-
- conf = deepcopy(default_conf)
- conf['stake_amount'] = UNLIMITED_STAKE_AMOUNT
- conf['dry_run_wallet'] = 0.01
- conf['max_open_trades'] = 2
- conf['tradable_balance_ratio'] = balance_ratio
-
- freqtrade = FreqtradeBot(conf)
- patch_get_signal(freqtrade)
-
- # no open trades, order amount should be 'balance / max_open_trades'
- result = freqtrade.wallets.get_trade_stake_amount('ETH/BTC', freqtrade.get_free_open_trades())
- assert result == result1
-
- # create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)'
- freqtrade.execute_buy('ETH/BTC', result)
-
- result = freqtrade.wallets.get_trade_stake_amount('LTC/BTC', freqtrade.get_free_open_trades())
- assert result == result1
-
- # create 2 trades, order amount should be None
- freqtrade.execute_buy('LTC/BTC', result)
-
- result = freqtrade.wallets.get_trade_stake_amount('XRP/BTC', freqtrade.get_free_open_trades())
- assert result == 0
-
- # set max_open_trades = None, so do not trade
- conf['max_open_trades'] = 0
- freqtrade = FreqtradeBot(conf)
- result = freqtrade.wallets.get_trade_stake_amount('NEO/BTC', freqtrade.get_free_open_trades())
- assert result == 0
+ freqtrade.wallets.get_trade_stake_amount('ETH/BTC')
def test_edge_called_in_process(mocker, edge_conf) -> None:
@@ -289,9 +215,9 @@ def test_edge_overrides_stake_amount(mocker, edge_conf) -> None:
freqtrade = FreqtradeBot(edge_conf)
assert freqtrade.wallets.get_trade_stake_amount(
- 'NEO/BTC', freqtrade.get_free_open_trades(), freqtrade.edge) == (999.9 * 0.5 * 0.01) / 0.20
+ 'NEO/BTC', freqtrade.edge) == (999.9 * 0.5 * 0.01) / 0.20
assert freqtrade.wallets.get_trade_stake_amount(
- 'LTC/BTC', freqtrade.get_free_open_trades(), freqtrade.edge) == (999.9 * 0.5 * 0.01) / 0.21
+ 'LTC/BTC', freqtrade.edge) == (999.9 * 0.5 * 0.01) / 0.21
def test_edge_overrides_stoploss(limit_buy_order, fee, caplog, mocker, edge_conf) -> None:
@@ -421,7 +347,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, fee, mocker) -> Non
assert trade.stake_amount == 0.001
assert trade.is_open
assert trade.open_date is not None
- assert trade.exchange == 'bittrex'
+ assert trade.exchange == 'binance'
# Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_buy_order)
@@ -497,7 +423,6 @@ def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order_open,
'freqtrade.exchange.Exchange',
fetch_ticker=ticker,
buy=MagicMock(return_value=limit_buy_order_open),
- get_balance=MagicMock(return_value=default_conf['stake_amount']),
get_fee=fee,
)
default_conf['max_open_trades'] = 0
@@ -507,8 +432,7 @@ def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order_open,
patch_get_signal(freqtrade)
assert not freqtrade.create_trade('ETH/BTC')
- assert freqtrade.wallets.get_trade_stake_amount('ETH/BTC', freqtrade.get_free_open_trades(),
- freqtrade.edge) == 0
+ assert freqtrade.wallets.get_trade_stake_amount('ETH/BTC', freqtrade.edge) == 0
def test_enter_positions_no_pairs_left(default_conf, ticker, limit_buy_order_open, fee,
@@ -586,7 +510,6 @@ def test_create_trade_no_signal(default_conf, fee, mocker) -> None:
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
- get_balance=MagicMock(return_value=20),
get_fee=fee,
)
default_conf['stake_amount'] = 10
@@ -680,7 +603,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order, limit_buy
assert trade.stake_amount == default_conf['stake_amount']
assert trade.is_open
assert trade.open_date is not None
- assert trade.exchange == 'bittrex'
+ assert trade.exchange == 'binance'
assert trade.open_rate == 0.00001098
assert trade.amount == 91.07468123
@@ -777,7 +700,7 @@ def test_process_trade_no_whitelist_pair(default_conf, ticker, limit_buy_order,
is_open=True,
amount=20,
open_rate=0.01,
- exchange='bittrex',
+ exchange='binance',
))
Trade.query.session.add(Trade(
pair='ETH/BTC',
@@ -787,7 +710,7 @@ def test_process_trade_no_whitelist_pair(default_conf, ticker, limit_buy_order,
is_open=True,
amount=12,
open_rate=0.001,
- exchange='bittrex',
+ exchange='binance',
))
assert pair not in freqtrade.active_pair_whitelist
@@ -1028,7 +951,7 @@ def test_add_stoploss_on_exchange(mocker, default_conf, limit_buy_order) -> None
return_value=limit_buy_order['amount'])
stoploss = MagicMock(return_value={'id': 13434334})
- mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss)
+ mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
freqtrade = FreqtradeBot(default_conf)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
@@ -1060,6 +983,9 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
get_fee=fee,
+ )
+ mocker.patch.multiple(
+ 'freqtrade.exchange.Binance',
stoploss=stoploss
)
freqtrade = FreqtradeBot(default_conf)
@@ -1084,7 +1010,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
trade.stoploss_order_id = 100
hanging_stoploss_order = MagicMock(return_value={'status': 'open'})
- mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', hanging_stoploss_order)
+ mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', hanging_stoploss_order)
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert trade.stoploss_order_id == 100
@@ -1097,7 +1023,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
trade.stoploss_order_id = 100
canceled_stoploss_order = MagicMock(return_value={'status': 'canceled'})
- mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', canceled_stoploss_order)
+ mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', canceled_stoploss_order)
stoploss.reset_mock()
assert freqtrade.handle_stoploss_on_exchange(trade) is False
@@ -1123,14 +1049,14 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
'average': 2,
'amount': limit_buy_order['amount'],
})
- mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hit)
+ mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', stoploss_order_hit)
assert freqtrade.handle_stoploss_on_exchange(trade) is True
assert log_has_re(r'STOP_LOSS_LIMIT is hit for Trade\(id=1, .*\)\.', caplog)
assert trade.stoploss_order_id is None
assert trade.is_open is False
mocker.patch(
- 'freqtrade.exchange.Exchange.stoploss',
+ 'freqtrade.exchange.Binance.stoploss',
side_effect=ExchangeError()
)
trade.is_open = True
@@ -1142,9 +1068,9 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
# It should try to add stoploss order
trade.stoploss_order_id = 100
stoploss.reset_mock()
- mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order',
+ mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order',
side_effect=InvalidOrderException())
- mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss)
+ mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
freqtrade.handle_stoploss_on_exchange(trade)
assert stoploss.call_count == 1
@@ -1154,7 +1080,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf, fee, caplog,
trade.is_open = False
stoploss.reset_mock()
mocker.patch('freqtrade.exchange.Exchange.fetch_order')
- mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss)
+ mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert stoploss.call_count == 0
@@ -1174,6 +1100,9 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf, fee, caplog,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
get_fee=fee,
+ )
+ mocker.patch.multiple(
+ 'freqtrade.exchange.Binance',
fetch_stoploss_order=MagicMock(return_value={'status': 'canceled', 'id': 100}),
stoploss=MagicMock(side_effect=ExchangeError()),
)
@@ -1208,6 +1137,9 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf, caplog, fee,
buy=MagicMock(return_value=limit_buy_order_open),
sell=sell_mock,
get_fee=fee,
+ )
+ mocker.patch.multiple(
+ 'freqtrade.exchange.Binance',
fetch_order=MagicMock(return_value={'status': 'canceled'}),
stoploss=MagicMock(side_effect=InvalidOrderException()),
)
@@ -1253,6 +1185,9 @@ def test_create_stoploss_order_insufficient_funds(mocker, default_conf, caplog,
sell=sell_mock,
get_fee=fee,
fetch_order=MagicMock(return_value={'status': 'canceled'}),
+ )
+ mocker.patch.multiple(
+ 'freqtrade.exchange.Binance',
stoploss=MagicMock(side_effect=InsufficientFundsError()),
)
patch_get_signal(freqtrade)
@@ -1290,6 +1225,9 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
get_fee=fee,
+ )
+ mocker.patch.multiple(
+ 'freqtrade.exchange.Binance',
stoploss=stoploss,
stoploss_adjust=MagicMock(return_value=True),
)
@@ -1330,7 +1268,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee,
}
})
- mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hanging)
+ mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', stoploss_order_hanging)
# stoploss initially at 5%
assert freqtrade.handle_trade(trade) is False
@@ -1345,8 +1283,8 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee,
cancel_order_mock = MagicMock()
stoploss_order_mock = MagicMock(return_value={'id': 13434334})
- mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', cancel_order_mock)
- mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss_order_mock)
+ mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', cancel_order_mock)
+ mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss_order_mock)
# stoploss should not be updated as the interval is 60 seconds
assert freqtrade.handle_trade(trade) is False
@@ -1393,6 +1331,9 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
get_fee=fee,
+ )
+ mocker.patch.multiple(
+ 'freqtrade.exchange.Binance',
stoploss=stoploss,
stoploss_adjust=MagicMock(return_value=True),
)
@@ -1428,9 +1369,10 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c
'stopPrice': '0.1'
}
}
- mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
+ mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order',
side_effect=InvalidOrderException())
- mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hanging)
+ mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order',
+ return_value=stoploss_order_hanging)
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/BTC.*", caplog)
@@ -1439,8 +1381,8 @@ def test_handle_stoploss_on_exchange_trailing_error(mocker, default_conf, fee, c
# Fail creating stoploss order
caplog.clear()
- cancel_mock = mocker.patch("freqtrade.exchange.Exchange.cancel_stoploss_order", MagicMock())
- mocker.patch("freqtrade.exchange.Exchange.stoploss", side_effect=ExchangeError())
+ cancel_mock = mocker.patch("freqtrade.exchange.Binance.cancel_stoploss_order", MagicMock())
+ mocker.patch("freqtrade.exchange.Binance.stoploss", side_effect=ExchangeError())
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
assert cancel_mock.call_count == 1
assert log_has_re(r"Could not create trailing stoploss order for pair ETH/BTC\..*", caplog)
@@ -1462,6 +1404,9 @@ def test_handle_stoploss_on_exchange_custom_stop(mocker, default_conf, fee,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
get_fee=fee,
+ )
+ mocker.patch.multiple(
+ 'freqtrade.exchange.Binance',
stoploss=stoploss,
stoploss_adjust=MagicMock(return_value=True),
)
@@ -1502,7 +1447,7 @@ def test_handle_stoploss_on_exchange_custom_stop(mocker, default_conf, fee,
}
})
- mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hanging)
+ mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', stoploss_order_hanging)
assert freqtrade.handle_trade(trade) is False
assert freqtrade.handle_stoploss_on_exchange(trade) is False
@@ -1516,8 +1461,8 @@ def test_handle_stoploss_on_exchange_custom_stop(mocker, default_conf, fee,
cancel_order_mock = MagicMock()
stoploss_order_mock = MagicMock(return_value={'id': 13434334})
- mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order', cancel_order_mock)
- mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss_order_mock)
+ mocker.patch('freqtrade.exchange.Binance.cancel_stoploss_order', cancel_order_mock)
+ mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss_order_mock)
# stoploss should not be updated as the interval is 60 seconds
assert freqtrade.handle_trade(trade) is False
@@ -1748,6 +1693,7 @@ def test_update_trade_state(mocker, default_conf, limit_buy_order, caplog) -> No
open_rate=0.01,
open_date=arrow.utcnow().datetime,
amount=11,
+ exchange="binance",
)
assert not freqtrade.update_trade_state(trade, None)
assert log_has_re(r'Orderid for trade .* is empty.', caplog)
@@ -2014,7 +1960,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open,
# if ROI is reached we must sell
patch_get_signal(freqtrade, value=(False, True))
assert freqtrade.handle_trade(trade)
- assert log_has("ETH/BTC - Required profit reached. sell_flag=True, sell_type=SellType.ROI",
+ assert log_has("ETH/BTC - Required profit reached. sell_type=SellType.ROI",
caplog)
@@ -2043,7 +1989,7 @@ def test_handle_trade_use_sell_signal(
patch_get_signal(freqtrade, value=(False, True))
assert freqtrade.handle_trade(trade)
- assert log_has("ETH/BTC - Sell signal received. sell_flag=True, sell_type=SellType.SELL_SIGNAL",
+ assert log_has("ETH/BTC - Sell signal received. sell_type=SellType.SELL_SIGNAL",
caplog)
@@ -2357,7 +2303,7 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
# note this is for a partially-complete buy order
freqtrade.check_handle_timedout()
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()
assert len(trades) == 1
assert trades[0].amount == 23.0
@@ -2392,7 +2338,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.*", caplog)
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()
assert len(trades) == 1
# Verify that trade has been updated
@@ -2432,7 +2378,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 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()
assert len(trades) == 1
# Verify that trade has been updated
@@ -2485,13 +2431,22 @@ def test_handle_cancel_buy(mocker, caplog, default_conf, limit_buy_order) -> Non
freqtrade._notify_buy_cancel = MagicMock()
trade = MagicMock()
- trade.pair = 'LTC/ETH'
+ trade.pair = 'LTC/USDT'
+ trade.open_rate = 200
limit_buy_order['filled'] = 0.0
limit_buy_order['status'] = 'open'
reason = CANCEL_REASON['TIMEOUT']
assert freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
assert cancel_order_mock.call_count == 1
+ cancel_order_mock.reset_mock()
+ caplog.clear()
+ limit_buy_order['filled'] = 0.01
+ assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
+ assert cancel_order_mock.call_count == 0
+ assert log_has_re("Order .* for .* not cancelled, as the filled amount.* unsellable.*", caplog)
+
+ caplog.clear()
cancel_order_mock.reset_mock()
limit_buy_order['filled'] = 2
assert not freqtrade.handle_cancel_buy(trade, limit_buy_order, reason)
@@ -2546,7 +2501,8 @@ def test_handle_cancel_buy_corder_empty(mocker, default_conf, limit_buy_order,
freqtrade._notify_buy_cancel = MagicMock()
trade = MagicMock()
- trade.pair = 'LTC/ETH'
+ trade.pair = 'LTC/USDT'
+ trade.open_rate = 200
limit_buy_order['filled'] = 0.0
limit_buy_order['status'] = 'open'
reason = CANCEL_REASON['TIMEOUT']
@@ -2647,22 +2603,24 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
fetch_ticker=ticker_sell_up
)
# Prevented sell ...
- freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sell_reason=SellType.ROI)
+ freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
+ sell_reason=SellCheckTuple(sell_type=SellType.ROI))
assert rpc_mock.call_count == 0
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
# Repatch with true
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
- freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sell_reason=SellType.ROI)
+ freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
+ sell_reason=SellCheckTuple(sell_type=SellType.ROI))
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
assert rpc_mock.call_count == 1
last_msg = rpc_mock.call_args_list[-1][0][0]
assert {
'trade_id': 1,
- 'type': RPCMessageType.SELL_NOTIFICATION,
- 'exchange': 'Bittrex',
+ 'type': RPCMessageType.SELL,
+ 'exchange': 'Binance',
'pair': 'ETH/BTC',
'gain': 'profit',
'limit': 1.172e-05,
@@ -2677,6 +2635,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
'sell_reason': SellType.ROI.value,
'open_date': ANY,
'close_date': ANY,
+ 'close_rate': ANY,
} == last_msg
@@ -2705,14 +2664,14 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker)
)
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
- sell_reason=SellType.STOP_LOSS)
+ sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
assert rpc_mock.call_count == 2
last_msg = rpc_mock.call_args_list[-1][0][0]
assert {
- 'type': RPCMessageType.SELL_NOTIFICATION,
+ 'type': RPCMessageType.SELL,
'trade_id': 1,
- 'exchange': 'Bittrex',
+ 'exchange': 'Binance',
'pair': 'ETH/BTC',
'gain': 'loss',
'limit': 1.044e-05,
@@ -2727,6 +2686,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker)
'sell_reason': SellType.STOP_LOSS.value,
'open_date': ANY,
'close_date': ANY,
+ 'close_rate': ANY,
} == last_msg
@@ -2761,15 +2721,15 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
trade.stop_loss = 0.00001099 * 0.99
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
- sell_reason=SellType.STOP_LOSS)
+ sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
assert rpc_mock.call_count == 2
last_msg = rpc_mock.call_args_list[-1][0][0]
assert {
- 'type': RPCMessageType.SELL_NOTIFICATION,
+ 'type': RPCMessageType.SELL,
'trade_id': 1,
- 'exchange': 'Bittrex',
+ 'exchange': 'Binance',
'pair': 'ETH/BTC',
'gain': 'loss',
'limit': 1.08801e-05,
@@ -2784,7 +2744,7 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
'sell_reason': SellType.STOP_LOSS.value,
'open_date': ANY,
'close_date': ANY,
-
+ 'close_rate': ANY,
} == last_msg
@@ -2813,7 +2773,7 @@ def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, c
trade.stoploss_order_id = "abcd"
freqtrade.execute_sell(trade=trade, limit=1234,
- sell_reason=SellType.STOP_LOSS)
+ sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
assert sellmock.call_count == 1
assert log_has('Could not cancel stoploss order abcd', caplog)
@@ -2863,12 +2823,12 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke
)
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
- sell_reason=SellType.SELL_SIGNAL)
+ sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
trade = Trade.query.first()
assert trade
assert cancel_order.call_count == 1
- assert rpc_mock.call_count == 2
+ assert rpc_mock.call_count == 3
def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, fee,
@@ -2936,7 +2896,10 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, f
assert trade.stoploss_order_id is None
assert trade.is_open is False
assert trade.sell_reason == SellType.STOPLOSS_ON_EXCHANGE.value
- assert rpc_mock.call_count == 2
+ assert rpc_mock.call_count == 3
+ assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.BUY
+ assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.BUY_FILL
+ assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL
def test_execute_sell_market_order(default_conf, ticker, fee,
@@ -2965,17 +2928,18 @@ def test_execute_sell_market_order(default_conf, ticker, fee,
)
freqtrade.config['order_types']['sell'] = 'market'
- freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sell_reason=SellType.ROI)
+ freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
+ sell_reason=SellCheckTuple(sell_type=SellType.ROI))
assert not trade.is_open
assert trade.close_profit == 0.0620716
- assert rpc_mock.call_count == 2
+ assert rpc_mock.call_count == 3
last_msg = rpc_mock.call_args_list[-1][0][0]
assert {
- 'type': RPCMessageType.SELL_NOTIFICATION,
+ 'type': RPCMessageType.SELL,
'trade_id': 1,
- 'exchange': 'Bittrex',
+ 'exchange': 'Binance',
'pair': 'ETH/BTC',
'gain': 'profit',
'limit': 1.172e-05,
@@ -2990,6 +2954,7 @@ def test_execute_sell_market_order(default_conf, ticker, fee,
'sell_reason': SellType.ROI.value,
'open_date': ANY,
'close_date': ANY,
+ 'close_rate': ANY,
} == last_msg
@@ -3018,8 +2983,9 @@ def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee,
fetch_ticker=ticker_sell_up
)
+ sell_reason = SellCheckTuple(sell_type=SellType.ROI)
assert not freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
- sell_reason=SellType.ROI)
+ sell_reason=sell_reason)
assert mock_insuf.call_count == 1
@@ -3112,7 +3078,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, limit_buy_o
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple(
- sell_flag=False, sell_type=SellType.NONE))
+ sell_type=SellType.NONE))
freqtrade.enter_positions()
trade = Trade.query.first()
@@ -3261,7 +3227,7 @@ def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplo
)
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
- sell_reason=SellType.STOP_LOSS)
+ sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
trade.close(ticker_sell_down()['bid'])
assert freqtrade.strategy.is_pair_locked(trade.pair)
@@ -3958,7 +3924,7 @@ def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order_open,
assert trade.stake_amount == 0.001
assert trade.is_open
assert trade.open_date is not None
- assert trade.exchange == 'bittrex'
+ assert trade.exchange == 'binance'
assert len(Trade.query.all()) == 1
@@ -4414,7 +4380,7 @@ def test_reupdate_buy_order_fees(mocker, default_conf, fee, caplog):
is_open=True,
amount=20,
open_rate=0.01,
- exchange='bittrex',
+ exchange='binance',
)
Trade.query.session.add(trade)
diff --git a/tests/test_integration.py b/tests/test_integration.py
index 1c60faa7b..33b3e1140 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -51,8 +51,8 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
side_effect=[stoploss_order_closed, stoploss_order_open, stoploss_order_open])
# Sell 3rd trade (not called for the first trade)
should_sell_mock = MagicMock(side_effect=[
- SellCheckTuple(sell_flag=False, sell_type=SellType.NONE),
- SellCheckTuple(sell_flag=True, sell_type=SellType.SELL_SIGNAL)]
+ SellCheckTuple(sell_type=SellType.NONE),
+ SellCheckTuple(sell_type=SellType.SELL_SIGNAL)]
)
cancel_order_mock = MagicMock()
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
@@ -63,7 +63,7 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y,
fetch_stoploss_order=stoploss_order_mock,
- cancel_stoploss_order=cancel_order_mock,
+ cancel_stoploss_order_with_result=cancel_order_mock,
)
mocker.patch.multiple(
@@ -156,11 +156,11 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
_notify_sell=MagicMock(),
)
should_sell_mock = MagicMock(side_effect=[
- SellCheckTuple(sell_flag=False, sell_type=SellType.NONE),
- SellCheckTuple(sell_flag=True, sell_type=SellType.SELL_SIGNAL),
- SellCheckTuple(sell_flag=False, sell_type=SellType.NONE),
- SellCheckTuple(sell_flag=False, sell_type=SellType.NONE),
- SellCheckTuple(sell_flag=None, sell_type=SellType.NONE)]
+ SellCheckTuple(sell_type=SellType.NONE),
+ SellCheckTuple(sell_type=SellType.SELL_SIGNAL),
+ SellCheckTuple(sell_type=SellType.NONE),
+ SellCheckTuple(sell_type=SellType.NONE),
+ SellCheckTuple(sell_type=SellType.NONE)]
)
mocker.patch("freqtrade.strategy.interface.IStrategy.should_sell", should_sell_mock)
@@ -177,8 +177,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
trades = Trade.query.all()
assert len(trades) == 4
- assert freqtrade.wallets.get_trade_stake_amount(
- 'XRP/BTC', freqtrade.get_free_open_trades()) == result1
+ assert freqtrade.wallets.get_trade_stake_amount('XRP/BTC') == result1
rpc._rpc_forcebuy('TKN/BTC', None)
@@ -199,8 +198,7 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc
# One trade sold
assert len(trades) == 4
# stake-amount should now be reduced, since one trade was sold at a loss.
- assert freqtrade.wallets.get_trade_stake_amount(
- 'XRP/BTC', freqtrade.get_free_open_trades()) < result1
+ assert freqtrade.wallets.get_trade_stake_amount('XRP/BTC') < result1
# Validate that balance of sold trade is not in dry-run balances anymore.
bals2 = freqtrade.wallets.get_all_balances()
assert bals != bals2
diff --git a/tests/test_persistence.py b/tests/test_persistence.py
index 3336e4e66..669f220bb 100644
--- a/tests/test_persistence.py
+++ b/tests/test_persistence.py
@@ -7,7 +7,7 @@ from unittest.mock import MagicMock
import arrow
import pytest
-from sqlalchemy import create_engine
+from sqlalchemy import create_engine, inspect
from freqtrade import constants
from freqtrade.exceptions import DependencyException, OperationalException
@@ -64,7 +64,7 @@ def test_init_dryrun_db(default_conf, tmpdir):
@pytest.mark.usefixtures("init_persistence")
-def test_update_with_bittrex(limit_buy_order, limit_sell_order, fee, caplog):
+def test_update_with_binance(limit_buy_order, limit_sell_order, fee, caplog):
"""
On this test we will buy and sell a crypto currency.
@@ -102,7 +102,7 @@ def test_update_with_bittrex(limit_buy_order, limit_sell_order, fee, caplog):
open_date=arrow.utcnow().datetime,
fee_open=fee.return_value,
fee_close=fee.return_value,
- exchange='bittrex',
+ exchange='binance',
)
assert trade.open_order_id is None
assert trade.close_profit is None
@@ -142,7 +142,7 @@ def test_update_market_order(market_buy_order, market_sell_order, fee, caplog):
fee_open=fee.return_value,
fee_close=fee.return_value,
open_date=arrow.utcnow().datetime,
- exchange='bittrex',
+ exchange='binance',
)
trade.open_order_id = 'something'
@@ -177,7 +177,7 @@ def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee):
amount=5,
fee_open=fee.return_value,
fee_close=fee.return_value,
- exchange='bittrex',
+ exchange='binance',
)
trade.open_order_id = 'something'
@@ -205,7 +205,7 @@ def test_trade_close(limit_buy_order, limit_sell_order, fee):
fee_open=fee.return_value,
fee_close=fee.return_value,
open_date=arrow.Arrow(2020, 2, 1, 15, 5, 1).datetime,
- exchange='bittrex',
+ exchange='binance',
)
assert trade.close_profit is None
assert trade.close_date is None
@@ -233,7 +233,7 @@ def test_calc_close_trade_price_exception(limit_buy_order, fee):
amount=5,
fee_open=fee.return_value,
fee_close=fee.return_value,
- exchange='bittrex',
+ exchange='binance',
)
trade.open_order_id = 'something'
@@ -250,7 +250,7 @@ def test_update_open_order(limit_buy_order):
amount=5,
fee_open=0.1,
fee_close=0.1,
- exchange='bittrex',
+ exchange='binance',
)
assert trade.open_order_id is None
@@ -274,7 +274,7 @@ def test_update_invalid_order(limit_buy_order):
open_rate=0.001,
fee_open=0.1,
fee_close=0.1,
- exchange='bittrex',
+ exchange='binance',
)
limit_buy_order['type'] = 'invalid'
with pytest.raises(ValueError, match=r'Unknown order type'):
@@ -290,7 +290,7 @@ def test_calc_open_trade_value(limit_buy_order, fee):
open_rate=0.00001099,
fee_open=fee.return_value,
fee_close=fee.return_value,
- exchange='bittrex',
+ exchange='binance',
)
trade.open_order_id = 'open_trade'
trade.update(limit_buy_order) # Buy @ 0.00001099
@@ -311,7 +311,7 @@ def test_calc_close_trade_price(limit_buy_order, limit_sell_order, fee):
open_rate=0.00001099,
fee_open=fee.return_value,
fee_close=fee.return_value,
- exchange='bittrex',
+ exchange='binance',
)
trade.open_order_id = 'close_trade'
trade.update(limit_buy_order) # Buy @ 0.00001099
@@ -336,7 +336,7 @@ def test_calc_profit(limit_buy_order, limit_sell_order, fee):
open_rate=0.00001099,
fee_open=fee.return_value,
fee_close=fee.return_value,
- exchange='bittrex',
+ exchange='binance',
)
trade.open_order_id = 'something'
trade.update(limit_buy_order) # Buy @ 0.00001099
@@ -370,7 +370,7 @@ def test_calc_profit_ratio(limit_buy_order, limit_sell_order, fee):
open_rate=0.00001099,
fee_open=fee.return_value,
fee_close=fee.return_value,
- exchange='bittrex',
+ exchange='binance',
)
trade.open_order_id = 'something'
trade.update(limit_buy_order) # Buy @ 0.00001099
@@ -388,6 +388,9 @@ def test_calc_profit_ratio(limit_buy_order, limit_sell_order, fee):
# Test with a custom fee rate on the close trade
assert trade.calc_profit_ratio(fee=0.003) == 0.06147824
+ trade.open_trade_value = 0.0
+ assert trade.calc_profit_ratio(fee=0.003) == 0.0
+
@pytest.mark.usefixtures("init_persistence")
def test_clean_dry_run_db(default_conf, fee):
@@ -400,7 +403,7 @@ def test_clean_dry_run_db(default_conf, fee):
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
- exchange='bittrex',
+ exchange='binance',
open_order_id='dry_run_buy_12345'
)
Trade.query.session.add(trade)
@@ -412,7 +415,7 @@ def test_clean_dry_run_db(default_conf, fee):
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
- exchange='bittrex',
+ exchange='binance',
open_order_id='dry_run_sell_12345'
)
Trade.query.session.add(trade)
@@ -425,7 +428,7 @@ def test_clean_dry_run_db(default_conf, fee):
fee_open=fee.return_value,
fee_close=fee.return_value,
open_rate=0.123,
- exchange='bittrex',
+ exchange='binance',
open_order_id='prod_buy_12345'
)
Trade.query.session.add(trade)
@@ -463,7 +466,7 @@ def test_migrate_old(mocker, default_conf, fee):
);"""
insert_table_old = """INSERT INTO trades (exchange, pair, is_open, open_order_id, fee,
open_rate, stake_amount, amount, open_date)
- VALUES ('BITTREX', 'BTC_ETC', 1, '123123', {fee},
+ VALUES ('binance', 'BTC_ETC', 1, '123123', {fee},
0.00258580, {stake}, {amount},
'2017-11-28 12:44:24.000000')
""".format(fee=fee.return_value,
@@ -472,7 +475,7 @@ def test_migrate_old(mocker, default_conf, fee):
)
insert_table_old2 = """INSERT INTO trades (exchange, pair, is_open, fee,
open_rate, close_rate, stake_amount, amount, open_date)
- VALUES ('BITTREX', 'BTC_ETC', 0, {fee},
+ VALUES ('binance', 'BTC_ETC', 0, {fee},
0.00258580, 0.00268580, {stake}, {amount},
'2017-11-28 12:44:24.000000')
""".format(fee=fee.return_value,
@@ -500,7 +503,7 @@ def test_migrate_old(mocker, default_conf, fee):
assert trade.amount_requested == amount
assert trade.stake_amount == default_conf.get("stake_amount")
assert trade.pair == "ETC/BTC"
- assert trade.exchange == "bittrex"
+ assert trade.exchange == "binance"
assert trade.max_rate == 0.0
assert trade.stop_loss == 0.0
assert trade.initial_stop_loss == 0.0
@@ -624,6 +627,63 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
assert orders[1].order_id == 'stop_order_id222'
assert orders[1].ft_order_side == 'stoploss'
+ caplog.clear()
+ # Drop latest column
+ engine.execute("alter table orders rename to orders_bak")
+ inspector = inspect(engine)
+
+ for index in inspector.get_indexes('orders_bak'):
+ engine.execute(f"drop index {index['name']}")
+ # Recreate table
+ engine.execute("""
+ CREATE TABLE orders (
+ id INTEGER NOT NULL,
+ ft_trade_id INTEGER,
+ ft_order_side VARCHAR NOT NULL,
+ ft_pair VARCHAR NOT NULL,
+ ft_is_open BOOLEAN NOT NULL,
+ order_id VARCHAR NOT NULL,
+ status VARCHAR,
+ symbol VARCHAR,
+ order_type VARCHAR,
+ side VARCHAR,
+ price FLOAT,
+ amount FLOAT,
+ filled FLOAT,
+ remaining FLOAT,
+ cost FLOAT,
+ order_date DATETIME,
+ order_filled_date DATETIME,
+ order_update_date DATETIME,
+ PRIMARY KEY (id),
+ CONSTRAINT _order_pair_order_id UNIQUE (ft_pair, order_id),
+ FOREIGN KEY(ft_trade_id) REFERENCES trades (id)
+ )
+ """)
+
+ engine.execute("""
+ insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status,
+ symbol, order_type, side, price, amount, filled, remaining, cost, order_date,
+ order_filled_date, order_update_date)
+ select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status,
+ symbol, order_type, side, price, amount, filled, remaining, cost, order_date,
+ order_filled_date, order_update_date
+ from orders_bak
+ """)
+
+ # Run init to test migration
+ init_db(default_conf['db_url'], default_conf['dry_run'])
+
+ assert log_has("trying orders_bak1", caplog)
+
+ orders = Order.query.all()
+ assert len(orders) == 2
+ assert orders[0].order_id == 'buy_order'
+ assert orders[0].ft_order_side == 'buy'
+
+ assert orders[1].order_id == 'stop_order_id222'
+ assert orders[1].ft_order_side == 'stoploss'
+
def test_migrate_mid_state(mocker, default_conf, fee, caplog):
"""
@@ -694,7 +754,7 @@ def test_adjust_stop_loss(fee):
amount=5,
fee_open=fee.return_value,
fee_close=fee.return_value,
- exchange='bittrex',
+ exchange='binance',
open_rate=1,
max_rate=1,
)
@@ -746,7 +806,7 @@ def test_adjust_min_max_rates(fee):
amount=5,
fee_open=fee.return_value,
fee_close=fee.return_value,
- exchange='bittrex',
+ exchange='binance',
open_rate=1,
)
@@ -771,11 +831,16 @@ def test_adjust_min_max_rates(fee):
@pytest.mark.usefixtures("init_persistence")
-def test_get_open(fee):
+@pytest.mark.parametrize('use_db', [True, False])
+def test_get_open(fee, use_db):
+ Trade.use_db = use_db
+ Trade.reset_trades()
- create_mock_trades(fee)
+ create_mock_trades(fee, use_db)
assert len(Trade.get_open_trades()) == 4
+ Trade.use_db = True
+
@pytest.mark.usefixtures("init_persistence")
def test_to_json(default_conf, fee):
@@ -790,7 +855,7 @@ def test_to_json(default_conf, fee):
fee_close=fee.return_value,
open_date=arrow.utcnow().shift(hours=-2).datetime,
open_rate=0.123,
- exchange='bittrex',
+ exchange='binance',
open_order_id='dry_run_buy_12345'
)
result = trade.to_json()
@@ -799,11 +864,9 @@ def test_to_json(default_conf, fee):
assert result == {'trade_id': None,
'pair': 'ETH/BTC',
'is_open': None,
- 'open_date_hum': '2 hours ago',
'open_date': trade.open_date.strftime("%Y-%m-%d %H:%M:%S"),
'open_timestamp': int(trade.open_date.timestamp() * 1000),
'open_order_id': 'dry_run_buy_12345',
- 'close_date_hum': None,
'close_date': None,
'close_timestamp': None,
'open_rate': 0.123,
@@ -843,7 +906,7 @@ def test_to_json(default_conf, fee):
'max_rate': None,
'strategy': None,
'timeframe': None,
- 'exchange': 'bittrex',
+ 'exchange': 'binance',
}
# Simulate dry_run entries
@@ -858,17 +921,15 @@ def test_to_json(default_conf, fee):
close_date=arrow.utcnow().shift(hours=-1).datetime,
open_rate=0.123,
close_rate=0.125,
- exchange='bittrex',
+ exchange='binance',
)
result = trade.to_json()
assert isinstance(result, dict)
assert result == {'trade_id': None,
'pair': 'XRP/BTC',
- 'open_date_hum': '2 hours ago',
'open_date': trade.open_date.strftime("%Y-%m-%d %H:%M:%S"),
'open_timestamp': int(trade.open_date.timestamp() * 1000),
- 'close_date_hum': 'an hour ago',
'close_date': trade.close_date.strftime("%Y-%m-%d %H:%M:%S"),
'close_timestamp': int(trade.close_date.timestamp() * 1000),
'open_rate': 0.123,
@@ -910,7 +971,7 @@ def test_to_json(default_conf, fee):
'sell_order_status': None,
'strategy': None,
'timeframe': None,
- 'exchange': 'bittrex',
+ 'exchange': 'binance',
}
@@ -923,7 +984,7 @@ def test_stoploss_reinitialization(default_conf, fee):
open_date=arrow.utcnow().shift(hours=-2).datetime,
amount=10,
fee_close=fee.return_value,
- exchange='bittrex',
+ exchange='binance',
open_rate=1,
max_rate=1,
)
@@ -982,7 +1043,7 @@ def test_update_fee(fee):
open_date=arrow.utcnow().shift(hours=-2).datetime,
amount=10,
fee_close=fee.return_value,
- exchange='bittrex',
+ exchange='binance',
open_rate=1,
max_rate=1,
)
@@ -1021,7 +1082,7 @@ def test_fee_updated(fee):
open_date=arrow.utcnow().shift(hours=-2).datetime,
amount=10,
fee_close=fee.return_value,
- exchange='bittrex',
+ exchange='binance',
open_rate=1,
max_rate=1,
)
@@ -1084,6 +1145,13 @@ def test_get_trades_proxy(fee, use_db):
Trade.use_db = True
+def test_get_trades_backtest():
+ Trade.use_db = False
+ with pytest.raises(NotImplementedError, match=r"`Trade.get_trades\(\)` not .*"):
+ Trade.get_trades([])
+ Trade.use_db = True
+
+
@pytest.mark.usefixtures("init_persistence")
def test_get_overall_performance(fee):
@@ -1217,11 +1285,24 @@ def test_Trade_object_idem():
trade = vars(Trade)
localtrade = vars(LocalTrade)
+ excludes = (
+ 'delete',
+ 'session',
+ 'query',
+ 'open_date',
+ 'get_best_pair',
+ 'get_overall_performance',
+ 'total_open_trades_stakes',
+ 'get_sold_trades_without_assigned_fees',
+ 'get_open_trades_without_assigned_fees',
+ 'get_open_order_trades',
+ 'get_trades',
+ )
+
# Parent (LocalTrade) should have the same attributes
for item in trade:
# Exclude private attributes and open_date (as it's not assigned a default)
- if (not item.startswith('_')
- and item not in ('delete', 'session', 'query', 'open_date')):
+ if (not item.startswith('_') and item not in excludes):
assert item in localtrade
# Fails if only a column is added without corresponding parent field
diff --git a/tests/test_plotting.py b/tests/test_plotting.py
index 1752f9b94..a22c8c681 100644
--- a/tests/test_plotting.py
+++ b/tests/test_plotting.py
@@ -331,13 +331,13 @@ def test_generate_profit_graph(testdatadir):
trades = trades[trades['pair'].isin(pairs)]
- fig = generate_profit_graph(pairs, data, trades, timeframe="5m")
+ fig = generate_profit_graph(pairs, data, trades, timeframe="5m", stake_currency='BTC')
assert isinstance(fig, go.Figure)
assert fig.layout.title.text == "Freqtrade Profit plot"
assert fig.layout.yaxis.title.text == "Price"
- assert fig.layout.yaxis2.title.text == "Profit"
- assert fig.layout.yaxis3.title.text == "Profit"
+ assert fig.layout.yaxis2.title.text == "Profit BTC"
+ assert fig.layout.yaxis3.title.text == "Profit BTC"
figure = fig.layout.figure
assert len(figure.data) == 5
@@ -356,7 +356,8 @@ def test_generate_profit_graph(testdatadir):
with pytest.raises(OperationalException, match=r"No trades found.*"):
# Pair cannot be empty - so it's an empty dataframe.
- generate_profit_graph(pairs, data, trades.loc[trades['pair'].isnull()], timeframe="5m")
+ generate_profit_graph(pairs, data, trades.loc[trades['pair'].isnull()], timeframe="5m",
+ stake_currency='BTC')
def test_start_plot_dataframe(mocker):
diff --git a/tests/test_wallets.py b/tests/test_wallets.py
index b7aead0c4..ff303e2ec 100644
--- a/tests/test_wallets.py
+++ b/tests/test_wallets.py
@@ -1,7 +1,12 @@
# pragma pylint: disable=missing-docstring
+from copy import deepcopy
from unittest.mock import MagicMock
-from tests.conftest import get_patched_freqtradebot
+import pytest
+
+from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
+from freqtrade.exceptions import DependencyException
+from tests.conftest import get_patched_freqtradebot, patch_wallet
def test_sync_wallet_at_boot(mocker, default_conf):
@@ -106,3 +111,62 @@ def test_sync_wallet_missing_data(mocker, default_conf):
assert freqtrade.wallets._wallets['GAS'].used is None
assert freqtrade.wallets._wallets['GAS'].total == 0.260739
assert freqtrade.wallets.get_free('GAS') == 0.260739
+
+
+def test_get_trade_stake_amount_no_stake_amount(default_conf, mocker) -> None:
+ patch_wallet(mocker, free=default_conf['stake_amount'] * 0.5)
+ freqtrade = get_patched_freqtradebot(mocker, default_conf)
+
+ with pytest.raises(DependencyException, match=r'.*stake amount.*'):
+ freqtrade.wallets.get_trade_stake_amount('ETH/BTC')
+
+
+@pytest.mark.parametrize("balance_ratio,result1,result2", [
+ (1, 50, 66.66666),
+ (0.99, 49.5, 66.0),
+ (0.50, 25, 33.3333),
+])
+def test_get_trade_stake_amount_unlimited_amount(default_conf, ticker, balance_ratio, result1,
+ result2, limit_buy_order_open,
+ fee, mocker) -> None:
+ mocker.patch.multiple(
+ 'freqtrade.exchange.Exchange',
+ fetch_ticker=ticker,
+ buy=MagicMock(return_value=limit_buy_order_open),
+ get_fee=fee
+ )
+
+ conf = deepcopy(default_conf)
+ conf['stake_amount'] = UNLIMITED_STAKE_AMOUNT
+ conf['dry_run_wallet'] = 100
+ conf['max_open_trades'] = 2
+ conf['tradable_balance_ratio'] = balance_ratio
+
+ freqtrade = get_patched_freqtradebot(mocker, conf)
+
+ # no open trades, order amount should be 'balance / max_open_trades'
+ result = freqtrade.wallets.get_trade_stake_amount('ETH/USDT')
+ assert result == result1
+
+ # create one trade, order amount should be 'balance / (max_open_trades - num_open_trades)'
+ freqtrade.execute_buy('ETH/USDT', result)
+
+ result = freqtrade.wallets.get_trade_stake_amount('LTC/USDT')
+ assert result == result1
+
+ # create 2 trades, order amount should be None
+ freqtrade.execute_buy('LTC/BTC', result)
+
+ result = freqtrade.wallets.get_trade_stake_amount('XRP/USDT')
+ assert result == 0
+
+ freqtrade.config['max_open_trades'] = 3
+ freqtrade.config['dry_run_wallet'] = 200
+ freqtrade.wallets.start_cap = 200
+ result = freqtrade.wallets.get_trade_stake_amount('XRP/USDT')
+ assert round(result, 4) == round(result2, 4)
+
+ # set max_open_trades = None, so do not trade
+ freqtrade.config['max_open_trades'] = 0
+ result = freqtrade.wallets.get_trade_stake_amount('NEO/USDT')
+ assert result == 0
diff --git a/tests/testdata/hyperopt_results_SampleStrategy.pickle b/tests/testdata/hyperopt_results_SampleStrategy.pickle
new file mode 100644
index 000000000..2231de7bf
Binary files /dev/null and b/tests/testdata/hyperopt_results_SampleStrategy.pickle differ
diff --git a/tests/testdata/strategy_SampleStrategy.fthypt b/tests/testdata/strategy_SampleStrategy.fthypt
new file mode 100644
index 000000000..6dc2b9ab1
--- /dev/null
+++ b/tests/testdata/strategy_SampleStrategy.fthypt
@@ -0,0 +1,5 @@
+{"loss":100000,"params_dict":{"mfi-value":"20","fastd-value":"21","adx-value":"26","rsi-value":"23","mfi-enabled":true,"fastd-enabled":false,"adx-enabled":false,"rsi-enabled":true,"trigger":"sar_reversal","sell-mfi-value":"97","sell-fastd-value":"85","sell-adx-value":"55","sell-rsi-value":"76","sell-mfi-enabled":true,"sell-fastd-enabled":false,"sell-adx-enabled":true,"sell-rsi-enabled":true,"sell-trigger":"sell-bb_upper","roi_t1":"34","roi_t2":"28","roi_t3":"32","roi_p1":0.031,"roi_p2":0.033,"roi_p3":0.146,"stoploss":-0.05},"params_details":{"buy":{"mfi-value":"20","fastd-value":"21","adx-value":"26","rsi-value":"23","mfi-enabled":true,"fastd-enabled":false,"adx-enabled":false,"rsi-enabled":true,"trigger":"sar_reversal"},"sell":{"sell-mfi-value":"97","sell-fastd-value":"85","sell-adx-value":"55","sell-rsi-value":"76","sell-mfi-enabled":true,"sell-fastd-enabled":false,"sell-adx-enabled":true,"sell-rsi-enabled":true,"sell-trigger":"sell-bb_upper"},"roi":"{0: 0.21, 32: 0.064, 60: 0.031, 94: 0}","stoploss":{"stoploss":-0.05}},"params_not_optimized":{"buy":{},"sell":{}},"results_metrics":{"trades":[],"locks":[],"best_pair":{"key":"ETH/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},"worst_pair":{"key":"ETH/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},"results_per_pair":[{"key":"ETH/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"LTC/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"ETC/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"XLM/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"TRX/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"ADA/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"sell_reason_summary":[],"left_open_trades":[{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"total_trades":0,"total_volume":0.0,"avg_stake_amount":0,"profit_mean":0,"profit_median":0,"profit_total":0.0,"profit_total_abs":0,"backtest_start":"2018-01-10 07:25:00","backtest_start_ts":1515569100000,"backtest_end":"2018-01-30 04:45:00","backtest_end_ts":1517287500000,"backtest_days":19,"backtest_run_start_ts":1620793107,"backtest_run_end_ts":1620793107,"trades_per_day":0.0,"market_change":0,"pairlist":["ETH/BTC","LTC/BTC","ETC/BTC","XLM/BTC","TRX/BTC","ADA/BTC"],"stake_amount":0.05,"stake_currency":"BTC","stake_currency_decimals":8,"starting_balance":1000,"dry_run_wallet":1000,"final_balance":1000,"max_open_trades":3,"max_open_trades_setting":3,"timeframe":"5m","timerange":"","enable_protections":false,"strategy_name":"SampleStrategy","stoploss":-0.1,"trailing_stop":false,"trailing_stop_positive":null,"trailing_stop_positive_offset":0.0,"trailing_only_offset_is_reached":false,"use_custom_stoploss":false,"minimal_roi":{"60":0.01,"30":0.02,"0":0.04},"use_sell_signal":true,"sell_profit_only":false,"sell_profit_offset":0.0,"ignore_roi_if_buy_signal":false,"backtest_best_day":0,"backtest_worst_day":0,"backtest_best_day_abs":0,"backtest_worst_day_abs":0,"winning_days":0,"draw_days":0,"losing_days":0,"wins":0,"losses":0,"draws":0,"holding_avg":"0:00:00","winner_holding_avg":"0:00:00","loser_holding_avg":"0:00:00","max_drawdown":0.0,"max_drawdown_abs":0.0,"max_drawdown_low":0.0,"max_drawdown_high":0.0,"drawdown_start":"1970-01-01 00:00:00+00:00","drawdown_start_ts":0,"drawdown_end":"1970-01-01 00:00:00+00:00","drawdown_end_ts":0,"csum_min":0,"csum_max":0},"results_explanation":" 0 trades. 0/0/0 Wins/Draws/Losses. Avg profit 0.00%. Median profit 0.00%. Total profit 0.00000000 BTC ( 0.00\u03A3%). Avg duration 0:00:00 min.","total_profit":0.0,"current_epoch":1,"is_initial_point":true,"is_best":false}
+{"loss":100000,"params_dict":{"mfi-value":"14","fastd-value":"43","adx-value":"30","rsi-value":"24","mfi-enabled":true,"fastd-enabled":true,"adx-enabled":false,"rsi-enabled":true,"trigger":"sar_reversal","sell-mfi-value":"97","sell-fastd-value":"71","sell-adx-value":"82","sell-rsi-value":"99","sell-mfi-enabled":false,"sell-fastd-enabled":false,"sell-adx-enabled":false,"sell-rsi-enabled":true,"sell-trigger":"sell-bb_upper","roi_t1":"84","roi_t2":"35","roi_t3":"19","roi_p1":0.024,"roi_p2":0.022,"roi_p3":0.061,"stoploss":-0.083},"params_details":{"buy":{"mfi-value":"14","fastd-value":"43","adx-value":"30","rsi-value":"24","mfi-enabled":true,"fastd-enabled":true,"adx-enabled":false,"rsi-enabled":true,"trigger":"sar_reversal"},"sell":{"sell-mfi-value":"97","sell-fastd-value":"71","sell-adx-value":"82","sell-rsi-value":"99","sell-mfi-enabled":false,"sell-fastd-enabled":false,"sell-adx-enabled":false,"sell-rsi-enabled":true,"sell-trigger":"sell-bb_upper"},"roi":"{0: 0.107, 19: 0.046, 54: 0.024, 138: 0}","stoploss":{"stoploss":-0.083}},"params_not_optimized":{"buy":{},"sell":{}},"results_metrics":{"trades":[],"locks":[],"best_pair":{"key":"ETH/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},"worst_pair":{"key":"ETH/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},"results_per_pair":[{"key":"ETH/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"LTC/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"ETC/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"XLM/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"TRX/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"ADA/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"sell_reason_summary":[],"left_open_trades":[{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0,"profit_sum_pct":0.0,"profit_total_abs":0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"total_trades":0,"total_volume":0.0,"avg_stake_amount":0,"profit_mean":0,"profit_median":0,"profit_total":0.0,"profit_total_abs":0,"backtest_start":"2018-01-10 07:25:00","backtest_start_ts":1515569100000,"backtest_end":"2018-01-30 04:45:00","backtest_end_ts":1517287500000,"backtest_days":19,"backtest_run_start_ts":1620793107,"backtest_run_end_ts":1620793108,"trades_per_day":0.0,"market_change":0,"pairlist":["ETH/BTC","LTC/BTC","ETC/BTC","XLM/BTC","TRX/BTC","ADA/BTC"],"stake_amount":0.05,"stake_currency":"BTC","stake_currency_decimals":8,"starting_balance":1000,"dry_run_wallet":1000,"final_balance":1000,"max_open_trades":3,"max_open_trades_setting":3,"timeframe":"5m","timerange":"","enable_protections":false,"strategy_name":"SampleStrategy","stoploss":-0.1,"trailing_stop":false,"trailing_stop_positive":null,"trailing_stop_positive_offset":0.0,"trailing_only_offset_is_reached":false,"use_custom_stoploss":false,"minimal_roi":{"60":0.01,"30":0.02,"0":0.04},"use_sell_signal":true,"sell_profit_only":false,"sell_profit_offset":0.0,"ignore_roi_if_buy_signal":false,"backtest_best_day":0,"backtest_worst_day":0,"backtest_best_day_abs":0,"backtest_worst_day_abs":0,"winning_days":0,"draw_days":0,"losing_days":0,"wins":0,"losses":0,"draws":0,"holding_avg":"0:00:00","winner_holding_avg":"0:00:00","loser_holding_avg":"0:00:00","max_drawdown":0.0,"max_drawdown_abs":0.0,"max_drawdown_low":0.0,"max_drawdown_high":0.0,"drawdown_start":"1970-01-01 00:00:00+00:00","drawdown_start_ts":0,"drawdown_end":"1970-01-01 00:00:00+00:00","drawdown_end_ts":0,"csum_min":0,"csum_max":0},"results_explanation":" 0 trades. 0/0/0 Wins/Draws/Losses. Avg profit 0.00%. Median profit 0.00%. Total profit 0.00000000 BTC ( 0.00\u03A3%). Avg duration 0:00:00 min.","total_profit":0.0,"current_epoch":2,"is_initial_point":true,"is_best":false}
+{"loss":2.183447401951895,"params_dict":{"mfi-value":"14","fastd-value":"15","adx-value":"40","rsi-value":"36","mfi-enabled":false,"fastd-enabled":true,"adx-enabled":false,"rsi-enabled":false,"trigger":"sar_reversal","sell-mfi-value":"92","sell-fastd-value":"84","sell-adx-value":"61","sell-rsi-value":"61","sell-mfi-enabled":true,"sell-fastd-enabled":true,"sell-adx-enabled":true,"sell-rsi-enabled":true,"sell-trigger":"sell-bb_upper","roi_t1":"68","roi_t2":"41","roi_t3":"21","roi_p1":0.015,"roi_p2":0.064,"roi_p3":0.126,"stoploss":-0.024},"params_details":{"buy":{"mfi-value":"14","fastd-value":"15","adx-value":"40","rsi-value":"36","mfi-enabled":false,"fastd-enabled":true,"adx-enabled":false,"rsi-enabled":false,"trigger":"sar_reversal"},"sell":{"sell-mfi-value":"92","sell-fastd-value":"84","sell-adx-value":"61","sell-rsi-value":"61","sell-mfi-enabled":true,"sell-fastd-enabled":true,"sell-adx-enabled":true,"sell-rsi-enabled":true,"sell-trigger":"sell-bb_upper"},"roi":"{0: 0.20500000000000002, 21: 0.079, 62: 0.015, 130: 0}","stoploss":{"stoploss":-0.024}},"params_not_optimized":{"buy":{},"sell":{}},"results_metrics":{"trades":[{"pair":"LTC/BTC","stake_amount":0.05,"amount":2.94115571,"open_date":"2018-01-11 11:40:00+00:00","close_date":"2018-01-11 19:40:00+00:00","open_rate":0.01700012,"close_rate":0.017119538805820372,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":480,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.01659211712,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.01659211712,"stop_loss_ratio":-0.024,"min_rate":0.01689809,"max_rate":0.0171462,"is_open":false,"open_timestamp":1515670800000.0,"close_timestamp":1515699600000.0},{"pair":"ETH/BTC","stake_amount":0.05,"amount":0.57407318,"open_date":"2018-01-12 11:05:00+00:00","close_date":"2018-01-12 12:30:00+00:00","open_rate":0.08709691,"close_rate":0.08901977203712995,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":85,"profit_ratio":0.01494768,"profit_abs":0.00075,"sell_reason":"roi","initial_stop_loss_abs":0.08500658416,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.08500658416,"stop_loss_ratio":-0.024,"min_rate":0.08702974000000001,"max_rate":0.08929248000000001,"is_open":false,"open_timestamp":1515755100000.0,"close_timestamp":1515760200000.0},{"pair":"LTC/BTC","stake_amount":0.05,"amount":2.93166236,"open_date":"2018-01-12 03:30:00+00:00","close_date":"2018-01-12 13:05:00+00:00","open_rate":0.01705517,"close_rate":0.01717497550928249,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":575,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.016645845920000003,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.016645845920000003,"stop_loss_ratio":-0.024,"min_rate":0.0169841,"max_rate":0.01719135,"is_open":false,"open_timestamp":1515727800000.0,"close_timestamp":1515762300000.0},{"pair":"LTC/BTC","stake_amount":0.05,"amount":2.96876855,"open_date":"2018-01-13 03:50:00+00:00","close_date":"2018-01-13 06:05:00+00:00","open_rate":0.016842,"close_rate":0.016960308078273957,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":135,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.016437792,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.016437792,"stop_loss_ratio":-0.024,"min_rate":0.016836999999999998,"max_rate":0.017,"is_open":false,"open_timestamp":1515815400000.0,"close_timestamp":1515823500000.0},{"pair":"ETH/BTC","stake_amount":0.05,"amount":0.53163205,"open_date":"2018-01-13 13:25:00+00:00","close_date":"2018-01-13 15:35:00+00:00","open_rate":0.09405001,"close_rate":0.09471067238835926,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":130,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.09179280976,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.09179280976,"stop_loss_ratio":-0.024,"min_rate":0.09369894000000001,"max_rate":0.09479997,"is_open":false,"open_timestamp":1515849900000.0,"close_timestamp":1515857700000.0},{"pair":"ETC/BTC","stake_amount":0.05,"amount":19.23816853,"open_date":"2018-01-13 15:30:00+00:00","close_date":"2018-01-13 16:20:00+00:00","open_rate":0.0025989999999999997,"close_rate":0.0028232990466633217,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":50,"profit_ratio":0.07872446,"profit_abs":0.00395,"sell_reason":"roi","initial_stop_loss_abs":0.002536624,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.002536624,"stop_loss_ratio":-0.024,"min_rate":0.00259525,"max_rate":0.0028288700000000003,"is_open":false,"open_timestamp":1515857400000.0,"close_timestamp":1515860400000.0},{"pair":"TRX/BTC","stake_amount":0.05,"amount":492.80504632,"open_date":"2018-01-14 21:35:00+00:00","close_date":"2018-01-14 23:15:00+00:00","open_rate":0.00010146000000000001,"close_rate":0.00010369995985950828,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":100,"profit_ratio":0.01494768,"profit_abs":0.00075,"sell_reason":"roi","initial_stop_loss_abs":9.902496e-05,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":9.902496e-05,"stop_loss_ratio":-0.024,"min_rate":0.0001012,"max_rate":0.00010414,"is_open":false,"open_timestamp":1515965700000.0,"close_timestamp":1515971700000.0},{"pair":"LTC/BTC","stake_amount":0.05,"amount":2.92398174,"open_date":"2018-01-15 12:45:00+00:00","close_date":"2018-01-15 21:05:00+00:00","open_rate":0.01709997,"close_rate":0.01722009021073758,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":500,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.016689570719999998,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.016689570719999998,"stop_loss_ratio":-0.024,"min_rate":0.01694,"max_rate":0.01725,"is_open":false,"open_timestamp":1516020300000.0,"close_timestamp":1516050300000.0},{"pair":"XLM/BTC","stake_amount":0.05,"amount":1111.60515785,"open_date":"2018-01-15 19:50:00+00:00","close_date":"2018-01-15 23:45:00+00:00","open_rate":4.4980000000000006e-05,"close_rate":4.390048e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":235,"profit_ratio":-0.03080817,"profit_abs":-0.0015458,"sell_reason":"stop_loss","initial_stop_loss_abs":4.390048e-05,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":4.390048e-05,"stop_loss_ratio":-0.024,"min_rate":4.409e-05,"max_rate":4.502e-05,"is_open":false,"open_timestamp":1516045800000.0,"close_timestamp":1516059900000.0},{"pair":"TRX/BTC","stake_amount":0.05,"amount":519.80455349,"open_date":"2018-01-21 03:55:00+00:00","close_date":"2018-01-21 04:05:00+00:00","open_rate":9.619e-05,"close_rate":9.388144e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":10,"profit_ratio":-0.03080817,"profit_abs":-0.0015458,"sell_reason":"stop_loss","initial_stop_loss_abs":9.388144e-05,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":9.388144e-05,"stop_loss_ratio":-0.024,"min_rate":9.568e-05,"max_rate":9.673e-05,"is_open":false,"open_timestamp":1516506900000.0,"close_timestamp":1516507500000.0},{"pair":"LTC/BTC","stake_amount":0.05,"amount":3.029754,"open_date":"2018-01-20 22:15:00+00:00","close_date":"2018-01-21 07:45:00+00:00","open_rate":0.01650299,"close_rate":0.016106918239999997,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":570,"profit_ratio":-0.03080817,"profit_abs":-0.0015458,"sell_reason":"stop_loss","initial_stop_loss_abs":0.016106918239999997,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.016106918239999997,"stop_loss_ratio":-0.024,"min_rate":0.0162468,"max_rate":0.01663179,"is_open":false,"open_timestamp":1516486500000.0,"close_timestamp":1516520700000.0},{"pair":"ETC/BTC","stake_amount":0.05,"amount":18.75461832,"open_date":"2018-01-21 13:00:00+00:00","close_date":"2018-01-21 16:25:00+00:00","open_rate":0.00266601,"close_rate":0.00260202576,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":205,"profit_ratio":-0.03080817,"profit_abs":-0.0015458,"sell_reason":"stop_loss","initial_stop_loss_abs":0.00260202576,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.00260202576,"stop_loss_ratio":-0.024,"min_rate":0.0026290800000000002,"max_rate":0.00269384,"is_open":false,"open_timestamp":1516539600000.0,"close_timestamp":1516551900000.0},{"pair":"TRX/BTC","stake_amount":0.05,"amount":552.18111541,"open_date":"2018-01-22 02:10:00+00:00","close_date":"2018-01-22 04:20:00+00:00","open_rate":9.055e-05,"close_rate":9.118607626693427e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":130,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":8.83768e-05,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":8.83768e-05,"stop_loss_ratio":-0.024,"min_rate":9.013e-05,"max_rate":9.197e-05,"is_open":false,"open_timestamp":1516587000000.0,"close_timestamp":1516594800000.0},{"pair":"LTC/BTC","stake_amount":0.05,"amount":2.99733237,"open_date":"2018-01-22 03:20:00+00:00","close_date":"2018-01-22 13:50:00+00:00","open_rate":0.0166815,"close_rate":0.016281143999999997,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":630,"profit_ratio":-0.03080817,"profit_abs":-0.0015458,"sell_reason":"stop_loss","initial_stop_loss_abs":0.016281143999999997,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.016281143999999997,"stop_loss_ratio":-0.024,"min_rate":0.01641443,"max_rate":0.016800000000000002,"is_open":false,"open_timestamp":1516591200000.0,"close_timestamp":1516629000000.0},{"pair":"TRX/BTC","stake_amount":0.05,"amount":503.52467271,"open_date":"2018-01-23 08:55:00+00:00","close_date":"2018-01-23 09:40:00+00:00","open_rate":9.93e-05,"close_rate":9.69168e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":45,"profit_ratio":-0.03080817,"profit_abs":-0.0015458,"sell_reason":"stop_loss","initial_stop_loss_abs":9.69168e-05,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":9.69168e-05,"stop_loss_ratio":-0.024,"min_rate":9.754e-05,"max_rate":0.00010025,"is_open":false,"open_timestamp":1516697700000.0,"close_timestamp":1516700400000.0},{"pair":"ETH/BTC","stake_amount":0.05,"amount":0.55148073,"open_date":"2018-01-24 02:10:00+00:00","close_date":"2018-01-24 04:40:00+00:00","open_rate":0.090665,"close_rate":0.09130188409433015,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":150,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.08848903999999999,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.08848903999999999,"stop_loss_ratio":-0.024,"min_rate":0.090665,"max_rate":0.09146000000000001,"is_open":false,"open_timestamp":1516759800000.0,"close_timestamp":1516768800000.0},{"pair":"ETC/BTC","stake_amount":0.05,"amount":19.10584639,"open_date":"2018-01-24 19:20:00+00:00","close_date":"2018-01-24 21:35:00+00:00","open_rate":0.002617,"close_rate":0.0026353833416959357,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":135,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.002554192,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.002554192,"stop_loss_ratio":-0.024,"min_rate":0.002617,"max_rate":0.00264999,"is_open":false,"open_timestamp":1516821600000.0,"close_timestamp":1516829700000.0},{"pair":"ETC/BTC","stake_amount":0.05,"amount":19.34602691,"open_date":"2018-01-25 14:35:00+00:00","close_date":"2018-01-25 16:35:00+00:00","open_rate":0.00258451,"close_rate":0.002641568926241846,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":120,"profit_ratio":0.01494768,"profit_abs":0.00075,"sell_reason":"roi","initial_stop_loss_abs":0.00252248176,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.00252248176,"stop_loss_ratio":-0.024,"min_rate":0.00258451,"max_rate":0.00264579,"is_open":false,"open_timestamp":1516890900000.0,"close_timestamp":1516898100000.0},{"pair":"LTC/BTC","stake_amount":0.05,"amount":3.11910295,"open_date":"2018-01-24 23:05:00+00:00","close_date":"2018-01-25 16:55:00+00:00","open_rate":0.01603025,"close_rate":0.016142855870546913,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":1070,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.015645523999999997,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.015645523999999997,"stop_loss_ratio":-0.024,"min_rate":0.015798760000000002,"max_rate":0.01617,"is_open":false,"open_timestamp":1516835100000.0,"close_timestamp":1516899300000.0},{"pair":"TRX/BTC","stake_amount":0.05,"amount":553.70985604,"open_date":"2018-01-26 19:30:00+00:00","close_date":"2018-01-26 23:30:00+00:00","open_rate":9.03e-05,"close_rate":9.093432012042147e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":240,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":8.813279999999999e-05,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":8.813279999999999e-05,"stop_loss_ratio":-0.024,"min_rate":8.961e-05,"max_rate":9.1e-05,"is_open":false,"open_timestamp":1516995000000.0,"close_timestamp":1517009400000.0},{"pair":"ETC/BTC","stake_amount":0.05,"amount":19.22929005,"open_date":"2018-01-26 21:15:00+00:00","close_date":"2018-01-28 03:50:00+00:00","open_rate":0.0026002,"close_rate":0.0026184653286502758,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":1835,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.0025377952,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.0025377952,"stop_loss_ratio":-0.024,"min_rate":0.00254702,"max_rate":0.00262797,"is_open":false,"open_timestamp":1517001300000.0,"close_timestamp":1517111400000.0},{"pair":"LTC/BTC","stake_amount":0.05,"amount":3.15677093,"open_date":"2018-01-27 22:05:00+00:00","close_date":"2018-01-28 10:45:00+00:00","open_rate":0.01583897,"close_rate":0.015950232207727046,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":760,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.01545883472,"initial_stop_loss_ratio":-0.024,"stop_loss_abs":0.01545883472,"stop_loss_ratio":-0.024,"min_rate":0.015700000000000002,"max_rate":0.01596521,"is_open":false,"open_timestamp":1517090700000.0,"close_timestamp":1517136300000.0}],"locks":[],"best_pair":{"key":"ETC/BTC","trades":5,"profit_mean":0.012572794000000002,"profit_mean_pct":1.2572794000000003,"profit_sum":0.06286397,"profit_sum_pct":6.29,"profit_total_abs":0.0031542000000000002,"profit_total":3.1542000000000002e-06,"profit_total_pct":0.0,"duration_avg":"7:49:00","wins":2,"draws":2,"losses":1},"worst_pair":{"key":"LTC/BTC","trades":8,"profit_mean":-0.0077020425,"profit_mean_pct":-0.77020425,"profit_sum":-0.06161634,"profit_sum_pct":-6.16,"profit_total_abs":-0.0030916,"profit_total":-3.0915999999999998e-06,"profit_total_pct":-0.0,"duration_avg":"9:50:00","wins":0,"draws":6,"losses":2},"results_per_pair":[{"key":"ETC/BTC","trades":5,"profit_mean":0.012572794000000002,"profit_mean_pct":1.2572794000000003,"profit_sum":0.06286397,"profit_sum_pct":6.29,"profit_total_abs":0.0031542000000000002,"profit_total":3.1542000000000002e-06,"profit_total_pct":0.0,"duration_avg":"7:49:00","wins":2,"draws":2,"losses":1},{"key":"ETH/BTC","trades":3,"profit_mean":0.00498256,"profit_mean_pct":0.498256,"profit_sum":0.01494768,"profit_sum_pct":1.49,"profit_total_abs":0.00075,"profit_total":7.5e-07,"profit_total_pct":0.0,"duration_avg":"2:02:00","wins":1,"draws":2,"losses":0},{"key":"ADA/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"XLM/BTC","trades":1,"profit_mean":-0.03080817,"profit_mean_pct":-3.080817,"profit_sum":-0.03080817,"profit_sum_pct":-3.08,"profit_total_abs":-0.0015458,"profit_total":-1.5457999999999999e-06,"profit_total_pct":-0.0,"duration_avg":"3:55:00","wins":0,"draws":0,"losses":1},{"key":"TRX/BTC","trades":5,"profit_mean":-0.009333732,"profit_mean_pct":-0.9333732000000001,"profit_sum":-0.04666866,"profit_sum_pct":-4.67,"profit_total_abs":-0.0023416,"profit_total":-2.3416e-06,"profit_total_pct":-0.0,"duration_avg":"1:45:00","wins":1,"draws":2,"losses":2},{"key":"LTC/BTC","trades":8,"profit_mean":-0.0077020425,"profit_mean_pct":-0.77020425,"profit_sum":-0.06161634,"profit_sum_pct":-6.16,"profit_total_abs":-0.0030916,"profit_total":-3.0915999999999998e-06,"profit_total_pct":-0.0,"duration_avg":"9:50:00","wins":0,"draws":6,"losses":2},{"key":"TOTAL","trades":22,"profit_mean":-0.0027855236363636365,"profit_mean_pct":-0.27855236363636365,"profit_sum":-0.06128152,"profit_sum_pct":-6.13,"profit_total_abs":-0.0030748,"profit_total":-3.0747999999999998e-06,"profit_total_pct":-0.0,"duration_avg":"6:12:00","wins":4,"draws":12,"losses":6}],"sell_reason_summary":[{"sell_reason":"roi","trades":16,"wins":4,"draws":12,"losses":0,"profit_mean":0.00772296875,"profit_mean_pct":0.77,"profit_sum":0.1235675,"profit_sum_pct":12.36,"profit_total_abs":0.006200000000000001,"profit_total":0.041189166666666666,"profit_total_pct":4.12},{"sell_reason":"stop_loss","trades":6,"wins":0,"draws":0,"losses":6,"profit_mean":-0.03080817,"profit_mean_pct":-3.08,"profit_sum":-0.18484902,"profit_sum_pct":-18.48,"profit_total_abs":-0.0092748,"profit_total":-0.06161634,"profit_total_pct":-6.16}],"left_open_trades":[{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"total_trades":22,"total_volume":1.1000000000000003,"avg_stake_amount":0.05000000000000002,"profit_mean":-0.0027855236363636365,"profit_median":0.0,"profit_total":-3.0747999999999998e-06,"profit_total_abs":-0.0030748,"backtest_start":"2018-01-10 07:25:00","backtest_start_ts":1515569100000,"backtest_end":"2018-01-30 04:45:00","backtest_end_ts":1517287500000,"backtest_days":19,"backtest_run_start_ts":1620793107,"backtest_run_end_ts":1620793108,"trades_per_day":1.16,"market_change":0,"pairlist":["ETH/BTC","LTC/BTC","ETC/BTC","XLM/BTC","TRX/BTC","ADA/BTC"],"stake_amount":0.05,"stake_currency":"BTC","stake_currency_decimals":8,"starting_balance":1000,"dry_run_wallet":1000,"final_balance":999.9969252,"max_open_trades":3,"max_open_trades_setting":3,"timeframe":"5m","timerange":"","enable_protections":false,"strategy_name":"SampleStrategy","stoploss":-0.1,"trailing_stop":false,"trailing_stop_positive":null,"trailing_stop_positive_offset":0.0,"trailing_only_offset_is_reached":false,"use_custom_stoploss":false,"minimal_roi":{"60":0.01,"30":0.02,"0":0.04},"use_sell_signal":true,"sell_profit_only":false,"sell_profit_offset":0.0,"ignore_roi_if_buy_signal":false,"backtest_best_day":0.07872446,"backtest_worst_day":-0.09242451,"backtest_best_day_abs":0.00395,"backtest_worst_day_abs":-0.0046374,"winning_days":4,"draw_days":10,"losing_days":4,"wins":4,"losses":6,"draws":12,"holding_avg":"6:12:00","winner_holding_avg":"1:29:00","loser_holding_avg":"4:42:00","max_drawdown":0.18484901999999998,"max_drawdown_abs":0.0092748,"drawdown_start":"2018-01-14 23:15:00","drawdown_start_ts":1515971700000.0,"drawdown_end":"2018-01-23 09:40:00","drawdown_end_ts":1516700400000.0,"max_drawdown_low":-0.0038247999999999997,"max_drawdown_high":0.00545,"csum_min":999.9961752,"csum_max":1000.00545},"results_explanation":" 22 trades. 4/12/6 Wins/Draws/Losses. Avg profit -0.28%. Median profit 0.00%. Total profit -0.00307480 BTC ( -0.00\u03A3%). Avg duration 6:12:00 min.","total_profit":-3.0747999999999998e-06,"current_epoch":3,"is_initial_point":true,"is_best":true}
+{"loss":-4.9544427978437175,"params_dict":{"mfi-value":"23","fastd-value":"40","adx-value":"50","rsi-value":"27","mfi-enabled":false,"fastd-enabled":true,"adx-enabled":true,"rsi-enabled":true,"trigger":"bb_lower","sell-mfi-value":"87","sell-fastd-value":"60","sell-adx-value":"81","sell-rsi-value":"69","sell-mfi-enabled":true,"sell-fastd-enabled":true,"sell-adx-enabled":false,"sell-rsi-enabled":false,"sell-trigger":"sell-sar_reversal","roi_t1":"105","roi_t2":"43","roi_t3":"12","roi_p1":0.03,"roi_p2":0.036,"roi_p3":0.103,"stoploss":-0.081},"params_details":{"buy":{"mfi-value":"23","fastd-value":"40","adx-value":"50","rsi-value":"27","mfi-enabled":false,"fastd-enabled":true,"adx-enabled":true,"rsi-enabled":true,"trigger":"bb_lower"},"sell":{"sell-mfi-value":"87","sell-fastd-value":"60","sell-adx-value":"81","sell-rsi-value":"69","sell-mfi-enabled":true,"sell-fastd-enabled":true,"sell-adx-enabled":false,"sell-rsi-enabled":false,"sell-trigger":"sell-sar_reversal"},"roi":"{0: 0.16899999999999998, 12: 0.066, 55: 0.03, 160: 0}","stoploss":{"stoploss":-0.081}},"params_not_optimized":{"buy":{},"sell":{}},"results_metrics":{"trades":[{"pair":"XLM/BTC","stake_amount":0.05,"amount":1086.95652174,"open_date":"2018-01-13 13:30:00+00:00","close_date":"2018-01-13 16:30:00+00:00","open_rate":4.6e-05,"close_rate":4.632313095835424e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":180,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":4.2274e-05,"initial_stop_loss_ratio":-0.081,"stop_loss_abs":4.2274e-05,"stop_loss_ratio":-0.081,"min_rate":4.4980000000000006e-05,"max_rate":4.673e-05,"is_open":false,"open_timestamp":1515850200000.0,"close_timestamp":1515861000000.0},{"pair":"ADA/BTC","stake_amount":0.05,"amount":851.35365231,"open_date":"2018-01-15 14:50:00+00:00","close_date":"2018-01-15 16:15:00+00:00","open_rate":5.873000000000001e-05,"close_rate":6.0910642247867544e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":85,"profit_ratio":0.02989537,"profit_abs":0.0015,"sell_reason":"roi","initial_stop_loss_abs":5.397287000000001e-05,"initial_stop_loss_ratio":-0.081,"stop_loss_abs":5.397287000000001e-05,"stop_loss_ratio":-0.081,"min_rate":5.873000000000001e-05,"max_rate":6.120000000000001e-05,"is_open":false,"open_timestamp":1516027800000.0,"close_timestamp":1516032900000.0},{"pair":"ADA/BTC","stake_amount":0.05,"amount":896.86098655,"open_date":"2018-01-16 00:35:00+00:00","close_date":"2018-01-16 03:15:00+00:00","open_rate":5.575000000000001e-05,"close_rate":5.6960000000000004e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":160,"profit_ratio":0.01457705,"profit_abs":0.0007314,"sell_reason":"roi","initial_stop_loss_abs":5.123425000000001e-05,"initial_stop_loss_ratio":-0.081,"stop_loss_abs":5.123425000000001e-05,"stop_loss_ratio":-0.081,"min_rate":5.575000000000001e-05,"max_rate":5.730000000000001e-05,"is_open":false,"open_timestamp":1516062900000.0,"close_timestamp":1516072500000.0},{"pair":"TRX/BTC","stake_amount":0.05,"amount":747.160789,"open_date":"2018-01-16 22:30:00+00:00","close_date":"2018-01-16 22:45:00+00:00","open_rate":6.692e-05,"close_rate":7.182231811339689e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":15,"profit_ratio":0.06576981,"profit_abs":0.0033,"sell_reason":"roi","initial_stop_loss_abs":6.149948000000001e-05,"initial_stop_loss_ratio":-0.081,"stop_loss_abs":6.149948000000001e-05,"stop_loss_ratio":-0.081,"min_rate":6.692e-05,"max_rate":7.566e-05,"is_open":false,"open_timestamp":1516141800000.0,"close_timestamp":1516142700000.0},{"pair":"TRX/BTC","stake_amount":0.05,"amount":720.5649229,"open_date":"2018-01-17 15:15:00+00:00","close_date":"2018-01-17 16:40:00+00:00","open_rate":6.939000000000001e-05,"close_rate":7.19664475664827e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":85,"profit_ratio":0.02989537,"profit_abs":0.0015,"sell_reason":"roi","initial_stop_loss_abs":6.376941000000001e-05,"initial_stop_loss_ratio":-0.081,"stop_loss_abs":6.376941000000001e-05,"stop_loss_ratio":-0.081,"min_rate":6.758e-05,"max_rate":7.244e-05,"is_open":false,"open_timestamp":1516202100000.0,"close_timestamp":1516207200000.0},{"pair":"XLM/BTC","stake_amount":0.05,"amount":1144.42664225,"open_date":"2018-01-18 22:20:00+00:00","close_date":"2018-01-19 00:35:00+00:00","open_rate":4.3690000000000004e-05,"close_rate":4.531220772704466e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":135,"profit_ratio":0.02989537,"profit_abs":0.0015,"sell_reason":"roi","initial_stop_loss_abs":4.015111e-05,"initial_stop_loss_ratio":-0.081,"stop_loss_abs":4.015111e-05,"stop_loss_ratio":-0.081,"min_rate":4.3690000000000004e-05,"max_rate":4.779e-05,"is_open":false,"open_timestamp":1516314000000.0,"close_timestamp":1516322100000.0},{"pair":"ADA/BTC","stake_amount":0.05,"amount":876.57784011,"open_date":"2018-01-18 22:25:00+00:00","close_date":"2018-01-19 01:05:00+00:00","open_rate":5.704e-05,"close_rate":5.792e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":160,"profit_ratio":0.00834457,"profit_abs":0.00041869,"sell_reason":"roi","initial_stop_loss_abs":5.2419760000000006e-05,"initial_stop_loss_ratio":-0.081,"stop_loss_abs":5.2419760000000006e-05,"stop_loss_ratio":-0.081,"min_rate":5.704e-05,"max_rate":5.8670000000000006e-05,"is_open":false,"open_timestamp":1516314300000.0,"close_timestamp":1516323900000.0},{"pair":"TRX/BTC","stake_amount":0.05,"amount":525.59655209,"open_date":"2018-01-20 05:05:00+00:00","close_date":"2018-01-20 06:25:00+00:00","open_rate":9.513e-05,"close_rate":9.86621726041144e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":80,"profit_ratio":0.02989537,"profit_abs":0.0015,"sell_reason":"roi","initial_stop_loss_abs":8.742447000000001e-05,"initial_stop_loss_ratio":-0.081,"stop_loss_abs":8.742447000000001e-05,"stop_loss_ratio":-0.081,"min_rate":9.513e-05,"max_rate":9.95e-05,"is_open":false,"open_timestamp":1516424700000.0,"close_timestamp":1516429500000.0},{"pair":"ADA/BTC","stake_amount":0.05,"amount":920.64076597,"open_date":"2018-01-26 07:40:00+00:00","close_date":"2018-01-26 10:20:00+00:00","open_rate":5.431000000000001e-05,"close_rate":5.474000000000001e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":160,"profit_ratio":0.0008867,"profit_abs":4.449e-05,"sell_reason":"roi","initial_stop_loss_abs":4.991089000000001e-05,"initial_stop_loss_ratio":-0.081,"stop_loss_abs":4.991089000000001e-05,"stop_loss_ratio":-0.081,"min_rate":5.3670000000000006e-05,"max_rate":5.5e-05,"is_open":false,"open_timestamp":1516952400000.0,"close_timestamp":1516962000000.0},{"pair":"XLM/BTC","stake_amount":0.05,"amount":944.28706327,"open_date":"2018-01-28 04:35:00+00:00","close_date":"2018-01-30 04:45:00+00:00","open_rate":5.2950000000000006e-05,"close_rate":4.995000000000001e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":2890,"profit_ratio":-0.06323759,"profit_abs":-0.00317295,"sell_reason":"force_sell","initial_stop_loss_abs":4.866105000000001e-05,"initial_stop_loss_ratio":-0.081,"stop_loss_abs":4.866105000000001e-05,"stop_loss_ratio":-0.081,"min_rate":4.980000000000001e-05,"max_rate":5.3280000000000005e-05,"is_open":true,"open_timestamp":1517114100000.0,"close_timestamp":1517287500000.0}],"locks":[],"best_pair":{"key":"TRX/BTC","trades":3,"profit_mean":0.04185351666666667,"profit_mean_pct":4.185351666666667,"profit_sum":0.12556055,"profit_sum_pct":12.56,"profit_total_abs":0.0063,"profit_total":6.3e-06,"profit_total_pct":0.0,"duration_avg":"1:00:00","wins":3,"draws":0,"losses":0},"worst_pair":{"key":"XLM/BTC","trades":3,"profit_mean":-0.01111407333333333,"profit_mean_pct":-1.111407333333333,"profit_sum":-0.03334221999999999,"profit_sum_pct":-3.33,"profit_total_abs":-0.0016729499999999999,"profit_total":-1.6729499999999998e-06,"profit_total_pct":-0.0,"duration_avg":"17:48:00","wins":1,"draws":1,"losses":1},"results_per_pair":[{"key":"TRX/BTC","trades":3,"profit_mean":0.04185351666666667,"profit_mean_pct":4.185351666666667,"profit_sum":0.12556055,"profit_sum_pct":12.56,"profit_total_abs":0.0063,"profit_total":6.3e-06,"profit_total_pct":0.0,"duration_avg":"1:00:00","wins":3,"draws":0,"losses":0},{"key":"ADA/BTC","trades":4,"profit_mean":0.0134259225,"profit_mean_pct":1.34259225,"profit_sum":0.05370369,"profit_sum_pct":5.37,"profit_total_abs":0.00269458,"profit_total":2.69458e-06,"profit_total_pct":0.0,"duration_avg":"2:21:00","wins":4,"draws":0,"losses":0},{"key":"ETH/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"LTC/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"ETC/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"XLM/BTC","trades":3,"profit_mean":-0.01111407333333333,"profit_mean_pct":-1.111407333333333,"profit_sum":-0.03334221999999999,"profit_sum_pct":-3.33,"profit_total_abs":-0.0016729499999999999,"profit_total":-1.6729499999999998e-06,"profit_total_pct":-0.0,"duration_avg":"17:48:00","wins":1,"draws":1,"losses":1},{"key":"TOTAL","trades":10,"profit_mean":0.014592201999999999,"profit_mean_pct":1.4592201999999999,"profit_sum":0.14592201999999999,"profit_sum_pct":14.59,"profit_total_abs":0.00732163,"profit_total":7.32163e-06,"profit_total_pct":0.0,"duration_avg":"6:35:00","wins":8,"draws":1,"losses":1}],"sell_reason_summary":[{"sell_reason":"roi","trades":9,"wins":8,"draws":1,"losses":0,"profit_mean":0.023239956666666665,"profit_mean_pct":2.32,"profit_sum":0.20915961,"profit_sum_pct":20.92,"profit_total_abs":0.01049458,"profit_total":0.06971987,"profit_total_pct":6.97},{"sell_reason":"force_sell","trades":1,"wins":0,"draws":0,"losses":1,"profit_mean":-0.06323759,"profit_mean_pct":-6.32,"profit_sum":-0.06323759,"profit_sum_pct":-6.32,"profit_total_abs":-0.00317295,"profit_total":-0.021079196666666664,"profit_total_pct":-2.11}],"left_open_trades":[{"key":"XLM/BTC","trades":1,"profit_mean":-0.06323759,"profit_mean_pct":-6.323759,"profit_sum":-0.06323759,"profit_sum_pct":-6.32,"profit_total_abs":-0.00317295,"profit_total":-3.17295e-06,"profit_total_pct":-0.0,"duration_avg":"2 days, 0:10:00","wins":0,"draws":0,"losses":1},{"key":"TOTAL","trades":1,"profit_mean":-0.06323759,"profit_mean_pct":-6.323759,"profit_sum":-0.06323759,"profit_sum_pct":-6.32,"profit_total_abs":-0.00317295,"profit_total":-3.17295e-06,"profit_total_pct":-0.0,"duration_avg":"2 days, 0:10:00","wins":0,"draws":0,"losses":1}],"total_trades":10,"total_volume":0.5,"avg_stake_amount":0.05,"profit_mean":0.014592201999999999,"profit_median":0.02223621,"profit_total":7.32163e-06,"profit_total_abs":0.00732163,"backtest_start":"2018-01-10 07:25:00","backtest_start_ts":1515569100000,"backtest_end":"2018-01-30 04:45:00","backtest_end_ts":1517287500000,"backtest_days":19,"backtest_run_start_ts":1620793107,"backtest_run_end_ts":1620793108,"trades_per_day":0.53,"market_change":0,"pairlist":["ETH/BTC","LTC/BTC","ETC/BTC","XLM/BTC","TRX/BTC","ADA/BTC"],"stake_amount":0.05,"stake_currency":"BTC","stake_currency_decimals":8,"starting_balance":1000,"dry_run_wallet":1000,"final_balance":1000.00732163,"max_open_trades":3,"max_open_trades_setting":3,"timeframe":"5m","timerange":"","enable_protections":false,"strategy_name":"SampleStrategy","stoploss":-0.1,"trailing_stop":false,"trailing_stop_positive":null,"trailing_stop_positive_offset":0.0,"trailing_only_offset_is_reached":false,"use_custom_stoploss":false,"minimal_roi":{"60":0.01,"30":0.02,"0":0.04},"use_sell_signal":true,"sell_profit_only":false,"sell_profit_offset":0.0,"ignore_roi_if_buy_signal":false,"backtest_best_day":0.08034685999999999,"backtest_worst_day":-0.06323759,"backtest_best_day_abs":0.0040314,"backtest_worst_day_abs":-0.00317295,"winning_days":6,"draw_days":11,"losing_days":1,"wins":8,"losses":1,"draws":1,"holding_avg":"6:35:00","winner_holding_avg":"1:50:00","loser_holding_avg":"2 days, 0:10:00","max_drawdown":0.06323759000000001,"max_drawdown_abs":0.00317295,"drawdown_start":"2018-01-26 10:20:00","drawdown_start_ts":1516962000000.0,"drawdown_end":"2018-01-30 04:45:00","drawdown_end_ts":1517287500000.0,"max_drawdown_low":0.007321629999999998,"max_drawdown_high":0.010494579999999998,"csum_min":1000.0,"csum_max":1000.01049458},"results_explanation":" 10 trades. 8/1/1 Wins/Draws/Losses. Avg profit 1.46%. Median profit 2.22%. Total profit 0.00732163 BTC ( 0.00\u03A3%). Avg duration 6:35:00 min.","total_profit":7.32163e-06,"current_epoch":4,"is_initial_point":true,"is_best":true}
+{"loss":0.16709185414267655,"params_dict":{"mfi-value":"10","fastd-value":"45","adx-value":"28","rsi-value":"37","mfi-enabled":false,"fastd-enabled":false,"adx-enabled":true,"rsi-enabled":true,"trigger":"macd_cross_signal","sell-mfi-value":"85","sell-fastd-value":"56","sell-adx-value":"98","sell-rsi-value":"89","sell-mfi-enabled":true,"sell-fastd-enabled":false,"sell-adx-enabled":true,"sell-rsi-enabled":false,"sell-trigger":"sell-sar_reversal","roi_t1":"85","roi_t2":"11","roi_t3":"24","roi_p1":0.04,"roi_p2":0.043,"roi_p3":0.053,"stoploss":-0.057},"params_details":{"buy":{"mfi-value":"10","fastd-value":"45","adx-value":"28","rsi-value":"37","mfi-enabled":false,"fastd-enabled":false,"adx-enabled":true,"rsi-enabled":true,"trigger":"macd_cross_signal"},"sell":{"sell-mfi-value":"85","sell-fastd-value":"56","sell-adx-value":"98","sell-rsi-value":"89","sell-mfi-enabled":true,"sell-fastd-enabled":false,"sell-adx-enabled":true,"sell-rsi-enabled":false,"sell-trigger":"sell-sar_reversal"},"roi":"{0: 0.13599999999999998, 24: 0.08299999999999999, 35: 0.04, 120: 0}","stoploss":{"stoploss":-0.057}},"params_not_optimized":{"buy":{},"sell":{}},"results_metrics":{"trades":[{"pair":"ETH/BTC","stake_amount":0.05,"amount":0.56173464,"open_date":"2018-01-10 19:15:00+00:00","close_date":"2018-01-10 21:15:00+00:00","open_rate":0.08901,"close_rate":0.09112999000000001,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":120,"profit_ratio":0.01667571,"profit_abs":0.0008367,"sell_reason":"roi","initial_stop_loss_abs":0.08393643,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":0.08393643,"stop_loss_ratio":-0.057,"min_rate":0.08894498,"max_rate":0.09116998,"is_open":false,"open_timestamp":1515611700000.0,"close_timestamp":1515618900000.0},{"pair":"ADA/BTC","stake_amount":0.05,"amount":794.65988557,"open_date":"2018-01-13 11:30:00+00:00","close_date":"2018-01-13 15:10:00+00:00","open_rate":6.292e-05,"close_rate":5.9333559999999994e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":220,"profit_ratio":-0.06357798,"profit_abs":-0.00319003,"sell_reason":"stop_loss","initial_stop_loss_abs":5.9333559999999994e-05,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":5.9333559999999994e-05,"stop_loss_ratio":-0.057,"min_rate":5.9900000000000006e-05,"max_rate":6.353e-05,"is_open":false,"open_timestamp":1515843000000.0,"close_timestamp":1515856200000.0},{"pair":"XLM/BTC","stake_amount":0.05,"amount":1086.95652174,"open_date":"2018-01-13 14:35:00+00:00","close_date":"2018-01-13 21:40:00+00:00","open_rate":4.6e-05,"close_rate":4.632313095835424e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":425,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":4.3378e-05,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":4.3378e-05,"stop_loss_ratio":-0.057,"min_rate":4.4980000000000006e-05,"max_rate":4.6540000000000005e-05,"is_open":false,"open_timestamp":1515854100000.0,"close_timestamp":1515879600000.0},{"pair":"ETH/BTC","stake_amount":0.05,"amount":0.53757603,"open_date":"2018-01-15 13:15:00+00:00","close_date":"2018-01-15 15:15:00+00:00","open_rate":0.0930101,"close_rate":0.09366345745107878,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":120,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.0877085243,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":0.0877085243,"stop_loss_ratio":-0.057,"min_rate":0.09188489999999999,"max_rate":0.09380000000000001,"is_open":false,"open_timestamp":1516022100000.0,"close_timestamp":1516029300000.0},{"pair":"ETC/BTC","stake_amount":0.05,"amount":17.07469496,"open_date":"2018-01-15 14:35:00+00:00","close_date":"2018-01-15 16:35:00+00:00","open_rate":0.00292831,"close_rate":0.00297503,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":120,"profit_ratio":0.00886772,"profit_abs":0.00044494,"sell_reason":"roi","initial_stop_loss_abs":0.0027613963299999997,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":0.0027613963299999997,"stop_loss_ratio":-0.057,"min_rate":0.00292831,"max_rate":0.00301259,"is_open":false,"open_timestamp":1516026900000.0,"close_timestamp":1516034100000.0},{"pair":"TRX/BTC","stake_amount":0.05,"amount":702.44450688,"open_date":"2018-01-17 04:25:00+00:00","close_date":"2018-01-17 05:00:00+00:00","open_rate":7.118e-05,"close_rate":7.453721023582538e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":35,"profit_ratio":0.03986049,"profit_abs":0.002,"sell_reason":"roi","initial_stop_loss_abs":6.712274e-05,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":6.712274e-05,"stop_loss_ratio":-0.057,"min_rate":7.118e-05,"max_rate":7.658000000000002e-05,"is_open":false,"open_timestamp":1516163100000.0,"close_timestamp":1516165200000.0},{"pair":"ETC/BTC","stake_amount":0.05,"amount":18.86756854,"open_date":"2018-01-20 06:05:00+00:00","close_date":"2018-01-20 08:05:00+00:00","open_rate":0.00265005,"close_rate":0.00266995,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":120,"profit_ratio":0.00048133,"profit_abs":2.415e-05,"sell_reason":"roi","initial_stop_loss_abs":0.00249899715,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":0.00249899715,"stop_loss_ratio":-0.057,"min_rate":0.00265005,"max_rate":0.00271,"is_open":false,"open_timestamp":1516428300000.0,"close_timestamp":1516435500000.0},{"pair":"ADA/BTC","stake_amount":0.05,"amount":966.18357488,"open_date":"2018-01-22 03:25:00+00:00","close_date":"2018-01-22 07:05:00+00:00","open_rate":5.1750000000000004e-05,"close_rate":5.211352232814853e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":220,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":4.8800250000000004e-05,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":4.8800250000000004e-05,"stop_loss_ratio":-0.057,"min_rate":5.1750000000000004e-05,"max_rate":5.2170000000000004e-05,"is_open":false,"open_timestamp":1516591500000.0,"close_timestamp":1516604700000.0},{"pair":"ETC/BTC","stake_amount":0.05,"amount":18.95303438,"open_date":"2018-01-23 13:10:00+00:00","close_date":"2018-01-23 16:00:00+00:00","open_rate":0.0026381,"close_rate":0.002656631560461616,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":170,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":0.0024877283,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":0.0024877283,"stop_loss_ratio":-0.057,"min_rate":0.0026100000000000003,"max_rate":0.00266,"is_open":false,"open_timestamp":1516713000000.0,"close_timestamp":1516723200000.0},{"pair":"ADA/BTC","stake_amount":0.05,"amount":912.40875912,"open_date":"2018-01-26 06:30:00+00:00","close_date":"2018-01-26 10:45:00+00:00","open_rate":5.480000000000001e-05,"close_rate":5.518494731560462e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":255,"profit_ratio":-0.0,"profit_abs":-0.0,"sell_reason":"roi","initial_stop_loss_abs":5.1676400000000006e-05,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":5.1676400000000006e-05,"stop_loss_ratio":-0.057,"min_rate":5.3670000000000006e-05,"max_rate":5.523e-05,"is_open":false,"open_timestamp":1516948200000.0,"close_timestamp":1516963500000.0},{"pair":"ADA/BTC","stake_amount":0.05,"amount":909.58704748,"open_date":"2018-01-27 02:10:00+00:00","close_date":"2018-01-27 05:40:00+00:00","open_rate":5.4970000000000004e-05,"close_rate":5.535614149523332e-05,"fee_open":0.0035,"fee_close":0.0035,"trade_duration":210,"profit_ratio":0.0,"profit_abs":0.0,"sell_reason":"roi","initial_stop_loss_abs":5.183671e-05,"initial_stop_loss_ratio":-0.057,"stop_loss_abs":5.183671e-05,"stop_loss_ratio":-0.057,"min_rate":5.472000000000001e-05,"max_rate":5.556e-05,"is_open":false,"open_timestamp":1517019000000.0,"close_timestamp":1517031600000.0}],"locks":[],"best_pair":{"key":"TRX/BTC","trades":1,"profit_mean":0.03986049,"profit_mean_pct":3.986049,"profit_sum":0.03986049,"profit_sum_pct":3.99,"profit_total_abs":0.002,"profit_total":2e-06,"profit_total_pct":0.0,"duration_avg":"0:35:00","wins":1,"draws":0,"losses":0},"worst_pair":{"key":"ADA/BTC","trades":4,"profit_mean":-0.015894495,"profit_mean_pct":-1.5894495000000002,"profit_sum":-0.06357798,"profit_sum_pct":-6.36,"profit_total_abs":-0.00319003,"profit_total":-3.19003e-06,"profit_total_pct":-0.0,"duration_avg":"3:46:00","wins":0,"draws":3,"losses":1},"results_per_pair":[{"key":"TRX/BTC","trades":1,"profit_mean":0.03986049,"profit_mean_pct":3.986049,"profit_sum":0.03986049,"profit_sum_pct":3.99,"profit_total_abs":0.002,"profit_total":2e-06,"profit_total_pct":0.0,"duration_avg":"0:35:00","wins":1,"draws":0,"losses":0},{"key":"ETH/BTC","trades":2,"profit_mean":0.008337855,"profit_mean_pct":0.8337855,"profit_sum":0.01667571,"profit_sum_pct":1.67,"profit_total_abs":0.0008367,"profit_total":8.367e-07,"profit_total_pct":0.0,"duration_avg":"2:00:00","wins":1,"draws":1,"losses":0},{"key":"ETC/BTC","trades":3,"profit_mean":0.0031163500000000004,"profit_mean_pct":0.31163500000000005,"profit_sum":0.009349050000000001,"profit_sum_pct":0.93,"profit_total_abs":0.00046909,"profit_total":4.6909000000000003e-07,"profit_total_pct":0.0,"duration_avg":"2:17:00","wins":2,"draws":1,"losses":0},{"key":"LTC/BTC","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0},{"key":"XLM/BTC","trades":1,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"7:05:00","wins":0,"draws":1,"losses":0},{"key":"ADA/BTC","trades":4,"profit_mean":-0.015894495,"profit_mean_pct":-1.5894495000000002,"profit_sum":-0.06357798,"profit_sum_pct":-6.36,"profit_total_abs":-0.00319003,"profit_total":-3.19003e-06,"profit_total_pct":-0.0,"duration_avg":"3:46:00","wins":0,"draws":3,"losses":1},{"key":"TOTAL","trades":11,"profit_mean":0.00020975181818181756,"profit_mean_pct":0.020975181818181757,"profit_sum":0.002307269999999993,"profit_sum_pct":0.23,"profit_total_abs":0.00011576000000000034,"profit_total":1.1576000000000034e-07,"profit_total_pct":0.0,"duration_avg":"3:03:00","wins":4,"draws":6,"losses":1}],"sell_reason_summary":[{"sell_reason":"roi","trades":10,"wins":4,"draws":6,"losses":0,"profit_mean":0.0065885250000000005,"profit_mean_pct":0.66,"profit_sum":0.06588525,"profit_sum_pct":6.59,"profit_total_abs":0.0033057900000000003,"profit_total":0.021961750000000002,"profit_total_pct":2.2},{"sell_reason":"stop_loss","trades":1,"wins":0,"draws":0,"losses":1,"profit_mean":-0.06357798,"profit_mean_pct":-6.36,"profit_sum":-0.06357798,"profit_sum_pct":-6.36,"profit_total_abs":-0.00319003,"profit_total":-0.021192660000000002,"profit_total_pct":-2.12}],"left_open_trades":[{"key":"TOTAL","trades":0,"profit_mean":0.0,"profit_mean_pct":0.0,"profit_sum":0.0,"profit_sum_pct":0.0,"profit_total_abs":0.0,"profit_total":0.0,"profit_total_pct":0.0,"duration_avg":"0:00","wins":0,"draws":0,"losses":0}],"total_trades":11,"total_volume":0.55,"avg_stake_amount":0.05,"profit_mean":0.00020975181818181756,"profit_median":0.0,"profit_total":1.1576000000000034e-07,"profit_total_abs":0.00011576000000000034,"backtest_start":"2018-01-10 07:25:00","backtest_start_ts":1515569100000,"backtest_end":"2018-01-30 04:45:00","backtest_end_ts":1517287500000,"backtest_days":19,"backtest_run_start_ts":1620793107,"backtest_run_end_ts":1620793108,"trades_per_day":0.58,"market_change":0,"pairlist":["ETH/BTC","LTC/BTC","ETC/BTC","XLM/BTC","TRX/BTC","ADA/BTC"],"stake_amount":0.05,"stake_currency":"BTC","stake_currency_decimals":8,"starting_balance":1000,"dry_run_wallet":1000,"final_balance":1000.00011576,"max_open_trades":3,"max_open_trades_setting":3,"timeframe":"5m","timerange":"","enable_protections":false,"strategy_name":"SampleStrategy","stoploss":-0.1,"trailing_stop":false,"trailing_stop_positive":null,"trailing_stop_positive_offset":0.0,"trailing_only_offset_is_reached":false,"use_custom_stoploss":false,"minimal_roi":{"60":0.01,"30":0.02,"0":0.04},"use_sell_signal":true,"sell_profit_only":false,"sell_profit_offset":0.0,"ignore_roi_if_buy_signal":false,"backtest_best_day":0.03986049,"backtest_worst_day":-0.06357798,"backtest_best_day_abs":0.002,"backtest_worst_day_abs":-0.00319003,"winning_days":4,"draw_days":13,"losing_days":1,"wins":4,"losses":1,"draws":6,"holding_avg":"3:03:00","winner_holding_avg":"1:39:00","loser_holding_avg":"3:40:00","max_drawdown":0.06357798,"max_drawdown_abs":0.00319003,"drawdown_start":"2018-01-10 21:15:00","drawdown_start_ts":1515618900000.0,"drawdown_end":"2018-01-13 15:10:00","drawdown_end_ts":1515856200000.0,"max_drawdown_low":-0.00235333,"max_drawdown_high":0.0008367,"csum_min":999.99764667,"csum_max":1000.0008367},"results_explanation":" 11 trades. 4/6/1 Wins/Draws/Losses. Avg profit 0.02%. Median profit 0.00%. Total profit 0.00011576 BTC ( 0.00\u03A3%). Avg duration 3:03:00 min.","total_profit":1.1576000000000034e-07,"current_epoch":5,"is_initial_point":true,"is_best":false}