- Max drawdown was double-broken: the backend built the equity curve on a
hardcoded $10k baseline (understating a small account's drawdown ~20x) and
the frontend multiplied the already-percent value by 100 again (0.87% shown
as -86.9%). The curve now starts from the trader's real initial balance
(10k fallback when unknown) and the UI renders the percent once; the demo
engine and type docs are aligned to percent semantics.
- Header now separates equity-based 'Total P/L (incl. unrealized)' from
'Realized P/L (closed trades)' so it no longer contradicts profit factor /
win rate, and the stats strip shows the fee-drag chain
(gross - fees = net) plus a per-trade-labelled sharpe.
- Risk radar read a non-existent account field (total_unrealized_profit) so
unrealized PnL always showed $0; small PnLs also render with cents now.
- Nav 'Connect Hyperliquid' turns into a green connected chip when the server
already holds a fully-authorized exchange, instead of nagging forever from
a browser without the localStorage flow state.
- POST /api/launch/preflight and GET /api/traders/:id/preflight run every
launch prerequisite (AI model credential, claw402 wallet key + Base USDC
balance, strategy, exchange config, live account state, funding minimums)
and return a structured checklist with stable codes; minimums (1 USDC AI
fee / 12 USDC trading) now live server-side and are exposed in the response.
- POST /traders/:id/start enforces the preflight (400 with error_key
trader.start.preflight_failed + full checklist; ?force=true to override).
Funding gates use max(available, equity) so restarting a bot with deployed
margin is not blocked; indeterminate probes (RPC outage) degrade to
warnings instead of blocking.
- Run loop now surfaces runtime health via GetStatus: safe_mode(+reason) and
ai_wallet_status/balance (ok|low|empty|unknown), including typed detection
of claw402 ErrInsufficientFunds instead of burying it in logs; safe-mode
state is mutex-guarded for API readers.
- Onboarding wallet endpoints switch to the cached, error-aware balance query
and report balance_status so the UI can tell an RPC outage from an empty
wallet.
A single-symbol QNT-USDC trader produced 0 candidate coins, 500 errors
from Hyperliquid, and "🚫 Dropped AI decision" warnings — the agent had
no market data to reason about, so it sat in `wait` forever. Three
chained bugs:
1. provider/hyperliquid/kline.go (IsXYZAsset / FormatCoinForAPI):
asset detection required the base symbol to appear in the hardcoded
StockPerpsSymbols / XYZOtherSymbols / display-alias lists. QNT, ARM,
and every other newly-listed Hyperliquid USDC perp wasn't in the
list, so the code routed them to the crypto path (CoinAnk) which
doesn't have them. Now the `-USDC` suffix and `xyz:` prefix are
trusted as definitive Hyperliquid signals — these tokens are
Hyperliquid-specific and new listings don't require a code change.
The hardcoded lists are kept as fallbacks for bare base symbols.
2. market/data_klines.go (getKlinesFromHyperliquid): the function
stripped the `xyz:` prefix before calling GetCandles, defeating
GetCandles's own FormatCoinForAPI logic. With the hardcoded list
missing the new ticker, FormatCoinForAPI couldn't re-add the prefix
and the request hit Hyperliquid's crypto perp endpoint — which
returns 500 for stock-only tickers. Pass the symbol through as-is.
3. trader/auto_trader_loop.go (filterDecisionsToStrategyUniverse): the
AI sometimes echoes a candidate as "QNTUSDC" / "QNT-USDC" / "QNTUSDT"
/ bare "QNT" instead of the canonical "xyz:QNT" we supplied. Strict
exact-match was dropping all of them. Added a base-level key
(strips xyz:, -USDC, -USDT, USDC, USDT, USD; normalizes display
aliases like ROBINHOOD → HOOD) and rewrites the matched decision's
symbol to the canonical form so the order pipeline downstream sees
the format it expects.
After this, a single-symbol stock trader fetches real K-line data from
Hyperliquid, the AI sees real candidates, and decisions get executed
on-chain instead of silently filtered.
- Track consecutive AI failures
- After 3 failures: activate safe mode (no new positions, close/hold only)
- Auto-deactivate when AI recovers
- Keep loop running in safe mode (retry each cycle)
- Log clearly: 🛡️ SAFE MODE ACTIVATED/DEACTIVATED