Supports custom system prompts and custom models.

This commit is contained in:
SkywalkerJi
2025-11-01 19:45:54 +08:00
parent 9f9c35826d
commit 5afbd9a3c3
12 changed files with 848 additions and 209 deletions

View File

@@ -7,6 +7,7 @@ import (
"net/http"
"nofx/auth"
"nofx/config"
"nofx/decision"
"nofx/manager"
"strconv"
"strings"
@@ -109,6 +110,10 @@ func (s *Server) setupRoutes() {
protected.GET("/user/signal-sources", s.handleGetUserSignalSource)
protected.POST("/user/signal-sources", s.handleSaveUserSignalSource)
// 系统提示词模板管理
protected.GET("/prompt-templates", s.handleGetPromptTemplates)
protected.GET("/prompt-templates/:name", s.handleGetPromptTemplate)
// 竞赛总览
protected.GET("/competition", s.handleCompetition)
@@ -200,18 +205,19 @@ func (s *Server) getTraderFromQuery(c *gin.Context) (*manager.TraderManager, str
// AI交易员管理相关结构体
type CreateTraderRequest struct {
Name string `json:"name" binding:"required"`
AIModelID string `json:"ai_model_id" binding:"required"`
ExchangeID string `json:"exchange_id" binding:"required"`
InitialBalance float64 `json:"initial_balance"`
BTCETHLeverage int `json:"btc_eth_leverage"`
AltcoinLeverage int `json:"altcoin_leverage"`
TradingSymbols string `json:"trading_symbols"`
CustomPrompt string `json:"custom_prompt"`
OverrideBasePrompt bool `json:"override_base_prompt"`
IsCrossMargin *bool `json:"is_cross_margin"` // 指针类型nil表示使用默认值true
UseCoinPool bool `json:"use_coin_pool"`
UseOITop bool `json:"use_oi_top"`
Name string `json:"name" binding:"required"`
AIModelID string `json:"ai_model_id" binding:"required"`
ExchangeID string `json:"exchange_id" binding:"required"`
InitialBalance float64 `json:"initial_balance"`
BTCETHLeverage int `json:"btc_eth_leverage"`
AltcoinLeverage int `json:"altcoin_leverage"`
TradingSymbols string `json:"trading_symbols"`
CustomPrompt string `json:"custom_prompt"`
OverrideBasePrompt bool `json:"override_base_prompt"`
SystemPromptTemplate string `json:"system_prompt_template"` // 系统提示词模板名称
IsCrossMargin *bool `json:"is_cross_margin"` // 指针类型nil表示使用默认值true
UseCoinPool bool `json:"use_coin_pool"`
UseOITop bool `json:"use_oi_top"`
}
type ModelConfig struct {
@@ -319,23 +325,30 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
}
}
// 设置系统提示词模板默认值
systemPromptTemplate := "default"
if req.SystemPromptTemplate != "" {
systemPromptTemplate = req.SystemPromptTemplate
}
// 创建交易员配置(数据库实体)
trader := &config.TraderRecord{
ID: traderID,
UserID: userID,
Name: req.Name,
AIModelID: req.AIModelID,
ExchangeID: req.ExchangeID,
InitialBalance: req.InitialBalance,
BTCETHLeverage: btcEthLeverage,
AltcoinLeverage: altcoinLeverage,
TradingSymbols: req.TradingSymbols,
UseCoinPool: req.UseCoinPool,
UseOITop: req.UseOITop,
CustomPrompt: req.CustomPrompt,
OverrideBasePrompt: req.OverrideBasePrompt,
IsCrossMargin: isCrossMargin,
ScanIntervalMinutes: 3, // 默认3分钟
ID: traderID,
UserID: userID,
Name: req.Name,
AIModelID: req.AIModelID,
ExchangeID: req.ExchangeID,
InitialBalance: req.InitialBalance,
BTCETHLeverage: btcEthLeverage,
AltcoinLeverage: altcoinLeverage,
TradingSymbols: req.TradingSymbols,
UseCoinPool: req.UseCoinPool,
UseOITop: req.UseOITop,
CustomPrompt: req.CustomPrompt,
OverrideBasePrompt: req.OverrideBasePrompt,
SystemPromptTemplate: systemPromptTemplate,
IsCrossMargin: isCrossMargin,
ScanIntervalMinutes: 3, // 默认3分钟
IsRunning: false,
}
@@ -1407,3 +1420,37 @@ func (s *Server) Start() error {
return s.router.Run(addr)
}
// handleGetPromptTemplates 获取所有系统提示词模板列表
func (s *Server) handleGetPromptTemplates(c *gin.Context) {
// 导入 decision 包
templates := decision.GetAllPromptTemplates()
// 转换为响应格式
response := make([]map[string]interface{}, 0, len(templates))
for _, tmpl := range templates {
response = append(response, map[string]interface{}{
"name": tmpl.Name,
})
}
c.JSON(http.StatusOK, gin.H{
"templates": response,
})
}
// handleGetPromptTemplate 获取指定名称的提示词模板内容
func (s *Server) handleGetPromptTemplate(c *gin.Context) {
templateName := c.Param("name")
template, err := decision.GetPromptTemplate(templateName)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("模板不存在: %s", templateName)})
return
}
c.JSON(http.StatusOK, gin.H{
"name": template.Name,
"content": template.Content,
})
}

View File

