refactor: rename decision package to kernel

This commit is contained in:
tinkle-community
2026-01-03 14:25:40 +08:00
parent d664dcca3d
commit 13fda47151
13 changed files with 76 additions and 76 deletions
+1773
View File
File diff suppressed because it is too large Load Diff
+643
View File
@@ -0,0 +1,643 @@
package kernel
import (
"fmt"
"nofx/market"
"sort"
"strings"
"time"
)
// ============================================================================
// AI Data Formatter - AI数据格式化器
// ============================================================================
// 将交易上下文转换为AI友好的格式,确保AI能够100%理解数据
// ============================================================================
// FormatContextForAI 将交易上下文格式化为AI可理解的文本(包含Schema)
func FormatContextForAI(ctx *Context, lang Language) string {
var sb strings.Builder
// 1. 添加Schema说明(让AI理解数据格式)
sb.WriteString(GetSchemaPrompt(lang))
sb.WriteString("\n---\n\n")
// 2. 当前状态概览
sb.WriteString(formatContextData(ctx, lang))
return sb.String()
}
// FormatContextDataOnly 仅格式化上下文数据,不包含Schema(用于已有Schema的场景)
func FormatContextDataOnly(ctx *Context, lang Language) string {
return formatContextData(ctx, lang)
}
// formatContextData 格式化核心数据部分
func formatContextData(ctx *Context, lang Language) string {
var sb strings.Builder
// 1. 当前状态概览
if lang == LangChinese {
sb.WriteString(formatHeaderZH(ctx))
} else {
sb.WriteString(formatHeaderEN(ctx))
}
// 3. 账户信息
if lang == LangChinese {
sb.WriteString(formatAccountZH(ctx))
} else {
sb.WriteString(formatAccountEN(ctx))
}
// 4. 历史交易统计
if ctx.TradingStats != nil && ctx.TradingStats.TotalTrades > 0 {
if lang == LangChinese {
sb.WriteString(formatTradingStatsZH(ctx.TradingStats))
} else {
sb.WriteString(formatTradingStatsEN(ctx.TradingStats))
}
}
// 5. 最近交易记录
if len(ctx.RecentOrders) > 0 {
if lang == LangChinese {
sb.WriteString(formatRecentTradesZH(ctx.RecentOrders))
} else {
sb.WriteString(formatRecentTradesEN(ctx.RecentOrders))
}
}
// 5. 当前持仓
if len(ctx.Positions) > 0 {
if lang == LangChinese {
sb.WriteString(formatCurrentPositionsZH(ctx))
} else {
sb.WriteString(formatCurrentPositionsEN(ctx))
}
}
// 6. 候选币种(带市场数据)
if len(ctx.CandidateCoins) > 0 {
if lang == LangChinese {
sb.WriteString(formatCandidateCoinsZH(ctx))
} else {
sb.WriteString(formatCandidateCoinsEN(ctx))
}
}
// 7. OI排名数据(如果有)
if ctx.OIRankingData != nil {
if lang == LangChinese {
sb.WriteString(formatOIRankingZH(ctx.OIRankingData))
} else {
sb.WriteString(formatOIRankingEN(ctx.OIRankingData))
}
}
return sb.String()
}
// ========== 中文格式化函数 ==========
// formatHeaderZH 格式化头部信息(中文)
func formatHeaderZH(ctx *Context) string {
return fmt.Sprintf("# 📊 交易决策请求\n\n时间: %s | 周期: #%d | 运行时长: %d 分钟\n\n",
ctx.CurrentTime, ctx.CallCount, ctx.RuntimeMinutes)
}
// formatAccountZH 格式化账户信息(中文)
func formatAccountZH(ctx *Context) string {
acc := ctx.Account
var sb strings.Builder
sb.WriteString("## 账户状态\n\n")
sb.WriteString(fmt.Sprintf("总权益: %.2f USDT | ", acc.TotalEquity))
sb.WriteString(fmt.Sprintf("可用余额: %.2f USDT (%.1f%%) | ", acc.AvailableBalance, (acc.AvailableBalance/acc.TotalEquity)*100))
sb.WriteString(fmt.Sprintf("总盈亏: %+.2f%% | ", acc.TotalPnLPct))
sb.WriteString(fmt.Sprintf("保证金使用率: %.1f%% | ", acc.MarginUsedPct))
sb.WriteString(fmt.Sprintf("持仓数: %d\n\n", acc.PositionCount))
// 添加风险提示
if acc.MarginUsedPct > 70 {
sb.WriteString("⚠️ **风险警告**: 保证金使用率 > 70%,处于高风险状态!\n\n")
} else if acc.MarginUsedPct > 50 {
sb.WriteString("⚠️ **风险提示**: 保证金使用率 > 50%,建议谨慎开仓\n\n")
}
return sb.String()
}
// formatTradingStatsZH 格式化历史交易统计(中文)
func formatTradingStatsZH(stats *TradingStats) string {
var sb strings.Builder
sb.WriteString("## 历史交易统计\n\n")
// 盈亏比计算
var winLossRatio float64
if stats.AvgLoss > 0 {
winLossRatio = stats.AvgWin / stats.AvgLoss
}
// 指标定义说明(去掉胜率,聚焦核心指标)
sb.WriteString("**指标说明**:\n")
sb.WriteString("- 盈利因子: 总盈利 ÷ 总亏损(>1表示盈利,>1.5为良好,>2为优秀)\n")
sb.WriteString("- 夏普比率: (平均收益 - 无风险收益) ÷ 收益标准差(>1良好,>2优秀)\n")
sb.WriteString("- 盈亏比: 平均盈利 ÷ 平均亏损(>1.5为良好,>2为优秀)\n")
sb.WriteString("- 最大回撤: 资金曲线从峰值到谷底的最大跌幅(<20%为低风险)\n\n")
// 数据值
sb.WriteString("**当前数据**:\n")
sb.WriteString(fmt.Sprintf("- 总交易: %d 笔\n", stats.TotalTrades))
sb.WriteString(fmt.Sprintf("- 盈利因子: %.2f\n", stats.ProfitFactor))
sb.WriteString(fmt.Sprintf("- 夏普比率: %.2f\n", stats.SharpeRatio))
sb.WriteString(fmt.Sprintf("- 盈亏比: %.2f\n", winLossRatio))
sb.WriteString(fmt.Sprintf("- 总盈亏: %+.2f USDT\n", stats.TotalPnL))
sb.WriteString(fmt.Sprintf("- 平均盈利: +%.2f USDT\n", stats.AvgWin))
sb.WriteString(fmt.Sprintf("- 平均亏损: -%.2f USDT\n", stats.AvgLoss))
sb.WriteString(fmt.Sprintf("- 最大回撤: %.1f%%\n\n", stats.MaxDrawdownPct))
// 综合分析和决策建议
sb.WriteString("**决策参考**:\n")
// 根据统计数据给出具体建议
if stats.TotalTrades < 10 {
sb.WriteString("- 样本量较小(<10笔),统计结果参考意义有限\n")
}
if stats.ProfitFactor >= 1.5 && stats.SharpeRatio >= 1 {
sb.WriteString("- 📈 表现良好: 可以维持当前策略风格\n")
} else if stats.ProfitFactor >= 1.0 {
sb.WriteString("- 📊 表现正常: 策略可行但有优化空间\n")
}
if stats.ProfitFactor < 1.0 {
sb.WriteString("- ⚠️ 盈利因子<1: 亏损大于盈利,需要提高盈亏比,优化止盈止损\n")
}
if winLossRatio > 0 && winLossRatio < 1.5 {
sb.WriteString("- ⚠️ 盈亏比偏低: 建议让利润奔跑,提高止盈目标\n")
}
if stats.MaxDrawdownPct > 30 {
sb.WriteString("- ⚠️ 最大回撤过高: 建议降低仓位大小控制风险\n")
} else if stats.MaxDrawdownPct < 10 {
sb.WriteString("- ✅ 回撤控制良好: 风险管理有效\n")
}
sb.WriteString("\n")
return sb.String()
}
// formatRecentTradesZH 格式化最近交易(中文)
func formatRecentTradesZH(orders []RecentOrder) string {
var sb strings.Builder
sb.WriteString("## 最近完成的交易\n\n")
for i, order := range orders {
// 判断盈亏
profitOrLoss := "盈利"
if order.RealizedPnL < 0 {
profitOrLoss = "亏损"
}
sb.WriteString(fmt.Sprintf("%d. %s %s | 进场 %.4f 出场 %.4f | %s: %+.2f USDT (%+.2f%%) | %s → %s (%s)\n",
i+1,
order.Symbol,
order.Side,
order.EntryPrice,
order.ExitPrice,
profitOrLoss,
order.RealizedPnL,
order.PnLPct,
order.EntryTime,
order.ExitTime,
order.HoldDuration,
))
}
sb.WriteString("\n")
return sb.String()
}
// formatCurrentPositionsZH 格式化当前持仓(中文)
func formatCurrentPositionsZH(ctx *Context) string {
var sb strings.Builder
sb.WriteString("## 当前持仓\n\n")
for i, pos := range ctx.Positions {
// 计算回撤
drawdown := pos.UnrealizedPnLPct - pos.PeakPnLPct
sb.WriteString(fmt.Sprintf("%d. %s %s | ", i+1, pos.Symbol, strings.ToUpper(pos.Side)))
sb.WriteString(fmt.Sprintf("进场 %.4f 当前 %.4f | ", pos.EntryPrice, pos.MarkPrice))
sb.WriteString(fmt.Sprintf("数量 %.4f | ", pos.Quantity))
sb.WriteString(fmt.Sprintf("仓位价值 %.2f USDT | ", pos.Quantity*pos.MarkPrice))
sb.WriteString(fmt.Sprintf("盈亏 %+.2f%% | ", pos.UnrealizedPnLPct))
sb.WriteString(fmt.Sprintf("盈亏金额 %+.2f USDT | ", pos.UnrealizedPnL))
sb.WriteString(fmt.Sprintf("峰值盈亏 %.2f%% | ", pos.PeakPnLPct))
sb.WriteString(fmt.Sprintf("杠杆 %dx | ", pos.Leverage))
sb.WriteString(fmt.Sprintf("保证金 %.0f USDT | ", pos.MarginUsed))
sb.WriteString(fmt.Sprintf("强平价 %.4f\n", pos.LiquidationPrice))
// 添加分析提示
if drawdown < -0.30*pos.PeakPnLPct && pos.PeakPnLPct > 0.02 {
sb.WriteString(fmt.Sprintf(" ⚠️ **止盈提示**: 当前盈亏从峰值 %.2f%% 回撤到 %.2f%%,回撤幅度 %.2f%%,建议考虑止盈\n",
pos.PeakPnLPct, pos.UnrealizedPnLPct, (drawdown/pos.PeakPnLPct)*100))
}
if pos.UnrealizedPnLPct < -4.0 {
sb.WriteString(" ⚠️ **止损提示**: 亏损接近-5%止损线,建议考虑止损\n")
}
// 显示当前价格(如果有市场数据)
if ctx.MarketDataMap != nil {
if mdata, ok := ctx.MarketDataMap[pos.Symbol]; ok {
sb.WriteString(fmt.Sprintf(" 📈 当前价格: %.4f\n", mdata.CurrentPrice))
}
}
sb.WriteString("\n")
}
return sb.String()
}
// formatCandidateCoinsZH 格式化候选币种(中文)
func formatCandidateCoinsZH(ctx *Context) string {
var sb strings.Builder
sb.WriteString("## 候选币种\n\n")
for i, coin := range ctx.CandidateCoins {
sb.WriteString(fmt.Sprintf("### %d. %s\n\n", i+1, coin.Symbol))
// 当前价格
if ctx.MarketDataMap != nil {
if mdata, ok := ctx.MarketDataMap[coin.Symbol]; ok {
sb.WriteString(fmt.Sprintf("当前价格: %.4f\n\n", mdata.CurrentPrice))
// K线数据(多时间框架)
if mdata.TimeframeData != nil {
sb.WriteString(formatKlineDataZH(coin.Symbol, mdata.TimeframeData, ctx.Timeframes))
}
}
}
// OI数据(如果有)
if ctx.OITopDataMap != nil {
if oiData, ok := ctx.OITopDataMap[coin.Symbol]; ok {
sb.WriteString(fmt.Sprintf("**持仓量变化**: OI排名 #%d | 变化 %+.2f%% (%+.2fM USDT) | 价格变化 %+.2f%%\n\n",
oiData.Rank,
oiData.OIDeltaPercent,
oiData.OIDeltaValue/1_000_000,
oiData.PriceDeltaPercent,
))
// OI解读
oiChange := "增加"
if oiData.OIDeltaPercent < 0 {
oiChange = "减少"
}
priceChange := "上涨"
if oiData.PriceDeltaPercent < 0 {
priceChange = "下跌"
}
interpretation := getOIInterpretationZH(oiChange, priceChange)
sb.WriteString(fmt.Sprintf("**市场解读**: %s\n\n", interpretation))
}
}
}
return sb.String()
}
// formatKlineDataZH 格式化K线数据(中文)
func formatKlineDataZH(symbol string, tfData map[string]*market.TimeframeSeriesData, timeframes []string) string {
var sb strings.Builder
for _, tf := range timeframes {
if data, ok := tfData[tf]; ok && len(data.Klines) > 0 {
sb.WriteString(fmt.Sprintf("#### %s 时间框架 (从旧到新)\n\n", tf))
sb.WriteString("```\n")
sb.WriteString("时间(UTC) 开盘 最高 最低 收盘 成交量\n")
// 只显示最近30根K线
startIdx := 0
if len(data.Klines) > 30 {
startIdx = len(data.Klines) - 30
}
for i := startIdx; i < len(data.Klines); i++ {
k := data.Klines[i]
t := time.UnixMilli(k.Time).UTC()
sb.WriteString(fmt.Sprintf("%s %.4f %.4f %.4f %.4f %.2f\n",
t.Format("01-02 15:04"),
k.Open,
k.High,
k.Low,
k.Close,
k.Volume,
))
}
// 标记最后一根K线
if len(data.Klines) > 0 {
sb.WriteString(" <- 当前\n")
}
sb.WriteString("```\n\n")
}
}
return sb.String()
}
// formatOIRankingZH 格式化OI排名数据(中文)
func formatOIRankingZH(oiData interface{}) string {
// TODO: 根据实际OIRankingData结构实现
return "## 市场持仓量排名\n\n(数据加载中...)\n\n"
}
// getOIInterpretationZH 获取OI变化解读(中文)
func getOIInterpretationZH(oiChange, priceChange string) string {
if oiChange == "增加" && priceChange == "上涨" {
return OIInterpretation.OIUp_PriceUp.ZH
} else if oiChange == "增加" && priceChange == "下跌" {
return OIInterpretation.OIUp_PriceDown.ZH
} else if oiChange == "减少" && priceChange == "上涨" {
return OIInterpretation.OIDown_PriceUp.ZH
} else {
return OIInterpretation.OIDown_PriceDown.ZH
}
}
// ========== 英文格式化函数 ==========
// formatHeaderEN 格式化头部信息(英文)
func formatHeaderEN(ctx *Context) string {
return fmt.Sprintf("# 📊 Trading Decision Request\n\nTime: %s | Period: #%d | Runtime: %d minutes\n\n",
ctx.CurrentTime, ctx.CallCount, ctx.RuntimeMinutes)
}
// formatAccountEN 格式化账户信息(英文)
func formatAccountEN(ctx *Context) string {
acc := ctx.Account
var sb strings.Builder
sb.WriteString("## Account Status\n\n")
sb.WriteString(fmt.Sprintf("Total Equity: %.2f USDT | ", acc.TotalEquity))
sb.WriteString(fmt.Sprintf("Available Balance: %.2f USDT (%.1f%%) | ", acc.AvailableBalance, (acc.AvailableBalance/acc.TotalEquity)*100))
sb.WriteString(fmt.Sprintf("Total PnL: %+.2f%% | ", acc.TotalPnLPct))
sb.WriteString(fmt.Sprintf("Margin Usage: %.1f%% | ", acc.MarginUsedPct))
sb.WriteString(fmt.Sprintf("Positions: %d\n\n", acc.PositionCount))
// Risk warning
if acc.MarginUsedPct > 70 {
sb.WriteString("⚠️ **Risk Alert**: Margin usage > 70%, high risk!\n\n")
} else if acc.MarginUsedPct > 50 {
sb.WriteString("⚠️ **Risk Notice**: Margin usage > 50%, be cautious with new positions\n\n")
}
return sb.String()
}
// formatTradingStatsEN 格式化历史交易统计(英文)
func formatTradingStatsEN(stats *TradingStats) string {
var sb strings.Builder
sb.WriteString("## Historical Trading Statistics\n\n")
// Win/Loss ratio calculation
var winLossRatio float64
if stats.AvgLoss > 0 {
winLossRatio = stats.AvgWin / stats.AvgLoss
}
// Metric definitions (focus on core metrics, remove win rate)
sb.WriteString("**Metric Definitions**:\n")
sb.WriteString("- Profit Factor: Total profits ÷ Total losses (>1 = profitable, >1.5 = good, >2 = excellent)\n")
sb.WriteString("- Sharpe Ratio: (Avg return - Risk-free rate) ÷ Std dev of returns (>1 = good, >2 = excellent)\n")
sb.WriteString("- Win/Loss Ratio: Avg win ÷ Avg loss (>1.5 = good, >2 = excellent)\n")
sb.WriteString("- Max Drawdown: Largest peak-to-trough decline in equity curve (<20% = low risk)\n\n")
// Data values
sb.WriteString("**Current Data**:\n")
sb.WriteString(fmt.Sprintf("- Total Trades: %d\n", stats.TotalTrades))
sb.WriteString(fmt.Sprintf("- Profit Factor: %.2f\n", stats.ProfitFactor))
sb.WriteString(fmt.Sprintf("- Sharpe Ratio: %.2f\n", stats.SharpeRatio))
sb.WriteString(fmt.Sprintf("- Win/Loss Ratio: %.2f\n", winLossRatio))
sb.WriteString(fmt.Sprintf("- Total PnL: %+.2f USDT\n", stats.TotalPnL))
sb.WriteString(fmt.Sprintf("- Avg Win: +%.2f USDT\n", stats.AvgWin))
sb.WriteString(fmt.Sprintf("- Avg Loss: -%.2f USDT\n", stats.AvgLoss))
sb.WriteString(fmt.Sprintf("- Max Drawdown: %.1f%%\n\n", stats.MaxDrawdownPct))
// Analysis and decision guidance
sb.WriteString("**Decision Guidance**:\n")
// Specific recommendations based on stats
if stats.TotalTrades < 10 {
sb.WriteString("- Small sample size (<10 trades), statistics have limited significance\n")
}
if stats.ProfitFactor >= 1.5 && stats.SharpeRatio >= 1 {
sb.WriteString("- 📈 Good performance: Maintain current strategy approach\n")
} else if stats.ProfitFactor >= 1.0 {
sb.WriteString("- 📊 Normal performance: Strategy viable but has room for optimization\n")
}
if stats.ProfitFactor < 1.0 {
sb.WriteString("- ⚠️ Profit factor <1: Losses exceed profits, improve win/loss ratio, optimize TP/SL\n")
}
if winLossRatio > 0 && winLossRatio < 1.5 {
sb.WriteString("- ⚠️ Low win/loss ratio: Let profits run, increase take-profit targets\n")
}
if stats.MaxDrawdownPct > 30 {
sb.WriteString("- ⚠️ High max drawdown: Consider reducing position sizes to control risk\n")
} else if stats.MaxDrawdownPct < 10 {
sb.WriteString("- ✅ Good drawdown control: Risk management is effective\n")
}
sb.WriteString("\n")
return sb.String()
}
// formatRecentTradesEN 格式化最近交易(英文)
func formatRecentTradesEN(orders []RecentOrder) string {
var sb strings.Builder
sb.WriteString("## Recent Completed Trades\n\n")
for i, order := range orders {
profitOrLoss := "Profit"
if order.RealizedPnL < 0 {
profitOrLoss = "Loss"
}
sb.WriteString(fmt.Sprintf("%d. %s %s | Entry %.4f Exit %.4f | %s: %+.2f USDT (%+.2f%%) | %s → %s (%s)\n",
i+1,
order.Symbol,
order.Side,
order.EntryPrice,
order.ExitPrice,
profitOrLoss,
order.RealizedPnL,
order.PnLPct,
order.EntryTime,
order.ExitTime,
order.HoldDuration,
))
}
sb.WriteString("\n")
return sb.String()
}
// formatCurrentPositionsEN 格式化当前持仓(英文)
func formatCurrentPositionsEN(ctx *Context) string {
var sb strings.Builder
sb.WriteString("## Current Positions\n\n")
for i, pos := range ctx.Positions {
drawdown := pos.UnrealizedPnLPct - pos.PeakPnLPct
sb.WriteString(fmt.Sprintf("%d. %s %s | ", i+1, pos.Symbol, strings.ToUpper(pos.Side)))
sb.WriteString(fmt.Sprintf("Entry %.4f Current %.4f | ", pos.EntryPrice, pos.MarkPrice))
sb.WriteString(fmt.Sprintf("Qty %.4f | ", pos.Quantity))
sb.WriteString(fmt.Sprintf("Value %.2f USDT | ", pos.Quantity*pos.MarkPrice))
sb.WriteString(fmt.Sprintf("PnL %+.2f%% | ", pos.UnrealizedPnLPct))
sb.WriteString(fmt.Sprintf("PnL Amount %+.2f USDT | ", pos.UnrealizedPnL))
sb.WriteString(fmt.Sprintf("Peak PnL %.2f%% | ", pos.PeakPnLPct))
sb.WriteString(fmt.Sprintf("Leverage %dx | ", pos.Leverage))
sb.WriteString(fmt.Sprintf("Margin %.0f USDT | ", pos.MarginUsed))
sb.WriteString(fmt.Sprintf("Liq Price %.4f\n", pos.LiquidationPrice))
// Analysis hints
if drawdown < -0.30*pos.PeakPnLPct && pos.PeakPnLPct > 0.02 {
sb.WriteString(fmt.Sprintf(" ⚠️ **Take Profit Alert**: PnL dropped from peak %.2f%% to %.2f%%, drawdown %.2f%%, consider taking profit\n",
pos.PeakPnLPct, pos.UnrealizedPnLPct, (drawdown/pos.PeakPnLPct)*100))
}
if pos.UnrealizedPnLPct < -4.0 {
sb.WriteString(" ⚠️ **Stop Loss Alert**: Loss approaching -5% threshold, consider cutting loss\n")
}
if ctx.MarketDataMap != nil {
if mdata, ok := ctx.MarketDataMap[pos.Symbol]; ok {
sb.WriteString(fmt.Sprintf(" 📈 Current Price: %.4f\n", mdata.CurrentPrice))
}
}
sb.WriteString("\n")
}
return sb.String()
}
// formatCandidateCoinsEN 格式化候选币种(英文)
func formatCandidateCoinsEN(ctx *Context) string {
var sb strings.Builder
sb.WriteString("## Candidate Coins\n\n")
for i, coin := range ctx.CandidateCoins {
sb.WriteString(fmt.Sprintf("### %d. %s\n\n", i+1, coin.Symbol))
if ctx.MarketDataMap != nil {
if mdata, ok := ctx.MarketDataMap[coin.Symbol]; ok {
sb.WriteString(fmt.Sprintf("Current Price: %.4f\n\n", mdata.CurrentPrice))
if mdata.TimeframeData != nil {
sb.WriteString(formatKlineDataEN(coin.Symbol, mdata.TimeframeData, ctx.Timeframes))
}
}
}
if ctx.OITopDataMap != nil {
if oiData, ok := ctx.OITopDataMap[coin.Symbol]; ok {
sb.WriteString(fmt.Sprintf("**OI Change**: Rank #%d | Change %+.2f%% (%+.2fM USDT) | Price Change %+.2f%%\n\n",
oiData.Rank,
oiData.OIDeltaPercent,
oiData.OIDeltaValue/1_000_000,
oiData.PriceDeltaPercent,
))
oiChange := "increase"
if oiData.OIDeltaPercent < 0 {
oiChange = "decrease"
}
priceChange := "up"
if oiData.PriceDeltaPercent < 0 {
priceChange = "down"
}
interpretation := getOIInterpretationEN(oiChange, priceChange)
sb.WriteString(fmt.Sprintf("**Market Interpretation**: %s\n\n", interpretation))
}
}
}
return sb.String()
}
// formatKlineDataEN 格式化K线数据(英文)
func formatKlineDataEN(symbol string, tfData map[string]*market.TimeframeSeriesData, timeframes []string) string {
var sb strings.Builder
// Sort timeframes for consistent output
sortedTF := make([]string, len(timeframes))
copy(sortedTF, timeframes)
sort.Strings(sortedTF)
for _, tf := range sortedTF {
if data, ok := tfData[tf]; ok && len(data.Klines) > 0 {
sb.WriteString(fmt.Sprintf("#### %s Timeframe (oldest → latest)\n\n", tf))
sb.WriteString("```\n")
sb.WriteString("Time(UTC) Open High Low Close Volume\n")
startIdx := 0
if len(data.Klines) > 30 {
startIdx = len(data.Klines) - 30
}
for i := startIdx; i < len(data.Klines); i++ {
k := data.Klines[i]
t := time.UnixMilli(k.Time).UTC()
sb.WriteString(fmt.Sprintf("%s %.4f %.4f %.4f %.4f %.2f\n",
t.Format("01-02 15:04"),
k.Open,
k.High,
k.Low,
k.Close,
k.Volume,
))
}
if len(data.Klines) > 0 {
sb.WriteString(" <- current\n")
}
sb.WriteString("```\n\n")
}
}
return sb.String()
}
// formatOIRankingEN 格式化OI排名数据(英文)
func formatOIRankingEN(oiData interface{}) string {
return "## Market-wide OI Ranking\n\n(Loading data...)\n\n"
}
// getOIInterpretationEN 获取OI变化解读(英文)
func getOIInterpretationEN(oiChange, priceChange string) string {
if oiChange == "increase" && priceChange == "up" {
return OIInterpretation.OIUp_PriceUp.EN
} else if oiChange == "increase" && priceChange == "down" {
return OIInterpretation.OIUp_PriceDown.EN
} else if oiChange == "decrease" && priceChange == "up" {
return OIInterpretation.OIDown_PriceUp.EN
} else {
return OIInterpretation.OIDown_PriceDown.EN
}
}
+376
View File
@@ -0,0 +1,376 @@
package kernel
import (
"encoding/json"
"fmt"
)
// ============================================================================
// AI Prompt Builder - AI提示词构建器
// ============================================================================
// 构建完整的AI提示词,包括系统提示词和用户提示词
// ============================================================================
// PromptBuilder 提示词构建器
type PromptBuilder struct {
lang Language
}
// NewPromptBuilder 创建提示词构建器
func NewPromptBuilder(lang Language) *PromptBuilder {
return &PromptBuilder{lang: lang}
}
// BuildSystemPrompt 构建系统提示词
func (pb *PromptBuilder) BuildSystemPrompt() string {
if pb.lang == LangChinese {
return pb.buildSystemPromptZH()
}
return pb.buildSystemPromptEN()
}
// BuildUserPrompt 构建用户提示词(包含完整的交易上下文)
func (pb *PromptBuilder) BuildUserPrompt(ctx *Context) string {
// 使用Formatter格式化交易上下文
formattedData := FormatContextForAI(ctx, pb.lang)
// 添加决策要求
if pb.lang == LangChinese {
return formattedData + pb.getDecisionRequirementsZH()
}
return formattedData + pb.getDecisionRequirementsEN()
}
// ========== 中文提示词 ==========
func (pb *PromptBuilder) buildSystemPromptZH() string {
return `你是一个专业的量化交易AI助手,负责分析市场数据并做出交易决策。
## 你的任务
1. **分析账户状态**: 评估当前风险水平、保证金使用率、持仓情况
2. **分析当前持仓**: 判断是否需要止盈、止损、加仓或持有
3. **分析候选币种**: 评估新的交易机会,结合技术分析和资金流向
4. **做出决策**: 输出明确的交易决策,包含详细的推理过程
## 决策原则
### 风险优先
- 保证金使用率不得超过30%
- 单个持仓亏损达到-5%必须止损
- 优先保护资本,再考虑盈利
### 跟踪止盈
- 当持仓盈亏从峰值回撤30%时,考虑部分或全部止盈
- 例如:Peak PnL +5%Current PnL +3.5% → 回撤了30%,应该止盈
### 顺势交易
- 只在多个时间框架趋势一致时进场
- 结合持仓量(OI)变化判断资金流向真实性
- OI增加+价格上涨 = 强多头趋势
- OI减少+价格上涨 = 空头平仓(可能反转)
### 分批操作
- 分批建仓:第一次开仓不超过目标仓位的50%
- 分批止盈:盈利3%平33%,盈利5%平50%,盈利8%全平
- 只在盈利仓位上加仓,永远不要追亏损
## 输出格式要求
**必须**使用以下JSON格式输出决策:
` + "```json" + `
[
{
"symbol": "BTCUSDT",
"action": "HOLD|PARTIAL_CLOSE|FULL_CLOSE|ADD_POSITION|OPEN_NEW|WAIT",
"leverage": 3,
"position_size_usd": 1000,
"stop_loss": 42000,
"take_profit": 48000,
"confidence": 85,
"reasoning": "详细的推理过程,说明为什么做出这个决策"
}
]
` + "```" + `
### 字段说明
- **symbol**: 交易对(必需)
- **action**: 动作类型(必需)
- HOLD: 持有当前仓位
- PARTIAL_CLOSE: 部分平仓
- FULL_CLOSE: 全部平仓
- ADD_POSITION: 在现有仓位上加仓
- OPEN_NEW: 开设新仓位
- WAIT: 等待,不采取任何行动
- **leverage**: 杠杆倍数(开新仓时必需)
- **position_size_usd**: 仓位大小(USDT,开新仓时必需)
- **stop_loss**: 止损价格(开新仓时建议提供)
- **take_profit**: 止盈价格(开新仓时建议提供)
- **confidence**: 信心度(0-100
- **reasoning**: 推理过程(必需,必须详细说明决策依据)
## 重要提醒
1. **永远不要**混淆已实现盈亏和未实现盈亏
2. **永远记得**考虑杠杆对盈亏的放大作用
3. **永远关注**Peak PnL,这是判断止盈的关键指标
4. **永远结合**持仓量(OI)变化来判断趋势真实性
5. **永远遵守**风险管理规则,保护资本是第一位的
现在,请仔细分析接下来提供的交易数据,并做出专业的决策。`
}
func (pb *PromptBuilder) getDecisionRequirementsZH() string {
return `
---
## 📝 现在请做出决策
### 决策步骤
1. **分析账户风险**:
- 当前保证金使用率是否在安全范围?
- 是否有足够资金开新仓?
2. **分析现有持仓**(如果有):
- 是否触发止损条件?
- 是否触发跟踪止盈条件?
- 是否适合加仓?
3. **分析候选币种**(如果有):
- 技术形态是否符合进场条件?
- 持仓量变化是否支持趋势?
- 多个时间框架是否共振?
4. **输出决策**:
- 使用规定的JSON格式
- 提供详细的推理过程
- 给出明确的行动指令
### 输出示例
` + "```json" + `
[
{
"symbol": "PIPPINUSDT",
"action": "PARTIAL_CLOSE",
"confidence": 85,
"reasoning": "当前PnL +2.96%,接近历史峰值+2.99%(回撤仅0.03%)。建议部分平仓锁定利润,因为:1) 持仓时间仅11分钟,已获得3%收益;2) 5分钟K线显示价格接近短期阻力位;3) 成交量开始萎缩,上涨动能减弱。建议平仓50%,剩余仓位设置跟踪止盈在峰值回撤20%处。"
},
{
"symbol": "HUSDT",
"action": "OPEN_NEW",
"leverage": 3,
"position_size_usd": 500,
"stop_loss": 0.1560,
"take_profit": 0.1720,
"confidence": 75,
"reasoning": "HUSDT在5分钟时间框架突破关键阻力位0.1630,持仓量1小时内增加+1.57M (+0.89%),配合价格上涨+4.92%,符合'OI增加+价格上涨'的强多头模式。15分钟和1小时时间框架均呈现上涨趋势,多周期共振。建议开仓做多,止损设在突破点下方-5%,止盈目标+8%。"
}
]
` + "```" + `
**请立即输出你的决策(JSON格式)**:`
}
// ========== 英文提示词 ==========
func (pb *PromptBuilder) buildSystemPromptEN() string {
return `You are a professional quantitative trading AI assistant responsible for analyzing market data and making trading decisions.
## Your Mission
1. **Analyze Account Status**: Evaluate current risk level, margin usage, and positions
2. **Analyze Current Positions**: Determine if stop-loss, take-profit, scaling, or holding is needed
3. **Analyze Candidate Coins**: Assess new trading opportunities using technical analysis and capital flows
4. **Make Decisions**: Output clear trading decisions with detailed reasoning
## Decision Principles
### Risk First
- Margin usage must not exceed 30%
- Must stop-loss when single position loss reaches -5%
- Capital protection first, profit second
### Trailing Take-Profit
- Consider partial/full profit-taking when PnL pulls back 30% from peak
- Example: Peak PnL +5%, Current PnL +3.5% → 30% drawdown, should take profit
### Trend Following
- Only enter when trends align across multiple timeframes
- Use Open Interest (OI) changes to validate capital flow authenticity
- OI up + Price up = Strong bullish trend
- OI down + Price up = Shorts covering (potential reversal)
### Scale Operations
- Scale-in: First entry max 50% of target position
- Scale-out: Close 33% at +3%, 50% at +5%, 100% at +8%
- Only add to winning positions, never average down losers
## Output Format Requirements
**Must** use the following JSON format:
` + "```json" + `
[
{
"symbol": "BTCUSDT",
"action": "HOLD|PARTIAL_CLOSE|FULL_CLOSE|ADD_POSITION|OPEN_NEW|WAIT",
"leverage": 3,
"position_size_usd": 1000,
"stop_loss": 42000,
"take_profit": 48000,
"confidence": 85,
"reasoning": "Detailed reasoning explaining why this decision was made"
}
]
` + "```" + `
### Field Descriptions
- **symbol**: Trading pair (required)
- **action**: Action type (required)
- HOLD: Hold current position
- PARTIAL_CLOSE: Partially close position
- FULL_CLOSE: Fully close position
- ADD_POSITION: Add to existing position
- OPEN_NEW: Open new position
- WAIT: Wait, take no action
- **leverage**: Leverage multiplier (required for new positions)
- **position_size_usd**: Position size in USDT (required for new positions)
- **stop_loss**: Stop-loss price (recommended for new positions)
- **take_profit**: Take-profit price (recommended for new positions)
- **confidence**: Confidence level (0-100)
- **reasoning**: Detailed reasoning (required, must explain decision basis)
## Critical Reminders
1. **Never** confuse realized and unrealized P&L
2. **Always remember** leverage amplifies both gains and losses
3. **Always watch** Peak PnL - it's key for take-profit decisions
4. **Always combine** OI changes to validate trend authenticity
5. **Always follow** risk management rules - capital protection is priority #1
Now, please carefully analyze the trading data provided next and make professional decisions.`
}
func (pb *PromptBuilder) getDecisionRequirementsEN() string {
return `
---
## 📝 Make Your Decision Now
### Decision Steps
1. **Analyze Account Risk**:
- Is margin usage within safe range?
- Is there enough capital for new positions?
2. **Analyze Existing Positions** (if any):
- Is stop-loss triggered?
- Is trailing take-profit triggered?
- Is it suitable to scale-in?
3. **Analyze Candidate Coins** (if any):
- Does technical pattern meet entry criteria?
- Do OI changes support the trend?
- Do multiple timeframes align?
4. **Output Decision**:
- Use the specified JSON format
- Provide detailed reasoning
- Give clear action instructions
### Output Example
` + "```json" + `
[
{
"symbol": "PIPPINUSDT",
"action": "PARTIAL_CLOSE",
"confidence": 85,
"reasoning": "Current PnL +2.96%, near historical peak +2.99% (only 0.03% pullback). Suggest partial close to lock profits because: 1) Only 11 minutes holding time with 3% gain; 2) 5M chart shows price approaching short-term resistance; 3) Volume declining, upward momentum weakening. Recommend closing 50%, set trailing stop at 20% pullback from peak for remainder."
},
{
"symbol": "HUSDT",
"action": "OPEN_NEW",
"leverage": 3,
"position_size_usd": 500,
"stop_loss": 0.1560,
"take_profit": 0.1720,
"confidence": 75,
"reasoning": "HUSDT broke key resistance 0.1630 on 5M timeframe. OI increased +1.57M (+0.89%) in 1H paired with price +4.92%, matching 'OI up + price up' strong bullish pattern. Both 15M and 1H timeframes show uptrend, multi-timeframe resonance confirmed. Recommend long entry, stop-loss -5% below breakout, target +8% profit."
}
]
` + "```" + `
**Please output your decision (JSON format) immediately**:`
}
// ========== 辅助函数 ==========
// FormatDecisionExample 格式化决策示例(用于文档)
func FormatDecisionExample(lang Language) string {
example := Decision{
Symbol: "BTCUSDT",
Action: "OPEN_NEW",
Leverage: 3,
PositionSizeUSD: 1000,
StopLoss: 42000,
TakeProfit: 48000,
Confidence: 85,
Reasoning: "详细的推理过程...",
}
data, _ := json.MarshalIndent([]Decision{example}, "", " ")
return string(data)
}
// ValidateDecisionFormat 验证决策格式是否正确
func ValidateDecisionFormat(decisions []Decision) error {
if len(decisions) == 0 {
return fmt.Errorf("决策列表不能为空")
}
for i, d := range decisions {
// 必需字段检查
if d.Symbol == "" {
return fmt.Errorf("决策#%d: symbol不能为空", i+1)
}
if d.Action == "" {
return fmt.Errorf("决策#%d: action不能为空", i+1)
}
if d.Reasoning == "" {
return fmt.Errorf("决策#%d: reasoning不能为空", i+1)
}
// 动作类型检查
validActions := map[string]bool{
"HOLD": true,
"PARTIAL_CLOSE": true,
"FULL_CLOSE": true,
"ADD_POSITION": true,
"OPEN_NEW": true,
"WAIT": true,
}
if !validActions[d.Action] {
return fmt.Errorf("决策#%d: 无效的action类型: %s", i+1, d.Action)
}
// 开新仓位的必需参数检查
if d.Action == "OPEN_NEW" {
if d.Leverage == 0 {
return fmt.Errorf("决策#%d: OPEN_NEW动作需要提供leverage", i+1)
}
if d.PositionSizeUSD == 0 {
return fmt.Errorf("决策#%d: OPEN_NEW动作需要提供position_size_usd", i+1)
}
}
}
return nil
}
+463
View File
@@ -0,0 +1,463 @@
package kernel
import (
"strings"
"testing"
"time"
)
// TestPromptBuilder 测试提示词构建器
func TestPromptBuilder(t *testing.T) {
t.Run("NewPromptBuilder", func(t *testing.T) {
builderZH := NewPromptBuilder(LangChinese)
if builderZH == nil {
t.Fatal("NewPromptBuilder returned nil")
}
if builderZH.lang != LangChinese {
t.Error("Language not set correctly")
}
builderEN := NewPromptBuilder(LangEnglish)
if builderEN.lang != LangEnglish {
t.Error("Language not set correctly")
}
})
t.Run("BuildSystemPrompt_Chinese", func(t *testing.T) {
builder := NewPromptBuilder(LangChinese)
systemPrompt := builder.BuildSystemPrompt()
if systemPrompt == "" {
t.Fatal("System prompt is empty")
}
// 验证包含关键内容
mustContain := []string{
"量化交易AI助手",
"分析账户状态",
"分析当前持仓",
"分析候选币种",
"做出决策",
"风险优先",
"跟踪止盈",
"顺势交易",
"分批操作",
"JSON",
"symbol",
"action",
"reasoning",
}
for _, keyword := range mustContain {
if !strings.Contains(systemPrompt, keyword) {
t.Errorf("System prompt should contain '%s'", keyword)
}
}
// 验证包含所有有效的action类型
actions := []string{"HOLD", "PARTIAL_CLOSE", "FULL_CLOSE", "ADD_POSITION", "OPEN_NEW", "WAIT"}
for _, action := range actions {
if !strings.Contains(systemPrompt, action) {
t.Errorf("System prompt should mention action type '%s'", action)
}
}
})
t.Run("BuildSystemPrompt_English", func(t *testing.T) {
builder := NewPromptBuilder(LangEnglish)
systemPrompt := builder.BuildSystemPrompt()
if systemPrompt == "" {
t.Fatal("System prompt is empty")
}
// 验证包含关键内容
mustContain := []string{
"quantitative trading AI",
"Analyze Account Status",
"Analyze Current Positions",
"Analyze Candidate Coins",
"Make Decisions",
"Risk First",
"Trailing Take-Profit",
"Trend Following",
"Scale Operations",
"JSON",
"symbol",
"action",
"reasoning",
}
for _, keyword := range mustContain {
if !strings.Contains(systemPrompt, keyword) {
t.Errorf("System prompt should contain '%s'", keyword)
}
}
})
t.Run("BuildUserPrompt", func(t *testing.T) {
// 创建测试上下文
ctx := createTestContext()
builderZH := NewPromptBuilder(LangChinese)
userPromptZH := builderZH.BuildUserPrompt(ctx)
if userPromptZH == "" {
t.Fatal("User prompt is empty")
}
// 验证包含数据字典
if !strings.Contains(userPromptZH, "数据字典") {
t.Error("User prompt should contain data dictionary")
}
// 验证包含账户信息
if !strings.Contains(userPromptZH, "3079.40") { // Equity
t.Error("User prompt should contain account equity")
}
// 验证包含持仓信息
if !strings.Contains(userPromptZH, "PIPPINUSDT") {
t.Error("User prompt should contain position symbol")
}
// 验证包含决策要求
if !strings.Contains(userPromptZH, "现在请做出决策") {
t.Error("User prompt should contain decision requirements")
}
// 英文版本
builderEN := NewPromptBuilder(LangEnglish)
userPromptEN := builderEN.BuildUserPrompt(ctx)
if !strings.Contains(userPromptEN, "Data Dictionary") {
t.Error("English user prompt should contain data dictionary")
}
if !strings.Contains(userPromptEN, "Make Your Decision Now") {
t.Error("English user prompt should contain decision requirements")
}
})
}
// TestValidateDecisionFormat 测试决策格式验证
func TestValidateDecisionFormat(t *testing.T) {
t.Run("ValidDecision", func(t *testing.T) {
decisions := []Decision{
{
Symbol: "BTCUSDT",
Action: "OPEN_NEW",
Leverage: 3,
PositionSizeUSD: 1000,
StopLoss: 42000,
TakeProfit: 48000,
Confidence: 85,
Reasoning: "详细的推理过程",
},
}
err := ValidateDecisionFormat(decisions)
if err != nil {
t.Errorf("Valid decision should not return error: %v", err)
}
})
t.Run("EmptyDecisions", func(t *testing.T) {
decisions := []Decision{}
err := ValidateDecisionFormat(decisions)
if err == nil {
t.Error("Empty decisions should return error")
}
if !strings.Contains(err.Error(), "不能为空") {
t.Errorf("Error message should mention '不能为空', got: %v", err)
}
})
t.Run("MissingSymbol", func(t *testing.T) {
decisions := []Decision{
{
Symbol: "", // Missing
Action: "HOLD",
Reasoning: "Test",
},
}
err := ValidateDecisionFormat(decisions)
if err == nil {
t.Error("Missing symbol should return error")
}
if !strings.Contains(err.Error(), "symbol") {
t.Errorf("Error should mention 'symbol', got: %v", err)
}
})
t.Run("MissingAction", func(t *testing.T) {
decisions := []Decision{
{
Symbol: "BTCUSDT",
Action: "", // Missing
Reasoning: "Test",
},
}
err := ValidateDecisionFormat(decisions)
if err == nil {
t.Error("Missing action should return error")
}
})
t.Run("MissingReasoning", func(t *testing.T) {
decisions := []Decision{
{
Symbol: "BTCUSDT",
Action: "HOLD",
Reasoning: "", // Missing
},
}
err := ValidateDecisionFormat(decisions)
if err == nil {
t.Error("Missing reasoning should return error")
}
})
t.Run("InvalidAction", func(t *testing.T) {
decisions := []Decision{
{
Symbol: "BTCUSDT",
Action: "INVALID_ACTION",
Reasoning: "Test",
},
}
err := ValidateDecisionFormat(decisions)
if err == nil {
t.Error("Invalid action should return error")
}
if !strings.Contains(err.Error(), "无效的action") {
t.Errorf("Error should mention '无效的action', got: %v", err)
}
})
t.Run("OpenNewMissingLeverage", func(t *testing.T) {
decisions := []Decision{
{
Symbol: "BTCUSDT",
Action: "OPEN_NEW",
Leverage: 0, // Missing
PositionSizeUSD: 1000,
Reasoning: "Test",
},
}
err := ValidateDecisionFormat(decisions)
if err == nil {
t.Error("OPEN_NEW without leverage should return error")
}
if !strings.Contains(err.Error(), "leverage") {
t.Errorf("Error should mention 'leverage', got: %v", err)
}
})
t.Run("OpenNewMissingPositionSize", func(t *testing.T) {
decisions := []Decision{
{
Symbol: "BTCUSDT",
Action: "OPEN_NEW",
Leverage: 3,
PositionSizeUSD: 0, // Missing
Reasoning: "Test",
},
}
err := ValidateDecisionFormat(decisions)
if err == nil {
t.Error("OPEN_NEW without position_size_usd should return error")
}
if !strings.Contains(err.Error(), "position_size_usd") {
t.Errorf("Error should mention 'position_size_usd', got: %v", err)
}
})
t.Run("MultipleDecisions", func(t *testing.T) {
decisions := []Decision{
{
Symbol: "BTCUSDT",
Action: "HOLD",
Reasoning: "Hold BTC",
},
{
Symbol: "ETHUSDT",
Action: "OPEN_NEW",
Leverage: 3,
PositionSizeUSD: 500,
Reasoning: "Open ETH",
},
}
err := ValidateDecisionFormat(decisions)
if err != nil {
t.Errorf("Multiple valid decisions should not return error: %v", err)
}
})
t.Run("ValidActions", func(t *testing.T) {
validActions := []string{"HOLD", "PARTIAL_CLOSE", "FULL_CLOSE", "ADD_POSITION", "OPEN_NEW", "WAIT"}
for _, action := range validActions {
decisions := []Decision{
{
Symbol: "BTCUSDT",
Action: action,
Reasoning: "Test " + action,
},
}
// OPEN_NEW需要额外字段
if action == "OPEN_NEW" {
decisions[0].Leverage = 3
decisions[0].PositionSizeUSD = 1000
}
err := ValidateDecisionFormat(decisions)
if err != nil {
t.Errorf("Valid action '%s' should not return error: %v", action, err)
}
}
})
}
// TestFormatDecisionExample 测试决策示例格式化
func TestFormatDecisionExample(t *testing.T) {
t.Run("Chinese", func(t *testing.T) {
example := FormatDecisionExample(LangChinese)
if example == "" {
t.Fatal("Decision example is empty")
}
// 应该是有效的JSON
if !strings.HasPrefix(strings.TrimSpace(example), "[") {
t.Error("Example should be a JSON array")
}
if !strings.Contains(example, "BTCUSDT") {
t.Error("Example should contain BTCUSDT")
}
})
t.Run("English", func(t *testing.T) {
example := FormatDecisionExample(LangEnglish)
if example == "" {
t.Fatal("Decision example is empty")
}
// 验证是有效的JSON格式
if !strings.HasPrefix(strings.TrimSpace(example), "[") {
t.Error("Example should be a JSON array")
}
})
}
// BenchmarkBuildSystemPrompt 性能测试
func BenchmarkBuildSystemPrompt(b *testing.B) {
builder := NewPromptBuilder(LangChinese)
b.Run("Chinese", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = builder.BuildSystemPrompt()
}
})
builderEN := NewPromptBuilder(LangEnglish)
b.Run("English", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = builderEN.BuildSystemPrompt()
}
})
}
// BenchmarkBuildUserPrompt 性能测试
func BenchmarkBuildUserPrompt(b *testing.B) {
builder := NewPromptBuilder(LangChinese)
ctx := createTestContext()
b.Run("Chinese", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = builder.BuildUserPrompt(ctx)
}
})
builderEN := NewPromptBuilder(LangEnglish)
b.Run("English", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = builderEN.BuildUserPrompt(ctx)
}
})
}
// createTestContext 创建测试用的交易上下文
func createTestContext() *Context {
return &Context{
CurrentTime: time.Now().UTC().Format("2006-01-02 15:04:05 UTC"),
RuntimeMinutes: 78,
CallCount: 27,
Account: AccountInfo{
TotalEquity: 3079.40,
AvailableBalance: 2353.02,
UnrealizedPnL: 21.48,
TotalPnL: 470.89,
TotalPnLPct: 15.87,
MarginUsed: 726.38,
MarginUsedPct: 23.6,
PositionCount: 1,
},
Positions: []PositionInfo{
{
Symbol: "PIPPINUSDT",
Side: "long",
EntryPrice: 0.4888,
MarkPrice: 0.4937,
Quantity: 4414.0,
Leverage: 3,
UnrealizedPnL: 21.48,
UnrealizedPnLPct: 2.96,
PeakPnLPct: 2.99,
LiquidationPrice: 0.0000,
MarginUsed: 726.0,
UpdateTime: time.Now().UnixMilli(),
},
},
RecentOrders: []RecentOrder{
{
Symbol: "PIPPINUSDT",
Side: "long",
EntryPrice: 0.4756,
ExitPrice: 0.4862,
RealizedPnL: 46.10,
PnLPct: 6.71,
EntryTime: "12-24 04:36 UTC",
ExitTime: "12-24 05:35 UTC",
HoldDuration: "58m",
},
},
CandidateCoins: []CandidateCoin{
{
Symbol: "BTCUSDT",
Sources: []string{"ai500"},
},
{
Symbol: "ETHUSDT",
Sources: []string{"oi_top"},
},
},
Timeframes: []string{"5M", "15M", "1H", "4H"},
}
}
+597
View File
@@ -0,0 +1,597 @@
package kernel
import "fmt"
// ============================================================================
// Trading Data Schema - 交易数据字典
// ============================================================================
// 双语数据字典,支持中文和英文
// 确保AI能够100%理解数据格式,无论使用哪种语言
// ============================================================================
const (
SchemaVersion = "1.0.0"
)
// Language 语言类型
type Language string
const (
LangChinese Language = "zh-CN"
LangEnglish Language = "en-US"
)
// ========== 双语字段定义 ==========
// BilingualFieldDef 双语字段定义
type BilingualFieldDef struct {
NameZH string // 中文名称
NameEN string // English name
Unit string // 单位
FormulaZH string // 中文公式
FormulaEN string // English formula
DescZH string // 中文描述
DescEN string // English description
}
// GetName 获取字段名称(根据语言)
func (d BilingualFieldDef) GetName(lang Language) string {
if lang == LangChinese {
return d.NameZH
}
return d.NameEN
}
// GetFormula 获取公式(根据语言)
func (d BilingualFieldDef) GetFormula(lang Language) string {
if lang == LangChinese {
return d.FormulaZH
}
return d.FormulaEN
}
// GetDesc 获取描述(根据语言)
func (d BilingualFieldDef) GetDesc(lang Language) string {
if lang == LangChinese {
return d.DescZH
}
return d.DescEN
}
// ========== 数据字典 ==========
// DataDictionary 数据字典:定义所有字段的含义
var DataDictionary = map[string]map[string]BilingualFieldDef{
"AccountMetrics": {
"Equity": {
NameZH: "总权益",
NameEN: "Total Equity",
Unit: "USDT",
FormulaZH: "可用余额 + 未实现盈亏",
FormulaEN: "Available Balance + Unrealized PnL",
DescZH: "账户的实际净值,包含所有持仓的浮动盈亏",
DescEN: "Actual account value including all unrealized P&L from positions",
},
"Balance": {
NameZH: "可用余额",
NameEN: "Available Balance",
Unit: "USDT",
FormulaZH: "初始资金 + 已实现盈亏",
FormulaEN: "Initial Capital + Realized PnL",
DescZH: "可用于开新仓位的资金,不包括已用保证金",
DescEN: "Available funds for opening new positions, excluding used margin",
},
"PnL": {
NameZH: "总盈亏百分比",
NameEN: "Total PnL Percentage",
Unit: "%",
FormulaZH: "(总权益 - 初始资金) / 初始资金 × 100",
FormulaEN: "(Total Equity - Initial Capital) / Initial Capital × 100",
DescZH: "自系统启动以来的总收益率,+15.87%表示盈利15.87%",
DescEN: "Total return since inception, +15.87% means 15.87% profit",
},
"Margin": {
NameZH: "保证金使用率",
NameEN: "Margin Usage Rate",
Unit: "%",
FormulaZH: "已用保证金合计 / 总权益 × 100",
FormulaEN: "Total Used Margin / Total Equity × 100",
DescZH: "该值越高,账户风险越大。安全值<30%,危险值>70%",
DescEN: "Higher value = higher risk. Safe <30%, Dangerous >70%",
},
},
"TradeMetrics": {
"Entry": {
NameZH: "进场价",
NameEN: "Entry Price",
Unit: "USDT",
DescZH: "开仓时的平均价格",
DescEN: "Average price when opening position",
},
"Exit": {
NameZH: "出场价",
NameEN: "Exit Price",
Unit: "USDT",
DescZH: "平仓时的平均价格",
DescEN: "Average price when closing position",
},
"Profit": {
NameZH: "已实现盈亏",
NameEN: "Realized PnL",
Unit: "USDT",
FormulaZH: "(出场价 - 进场价) / 进场价 × 杠杆 × 仓位价值",
FormulaEN: "(Exit Price - Entry Price) / Entry Price × Leverage × Position Value",
DescZH: "已平仓交易的实际盈亏,包含手续费。正值=盈利,负值=亏损",
DescEN: "Actual profit/loss of closed trades including fees. Positive=profit, Negative=loss",
},
"PnL%": {
NameZH: "盈亏百分比",
NameEN: "PnL Percentage",
Unit: "%",
FormulaZH: "(出场价 - 进场价) / 进场价 × 杠杆 × 100",
FormulaEN: "(Exit - Entry) / Entry × Leverage × 100",
DescZH: "已平仓交易的收益率,+6.71%表示盈利6.71%",
DescEN: "Return on closed trade, +6.71% means 6.71% profit",
},
"HoldDuration": {
NameZH: "持仓时长",
NameEN: "Holding Duration",
Unit: "minutes",
DescZH: "从开仓到平仓的时间。<15分钟=超短线,15分钟-4小时=日内,>4小时=波段",
DescEN: "Time from open to close. <15min=scalping, 15min-4h=intraday, >4h=swing",
},
},
"PositionMetrics": {
"UnrealizedPnL%": {
NameZH: "未实现盈亏百分比",
NameEN: "Unrealized PnL Percentage",
Unit: "%",
FormulaZH: "(当前价 - 进场价) / 进场价 × 杠杆 × 100",
FormulaEN: "(Current Price - Entry Price) / Entry Price × Leverage × 100",
DescZH: "当前持仓的浮动盈亏,未平仓前是浮动的",
DescEN: "Floating P&L of current position, not realized until closed",
},
"PeakPnL%": {
NameZH: "峰值盈亏百分比",
NameEN: "Peak PnL Percentage",
Unit: "%",
DescZH: "该持仓曾经达到的最高未实现盈亏。用于判断是否需要止盈",
DescEN: "Historical max unrealized PnL for this position. Used for take-profit decisions",
},
"Drawdown": {
NameZH: "从峰值回撤",
NameEN: "Drawdown from Peak",
Unit: "%",
FormulaZH: "当前盈亏% - 峰值盈亏%",
FormulaEN: "Current PnL% - Peak PnL%",
DescZH: "负值表示正在回撤。例如:峰值+5%,当前+3%,回撤=-2%",
DescEN: "Negative = pulling back. E.g., Peak +5%, Current +3%, Drawdown = -2%",
},
"Leverage": {
NameZH: "杠杆倍数",
NameEN: "Leverage",
Unit: "x",
DescZH: "3x表示价格变动1%,持仓盈亏变动3%。杠杆越高,风险越大",
DescEN: "3x means 1% price move = 3% position PnL. Higher leverage = higher risk",
},
"Margin": {
NameZH: "占用保证金",
NameEN: "Margin Used",
Unit: "USDT",
FormulaZH: "仓位价值 / 杠杆",
FormulaEN: "Position Value / Leverage",
DescZH: "该仓位锁定的保证金金额",
DescEN: "Collateral locked for this position",
},
"LiqPrice": {
NameZH: "强平价格",
NameEN: "Liquidation Price",
Unit: "USDT",
DescZH: "价格触及此值时会被强制平仓。0.0000表示无爆仓风险",
DescEN: "Price at which position will be force-closed. 0.0000 = no liquidation risk",
},
},
"MarketData": {
"Volume": {
NameZH: "成交量",
NameEN: "Volume",
Unit: "base asset",
DescZH: "该时间段的交易量",
DescEN: "Trading volume in this period",
},
"OI": {
NameZH: "持仓量",
NameEN: "Open Interest",
Unit: "USDT",
DescZH: "未平仓合约的总价值。持仓量增加=资金流入,减少=资金流出",
DescEN: "Total value of open contracts. Increasing OI = capital inflow, decreasing = outflow",
},
"OIChange": {
NameZH: "持仓量变化",
NameEN: "OI Change",
Unit: "USDT & %",
DescZH: "1小时内持仓量的变化。用于判断市场真实资金流向",
DescEN: "OI change in 1 hour. Used to determine real capital flow direction",
},
},
}
// ========== 双语规则定义 ==========
// BilingualRuleDef 双语规则定义
type BilingualRuleDef struct {
Value interface{} // 规则值
DescZH string // 中文描述
DescEN string // English description
ReasonZH string // 中文原因
ReasonEN string // English reason
}
// GetDesc 获取描述(根据语言)
func (d BilingualRuleDef) GetDesc(lang Language) string {
if lang == LangChinese {
return d.DescZH
}
return d.DescEN
}
// GetReason 获取原因(根据语言)
func (d BilingualRuleDef) GetReason(lang Language) string {
if lang == LangChinese {
return d.ReasonZH
}
return d.ReasonEN
}
// ========== 交易规则 ==========
// TradingRules 交易规则定义
var TradingRules = struct {
RiskManagement map[string]BilingualRuleDef
EntrySignals map[string]BilingualRuleDef
ExitSignals map[string]BilingualRuleDef
PositionControl map[string]BilingualRuleDef
}{
RiskManagement: map[string]BilingualRuleDef{
"MaxMarginUsage": {
Value: 0.30,
DescZH: "保证金使用率不得超过30%",
DescEN: "Margin usage must not exceed 30%",
ReasonZH: "保留70%的资金应对极端行情和追加保证金",
ReasonEN: "Reserve 70% capital for extreme market conditions and margin calls",
},
"MaxPositionLoss": {
Value: -0.05,
DescZH: "单个持仓亏损达到-5%时必须止损",
DescEN: "Must stop-loss when single position loss reaches -5%",
ReasonZH: "避免单笔交易造成过大损失",
ReasonEN: "Prevent excessive loss from single trade",
},
"MaxDailyLoss": {
Value: -0.10,
DescZH: "单日亏损达到-10%时停止交易",
DescEN: "Stop trading when daily loss reaches -10%",
ReasonZH: "防止情绪化交易导致连续亏损",
ReasonEN: "Prevent emotional trading leading to consecutive losses",
},
"PositionSizeLimit": {
Value: 0.15,
DescZH: "单个仓位不得超过总权益的15%",
DescEN: "Single position must not exceed 15% of total equity",
ReasonZH: "避免过度集中风险",
ReasonEN: "Avoid excessive risk concentration",
},
},
EntrySignals: map[string]BilingualRuleDef{
"VolumeSpike": {
Value: 2.0,
DescZH: "成交量是平均值的2倍以上时考虑进场",
DescEN: "Consider entry when volume is 2x above average",
ReasonZH: "放量突破通常意味着强趋势",
ReasonEN: "Volume breakout usually indicates strong trend",
},
"OIChangeThreshold": {
Value: 0.02,
DescZH: "持仓量1小时内变化超过2%视为显著变化",
DescEN: "OI change >2% in 1 hour is considered significant",
ReasonZH: "大额资金进出会导致持仓量显著变化",
ReasonEN: "Large capital flows cause significant OI changes",
},
},
ExitSignals: map[string]BilingualRuleDef{
"TrailingStop": {
Value: 0.30,
DescZH: "当盈亏从峰值回撤30%时平仓止盈",
DescEN: "Close position when PnL pulls back 30% from peak",
ReasonZH: "锁定大部分利润,避免盈利回吐。例如:峰值+5%,回撤到+3.5%时平仓",
ReasonEN: "Lock in most profits, avoid profit giveback. E.g., Peak +5%, close at +3.5%",
},
"StopLoss": {
Value: -0.05,
DescZH: "硬止损设置在-5%",
DescEN: "Hard stop-loss at -5%",
ReasonZH: "严格控制单笔最大损失",
ReasonEN: "Strictly control maximum single-trade loss",
},
},
PositionControl: map[string]BilingualRuleDef{
"ScaleIn": {
Value: map[string]interface{}{"enabled": true, "max_additions": 2, "price_requirement": 0.01},
DescZH: "只在盈利仓位上加仓,最多加2次,价格需比平均成本高1%",
DescEN: "Only add to winning positions, max 2 additions, price must be 1% above avg cost",
ReasonZH: "顺势加仓,不追亏损",
ReasonEN: "Add to winners, never average down losers",
},
"ScaleOut": {
Value: []map[string]interface{}{
{"pnl": 0.03, "close_pct": 0.33},
{"pnl": 0.05, "close_pct": 0.50},
{"pnl": 0.08, "close_pct": 1.00},
},
DescZH: "分批止盈:盈利3%时平33%5%时平50%8%时全平",
DescEN: "Scale-out: Close 33% at +3%, 50% at +5%, 100% at +8%",
ReasonZH: "在保证利润的同时让盈利奔跑",
ReasonEN: "Lock profits while letting winners run",
},
},
}
// ========== OI解读 ==========
// OIInterpretation OI变化的市场解读(双语)
type OIInterpretationType struct {
OIUp_PriceUp struct {
ZH string
EN string
}
OIUp_PriceDown struct {
ZH string
EN string
}
OIDown_PriceUp struct {
ZH string
EN string
}
OIDown_PriceDown struct {
ZH string
EN string
}
}
var OIInterpretation = OIInterpretationType{
OIUp_PriceUp: struct {
ZH string
EN string
}{
ZH: "强多头趋势(新多单开仓,资金流入做多)",
EN: "Strong bullish trend (new longs opening, capital flowing into long positions)",
},
OIUp_PriceDown: struct {
ZH string
EN string
}{
ZH: "强空头趋势(新空单开仓,资金流入做空)",
EN: "Strong bearish trend (new shorts opening, capital flowing into short positions)",
},
OIDown_PriceUp: struct {
ZH string
EN string
}{
ZH: "空头平仓(空头止损离场,可能出现反转)",
EN: "Shorts covering (shorts stopped out, potential reversal)",
},
OIDown_PriceDown: struct {
ZH string
EN string
}{
ZH: "多头平仓(多头止损离场,可能出现反转)",
EN: "Longs closing (longs stopped out, potential reversal)",
},
}
// ========== 常见错误 ==========
// CommonMistake 常见错误定义
type CommonMistake struct {
ErrorZH string
ErrorEN string
ExampleZH string
ExampleEN string
CorrectZH string
CorrectEN string
}
var CommonMistakes = []CommonMistake{
{
ErrorZH: "混淆已实现盈亏和未实现盈亏",
ErrorEN: "Confusing realized and unrealized P&L",
ExampleZH: "将历史交易的盈亏与当前持仓的盈亏相加",
ExampleEN: "Adding historical trade P&L with current position P&L",
CorrectZH: "已实现盈亏已经计入账户余额,不应重复计算",
CorrectEN: "Realized P&L is already included in account balance, don't double count",
},
{
ErrorZH: "忽略杠杆对盈亏的影响",
ErrorEN: "Ignoring leverage's impact on P&L",
ExampleZH: "价格涨1%,认为盈利1%",
ExampleEN: "Price up 1%, thinking profit is 1%",
CorrectZH: "3x杠杆时,价格涨1%,实际盈利约3%",
CorrectEN: "With 3x leverage, 1% price move = ~3% P&L",
},
{
ErrorZH: "不理解Peak PnL的重要性",
ErrorEN: "Not understanding Peak PnL's importance",
ExampleZH: "只关注当前PnL,不关注回撤",
ExampleEN: "Only watching current PnL, ignoring drawdown",
CorrectZH: "当前PnL接近Peak PnL时,应考虑止盈以锁定利润",
CorrectEN: "When current PnL near Peak PnL, consider taking profit to lock in gains",
},
{
ErrorZH: "忽略持仓量(OI)变化",
ErrorEN: "Ignoring Open Interest changes",
ExampleZH: "只看价格K线,不看资金流向",
ExampleEN: "Only watching price candles, not capital flows",
CorrectZH: "结合OI变化判断趋势的真实性和持续性",
CorrectEN: "Use OI changes to validate trend authenticity and sustainability",
},
}
// ========== Prompt生成函数 ==========
// GetSchemaPrompt 生成Schema说明文本,用于AI Prompt
func GetSchemaPrompt(lang Language) string {
if lang == LangChinese {
return getSchemaPromptZH()
}
return getSchemaPromptEN()
}
// getSchemaPromptZH 生成中文Prompt
func getSchemaPromptZH() string {
prompt := "# 📖 数据字典与交易规则\n\n"
prompt += "## 📊 字段含义说明\n\n"
// 账户指标
prompt += "### 账户指标\n"
for key, field := range DataDictionary["AccountMetrics"] {
prompt += formatFieldDefZH(key, field)
}
// 交易指标
prompt += "\n### 交易指标\n"
for key, field := range DataDictionary["TradeMetrics"] {
prompt += formatFieldDefZH(key, field)
}
// 持仓指标
prompt += "\n### 持仓指标\n"
for key, field := range DataDictionary["PositionMetrics"] {
prompt += formatFieldDefZH(key, field)
}
// 市场数据
prompt += "\n### 市场数据\n"
for key, field := range DataDictionary["MarketData"] {
prompt += formatFieldDefZH(key, field)
}
// 交易规则
prompt += "\n## ⚖️ 交易规则\n\n"
prompt += "### 风险管理\n"
for name, rule := range TradingRules.RiskManagement {
prompt += "- **" + name + "**: " + rule.DescZH + "\n 理由:" + rule.ReasonZH + "\n"
}
prompt += "\n### 出场信号\n"
for name, rule := range TradingRules.ExitSignals {
prompt += "- **" + name + "**: " + rule.DescZH + "\n 理由:" + rule.ReasonZH + "\n"
}
// OI解读
prompt += "\n## 💹 持仓量(OI)变化解读\n\n"
prompt += "- **OI增加 + 价格上涨**: " + OIInterpretation.OIUp_PriceUp.ZH + "\n"
prompt += "- **OI增加 + 价格下跌**: " + OIInterpretation.OIUp_PriceDown.ZH + "\n"
prompt += "- **OI减少 + 价格上涨**: " + OIInterpretation.OIDown_PriceUp.ZH + "\n"
prompt += "- **OI减少 + 价格下跌**: " + OIInterpretation.OIDown_PriceDown.ZH + "\n"
// 常见错误
prompt += "\n## ⚠️ 常见错误(请避免)\n\n"
for i, mistake := range CommonMistakes {
prompt += fmt.Sprintf("**错误%d**: %s\n", i+1, mistake.ErrorZH)
prompt += "- 错误示例:" + mistake.ExampleZH + "\n"
prompt += "- 正确做法:" + mistake.CorrectZH + "\n\n"
}
return prompt
}
// getSchemaPromptEN 生成英文Prompt
func getSchemaPromptEN() string {
prompt := "# 📖 Data Dictionary & Trading Rules\n\n"
prompt += "## 📊 Field Definitions\n\n"
// Account Metrics
prompt += "### Account Metrics\n"
for key, field := range DataDictionary["AccountMetrics"] {
prompt += formatFieldDefEN(key, field)
}
// Trade Metrics
prompt += "\n### Trade Metrics\n"
for key, field := range DataDictionary["TradeMetrics"] {
prompt += formatFieldDefEN(key, field)
}
// Position Metrics
prompt += "\n### Position Metrics\n"
for key, field := range DataDictionary["PositionMetrics"] {
prompt += formatFieldDefEN(key, field)
}
// Market Data
prompt += "\n### Market Data\n"
for key, field := range DataDictionary["MarketData"] {
prompt += formatFieldDefEN(key, field)
}
// Trading Rules
prompt += "\n## ⚖️ Trading Rules\n\n"
prompt += "### Risk Management\n"
for name, rule := range TradingRules.RiskManagement {
prompt += "- **" + name + "**: " + rule.DescEN + "\n Reason: " + rule.ReasonEN + "\n"
}
prompt += "\n### Exit Signals\n"
for name, rule := range TradingRules.ExitSignals {
prompt += "- **" + name + "**: " + rule.DescEN + "\n Reason: " + rule.ReasonEN + "\n"
}
// OI Interpretation
prompt += "\n## 💹 Open Interest (OI) Change Interpretation\n\n"
prompt += "- **OI Up + Price Up**: " + OIInterpretation.OIUp_PriceUp.EN + "\n"
prompt += "- **OI Up + Price Down**: " + OIInterpretation.OIUp_PriceDown.EN + "\n"
prompt += "- **OI Down + Price Up**: " + OIInterpretation.OIDown_PriceUp.EN + "\n"
prompt += "- **OI Down + Price Down**: " + OIInterpretation.OIDown_PriceDown.EN + "\n"
// Common Mistakes
prompt += "\n## ⚠️ Common Mistakes to Avoid\n\n"
for i, mistake := range CommonMistakes {
prompt += fmt.Sprintf("**Mistake %d**: %s\n", i+1, mistake.ErrorEN)
prompt += "- Bad Example: " + mistake.ExampleEN + "\n"
prompt += "- Correct Approach: " + mistake.CorrectEN + "\n\n"
}
return prompt
}
// formatFieldDefZH 格式化中文字段定义
func formatFieldDefZH(key string, field BilingualFieldDef) string {
result := "- **" + key + "**" + field.NameZH + ": " + field.DescZH
if field.FormulaZH != "" {
result += " | 公式: `" + field.FormulaZH + "`"
}
if field.Unit != "" {
result += " | 单位: " + field.Unit
}
result += "\n"
return result
}
// formatFieldDefEN 格式化英文字段定义
func formatFieldDefEN(key string, field BilingualFieldDef) string {
result := "- **" + key + "** (" + field.NameEN + "): " + field.DescEN
if field.FormulaEN != "" {
result += " | Formula: `" + field.FormulaEN + "`"
}
if field.Unit != "" {
result += " | Unit: " + field.Unit
}
result += "\n"
return result
}
+284
View File
@@ -0,0 +1,284 @@
package kernel
import (
"strings"
"testing"
)
// TestDataDictionary 测试数据字典定义
func TestDataDictionary(t *testing.T) {
// 测试账户指标字典
t.Run("AccountMetrics", func(t *testing.T) {
equity := DataDictionary["AccountMetrics"]["Equity"]
if equity.NameZH != "总权益" {
t.Errorf("Expected NameZH='总权益', got '%s'", equity.NameZH)
}
if equity.NameEN != "Total Equity" {
t.Errorf("Expected NameEN='Total Equity', got '%s'", equity.NameEN)
}
if equity.Unit != "USDT" {
t.Errorf("Expected Unit='USDT', got '%s'", equity.Unit)
}
if equity.GetName(LangChinese) != "总权益" {
t.Errorf("GetName(Chinese) failed")
}
if equity.GetName(LangEnglish) != "Total Equity" {
t.Errorf("GetName(English) failed")
}
})
// 测试持仓指标字典
t.Run("PositionMetrics", func(t *testing.T) {
peakPnL := DataDictionary["PositionMetrics"]["PeakPnL%"]
if peakPnL.NameZH == "" {
t.Error("PeakPnL% NameZH is empty")
}
if peakPnL.NameEN == "" {
t.Error("PeakPnL% NameEN is empty")
}
if !strings.Contains(peakPnL.DescZH, "峰值") {
t.Error("PeakPnL% DescZH should contain '峰值'")
}
})
}
// TestTradingRules 测试交易规则定义
func TestTradingRules(t *testing.T) {
t.Run("RiskManagement", func(t *testing.T) {
maxMargin := TradingRules.RiskManagement["MaxMarginUsage"]
if maxMargin.Value != 0.30 {
t.Errorf("Expected MaxMarginUsage=0.30, got %v", maxMargin.Value)
}
if maxMargin.GetDesc(LangChinese) == "" {
t.Error("MaxMarginUsage DescZH is empty")
}
if maxMargin.GetDesc(LangEnglish) == "" {
t.Error("MaxMarginUsage DescEN is empty")
}
if !strings.Contains(maxMargin.DescZH, "30%") {
t.Error("MaxMarginUsage DescZH should mention 30%")
}
})
t.Run("ExitSignals", func(t *testing.T) {
trailing := TradingRules.ExitSignals["TrailingStop"]
if trailing.Value != 0.30 {
t.Errorf("Expected TrailingStop=0.30, got %v", trailing.Value)
}
if !strings.Contains(trailing.ReasonZH, "止盈") {
t.Error("TrailingStop ReasonZH should mention '止盈'")
}
if !strings.Contains(trailing.ReasonEN, "profit") {
t.Error("TrailingStop ReasonEN should mention 'profit'")
}
})
}
// TestOIInterpretation 测试OI解读
func TestOIInterpretation(t *testing.T) {
t.Run("OI_Up_Price_Up", func(t *testing.T) {
if OIInterpretation.OIUp_PriceUp.ZH == "" {
t.Error("OI Up + Price Up ZH is empty")
}
if OIInterpretation.OIUp_PriceUp.EN == "" {
t.Error("OI Up + Price Up EN is empty")
}
if !strings.Contains(OIInterpretation.OIUp_PriceUp.ZH, "多头") {
t.Error("OI Up + Price Up should indicate bullish trend")
}
})
}
// TestCommonMistakes 测试常见错误定义
func TestCommonMistakes(t *testing.T) {
if len(CommonMistakes) == 0 {
t.Error("CommonMistakes should not be empty")
}
for i, mistake := range CommonMistakes {
if mistake.ErrorZH == "" {
t.Errorf("Mistake #%d ErrorZH is empty", i+1)
}
if mistake.ErrorEN == "" {
t.Errorf("Mistake #%d ErrorEN is empty", i+1)
}
if mistake.CorrectZH == "" {
t.Errorf("Mistake #%d CorrectZH is empty", i+1)
}
if mistake.CorrectEN == "" {
t.Errorf("Mistake #%d CorrectEN is empty", i+1)
}
}
}
// TestGetSchemaPrompt 测试Schema提示词生成
func TestGetSchemaPrompt(t *testing.T) {
t.Run("Chinese", func(t *testing.T) {
prompt := GetSchemaPrompt(LangChinese)
if prompt == "" {
t.Fatal("Chinese schema prompt is empty")
}
// 验证包含关键内容
mustContain := []string{
"数据字典",
"账户指标",
"交易指标",
"持仓指标",
"市场数据",
"交易规则",
"风险管理",
"持仓量(OI)变化解读",
"常见错误",
}
for _, keyword := range mustContain {
if !strings.Contains(prompt, keyword) {
t.Errorf("Chinese prompt should contain '%s'", keyword)
}
}
})
t.Run("English", func(t *testing.T) {
prompt := GetSchemaPrompt(LangEnglish)
if prompt == "" {
t.Fatal("English schema prompt is empty")
}
// 验证包含关键内容
mustContain := []string{
"Data Dictionary",
"Account Metrics",
"Trade Metrics",
"Position Metrics",
"Market Data",
"Trading Rules",
"Risk Management",
"Open Interest",
"Common Mistakes",
}
for _, keyword := range mustContain {
if !strings.Contains(prompt, keyword) {
t.Errorf("English prompt should contain '%s'", keyword)
}
}
})
t.Run("Consistency", func(t *testing.T) {
promptZH := GetSchemaPrompt(LangChinese)
promptEN := GetSchemaPrompt(LangEnglish)
// 两个版本都应该包含相同数量的字段定义
// 虽然内容不同,但结构应该相似
zhLines := strings.Split(promptZH, "\n")
enLines := strings.Split(promptEN, "\n")
// 行数应该大致相当(允许10%的差异)
ratio := float64(len(zhLines)) / float64(len(enLines))
if ratio < 0.9 || ratio > 1.1 {
t.Logf("Warning: Line count difference is significant (ZH: %d, EN: %d)",
len(zhLines), len(enLines))
}
})
}
// BenchmarkGetSchemaPrompt 性能测试
func BenchmarkGetSchemaPrompt(b *testing.B) {
b.Run("Chinese", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = GetSchemaPrompt(LangChinese)
}
})
b.Run("English", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = GetSchemaPrompt(LangEnglish)
}
})
}
// TestFieldDefinitionMethods 测试字段定义方法
func TestFieldDefinitionMethods(t *testing.T) {
field := BilingualFieldDef{
NameZH: "测试字段",
NameEN: "Test Field",
Unit: "USDT",
FormulaZH: "中文公式",
FormulaEN: "English formula",
DescZH: "中文描述",
DescEN: "English description",
}
// 测试GetName
if field.GetName(LangChinese) != "测试字段" {
t.Error("GetName(Chinese) failed")
}
if field.GetName(LangEnglish) != "Test Field" {
t.Error("GetName(English) failed")
}
// 测试GetFormula
if field.GetFormula(LangChinese) != "中文公式" {
t.Error("GetFormula(Chinese) failed")
}
if field.GetFormula(LangEnglish) != "English formula" {
t.Error("GetFormula(English) failed")
}
// 测试GetDesc
if field.GetDesc(LangChinese) != "中文描述" {
t.Error("GetDesc(Chinese) failed")
}
if field.GetDesc(LangEnglish) != "English description" {
t.Error("GetDesc(English) failed")
}
}
// TestRuleDefinitionMethods 测试规则定义方法
func TestRuleDefinitionMethods(t *testing.T) {
rule := BilingualRuleDef{
Value: 0.30,
DescZH: "中文描述",
DescEN: "English description",
ReasonZH: "中文原因",
ReasonEN: "English reason",
}
if rule.GetDesc(LangChinese) != "中文描述" {
t.Error("GetDesc(Chinese) failed")
}
if rule.GetDesc(LangEnglish) != "English description" {
t.Error("GetDesc(English) failed")
}
if rule.GetReason(LangChinese) != "中文原因" {
t.Error("GetReason(Chinese) failed")
}
if rule.GetReason(LangEnglish) != "English reason" {
t.Error("GetReason(English) failed")
}
}
+117
View File
@@ -0,0 +1,117 @@
package kernel
import (
"testing"
)
// TestLeverageFallback tests automatic correction when leverage exceeds limit
func TestLeverageFallback(t *testing.T) {
tests := []struct {
name string
decision Decision
accountEquity float64
btcEthLeverage int
altcoinLeverage int
wantLeverage int // Expected leverage after correction
wantError bool
}{
{
name: "Altcoin leverage exceeded - auto-correct to limit",
decision: Decision{
Symbol: "SOLUSDT",
Action: "open_long",
Leverage: 20, // Exceeds limit
PositionSizeUSD: 100,
StopLoss: 50,
TakeProfit: 200,
},
accountEquity: 100,
btcEthLeverage: 10,
altcoinLeverage: 5, // Limit 5x
wantLeverage: 5, // Should be corrected to 5
wantError: false,
},
{
name: "BTC leverage exceeded - auto-correct to limit",
decision: Decision{
Symbol: "BTCUSDT",
Action: "open_long",
Leverage: 20, // Exceeds limit
PositionSizeUSD: 1000,
StopLoss: 90000,
TakeProfit: 110000,
},
accountEquity: 100,
btcEthLeverage: 10, // Limit 10x
altcoinLeverage: 5,
wantLeverage: 10, // Should be corrected to 10
wantError: false,
},
{
name: "Leverage within limit - no correction",
decision: Decision{
Symbol: "ETHUSDT",
Action: "open_short",
Leverage: 5, // Not exceeded
PositionSizeUSD: 500,
StopLoss: 4000,
TakeProfit: 3000,
},
accountEquity: 100,
btcEthLeverage: 10,
altcoinLeverage: 5,
wantLeverage: 5, // Stays unchanged
wantError: false,
},
{
name: "Leverage is 0 - should error",
decision: Decision{
Symbol: "SOLUSDT",
Action: "open_long",
Leverage: 0, // Invalid
PositionSizeUSD: 100,
StopLoss: 50,
TakeProfit: 200,
},
accountEquity: 100,
btcEthLeverage: 10,
altcoinLeverage: 5,
wantLeverage: 0,
wantError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Use default position value ratios for testing (10x for BTC/ETH, 1.5x for altcoins)
err := validateDecision(&tt.decision, tt.accountEquity, tt.btcEthLeverage, tt.altcoinLeverage, 10.0, 1.5)
// Check error status
if (err != nil) != tt.wantError {
t.Errorf("validateDecision() error = %v, wantError %v", err, tt.wantError)
return
}
// If shouldn't error, check if leverage was correctly corrected
if !tt.wantError && tt.decision.Leverage != tt.wantLeverage {
t.Errorf("Leverage not corrected: got %d, want %d", tt.decision.Leverage, tt.wantLeverage)
}
})
}
}
// contains checks if string contains substring (helper function)
func contains(s, substr string) bool {
return len(s) >= len(substr) && (s == substr || len(substr) == 0 ||
(len(s) > 0 && len(substr) > 0 && stringContains(s, substr)))
}
func stringContains(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}