From a9c3799547ebb0aaa430f181461a37dfd931928b Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 25 Sep 2025 07:26:14 +0200 Subject: [PATCH 1/7] feat: enable hyperliquid cross futures --- freqtrade/exchange/hyperliquid.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/hyperliquid.py b/freqtrade/exchange/hyperliquid.py index 74f7c5694..db9e44e7d 100644 --- a/freqtrade/exchange/hyperliquid.py +++ b/freqtrade/exchange/hyperliquid.py @@ -44,6 +44,7 @@ class Hyperliquid(Exchange): _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ (TradingMode.SPOT, MarginMode.NONE), (TradingMode.FUTURES, MarginMode.ISOLATED), + (TradingMode.FUTURES, MarginMode.CROSS), ] @property @@ -118,7 +119,12 @@ class Hyperliquid(Exchange): maintenance_margin_required = position_value / max_leverage / 2 # Docs: margin_available (isolated) = isolated_margin - maintenance_margin_required - margin_available = isolated_margin - maintenance_margin_required + if self.margin_mode == MarginMode.ISOLATED: + margin_available = isolated_margin - maintenance_margin_required + elif self.margin_mode == MarginMode.CROSS: + margin_available = wallet_balance - maintenance_margin_required + else: + raise OperationalException("Unsupported margin mode for liquidation price calculation") # Docs: The maintenance margin is half of the initial margin at max leverage # The docs don't explicitly specify maintenance leverage, but this works. From d6bc8f4a2447ed1141fb4f21f17fa15e54724a0d Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Sep 2025 06:47:09 +0200 Subject: [PATCH 2/7] docs: enable hyperliquid cross --- docs/includes/exchange-features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/includes/exchange-features.md b/docs/includes/exchange-features.md index 5d84f763b..7aa6e6436 100644 --- a/docs/includes/exchange-features.md +++ b/docs/includes/exchange-features.md @@ -11,7 +11,7 @@ | [Gate.io](exchanges.md#gateio) | futures | isolated | limit | | [HTX](exchanges.md#htx) | spot | | limit | | [Hyperliquid](exchanges.md#hyperliquid) | spot | | ❌ (not supported) | -| [Hyperliquid](exchanges.md#hyperliquid) | futures | isolated | limit | +| [Hyperliquid](exchanges.md#hyperliquid) | futures | isolated, cross | limit | | [Kraken](exchanges.md#kraken) | spot | | market, limit | | [OKX](exchanges.md#okx) | spot | | limit | | [OKX](exchanges.md#okx) | futures | isolated | limit | From f6c4227ca6331fee7627f06562aef425d15eabc4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Sep 2025 07:05:32 +0200 Subject: [PATCH 3/7] feat: Add documentation to hyperliquid liquidation calc --- freqtrade/exchange/hyperliquid.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/hyperliquid.py b/freqtrade/exchange/hyperliquid.py index db9e44e7d..8c232bac1 100644 --- a/freqtrade/exchange/hyperliquid.py +++ b/freqtrade/exchange/hyperliquid.py @@ -118,10 +118,11 @@ class Hyperliquid(Exchange): # 3. Divide this by 2 maintenance_margin_required = position_value / max_leverage / 2 - # Docs: margin_available (isolated) = isolated_margin - maintenance_margin_required if self.margin_mode == MarginMode.ISOLATED: + # Docs: margin_available (isolated) = isolated_margin - maintenance_margin_required margin_available = isolated_margin - maintenance_margin_required elif self.margin_mode == MarginMode.CROSS: + # Docs: margin_available (cross) = account_value - maintenance_margin_required margin_available = wallet_balance - maintenance_margin_required else: raise OperationalException("Unsupported margin mode for liquidation price calculation") From 88c0c3503f605a78054045176f7d5c9625809665 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Sep 2025 19:50:50 +0200 Subject: [PATCH 4/7] docs: Add cross margin warning note --- docs/leverage.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/leverage.md b/docs/leverage.md index d1517e2d3..ca37a0d35 100644 --- a/docs/leverage.md +++ b/docs/leverage.md @@ -92,6 +92,11 @@ One account is used to share collateral between markets (trading pairs). Margin Please read the [exchange specific notes](exchanges.md) for exchanges that support this mode and how they differ. +!!! Warning "Increased risk of liquidation" + Cross margin mode increases the risk of full account liquidation, as all trades share the same collateral. + A loss on one trade can affect the liquidation price of other trades. + Also, cross-position influence may not be fully simulated in dry-run or backtesting mode. + ## Set leverage to use Different strategies and risk profiles will require different levels of leverage. From 361b34641c839312be7db6202c53209907b52abf Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Sep 2025 20:33:03 +0200 Subject: [PATCH 5/7] chore: simplify hyperliquid dry-run calc --- freqtrade/exchange/hyperliquid.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/exchange/hyperliquid.py b/freqtrade/exchange/hyperliquid.py index 8c232bac1..9dcc0e6a4 100644 --- a/freqtrade/exchange/hyperliquid.py +++ b/freqtrade/exchange/hyperliquid.py @@ -100,7 +100,6 @@ class Hyperliquid(Exchange): 'SOL/USDC:USDC': 43}} """ # Defining/renaming variables to match the documentation - isolated_margin = wallet_balance position_size = amount price = open_rate position_value = price * position_size @@ -120,7 +119,7 @@ class Hyperliquid(Exchange): if self.margin_mode == MarginMode.ISOLATED: # Docs: margin_available (isolated) = isolated_margin - maintenance_margin_required - margin_available = isolated_margin - maintenance_margin_required + margin_available = stake_amount - maintenance_margin_required elif self.margin_mode == MarginMode.CROSS: # Docs: margin_available (cross) = account_value - maintenance_margin_required margin_available = wallet_balance - maintenance_margin_required From 1e8252ffcc7fa3ed5d938b11a11fffcde6a3c100 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Sep 2025 20:33:13 +0200 Subject: [PATCH 6/7] test: simplify hyperliquid test --- tests/exchange/test_hyperliquid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/exchange/test_hyperliquid.py b/tests/exchange/test_hyperliquid.py index c7924080e..ceb503321 100644 --- a/tests/exchange/test_hyperliquid.py +++ b/tests/exchange/test_hyperliquid.py @@ -299,7 +299,7 @@ def test_hyperliquid_dry_run_liquidation_price(default_conf, mocker): position["contracts"], position["collateral"], position["leverage"], - position["collateral"], + 0.0, # wallet balance not used in isolated margin mode [], ) assert pytest.approx(liq_price_returned, rel=0.0001) == liq_price_calculated From c7aaa77e44053299f511d346ca51c26a3aca1aea Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Sep 2025 20:39:40 +0200 Subject: [PATCH 7/7] test: test cross margin calc for hyperliquid --- tests/exchange/test_hyperliquid.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/tests/exchange/test_hyperliquid.py b/tests/exchange/test_hyperliquid.py index ceb503321..c2bff9339 100644 --- a/tests/exchange/test_hyperliquid.py +++ b/tests/exchange/test_hyperliquid.py @@ -6,7 +6,8 @@ import pytest from tests.conftest import EXMS, get_mock_coro, get_patched_exchange -def test_hyperliquid_dry_run_liquidation_price(default_conf, mocker): +@pytest.mark.parametrize("margin_mode", ["isolated", "cross"]) +def test_hyperliquid_dry_run_liquidation_price(default_conf, mocker, margin_mode): # test if liq price calculated by dry_run_liquidation_price() is close to ccxt liq price # testing different pairs with large/small prices, different leverages, long, short markets = { @@ -281,7 +282,7 @@ def test_hyperliquid_dry_run_liquidation_price(default_conf, mocker): api_mock = MagicMock() default_conf["trading_mode"] = "futures" - default_conf["margin_mode"] = "isolated" + default_conf["margin_mode"] = margin_mode default_conf["stake_currency"] = "USDC" api_mock.load_markets = get_mock_coro() api_mock.markets = markets @@ -299,11 +300,32 @@ def test_hyperliquid_dry_run_liquidation_price(default_conf, mocker): position["contracts"], position["collateral"], position["leverage"], - 0.0, # wallet balance not used in isolated margin mode - [], + # isolated doesn't use wallet-balance + wallet_balance=0.0 if margin_mode == "isolated" else position["collateral"], + open_trades=[], ) + # Assume full position size is the wallet balance assert pytest.approx(liq_price_returned, rel=0.0001) == liq_price_calculated + if margin_mode == "cross": + # test with larger wallet balance + liq_price_calculated_cross = exchange.dry_run_liquidation_price( + position["symbol"], + position["entryPrice"], + is_short, + position["contracts"], + position["collateral"], + position["leverage"], + wallet_balance=position["collateral"] * 2, + open_trades=[], + ) + # Assume full position size is the wallet balance + # This + if position["side"] == "long": + assert liq_price_returned > liq_price_calculated_cross < position["entryPrice"] + else: + assert liq_price_returned < liq_price_calculated_cross > position["entryPrice"] + def test_hyperliquid_get_funding_fees(default_conf, mocker): now = datetime.now(UTC)