HIP3 dexes now fetched by market info of specific pairs

This commit is contained in:
Ingo
2025-12-17 21:58:10 +01:00
parent 245ca4bc2a
commit 301aff141a
+36 -104
View File
@@ -3,7 +3,6 @@
import logging import logging
from copy import deepcopy from copy import deepcopy
from datetime import datetime from datetime import datetime
from typing import Any
from freqtrade.constants import BuySell from freqtrade.constants import BuySell
from freqtrade.enums import MarginMode, TradingMode from freqtrade.enums import MarginMode, TradingMode
@@ -37,9 +36,9 @@ class Hyperliquid(Exchange):
"stoploss_order_types": {"limit": "limit"}, "stoploss_order_types": {"limit": "limit"},
"stoploss_blocks_assets": False, "stoploss_blocks_assets": False,
"stop_price_prop": "stopPrice", "stop_price_prop": "stopPrice",
"funding_fee_timeframe": "1h",
"funding_fee_candle_limit": 500, "funding_fee_candle_limit": 500,
"uses_leverage_tiers": False, "uses_leverage_tiers": False,
"mark_ohlcv_price": "futures",
} }
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [ _supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
@@ -57,123 +56,56 @@ class Hyperliquid(Exchange):
config.update(super()._ccxt_config) config.update(super()._ccxt_config)
return config return config
def market_is_tradable(self, market: dict[str, Any]) -> bool: def _get_required_hip3_dexes(self) -> set[str]:
parent_check = super().market_is_tradable(market)
hip3_dexes = self._config.get("exchange", {}).get("hip3_dexes", [])
# Allow HIP-3 markets (with ':' in base) only if hip3_dexes configured
if ":" in market["base"]:
return parent_check and bool(hip3_dexes)
return parent_check
def get_balances(self) -> dict:
""" """
Fetch balance including HIP-3 DEX balances if configured. Get HIP-3 DEXes that are needed based on tradable pairs.
Completely automatic - no configuration needed!
For Hyperliquid, this fetches balances from the default DEX and any
configured HIP-3 DEXes (e.g., 'xyz' for equities).
""" """
balances = super().get_balances() required_dexes = set()
# Fetch raw balance info (CCXT normalizes and removes 'info') for market in self.markets.values():
try: if not super().market_is_tradable(market):
raw_balance = self._api.fetch_balance()
if "info" in raw_balance:
balances["info"] = raw_balance["info"]
except Exception as e:
logger.warning(f"Could not fetch raw balance info: {e}")
# Fetch HIP-3 DEX balances if configured
hip3_dexes = self._config.get("exchange", {}).get("hip3_dexes", [])
if hip3_dexes:
balances = self._fetch_hip3_balances(hip3_dexes, balances)
return balances
def _fetch_hip3_balances(self, hip3_dexes: list[str], base_balance: dict) -> dict:
"""Fetch balances from configured HIP-3 DEXes and merge them."""
logger.info(f"Fetching balances from {len(hip3_dexes)} HIP-3 DEX(es): {hip3_dexes}")
for dex in hip3_dexes:
try:
logger.debug(f"Fetching balance for HIP-3 DEX: {dex}")
dex_balance = self._api.fetch_balance({"dex": dex})
if not dex_balance or "info" not in dex_balance:
logger.error(
f"HIP-3 DEX '{dex}' returned invalid response. Check configuration."
)
continue continue
base_balance = self._merge_hip3_balances(base_balance, dex_balance) market_info = market.get("info", {})
if market_info.get("hip3") is True:
dex = market_info.get("dex")
if dex:
required_dexes.add(dex)
positions_count = len(dex_balance.get("info", {}).get("assetPositions", [])) return required_dexes
if positions_count > 0:
logger.info(f"Merged {positions_count} position(s) from HIP-3 DEX '{dex}'") def get_balances(self) -> dict:
"""Fetch balances from default DEX and HIP-3 DEXes needed by tradable pairs."""
balances = super().get_balances()
for dex in self._get_required_hip3_dexes():
try:
dex_balance = self._api.fetch_balance({"dex": dex})
for currency, amount_info in dex_balance.items():
if currency in ["info", "free", "used", "total", "datetime", "timestamp"]:
continue
if currency not in balances:
balances[currency] = amount_info
else: else:
logger.warning(f"HIP-3 DEX '{dex}' returned no positions") balances[currency]["free"] += amount_info["free"]
balances[currency]["used"] += amount_info["used"]
balances[currency]["total"] += amount_info["total"]
except Exception as e: except Exception as e:
logger.error(f"Could not fetch balance for HIP-3 DEX '{dex}': {e}") logger.error(f"Could not fetch balance for HIP-3 DEX '{dex}': {e}")
return base_balance return balances
def _merge_hip3_balances(self, base_balance: dict, new_balance: dict) -> dict:
"""Merge balances from different Hyperliquid DEXes."""
# Merge assetPositions (raw API response)
if "info" in new_balance and "assetPositions" in new_balance.get("info", {}):
if "info" not in base_balance:
base_balance["info"] = {}
if "assetPositions" not in base_balance["info"]:
base_balance["info"]["assetPositions"] = []
new_positions = new_balance["info"]["assetPositions"]
base_balance["info"]["assetPositions"].extend(new_positions)
logger.debug(f"Merged {len(new_positions)} asset position(s) from HIP-3 DEX")
# Merge normalized CCXT balance structure (skip standard fields)
for currency, amount_info in new_balance.items():
if currency in ["info", "USDC", "free", "used", "total"]:
continue
if isinstance(amount_info, dict):
normalized_currency = currency.upper() if ":" in currency else currency
base_balance[normalized_currency] = amount_info
logger.debug(f"Added balance for currency: {normalized_currency}")
return base_balance
def fetch_positions(self, pair: str | None = None) -> list[CcxtPosition]: def fetch_positions(self, pair: str | None = None) -> list[CcxtPosition]:
""" """Fetch positions from default DEX and HIP-3 DEXes needed by tradable pairs."""
Fetch positions including HIP-3 positions. positions = super().fetch_positions(pair)
Hyperliquid supports fetching positions from different DEXes using the for dex in self._get_required_hip3_dexes():
'dex' parameter. This method fetches positions from the default DEX and
all configured HIP-3 DEXes.
"""
# Fetch standard positions from default DEX
try: try:
if pair: positions.extend(self._api.fetch_positions(params={"dex": dex}))
positions = self._api.fetch_positions([pair])
else:
positions = self._api.fetch_positions()
except Exception as e:
logger.warning(f"Could not fetch standard positions: {e}")
positions = []
# Fetch HIP-3 positions if configured
hip3_dexes = self._config.get("exchange", {}).get("hip3_dexes", [])
if hip3_dexes:
for dex in hip3_dexes:
try:
logger.debug(f"Fetching positions from HIP-3 DEX: {dex}")
hip3_positions = self._api.fetch_positions(params={"dex": dex})
if hip3_positions:
positions.extend(hip3_positions)
logger.debug(f"Added {len(hip3_positions)} position(s) from DEX '{dex}'")
except Exception as e: except Exception as e:
logger.error(f"Could not fetch positions from HIP-3 DEX '{dex}': {e}") logger.error(f"Could not fetch positions from HIP-3 DEX '{dex}': {e}")