From bf62fc9b25523a1f119b30304ebce1a111397451 Mon Sep 17 00:00:00 2001 From: Reigo Reinmets Date: Sun, 23 Jan 2022 21:58:46 +0200 Subject: [PATCH 1/5] Add /health endpoint that returns last_process timestamp, fix issue #6009 --- freqtrade/freqtradebot.py | 3 +++ freqtrade/rpc/api_server/api_schemas.py | 4 ++++ freqtrade/rpc/api_server/api_v1.py | 17 +++++++++++------ freqtrade/rpc/rpc.py | 5 +++++ freqtrade/rpc/telegram.py | 18 +++++++++++++++++- tests/rpc/test_rpc.py | 9 +++++++++ tests/rpc/test_rpc_apiserver.py | 10 ++++++++++ 7 files changed, 59 insertions(+), 7 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index e8f24864f..310f21b7b 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -100,6 +100,8 @@ class FreqtradeBot(LoggingMixin): self._exit_lock = Lock() LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe)) + self.last_process = datetime.utcfromtimestamp(0.0) + def notify_status(self, msg: str) -> None: """ Public method for users of this class (worker, etc.) to send notifications @@ -187,6 +189,7 @@ class FreqtradeBot(LoggingMixin): self.enter_positions() Trade.commit() + self.last_process = datetime.utcnow() def process_stopped(self) -> None: """ diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index bbd858795..5709639b3 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -382,3 +382,7 @@ class BacktestResponse(BaseModel): class SysInfo(BaseModel): cpu_pct: List[float] ram_pct: float + + +class Health(BaseModel): + last_process: datetime diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 4c430dd46..506c886a3 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -14,12 +14,12 @@ from freqtrade.rpc import RPC from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, Balances, BlacklistPayload, BlacklistResponse, Count, Daily, DeleteLockRequest, DeleteTrade, ForceBuyPayload, - ForceBuyResponse, ForceSellPayload, Locks, Logs, - OpenTradeSchema, PairHistory, PerformanceEntry, - Ping, PlotConfig, Profit, ResultMsg, ShowConfig, - Stats, StatusMsg, StrategyListResponse, - StrategyResponse, SysInfo, Version, - WhitelistResponse) + ForceBuyResponse, ForceSellPayload, Health, + Locks, Logs, OpenTradeSchema, PairHistory, + PerformanceEntry, Ping, PlotConfig, Profit, + ResultMsg, ShowConfig, Stats, StatusMsg, + StrategyListResponse, StrategyResponse, SysInfo, + Version, WhitelistResponse) from freqtrade.rpc.api_server.deps import get_config, get_exchange, get_rpc, get_rpc_optional from freqtrade.rpc.rpc import RPCException @@ -290,3 +290,8 @@ def list_available_pairs(timeframe: Optional[str] = None, stake_currency: Option @router.get('/sysinfo', response_model=SysInfo, tags=['info']) def sysinfo(): return RPC._rpc_sysinfo() + + +@router.get('/health', response_model=Health, tags=['info']) +def health(rpc: RPC = Depends(get_rpc)): + return rpc._health() diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index c78ff1079..fa301ed60 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -1030,3 +1030,8 @@ class RPC: "cpu_pct": psutil.cpu_percent(interval=1, percpu=True), "ram_pct": psutil.virtual_memory().percent } + + def _health(self) -> Dict[str, str]: + return { + 'last_process': str(self._freqtrade.last_process) + } diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 716694a81..13ad98e0e 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -113,7 +113,7 @@ class Telegram(RPCHandler): r'/stopbuy$', r'/reload_config$', r'/show_config$', r'/logs$', r'/whitelist$', r'/blacklist$', r'/bl_delete$', r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$', - r'/forcebuy$', r'/edge$', r'/help$', r'/version$'] + r'/forcebuy$', r'/edge$', r'/health$', r'/help$', r'/version$'] # Create keys for generation valid_keys_print = [k.replace('$', '') for k in valid_keys] @@ -173,6 +173,7 @@ class Telegram(RPCHandler): CommandHandler(['blacklist_delete', 'bl_delete'], self._blacklist_delete), CommandHandler('logs', self._logs), CommandHandler('edge', self._edge), + CommandHandler('health', self._health), CommandHandler('help', self._help), CommandHandler('version', self._version), ] @@ -1282,6 +1283,7 @@ class Telegram(RPCHandler): "*/logs [limit]:* `Show latest logs - defaults to 10` \n" "*/count:* `Show number of active trades compared to allowed number of trades`\n" "*/edge:* `Shows validated pairs by Edge if it is enabled` \n" + "*/health* `Show latest process timestamp - defaults to 1970-01-01 00:00:00` \n" "_Statistics_\n" "------------\n" @@ -1309,6 +1311,20 @@ class Telegram(RPCHandler): self._send_msg(message, parse_mode=ParseMode.MARKDOWN) + @authorized_only + def _health(self, update: Update, context: CallbackContext) -> None: + """ + Handler for /health + Shows the last process timestamp + """ + try: + health = self._rpc._health() + message = f"Last process: `{health['last_process']}`" + logger.debug(message) + self._send_msg(message) + except RPCException as e: + self._send_msg(str(e)) + @authorized_only def _version(self, update: Update, context: CallbackContext) -> None: """ diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 27c509c94..c7616a295 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -1272,3 +1272,12 @@ def test_rpc_edge_enabled(mocker, edge_conf) -> None: assert ret[0]['Winrate'] == 0.66 assert ret[0]['Expectancy'] == 1.71 assert ret[0]['Stoploss'] == -0.02 + + +def test_rpc_health(mocker, default_conf) -> None: + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + rpc = RPC(freqtradebot) + ret = rpc._health() + assert ret['last_process'] == '1970-01-01 00:00:00' diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 207d80cef..3e891baa8 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1442,3 +1442,13 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir): assert result['status'] == 'reset' assert not result['running'] assert result['status_msg'] == 'Backtest reset' + + +def test_health(botclient): + ftbot, client = botclient + + rc = client_get(client, f"{BASE_URI}/health") + + assert_response(rc) + ret = rc.json() + assert ret['last_process'] == '1970-01-01T00:00:00' From acf6e94591a639310387049e0d093597ec1c0bde Mon Sep 17 00:00:00 2001 From: Reigo Reinmets Date: Mon, 24 Jan 2022 13:56:52 +0200 Subject: [PATCH 2/5] Fix unittest. --- tests/rpc/test_rpc_telegram.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 1d638eed1..5e07e05e5 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -99,7 +99,7 @@ def test_telegram_init(default_conf, mocker, caplog) -> None: "['count'], ['locks'], ['unlock', 'delete_locks'], " "['reload_config', 'reload_conf'], ['show_config', 'show_conf'], " "['stopbuy'], ['whitelist'], ['blacklist'], ['blacklist_delete', 'bl_delete'], " - "['logs'], ['edge'], ['help'], ['version']" + "['logs'], ['edge'], ['health'], ['help'], ['version']" "]") assert log_has(message_str, caplog) From 78986a0defb27be51dc644adfefe65653e6e2974 Mon Sep 17 00:00:00 2001 From: Reigo Reinmets Date: Mon, 24 Jan 2022 14:09:23 +0200 Subject: [PATCH 3/5] I sort managed to fit it on another row. Impressive. --- freqtrade/rpc/api_server/api_v1.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 506c886a3..2dbf43f94 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -14,8 +14,8 @@ from freqtrade.rpc import RPC from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, Balances, BlacklistPayload, BlacklistResponse, Count, Daily, DeleteLockRequest, DeleteTrade, ForceBuyPayload, - ForceBuyResponse, ForceSellPayload, Health, - Locks, Logs, OpenTradeSchema, PairHistory, + ForceBuyResponse, ForceSellPayload, Health, Locks, + Logs, OpenTradeSchema, PairHistory, PerformanceEntry, Ping, PlotConfig, Profit, ResultMsg, ShowConfig, Stats, StatusMsg, StrategyListResponse, StrategyResponse, SysInfo, From e72c3ec19f110803f9be4981ef530b3cb3819838 Mon Sep 17 00:00:00 2001 From: Reigo Reinmets Date: Mon, 24 Jan 2022 15:27:03 +0200 Subject: [PATCH 4/5] Commit just to force tests to run again. --- tests/rpc/test_rpc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index c7616a295..9d8db06f2 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -1279,5 +1279,5 @@ def test_rpc_health(mocker, default_conf) -> None: freqtradebot = get_patched_freqtradebot(mocker, default_conf) rpc = RPC(freqtradebot) - ret = rpc._health() - assert ret['last_process'] == '1970-01-01 00:00:00' + result = rpc._health() + assert result['last_process'] == '1970-01-01 00:00:00' From 15d5389564c5aa5297ed9ce6d123f63ba290747b Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 28 Jan 2022 07:57:43 +0100 Subject: [PATCH 5/5] Update /health endpoint to be in local timezone --- freqtrade/freqtradebot.py | 4 ++-- freqtrade/rpc/api_server/api_schemas.py | 1 + freqtrade/rpc/rpc.py | 8 ++++++-- freqtrade/rpc/telegram.py | 3 +-- tests/rpc/test_rpc.py | 3 ++- tests/rpc/test_rpc_apiserver.py | 3 ++- 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 17d345f48..c98c9a804 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -100,7 +100,7 @@ class FreqtradeBot(LoggingMixin): self._exit_lock = Lock() LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe)) - self.last_process = datetime.utcfromtimestamp(0.0) + self.last_process = datetime(1970, 1, 1, tzinfo=timezone.utc) def notify_status(self, msg: str) -> None: """ @@ -189,7 +189,7 @@ class FreqtradeBot(LoggingMixin): self.enter_positions() Trade.commit() - self.last_process = datetime.utcnow() + self.last_process = datetime.now(timezone.utc) def process_stopped(self) -> None: """ diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 5709639b3..d3a0f3e7d 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -386,3 +386,4 @@ class SysInfo(BaseModel): class Health(BaseModel): last_process: datetime + last_process_ts: int diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index fa301ed60..f57253562 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -10,6 +10,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union import arrow import psutil from dateutil.relativedelta import relativedelta +from dateutil.tz import tzlocal from numpy import NAN, inf, int64, mean from pandas import DataFrame @@ -1031,7 +1032,10 @@ class RPC: "ram_pct": psutil.virtual_memory().percent } - def _health(self) -> Dict[str, str]: + def _health(self) -> Dict[str, Union[str, int]]: + last_p = self._freqtrade.last_process return { - 'last_process': str(self._freqtrade.last_process) + 'last_process': str(last_p), + 'last_process_loc': last_p.astimezone(tzlocal()).strftime(DATETIME_PRINT_FORMAT), + 'last_process_ts': int(last_p.timestamp()), } diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 13ad98e0e..74f56b4c1 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1319,8 +1319,7 @@ class Telegram(RPCHandler): """ try: health = self._rpc._health() - message = f"Last process: `{health['last_process']}`" - logger.debug(message) + message = f"Last process: `{health['last_process_loc']}`" self._send_msg(message) except RPCException as e: self._send_msg(str(e)) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 9d8db06f2..03c068966 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -1280,4 +1280,5 @@ def test_rpc_health(mocker, default_conf) -> None: freqtradebot = get_patched_freqtradebot(mocker, default_conf) rpc = RPC(freqtradebot) result = rpc._health() - assert result['last_process'] == '1970-01-01 00:00:00' + assert result['last_process'] == '1970-01-01 00:00:00+00:00' + assert result['last_process_ts'] == 0 diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 3e891baa8..1c33dd928 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1451,4 +1451,5 @@ def test_health(botclient): assert_response(rc) ret = rc.json() - assert ret['last_process'] == '1970-01-01T00:00:00' + assert ret['last_process_ts'] == 0 + assert ret['last_process'] == '1970-01-01T00:00:00+00:00'