compatible with HIP3

This commit is contained in:
Ingo
2025-12-03 05:30:36 +01:00
parent 7137461891
commit 670999abda
+163 -4
View File
@@ -9,7 +9,7 @@ from freqtrade.constants import BuySell
from freqtrade.enums import MarginMode, TradingMode from freqtrade.enums import MarginMode, TradingMode
from freqtrade.exceptions import ExchangeError, OperationalException from freqtrade.exceptions import ExchangeError, OperationalException
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
from freqtrade.exchange.exchange_types import CcxtOrder, FtHas from freqtrade.exchange.exchange_types import CcxtOrder, CcxtPosition, FtHas
from freqtrade.util.datetime_helpers import dt_from_ts from freqtrade.util.datetime_helpers import dt_from_ts
@@ -59,10 +59,169 @@ class Hyperliquid(Exchange):
def market_is_tradable(self, market: dict[str, Any]) -> bool: def market_is_tradable(self, market: dict[str, Any]) -> bool:
parent_check = super().market_is_tradable(market) parent_check = super().market_is_tradable(market)
hip3_dexes = self._config.get("exchange", {}).get("hip3_dexes", [])
# Exclude hip3 markets for now - which have the format XYZ:GOOGL/USDT:USDT - # Allow HIP-3 markets (with ':' in base) only if hip3_dexes configured
# and XYZ:GOOGL as base if ":" in market["base"]:
return parent_check and ":" not in market["base"] return parent_check and bool(hip3_dexes)
return parent_check
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
base_balance = self._merge_hip3_balances(base_balance, dex_balance)
positions_count = len(dex_balance.get("info", {}).get("assetPositions", []))
if positions_count > 0:
logger.info(f"Merged {positions_count} position(s) from HIP-3 DEX '{dex}'")
else:
logger.warning(f"HIP-3 DEX '{dex}' returned no positions")
except Exception as e:
logger.error(f"Could not fetch balance for HIP-3 DEX '{dex}': {e}")
return base_balance
def get_balances(self) -> dict:
"""Fetch balance including HIP-3 DEX balances if configured."""
balances = super().get_balances()
# Fetch raw balance info (CCXT normalizes and removes 'info')
try:
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 _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]:
"""
Fetch positions including HIP-3 positions from assetPositions.
Override standard fetch_positions to add HIP-3 equity positions
which are not returned by the standard CCXT fetch_positions call.
"""
positions = super().fetch_positions(pair)
hip3_dexes = self._config.get("exchange", {}).get("hip3_dexes", [])
if not hip3_dexes:
return positions
try:
balances = self.get_balances()
hip3_positions = self._parse_hip3_positions(balances)
if hip3_positions:
positions.extend(hip3_positions)
logger.debug(f"Added {len(hip3_positions)} HIP-3 position(s)")
except Exception as e:
logger.warning(f"Could not fetch HIP-3 positions: {e}")
return positions
def _parse_hip3_positions(self, balances: dict) -> list[CcxtPosition]:
"""
Parse HIP-3 positions from balance info into CCXT position format.
HIP-3 positions are stored in assetPositions with a colon in the coin name
(e.g., 'xyz:TSLA'). This method converts them to the standard CCXT format
that Freqtrade expects.
"""
hip3_positions: list[CcxtPosition] = []
asset_positions = balances.get("info", {}).get("assetPositions", [])
if not isinstance(asset_positions, list):
return hip3_positions
for asset_pos in asset_positions:
position = asset_pos.get("position", {})
if not isinstance(position, dict):
continue
coin = position.get("coin", "")
if not coin or ":" not in coin:
continue # Skip non-HIP-3 positions
szi = float(position.get("szi", 0))
if szi == 0:
continue # Skip empty positions
# Parse leverage
leverage_info = position.get("leverage", {})
if isinstance(leverage_info, dict):
leverage = float(leverage_info.get("value", 1))
else:
leverage = 1.0
# Parse collateral
collateral = float(position.get("marginUsed", 0))
# Convert to pair format: xyz:TSLA → XYZ-TSLA/USDC:USDC
symbol = f"{coin.upper().replace(':', '-')}/USDC:USDC"
side = "short" if szi < 0 else "long"
contracts = abs(szi)
hip3_positions.append(
{
"symbol": symbol,
"contracts": contracts,
"side": side,
"collateral": collateral,
"initialMargin": collateral,
"leverage": leverage,
"liquidationPrice": None,
}
)
logger.debug(f"Parsed HIP-3 position: {symbol} = {contracts} contracts {side}")
return hip3_positions
def get_max_leverage(self, pair: str, stake_amount: float | None) -> float: def get_max_leverage(self, pair: str, stake_amount: float | None) -> float:
# There are no leverage tiers # There are no leverage tiers