From b7a1a60c6fad2b67592401ded306ce6f7d644901 Mon Sep 17 00:00:00 2001 From: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com> Date: Sun, 2 Nov 2025 22:08:39 +0800 Subject: [PATCH 1/7] =?UTF-8?q?fix:=20=E8=BF=87=E6=BB=A4=E5=B9=BD=E7=81=B5?= =?UTF-8?q?=E6=8C=81=E4=BB=93=20-=20=E8=B7=B3=E8=BF=87=20quantity=3D0=20?= =?UTF-8?q?=E7=9A=84=E6=8C=81=E4=BB=93=E9=98=B2=E6=AD=A2=20AI=20=E8=AF=AF?= =?UTF-8?q?=E5=88=A4=20=E9=97=AE=E9=A2=98=EF=BC=9A=20-=20=E6=AD=A2?= =?UTF-8?q?=E6=8D=9F/=E6=AD=A2=E7=9B=88=E8=A7=A6=E5=8F=91=E5=90=8E?= =?UTF-8?q?=EF=BC=8C=E4=BA=A4=E6=98=93=E6=89=80=E8=BF=94=E5=9B=9E=20positi?= =?UTF-8?q?onAmt=3D0=20=E7=9A=84=E6=8C=81=E4=BB=93=E8=AE=B0=E5=BD=95=20-?= =?UTF-8?q?=20=E8=BF=99=E4=BA=9B=E5=B9=BD=E7=81=B5=E6=8C=81=E4=BB=93?= =?UTF-8?q?=E8=A2=AB=E4=BC=A0=E9=80=92=E7=BB=99=20AI=EF=BC=8C=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=20AI=20=E8=AF=AF=E4=BB=A5=E4=B8=BA=E4=BB=8D=E6=8C=81?= =?UTF-8?q?=E6=9C=89=E8=AF=A5=E5=B8=81=E7=A7=8D=20-=20AI=20=E5=8F=AF?= =?UTF-8?q?=E8=83=BD=E5=9F=BA=E4=BA=8E=E9=94=99=E8=AF=AF=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E5=81=9A=E5=87=BA=E5=86=B3=E7=AD=96=EF=BC=88=E5=A6=82=E5=B0=9D?= =?UTF-8?q?=E8=AF=95=E8=B0=83=E6=95=B4=E5=B7=B2=E4=B8=8D=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E7=9A=84=E6=AD=A2=E6=8D=9F=EF=BC=89=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=EF=BC=9A=20-=20buildTradingContext()=20=E4=B8=AD=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=20quantity=3D=3D0=20=E6=A3=80=E6=9F=A5=20-=20?= =?UTF-8?q?=E8=B7=B3=E8=BF=87=E5=B7=B2=E5=B9=B3=E4=BB=93=E7=9A=84=E6=8C=81?= =?UTF-8?q?=E4=BB=93=EF=BC=8C=E7=A1=AE=E4=BF=9D=E5=8F=AA=E4=BC=A0=E9=80=92?= =?UTF-8?q?=E7=9C=9F=E5=AE=9E=E6=8C=81=E4=BB=93=E7=BB=99=20AI=20-=20?= =?UTF-8?q?=E8=A7=A6=E5=8F=91=E6=B8=85=E7=90=86=E9=80=BB=E8=BE=91=EF=BC=9A?= =?UTF-8?q?=E6=92=A4=E9=94=80=E5=AD=A4=E5=84=BF=E8=AE=A2=E5=8D=95=E3=80=81?= =?UTF-8?q?=E6=B8=85=E7=90=86=E5=86=85=E9=83=A8=E7=8A=B6=E6=80=81=20?= =?UTF-8?q?=E5=BD=B1=E5=93=8D=E8=8C=83=E5=9B=B4=EF=BC=9A=20-=20trader/auto?= =?UTF-8?q?=5Ftrader.go:487-490=20=E6=B5=8B=E8=AF=95=EF=BC=9A=20-=20?= =?UTF-8?q?=E7=BC=96=E8=AF=91=E6=88=90=E5=8A=9F=20-=20=E5=AE=B9=E5=99=A8?= =?UTF-8?q?=E9=87=8D=E5=BB=BA=E5=B9=B6=E5=90=AF=E5=8A=A8=E6=AD=A3=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- trader/auto_trader.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/trader/auto_trader.go b/trader/auto_trader.go index 1e93ab5c..6dfdac99 100644 --- a/trader/auto_trader.go +++ b/trader/auto_trader.go @@ -195,7 +195,8 @@ func NewAutoTrader(config AutoTraderConfig) (*AutoTrader, error) { // 设置默认系统提示词模板 systemPromptTemplate := config.SystemPromptTemplate if systemPromptTemplate == "" { - systemPromptTemplate = "default" // 默认使用 default 模板 + // feature/partial-close-dynamic-tpsl 分支默认使用 adaptive(支持动态止盈止损) + systemPromptTemplate = "adaptive" } return &AutoTrader{ @@ -481,6 +482,12 @@ func (at *AutoTrader) buildTradingContext() (*decision.Context, error) { if quantity < 0 { quantity = -quantity // 空仓数量为负,转为正数 } + + // 跳过已平仓的持仓(quantity = 0),防止"幽灵持仓"传递给AI + if quantity == 0 { + continue + } + unrealizedPnl := pos["unRealizedProfit"].(float64) liquidationPrice := pos["liquidationPrice"].(float64) From 98b5b20043e54045c15cdfda08c3560632d57e1b Mon Sep 17 00:00:00 2001 From: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com> Date: Sun, 2 Nov 2025 09:05:47 +0800 Subject: [PATCH 2/7] =?UTF-8?q?fix:=20=E6=B7=BB=E5=8A=A0=20HTTP/2=20stream?= =?UTF-8?q?=20error=20=E5=88=B0=E5=8F=AF=E9=87=8D=E8=A9=A6=E9=8C=AF?= =?UTF-8?q?=E8=AA=A4=E5=88=97=E8=A1=A8=20=E5=95=8F=E9=A1=8C=EF=BC=9A=20-?= =?UTF-8?q?=20=E7=94=A8=E6=88=B6=E9=81=87=E5=88=B0=E9=8C=AF=E8=AA=A4?= =?UTF-8?q?=EF=BC=9Astream=20error:=20stream=20ID=201;=20INTERNAL=5FERROR?= =?UTF-8?q?=20-=20=E9=80=99=E6=98=AF=20HTTP/2=20=E9=80=A3=E6=8E=A5?= =?UTF-8?q?=E8=A2=AB=E6=9C=8D=E5=8B=99=E7=AB=AF=E9=97=9C=E9=96=89=E7=9A=84?= =?UTF-8?q?=E9=8C=AF=E8=AA=A4=20-=20=E7=95=B6=E5=89=8D=E9=87=8D=E8=A9=A6?= =?UTF-8?q?=E6=A9=9F=E5=88=B6=E4=B8=8D=E5=8C=85=E5=90=AB=E6=AD=A4=E9=A1=9E?= =?UTF-8?q?=E9=8C=AF=E8=AA=A4=EF=BC=8C=E5=B0=8E=E8=87=B4=E7=9B=B4=E6=8E=A5?= =?UTF-8?q?=E5=A4=B1=E6=95=97=20=E4=BF=AE=E5=BE=A9=EF=BC=9A=20-=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20"stream=20error"=20=E5=88=B0=E5=8F=AF?= =?UTF-8?q?=E9=87=8D=E8=A9=A6=E5=88=97=E8=A1=A8=20-=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20"INTERNAL=5FERROR"=20=E5=88=B0=E5=8F=AF=E9=87=8D=E8=A9=A6?= =?UTF-8?q?=E5=88=97=E8=A1=A8=20-=20=E9=81=87=E5=88=B0=E6=AD=A4=E9=A1=9E?= =?UTF-8?q?=E9=8C=AF=E8=AA=A4=E6=99=82=E6=9C=83=E8=87=AA=E5=8B=95=E9=87=8D?= =?UTF-8?q?=E8=A9=A6=EF=BC=88=E6=9C=80=E5=A4=9A=203=20=E6=AC=A1=EF=BC=89?= =?UTF-8?q?=20=E5=BD=B1=E9=9F=BF=EF=BC=9A=20-=20=E6=8F=90=E9=AB=98=20API?= =?UTF-8?q?=20=E8=AA=BF=E7=94=A8=E7=A9=A9=E5=AE=9A=E6=80=A7=20-=20?= =?UTF-8?q?=E8=87=AA=E5=8B=95=E8=99=95=E7=90=86=E6=9C=8D=E5=8B=99=E7=AB=AF?= =?UTF-8?q?=E8=87=A8=E6=99=82=E6=95=85=E9=9A=9C=20-=20=E6=B8=9B=E5=B0=91?= =?UTF-8?q?=E5=9B=A0=E7=B6=B2=E7=B5=A1=E6=B3=A2=E5=8B=95=E5=B0=8E=E8=87=B4?= =?UTF-8?q?=E7=9A=84=E5=A4=B1=E6=95=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mcp/client.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mcp/client.go b/mcp/client.go index 9191dfaf..5ef090e8 100644 --- a/mcp/client.go +++ b/mcp/client.go @@ -280,6 +280,8 @@ func isRetryableError(err error) bool { "connection refused", "temporary failure", "no such host", + "stream error", // HTTP/2 stream 错误 + "INTERNAL_ERROR", // 服务端内部错误 } for _, retryable := range retryableErrors { if strings.Contains(errStr, retryable) { From 7b43fc40725d65700b43250608a2fbbcb6e1d8e1 Mon Sep 17 00:00:00 2001 From: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com> Date: Sun, 2 Nov 2025 08:21:56 +0800 Subject: [PATCH 3/7] =?UTF-8?q?fix:=20=E4=BF=AE=E5=BE=A9=E9=A6=96=E6=AC=A1?= =?UTF-8?q?=E9=81=8B=E8=A1=8C=E6=99=82=E6=95=B8=E6=93=9A=E5=BA=AB=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E5=8C=96=E5=A4=B1=E6=95=97=E5=95=8F=E9=A1=8C=20?= =?UTF-8?q?=E5=95=8F=E9=A1=8C=EF=BC=9A=20-=20=E7=94=A8=E6=88=B6=E9=A6=96?= =?UTF-8?q?=E6=AC=A1=E9=81=8B=E8=A1=8C=E5=A0=B1=E9=8C=AF=EF=BC=9Aunable=20?= =?UTF-8?q?to=20open=20database=20file:=20is=20a=20directory=20-=20?= =?UTF-8?q?=E5=8E=9F=E5=9B=A0=EF=BC=9ADocker=20volume=20=E6=8E=9B=E8=BC=89?= =?UTF-8?q?=E6=99=82=EF=BC=8C=E5=A6=82=E6=9E=9C=20config.db=20=E4=B8=8D?= =?UTF-8?q?=E5=AD=98=E5=9C=A8=EF=BC=8C=E6=9C=83=E5=89=B5=E5=BB=BA=E7=9B=AE?= =?UTF-8?q?=E9=8C=84=E8=80=8C=E9=9D=9E=E6=96=87=E4=BB=B6=20-=20=E5=BD=B1?= =?UTF-8?q?=E9=9F=BF=EF=BC=9A=E6=96=B0=E7=94=A8=E6=88=B6=E7=84=A1=E6=B3=95?= =?UTF-8?q?=E6=AD=A3=E5=B8=B8=E5=95=9F=E5=8B=95=E7=B3=BB=E7=B5=B1=20?= =?UTF-8?q?=E4=BF=AE=E5=BE=A9=EF=BC=9A=20-=20=E5=9C=A8=20start.sh=20?= =?UTF-8?q?=E5=95=9F=E5=8B=95=E5=89=8D=E6=AA=A2=E6=9F=A5=20config.db=20?= =?UTF-8?q?=E6=98=AF=E5=90=A6=E5=AD=98=E5=9C=A8=20-=20=E5=A6=82=E4=B8=8D?= =?UTF-8?q?=E5=AD=98=E5=9C=A8=E5=89=87=E5=89=B5=E5=BB=BA=E7=A9=BA=E6=96=87?= =?UTF-8?q?=E4=BB=B6=EF=BC=88touch=20config.db=EF=BC=89=20-=20=E7=A2=BA?= =?UTF-8?q?=E4=BF=9D=20Docker=20=E6=8E=9B=E8=BC=89=E7=82=BA=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E8=80=8C=E9=9D=9E=E7=9B=AE=E9=8C=84=20=E6=B8=AC?= =?UTF-8?q?=E8=A9=A6=EF=BC=9A=20-=20=E9=A6=96=E6=AC=A1=E9=81=8B=E8=A1=8C?= =?UTF-8?q?=EF=BC=9A./start.sh=20start=20=E2=86=92=20=E6=AD=A3=E5=B8=B8?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=20=E2=9C=93=20-=20=E7=8F=BE?= =?UTF-8?q?=E6=9C=89=E7=94=A8=E6=88=B6=EF=BC=9A=E7=84=A1=E5=BD=B1=E9=9F=BF?= =?UTF-8?q?=EF=BC=8C=E5=90=91=E5=BE=8C=E5=85=BC=E5=AE=B9=20=E2=9C=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- start.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/start.sh b/start.sh index 47cb2536..3c571067 100755 --- a/start.sh +++ b/start.sh @@ -165,6 +165,16 @@ start() { # 读取环境变量 read_env_vars + # 确保必要的文件和目录存在(修复 Docker volume 挂载问题) + if [ ! -f "config.db" ]; then + print_info "创建数据库文件..." + touch config.db + fi + if [ ! -d "decision_logs" ]; then + print_info "创建日志目录..." + mkdir -p decision_logs + fi + # Auto-build frontend if missing or forced # if [ ! -d "web/dist" ] || [ "$1" == "--build" ]; then # build_frontend From 96d59e624180340ae94c809d560920689a02a19a Mon Sep 17 00:00:00 2001 From: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com> Date: Sun, 2 Nov 2025 07:53:17 +0800 Subject: [PATCH 4/7] =?UTF-8?q?fix:=20=E4=BF=AE=E5=BE=A9=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E4=BD=99=E9=A1=8D=E9=A1=AF=E7=A4=BA=E9=8C=AF=E8=AA=A4=EF=BC=88?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E7=95=B6=E5=89=8D=E6=B7=A8=E5=80=BC=E8=80=8C?= =?UTF-8?q?=E9=9D=9E=E9=85=8D=E7=BD=AE=E5=80=BC=EF=BC=89=20=E5=95=8F?= =?UTF-8?q?=E9=A1=8C=EF=BC=9A=20-=20=E5=9C=96=E8=A1=A8=E9=A1=AF=E7=A4=BA?= =?UTF-8?q?=E3=80=8C=E5=88=9D=E5=A7=8B=E4=BD=99=E9=A1=8D=20693.15=20USDT?= =?UTF-8?q?=E3=80=8D=EF=BC=88=E5=AF=A6=E9=9A=9B=E6=87=89=E8=A9=B2=E6=98=AF?= =?UTF-8?q?=20600=EF=BC=89=20-=20=E5=8E=9F=E5=9B=A0=EF=BC=9A=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=20validHistory[0].total=5Fequity=EF=BC=88=E7=95=B6?= =?UTF-8?q?=E5=89=8D=E6=B7=A8=E5=80=BC=EF=BC=89=20-=20=E5=B0=8E=E8=87=B4?= =?UTF-8?q?=E5=88=9D=E5=A7=8B=E4=BD=99=E9=A1=8D=E9=9A=A8=E8=91=97=E7=9B=88?= =?UTF-8?q?=E8=99=A7=E8=AE=8A=E5=8C=96=EF=BC=8C=E6=95=B8=E5=AD=B8=E9=82=8F?= =?UTF-8?q?=E8=BC=AF=E9=8C=AF=E8=AA=A4=20=E4=BF=AE=E5=BE=A9=EF=BC=9A=20-?= =?UTF-8?q?=20=E5=84=AA=E5=85=88=E5=BE=9E=20account.initial=5Fbalance=20?= =?UTF-8?q?=E8=AE=80=E5=8F=96=E7=9C=9F=E5=AF=A6=E9=85=8D=E7=BD=AE=E5=80=BC?= =?UTF-8?q?=20-=20=E5=82=99=E9=81=B8=E6=96=B9=E6=A1=88=EF=BC=9A=E5=BE=9E?= =?UTF-8?q?=E6=AD=B7=E5=8F=B2=E6=95=B8=E6=93=9A=E5=8F=8D=E6=8E=A8=EF=BC=88?= =?UTF-8?q?=E6=B7=A8=E5=80=BC=20-=20=E7=9B=88=E8=99=A7=EF=BC=89=20-=20?= =?UTF-8?q?=E9=BB=98=E8=AA=8D=E5=80=BC=E4=BD=BF=E7=94=A8=201000=EF=BC=88?= =?UTF-8?q?=E8=88=87=E5=89=B5=E5=BB=BA=E4=BA=A4=E6=98=93=E5=93=A1=E6=99=82?= =?UTF-8?q?=E7=9A=84=E9=BB=98=E8=AA=8D=E9=85=8D=E7=BD=AE=E4=B8=80=E8=87=B4?= =?UTF-8?q?=EF=BC=89=20=E6=B8=AC=E8=A9=A6=EF=BC=9A=20-=20=E5=88=9D?= =?UTF-8?q?=E5=A7=8B=E4=BD=99=E9=A1=8D=EF=BC=9A600=20USDT=EF=BC=88?= =?UTF-8?q?=E5=9B=BA=E5=AE=9A=EF=BC=89=20-=20=E7=95=B6=E5=89=8D=E6=B7=A8?= =?UTF-8?q?=E5=80=BC=EF=BC=9A693.15=20USDT=20-=20=E7=9B=88=E8=99=A7?= =?UTF-8?q?=EF=BC=9A+93.15=20USDT=20(+15.52%)=20=E2=9C=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/EquityChart.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/src/components/EquityChart.tsx b/web/src/components/EquityChart.tsx index e7441779..fdc8131f 100644 --- a/web/src/components/EquityChart.tsx +++ b/web/src/components/EquityChart.tsx @@ -104,10 +104,10 @@ export function EquityChart({ traderId }: EquityChartProps) { ? validHistory.slice(-MAX_DISPLAY_POINTS) : validHistory; - // 计算初始余额(使用第一个有效数据点,如果无数据则从account获取,最后才用默认值) - const initialBalance = validHistory[0]?.total_equity - || account?.total_equity - || 100; // 默认值改为100,与常见配置一致 + // 计算初始余额(优先从 account 获取配置的初始余额,备选从历史数据反推) + const initialBalance = account?.initial_balance // 从交易员配置读取真实初始余额 + || (validHistory[0] ? validHistory[0].total_equity - validHistory[0].pnl : undefined) // 备选:淨值 - 盈亏 + || 1000; // 默认值(与创建交易员时的默认配置一致) // 转换数据格式 const chartData = displayHistory.map((point) => { From 4d54a4704c12659a4dfb62942d870e909e14b4e2 Mon Sep 17 00:00:00 2001 From: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com> Date: Sun, 2 Nov 2025 07:11:57 +0800 Subject: [PATCH 5/7] =?UTF-8?q?fix:=20=E7=B5=B1=E4=B8=80=20handleTraderLis?= =?UTF-8?q?t=20=E8=BF=94=E5=9B=9E=E5=AE=8C=E6=95=B4=20AI=20model=20ID?= =?UTF-8?q?=EF=BC=88=E4=BF=9D=E6=8C=81=E8=88=87=20handleGetTraderConfig=20?= =?UTF-8?q?=E4=B8=80=E8=87=B4=EF=BC=89=20=E5=95=8F=E9=A1=8C=EF=BC=9A=20-?= =?UTF-8?q?=20handleTraderList=20=E4=BB=8D=E5=9C=A8=E6=88=AA=E6=96=B7=20AI?= =?UTF-8?q?=20model=20ID=20(admin=5Fdeepseek=20=E2=86=92=20deepseek)=20-?= =?UTF-8?q?=20=E8=88=87=20handleGetTraderConfig=20=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E7=9A=84=E5=AE=8C=E6=95=B4=20ID=20=E4=B8=8D=E4=B8=80=E8=87=B4?= =?UTF-8?q?=20-=20=E5=B0=8E=E8=87=B4=E5=89=8D=E7=AB=AF=20isModelInUse=20?= =?UTF-8?q?=E6=AA=A2=E6=9F=A5=E5=A4=B1=E6=95=88=20=E4=BF=AE=E5=BE=A9?= =?UTF-8?q?=EF=BC=9A=20-=20=E7=A7=BB=E9=99=A4=20handleTraderList=20?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E6=88=AA=E6=96=B7=E9=82=8F=E8=BC=AF=20-=20?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E5=AE=8C=E6=95=B4=20AIModelID=20(admin=5Fdee?= =?UTF-8?q?pseek)=20-=20=E8=88=87=E5=85=B6=E4=BB=96=20API=20=E7=AB=AF?= =?UTF-8?q?=E9=BB=9E=E4=BF=9D=E6=8C=81=E4=B8=80=E8=87=B4=20=E6=B8=AC?= =?UTF-8?q?=E8=A9=A6=EF=BC=9A=20-=20GET=20/api/traders=20=E2=86=92=20ai=5F?= =?UTF-8?q?model:=20admin=5Fdeepseek=20=E2=9C=93=20-=20GET=20/api/traders/?= =?UTF-8?q?:id=20=E2=86=92=20ai=5Fmodel:=20admin=5Fdeepseek=20=E2=9C=93=20?= =?UTF-8?q?-=20=E6=A8=A1=E5=9E=8B=E4=BD=BF=E7=94=A8=E6=AA=A2=E6=9F=A5?= =?UTF-8?q?=E9=82=8F=E8=BC=AF=E6=AD=A3=E7=A2=BA=20=E2=9C=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/server.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/api/server.go b/api/server.go index 94ae4a60..0098a7a3 100644 --- a/api/server.go +++ b/api/server.go @@ -791,19 +791,12 @@ func (s *Server) handleTraderList(c *gin.Context) { } } - // AIModelID 应该已经是 provider(如 "deepseek"),直接使用 - // 如果是旧数据格式(如 "admin_deepseek"),提取 provider 部分 - aiModelID := trader.AIModelID - // 兼容旧数据:如果包含下划线,提取最后一部分作为 provider - if strings.Contains(aiModelID, "_") { - parts := strings.Split(aiModelID, "_") - aiModelID = parts[len(parts)-1] - } - + // 返回完整的 AIModelID(如 "admin_deepseek"),不要截断 + // 前端需要完整 ID 来验证模型是否存在(与 handleGetTraderConfig 保持一致) result = append(result, map[string]interface{}{ "trader_id": trader.ID, "trader_name": trader.Name, - "ai_model": aiModelID, + "ai_model": trader.AIModelID, // 使用完整 ID "exchange_id": trader.ExchangeID, "is_running": isRunning, "initial_balance": trader.InitialBalance, From dc44bc9a1bc9a78f712bf3806c25ef6fc7dd2fe1 Mon Sep 17 00:00:00 2001 From: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com> Date: Mon, 3 Nov 2025 00:56:02 +0800 Subject: [PATCH 6/7] =?UTF-8?q?chore:=20upgrade=20sqlite3=20to=20v1.14.22?= =?UTF-8?q?=20for=20Alpine=20Linux=20compatibility=20-=20Fix=20compilation?= =?UTF-8?q?=20error=20on=20Alpine:=20off64=5Ft=20type=20not=20defined=20in?= =?UTF-8?q?=20v1.14.16=20-=20Remove=20unused=20pure-Go=20sqlite=20implemen?= =?UTF-8?q?tation=20(modernc.org/sqlite)=20and=20its=20dependencies=20-=20?= =?UTF-8?q?v1.14.22=20is=20the=20first=20version=20fixing=20Alpine/musl=20?= =?UTF-8?q?build=20issues=20(2024-02-02)=20-=20Minimizes=20version=20jump?= =?UTF-8?q?=20(v1.14.16=20=E2=86=92=20v1.14.22,=2018=20commits)=20to=20red?= =?UTF-8?q?uce=20risk=20Reference:=20https://github.com/mattn/go-sqlite3/i?= =?UTF-8?q?ssues/1164=20Verified:=20Builds=20successfully=20on=20golang:1.?= =?UTF-8?q?25-alpine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 72291ee0..5a9d3e60 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.0 github.com/google/uuid v1.6.0 github.com/gorilla/websocket v1.5.3 - github.com/mattn/go-sqlite3 v1.14.16 + github.com/mattn/go-sqlite3 v1.14.22 github.com/pquerna/otp v1.4.0 github.com/sonirico/go-hyperliquid v0.17.0 golang.org/x/crypto v0.42.0 diff --git a/go.sum b/go.sum index 655fcf92..d31c93fc 100644 --- a/go.sum +++ b/go.sum @@ -118,8 +118,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= -github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= From 1e8746e6921078546291cf6f5c0f0c6d7e968310 Mon Sep 17 00:00:00 2001 From: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com> Date: Tue, 4 Nov 2025 17:39:00 +0800 Subject: [PATCH 7/7] chore: run go fmt to fix formatting issues --- api/server.go | 49 +++++++++++++++++++-------------------- config/database.go | 24 +++++++++---------- main.go | 22 +++++++++--------- manager/trader_manager.go | 40 ++++++++++++++++---------------- mcp/client.go | 4 ++-- 5 files changed, 69 insertions(+), 70 deletions(-) diff --git a/api/server.go b/api/server.go index 0098a7a3..d83e3887 100644 --- a/api/server.go +++ b/api/server.go @@ -88,7 +88,7 @@ func (s *Server) setupRoutes() { // 系统提示词模板管理(无需认证) api.GET("/prompt-templates", s.handleGetPromptTemplates) api.GET("/prompt-templates/:name", s.handleGetPromptTemplate) - + // 公开的竞赛数据(无需认证) api.GET("/traders", s.handlePublicTraderList) api.GET("/competition", s.handlePublicCompetition) @@ -168,7 +168,7 @@ func (s *Server) handleGetSystemConfig(c *gin.Context) { if val, err := strconv.Atoi(altcoinLeverageStr); err == nil && val > 0 { altcoinLeverage = val } - + // 获取内测模式配置 betaModeStr, _ := s.database.GetSystemConfig("beta_mode") betaMode := betaModeStr == "true" @@ -531,14 +531,14 @@ func (s *Server) handleDeleteTrader(c *gin.Context) { func (s *Server) handleStartTrader(c *gin.Context) { userID := c.GetString("user_id") traderID := c.Param("id") - + // 校验交易员是否属于当前用户 _, _, _, err := s.database.GetTraderConfig(userID, traderID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "交易员不存在或无访问权限"}) return } - + trader, err := s.traderManager.GetTrader(traderID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "交易员不存在"}) @@ -574,14 +574,14 @@ func (s *Server) handleStartTrader(c *gin.Context) { func (s *Server) handleStopTrader(c *gin.Context) { userID := c.GetString("user_id") traderID := c.Param("id") - + // 校验交易员是否属于当前用户 _, _, _, err := s.database.GetTraderConfig(userID, traderID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "交易员不存在或无访问权限"}) return } - + trader, err := s.traderManager.GetTrader(traderID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "交易员不存在"}) @@ -796,7 +796,7 @@ func (s *Server) handleTraderList(c *gin.Context) { result = append(result, map[string]interface{}{ "trader_id": trader.ID, "trader_name": trader.Name, - "ai_model": trader.AIModelID, // 使用完整 ID + "ai_model": trader.AIModelID, // 使用完整 ID "exchange_id": trader.ExchangeID, "is_running": isRunning, "initial_balance": trader.InitialBalance, @@ -1574,7 +1574,7 @@ func (s *Server) handlePublicCompetition(c *gin.Context) { }) return } - + c.JSON(http.StatusOK, competition) } @@ -1587,7 +1587,7 @@ func (s *Server) handleTopTraders(c *gin.Context) { }) return } - + c.JSON(http.StatusOK, topTraders) } @@ -1596,7 +1596,7 @@ func (s *Server) handleEquityHistoryBatch(c *gin.Context) { var requestBody struct { TraderIDs []string `json:"trader_ids"` } - + // 尝试解析POST请求的JSON body if err := c.ShouldBindJSON(&requestBody); err != nil { // 如果JSON解析失败,尝试从query参数获取(兼容GET请求) @@ -1610,13 +1610,13 @@ func (s *Server) handleEquityHistoryBatch(c *gin.Context) { }) return } - + traders, ok := topTraders["traders"].([]map[string]interface{}) if !ok { c.JSON(http.StatusInternalServerError, gin.H{"error": "交易员数据格式错误"}) return } - + // 提取trader IDs traderIDs := make([]string, 0, len(traders)) for _, trader := range traders { @@ -1624,24 +1624,24 @@ func (s *Server) handleEquityHistoryBatch(c *gin.Context) { traderIDs = append(traderIDs, traderID) } } - + result := s.getEquityHistoryForTraders(traderIDs) c.JSON(http.StatusOK, result) return } - + // 解析逗号分隔的trader IDs requestBody.TraderIDs = strings.Split(traderIDsParam, ",") for i := range requestBody.TraderIDs { requestBody.TraderIDs[i] = strings.TrimSpace(requestBody.TraderIDs[i]) } } - + // 限制最多20个交易员,防止请求过大 if len(requestBody.TraderIDs) > 20 { requestBody.TraderIDs = requestBody.TraderIDs[:20] } - + result := s.getEquityHistoryForTraders(requestBody.TraderIDs) c.JSON(http.StatusOK, result) } @@ -1651,31 +1651,31 @@ func (s *Server) getEquityHistoryForTraders(traderIDs []string) map[string]inter result := make(map[string]interface{}) histories := make(map[string]interface{}) errors := make(map[string]string) - + for _, traderID := range traderIDs { if traderID == "" { continue } - + trader, err := s.traderManager.GetTrader(traderID) if err != nil { errors[traderID] = "交易员不存在" continue } - + // 获取历史数据(用于对比展示,限制数据量) records, err := trader.GetDecisionLogger().GetLatestRecords(500) if err != nil { errors[traderID] = fmt.Sprintf("获取历史数据失败: %v", err) continue } - + // 构建收益率历史数据 history := make([]map[string]interface{}, 0, len(records)) for _, record := range records { // 计算总权益(余额+未实现盈亏) totalEquity := record.AccountState.TotalBalance + record.AccountState.TotalUnrealizedProfit - + history = append(history, map[string]interface{}{ "timestamp": record.Timestamp, "total_equity": totalEquity, @@ -1683,16 +1683,16 @@ func (s *Server) getEquityHistoryForTraders(traderIDs []string) map[string]inter "balance": record.AccountState.TotalBalance, }) } - + histories[traderID] = history } - + result["histories"] = histories result["count"] = len(histories) if len(errors) > 0 { result["errors"] = errors } - + return result } @@ -1726,4 +1726,3 @@ func (s *Server) handleGetPublicTraderConfig(c *gin.Context) { c.JSON(http.StatusOK, result) } - diff --git a/config/database.go b/config/database.go index 651c425d..052a52ff 100644 --- a/config/database.go +++ b/config/database.go @@ -258,17 +258,17 @@ func (d *Database) initDefaultData() error { // 初始化系统配置 - 创建所有字段,设置默认值,后续由config.json同步更新 systemConfigs := map[string]string{ - "admin_mode": "true", // 默认开启管理员模式,便于首次使用 - "beta_mode": "false", // 默认关闭内测模式 - "api_server_port": "8080", // 默认API端口 - "use_default_coins": "true", // 默认使用内置币种列表 - "default_coins": `["BTCUSDT","ETHUSDT","SOLUSDT","BNBUSDT","XRPUSDT","DOGEUSDT","ADAUSDT","HYPEUSDT"]`, // 默认币种列表(JSON格式) - "max_daily_loss": "10.0", // 最大日损失百分比 - "max_drawdown": "20.0", // 最大回撤百分比 - "stop_trading_minutes": "60", // 停止交易时间(分钟) - "btc_eth_leverage": "5", // BTC/ETH杠杆倍数 - "altcoin_leverage": "5", // 山寨币杠杆倍数 - "jwt_secret": "", // JWT密钥,默认为空,由config.json或系统生成 + "admin_mode": "true", // 默认开启管理员模式,便于首次使用 + "beta_mode": "false", // 默认关闭内测模式 + "api_server_port": "8080", // 默认API端口 + "use_default_coins": "true", // 默认使用内置币种列表 + "default_coins": `["BTCUSDT","ETHUSDT","SOLUSDT","BNBUSDT","XRPUSDT","DOGEUSDT","ADAUSDT","HYPEUSDT"]`, // 默认币种列表(JSON格式) + "max_daily_loss": "10.0", // 最大日损失百分比 + "max_drawdown": "20.0", // 最大回撤百分比 + "stop_trading_minutes": "60", // 停止交易时间(分钟) + "btc_eth_leverage": "5", // BTC/ETH杠杆倍数 + "altcoin_leverage": "5", // 山寨币杠杆倍数 + "jwt_secret": "", // JWT密钥,默认为空,由config.json或系统生成 } for key, value := range systemConfigs { @@ -1037,7 +1037,7 @@ func (d *Database) LoadBetaCodesFromFile(filePath string) error { log.Printf("插入内测码 %s 失败: %v", code, err) continue } - + if rowsAffected, _ := result.RowsAffected(); rowsAffected > 0 { insertedCount++ } diff --git a/main.go b/main.go index 8aa83dde..9e9d1aa7 100644 --- a/main.go +++ b/main.go @@ -64,15 +64,15 @@ func syncConfigToDatabase(database *config.Database) error { // 同步各配置项到数据库 configs := map[string]string{ - "admin_mode": fmt.Sprintf("%t", configFile.AdminMode), - "beta_mode": fmt.Sprintf("%t", configFile.BetaMode), - "api_server_port": strconv.Itoa(configFile.APIServerPort), - "use_default_coins": fmt.Sprintf("%t", configFile.UseDefaultCoins), - "coin_pool_api_url": configFile.CoinPoolAPIURL, - "oi_top_api_url": configFile.OITopAPIURL, - "max_daily_loss": fmt.Sprintf("%.1f", configFile.MaxDailyLoss), - "max_drawdown": fmt.Sprintf("%.1f", configFile.MaxDrawdown), - "stop_trading_minutes": strconv.Itoa(configFile.StopTradingMinutes), + "admin_mode": fmt.Sprintf("%t", configFile.AdminMode), + "beta_mode": fmt.Sprintf("%t", configFile.BetaMode), + "api_server_port": strconv.Itoa(configFile.APIServerPort), + "use_default_coins": fmt.Sprintf("%t", configFile.UseDefaultCoins), + "coin_pool_api_url": configFile.CoinPoolAPIURL, + "oi_top_api_url": configFile.OITopAPIURL, + "max_daily_loss": fmt.Sprintf("%.1f", configFile.MaxDailyLoss), + "max_drawdown": fmt.Sprintf("%.1f", configFile.MaxDrawdown), + "stop_trading_minutes": strconv.Itoa(configFile.StopTradingMinutes), } // 同步default_coins(转换为JSON字符串存储) @@ -112,7 +112,7 @@ func syncConfigToDatabase(database *config.Database) error { // loadBetaCodesToDatabase 加载内测码文件到数据库 func loadBetaCodesToDatabase(database *config.Database) error { betaCodeFile := "beta_codes.txt" - + // 检查内测码文件是否存在 if _, err := os.Stat(betaCodeFile); os.IsNotExist(err) { log.Printf("📄 内测码文件 %s 不存在,跳过加载", betaCodeFile) @@ -126,7 +126,7 @@ func loadBetaCodesToDatabase(database *config.Database) error { } log.Printf("🔄 发现内测码文件 %s (%.1f KB),开始加载...", betaCodeFile, float64(fileInfo.Size())/1024) - + // 加载内测码到数据库 err = database.LoadBetaCodesFromFile(betaCodeFile) if err != nil { diff --git a/manager/trader_manager.go b/manager/trader_manager.go index 4ebcf20b..ccb68bf0 100644 --- a/manager/trader_manager.go +++ b/manager/trader_manager.go @@ -23,9 +23,9 @@ type CompetitionCache struct { // TraderManager 管理多个trader实例 type TraderManager struct { - traders map[string]*trader.AutoTrader // key: trader ID + traders map[string]*trader.AutoTrader // key: trader ID competitionCache *CompetitionCache - mu sync.RWMutex + mu sync.RWMutex } // NewTraderManager 创建trader管理器 @@ -506,19 +506,19 @@ func (tm *TraderManager) GetCompetitionData() (map[string]interface{}, error) { tm.competitionCache.mu.RUnlock() tm.mu.RLock() - + // 获取所有交易员列表 allTraders := make([]*trader.AutoTrader, 0, len(tm.traders)) for _, t := range tm.traders { allTraders = append(allTraders, t) } tm.mu.RUnlock() - + log.Printf("🔄 重新获取竞赛数据,交易员数量: %d", len(allTraders)) - + // 并发获取交易员数据 traders := tm.getConcurrentTraderData(allTraders) - + // 按收益率排序(降序) sort.Slice(traders, func(i, j int) bool { pnlPctI, okI := traders[i]["total_pnl_pct"].(float64) @@ -531,14 +531,14 @@ func (tm *TraderManager) GetCompetitionData() (map[string]interface{}, error) { } return pnlPctI > pnlPctJ }) - + // 限制返回前50名 totalCount := len(traders) limit := 50 if len(traders) > limit { traders = traders[:limit] } - + comparison := make(map[string]interface{}) comparison["traders"] = traders comparison["count"] = len(traders) @@ -559,21 +559,21 @@ func (tm *TraderManager) getConcurrentTraderData(traders []*trader.AutoTrader) [ index int data map[string]interface{} } - + // 创建结果通道 resultChan := make(chan traderResult, len(traders)) - + // 并发获取每个交易员的数据 for i, t := range traders { go func(index int, trader *trader.AutoTrader) { // 设置单个交易员的超时时间为3秒 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() - + // 使用通道来实现超时控制 accountChan := make(chan map[string]interface{}, 1) errorChan := make(chan error, 1) - + go func() { account, err := trader.GetAccountInfo() if err != nil { @@ -582,10 +582,10 @@ func (tm *TraderManager) getConcurrentTraderData(traders []*trader.AutoTrader) [ accountChan <- account } }() - + status := trader.GetStatus() var traderData map[string]interface{} - + select { case account := <-accountChan: // 成功获取账户信息 @@ -634,18 +634,18 @@ func (tm *TraderManager) getConcurrentTraderData(traders []*trader.AutoTrader) [ "error": "获取超时", } } - + resultChan <- traderResult{index: index, data: traderData} }(i, t) } - + // 收集所有结果 results := make([]map[string]interface{}, len(traders)) for i := 0; i < len(traders); i++ { result := <-resultChan results[result.index] = result.data } - + return results } @@ -656,20 +656,20 @@ func (tm *TraderManager) GetTopTradersData() (map[string]interface{}, error) { if err != nil { return nil, err } - + // 从竞赛数据中提取前5名 allTraders, ok := competitionData["traders"].([]map[string]interface{}) if !ok { return nil, fmt.Errorf("竞赛数据格式错误") } - + // 限制返回前5名 limit := 5 topTraders := allTraders if len(allTraders) > limit { topTraders = allTraders[:limit] } - + result := map[string]interface{}{ "traders": topTraders, "count": len(topTraders), diff --git a/mcp/client.go b/mcp/client.go index 5ef090e8..aa1d5435 100644 --- a/mcp/client.go +++ b/mcp/client.go @@ -280,8 +280,8 @@ func isRetryableError(err error) bool { "connection refused", "temporary failure", "no such host", - "stream error", // HTTP/2 stream 错误 - "INTERNAL_ERROR", // 服务端内部错误 + "stream error", // HTTP/2 stream 错误 + "INTERNAL_ERROR", // 服务端内部错误 } for _, retryable := range retryableErrors { if strings.Contains(errStr, retryable) {