Update strategy-customization documentation
This commit is contained in:
parent
5e1032c4af
commit
09aa954b68
@ -58,12 +58,12 @@ file as reference.**
|
|||||||
|
|
||||||
!!! Note "Strategies and Backtesting"
|
!!! Note "Strategies and Backtesting"
|
||||||
To avoid problems and unexpected differences between Backtesting and dry/live modes, please be aware
|
To avoid problems and unexpected differences between Backtesting and dry/live modes, please be aware
|
||||||
that during backtesting the full time-interval is passed to the `populate_*()` methods at once.
|
that during backtesting the full time range is passed to the `populate_*()` methods at once.
|
||||||
It is therefore best to use vectorized operations (across the whole dataframe, not loops) and
|
It is therefore best to use vectorized operations (across the whole dataframe, not loops) and
|
||||||
avoid index referencing (`df.iloc[-1]`), but instead use `df.shift()` to get to the previous candle.
|
avoid index referencing (`df.iloc[-1]`), but instead use `df.shift()` to get to the previous candle.
|
||||||
|
|
||||||
!!! Warning "Warning: Using future data"
|
!!! Warning "Warning: Using future data"
|
||||||
Since backtesting passes the full time interval to the `populate_*()` methods, the strategy author
|
Since backtesting passes the full time range to the `populate_*()` methods, the strategy author
|
||||||
needs to take care to avoid having the strategy utilize data from the future.
|
needs to take care to avoid having the strategy utilize data from the future.
|
||||||
Some common patterns for this are listed in the [Common Mistakes](#common-mistakes-when-developing-strategies) section of this document.
|
Some common patterns for this are listed in the [Common Mistakes](#common-mistakes-when-developing-strategies) section of this document.
|
||||||
|
|
||||||
@ -251,7 +251,7 @@ minimal_roi = {
|
|||||||
While technically not completely disabled, this would sell once the trade reaches 10000% Profit.
|
While technically not completely disabled, this would sell once the trade reaches 10000% Profit.
|
||||||
|
|
||||||
To use times based on candle duration (timeframe), the following snippet can be handy.
|
To use times based on candle duration (timeframe), the following snippet can be handy.
|
||||||
This will allow you to change the ticket_interval for the strategy, and ROI times will still be set as candles (e.g. after 3 candles ...)
|
This will allow you to change the timeframe for the strategy, and ROI times will still be set as candles (e.g. after 3 candles ...)
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
from freqtrade.exchange import timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
@ -285,7 +285,7 @@ If your exchange supports it, it's recommended to also set `"stoploss_on_exchang
|
|||||||
|
|
||||||
For more information on order_types please look [here](configuration.md#understand-order_types).
|
For more information on order_types please look [here](configuration.md#understand-order_types).
|
||||||
|
|
||||||
### Timeframe (ticker interval)
|
### Timeframe (formerly ticker interval)
|
||||||
|
|
||||||
This is the set of candles the bot should download and use for the analysis.
|
This is the set of candles the bot should download and use for the analysis.
|
||||||
Common values are `"1m"`, `"5m"`, `"15m"`, `"1h"`, however all values supported by your exchange should work.
|
Common values are `"1m"`, `"5m"`, `"15m"`, `"1h"`, however all values supported by your exchange should work.
|
||||||
@ -333,10 +333,10 @@ class Awesomestrategy(IStrategy):
|
|||||||
#### Get data for non-tradeable pairs
|
#### Get data for non-tradeable pairs
|
||||||
|
|
||||||
Data for additional, informative pairs (reference pairs) can be beneficial for some strategies.
|
Data for additional, informative pairs (reference pairs) can be beneficial for some strategies.
|
||||||
Ohlcv data for these pairs will be downloaded as part of the regular whitelist refresh process and is available via `DataProvider` just as other pairs (see below).
|
OHLCV data for these pairs will be downloaded as part of the regular whitelist refresh process and is available via `DataProvider` just as other pairs (see below).
|
||||||
These parts will **not** be traded unless they are also specified in the pair whitelist, or have been selected by Dynamic Whitelisting.
|
These parts will **not** be traded unless they are also specified in the pair whitelist, or have been selected by Dynamic Whitelisting.
|
||||||
|
|
||||||
The pairs need to be specified as tuples in the format `("pair", "interval")`, with pair as the first and time interval as the second argument.
|
The pairs need to be specified as tuples in the format `("pair", "timeframe")`, with pair as the first and timeframe as the second argument.
|
||||||
|
|
||||||
Sample:
|
Sample:
|
||||||
|
|
||||||
@ -349,8 +349,8 @@ def informative_pairs(self):
|
|||||||
|
|
||||||
!!! Warning
|
!!! Warning
|
||||||
As these pairs will be refreshed as part of the regular whitelist refresh, it's best to keep this list short.
|
As these pairs will be refreshed as part of the regular whitelist refresh, it's best to keep this list short.
|
||||||
All intervals and all pairs can be specified as long as they are available (and active) on the used exchange.
|
All timeframes and all pairs can be specified as long as they are available (and active) on the used exchange.
|
||||||
It is however better to use resampling to longer time-intervals when possible
|
It is however better to use resampling to longer timeframes whenever possible
|
||||||
to avoid hammering the exchange with too many requests and risk being blocked.
|
to avoid hammering the exchange with too many requests and risk being blocked.
|
||||||
|
|
||||||
***
|
***
|
||||||
@ -363,10 +363,14 @@ All methods return `None` in case of failure (do not raise an exception).
|
|||||||
|
|
||||||
Please always check the mode of operation to select the correct method to get data (samples see below).
|
Please always check the mode of operation to select the correct method to get data (samples see below).
|
||||||
|
|
||||||
|
!!! Warning "Hyperopt"
|
||||||
|
Dataprovider is available during hyperopt, however it can only be used in `populate_indicators()`.
|
||||||
|
It is not available in `populate_buy()` and `populate_sell()` methods.
|
||||||
|
|
||||||
### Possible options for DataProvider
|
### Possible options for DataProvider
|
||||||
|
|
||||||
- [`available_pairs`](#available_pairs) - Property with tuples listing cached pairs with their intervals (pair, interval).
|
- [`available_pairs`](#available_pairs) - Property with tuples listing cached pairs with their timeframe (pair, timeframe).
|
||||||
- [`current_whitelist()`](#current_whitelist) - Returns a current list of whitelisted pairs. Useful for accessing dynamic whitelists (ie. VolumePairlist)
|
- [`current_whitelist()`](#current_whitelist) - Returns a current list of whitelisted pairs. Useful for accessing dynamic whitelists (i.e. VolumePairlist)
|
||||||
- [`get_pair_dataframe(pair, timeframe)`](#get_pair_dataframepair-timeframe) - This is a universal method, which returns either historical data (for backtesting) or cached live data (for the Dry-Run and Live-Run modes).
|
- [`get_pair_dataframe(pair, timeframe)`](#get_pair_dataframepair-timeframe) - This is a universal method, which returns either historical data (for backtesting) or cached live data (for the Dry-Run and Live-Run modes).
|
||||||
- [`get_analyzed_dataframe(pair, timeframe)`](#get_analyzed_dataframepair-timeframe) - Returns the analyzed dataframe (after calling `populate_indicators()`, `populate_buy()`, `populate_sell()`) and the time of the latest analysis.
|
- [`get_analyzed_dataframe(pair, timeframe)`](#get_analyzed_dataframepair-timeframe) - Returns the analyzed dataframe (after calling `populate_indicators()`, `populate_buy()`, `populate_sell()`) and the time of the latest analysis.
|
||||||
- `historic_ohlcv(pair, timeframe)` - Returns historical data stored on disk.
|
- `historic_ohlcv(pair, timeframe)` - Returns historical data stored on disk.
|
||||||
@ -401,58 +405,13 @@ Since we can't resample our data we will have to use an informative pair; and si
|
|||||||
This is where calling `self.dp.current_whitelist()` comes in handy.
|
This is where calling `self.dp.current_whitelist()` comes in handy.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class SampleStrategy(IStrategy):
|
|
||||||
# strategy init stuff...
|
|
||||||
|
|
||||||
timeframe = '5m'
|
|
||||||
|
|
||||||
# more strategy init stuff..
|
|
||||||
|
|
||||||
def informative_pairs(self):
|
def informative_pairs(self):
|
||||||
|
|
||||||
# get access to all pairs available in whitelist.
|
# get access to all pairs available in whitelist.
|
||||||
pairs = self.dp.current_whitelist()
|
pairs = self.dp.current_whitelist()
|
||||||
# Assign tf to each pair so they can be downloaded and cached for strategy.
|
# Assign tf to each pair so they can be downloaded and cached for strategy.
|
||||||
informative_pairs = [(pair, '1d') for pair in pairs]
|
informative_pairs = [(pair, '1d') for pair in pairs]
|
||||||
return informative_pairs
|
return informative_pairs
|
||||||
|
|
||||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
||||||
|
|
||||||
inf_tf = '1d'
|
|
||||||
# Get the informative pair
|
|
||||||
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=inf_tf)
|
|
||||||
# Get the 14 day rsi
|
|
||||||
informative['rsi'] = ta.RSI(informative, timeperiod=14)
|
|
||||||
|
|
||||||
# Rename columns to be unique
|
|
||||||
informative.columns = [f"{col}_{inf_tf}" for col in informative.columns]
|
|
||||||
# Assuming inf_tf = '1d' - then the columns will now be:
|
|
||||||
# date_1d, open_1d, high_1d, low_1d, close_1d, rsi_1d
|
|
||||||
|
|
||||||
# Combine the 2 dataframes
|
|
||||||
# all indicators on the informative sample MUST be calculated before this point
|
|
||||||
dataframe = pd.merge(dataframe, informative, left_on='date', right_on=f'date_{inf_tf}', how='left')
|
|
||||||
# FFill to have the 1d value available in every row throughout the day.
|
|
||||||
# Without this, comparisons would only work once per day.
|
|
||||||
dataframe = dataframe.ffill()
|
|
||||||
# Calculate rsi of the original dataframe (5m timeframe)
|
|
||||||
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
|
|
||||||
|
|
||||||
# Do other stuff
|
|
||||||
# ...
|
|
||||||
|
|
||||||
return dataframe
|
|
||||||
|
|
||||||
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
|
||||||
|
|
||||||
dataframe.loc[
|
|
||||||
(
|
|
||||||
(qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses above 30
|
|
||||||
(dataframe['rsi_1d'] < 30) & # Ensure daily RSI is < 30
|
|
||||||
(dataframe['volume'] > 0) # Ensure this candle had volume (important for backtesting)
|
|
||||||
),
|
|
||||||
'buy'] = 1
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### *get_pair_dataframe(pair, timeframe)*
|
### *get_pair_dataframe(pair, timeframe)*
|
||||||
@ -479,7 +438,7 @@ It can also be used in specific callbacks to get the signal that caused the acti
|
|||||||
# fetch current dataframe
|
# fetch current dataframe
|
||||||
if self.dp:
|
if self.dp:
|
||||||
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=metadata['pair'],
|
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=metadata['pair'],
|
||||||
timeframe=self.ticker_interval)
|
timeframe=self.timeframe)
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! Note "No data available"
|
!!! Note "No data available"
|
||||||
@ -516,6 +475,74 @@ if self.dp:
|
|||||||
does not always fills in the `last` field (so it can be None), etc. So you need to carefully verify the ticker
|
does not always fills in the `last` field (so it can be None), etc. So you need to carefully verify the ticker
|
||||||
data returned from the exchange and add appropriate error handling / defaults.
|
data returned from the exchange and add appropriate error handling / defaults.
|
||||||
|
|
||||||
|
!!! Warning "Warning about backtesting"
|
||||||
|
This method will always return up-to-date values - so usage during backtesting / hyperopt will lead to wrong results.
|
||||||
|
|
||||||
|
### Complete Data-provider sample
|
||||||
|
|
||||||
|
```python
|
||||||
|
class SampleStrategy(IStrategy):
|
||||||
|
# strategy init stuff...
|
||||||
|
|
||||||
|
timeframe = '5m'
|
||||||
|
|
||||||
|
# more strategy init stuff..
|
||||||
|
|
||||||
|
def informative_pairs(self):
|
||||||
|
|
||||||
|
# get access to all pairs available in whitelist.
|
||||||
|
pairs = self.dp.current_whitelist()
|
||||||
|
# Assign tf to each pair so they can be downloaded and cached for strategy.
|
||||||
|
informative_pairs = [(pair, '1d') for pair in pairs]
|
||||||
|
# Optionally Add additional "static" pairs
|
||||||
|
informative_pairs += [("ETH/USDT", "5m"),
|
||||||
|
("BTC/TUSD", "15m"),
|
||||||
|
]
|
||||||
|
return informative_pairs
|
||||||
|
|
||||||
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
if not self.dp:
|
||||||
|
# Don't do anything if DataProvider is not available.
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
inf_tf = '1d'
|
||||||
|
# Get the informative pair
|
||||||
|
informative = self.dp.get_pair_dataframe(pair=metadata['pair'], timeframe=inf_tf)
|
||||||
|
# Get the 14 day rsi
|
||||||
|
informative['rsi'] = ta.RSI(informative, timeperiod=14)
|
||||||
|
|
||||||
|
# Rename columns to be unique
|
||||||
|
informative.columns = [f"{col}_{inf_tf}" for col in informative.columns]
|
||||||
|
# Assuming inf_tf = '1d' - then the columns will now be:
|
||||||
|
# date_1d, open_1d, high_1d, low_1d, close_1d, rsi_1d
|
||||||
|
|
||||||
|
# Combine the 2 dataframes
|
||||||
|
# all indicators on the informative sample MUST be calculated before this point
|
||||||
|
dataframe = pd.merge(dataframe, informative, left_on='date', right_on=f'date_{inf_tf}', how='left')
|
||||||
|
# FFill to have the 1d value available in every row throughout the day.
|
||||||
|
# Without this, comparisons would only work once per day.
|
||||||
|
dataframe = dataframe.ffill()
|
||||||
|
|
||||||
|
# Calculate rsi of the original dataframe (5m timeframe)
|
||||||
|
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
|
||||||
|
|
||||||
|
# Do other stuff
|
||||||
|
# ...
|
||||||
|
|
||||||
|
return dataframe
|
||||||
|
|
||||||
|
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
|
||||||
|
dataframe.loc[
|
||||||
|
(
|
||||||
|
(qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses above 30
|
||||||
|
(dataframe['rsi_1d'] < 30) & # Ensure daily RSI is < 30
|
||||||
|
(dataframe['volume'] > 0) # Ensure this candle had volume (important for backtesting)
|
||||||
|
),
|
||||||
|
'buy'] = 1
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
## Additional data (Wallets)
|
## Additional data (Wallets)
|
||||||
|
Loading…
Reference in New Issue
Block a user