Document dataframe parameter in custom_stoploss().

This commit is contained in:
Rokas Kupstys 2021-04-25 09:31:53 +03:00
parent 595b8735f8
commit 004550529e

View File

@ -42,33 +42,6 @@ class AwesomeStrategy(IStrategy):
*** ***
### Storing custom information using DatetimeIndex from `dataframe`
Imagine you need to store an indicator like `ATR` or `RSI` into `custom_info`. To use this in a meaningful way, you will not only need the raw data of the indicator, but probably also need to keep the right timestamps.
```python
import talib.abstract as ta
class AwesomeStrategy(IStrategy):
# Create custom dictionary
custom_info = {}
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# using "ATR" here as example
dataframe['atr'] = ta.ATR(dataframe)
if self.dp.runmode.value in ('backtest', 'hyperopt'):
# add indicator mapped to correct DatetimeIndex to custom_info
self.custom_info[metadata['pair']] = dataframe[['date', 'atr']].set_index('date')
return dataframe
```
!!! Warning
The data is not persisted after a bot-restart (or config-reload). Also, the amount of data should be kept smallish (no DataFrames and such), otherwise the bot will start to consume a lot of memory and eventually run out of memory and crash.
!!! Note
If the data is pair-specific, make sure to use pair as one of the keys in the dictionary.
See `custom_stoploss` examples below on how to access the saved dataframe columns
## Custom sell signal ## Custom sell signal
It is possible to define custom sell signals. This is very useful when we need to customize sell conditions for each individual trade. It is possible to define custom sell signals. This is very useful when we need to customize sell conditions for each individual trade.
@ -107,7 +80,8 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float: current_rate: float, current_profit: float, dataframe: Dataframe,
**kwargs) -> float:
""" """
Custom stoploss logic, returning the new distance relative to current_rate (as ratio). Custom stoploss logic, returning the new distance relative to current_rate (as ratio).
e.g. returning -0.05 would create a stoploss 5% below current_rate. e.g. returning -0.05 would create a stoploss 5% below current_rate.
@ -157,7 +131,8 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float: current_rate: float, current_profit: float, dataframe: DataFrame,
**kwargs) -> float:
# Make sure you have the longest interval first - these conditions are evaluated from top to bottom. # Make sure you have the longest interval first - these conditions are evaluated from top to bottom.
if current_time - timedelta(minutes=120) > trade.open_date_utc: if current_time - timedelta(minutes=120) > trade.open_date_utc:
@ -183,7 +158,8 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float: current_rate: float, current_profit: float, dataframe: DataFrame,
**kwargs) -> float:
if pair in ('ETH/BTC', 'XRP/BTC'): if pair in ('ETH/BTC', 'XRP/BTC'):
return -0.10 return -0.10
@ -209,7 +185,8 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float: current_rate: float, current_profit: float, dataframe: DataFrame,
**kwargs) -> float:
if current_profit < 0.04: if current_profit < 0.04:
return -1 # return a value bigger than the inital stoploss to keep using the inital stoploss return -1 # return a value bigger than the inital stoploss to keep using the inital stoploss
@ -249,7 +226,8 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float: current_rate: float, current_profit: float, dataframe: DataFrame,
**kwargs) -> float:
# evaluate highest to lowest, so that highest possible stop is used # evaluate highest to lowest, so that highest possible stop is used
if current_profit > 0.40: if current_profit > 0.40:
@ -266,14 +244,20 @@ class AwesomeStrategy(IStrategy):
Imagine you want to use `custom_stoploss()` to use a trailing indicator like e.g. "ATR" Imagine you want to use `custom_stoploss()` to use a trailing indicator like e.g. "ATR"
See: "Storing custom information using DatetimeIndex from `dataframe`" example above) on how to store the indicator into `custom_info`
!!! Warning !!! Warning
only use .iat[-1] in live mode, not in backtesting/hyperopt Only use `dataframe` values up until and including `current_time` value. Reading past
otherwise you will look into the future `current_time` you will look into the future, which will produce incorrect backtesting results
and throw an exception in dry/live runs.
see [Common mistakes when developing strategies](strategy-customization.md#common-mistakes-when-developing-strategies) for more info. see [Common mistakes when developing strategies](strategy-customization.md#common-mistakes-when-developing-strategies) for more info.
!!! Note
DataFrame is indexed by candle date. During dry/live runs `current_time` and
`trade.open_date_utc` will not match candle dates precisely and using them as indices will throw
an error. Use `date = timeframe_to_prev_date(self.timeframe, date)` to round a date to previous
candle before using it as a `dataframe` index.
``` python ``` python
from freqtrade.exchange import timeframe_to_prev_date
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.state import RunMode from freqtrade.state import RunMode
@ -284,28 +268,19 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime, def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float: current_rate: float, current_profit: float, dataframe: DataFrame,
**kwargs) -> float:
result = 1 result = 1
if self.custom_info and pair in self.custom_info and trade: if self.custom_info and pair in self.custom_info and trade:
# using current_time directly (like below) will only work in backtesting. # Using current_time directly would only work in backtesting. Live/dry runs need time to
# so check "runmode" to make sure that it's only used in backtesting/hyperopt # be rounded to previous candle to be used as dataframe index. Rounding must also be
if self.dp and self.dp.runmode.value in ('backtest', 'hyperopt'): # applied to `trade.open_date(_utc)` if it is used for `dataframe` indexing.
relative_sl = self.custom_info[pair].loc[current_time]['atr'] current_time = timeframe_to_prev_date(self.timeframe, current_time)
# in live / dry-run, it'll be really the current time current_row = dataframe.loc[current_time]
else: if 'atr' in current_row:
# but we can just use the last entry from an already analyzed dataframe instead
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
timeframe=self.timeframe)
# WARNING
# only use .iat[-1] in live mode, not in backtesting/hyperopt
# otherwise you will look into the future
# see: https://www.freqtrade.io/en/latest/strategy-customization/#common-mistakes-when-developing-strategies
relative_sl = dataframe['atr'].iat[-1]
if (relative_sl is not None):
# new stoploss relative to current_rate # new stoploss relative to current_rate
new_stoploss = (current_rate-relative_sl)/current_rate new_stoploss = (current_rate - current_row['atr']) / current_rate
# turn into relative negative offset required by `custom_stoploss` return implementation # turn into relative negative offset required by `custom_stoploss` return implementation
result = new_stoploss - 1 result = new_stoploss - 1