Skip to content

feat(tradebox): bracket order one-click toggle #93

@vipineth

Description

@vipineth

Problem Statement

Traders placing perp orders on HypeTerminal today have to manually define a take-profit and stop-loss on every trade they want to protect. The existing TP/SL control is a small checkbox buried in a flat row with Reduce Only and TIF — it's not visually discoverable, it has no defaults, and every time a user wants exits they have to tick the box and type two prices.

Traders coming from TradFi brokers (ThinkorSwim, IBKR, Tradovate) are used to placing "bracket orders" as a first-class primitive — one action that sets an entry plus both exit legs with sensible defaults. The current HypeTerminal flow forces them to relearn a familiar pattern, and the friction means many orders end up going out without any stop at all.

The user's ask: "I want to click one button and get a sensibly-configured bracket order ready to submit."

Solution

Introduce a prominent Bracket Order toggle in its own dedicated row in the tradebox, placed directly below the size slider and above the Reduce Only / TIF row. The toggle's label teaches the feature:

Bracket Order · Auto TP 5% · SL 2.5%

Clicking it enables TP/SL mode and prefills the two price inputs with defaults computed from the current reference price and order side. The existing TpSlSection expands inline, showing the concrete TP/SL prices, estimated PnL per leg, and the risk/reward bar. Users can submit as-is, or edit either prefilled value before submitting. Submission continues to use Hyperliquid's atomic normalTpsl grouping, so entry + TP + SL are placed as a single unit.

The new toggle replaces the existing TP/SL checkbox in the tradebox — there is no longer a separate manual-entry affordance, because clicking Bracket and then editing the prefilled values accomplishes the same goal. Reduce Only and TIF stay in their current row (now minus TP/SL).

The feature ships on both the desktop tradebox and the mobile trade view in V1.

User Stories

  1. As a TradFi trader new to Hyperliquid, I want a clearly labeled "Bracket Order" control so I can recognize a familiar primitive without learning crypto jargon.
  2. As a disciplined trader, I want to enable an exit plan with one click so I don't have to manually type TP and SL prices on every trade.
  3. As a user scanning the form for the first time, I want the default percentages (TP 5% / SL 2.5%) surfaced on the Bracket toggle label so I know what will happen before I click.
  4. As a user placing a long order, I want the default TP to sit above the reference price and the default SL below it so the bracket matches the direction of my trade.
  5. As a user placing a short order, I want the default TP to sit below the reference price and the default SL above it so the bracket matches the direction of my trade.
  6. As a user who enables Bracket, I want the TP and SL input fields to appear prefilled with concrete prices (not just percentages) so I can confirm the actual levels.
  7. As a user with an active Bracket, I want to edit either the prefilled TP or SL value freely so I can tune levels without disabling the feature.
  8. As a user with an active Bracket, I want to see the estimated PnL for each leg and the 1:2 risk/reward visualization so I understand my exposure.
  9. As a user placing a market order, I want the Bracket toggle to behave the same way as on a limit order so the feature is consistent across order types.
  10. As a user placing a limit order, I want the Bracket defaults to be computed from the limit price I've entered, not the mark price, so the levels are relevant to my intended entry.
  11. As a user, I want toggling Bracket off to clear the TP and SL fields so I can submit a plain entry without residual values.
  12. As a user, I want toggling Bracket off and then back on to recompute fresh defaults (rather than restoring my prior edits) so the feature behaves predictably.
  13. As a mobile user, I want the same Bracket toggle in the mobile trade view so my experience is consistent across devices.
  14. As a user, I want the entry, TP, and SL orders to be submitted atomically so I never end up with a filled entry and no stop.
  15. As a user, I want the Bracket toggle to show a visible active state after clicking so I have confirmation that my exits are set.
  16. As a keyboard user, I want the Bracket toggle to be focusable and activatable with Enter/Space so I can operate it without a mouse.
  17. As a screen-reader user, I want the toggle to announce its role and current on/off state so I can use the feature with assistive tech.
  18. As a user trading an asset with tight price decimals (e.g., TAO), I want the prefilled TP/SL prices to be rounded to the market's correct decimals so the bracket is accepted by the exchange.
  19. As a user switching markets while Bracket is active, I want the prefilled TP/SL values to update for the new market's reference price so I don't submit stale levels.
  20. As a user, I want TP/SL validation errors (e.g., "TP must be above entry" for a long) to still apply when Bracket is active so I cannot submit an invalid bracket.
  21. As a user whose wallet is not connected, I want the Bracket toggle to be disabled along with the other controls so I don't click something that has no effect.
  22. As a user viewing an order type that doesn't support TP/SL (trigger, scale, TWAP, spot), I want the Bracket toggle hidden so the UI does not suggest unavailable capabilities.
  23. As a user in any supported locale (en, es, fr, hi, zh, ar), I want the Bracket toggle label translated naturally so the feature reads well in my language.
  24. As a user, I want the Bracket toggle to sit in its own row (not crammed in with Reduce Only / TIF) so the hierarchy of decisions is visually obvious.
  25. As a user, I want no behavioral change to trigger, scale, and TWAP order submissions so existing workflows continue to work.

Implementation Decisions

