Merge branch 'freqtrade:develop' into fix-docs
This commit is contained in:
commit
bd1b991448
@ -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. <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 here](strategy-callbacks.md#adjust-trade-position). <br> [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.*<br> **Datatype:** Boolean
|
| `position_adjustment_enable` | Enables the strategy to use position adjustments (additional buys or sells). [More information here](strategy-callbacks.md#adjust-trade-position). <br> [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.*<br> **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). <br> [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `-1`.*<br> **Datatype:** Positive Integer or -1
|
||||||
|
|
||||||
### Parameters in the strategy
|
### 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_roi_if_buy_signal`
|
||||||
* `ignore_buying_expired_candle_after`
|
* `ignore_buying_expired_candle_after`
|
||||||
* `position_adjustment_enable`
|
* `position_adjustment_enable`
|
||||||
|
* `max_entry_position_adjustment`
|
||||||
|
|
||||||
### Configuring amount per trade
|
### Configuring amount per trade
|
||||||
|
|
||||||
|
@ -126,6 +126,12 @@ All freqtrade arguments will be available by running `docker-compose run --rm fr
|
|||||||
!!! Note "`docker-compose run --rm`"
|
!!! 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).
|
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
|
#### 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.
|
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.
|
||||||
|
@ -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.
|
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).
|
`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).
|
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.
|
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`.
|
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.
|
`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"
|
!!! Note "About stake size"
|
||||||
@ -614,7 +616,7 @@ class DigDeeperStrategy(IStrategy):
|
|||||||
# ... populate_* methods
|
# ... populate_* methods
|
||||||
|
|
||||||
# Example specific variables
|
# Example specific variables
|
||||||
max_dca_orders = 3
|
max_entry_position_adjustment = 3
|
||||||
# This number is explained a bit further down
|
# This number is explained a bit further down
|
||||||
max_dca_multiplier = 5.5
|
max_dca_multiplier = 5.5
|
||||||
|
|
||||||
@ -656,8 +658,7 @@ class DigDeeperStrategy(IStrategy):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
filled_buys = trade.select_filled_orders('buy')
|
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)
|
# Allow up to 3 additional increasingly larger buys (4 in total)
|
||||||
# Initial buy is 1x
|
# 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 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.
|
# 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
|
# That is why max_dca_multiplier is 5.5
|
||||||
# Hope you have a deep wallet!
|
# Hope you have a deep wallet!
|
||||||
if 0 < count_of_buys <= self.max_dca_orders:
|
try:
|
||||||
try:
|
# This returns first order stake size
|
||||||
# This returns first order stake size
|
stake_amount = filled_buys[0].cost
|
||||||
stake_amount = filled_buys[0].cost
|
# This then calculates current safety order size
|
||||||
# This then calculates current safety order size
|
stake_amount = stake_amount * (1 + (count_of_buys * 0.25))
|
||||||
stake_amount = stake_amount * (1 + (count_of_buys * 0.25))
|
return stake_amount
|
||||||
return stake_amount
|
except Exception as exception:
|
||||||
except Exception as exception:
|
return None
|
||||||
return None
|
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -371,7 +371,9 @@ CONF_SCHEMA = {
|
|||||||
'type': 'string',
|
'type': 'string',
|
||||||
'enum': AVAILABLE_DATAHANDLERS,
|
'enum': AVAILABLE_DATAHANDLERS,
|
||||||
'default': 'jsongz'
|
'default': 'jsongz'
|
||||||
}
|
},
|
||||||
|
'position_adjustment_enable': {'type': 'boolean', 'default': False},
|
||||||
|
'max_entry_position_adjustment': {'type': ['integer', 'number'], 'minimum': -1},
|
||||||
},
|
},
|
||||||
'definitions': {
|
'definitions': {
|
||||||
'exchange': {
|
'exchange': {
|
||||||
|
@ -462,8 +462,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
try:
|
try:
|
||||||
self.check_and_call_adjust_trade_position(trade)
|
self.check_and_call_adjust_trade_position(trade)
|
||||||
except DependencyException as exception:
|
except DependencyException as exception:
|
||||||
logger.warning('Unable to adjust position of trade for %s: %s',
|
logger.warning(
|
||||||
trade.pair, exception)
|
f"Unable to adjust position of trade for {trade.pair}: {exception}")
|
||||||
|
|
||||||
def check_and_call_adjust_trade_position(self, trade: Trade):
|
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.
|
If the strategy triggers the adjustment, a new order gets issued.
|
||||||
Once that completes, the existing trade is modified to match new data.
|
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_rate = self.exchange.get_rate(trade.pair, refresh=True, side="buy")
|
||||||
current_profit = trade.calc_profit_ratio(current_rate)
|
current_profit = trade.calc_profit_ratio(current_rate)
|
||||||
|
|
||||||
|
@ -381,7 +381,12 @@ class Backtesting:
|
|||||||
|
|
||||||
# Check if we need to adjust our current positions
|
# Check if we need to adjust our current positions
|
||||||
if self.strategy.position_adjustment_enable:
|
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_candle_time = sell_row[DATE_IDX].to_pydatetime()
|
||||||
sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], # type: ignore
|
sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], # type: ignore
|
||||||
|
@ -97,7 +97,8 @@ class StrategyResolver(IResolver):
|
|||||||
("sell_profit_offset", 0.0),
|
("sell_profit_offset", 0.0),
|
||||||
("disable_dataframe_checks", False),
|
("disable_dataframe_checks", False),
|
||||||
("ignore_buying_expired_candle_after", 0),
|
("ignore_buying_expired_candle_after", 0),
|
||||||
("position_adjustment_enable", False)
|
("position_adjustment_enable", False),
|
||||||
|
("max_entry_position_adjustment", -1),
|
||||||
]
|
]
|
||||||
for attribute, default in attributes:
|
for attribute, default in attributes:
|
||||||
StrategyResolver._override_attribute_helper(strategy, config,
|
StrategyResolver._override_attribute_helper(strategy, config,
|
||||||
|
@ -173,6 +173,8 @@ class ShowConfig(BaseModel):
|
|||||||
bot_name: str
|
bot_name: str
|
||||||
state: str
|
state: str
|
||||||
runmode: str
|
runmode: str
|
||||||
|
position_adjustment_enable: bool
|
||||||
|
max_entry_position_adjustment: int
|
||||||
|
|
||||||
|
|
||||||
class TradeSchema(BaseModel):
|
class TradeSchema(BaseModel):
|
||||||
|
@ -136,7 +136,12 @@ class RPC:
|
|||||||
'ask_strategy': config.get('ask_strategy', {}),
|
'ask_strategy': config.get('ask_strategy', {}),
|
||||||
'bid_strategy': config.get('bid_strategy', {}),
|
'bid_strategy': config.get('bid_strategy', {}),
|
||||||
'state': str(botstate),
|
'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
|
return val
|
||||||
|
|
||||||
@ -249,8 +254,9 @@ class RPC:
|
|||||||
profit_str
|
profit_str
|
||||||
]
|
]
|
||||||
if self._config.get('position_adjustment_enable', False):
|
if self._config.get('position_adjustment_enable', False):
|
||||||
filled_buys = trade.select_filled_orders('buy')
|
max_buy = self._config['max_entry_position_adjustment'] + 1
|
||||||
detail_trade.append(str(len(filled_buys)))
|
filled_buys = trade.nr_of_successful_buys
|
||||||
|
detail_trade.append(f"{filled_buys}/{max_buy}")
|
||||||
trades_list.append(detail_trade)
|
trades_list.append(detail_trade)
|
||||||
profitcol = "Profit"
|
profitcol = "Profit"
|
||||||
if self._fiat_converter:
|
if self._fiat_converter:
|
||||||
|
@ -1405,6 +1405,14 @@ class Telegram(RPCHandler):
|
|||||||
else:
|
else:
|
||||||
sl_info = f"*Stoploss:* `{val['stoploss']}`\n"
|
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(
|
self._send_msg(
|
||||||
f"*Mode:* `{'Dry-run' if val['dry_run'] else 'Live'}`\n"
|
f"*Mode:* `{'Dry-run' if val['dry_run'] else 'Live'}`\n"
|
||||||
f"*Exchange:* `{val['exchange']}`\n"
|
f"*Exchange:* `{val['exchange']}`\n"
|
||||||
@ -1414,6 +1422,7 @@ class Telegram(RPCHandler):
|
|||||||
f"*Ask strategy:* ```\n{json.dumps(val['ask_strategy'])}```\n"
|
f"*Ask strategy:* ```\n{json.dumps(val['ask_strategy'])}```\n"
|
||||||
f"*Bid strategy:* ```\n{json.dumps(val['bid_strategy'])}```\n"
|
f"*Bid strategy:* ```\n{json.dumps(val['bid_strategy'])}```\n"
|
||||||
f"{sl_info}"
|
f"{sl_info}"
|
||||||
|
f"{pa_info}"
|
||||||
f"*Timeframe:* `{val['timeframe']}`\n"
|
f"*Timeframe:* `{val['timeframe']}`\n"
|
||||||
f"*Strategy:* `{val['strategy']}`\n"
|
f"*Strategy:* `{val['strategy']}`\n"
|
||||||
f"*Current state:* `{val['state']}`"
|
f"*Current state:* `{val['state']}`"
|
||||||
|
@ -108,6 +108,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
|
|
||||||
# Position adjustment is disabled by default
|
# Position adjustment is disabled by default
|
||||||
position_adjustment_enable: bool = False
|
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
|
# Number of seconds after which the candle will no longer result in a buy on expired candles
|
||||||
ignore_buying_expired_candle_after: int = 0
|
ignore_buying_expired_candle_after: int = 0
|
||||||
|
@ -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.
|
# Make sure the closed order is found as the second order.
|
||||||
order = trade.select_order('buy', False)
|
order = trade.select_order('buy', False)
|
||||||
assert order.order_id == '652'
|
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)
|
||||||
|
Loading…
Reference in New Issue
Block a user