Merge branch 'develop' into pr/imxuwang/3799

This commit is contained in:
Matthias
2020-10-22 07:55:48 +02:00
174 changed files with 4148 additions and 1648 deletions

View File

@@ -13,8 +13,7 @@ from freqtrade.persistence import Trade
from freqtrade.rpc import RPC, RPCException
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
from freqtrade.state import State
from tests.conftest import (create_mock_trades, get_patched_freqtradebot,
patch_get_signal)
from tests.conftest import create_mock_trades, get_patched_freqtradebot, patch_get_signal
# Functions for recurrent object patching
@@ -313,7 +312,6 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog):
with pytest.raises(RPCException, match='invalid argument'):
rpc._rpc_delete('200')
create_mock_trades(fee)
trades = Trade.query.all()
trades[1].stoploss_order_id = '1234'
trades[2].stoploss_order_id = '1234'
@@ -717,11 +715,13 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
mocker.patch(
'freqtrade.exchange.Exchange.fetch_order',
side_effect=[{
'id': '1234',
'status': 'open',
'type': 'limit',
'side': 'buy',
'filled': filled_amount
}, {
'id': '1234',
'status': 'closed',
'type': 'limit',
'side': 'buy',
@@ -837,10 +837,10 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
assert counts["current"] == 1
def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order) -> None:
def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> None:
default_conf['forcebuy_enable'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
buy_mm = MagicMock(return_value={'id': limit_buy_order['id']})
buy_mm = MagicMock(return_value=limit_buy_order_open)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),

View File

@@ -2,7 +2,8 @@
Unit test file for rpc/api_server.py
"""
from datetime import datetime
from datetime import datetime, timedelta
from pathlib import Path
from unittest.mock import ANY, MagicMock, PropertyMock
import pytest
@@ -11,11 +12,11 @@ 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.persistence import PairLock, Trade
from freqtrade.rpc.api_server import BASE_URI, ApiServer
from freqtrade.state import State
from tests.conftest import (create_mock_trades, get_patched_freqtradebot,
log_has, patch_get_signal)
from tests.conftest import create_mock_trades, get_patched_freqtradebot, log_has, patch_get_signal
_TEST_USER = "FreqTrader"
_TEST_PASS = "SuperSecurePassword1!"
@@ -327,6 +328,30 @@ def test_api_count(botclient, mocker, ticker, fee, markets):
assert rc.json["max"] == 1.0
def test_api_locks(botclient):
ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/locks")
assert_response(rc)
assert 'locks' in rc.json
assert rc.json['lock_count'] == 0
assert rc.json['lock_count'] == len(rc.json['locks'])
PairLock.lock_pair('ETH/BTC', datetime.utcnow() + timedelta(minutes=4), 'randreason')
PairLock.lock_pair('XRP/BTC', datetime.utcnow() + timedelta(minutes=20), 'deadbeef')
rc = client_get(client, f"{BASE_URI}/locks")
assert_response(rc)
assert rc.json['lock_count'] == 2
assert rc.json['lock_count'] == len(rc.json['locks'])
assert 'ETH/BTC' in (rc.json['locks'][0]['pair'], rc.json['locks'][1]['pair'])
assert 'randreason' in (rc.json['locks'][0]['reason'], rc.json['locks'][1]['reason'])
assert 'deadbeef' in (rc.json['locks'][0]['reason'], rc.json['locks'][1]['reason'])
def test_api_show_config(botclient, mocker):
ftbot, client = botclient
patch_get_signal(ftbot, (True, False))
@@ -811,3 +836,176 @@ def test_api_forcesell(botclient, mocker, ticker, fee, markets):
data='{"tradeid": "1"}')
assert_response(rc)
assert rc.json == {'result': 'Created sell order for trade 1.'}
def test_api_pair_candles(botclient, ohlcv_history):
ftbot, client = botclient
timeframe = '5m'
amount = 2
# No pair
rc = client_get(client,
f"{BASE_URI}/pair_candles?limit={amount}&timeframe={timeframe}")
assert_response(rc, 400)
# No timeframe
rc = client_get(client,
f"{BASE_URI}/pair_candles?pair=XRP%2FBTC")
assert_response(rc, 400)
rc = client_get(client,
f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}")
assert_response(rc)
assert 'columns' in rc.json
assert 'data_start_ts' in rc.json
assert 'data_start' in rc.json
assert 'data_stop' in rc.json
assert 'data_stop_ts' in rc.json
assert len(rc.json['data']) == 0
ohlcv_history['sma'] = ohlcv_history['close'].rolling(2).mean()
ohlcv_history['buy'] = 0
ohlcv_history.loc[1, 'buy'] = 1
ohlcv_history['sell'] = 0
ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history)
rc = client_get(client,
f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}")
assert_response(rc)
assert 'strategy' in rc.json
assert rc.json['strategy'] == 'DefaultStrategy'
assert 'columns' in rc.json
assert 'data_start_ts' in rc.json
assert 'data_start' in rc.json
assert 'data_stop' in rc.json
assert 'data_stop_ts' in rc.json
assert rc.json['data_start'] == '2017-11-26 08:50:00+00:00'
assert rc.json['data_start_ts'] == 1511686200000
assert rc.json['data_stop'] == '2017-11-26 08:55:00+00:00'
assert rc.json['data_stop_ts'] == 1511686500000
assert isinstance(rc.json['columns'], list)
assert rc.json['columns'] == ['date', 'open', 'high',
'low', 'close', 'volume', 'sma', 'buy', 'sell',
'__date_ts', '_buy_signal_open', '_sell_signal_open']
assert 'pair' in rc.json
assert rc.json['pair'] == 'XRP/BTC'
assert 'data' in rc.json
assert len(rc.json['data']) == amount
assert (rc.json['data'] ==
[['2017-11-26 08:50:00', 8.794e-05, 8.948e-05, 8.794e-05, 8.88e-05, 0.0877869,
None, 0, 0, 1511686200000, None, None],
['2017-11-26 08:55:00', 8.88e-05, 8.942e-05, 8.88e-05,
8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0, 1511686500000, 8.88e-05, None]
])
def test_api_pair_history(botclient, ohlcv_history):
ftbot, client = botclient
timeframe = '5m'
# No pair
rc = client_get(client,
f"{BASE_URI}/pair_history?timeframe={timeframe}"
"&timerange=20180111-20180112&strategy=DefaultStrategy")
assert_response(rc, 400)
# No Timeframe
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC"
"&timerange=20180111-20180112&strategy=DefaultStrategy")
assert_response(rc, 400)
# No timerange
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
"&strategy=DefaultStrategy")
assert_response(rc, 400)
# No strategy
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
"&timerange=20180111-20180112")
assert_response(rc, 400)
# Working
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
"&timerange=20180111-20180112&strategy=DefaultStrategy")
assert_response(rc, 200)
assert rc.json['length'] == 289
assert len(rc.json['data']) == rc.json['length']
assert 'columns' in rc.json
assert 'data' in rc.json
assert rc.json['pair'] == 'UNITTEST/BTC'
assert rc.json['strategy'] == 'DefaultStrategy'
assert rc.json['data_start'] == '2018-01-11 00:00:00+00:00'
assert rc.json['data_start_ts'] == 1515628800000
assert rc.json['data_stop'] == '2018-01-12 00:00:00+00:00'
assert rc.json['data_stop_ts'] == 1515715200000
def test_api_plot_config(botclient):
ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/plot_config")
assert_response(rc)
assert rc.json == {}
ftbot.strategy.plot_config = {'main_plot': {'sma': {}},
'subplots': {'RSI': {'rsi': {'color': 'red'}}}}
rc = client_get(client, f"{BASE_URI}/plot_config")
assert_response(rc)
assert rc.json == ftbot.strategy.plot_config
assert isinstance(rc.json['main_plot'], dict)
def test_api_strategies(botclient):
ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/strategies")
assert_response(rc)
assert rc.json == {'strategies': ['DefaultStrategy', 'TestStrategyLegacy']}
def test_api_strategy(botclient):
ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/strategy/DefaultStrategy")
assert_response(rc)
assert rc.json['strategy'] == 'DefaultStrategy'
data = (Path(__file__).parents[1] / "strategy/strats/default_strategy.py").read_text()
assert rc.json['code'] == data
rc = client_get(client, f"{BASE_URI}/strategy/NoStrat")
assert_response(rc, 404)
def test_list_available_pairs(botclient):
ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/available_pairs")
assert_response(rc)
assert rc.json['length'] == 12
assert isinstance(rc.json['pairs'], list)
rc = client_get(client, f"{BASE_URI}/available_pairs?timeframe=5m")
assert_response(rc)
assert rc.json['length'] == 12
rc = client_get(client, f"{BASE_URI}/available_pairs?stake_currency=ETH")
assert_response(rc)
assert rc.json['length'] == 1
assert rc.json['pairs'] == ['XRP/ETH']
assert len(rc.json['pair_interval']) == 2
rc = client_get(client, f"{BASE_URI}/available_pairs?stake_currency=ETH&timeframe=5m")
assert_response(rc)
assert rc.json['length'] == 1
assert rc.json['pairs'] == ['XRP/ETH']
assert len(rc.json['pair_interval']) == 1

View File

@@ -1,10 +1,10 @@
# pragma pylint: disable=missing-docstring, C0103
import time
import logging
import time
from unittest.mock import MagicMock
from freqtrade.rpc import RPCMessageType, RPCManager
from tests.conftest import log_has, get_patched_freqtradebot
from freqtrade.rpc import RPCManager, RPCMessageType
from tests.conftest import get_patched_freqtradebot, log_has
def test__init__(mocker, default_conf) -> None:
@@ -124,10 +124,10 @@ 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.CUSTOM_NOTIFICATION,
rpc_manager.send_msg({'type': RPCMessageType.STARTUP_NOTIFICATION,
'status': 'TestMessage'})
assert log_has(
"Message type RPCMessageType.CUSTOM_NOTIFICATION not implemented by handler webhook.",
"Message type 'startup' not implemented by handler webhook.",
caplog)

View File

@@ -18,14 +18,13 @@ from freqtrade.constants import CANCEL_REASON
from freqtrade.edge import PairInfo
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.loggers import setup_logging
from freqtrade.persistence import Trade
from freqtrade.persistence import PairLock, Trade
from freqtrade.rpc import RPCMessageType
from freqtrade.rpc.telegram import Telegram, authorized_only
from freqtrade.state import State
from freqtrade.strategy.interface import SellType
from tests.conftest import (create_mock_trades, get_patched_freqtradebot,
log_has, patch_exchange, patch_get_signal,
patch_whitelist)
from tests.conftest import (create_mock_trades, get_patched_freqtradebot, log_has, patch_exchange,
patch_get_signal, patch_whitelist)
class DummyCls(Telegram):
@@ -76,15 +75,15 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], "
"['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['trades'], "
"['delete'], ['performance'], ['daily'], ['count'], ['reload_config', "
"'reload_conf'], ['show_config', 'show_conf'], ['stopbuy'], "
"['delete'], ['performance'], ['daily'], ['count'], ['locks'], "
"['reload_config', 'reload_conf'], ['show_config', 'show_conf'], ['stopbuy'], "
"['whitelist'], ['blacklist'], ['logs'], ['edge'], ['help'], ['version'], "
"['stats']]")
assert log_has(message_str, caplog)
def test_cleanup(default_conf, mocker) -> None:
def test_cleanup(default_conf, mocker, ) -> None:
updater_mock = MagicMock()
updater_mock.stop = MagicMock()
mocker.patch('freqtrade.rpc.telegram.Updater', updater_mock)
@@ -94,13 +93,9 @@ def test_cleanup(default_conf, mocker) -> None:
assert telegram._updater.stop.call_count == 1
def test_authorized_only(default_conf, mocker, caplog) -> None:
def test_authorized_only(default_conf, mocker, caplog, update) -> None:
patch_exchange(mocker)
chat = Chat(0, 0)
update = Update(randint(1, 100))
update.message = Message(randint(1, 100), 0, datetime.utcnow(), chat)
default_conf['telegram']['enabled'] = False
bot = FreqtradeBot(default_conf)
patch_get_signal(bot, (True, False))
@@ -116,7 +111,7 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
patch_exchange(mocker)
chat = Chat(0xdeadbeef, 0)
update = Update(randint(1, 100))
update.message = Message(randint(1, 100), 0, datetime.utcnow(), chat)
update.message = Message(randint(1, 100), datetime.utcnow(), chat)
default_conf['telegram']['enabled'] = False
bot = FreqtradeBot(default_conf)
@@ -129,12 +124,9 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
assert not log_has('Exception occurred within Telegram module', caplog)
def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
def test_authorized_only_exception(default_conf, mocker, caplog, update) -> None:
patch_exchange(mocker)
update = Update(randint(1, 100))
update.message = Message(randint(1, 100), 0, datetime.utcnow(), Chat(0, 0))
default_conf['telegram']['enabled'] = False
bot = FreqtradeBot(default_conf)
@@ -148,7 +140,7 @@ def test_authorized_only_exception(default_conf, mocker, caplog) -> None:
assert log_has('Exception occurred within Telegram module', caplog)
def test_telegram_status(default_conf, update, mocker, fee, ticker,) -> None:
def test_telegram_status(default_conf, update, mocker) -> None:
update.message.chat.id = "123"
default_conf['telegram']['enabled'] = False
default_conf['telegram']['chat_id'] = "123"
@@ -254,7 +246,6 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': 'mocked_order_id'}),
get_fee=fee,
)
msg_mock = MagicMock()
@@ -1008,7 +999,6 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker,
buy=MagicMock(return_value={'id': 'mocked_order_id'}),
get_fee=fee,
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
@@ -1035,6 +1025,43 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
assert msg in msg_mock.call_args_list[0][0][0]
def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None:
msg_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
_send_msg=msg_mock
)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
fetch_ticker=ticker,
get_fee=fee,
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False))
telegram = Telegram(freqtradebot)
freqtradebot.state = State.STOPPED
telegram._locks(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert 'not running' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
freqtradebot.state = State.RUNNING
PairLock.lock_pair('ETH/BTC', arrow.utcnow().shift(minutes=4).datetime, 'randreason')
PairLock.lock_pair('XRP/BTC', arrow.utcnow().shift(minutes=20).datetime, 'deadbeef')
telegram._locks(update=update, context=MagicMock())
assert 'Pair' in msg_mock.call_args_list[0][0][0]
assert 'Until' in msg_mock.call_args_list[0][0][0]
assert 'Reason\n' in msg_mock.call_args_list[0][0][0]
assert 'ETH/BTC' in msg_mock.call_args_list[0][0][0]
assert 'XRP/BTC' in msg_mock.call_args_list[0][0][0]
assert 'deadbeef' in msg_mock.call_args_list[0][0][0]
assert 'randreason' in msg_mock.call_args_list[0][0][0]
def test_whitelist_static(default_conf, update, mocker) -> None:
msg_mock = MagicMock()
mocker.patch.multiple(
@@ -1142,9 +1169,9 @@ def test_telegram_logs(default_conf, update, mocker) -> None:
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
# Called at least 2 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
assert msg_mock.call_count >= 2
def test_edge_disabled(default_conf, update, mocker) -> None:
@@ -1302,16 +1329,14 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
def test_send_msg_buy_notification(default_conf, mocker) -> None:
def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
msg_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
_send_msg=msg_mock
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot)
telegram.send_msg({
msg = {
'type': RPCMessageType.BUY_NOTIFICATION,
'exchange': 'Bittrex',
'pair': 'ETH/BTC',
@@ -1324,7 +1349,10 @@ def test_send_msg_buy_notification(default_conf, mocker) -> None:
'current_rate': 1.099e-05,
'amount': 1333.3333333333335,
'open_date': arrow.utcnow().shift(hours=-1)
})
}
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot)
telegram.send_msg(msg)
assert msg_mock.call_args[0][0] \
== '\N{LARGE BLUE CIRCLE} *Bittrex:* Buying ETH/BTC\n' \
'*Amount:* `1333.33333333`\n' \
@@ -1332,6 +1360,21 @@ def test_send_msg_buy_notification(default_conf, mocker) -> None:
'*Current Rate:* `0.00001099`\n' \
'*Total:* `(0.001000 BTC, 12.345 USD)`'
freqtradebot.config['telegram']['notification_settings'] = {'buy': 'off'}
caplog.clear()
msg_mock.reset_mock()
telegram.send_msg(msg)
msg_mock.call_count == 0
log_has("Notification 'buy' not sent.", caplog)
freqtradebot.config['telegram']['notification_settings'] = {'buy': 'silent'}
caplog.clear()
msg_mock.reset_mock()
telegram.send_msg(msg)
msg_mock.call_count == 1
msg_mock.call_args_list[0][1]['disable_notification'] is True
def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None:
msg_mock = MagicMock()
@@ -1488,7 +1531,7 @@ def test_warning_notification(default_conf, mocker) -> None:
assert msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Warning:* `message`'
def test_custom_notification(default_conf, mocker) -> None:
def test_startup_notification(default_conf, mocker) -> None:
msg_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
@@ -1498,7 +1541,7 @@ def test_custom_notification(default_conf, mocker) -> None:
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot)
telegram.send_msg({
'type': RPCMessageType.CUSTOM_NOTIFICATION,
'type': RPCMessageType.STARTUP_NOTIFICATION,
'status': '*Custom:* `Hello World`'
})
assert msg_mock.call_args[0][0] == '*Custom:* `Hello World`'

View File

@@ -150,7 +150,7 @@ def test_send_msg(default_conf, mocker):
default_conf["webhook"]["webhooksellcancel"]["value3"].format(**msg))
for msgtype in [RPCMessageType.STATUS_NOTIFICATION,
RPCMessageType.WARNING_NOTIFICATION,
RPCMessageType.CUSTOM_NOTIFICATION]:
RPCMessageType.STARTUP_NOTIFICATION]:
# Test notification
msg = {
'type': msgtype,
@@ -174,7 +174,7 @@ def test_exception_send_msg(default_conf, mocker, caplog):
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf))
webhook.send_msg({'type': RPCMessageType.BUY_NOTIFICATION})
assert log_has(f"Message type {RPCMessageType.BUY_NOTIFICATION} not configured for webhooks",
assert log_has(f"Message type '{RPCMessageType.BUY_NOTIFICATION}' not configured for webhooks",
caplog)
default_conf["webhook"] = get_webhook_dict()