mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-04 09:58:22 +08:00
Supports custom system prompts and custom models.
This commit is contained in:
+74
-27
@@ -7,6 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"nofx/auth"
|
"nofx/auth"
|
||||||
"nofx/config"
|
"nofx/config"
|
||||||
|
"nofx/decision"
|
||||||
"nofx/manager"
|
"nofx/manager"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -109,6 +110,10 @@ func (s *Server) setupRoutes() {
|
|||||||
protected.GET("/user/signal-sources", s.handleGetUserSignalSource)
|
protected.GET("/user/signal-sources", s.handleGetUserSignalSource)
|
||||||
protected.POST("/user/signal-sources", s.handleSaveUserSignalSource)
|
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)
|
protected.GET("/competition", s.handleCompetition)
|
||||||
|
|
||||||
@@ -200,18 +205,19 @@ func (s *Server) getTraderFromQuery(c *gin.Context) (*manager.TraderManager, str
|
|||||||
|
|
||||||
// AI交易员管理相关结构体
|
// AI交易员管理相关结构体
|
||||||
type CreateTraderRequest struct {
|
type CreateTraderRequest struct {
|
||||||
Name string `json:"name" binding:"required"`
|
Name string `json:"name" binding:"required"`
|
||||||
AIModelID string `json:"ai_model_id" binding:"required"`
|
AIModelID string `json:"ai_model_id" binding:"required"`
|
||||||
ExchangeID string `json:"exchange_id" binding:"required"`
|
ExchangeID string `json:"exchange_id" binding:"required"`
|
||||||
InitialBalance float64 `json:"initial_balance"`
|
InitialBalance float64 `json:"initial_balance"`
|
||||||
BTCETHLeverage int `json:"btc_eth_leverage"`
|
BTCETHLeverage int `json:"btc_eth_leverage"`
|
||||||
AltcoinLeverage int `json:"altcoin_leverage"`
|
AltcoinLeverage int `json:"altcoin_leverage"`
|
||||||
TradingSymbols string `json:"trading_symbols"`
|
TradingSymbols string `json:"trading_symbols"`
|
||||||
CustomPrompt string `json:"custom_prompt"`
|
CustomPrompt string `json:"custom_prompt"`
|
||||||
OverrideBasePrompt bool `json:"override_base_prompt"`
|
OverrideBasePrompt bool `json:"override_base_prompt"`
|
||||||
IsCrossMargin *bool `json:"is_cross_margin"` // 指针类型,nil表示使用默认值true
|
SystemPromptTemplate string `json:"system_prompt_template"` // 系统提示词模板名称
|
||||||
UseCoinPool bool `json:"use_coin_pool"`
|
IsCrossMargin *bool `json:"is_cross_margin"` // 指针类型,nil表示使用默认值true
|
||||||
UseOITop bool `json:"use_oi_top"`
|
UseCoinPool bool `json:"use_coin_pool"`
|
||||||
|
UseOITop bool `json:"use_oi_top"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ModelConfig struct {
|
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{
|
trader := &config.TraderRecord{
|
||||||
ID: traderID,
|
ID: traderID,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
AIModelID: req.AIModelID,
|
AIModelID: req.AIModelID,
|
||||||
ExchangeID: req.ExchangeID,
|
ExchangeID: req.ExchangeID,
|
||||||
InitialBalance: req.InitialBalance,
|
InitialBalance: req.InitialBalance,
|
||||||
BTCETHLeverage: btcEthLeverage,
|
BTCETHLeverage: btcEthLeverage,
|
||||||
AltcoinLeverage: altcoinLeverage,
|
AltcoinLeverage: altcoinLeverage,
|
||||||
TradingSymbols: req.TradingSymbols,
|
TradingSymbols: req.TradingSymbols,
|
||||||
UseCoinPool: req.UseCoinPool,
|
UseCoinPool: req.UseCoinPool,
|
||||||
UseOITop: req.UseOITop,
|
UseOITop: req.UseOITop,
|
||||||
CustomPrompt: req.CustomPrompt,
|
CustomPrompt: req.CustomPrompt,
|
||||||
OverrideBasePrompt: req.OverrideBasePrompt,
|
OverrideBasePrompt: req.OverrideBasePrompt,
|
||||||
IsCrossMargin: isCrossMargin,
|
SystemPromptTemplate: systemPromptTemplate,
|
||||||
ScanIntervalMinutes: 3, // 默认3分钟
|
IsCrossMargin: isCrossMargin,
|
||||||
|
ScanIntervalMinutes: 3, // 默认3分钟
|
||||||
IsRunning: false,
|
IsRunning: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1407,3 +1420,37 @@ func (s *Server) Start() error {
|
|||||||
|
|
||||||
return s.router.Run(addr)
|
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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
+74
-35
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/base32"
|
"encoding/base32"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "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 btc_eth_leverage INTEGER DEFAULT 5`, // BTC/ETH杠杆倍数
|
||||||
`ALTER TABLE traders ADD COLUMN altcoin_leverage INTEGER DEFAULT 5`, // 山寨币杠杆倍数
|
`ALTER TABLE traders ADD COLUMN altcoin_leverage INTEGER DEFAULT 5`, // 山寨币杠杆倍数
|
||||||
`ALTER TABLE traders ADD COLUMN trading_symbols TEXT DEFAULT ''`, // 交易币种,逗号分隔
|
`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_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 use_oi_top BOOLEAN DEFAULT 0`, // 是否使用OI TOP信号源
|
||||||
`ALTER TABLE ai_models ADD COLUMN custom_api_url TEXT DEFAULT ''`, // 自定义API地址
|
`ALTER TABLE traders ADD COLUMN system_prompt_template TEXT DEFAULT 'default'`, // 系统提示词模板名称
|
||||||
`ALTER TABLE ai_models ADD COLUMN custom_model_name TEXT 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 {
|
for _, query := range alterQueries {
|
||||||
@@ -407,14 +409,15 @@ type TraderRecord struct {
|
|||||||
IsRunning bool `json:"is_running"`
|
IsRunning bool `json:"is_running"`
|
||||||
BTCETHLeverage int `json:"btc_eth_leverage"` // BTC/ETH杠杆倍数
|
BTCETHLeverage int `json:"btc_eth_leverage"` // BTC/ETH杠杆倍数
|
||||||
AltcoinLeverage int `json:"altcoin_leverage"` // 山寨币杠杆倍数
|
AltcoinLeverage int `json:"altcoin_leverage"` // 山寨币杠杆倍数
|
||||||
TradingSymbols string `json:"trading_symbols"` // 交易币种,逗号分隔
|
TradingSymbols string `json:"trading_symbols"` // 交易币种,逗号分隔
|
||||||
UseCoinPool bool `json:"use_coin_pool"` // 是否使用COIN POOL信号源
|
UseCoinPool bool `json:"use_coin_pool"` // 是否使用COIN POOL信号源
|
||||||
UseOITop bool `json:"use_oi_top"` // 是否使用OI TOP信号源
|
UseOITop bool `json:"use_oi_top"` // 是否使用OI TOP信号源
|
||||||
CustomPrompt string `json:"custom_prompt"` // 自定义交易策略prompt
|
CustomPrompt string `json:"custom_prompt"` // 自定义交易策略prompt
|
||||||
OverrideBasePrompt bool `json:"override_base_prompt"` // 是否覆盖基础prompt
|
OverrideBasePrompt bool `json:"override_base_prompt"` // 是否覆盖基础prompt
|
||||||
IsCrossMargin bool `json:"is_cross_margin"` // 是否为全仓模式(true=全仓,false=逐仓)
|
SystemPromptTemplate string `json:"system_prompt_template"` // 系统提示词模板名称
|
||||||
CreatedAt time.Time `json:"created_at"`
|
IsCrossMargin bool `json:"is_cross_margin"` // 是否为全仓模式(true=全仓,false=逐仓)
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserSignalSource 用户信号源配置
|
// UserSignalSource 用户信号源配置
|
||||||
@@ -563,17 +566,14 @@ func (d *Database) GetAIModels(userID string) ([]*AIModelConfig, error) {
|
|||||||
|
|
||||||
// UpdateAIModel 更新AI模型配置,如果不存在则创建用户特定配置
|
// UpdateAIModel 更新AI模型配置,如果不存在则创建用户特定配置
|
||||||
func (d *Database) UpdateAIModel(userID, id string, enabled bool, apiKey, customAPIURL, customModelName string) error {
|
func (d *Database) UpdateAIModel(userID, id string, enabled bool, apiKey, customAPIURL, customModelName string) error {
|
||||||
// id 参数实际上是 provider(如 "deepseek", "qwen")
|
// 先尝试精确匹配 ID(新版逻辑,支持多个相同 provider 的模型)
|
||||||
provider := id
|
|
||||||
|
|
||||||
// 先查找用户是否已有这个 provider 的配置
|
|
||||||
var existingID string
|
var existingID string
|
||||||
err := d.db.QueryRow(`
|
err := d.db.QueryRow(`
|
||||||
SELECT id FROM ai_models WHERE user_id = ? AND provider = ? LIMIT 1
|
SELECT id FROM ai_models WHERE user_id = ? AND id = ? LIMIT 1
|
||||||
`, userID, provider).Scan(&existingID)
|
`, userID, id).Scan(&existingID)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// 找到了现有配置,更新它
|
// 找到了现有配置(精确匹配 ID),更新它
|
||||||
_, err = d.db.Exec(`
|
_, err = d.db.Exec(`
|
||||||
UPDATE ai_models SET enabled = ?, api_key = ?, custom_api_url = ?, custom_model_name = ?, updated_at = datetime('now')
|
UPDATE ai_models SET enabled = ?, api_key = ?, custom_api_url = ?, custom_model_name = ?, updated_at = datetime('now')
|
||||||
WHERE id = ? AND user_id = ?
|
WHERE id = ? AND user_id = ?
|
||||||
@@ -581,7 +581,37 @@ func (d *Database) UpdateAIModel(userID, id string, enabled bool, apiKey, custom
|
|||||||
return err
|
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
|
var name string
|
||||||
err = d.db.QueryRow(`
|
err = d.db.QueryRow(`
|
||||||
@@ -598,12 +628,19 @@ func (d *Database) UpdateAIModel(userID, id string, enabled bool, apiKey, custom
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建用户特定的配置
|
// 如果传入的 ID 已经是完整格式(如 "admin_deepseek_custom1"),直接使用
|
||||||
userModelID := fmt.Sprintf("%s_%s", userID, provider)
|
// 否则生成新的 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(`
|
_, 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)
|
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'))
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
|
||||||
`, userModelID, userID, name, provider, enabled, apiKey, customAPIURL, customModelName)
|
`, newModelID, userID, name, provider, enabled, apiKey, customAPIURL, customModelName)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -730,20 +767,21 @@ func (d *Database) CreateExchange(userID, id, name, typ string, enabled bool, ap
|
|||||||
// CreateTrader 创建交易员
|
// CreateTrader 创建交易员
|
||||||
func (d *Database) CreateTrader(trader *TraderRecord) error {
|
func (d *Database) CreateTrader(trader *TraderRecord) error {
|
||||||
_, err := d.db.Exec(`
|
_, 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)
|
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
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)
|
`, 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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTraders 获取用户的交易员
|
// GetTraders 获取用户的交易员
|
||||||
func (d *Database) GetTraders(userID string) ([]*TraderRecord, error) {
|
func (d *Database) GetTraders(userID string) ([]*TraderRecord, error) {
|
||||||
rows, err := d.db.Query(`
|
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(btc_eth_leverage, 5) as btc_eth_leverage, COALESCE(altcoin_leverage, 5) as altcoin_leverage,
|
||||||
COALESCE(trading_symbols, '') as trading_symbols,
|
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(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
|
COALESCE(is_cross_margin, 1) as is_cross_margin, created_at, updated_at
|
||||||
FROM traders WHERE user_id = ? ORDER BY created_at DESC
|
FROM traders WHERE user_id = ? ORDER BY created_at DESC
|
||||||
`, userID)
|
`, userID)
|
||||||
@@ -760,7 +798,8 @@ func (d *Database) GetTraders(userID string) ([]*TraderRecord, error) {
|
|||||||
&trader.InitialBalance, &trader.ScanIntervalMinutes, &trader.IsRunning,
|
&trader.InitialBalance, &trader.ScanIntervalMinutes, &trader.IsRunning,
|
||||||
&trader.BTCETHLeverage, &trader.AltcoinLeverage, &trader.TradingSymbols,
|
&trader.BTCETHLeverage, &trader.AltcoinLeverage, &trader.TradingSymbols,
|
||||||
&trader.UseCoinPool, &trader.UseOITop,
|
&trader.UseCoinPool, &trader.UseOITop,
|
||||||
&trader.CustomPrompt, &trader.OverrideBasePrompt, &trader.IsCrossMargin,
|
&trader.CustomPrompt, &trader.OverrideBasePrompt, &trader.SystemPromptTemplate,
|
||||||
|
&trader.IsCrossMargin,
|
||||||
&trader.CreatedAt, &trader.UpdatedAt,
|
&trader.CreatedAt, &trader.UpdatedAt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -781,16 +820,16 @@ func (d *Database) UpdateTraderStatus(userID, id string, isRunning bool) error {
|
|||||||
// UpdateTrader 更新交易员配置
|
// UpdateTrader 更新交易员配置
|
||||||
func (d *Database) UpdateTrader(trader *TraderRecord) error {
|
func (d *Database) UpdateTrader(trader *TraderRecord) error {
|
||||||
_, err := d.db.Exec(`
|
_, err := d.db.Exec(`
|
||||||
UPDATE traders SET
|
UPDATE traders SET
|
||||||
name = ?, ai_model_id = ?, exchange_id = ?, initial_balance = ?,
|
name = ?, ai_model_id = ?, exchange_id = ?, initial_balance = ?,
|
||||||
scan_interval_minutes = ?, btc_eth_leverage = ?, altcoin_leverage = ?,
|
scan_interval_minutes = ?, btc_eth_leverage = ?, altcoin_leverage = ?,
|
||||||
trading_symbols = ?, custom_prompt = ?, override_base_prompt = ?,
|
trading_symbols = ?, custom_prompt = ?, override_base_prompt = ?,
|
||||||
is_cross_margin = ?, updated_at = CURRENT_TIMESTAMP
|
system_prompt_template = ?, is_cross_margin = ?, updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = ? AND user_id = ?
|
WHERE id = ? AND user_id = ?
|
||||||
`, trader.Name, trader.AIModelID, trader.ExchangeID, trader.InitialBalance,
|
`, trader.Name, trader.AIModelID, trader.ExchangeID, trader.InitialBalance,
|
||||||
trader.ScanIntervalMinutes, trader.BTCETHLeverage, trader.AltcoinLeverage,
|
trader.ScanIntervalMinutes, trader.BTCETHLeverage, trader.AltcoinLeverage,
|
||||||
trader.TradingSymbols, trader.CustomPrompt, trader.OverrideBasePrompt,
|
trader.TradingSymbols, trader.CustomPrompt, trader.OverrideBasePrompt,
|
||||||
trader.IsCrossMargin, trader.ID, trader.UserID)
|
trader.SystemPromptTemplate, trader.IsCrossMargin, trader.ID, trader.UserID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+43
-110
@@ -83,26 +83,27 @@ type Decision struct {
|
|||||||
|
|
||||||
// FullDecision AI的完整决策(包含思维链)
|
// FullDecision AI的完整决策(包含思维链)
|
||||||
type FullDecision struct {
|
type FullDecision struct {
|
||||||
UserPrompt string `json:"user_prompt"` // 发送给AI的输入prompt
|
SystemPrompt string `json:"system_prompt"` // 系统提示词(发送给AI的系统prompt)
|
||||||
CoTTrace string `json:"cot_trace"` // 思维链分析(AI输出)
|
UserPrompt string `json:"user_prompt"` // 发送给AI的输入prompt
|
||||||
Decisions []Decision `json:"decisions"` // 具体决策列表
|
CoTTrace string `json:"cot_trace"` // 思维链分析(AI输出)
|
||||||
Timestamp time.Time `json:"timestamp"`
|
Decisions []Decision `json:"decisions"` // 具体决策列表
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFullDecision 获取AI的完整交易决策(批量分析所有币种和持仓)
|
// GetFullDecision 获取AI的完整交易决策(批量分析所有币种和持仓)
|
||||||
func GetFullDecision(ctx *Context, mcpClient *mcp.Client) (*FullDecision, error) {
|
func GetFullDecision(ctx *Context, mcpClient *mcp.Client) (*FullDecision, error) {
|
||||||
return GetFullDecisionWithCustomPrompt(ctx, mcpClient, "", false)
|
return GetFullDecisionWithCustomPrompt(ctx, mcpClient, "", false, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFullDecisionWithCustomPrompt 获取AI的完整交易决策(支持自定义prompt)
|
// GetFullDecisionWithCustomPrompt 获取AI的完整交易决策(支持自定义prompt和模板选择)
|
||||||
func GetFullDecisionWithCustomPrompt(ctx *Context, mcpClient *mcp.Client, customPrompt string, overrideBase bool) (*FullDecision, error) {
|
func GetFullDecisionWithCustomPrompt(ctx *Context, mcpClient *mcp.Client, customPrompt string, overrideBase bool, templateName string) (*FullDecision, error) {
|
||||||
// 1. 为所有币种获取市场数据
|
// 1. 为所有币种获取市场数据
|
||||||
if err := fetchMarketDataForContext(ctx); err != nil {
|
if err := fetchMarketDataForContext(ctx); err != nil {
|
||||||
return nil, fmt.Errorf("获取市场数据失败: %w", err)
|
return nil, fmt.Errorf("获取市场数据失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 构建 System Prompt(固定规则)和 User Prompt(动态数据)
|
// 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)
|
userPrompt := buildUserPrompt(ctx)
|
||||||
|
|
||||||
// 3. 调用AI API(使用 system + user prompt)
|
// 3. 调用AI API(使用 system + user prompt)
|
||||||
@@ -118,7 +119,8 @@ func GetFullDecisionWithCustomPrompt(ctx *Context, mcpClient *mcp.Client, custom
|
|||||||
}
|
}
|
||||||
|
|
||||||
decision.Timestamp = time.Now()
|
decision.Timestamp = time.Now()
|
||||||
decision.UserPrompt = userPrompt // 保存输入prompt
|
decision.SystemPrompt = systemPrompt // 保存系统prompt
|
||||||
|
decision.UserPrompt = userPrompt // 保存输入prompt
|
||||||
return decision, nil
|
return decision, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,20 +207,20 @@ func calculateMaxCandidates(ctx *Context) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buildSystemPromptWithCustom 构建包含自定义内容的 System Prompt
|
// 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
|
// 如果覆盖基础prompt且有自定义prompt,只使用自定义prompt
|
||||||
if overrideBase && customPrompt != "" {
|
if overrideBase && customPrompt != "" {
|
||||||
return customPrompt
|
return customPrompt
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取基础prompt
|
// 获取基础prompt(使用指定的模板)
|
||||||
basePrompt := buildSystemPrompt(accountEquity, btcEthLeverage, altcoinLeverage)
|
basePrompt := buildSystemPrompt(accountEquity, btcEthLeverage, altcoinLeverage, templateName)
|
||||||
|
|
||||||
// 如果没有自定义prompt,直接返回基础prompt
|
// 如果没有自定义prompt,直接返回基础prompt
|
||||||
if customPrompt == "" {
|
if customPrompt == "" {
|
||||||
return basePrompt
|
return basePrompt
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加自定义prompt部分到基础prompt
|
// 添加自定义prompt部分到基础prompt
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
sb.WriteString(basePrompt)
|
sb.WriteString(basePrompt)
|
||||||
@@ -227,107 +229,38 @@ func buildSystemPromptWithCustom(accountEquity float64, btcEthLeverage, altcoinL
|
|||||||
sb.WriteString(customPrompt)
|
sb.WriteString(customPrompt)
|
||||||
sb.WriteString("\n\n")
|
sb.WriteString("\n\n")
|
||||||
sb.WriteString("注意: 以上个性化策略是对基础规则的补充,不能违背基础风险控制原则。\n")
|
sb.WriteString("注意: 以上个性化策略是对基础规则的补充,不能违背基础风险控制原则。\n")
|
||||||
|
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildSystemPrompt 构建 System Prompt(固定规则,可缓存)
|
// buildSystemPrompt 构建 System Prompt(使用模板+动态部分)
|
||||||
func buildSystemPrompt(accountEquity float64, btcEthLeverage, altcoinLeverage int) string {
|
func buildSystemPrompt(accountEquity float64, btcEthLeverage, altcoinLeverage int, templateName string) string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
|
|
||||||
// === 核心使命 ===
|
// 1. 加载提示词模板(核心交易策略部分)
|
||||||
sb.WriteString("你是专业的加密货币交易AI,在合约市场进行自主交易。\n\n")
|
if templateName == "" {
|
||||||
sb.WriteString("# 核心目标\n\n")
|
templateName = "default" // 默认使用 default 模板
|
||||||
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")
|
|
||||||
|
|
||||||
// === 交易哲学 & 最佳实践 ===
|
template, err := GetPromptTemplate(templateName)
|
||||||
sb.WriteString("# 交易哲学 & 最佳实践\n\n")
|
if err != nil {
|
||||||
sb.WriteString("## 核心原则:\n\n")
|
// 如果模板不存在,记录错误并使用 default
|
||||||
sb.WriteString("资金保全第一:保护资本比追求收益更重要\n\n")
|
log.Printf("⚠️ 提示词模板 '%s' 不存在,使用 default: %v", templateName, err)
|
||||||
sb.WriteString("纪律胜于情绪:执行你的退出方案,不随意移动止损或目标\n\n")
|
template, err = GetPromptTemplate("default")
|
||||||
sb.WriteString("质量优于数量:少量高信念交易胜过大量低信念交易\n\n")
|
if err != nil {
|
||||||
sb.WriteString("适应波动性:根据市场条件调整仓位\n\n")
|
// 如果连 default 都不存在,使用内置的简化版本
|
||||||
sb.WriteString("尊重趋势:不要与强趋势作对\n\n")
|
log.Printf("❌ 无法加载任何提示词模板,使用内置简化版本")
|
||||||
sb.WriteString("## 常见误区避免:\n\n")
|
sb.WriteString("你是专业的加密货币交易AI。请根据市场数据做出交易决策。\n\n")
|
||||||
sb.WriteString("过度交易:频繁交易导致费用侵蚀利润\n\n")
|
} else {
|
||||||
sb.WriteString("复仇式交易:亏损后立即加码试图\"翻本\"\n\n")
|
sb.WriteString(template.Content)
|
||||||
sb.WriteString("分析瘫痪:过度等待完美信号,导致失机\n\n")
|
sb.WriteString("\n\n")
|
||||||
sb.WriteString("忽视相关性:BTC常引领山寨币,须优先观察BTC\n\n")
|
}
|
||||||
sb.WriteString("过度杠杆:放大收益同时放大亏损\n\n")
|
} else {
|
||||||
|
sb.WriteString(template.Content)
|
||||||
|
sb.WriteString("\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
// === 交易频率认知 ===
|
// 2. 硬约束(风险控制)- 动态生成
|
||||||
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")
|
|
||||||
|
|
||||||
// === 硬约束(风险控制)===
|
|
||||||
sb.WriteString("# 硬约束(风险控制)\n\n")
|
sb.WriteString("# 硬约束(风险控制)\n\n")
|
||||||
sb.WriteString("1. 风险回报比: 必须 ≥ 1:3(冒1%风险,赚3%+收益)\n")
|
sb.WriteString("1. 风险回报比: 必须 ≥ 1:3(冒1%风险,赚3%+收益)\n")
|
||||||
sb.WriteString("2. 最多持仓: 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))
|
accountEquity*0.8, accountEquity*1.5, altcoinLeverage, accountEquity*5, accountEquity*10, btcEthLeverage))
|
||||||
sb.WriteString("4. 保证金: 总使用率 ≤ 90%\n\n")
|
sb.WriteString("4. 保证金: 总使用率 ≤ 90%\n\n")
|
||||||
|
|
||||||
// === 输出格式 ===
|
// 3. 输出格式 - 动态生成
|
||||||
sb.WriteString("#输出格式\n\n")
|
sb.WriteString("#输出格式\n\n")
|
||||||
sb.WriteString("第一步: 思维链(纯文本)\n")
|
sb.WriteString("第一步: 思维链(纯文本)\n")
|
||||||
sb.WriteString("简洁分析你的思考过程\n\n")
|
sb.WriteString("简洁分析你的思考过程\n\n")
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
type DecisionRecord struct {
|
type DecisionRecord struct {
|
||||||
Timestamp time.Time `json:"timestamp"` // 决策时间
|
Timestamp time.Time `json:"timestamp"` // 决策时间
|
||||||
CycleNumber int `json:"cycle_number"` // 周期编号
|
CycleNumber int `json:"cycle_number"` // 周期编号
|
||||||
|
SystemPrompt string `json:"system_prompt"` // 系统提示词(发送给AI的系统prompt)
|
||||||
InputPrompt string `json:"input_prompt"` // 发送给AI的输入prompt
|
InputPrompt string `json:"input_prompt"` // 发送给AI的输入prompt
|
||||||
CoTTrace string `json:"cot_trace"` // AI思维链(输出)
|
CoTTrace string `json:"cot_trace"` // AI思维链(输出)
|
||||||
DecisionJSON string `json:"decision_json"` // 决策JSON
|
DecisionJSON string `json:"decision_json"` // 决策JSON
|
||||||
|
|||||||
@@ -93,14 +93,23 @@ func (tm *TraderManager) LoadTradersFromDatabase(database *config.Database) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
var aiModelCfg *config.AIModelConfig
|
var aiModelCfg *config.AIModelConfig
|
||||||
|
// 优先精确匹配 model.ID(新版逻辑)
|
||||||
for _, model := range aiModels {
|
for _, model := range aiModels {
|
||||||
// 使用 provider 来匹配,因为 AIModelID 存储的是 provider(如 "deepseek")
|
if model.ID == traderCfg.AIModelID {
|
||||||
// 而 model.ID 可能是 "admin_deepseek"
|
|
||||||
if model.Provider == traderCfg.AIModelID {
|
|
||||||
aiModelCfg = model
|
aiModelCfg = model
|
||||||
break
|
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 {
|
if aiModelCfg == nil {
|
||||||
log.Printf("⚠️ 交易员 %s 的AI模型 %s 不存在,跳过", traderCfg.Name, traderCfg.AIModelID)
|
log.Printf("⚠️ 交易员 %s 的AI模型 %s 不存在,跳过", traderCfg.Name, traderCfg.AIModelID)
|
||||||
@@ -216,6 +225,7 @@ func (tm *TraderManager) addTraderFromDB(traderCfg *config.TraderRecord, aiModel
|
|||||||
IsCrossMargin: traderCfg.IsCrossMargin,
|
IsCrossMargin: traderCfg.IsCrossMargin,
|
||||||
DefaultCoins: defaultCoins,
|
DefaultCoins: defaultCoins,
|
||||||
TradingCoins: tradingCoins,
|
TradingCoins: tradingCoins,
|
||||||
|
SystemPromptTemplate: traderCfg.SystemPromptTemplate, // 系统提示词模板
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据交易所类型设置API密钥
|
// 根据交易所类型设置API密钥
|
||||||
@@ -621,13 +631,23 @@ func (tm *TraderManager) LoadUserTraders(database *config.Database, userID strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
var aiModelCfg *config.AIModelConfig
|
var aiModelCfg *config.AIModelConfig
|
||||||
|
// 优先精确匹配 model.ID(新版逻辑)
|
||||||
for _, model := range aiModels {
|
for _, model := range aiModels {
|
||||||
// 使用 provider 来匹配,因为 AIModelID 存储的是 provider(如 "deepseek")
|
if model.ID == traderCfg.AIModelID {
|
||||||
if model.Provider == traderCfg.AIModelID {
|
|
||||||
aiModelCfg = model
|
aiModelCfg = model
|
||||||
break
|
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 {
|
if aiModelCfg == nil {
|
||||||
log.Printf("⚠️ 交易员 %s 的AI模型 %s 不存在,跳过", traderCfg.Name, traderCfg.AIModelID)
|
log.Printf("⚠️ 交易员 %s 的AI模型 %s 不存在,跳过", traderCfg.Name, traderCfg.AIModelID)
|
||||||
@@ -712,12 +732,16 @@ func (tm *TraderManager) loadSingleTrader(traderCfg *config.TraderRecord, aiMode
|
|||||||
AltcoinLeverage: traderCfg.AltcoinLeverage,
|
AltcoinLeverage: traderCfg.AltcoinLeverage,
|
||||||
ScanInterval: time.Duration(traderCfg.ScanIntervalMinutes) * time.Minute,
|
ScanInterval: time.Duration(traderCfg.ScanIntervalMinutes) * time.Minute,
|
||||||
CoinPoolAPIURL: effectiveCoinPoolURL,
|
CoinPoolAPIURL: effectiveCoinPoolURL,
|
||||||
|
CustomAPIURL: aiModelCfg.CustomAPIURL, // 自定义API URL
|
||||||
|
CustomModelName: aiModelCfg.CustomModelName, // 自定义模型名称
|
||||||
|
UseQwen: aiModelCfg.Provider == "qwen",
|
||||||
MaxDailyLoss: maxDailyLoss,
|
MaxDailyLoss: maxDailyLoss,
|
||||||
MaxDrawdown: maxDrawdown,
|
MaxDrawdown: maxDrawdown,
|
||||||
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
|
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
|
||||||
IsCrossMargin: traderCfg.IsCrossMargin,
|
IsCrossMargin: traderCfg.IsCrossMargin,
|
||||||
DefaultCoins: defaultCoins,
|
DefaultCoins: defaultCoins,
|
||||||
TradingCoins: tradingCoins,
|
TradingCoins: tradingCoins,
|
||||||
|
SystemPromptTemplate: traderCfg.SystemPromptTemplate, // 系统提示词模板
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据交易所类型设置API密钥
|
// 根据交易所类型设置API密钥
|
||||||
|
|||||||
@@ -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是底线
|
||||||
@@ -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.
|
||||||
+66
-27
@@ -66,10 +66,13 @@ type AutoTraderConfig struct {
|
|||||||
|
|
||||||
// 仓位模式
|
// 仓位模式
|
||||||
IsCrossMargin bool // true=全仓模式, false=逐仓模式
|
IsCrossMargin bool // true=全仓模式, false=逐仓模式
|
||||||
|
|
||||||
// 币种配置
|
// 币种配置
|
||||||
DefaultCoins []string // 默认币种列表(从数据库获取)
|
DefaultCoins []string // 默认币种列表(从数据库获取)
|
||||||
TradingCoins []string // 实际交易币种列表
|
TradingCoins []string // 实际交易币种列表
|
||||||
|
|
||||||
|
// 系统提示词模板
|
||||||
|
SystemPromptTemplate string // 系统提示词模板名称(如 "default", "aggressive")
|
||||||
}
|
}
|
||||||
|
|
||||||
// AutoTrader 自动交易器
|
// AutoTrader 自动交易器
|
||||||
@@ -86,6 +89,7 @@ type AutoTrader struct {
|
|||||||
dailyPnL float64
|
dailyPnL float64
|
||||||
customPrompt string // 自定义交易策略prompt
|
customPrompt string // 自定义交易策略prompt
|
||||||
overrideBasePrompt bool // 是否覆盖基础prompt
|
overrideBasePrompt bool // 是否覆盖基础prompt
|
||||||
|
systemPromptTemplate string // 系统提示词模板名称
|
||||||
defaultCoins []string // 默认币种列表(从数据库获取)
|
defaultCoins []string // 默认币种列表(从数据库获取)
|
||||||
tradingCoins []string // 实际交易币种列表
|
tradingCoins []string // 实际交易币种列表
|
||||||
lastResetTime time.Time
|
lastResetTime time.Time
|
||||||
@@ -188,6 +192,12 @@ func NewAutoTrader(config AutoTraderConfig) (*AutoTrader, error) {
|
|||||||
logDir := fmt.Sprintf("decision_logs/%s", config.ID)
|
logDir := fmt.Sprintf("decision_logs/%s", config.ID)
|
||||||
decisionLogger := logger.NewDecisionLogger(logDir)
|
decisionLogger := logger.NewDecisionLogger(logDir)
|
||||||
|
|
||||||
|
// 设置默认系统提示词模板
|
||||||
|
systemPromptTemplate := config.SystemPromptTemplate
|
||||||
|
if systemPromptTemplate == "" {
|
||||||
|
systemPromptTemplate = "default" // 默认使用 default 模板
|
||||||
|
}
|
||||||
|
|
||||||
return &AutoTrader{
|
return &AutoTrader{
|
||||||
id: config.ID,
|
id: config.ID,
|
||||||
name: config.Name,
|
name: config.Name,
|
||||||
@@ -198,6 +208,7 @@ func NewAutoTrader(config AutoTraderConfig) (*AutoTrader, error) {
|
|||||||
mcpClient: mcpClient,
|
mcpClient: mcpClient,
|
||||||
decisionLogger: decisionLogger,
|
decisionLogger: decisionLogger,
|
||||||
initialBalance: config.InitialBalance,
|
initialBalance: config.InitialBalance,
|
||||||
|
systemPromptTemplate: systemPromptTemplate,
|
||||||
defaultCoins: config.DefaultCoins,
|
defaultCoins: config.DefaultCoins,
|
||||||
tradingCoins: config.TradingCoins,
|
tradingCoins: config.TradingCoins,
|
||||||
lastResetTime: time.Now(),
|
lastResetTime: time.Now(),
|
||||||
@@ -314,11 +325,12 @@ func (at *AutoTrader) runCycle() error {
|
|||||||
ctx.Account.TotalEquity, ctx.Account.AvailableBalance, ctx.Account.PositionCount)
|
ctx.Account.TotalEquity, ctx.Account.AvailableBalance, ctx.Account.PositionCount)
|
||||||
|
|
||||||
// 4. 调用AI获取完整决策
|
// 4. 调用AI获取完整决策
|
||||||
log.Println("🤖 正在请求AI分析并决策...")
|
log.Printf("🤖 正在请求AI分析并决策... [模板: %s]", at.systemPromptTemplate)
|
||||||
decision, err := decision.GetFullDecisionWithCustomPrompt(ctx, at.mcpClient, at.customPrompt, at.overrideBasePrompt)
|
decision, err := decision.GetFullDecisionWithCustomPrompt(ctx, at.mcpClient, at.customPrompt, at.overrideBasePrompt, at.systemPromptTemplate)
|
||||||
|
|
||||||
// 即使有错误,也保存思维链、决策和输入prompt(用于debug)
|
// 即使有错误,也保存思维链、决策和输入prompt(用于debug)
|
||||||
if decision != nil {
|
if decision != nil {
|
||||||
|
record.SystemPrompt = decision.SystemPrompt // 保存系统提示词
|
||||||
record.InputPrompt = decision.UserPrompt
|
record.InputPrompt = decision.UserPrompt
|
||||||
record.CoTTrace = decision.CoTTrace
|
record.CoTTrace = decision.CoTTrace
|
||||||
if len(decision.Decisions) > 0 {
|
if len(decision.Decisions) > 0 {
|
||||||
@@ -331,38 +343,55 @@ func (at *AutoTrader) runCycle() error {
|
|||||||
record.Success = false
|
record.Success = false
|
||||||
record.ErrorMessage = fmt.Sprintf("获取AI决策失败: %v", err)
|
record.ErrorMessage = fmt.Sprintf("获取AI决策失败: %v", err)
|
||||||
|
|
||||||
// 打印AI思维链(即使有错误)
|
// 打印系统提示词和AI思维链(即使有错误,也要输出以便调试)
|
||||||
if decision != nil && decision.CoTTrace != "" {
|
if decision != nil {
|
||||||
log.Printf("\n" + strings.Repeat("-", 70))
|
if decision.SystemPrompt != "" {
|
||||||
log.Println("💭 AI思维链分析(错误情况):")
|
log.Printf("\n" + strings.Repeat("=", 70))
|
||||||
log.Println(strings.Repeat("-", 70))
|
log.Printf("📋 系统提示词 [模板: %s] (错误情况)", at.systemPromptTemplate)
|
||||||
log.Println(decision.CoTTrace)
|
log.Println(strings.Repeat("=", 70))
|
||||||
log.Printf(strings.Repeat("-", 70) + "\n")
|
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)
|
at.decisionLogger.LogDecision(record)
|
||||||
return fmt.Errorf("获取AI决策失败: %w", err)
|
return fmt.Errorf("获取AI决策失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 打印AI思维链
|
// // 5. 打印系统提示词
|
||||||
log.Printf("\n" + strings.Repeat("-", 70))
|
// log.Printf("\n" + strings.Repeat("=", 70))
|
||||||
log.Println("💭 AI思维链分析:")
|
// log.Printf("📋 系统提示词 [模板: %s]", at.systemPromptTemplate)
|
||||||
log.Println(strings.Repeat("-", 70))
|
// log.Println(strings.Repeat("=", 70))
|
||||||
log.Println(decision.CoTTrace)
|
// log.Println(decision.SystemPrompt)
|
||||||
log.Printf(strings.Repeat("-", 70) + "\n")
|
// log.Printf(strings.Repeat("=", 70) + "\n")
|
||||||
|
|
||||||
// 6. 打印AI决策
|
// 6. 打印AI思维链
|
||||||
log.Printf("📋 AI决策列表 (%d 个):\n", len(decision.Decisions))
|
// log.Printf("\n" + strings.Repeat("-", 70))
|
||||||
for i, d := range decision.Decisions {
|
// log.Println("💭 AI思维链分析:")
|
||||||
log.Printf(" [%d] %s: %s - %s", i+1, d.Symbol, d.Action, d.Reasoning)
|
// log.Println(strings.Repeat("-", 70))
|
||||||
if d.Action == "open_long" || d.Action == "open_short" {
|
// log.Println(decision.CoTTrace)
|
||||||
log.Printf(" 杠杆: %dx | 仓位: %.2f USDT | 止损: %.4f | 止盈: %.4f",
|
// log.Printf(strings.Repeat("-", 70) + "\n")
|
||||||
d.Leverage, d.PositionSizeUSD, d.StopLoss, d.TakeProfit)
|
|
||||||
}
|
// 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()
|
log.Println()
|
||||||
|
|
||||||
// 7. 对决策排序:确保先平仓后开仓(防止仓位叠加超限)
|
// 8. 对决策排序:确保先平仓后开仓(防止仓位叠加超限)
|
||||||
sortedDecisions := sortDecisionsByPriority(decision.Decisions)
|
sortedDecisions := sortDecisionsByPriority(decision.Decisions)
|
||||||
|
|
||||||
log.Println("🔄 执行顺序(已优化): 先平仓→后开仓")
|
log.Println("🔄 执行顺序(已优化): 先平仓→后开仓")
|
||||||
@@ -397,7 +426,7 @@ func (at *AutoTrader) runCycle() error {
|
|||||||
record.Decisions = append(record.Decisions, actionRecord)
|
record.Decisions = append(record.Decisions, actionRecord)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 8. 保存决策记录
|
// 9. 保存决策记录
|
||||||
if err := at.decisionLogger.LogDecision(record); err != nil {
|
if err := at.decisionLogger.LogDecision(record); err != nil {
|
||||||
log.Printf("⚠ 保存决策记录失败: %v", err)
|
log.Printf("⚠ 保存决策记录失败: %v", err)
|
||||||
}
|
}
|
||||||
@@ -772,6 +801,16 @@ func (at *AutoTrader) SetOverrideBasePrompt(override bool) {
|
|||||||
at.overrideBasePrompt = override
|
at.overrideBasePrompt = override
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSystemPromptTemplate 设置系统提示词模板
|
||||||
|
func (at *AutoTrader) SetSystemPromptTemplate(templateName string) {
|
||||||
|
at.systemPromptTemplate = templateName
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSystemPromptTemplate 获取当前系统提示词模板名称
|
||||||
|
func (at *AutoTrader) GetSystemPromptTemplate() string {
|
||||||
|
return at.systemPromptTemplate
|
||||||
|
}
|
||||||
|
|
||||||
// GetDecisionLogger 获取决策日志记录器
|
// GetDecisionLogger 获取决策日志记录器
|
||||||
func (at *AutoTrader) GetDecisionLogger() *logger.DecisionLogger {
|
func (at *AutoTrader) GetDecisionLogger() *logger.DecisionLogger {
|
||||||
return at.decisionLogger
|
return at.decisionLogger
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
|
|
||||||
const handleCreateTrader = async (data: CreateTraderRequest) => {
|
const handleCreateTrader = async (data: CreateTraderRequest) => {
|
||||||
try {
|
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);
|
const exchange = allExchanges?.find(e => e.id === data.exchange_id);
|
||||||
|
|
||||||
if (!model?.enabled) {
|
if (!model?.enabled) {
|
||||||
@@ -162,7 +162,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
|||||||
if (!editingTrader) return;
|
if (!editingTrader) return;
|
||||||
|
|
||||||
try {
|
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);
|
const exchange = enabledExchanges?.find(e => e.id === data.exchange_id);
|
||||||
|
|
||||||
if (!model) {
|
if (!model) {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ interface TraderConfigData {
|
|||||||
trading_symbols: string;
|
trading_symbols: string;
|
||||||
custom_prompt: string;
|
custom_prompt: string;
|
||||||
override_base_prompt: boolean;
|
override_base_prompt: boolean;
|
||||||
|
system_prompt_template: string;
|
||||||
is_cross_margin: boolean;
|
is_cross_margin: boolean;
|
||||||
use_coin_pool: boolean;
|
use_coin_pool: boolean;
|
||||||
use_oi_top: boolean;
|
use_oi_top: boolean;
|
||||||
@@ -51,6 +52,7 @@ export function TraderConfigModal({
|
|||||||
trading_symbols: '',
|
trading_symbols: '',
|
||||||
custom_prompt: '',
|
custom_prompt: '',
|
||||||
override_base_prompt: false,
|
override_base_prompt: false,
|
||||||
|
system_prompt_template: 'default',
|
||||||
is_cross_margin: true,
|
is_cross_margin: true,
|
||||||
use_coin_pool: false,
|
use_coin_pool: false,
|
||||||
use_oi_top: false,
|
use_oi_top: false,
|
||||||
@@ -60,6 +62,7 @@ export function TraderConfigModal({
|
|||||||
const [availableCoins, setAvailableCoins] = useState<string[]>([]);
|
const [availableCoins, setAvailableCoins] = useState<string[]>([]);
|
||||||
const [selectedCoins, setSelectedCoins] = useState<string[]>([]);
|
const [selectedCoins, setSelectedCoins] = useState<string[]>([]);
|
||||||
const [showCoinSelector, setShowCoinSelector] = useState(false);
|
const [showCoinSelector, setShowCoinSelector] = useState(false);
|
||||||
|
const [promptTemplates, setPromptTemplates] = useState<{name: string}[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (traderData) {
|
if (traderData) {
|
||||||
@@ -72,19 +75,27 @@ export function TraderConfigModal({
|
|||||||
} else if (!isEditMode) {
|
} else if (!isEditMode) {
|
||||||
setFormData({
|
setFormData({
|
||||||
trader_name: '',
|
trader_name: '',
|
||||||
ai_model: availableModels[0]?.provider || '',
|
ai_model: availableModels[0]?.id || '',
|
||||||
exchange_id: availableExchanges[0]?.id || '',
|
exchange_id: availableExchanges[0]?.id || '',
|
||||||
btc_eth_leverage: 5,
|
btc_eth_leverage: 5,
|
||||||
altcoin_leverage: 3,
|
altcoin_leverage: 3,
|
||||||
trading_symbols: '',
|
trading_symbols: '',
|
||||||
custom_prompt: '',
|
custom_prompt: '',
|
||||||
override_base_prompt: false,
|
override_base_prompt: false,
|
||||||
|
system_prompt_template: 'default',
|
||||||
is_cross_margin: true,
|
is_cross_margin: true,
|
||||||
use_coin_pool: false,
|
use_coin_pool: false,
|
||||||
use_oi_top: false,
|
use_oi_top: false,
|
||||||
initial_balance: 1000,
|
initial_balance: 1000,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// 确保旧数据也有默认的 system_prompt_template
|
||||||
|
if (traderData && !traderData.system_prompt_template) {
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
system_prompt_template: 'default'
|
||||||
|
}));
|
||||||
|
}
|
||||||
}, [traderData, isEditMode, availableModels, availableExchanges]);
|
}, [traderData, isEditMode, availableModels, availableExchanges]);
|
||||||
|
|
||||||
// 获取系统配置中的币种列表
|
// 获取系统配置中的币种列表
|
||||||
@@ -105,6 +116,29 @@ export function TraderConfigModal({
|
|||||||
fetchConfig();
|
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(() => {
|
useEffect(() => {
|
||||||
const symbolsString = selectedCoins.join(',');
|
const symbolsString = selectedCoins.join(',');
|
||||||
@@ -135,7 +169,7 @@ export function TraderConfigModal({
|
|||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
if (!onSave) return;
|
if (!onSave) return;
|
||||||
|
|
||||||
setIsSaving(true);
|
setIsSaving(true);
|
||||||
try {
|
try {
|
||||||
const saveData: CreateTraderRequest = {
|
const saveData: CreateTraderRequest = {
|
||||||
@@ -147,6 +181,7 @@ export function TraderConfigModal({
|
|||||||
trading_symbols: formData.trading_symbols,
|
trading_symbols: formData.trading_symbols,
|
||||||
custom_prompt: formData.custom_prompt,
|
custom_prompt: formData.custom_prompt,
|
||||||
override_base_prompt: formData.override_base_prompt,
|
override_base_prompt: formData.override_base_prompt,
|
||||||
|
system_prompt_template: formData.system_prompt_template,
|
||||||
is_cross_margin: formData.is_cross_margin,
|
is_cross_margin: formData.is_cross_margin,
|
||||||
use_coin_pool: formData.use_coin_pool,
|
use_coin_pool: formData.use_coin_pool,
|
||||||
use_oi_top: formData.use_oi_top,
|
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"
|
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
|
||||||
>
|
>
|
||||||
{availableModels.map(model => (
|
{availableModels.map(model => (
|
||||||
<option key={model.id} value={model.provider}>
|
<option key={model.id} value={model.id}>
|
||||||
{getShortName(model.name || model.id).toUpperCase()}
|
{getShortName(model.name || model.id).toUpperCase()}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
@@ -394,6 +429,27 @@ export function TraderConfigModal({
|
|||||||
💬 交易策略提示词
|
💬 交易策略提示词
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-4">
|
<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">
|
<div className="flex items-center gap-3">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ export interface CreateTraderRequest {
|
|||||||
trading_symbols?: string;
|
trading_symbols?: string;
|
||||||
custom_prompt?: string;
|
custom_prompt?: string;
|
||||||
override_base_prompt?: boolean;
|
override_base_prompt?: boolean;
|
||||||
|
system_prompt_template?: string;
|
||||||
is_cross_margin?: boolean;
|
is_cross_margin?: boolean;
|
||||||
use_coin_pool?: boolean;
|
use_coin_pool?: boolean;
|
||||||
use_oi_top?: boolean;
|
use_oi_top?: boolean;
|
||||||
|
|||||||
Reference in New Issue
Block a user