Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0beb76ce48 | |||
| f002ce67b1 | |||
| 5fdc8acbe9 | |||
| 1811f9581f | |||
| 66235f3198 | |||
| 28517085ca | |||
| c9ed79d2c8 | |||
| 043574f558 | |||
| fc5680d95c | |||
| eabb7c98ce |
@@ -1,6 +1,6 @@
|
|||||||
"""Freqtrade bot"""
|
"""Freqtrade bot"""
|
||||||
|
|
||||||
__version__ = "2025.11"
|
__version__ = "2025.11.2"
|
||||||
|
|
||||||
if "dev" in __version__:
|
if "dev" in __version__:
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from freqtrade.exchange.binance_public_data import (
|
|||||||
download_archive_trades,
|
download_archive_trades,
|
||||||
)
|
)
|
||||||
from freqtrade.exchange.common import retrier
|
from freqtrade.exchange.common import retrier
|
||||||
from freqtrade.exchange.exchange_types import FtHas, Tickers
|
from freqtrade.exchange.exchange_types import CcxtOrder, FtHas, Tickers
|
||||||
from freqtrade.exchange.exchange_utils_timeframe import timeframe_to_msecs
|
from freqtrade.exchange.exchange_utils_timeframe import timeframe_to_msecs
|
||||||
from freqtrade.misc import deep_merge_dicts, json_load
|
from freqtrade.misc import deep_merge_dicts, json_load
|
||||||
from freqtrade.util import FtTTLCache
|
from freqtrade.util import FtTTLCache
|
||||||
@@ -145,6 +145,34 @@ class Binance(Exchange):
|
|||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e) from e
|
raise OperationalException(e) from e
|
||||||
|
|
||||||
|
def fetch_stoploss_order(
|
||||||
|
self, order_id: str, pair: str, params: dict | None = None
|
||||||
|
) -> CcxtOrder:
|
||||||
|
if self.trading_mode == TradingMode.FUTURES:
|
||||||
|
params = params or {}
|
||||||
|
params.update({"stop": True})
|
||||||
|
order = self.fetch_order(order_id, pair, params)
|
||||||
|
if self.trading_mode == TradingMode.FUTURES and order.get("status", "open") == "closed":
|
||||||
|
# Places a real order - which we need to fetch explicitly.
|
||||||
|
|
||||||
|
if new_orderid := order.get("info", {}).get("actualOrderId"):
|
||||||
|
order1 = self.fetch_order(order_id=new_orderid, pair=pair, params={})
|
||||||
|
order1["id_stop"] = order1["id"]
|
||||||
|
order1["id"] = order_id
|
||||||
|
order1["type"] = "stoploss"
|
||||||
|
order1["stopPrice"] = order.get("stopPrice")
|
||||||
|
order1["status_stop"] = "triggered"
|
||||||
|
|
||||||
|
return order1
|
||||||
|
|
||||||
|
return order
|
||||||
|
|
||||||
|
def cancel_stoploss_order(self, order_id: str, pair: str, params: dict | None = None) -> dict:
|
||||||
|
if self.trading_mode == TradingMode.FUTURES:
|
||||||
|
params = params or {}
|
||||||
|
params.update({"stop": True})
|
||||||
|
return self.cancel_order(order_id=order_id, pair=pair, params=params)
|
||||||
|
|
||||||
def get_historic_ohlcv(
|
def get_historic_ohlcv(
|
||||||
self,
|
self,
|
||||||
pair: str,
|
pair: str,
|
||||||
|
|||||||
@@ -1362,8 +1362,9 @@ class Exchange:
|
|||||||
amount: float,
|
amount: float,
|
||||||
rate: float,
|
rate: float,
|
||||||
leverage: float,
|
leverage: float,
|
||||||
reduceOnly: bool = False,
|
|
||||||
time_in_force: str = "GTC",
|
time_in_force: str = "GTC",
|
||||||
|
reduceOnly: bool = False,
|
||||||
|
initial_order: bool = True,
|
||||||
) -> CcxtOrder:
|
) -> CcxtOrder:
|
||||||
if self._config["dry_run"]:
|
if self._config["dry_run"]:
|
||||||
dry_order = self.create_dry_run_order(
|
dry_order = self.create_dry_run_order(
|
||||||
@@ -1380,7 +1381,7 @@ class Exchange:
|
|||||||
rate_for_order = self.price_to_precision(pair, rate) if needs_price else None
|
rate_for_order = self.price_to_precision(pair, rate) if needs_price else None
|
||||||
|
|
||||||
if not reduceOnly:
|
if not reduceOnly:
|
||||||
self._lev_prep(pair, leverage, side)
|
self._lev_prep(pair, leverage, side, accept_fail=not initial_order)
|
||||||
|
|
||||||
order = self._api.create_order(
|
order = self._api.create_order(
|
||||||
pair,
|
pair,
|
||||||
|
|||||||
@@ -44,8 +44,9 @@ class Kucoin(Exchange):
|
|||||||
amount: float,
|
amount: float,
|
||||||
rate: float,
|
rate: float,
|
||||||
leverage: float,
|
leverage: float,
|
||||||
reduceOnly: bool = False,
|
|
||||||
time_in_force: str = "GTC",
|
time_in_force: str = "GTC",
|
||||||
|
reduceOnly: bool = False,
|
||||||
|
initial_order: bool = True,
|
||||||
) -> CcxtOrder:
|
) -> CcxtOrder:
|
||||||
res = super().create_order(
|
res = super().create_order(
|
||||||
pair=pair,
|
pair=pair,
|
||||||
@@ -56,6 +57,7 @@ class Kucoin(Exchange):
|
|||||||
leverage=leverage,
|
leverage=leverage,
|
||||||
reduceOnly=reduceOnly,
|
reduceOnly=reduceOnly,
|
||||||
time_in_force=time_in_force,
|
time_in_force=time_in_force,
|
||||||
|
initial_order=initial_order,
|
||||||
)
|
)
|
||||||
# Kucoin returns only the order-id.
|
# Kucoin returns only the order-id.
|
||||||
# ccxt returns status = 'closed' at the moment - which is information ccxt invented.
|
# ccxt returns status = 'closed' at the moment - which is information ccxt invented.
|
||||||
|
|||||||
@@ -937,6 +937,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
reduceOnly=False,
|
reduceOnly=False,
|
||||||
time_in_force=time_in_force,
|
time_in_force=time_in_force,
|
||||||
leverage=leverage,
|
leverage=leverage,
|
||||||
|
initial_order=trade is None,
|
||||||
)
|
)
|
||||||
order_obj = Order.parse_from_ccxt_object(order, pair, side, amount, enter_limit_requested)
|
order_obj = Order.parse_from_ccxt_object(order, pair, side, amount, enter_limit_requested)
|
||||||
order_obj.ft_order_tag = enter_tag
|
order_obj.ft_order_tag = enter_tag
|
||||||
@@ -2131,6 +2132,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
leverage=trade.leverage,
|
leverage=trade.leverage,
|
||||||
reduceOnly=self.trading_mode == TradingMode.FUTURES,
|
reduceOnly=self.trading_mode == TradingMode.FUTURES,
|
||||||
time_in_force=time_in_force,
|
time_in_force=time_in_force,
|
||||||
|
initial_order=False,
|
||||||
)
|
)
|
||||||
except InsufficientFundsError as e:
|
except InsufficientFundsError as e:
|
||||||
logger.warning(f"Unable to place order {e}.")
|
logger.warning(f"Unable to place order {e}.")
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from freqtrade_client.ft_rest_client import FtRestClient
|
from freqtrade_client.ft_rest_client import FtRestClient
|
||||||
|
|
||||||
|
|
||||||
__version__ = "2025.11"
|
__version__ = "2025.11.2"
|
||||||
|
|
||||||
if "dev" in __version__:
|
if "dev" in __version__:
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ ft-pandas-ta==0.3.16
|
|||||||
ta-lib==0.6.8
|
ta-lib==0.6.8
|
||||||
technical==1.5.3
|
technical==1.5.3
|
||||||
|
|
||||||
ccxt==4.5.20
|
ccxt==4.5.27
|
||||||
cryptography==46.0.3
|
cryptography==46.0.3
|
||||||
aiohttp==3.13.2
|
aiohttp==3.13.2
|
||||||
SQLAlchemy==2.0.44
|
SQLAlchemy==2.0.44
|
||||||
|
|||||||
@@ -3826,37 +3826,29 @@ def test_cancel_stoploss_order(default_conf, mocker, exchange_name):
|
|||||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||||
def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name):
|
def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name):
|
||||||
default_conf["dry_run"] = False
|
default_conf["dry_run"] = False
|
||||||
mock_prefix = "freqtrade.exchange.gate.Gate"
|
|
||||||
if exchange_name == "okx":
|
|
||||||
mock_prefix = "freqtrade.exchange.okx.Okx"
|
|
||||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", return_value={"for": 123})
|
|
||||||
mocker.patch(f"{mock_prefix}.fetch_stoploss_order", return_value={"for": 123})
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
|
||||||
|
mocker.patch.object(exchange, "fetch_stoploss_order", return_value={"for": 123})
|
||||||
|
|
||||||
res = {"fee": {}, "status": "canceled", "amount": 1234}
|
res = {"fee": {}, "status": "canceled", "amount": 1234}
|
||||||
mocker.patch(f"{EXMS}.cancel_stoploss_order", return_value=res)
|
mocker.patch.object(exchange, "cancel_stoploss_order", return_value=res)
|
||||||
mocker.patch(f"{mock_prefix}.cancel_stoploss_order", return_value=res)
|
|
||||||
co = exchange.cancel_stoploss_order_with_result(order_id="_", pair="TKN/BTC", amount=555)
|
co = exchange.cancel_stoploss_order_with_result(order_id="_", pair="TKN/BTC", amount=555)
|
||||||
assert co == res
|
assert co == res
|
||||||
|
|
||||||
mocker.patch(f"{EXMS}.cancel_stoploss_order", return_value="canceled")
|
mocker.patch.object(exchange, "cancel_stoploss_order", return_value="canceled")
|
||||||
mocker.patch(f"{mock_prefix}.cancel_stoploss_order", return_value="canceled")
|
|
||||||
# Fall back to fetch_stoploss_order
|
# Fall back to fetch_stoploss_order
|
||||||
co = exchange.cancel_stoploss_order_with_result(order_id="_", pair="TKN/BTC", amount=555)
|
co = exchange.cancel_stoploss_order_with_result(order_id="_", pair="TKN/BTC", amount=555)
|
||||||
assert co == {"for": 123}
|
assert co == {"for": 123}
|
||||||
|
|
||||||
exc = InvalidOrderException("")
|
exc = InvalidOrderException("")
|
||||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", side_effect=exc)
|
mocker.patch.object(exchange, "fetch_stoploss_order", side_effect=exc)
|
||||||
mocker.patch(f"{mock_prefix}.fetch_stoploss_order", side_effect=exc)
|
|
||||||
co = exchange.cancel_stoploss_order_with_result(order_id="_", pair="TKN/BTC", amount=555)
|
co = exchange.cancel_stoploss_order_with_result(order_id="_", pair="TKN/BTC", amount=555)
|
||||||
assert co["amount"] == 555
|
assert co["amount"] == 555
|
||||||
assert co == {"id": "_", "fee": {}, "status": "canceled", "amount": 555, "info": {}}
|
assert co == {"id": "_", "fee": {}, "status": "canceled", "amount": 555, "info": {}}
|
||||||
|
|
||||||
with pytest.raises(InvalidOrderException):
|
with pytest.raises(InvalidOrderException):
|
||||||
exc = InvalidOrderException("Did not find order")
|
exc = InvalidOrderException("Did not find order")
|
||||||
mocker.patch(f"{EXMS}.cancel_stoploss_order", side_effect=exc)
|
|
||||||
mocker.patch(f"{mock_prefix}.cancel_stoploss_order", side_effect=exc)
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
|
exchange = get_patched_exchange(mocker, default_conf, exchange=exchange_name)
|
||||||
|
mocker.patch.object(exchange, "cancel_stoploss_order", side_effect=exc)
|
||||||
exchange.cancel_stoploss_order_with_result(order_id="_", pair="TKN/BTC", amount=123)
|
exchange.cancel_stoploss_order_with_result(order_id="_", pair="TKN/BTC", amount=123)
|
||||||
|
|
||||||
|
|
||||||
@@ -4041,7 +4033,7 @@ def test_fetch_order_or_stoploss_order(default_conf, mocker):
|
|||||||
fetch_order_mock = MagicMock()
|
fetch_order_mock = MagicMock()
|
||||||
fetch_stoploss_order_mock = MagicMock()
|
fetch_stoploss_order_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
EXMS,
|
exchange,
|
||||||
fetch_order=fetch_order_mock,
|
fetch_order=fetch_order_mock,
|
||||||
fetch_stoploss_order=fetch_stoploss_order_mock,
|
fetch_stoploss_order=fetch_stoploss_order_mock,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -515,7 +515,8 @@ EXCHANGES = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
"hyperliquid": {
|
"hyperliquid": {
|
||||||
"pair": "UBTC/USDC",
|
# TODO: Should be UBTC/USDC - probably needs a fix in ccxt
|
||||||
|
"pair": "BTC/USDC",
|
||||||
"stake_currency": "USDC",
|
"stake_currency": "USDC",
|
||||||
"hasQuoteVolume": False,
|
"hasQuoteVolume": False,
|
||||||
"timeframe": "30m",
|
"timeframe": "30m",
|
||||||
|
|||||||
@@ -53,13 +53,10 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
|||||||
cancel_order_mock = MagicMock()
|
cancel_order_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
EXMS,
|
EXMS,
|
||||||
create_stoploss=stoploss,
|
|
||||||
fetch_ticker=ticker,
|
fetch_ticker=ticker,
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
amount_to_precision=lambda s, x, y: y,
|
amount_to_precision=lambda s, x, y: y,
|
||||||
price_to_precision=lambda s, x, y: y,
|
price_to_precision=lambda s, x, y: y,
|
||||||
fetch_stoploss_order=stoploss_order_mock,
|
|
||||||
cancel_stoploss_order_with_result=cancel_order_mock,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@@ -73,6 +70,12 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
|||||||
mocker.patch("freqtrade.wallets.Wallets.check_exit_amount", return_value=True)
|
mocker.patch("freqtrade.wallets.Wallets.check_exit_amount", return_value=True)
|
||||||
|
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
|
mocker.patch.multiple(
|
||||||
|
freqtrade.exchange,
|
||||||
|
create_stoploss=stoploss,
|
||||||
|
fetch_stoploss_order=stoploss_order_mock,
|
||||||
|
cancel_stoploss_order_with_result=cancel_order_mock,
|
||||||
|
)
|
||||||
freqtrade.strategy.order_types["stoploss_on_exchange"] = True
|
freqtrade.strategy.order_types["stoploss_on_exchange"] = True
|
||||||
# Switch ordertype to market to close trade immediately
|
# Switch ordertype to market to close trade immediately
|
||||||
freqtrade.strategy.order_types["exit"] = "market"
|
freqtrade.strategy.order_types["exit"] = "market"
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ def test_handle_stoploss_on_exchange(
|
|||||||
trade.is_open = True
|
trade.is_open = True
|
||||||
|
|
||||||
hanging_stoploss_order = MagicMock(return_value={"id": "13434334", "status": "open"})
|
hanging_stoploss_order = MagicMock(return_value={"id": "13434334", "status": "open"})
|
||||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", hanging_stoploss_order)
|
mocker.patch.object(freqtrade.exchange, "fetch_stoploss_order", hanging_stoploss_order)
|
||||||
|
|
||||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||||
hanging_stoploss_order.assert_called_once_with("13434334", trade.pair)
|
hanging_stoploss_order.assert_called_once_with("13434334", trade.pair)
|
||||||
@@ -116,7 +116,7 @@ def test_handle_stoploss_on_exchange(
|
|||||||
trade.is_open = True
|
trade.is_open = True
|
||||||
|
|
||||||
canceled_stoploss_order = MagicMock(return_value={"id": "13434334", "status": "canceled"})
|
canceled_stoploss_order = MagicMock(return_value={"id": "13434334", "status": "canceled"})
|
||||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", canceled_stoploss_order)
|
mocker.patch.object(freqtrade.exchange, "fetch_stoploss_order", canceled_stoploss_order)
|
||||||
stoploss.reset_mock()
|
stoploss.reset_mock()
|
||||||
amount_before = trade.amount
|
amount_before = trade.amount
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ def test_handle_stoploss_on_exchange(
|
|||||||
"amount": enter_order["amount"],
|
"amount": enter_order["amount"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", stoploss_order_hit)
|
mocker.patch.object(freqtrade.exchange, "fetch_stoploss_order", stoploss_order_hit)
|
||||||
freqtrade.strategy.order_filled = MagicMock(return_value=None)
|
freqtrade.strategy.order_filled = MagicMock(return_value=None)
|
||||||
assert freqtrade.handle_stoploss_on_exchange(trade) is True
|
assert freqtrade.handle_stoploss_on_exchange(trade) is True
|
||||||
assert log_has_re(r"STOP_LOSS_LIMIT is hit for Trade\(id=1, .*\)\.", caplog)
|
assert log_has_re(r"STOP_LOSS_LIMIT is hit for Trade\(id=1, .*\)\.", caplog)
|
||||||
@@ -158,7 +158,7 @@ def test_handle_stoploss_on_exchange(
|
|||||||
assert freqtrade.strategy.order_filled.call_count == 1
|
assert freqtrade.strategy.order_filled.call_count == 1
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
|
|
||||||
mocker.patch(f"{EXMS}.create_stoploss", side_effect=ExchangeError())
|
mocker.patch.object(freqtrade.exchange, "create_stoploss", side_effect=ExchangeError())
|
||||||
trade.is_open = True
|
trade.is_open = True
|
||||||
freqtrade.handle_stoploss_on_exchange(trade)
|
freqtrade.handle_stoploss_on_exchange(trade)
|
||||||
assert log_has("Unable to place a stoploss order on exchange.", caplog)
|
assert log_has("Unable to place a stoploss order on exchange.", caplog)
|
||||||
@@ -168,8 +168,13 @@ def test_handle_stoploss_on_exchange(
|
|||||||
# It should try to add stoploss order
|
# It should try to add stoploss order
|
||||||
stop_order_dict.update({"id": "105"})
|
stop_order_dict.update({"id": "105"})
|
||||||
stoploss.reset_mock()
|
stoploss.reset_mock()
|
||||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", side_effect=InvalidOrderException())
|
mocker.patch.multiple(
|
||||||
mocker.patch(f"{EXMS}.create_stoploss", stoploss)
|
freqtrade.exchange,
|
||||||
|
fetch_stoploss_order=MagicMock(
|
||||||
|
side_effect=InvalidOrderException(),
|
||||||
|
),
|
||||||
|
create_stoploss=stoploss,
|
||||||
|
)
|
||||||
freqtrade.handle_stoploss_on_exchange(trade)
|
freqtrade.handle_stoploss_on_exchange(trade)
|
||||||
assert len(trade.open_sl_orders) == 1
|
assert len(trade.open_sl_orders) == 1
|
||||||
assert stoploss.call_count == 1
|
assert stoploss.call_count == 1
|
||||||
@@ -179,8 +184,7 @@ def test_handle_stoploss_on_exchange(
|
|||||||
trade.is_open = False
|
trade.is_open = False
|
||||||
trade.open_sl_orders[-1].ft_is_open = False
|
trade.open_sl_orders[-1].ft_is_open = False
|
||||||
stoploss.reset_mock()
|
stoploss.reset_mock()
|
||||||
mocker.patch(f"{EXMS}.fetch_order")
|
mocker.patch.multiple(freqtrade.exchange, fetch_order=MagicMock(), create_stoploss=stoploss)
|
||||||
mocker.patch(f"{EXMS}.create_stoploss", stoploss)
|
|
||||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||||
assert trade.has_open_sl_orders is False
|
assert trade.has_open_sl_orders is False
|
||||||
assert stoploss.call_count == 0
|
assert stoploss.call_count == 0
|
||||||
@@ -252,9 +256,12 @@ def test_handle_stoploss_on_exchange_emergency(
|
|||||||
stoploss = MagicMock(side_effect=InvalidOrderException())
|
stoploss = MagicMock(side_effect=InvalidOrderException())
|
||||||
assert trade.has_open_sl_orders is True
|
assert trade.has_open_sl_orders is True
|
||||||
Trade.commit()
|
Trade.commit()
|
||||||
mocker.patch(f"{EXMS}.cancel_stoploss_order_with_result", side_effect=InvalidOrderException())
|
mocker.patch.multiple(
|
||||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", stoploss_order_cancelled)
|
freqtrade.exchange,
|
||||||
mocker.patch(f"{EXMS}.create_stoploss", stoploss)
|
cancel_stoploss_order_with_result=MagicMock(side_effect=InvalidOrderException()),
|
||||||
|
fetch_stoploss_order=stoploss_order_cancelled,
|
||||||
|
create_stoploss=stoploss,
|
||||||
|
)
|
||||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||||
assert trade.has_open_sl_orders is False
|
assert trade.has_open_sl_orders is False
|
||||||
assert trade.is_open is False
|
assert trade.is_open is False
|
||||||
@@ -311,7 +318,7 @@ def test_handle_stoploss_on_exchange_partial(
|
|||||||
"amount": enter_order["amount"],
|
"amount": enter_order["amount"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", stoploss_order_hit)
|
mocker.patch.multiple(freqtrade.exchange, fetch_stoploss_order=stoploss_order_hit)
|
||||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||||
# Stoploss filled partially ...
|
# Stoploss filled partially ...
|
||||||
assert trade.amount == 15
|
assert trade.amount == 15
|
||||||
@@ -383,8 +390,11 @@ def test_handle_stoploss_on_exchange_partial_cancel_here(
|
|||||||
"amount": enter_order["amount"],
|
"amount": enter_order["amount"],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", stoploss_order_hit)
|
mocker.patch.multiple(
|
||||||
mocker.patch(f"{EXMS}.cancel_stoploss_order_with_result", stoploss_order_cancel)
|
freqtrade.exchange,
|
||||||
|
fetch_stoploss_order=stoploss_order_hit,
|
||||||
|
cancel_stoploss_order_with_result=stoploss_order_cancel,
|
||||||
|
)
|
||||||
time_machine.shift(timedelta(minutes=15))
|
time_machine.shift(timedelta(minutes=15))
|
||||||
|
|
||||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||||
@@ -408,20 +418,20 @@ def test_handle_sle_cancel_cant_recreate(
|
|||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
EXMS,
|
EXMS,
|
||||||
fetch_ticker=MagicMock(return_value={"bid": 1.9, "ask": 2.2, "last": 1.9}),
|
fetch_ticker=MagicMock(return_value={"bid": 1.9, "ask": 2.2, "last": 1.9}),
|
||||||
|
get_fee=fee,
|
||||||
|
)
|
||||||
|
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||||
|
mocker.patch.multiple(
|
||||||
|
freqtrade.exchange,
|
||||||
create_order=MagicMock(
|
create_order=MagicMock(
|
||||||
side_effect=[
|
side_effect=[
|
||||||
enter_order,
|
enter_order,
|
||||||
exit_order,
|
exit_order,
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
get_fee=fee,
|
|
||||||
)
|
|
||||||
mocker.patch.multiple(
|
|
||||||
EXMS,
|
|
||||||
fetch_stoploss_order=MagicMock(return_value={"status": "canceled", "id": "100"}),
|
fetch_stoploss_order=MagicMock(return_value={"status": "canceled", "id": "100"}),
|
||||||
create_stoploss=MagicMock(side_effect=ExchangeError()),
|
create_stoploss=MagicMock(side_effect=ExchangeError()),
|
||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
|
||||||
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
||||||
|
|
||||||
freqtrade.enter_positions()
|
freqtrade.enter_positions()
|
||||||
@@ -644,8 +654,11 @@ def test_handle_stoploss_on_exchange_trailing(
|
|||||||
stoploss_order_cancel = deepcopy(stoploss_order_hanging)
|
stoploss_order_cancel = deepcopy(stoploss_order_hanging)
|
||||||
stoploss_order_cancel["status"] = "canceled"
|
stoploss_order_cancel["status"] = "canceled"
|
||||||
|
|
||||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", return_value=stoploss_order_hanging)
|
mocker.patch.multiple(
|
||||||
mocker.patch(f"{EXMS}.cancel_stoploss_order", return_value=stoploss_order_cancel)
|
freqtrade.exchange,
|
||||||
|
fetch_stoploss_order=MagicMock(return_value=stoploss_order_hanging),
|
||||||
|
cancel_stoploss_order=MagicMock(return_value=stoploss_order_cancel),
|
||||||
|
)
|
||||||
|
|
||||||
# stoploss initially at 5%
|
# stoploss initially at 5%
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
@@ -671,9 +684,12 @@ def test_handle_stoploss_on_exchange_trailing(
|
|||||||
return_value={"id": "13434334", "status": "canceled", "fee": {}, "amount": trade.amount}
|
return_value={"id": "13434334", "status": "canceled", "fee": {}, "amount": trade.amount}
|
||||||
)
|
)
|
||||||
stoploss_order_mock = MagicMock(return_value={"id": "so1", "status": "open"})
|
stoploss_order_mock = MagicMock(return_value={"id": "so1", "status": "open"})
|
||||||
mocker.patch(f"{EXMS}.fetch_stoploss_order")
|
mocker.patch.multiple(
|
||||||
mocker.patch(f"{EXMS}.cancel_stoploss_order", cancel_order_mock)
|
freqtrade.exchange,
|
||||||
mocker.patch(f"{EXMS}.create_stoploss", stoploss_order_mock)
|
fetch_stoploss_order=MagicMock(),
|
||||||
|
cancel_stoploss_order=cancel_order_mock,
|
||||||
|
create_stoploss=stoploss_order_mock,
|
||||||
|
)
|
||||||
|
|
||||||
# stoploss should not be updated as the interval is 60 seconds
|
# stoploss should not be updated as the interval is 60 seconds
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
@@ -711,8 +727,9 @@ def test_handle_stoploss_on_exchange_trailing(
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
mocker.patch(
|
mocker.patch.object(
|
||||||
f"{EXMS}.cancel_stoploss_order_with_result",
|
freqtrade.exchange,
|
||||||
|
"cancel_stoploss_order_with_result",
|
||||||
return_value={"id": "so1", "status": "canceled"},
|
return_value={"id": "so1", "status": "canceled"},
|
||||||
)
|
)
|
||||||
assert len(trade.open_sl_orders) == 1
|
assert len(trade.open_sl_orders) == 1
|
||||||
@@ -786,8 +803,12 @@ def test_handle_stoploss_on_exchange_trailing_error(
|
|||||||
order_date=dt_now(),
|
order_date=dt_now(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
mocker.patch(f"{EXMS}.cancel_stoploss_order", side_effect=InvalidOrderException())
|
mocker.patch.object(
|
||||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", return_value=stoploss_order_hanging)
|
freqtrade.exchange, "cancel_stoploss_order", side_effect=InvalidOrderException()
|
||||||
|
)
|
||||||
|
mocker.patch.object(
|
||||||
|
freqtrade.exchange, "fetch_stoploss_order", return_value=stoploss_order_hanging
|
||||||
|
)
|
||||||
time_machine.shift(timedelta(minutes=50))
|
time_machine.shift(timedelta(minutes=50))
|
||||||
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
|
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
|
||||||
assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/USDT.*", caplog)
|
assert log_has_re(r"Could not cancel stoploss order abcd for pair ETH/USDT.*", caplog)
|
||||||
@@ -799,8 +820,8 @@ def test_handle_stoploss_on_exchange_trailing_error(
|
|||||||
|
|
||||||
# Fail creating stoploss order
|
# Fail creating stoploss order
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
cancel_mock = mocker.patch(f"{EXMS}.cancel_stoploss_order")
|
cancel_mock = mocker.patch.object(freqtrade.exchange, "cancel_stoploss_order")
|
||||||
mocker.patch(f"{EXMS}.create_stoploss", side_effect=ExchangeError())
|
mocker.patch.object(freqtrade.exchange, "create_stoploss", side_effect=ExchangeError())
|
||||||
time_machine.shift(timedelta(minutes=50))
|
time_machine.shift(timedelta(minutes=50))
|
||||||
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
|
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
|
||||||
assert cancel_mock.call_count == 2
|
assert cancel_mock.call_count == 2
|
||||||
@@ -846,20 +867,9 @@ def test_handle_stoploss_on_exchange_custom_stop(
|
|||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
EXMS,
|
EXMS,
|
||||||
fetch_ticker=MagicMock(return_value={"bid": 1.9, "ask": 2.2, "last": 1.9}),
|
fetch_ticker=MagicMock(return_value={"bid": 1.9, "ask": 2.2, "last": 1.9}),
|
||||||
create_order=MagicMock(
|
|
||||||
side_effect=[
|
|
||||||
enter_order,
|
|
||||||
exit_order,
|
|
||||||
]
|
|
||||||
),
|
|
||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
is_cancel_order_result_suitable=MagicMock(return_value=True),
|
is_cancel_order_result_suitable=MagicMock(return_value=True),
|
||||||
)
|
)
|
||||||
mocker.patch.multiple(
|
|
||||||
EXMS,
|
|
||||||
create_stoploss=stoploss,
|
|
||||||
stoploss_adjust=MagicMock(return_value=True),
|
|
||||||
)
|
|
||||||
|
|
||||||
# enabling TSL
|
# enabling TSL
|
||||||
default_conf_usdt["use_custom_stoploss"] = True
|
default_conf_usdt["use_custom_stoploss"] = True
|
||||||
@@ -868,6 +878,17 @@ def test_handle_stoploss_on_exchange_custom_stop(
|
|||||||
default_conf_usdt["minimal_roi"]["0"] = 999999999
|
default_conf_usdt["minimal_roi"]["0"] = 999999999
|
||||||
|
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||||
|
mocker.patch.multiple(
|
||||||
|
freqtrade.exchange,
|
||||||
|
create_order=MagicMock(
|
||||||
|
side_effect=[
|
||||||
|
enter_order,
|
||||||
|
exit_order,
|
||||||
|
]
|
||||||
|
),
|
||||||
|
create_stoploss=stoploss,
|
||||||
|
stoploss_adjust=MagicMock(return_value=True),
|
||||||
|
)
|
||||||
|
|
||||||
# enabling stoploss on exchange
|
# enabling stoploss on exchange
|
||||||
freqtrade.strategy.order_types["stoploss_on_exchange"] = True
|
freqtrade.strategy.order_types["stoploss_on_exchange"] = True
|
||||||
@@ -912,8 +933,11 @@ def test_handle_stoploss_on_exchange_custom_stop(
|
|||||||
x["id"] = order_id
|
x["id"] = order_id
|
||||||
return x
|
return x
|
||||||
|
|
||||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", MagicMock(fetch_stoploss_order_mock))
|
mocker.patch.multiple(
|
||||||
mocker.patch(f"{EXMS}.cancel_stoploss_order", return_value=slo_canceled)
|
freqtrade.exchange,
|
||||||
|
fetch_stoploss_order=MagicMock(fetch_stoploss_order_mock),
|
||||||
|
cancel_stoploss_order=MagicMock(return_value=slo_canceled),
|
||||||
|
)
|
||||||
|
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||||
@@ -932,8 +956,11 @@ def test_handle_stoploss_on_exchange_custom_stop(
|
|||||||
|
|
||||||
cancel_order_mock = MagicMock()
|
cancel_order_mock = MagicMock()
|
||||||
stoploss_order_mock = MagicMock(return_value={"id": "so1", "status": "open"})
|
stoploss_order_mock = MagicMock(return_value={"id": "so1", "status": "open"})
|
||||||
mocker.patch(f"{EXMS}.cancel_stoploss_order", cancel_order_mock)
|
mocker.patch.multiple(
|
||||||
mocker.patch(f"{EXMS}.create_stoploss", stoploss_order_mock)
|
freqtrade.exchange,
|
||||||
|
cancel_stoploss_order=cancel_order_mock,
|
||||||
|
create_stoploss=stoploss_order_mock,
|
||||||
|
)
|
||||||
|
|
||||||
# stoploss should not be updated as the interval is 60 seconds
|
# stoploss should not be updated as the interval is 60 seconds
|
||||||
assert freqtrade.handle_trade(trade) is False
|
assert freqtrade.handle_trade(trade) is False
|
||||||
@@ -1054,7 +1081,9 @@ def test_execute_trade_exit_sloe_cancel_exception(
|
|||||||
mocker, default_conf_usdt, ticker_usdt, fee, caplog
|
mocker, default_conf_usdt, ticker_usdt, fee, caplog
|
||||||
) -> None:
|
) -> None:
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
|
||||||
mocker.patch(f"{EXMS}.cancel_stoploss_order", side_effect=InvalidOrderException())
|
mocker.patch.object(
|
||||||
|
freqtrade.exchange, "cancel_stoploss_order", side_effect=InvalidOrderException()
|
||||||
|
)
|
||||||
mocker.patch("freqtrade.wallets.Wallets.get_free", MagicMock(return_value=300))
|
mocker.patch("freqtrade.wallets.Wallets.get_free", MagicMock(return_value=300))
|
||||||
create_order_mock = MagicMock(
|
create_order_mock = MagicMock(
|
||||||
side_effect=[
|
side_effect=[
|
||||||
@@ -1114,12 +1143,15 @@ def test_execute_trade_exit_with_stoploss_on_exchange(
|
|||||||
get_fee=fee,
|
get_fee=fee,
|
||||||
amount_to_precision=lambda s, x, y: y,
|
amount_to_precision=lambda s, x, y: y,
|
||||||
price_to_precision=lambda s, x, y: y,
|
price_to_precision=lambda s, x, y: y,
|
||||||
|
)
|
||||||
|
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||||
|
mocker.patch.multiple(
|
||||||
|
freqtrade.exchange,
|
||||||
create_stoploss=stoploss,
|
create_stoploss=stoploss,
|
||||||
cancel_stoploss_order=cancel_order,
|
cancel_stoploss_order=cancel_order,
|
||||||
_dry_is_price_crossed=MagicMock(side_effect=[True, False]),
|
_dry_is_price_crossed=MagicMock(side_effect=[True, False]),
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
|
||||||
freqtrade.strategy.order_types["stoploss_on_exchange"] = True
|
freqtrade.strategy.order_types["stoploss_on_exchange"] = True
|
||||||
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
||||||
|
|
||||||
@@ -1208,7 +1240,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(
|
|||||||
"trades": None,
|
"trades": None,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", stoploss_executed)
|
mocker.patch.object(freqtrade.exchange, "fetch_stoploss_order", stoploss_executed)
|
||||||
|
|
||||||
freqtrade.exit_positions(trades)
|
freqtrade.exit_positions(trades)
|
||||||
assert trade.has_open_sl_orders is False
|
assert trade.has_open_sl_orders is False
|
||||||
|
|||||||
+11
-4
@@ -386,11 +386,14 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short):
|
|||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
EXMS,
|
EXMS,
|
||||||
markets=PropertyMock(return_value=markets),
|
markets=PropertyMock(return_value=markets),
|
||||||
cancel_order=cancel_mock,
|
|
||||||
cancel_stoploss_order=stoploss_mock,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
|
mocker.patch.multiple(
|
||||||
|
freqtradebot.exchange,
|
||||||
|
cancel_order=cancel_mock,
|
||||||
|
cancel_stoploss_order=stoploss_mock,
|
||||||
|
)
|
||||||
freqtradebot.strategy.order_types["stoploss_on_exchange"] = True
|
freqtradebot.strategy.order_types["stoploss_on_exchange"] = True
|
||||||
create_mock_trades(fee, is_short)
|
create_mock_trades(fee, is_short)
|
||||||
rpc = RPC(freqtradebot)
|
rpc = RPC(freqtradebot)
|
||||||
@@ -426,13 +429,17 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short):
|
|||||||
assert stoploss_mock.call_count == 1
|
assert stoploss_mock.call_count == 1
|
||||||
assert res["cancel_order_count"] == 1
|
assert res["cancel_order_count"] == 1
|
||||||
|
|
||||||
stoploss_mock = mocker.patch(f"{EXMS}.cancel_stoploss_order", side_effect=InvalidOrderException)
|
stoploss_mock = mocker.patch.object(
|
||||||
|
freqtradebot.exchange, "cancel_stoploss_order", side_effect=InvalidOrderException
|
||||||
|
)
|
||||||
|
|
||||||
res = rpc._rpc_delete("3")
|
res = rpc._rpc_delete("3")
|
||||||
assert stoploss_mock.call_count == 1
|
assert stoploss_mock.call_count == 1
|
||||||
stoploss_mock.reset_mock()
|
stoploss_mock.reset_mock()
|
||||||
|
|
||||||
cancel_mock = mocker.patch(f"{EXMS}.cancel_order", side_effect=InvalidOrderException)
|
cancel_mock = mocker.patch.object(
|
||||||
|
freqtradebot.exchange, "cancel_order", side_effect=InvalidOrderException
|
||||||
|
)
|
||||||
|
|
||||||
res = rpc._rpc_delete("4")
|
res = rpc._rpc_delete("4")
|
||||||
assert cancel_mock.call_count == 1
|
assert cancel_mock.call_count == 1
|
||||||
|
|||||||
@@ -1034,8 +1034,7 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short):
|
|||||||
stoploss_mock = MagicMock()
|
stoploss_mock = MagicMock()
|
||||||
cancel_mock = MagicMock()
|
cancel_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
EXMS,
|
ftbot.exchange,
|
||||||
markets=PropertyMock(return_value=markets),
|
|
||||||
cancel_order=cancel_mock,
|
cancel_order=cancel_mock,
|
||||||
cancel_stoploss_order=stoploss_mock,
|
cancel_stoploss_order=stoploss_mock,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user