fix: hyperliquid end-to-end support (markets, market orders, markPrice)#119
Merged
luokerenx4 merged 3 commits intomasterfrom Apr 11, 2026
Merged
fix: hyperliquid end-to-end support (markets, market orders, markPrice)#119luokerenx4 merged 3 commits intomasterfrom
luokerenx4 merged 3 commits intomasterfrom
Conversation
CcxtBroker hardcoded fetchMarkets.types = ['spot', 'linear', 'inverse'], which overrode the exchange's own defaults. Bybit happened to work because its defaults matched, but Hyperliquid uses ['spot', 'swap', 'hip3'] — so all swap/perp markets were silently dropped, leaving users unable to find BTC-PERP or any derivative. - Stop overriding fetchMarkets.types in the constructor; let each exchange use its declared defaults - fetchMarkets wrapper reads exchange's own types and only filters out 'option' (the universal option-stripping intent stays) - Guard searchContracts against markets with undefined base/quote (some hyperliquid spot markets have these fields missing) E2E setup - hasCredentials() previously hardcoded `bc.apiKey` for ccxt, filtering out any wallet-based account. Use CCXT_CREDENTIAL_FIELDS.some() so wallet-based exchanges (hyperliquid, dYdX) are recognized New e2e specs - ccxt-hyperliquid-markets.e2e.spec.ts: credential-free regression test using dummy keys, validates spot+swap markets load and BTC perp is searchable. Always runs. - ccxt-hyperliquid.e2e.spec.ts: full e2e against hyperliquid testnet. Skips when no hyperliquid sandbox account is configured. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hyperliquid has no native market order type — orders are emulated as IOC limit orders bounded by a slippage price (default 5%). CCXT requires the caller to pass a reference price even for type='market' so it can compute the slippage bound, otherwise it throws ArgumentsRequired. The hyperliquid server also enforces an 80% deviation cap from mark price, ruling out extreme dummy values — a real reference price is required. - Add placeOrder hook to CcxtExchangeOverrides interface (mirrors the existing fetchOrderById/cancelOrderById hooks) - defaultPlaceOrder passes through to ccxt.createOrder unchanged - exchanges/hyperliquid.ts overrides placeOrder to fetchTicker first when the order is a market order missing a reference price - CcxtBroker.placeOrder routes through this.overrides.placeOrder ?? default - Bump hyperliquid e2e test timeouts from 15s/30s to 60s — testnet is noticeably slower than bybit (extra fetchTicker RTT plus base latency) E2E result on hyperliquid testnet: 8/8 passing — market buy/sell, position verification, reduceOnly close, and getOrder all work end-to-end. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rkPrice Reshape CcxtExchangeOverrides so each hook receives the original args plus a defaultImpl as the final parameter. The override decides what to do with it: invoke directly, modify inputs and call it, post-process its result, or ignore it entirely. This replaces the previous either/or pattern that forced overrides to either fully replace the default or copy its body. - All four hooks (fetchOrderById, cancelOrderById, placeOrder, fetchPositions) now follow the same convention - New hook fetchPositions + defaultFetchPositions for cases where ccxt's parsePosition leaves fields undefined - bybit's fetchOrderById is unchanged (it fully replaces); just adopts the new signature with an unused _defaultImpl parameter - hyperliquid's placeOrder now calls defaultImpl(modifiedArgs) instead of exchange.createOrder directly — future enhancements to defaultPlaceOrder (retries, logging) will flow through automatically - New hyperliquid fetchPositions override recovers markPrice from notional / contracts. CCXT's parsePosition hardcodes markPrice: undefined for hyperliquid, but it does expose positionValue (mapped to notional), so the math works out: |notional| / |contracts| = mark price E2E confirms: BTC perp position now reports `@ 72931 USD` instead of `@ 0`, and the new regression assertion (marketPrice > 0, marketValue ≈ qty × price) catches future regressions. All 8 hyperliquid e2e tests pass. - Bump version to 0.9.0-beta.12 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Make Hyperliquid actually work in OpenAlice. Three independent fixes needed before users could trade on it, plus a small refactor of the CCXT override mechanism that emerged from the work.
Hyperliquid market loading
`CcxtBroker` was overriding `fetchMarkets.types` with a hardcoded `['spot', 'linear', 'inverse']` list. Bybit defaults match this, but Hyperliquid uses `['spot', 'swap', 'hip3']` — so all swap/perp markets were silently dropped, leaving users unable to find BTC-PERP. The wrapper now uses each exchange's own declared types and only filters out `option`.
E2E setup recognized wallet-based CCXT
`hasCredentials()` hardcoded `bc.apiKey` for ccxt accounts, filtering out wallet-based exchanges. Now uses `CCXT_CREDENTIAL_FIELDS.some()` so hyperliquid (walletAddress + privateKey) is recognized.
Hyperliquid market orders
Hyperliquid has no native market orders — they're emulated as IOC limit orders bounded by a slippage price. CCXT requires the caller to pass a reference price even for `type='market'`. Hyperliquid override now `fetchTicker`s first, then delegates.
CCXT override refactor
The previous override pattern was "either replace the default or copy its body". Refactored so each hook receives `defaultImpl` as the final parameter. Overrides can:
Hyperliquid markPrice fix
CCXT's hyperliquid `parsePosition` hardcodes `markPrice: undefined` (line 3613). It does expose `positionValue` (mapped to `notional`), so the new `fetchPositions` override recovers `markPrice = |notional| / |contracts|`.
New e2e specs
Bumps to `0.9.0-beta.12`.
Test plan
🤖 Generated with Claude Code