Let traders pre-arm tiered partial exits (e.g., 25% @ TP1, 50% @ TP2, trail remainder) via a slider UI. Orders are server-side (not client-dependent), OCO-linked, tick-rounded, and risk-checked.
Slider with up to 3 targets (configurable):
Knobs = TP1/TP2/TP3 price levels (drag to price or type exact level).
Fill bars / handles show % to close at each target (e.g., 25%, 50%, 25%).
Toggle “Trail remainder” with offset (ticks or ATR multiple).
Quick-set buttons: 1R / 2R / 3R, Last swing high/low, +X ticks.
Per-account or grouped: if copy-trading, apply same risk % per account or map using copy-ratio.
“Arm” vs “Live”:
Armed: preview shows working child orders that will post on entry.
Live: after entry, child orders are placed and server-managed.
// shared types type AccountId = string; type Symbol = "ES" | "NQ" | string; interface TpTier { price: number; // exchange price, rounded to tick size pct: number; // 0..100, sum across tiers <= 100 } interface TpPlan { tiers: TpTier[]; // e.g., [{price: 5400.00, pct: 25}, ...] trailRemainder?: { // optional trailing for leftover qty mode: "ticks" | "atr"; value: number; // ticks or ATR multiple activationPrice?: number; // optional }; reduceOnly: boolean; // enforce exits only goodTil: "DAY" | "GTD" | "GTC"; } interface ArmedPlan { symbol: Symbol; side: "LONG" | "SHORT"; // determines TP direction qtyPlan: { // how quantity is computed mode: "fixed" | "percent"; value: number; // fixed contracts OR % of open qty }; tp: TpPlan; riskEnvelopeId?: string; // link to risk ruleset copyMap?: CopyRatio[]; // optional for multi-account } interface CopyRatio { accountId: AccountId; qtyRatio: number; // e.g., 0.5 → half the base qty; supports floor>=1 tick-size rules dollarCap?: number; // max notional per account } function TpSlider({ price, tickSize, initialPlan, onChange }: { price: number; tickSize: number; initialPlan: TpPlan; onChange: (tp: TpPlan) => void }) { // internal state manages tier prices & percents // Ensure tick rounding on drag const round = (p:number)=> Math.round(p / tickSize) * tickSize; // Emit onChange with validated tiers (sum<=100, each pct>=0) // Visual handles = prices; separate mini-sliders = % allocations // Not full code—shows architecture & validation hooks return ( <div className="tp-slider"> {/* price handles, percent sliders, quick presets, trail toggle */} </div> ); } Key client behaviors:
Tick rounding on drag/end.
Validation: sum of pct ≤ 100; show remaining remainder.
Latency-safe: arming only configures; no orders until server confirms “live.”
WebSocket stream shows working child orders / fills in real time.
POST /api/tp/arm Body: ArmedPlan + { accounts: AccountId[] } Resp: { armedPlanId, preview: ChildOrderPreview[] } POST /api/tp/activate Body: { armedPlanId, parentOrderId } // called after entry placed/filled Resp: { childOrderGroupId } POST /api/tp/cancel Body: { childOrderGroupId } WS feed: /stream/orders // publishes child order lifecycle & fills Preview endpoint simulates tier splits, tick rounding, and risk caps before anything hits the exchange.
On entry filled (or partially filled), compute activeQty per account.
For each account:
Apply copyMap.qtyRatio (if present) OR base qty.
Compute tierQty = floor(activeQty * tier.pct/100) with minQty=1 where allowed; fix rounding by adding remainder to the last tier.
Direction:
Long → TPs above entry; Short → below.
Validate distance≥1 tick and within exchange price bands.
Place child orders as Reduce-Only Limit at each tier price, OCO-grouped with stop(s) if you support brackets.
If trailRemainder:
After last fixed tier is filled (or from activationPrice), register a server-side trailing stop (ticks/ATR).
Chase logic runs on server, not client (protects against app disconnects).
OCO & State Machine
Each tier limit is OCO with a group cancel on Flatten or Stop hit.
If a tier partially fills, update remaining tier qty or collapse to next tier per policy.
Risk checks (pre-trade + post-trade):
Verify Topstep rules (max daily loss, trailing drawdown, product limits).
Block orders that would breach limits; return reason codes to client.
Use Reduce-Only (or synthetic) + TimeInForce (DAY, GTD, or synthetic GTC if the venue requires).
Tick rounding with instrument metadata (min price increment varies by symbol).
Respect price bands / velocity logic—reject if price outside.
Support partial fills: maintain remainingQty and cascade adjustments.
Idempotency keys per child to avoid dupes on retries.
def activate_tp_plan(armed_plan, parent_fill): entry_price = parent_fill.avg_price active_qty = parent_fill.filled_qty for acct in accounts(armed_plan): base = apply_copy_ratio(active_qty, acct, armed_plan.copyMap) tiers = [] running = 0 for i, t in enumerate(armed_plan.tp.tiers): q = int(floor(base * t.pct / 100)) if i == len(armed_plan.tp.tiers) - 1: q = max(base - running, 0) # allocate remainder running += q if q <= 0: continue price = round_to_tick(t.price, instrument.tick) side = "SELL" if armed_plan.side=="LONG" else "BUY" tiers.append(place_limit_reduce_only( account=acct, symbol=armed_plan.symbol, side=side, qty=q, price=price, oco_group=f"TP-{parent_fill.order_id}" )) if armed_plan.tp.trailRemainder and running < base: qty_left = base - running place_trailing_stop( account=acct, symbol=armed_plan.symbol, side=("SELL" if armed_plan.side=="LONG" else "BUY"), qty=qty_left, mode=armed_plan.tp.trailRemainder.mode, value=armed_plan.tp.trailRemainder.value, activation=armed_plan.tp.trailRemainder.activationPrice, oco_group=f"TP-{parent_fill.order_id}" ) return {"status":"live", "group": f"TP-{parent_fill.order_id}"} Reduce-Only enforced; never increases exposure.
Per-tier dollar caps and per-account notional caps.
Auto-flatten on: daily loss breach, trailing drawdown breach, or end-of-session policy.
Audit log: every config, calculation, and exchange ACK with timestamps.
On activation, apply account-specific ratios: e.g., base qty from the “lead” account, map 150K:50K = 3:1.
All child orders inherit the per-account qty after ratioing; TP percentages remain consistent across accounts.
Partial parent fills: arm TPs only for filled qty; on additional fills, append or rebalance per policy.
Price knob overlap: if TP2 dragged below TP1, auto-swap or block until valid.
Zero/low qty after rounding: borrow one contract from the next tier to keep at least 1 where allowed.
Connection loss: orders live on server; client just reflects state via WS.
Market gaps: if TP price crosses gap and is invalid, convert to market-to-limit with band checks.
Unit: tick rounding, qty split, copy ratios, ATR trailing math.
Sim: partial fills, rapid fills, rejects, cancels, OCO behavior.
UAT: latency, WS updates, cross-account consistency, rule-breach auto-flatten.
Uses primitives most venues/brokers already support: reduce-only limit, trailing stop, OCO.
Server-side orchestration means no UX dependency for execution safety.
Backward-compatible: if slider is off, nothing changes.
If you want, I can also provide:
A drop-in React component with tick-aware handles and percent mini-sliders.
A Node/Python reference microservice that converts a TP plan → exchange orders (with mock FIX/REST adapters).
A QA checklist specific to ES/NQ (CME) session boundaries and price bands.
Want me to package this into a one-page PDF spec you can forward to their product/engineering lead?
Please authenticate to join the conversation.
In Review
💡 Feature Request
Risk Tools
6 months ago

Joseph Smith
Get notified by email when there are changes.
In Review
💡 Feature Request
Risk Tools
6 months ago

Joseph Smith
Get notified by email when there are changes.