fix(decision+trader): limit candidate coins & fix news collection

## Changes

### 1. decision/engine.go
- Fix calculateMaxCandidates to enforce proper limits (was returning all candidates)
- Dynamic limits based on position count:
  * 0 positions: max 30 candidates
  * 1 position: max 25 candidates
  * 2 positions: max 20 candidates
  * 3+ positions: max 15 candidates
- Prevents Prompt bloat when users configure many coins

### 2. trader/auto_trader.go
- Fix news collection to use actual positions + candidates (was hardcoded to BTC only)
- Add extractNewsSymbols() helper function
- Collect news for:
  * All current positions (highest priority)
  * Top 5 candidate coins
  * Always include BTC (market indicator)
  * Max 10 coins total (avoid excessive API calls)
- Properly convert symbols for news API (lowercase, remove USDT suffix)

## Impact
- Prevents excessive market data fetching
- Makes news feature actually useful (was only fetching BTC news)
- Better resource utilization

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
ZhouYongyou
2025-11-05 00:23:04 +08:00
parent aa63298532
commit f1e981b207
2 changed files with 80 additions and 8 deletions

View File

@@ -222,10 +222,31 @@ func fetchMarketDataForContext(ctx *Context) error {
// calculateMaxCandidates 根据账户状态计算需要分析的候选币种数量
func calculateMaxCandidates(ctx *Context) int {
// 直接返回候选池的全部币种数量
// 因为候选池已经在 auto_trader.go 中筛选过了
// 固定分析前20个评分最高的币种来自AI500
return len(ctx.CandidateCoins)
// ⚠️ 重要:限制候选币种数量,避免 Prompt 过大
// 根据持仓数量动态调整:持仓越少,可以分析更多候选币
const (
maxCandidatesWhenEmpty = 30 // 无持仓时最多分析30个候选币
maxCandidatesWhenHolding1 = 25 // 持仓1个时最多分析25个候选币
maxCandidatesWhenHolding2 = 20 // 持仓2个时最多分析20个候选币
maxCandidatesWhenHolding3 = 15 // 持仓3个时最多分析15个候选币避免 Prompt 过大)
)
positionCount := len(ctx.Positions)
var maxCandidates int
switch positionCount {
case 0:
maxCandidates = maxCandidatesWhenEmpty
case 1:
maxCandidates = maxCandidatesWhenHolding1
case 2:
maxCandidates = maxCandidatesWhenHolding2
default: // 3+ 持仓
maxCandidates = maxCandidatesWhenHolding3
}
// 返回实际候选币数量和上限中的较小值
return min(len(ctx.CandidateCoins), maxCandidates)
}
// buildSystemPromptWithCustom 构建包含自定义内容的 System Prompt

View File

@@ -761,20 +761,28 @@ func (at *AutoTrader) buildTradingContext() (*decision.Context, error) {
performance = nil
}
// 6.提取新闻内容
// 6. 提取新闻内容(根据持仓和候选币种动态收集)
newsItem := make(map[string][]news.NewsItem)
for _, newspro := range at.newsProcessor {
// TODO: 此出是为后续扩展考虑,当前随意给了个值占位
newsMap, err := newspro.FetchNews([]string{"btc"}, 100)
// 收集需要新闻的币种(持仓 + 候选币前几个)
newsSymbols := at.extractNewsSymbols(positionInfos, candidateCoins)
if len(newsSymbols) == 0 {
log.Printf("⚠️ 没有需要收集新闻的币种,跳过新闻收集")
continue
}
newsMap, err := newspro.FetchNews(newsSymbols, 100)
if err != nil {
log.Printf("⚠️ 获取新闻内容失败: %v", err)
continue
}
for symbol, value := range newsMap {
newsItem[symbol] = append(newsItem[symbol], value...)
}
log.Printf("📰 收集了 %d 个币种的新闻: %v", len(newsSymbols), newsSymbols)
}
// 7. 构建上下文
@@ -1583,6 +1591,49 @@ func normalizeSymbol(symbol string) string {
return symbol
}
// extractNewsSymbols 提取需要收集新闻的币种(持仓 + 候选币前几个 + BTC
func (at *AutoTrader) extractNewsSymbols(positions []decision.PositionInfo, candidates []decision.CandidateCoin) []string {
const (
maxNewsSymbols = 10 // 最多收集10个币种的新闻避免请求过多
maxCandidatesForNews = 5 // 从候选币中取前5个
)
symbolSet := make(map[string]bool)
result := make([]string, 0, maxNewsSymbols)
// 1. 总是包含 BTC市场风向标
symbolSet["btc"] = true
result = append(result, "btc")
// 2. 添加所有持仓币种(这些是最重要的)
for _, pos := range positions {
// 转换为新闻 API 格式(小写,移除 USDT 后缀)
baseSymbol := strings.ToLower(strings.TrimSuffix(pos.Symbol, "USDT"))
if !symbolSet[baseSymbol] && len(result) < maxNewsSymbols {
symbolSet[baseSymbol] = true
result = append(result, baseSymbol)
}
}
// 3. 添加候选币种(前几个,按优先级)
for i, coin := range candidates {
if i >= maxCandidatesForNews {
break
}
if len(result) >= maxNewsSymbols {
break
}
baseSymbol := strings.ToLower(strings.TrimSuffix(coin.Symbol, "USDT"))
if !symbolSet[baseSymbol] {
symbolSet[baseSymbol] = true
result = append(result, baseSymbol)
}
}
return result
}
// detectAutoClosedPositions 检测自动平仓的持仓(止损/止盈触发)
func (at *AutoTrader) detectAutoClosedPositions(currentPositions []decision.PositionInfo) []logger.DecisionAction {
var autoClosedActions []logger.DecisionAction