mirror of
https://github.com/freqtrade/freqtrade.git
synced 2026-05-02 06:56:50 +00:00
Merge pull request #11412 from freqtrade/feat/adjust_order_price
Add adjust_order_price callback
This commit is contained in:
@@ -436,6 +436,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||
|
||||
# Replace new order with diff. order at a lower price
|
||||
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=1.95)
|
||||
freqtrade.strategy.adjust_exit_price = MagicMock(side_effect=ValueError)
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=None)
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
@@ -445,6 +446,8 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||
assert pytest.approx(trade.stake_amount) == 60
|
||||
assert trade.orders[-1].price == 1.95
|
||||
assert pytest.approx(trade.orders[-1].cost) == 120 * leverage
|
||||
assert freqtrade.strategy.adjust_entry_price.call_count == 1
|
||||
assert freqtrade.strategy.adjust_exit_price.call_count == 0
|
||||
|
||||
# Fill DCA order
|
||||
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=None)
|
||||
@@ -469,6 +472,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||
mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=False)
|
||||
freqtrade.strategy.custom_exit = MagicMock(return_value="Exit now")
|
||||
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=2.02)
|
||||
freqtrade.strategy.adjust_exit_price = MagicMock(side_effect=ValueError)
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
assert len(trade.orders) == 5
|
||||
@@ -478,8 +482,9 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||
assert pytest.approx(trade.amount) == 91.689215 * leverage
|
||||
assert pytest.approx(trade.orders[-1].amount) == 91.689215 * leverage
|
||||
assert freqtrade.strategy.adjust_entry_price.call_count == 0
|
||||
assert freqtrade.strategy.adjust_exit_price.call_count == 0
|
||||
|
||||
# Process again, should not adjust entry price
|
||||
# Process again, should not adjust price
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
|
||||
@@ -490,6 +495,21 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
|
||||
assert trade.orders[-1].price == 2.02
|
||||
# Adjust entry price cannot be called - this is an exit order
|
||||
assert freqtrade.strategy.adjust_entry_price.call_count == 0
|
||||
assert freqtrade.strategy.adjust_exit_price.call_count == 1
|
||||
|
||||
freqtrade.strategy.adjust_exit_price = MagicMock(return_value=2.03)
|
||||
|
||||
# Process again, should adjust exit price
|
||||
freqtrade.process()
|
||||
trade = Trade.get_trades().first()
|
||||
|
||||
assert trade.orders[-2].status == "canceled"
|
||||
assert len(trade.orders) == 6
|
||||
assert trade.orders[-1].side == trade.exit_side
|
||||
assert trade.orders[-1].status == "open"
|
||||
assert trade.orders[-1].price == 2.03
|
||||
assert freqtrade.strategy.adjust_entry_price.call_count == 0
|
||||
assert freqtrade.strategy.adjust_exit_price.call_count == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("leverage", [1, 2])
|
||||
|
||||
@@ -45,6 +45,7 @@ class BTContainer(NamedTuple):
|
||||
leverage: float = 1.0
|
||||
timeout: int | None = None
|
||||
adjust_entry_price: float | None = None
|
||||
adjust_exit_price: float | None = None
|
||||
adjust_trade_position: list[float] | None = None
|
||||
|
||||
|
||||
|
||||
@@ -1217,6 +1217,46 @@ tc57 = BTContainer(
|
||||
],
|
||||
)
|
||||
|
||||
# Test 58: Custom-exit-price short - below all candles
|
||||
tc58 = BTContainer(
|
||||
data=[
|
||||
# D O H L C V EL XL ES Xs BT
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 0, 0, 1, 0],
|
||||
[1, 5000, 5200, 4951, 5000, 6172, 0, 0, 0, 0], # enter trade (signal on last candle)
|
||||
[2, 4900, 5250, 4900, 5100, 6172, 0, 0, 0, 1], # Exit - delayed
|
||||
[3, 5100, 5100, 4650, 4750, 6172, 0, 0, 0, 0], #
|
||||
[4, 4750, 5100, 4350, 4750, 6172, 0, 0, 0, 0],
|
||||
],
|
||||
stop_loss=-0.10,
|
||||
roi={"0": 1.00},
|
||||
profit_perc=-0.01,
|
||||
use_exit_signal=True,
|
||||
timeout=1000,
|
||||
custom_exit_price=4300,
|
||||
adjust_exit_price=5050,
|
||||
trades=[BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=4, is_short=True)],
|
||||
)
|
||||
|
||||
# Test 59: Custom-exit-price above all candles - readjust order
|
||||
tc59 = BTContainer(
|
||||
data=[
|
||||
# D O H L C V EL XL ES Xs BT
|
||||
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
|
||||
[1, 5000, 5500, 4951, 5000, 6172, 0, 0],
|
||||
[2, 4900, 5250, 4500, 5100, 6172, 0, 1], # exit
|
||||
[3, 5100, 5100, 4650, 4750, 6172, 0, 0], # order readjust
|
||||
[4, 4750, 4950, 4350, 4750, 6172, 0, 0],
|
||||
],
|
||||
stop_loss=-0.2,
|
||||
roi={"0": 0.10},
|
||||
profit_perc=-0.02,
|
||||
use_exit_signal=True,
|
||||
timeout=1000,
|
||||
custom_exit_price=5300,
|
||||
adjust_exit_price=4900,
|
||||
trades=[BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=4, is_short=False)],
|
||||
)
|
||||
|
||||
|
||||
TESTS = [
|
||||
tc0,
|
||||
@@ -1277,6 +1317,8 @@ TESTS = [
|
||||
tc55,
|
||||
tc56,
|
||||
tc57,
|
||||
tc58,
|
||||
tc59,
|
||||
]
|
||||
|
||||
|
||||
@@ -1330,6 +1372,8 @@ def test_backtest_results(default_conf, mocker, caplog, data: BTContainer) -> No
|
||||
)
|
||||
if data.adjust_entry_price:
|
||||
backtesting.strategy.adjust_entry_price = MagicMock(return_value=data.adjust_entry_price)
|
||||
if data.adjust_exit_price:
|
||||
backtesting.strategy.adjust_exit_price = MagicMock(return_value=data.adjust_exit_price)
|
||||
|
||||
backtesting.strategy.use_custom_stoploss = data.use_custom_stoploss
|
||||
backtesting.strategy.leverage = lambda **kwargs: data.leverage
|
||||
|
||||
@@ -21,10 +21,12 @@ class TestStrategyNoImplementSell(TestStrategyNoImplements):
|
||||
return super().populate_entry_trend(dataframe, metadata)
|
||||
|
||||
|
||||
class TestStrategyImplementCustomSell(TestStrategyNoImplementSell):
|
||||
class TestStrategyImplementEmptyWorking(TestStrategyNoImplementSell):
|
||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
return super().populate_exit_trend(dataframe, metadata)
|
||||
|
||||
|
||||
class TestStrategyImplementCustomSell(TestStrategyImplementEmptyWorking):
|
||||
def custom_sell(
|
||||
self,
|
||||
pair: str,
|
||||
@@ -55,3 +57,34 @@ class TestStrategyImplementSellTimeout(TestStrategyNoImplementSell):
|
||||
self, pair: str, trade, order: Order, current_time: datetime, **kwargs
|
||||
) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class TestStrategyAdjustOrderPrice(TestStrategyImplementEmptyWorking):
|
||||
def adjust_entry_price(
|
||||
self,
|
||||
trade,
|
||||
order,
|
||||
pair,
|
||||
current_time,
|
||||
proposed_rate,
|
||||
current_order_rate,
|
||||
entry_tag,
|
||||
side,
|
||||
**kwargs,
|
||||
):
|
||||
return proposed_rate
|
||||
|
||||
def adjust_order_price(
|
||||
self,
|
||||
trade,
|
||||
order,
|
||||
pair,
|
||||
current_time,
|
||||
proposed_rate,
|
||||
current_order_rate,
|
||||
entry_tag,
|
||||
side,
|
||||
is_entry,
|
||||
**kwargs,
|
||||
):
|
||||
return proposed_rate
|
||||
|
||||
@@ -460,6 +460,10 @@ def test_missing_implements(default_conf, caplog):
|
||||
):
|
||||
StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
default_conf["strategy"] = "TestStrategyAdjustOrderPrice"
|
||||
with pytest.raises(OperationalException, match=r"If you implement `adjust_order_price`.*"):
|
||||
StrategyResolver.load_strategy(default_conf)
|
||||
|
||||
|
||||
def test_call_deprecated_function(default_conf):
|
||||
default_location = Path(__file__).parent / "strats/broken_strats/"
|
||||
|
||||
Reference in New Issue
Block a user