diff --git a/docs/configuration.md b/docs/configuration.md
index 340ae2e72..172ad468d 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_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
@@ -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_entry_position_adjustment`
### Configuring amount per trade
diff --git a/docs/docker_quickstart.md b/docs/docker_quickstart.md
index 95df37811..84c1d596a 100644
--- a/docs/docker_quickstart.md
+++ b/docs/docker_quickstart.md
@@ -126,6 +126,12 @@ All freqtrade arguments will be available by running `docker-compose run --rm fr
!!! Note "`docker-compose run --rm`"
Including `--rm` will remove the container after completion, and is highly recommended for all modes except trading mode (running with `freqtrade trade` command).
+??? Note "Using docker without docker-compose"
+ "`docker-compose run --rm`" will require a compose file to be provided.
+ Some freqtrade commands that don't require authentication such as `list-pairs` can be run with "`docker run --rm`" instead.
+ For example `docker run --rm freqtradeorg/freqtrade:stable list-pairs --exchange binance --quote BTC --print-json`.
+ This can be useful for fetching exchange information to add to your `config.json` without affecting your running containers.
+
#### Example: Download data with docker-compose
Download backtesting data for 5 days for the pair ETH/BTC and 1h timeframe from Binance. The data will be stored in the directory `user_data/data/` on the host.
diff --git a/docs/strategy-callbacks.md b/docs/strategy-callbacks.md
index e83fee46f..3a30a2a28 100644
--- a/docs/strategy-callbacks.md
+++ b/docs/strategy-callbacks.md
@@ -579,11 +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_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.
+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"
@@ -614,7 +616,7 @@ class DigDeeperStrategy(IStrategy):
# ... populate_* methods
# Example specific variables
- max_dca_orders = 3
+ max_entry_position_adjustment = 3
# This number is explained a bit further down
max_dca_multiplier = 5.5
@@ -656,8 +658,7 @@ class DigDeeperStrategy(IStrategy):
return None
filled_buys = trade.select_filled_orders('buy')
- count_of_buys = len(filled_buys)
-
+ 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%
@@ -666,15 +667,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_dca_orders:
- 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
diff --git a/freqtrade/constants.py b/freqtrade/constants.py
index 504c7dce9..f02e39792 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_entry_position_adjustment': {'type': ['integer', 'number'], 'minimum': -1},
},
'definitions': {
'exchange': {
diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index 972b6f6b7..c3c03ed67 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -462,8 +462,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):
"""
@@ -471,6 +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_entry_position_adjustment > -1:
+ count_of_buys = trade.nr_of_successful_buys
+ 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 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 8e52a62fa..e173c3367 100644
--- a/freqtrade/optimize/backtesting.py
+++ b/freqtrade/optimize/backtesting.py
@@ -381,7 +381,12 @@ 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_entry_position_adjustment > -1:
+ count_of_buys = trade.nr_of_successful_buys
+ 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)
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/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py
index 95177c000..e9fcc3496 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_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 bbd858795..96421ed32 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_entry_position_adjustment: int
class TradeSchema(BaseModel):
diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index a0f8cdb9d..f53e9cdeb 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -136,7 +136,12 @@ 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_entry_position_adjustment': (
+ config['max_entry_position_adjustment']
+ if config['max_entry_position_adjustment'] != float('inf')
+ else -1)
}
return val
@@ -249,8 +254,9 @@ class RPC:
profit_str
]
if self._config.get('position_adjustment_enable', False):
- filled_buys = trade.select_filled_orders('buy')
- detail_trade.append(str(len(filled_buys)))
+ 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)
profitcol = "Profit"
if self._fiat_converter:
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index 7f8c3fb1a..4943f9df2 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -1405,6 +1405,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 enter position adjustment:* `{val['max_entry_position_adjustment']}`\n"
+ )
+ else:
+ pa_info = "*Position adjustment:* Off\n"
+
self._send_msg(
f"*Mode:* `{'Dry-run' if val['dry_run'] else 'Live'}`\n"
f"*Exchange:* `{val['exchange']}`\n"
@@ -1414,6 +1422,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']}`"
diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py
index 6f139026e..78dae6c5d 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_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 d226280fe..523696759 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_entry_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 entries for .* has been reached\.", caplog)