Merge branch 'develop' into db_keep_orders
This commit is contained in:
@@ -78,7 +78,7 @@ def patch_exchange(mocker, api_mock=None, id='bittrex', mock_markets=True) -> No
|
||||
def get_patched_exchange(mocker, config, api_mock=None, id='bittrex',
|
||||
mock_markets=True) -> Exchange:
|
||||
patch_exchange(mocker, api_mock, id, mock_markets)
|
||||
config["exchange"]["name"] = id
|
||||
config['exchange']['name'] = id
|
||||
try:
|
||||
exchange = ExchangeResolver.load_exchange(id, config)
|
||||
except ImportError:
|
||||
|
@@ -12,7 +12,9 @@ from pandas import DataFrame
|
||||
from pandas.testing import assert_frame_equal
|
||||
|
||||
from freqtrade.configuration import TimeRange
|
||||
from freqtrade.constants import AVAILABLE_DATAHANDLERS
|
||||
from freqtrade.data.converter import ohlcv_to_dataframe
|
||||
from freqtrade.data.history.hdf5datahandler import HDF5DataHandler
|
||||
from freqtrade.data.history.history_utils import (
|
||||
_download_pair_history, _download_trades_history,
|
||||
_load_cached_data_for_updating, convert_trades_to_ohlcv, get_timerange,
|
||||
@@ -620,7 +622,7 @@ def test_convert_trades_to_ohlcv(mocker, default_conf, testdatadir, caplog):
|
||||
_clean_test_file(file5)
|
||||
|
||||
|
||||
def test_jsondatahandler_ohlcv_get_pairs(testdatadir):
|
||||
def test_datahandler_ohlcv_get_pairs(testdatadir):
|
||||
pairs = JsonDataHandler.ohlcv_get_pairs(testdatadir, '5m')
|
||||
# Convert to set to avoid failures due to sorting
|
||||
assert set(pairs) == {'UNITTEST/BTC', 'XLM/BTC', 'ETH/BTC', 'TRX/BTC', 'LTC/BTC',
|
||||
@@ -630,8 +632,11 @@ def test_jsondatahandler_ohlcv_get_pairs(testdatadir):
|
||||
pairs = JsonGzDataHandler.ohlcv_get_pairs(testdatadir, '8m')
|
||||
assert set(pairs) == {'UNITTEST/BTC'}
|
||||
|
||||
pairs = HDF5DataHandler.ohlcv_get_pairs(testdatadir, '5m')
|
||||
assert set(pairs) == {'UNITTEST/BTC'}
|
||||
|
||||
def test_jsondatahandler_ohlcv_get_available_data(testdatadir):
|
||||
|
||||
def test_datahandler_ohlcv_get_available_data(testdatadir):
|
||||
paircombs = JsonDataHandler.ohlcv_get_available_data(testdatadir)
|
||||
# Convert to set to avoid failures due to sorting
|
||||
assert set(paircombs) == {('UNITTEST/BTC', '5m'), ('ETH/BTC', '5m'), ('XLM/BTC', '5m'),
|
||||
@@ -643,6 +648,8 @@ def test_jsondatahandler_ohlcv_get_available_data(testdatadir):
|
||||
|
||||
paircombs = JsonGzDataHandler.ohlcv_get_available_data(testdatadir)
|
||||
assert set(paircombs) == {('UNITTEST/BTC', '8m')}
|
||||
paircombs = HDF5DataHandler.ohlcv_get_available_data(testdatadir)
|
||||
assert set(paircombs) == {('UNITTEST/BTC', '5m')}
|
||||
|
||||
|
||||
def test_jsondatahandler_trades_get_pairs(testdatadir):
|
||||
@@ -653,15 +660,17 @@ def test_jsondatahandler_trades_get_pairs(testdatadir):
|
||||
|
||||
def test_jsondatahandler_ohlcv_purge(mocker, testdatadir):
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
|
||||
mocker.patch.object(Path, "unlink", MagicMock())
|
||||
unlinkmock = mocker.patch.object(Path, "unlink", MagicMock())
|
||||
dh = JsonGzDataHandler(testdatadir)
|
||||
assert not dh.ohlcv_purge('UNITTEST/NONEXIST', '5m')
|
||||
assert unlinkmock.call_count == 0
|
||||
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
|
||||
assert dh.ohlcv_purge('UNITTEST/NONEXIST', '5m')
|
||||
assert unlinkmock.call_count == 1
|
||||
|
||||
|
||||
def test_jsondatahandler_trades_load(mocker, testdatadir, caplog):
|
||||
def test_jsondatahandler_trades_load(testdatadir, caplog):
|
||||
dh = JsonGzDataHandler(testdatadir)
|
||||
logmsg = "Old trades format detected - converting"
|
||||
dh.trades_load('XRP/ETH')
|
||||
@@ -674,26 +683,144 @@ def test_jsondatahandler_trades_load(mocker, testdatadir, caplog):
|
||||
|
||||
def test_jsondatahandler_trades_purge(mocker, testdatadir):
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
|
||||
mocker.patch.object(Path, "unlink", MagicMock())
|
||||
unlinkmock = mocker.patch.object(Path, "unlink", MagicMock())
|
||||
dh = JsonGzDataHandler(testdatadir)
|
||||
assert not dh.trades_purge('UNITTEST/NONEXIST')
|
||||
assert unlinkmock.call_count == 0
|
||||
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
|
||||
assert dh.trades_purge('UNITTEST/NONEXIST')
|
||||
assert unlinkmock.call_count == 1
|
||||
|
||||
|
||||
def test_jsondatahandler_ohlcv_append(testdatadir):
|
||||
dh = JsonGzDataHandler(testdatadir)
|
||||
@pytest.mark.parametrize('datahandler', AVAILABLE_DATAHANDLERS)
|
||||
def test_datahandler_ohlcv_append(datahandler, testdatadir, ):
|
||||
dh = get_datahandler(testdatadir, datahandler)
|
||||
with pytest.raises(NotImplementedError):
|
||||
dh.ohlcv_append('UNITTEST/ETH', '5m', DataFrame())
|
||||
|
||||
|
||||
def test_jsondatahandler_trades_append(testdatadir):
|
||||
dh = JsonGzDataHandler(testdatadir)
|
||||
@pytest.mark.parametrize('datahandler', AVAILABLE_DATAHANDLERS)
|
||||
def test_datahandler_trades_append(datahandler, testdatadir):
|
||||
dh = get_datahandler(testdatadir, datahandler)
|
||||
with pytest.raises(NotImplementedError):
|
||||
dh.trades_append('UNITTEST/ETH', [])
|
||||
|
||||
|
||||
def test_hdf5datahandler_trades_get_pairs(testdatadir):
|
||||
pairs = HDF5DataHandler.trades_get_pairs(testdatadir)
|
||||
# Convert to set to avoid failures due to sorting
|
||||
assert set(pairs) == {'XRP/ETH'}
|
||||
|
||||
|
||||
def test_hdf5datahandler_trades_load(testdatadir):
|
||||
dh = HDF5DataHandler(testdatadir)
|
||||
trades = dh.trades_load('XRP/ETH')
|
||||
assert isinstance(trades, list)
|
||||
|
||||
trades1 = dh.trades_load('UNITTEST/NONEXIST')
|
||||
assert trades1 == []
|
||||
# data goes from 2019-10-11 - 2019-10-13
|
||||
timerange = TimeRange.parse_timerange('20191011-20191012')
|
||||
|
||||
trades2 = dh._trades_load('XRP/ETH', timerange)
|
||||
assert len(trades) > len(trades2)
|
||||
|
||||
# unfiltered load has trades before starttime
|
||||
assert len([t for t in trades if t[0] < timerange.startts * 1000]) >= 0
|
||||
# filtered list does not have trades before starttime
|
||||
assert len([t for t in trades2 if t[0] < timerange.startts * 1000]) == 0
|
||||
# unfiltered load has trades after endtime
|
||||
assert len([t for t in trades if t[0] > timerange.stopts * 1000]) > 0
|
||||
# filtered list does not have trades after endtime
|
||||
assert len([t for t in trades2 if t[0] > timerange.stopts * 1000]) == 0
|
||||
|
||||
|
||||
def test_hdf5datahandler_trades_store(testdatadir):
|
||||
dh = HDF5DataHandler(testdatadir)
|
||||
trades = dh.trades_load('XRP/ETH')
|
||||
|
||||
dh.trades_store('XRP/NEW', trades)
|
||||
file = testdatadir / 'XRP_NEW-trades.h5'
|
||||
assert file.is_file()
|
||||
# Load trades back
|
||||
trades_new = dh.trades_load('XRP/NEW')
|
||||
|
||||
assert len(trades_new) == len(trades)
|
||||
assert trades[0][0] == trades_new[0][0]
|
||||
assert trades[0][1] == trades_new[0][1]
|
||||
# assert trades[0][2] == trades_new[0][2] # This is nan - so comparison does not make sense
|
||||
assert trades[0][3] == trades_new[0][3]
|
||||
assert trades[0][4] == trades_new[0][4]
|
||||
assert trades[0][5] == trades_new[0][5]
|
||||
assert trades[0][6] == trades_new[0][6]
|
||||
assert trades[-1][0] == trades_new[-1][0]
|
||||
assert trades[-1][1] == trades_new[-1][1]
|
||||
# assert trades[-1][2] == trades_new[-1][2] # This is nan - so comparison does not make sense
|
||||
assert trades[-1][3] == trades_new[-1][3]
|
||||
assert trades[-1][4] == trades_new[-1][4]
|
||||
assert trades[-1][5] == trades_new[-1][5]
|
||||
assert trades[-1][6] == trades_new[-1][6]
|
||||
|
||||
_clean_test_file(file)
|
||||
|
||||
|
||||
def test_hdf5datahandler_trades_purge(mocker, testdatadir):
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
|
||||
unlinkmock = mocker.patch.object(Path, "unlink", MagicMock())
|
||||
dh = HDF5DataHandler(testdatadir)
|
||||
assert not dh.trades_purge('UNITTEST/NONEXIST')
|
||||
assert unlinkmock.call_count == 0
|
||||
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
|
||||
assert dh.trades_purge('UNITTEST/NONEXIST')
|
||||
assert unlinkmock.call_count == 1
|
||||
|
||||
|
||||
def test_hdf5datahandler_ohlcv_load_and_resave(testdatadir):
|
||||
dh = HDF5DataHandler(testdatadir)
|
||||
ohlcv = dh.ohlcv_load('UNITTEST/BTC', '5m')
|
||||
assert isinstance(ohlcv, DataFrame)
|
||||
assert len(ohlcv) > 0
|
||||
|
||||
file = testdatadir / 'UNITTEST_NEW-5m.h5'
|
||||
assert not file.is_file()
|
||||
|
||||
dh.ohlcv_store('UNITTEST/NEW', '5m', ohlcv)
|
||||
assert file.is_file()
|
||||
|
||||
assert not ohlcv[ohlcv['date'] < '2018-01-15'].empty
|
||||
|
||||
# Data gores from 2018-01-10 - 2018-01-30
|
||||
timerange = TimeRange.parse_timerange('20180115-20180119')
|
||||
|
||||
# Call private function to ensure timerange is filtered in hdf5
|
||||
ohlcv = dh._ohlcv_load('UNITTEST/BTC', '5m', timerange)
|
||||
ohlcv1 = dh._ohlcv_load('UNITTEST/NEW', '5m', timerange)
|
||||
assert len(ohlcv) == len(ohlcv1)
|
||||
assert ohlcv.equals(ohlcv1)
|
||||
assert ohlcv[ohlcv['date'] < '2018-01-15'].empty
|
||||
assert ohlcv[ohlcv['date'] > '2018-01-19'].empty
|
||||
|
||||
_clean_test_file(file)
|
||||
|
||||
# Try loading inexisting file
|
||||
ohlcv = dh.ohlcv_load('UNITTEST/NONEXIST', '5m')
|
||||
assert ohlcv.empty
|
||||
|
||||
|
||||
def test_hdf5datahandler_ohlcv_purge(mocker, testdatadir):
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=False))
|
||||
unlinkmock = mocker.patch.object(Path, "unlink", MagicMock())
|
||||
dh = HDF5DataHandler(testdatadir)
|
||||
assert not dh.ohlcv_purge('UNITTEST/NONEXIST', '5m')
|
||||
assert unlinkmock.call_count == 0
|
||||
|
||||
mocker.patch.object(Path, "exists", MagicMock(return_value=True))
|
||||
assert dh.ohlcv_purge('UNITTEST/NONEXIST', '5m')
|
||||
assert unlinkmock.call_count == 1
|
||||
|
||||
|
||||
def test_gethandlerclass():
|
||||
cl = get_datahandlerclass('json')
|
||||
assert cl == JsonDataHandler
|
||||
@@ -702,6 +829,9 @@ def test_gethandlerclass():
|
||||
assert cl == JsonGzDataHandler
|
||||
assert issubclass(cl, IDataHandler)
|
||||
assert issubclass(cl, JsonDataHandler)
|
||||
cl = get_datahandlerclass('hdf5')
|
||||
assert cl == HDF5DataHandler
|
||||
assert issubclass(cl, IDataHandler)
|
||||
with pytest.raises(ValueError, match=r"No datahandler for .*"):
|
||||
get_datahandlerclass('DeadBeef')
|
||||
|
||||
@@ -713,3 +843,6 @@ def test_get_datahandler(testdatadir):
|
||||
assert type(dh) == JsonGzDataHandler
|
||||
dh1 = get_datahandler(testdatadir, 'jsongz', dh)
|
||||
assert id(dh1) == id(dh)
|
||||
|
||||
dh = get_datahandler(testdatadir, 'hdf5')
|
||||
assert type(dh) == HDF5DataHandler
|
||||
|
@@ -359,6 +359,7 @@ def test_backtesting_start(default_conf, mocker, testdatadir, caplog) -> None:
|
||||
]
|
||||
for line in exists:
|
||||
assert log_has(line, caplog)
|
||||
assert backtesting.strategy.dp._pairlists is not None
|
||||
|
||||
|
||||
def test_backtesting_start_no_data(default_conf, mocker, caplog, testdatadir) -> None:
|
||||
|
@@ -101,6 +101,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'initial_stop_loss_ratio': -0.1,
|
||||
'stoploss_current_dist': -1.1080000000000002e-06,
|
||||
'stoploss_current_dist_ratio': -0.10081893,
|
||||
'stoploss_current_dist_pct': -10.08,
|
||||
'stoploss_entry_dist': -0.00010475,
|
||||
'stoploss_entry_dist_ratio': -0.10448878,
|
||||
'open_order': None,
|
||||
@@ -165,6 +166,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'initial_stop_loss_ratio': -0.1,
|
||||
'stoploss_current_dist': ANY,
|
||||
'stoploss_current_dist_ratio': ANY,
|
||||
'stoploss_current_dist_pct': ANY,
|
||||
'stoploss_entry_dist': -0.00010475,
|
||||
'stoploss_entry_dist_ratio': -0.10448878,
|
||||
'open_order': None,
|
||||
|
@@ -10,10 +10,12 @@ from flask import Flask
|
||||
from requests.auth import _basic_auth_str
|
||||
|
||||
from freqtrade.__init__ import __version__
|
||||
from freqtrade.loggers import setup_logging, setup_logging_pre
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.rpc.api_server import BASE_URI, ApiServer
|
||||
from freqtrade.state import State
|
||||
from tests.conftest import get_patched_freqtradebot, log_has, patch_get_signal, create_mock_trades
|
||||
from tests.conftest import (create_mock_trades, get_patched_freqtradebot,
|
||||
log_has, patch_get_signal)
|
||||
|
||||
_TEST_USER = "FreqTrader"
|
||||
_TEST_PASS = "SuperSecurePassword1!"
|
||||
@@ -21,6 +23,9 @@ _TEST_PASS = "SuperSecurePassword1!"
|
||||
|
||||
@pytest.fixture
|
||||
def botclient(default_conf, mocker):
|
||||
setup_logging_pre()
|
||||
setup_logging(default_conf)
|
||||
|
||||
default_conf.update({"api_server": {"enabled": True,
|
||||
"listen_ip_address": "127.0.0.1",
|
||||
"listen_port": 8080,
|
||||
@@ -87,20 +92,20 @@ def test_api_unauthorized(botclient):
|
||||
assert rc.json == {'error': 'Unauthorized'}
|
||||
|
||||
# Change only username
|
||||
ftbot.config['api_server']['username'] = "Ftrader"
|
||||
ftbot.config['api_server']['username'] = 'Ftrader'
|
||||
rc = client_get(client, f"{BASE_URI}/version")
|
||||
assert_response(rc, 401)
|
||||
assert rc.json == {'error': 'Unauthorized'}
|
||||
|
||||
# Change only password
|
||||
ftbot.config['api_server']['username'] = _TEST_USER
|
||||
ftbot.config['api_server']['password'] = "WrongPassword"
|
||||
ftbot.config['api_server']['password'] = 'WrongPassword'
|
||||
rc = client_get(client, f"{BASE_URI}/version")
|
||||
assert_response(rc, 401)
|
||||
assert rc.json == {'error': 'Unauthorized'}
|
||||
|
||||
ftbot.config['api_server']['username'] = "Ftrader"
|
||||
ftbot.config['api_server']['password'] = "WrongPassword"
|
||||
ftbot.config['api_server']['username'] = 'Ftrader'
|
||||
ftbot.config['api_server']['password'] = 'WrongPassword'
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/version")
|
||||
assert_response(rc, 401)
|
||||
@@ -423,6 +428,34 @@ def test_api_delete_trade(botclient, mocker, fee, markets):
|
||||
assert stoploss_mock.call_count == 1
|
||||
|
||||
|
||||
def test_api_logs(botclient):
|
||||
ftbot, client = botclient
|
||||
rc = client_get(client, f"{BASE_URI}/logs")
|
||||
assert_response(rc)
|
||||
assert len(rc.json) == 2
|
||||
assert 'logs' in rc.json
|
||||
# Using a fixed comparison here would make this test fail!
|
||||
assert rc.json['log_count'] > 10
|
||||
assert len(rc.json['logs']) == rc.json['log_count']
|
||||
|
||||
assert isinstance(rc.json['logs'][0], list)
|
||||
# date
|
||||
assert isinstance(rc.json['logs'][0][0], str)
|
||||
# created_timestamp
|
||||
assert isinstance(rc.json['logs'][0][1], float)
|
||||
assert isinstance(rc.json['logs'][0][2], str)
|
||||
assert isinstance(rc.json['logs'][0][3], str)
|
||||
assert isinstance(rc.json['logs'][0][4], str)
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/logs?limit=5")
|
||||
assert_response(rc)
|
||||
assert len(rc.json) == 2
|
||||
assert 'logs' in rc.json
|
||||
# Using a fixed comparison here would make this test fail!
|
||||
assert rc.json['log_count'] == 5
|
||||
assert len(rc.json['logs']) == rc.json['log_count']
|
||||
|
||||
|
||||
def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
@@ -600,6 +633,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
|
||||
'initial_stop_loss_ratio': -0.1,
|
||||
'stoploss_current_dist': -1.1080000000000002e-06,
|
||||
'stoploss_current_dist_ratio': -0.10081893,
|
||||
'stoploss_current_dist_pct': -10.08,
|
||||
'stoploss_entry_dist': -0.00010475,
|
||||
'stoploss_entry_dist_ratio': -0.10448878,
|
||||
'trade_id': 1,
|
||||
@@ -676,7 +710,7 @@ def test_api_forcebuy(botclient, mocker, fee):
|
||||
assert rc.json == {"error": "Error querying _forcebuy: Forcebuy not enabled."}
|
||||
|
||||
# enable forcebuy
|
||||
ftbot.config["forcebuy_enable"] = True
|
||||
ftbot.config['forcebuy_enable'] = True
|
||||
|
||||
fbuy_mock = MagicMock(return_value=None)
|
||||
mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock)
|
||||
|
@@ -16,6 +16,7 @@ from telegram.error import NetworkError
|
||||
from freqtrade import __version__
|
||||
from freqtrade.edge import PairInfo
|
||||
from freqtrade.freqtradebot import FreqtradeBot
|
||||
from freqtrade.loggers import setup_logging
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.rpc import RPCMessageType
|
||||
from freqtrade.rpc.telegram import Telegram, authorized_only
|
||||
@@ -76,7 +77,7 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
|
||||
"['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['trades'], "
|
||||
"['delete'], ['performance'], ['daily'], ['count'], ['reload_config', "
|
||||
"'reload_conf'], ['show_config', 'show_conf'], ['stopbuy'], "
|
||||
"['whitelist'], ['blacklist'], ['edge'], ['help'], ['version']]")
|
||||
"['whitelist'], ['blacklist'], ['logs'], ['edge'], ['help'], ['version']]")
|
||||
|
||||
assert log_has(message_str, caplog)
|
||||
|
||||
@@ -145,7 +146,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
|
||||
assert log_has('Exception occurred within Telegram module', caplog)
|
||||
|
||||
|
||||
def test_status(default_conf, update, mocker, fee, ticker,) -> None:
|
||||
def test_telegram_status(default_conf, update, mocker, fee, ticker,) -> None:
|
||||
update.message.chat.id = "123"
|
||||
default_conf['telegram']['enabled'] = False
|
||||
default_conf['telegram']['chat_id'] = "123"
|
||||
@@ -175,6 +176,8 @@ def test_status(default_conf, update, mocker, fee, ticker,) -> None:
|
||||
'stop_loss': 1.099e-05,
|
||||
'sell_order_status': None,
|
||||
'initial_stop_loss_pct': -0.05,
|
||||
'stoploss_current_dist': 1e-08,
|
||||
'stoploss_current_dist_pct': -0.02,
|
||||
'stop_loss_pct': -0.01,
|
||||
'open_order': '(limit buy rem=0.00000000)'
|
||||
}]),
|
||||
@@ -1105,6 +1108,40 @@ def test_blacklist_static(default_conf, update, mocker) -> None:
|
||||
assert freqtradebot.pairlists.blacklist == ["DOGE/BTC", "HOT/BTC", "ETH/BTC"]
|
||||
|
||||
|
||||
def test_telegram_logs(default_conf, update, mocker) -> None:
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.rpc.telegram.Telegram',
|
||||
_init=MagicMock(),
|
||||
_send_msg=msg_mock
|
||||
)
|
||||
setup_logging(default_conf)
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
|
||||
telegram = Telegram(freqtradebot)
|
||||
context = MagicMock()
|
||||
context.args = []
|
||||
telegram._logs(update=update, context=context)
|
||||
assert msg_mock.call_count == 1
|
||||
assert "freqtrade\\.rpc\\.telegram" in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
msg_mock.reset_mock()
|
||||
context.args = ["1"]
|
||||
telegram._logs(update=update, context=context)
|
||||
assert msg_mock.call_count == 1
|
||||
|
||||
msg_mock.reset_mock()
|
||||
# Test with changed MaxMessageLength
|
||||
mocker.patch('freqtrade.rpc.telegram.MAX_TELEGRAM_MESSAGE_LENGTH', 200)
|
||||
context = MagicMock()
|
||||
context.args = []
|
||||
telegram._logs(update=update, context=context)
|
||||
# Called at least 3 times. Exact times will change with unrelated changes to setup messages
|
||||
# Therefore we don't test for this explicitly.
|
||||
assert msg_mock.call_count > 3
|
||||
|
||||
|
||||
def test_edge_disabled(default_conf, update, mocker) -> None:
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
|
@@ -1,6 +1,7 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import arrow
|
||||
@@ -8,12 +9,12 @@ import pytest
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.configuration import TimeRange
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
from freqtrade.data.history import load_data
|
||||
from freqtrade.exceptions import StrategyError
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.resolvers import StrategyResolver
|
||||
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
||||
from freqtrade.data.dataprovider import DataProvider
|
||||
from tests.conftest import log_has, log_has_re
|
||||
|
||||
from .strats.default_strategy import DefaultStrategy
|
||||
@@ -261,14 +262,14 @@ def test_min_roi_reached3(default_conf, fee) -> None:
|
||||
strategy = StrategyResolver.load_strategy(default_conf)
|
||||
strategy.minimal_roi = min_roi
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
amount=5,
|
||||
open_date=arrow.utcnow().shift(hours=-1).datetime,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
open_rate=1,
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
amount=5,
|
||||
open_date=arrow.utcnow().shift(hours=-1).datetime,
|
||||
fee_open=fee.return_value,
|
||||
fee_close=fee.return_value,
|
||||
exchange='bittrex',
|
||||
open_rate=1,
|
||||
)
|
||||
|
||||
assert not strategy.min_roi_reached(trade, 0.02, arrow.utcnow().shift(minutes=-56).datetime)
|
||||
@@ -387,6 +388,31 @@ def test_is_pair_locked(default_conf):
|
||||
strategy.unlock_pair(pair)
|
||||
assert not strategy.is_pair_locked(pair)
|
||||
|
||||
pair = 'BTC/USDT'
|
||||
# Lock until 14:30
|
||||
lock_time = datetime(2020, 5, 1, 14, 30, 0, tzinfo=timezone.utc)
|
||||
strategy.lock_pair(pair, lock_time)
|
||||
# Lock is in the past ...
|
||||
assert not strategy.is_pair_locked(pair)
|
||||
# latest candle is from 14:20, lock goes to 14:30
|
||||
assert strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-10))
|
||||
assert strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-50))
|
||||
|
||||
# latest candle is from 14:25 (lock should be lifted)
|
||||
# Since this is the "new candle" available at 14:30
|
||||
assert not strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-4))
|
||||
|
||||
# Should not be locked after time expired
|
||||
assert not strategy.is_pair_locked(pair, lock_time + timedelta(minutes=10))
|
||||
|
||||
# Change timeframe to 15m
|
||||
strategy.timeframe = '15m'
|
||||
# Candle from 14:14 - lock goes until 14:30
|
||||
assert strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-16))
|
||||
assert strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-15, seconds=-2))
|
||||
# Candle from 14:15 - lock goes until 14:30
|
||||
assert not strategy.is_pair_locked(pair, lock_time + timedelta(minutes=-15))
|
||||
|
||||
|
||||
def test_is_informative_pairs_callback(default_conf):
|
||||
default_conf.update({'strategy': 'TestStrategyLegacy'})
|
||||
|
@@ -19,64 +19,64 @@ def test_parse_args_none() -> None:
|
||||
|
||||
|
||||
def test_parse_args_defaults(mocker) -> None:
|
||||
mocker.patch.object(Path, "is_file", MagicMock(side_effect=[False, True]))
|
||||
mocker.patch.object(Path, 'is_file', MagicMock(side_effect=[False, True]))
|
||||
args = Arguments(['trade']).get_parsed_arg()
|
||||
assert args["config"] == ['config.json']
|
||||
assert args["strategy_path"] is None
|
||||
assert args["datadir"] is None
|
||||
assert args["verbosity"] == 0
|
||||
assert args['config'] == ['config.json']
|
||||
assert args['strategy_path'] is None
|
||||
assert args['datadir'] is None
|
||||
assert args['verbosity'] == 0
|
||||
|
||||
|
||||
def test_parse_args_default_userdatadir(mocker) -> None:
|
||||
mocker.patch.object(Path, "is_file", MagicMock(return_value=True))
|
||||
mocker.patch.object(Path, 'is_file', MagicMock(return_value=True))
|
||||
args = Arguments(['trade']).get_parsed_arg()
|
||||
# configuration defaults to user_data if that is available.
|
||||
assert args["config"] == [str(Path('user_data/config.json'))]
|
||||
assert args["strategy_path"] is None
|
||||
assert args["datadir"] is None
|
||||
assert args["verbosity"] == 0
|
||||
assert args['config'] == [str(Path('user_data/config.json'))]
|
||||
assert args['strategy_path'] is None
|
||||
assert args['datadir'] is None
|
||||
assert args['verbosity'] == 0
|
||||
|
||||
|
||||
def test_parse_args_userdatadir(mocker) -> None:
|
||||
mocker.patch.object(Path, "is_file", MagicMock(return_value=True))
|
||||
mocker.patch.object(Path, 'is_file', MagicMock(return_value=True))
|
||||
args = Arguments(['trade', '--user-data-dir', 'user_data']).get_parsed_arg()
|
||||
# configuration defaults to user_data if that is available.
|
||||
assert args["config"] == [str(Path('user_data/config.json'))]
|
||||
assert args["strategy_path"] is None
|
||||
assert args["datadir"] is None
|
||||
assert args["verbosity"] == 0
|
||||
assert args['config'] == [str(Path('user_data/config.json'))]
|
||||
assert args['strategy_path'] is None
|
||||
assert args['datadir'] is None
|
||||
assert args['verbosity'] == 0
|
||||
|
||||
|
||||
def test_parse_args_config() -> None:
|
||||
args = Arguments(['trade', '-c', '/dev/null']).get_parsed_arg()
|
||||
assert args["config"] == ['/dev/null']
|
||||
assert args['config'] == ['/dev/null']
|
||||
|
||||
args = Arguments(['trade', '--config', '/dev/null']).get_parsed_arg()
|
||||
assert args["config"] == ['/dev/null']
|
||||
assert args['config'] == ['/dev/null']
|
||||
|
||||
args = Arguments(['trade', '--config', '/dev/null',
|
||||
'--config', '/dev/zero'],).get_parsed_arg()
|
||||
assert args["config"] == ['/dev/null', '/dev/zero']
|
||||
assert args['config'] == ['/dev/null', '/dev/zero']
|
||||
|
||||
|
||||
def test_parse_args_db_url() -> None:
|
||||
args = Arguments(['trade', '--db-url', 'sqlite:///test.sqlite']).get_parsed_arg()
|
||||
assert args["db_url"] == 'sqlite:///test.sqlite'
|
||||
assert args['db_url'] == 'sqlite:///test.sqlite'
|
||||
|
||||
|
||||
def test_parse_args_verbose() -> None:
|
||||
args = Arguments(['trade', '-v']).get_parsed_arg()
|
||||
assert args["verbosity"] == 1
|
||||
assert args['verbosity'] == 1
|
||||
|
||||
args = Arguments(['trade', '--verbose']).get_parsed_arg()
|
||||
assert args["verbosity"] == 1
|
||||
assert args['verbosity'] == 1
|
||||
|
||||
|
||||
def test_common_scripts_options() -> None:
|
||||
args = Arguments(['download-data', '-p', 'ETH/BTC', 'XRP/BTC']).get_parsed_arg()
|
||||
|
||||
assert args["pairs"] == ['ETH/BTC', 'XRP/BTC']
|
||||
assert "func" in args
|
||||
assert args['pairs'] == ['ETH/BTC', 'XRP/BTC']
|
||||
assert 'func' in args
|
||||
|
||||
|
||||
def test_parse_args_version() -> None:
|
||||
@@ -91,7 +91,7 @@ def test_parse_args_invalid() -> None:
|
||||
|
||||
def test_parse_args_strategy() -> None:
|
||||
args = Arguments(['trade', '--strategy', 'SomeStrategy']).get_parsed_arg()
|
||||
assert args["strategy"] == 'SomeStrategy'
|
||||
assert args['strategy'] == 'SomeStrategy'
|
||||
|
||||
|
||||
def test_parse_args_strategy_invalid() -> None:
|
||||
@@ -101,7 +101,7 @@ def test_parse_args_strategy_invalid() -> None:
|
||||
|
||||
def test_parse_args_strategy_path() -> None:
|
||||
args = Arguments(['trade', '--strategy-path', '/some/path']).get_parsed_arg()
|
||||
assert args["strategy_path"] == '/some/path'
|
||||
assert args['strategy_path'] == '/some/path'
|
||||
|
||||
|
||||
def test_parse_args_strategy_path_invalid() -> None:
|
||||
@@ -127,13 +127,13 @@ def test_parse_args_backtesting_custom() -> None:
|
||||
'SampleStrategy'
|
||||
]
|
||||
call_args = Arguments(args).get_parsed_arg()
|
||||
assert call_args["config"] == ['test_conf.json']
|
||||
assert call_args["verbosity"] == 0
|
||||
assert call_args["command"] == 'backtesting'
|
||||
assert call_args["func"] is not None
|
||||
assert call_args["timeframe"] == '1m'
|
||||
assert type(call_args["strategy_list"]) is list
|
||||
assert len(call_args["strategy_list"]) == 2
|
||||
assert call_args['config'] == ['test_conf.json']
|
||||
assert call_args['verbosity'] == 0
|
||||
assert call_args['command'] == 'backtesting'
|
||||
assert call_args['func'] is not None
|
||||
assert call_args['timeframe'] == '1m'
|
||||
assert type(call_args['strategy_list']) is list
|
||||
assert len(call_args['strategy_list']) == 2
|
||||
|
||||
|
||||
def test_parse_args_hyperopt_custom() -> None:
|
||||
@@ -144,13 +144,13 @@ def test_parse_args_hyperopt_custom() -> None:
|
||||
'--spaces', 'buy'
|
||||
]
|
||||
call_args = Arguments(args).get_parsed_arg()
|
||||
assert call_args["config"] == ['test_conf.json']
|
||||
assert call_args["epochs"] == 20
|
||||
assert call_args["verbosity"] == 0
|
||||
assert call_args["command"] == 'hyperopt'
|
||||
assert call_args["spaces"] == ['buy']
|
||||
assert call_args["func"] is not None
|
||||
assert callable(call_args["func"])
|
||||
assert call_args['config'] == ['test_conf.json']
|
||||
assert call_args['epochs'] == 20
|
||||
assert call_args['verbosity'] == 0
|
||||
assert call_args['command'] == 'hyperopt'
|
||||
assert call_args['spaces'] == ['buy']
|
||||
assert call_args['func'] is not None
|
||||
assert callable(call_args['func'])
|
||||
|
||||
|
||||
def test_download_data_options() -> None:
|
||||
@@ -163,10 +163,10 @@ def test_download_data_options() -> None:
|
||||
]
|
||||
pargs = Arguments(args).get_parsed_arg()
|
||||
|
||||
assert pargs["pairs_file"] == 'file_with_pairs'
|
||||
assert pargs["datadir"] == 'datadir/directory'
|
||||
assert pargs["days"] == 30
|
||||
assert pargs["exchange"] == 'binance'
|
||||
assert pargs['pairs_file'] == 'file_with_pairs'
|
||||
assert pargs['datadir'] == 'datadir/directory'
|
||||
assert pargs['days'] == 30
|
||||
assert pargs['exchange'] == 'binance'
|
||||
|
||||
|
||||
def test_plot_dataframe_options() -> None:
|
||||
@@ -180,10 +180,10 @@ def test_plot_dataframe_options() -> None:
|
||||
]
|
||||
pargs = Arguments(args).get_parsed_arg()
|
||||
|
||||
assert pargs["indicators1"] == ["sma10", "sma100"]
|
||||
assert pargs["indicators2"] == ["macd", "fastd", "fastk"]
|
||||
assert pargs["plot_limit"] == 30
|
||||
assert pargs["pairs"] == ["UNITTEST/BTC"]
|
||||
assert pargs['indicators1'] == ['sma10', 'sma100']
|
||||
assert pargs['indicators2'] == ['macd', 'fastd', 'fastk']
|
||||
assert pargs['plot_limit'] == 30
|
||||
assert pargs['pairs'] == ['UNITTEST/BTC']
|
||||
|
||||
|
||||
def test_plot_profit_options() -> None:
|
||||
@@ -191,66 +191,66 @@ def test_plot_profit_options() -> None:
|
||||
'plot-profit',
|
||||
'-p', 'UNITTEST/BTC',
|
||||
'--trade-source', 'DB',
|
||||
"--db-url", "sqlite:///whatever.sqlite",
|
||||
'--db-url', 'sqlite:///whatever.sqlite',
|
||||
]
|
||||
pargs = Arguments(args).get_parsed_arg()
|
||||
|
||||
assert pargs["trade_source"] == "DB"
|
||||
assert pargs["pairs"] == ["UNITTEST/BTC"]
|
||||
assert pargs["db_url"] == "sqlite:///whatever.sqlite"
|
||||
assert pargs['trade_source'] == 'DB'
|
||||
assert pargs['pairs'] == ['UNITTEST/BTC']
|
||||
assert pargs['db_url'] == 'sqlite:///whatever.sqlite'
|
||||
|
||||
|
||||
def test_config_notallowed(mocker) -> None:
|
||||
mocker.patch.object(Path, "is_file", MagicMock(return_value=False))
|
||||
mocker.patch.object(Path, 'is_file', MagicMock(return_value=False))
|
||||
args = [
|
||||
'create-userdir',
|
||||
]
|
||||
pargs = Arguments(args).get_parsed_arg()
|
||||
|
||||
assert "config" not in pargs
|
||||
assert 'config' not in pargs
|
||||
|
||||
# When file exists:
|
||||
mocker.patch.object(Path, "is_file", MagicMock(return_value=True))
|
||||
mocker.patch.object(Path, 'is_file', MagicMock(return_value=True))
|
||||
args = [
|
||||
'create-userdir',
|
||||
]
|
||||
pargs = Arguments(args).get_parsed_arg()
|
||||
# config is not added even if it exists, since create-userdir is in the notallowed list
|
||||
assert "config" not in pargs
|
||||
assert 'config' not in pargs
|
||||
|
||||
|
||||
def test_config_notrequired(mocker) -> None:
|
||||
mocker.patch.object(Path, "is_file", MagicMock(return_value=False))
|
||||
mocker.patch.object(Path, 'is_file', MagicMock(return_value=False))
|
||||
args = [
|
||||
'download-data',
|
||||
]
|
||||
pargs = Arguments(args).get_parsed_arg()
|
||||
|
||||
assert pargs["config"] is None
|
||||
assert pargs['config'] is None
|
||||
|
||||
# When file exists:
|
||||
mocker.patch.object(Path, "is_file", MagicMock(side_effect=[False, True]))
|
||||
mocker.patch.object(Path, 'is_file', MagicMock(side_effect=[False, True]))
|
||||
args = [
|
||||
'download-data',
|
||||
]
|
||||
pargs = Arguments(args).get_parsed_arg()
|
||||
# config is added if it exists
|
||||
assert pargs["config"] == ['config.json']
|
||||
assert pargs['config'] == ['config.json']
|
||||
|
||||
|
||||
def test_check_int_positive() -> None:
|
||||
assert check_int_positive("3") == 3
|
||||
assert check_int_positive("1") == 1
|
||||
assert check_int_positive("100") == 100
|
||||
assert check_int_positive('3') == 3
|
||||
assert check_int_positive('1') == 1
|
||||
assert check_int_positive('100') == 100
|
||||
|
||||
with pytest.raises(argparse.ArgumentTypeError):
|
||||
check_int_positive("-2")
|
||||
check_int_positive('-2')
|
||||
|
||||
with pytest.raises(argparse.ArgumentTypeError):
|
||||
check_int_positive("0")
|
||||
check_int_positive('0')
|
||||
|
||||
with pytest.raises(argparse.ArgumentTypeError):
|
||||
check_int_positive("3.5")
|
||||
check_int_positive('3.5')
|
||||
|
||||
with pytest.raises(argparse.ArgumentTypeError):
|
||||
check_int_positive("DeadBeef")
|
||||
check_int_positive('DeadBeef')
|
||||
|
@@ -21,7 +21,7 @@ from freqtrade.configuration.deprecated_settings import (
|
||||
from freqtrade.configuration.load_config import load_config_file, log_config_error_range
|
||||
from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.loggers import _set_loggers, setup_logging
|
||||
from freqtrade.loggers import _set_loggers, setup_logging, setup_logging_pre
|
||||
from freqtrade.state import RunMode
|
||||
from tests.conftest import (log_has, log_has_re,
|
||||
patched_configuration_load_config_file)
|
||||
@@ -674,10 +674,12 @@ def test_set_loggers_syslog(mocker):
|
||||
'logfile': 'syslog:/dev/log',
|
||||
}
|
||||
|
||||
setup_logging_pre()
|
||||
setup_logging(config)
|
||||
assert len(logger.handlers) == 2
|
||||
assert len(logger.handlers) == 3
|
||||
assert [x for x in logger.handlers if type(x) == logging.handlers.SysLogHandler]
|
||||
assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
|
||||
assert [x for x in logger.handlers if type(x) == logging.handlers.BufferingHandler]
|
||||
# reset handlers to not break pytest
|
||||
logger.handlers = orig_handlers
|
||||
|
||||
@@ -727,7 +729,10 @@ def test_set_logfile(default_conf, mocker):
|
||||
assert validated_conf['logfile'] == "test_file.log"
|
||||
f = Path("test_file.log")
|
||||
assert f.is_file()
|
||||
f.unlink()
|
||||
try:
|
||||
f.unlink()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def test_load_config_warn_forcebuy(default_conf, mocker, caplog) -> None:
|
||||
@@ -1005,7 +1010,7 @@ def test_pairlist_resolving_fallback(mocker):
|
||||
|
||||
args = Arguments(arglist).get_parsed_arg()
|
||||
# Fix flaky tests if config.json exists
|
||||
args["config"] = None
|
||||
args['config'] = None
|
||||
|
||||
configuration = Configuration(args, RunMode.OTHER)
|
||||
config = configuration.get_config()
|
||||
|
18
tests/test_indicators.py
Normal file
18
tests/test_indicators.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def test_crossed_numpy_types():
|
||||
"""
|
||||
This test is only present since this method currently diverges from the qtpylib implementation.
|
||||
And we must ensure to not break this again once we update from the original source.
|
||||
"""
|
||||
series = pd.Series([56, 97, 19, 76, 65, 25, 87, 91, 79, 79])
|
||||
expected_result = pd.Series([False, True, False, True, False, False, True, False, False, False])
|
||||
|
||||
assert qtpylib.crossed_above(series, 60).equals(expected_result)
|
||||
assert qtpylib.crossed_above(series, 60.0).equals(expected_result)
|
||||
assert qtpylib.crossed_above(series, np.int32(60)).equals(expected_result)
|
||||
assert qtpylib.crossed_above(series, np.int64(60)).equals(expected_result)
|
||||
assert qtpylib.crossed_above(series, np.float64(60.0)).equals(expected_result)
|
@@ -44,19 +44,19 @@ def test_parse_args_backtesting(mocker) -> None:
|
||||
|
||||
|
||||
def test_main_start_hyperopt(mocker) -> None:
|
||||
mocker.patch.object(Path, "is_file", MagicMock(side_effect=[False, True]))
|
||||
mocker.patch.object(Path, 'is_file', MagicMock(side_effect=[False, True]))
|
||||
hyperopt_mock = mocker.patch('freqtrade.commands.start_hyperopt', MagicMock())
|
||||
hyperopt_mock.__name__ = PropertyMock("start_hyperopt")
|
||||
hyperopt_mock.__name__ = PropertyMock('start_hyperopt')
|
||||
# it's sys.exit(0) at the end of hyperopt
|
||||
with pytest.raises(SystemExit):
|
||||
main(['hyperopt'])
|
||||
assert hyperopt_mock.call_count == 1
|
||||
call_args = hyperopt_mock.call_args[0][0]
|
||||
assert call_args["config"] == ['config.json']
|
||||
assert call_args["verbosity"] == 0
|
||||
assert call_args["command"] == 'hyperopt'
|
||||
assert call_args["func"] is not None
|
||||
assert callable(call_args["func"])
|
||||
assert call_args['config'] == ['config.json']
|
||||
assert call_args['verbosity'] == 0
|
||||
assert call_args['command'] == 'hyperopt'
|
||||
assert call_args['func'] is not None
|
||||
assert callable(call_args['func'])
|
||||
|
||||
|
||||
def test_main_fatal_exception(mocker, default_conf, caplog) -> None:
|
||||
|
@@ -362,22 +362,22 @@ def test_start_plot_profit(mocker):
|
||||
def test_start_plot_profit_error(mocker):
|
||||
|
||||
args = [
|
||||
"plot-profit",
|
||||
"--pairs", "ETH/BTC"
|
||||
'plot-profit',
|
||||
'--pairs', 'ETH/BTC'
|
||||
]
|
||||
argsp = get_args(args)
|
||||
# Make sure we use no config. Details: #2241
|
||||
# not resetting config causes random failures if config.json exists
|
||||
argsp["config"] = []
|
||||
argsp['config'] = []
|
||||
with pytest.raises(OperationalException):
|
||||
start_plot_profit(argsp)
|
||||
|
||||
|
||||
def test_plot_profit(default_conf, mocker, testdatadir, caplog):
|
||||
default_conf['trade_source'] = 'file'
|
||||
default_conf["datadir"] = testdatadir
|
||||
default_conf['exportfilename'] = testdatadir / "backtest-result_test_nofile.json"
|
||||
default_conf['pairs'] = ["ETH/BTC", "LTC/BTC"]
|
||||
default_conf['datadir'] = testdatadir
|
||||
default_conf['exportfilename'] = testdatadir / 'backtest-result_test_nofile.json'
|
||||
default_conf['pairs'] = ['ETH/BTC', 'LTC/BTC']
|
||||
|
||||
profit_mock = MagicMock()
|
||||
store_mock = MagicMock()
|
||||
|
@@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
import talib.abstract as ta
|
||||
import pandas as pd
|
||||
|
||||
|
BIN
tests/testdata/UNITTEST_BTC-5m.h5
vendored
Normal file
BIN
tests/testdata/UNITTEST_BTC-5m.h5
vendored
Normal file
Binary file not shown.
BIN
tests/testdata/XRP_ETH-trades.h5
vendored
Normal file
BIN
tests/testdata/XRP_ETH-trades.h5
vendored
Normal file
Binary file not shown.
Reference in New Issue
Block a user