Dev backtest (#1134)

This commit is contained in:
Rick
2025-11-28 21:34:27 +08:00
committed by GitHub
parent 64a5734011
commit 7eebb4e218
39 changed files with 9293 additions and 125 deletions

View File

@@ -74,17 +74,19 @@ type OITopData struct {
// 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:"-"` // 山寨币杠杆倍数(从配置读取
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"`
PromptVariant string `json:"prompt_variant,omitempty"`
MarketDataMap map[string]*market.Data `json:"-"` // 不序列化,但内部使用
MultiTFMarket map[string]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的交易决策
@@ -127,13 +129,30 @@ func GetFullDecision(ctx *Context, mcpClient mcp.AIClient) (*FullDecision, error
// GetFullDecisionWithCustomPrompt 获取AI的完整交易决策支持自定义prompt和模板选择
func GetFullDecisionWithCustomPrompt(ctx *Context, mcpClient mcp.AIClient, customPrompt string, overrideBase bool, templateName string) (*FullDecision, error) {
// 1. 为所有币种获取市场数据
if err := fetchMarketDataForContext(ctx); err != nil {
return nil, fmt.Errorf("获取市场数据失败: %w", err)
if ctx == nil {
return nil, fmt.Errorf("context is nil")
}
// 1. 为所有币种获取市场数据(若上层已提供,则无需重复拉取)
if len(ctx.MarketDataMap) == 0 {
if err := fetchMarketDataForContext(ctx); err != nil {
return nil, fmt.Errorf("获取市场数据失败: %w", err)
}
} else if ctx.OITopDataMap == nil {
// 确保 OI 数据映射已初始化,避免后续访问空指针
ctx.OITopDataMap = make(map[string]*OITopData)
}
// 2. 构建 System Prompt固定规则和 User Prompt动态数据
systemPrompt := buildSystemPromptWithCustom(ctx.Account.TotalEquity, ctx.BTCETHLeverage, ctx.AltcoinLeverage, customPrompt, overrideBase, templateName)
systemPrompt := buildSystemPromptWithCustom(
ctx.Account.TotalEquity,
ctx.BTCETHLeverage,
ctx.AltcoinLeverage,
customPrompt,
overrideBase,
templateName,
ctx.PromptVariant,
)
userPrompt := buildUserPrompt(ctx)
// 3. 调用AI API使用 system + user prompt
@@ -272,14 +291,14 @@ func calculateMaxCandidates(ctx *Context) int {
}
// buildSystemPromptWithCustom 构建包含自定义内容的 System Prompt
func buildSystemPromptWithCustom(accountEquity float64, btcEthLeverage, altcoinLeverage int, customPrompt string, overrideBase bool, templateName string) string {
func buildSystemPromptWithCustom(accountEquity float64, btcEthLeverage, altcoinLeverage int, customPrompt string, overrideBase bool, templateName string, variant string) string {
// 如果覆盖基础prompt且有自定义prompt只使用自定义prompt
if overrideBase && customPrompt != "" {
return customPrompt
}
// 获取基础prompt使用指定的模板
basePrompt := buildSystemPrompt(accountEquity, btcEthLeverage, altcoinLeverage, templateName)
basePrompt := buildSystemPrompt(accountEquity, btcEthLeverage, altcoinLeverage, templateName, variant)
// 如果没有自定义prompt直接返回基础prompt
if customPrompt == "" {
@@ -299,7 +318,7 @@ func buildSystemPromptWithCustom(accountEquity float64, btcEthLeverage, altcoinL
}
// buildSystemPrompt 构建 System Prompt使用模板+动态部分)
func buildSystemPrompt(accountEquity float64, btcEthLeverage, altcoinLeverage int, templateName string) string {
func buildSystemPrompt(accountEquity float64, btcEthLeverage, altcoinLeverage int, templateName string, variant string) string {
var sb strings.Builder
// 1. 加载提示词模板(核心交易策略部分)
@@ -325,17 +344,56 @@ func buildSystemPrompt(accountEquity float64, btcEthLeverage, altcoinLeverage in
sb.WriteString("\n\n")
}
// 2. 硬约束(风险控制)- 动态生成
// 2. 交易模式变体
switch strings.ToLower(strings.TrimSpace(variant)) {
case "aggressive":
sb.WriteString("## 模式Aggressive进攻型\n- 优先捕捉趋势突破可在信心度≥70时分批建仓\n- 允许更高仓位,但须严格设置止损并说明盈亏比\n\n")
case "conservative":
sb.WriteString("## 模式Conservative稳健型\n- 仅在多重信号共振时开仓\n- 优先保留现金,连续亏损必须暂停多个周期\n\n")
case "scalping":
sb.WriteString("## 模式Scalping剥头皮\n- 聚焦短周期动量,目标收益较小但要求迅速\n- 若价格两根bar内未按预期运行立即减仓或止损\n\n")
}
// 3. 硬约束(风险控制)
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 | BTC/ETH %.0f-%.0f U\n",
accountEquity*0.8, accountEquity*1.5, accountEquity*5, accountEquity*10))
sb.WriteString(fmt.Sprintf("4. 杠杆限制: **山寨币最大%dx杠杆** | **BTC/ETH最大%dx杠杆** (⚠️ 严格执行,不可超过)\n", altcoinLeverage, btcEthLeverage))
sb.WriteString("5. 保证金: 总使用率 ≤ 90%\n")
sb.WriteString("6. 开仓金额: 建议 **≥12 USDT** (交易所最小名义价值 10 USDT + 安全边际)\n\n")
sb.WriteString(fmt.Sprintf("4. 杠杆限制: **山寨币最大%dx杠杆** | **BTC/ETH最大%dx杠杆**\n", altcoinLeverage, btcEthLeverage))
sb.WriteString("5. 保证金使用率 ≤ 90%\n")
sb.WriteString("6. 开仓金额: 建议 ≥12 USDT交易所最小名义价值10 USDT + 安全边际\n\n")
// 3. 输出格式 - 动态生成
// 4. 交易频率与信号质量
sb.WriteString("# ⏱️ 交易频率认知\n\n")
sb.WriteString("- 优秀交易员每天2-4笔 ≈ 每小时0.1-0.2笔\n")
sb.WriteString("- 每小时>2笔 = 过度交易\n")
sb.WriteString("- 单笔持仓时间≥30-60分钟\n")
sb.WriteString("如果你发现自己每个周期都在交易 → 标准过低;若持仓<30分钟就平仓 → 过于急躁。\n\n")
sb.WriteString("# 🎯 开仓标准(严格)\n\n")
sb.WriteString("只在多重信号共振时开仓。你拥有:\n")
sb.WriteString("- 3分钟价格序列 + 4小时K线序列\n")
sb.WriteString("- EMA20 / MACD / RSI7 / RSI14 等指标序列\n")
sb.WriteString("- 成交量、持仓量(OI)、资金费率等资金面序列\n")
sb.WriteString("- AI500 / OI_Top 筛选标签(若有)\n\n")
sb.WriteString("自由运用任何有效的分析方法,但**信心度 ≥75** 才能开仓;避免单一指标、信号矛盾、横盘震荡、刚平仓即重启等低质量行为。\n\n")
// 5. 夏普比率驱动的自适应
sb.WriteString("# 🧬 夏普比率自我进化\n\n")
sb.WriteString("- Sharpe < -0.5立即停止交易至少观望6个周期并深度复盘\n")
sb.WriteString("- -0.5 ~ 0只做信心度>80的交易并降低频率\n")
sb.WriteString("- 0 ~ 0.7:保持当前策略\n")
sb.WriteString("- >0.7:允许适度加仓,但仍遵守风控\n\n")
// 6. 决策流程提示
sb.WriteString("# 📋 决策流程\n\n")
sb.WriteString("1. 回顾夏普比率/盈亏 → 是否需要降频或暂停\n")
sb.WriteString("2. 检查持仓 → 是否该止盈/止损/调整\n")
sb.WriteString("3. 扫描候选币 + 多时间框 → 是否存在强信号\n")
sb.WriteString("4. 先写思维链再输出结构化JSON\n\n")
// 7. 输出格式 - 动态生成
sb.WriteString("# 输出格式 (严格遵守)\n\n")
sb.WriteString("**必须使用XML标签 <reasoning> 和 <decision> 标签分隔思维链和决策JSON避免解析错误**\n\n")
sb.WriteString("## 格式要求\n\n")
@@ -344,6 +402,7 @@ func buildSystemPrompt(accountEquity float64, btcEthLeverage, altcoinLeverage in
sb.WriteString("- 简洁分析你的思考过程 \n")
sb.WriteString("</reasoning>\n\n")
sb.WriteString("<decision>\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\": \"SOLUSDT\", \"action\": \"update_stop_loss\", \"new_stop_loss\": 155, \"reasoning\": \"移动止损至保本位\"},\n")

View File

@@ -42,7 +42,7 @@ func TestPromptReloadEndToEnd(t *testing.T) {
}
// 步骤4: 使用 buildSystemPrompt 验证模板被正确使用
systemPrompt := buildSystemPrompt(10000.0, 10, 5, "test_strategy")
systemPrompt := buildSystemPrompt(10000.0, 10, 5, "test_strategy", "")
if !strings.Contains(systemPrompt, initialContent) {
t.Errorf("buildSystemPrompt 未包含模板内容\n生成的 prompt:\n%s", systemPrompt)
}
@@ -69,7 +69,7 @@ func TestPromptReloadEndToEnd(t *testing.T) {
}
// 步骤8: 验证 buildSystemPrompt 使用了新内容
newSystemPrompt := buildSystemPrompt(10000.0, 10, 5, "test_strategy")
newSystemPrompt := buildSystemPrompt(10000.0, 10, 5, "test_strategy", "")
if !strings.Contains(newSystemPrompt, updatedContent) {
t.Errorf("buildSystemPrompt 未包含更新后的模板内容\n生成的 prompt:\n%s", newSystemPrompt)
}
@@ -108,7 +108,7 @@ func TestPromptReloadWithCustomPrompt(t *testing.T) {
// 测试1: 基础模板 + 自定义 prompt不覆盖
customPrompt := "个性化规则:只交易 BTC"
result := buildSystemPromptWithCustom(10000.0, 10, 5, customPrompt, false, "base")
result := buildSystemPromptWithCustom(10000.0, 10, 5, customPrompt, false, "base", "")
if !strings.Contains(result, baseContent) {
t.Errorf("未包含基础模板内容")
}
@@ -117,7 +117,7 @@ func TestPromptReloadWithCustomPrompt(t *testing.T) {
}
// 测试2: 覆盖基础 prompt
result = buildSystemPromptWithCustom(10000.0, 10, 5, customPrompt, true, "base")
result = buildSystemPromptWithCustom(10000.0, 10, 5, customPrompt, true, "base", "")
if strings.Contains(result, baseContent) {
t.Errorf("覆盖模式下仍包含基础模板内容")
}
@@ -135,7 +135,7 @@ func TestPromptReloadWithCustomPrompt(t *testing.T) {
t.Fatalf("重新加载失败: %v", err)
}
result = buildSystemPromptWithCustom(10000.0, 10, 5, customPrompt, false, "base")
result = buildSystemPromptWithCustom(10000.0, 10, 5, customPrompt, false, "base", "")
if !strings.Contains(result, updatedBase) {
t.Errorf("重新加载后未包含更新的基础模板内容")
}
@@ -168,13 +168,13 @@ func TestPromptReloadFallback(t *testing.T) {
}
// 测试1: 请求不存在的模板,应该降级到 default
result := buildSystemPrompt(10000.0, 10, 5, "nonexistent")
result := buildSystemPrompt(10000.0, 10, 5, "nonexistent", "")
if !strings.Contains(result, defaultContent) {
t.Errorf("请求不存在的模板时,未降级到 default")
}
// 测试2: 空模板名,应该使用 default
result = buildSystemPrompt(10000.0, 10, 5, "")
result = buildSystemPrompt(10000.0, 10, 5, "", "")
if !strings.Contains(result, defaultContent) {
t.Errorf("空模板名时,未使用 default")
}

View File

@@ -21,7 +21,7 @@ func TestBuildSystemPrompt_ContainsAllValidActions(t *testing.T) {
}
// 构建 prompt
prompt := buildSystemPrompt(1000.0, 10, 5, "default")
prompt := buildSystemPrompt(1000.0, 10, 5, "default", "")
// 验证每个有效 action 都在 prompt 中出现
for _, action := range validActions {
@@ -33,7 +33,7 @@ func TestBuildSystemPrompt_ContainsAllValidActions(t *testing.T) {
// TestBuildSystemPrompt_ActionListCompleteness 测试 action 列表的完整性
func TestBuildSystemPrompt_ActionListCompleteness(t *testing.T) {
prompt := buildSystemPrompt(1000.0, 10, 5, "default")
prompt := buildSystemPrompt(1000.0, 10, 5, "default", "")
// 检查是否包含关键的缺失 action
missingActions := []string{