Merge branch 'develop' into db_keep_orders

This commit is contained in:
Matthias
2020-09-01 07:51:16 +02:00
56 changed files with 1038 additions and 423 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,3 @@
import talib.abstract as ta
import pandas as pd

BIN
tests/testdata/UNITTEST_BTC-5m.h5 vendored Normal file

Binary file not shown.

BIN
tests/testdata/XRP_ETH-trades.h5 vendored Normal file

Binary file not shown.