New pure-function module — Bracket defaults calculator

  • A single exported function takes { referencePrice, side, szDecimals } and returns { tpPrice, slPrice } as strings.
  • Hardcoded constants for V1: take-profit at 5% of reference price, stop-loss at 2.5% of reference price.
  • Direction of offset is derived from side (long: TP above / SL below; short: TP below / SL above).
  • Uses the same Big.js-based decimal handling as the existing calculateTpPrice / calculateSlPrice helpers.
  • Deep module — the interface stays stable even if we later add user-configurable settings, per-market overrides, or leverage-aware tuning.

Order entry store — new atomic action

  • A new enableBracket() action sets tpSlEnabled = true, computes defaults via the new module, and writes both prices to the store in a single update.
  • Disabling (either via the toggle or by swapping to a non-TP/SL-capable order type) clears tpPrice and slPrice.
  • Toggling off and on again always recomputes fresh defaults — no restore of prior edits.

New UI component — BracketToggle

  • Full-width chip-style button. Active and inactive visual states.
  • Label includes the default percentages so the user knows what the button will do before clicking.
  • Emits a single click event that dispatches either the new store action or the disable path.
  • Hidden when the current order type does not support TP/SL (spot, trigger, scale, TWAP).
  • Respects the same isFormDisabled gating as other form controls (wallet connection, etc.).

Tradebox form (desktop) — modifications

  • Remove the existing TP/SL checkbox from the Reduce Only / TIF row.
  • Insert the new BracketToggle in its own row, positioned between the size slider block and the Reduce Only / TIF row.
  • No changes to the Reduce Only checkbox, the TIF select, or any other field.
  • The existing TpSlSection renders unchanged when tpSlEnabled is true.

Mobile trade view — modifications

  • Equivalent placement: BracketToggle replaces whatever existing TP/SL affordance the mobile view uses, in the same relative position in the flow.
  • Uses the same component and same store action.

Order submission — no changes

  • Continue to use the existing buildOrderPlan with normalTpsl grouping when TP/SL are active.
  • HL's native grouping guarantees atomicity; no client-side state machine is required.
  • Validation (e.g., "TP must be above entry" for a long) continues to flow through existing getTpSlValidationError.

Reference price

  • The prefill uses whatever reference price the existing TpSlSection already uses: limit price for limit orders, mark price otherwise.
  • This preserves consistency with the existing quick-percent buttons.

i18n

  • New string keys for the BracketToggle label and active/inactive accessible names. Translations added to all six locale files (en, es, fr, hi, zh, ar).

Testing Decisions

What makes a good test here

  • Test the external behavior of pure functions: given inputs, assert outputs.
  • Do not assert component rendering details or store implementation internals.
  • Prior art lives in src/lib/tests/ (format, orders, ring-buffer, etc.) — those files test domain functions with straightforward assertions and no framework-heavy setup.

Module to test — Bracket defaults calculator

  • Long entry at various reference prices: TP is above, SL is below, both at the correct percentage offsets.
  • Short entry at various reference prices: TP is below, SL is above, both at the correct percentage offsets.
  • Decimal rounding: output strings respect the market's szDecimals (verified for majors like BTC and tight-decimal markets like TAO).
  • Invalid inputs: zero, negative, or non-numeric reference prices return safely without throwing.
  • Consistency with the existing helpers: the default calculator's output matches what calculateTpPrice(ref, side, 5, decimals) and calculateSlPrice(ref, side, 2.5, decimals) would produce.

Modules intentionally not tested

  • BracketToggle component — the app has no UI component tests; consistency with codebase conventions matters.
  • enableBracket store action — thin wrapper, coverage value is marginal given the pure module below it is tested.

Out of Scope

  • Unit switching on TP/SL inputs (Price / % / ROE / $ / R:R). V1 keeps the existing Price-only input.
  • Draggable risk/reward bar. The existing read-only R:R visualization is retained.
  • Configurable defaults in settings. Percentages are hardcoded for V1; no settings pane.
  • Per-market default overrides.
  • Orders table grouping. A submitted bracket will appear as separate entry/TP/SL rows in the orders table; visual grouping is deferred.
  • Position TP/SL modal parity. The PositionTpSlModal for editing exits on an already-open position stays unchanged; no auto-fill is applied there.
  • Demoting Reduce Only + TIF into a collapsed Advanced disclosure.
  • Submit button copy changes (e.g., "Place Bracket Long · BTC"). The existing copy is retained.
  • One-time onboarding hint / tooltip explaining the feature.
  • "Always default to Bracket on new orders" user preference.
  • Applying Bracket behavior to trigger, scale, or TWAP order types.

Further Notes

  • This feature primarily builds on infrastructure that already exists: HL's normalTpsl grouping, the shared TpSlSection, the useOrderEntryStore actions, and the tpsl.ts math helpers. Structural risk is low; the change is mostly a relocation and a defaults engine.
  • The mobile tradebox should match the desktop behavior 1:1 — same component, same store, same defaults.
  • Likely V1.1 follow-ups (filed separately when we see usage data): unit-switchable input fields, draggable R:R bar, a configurable defaults pane in settings, per-market defaults, visual grouping of bracket legs in the orders table.

Codex will review your output once you are done.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions