Reconciliation, Risk & Exchange Consistency
This page documents how the discretionary runtime keeps its local trading state aligned with Hyperliquid while still responding quickly enough for realtime agent use, and how the risk engine bounds new entries and adapts to drawdown.
Design Goal
Section titled “Design Goal”The discretionary runtime uses a hybrid model:
- local runtime state for low-latency decision-making
- exchange sync and reconciliation for correction and validation
Main Components
Section titled “Main Components”backend/services/autodiscretionary/reconciliation.tsbackend/services/autodiscretionary/sync-worker.tsbackend/services/autodiscretionary/trader.tsbackend/services/autodiscretionary/runtime-snapshot.tsbackend/services/autodiscretionary/risk-engine.ts
Reconciliation Lifecycle
Section titled “Reconciliation Lifecycle”Reconciliation is session-scoped — bound to a single agent wallet’s HL account.
Typical phases include:
checkingreconcilingverifyingconsistentfailed
Why session-scoped: the discretionary agent is 1-to-1 with an agent wallet. Each agent wallet operates its own isolated HL portfolio. Reconciliation cannot cross sessions, because each session reconciles a different on-chain account.
Risk Engine
Section titled “Risk Engine”Every discretionary trade requires:
- stop-loss
- take-profit reference (
heuristic_target) - conviction (0–100)
absolute_risk_usd— maximum USD loss if the stop is hit
Position size is derived: size = absolute_risk_usd / |entry − SL|.
Two Independent Risk Caps
Section titled “Two Independent Risk Caps”A trade is rejected if either cap is breached:
cap_portfolio— sum of allabsolute_risk_usdacross all marketscap_per_market— sum ofabsolute_risk_usdwithin a single market
Per-Market Cap Is A Percentage Of The Portfolio Cap
Section titled “Per-Market Cap Is A Percentage Of The Portfolio Cap”Configuration:
maxPortfolioRiskUsd— the portfolio cap, in USD (e.g.$500)maxIndividualRiskPct— the per-market cap, expressed as a percentage of the (effective) portfolio cap (e.g.20)
The per-market cap is derived dynamically as:
effectiveIndividualCap = effectivePortfolioCap × (maxIndividualRiskPct / 100)So $500 portfolio × 20% → $100 per-market cap. If the effective portfolio cap shrinks (drawdown penalty, account-level cap clipping), the per-market cap shrinks proportionally — the agent never has to reconfigure both.
Drawdown Penalty
Section titled “Drawdown Penalty”Realised losses subtract from the effective portfolio cap as a decaying penalty:
- A loss adds to
cumulativeLoss penalty = cumulativeLoss × (1 − minutesElapsed / lossDecayMinutes)(clamped at zero)effectivePortfolioCap = max(0, maxPortfolioRiskUsd − penalty)effectiveIndividualCap = effectivePortfolioCap × (maxIndividualRiskPct / 100)
The penalty decays linearly to zero over lossDecayMinutes. The runtime exposes penaltyUsd and decayRemainingMinutes to the agent so it can reason “I’m capped lower than nominal, but the cap will recover in N minutes.”
Drawdown Lockout
Section titled “Drawdown Lockout”When equity drops below the configured lockout threshold, new entries are blocked at the exchange layer — not by the agent, by the runtime. The agent sees a DRAWDOWN LOCKOUT ACTIVE section in its context and is expected to focus on managing existing positions (tightening SLs, reducing, locking in profit). The lockout lifts when equity recovers above the threshold or the user manually resets it.
Risk-Free Positions
Section titled “Risk-Free Positions”When a stop is trailed past entry (longs: SL above entry; shorts: SL below entry), the position is risk-free. It contributes $0 to both caps. Locked-in unrealised profit is surfaced as total_covered_unrealised_profit so the agent can decide whether to hold or close to free margin.
Sizing vs Margin
Section titled “Sizing vs Margin”Cap checks are risk-USD-based. The exchange can still reject with Insufficient margin if notional × (1 / max_leverage) exceeds available margin.
If execute_trade_setup fails with Insufficient margin, the agent should:
- reduce
absolute_risk_usd, or - widen the SL (smaller size at the same risk), or
- close a lower-conviction existing position to free margin.
Why Runtime Scope Matters
Section titled “Why Runtime Scope Matters”The session-scoped runtime identity is:
runtimeScope = `session:${sessionId}`This is the core isolation boundary for multi-agent-per-user support. Each discretionary session:
- is bound to one agent wallet (
apiWalletId) - owns its own Redis state (positions, trade ledger, alerts, drawdown, equity peak, market conditions, risk config)
- runs its own reconciliation worker
- runs its own auto-risk worker, time-alert scheduler, and HL bridge
This prevents one agent wallet from correcting another wallet’s state under the same user, and prevents portfolio risk on one wallet from constraining trades on another.
Risk State Surfaced To The Agent
Section titled “Risk State Surfaced To The Agent”Each turn, the discretionary system prompt is built with a structured Portfolio Risk block:
{ "active_risk_usd": 38.50, "total_risk_usd": 38.50, "headroom_usd": 11.50, "market_risk_cap": 50.00, "cap_portfolio": 50.00, "active_trades": 2, "drawdown_penalty_usd": 12.00, "penalty_decays_in_minutes": 18, "static_cap_portfolio": 62.00, "per_market_risk": [ { "market": "ETH/USDC:USDC", "risk_usd": 22.00, "pct_of_market_risk_cap": 44, "headroom_usd": 28.00, "trades": 1 }, { "market": "SOL/USDC:USDC", "risk_usd": 16.50, "pct_of_market_risk_cap": 33, "headroom_usd": 33.50, "trades": 1 } ], "auto_risk": { "portfolio_bounds": "$50 — $2000", "per_market_risk_pct": "20%" }}Note that per_market_risk_pct is the configured percentage, while market_risk_cap is the derived USD value at the current effective portfolio cap.