From 5525fdae1a4211949d77fe3676dbd6f37e4fbe22 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Wed, 19 Jan 2022 16:50:13 +0900 Subject: [PATCH 01/14] add max_buy_position_adjustment as attribute --- docs/configuration.md | 2 ++ docs/strategy-callbacks.md | 4 ++-- freqtrade/resolvers/strategy_resolver.py | 3 ++- freqtrade/strategy/interface.py | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 340ae2e72..3b7ae3c86 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -173,6 +173,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `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 here](strategy-callbacks.md#adjust-trade-position).
[Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean +| `max_buy_position_adjustment` | Maximum additional buy(s) for each open trades on top of the first buy. [More information here](strategy-callbacks.md#adjust-trade-position).
[Strategy Override](#parameters-in-the-strategy).
*Defaults to `1`.*
**Datatype:** Positive Integer ### Parameters in the strategy @@ -198,6 +199,7 @@ Values set in the configuration file always overwrite values set in the strategy * `ignore_roi_if_buy_signal` * `ignore_buying_expired_candle_after` * `position_adjustment_enable` +* `max_buy_position_adjustment` ### Configuring amount per trade diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 6edc549a4..f0f9a1853 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -611,7 +611,7 @@ class DigDeeperStrategy(IStrategy): # ... populate_* methods # Example specific variables - max_dca_orders = 3 + max_buy_position_adjustment = 3 # This number is explained a bit further down max_dca_multiplier = 5.5 @@ -663,7 +663,7 @@ class DigDeeperStrategy(IStrategy): # 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: + if 0 < count_of_buys <= self.max_buy_position_adjustment: try: # This returns first order stake size stake_amount = filled_buys[0].cost diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 95177c000..d331bd893 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -97,7 +97,8 @@ class StrategyResolver(IResolver): ("sell_profit_offset", 0.0), ("disable_dataframe_checks", False), ("ignore_buying_expired_candle_after", 0), - ("position_adjustment_enable", False) + ("position_adjustment_enable", False), + ("max_buy_position_adjustment", 1) ] for attribute, default in attributes: StrategyResolver._override_attribute_helper(strategy, config, diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index c8fb24da1..5a095aed3 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -108,6 +108,7 @@ class IStrategy(ABC, HyperStrategyMixin): # Position adjustment is disabled by default position_adjustment_enable: bool = False + max_buy_position_adjustment: int = 1 # Number of seconds after which the candle will no longer result in a buy on expired candles ignore_buying_expired_candle_after: int = 0 From ac93eea58534734cacce2a986fff25d9a4dafd12 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Wed, 19 Jan 2022 21:58:24 +0900 Subject: [PATCH 02/14] update --- docs/configuration.md | 2 +- freqtrade/freqtradebot.py | 10 ++++++++++ freqtrade/strategy/interface.py | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 3b7ae3c86..8723f51da 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -173,7 +173,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `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 here](strategy-callbacks.md#adjust-trade-position).
[Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean -| `max_buy_position_adjustment` | Maximum additional buy(s) for each open trades on top of the first buy. [More information here](strategy-callbacks.md#adjust-trade-position).
[Strategy Override](#parameters-in-the-strategy).
*Defaults to `1`.*
**Datatype:** Positive Integer +| `max_buy_position_adjustment` | Maximum additional buy(s) for each open trades on top of the first buy. Set it to `-1` for unlimited additional buys. [More information here](strategy-callbacks.md#adjust-trade-position).
[Strategy Override](#parameters-in-the-strategy).
*Defaults to `-1`.*
**Datatype:** Positive Integer or -1 ### Parameters in the strategy diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 8980fb91c..f45a49d81 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -473,6 +473,16 @@ class FreqtradeBot(LoggingMixin): If the strategy triggers the adjustment, a new order gets issued. Once that completes, the existing trade is modified to match new data. """ + if self.strategy.max_buy_position_adjustment > -1: + logger.info(f"Max adjustment buy is set to {self.strategy.max_buy_position_adsjutment}.") + + filled_buys = trade.select_filled_orders('buy') + count_of_buys = len(filled_buys) + if count_of_buys > self.strategy.max_buy_position_adjustment: + logger.info(f"Max adjustment buy for {trade.pair} has been reached.") + return + else: + logger.info(f"Max adjustment buy is set to unlimited.") current_rate = self.exchange.get_rate(trade.pair, refresh=True, side="buy") current_profit = trade.calc_profit_ratio(current_rate) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 5a095aed3..41023cc6a 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -108,7 +108,7 @@ class IStrategy(ABC, HyperStrategyMixin): # Position adjustment is disabled by default position_adjustment_enable: bool = False - max_buy_position_adjustment: int = 1 + max_buy_position_adjustment: int = -1 # Number of seconds after which the candle will no longer result in a buy on expired candles ignore_buying_expired_candle_after: int = 0 From 7c010b3058f33040e092dd5a0d76804f18a48c39 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Wed, 19 Jan 2022 22:16:42 +0900 Subject: [PATCH 03/14] Update strategy-callbacks.md --- docs/strategy-callbacks.md | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index f0f9a1853..7a4aed5d6 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -652,9 +652,6 @@ class DigDeeperStrategy(IStrategy): if last_candle['close'] < previous_candle['close']: return None - filled_buys = trade.select_filled_orders('buy') - count_of_buys = len(filled_buys) - # 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% @@ -663,15 +660,14 @@ class DigDeeperStrategy(IStrategy): # 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_buy_position_adjustment: - try: - # This returns first order stake size - stake_amount = filled_buys[0].cost - # 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 + try: + # This returns first order stake size + stake_amount = filled_buys[0].cost + # 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 From 2e537df3582a3d55b5a15900f9395ae7087c1f2f Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Thu, 20 Jan 2022 09:07:59 +0900 Subject: [PATCH 04/14] Update strategy_resolver.py --- freqtrade/resolvers/strategy_resolver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index d331bd893..82e3ad938 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -98,7 +98,7 @@ class StrategyResolver(IResolver): ("disable_dataframe_checks", False), ("ignore_buying_expired_candle_after", 0), ("position_adjustment_enable", False), - ("max_buy_position_adjustment", 1) + ("max_buy_position_adjustment", -1) ] for attribute, default in attributes: StrategyResolver._override_attribute_helper(strategy, config, From 62ea1a445e97e1f58ab40465f0aa760f69c05857 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Thu, 20 Jan 2022 10:03:26 +0900 Subject: [PATCH 05/14] add lines to show_config message --- freqtrade/optimize/backtesting.py | 8 +++++++- freqtrade/rpc/rpc.py | 5 ++++- freqtrade/rpc/telegram.py | 9 +++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index a4a5fd140..e49e3dc52 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -379,7 +379,13 @@ class Backtesting: # Check if we need to adjust our current positions if self.strategy.position_adjustment_enable: - trade = self._get_adjust_trade_entry_for_candle(trade, sell_row) + check_adjust_buy = True + if self.strategy.max_buy_position_adjustment > -1: + filled_buys = trade.select_filled_orders('buy') + count_of_buys = len(filled_buys) + check_adjust_buy = (count_of_buys <= self.strategy.max_buy_position_adjustment) + if check_adjust_buy: + trade = self._get_adjust_trade_entry_for_candle(trade, sell_row) sell_candle_time = sell_row[DATE_IDX].to_pydatetime() sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], # type: ignore diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index e568fca8c..044f725ab 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -136,7 +136,10 @@ class RPC: 'ask_strategy': config.get('ask_strategy', {}), 'bid_strategy': config.get('bid_strategy', {}), 'state': str(botstate), - 'runmode': config['runmode'].value + 'runmode': config['runmode'].value, + 'position_adjustment_enable': config.get('position_adjustment_enable', False), + 'max_buy_position_adjustment': (config['max_buy_position_adjustment'] + if config['max_buy_position_adjustment'] != float('inf') else -1) } return val diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 716694a81..ac2062bb3 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1347,6 +1347,14 @@ class Telegram(RPCHandler): else: sl_info = f"*Stoploss:* `{val['stoploss']}`\n" + if val['position_adjustment_enable']: + pa_info = ( + f"*Position adjustment:* On\n" + f"*Max buy position adjustment:* `{val['max_buy_position_adjustment']}`\n" + ) + else: + pa_info = f"*Position adjustment:* Off\n" + self._send_msg( f"*Mode:* `{'Dry-run' if val['dry_run'] else 'Live'}`\n" f"*Exchange:* `{val['exchange']}`\n" @@ -1356,6 +1364,7 @@ class Telegram(RPCHandler): f"*Ask strategy:* ```\n{json.dumps(val['ask_strategy'])}```\n" f"*Bid strategy:* ```\n{json.dumps(val['bid_strategy'])}```\n" f"{sl_info}" + f"{pa_info}" f"*Timeframe:* `{val['timeframe']}`\n" f"*Strategy:* `{val['strategy']}`\n" f"*Current state:* `{val['state']}`" From 5fb951155617c838e6b25f2174b2a6e885204c9a Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Thu, 20 Jan 2022 10:34:35 +0900 Subject: [PATCH 06/14] fix typo --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index f45a49d81..f783b01a1 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -474,7 +474,7 @@ class FreqtradeBot(LoggingMixin): Once that completes, the existing trade is modified to match new data. """ if self.strategy.max_buy_position_adjustment > -1: - logger.info(f"Max adjustment buy is set to {self.strategy.max_buy_position_adsjutment}.") + logger.info(f"Max adjustment buy is set to {self.strategy.max_buy_position_adjustment}.") filled_buys = trade.select_filled_orders('buy') count_of_buys = len(filled_buys) From f30580e5f2a1c0c5bbd928439e337265e39f34ea Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Thu, 20 Jan 2022 11:40:29 +0900 Subject: [PATCH 07/14] Update freqtradebot.py --- freqtrade/freqtradebot.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index f783b01a1..ce781025e 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -474,15 +474,13 @@ class FreqtradeBot(LoggingMixin): Once that completes, the existing trade is modified to match new data. """ if self.strategy.max_buy_position_adjustment > -1: - logger.info(f"Max adjustment buy is set to {self.strategy.max_buy_position_adjustment}.") - filled_buys = trade.select_filled_orders('buy') count_of_buys = len(filled_buys) if count_of_buys > self.strategy.max_buy_position_adjustment: - logger.info(f"Max adjustment buy for {trade.pair} has been reached.") + logger.debug(f"Max adjustment buy for {trade.pair} has been reached.") return else: - logger.info(f"Max adjustment buy is set to unlimited.") + logger.debug(f"Max adjustment buy is set to unlimited.") current_rate = self.exchange.get_rate(trade.pair, refresh=True, side="buy") current_profit = trade.calc_profit_ratio(current_rate) From 748381c5cd15a059f2cc951022a6b47f2379c3a2 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Fri, 21 Jan 2022 00:35:22 +0000 Subject: [PATCH 08/14] Update based on flake8 --- freqtrade/freqtradebot.py | 4 ++-- freqtrade/rpc/api_server/api_schemas.py | 2 ++ freqtrade/rpc/rpc.py | 3 ++- freqtrade/rpc/telegram.py | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ce781025e..f2f04fb08 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -478,9 +478,9 @@ class FreqtradeBot(LoggingMixin): count_of_buys = len(filled_buys) if count_of_buys > self.strategy.max_buy_position_adjustment: logger.debug(f"Max adjustment buy for {trade.pair} has been reached.") - return + return else: - logger.debug(f"Max adjustment buy is set to unlimited.") + logger.debug("Max adjustment buy is set to unlimited.") current_rate = self.exchange.get_rate(trade.pair, refresh=True, side="buy") current_profit = trade.calc_profit_ratio(current_rate) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 10f181bb6..84b0782c9 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -173,6 +173,8 @@ class ShowConfig(BaseModel): bot_name: str state: str runmode: str + position_adjustment_enable: bool + max_buy_position_adjustment: int class TradeSchema(BaseModel): diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 044f725ab..6b966487a 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -139,7 +139,8 @@ class RPC: 'runmode': config['runmode'].value, 'position_adjustment_enable': config.get('position_adjustment_enable', False), 'max_buy_position_adjustment': (config['max_buy_position_adjustment'] - if config['max_buy_position_adjustment'] != float('inf') else -1) + if config['max_buy_position_adjustment'] != float('inf') + else -1) } return val diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index ac2062bb3..1017d5f48 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1353,7 +1353,7 @@ class Telegram(RPCHandler): f"*Max buy position adjustment:* `{val['max_buy_position_adjustment']}`\n" ) else: - pa_info = f"*Position adjustment:* Off\n" + pa_info = "*Position adjustment:* Off\n" self._send_msg( f"*Mode:* `{'Dry-run' if val['dry_run'] else 'Live'}`\n" From 3249f9fb98e812b44b25234b6bece0a053a9a0b1 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Fri, 21 Jan 2022 08:27:54 +0000 Subject: [PATCH 09/14] Add max buys on status table --- freqtrade/rpc/rpc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index a6813f0d2..06f3b9cb2 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -251,8 +251,9 @@ class RPC: profit_str ] if self._config.get('position_adjustment_enable', False): + max_buy = self._config['max_buy_position_adjustment'] + 1 filled_buys = trade.select_filled_orders('buy') - detail_trade.append(str(len(filled_buys))) + detail_trade.append(f"{len(filled_buys)}/{max_buy}") trades_list.append(detail_trade) profitcol = "Profit" if self._fiat_converter: From cc3852daf3750483740f8af8261a4dcdab1d0d4e Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Sun, 23 Jan 2022 03:54:58 +0000 Subject: [PATCH 10/14] Add defaults to constants.py and update docs --- docs/strategy-callbacks.md | 4 +++- freqtrade/constants.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 7a4aed5d6..8ff4cc095 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -576,11 +576,13 @@ The `position_adjustment_enable` strategy property enables the usage of `adjust_ 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, for example to manage risk with DCA (Dollar Cost Averaging). +`max_buy_position_adjustment` property is used to limit the number of additional buys per trade (on top of the first buy) that the bot can execute. By default, the value is -1 which means the bot have no limit on number of adjustment buys. + The strategy is expected to return a stake_amount (in stake currency) between `min_stake` and `max_stake` if and when an additional buy order should be made (position is increased). If there are not enough funds in the wallet (the return value is above `max_stake`) then the signal will be ignored. Additional orders also result in additional fees and those orders don't count towards `max_open_trades`. -This callback is **not** called when there is an open order (either buy or sell) waiting for execution. +This callback is **not** called when there is an open order (either buy or sell) waiting for execution, or when you have reached the maximum amount of extra buys that you have set on `max_buy_position_adjustment`. `adjust_trade_position()` is called very frequently for the duration of a trade, so you must keep your implementation as performant as possible. !!! Note "About stake size" diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 504c7dce9..a166c074b 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -371,7 +371,9 @@ CONF_SCHEMA = { 'type': 'string', 'enum': AVAILABLE_DATAHANDLERS, 'default': 'jsongz' - } + }, + 'position_adjustment_enable': {'type': 'boolean', 'default': False}, + 'max_buy_position_adjustment': {'type': ['integer', 'number'], 'minimum': -1}, }, 'definitions': { 'exchange': { From 7429f535c118e93c285f36a8807f6a9f9a0786a6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 23 Jan 2022 18:59:09 +0100 Subject: [PATCH 11/14] Imrpove code by reusing available properties --- docs/configuration.md | 2 +- docs/strategy-callbacks.md | 1 + freqtrade/freqtradebot.py | 3 +-- freqtrade/optimize/backtesting.py | 3 +-- freqtrade/resolvers/strategy_resolver.py | 2 +- freqtrade/rpc/rpc.py | 4 ++-- 6 files changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 8723f51da..aa02a95a5 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -173,7 +173,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `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 here](strategy-callbacks.md#adjust-trade-position).
[Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean -| `max_buy_position_adjustment` | Maximum additional buy(s) for each open trades on top of the first buy. Set it to `-1` for unlimited additional buys. [More information here](strategy-callbacks.md#adjust-trade-position).
[Strategy Override](#parameters-in-the-strategy).
*Defaults to `-1`.*
**Datatype:** Positive Integer or -1 +| `max_buy_position_adjustment` | Maximum additional buy(s) for each open trade on top of the first buy. Set it to `-1` for unlimited additional buys. [More information here](strategy-callbacks.md#adjust-trade-position).
[Strategy Override](#parameters-in-the-strategy).
*Defaults to `-1`.*
**Datatype:** Positive Integer or -1 ### Parameters in the strategy diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index 8ff4cc095..a06e76454 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -654,6 +654,7 @@ class DigDeeperStrategy(IStrategy): if last_candle['close'] < previous_candle['close']: return None + count_of_buys = trade.nr_of_successful_buys # 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% diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index f2f04fb08..6a8dde1f6 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -474,8 +474,7 @@ class FreqtradeBot(LoggingMixin): Once that completes, the existing trade is modified to match new data. """ if self.strategy.max_buy_position_adjustment > -1: - filled_buys = trade.select_filled_orders('buy') - count_of_buys = len(filled_buys) + count_of_buys = trade.nr_of_successful_buys if count_of_buys > self.strategy.max_buy_position_adjustment: logger.debug(f"Max adjustment buy for {trade.pair} has been reached.") return diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index b9023c435..c3c16477b 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -383,8 +383,7 @@ class Backtesting: if self.strategy.position_adjustment_enable: check_adjust_buy = True if self.strategy.max_buy_position_adjustment > -1: - filled_buys = trade.select_filled_orders('buy') - count_of_buys = len(filled_buys) + count_of_buys = trade.nr_of_successful_buys check_adjust_buy = (count_of_buys <= self.strategy.max_buy_position_adjustment) if check_adjust_buy: trade = self._get_adjust_trade_entry_for_candle(trade, sell_row) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 82e3ad938..f48a42f11 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -98,7 +98,7 @@ class StrategyResolver(IResolver): ("disable_dataframe_checks", False), ("ignore_buying_expired_candle_after", 0), ("position_adjustment_enable", False), - ("max_buy_position_adjustment", -1) + ("max_buy_position_adjustment", -1), ] for attribute, default in attributes: StrategyResolver._override_attribute_helper(strategy, config, diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 1882da6e6..0081e7a07 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -252,8 +252,8 @@ class RPC: ] if self._config.get('position_adjustment_enable', False): max_buy = self._config['max_buy_position_adjustment'] + 1 - filled_buys = trade.select_filled_orders('buy') - detail_trade.append(f"{len(filled_buys)}/{max_buy}") + filled_buys = trade.nr_of_successful_buys + detail_trade.append(f"{filled_buys}/{max_buy}") trades_list.append(detail_trade) profitcol = "Profit" if self._fiat_converter: From 57067ce88d420c025dd40de1d84e679a3bc96229 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 23 Jan 2022 19:07:37 +0100 Subject: [PATCH 12/14] Add tests for max_adjustment_buy handling --- freqtrade/freqtradebot.py | 4 ++-- tests/test_freqtradebot.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 6a8dde1f6..dad580bcc 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -464,8 +464,8 @@ class FreqtradeBot(LoggingMixin): 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) + logger.warning( + f"Unable to adjust position of trade for {trade.pair}: {exception}") def check_and_call_adjust_trade_position(self, trade: Trade): """ diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index d226280fe..6946cd08f 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -4550,3 +4550,32 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None: # Make sure the closed order is found as the second order. order = trade.select_order('buy', False) assert order.order_id == '652' + + +def test_process_open_trade_positions_exception(mocker, default_conf_usdt, fee, caplog) -> None: + default_conf_usdt.update({ + "position_adjustment_enable": True, + }) + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + + mocker.patch('freqtrade.freqtradebot.FreqtradeBot.check_and_call_adjust_trade_position', + side_effect=DependencyException()) + + create_mock_trades(fee) + + freqtrade.process_open_trade_positions() + assert log_has_re(r"Unable to adjust position of trade for .*", caplog) + + +def test_check_and_call_adjust_trade_position(mocker, default_conf_usdt, fee, caplog) -> None: + default_conf_usdt.update({ + "position_adjustment_enable": True, + "max_buy_position_adjustment": 0, + }) + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + + create_mock_trades(fee) + caplog.set_level(logging.DEBUG) + + freqtrade.process_open_trade_positions() + assert log_has_re(r"Max adjustment buy for .* has been reached\.", caplog) From 45a22989299d28b8cff50b75c4546f19ae98ffb7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 24 Jan 2022 07:11:22 +0100 Subject: [PATCH 13/14] Re-add getting buy orders in docs --- docs/strategy-callbacks.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index a06e76454..f4ad109d5 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -654,6 +654,7 @@ class DigDeeperStrategy(IStrategy): if last_candle['close'] < previous_candle['close']: return None + filled_buys = trade.select_filled_orders('buy') count_of_buys = trade.nr_of_successful_buys # Allow up to 3 additional increasingly larger buys (4 in total) # Initial buy is 1x From 002226f5fdf6e899143d334a32ee8f7660f6ff52 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 27 Jan 2022 16:57:50 +0100 Subject: [PATCH 14/14] Update setting to max_entry_position_adjustment --- docs/configuration.md | 4 ++-- docs/strategy-callbacks.md | 6 +++--- freqtrade/constants.py | 2 +- freqtrade/freqtradebot.py | 8 ++++---- freqtrade/optimize/backtesting.py | 4 ++-- freqtrade/resolvers/strategy_resolver.py | 2 +- freqtrade/rpc/api_server/api_schemas.py | 2 +- freqtrade/rpc/rpc.py | 9 +++++---- freqtrade/rpc/telegram.py | 2 +- freqtrade/strategy/interface.py | 2 +- tests/test_freqtradebot.py | 4 ++-- 11 files changed, 23 insertions(+), 22 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index aa02a95a5..172ad468d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -173,7 +173,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `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 here](strategy-callbacks.md#adjust-trade-position).
[Strategy Override](#parameters-in-the-strategy).
*Defaults to `false`.*
**Datatype:** Boolean -| `max_buy_position_adjustment` | Maximum additional buy(s) for each open trade on top of the first buy. Set it to `-1` for unlimited additional buys. [More information here](strategy-callbacks.md#adjust-trade-position).
[Strategy Override](#parameters-in-the-strategy).
*Defaults to `-1`.*
**Datatype:** Positive Integer or -1 +| `max_entry_position_adjustment` | Maximum additional buy(s) for each open trade on top of the first entry Order. Set it to `-1` for unlimited additional additional orders. [More information here](strategy-callbacks.md#adjust-trade-position).
[Strategy Override](#parameters-in-the-strategy).
*Defaults to `-1`.*
**Datatype:** Positive Integer or -1 ### Parameters in the strategy @@ -199,7 +199,7 @@ Values set in the configuration file always overwrite values set in the strategy * `ignore_roi_if_buy_signal` * `ignore_buying_expired_candle_after` * `position_adjustment_enable` -* `max_buy_position_adjustment` +* `max_entry_position_adjustment` ### Configuring amount per trade diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md index ca5fa4274..3a30a2a28 100644 --- a/docs/strategy-callbacks.md +++ b/docs/strategy-callbacks.md @@ -579,13 +579,13 @@ The `position_adjustment_enable` strategy property enables the usage of `adjust_ 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, for example to manage risk with DCA (Dollar Cost Averaging). -`max_buy_position_adjustment` property is used to limit the number of additional buys per trade (on top of the first buy) that the bot can execute. By default, the value is -1 which means the bot have no limit on number of adjustment buys. +`max_entry_position_adjustment` property is used to limit the number of additional buys per trade (on top of the first buy) that the bot can execute. By default, the value is -1 which means the bot have no limit on number of adjustment buys. The strategy is expected to return a stake_amount (in stake currency) between `min_stake` and `max_stake` if and when an additional buy order should be made (position is increased). If there are not enough funds in the wallet (the return value is above `max_stake`) then the signal will be ignored. Additional orders also result in additional fees and those orders don't count towards `max_open_trades`. -This callback is **not** called when there is an open order (either buy or sell) waiting for execution, or when you have reached the maximum amount of extra buys that you have set on `max_buy_position_adjustment`. +This callback is **not** called when there is an open order (either buy or sell) waiting for execution, or when you have reached the maximum amount of extra buys that you have set on `max_entry_position_adjustment`. `adjust_trade_position()` is called very frequently for the duration of a trade, so you must keep your implementation as performant as possible. !!! Note "About stake size" @@ -616,7 +616,7 @@ class DigDeeperStrategy(IStrategy): # ... populate_* methods # Example specific variables - max_buy_position_adjustment = 3 + max_entry_position_adjustment = 3 # This number is explained a bit further down max_dca_multiplier = 5.5 diff --git a/freqtrade/constants.py b/freqtrade/constants.py index a166c074b..f02e39792 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -373,7 +373,7 @@ CONF_SCHEMA = { 'default': 'jsongz' }, 'position_adjustment_enable': {'type': 'boolean', 'default': False}, - 'max_buy_position_adjustment': {'type': ['integer', 'number'], 'minimum': -1}, + 'max_entry_position_adjustment': {'type': ['integer', 'number'], 'minimum': -1}, }, 'definitions': { 'exchange': { diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index bc2be37ed..c3c03ed67 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -471,13 +471,13 @@ class FreqtradeBot(LoggingMixin): If the strategy triggers the adjustment, a new order gets issued. Once that completes, the existing trade is modified to match new data. """ - if self.strategy.max_buy_position_adjustment > -1: + if self.strategy.max_entry_position_adjustment > -1: count_of_buys = trade.nr_of_successful_buys - if count_of_buys > self.strategy.max_buy_position_adjustment: - logger.debug(f"Max adjustment buy for {trade.pair} has been reached.") + if count_of_buys > self.strategy.max_entry_position_adjustment: + logger.debug(f"Max adjustment entries for {trade.pair} has been reached.") return else: - logger.debug("Max adjustment buy is set to unlimited.") + logger.debug("Max adjustment entries is set to unlimited.") current_rate = self.exchange.get_rate(trade.pair, refresh=True, side="buy") current_profit = trade.calc_profit_ratio(current_rate) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index e5aff9374..e173c3367 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -382,9 +382,9 @@ class Backtesting: # Check if we need to adjust our current positions if self.strategy.position_adjustment_enable: check_adjust_buy = True - if self.strategy.max_buy_position_adjustment > -1: + if self.strategy.max_entry_position_adjustment > -1: count_of_buys = trade.nr_of_successful_buys - check_adjust_buy = (count_of_buys <= self.strategy.max_buy_position_adjustment) + check_adjust_buy = (count_of_buys <= self.strategy.max_entry_position_adjustment) if check_adjust_buy: trade = self._get_adjust_trade_entry_for_candle(trade, sell_row) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index f48a42f11..e9fcc3496 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -98,7 +98,7 @@ class StrategyResolver(IResolver): ("disable_dataframe_checks", False), ("ignore_buying_expired_candle_after", 0), ("position_adjustment_enable", False), - ("max_buy_position_adjustment", -1), + ("max_entry_position_adjustment", -1), ] for attribute, default in attributes: StrategyResolver._override_attribute_helper(strategy, config, diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 421209863..96421ed32 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -174,7 +174,7 @@ class ShowConfig(BaseModel): state: str runmode: str position_adjustment_enable: bool - max_buy_position_adjustment: int + max_entry_position_adjustment: int class TradeSchema(BaseModel): diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index b6bed9e0b..2374dbd39 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -138,9 +138,10 @@ class RPC: 'state': str(botstate), 'runmode': config['runmode'].value, 'position_adjustment_enable': config.get('position_adjustment_enable', False), - 'max_buy_position_adjustment': (config['max_buy_position_adjustment'] - if config['max_buy_position_adjustment'] != float('inf') - else -1) + 'max_entry_position_adjustment': ( + config['max_entry_position_adjustment'] + if config['max_entry_position_adjustment'] != float('inf') + else -1) } return val @@ -251,7 +252,7 @@ class RPC: profit_str ] if self._config.get('position_adjustment_enable', False): - max_buy = self._config['max_buy_position_adjustment'] + 1 + max_buy = self._config['max_entry_position_adjustment'] + 1 filled_buys = trade.nr_of_successful_buys detail_trade.append(f"{filled_buys}/{max_buy}") trades_list.append(detail_trade) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 1017d5f48..0f0bd7432 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1350,7 +1350,7 @@ class Telegram(RPCHandler): if val['position_adjustment_enable']: pa_info = ( f"*Position adjustment:* On\n" - f"*Max buy position adjustment:* `{val['max_buy_position_adjustment']}`\n" + f"*Max enter position adjustment:* `{val['max_entry_position_adjustment']}`\n" ) else: pa_info = "*Position adjustment:* Off\n" diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 220c7f0a9..78dae6c5d 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -108,7 +108,7 @@ class IStrategy(ABC, HyperStrategyMixin): # Position adjustment is disabled by default position_adjustment_enable: bool = False - max_buy_position_adjustment: int = -1 + max_entry_position_adjustment: int = -1 # Number of seconds after which the candle will no longer result in a buy on expired candles ignore_buying_expired_candle_after: int = 0 diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 6946cd08f..523696759 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -4570,7 +4570,7 @@ def test_process_open_trade_positions_exception(mocker, default_conf_usdt, fee, def test_check_and_call_adjust_trade_position(mocker, default_conf_usdt, fee, caplog) -> None: default_conf_usdt.update({ "position_adjustment_enable": True, - "max_buy_position_adjustment": 0, + "max_entry_position_adjustment": 0, }) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) @@ -4578,4 +4578,4 @@ def test_check_and_call_adjust_trade_position(mocker, default_conf_usdt, fee, ca caplog.set_level(logging.DEBUG) freqtrade.process_open_trade_positions() - assert log_has_re(r"Max adjustment buy for .* has been reached\.", caplog) + assert log_has_re(r"Max adjustment entries for .* has been reached\.", caplog)