From d4fd13bf501b73b6a54fe1231c9bf9fb8d801b25 Mon Sep 17 00:00:00 2001 From: Dardon Date: Sat, 20 Nov 2021 16:26:07 +0000 Subject: [PATCH 1/4] Telegram and log prints strategy version. --- freqtrade/rpc/telegram.py | 6 +++++- freqtrade/strategy/interface.py | 6 ++++++ freqtrade/worker.py | 8 ++++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 0e1a6fe27..7fa4cf0f9 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1304,7 +1304,11 @@ class Telegram(RPCHandler): :param update: message update :return: None """ - self._send_msg('*Version:* `{}`'.format(__version__)) + strategy_version = self._rpc._freqtrade.strategy.version() + if strategy_version is None: + self._send_msg('*Version:* `{}`'.format(__version__)) + else: + self._send_msg('*Freqtrade version:* `{}`, *Strategy version:* `{}`'.format(__version__, strategy_version)) @authorized_only def _show_config(self, update: Update, context: CallbackContext) -> None: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index d4b496ed0..38be19384 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -393,6 +393,12 @@ class IStrategy(ABC, HyperStrategyMixin): ] """ return [] + + def version(self) -> str: + """ + Returns version of the strategy. + """ + return None ### # END - Intended to be overridden by strategy diff --git a/freqtrade/worker.py b/freqtrade/worker.py index 5c0de86ff..c3822d2fc 100755 --- a/freqtrade/worker.py +++ b/freqtrade/worker.py @@ -113,12 +113,16 @@ class Worker: if self._heartbeat_interval: now = time.time() if (now - self._heartbeat_msg) > self._heartbeat_interval: + version = __version__ + strategy_version = self.freqtrade.strategy.version() + if (strategy_version != None): + version += ', strategy_version: ' + strategy_version logger.info(f"Bot heartbeat. PID={getpid()}, " - f"version='{__version__}', state='{state.name}'") + f"version='{version}', state='{state.name}'") self._heartbeat_msg = now return state - + def _throttle(self, func: Callable[..., Any], throttle_secs: float, *args, **kwargs) -> Any: """ Throttles the given callable that it From 2080bf09525847129e97d573ce4ffaa4476ea60f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Dec 2021 14:40:05 +0100 Subject: [PATCH 2/4] Fix some formatting errors, add test for strategy version --- freqtrade/rpc/telegram.py | 9 +++++---- freqtrade/strategy/interface.py | 2 +- freqtrade/worker.py | 6 +++--- tests/rpc/test_rpc_telegram.py | 10 +++++++++- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 7fa4cf0f9..e6af85267 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1305,10 +1305,11 @@ class Telegram(RPCHandler): :return: None """ strategy_version = self._rpc._freqtrade.strategy.version() - if strategy_version is None: - self._send_msg('*Version:* `{}`'.format(__version__)) - else: - self._send_msg('*Freqtrade version:* `{}`, *Strategy version:* `{}`'.format(__version__, strategy_version)) + version_string = f'*Version:* `{__version__}`' + if strategy_version is not None: + version_string += f', *Strategy version: * `{strategy_version}`' + + self._send_msg(version_string) @authorized_only def _show_config(self, update: Update, context: CallbackContext) -> None: diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 38be19384..088777654 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -393,7 +393,7 @@ class IStrategy(ABC, HyperStrategyMixin): ] """ return [] - + def version(self) -> str: """ Returns version of the strategy. diff --git a/freqtrade/worker.py b/freqtrade/worker.py index c3822d2fc..2fe2de9e1 100755 --- a/freqtrade/worker.py +++ b/freqtrade/worker.py @@ -115,14 +115,14 @@ class Worker: if (now - self._heartbeat_msg) > self._heartbeat_interval: version = __version__ strategy_version = self.freqtrade.strategy.version() - if (strategy_version != None): - version += ', strategy_version: ' + strategy_version + if (strategy_version is not None): + version += ', strategy_version: ' + strategy_version logger.info(f"Bot heartbeat. PID={getpid()}, " f"version='{version}', state='{state.name}'") self._heartbeat_msg = now return state - + def _throttle(self, func: Callable[..., Any], throttle_secs: float, *args, **kwargs) -> Any: """ Throttles the given callable that it diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 1247affae..c1677320e 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1559,12 +1559,20 @@ def test_help_handle(default_conf, update, mocker) -> None: def test_version_handle(default_conf, update, mocker) -> None: - telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) + telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram._version(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert '*Version:* `{}`'.format(__version__) in msg_mock.call_args_list[0][0][0] + msg_mock.reset_mock() + freqtradebot.strategy.version = lambda: '1.1.1' + + telegram._version(update=update, context=MagicMock()) + assert msg_mock.call_count == 1 + assert '*Version:* `{}`'.format(__version__) in msg_mock.call_args_list[0][0][0] + assert '*Strategy version: * `1.1.1`' in msg_mock.call_args_list[0][0][0] + def test_show_config_handle(default_conf, update, mocker) -> None: From e3190cf8a88533baca256775621c586afb821243 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Dec 2021 14:44:03 +0100 Subject: [PATCH 3/4] Update documentation --- docs/strategy-advanced.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index 573d184ff..4cc607883 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -127,6 +127,21 @@ The provided exit-tag is then used as sell-reason - and shown as such in backtes !!! Note `sell_reason` is limited to 100 characters, remaining data will be truncated. +## Strategy version + +You can implement custom strategy versioning by using the "version" method, and returning the version you would like this strategy to have. + +``` python +def version(self) -> str: + """ + Returns version of the strategy. + """ + return "1.1" +``` + +!!! Note + You should make sure to implement proper version control (like a git repository) alongside this, as freqtrade will not keep historic versions of your strategy, so it's up to the user to be able to eventually roll back to a prior version of the strategy. + ## Derived strategies The strategies can be derived from other strategies. This avoids duplication of your custom strategy code. You can use this technique to override small parts of your main strategy, leaving the rest untouched: From d0467b30ba8055ab347ab82cd899b11307e9a59f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 4 Dec 2021 14:49:45 +0100 Subject: [PATCH 4/4] Add strategy_version to API response --- freqtrade/rpc/api_server/api_schemas.py | 1 + freqtrade/rpc/api_server/api_v1.py | 4 +++- freqtrade/rpc/rpc.py | 4 +++- freqtrade/strategy/interface.py | 2 +- tests/rpc/test_rpc_apiserver.py | 1 + 5 files changed, 9 insertions(+), 3 deletions(-) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index f9389d810..d669600a2 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -145,6 +145,7 @@ class OrderTypes(BaseModel): class ShowConfig(BaseModel): version: str + strategy_version: Optional[str] api_version: float dry_run: bool stake_currency: str diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 65b6941e2..721d5dbc0 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -121,9 +121,11 @@ def edge(rpc: RPC = Depends(get_rpc)): @router.get('/show_config', response_model=ShowConfig, tags=['info']) def show_config(rpc: Optional[RPC] = Depends(get_rpc_optional), config=Depends(get_config)): state = '' + strategy_version = None if rpc: state = rpc._freqtrade.state - resp = RPC._rpc_show_config(config, state) + strategy_version = rpc._freqtrade.strategy.version() + resp = RPC._rpc_show_config(config, state, strategy_version) resp['api_version'] = API_VERSION return resp diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index c21890b7d..b3728d67a 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -98,7 +98,8 @@ class RPC: self._fiat_converter = CryptoToFiatConverter() @staticmethod - def _rpc_show_config(config, botstate: Union[State, str]) -> Dict[str, Any]: + def _rpc_show_config(config, botstate: Union[State, str], + strategy_version: Optional[str] = None) -> Dict[str, Any]: """ Return a dict of config options. Explicitly does NOT return the full config to avoid leakage of sensitive @@ -106,6 +107,7 @@ class RPC: """ val = { 'version': __version__, + 'strategy_version': strategy_version, 'dry_run': config['dry_run'], 'stake_currency': config['stake_currency'], 'stake_currency_decimals': decimals_per_coin(config['stake_currency']), diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 088777654..05469317b 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -394,7 +394,7 @@ class IStrategy(ABC, HyperStrategyMixin): """ return [] - def version(self) -> str: + def version(self) -> Optional[str]: """ Returns version of the strategy. """ diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 76372df55..883a33e38 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -533,6 +533,7 @@ def test_api_show_config(botclient): assert rc.json()['timeframe_min'] == 5 assert rc.json()['state'] == 'running' assert rc.json()['bot_name'] == 'freqtrade' + assert rc.json()['strategy_version'] is None assert not rc.json()['trailing_stop'] assert 'bid_strategy' in rc.json() assert 'ask_strategy' in rc.json()