2022-09-22 19:08:06 +00:00
# Producer / Consumer mode
2022-09-22 17:58:38 +00:00
2022-09-22 19:08:06 +00:00
freqtrade provides a mechanism whereby an instance (also called `consumer` ) may listen to messages from an upstream freqtrade instance (also called `producer` ) 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.
2022-09-21 21:50:11 +00:00
2022-09-22 19:08:06 +00:00
See [Message Websocket ](rest-api.md#message-websocket ) in the Rest API docs for setting up the `api_server` configuration for your message websocket (this will be your producer).
2022-09-21 21:50:11 +00:00
!!! Note
2022-09-22 19:08:06 +00:00
We strongly recommend to set `ws_token` to something random and known only to yourself to avoid unauthorized access to your bot.
2022-09-21 21:50:11 +00:00
2022-09-22 17:58:38 +00:00
## Configuration
2022-09-21 21:50:11 +00:00
2022-09-22 19:08:06 +00:00
Enable subscribing to an instance by adding the `external_message_consumer` section to the consumer's config file.
2022-09-21 21:50:11 +00:00
2022-09-22 17:58:38 +00:00
```json
2022-09-21 21:50:11 +00:00
{
//...
"external_message_consumer": {
"enabled": true,
"producers": [
{
"name": "default", // This can be any name you'd like, default is "default"
2022-09-22 19:08:06 +00:00
"host": "127.0.0.1", // The host from your producer's api_server config
"port": 8080, // The port from your producer's api_server config
2022-11-16 05:33:42 +00:00
"secure": false, // Use a secure websockets connection, default false
2022-09-22 19:08:06 +00:00
"ws_token": "sercet_Ws_t0ken" // The ws_token from your producer's api_server config
2022-09-21 21:50:11 +00:00
}
],
2022-09-22 17:58:38 +00:00
// The following configurations are optional, and usually not required
// "wait_timeout": 300,
// "ping_timeout": 10,
// "sleep_time": 10,
// "remove_entry_exit_signals": false,
// "message_size_limit": 8
2022-09-21 21:50:11 +00:00
}
//...
}
```
2022-09-22 17:58:38 +00:00
| Parameter | Description |
|------------|-------------|
2022-09-22 19:08:06 +00:00
| `enabled` | **Required.** Enable consumer mode. If set to false, all other settings in this section are ignored.< br > *Defaults to `false` .*< br > **Datatype:** boolean .
2022-09-22 17:58:38 +00:00
| `producers` | **Required.** List of producers < br > **Datatype:** Array.
2022-09-22 18:46:40 +00:00
| `producers.name` | **Required.** Name of this producer. This name must be used in calls to `get_producer_pairs()` and `get_producer_df()` if more than one producer is used.< br > **Datatype:** string
2022-09-22 19:08:06 +00:00
| `producers.host` | **Required.** The hostname or IP address from your producer.< br > **Datatype:** string
2023-03-29 18:43:23 +00:00
| `producers.port` | **Required.** The port matching the above host.< br > *Defaults to `8080` .*< br > **Datatype:** Integer
2022-11-16 05:33:42 +00:00
| `producers.secure` | **Optional.** Use ssl in websockets connection. Default False.< br > **Datatype:** string
2022-09-22 19:08:06 +00:00
| `producers.ws_token` | **Required.** `ws_token` as configured on the producer.< br > **Datatype:** string
2022-09-22 17:58:38 +00:00
| | **Optional settings**
| `wait_timeout` | Timeout until we ping again if no message is received. < br > *Defaults to `300` .*< br > **Datatype:** Integer - in seconds.
2023-03-29 18:43:23 +00:00
| `ping_timeout` | Ping timeout < br > *Defaults to `10` .*< br > **Datatype:** Integer - in seconds.
2022-09-22 17:58:38 +00:00
| `sleep_time` | Sleep time before retrying to connect.< br > *Defaults to `10` .*< br > **Datatype:** Integer - in seconds.
2023-03-29 18:43:23 +00:00
| `remove_entry_exit_signals` | Remove signal columns from the dataframe (set them to 0) on dataframe receipt.< br > *Defaults to `False` .*< br > **Datatype:** Boolean.
2022-09-22 17:58:38 +00:00
| `message_size_limit` | Size limit per message< br > *Defaults to `8` .*< br > **Datatype:** Integer - Megabytes.
2022-09-22 19:08:06 +00:00
Instead of (or as well as) calculating indicators in `populate_indicators()` the follower instance listens on the connection to a producer instance's messages (or multiple producer instances in advanced configurations) and requests the producer's most recently analyzed dataframes for each pair in the active whitelist.
2022-09-21 21:50:11 +00:00
2022-09-22 19:08:06 +00:00
A consumer instance will then have a full copy of the analyzed dataframes without the need to calculate them itself.
2022-09-21 21:50:11 +00:00
2022-09-22 17:58:38 +00:00
## Examples
2022-09-22 19:08:06 +00:00
### Example - Producer Strategy
2022-09-21 21:50:11 +00:00
A simple strategy with multiple indicators. No special considerations are required in the strategy itself.
```py
2022-09-22 19:08:06 +00:00
class ProducerStrategy(IStrategy):
2022-09-21 21:50:11 +00:00
#...
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
```
2022-09-22 19:08:06 +00:00
!!! Tip "FreqAI"
You can use this to setup [FreqAI ](freqai.md ) on a powerful machine, while you run consumers on simple machines like raspberries, which can interpret the signals generated from the producer in different ways.
2022-09-21 21:50:11 +00:00
2022-09-22 19:08:06 +00:00
### Example - Consumer Strategy
A logically equivalent strategy which calculates no indicators itself, but will have the same analyzed dataframes available to make trading decisions based on the indicators calculated in the producer. In this example the consumer has the same entry criteria, however this is not necessary. The consumer may use different logic to enter/exit trades, and only use the indicators as specified.
2022-09-21 21:50:11 +00:00
```py
2022-09-22 19:08:06 +00:00
class ConsumerStrategy(IStrategy):
2022-09-21 21:50:11 +00:00
#...
2022-09-22 19:08:06 +00:00
process_only_new_candles = False # required for consumers
2022-09-21 21:50:11 +00:00
_columns_to_expect = ['rsi_default', 'tema_default', 'bb_middleband_default']
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
2022-09-22 17:58:38 +00:00
Use the websocket api to get pre-populated indicators from another freqtrade instance.
2022-09-22 18:46:40 +00:00
Use `self.dp.get_producer_df(pair)` to get the dataframe
2022-09-21 21:50:11 +00:00
"""
pair = metadata['pair']
timeframe = self.timeframe
2022-09-22 19:08:06 +00:00
producer_pairs = self.dp.get_producer_pairs()
2022-09-21 21:50:11 +00:00
# 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
2022-09-22 19:08:06 +00:00
producer_dataframe, _ = self.dp.get_producer_df(pair)
# You can get other data if the producer makes it available:
2022-09-22 18:46:40 +00:00
# self.dp.get_producer_df(
2022-09-21 21:50:11 +00:00
# pair,
# timeframe="1h",
# candle_type=CandleType.SPOT,
# producer_name="my_other_producer"
# )
2022-09-22 19:08:06 +00:00
if not producer_dataframe.empty:
# If you plan on passing the producer's entry/exit signal directly,
2022-09-21 21:50:11 +00:00
# specify ffill=False or it will have unintended results
2022-09-22 19:08:06 +00:00
merged_dataframe = merge_informative_pair(dataframe, producer_dataframe,
2022-09-21 21:50:11 +00:00
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
```
2022-09-22 19:08:06 +00:00
!!! Tip "Using upstream signals"
By setting `remove_entry_exit_signals=false` , you can also use the producer's signals directly. They should be available as `enter_long_default` (assuming `suffix="default"` was used) - and can be used as either signal directly, or as additional indicator.