mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-06 20:41:14 +08:00
Major improvements: - Use period-level Sharpe ratio (range -2 to +2) instead of annualized - Save full user prompt in decision logs for debugging - Format complete market data (3m + 4h candles) for AI analysis - Prevent position stacking with duplicate position checks - Update Sharpe ratio interpretation thresholds Market data enhancements: - Display full technical indicators in user prompt - Include 3-minute and 4-hour timeframe data - Add OI (Open Interest) change and funding rate signals Risk control: - Block opening duplicate positions (same symbol + direction) - Suggest close action first before opening new position - Prevent margin usage from exceeding limits UI improvements: - Update multi-language translations - Refine AI learning dashboard display Co-Authored-By: tinkle-community <tinklefund@gmail.com>
614 lines
24 KiB
Go
614 lines
24 KiB
Go
package market
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"log"
|
||
"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"`
|
||
}
|
||
|
||
// 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 // 净空仓
|
||
}
|
||
|
||
// TradingContext 交易上下文(传递给AI的完整信息)
|
||
type TradingContext 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]*MarketData `json:"-"` // 不序列化,但内部使用
|
||
OITopDataMap map[string]*OITopData `json:"-"` // OI Top数据映射
|
||
Performance interface{} `json:"-"` // 历史表现分析(logger.PerformanceAnalysis)
|
||
}
|
||
|
||
// TradingDecision AI的交易决策
|
||
type TradingDecision 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"`
|
||
}
|
||
|
||
// AIFullDecision AI的完整决策(包含思维链)
|
||
type AIFullDecision struct {
|
||
UserPrompt string `json:"user_prompt"` // 发送给AI的输入prompt
|
||
CoTTrace string `json:"cot_trace"` // 思维链分析(AI输出)
|
||
Decisions []TradingDecision `json:"decisions"` // 具体决策列表
|
||
Timestamp time.Time `json:"timestamp"`
|
||
}
|
||
|
||
// GetFullTradingDecision 获取AI的完整交易决策(批量分析所有币种和持仓)
|
||
func GetFullTradingDecision(ctx *TradingContext) (*AIFullDecision, error) {
|
||
// 1. 为所有币种获取市场数据
|
||
if err := fetchMarketDataForContext(ctx); err != nil {
|
||
return nil, fmt.Errorf("获取市场数据失败: %w", err)
|
||
}
|
||
|
||
// 2. 构建 System Prompt(固定规则)和 User Prompt(动态数据)
|
||
systemPrompt := buildSystemPrompt(ctx.Account.TotalEquity)
|
||
userPrompt := buildUserPrompt(ctx)
|
||
|
||
// 3. 调用AI API(使用 system + user prompt)
|
||
aiResponse, err := callAIWithMessages(systemPrompt, userPrompt)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("调用AI API失败: %w", err)
|
||
}
|
||
|
||
// 4. 解析AI响应
|
||
decision, err := parseFullDecisionResponse(aiResponse, ctx.Account.TotalEquity)
|
||
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 *TradingContext) error {
|
||
ctx.MarketDataMap = make(map[string]*MarketData)
|
||
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 := GetMarketData(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 *TradingContext) int {
|
||
// 直接返回候选池的全部币种数量
|
||
// 因为候选池已经在 auto_trader.go 中筛选过了
|
||
// 固定分析前20个评分最高的币种(来自AI500)
|
||
return len(ctx.CandidateCoins)
|
||
}
|
||
|
||
// buildSystemPrompt 构建 System Prompt(固定规则,可缓存)
|
||
func buildSystemPrompt(accountEquity float64) string {
|
||
var sb strings.Builder
|
||
|
||
// 角色定义
|
||
sb.WriteString("你是专业的加密货币交易AI,在币安合约市场进行自主交易。\n\n")
|
||
sb.WriteString("**使命**: 最大化风险调整后收益(Sharpe Ratio)\n\n")
|
||
|
||
// 自我进化核心
|
||
sb.WriteString("## 🧬 自我进化机制\n")
|
||
sb.WriteString("每次调用你都会收到**夏普比率**作为你的业绩指标(周期级别,非年化):\n\n")
|
||
sb.WriteString("**夏普比率解读**(正常范围 -2 到 +2):\n")
|
||
sb.WriteString("- < -0.5:持续亏损 → 🔴 极度保守策略(减仓、收紧止损、减少持仓数)\n")
|
||
sb.WriteString("- -0.5 到 0:轻微亏损 → 🟡 优化策略(保守仓位、提高选币标准)\n")
|
||
sb.WriteString("- 0 到 0.7:正收益 → 🟢 维持/优化当前策略\n")
|
||
sb.WriteString("- > 0.7:优异表现 → 🟢 可适度扩大仓位\n\n")
|
||
|
||
// 仓位管理规则
|
||
sb.WriteString("## 仓位管理\n")
|
||
sb.WriteString("- 最多持有 **3个币种**(质量>数量)\n")
|
||
sb.WriteString(fmt.Sprintf("- 山寨币: %.0f-%.0f USDT/仓(推荐%.0f),杠杆20x\n",
|
||
accountEquity*0.8, accountEquity*1.5, accountEquity*1.2))
|
||
sb.WriteString(fmt.Sprintf("- BTC/ETH: %.0f-%.0f USDT/仓(推荐%.0f),杠杆50x\n",
|
||
accountEquity*3, accountEquity*10, accountEquity*5))
|
||
sb.WriteString("- 保证金使用率 ≤90%%\n")
|
||
sb.WriteString("- 风险回报比 ≥1:2\n\n")
|
||
|
||
// 决策流程
|
||
sb.WriteString("## 决策流程\n")
|
||
sb.WriteString("1. **检查夏普比率**:理解当前策略效果,根据夏普比率调整策略\n")
|
||
sb.WriteString("2. **评估持仓**:决定平仓/持有\n")
|
||
sb.WriteString("3. **寻找机会**:筛选候选币种\n")
|
||
sb.WriteString("4. **执行决策**:输出思维链和JSON决策\n\n")
|
||
|
||
// JSON 输出格式
|
||
sb.WriteString("## 输出格式\n\n")
|
||
sb.WriteString("**先输出思维链(纯文本),再输出JSON数组**\n\n")
|
||
sb.WriteString("JSON示例:\n")
|
||
sb.WriteString("```json\n")
|
||
sb.WriteString("[\n")
|
||
sb.WriteString(fmt.Sprintf(" {\"symbol\": \"BTCUSDT\", \"action\": \"open_long\", \"leverage\": 50, \"position_size_usd\": %.0f, \"stop_loss\": 92000, \"take_profit\": 98000, \"confidence\": 85, \"risk_usd\": 200, \"reasoning\": \"强势突破\"},\n", accountEquity*5))
|
||
sb.WriteString(" {\"symbol\": \"ETHUSDT\", \"action\": \"close_long\", \"reasoning\": \"止盈\"}\n")
|
||
sb.WriteString("]\n")
|
||
sb.WriteString("```\n\n")
|
||
sb.WriteString("**字段说明**:\n")
|
||
sb.WriteString("- `action`: open_long | open_short | close_long | close_short | hold | wait\n")
|
||
sb.WriteString("- `confidence`: 信心度0-100(必填,即使不确定也要给出)\n")
|
||
sb.WriteString("- `risk_usd`: 最大美元风险 = (entry_price - stop_loss) × quantity(开仓时必填)\n")
|
||
sb.WriteString("- 开仓时必填: leverage, position_size_usd, stop_loss, take_profit, confidence, risk_usd\n\n")
|
||
|
||
// DeepSeek/Qwen 特定优化
|
||
sb.WriteString("**提示**: 运用技术分析原理,趋势确认>指标信号,不要过度依赖单一指标\n")
|
||
|
||
return sb.String()
|
||
}
|
||
|
||
// buildUserPrompt 构建 User Prompt(动态数据)
|
||
func buildUserPrompt(ctx *TradingContext) 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 {
|
||
sb.WriteString(fmt.Sprintf("%d. %s %s | 入场价%.4f 当前价%.4f | 盈亏%+.2f%% | 杠杆%dx | 保证金%.0f | 强平价%.4f\n\n",
|
||
i+1, pos.Symbol, strings.ToUpper(pos.Side),
|
||
pos.EntryPrice, pos.MarkPrice, pos.UnrealizedPnLPct,
|
||
pos.Leverage, pos.MarginUsed, pos.LiquidationPrice))
|
||
|
||
// 使用FormatMarketData输出完整市场数据
|
||
if marketData, ok := ctx.MarketDataMap[pos.Symbol]; ok {
|
||
sb.WriteString(FormatMarketData(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(FormatMarketData(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) (*AIFullDecision, error) {
|
||
// 1. 提取思维链
|
||
cotTrace := extractCoTTrace(aiResponse)
|
||
|
||
// 2. 提取JSON决策列表
|
||
decisions, err := extractDecisions(aiResponse)
|
||
if err != nil {
|
||
return &AIFullDecision{
|
||
CoTTrace: cotTrace,
|
||
Decisions: []TradingDecision{},
|
||
}, fmt.Errorf("提取决策失败: %w\n\n=== AI思维链分析 ===\n%s", err, cotTrace)
|
||
}
|
||
|
||
// 3. 验证决策
|
||
if err := validateDecisions(decisions, accountEquity); err != nil {
|
||
return &AIFullDecision{
|
||
CoTTrace: cotTrace,
|
||
Decisions: decisions,
|
||
}, fmt.Errorf("决策验证失败: %w\n\n=== AI思维链分析 ===\n%s", err, cotTrace)
|
||
}
|
||
|
||
return &AIFullDecision{
|
||
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) ([]TradingDecision, 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 []TradingDecision
|
||
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 []TradingDecision, accountEquity float64) error {
|
||
for i, decision := range decisions {
|
||
if err := validateDecision(&decision, accountEquity); 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 *TradingDecision, accountEquity float64) 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 := 20 // 山寨币固定20倍
|
||
maxPositionValue := accountEquity * 1.5 // 山寨币最多1.5倍账户净值
|
||
if d.Symbol == "BTCUSDT" || d.Symbol == "ETHUSDT" {
|
||
maxLeverage = 50 // BTC和ETH固定50倍
|
||
maxPositionValue = accountEquity * 10 // BTC/ETH最多10倍账户净值
|
||
}
|
||
|
||
if d.Leverage <= 0 || d.Leverage > maxLeverage {
|
||
return fmt.Errorf("杠杆必须在1-%d之间(%s): %d", maxLeverage, d.Symbol, 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("做空时止损价必须大于止盈价")
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// interpretSharpeRatio 解释夏普比率的含义
|
||
// 注:这里是周期级别(非年化)的夏普比率,正常范围在 -2 到 +2
|
||
func interpretSharpeRatio(sharpe float64) string {
|
||
if sharpe < -0.5 {
|
||
return "持续亏损,策略需大幅调整"
|
||
} else if sharpe < 0 {
|
||
return "轻微亏损,需优化策略"
|
||
} else if sharpe < 0.3 {
|
||
return "正收益但波动大"
|
||
} else if sharpe < 0.7 {
|
||
return "良好表现"
|
||
} else if sharpe < 1.0 {
|
||
return "优秀表现"
|
||
} else {
|
||
return "卓越表现"
|
||
}
|
||
}
|
||
|
||
// getAdaptiveBehaviorRecommendation 根据夏普比率生成自适应行为建议
|
||
// 这是AI自我进化的核心:根据风险调整后收益动态调整交易策略
|
||
// 注:sharpe是周期级别(非年化),正常范围 -2 到 +2
|
||
func getAdaptiveBehaviorRecommendation(sharpe float64, accountEquity float64) string {
|
||
var sb strings.Builder
|
||
|
||
sb.WriteString("### 🎯 自适应行为建议(基于夏普比率)\n\n")
|
||
|
||
if sharpe < -0.5 {
|
||
// 🔴 持续亏损:需要极度保守
|
||
sb.WriteString("**⚠️ 警告:当前策略产生负收益,立即调整!**\n\n")
|
||
sb.WriteString("**策略调整**:\n")
|
||
sb.WriteString(fmt.Sprintf("- 仓位规模:**减半**(山寨币: %.0f USDT, BTC/ETH: %.0f USDT)\n",
|
||
accountEquity*0.6, accountEquity*2.5))
|
||
sb.WriteString("- 止损幅度:**收紧至-1%**(快速止损,保护本金)\n")
|
||
sb.WriteString("- 选币标准:**只做最高确定性**(信心度≥95%,风险回报比≥1:3)\n")
|
||
sb.WriteString("- 持仓数量:**最多1个**(极度精选)\n")
|
||
sb.WriteString("- 决策频率:**减少交易**(宁可不做,也不要乱做)\n\n")
|
||
sb.WriteString("**反思要点**:\n")
|
||
sb.WriteString("- 为什么之前的交易亏损?是选币问题还是时机问题?\n")
|
||
sb.WriteString("- 是否追涨杀跌?是否逆势交易?\n")
|
||
sb.WriteString("- 止损是否执行到位?\n\n")
|
||
|
||
} else if sharpe < 0 {
|
||
// 🟡 -0.5 到 0:轻微亏损
|
||
sb.WriteString("**状态:轻微亏损,需要优化策略**\n\n")
|
||
sb.WriteString("**策略调整**:\n")
|
||
sb.WriteString(fmt.Sprintf("- 仓位规模:**保守**(山寨币: %.0f USDT, BTC/ETH: %.0f USDT)\n",
|
||
accountEquity*0.8, accountEquity*3.5))
|
||
sb.WriteString("- 止损幅度:**收紧至-1.5%**\n")
|
||
sb.WriteString("- 选币标准:**提高阈值**(信心度≥80%,风险回报比≥1:2.5)\n")
|
||
sb.WriteString("- 持仓数量:**最多2个**\n")
|
||
sb.WriteString("- 重点改进:**找出亏损原因**,调整选币或时机\n\n")
|
||
|
||
} else if sharpe < 0.7 {
|
||
// 🟢 0-0.7:正收益但可继续优化
|
||
sb.WriteString("**状态:正收益但风险较高,需要优化**\n\n")
|
||
sb.WriteString("**策略调整**:\n")
|
||
sb.WriteString(fmt.Sprintf("- 仓位规模:**保守**(山寨币: %.0f USDT, BTC/ETH: %.0f USDT)\n",
|
||
accountEquity*0.8, accountEquity*3.5))
|
||
sb.WriteString("- 止损幅度:**收紧至-1.5%**\n")
|
||
sb.WriteString("- 选币标准:**提高阈值**(信心度≥80%,风险回报比≥1:2.5)\n")
|
||
sb.WriteString("- 持仓数量:**最多2个**\n")
|
||
sb.WriteString("- 重点改进:**减少亏损幅度**,提高止损执行力\n\n")
|
||
sb.WriteString("**优化方向**:\n")
|
||
sb.WriteString("- 避免冲动交易,等待更好的入场时机\n")
|
||
sb.WriteString("- 减少交易频率,提高单笔交易质量\n")
|
||
sb.WriteString("- 盈利时及时止盈,不要贪多\n\n")
|
||
|
||
} else if sharpe < 1.0 {
|
||
// 🟢 0.7-1.0:优秀表现
|
||
sb.WriteString("**状态:表现良好,继续保持当前策略**\n\n")
|
||
sb.WriteString("**策略调整**:\n")
|
||
sb.WriteString(fmt.Sprintf("- 仓位规模:**标准**(山寨币: %.0f USDT, BTC/ETH: %.0f USDT)\n",
|
||
accountEquity*1.2, accountEquity*5))
|
||
sb.WriteString("- 止损幅度:**-2%**(标准设置)\n")
|
||
sb.WriteString("- 选币标准:**正常**(信心度≥75%,风险回报比≥1:2)\n")
|
||
sb.WriteString("- 持仓数量:**最多3个**\n")
|
||
sb.WriteString("- 保持纪律:**严格执行止损止盈**\n\n")
|
||
sb.WriteString("**持续改进**:\n")
|
||
sb.WriteString("- 总结盈利交易的共性特征,复制成功模式\n")
|
||
sb.WriteString("- 分析亏损交易,避免重复错误\n")
|
||
sb.WriteString("- 保持冷静客观,不要因为短期盈利而冒进\n\n")
|
||
|
||
} else {
|
||
// 🟢 >1.0:卓越表现
|
||
sb.WriteString("**状态:卓越表现,策略非常有效!**\n\n")
|
||
sb.WriteString("**策略调整**:\n")
|
||
sb.WriteString(fmt.Sprintf("- 仓位规模:**可适度放大**(山寨币: %.0f USDT, BTC/ETH: %.0f USDT)\n",
|
||
accountEquity*1.5, accountEquity*6))
|
||
sb.WriteString("- 止损幅度:**-2%**(保持纪律,不要因为盈利而放松)\n")
|
||
sb.WriteString("- 选币标准:**正常**(信心度≥75%)\n")
|
||
sb.WriteString("- 持仓数量:**最多3个**\n")
|
||
sb.WriteString("- **核心原则:保持纪律,不要过度自信**\n\n")
|
||
sb.WriteString("**风险提示**:\n")
|
||
sb.WriteString("- 即使表现优异,也要保持风险管理纪律\n")
|
||
sb.WriteString("- 市场环境会变化,不要因短期成功而冒进\n")
|
||
sb.WriteString("- 继续严格执行止损,保护已有收益\n\n")
|
||
}
|
||
|
||
return sb.String()
|
||
}
|