mirror of
https://github.com/freqtrade/freqtrade.git
synced 2025-12-13 19:31:15 +00:00
Compare commits
34 Commits
fix/dynami
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6848f9197e | ||
|
|
6d2c30abca | ||
|
|
93bde7dc46 | ||
|
|
97e2e0a405 | ||
|
|
12206f028b | ||
|
|
4c7944ac77 | ||
|
|
d4ced7b416 | ||
|
|
451eef5c99 | ||
|
|
ae8f059de0 | ||
|
|
878bd7cbc7 | ||
|
|
6d017c9a6c | ||
|
|
4c3d9b8c70 | ||
|
|
23a4260859 | ||
|
|
5919736904 | ||
|
|
732610e200 | ||
|
|
3689b52309 | ||
|
|
01fbf31405 | ||
|
|
1e187e0945 | ||
|
|
e4fc5df1cf | ||
|
|
253950deb6 | ||
|
|
ed92d6beb9 | ||
|
|
ebb362d9fa | ||
|
|
f23fad420e | ||
|
|
aaf23221ff | ||
|
|
156c1a99a9 | ||
|
|
cb55ef5c59 | ||
|
|
6540fbb8e7 | ||
|
|
412392aea9 | ||
|
|
e3229935f6 | ||
|
|
b1ee115b77 | ||
|
|
d6060f04bc | ||
|
|
1ae5310d2f | ||
|
|
417a0817a7 | ||
|
|
8ca25b1757 |
@@ -31,7 +31,7 @@ repos:
|
||||
- types-requests==2.32.4.20250913
|
||||
- types-tabulate==0.9.0.20241207
|
||||
- types-python-dateutil==2.9.0.20251115
|
||||
- scipy-stubs==1.16.3.1
|
||||
- scipy-stubs==1.16.3.2
|
||||
- SQLAlchemy==2.0.44
|
||||
# stages: [push]
|
||||
|
||||
@@ -44,7 +44,7 @@ repos:
|
||||
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
# Ruff version.
|
||||
rev: 'v0.14.7'
|
||||
rev: 'v0.14.8'
|
||||
hooks:
|
||||
- id: ruff
|
||||
- id: ruff-format
|
||||
|
||||
@@ -15,7 +15,7 @@ This software is for educational purposes only. Do not risk money which
|
||||
you are afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS
|
||||
AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS.
|
||||
|
||||
Always start by running a trading bot in Dry-run and do not engage money
|
||||
Always start by running a trading bot in Dry-Run and do not engage money
|
||||
before you understand how it works and what profit/loss you should
|
||||
expect.
|
||||
|
||||
@@ -24,7 +24,7 @@ hesitate to read the source code and understand the mechanism of this bot.
|
||||
|
||||
## Supported Exchange marketplaces
|
||||
|
||||
Please read the [exchange specific notes](docs/exchanges.md) to learn about eventual, special configurations needed for each exchange.
|
||||
Please read the [exchange-specific notes](docs/exchanges.md) to learn about special configurations that maybe needed for each exchange.
|
||||
|
||||
- [X] [Binance](https://www.binance.com/)
|
||||
- [X] [BingX](https://bingx.com/invite/0EM9RX)
|
||||
|
||||
@@ -31,9 +31,14 @@ The Order-type will be ignored if only one mode is available.
|
||||
--8<-- "includes/exchange-features.md"
|
||||
|
||||
!!! Note "Tight stoploss"
|
||||
<ins>Do not set too low/tight stoploss value when using stop loss on exchange!</ins>
|
||||
Do not set too low/tight stoploss value when using stop loss on exchange!
|
||||
If set to low/tight you will have greater risk of missing fill on the order and stoploss will not work.
|
||||
|
||||
!!! Warning "Loose stoploss"
|
||||
Using stoploss on exchange with a very wide stoploss (e.g. -1) may fail to place the stoploss order on exchange due to exchange limitations.
|
||||
In that case, the bot will fallback to using the `emergency_exit` order type to place a market order as placing the stoploss order failed.
|
||||
Freqtrade currently does not implement a limitation to avoid this situation, so please ensure your stoploss values are within reasonable limits for your exchange or disable stoploss on exchange.
|
||||
|
||||
### stoploss_on_exchange and stoploss_on_exchange_limit_ratio
|
||||
|
||||
Enable or Disable stop loss on exchange.
|
||||
|
||||
@@ -644,7 +644,7 @@ Each of these methods are called right before placing an order on the exchange.
|
||||
If your custom pricing function return None or an invalid value, price will fall back to `proposed_rate`, which is based on the regular pricing configuration.
|
||||
|
||||
!!! Note
|
||||
Using custom_entry_price, the Trade object will be available as soon as the first entry order associated with the trade is created, for the first entry, `trade` parameter value will be `None`.
|
||||
When using `custom_entry_price()`, the Trade object will be available as soon as the first entry order associated with the trade is created, for the first entry, `trade` parameter value will be `None`.
|
||||
|
||||
### Custom order entry and exit price example
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from freqtrade.exchange.common import MAP_EXCHANGE_CHILDCLASS
|
||||
from freqtrade.exchange.exchange import Exchange
|
||||
|
||||
# isort: on
|
||||
from freqtrade.exchange.binance import Binance
|
||||
from freqtrade.exchange.binance import Binance, Binanceus, Binanceusdm
|
||||
from freqtrade.exchange.bingx import Bingx
|
||||
from freqtrade.exchange.bitget import Bitget
|
||||
from freqtrade.exchange.bitmart import Bitmart
|
||||
|
||||
@@ -17,7 +17,7 @@ from freqtrade.exchange.binance_public_data import (
|
||||
download_archive_trades,
|
||||
)
|
||||
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.misc import deep_merge_dicts, json_load
|
||||
from freqtrade.util import FtTTLCache
|
||||
@@ -145,6 +145,20 @@ class Binance(Exchange):
|
||||
except ccxt.BaseError as 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})
|
||||
return self.fetch_order(order_id, pair, params)
|
||||
|
||||
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(
|
||||
self,
|
||||
pair: str,
|
||||
@@ -544,3 +558,26 @@ class Binance(Exchange):
|
||||
cache[ft_symbol] = delist_dt
|
||||
|
||||
return cache.get(pair, None)
|
||||
|
||||
|
||||
class Binanceusdm(Binance):
|
||||
"""Binacne USDM Exchange
|
||||
Same as Binance - only futures trading is supported (via ccxt).
|
||||
|
||||
Not actually necessary, binance should be preferred.
|
||||
"""
|
||||
|
||||
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
|
||||
(TradingMode.FUTURES, MarginMode.CROSS),
|
||||
(TradingMode.FUTURES, MarginMode.ISOLATED),
|
||||
]
|
||||
|
||||
|
||||
class Binanceus(Binance):
|
||||
"""Binance US exchange class.
|
||||
Minimal adjustment to disable futures trading for the US subsidiary of Binance
|
||||
"""
|
||||
|
||||
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
|
||||
(TradingMode.SPOT, MarginMode.NONE),
|
||||
]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -45,8 +45,6 @@ BAD_EXCHANGES = {
|
||||
}
|
||||
|
||||
MAP_EXCHANGE_CHILDCLASS = {
|
||||
"binanceus": "binance",
|
||||
"binanceusdm": "binance",
|
||||
"okex": "okx",
|
||||
"gateio": "gate",
|
||||
"huboi": "htx",
|
||||
@@ -54,6 +52,8 @@ MAP_EXCHANGE_CHILDCLASS = {
|
||||
|
||||
SUPPORTED_EXCHANGES = [
|
||||
"binance",
|
||||
"binanceus",
|
||||
"binanceusdm",
|
||||
"bingx",
|
||||
"bitmart",
|
||||
"bitget",
|
||||
|
||||
@@ -1834,9 +1834,9 @@ class Exchange:
|
||||
if self._config["dry_run"] or self.trading_mode != TradingMode.FUTURES:
|
||||
return []
|
||||
try:
|
||||
symbols = []
|
||||
symbols = None
|
||||
if pair:
|
||||
symbols.append(pair)
|
||||
symbols = [pair]
|
||||
positions: list[CcxtPosition] = self._api.fetch_positions(symbols)
|
||||
self._log_exchange_response("fetch_positions", positions)
|
||||
return positions
|
||||
|
||||
@@ -266,14 +266,7 @@ class Okx(Exchange):
|
||||
return order["id"]
|
||||
|
||||
def cancel_stoploss_order(self, order_id: str, pair: str, params: dict | None = None) -> dict:
|
||||
params1 = {"stop": True}
|
||||
# 'ordType': 'conditional'
|
||||
#
|
||||
return self.cancel_order(
|
||||
order_id=order_id,
|
||||
pair=pair,
|
||||
params=params1,
|
||||
)
|
||||
return self.cancel_order(order_id=order_id, pair=pair, params={"stop": True})
|
||||
|
||||
def _fetch_orders_emulate(self, pair: str, since_ms: int) -> list[CcxtOrder]:
|
||||
orders = []
|
||||
|
||||
@@ -2011,14 +2011,14 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
def _safe_exit_amount(self, trade: Trade, pair: str, amount: float) -> float:
|
||||
"""
|
||||
Get sellable amount.
|
||||
Get exitable amount.
|
||||
Should be trade.amount - but will fall back to the available amount if necessary.
|
||||
This should cover cases where get_real_amount() was not able to update the amount
|
||||
for whatever reason.
|
||||
:param trade: Trade we're working with
|
||||
:param pair: Pair we're trying to sell
|
||||
:param pair: Pair we're trying to exit
|
||||
:param amount: amount we expect to be available
|
||||
:return: amount to sell
|
||||
:return: amount to exit
|
||||
:raise: DependencyException: if available balance is not within 2% of the available amount.
|
||||
"""
|
||||
# Update wallets to ensure amounts tied up in a stoploss is now free!
|
||||
@@ -2058,7 +2058,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
"""
|
||||
Executes a trade exit for the given trade and limit
|
||||
:param trade: Trade instance
|
||||
:param limit: limit rate for the sell order
|
||||
:param limit: limit rate for the exit order
|
||||
:param exit_check: CheckTuple with signal and reason
|
||||
:return: True if it succeeds False
|
||||
"""
|
||||
@@ -2101,7 +2101,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
order_type = ordertype or self.strategy.order_types[exit_type]
|
||||
if exit_check.exit_type == ExitType.EMERGENCY_EXIT:
|
||||
# Emergency sells (default to market!)
|
||||
# Emergency exits (default to market!)
|
||||
order_type = self.strategy.order_types.get("emergency_exit", "market")
|
||||
|
||||
amount = self._safe_exit_amount(trade, trade.pair, sub_trade_amt or trade.amount)
|
||||
@@ -2130,7 +2130,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
return False
|
||||
|
||||
try:
|
||||
# Execute sell and update trade record
|
||||
# Execute exit and update trade record
|
||||
order = self.exchange.create_order(
|
||||
pair=trade.pair,
|
||||
ordertype=order_type,
|
||||
@@ -2157,7 +2157,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
trade.exit_reason = exit_reason
|
||||
|
||||
self._notify_exit(trade, order_type, sub_trade=bool(sub_trade_amt), order=order_obj)
|
||||
# In case of market sell orders the order can be closed immediately
|
||||
# In case of market exit orders the order can be closed immediately
|
||||
if order.get("status", "unknown") in ("closed", "expired"):
|
||||
self.update_trade_state(trade, order_obj.order_id, order)
|
||||
Trade.commit()
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
-r requirements-freqai-rl.txt
|
||||
-r docs/requirements-docs.txt
|
||||
|
||||
ruff==0.14.6
|
||||
mypy==1.18.2
|
||||
ruff==0.14.7
|
||||
mypy==1.19.0
|
||||
pre-commit==4.5.0
|
||||
pytest==9.0.1
|
||||
pytest-asyncio==1.3.0
|
||||
@@ -24,7 +24,7 @@ time-machine==3.1.0
|
||||
nbconvert==7.16.6
|
||||
|
||||
# mypy types
|
||||
scipy-stubs==1.16.3.1 # keep in sync with `scipy` in `requirements-hyperopt.txt`
|
||||
scipy-stubs==1.16.3.2 # keep in sync with `scipy` in `requirements-hyperopt.txt`
|
||||
types-cachetools==6.2.0.20251022
|
||||
types-filelock==3.2.7
|
||||
types-requests==2.32.4.20250913
|
||||
|
||||
@@ -7,7 +7,7 @@ ft-pandas-ta==0.3.16
|
||||
ta-lib==0.6.8
|
||||
technical==1.5.3
|
||||
|
||||
ccxt==4.5.24
|
||||
ccxt==4.5.27
|
||||
cryptography==46.0.3
|
||||
aiohttp==3.13.2
|
||||
SQLAlchemy==2.0.44
|
||||
@@ -17,7 +17,7 @@ httpx>=0.24.1
|
||||
humanize==4.14.0
|
||||
cachetools==6.2.2
|
||||
requests==2.32.5
|
||||
urllib3==2.5.0
|
||||
urllib3==2.6.0
|
||||
certifi==2025.11.12
|
||||
jsonschema==4.25.1
|
||||
tabulate==0.9.0
|
||||
@@ -37,7 +37,7 @@ orjson==3.11.4
|
||||
sdnotify==0.3.2
|
||||
|
||||
# API Server
|
||||
fastapi==0.122.0
|
||||
fastapi==0.123.0
|
||||
pydantic==2.12.5
|
||||
uvicorn==0.38.0
|
||||
pyjwt==2.10.1
|
||||
|
||||
@@ -3901,37 +3901,29 @@ def test_cancel_stoploss_order(default_conf, mocker, exchange_name):
|
||||
@pytest.mark.parametrize("exchange_name", EXCHANGES)
|
||||
def test_cancel_stoploss_order_with_result(default_conf, mocker, exchange_name):
|
||||
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)
|
||||
mocker.patch.object(exchange, "fetch_stoploss_order", return_value={"for": 123})
|
||||
|
||||
res = {"fee": {}, "status": "canceled", "amount": 1234}
|
||||
mocker.patch(f"{EXMS}.cancel_stoploss_order", return_value=res)
|
||||
mocker.patch(f"{mock_prefix}.cancel_stoploss_order", return_value=res)
|
||||
mocker.patch.object(exchange, "cancel_stoploss_order", return_value=res)
|
||||
co = exchange.cancel_stoploss_order_with_result(order_id="_", pair="TKN/BTC", amount=555)
|
||||
assert co == res
|
||||
|
||||
mocker.patch(f"{EXMS}.cancel_stoploss_order", return_value="canceled")
|
||||
mocker.patch(f"{mock_prefix}.cancel_stoploss_order", return_value="canceled")
|
||||
mocker.patch.object(exchange, "cancel_stoploss_order", return_value="canceled")
|
||||
# Fall back to fetch_stoploss_order
|
||||
co = exchange.cancel_stoploss_order_with_result(order_id="_", pair="TKN/BTC", amount=555)
|
||||
assert co == {"for": 123}
|
||||
|
||||
exc = InvalidOrderException("")
|
||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", side_effect=exc)
|
||||
mocker.patch(f"{mock_prefix}.fetch_stoploss_order", side_effect=exc)
|
||||
mocker.patch.object(exchange, "fetch_stoploss_order", side_effect=exc)
|
||||
co = exchange.cancel_stoploss_order_with_result(order_id="_", pair="TKN/BTC", amount=555)
|
||||
assert co["amount"] == 555
|
||||
assert co == {"id": "_", "fee": {}, "status": "canceled", "amount": 555, "info": {}}
|
||||
|
||||
with pytest.raises(InvalidOrderException):
|
||||
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)
|
||||
mocker.patch.object(exchange, "cancel_stoploss_order", side_effect=exc)
|
||||
exchange.cancel_stoploss_order_with_result(order_id="_", pair="TKN/BTC", amount=123)
|
||||
|
||||
|
||||
@@ -4116,7 +4108,7 @@ def test_fetch_order_or_stoploss_order(default_conf, mocker):
|
||||
fetch_order_mock = MagicMock()
|
||||
fetch_stoploss_order_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
exchange,
|
||||
fetch_order=fetch_order_mock,
|
||||
fetch_stoploss_order=fetch_stoploss_order_mock,
|
||||
)
|
||||
|
||||
@@ -60,13 +60,10 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||
cancel_order_mock = MagicMock(side_effect=patch_stoploss)
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
create_stoploss=stoploss,
|
||||
fetch_ticker=ticker,
|
||||
get_fee=fee,
|
||||
amount_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(
|
||||
@@ -80,6 +77,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)
|
||||
|
||||
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
|
||||
# Switch ordertype to market to close trade immediately
|
||||
freqtrade.strategy.order_types["exit"] = "market"
|
||||
|
||||
@@ -103,7 +103,7 @@ def test_handle_stoploss_on_exchange(
|
||||
trade.is_open = True
|
||||
|
||||
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
|
||||
hanging_stoploss_order.assert_called_once_with("13434334", trade.pair)
|
||||
@@ -116,7 +116,7 @@ def test_handle_stoploss_on_exchange(
|
||||
trade.is_open = True
|
||||
|
||||
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()
|
||||
amount_before = trade.amount
|
||||
|
||||
@@ -149,7 +149,7 @@ def test_handle_stoploss_on_exchange(
|
||||
"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)
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is True
|
||||
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
|
||||
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
|
||||
freqtrade.handle_stoploss_on_exchange(trade)
|
||||
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
|
||||
stop_order_dict.update({"id": "105"})
|
||||
stoploss.reset_mock()
|
||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", side_effect=InvalidOrderException())
|
||||
mocker.patch(f"{EXMS}.create_stoploss", stoploss)
|
||||
mocker.patch.multiple(
|
||||
freqtrade.exchange,
|
||||
fetch_stoploss_order=MagicMock(
|
||||
side_effect=InvalidOrderException(),
|
||||
),
|
||||
create_stoploss=stoploss,
|
||||
)
|
||||
freqtrade.handle_stoploss_on_exchange(trade)
|
||||
assert len(trade.open_sl_orders) == 1
|
||||
assert stoploss.call_count == 1
|
||||
@@ -179,8 +184,7 @@ def test_handle_stoploss_on_exchange(
|
||||
trade.is_open = False
|
||||
trade.open_sl_orders[-1].ft_is_open = False
|
||||
stoploss.reset_mock()
|
||||
mocker.patch(f"{EXMS}.fetch_order")
|
||||
mocker.patch(f"{EXMS}.create_stoploss", stoploss)
|
||||
mocker.patch.multiple(freqtrade.exchange, fetch_order=MagicMock(), create_stoploss=stoploss)
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
assert trade.has_open_sl_orders is False
|
||||
assert stoploss.call_count == 0
|
||||
@@ -252,9 +256,12 @@ def test_handle_stoploss_on_exchange_emergency(
|
||||
stoploss = MagicMock(side_effect=InvalidOrderException())
|
||||
assert trade.has_open_sl_orders is True
|
||||
Trade.commit()
|
||||
mocker.patch(f"{EXMS}.cancel_stoploss_order_with_result", side_effect=InvalidOrderException())
|
||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", stoploss_order_cancelled)
|
||||
mocker.patch(f"{EXMS}.create_stoploss", stoploss)
|
||||
mocker.patch.multiple(
|
||||
freqtrade.exchange,
|
||||
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 trade.has_open_sl_orders is False
|
||||
assert trade.is_open is False
|
||||
@@ -311,7 +318,7 @@ def test_handle_stoploss_on_exchange_partial(
|
||||
"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
|
||||
# Stoploss filled partially ...
|
||||
assert trade.amount == 15
|
||||
@@ -383,8 +390,11 @@ def test_handle_stoploss_on_exchange_partial_cancel_here(
|
||||
"amount": enter_order["amount"],
|
||||
}
|
||||
)
|
||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", stoploss_order_hit)
|
||||
mocker.patch(f"{EXMS}.cancel_stoploss_order_with_result", stoploss_order_cancel)
|
||||
mocker.patch.multiple(
|
||||
freqtrade.exchange,
|
||||
fetch_stoploss_order=stoploss_order_hit,
|
||||
cancel_stoploss_order_with_result=stoploss_order_cancel,
|
||||
)
|
||||
time_machine.shift(timedelta(minutes=15))
|
||||
|
||||
assert freqtrade.handle_stoploss_on_exchange(trade) is False
|
||||
@@ -408,20 +418,20 @@ def test_handle_sle_cancel_cant_recreate(
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
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(
|
||||
side_effect=[
|
||||
enter_order,
|
||||
exit_order,
|
||||
]
|
||||
),
|
||||
get_fee=fee,
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
fetch_stoploss_order=MagicMock(return_value={"status": "canceled", "id": "100"}),
|
||||
create_stoploss=MagicMock(side_effect=ExchangeError()),
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
|
||||
|
||||
freqtrade.enter_positions()
|
||||
@@ -644,8 +654,11 @@ def test_handle_stoploss_on_exchange_trailing(
|
||||
stoploss_order_cancel = deepcopy(stoploss_order_hanging)
|
||||
stoploss_order_cancel["status"] = "canceled"
|
||||
|
||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", return_value=stoploss_order_hanging)
|
||||
mocker.patch(f"{EXMS}.cancel_stoploss_order", return_value=stoploss_order_cancel)
|
||||
mocker.patch.multiple(
|
||||
freqtrade.exchange,
|
||||
fetch_stoploss_order=MagicMock(return_value=stoploss_order_hanging),
|
||||
cancel_stoploss_order=MagicMock(return_value=stoploss_order_cancel),
|
||||
)
|
||||
|
||||
# stoploss initially at 5%
|
||||
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}
|
||||
)
|
||||
stoploss_order_mock = MagicMock(return_value={"id": "so1", "status": "open"})
|
||||
mocker.patch(f"{EXMS}.fetch_stoploss_order")
|
||||
mocker.patch(f"{EXMS}.cancel_stoploss_order", cancel_order_mock)
|
||||
mocker.patch(f"{EXMS}.create_stoploss", stoploss_order_mock)
|
||||
mocker.patch.multiple(
|
||||
freqtrade.exchange,
|
||||
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
|
||||
assert freqtrade.handle_trade(trade) is False
|
||||
@@ -711,8 +727,9 @@ def test_handle_stoploss_on_exchange_trailing(
|
||||
}
|
||||
),
|
||||
)
|
||||
mocker.patch(
|
||||
f"{EXMS}.cancel_stoploss_order_with_result",
|
||||
mocker.patch.object(
|
||||
freqtrade.exchange,
|
||||
"cancel_stoploss_order_with_result",
|
||||
return_value={"id": "so1", "status": "canceled"},
|
||||
)
|
||||
assert len(trade.open_sl_orders) == 1
|
||||
@@ -786,8 +803,12 @@ def test_handle_stoploss_on_exchange_trailing_error(
|
||||
order_date=dt_now(),
|
||||
)
|
||||
)
|
||||
mocker.patch(f"{EXMS}.cancel_stoploss_order", side_effect=InvalidOrderException())
|
||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", return_value=stoploss_order_hanging)
|
||||
mocker.patch.object(
|
||||
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))
|
||||
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)
|
||||
@@ -799,8 +820,8 @@ def test_handle_stoploss_on_exchange_trailing_error(
|
||||
|
||||
# Fail creating stoploss order
|
||||
caplog.clear()
|
||||
cancel_mock = mocker.patch(f"{EXMS}.cancel_stoploss_order")
|
||||
mocker.patch(f"{EXMS}.create_stoploss", side_effect=ExchangeError())
|
||||
cancel_mock = mocker.patch.object(freqtrade.exchange, "cancel_stoploss_order")
|
||||
mocker.patch.object(freqtrade.exchange, "create_stoploss", side_effect=ExchangeError())
|
||||
time_machine.shift(timedelta(minutes=50))
|
||||
freqtrade.handle_trailing_stoploss_on_exchange(trade, stoploss_order_hanging)
|
||||
assert cancel_mock.call_count == 2
|
||||
@@ -846,20 +867,9 @@ def test_handle_stoploss_on_exchange_custom_stop(
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
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,
|
||||
is_cancel_order_result_suitable=MagicMock(return_value=True),
|
||||
)
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
create_stoploss=stoploss,
|
||||
stoploss_adjust=MagicMock(return_value=True),
|
||||
)
|
||||
|
||||
# enabling TSL
|
||||
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
|
||||
|
||||
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
|
||||
freqtrade.strategy.order_types["stoploss_on_exchange"] = True
|
||||
@@ -912,8 +933,11 @@ def test_handle_stoploss_on_exchange_custom_stop(
|
||||
x["id"] = order_id
|
||||
return x
|
||||
|
||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", MagicMock(fetch_stoploss_order_mock))
|
||||
mocker.patch(f"{EXMS}.cancel_stoploss_order", return_value=slo_canceled)
|
||||
mocker.patch.multiple(
|
||||
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_stoploss_on_exchange(trade) is False
|
||||
@@ -932,8 +956,11 @@ def test_handle_stoploss_on_exchange_custom_stop(
|
||||
|
||||
cancel_order_mock = MagicMock()
|
||||
stoploss_order_mock = MagicMock(return_value={"id": "so1", "status": "open"})
|
||||
mocker.patch(f"{EXMS}.cancel_stoploss_order", cancel_order_mock)
|
||||
mocker.patch(f"{EXMS}.create_stoploss", stoploss_order_mock)
|
||||
mocker.patch.multiple(
|
||||
freqtrade.exchange,
|
||||
cancel_stoploss_order=cancel_order_mock,
|
||||
create_stoploss=stoploss_order_mock,
|
||||
)
|
||||
|
||||
# stoploss should not be updated as the interval is 60 seconds
|
||||
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
|
||||
) -> None:
|
||||
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))
|
||||
create_order_mock = MagicMock(
|
||||
side_effect=[
|
||||
@@ -1114,12 +1143,15 @@ def test_execute_trade_exit_with_stoploss_on_exchange(
|
||||
get_fee=fee,
|
||||
amount_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,
|
||||
cancel_stoploss_order=cancel_order,
|
||||
_dry_is_price_crossed=MagicMock(side_effect=[True, False]),
|
||||
)
|
||||
|
||||
freqtrade = FreqtradeBot(default_conf_usdt)
|
||||
freqtrade.strategy.order_types["stoploss_on_exchange"] = True
|
||||
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,
|
||||
}
|
||||
)
|
||||
mocker.patch(f"{EXMS}.fetch_stoploss_order", stoploss_executed)
|
||||
mocker.patch.object(freqtrade.exchange, "fetch_stoploss_order", stoploss_executed)
|
||||
|
||||
freqtrade.exit_positions(trades)
|
||||
assert trade.has_open_sl_orders is False
|
||||
|
||||
@@ -386,11 +386,14 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short):
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
markets=PropertyMock(return_value=markets),
|
||||
cancel_order=cancel_mock,
|
||||
cancel_stoploss_order=stoploss_mock,
|
||||
)
|
||||
|
||||
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
|
||||
create_mock_trades(fee, is_short)
|
||||
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 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")
|
||||
assert stoploss_mock.call_count == 1
|
||||
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")
|
||||
assert cancel_mock.call_count == 1
|
||||
|
||||
@@ -1034,8 +1034,7 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short):
|
||||
stoploss_mock = MagicMock()
|
||||
cancel_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
EXMS,
|
||||
markets=PropertyMock(return_value=markets),
|
||||
ftbot.exchange,
|
||||
cancel_order=cancel_mock,
|
||||
cancel_stoploss_order=stoploss_mock,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user