Skip to content

fix(http): send market orders as FrontendMarket, not GTC - #80

Merged
ifdario merged 1 commit into
infinitefield:mainfrom
changshenhan:fix/market-open-frontend-market
Aug 21, 2026
Merged

fix(http): send market orders as FrontendMarket, not GTC#80
ifdario merged 1 commit into
infinitefield:mainfrom
changshenhan:fix/market-open-frontend-market

Conversation

@changshenhan

Copy link
Copy Markdown
Contributor

Summary

Two related price-handling fixes in the trading path.

1. market_open sends real market orders (was sending GTC limits)

market_open() is documented as using Hyperliquid's native FrontendMarket order type ("fills immediately up to the provided worst acceptable limit price"), and the example examples/hypercore/market_order.rs says the same — but the code built the order with TimeInForce::Gtc:

order_type: OrderTypePlacement::Limit {
    tif: TimeInForce::Gtc,   // ← rest as a passive limit
},

TimeInForce::FrontendMarket has always existed (src/hypercore/types/mod.rs) but was never actually used anywhere. The practical impact on live trading:

  • A market buy whose limit_px (worst acceptable) is below the current ask rests as a passive GTC limit instead of filling — the caller believes they're filled, their position isn't.
  • A market sell with limit_px above the current bid never fills unless price crosses it.

FIX: GtcFrontendMarket. This is the Hyperliquid-protocol market order (a Limit with tif: FrontendMarket), matching the frontend semantics the method documents.

2. PriceTick::tick_for correct at powers of ten

tick_for's doc algorithm is sig_figs = floor(log10(price)) + 1, but the code used ceil(log10(price)). The two agree for every non-power-of-ten price and disagree exactly at powers of ten, where ceil undercounts the integer digits by one and returns a tick ten times too fine:

price documented actual (before) actual (after)
1000 0.1 0.01 0.1
100 0.01 0.001 0.01
1 0.0001 0.00001 0.0001

Visible on any market with max_decimals ≥ the computed value (e.g. a perp/spot with low sz_decimals); round/round_by_side inherit it. No existing test covered a power-of-ten price — added power_of_ten_prices_keep_five_sig_figs.

Testing

  • New regression test for power-of-ten prices.
  • cargo test --lib: 149 passed, 0 failed.
  • No new clippy warnings (23 pre-existing warnings on main are unchanged).

market_open() documented and shipped "Hyperliquid native FrontendMarket
order type, fills immediately up to the worst acceptable price", but
built the order with TimeInForce::Gtc. A market buy at a limit below
market would rest as a passive GTC limit instead of filling immediately,
and a GTC sell would never fill unless price crossed. FrontendMarket is
the Hyperliquid-protocol market order (Limit + tif=FrontendMarket); the
variant existed but was never actually used.

Also fix PriceTick::tick_for(): the docs specify integer digits as
floor(log10(price)) + 1, but the code used ceil(log10(price)). The two
agree except when log10(price) is exact — i.e. price is a power of ten —
where ceil undercounts the digits by one and returns a tick ten times
too fine (1000 → 0.01 instead of 0.1 for a 5-significant-figure market).
Add power-of-ten regression test.

149 tests pass; no new clippy warnings.

Co-Authored-By: Claude <noreply@anthropic.com>
@ifdario
ifdario merged commit e6c81fe into infinitefield:main Aug 21, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants