package decision import ( "encoding/json" "fmt" "log" "nofx/market" "nofx/mcp" "nofx/pool" "strings" "time" ) // PositionInfo 持仓信息 type PositionInfo struct { Symbol string `json:"symbol"` Side string `json:"side"` // "long" or "short" EntryPrice float64 `json:"entry_price"` MarkPrice float64 `json:"mark_price"` Quantity float64 `json:"quantity"` Leverage int `json:"leverage"` UnrealizedPnL float64 `json:"unrealized_pnl"` UnrealizedPnLPct float64 `json:"unrealized_pnl_pct"` LiquidationPrice float64 `json:"liquidation_price"` MarginUsed float64 `json:"margin_used"` UpdateTime int64 `json:"update_time"` // 持仓更新时间戳(毫秒) } // AccountInfo 账户信息 type AccountInfo struct { TotalEquity float64 `json:"total_equity"` // 账户净值 AvailableBalance float64 `json:"available_balance"` // 可用余额 TotalPnL float64 `json:"total_pnl"` // 总盈亏 TotalPnLPct float64 `json:"total_pnl_pct"` // 总盈亏百分比 MarginUsed float64 `json:"margin_used"` // 已用保证金 MarginUsedPct float64 `json:"margin_used_pct"` // 保证金使用率 PositionCount int `json:"position_count"` // 持仓数量 } // CandidateCoin 候选币种(来自币种池) type CandidateCoin struct { Symbol string `json:"symbol"` Sources []string `json:"sources"` // 来源: "ai500" 和/或 "oi_top" } // OITopData 持仓量增长Top数据(用于AI决策参考) type OITopData struct { Rank int // OI Top排名 OIDeltaPercent float64 // 持仓量变化百分比(1小时) OIDeltaValue float64 // 持仓量变化价值 PriceDeltaPercent float64 // 价格变化百分比 NetLong float64 // 净多仓 NetShort float64 // 净空仓 } // Context 交易上下文(传递给AI的完整信息) type Context struct { CurrentTime string `json:"current_time"` RuntimeMinutes int `json:"runtime_minutes"` CallCount int `json:"call_count"` Account AccountInfo `json:"account"` Positions []PositionInfo `json:"positions"` CandidateCoins []CandidateCoin `json:"candidate_coins"` MarketDataMap map[string]*market.Data `json:"-"` // 不序列化,但内部使用 OITopDataMap map[string]*OITopData `json:"-"` // OI Top数据映射 Performance interface{} `json:"-"` // 历史表现分析(logger.PerformanceAnalysis) BTCETHLeverage int `json:"-"` // BTC/ETH杠杆倍数(从配置读取) AltcoinLeverage int `json:"-"` // 山寨币杠杆倍数(从配置读取) } // Decision AI的交易决策 type Decision struct { Symbol string `json:"symbol"` Action string `json:"action"` // "open_long", "open_short", "close_long", "close_short", "hold", "wait" Leverage int `json:"leverage,omitempty"` PositionSizeUSD float64 `json:"position_size_usd,omitempty"` StopLoss float64 `json:"stop_loss,omitempty"` TakeProfit float64 `json:"take_profit,omitempty"` Confidence int `json:"confidence,omitempty"` // 信心度 (0-100) RiskUSD float64 `json:"risk_usd,omitempty"` // 最大美元风险 Reasoning string `json:"reasoning"` } // FullDecision AI的完整决策(包含思维链) type FullDecision struct { UserPrompt string `json:"user_prompt"` // 发送给AI的输入prompt CoTTrace string `json:"cot_trace"` // 思维链分析(AI输出) Decisions []Decision `json:"decisions"` // 具体决策列表 Timestamp time.Time `json:"timestamp"` } // GetFullDecision 获取AI的完整交易决策(批量分析所有币种和持仓) func GetFullDecision(ctx *Context, mcpClient *mcp.Client) (*FullDecision, error) { // 1. 为所有币种获取市场数据 if err := fetchMarketDataForContext(ctx); err != nil { return nil, fmt.Errorf("获取市场数据失败: %w", err) } // 2. 构建 System Prompt(固定规则)和 User Prompt(动态数据) systemPrompt := buildSystemPrompt(ctx.Account.TotalEquity, ctx.BTCETHLeverage, ctx.AltcoinLeverage) userPrompt := buildUserPrompt(ctx) // 3. 调用AI API(使用 system + user prompt) aiResponse, err := mcpClient.CallWithMessages(systemPrompt, userPrompt) if err != nil { return nil, fmt.Errorf("调用AI API失败: %w", err) } // 4. 解析AI响应 decision, err := parseFullDecisionResponse(aiResponse, ctx.Account.TotalEquity, ctx.BTCETHLeverage, ctx.AltcoinLeverage) if err != nil { return nil, fmt.Errorf("解析AI响应失败: %w", err) } decision.Timestamp = time.Now() decision.UserPrompt = userPrompt // 保存输入prompt return decision, nil } // fetchMarketDataForContext 为上下文中的所有币种获取市场数据和OI数据 func fetchMarketDataForContext(ctx *Context) error { ctx.MarketDataMap = make(map[string]*market.Data) ctx.OITopDataMap = make(map[string]*OITopData) // 收集所有需要获取数据的币种 symbolSet := make(map[string]bool) // 1. 优先获取持仓币种的数据(这是必须的) for _, pos := range ctx.Positions { symbolSet[pos.Symbol] = true } // 2. 候选币种数量根据账户状态动态调整 maxCandidates := calculateMaxCandidates(ctx) for i, coin := range ctx.CandidateCoins { if i >= maxCandidates { break } symbolSet[coin.Symbol] = true } // 并发获取市场数据 // 持仓币种集合(用于判断是否跳过OI检查) positionSymbols := make(map[string]bool) for _, pos := range ctx.Positions { positionSymbols[pos.Symbol] = true } for symbol := range symbolSet { data, err := market.Get(symbol) if err != nil { // 单个币种失败不影响整体,只记录错误 continue } // ⚠️ 流动性过滤:持仓价值低于15M USD的币种不做(多空都不做) // 持仓价值 = 持仓量 × 当前价格 // 但现有持仓必须保留(需要决策是否平仓) isExistingPosition := positionSymbols[symbol] if !isExistingPosition && data.OpenInterest != nil && data.CurrentPrice > 0 { // 计算持仓价值(USD)= 持仓量 × 当前价格 oiValue := data.OpenInterest.Latest * data.CurrentPrice oiValueInMillions := oiValue / 1_000_000 // 转换为百万美元单位 if oiValueInMillions < 15 { log.Printf("⚠️ %s 持仓价值过低(%.2fM USD < 15M),跳过此币种 [持仓量:%.0f × 价格:%.4f]", symbol, oiValueInMillions, data.OpenInterest.Latest, data.CurrentPrice) continue } } ctx.MarketDataMap[symbol] = data } // 加载OI Top数据(不影响主流程) oiPositions, err := pool.GetOITopPositions() if err == nil { for _, pos := range oiPositions { // 标准化符号匹配 symbol := pos.Symbol ctx.OITopDataMap[symbol] = &OITopData{ Rank: pos.Rank, OIDeltaPercent: pos.OIDeltaPercent, OIDeltaValue: pos.OIDeltaValue, PriceDeltaPercent: pos.PriceDeltaPercent, NetLong: pos.NetLong, NetShort: pos.NetShort, } } } return nil } // calculateMaxCandidates 根据账户状态计算需要分析的候选币种数量 func calculateMaxCandidates(ctx *Context) int { // 直接返回候选池的全部币种数量 // 因为候选池已经在 auto_trader.go 中筛选过了 // 固定分析前20个评分最高的币种(来自AI500) return len(ctx.CandidateCoins) } // buildSystemPrompt 构建 System Prompt(固定规则,可缓存) func buildSystemPrompt(accountEquity float64, btcEthLeverage, altcoinLeverage int) string { var sb strings.Builder // === 核心使命 === sb.WriteString("你是专业的加密货币交易AI,在币安合约市场进行自主交易。\n\n") sb.WriteString("# 🎯 核心目标\n\n") sb.WriteString("**最大化夏普比率(Sharpe Ratio)**\n\n") sb.WriteString("夏普比率 = 平均收益 / 收益波动率\n\n") sb.WriteString("**这意味着**:\n") sb.WriteString("- ✅ 高质量交易(高胜率、大盈亏比)→ 提升夏普\n") sb.WriteString("- ✅ 稳定收益、控制回撤 → 提升夏普\n") sb.WriteString("- ✅ 耐心持仓、让利润奔跑 → 提升夏普\n") sb.WriteString("- ❌ 频繁交易、小盈小亏 → 增加波动,严重降低夏普\n") sb.WriteString("- ❌ 过度交易、手续费损耗 → 直接亏损\n") sb.WriteString("- ❌ 过早平仓、频繁进出 → 错失大行情\n\n") sb.WriteString("**关键认知**: 系统每3分钟扫描一次,但不意味着每次都要交易!\n") sb.WriteString("大多数时候应该是 `wait` 或 `hold`,只在极佳机会时才开仓。\n\n") // === 硬约束(风险控制)=== sb.WriteString("# ⚖️ 硬约束(风险控制)\n\n") sb.WriteString("1. **风险回报比**: 必须 ≥ 1:3(冒1%风险,赚3%+收益)\n") sb.WriteString("2. **最多持仓**: 3个币种(质量>数量)\n") sb.WriteString(fmt.Sprintf("3. **单币仓位**: 山寨%.0f-%.0f U(%dx杠杆) | BTC/ETH %.0f-%.0f U(%dx杠杆)\n", accountEquity*0.8, accountEquity*1.5, altcoinLeverage, accountEquity*5, accountEquity*10, btcEthLeverage)) sb.WriteString("4. **保证金**: 总使用率 ≤ 90%\n\n") // === 做空激励 === sb.WriteString("# 📉 做多做空平衡\n\n") sb.WriteString("**重要**: 下跌趋势做空的利润 = 上涨趋势做多的利润\n\n") sb.WriteString("- 上涨趋势 → 做多\n") sb.WriteString("- 下跌趋势 → 做空\n") sb.WriteString("- 震荡市场 → 观望\n\n") sb.WriteString("**不要有做多偏见!做空是你的核心工具之一**\n\n") // === 交易频率认知 === sb.WriteString("# ⏱️ 交易频率认知\n\n") sb.WriteString("**量化标准**:\n") sb.WriteString("- 优秀交易员:每天2-4笔 = 每小时0.1-0.2笔\n") sb.WriteString("- 过度交易:每小时>2笔 = 严重问题\n") sb.WriteString("- 最佳节奏:开仓后持有至少30-60分钟\n\n") sb.WriteString("**自查**:\n") sb.WriteString("如果你发现自己每个周期都在交易 → 说明标准太低\n") sb.WriteString("如果你发现持仓<30分钟就平仓 → 说明太急躁\n\n") // === 开仓信号强度 === sb.WriteString("# 🎯 开仓标准(严格)\n\n") sb.WriteString("只在**强信号**时开仓,不确定就观望。\n\n") sb.WriteString("**你拥有的完整数据**:\n") sb.WriteString("- 📊 **原始序列**:3分钟价格序列(MidPrices数组) + 4小时K线序列\n") sb.WriteString("- 📈 **技术序列**:EMA20序列、MACD序列、RSI7序列、RSI14序列\n") sb.WriteString("- 💰 **资金序列**:成交量序列、持仓量(OI)序列、资金费率\n") sb.WriteString("- 🎯 **筛选标记**:AI500评分 / OI_Top排名(如果有标注)\n\n") sb.WriteString("**分析方法**(完全由你自主决定):\n") sb.WriteString("- 自由运用序列数据,你可以做但不限于趋势分析、形态识别、支撑阻力、技术阻力位、斐波那契、波动带计算\n") sb.WriteString("- 多维度交叉验证(价格+量+OI+指标+序列形态)\n") sb.WriteString("- 用你认为最有效的方法发现高确定性机会\n") sb.WriteString("- 综合信心度 ≥ 75 才开仓\n\n") sb.WriteString("**避免低质量信号**:\n") sb.WriteString("- 单一维度(只看一个指标)\n") sb.WriteString("- 相互矛盾(涨但量萎缩)\n") sb.WriteString("- 横盘震荡\n") sb.WriteString("- 刚平仓不久(<15分钟)\n\n") // === 夏普比率自我进化 === sb.WriteString("# 🧬 夏普比率自我进化\n\n") sb.WriteString("每次你会收到**夏普比率**作为绩效反馈(周期级别):\n\n") sb.WriteString("**夏普比率 < -0.5** (持续亏损):\n") sb.WriteString(" → 🛑 停止交易,连续观望至少6个周期(18分钟)\n") sb.WriteString(" → 🔍 深度反思:\n") sb.WriteString(" • 交易频率过高?(每小时>2次就是过度)\n") sb.WriteString(" • 持仓时间过短?(<30分钟就是过早平仓)\n") sb.WriteString(" • 信号强度不足?(信心度<75)\n") sb.WriteString(" • 是否在做空?(单边做多是错误的)\n\n") sb.WriteString("**夏普比率 -0.5 ~ 0** (轻微亏损):\n") sb.WriteString(" → ⚠️ 严格控制:只做信心度>80的交易\n") sb.WriteString(" → 减少交易频率:每小时最多1笔新开仓\n") sb.WriteString(" → 耐心持仓:至少持有30分钟以上\n\n") sb.WriteString("**夏普比率 0 ~ 0.7** (正收益):\n") sb.WriteString(" → ✅ 维持当前策略\n\n") sb.WriteString("**夏普比率 > 0.7** (优异表现):\n") sb.WriteString(" → 🚀 可适度扩大仓位\n\n") sb.WriteString("**关键**: 夏普比率是唯一指标,它会自然惩罚频繁交易和过度进出。\n\n") // === 决策流程 === sb.WriteString("# 📋 决策流程\n\n") sb.WriteString("1. **分析夏普比率**: 当前策略是否有效?需要调整吗?\n") sb.WriteString("2. **评估持仓**: 趋势是否改变?是否该止盈/止损?\n") sb.WriteString("3. **寻找新机会**: 有强信号吗?多空机会?\n") sb.WriteString("4. **输出决策**: 思维链分析 + JSON\n\n") // === 输出格式 === sb.WriteString("# 📤 输出格式\n\n") sb.WriteString("**第一步: 思维链(纯文本)**\n") sb.WriteString("简洁分析你的思考过程\n\n") sb.WriteString("**第二步: JSON决策数组**\n\n") sb.WriteString("```json\n[\n") sb.WriteString(fmt.Sprintf(" {\"symbol\": \"BTCUSDT\", \"action\": \"open_short\", \"leverage\": %d, \"position_size_usd\": %.0f, \"stop_loss\": 97000, \"take_profit\": 91000, \"confidence\": 85, \"risk_usd\": 300, \"reasoning\": \"下跌趋势+MACD死叉\"},\n", btcEthLeverage, accountEquity*5)) sb.WriteString(" {\"symbol\": \"ETHUSDT\", \"action\": \"close_long\", \"reasoning\": \"止盈离场\"}\n") sb.WriteString("]\n```\n\n") sb.WriteString("**字段说明**:\n") sb.WriteString("- `action`: open_long | open_short | close_long | close_short | hold | wait\n") sb.WriteString("- `confidence`: 0-100(开仓建议≥75)\n") sb.WriteString("- 开仓时必填: leverage, position_size_usd, stop_loss, take_profit, confidence, risk_usd, reasoning\n\n") // === 关键提醒 === sb.WriteString("---\n\n") sb.WriteString("**记住**: \n") sb.WriteString("- 目标是夏普比率,不是交易频率\n") sb.WriteString("- 做空 = 做多,都是赚钱工具\n") sb.WriteString("- 宁可错过,不做低质量交易\n") sb.WriteString("- 风险回报比1:3是底线\n") return sb.String() } // buildUserPrompt 构建 User Prompt(动态数据) func buildUserPrompt(ctx *Context) string { var sb strings.Builder // 系统状态 sb.WriteString(fmt.Sprintf("**时间**: %s | **周期**: #%d | **运行**: %d分钟\n\n", ctx.CurrentTime, ctx.CallCount, ctx.RuntimeMinutes)) // BTC 市场 if btcData, hasBTC := ctx.MarketDataMap["BTCUSDT"]; hasBTC { sb.WriteString(fmt.Sprintf("**BTC**: %.2f (1h: %+.2f%%, 4h: %+.2f%%) | MACD: %.4f | RSI: %.2f\n\n", btcData.CurrentPrice, btcData.PriceChange1h, btcData.PriceChange4h, btcData.CurrentMACD, btcData.CurrentRSI7)) } // 账户 sb.WriteString(fmt.Sprintf("**账户**: 净值%.2f | 余额%.2f (%.1f%%) | 盈亏%+.2f%% | 保证金%.1f%% | 持仓%d个\n\n", ctx.Account.TotalEquity, ctx.Account.AvailableBalance, (ctx.Account.AvailableBalance/ctx.Account.TotalEquity)*100, ctx.Account.TotalPnLPct, ctx.Account.MarginUsedPct, ctx.Account.PositionCount)) // 持仓(完整市场数据) if len(ctx.Positions) > 0 { sb.WriteString("## 当前持仓\n") for i, pos := range ctx.Positions { // 计算持仓时长 holdingDuration := "" if pos.UpdateTime > 0 { durationMs := time.Now().UnixMilli() - pos.UpdateTime durationMin := durationMs / (1000 * 60) // 转换为分钟 if durationMin < 60 { holdingDuration = fmt.Sprintf(" | 持仓时长%d分钟", durationMin) } else { durationHour := durationMin / 60 durationMinRemainder := durationMin % 60 holdingDuration = fmt.Sprintf(" | 持仓时长%d小时%d分钟", durationHour, durationMinRemainder) } } sb.WriteString(fmt.Sprintf("%d. %s %s | 入场价%.4f 当前价%.4f | 盈亏%+.2f%% | 杠杆%dx | 保证金%.0f | 强平价%.4f%s\n\n", i+1, pos.Symbol, strings.ToUpper(pos.Side), pos.EntryPrice, pos.MarkPrice, pos.UnrealizedPnLPct, pos.Leverage, pos.MarginUsed, pos.LiquidationPrice, holdingDuration)) // 使用FormatMarketData输出完整市场数据 if marketData, ok := ctx.MarketDataMap[pos.Symbol]; ok { sb.WriteString(market.Format(marketData)) sb.WriteString("\n") } } } else { sb.WriteString("**当前持仓**: 无\n\n") } // 候选币种(完整市场数据) sb.WriteString(fmt.Sprintf("## 候选币种 (%d个)\n\n", len(ctx.MarketDataMap))) displayedCount := 0 for _, coin := range ctx.CandidateCoins { marketData, hasData := ctx.MarketDataMap[coin.Symbol] if !hasData { continue } displayedCount++ sourceTags := "" if len(coin.Sources) > 1 { sourceTags = " (AI500+OI_Top双重信号)" } else if len(coin.Sources) == 1 && coin.Sources[0] == "oi_top" { sourceTags = " (OI_Top持仓增长)" } // 使用FormatMarketData输出完整市场数据 sb.WriteString(fmt.Sprintf("### %d. %s%s\n\n", displayedCount, coin.Symbol, sourceTags)) sb.WriteString(market.Format(marketData)) sb.WriteString("\n") } sb.WriteString("\n") // 夏普比率(直接传值,不要复杂格式化) if ctx.Performance != nil { // 直接从interface{}中提取SharpeRatio type PerformanceData struct { SharpeRatio float64 `json:"sharpe_ratio"` } var perfData PerformanceData if jsonData, err := json.Marshal(ctx.Performance); err == nil { if err := json.Unmarshal(jsonData, &perfData); err == nil { sb.WriteString(fmt.Sprintf("## 📊 夏普比率: %.2f\n\n", perfData.SharpeRatio)) } } } sb.WriteString("---\n\n") sb.WriteString("现在请分析并输出决策(思维链 + JSON)\n") return sb.String() } // parseFullDecisionResponse 解析AI的完整决策响应 func parseFullDecisionResponse(aiResponse string, accountEquity float64, btcEthLeverage, altcoinLeverage int) (*FullDecision, error) { // 1. 提取思维链 cotTrace := extractCoTTrace(aiResponse) // 2. 提取JSON决策列表 decisions, err := extractDecisions(aiResponse) if err != nil { return &FullDecision{ CoTTrace: cotTrace, Decisions: []Decision{}, }, fmt.Errorf("提取决策失败: %w\n\n=== AI思维链分析 ===\n%s", err, cotTrace) } // 3. 验证决策 if err := validateDecisions(decisions, accountEquity, btcEthLeverage, altcoinLeverage); err != nil { return &FullDecision{ CoTTrace: cotTrace, Decisions: decisions, }, fmt.Errorf("决策验证失败: %w\n\n=== AI思维链分析 ===\n%s", err, cotTrace) } return &FullDecision{ CoTTrace: cotTrace, Decisions: decisions, }, nil } // extractCoTTrace 提取思维链分析 func extractCoTTrace(response string) string { // 查找JSON数组的开始位置 jsonStart := strings.Index(response, "[") if jsonStart > 0 { // 思维链是JSON数组之前的内容 return strings.TrimSpace(response[:jsonStart]) } // 如果找不到JSON,整个响应都是思维链 return strings.TrimSpace(response) } // extractDecisions 提取JSON决策列表 func extractDecisions(response string) ([]Decision, error) { // 直接查找JSON数组 - 找第一个完整的JSON数组 arrayStart := strings.Index(response, "[") if arrayStart == -1 { return nil, fmt.Errorf("无法找到JSON数组起始") } // 从 [ 开始,匹配括号找到对应的 ] arrayEnd := findMatchingBracket(response, arrayStart) if arrayEnd == -1 { return nil, fmt.Errorf("无法找到JSON数组结束") } jsonContent := strings.TrimSpace(response[arrayStart : arrayEnd+1]) // 🔧 修复常见的JSON格式错误:缺少引号的字段值 // 匹配: "reasoning": 内容"} 或 "reasoning": 内容} (没有引号) // 修复为: "reasoning": "内容"} // 使用简单的字符串扫描而不是正则表达式 jsonContent = fixMissingQuotes(jsonContent) // 解析JSON var decisions []Decision if err := json.Unmarshal([]byte(jsonContent), &decisions); err != nil { return nil, fmt.Errorf("JSON解析失败: %w\nJSON内容: %s", err, jsonContent) } return decisions, nil } // fixMissingQuotes 替换中文引号为英文引号(避免输入法自动转换) func fixMissingQuotes(jsonStr string) string { jsonStr = strings.ReplaceAll(jsonStr, "\u201c", "\"") // " jsonStr = strings.ReplaceAll(jsonStr, "\u201d", "\"") // " jsonStr = strings.ReplaceAll(jsonStr, "\u2018", "'") // ' jsonStr = strings.ReplaceAll(jsonStr, "\u2019", "'") // ' return jsonStr } // validateDecisions 验证所有决策(需要账户信息和杠杆配置) func validateDecisions(decisions []Decision, accountEquity float64, btcEthLeverage, altcoinLeverage int) error { for i, decision := range decisions { if err := validateDecision(&decision, accountEquity, btcEthLeverage, altcoinLeverage); err != nil { return fmt.Errorf("决策 #%d 验证失败: %w", i+1, err) } } return nil } // findMatchingBracket 查找匹配的右括号 func findMatchingBracket(s string, start int) int { if start >= len(s) || s[start] != '[' { return -1 } depth := 0 for i := start; i < len(s); i++ { switch s[i] { case '[': depth++ case ']': depth-- if depth == 0 { return i } } } return -1 } // validateDecision 验证单个决策的有效性 func validateDecision(d *Decision, accountEquity float64, btcEthLeverage, altcoinLeverage int) error { // 验证action validActions := map[string]bool{ "open_long": true, "open_short": true, "close_long": true, "close_short": true, "hold": true, "wait": true, } if !validActions[d.Action] { return fmt.Errorf("无效的action: %s", d.Action) } // 开仓操作必须提供完整参数 if d.Action == "open_long" || d.Action == "open_short" { // 根据币种使用配置的杠杆上限 maxLeverage := altcoinLeverage // 山寨币使用配置的杠杆 maxPositionValue := accountEquity * 1.5 // 山寨币最多1.5倍账户净值 if d.Symbol == "BTCUSDT" || d.Symbol == "ETHUSDT" { maxLeverage = btcEthLeverage // BTC和ETH使用配置的杠杆 maxPositionValue = accountEquity * 10 // BTC/ETH最多10倍账户净值 } if d.Leverage <= 0 || d.Leverage > maxLeverage { return fmt.Errorf("杠杆必须在1-%d之间(%s,当前配置上限%d倍): %d", maxLeverage, d.Symbol, maxLeverage, d.Leverage) } if d.PositionSizeUSD <= 0 { return fmt.Errorf("仓位大小必须大于0: %.2f", d.PositionSizeUSD) } // 验证仓位价值上限(加1%容差以避免浮点数精度问题) tolerance := maxPositionValue * 0.01 // 1%容差 if d.PositionSizeUSD > maxPositionValue+tolerance { if d.Symbol == "BTCUSDT" || d.Symbol == "ETHUSDT" { return fmt.Errorf("BTC/ETH单币种仓位价值不能超过%.0f USDT(10倍账户净值),实际: %.0f", maxPositionValue, d.PositionSizeUSD) } else { return fmt.Errorf("山寨币单币种仓位价值不能超过%.0f USDT(1.5倍账户净值),实际: %.0f", maxPositionValue, d.PositionSizeUSD) } } if d.StopLoss <= 0 || d.TakeProfit <= 0 { return fmt.Errorf("止损和止盈必须大于0") } // 验证止损止盈的合理性 if d.Action == "open_long" { if d.StopLoss >= d.TakeProfit { return fmt.Errorf("做多时止损价必须小于止盈价") } } else { if d.StopLoss <= d.TakeProfit { return fmt.Errorf("做空时止损价必须大于止盈价") } } // 验证风险回报比(必须≥1:3) // 计算入场价(假设当前市价) var entryPrice float64 if d.Action == "open_long" { // 做多:入场价在止损和止盈之间 entryPrice = d.StopLoss + (d.TakeProfit-d.StopLoss)*0.2 // 假设在20%位置入场 } else { // 做空:入场价在止损和止盈之间 entryPrice = d.StopLoss - (d.StopLoss-d.TakeProfit)*0.2 // 假设在20%位置入场 } var riskPercent, rewardPercent, riskRewardRatio float64 if d.Action == "open_long" { riskPercent = (entryPrice - d.StopLoss) / entryPrice * 100 rewardPercent = (d.TakeProfit - entryPrice) / entryPrice * 100 if riskPercent > 0 { riskRewardRatio = rewardPercent / riskPercent } } else { riskPercent = (d.StopLoss - entryPrice) / entryPrice * 100 rewardPercent = (entryPrice - d.TakeProfit) / entryPrice * 100 if riskPercent > 0 { riskRewardRatio = rewardPercent / riskPercent } } // 硬约束:风险回报比必须≥3.0 if riskRewardRatio < 3.0 { return fmt.Errorf("风险回报比过低(%.2f:1),必须≥3.0:1 [风险:%.2f%% 收益:%.2f%%] [止损:%.2f 止盈:%.2f]", riskRewardRatio, riskPercent, rewardPercent, d.StopLoss, d.TakeProfit) } } return nil }