revise docs, update dp method docstring

This commit is contained in:
Timothy Pogue 2022-09-21 15:50:11 -06:00
parent 366c6c24d8
commit 0811bca8b4
5 changed files with 137 additions and 147 deletions

View File

@ -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
```

View File

@ -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. <br>**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. <br>**Keep it in secret, do not disclose publicly.**<br> **Datatype:** String
| `api_server.password` | Password for API server. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.**<br> **Datatype:** String
| `api_server.ws_token` | API token for the Message WebSocket. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `bot_name` | Name of the bot. Passed via API to a client - can be shown to distinguish / name bots.<br> *Defaults to `freqtrade`*<br> **Datatype:** String
| `api_server.enable_message_ws` | Enable external signal publishing. See the [API Server documentation](rest-api.md) for more details. <br> **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. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `external_message_consumer` | Enable consuming of external signals. See the [API Server documentation](rest-api.md) for more details. <br> **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. <br>*Defaults to `stopped`.* <br> **Datatype:** Enum, either `stopped` or `running`

View File

@ -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
```

View File

@ -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

View File

@ -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