From e4adafa36433ebee482c87b132b12625f5f9a773 Mon Sep 17 00:00:00 2001 From: tinkle-community Date: Fri, 29 May 2026 22:13:51 +0800 Subject: [PATCH] feat(web): quick-trade button actually trades - auto-start + honest status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The lightning button on the symbol panel was the single biggest "agent does nothing" complaint: it created a trader and a strategy via direct REST calls, then handed the user a hardcoded reply that read "我没有自动启动实盘交易。请到 Traders 面板确认风控后手动 Start" — i.e. the chat bot openly admitted it bypassed the agent and refused to do the work the user had clearly asked for. - web/src/lib/hyperliquidQuickTrade.ts: after createStrategy + createTrader (or finding an existing trader), call POST /api/traders/:id/start immediately. Report `started`, `reusedTrader`, and an optional `startError` so the chat reply can be honest about what happened — created vs reused, running vs failed, and why. - web/src/pages/AgentChatPage.tsx: replace the canned "please start manually" reply with one that reflects reality. Success path shows the symbol, strategy, 5-min scan interval, and how to halt it via chat. Failure path surfaces the actual start error and tells the user the trader exists but is not running. - web/src/lib/hyperliquidQuickTrade.ts: per-symbol prompt now routes on category. Stocks (category="stock") get a long-only, momentum- seeking prompt — break of high, volume spike, support reclaim, sector catalyst — because shorting individual US equities through the agent is rarely what the user wants. Crypto stays bidirectional but disciplined. The trader-level custom_prompt is rewritten in the same style and explicitly forbids rotating to other symbols. --- web/src/lib/hyperliquidQuickTrade.ts | 105 ++++++++++++++++++++++----- web/src/pages/AgentChatPage.tsx | 39 +++++++++- 2 files changed, 123 insertions(+), 21 deletions(-) diff --git a/web/src/lib/hyperliquidQuickTrade.ts b/web/src/lib/hyperliquidQuickTrade.ts index b15ef8e0..cf177a72 100644 --- a/web/src/lib/hyperliquidQuickTrade.ts +++ b/web/src/lib/hyperliquidQuickTrade.ts @@ -10,6 +10,10 @@ export interface QuickTradeResult { symbol: string display: string reusedTrader: boolean + /** Whether the trader was successfully started after creation. */ + started: boolean + /** Set when start failed — the trader exists but is NOT running. */ + startError?: string } function compactSymbolName(symbol: string) { @@ -27,7 +31,30 @@ function pickHyperliquidExchange(exchanges: Exchange[]) { }) } -function buildSingleSymbolConfig(base: StrategyConfig, symbol: string, language: 'zh' | 'en'): StrategyConfig { +// Returns the custom prompt for the quick-create flow. Stocks default to a +// long-biased, momentum-seeking trader because the Hyperliquid US-stock +// products are designed for one-directional exposure and shorting individual +// equities through the agent has rarely been what the user actually wanted. +// Crypto stays bidirectional but conservative. +function buildQuickCreatePrompt(symbol: string, category: string | undefined, language: 'zh' | 'en'): string { + const isStock = (category || '').toLowerCase() === 'stock' + if (isStock) { + return language === 'zh' + ? `只交易 Hyperliquid USDC 永续合约 ${symbol} (美股)。\n\n核心策略: 直接做多, 不做空。\n- 出现以下任一情形主动开多: 突破前期高点、放量上涨、关键支撑位回踩反弹、强势板块同步走强、宏观/财报/事件利好。\n- 不要在没有任何看多依据时硬等"完美进场点"。轻仓试错优于错过趋势。\n- 单标的策略, 不切换到其他股票。\n- 风控: 严格止损 (1-3% 单笔), 利润目标至少 2:1, 单笔不超过权益的 25%, 默认 2-3 倍杠杆, 切勿满仓。\n- 每次决策前必须先调 get_balance, get_positions, get_market_snapshot 拿到实时数据。` + : `Trade only the Hyperliquid USDC perpetual market ${symbol} (US equity).\n\nCore strategy: long-only — do not short.\n- Open longs proactively on any of these: breakout above prior high, volume expansion on up bars, pullback to a key support that holds, strong sector tape, macro/earnings/news catalysts.\n- Don't wait endlessly for the "perfect entry" — a small probing long beats missing a trend.\n- Single-symbol; do not rotate into other stocks.\n- Risk: stop-loss 1-3% per trade, profit target ≥ 2:1, max 25% of equity per trade, default 2-3x leverage, never go all-in.\n- Always call get_balance, get_positions, get_market_snapshot before deciding.` + } + // Crypto / other → keep bidirectional but cautious. + return language === 'zh' + ? `只交易 Hyperliquid USDC 永续合约 ${symbol}。\n\n核心策略: 多空双向, 谨慎进场。\n- 每次决策前必须调 get_balance, get_positions, get_market_snapshot, 拿到实时价格、成交量、资金费率、持仓量。\n- 趋势明确 + 成交量配合 + 风险回报比 ≥ 2:1 才开仓; 模糊就空仓等。\n- 风控: 严格止损 (1-3% 单笔), 单笔不超过权益的 25%, 默认 3 倍杠杆, 切勿满仓。\n- 单标的策略, 不切换到其他币种。` + : `Trade only the Hyperliquid USDC perpetual market ${symbol}.\n\nCore strategy: bidirectional but disciplined.\n- Always call get_balance, get_positions, get_market_snapshot first for live price, volume, funding, and OI.\n- Open positions only when trend, volume, and a ≥ 2:1 risk/reward all line up; otherwise stay flat.\n- Risk: stop-loss 1-3% per trade, max 25% of equity per trade, default 3x leverage, never go all-in.\n- Single-symbol; do not rotate.` +} + +function buildSingleSymbolConfig( + base: StrategyConfig, + symbol: string, + category: string | undefined, + language: 'zh' | 'en' +): StrategyConfig { const staticCoinSource = { source_type: 'static' as const, static_coins: [symbol], @@ -38,10 +65,7 @@ function buildSingleSymbolConfig(base: StrategyConfig, symbol: string, language: use_hyper_all: false, use_hyper_main: false, } - const customPrompt = - language === 'zh' - ? `只交易 Hyperliquid USDC 永续合约 ${symbol}。每次决策必须先检查账户余额、现有持仓、最新价格、趋势、成交量、资金费率和风险限制。没有明确优势时保持观望。单标的策略,不要切换到其他币种。` - : `Trade only the Hyperliquid USDC perpetual market ${symbol}. Before every decision, check balance, current positions, latest price, trend, volume, funding, and risk limits. Stay flat when there is no clear edge. Single-symbol strategy; do not switch to other symbols.` + const customPrompt = buildQuickCreatePrompt(symbol, category, language) return { ...base, @@ -61,11 +85,15 @@ function buildSingleSymbolConfig(base: StrategyConfig, symbol: string, language: } export async function createHyperliquidQuickTrader( - symbolInput: MarketSymbol | { symbol: string; display?: string }, + symbolInput: MarketSymbol | { symbol: string; display?: string; category?: string }, language: 'zh' | 'en' ): Promise { const symbol = symbolInput.symbol const display = symbolInput.display || symbol + // Category drives the strategy bias: stocks default to long-only, crypto + // stays bidirectional. Passed through from the panel button which already + // knows whether each row is a stock or a crypto perpetual. + const category = (symbolInput as { category?: string }).category const compact = compactSymbolName(display) const traderName = `HL ${compact} Quick`.slice(0, 50) const strategyName = `HL ${compact} Strategy`.slice(0, 50) @@ -93,31 +121,59 @@ export async function createHyperliquidQuickTrader( ) if (existingTrader) { const existing = existingTrader as any + const existingId = existing.trader_id || existing.id + const wasRunning = Boolean(existing.is_running) + let started = wasRunning + let startError: string | undefined + if (!wasRunning) { + try { + await api.startTrader(existingId) + started = true + } catch (err: any) { + startError = err?.message || String(err) + } + } return { - traderId: existing.trader_id || existing.id, + traderId: existingId, traderName: existing.trader_name || existing.name || traderName, strategyId: existing.strategy_id || '', strategyName, symbol, display, reusedTrader: true, + started, + startError, } } let strategy = strategies.find((s: any) => String(s.name || '').toLowerCase() === strategyName.toLowerCase()) as any if (!strategy?.id) { const defaultConfig = await api.getDefaultStrategyConfig() - const config = buildSingleSymbolConfig(defaultConfig, symbol, language) + const config = buildSingleSymbolConfig(defaultConfig, symbol, category, language) + const isStock = (category || '').toLowerCase() === 'stock' + const description = language === 'zh' + ? isStock + ? `Hyperliquid ${display} (美股) 单标的快速做多策略 — 主动捕捉突破、放量、回踩反弹。` + : `Hyperliquid ${display} 单标的快速交易策略 — 多空双向, 等趋势 + 量能确认。` + : isStock + ? `Hyperliquid ${display} single-symbol long-only momentum strategy.` + : `Hyperliquid ${display} single-symbol bidirectional trading strategy.` strategy = await api.createStrategy({ name: strategyName, - description: - language === 'zh' - ? `Hyperliquid ${display} 单标的快速交易策略。` - : `Hyperliquid ${display} single-symbol quick trading strategy.`, + description, config, } as any) } + const isStock = (category || '').toLowerCase() === 'stock' + const traderPrompt = language === 'zh' + ? isStock + ? `固定只交易 Hyperliquid ${symbol} (美股), 单向做多, 不做空。\n关注: 突破、放量、回踩反弹、宏观/财报利好。\n禁: 切换标的、做空、满仓、无止损。` + : `固定只交易 Hyperliquid ${symbol}, 多空双向, 不要扩展到其他标的。每次决策前先看余额、仓位、最新价格。` + : isStock + ? `Only trade Hyperliquid ${symbol} (US stock), long-only, no shorting.\nWatch for: breakouts, volume spikes, support reclaims, macro/earnings catalysts.\nForbidden: rotating to other symbols, shorting, going all-in, trading without a stop.` + : `Only trade Hyperliquid ${symbol}; bidirectional, do not expand to other symbols. Re-check balance, positions, and live price before every decision.` + const trader = await api.createTrader({ name: traderName, ai_model_id: model.id, @@ -126,19 +182,34 @@ export async function createHyperliquidQuickTrader( scan_interval_minutes: 5, trading_symbols: symbol, show_in_competition: false, - custom_prompt: - language === 'zh' - ? `固定只交易 Hyperliquid ${symbol},不要扩展到其他标的。启动前再次检查余额、仓位和风险。` - : `Only trade Hyperliquid ${symbol}; do not expand to other symbols. Re-check balance, positions, and risk before starting.`, + custom_prompt: traderPrompt, }) + const traderId = trader.trader_id || (trader as any).id + + // The whole point of the ⚡ button is "do it now". After creating the trader + // we immediately start it; the previous behavior of stopping at "created, + // please start manually" was the single biggest source of the "agent does + // nothing" complaint. If start fails (e.g. wallet not funded yet), we + // report it so the chat reply can be honest about the state. + let started = false + let startError: string | undefined + try { + await api.startTrader(traderId) + started = true + } catch (err: any) { + startError = err?.message || String(err) + } + return { - traderId: trader.trader_id || (trader as any).id, + traderId, traderName: trader.trader_name || (trader as any).name || traderName, strategyId: strategy.id, strategyName, symbol, display, reusedTrader: false, + started, + startError, } } diff --git a/web/src/pages/AgentChatPage.tsx b/web/src/pages/AgentChatPage.tsx index 0ad2336a..6bf2cb3a 100644 --- a/web/src/pages/AgentChatPage.tsx +++ b/web/src/pages/AgentChatPage.tsx @@ -609,6 +609,40 @@ export function AgentChatPage() { ) try { const result = await createHyperliquidQuickTrader(symbol, language === 'zh' ? 'zh' : 'en') + + // The reply tells the truth about what happened: created OR reused, AND + // whether the trader is now actually running. If start failed we say so + // explicitly so the user knows there's still work to do — but we no + // longer pretend the user has to "go push a button" when we could've + // done it ourselves. + const isZh = language === 'zh' + let text: string + if (result.started) { + const verb = isZh + ? result.reusedTrader + ? '已复用并启动' + : '已创建并启动' + : result.reusedTrader + ? 'Reused and started' + : 'Created and started' + text = isZh + ? `${verb} Hyperliquid ${result.display} 单标的 Trader: ${result.traderName}\n\n• 策略: ${result.strategyName}\n• 标的: ${result.display}\n• 扫描间隔: 每 5 分钟\n\nAgent 已在工作。要看实时持仓在 Dashboard, 想停掉随时回这里说"停掉 ${result.traderName}"。` + : `${verb} Hyperliquid ${result.display} single-symbol trader: ${result.traderName}\n\n• Strategy: ${result.strategyName}\n• Symbol: ${result.display}\n• Scan interval: every 5 min\n\nThe agent is live. Watch positions in Dashboard, or tell me "stop ${result.traderName}" to halt it.` + } else { + const verb = isZh + ? result.reusedTrader + ? '已找到 Trader' + : '已创建 Trader' + : result.reusedTrader + ? 'Found existing trader' + : 'Created trader' + const reason = result.startError ? `\n\n启动失败原因: ${result.startError}` : '' + const reasonEn = result.startError ? `\n\nStart failed: ${result.startError}` : '' + text = isZh + ? `${verb}: ${result.traderName}\n\n• 策略: ${result.strategyName}\n• 标的: ${result.display}${reason}\n\n需要先解决启动问题, 我才能让它跑起来。` + : `${verb}: ${result.traderName}\n\n• Strategy: ${result.strategyName}\n• Symbol: ${result.display}${reasonEn}\n\nResolve the start error before the trader can run.` + } + patchMessagesInStore( (prev) => prev.map((m) => @@ -617,10 +651,7 @@ export function AgentChatPage() { ...m, streaming: false, time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), - text: - language === 'zh' - ? `${result.reusedTrader ? '已找到并复用' : '已创建'} Hyperliquid ${result.display} 单标的 Trader:${result.traderName}\n\n策略:${result.strategyName}\n标的:${result.display}\n\n我没有自动启动实盘交易。请到 Traders 面板确认风控后手动 Start。` - : `${result.reusedTrader ? 'Reused existing' : 'Created'} Hyperliquid ${result.display} single-symbol trader: ${result.traderName}\n\nStrategy: ${result.strategyName}\nSymbol: ${result.display}\n\nLive trading was not auto-started. Review risk controls in Traders, then start manually.`, + text, } : m ),