From f11a40f1445e70b38f86537219b47da9ca45ebfb Mon Sep 17 00:00:00 2001 From: Reigo Reinmets Date: Sat, 11 Dec 2021 17:14:04 +0200 Subject: [PATCH] Improve documentation on adjust_trade_position and position_adjustment_enable --- docs/bot-basics.md | 5 ++- docs/configuration.md | 10 +++++ docs/plotting.md | 3 ++ docs/strategy-advanced.md | 74 +++++++++++++++++++++++++++++++ docs/strategy-callbacks.md | 47 ++++++++++++++++++++ freqtrade/freqtradebot.py | 5 ++- freqtrade/optimize/backtesting.py | 7 +-- freqtrade/strategy/interface.py | 5 ++- 8 files changed, 149 insertions(+), 7 deletions(-) diff --git a/docs/bot-basics.md b/docs/bot-basics.md index 0b9f7b67c..92ecfc901 100644 --- a/docs/bot-basics.md +++ b/docs/bot-basics.md @@ -38,6 +38,8 @@ By default, loop runs every few seconds (`internals.process_throttle_secs`) and * Considers stoploss, ROI and sell-signal, `custom_sell()` and `custom_stoploss()`. * Determine sell-price based on `ask_strategy` configuration setting or by using the `custom_exit_price()` callback. * Before a sell order is placed, `confirm_trade_exit()` strategy callback is called. +* Check position adjustments for open trades if enabled. + * Call `adjust_trade_position()` strategy callback and place additional order if required. * Check if trade-slots are still available (if `max_open_trades` is reached). * Verifies buy signal trying to enter new positions. * Determine buy-price based on `bid_strategy` configuration setting, or by using the `custom_entry_price()` callback. @@ -60,7 +62,8 @@ This loop will be repeated again and again until the bot is stopped. * Determine stake size by calling the `custom_stake_amount()` callback. * Call `custom_stoploss()` and `custom_sell()` to find custom exit points. * For sells based on sell-signal and custom-sell: Call `custom_exit_price()` to determine exit price (Prices are moved to be within the closing candle). - + * Check position adjustments for open trades if enabled and call `adjust_trade_position()` determine additional order is required. + * Generate backtest report output !!! Note diff --git a/docs/configuration.md b/docs/configuration.md index 00ab66ceb..3a9c19161 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -171,6 +171,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `user_data_dir` | Directory containing user data.
*Defaults to `./user_data/`*.
**Datatype:** String | `dataformat_ohlcv` | Data format to use to store historical candle (OHLCV) data.
*Defaults to `json`*.
**Datatype:** String | `dataformat_trades` | Data format to use to store historical trades data.
*Defaults to `jsongz`*.
**Datatype:** String +| `position_adjustment_enable` | Enables the strategy to use position adjustments (additional buys or sells). More information below.
**Datatype:** Boolean ### Parameters in the strategy @@ -583,6 +584,15 @@ export HTTPS_PROXY="http://addr:port" freqtrade ``` +### Understand position_adjustment_enable + +The `position_adjustment_enable` configuration parameter enables the usage of `adjust_trade_position()` callback in strategy. +For performance reasons, it's disabled by default, and freqtrade will show a warning message on startup if enabled. +This can be dangerous with some strategies, so use with care. + +See [the strategy callbacks](strategy-callbacks.md) for details on usage. + + ## Next step Now you have configured your config.json, the next step is to [start your bot](bot-usage.md). diff --git a/docs/plotting.md b/docs/plotting.md index b2d7654f6..b81d57b7e 100644 --- a/docs/plotting.md +++ b/docs/plotting.md @@ -273,6 +273,9 @@ def plot_config(self): !!! Warning `plotly` arguments are only supported with plotly library and will not work with freq-ui. +!!! Note + If `position_adjustment_enable` / `adjust_trade_position()` is used, the trade initial buy price is averaged over multiple orders and the trade start price will most likely appear outside the candle range. + ## Plot profit ![plot-profit](assets/plot-profit.png) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index 4cc607883..c3527ec54 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -229,3 +229,77 @@ for val in self.buy_ema_short.range: # Append columns to existing dataframe merged_frame = pd.concat(frames, axis=1) ``` + +### Adjust trade position + +`adjust_trade_position()` can be used to perform additional orders to manage risk with DCA (Dollar Cost Averaging) for example. + +!!! Tip: The `position_adjustment_enable` configuration parameter must be enabled to use adjust_trade_position callback in strategy. + +!!! Warning: Additional orders also mean additional fees. + +!!! Warning: Stoploss is still calculated from the initial opening price, not averaged price. + +``` python +from freqtrade.persistence import Trade + + +class DigDeeperStrategy(IStrategy): + + # Attempts to handle large drops with DCA. High stoploss is required. + stoploss = -0.30 + + # ... populate_* methods + + def adjust_trade_position(self, pair: str, trade: Trade, + current_time: datetime, current_rate: float, current_profit: float, + **kwargs) -> Optional[float]: + """ + Custom trade adjustment logic, returning the stake amount that a trade should be increased. + This means extra buy orders with additional fees. + + :param pair: Pair that's currently analyzed + :param trade: trade object. + :param current_time: datetime object, containing the current datetime + :param current_rate: Rate, calculated based on pricing settings in ask_strategy. + :param current_profit: Current profit (as ratio), calculated based on current_rate. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + :return float: Stake amount to adjust your trade + """ + + if current_profit > -0.05: + return None + + # Obtain pair dataframe. + dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) + + # Only buy when not actively falling price. + if dataframe['close'] < dataframe['close'].shift(1): + return None + + count_of_buys = 0 + for order in trade.orders: + # Instantly stop when there's an open order + if order.ft_is_open: + return None + if order.ft_order_side == 'buy' and order.status == "closed": + count_of_buys += 1 + + # Allow up to 3 additional increasingly larger buys (4 in total) + # Initial buy is 1x + # If that falls to -5% profit, we buy 1.25x more, average profit should increase to roughly -2.2% + # If that falles down to -5% again, we buy 1.5x more + # If that falles once again down to -5%, we buy 1.75x more + # Total stake for this trade would be 1 + 1.25 + 1.5 + 1.75 = 5.5x of the initial allowed stake. + # Hope you have a deep wallet! + if 0 < count_of_buys <= 3: + try: + stake_amount = self.wallets.get_trade_stake_amount(pair, None) + stake_amount = stake_amount * (1 + (count_of_buys * 0.25)) + return stake_amount + except Exception as exception: + return None + + return None + +``` diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 11032433d..fcf861145 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -15,6 +15,7 @@ Currently available callbacks: * [`check_buy_timeout()` and `check_sell_timeout()](#custom-order-timeout-rules) * [`confirm_trade_entry()`](#trade-entry-buy-order-confirmation) * [`confirm_trade_exit()`](#trade-exit-sell-order-confirmation) +* [`adjust_trade_position()`](#adjust-trade-position) !!! Tip "Callback calling sequence" You can find the callback calling sequence in [bot-basics](bot-basics.md#bot-execution-logic) @@ -568,3 +569,49 @@ class AwesomeStrategy(IStrategy): return True ``` + +### Adjust trade position + +`adjust_trade_position()` can be used to perform additional orders to manage risk with DCA (Dollar Cost Averaging). + +!!! Tip: The `position_adjustment_enable` configuration parameter must be enabled to use adjust_trade_position callback in strategy. + +!!! Warning: Additional orders also mean additional fees. + +!!! Warning: Stoploss is still calculated from the initial opening price, not averaged price. + +``` python +from freqtrade.persistence import Trade + + +class AwesomeStrategy(IStrategy): + + # ... populate_* methods + + def adjust_trade_position(self, pair: str, trade: Trade, + current_time: datetime, current_rate: float, current_profit: float, + **kwargs) -> Optional[float]: + """ + Custom trade adjustment logic, returning the stake amount that a trade should be increased. + This means extra buy orders with additional fees. + + For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/ + + When not implemented by a strategy, returns None + + :param pair: Pair that's currently analyzed + :param trade: trade object. + :param current_time: datetime object, containing the current datetime + :param current_rate: Rate, calculated based on pricing settings in ask_strategy. + :param current_profit: Current profit (as ratio), calculated based on current_rate. + :param **kwargs: Ensure to keep this here so updates to this won't break your strategy. + :return float: Stake amount to adjust your trade + """ + + # Example: If 10% loss / -10% profit then buy more the same amount we had before. + if current_profit < -0.10: + return trade.stake_amount + + return None + +``` diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index bded7a3f2..b29eca7f3 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -102,6 +102,9 @@ class FreqtradeBot(LoggingMixin): self._exit_lock = Lock() LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe)) + # Is Position Adjustment enabled? + self.position_adjustment = bool(self.config.get('position_adjustment_enable', False)) + def notify_status(self, msg: str) -> None: """ Public method for users of this class (worker, etc.) to send notifications @@ -179,7 +182,7 @@ class FreqtradeBot(LoggingMixin): self.exit_positions(trades) # Check if we need to adjust our current positions before attempting to buy new trades. - if self.config.get('position_adjustment_enable', False): + if self.position_adjustment: self.process_open_trade_positions() # Then looking for buy opportunities diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 29633b4ed..db7896983 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -118,6 +118,7 @@ class Backtesting: # Add maximum startup candle count to configuration for informative pairs support self.config['startup_candle_count'] = self.required_startup self.exchange.validate_required_startup_candles(self.required_startup, self.timeframe) + self.position_adjustment = bool(self.config.get('position_adjustment_enable', False)) self.init_backtest() def __del__(self): @@ -353,7 +354,7 @@ class Backtesting: def _get_adjust_trade_entry_for_candle(self, trade: LocalTrade, row: Tuple) -> Optional[LocalTrade]: current_profit = trade.calc_profit_ratio(row[OPEN_IDX]) - + stake_amount = strategy_safe_wrapper(self.strategy.adjust_trade_position, default_retval=None)( pair=trade.pair, trade=trade, current_time=row[DATE_IDX].to_pydatetime(), current_rate=row[OPEN_IDX], current_profit=current_profit) @@ -403,9 +404,9 @@ class Backtesting: def _get_sell_trade_entry_for_candle(self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]: - + # Check if we need to adjust our current positions - if self.config.get('position_adjustment_enable', False): + if self.position_adjustment: trade = self._get_adjust_trade_entry_for_candle(trade, sell_row) sell_candle_time = sell_row[DATE_IDX].to_pydatetime() diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 57ccdef31..81909e773 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -386,11 +386,12 @@ class IStrategy(ABC, HyperStrategyMixin): current_time: datetime, current_rate: float, current_profit: float, **kwargs) -> Optional[float]: """ - Custom trade adjustment logic, returning the stake amount that a trade shold be either increased or decreased. + Custom trade adjustment logic, returning the stake amount that a trade should be increased. + This means extra buy orders with additional fees. For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/ - When not implemented by a strategy, returns 0.0 + When not implemented by a strategy, returns None :param pair: Pair that's currently analyzed :param trade: trade object.