From 2a728c676e5915f8fa25324e8078a1fe698006e1 Mon Sep 17 00:00:00 2001 From: Reigo Reinmets Date: Mon, 27 Dec 2021 19:41:33 +0200 Subject: [PATCH] Improve documentation. Fix bug. --- docs/strategy-advanced.md | 96 -------------------------------------- docs/strategy-callbacks.md | 91 +++++++++++++++++++++++++++++++++++- freqtrade/freqtradebot.py | 16 +++---- 3 files changed, 96 insertions(+), 107 deletions(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index a2d8d1b06..4cc607883 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -229,99 +229,3 @@ for val in self.buy_ema_short.range: # Append columns to existing dataframe merged_frame = pd.concat(frames, axis=1) ``` - -## Adjust trade position - -The `position_adjustment_enable` strategy property 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. -`adjust_trade_position()` can be used to perform additional orders to manage risk with DCA (Dollar Cost Averaging) for example. -The strategy is expected to return a stake_amount if and when an additional buy order should be made (position is increased). -If there is not enough funds in the wallet then nothing will happen. -Additional orders also mean additional fees and those orders don't count towards `max_open_trades`. -Using unlimited stake amount with DCA orders requires you to also implement `custom_stake_amount` callback to avoid allocating all funds to initial order. - -!!! 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 - - max_dca_orders = 3 - # This number is explained a bit further down - max_dca_multiplier = 5.5 - - # ... populate_* methods - - # Let unlimited stakes leave funds open for DCA orders - def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, - proposed_stake: float, min_stake: float, max_stake: float, - **kwargs) -> float: - - if self.config['stake_amount'] == 'unlimited': - return proposed_stake / self.max_dca_multiplier - - # Use default stake amount. - return proposed_stake - - 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. - last_candle = dataframe.iloc[-1].squeeze() - previous_candle = dataframe.iloc[-2].squeeze() - if last_candle['close'] < previous_candle['close']: - return None - - count_of_buys = 0 - for order in trade.orders: - 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 falls down to -5% again, we buy 1.5x more - # If that falls 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. - # That is why max_dca_multiplier is 5.5 - # Hope you have a deep wallet! - if 0 < count_of_buys <= self.max_dca_orders: - try: - # This returns max stakes for one trade - stake_amount = self.wallets.get_trade_stake_amount(pair, None) - # This calculates base order size - stake_amount = stake_amount / self.max_dca_multiplier - # This then calculates current safety order size - 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 42a63bae7..3e45a9077 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -574,8 +574,95 @@ class AwesomeStrategy(IStrategy): The `position_adjustment_enable` strategy property 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. -Enabling this does nothing unless the strategy also implements `adjust_trade_position()` callback. `adjust_trade_position()` can be used to perform additional orders, for example to manage risk with DCA (Dollar Cost Averaging). The strategy is expected to return a stake_amount if and when an additional buy order should be made (position is increased). +If there is not enough funds in the wallet then nothing will happen. +Additional orders also mean additional fees and those orders don't count towards `max_open_trades`. -[See Advanced Strategies for an example](strategy-advanced.md#adjust-trade-position) +!!! Note About stake size + Using fixed stake size means it will be the amount used for the first order just like without position adjustment. + Using 'unlimited' stake amount with DCA orders requires you to also implement custom_stake_amount callback to avoid allocating all funds to initial order. + +!!! 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 + + max_dca_orders = 3 + # This number is explained a bit further down + max_dca_multiplier = 5.5 + + # ... populate_* methods + + # This is called when placing the initial order (opening trade) + def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float, + proposed_stake: float, min_stake: float, max_stake: float, + **kwargs) -> float: + + # We need to leave most of the funds for possible further DCA orders + # This also applies to fixed stakes + return proposed_stake / self.max_dca_multiplier + + 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 (just to show how to access it) + dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) + # Only buy when not actively falling price. + last_candle = dataframe.iloc[-1].squeeze() + previous_candle = dataframe.iloc[-2].squeeze() + if last_candle['close'] < previous_candle['close']: + return None + + count_of_buys = 0 + initial_order_stake = + for order in trade.orders: + 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 falls down to -5% again, we buy 1.5x more + # If that falls 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. + # That is why max_dca_multiplier is 5.5 + # Hope you have a deep wallet! + if 0 < count_of_buys <= self.max_dca_orders: + try: + # This returns max stakes for one trade + stake_amount = self.wallets.get_trade_stake_amount(pair, None) + # This calculates base order size + stake_amount = stake_amount / self.max_dca_multiplier + # This then calculates current safety order size + stake_amount = stake_amount * (1 + (count_of_buys * 0.25)) + return stake_amount + except Exception as exception: + return None + + return None + +``` diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5d0773437..d1327eb4d 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -458,14 +458,12 @@ class FreqtradeBot(LoggingMixin): # Walk through each pair and check if it needs changes for trade in Trade.get_open_trades(): # If there is any open orders, wait for them to finish. - for order in trade.orders: - if order.ft_is_open: - break - try: - self.check_and_call_adjust_trade_position(trade) - except DependencyException as exception: - logger.warning('Unable to adjust position of trade for %s: %s', - trade.pair, exception) + if len([o for o in trade.orders if o.ft_is_open]) == 0: + try: + self.check_and_call_adjust_trade_position(trade) + except DependencyException as exception: + logger.warning('Unable to adjust position of trade for %s: %s', + trade.pair, exception) def check_and_call_adjust_trade_position(self, trade: Trade): """ @@ -1385,7 +1383,7 @@ class FreqtradeBot(LoggingMixin): if order['status'] in constants.NON_OPEN_EXCHANGE_STATES: # If a buy order was closed, force update on stoploss on exchange - if order['side'] == 'buy': + if order.get('side', None) == 'buy': trade = self.cancel_stoploss_on_exchange(trade) # Updating wallets when order is closed self.wallets.update()