Improve documentation on adjust_trade_position and position_adjustment_enable
This commit is contained in:
		| @@ -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()`. |   * 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. |   * 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. |   * 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). | * Check if trade-slots are still available (if `max_open_trades` is reached). | ||||||
| * Verifies buy signal trying to enter new positions. | * 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. |   * Determine buy-price based on `bid_strategy` configuration setting, or by using the `custom_entry_price()` callback. | ||||||
| @@ -60,6 +62,7 @@ This loop will be repeated again and again until the bot is stopped. | |||||||
|   * Determine stake size by calling the `custom_stake_amount()` callback. |   * Determine stake size by calling the `custom_stake_amount()` callback. | ||||||
|   * Call `custom_stoploss()` and `custom_sell()` to find custom exit points. |   * 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). |   * 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 | * Generate backtest report output | ||||||
|  |  | ||||||
|   | |||||||
| @@ -171,6 +171,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | |||||||
| | `user_data_dir` | Directory containing user data. <br> *Defaults to `./user_data/`*. <br> **Datatype:** String | | `user_data_dir` | Directory containing user data. <br> *Defaults to `./user_data/`*. <br> **Datatype:** String | ||||||
| | `dataformat_ohlcv` | Data format to use to store historical candle (OHLCV) data. <br> *Defaults to `json`*. <br> **Datatype:** String | | `dataformat_ohlcv` | Data format to use to store historical candle (OHLCV) data. <br> *Defaults to `json`*. <br> **Datatype:** String | ||||||
| | `dataformat_trades` | Data format to use to store historical trades data. <br> *Defaults to `jsongz`*. <br> **Datatype:** String | | `dataformat_trades` | Data format to use to store historical trades data. <br> *Defaults to `jsongz`*. <br> **Datatype:** String | ||||||
|  | | `position_adjustment_enable` | Enables the strategy to use position adjustments (additional buys or sells). More information below. <br> **Datatype:** Boolean | ||||||
|  |  | ||||||
| ### Parameters in the strategy | ### Parameters in the strategy | ||||||
|  |  | ||||||
| @@ -583,6 +584,15 @@ export HTTPS_PROXY="http://addr:port" | |||||||
| freqtrade | 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 | ## Next step | ||||||
|  |  | ||||||
| Now you have configured your config.json, the next step is to [start your bot](bot-usage.md). | Now you have configured your config.json, the next step is to [start your bot](bot-usage.md). | ||||||
|   | |||||||
| @@ -273,6 +273,9 @@ def plot_config(self): | |||||||
| !!! Warning | !!! Warning | ||||||
|     `plotly` arguments are only supported with plotly library and will not work with freq-ui. |     `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 | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -229,3 +229,77 @@ for val in self.buy_ema_short.range: | |||||||
| # Append columns to existing dataframe | # Append columns to existing dataframe | ||||||
| merged_frame = pd.concat(frames, axis=1) | 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 | ||||||
|  |  | ||||||
|  | ``` | ||||||
|   | |||||||
| @@ -15,6 +15,7 @@ Currently available callbacks: | |||||||
| * [`check_buy_timeout()` and `check_sell_timeout()](#custom-order-timeout-rules) | * [`check_buy_timeout()` and `check_sell_timeout()](#custom-order-timeout-rules) | ||||||
| * [`confirm_trade_entry()`](#trade-entry-buy-order-confirmation) | * [`confirm_trade_entry()`](#trade-entry-buy-order-confirmation) | ||||||
| * [`confirm_trade_exit()`](#trade-exit-sell-order-confirmation) | * [`confirm_trade_exit()`](#trade-exit-sell-order-confirmation) | ||||||
|  | * [`adjust_trade_position()`](#adjust-trade-position) | ||||||
|  |  | ||||||
| !!! Tip "Callback calling sequence" | !!! Tip "Callback calling sequence" | ||||||
|     You can find the callback calling sequence in [bot-basics](bot-basics.md#bot-execution-logic) |     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 |         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 | ||||||
|  |  | ||||||
|  | ``` | ||||||
|   | |||||||
| @@ -102,6 +102,9 @@ class FreqtradeBot(LoggingMixin): | |||||||
|         self._exit_lock = Lock() |         self._exit_lock = Lock() | ||||||
|         LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe)) |         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: |     def notify_status(self, msg: str) -> None: | ||||||
|         """ |         """ | ||||||
|         Public method for users of this class (worker, etc.) to send notifications |         Public method for users of this class (worker, etc.) to send notifications | ||||||
| @@ -179,7 +182,7 @@ class FreqtradeBot(LoggingMixin): | |||||||
|             self.exit_positions(trades) |             self.exit_positions(trades) | ||||||
|  |  | ||||||
|         # Check if we need to adjust our current positions before attempting to buy new 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() |             self.process_open_trade_positions() | ||||||
|  |  | ||||||
|         # Then looking for buy opportunities |         # Then looking for buy opportunities | ||||||
|   | |||||||
| @@ -118,6 +118,7 @@ class Backtesting: | |||||||
|         # Add maximum startup candle count to configuration for informative pairs support |         # Add maximum startup candle count to configuration for informative pairs support | ||||||
|         self.config['startup_candle_count'] = self.required_startup |         self.config['startup_candle_count'] = self.required_startup | ||||||
|         self.exchange.validate_required_startup_candles(self.required_startup, self.timeframe) |         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() |         self.init_backtest() | ||||||
|  |  | ||||||
|     def __del__(self): |     def __del__(self): | ||||||
| @@ -405,7 +406,7 @@ class Backtesting: | |||||||
|                                          sell_row: Tuple) -> Optional[LocalTrade]: |                                          sell_row: Tuple) -> Optional[LocalTrade]: | ||||||
|  |  | ||||||
|         # Check if we need to adjust our current positions |         # 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) |             trade = self._get_adjust_trade_entry_for_candle(trade, sell_row) | ||||||
|  |  | ||||||
|         sell_candle_time = sell_row[DATE_IDX].to_pydatetime() |         sell_candle_time = sell_row[DATE_IDX].to_pydatetime() | ||||||
|   | |||||||
| @@ -386,11 +386,12 @@ class IStrategy(ABC, HyperStrategyMixin): | |||||||
|                           current_time: datetime, current_rate: float, current_profit: float, |                           current_time: datetime, current_rate: float, current_profit: float, | ||||||
|                               **kwargs) -> Optional[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/ |         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 pair: Pair that's currently analyzed | ||||||
|         :param trade: trade object. |         :param trade: trade object. | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user