2019-10-31 06:15:45 +00:00
|
|
|
import logging
|
|
|
|
import time
|
2022-10-23 12:17:01 +00:00
|
|
|
from datetime import timedelta
|
2019-10-31 06:15:45 +00:00
|
|
|
from unittest.mock import MagicMock, PropertyMock
|
|
|
|
|
2022-10-23 12:17:01 +00:00
|
|
|
import time_machine
|
|
|
|
|
2019-10-31 06:15:45 +00:00
|
|
|
from freqtrade.data.dataprovider import DataProvider
|
2021-06-08 19:20:35 +00:00
|
|
|
from freqtrade.enums import State
|
2019-10-31 06:15:45 +00:00
|
|
|
from freqtrade.worker import Worker
|
2023-02-25 19:26:04 +00:00
|
|
|
from tests.conftest import EXMS, get_patched_worker, log_has, log_has_re
|
2019-10-31 06:15:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_worker_state(mocker, default_conf, markets) -> None:
|
2023-02-25 19:26:04 +00:00
|
|
|
mocker.patch(f'{EXMS}.markets', PropertyMock(return_value=markets))
|
2019-10-31 06:15:45 +00:00
|
|
|
worker = get_patched_worker(mocker, default_conf)
|
2020-01-29 14:08:36 +00:00
|
|
|
assert worker.freqtrade.state is State.RUNNING
|
2019-10-31 06:15:45 +00:00
|
|
|
|
|
|
|
default_conf.pop('initial_state')
|
|
|
|
worker = Worker(args=None, config=default_conf)
|
2020-01-29 14:08:36 +00:00
|
|
|
assert worker.freqtrade.state is State.STOPPED
|
2019-10-31 06:15:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_worker_running(mocker, default_conf, caplog) -> None:
|
|
|
|
mock_throttle = MagicMock()
|
|
|
|
mocker.patch('freqtrade.worker.Worker._throttle', mock_throttle)
|
|
|
|
mocker.patch('freqtrade.persistence.Trade.stoploss_reinitialization', MagicMock())
|
|
|
|
|
|
|
|
worker = get_patched_worker(mocker, default_conf)
|
|
|
|
|
|
|
|
state = worker._worker(old_state=None)
|
|
|
|
assert state is State.RUNNING
|
|
|
|
assert log_has('Changing state to: RUNNING', caplog)
|
|
|
|
assert mock_throttle.call_count == 1
|
|
|
|
# Check strategy is loaded, and received a dataprovider object
|
|
|
|
assert worker.freqtrade.strategy
|
|
|
|
assert worker.freqtrade.strategy.dp
|
|
|
|
assert isinstance(worker.freqtrade.strategy.dp, DataProvider)
|
|
|
|
|
|
|
|
|
|
|
|
def test_worker_stopped(mocker, default_conf, caplog) -> None:
|
|
|
|
mock_throttle = MagicMock()
|
|
|
|
mocker.patch('freqtrade.worker.Worker._throttle', mock_throttle)
|
|
|
|
|
|
|
|
worker = get_patched_worker(mocker, default_conf)
|
2020-01-29 14:08:36 +00:00
|
|
|
worker.freqtrade.state = State.STOPPED
|
2019-10-31 06:15:45 +00:00
|
|
|
state = worker._worker(old_state=State.RUNNING)
|
|
|
|
assert state is State.STOPPED
|
2021-12-23 19:33:13 +00:00
|
|
|
assert log_has('Changing state from RUNNING to: STOPPED', caplog)
|
2020-02-22 22:45:15 +00:00
|
|
|
assert mock_throttle.call_count == 1
|
2019-10-31 06:15:45 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_throttle(mocker, default_conf, caplog) -> None:
|
|
|
|
def throttled_func():
|
|
|
|
return 42
|
|
|
|
|
|
|
|
caplog.set_level(logging.DEBUG)
|
|
|
|
worker = get_patched_worker(mocker, default_conf)
|
|
|
|
|
|
|
|
start = time.time()
|
2020-02-22 22:45:15 +00:00
|
|
|
result = worker._throttle(throttled_func, throttle_secs=0.1)
|
2019-10-31 06:15:45 +00:00
|
|
|
end = time.time()
|
|
|
|
|
|
|
|
assert result == 42
|
2022-10-23 12:17:01 +00:00
|
|
|
assert 0.3 > end - start > 0.1
|
2020-03-05 05:20:36 +00:00
|
|
|
assert log_has_re(r"Throttling with 'throttled_func\(\)': sleep for \d\.\d{2} s.*", caplog)
|
2019-10-31 06:15:45 +00:00
|
|
|
|
2020-02-22 22:45:15 +00:00
|
|
|
result = worker._throttle(throttled_func, throttle_secs=-1)
|
2019-10-31 06:15:45 +00:00
|
|
|
assert result == 42
|
|
|
|
|
|
|
|
|
2022-10-23 12:17:01 +00:00
|
|
|
def test_throttle_sleep_time(mocker, default_conf, caplog) -> None:
|
|
|
|
|
|
|
|
caplog.set_level(logging.DEBUG)
|
|
|
|
worker = get_patched_worker(mocker, default_conf)
|
|
|
|
sleep_mock = mocker.patch("freqtrade.worker.Worker._sleep")
|
|
|
|
with time_machine.travel("2022-09-01 05:00:00 +00:00") as t:
|
|
|
|
def throttled_func(x=1):
|
|
|
|
t.shift(timedelta(seconds=x))
|
|
|
|
return 42
|
|
|
|
|
|
|
|
assert worker._throttle(throttled_func, throttle_secs=5) == 42
|
|
|
|
# This moves the clock by 1 second
|
|
|
|
assert sleep_mock.call_count == 1
|
|
|
|
assert 3.8 < sleep_mock.call_args[0][0] < 4.1
|
|
|
|
|
|
|
|
sleep_mock.reset_mock()
|
|
|
|
# This moves the clock by 1 second
|
|
|
|
assert worker._throttle(throttled_func, throttle_secs=10) == 42
|
|
|
|
assert sleep_mock.call_count == 1
|
|
|
|
assert 8.8 < sleep_mock.call_args[0][0] < 9.1
|
|
|
|
|
|
|
|
sleep_mock.reset_mock()
|
|
|
|
# This moves the clock by 5 second, so we only throttle by 5s
|
|
|
|
assert worker._throttle(throttled_func, throttle_secs=10, x=5) == 42
|
|
|
|
assert sleep_mock.call_count == 1
|
|
|
|
assert 4.8 < sleep_mock.call_args[0][0] < 5.1
|
|
|
|
|
|
|
|
t.move_to("2022-09-01 05:01:00 +00:00")
|
|
|
|
sleep_mock.reset_mock()
|
|
|
|
# Throttle for more than 5m (1 timeframe)
|
|
|
|
assert worker._throttle(throttled_func, throttle_secs=400, x=5) == 42
|
|
|
|
assert sleep_mock.call_count == 1
|
|
|
|
assert 394.8 < sleep_mock.call_args[0][0] < 395.1
|
|
|
|
|
2022-10-23 12:56:51 +00:00
|
|
|
t.move_to("2022-09-01 05:01:00 +00:00")
|
|
|
|
|
|
|
|
sleep_mock.reset_mock()
|
|
|
|
# Throttle for more than 5m (1 timeframe)
|
|
|
|
assert worker._throttle(throttled_func, throttle_secs=400, timeframe='5m',
|
|
|
|
timeframe_offset=0.4, x=5) == 42
|
|
|
|
assert sleep_mock.call_count == 1
|
|
|
|
# 300 (5m) - 60 (1m - see set time above) - 5 (duration of throttled_func) = 235
|
2022-10-25 06:14:42 +00:00
|
|
|
assert 235.2 < sleep_mock.call_args[0][0] < 235.6
|
2022-10-23 12:56:51 +00:00
|
|
|
|
2022-11-06 10:16:19 +00:00
|
|
|
t.move_to("2022-09-01 05:04:51 +00:00")
|
|
|
|
sleep_mock.reset_mock()
|
|
|
|
# Offset of 5s, so we hit the sweet-spot between "candle" and "candle offset"
|
|
|
|
# Which should not get a throttle iteration to avoid late candle fetching
|
|
|
|
assert worker._throttle(throttled_func, throttle_secs=10, timeframe='5m',
|
|
|
|
timeframe_offset=5, x=1.2) == 42
|
|
|
|
assert sleep_mock.call_count == 1
|
|
|
|
# Time is slightly bigger than throttle secs due to the high timeframe offset.
|
|
|
|
assert 11.1 < sleep_mock.call_args[0][0] < 13.2
|
|
|
|
|
2022-10-23 12:17:01 +00:00
|
|
|
|
2019-10-31 06:15:45 +00:00
|
|
|
def test_throttle_with_assets(mocker, default_conf) -> None:
|
|
|
|
def throttled_func(nb_assets=-1):
|
|
|
|
return nb_assets
|
|
|
|
|
|
|
|
worker = get_patched_worker(mocker, default_conf)
|
|
|
|
|
2020-02-22 22:45:15 +00:00
|
|
|
result = worker._throttle(throttled_func, throttle_secs=0.1, nb_assets=666)
|
2019-10-31 06:15:45 +00:00
|
|
|
assert result == 666
|
|
|
|
|
2020-02-22 22:45:15 +00:00
|
|
|
result = worker._throttle(throttled_func, throttle_secs=0.1)
|
2019-10-31 06:15:45 +00:00
|
|
|
assert result == -1
|
2020-02-22 22:45:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
def test_worker_heartbeat_running(default_conf, mocker, caplog):
|
|
|
|
message = r"Bot heartbeat\. PID=.*state='RUNNING'"
|
|
|
|
|
|
|
|
mock_throttle = MagicMock()
|
|
|
|
mocker.patch('freqtrade.worker.Worker._throttle', mock_throttle)
|
|
|
|
worker = get_patched_worker(mocker, default_conf)
|
|
|
|
|
|
|
|
worker.freqtrade.state = State.RUNNING
|
|
|
|
worker._worker(old_state=State.STOPPED)
|
|
|
|
assert log_has_re(message, caplog)
|
|
|
|
|
|
|
|
caplog.clear()
|
|
|
|
# Message is not shown before interval is up
|
|
|
|
worker._worker(old_state=State.RUNNING)
|
|
|
|
assert not log_has_re(message, caplog)
|
|
|
|
|
|
|
|
caplog.clear()
|
|
|
|
# Set clock - 70 seconds
|
|
|
|
worker._heartbeat_msg -= 70
|
|
|
|
worker._worker(old_state=State.RUNNING)
|
|
|
|
assert log_has_re(message, caplog)
|
|
|
|
|
|
|
|
|
|
|
|
def test_worker_heartbeat_stopped(default_conf, mocker, caplog):
|
|
|
|
message = r"Bot heartbeat\. PID=.*state='STOPPED'"
|
|
|
|
|
|
|
|
mock_throttle = MagicMock()
|
|
|
|
mocker.patch('freqtrade.worker.Worker._throttle', mock_throttle)
|
|
|
|
worker = get_patched_worker(mocker, default_conf)
|
|
|
|
|
|
|
|
worker.freqtrade.state = State.STOPPED
|
|
|
|
worker._worker(old_state=State.RUNNING)
|
|
|
|
assert log_has_re(message, caplog)
|
|
|
|
|
|
|
|
caplog.clear()
|
|
|
|
# Message is not shown before interval is up
|
|
|
|
worker._worker(old_state=State.STOPPED)
|
|
|
|
assert not log_has_re(message, caplog)
|
|
|
|
|
|
|
|
caplog.clear()
|
|
|
|
# Set clock - 70 seconds
|
|
|
|
worker._heartbeat_msg -= 70
|
|
|
|
worker._worker(old_state=State.STOPPED)
|
|
|
|
assert log_has_re(message, caplog)
|