From 0811bca8b4ef384ee6c08a08f7fa6b3e344cc086 Mon Sep 17 00:00:00 2001 From: Timothy Pogue Date: Wed, 21 Sep 2022 15:50:11 -0600 Subject: [PATCH] revise docs, update dp method docstring --- docs/advanced-external-signals.md | 133 +++++++++++++++++++++++++++ docs/configuration.md | 5 +- docs/rest-api.md | 144 ------------------------------ freqtrade/data/dataprovider.py | 1 + mkdocs.yml | 1 + 5 files changed, 137 insertions(+), 147 deletions(-) create mode 100644 docs/advanced-external-signals.md diff --git a/docs/advanced-external-signals.md b/docs/advanced-external-signals.md new file mode 100644 index 000000000..caf708ee2 --- /dev/null +++ b/docs/advanced-external-signals.md @@ -0,0 +1,133 @@ +FreqTrade provides a mechanism whereby an instance may listen to messages from an upstream FreqTrade instance using the message websocket. Mainly, `analyzed_df` and `whitelist` messages. This allows the reuse of computed indicators (and signals) for pairs in multiple bots without needing to compute them multiple times. + +See [Message Websocket](rest-api.md#message-websocket) in the Rest API docs for setting up the `api_server` configuration for your message websocket. + +!!! Note + We strongly recommend to also set `ws_token` to something random and known only to yourself to avoid unauthorized access to your bot. + +### Configuration + +Enable subscribing to an instance by adding the `external_message_consumer` section to the follower's config file. + +```jsonc +{ + //... + "external_message_consumer": { + "enabled": true, + "producers": [ + { + "name": "default", // This can be any name you'd like, default is "default" + "host": "127.0.0.1", // The host from your leader's api_server config + "port": 8080, // The port from your leader's api_server config + "ws_token": "mysecretapitoken" // The ws_token from your leader's api_server config + } + ], + } + //... +} +``` + +See [`config_examples/config_full.example.json`](https://github.com/freqtrade/freqtrade/blob/develop/config_examples/config_full.example.json) for all of the parameters you can set. + +Instead of (or as well as) calculating indicators in `populate_indicators()` the follower instance listens on the connection to a leader instance's message websocket (or multiple leader instances in advanced configurations) and requests the leader's most recently analyzed dataframes for each pair in the active whitelist. + +A follower instance will then have a full copy of the analyzed dataframes without the need to calculate them itself. + +### Example - Leader Strategy + +A simple strategy with multiple indicators. No special considerations are required in the strategy itself. + +```py +class LeaderStrategy(IStrategy): + #... + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Calculate indicators in the standard freqtrade way which can then be broadcast to other instances + """ + dataframe['rsi'] = ta.RSI(dataframe) + bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) + dataframe['bb_lowerband'] = bollinger['lower'] + dataframe['bb_middleband'] = bollinger['mid'] + dataframe['bb_upperband'] = bollinger['upper'] + dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) + + return dataframe + + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Populates the entry signal for the given dataframe + """ + dataframe.loc[ + ( + (qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)) & + (dataframe['tema'] <= dataframe['bb_middleband']) & + (dataframe['tema'] > dataframe['tema'].shift(1)) & + (dataframe['volume'] > 0) + ), + 'enter_long'] = 1 + + return dataframe +``` + +### Example - Follower Strategy + +A logically equivalent strategy which calculates no indicators itself, but will have the same analyzed dataframes to make trading decisions from as the leader. In this example the follower has the same entry criteria, however this is not necessary. The follower may use different logic to enter/exit trades. + +```py +class FollowerStrategy(IStrategy): + #... + process_only_new_candles = False # required for followers + + _columns_to_expect = ['rsi_default', 'tema_default', 'bb_middleband_default'] + + def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Use the websocket api to get pre-populated indicators from another FreqTrade instance. + Use `self.dp.get_external_df(pair)` to get the dataframe + """ + pair = metadata['pair'] + timeframe = self.timeframe + + leader_pairs = self.dp.get_producer_pairs() + # You can specify which producer to get pairs from via: + # self.dp.get_producer_pairs("my_other_producer") + + # This func returns the analyzed dataframe, and when it was analyzed + leader_dataframe, _ = self.dp.get_external_df(pair) + # You can get other data if your leader makes it available: + # self.dp.get_external_df( + # pair, + # timeframe="1h", + # candle_type=CandleType.SPOT, + # producer_name="my_other_producer" + # ) + + if not leader_dataframe.empty: + # If you plan on passing the leader's entry/exit signal directly, + # specify ffill=False or it will have unintended results + merged_dataframe = merge_informative_pair(dataframe, leader_dataframe, + timeframe, timeframe, + append_timeframe=False, + suffix="default") + return merged_dataframe + else: + dataframe[self._columns_to_expect] = 0 + + return dataframe + + def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Populates the entry signal for the given dataframe + """ + # Use the dataframe columns as if we calculated them ourselves + dataframe.loc[ + ( + (qtpylib.crossed_above(dataframe['rsi_default'], self.buy_rsi.value)) & + (dataframe['tema_default'] <= dataframe['bb_middleband_default']) & + (dataframe['tema_default'] > dataframe['tema_default'].shift(1)) & + (dataframe['volume'] > 0) + ), + 'enter_long'] = 1 + + return dataframe +``` diff --git a/docs/configuration.md b/docs/configuration.md index a37ca53ab..f4fb46707 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -110,7 +110,7 @@ This is similar to using multiple `--config` parameters, but simpler in usage as "stake_amount": "unlimited" } ``` - + If multiple files are in the `add_config_files` section, then they will be assumed to be at identical levels, having the last occurrence override the earlier config (unless a parent already defined such a key). ## Configuration parameters @@ -232,9 +232,8 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `api_server.verbosity` | Logging verbosity. `info` will print all RPC Calls, while "error" will only display errors.
**Datatype:** Enum, either `info` or `error`. Defaults to `info`. | `api_server.username` | Username for API server. See the [API Server documentation](rest-api.md) for more details.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String | `api_server.password` | Password for API server. See the [API Server documentation](rest-api.md) for more details.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String +| `api_server.ws_token` | API token for the Message WebSocket. See the [API Server documentation](rest-api.md) for more details.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String | `bot_name` | Name of the bot. Passed via API to a client - can be shown to distinguish / name bots.
*Defaults to `freqtrade`*
**Datatype:** String -| `api_server.enable_message_ws` | Enable external signal publishing. See the [API Server documentation](rest-api.md) for more details.
**Datatype:** Boolean -| `api_server.ws_token` | API token for external signal publishing. Only required if `api_server.enable_message_ws` is `true`. See the [API Server documentation](rest-api.md) for more details.
**Keep it in secret, do not disclose publicly.**
**Datatype:** String | `external_message_consumer` | Enable consuming of external signals. See the [API Server documentation](rest-api.md) for more details.
**Datatype:** Dict | | **Other** | `initial_state` | Defines the initial application state. If set to stopped, then the bot has to be explicitly started via `/start` RPC command.
*Defaults to `stopped`.*
**Datatype:** Enum, either `stopped` or `running` diff --git a/docs/rest-api.md b/docs/rest-api.md index 939258ca6..e1ba1e889 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -460,147 +460,3 @@ The correct configuration for this case is `http://localhost:8080` - the main pa !!! Note We strongly recommend to also set `jwt_secret_key` to something random and known only to yourself to avoid unauthorized access to your bot. - -## External Signals API - -FreqTrade provides a mechanism whereby a leader instance may provide analyzed dataframes to a subscriber/follower instance (or instances) using websockets. - -Run a bot in Leader mode to broadcast any populated indicators in the dataframes for each pair to bots running in Follower mode. This allows the reuse of computed indicators in multiple bots without needing to compute them multiple times. - -### Leader configuration - -Enable the leader websocket api by adding `enable_message_ws` to your `api_server` section and setting it to `true`, and providing an api token with `ws_token`. See [Security](#security) above for advice on token generation. - -!!! Note - We strongly recommend to also set `ws_token` to something random and known only to yourself to avoid unauthorized access to your bot. - -```jsonc -{ - //... - "api_server": { - //... - "enable_message_ws": true, - "ws_token": "mysecretapitoken" - //... - } - //... -} -``` - -The leader instance will listen for incoming reqests on the port configured by `api_server.listen_port` to forward its analyzed dataframes for each pair in its active whitelist calculated in its `populate_indicators()`. - -### Follower configuration - -Enable subscribing to a leader instance by adding the `external_message_consumer` section to the follower's config file. - -```jsonc -{ - //... - "external_message_consumer": { - "enabled": true, - "producers": [ - { - "name": "default", - "host": "127.0.0.1", - "port": 8080, - "ws_token": "mysecretapitoken" - } - ], - "reply_timeout": 10, - "ping_timeout": 5, - "sleep_time": 5, - "message_size_limit": 8, // in MB, default=8 - "remove_entry_exit_signals": false - } - //... -} -``` - -Instead of (or as well as) calculating indicators in `populate_indicators()` the follower instance opens a websocket connection to a leader instance (or multiple leader instances in advanced configurations) and requests the leader's most recently analyzed dataframes for each pair in the active whitelist. - -A follower instance will then have a full copy of the analyzed dataframes without the need to calculate them itself. - -### Example - Leader Strategy - -A simple strategy with multiple indicators. No special considerations are required in the strategy itself, only in the configuration file to enable websocket listening. - -```py -class LeaderStrategy(IStrategy): - #... - def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Calculate indicators in the standard freqtrade way which can then be broadcast to other instances - """ - dataframe['rsi'] = ta.RSI(dataframe) - bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) - dataframe['bb_lowerband'] = bollinger['lower'] - dataframe['bb_middleband'] = bollinger['mid'] - dataframe['bb_upperband'] = bollinger['upper'] - dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) - - return dataframe - - def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Populates the entry signal for the given dataframe - """ - dataframe.loc[ - ( - (qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)) & - (dataframe['tema'] <= dataframe['bb_middleband']) & - (dataframe['tema'] > dataframe['tema'].shift(1)) & - (dataframe['volume'] > 0) - ), - 'enter_long'] = 1 - - return dataframe -``` - -### Example - Follower Strategy - -A logically equivalent strategy which calculates no indicators itself, but will have the same analyzed dataframes to make trading decisions from as the leader. In this example the follower has the same entry criteria, however this is not necessary. The follower may use different logic to enter/exit trades. - -```py -class FollowerStrategy(IStrategy): - #... - process_only_new_candles = False # required for followers - - _columns_to_expect = ['rsi_default', 'tema_default', 'bb_middleband_default'] - - def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Use the websocket api to get pre-populated indicators from another FreqTrade instance. - Use `self.dp.get_external_df(pair)` to get the dataframe - """ - pair = metadata['pair'] - timeframe = self.timeframe - - leader_dataframe, _ = self.dp.get_external_df(pair) - - if not leader_dataframe.empty: - merged_dataframe = merge_informative_pair(dataframe, leader_dataframe, - timeframe, timeframe, - append_timeframe=False, - suffix="default") - return merged_dataframe - else: - dataframe[self._columns_to_expect] = 0 - - return dataframe - - def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Populates the entry signal for the given dataframe - """ - # Use the dataframe columns as if we calculated them ourselves - dataframe.loc[ - ( - (qtpylib.crossed_above(dataframe['rsi_default'], self.buy_rsi.value)) & - (dataframe['tema_default'] <= dataframe['bb_middleband_default']) & - (dataframe['tema_default'] > dataframe['tema_default'].shift(1)) & - (dataframe['volume'] > 0) - ), - 'enter_long'] = 1 - - return dataframe -``` \ No newline at end of file diff --git a/freqtrade/data/dataprovider.py b/freqtrade/data/dataprovider.py index 1dfa53caa..031a6d704 100644 --- a/freqtrade/data/dataprovider.py +++ b/freqtrade/data/dataprovider.py @@ -163,6 +163,7 @@ class DataProvider: :param pair: pair to get the data for :param timeframe: Timeframe to get data for :param candle_type: Any of the enum CandleType (must match trading mode!) + :returns: Tuple of the DataFrame and last analyzed timestamp """ _timeframe = self._default_timeframe if not timeframe else timeframe _candle_type = self._default_candle_type if not candle_type else candle_type diff --git a/mkdocs.yml b/mkdocs.yml index 257db7867..27995cfc0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -35,6 +35,7 @@ nav: - Advanced Post-installation Tasks: advanced-setup.md - Advanced Strategy: strategy-advanced.md - Advanced Hyperopt: advanced-hyperopt.md + - External Signals: advanced-external-signals.md - FreqAI: freqai.md - Edge Positioning: edge.md - Sandbox Testing: sandbox-testing.md