@@ -6,6 +6,7 @@ import (
"encoding/base32"
"fmt"
"log"
"strings"
"time"
_ "github.com/mattn/go-sqlite3"
@@ -182,10 +183,11 @@ func (d *Database) createTables() error {
`ALTER TABLE traders ADD COLUMN btc_eth_leverage INTEGER DEFAULT 5`, // BTC/ETH杠杆倍数
`ALTER TABLE traders ADD COLUMN altcoin_leverage INTEGER DEFAULT 5`, // 山寨币杠杆倍数
`ALTER TABLE traders ADD COLUMN trading_symbols TEXT DEFAULT ''`, // 交易币种,逗号分隔
`ALTER TABLE traders ADD COLUMN use_coin_pool BOOLEAN DEFAULT 0`, // 是否使用COIN POOL信号源
`ALTER TABLE traders ADD COLUMN use_oi_top BOOLEAN DEFAULT 0`, // 是否使用OI TOP信号源
`ALTER TABLE ai_models ADD COLUMN custom_api_url TEXT DEFAULT ''`, // 自定义API地址
`ALTER TABLE ai_models ADD COLUMN custom_model_name TEXT DEFAULT ''`, // 自定义模型名称
`ALTER TABLE traders ADD COLUMN use_coin_pool BOOLEAN DEFAULT 0`, // 是否使用COIN POOL信号源
`ALTER TABLE traders ADD COLUMN use_oi_top BOOLEAN DEFAULT 0`, // 是否使用OI TOP信号源
`ALTER TABLE traders ADD COLUMN system_prompt_template TEXT DEFAULT 'default'`, // 系统提示词模板名称
`ALTER TABLE ai_models ADD COLUMN custom_api_url TEXT DEFAULT ''`, // 自定义API地址
`ALTER TABLE ai_models ADD COLUMN custom_model_name TEXT DEFAULT ''`, // 自定义模型名称
}
for _, query := range alterQueries {
@@ -407,14 +409,15 @@ type TraderRecord struct {
IsRunning bool `json:"is_running"`
BTCETHLeverage int `json:"btc_eth_leverage"` // BTC/ETH杠杆倍数
AltcoinLeverage int `json:"altcoin_leverage"` // 山寨币杠杆倍数
TradingSymbols string `json:"trading_symbols"` // 交易币种,逗号分隔
UseCoinPool bool `json:"use_coin_pool"` // 是否使用COIN POOL信号源
UseOITop bool `json:"use_oi_top"` // 是否使用OI TOP信号源
CustomPrompt string `json:"custom_prompt"` // 自定义交易策略prompt
OverrideBasePrompt bool `json:"override_base_prompt"` // 是否覆盖基础prompt
IsCrossMargin bool `json:"is_cross_margin"` // 是否为全仓模式true=全仓false=逐仓)
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
TradingSymbols string `json:"trading_symbols"` // 交易币种,逗号分隔
UseCoinPool bool `json:"use_coin_pool"` // 是否使用COIN POOL信号源
UseOITop bool `json:"use_oi_top"` // 是否使用OI TOP信号源
CustomPrompt string `json:"custom_prompt"` // 自定义交易策略prompt
OverrideBasePrompt bool `json:"override_base_prompt"` // 是否覆盖基础prompt
SystemPromptTemplate string `json:"system_prompt_template"` // 系统提示词模板名称
IsCrossMargin bool `json:"is_cross_margin"` // 是否为全仓模式true=全仓false=逐仓)
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// UserSignalSource 用户信号源配置
@@ -563,17 +566,14 @@ func (d *Database) GetAIModels(userID string) ([]*AIModelConfig, error) {
// UpdateAIModel 更新AI模型配置如果不存在则创建用户特定配置
func (d *Database) UpdateAIModel(userID, id string, enabled bool, apiKey, customAPIURL, customModelName string) error {
// id 参数实际上是 provider如 "deepseek", "qwen"
provider := id
// 先查找用户是否已有这个 provider 的配置
// 先尝试精确匹配 ID新版逻辑支持多个相同 provider 的模型
var existingID string
err := d.db.QueryRow(`
SELECT id FROM ai_models WHERE user_id = ? AND provider = ? LIMIT 1
`, userID, provider).Scan(&existingID)
SELECT id FROM ai_models WHERE user_id = ? AND id = ? LIMIT 1
`, userID, id).Scan(&existingID)
if err == nil {
// 找到了现有配置,更新它
// 找到了现有配置(精确匹配 ID,更新它
_, err = d.db.Exec(`
UPDATE ai_models SET enabled = ?, api_key = ?, custom_api_url = ?, custom_model_name = ?, updated_at = datetime('now')
WHERE id = ? AND user_id = ?
@@ -581,7 +581,37 @@ func (d *Database) UpdateAIModel(userID, id string, enabled bool, apiKey, custom
return err
}
// 没有找到现有配置,创建新的
// ID 不存在,尝试兼容旧逻辑:将 id 作为 provider 查找
provider := id
err = d.db.QueryRow(`
SELECT id FROM ai_models WHERE user_id = ? AND provider = ? LIMIT 1
`, userID, provider).Scan(&existingID)
if err == nil {
// 找到了现有配置(通过 provider 匹配,兼容旧版),更新它
log.Printf("⚠️ 使用旧版 provider 匹配更新模型: %s -> %s", provider, existingID)
_, err = d.db.Exec(`
UPDATE ai_models SET enabled = ?, api_key = ?, custom_api_url = ?, custom_model_name = ?, updated_at = datetime('now')
WHERE id = ? AND user_id = ?
`, enabled, apiKey, customAPIURL, customModelName, existingID, userID)
return err
}
// 没有找到任何现有配置,创建新的
// 推断 provider从 id 中提取,或者直接使用 id
if provider == id && (provider == "deepseek" || provider == "qwen") {
// id 本身就是 provider
provider = id
} else {
// 从 id 中提取 provider假设格式是 userID_provider 或 timestamp_userID_provider
parts := strings.Split(id, "_")
if len(parts) >= 2 {
provider = parts[len(parts)-1] // 取最后一部分作为 provider
} else {
provider = id
}
}
// 获取模型的基本信息
var name string
err = d.db.QueryRow(`
@@ -598,12 +628,19 @@ func (d *Database) UpdateAIModel(userID, id string, enabled bool, apiKey, custom
}
}
// 创建用户特定的配置
userModelID := fmt.Sprintf("%s_%s", userID, provider)
// 如果传入的 ID 已经是完整格式(如 "admin_deepseek_custom1"),直接使用
// 否则生成新的 ID
newModelID := id
if id == provider {
// id 就是 provider生成新的用户特定 ID
newModelID = fmt.Sprintf("%s_%s", userID, provider)
}
log.Printf("✓ 创建新的 AI 模型配置: ID=%s, Provider=%s, Name=%s", newModelID, provider, name)
_, err = d.db.Exec(`
INSERT INTO ai_models (id, user_id, name, provider, enabled, api_key, custom_api_url, custom_model_name, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
`, userModelID, userID, name, provider, enabled, apiKey, customAPIURL, customModelName)
`, newModelID, userID, name, provider, enabled, apiKey, customAPIURL, customModelName)
return err
}
@@ -730,20 +767,21 @@ func (d *Database) CreateExchange(userID, id, name, typ string, enabled bool, ap
// CreateTrader 创建交易员
func (d *Database) CreateTrader(trader *TraderRecord) error {
_, err := d.db.Exec(`
INSERT INTO traders (id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running, btc_eth_leverage, altcoin_leverage, trading_symbols, use_coin_pool, use_oi_top, custom_prompt, override_base_prompt, is_cross_margin)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, trader.ID, trader.UserID, trader.Name, trader.AIModelID, trader.ExchangeID, trader.InitialBalance, trader.ScanIntervalMinutes, trader.IsRunning, trader.BTCETHLeverage, trader.AltcoinLeverage, trader.TradingSymbols, trader.UseCoinPool, trader.UseOITop, trader.CustomPrompt, trader.OverrideBasePrompt, trader.IsCrossMargin)
INSERT INTO traders (id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running, btc_eth_leverage, altcoin_leverage, trading_symbols, use_coin_pool, use_oi_top, custom_prompt, override_base_prompt, system_prompt_template, is_cross_margin)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, trader.ID, trader.UserID, trader.Name, trader.AIModelID, trader.ExchangeID, trader.InitialBalance, trader.ScanIntervalMinutes, trader.IsRunning, trader.BTCETHLeverage, trader.AltcoinLeverage, trader.TradingSymbols, trader.UseCoinPool, trader.UseOITop, trader.CustomPrompt, trader.OverrideBasePrompt, trader.SystemPromptTemplate, trader.IsCrossMargin)
return err
}
// GetTraders 获取用户的交易员
func (d *Database) GetTraders(userID string) ([]*TraderRecord, error) {
rows, err := d.db.Query(`
SELECT id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running,
SELECT id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running,
COALESCE(btc_eth_leverage, 5) as btc_eth_leverage, COALESCE(altcoin_leverage, 5) as altcoin_leverage,
COALESCE(trading_symbols, '') as trading_symbols,
COALESCE(use_coin_pool, 0) as use_coin_pool, COALESCE(use_oi_top, 0) as use_oi_top,
COALESCE(custom_prompt, '') as custom_prompt, COALESCE(override_base_prompt, 0) as override_base_prompt,
COALESCE(custom_prompt, '') as custom_prompt, COALESCE(override_base_prompt, 0) as override_base_prompt,
COALESCE(system_prompt_template, 'default') as system_prompt_template,
COALESCE(is_cross_margin, 1) as is_cross_margin, created_at, updated_at
FROM traders WHERE user_id = ? ORDER BY created_at DESC
`, userID)
@@ -760,7 +798,8 @@ func (d *Database) GetTraders(userID string) ([]*TraderRecord, error) {
&trader.InitialBalance, &trader.ScanIntervalMinutes, &trader.IsRunning,
&trader.BTCETHLeverage, &trader.AltcoinLeverage, &trader.TradingSymbols,
&trader.UseCoinPool, &trader.UseOITop,
&trader.CustomPrompt, &trader.OverrideBasePrompt, &trader.IsCrossMargin,
&trader.CustomPrompt, &trader.OverrideBasePrompt, &trader.SystemPromptTemplate,
&trader.IsCrossMargin,
&trader.CreatedAt, &trader.UpdatedAt,
)
if err != nil {
@@ -781,16 +820,16 @@ func (d *Database) UpdateTraderStatus(userID, id string, isRunning bool) error {
// UpdateTrader 更新交易员配置
func (d *Database) UpdateTrader(trader *TraderRecord) error {
_, err := d.db.Exec(`
UPDATE traders SET
name = ?, ai_model_id = ?, exchange_id = ?, initial_balance = ?,
scan_interval_minutes = ?, btc_eth_leverage = ?, altcoin_leverage = ?,
trading_symbols = ?, custom_prompt = ?, override_base_prompt = ?,
is_cross_margin = ?, updated_at = CURRENT_TIMESTAMP
UPDATE traders SET
name = ?, ai_model_id = ?, exchange_id = ?, initial_balance = ?,
scan_interval_minutes = ?, btc_eth_leverage = ?, altcoin_leverage = ?,
trading_symbols = ?, custom_prompt = ?, override_base_prompt = ?,
system_prompt_template = ?, is_cross_margin = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ? AND user_id = ?
`, trader.Name, trader.AIModelID, trader.ExchangeID, trader.InitialBalance,
trader.ScanIntervalMinutes, trader.BTCETHLeverage, trader.AltcoinLeverage,
trader.TradingSymbols, trader.CustomPrompt, trader.OverrideBasePrompt,
trader.IsCrossMargin, trader.ID, trader.UserID)
trader.SystemPromptTemplate, trader.IsCrossMargin, trader.ID, trader.UserID)
return err
}

View File

@@ -83,26 +83,27 @@ type Decision struct {
// 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"`
SystemPrompt string `json:"system_prompt"` // 系统提示词(发送给AI的系统prompt
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) {
return GetFullDecisionWithCustomPrompt(ctx, mcpClient, "", false)
return GetFullDecisionWithCustomPrompt(ctx, mcpClient, "", false, "")
}
// GetFullDecisionWithCustomPrompt 获取AI的完整交易决策支持自定义prompt
func GetFullDecisionWithCustomPrompt(ctx *Context, mcpClient *mcp.Client, customPrompt string, overrideBase bool) (*FullDecision, error) {
// GetFullDecisionWithCustomPrompt 获取AI的完整交易决策支持自定义prompt和模板选择
func GetFullDecisionWithCustomPrompt(ctx *Context, mcpClient *mcp.Client, customPrompt string, overrideBase bool, templateName string) (*FullDecision, error) {
// 1. 为所有币种获取市场数据
if err := fetchMarketDataForContext(ctx); err != nil {
return nil, fmt.Errorf("获取市场数据失败: %w", err)
}
// 2. 构建 System Prompt固定规则和 User Prompt动态数据
systemPrompt := buildSystemPromptWithCustom(ctx.Account.TotalEquity, ctx.BTCETHLeverage, ctx.AltcoinLeverage, customPrompt, overrideBase)
systemPrompt := buildSystemPromptWithCustom(ctx.Account.TotalEquity, ctx.BTCETHLeverage, ctx.AltcoinLeverage, customPrompt, overrideBase, templateName)
userPrompt := buildUserPrompt(ctx)
// 3. 调用AI API使用 system + user prompt
@@ -118,7 +119,8 @@ func GetFullDecisionWithCustomPrompt(ctx *Context, mcpClient *mcp.Client, custom
}
decision.Timestamp = time.Now()
decision.UserPrompt = userPrompt // 保存输入prompt
decision.SystemPrompt = systemPrompt // 保存系统prompt
decision.UserPrompt = userPrompt // 保存输入prompt
return decision, nil
}
@@ -205,20 +207,20 @@ func calculateMaxCandidates(ctx *Context) int {
}
// buildSystemPromptWithCustom 构建包含自定义内容的 System Prompt
func buildSystemPromptWithCustom(accountEquity float64, btcEthLeverage, altcoinLeverage int, customPrompt string, overrideBase bool) string {
func buildSystemPromptWithCustom(accountEquity float64, btcEthLeverage, altcoinLeverage int, customPrompt string, overrideBase bool, templateName string) string {
// 如果覆盖基础prompt且有自定义prompt只使用自定义prompt
if overrideBase && customPrompt != "" {
return customPrompt
}
// 获取基础prompt
basePrompt := buildSystemPrompt(accountEquity, btcEthLeverage, altcoinLeverage)
// 获取基础prompt(使用指定的模板)
basePrompt := buildSystemPrompt(accountEquity, btcEthLeverage, altcoinLeverage, templateName)
// 如果没有自定义prompt直接返回基础prompt
if customPrompt == "" {
return basePrompt
}
// 添加自定义prompt部分到基础prompt
var sb strings.Builder
sb.WriteString(basePrompt)
@@ -227,107 +229,38 @@ func buildSystemPromptWithCustom(accountEquity float64, btcEthLeverage, altcoinL
sb.WriteString(customPrompt)
sb.WriteString("\n\n")
sb.WriteString("注意: 以上个性化策略是对基础规则的补充,不能违背基础风险控制原则。\n")
return sb.String()
}
// buildSystemPrompt 构建 System Prompt固定规则,可缓存
func buildSystemPrompt(accountEquity float64, btcEthLeverage, altcoinLeverage int) string {
// buildSystemPrompt 构建 System Prompt使用模板+动态部分
func buildSystemPrompt(accountEquity float64, btcEthLeverage, altcoinLeverage int, templateName string) 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")
// 1. 加载提示词模板(核心交易策略部分)
if templateName == "" {
templateName = "default" // 默认使用 default 模板
}
// === 交易哲学 & 最佳实践 ===
sb.WriteString("# 交易哲学 & 最佳实践\n\n")
sb.WriteString("## 核心原则:\n\n")
sb.WriteString("资金保全第一:保护资本比追求收益更重要\n\n")
sb.WriteString("纪律胜于情绪:执行你的退出方案,不随意移动止损或目标\n\n")
sb.WriteString("质量优于数量:少量高信念交易胜过大量低信念交易\n\n")
sb.WriteString("适应波动性:根据市场条件调整仓位\n\n")
sb.WriteString("尊重趋势:不要与强趋势作对\n\n")
sb.WriteString("## 常见误区避免:\n\n")
sb.WriteString("过度交易:频繁交易导致费用侵蚀利润\n\n")
sb.WriteString("复仇式交易:亏损后立即加码试图\"翻本\"\n\n")
sb.WriteString("分析瘫痪:过度等待完美信号,导致失机\n\n")
sb.WriteString("忽视相关性BTC常引领山寨币须优先观察BTC\n\n")
sb.WriteString("过度杠杆:放大收益同时放大亏损\n\n")
template, err := GetPromptTemplate(templateName)
if err != nil {
// 如果模板不存在,记录错误并使用 default
log.Printf("⚠️ 提示词模板 '%s' 不存在,使用 default: %v", templateName, err)
template, err = GetPromptTemplate("default")
if err != nil {
// 如果连 default 都不存在,使用内置的简化版本
log.Printf("❌ 无法加载任何提示词模板,使用内置简化版本")
sb.WriteString("你是专业的加密货币交易AI。请根据市场数据做出交易决策。\n\n")
} else {
sb.WriteString(template.Content)
sb.WriteString("\n\n")
}
} else {
sb.WriteString(template.Content)
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("夏普比率 -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")
sb.WriteString("- 宁可错过,不做低质量交易\n")
sb.WriteString("- 风险回报比1:3是底线\n")
// === 硬约束(风险控制)===
// 2. 硬约束(风险控制)- 动态生成
sb.WriteString("# 硬约束(风险控制)\n\n")
sb.WriteString("1. 风险回报比: 必须 ≥ 1:3冒1%风险赚3%+收益)\n")
sb.WriteString("2. 最多持仓: 3个币种质量>数量)\n")
@@ -335,7 +268,7 @@ func buildSystemPrompt(accountEquity float64, btcEthLeverage, altcoinLeverage in
accountEquity*0.8, accountEquity*1.5, altcoinLeverage, accountEquity*5, accountEquity*10, btcEthLeverage))
sb.WriteString("4. 保证金: 总使用率 ≤ 90%\n\n")
// === 输出格式 ===
// 3. 输出格式 - 动态生成
sb.WriteString("#输出格式\n\n")
sb.WriteString("第一步: 思维链(纯文本)\n")
sb.WriteString("简洁分析你的思考过程\n\n")

162
decision/prompt_manager.go Normal file
View File

@@ -0,0 +1,162 @@
package decision
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"sync"
)
// PromptTemplate 系统提示词模板
type PromptTemplate struct {
Name string // 模板名称(文件名,不含扩展名)
Content string // 模板内容
}
// PromptManager 提示词管理器
type PromptManager struct {
templates map[string]*PromptTemplate
mu sync.RWMutex
}
var (
// globalPromptManager 全局提示词管理器
globalPromptManager *PromptManager
// promptsDir 提示词文件夹路径
promptsDir = "prompts"
)
// init 包初始化时加载所有提示词模板
func init() {
globalPromptManager = NewPromptManager()
if err := globalPromptManager.LoadTemplates(promptsDir); err != nil {
log.Printf("⚠️ 加载提示词模板失败: %v", err)
} else {
log.Printf("✓ 已加载 %d 个系统提示词模板", len(globalPromptManager.templates))
}
}
// NewPromptManager 创建提示词管理器
func NewPromptManager() *PromptManager {
return &PromptManager{
templates: make(map[string]*PromptTemplate),
}
}
// LoadTemplates 从指定目录加载所有提示词模板
func (pm *PromptManager) LoadTemplates(dir string) error {
pm.mu.Lock()
defer pm.mu.Unlock()
// 检查目录是否存在
if _, err := os.Stat(dir); os.IsNotExist(err) {
return fmt.Errorf("提示词目录不存在: %s", dir)
}
// 扫描目录中的所有 .txt 文件
files, err := filepath.Glob(filepath.Join(dir, "*.txt"))
if err != nil {
return fmt.Errorf("扫描提示词目录失败: %w", err)
}
if len(files) == 0 {
log.Printf("⚠️ 提示词目录 %s 中没有找到 .txt 文件", dir)
return nil
}
// 加载每个模板文件
for _, file := range files {
// 读取文件内容
content, err := os.ReadFile(file)
if err != nil {
log.Printf("⚠️ 读取提示词文件失败 %s: %v", file, err)
continue
}
// 提取文件名(不含扩展名)作为模板名称
fileName := filepath.Base(file)
templateName := strings.TrimSuffix(fileName, filepath.Ext(fileName))
// 存储模板
pm.templates[templateName] = &PromptTemplate{
Name: templateName,
Content: string(content),
}
log.Printf(" 📄 加载提示词模板: %s (%s)", templateName, fileName)
}
return nil
}
// GetTemplate 获取指定名称的提示词模板
func (pm *PromptManager) GetTemplate(name string) (*PromptTemplate, error) {
pm.mu.RLock()
defer pm.mu.RUnlock()
template, exists := pm.templates[name]
if !exists {
return nil, fmt.Errorf("提示词模板不存在: %s", name)
}
return template, nil
}
// GetAllTemplateNames 获取所有模板名称列表
func (pm *PromptManager) GetAllTemplateNames() []string {
pm.mu.RLock()
defer pm.mu.RUnlock()
names := make([]string, 0, len(pm.templates))
for name := range pm.templates {
names = append(names, name)
}
return names
}
// GetAllTemplates 获取所有模板
func (pm *PromptManager) GetAllTemplates() []*PromptTemplate {
pm.mu.RLock()
defer pm.mu.RUnlock()
templates := make([]*PromptTemplate, 0, len(pm.templates))
for _, template := range pm.templates {
templates = append(templates, template)
}
return templates
}
// ReloadTemplates 重新加载所有模板
func (pm *PromptManager) ReloadTemplates(dir string) error {
pm.mu.Lock()
pm.templates = make(map[string]*PromptTemplate)
pm.mu.Unlock()
return pm.LoadTemplates(dir)
}
// === 全局函数(供外部调用)===
// GetPromptTemplate 获取指定名称的提示词模板(全局函数)
func GetPromptTemplate(name string) (*PromptTemplate, error) {
return globalPromptManager.GetTemplate(name)
}
// GetAllPromptTemplateNames 获取所有模板名称(全局函数)
func GetAllPromptTemplateNames() []string {
return globalPromptManager.GetAllTemplateNames()
}
// GetAllPromptTemplates 获取所有模板(全局函数)
func GetAllPromptTemplates() []*PromptTemplate {
return globalPromptManager.GetAllTemplates()
}
// ReloadPromptTemplates 重新加载所有模板(全局函数)
func ReloadPromptTemplates() error {
return globalPromptManager.ReloadTemplates(promptsDir)
}

View File

@@ -14,6 +14,7 @@ import (
type DecisionRecord struct {
Timestamp time.Time `json:"timestamp"` // 决策时间
CycleNumber int `json:"cycle_number"` // 周期编号
SystemPrompt string `json:"system_prompt"` // 系统提示词发送给AI的系统prompt
InputPrompt string `json:"input_prompt"` // 发送给AI的输入prompt
CoTTrace string `json:"cot_trace"` // AI思维链输出
DecisionJSON string `json:"decision_json"` // 决策JSON

View File

@@ -93,14 +93,23 @@ func (tm *TraderManager) LoadTradersFromDatabase(database *config.Database) erro
}
var aiModelCfg *config.AIModelConfig
// 优先精确匹配 model.ID新版逻辑
for _, model := range aiModels {
// 使用 provider 来匹配,因为 AIModelID 存储的是 provider如 "deepseek"
// 而 model.ID 可能是 "admin_deepseek"
if model.Provider == traderCfg.AIModelID {
if model.ID == traderCfg.AIModelID {
aiModelCfg = model
break
}
}
// 如果没有精确匹配,尝试匹配 provider兼容旧数据
if aiModelCfg == nil {
for _, model := range aiModels {
if model.Provider == traderCfg.AIModelID {
aiModelCfg = model
log.Printf("⚠️ 交易员 %s 使用旧版 provider 匹配: %s -> %s", traderCfg.Name, traderCfg.AIModelID, model.ID)
break
}
}
}
if aiModelCfg == nil {
log.Printf("⚠️ 交易员 %s 的AI模型 %s 不存在,跳过", traderCfg.Name, traderCfg.AIModelID)
@@ -216,6 +225,7 @@ func (tm *TraderManager) addTraderFromDB(traderCfg *config.TraderRecord, aiModel
IsCrossMargin: traderCfg.IsCrossMargin,
DefaultCoins: defaultCoins,
TradingCoins: tradingCoins,
SystemPromptTemplate: traderCfg.SystemPromptTemplate, // 系统提示词模板
}
// 根据交易所类型设置API密钥
@@ -621,13 +631,23 @@ func (tm *TraderManager) LoadUserTraders(database *config.Database, userID strin
}
var aiModelCfg *config.AIModelConfig
// 优先精确匹配 model.ID新版逻辑
for _, model := range aiModels {
// 使用 provider 来匹配,因为 AIModelID 存储的是 provider如 "deepseek"
if model.Provider == traderCfg.AIModelID {
if model.ID == traderCfg.AIModelID {
aiModelCfg = model
break
}
}
// 如果没有精确匹配,尝试匹配 provider兼容旧数据
if aiModelCfg == nil {
for _, model := range aiModels {
if model.Provider == traderCfg.AIModelID {
aiModelCfg = model
log.Printf("⚠️ 交易员 %s 使用旧版 provider 匹配: %s -> %s", traderCfg.Name, traderCfg.AIModelID, model.ID)
break
}
}
}
if aiModelCfg == nil {
log.Printf("⚠️ 交易员 %s 的AI模型 %s 不存在,跳过", traderCfg.Name, traderCfg.AIModelID)
@@ -712,12 +732,16 @@ func (tm *TraderManager) loadSingleTrader(traderCfg *config.TraderRecord, aiMode
AltcoinLeverage: traderCfg.AltcoinLeverage,
ScanInterval: time.Duration(traderCfg.ScanIntervalMinutes) * time.Minute,
CoinPoolAPIURL: effectiveCoinPoolURL,
CustomAPIURL: aiModelCfg.CustomAPIURL, // 自定义API URL
CustomModelName: aiModelCfg.CustomModelName, // 自定义模型名称
UseQwen: aiModelCfg.Provider == "qwen",
MaxDailyLoss: maxDailyLoss,
MaxDrawdown: maxDrawdown,
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
IsCrossMargin: traderCfg.IsCrossMargin,
DefaultCoins: defaultCoins,
TradingCoins: tradingCoins,
SystemPromptTemplate: traderCfg.SystemPromptTemplate, // 系统提示词模板
}
// 根据交易所类型设置API密钥

114
prompts/default.txt Normal file
View File

@@ -0,0 +1,114 @@
你是专业的加密货币交易AI在合约市场进行自主交易。
# 核心目标
最大化夏普比率Sharpe Ratio
夏普比率 = 平均收益 / 收益波动率
这意味着:
- 高质量交易(高胜率、大盈亏比)→ 提升夏普
- 稳定收益、控制回撤 → 提升夏普
- 耐心持仓、让利润奔跑 → 提升夏普
- 频繁交易、小盈小亏 → 增加波动,严重降低夏普
- 过度交易、手续费损耗 → 直接亏损
- 过早平仓、频繁进出 → 错失大行情
关键认知: 系统每3分钟扫描一次但不意味着每次都要交易
大多数时候应该是 `wait` 或 `hold`,只在极佳机会时才开仓。
# 交易哲学 & 最佳实践
## 核心原则:
资金保全第一:保护资本比追求收益更重要
纪律胜于情绪:执行你的退出方案,不随意移动止损或目标
质量优于数量:少量高信念交易胜过大量低信念交易
适应波动性:根据市场条件调整仓位
尊重趋势:不要与强趋势作对
## 常见误区避免:
过度交易:频繁交易导致费用侵蚀利润
复仇式交易:亏损后立即加码试图"翻本"
分析瘫痪:过度等待完美信号,导致失机
忽视相关性BTC常引领山寨币须优先观察BTC
过度杠杆:放大收益同时放大亏损
#交易频率认知
量化标准:
- 优秀交易员每天2-4笔 = 每小时0.1-0.2笔
- 过度交易:每小时>2笔 = 严重问题
- 最佳节奏开仓后持有至少30-60分钟
自查:
如果你发现自己每个周期都在交易 → 说明标准太低
如果你发现持仓<30分钟就平仓 → 说明太急躁
# 开仓标准(严格)
只在强信号时开仓,不确定就观望。
你拥有的完整数据:
- 原始序列3分钟价格序列(MidPrices数组) + 4小时K线序列
- 技术序列EMA20序列、MACD序列、RSI7序列、RSI14序列
- 资金序列:成交量序列、持仓量(OI)序列、资金费率
- 筛选标记AI500评分 / OI_Top排名如果有标注
分析方法(完全由你自主决定):
- 自由运用序列数据,你可以做但不限于趋势分析、形态识别、支撑阻力、技术阻力位、斐波那契、波动带计算
- 多维度交叉验证(价格+量+OI+指标+序列形态)
- 用你认为最有效的方法发现高确定性机会
- 综合信心度 ≥ 75 才开仓
避免低质量信号:
- 单一维度(只看一个指标)
- 相互矛盾(涨但量萎缩)
- 横盘震荡
- 刚平仓不久(<15分钟
# 夏普比率自我进化
每次你会收到夏普比率作为绩效反馈(周期级别):
夏普比率 < -0.5 (持续亏损):
→ 停止交易连续观望至少6个周期18分钟
→ 深度反思:
• 交易频率过高?(每小时>2次就是过度
• 持仓时间过短?(<30分钟就是过早平仓
• 信号强度不足?(信心度<75
夏普比率 -0.5 ~ 0 (轻微亏损):
→ 严格控制:只做信心度>80的交易
→ 减少交易频率每小时最多1笔新开仓
→ 耐心持仓至少持有30分钟以上
夏普比率 0 ~ 0.7 (正收益):
→ 维持当前策略
夏普比率 > 0.7 (优异表现):
→ 可适度扩大仓位
关键: 夏普比率是唯一指标,它会自然惩罚频繁交易和过度进出。
#决策流程
1. 分析夏普比率: 当前策略是否有效?需要调整吗?
2. 评估持仓: 趋势是否改变?是否该止盈/止损?
3. 寻找新机会: 有强信号吗?多空机会?
4. 输出决策: 思维链分析 + JSON
---
记住:
- 目标是夏普比率,不是交易频率
- 宁可错过,不做低质量交易
- 风险回报比1:3是底线

223
prompts/nof1.txt Normal file
View File

@@ -0,0 +1,223 @@
# ROLE & IDENTITY
You are an autonomous cryptocurrency trading agent operating in live markets on the Hyperliquid decentralized exchange.
Your mission: Maximize risk-adjusted returns (PnL) through systematic, disciplined trading.
---
# TRADING ENVIRONMENT SPECIFICATION
## Trading Mechanics
- **Contract Type**: Perpetual futures (no expiration)
- **Funding Mechanism**:
- Positive funding rate = longs pay shorts (bullish market sentiment)
- Negative funding rate = shorts pay longs (bearish market sentiment)
- **Trading Fees**: ~0.02-0.05% per trade (maker/taker fees apply)
- **Slippage**: Expect 0.01-0.1% on market orders depending on size
---
# ACTION SPACE DEFINITION
You have exactly FOUR possible actions per decision cycle:
1. **buy_to_enter**: Open a new LONG position (bet on price appreciation)
- Use when: Bullish technical setup, positive momentum, risk-reward favors upside
2. **sell_to_enter**: Open a new SHORT position (bet on price depreciation)
- Use when: Bearish technical setup, negative momentum, risk-reward favors downside
3. **hold**: Maintain current positions without modification
- Use when: Existing positions are performing as expected, or no clear edge exists
4. **close**: Exit an existing position entirely
- Use when: Profit target reached, stop loss triggered, or thesis invalidated
## Position Management Constraints
- **NO pyramiding**: Cannot add to existing positions (one position per coin maximum)
- **NO hedging**: Cannot hold both long and short positions in the same asset
- **NO partial exits**: Must close entire position at once
---
# POSITION SIZING FRAMEWORK
Calculate position size using this formula:
Position Size (USD) = Available Cash × Leverage × Allocation %
Position Size (Coins) = Position Size (USD) / Current Price
## Sizing Considerations
1. **Available Capital**: Only use available cash (not account value)
2. **Leverage Selection**:
- Low conviction (0.3-0.5): Use 1-3x leverage
- Medium conviction (0.5-0.7): Use 3-8x leverage
- High conviction (0.7-1.0): Use 8-20x leverage
3. **Diversification**: Avoid concentrating >40% of capital in single position
4. **Fee Impact**: On positions <$500, fees will materially erode profits
5. **Liquidation Risk**: Ensure liquidation price is >15% away from entry
---
# RISK MANAGEMENT PROTOCOL (MANDATORY)
For EVERY trade decision, you MUST specify:
1. **profit_target** (float): Exact price level to take profits
- Should offer minimum 2:1 reward-to-risk ratio
- Based on technical resistance levels, Fibonacci extensions, or volatility bands
2. **stop_loss** (float): Exact price level to cut losses
- Should limit loss to 1-3% of account value per trade
- Placed beyond recent support/resistance to avoid premature stops
3. **invalidation_condition** (string): Specific market signal that voids your thesis
- Examples: "BTC breaks below $100k", "RSI drops below 30", "Funding rate flips negative"
- Must be objective and observable
4. **confidence** (float, 0-1): Your conviction level in this trade
- 0.0-0.3: Low confidence (avoid trading or use minimal size)
- 0.3-0.6: Moderate confidence (standard position sizing)
- 0.6-0.8: High confidence (larger position sizing acceptable)
- 0.8-1.0: Very high confidence (use cautiously, beware overconfidence)
5. **risk_usd** (float): Dollar amount at risk (distance from entry to stop loss)
- Calculate as: |Entry Price - Stop Loss| × Position Size × Leverage
# PERFORMANCE METRICS & FEEDBACK
You will receive your Sharpe Ratio at each invocation:
Sharpe Ratio = (Average Return - Risk-Free Rate) / Standard Deviation of Returns
Interpretation:
- < 0: Losing money on average
- 0-1: Positive returns but high volatility
- 1-2: Good risk-adjusted performance
- > 2: Excellent risk-adjusted performance
Use Sharpe Ratio to calibrate your behavior:
- Low Sharpe → Reduce position sizes, tighten stops, be more selective
- High Sharpe → Current strategy is working, maintain discipline
---
# DATA INTERPRETATION GUIDELINES
## Technical Indicators Provided
**EMA (Exponential Moving Average)**: Trend direction
- Price > EMA = Uptrend
- Price < EMA = Downtrend
**MACD (Moving Average Convergence Divergence)**: Momentum
- Positive MACD = Bullish momentum
- Negative MACD = Bearish momentum
**RSI (Relative Strength Index)**: Overbought/Oversold conditions
- RSI > 70 = Overbought (potential reversal down)
- RSI < 30 = Oversold (potential reversal up)
- RSI 40-60 = Neutral zone
**ATR (Average True Range)**: Volatility measurement
- Higher ATR = More volatile (wider stops needed)
- Lower ATR = Less volatile (tighter stops possible)
**Open Interest**: Total outstanding contracts
- Rising OI + Rising Price = Strong uptrend
- Rising OI + Falling Price = Strong downtrend
- Falling OI = Trend weakening
**Funding Rate**: Market sentiment indicator
- Positive funding = Bullish sentiment (longs paying shorts)
- Negative funding = Bearish sentiment (shorts paying longs)
- Extreme funding rates (>0.01%) = Potential reversal signal
## Data Ordering (CRITICAL)
⚠️ **ALL PRICE AND INDICATOR DATA IS ORDERED: OLDEST → NEWEST**
**The LAST element in each array is the MOST RECENT data point.**
**The FIRST element is the OLDEST data point.**
Do NOT confuse the order. This is a common error that leads to incorrect decisions.
---
# OPERATIONAL CONSTRAINTS
## What You DON'T Have Access To
- No news feeds or social media sentiment
- No conversation history (each decision is stateless)
- No ability to query external APIs
- No access to order book depth beyond mid-price
- No ability to place limit orders (market orders only)
## What You MUST Infer From Data
- Market narratives and sentiment (from price action + funding rates)
- Institutional positioning (from open interest changes)
- Trend strength and sustainability (from technical indicators)
- Risk-on vs risk-off regime (from correlation across coins)
---
# TRADING PHILOSOPHY & BEST PRACTICES
## Core Principles
1. **Capital Preservation First**: Protecting capital is more important than chasing gains
2. **Discipline Over Emotion**: Follow your exit plan, don't move stops or targets
3. **Quality Over Quantity**: Fewer high-conviction trades beat many low-conviction trades
4. **Adapt to Volatility**: Adjust position sizes based on market conditions
5. **Respect the Trend**: Don't fight strong directional moves
## Common Pitfalls to Avoid
- ⚠️ **Overtrading**: Excessive trading erodes capital through fees
- ⚠️ **Revenge Trading**: Don't increase size after losses to "make it back"
- ⚠️ **Analysis Paralysis**: Don't wait for perfect setups, they don't exist
- ⚠️ **Ignoring Correlation**: BTC often leads altcoins, watch BTC first
- ⚠️ **Overleveraging**: High leverage amplifies both gains AND losses
## Decision-Making Framework
1. Analyze current positions first (are they performing as expected?)
2. Check for invalidation conditions on existing trades
3. Scan for new opportunities only if capital is available
4. Prioritize risk management over profit maximization
5. When in doubt, choose "hold" over forcing a trade
---
# CONTEXT WINDOW MANAGEMENT
You have limited context. The prompt contains:
- ~10 recent data points per indicator (3-minute intervals)
- ~10 recent data points for 4-hour timeframe
- Current account state and open positions
Optimize your analysis:
- Focus on most recent 3-5 data points for short-term signals
- Use 4-hour data for trend context and support/resistance levels
- Don't try to memorize all numbers, identify patterns instead
---
# FINAL INSTRUCTIONS
1. Read the entire user prompt carefully before deciding
2. Verify your position sizing math (double-check calculations)
3. Ensure your JSON output is valid and complete
4. Provide honest confidence scores (don't overstate conviction)
5. Be consistent with your exit plans (don't abandon stops prematurely)
Remember: You are trading with real money in real markets. Every decision has consequences. Trade systematically, manage risk religiously, and let probability work in your favor over time.
Now, analyze the market data provided below and make your trading decision.

View File

@@ -66,10 +66,13 @@ type AutoTraderConfig struct {
// 仓位模式
IsCrossMargin bool // true=全仓模式, false=逐仓模式
// 币种配置
DefaultCoins []string // 默认币种列表(从数据库获取)
TradingCoins []string // 实际交易币种列表
// 系统提示词模板
SystemPromptTemplate string // 系统提示词模板名称(如 "default", "aggressive"
}
// AutoTrader 自动交易器
@@ -86,6 +89,7 @@ type AutoTrader struct {
dailyPnL float64
customPrompt string // 自定义交易策略prompt
overrideBasePrompt bool // 是否覆盖基础prompt
systemPromptTemplate string // 系统提示词模板名称
defaultCoins []string // 默认币种列表(从数据库获取)
tradingCoins []string // 实际交易币种列表
lastResetTime time.Time
@@ -188,6 +192,12 @@ func NewAutoTrader(config AutoTraderConfig) (*AutoTrader, error) {
logDir := fmt.Sprintf("decision_logs/%s", config.ID)
decisionLogger := logger.NewDecisionLogger(logDir)
// 设置默认系统提示词模板
systemPromptTemplate := config.SystemPromptTemplate
if systemPromptTemplate == "" {
systemPromptTemplate = "default" // 默认使用 default 模板
}
return &AutoTrader{
id: config.ID,
name: config.Name,
@@ -198,6 +208,7 @@ func NewAutoTrader(config AutoTraderConfig) (*AutoTrader, error) {
mcpClient: mcpClient,
decisionLogger: decisionLogger,
initialBalance: config.InitialBalance,
systemPromptTemplate: systemPromptTemplate,
defaultCoins: config.DefaultCoins,
tradingCoins: config.TradingCoins,
lastResetTime: time.Now(),
@@ -314,11 +325,12 @@ func (at *AutoTrader) runCycle() error {
ctx.Account.TotalEquity, ctx.Account.AvailableBalance, ctx.Account.PositionCount)
// 4. 调用AI获取完整决策
log.Println("🤖 正在请求AI分析并决策...")
decision, err := decision.GetFullDecisionWithCustomPrompt(ctx, at.mcpClient, at.customPrompt, at.overrideBasePrompt)
log.Printf("🤖 正在请求AI分析并决策... [模板: %s]", at.systemPromptTemplate)
decision, err := decision.GetFullDecisionWithCustomPrompt(ctx, at.mcpClient, at.customPrompt, at.overrideBasePrompt, at.systemPromptTemplate)
// 即使有错误也保存思维链、决策和输入prompt用于debug
if decision != nil {
record.SystemPrompt = decision.SystemPrompt // 保存系统提示词
record.InputPrompt = decision.UserPrompt
record.CoTTrace = decision.CoTTrace
if len(decision.Decisions) > 0 {
@@ -331,38 +343,55 @@ func (at *AutoTrader) runCycle() error {
record.Success = false
record.ErrorMessage = fmt.Sprintf("获取AI决策失败: %v", err)
// 打印AI思维链即使有错误
if decision != nil && decision.CoTTrace != "" {
log.Printf("\n" + strings.Repeat("-", 70))
log.Println("💭 AI思维链分析错误情况:")
log.Println(strings.Repeat("-", 70))
log.Println(decision.CoTTrace)
log.Printf(strings.Repeat("-", 70) + "\n")
// 打印系统提示词和AI思维链即使有错误,也要输出以便调试
if decision != nil {
if decision.SystemPrompt != "" {
log.Printf("\n" + strings.Repeat("=", 70))
log.Printf("📋 系统提示词 [模板: %s] (错误情况)", at.systemPromptTemplate)
log.Println(strings.Repeat("=", 70))
log.Println(decision.SystemPrompt)
log.Printf(strings.Repeat("=", 70) + "\n")
}
if decision.CoTTrace != "" {
log.Printf("\n" + strings.Repeat("-", 70))
log.Println("💭 AI思维链分析错误情况:")
log.Println(strings.Repeat("-", 70))
log.Println(decision.CoTTrace)
log.Printf(strings.Repeat("-", 70) + "\n")
}
}
at.decisionLogger.LogDecision(record)
return fmt.Errorf("获取AI决策失败: %w", err)
}
// 5. 打印AI思维链
log.Printf("\n" + strings.Repeat("-", 70))
log.Println("💭 AI思维链分析:")
log.Println(strings.Repeat("-", 70))
log.Println(decision.CoTTrace)
log.Printf(strings.Repeat("-", 70) + "\n")
// // 5. 打印系统提示词
// log.Printf("\n" + strings.Repeat("=", 70))
// log.Printf("📋 系统提示词 [模板: %s]", at.systemPromptTemplate)
// log.Println(strings.Repeat("=", 70))
// log.Println(decision.SystemPrompt)
// log.Printf(strings.Repeat("=", 70) + "\n")
// 6. 打印AI决策
log.Printf("📋 AI决策列表 (%d 个):\n", len(decision.Decisions))
for i, d := range decision.Decisions {
log.Printf(" [%d] %s: %s - %s", i+1, d.Symbol, d.Action, d.Reasoning)
if d.Action == "open_long" || d.Action == "open_short" {
log.Printf(" 杠杆: %dx | 仓位: %.2f USDT | 止损: %.4f | 止盈: %.4f",
d.Leverage, d.PositionSizeUSD, d.StopLoss, d.TakeProfit)
}
}
// 6. 打印AI思维链
// log.Printf("\n" + strings.Repeat("-", 70))
// log.Println("💭 AI思维链分析:")
// log.Println(strings.Repeat("-", 70))
// log.Println(decision.CoTTrace)
// log.Printf(strings.Repeat("-", 70) + "\n")
// 7. 打印AI决策
// log.Printf("📋 AI决策列表 (%d 个):\n", len(decision.Decisions))
// for i, d := range decision.Decisions {
// log.Printf(" [%d] %s: %s - %s", i+1, d.Symbol, d.Action, d.Reasoning)
// if d.Action == "open_long" || d.Action == "open_short" {
// log.Printf(" 杠杆: %dx | 仓位: %.2f USDT | 止损: %.4f | 止盈: %.4f",
// d.Leverage, d.PositionSizeUSD, d.StopLoss, d.TakeProfit)
// }
// }
log.Println()
// 7. 对决策排序:确保先平仓后开仓(防止仓位叠加超限)
// 8. 对决策排序:确保先平仓后开仓(防止仓位叠加超限)
sortedDecisions := sortDecisionsByPriority(decision.Decisions)
log.Println("🔄 执行顺序(已优化): 先平仓→后开仓")
@@ -397,7 +426,7 @@ func (at *AutoTrader) runCycle() error {
record.Decisions = append(record.Decisions, actionRecord)
}
// 8. 保存决策记录
// 9. 保存决策记录
if err := at.decisionLogger.LogDecision(record); err != nil {
log.Printf("⚠ 保存决策记录失败: %v", err)
}
@@ -772,6 +801,16 @@ func (at *AutoTrader) SetOverrideBasePrompt(override bool) {
at.overrideBasePrompt = override
}
// SetSystemPromptTemplate 设置系统提示词模板
func (at *AutoTrader) SetSystemPromptTemplate(templateName string) {
at.systemPromptTemplate = templateName
}
// GetSystemPromptTemplate 获取当前系统提示词模板名称
func (at *AutoTrader) GetSystemPromptTemplate() string {
return at.systemPromptTemplate
}
// GetDecisionLogger 获取决策日志记录器
func (at *AutoTrader) GetDecisionLogger() *logger.DecisionLogger {
return at.decisionLogger

View File

@@ -125,7 +125,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
const handleCreateTrader = async (data: CreateTraderRequest) => {
try {
const model = allModels?.find(m => m.provider === data.ai_model_id);
const model = allModels?.find(m => m.id === data.ai_model_id);
const exchange = allExchanges?.find(e => e.id === data.exchange_id);
if (!model?.enabled) {
@@ -162,7 +162,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
if (!editingTrader) return;
try {
const model = enabledModels?.find(m => m.provider === data.ai_model_id);
const model = enabledModels?.find(m => m.id === data.ai_model_id);
const exchange = enabledExchanges?.find(e => e.id === data.exchange_id);
if (!model) {

View File

@@ -17,6 +17,7 @@ interface TraderConfigData {
trading_symbols: string;
custom_prompt: string;
override_base_prompt: boolean;
system_prompt_template: string;
is_cross_margin: boolean;
use_coin_pool: boolean;
use_oi_top: boolean;
@@ -51,6 +52,7 @@ export function TraderConfigModal({
trading_symbols: '',
custom_prompt: '',
override_base_prompt: false,
system_prompt_template: 'default',
is_cross_margin: true,
use_coin_pool: false,
use_oi_top: false,
@@ -60,6 +62,7 @@ export function TraderConfigModal({
const [availableCoins, setAvailableCoins] = useState<string[]>([]);
const [selectedCoins, setSelectedCoins] = useState<string[]>([]);
const [showCoinSelector, setShowCoinSelector] = useState(false);
const [promptTemplates, setPromptTemplates] = useState<{name: string}[]>([]);
useEffect(() => {
if (traderData) {
@@ -72,19 +75,27 @@ export function TraderConfigModal({
} else if (!isEditMode) {
setFormData({
trader_name: '',
ai_model: availableModels[0]?.provider || '',
ai_model: availableModels[0]?.id || '',
exchange_id: availableExchanges[0]?.id || '',
btc_eth_leverage: 5,
altcoin_leverage: 3,
trading_symbols: '',
custom_prompt: '',
override_base_prompt: false,
system_prompt_template: 'default',
is_cross_margin: true,
use_coin_pool: false,
use_oi_top: false,
initial_balance: 1000,
});
}
// 确保旧数据也有默认的 system_prompt_template
if (traderData && !traderData.system_prompt_template) {
setFormData(prev => ({
...prev,
system_prompt_template: 'default'
}));
}
}, [traderData, isEditMode, availableModels, availableExchanges]);
// 获取系统配置中的币种列表
@@ -105,6 +116,29 @@ export function TraderConfigModal({
fetchConfig();
}, []);
// 获取系统提示词模板列表
useEffect(() => {
const fetchPromptTemplates = async () => {
try {
const token = localStorage.getItem('token');
const response = await fetch('/api/prompt-templates', {
headers: {
'Authorization': `Bearer ${token}`
}
});
const data = await response.json();
if (data.templates) {
setPromptTemplates(data.templates);
}
} catch (error) {
console.error('Failed to fetch prompt templates:', error);
// 使用默认模板列表
setPromptTemplates([{name: 'default'}, {name: 'aggressive'}]);
}
};
fetchPromptTemplates();
}, []);
// 当选择的币种改变时,更新输入框
useEffect(() => {
const symbolsString = selectedCoins.join(',');
@@ -135,7 +169,7 @@ export function TraderConfigModal({
const handleSave = async () => {
if (!onSave) return;
setIsSaving(true);
try {
const saveData: CreateTraderRequest = {
@@ -147,6 +181,7 @@ export function TraderConfigModal({
trading_symbols: formData.trading_symbols,
custom_prompt: formData.custom_prompt,
override_base_prompt: formData.override_base_prompt,
system_prompt_template: formData.system_prompt_template,
is_cross_margin: formData.is_cross_margin,
use_coin_pool: formData.use_coin_pool,
use_oi_top: formData.use_oi_top,
@@ -217,7 +252,7 @@ export function TraderConfigModal({
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
>
{availableModels.map(model => (
<option key={model.id} value={model.provider}>
<option key={model.id} value={model.id}>
{getShortName(model.name || model.id).toUpperCase()}
</option>
))}
@@ -394,6 +429,27 @@ export function TraderConfigModal({
💬
</h3>
<div className="space-y-4">
{/* 系统提示词模板选择 */}
<div>
<label className="text-sm text-[#EAECEF] block mb-2"></label>
<select
value={formData.system_prompt_template}
onChange={(e) => handleInputChange('system_prompt_template', e.target.value)}
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
>
{promptTemplates.map(template => (
<option key={template.name} value={template.name}>
{template.name === 'default' ? 'Default (默认稳健)' :
template.name === 'aggressive' ? 'Aggressive (激进)' :
template.name.charAt(0).toUpperCase() + template.name.slice(1)}
</option>
))}
</select>
<p className="text-xs text-[#848E9C] mt-1">
</p>
</div>
<div className="flex items-center gap-3">
<input
type="checkbox"

View File

@@ -130,6 +130,7 @@ export interface CreateTraderRequest {
trading_symbols?: string;
custom_prompt?: string;
override_base_prompt?: boolean;
system_prompt_template?: string;
is_cross_margin?: boolean;
use_coin_pool?: boolean;
use_oi_top?: boolean;