diff --git a/agent/agent.go b/agent/agent.go index 12eca5bc..4c3a4fab 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -308,11 +308,19 @@ func (a *Agent) buildSystemPrompt(lang string) string { - 交易确认信息要清晰展示:品种、方向、数量、杠杆 - 提醒用户确认命令格式 +### 数据真实性规则(极其重要!) +- **持仓信息必须且只能通过 get_positions 工具获取**,绝对禁止编造持仓 +- **余额信息必须且只能通过 get_balance 工具获取**,绝对禁止编造余额 +- 如果用户问持仓但 get_positions 返回空,就说"当前没有持仓",不要编造 +- 如果工具返回 error(如未配置交易所),如实告知用户 +- **你不知道用户持有什么股票/币种,除非工具返回了数据** +- 查股票行情 ≠ 用户持有该股票。不要混淆"查价格"和"有持仓" + ## 行为准则 - 简洁、专业、有观点。不说废话。 - 用户问什么答什么,不要推销配置。 - 有实时数据时给具体价位,没有时给策略框架和思路。 -- **诚实是第一原则** — 不确定就说不确定,没数据就说没数据。 +- **诚实是第一原则** — 不确定就说不确定,没数据就说没数据。绝不编造。 - 用交易相关的 emoji 让回复更直观。 - 用中文回复。 @@ -355,6 +363,14 @@ You can call these tools to take action: - Show trade details clearly: symbol, direction, quantity, leverage - Remind user of the confirmation command format +### Data Truthfulness Rules (CRITICAL!) +- **Position data MUST come from get_positions tool only** — NEVER fabricate positions +- **Balance data MUST come from get_balance tool only** — NEVER fabricate balances +- If get_positions returns empty, say "no open positions" — do NOT make up holdings +- If a tool returns an error (e.g. no exchange configured), tell the user honestly +- **You do NOT know what the user holds unless a tool tells you** +- Checking a stock price ≠ user owns that stock. Never confuse "quote lookup" with "holding" + ## Behavior - Concise, professional, opinionated. No fluff. - Answer what's asked. Don't push setup. diff --git a/agent/brain.go b/agent/brain.go index 4325ddf3..b167f57d 100644 --- a/agent/brain.go +++ b/agent/brain.go @@ -15,12 +15,18 @@ import ( type Brain struct { agent *Agent logger *slog.Logger + http *http.Client stopCh chan struct{} recentSignals sync.Map // debounce } func NewBrain(agent *Agent, logger *slog.Logger) *Brain { - return &Brain{agent: agent, logger: logger, stopCh: make(chan struct{})} + return &Brain{ + agent: agent, + logger: logger, + http: &http.Client{Timeout: 15 * time.Second}, + stopCh: make(chan struct{}), + } } func (b *Brain) Stop() { close(b.stopCh) } @@ -57,10 +63,11 @@ func (b *Brain) StartNewsScan(interval time.Duration) { } func (b *Brain) scanNews(seen map[string]bool) { - resp, err := http.Get("https://min-api.cryptocompare.com/data/v2/news/?lang=EN&sortOrder=latest") + resp, err := b.http.Get("https://min-api.cryptocompare.com/data/v2/news/?lang=EN&sortOrder=latest") if err != nil { return } defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { return } var result struct { Data []struct { @@ -72,7 +79,7 @@ func (b *Brain) scanNews(seen map[string]bool) { PublishedOn int64 `json:"published_on"` } `json:"Data"` } - json.Unmarshal(body, &result) + if err := json.Unmarshal(body, &result); err != nil { return } bullish := []string{"surge", "rally", "bullish", "breakout", "ath", "pump", "adoption"} bearish := []string{"crash", "dump", "bearish", "sell-off", "plunge", "hack", "ban", "fraud"} @@ -129,12 +136,13 @@ func (b *Brain) sendBrief(hour int) { // Fetch BTC/ETH prices for the brief var btcPrice, ethPrice, btcChg, ethChg string for _, sym := range []string{"BTCUSDT", "ETHUSDT"} { - resp, err := http.Get(fmt.Sprintf("https://fapi.binance.com/fapi/v1/ticker/24hr?symbol=%s", sym)) + resp, err := b.http.Get(fmt.Sprintf("https://fapi.binance.com/fapi/v1/ticker/24hr?symbol=%s", sym)) if err != nil { continue } - body, _ := io.ReadAll(resp.Body) + body, readErr := io.ReadAll(resp.Body) resp.Body.Close() + if readErr != nil { continue } var t map[string]string - json.Unmarshal(body, &t) + if err := json.Unmarshal(body, &t); err != nil { continue } if sym == "BTCUSDT" { btcPrice = t["lastPrice"]; btcChg = t["priceChangePercent"] } if sym == "ETHUSDT" { ethPrice = t["lastPrice"]; ethChg = t["priceChangePercent"] } } diff --git a/agent/sentinel.go b/agent/sentinel.go index 5397696e..a5057c08 100644 --- a/agent/sentinel.go +++ b/agent/sentinel.go @@ -115,9 +115,10 @@ func (s *Sentinel) check(symbol string) { resp, err := s.http.Get(fmt.Sprintf("https://fapi.binance.com/fapi/v1/ticker/24hr?symbol=%s", symbol)) if err != nil { return } defer resp.Body.Close() - body, _ := io.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) + if err != nil { return } var t map[string]interface{} - json.Unmarshal(body, &t) + if err := json.Unmarshal(body, &t); err != nil { return } price, _ := strconv.ParseFloat(fmt.Sprint(t["lastPrice"]), 64) vol, _ := strconv.ParseFloat(fmt.Sprint(t["quoteVolume"]), 64) diff --git a/api/handler_trader.go b/api/handler_trader.go index d26d0edd..452cc2b8 100644 --- a/api/handler_trader.go +++ b/api/handler_trader.go @@ -247,7 +247,6 @@ func (s *Server) handleCreateTrader(c *gin.Context) { } // Create trader configuration (database entity) - logger.Infof("🔧 DEBUG: Starting to create trader config, ID=%s, Name=%s, AIModel=%s, Exchange=%s, StrategyID=%s", traderID, req.Name, req.AIModelID, req.ExchangeID, req.StrategyID) traderRecord := &store.Trader{ ID: traderID, UserID: userID, @@ -271,24 +270,18 @@ func (s *Server) handleCreateTrader(c *gin.Context) { } // Save to database - logger.Infof("🔧 DEBUG: Preparing to call CreateTrader") err = s.store.Trader().Create(traderRecord) if err != nil { logger.Infof("❌ Failed to create trader: %v", err) SafeInternalError(c, "Failed to create trader", err) return } - logger.Infof("🔧 DEBUG: CreateTrader succeeded") - // Immediately load new trader into TraderManager - logger.Infof("🔧 DEBUG: Preparing to call LoadUserTraders") err = s.traderManager.LoadUserTradersFromStore(s.store, userID) if err != nil { logger.Infof("⚠️ Failed to load user traders into memory: %v", err) // Don't return error here since trader was successfully created in database } - logger.Infof("🔧 DEBUG: LoadUserTraders completed") - logger.Infof("✓ Trader created successfully: %s (model: %s, exchange: %s)", req.Name, req.AIModelID, req.ExchangeID) c.JSON(http.StatusCreated, gin.H{ diff --git a/trader/hyperliquid/trader_account.go b/trader/hyperliquid/trader_account.go index f556455a..da4e0b63 100644 --- a/trader/hyperliquid/trader_account.go +++ b/trader/hyperliquid/trader_account.go @@ -46,26 +46,20 @@ func (t *HyperliquidTrader) GetBalance() (map[string]interface{}, error) { var accountValue, totalMarginUsed float64 var summaryType string var summary interface{} + _, _ = summaryType, summary if t.isCrossMargin { // Cross margin mode: use CrossMarginSummary accountValue, _ = strconv.ParseFloat(accountState.CrossMarginSummary.AccountValue, 64) totalMarginUsed, _ = strconv.ParseFloat(accountState.CrossMarginSummary.TotalMarginUsed, 64) summaryType = "CrossMarginSummary (cross margin)" - summary = accountState.CrossMarginSummary } else { // Isolated margin mode: use MarginSummary accountValue, _ = strconv.ParseFloat(accountState.MarginSummary.AccountValue, 64) totalMarginUsed, _ = strconv.ParseFloat(accountState.MarginSummary.TotalMarginUsed, 64) summaryType = "MarginSummary (isolated margin)" - summary = accountState.MarginSummary } - // Debug: Print complete summary structure returned by API - summaryJSON, _ := json.MarshalIndent(summary, " ", " ") - logger.Infof("🔍 [DEBUG] Hyperliquid API %s complete data:", summaryType) - logger.Infof("%s", string(summaryJSON)) - // Critical fix: Accumulate actual unrealized PnL from all positions totalUnrealizedPnl := 0.0 for _, assetPos := range accountState.AssetPositions { @@ -159,7 +153,7 @@ func (t *HyperliquidTrader) GetBalance() (map[string]interface{}, error) { walletBalanceWithoutUnrealized, totalUnrealizedPnl) logger.Infof(" • Perpetuals available balance: %.2f USDC", availableBalance) - logger.Infof(" • Margin used: %.2f USDC", totalMarginUsed) + logger.Infof(" • Margin used: %.2f USDC (source: %s)", totalMarginUsed, summaryType) logger.Infof(" • xyz dex equity: %.2f USDC (wallet %.2f + unrealized %.2f)", xyzAccountValue, xyzWalletBalance,