diff --git a/core/src/exchanges/hyperliquid/fetcher.ts b/core/src/exchanges/hyperliquid/fetcher.ts index e4544519..a53aab7a 100644 --- a/core/src/exchanges/hyperliquid/fetcher.ts +++ b/core/src/exchanges/hyperliquid/fetcher.ts @@ -1,7 +1,7 @@ import { MarketFilterParams, EventFetchParams, OHLCVParams, TradesParams } from '../../BaseExchange'; import { IExchangeFetcher, FetcherContext } from '../interfaces'; import { hyperliquidErrorMapper } from './errors'; -import { toCoinNotation, toMidKey, fromMarketId } from './utils'; +import { toCoinNotation, toMidKey, fromMarketId, fromCoinEncoding } from './utils'; // ---------------------------------------------------------------------------- // Raw venue-native types (Hyperliquid HIP-4 Outcome Markets) @@ -142,11 +142,21 @@ export interface HyperliquidRawUserState { withdrawable: string; } +// Per-coin context from spotMetaAndAssetCtxs (only the fields we use) +export interface HyperliquidRawSpotAssetCtx { + coin: string; // "#NNNN" for outcome legs + dayNtlVlm: string; // 24h notional volume in USDC + prevDayPx?: string; + markPx?: string; + midPx?: string; +} + // Composite type: outcome + its question context export interface HyperliquidRawOutcomeWithQuestion { outcome: HyperliquidRawOutcome; question: HyperliquidRawQuestion | undefined; midPrice: string | undefined; // from allMids + volume24h?: number; // summed Yes+No dayNtlVlm from spotMetaAndAssetCtxs } // ---------------------------------------------------------------------------- @@ -176,9 +186,10 @@ export class HyperliquidFetcher implements IExchangeFetcher { - const [meta, mids] = await Promise.all([ + const [meta, mids, volumeMap] = await Promise.all([ this.fetchOutcomeMeta(), this.fetchAllMids(), + this.fetchOutcomeVolumeMap(), ]); const questionMap = new Map(); @@ -192,6 +203,7 @@ export class HyperliquidFetcher implements IExchangeFetcher({ type: 'allMids' }); } + /** + * Build a map of outcomeId -> 24h notional volume (Yes leg + No leg) + * by reading spotMetaAndAssetCtxs, where outcome legs appear as + * coin "#" with `dayNtlVlm` in USDC. + */ + async fetchOutcomeVolumeMap(): Promise> { + const map = new Map(); + try { + const resp = await this.postInfo<[unknown, HyperliquidRawSpotAssetCtx[]]>({ type: 'spotMetaAndAssetCtxs' }); + const ctxs = Array.isArray(resp) ? resp[1] : undefined; + if (!Array.isArray(ctxs)) return map; + for (const ctx of ctxs) { + if (!ctx?.coin || !ctx.coin.startsWith('#')) continue; + const vol = parseFloat(ctx.dayNtlVlm); + if (!Number.isFinite(vol)) continue; + const encoding = parseInt(ctx.coin.slice(1), 10); + if (!Number.isFinite(encoding)) continue; + const { outcomeId } = fromCoinEncoding(encoding); + map.set(outcomeId, (map.get(outcomeId) ?? 0) + vol); + } + } catch { + // ponytail: best-effort volume enrichment; if spotMetaAndAssetCtxs is unreachable, return empty map and callers fall back to 0 + } + return map; + } + private getMidForOutcome(mids: HyperliquidRawMid, outcomeId: number): string | undefined { const midKey = toMidKey(outcomeId); return mids[midKey]; diff --git a/core/src/exchanges/hyperliquid/index.ts b/core/src/exchanges/hyperliquid/index.ts index 8d5e43d0..a0b788ff 100644 --- a/core/src/exchanges/hyperliquid/index.ts +++ b/core/src/exchanges/hyperliquid/index.ts @@ -126,14 +126,15 @@ export class HyperliquidExchange extends PredictionMarketExchange { return []; } - const [rawQuestions, meta, mids] = await Promise.all([ + const [rawQuestions, meta, mids, volumeMap] = await Promise.all([ this.fetcher.fetchRawEvents(params), this.fetcher.fetchOutcomeMeta(), this.fetcher.fetchAllMids(), + this.fetcher.fetchOutcomeVolumeMap(), ]); return rawQuestions - .map(q => this.normalizer.normalizeEventWithMarkets(q, meta, mids)) + .map(q => this.normalizer.normalizeEventWithMarkets(q, meta, mids, volumeMap)) .filter((e): e is UnifiedEvent => e !== null); } diff --git a/core/src/exchanges/hyperliquid/normalizer.ts b/core/src/exchanges/hyperliquid/normalizer.ts index e7e25864..e818e484 100644 --- a/core/src/exchanges/hyperliquid/normalizer.ts +++ b/core/src/exchanges/hyperliquid/normalizer.ts @@ -233,7 +233,7 @@ export class HyperliquidNormalizer implements IExchangeNormalizer, ): UnifiedEvent | null { const event = this.normalizeEvent(raw); if (!event) return null; @@ -308,6 +309,7 @@ export class HyperliquidNormalizer implements IExchangeNormalizer None: class NetworkError(PmxtError): """503 Service Unavailable - Network connectivity issues.""" - def __init__(self, message: str, exchange: str | None = None): - super().__init__(message, code="NETWORK_ERROR", retryable=True, exchange=exchange) + def __init__(self, message: str, exchange: str | None = None, **kwargs: Any) -> None: + kwargs.setdefault("code", "NETWORK_ERROR") + kwargs.setdefault("retryable", True) + super().__init__(message, exchange=exchange, **kwargs) class ExchangeNotAvailable(PmxtError): """503 Service Unavailable - Exchange is down or unreachable.""" - def __init__(self, message: str, exchange: str | None = None): - super().__init__(message, code="EXCHANGE_NOT_AVAILABLE", retryable=True, exchange=exchange) + def __init__(self, message: str, exchange: str | None = None, **kwargs: Any) -> None: + kwargs.setdefault("code", "EXCHANGE_NOT_AVAILABLE") + kwargs.setdefault("retryable", True) + super().__init__(message, exchange=exchange, **kwargs) # Mapping from server error codes to error classes