From 92b0dd5f16e72d2a42c42b2fa56dd49ec8763b1b Mon Sep 17 00:00:00 2001 From: PorunC <09982.misaka@gmail.com> Date: Wed, 29 Oct 2025 20:29:34 +0800 Subject: [PATCH 1/9] Feat: Upgrade Docker deployment with multi-stage build and Nginx integration - Upgrade Dockerfile to Go 1.24 with multi-stage build (backend + frontend) - Add TA-Lib installation for technical analysis support - Integrate frontend build into main container image - Add Nginx reverse proxy configuration for API routing - Update docker-compose.yml to simplified single-container architecture - Update .dockerignore to include web source for build - Improve health checks and startup time handling Benefits: - One-click deployment with single Docker image - Better resource utilization with multi-stage build - Production-ready Nginx frontend serving - Easier maintenance and deployment Co-Authored-By: tinkle-community --- .dockerignore | 5 +- Dockerfile | 112 +++++++++++++++++++++++++++++++-------------- docker-compose.yml | 53 ++++++--------------- nginx.conf | 49 ++++++++++++++++++++ 4 files changed, 144 insertions(+), 75 deletions(-) create mode 100644 nginx.conf diff --git a/.dockerignore b/.dockerignore index 9d3ea1e6..9b9ec670 100644 --- a/.dockerignore +++ b/.dockerignore @@ -40,8 +40,9 @@ coin_pool_cache/ # Config files (should be mounted) config.json -# Web directory (has its own Dockerfile) -web/ +# Web build artifacts (but include source for multi-stage build) +web/node_modules/ +web/dist/ # Temporary files tmp/ diff --git a/Dockerfile b/Dockerfile index 49f54a09..d7327035 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,59 +1,103 @@ -# 构建阶段 -FROM golang:1.21-alpine AS builder +# Multi-stage build for NOFX AI Trading System +FROM golang:1.24-alpine AS backend-builder -# 安装必要的构建工具 -RUN apk add --no-cache git gcc musl-dev +# Install build dependencies including TA-Lib +RUN apk add --no-cache \ + git \ + make \ + gcc \ + g++ \ + musl-dev \ + wget \ + tar -# 设置工作目录 +# Install TA-Lib +RUN wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz && \ + tar -xzf ta-lib-0.4.0-src.tar.gz && \ + cd ta-lib && \ + ./configure --prefix=/usr && \ + make && \ + make install && \ + cd .. && \ + rm -rf ta-lib ta-lib-0.4.0-src.tar.gz + +# Set working directory WORKDIR /app -# 复制 go mod 文件 +# Copy go mod files COPY go.mod go.sum ./ -# 下载依赖 +# Download dependencies RUN go mod download -# 复制源代码 +# Copy backend source code COPY . . -# 构建应用 -RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o nofx . +# Build the application +RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -o nofx . -# 运行阶段 +# Frontend build stage +FROM node:18-alpine AS frontend-builder + +WORKDIR /app/web + +# Copy package files +COPY web/package*.json ./ + +# Install dependencies +RUN npm ci + +# Copy frontend source +COPY web/ ./ + +# Build frontend +RUN npm run build + +# Final stage FROM alpine:latest -# 安装 ca-certificates(HTTPS 请求需要) -RUN apk --no-cache add ca-certificates tzdata +# Install runtime dependencies +RUN apk add --no-cache \ + ca-certificates \ + tzdata \ + wget \ + tar \ + make \ + gcc \ + g++ \ + musl-dev -# 设置时区为上海 -ENV TZ=Asia/Shanghai +# Install TA-Lib runtime +RUN wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz && \ + tar -xzf ta-lib-0.4.0-src.tar.gz && \ + cd ta-lib && \ + ./configure --prefix=/usr && \ + make && \ + make install && \ + cd .. && \ + rm -rf ta-lib ta-lib-0.4.0-src.tar.gz -# 创建非 root 用户 -RUN addgroup -g 1000 nofx && \ - adduser -D -u 1000 -G nofx nofx +# Set timezone to UTC +ENV TZ=UTC -# 设置工作目录 WORKDIR /app -# 从构建阶段复制二进制文件 -COPY --from=builder /app/nofx . +# Copy backend binary from builder +COPY --from=backend-builder /app/nofx . -# 复制配置文件示例 -COPY config.json.example ./config.json.example +# Copy frontend build from builder +COPY --from=frontend-builder /app/web/dist ./web/dist -# 创建必要的目录 -RUN mkdir -p decision_logs coin_pool_cache && \ - chown -R nofx:nofx /app +# Create directories for logs and data +RUN mkdir -p /app/decision_logs -# 切换到非 root 用户 -USER nofx - -# 暴露 API 端口 +# Expose ports +# 8080 for backend API EXPOSE 8080 -# 健康检查 -HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ - CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1 +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1 -# 启动应用 +# Run the application CMD ["./nofx"] diff --git a/docker-compose.yml b/docker-compose.yml index 0a64d72c..a4b35310 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,24 +1,21 @@ version: '3.8' services: - # 后端服务 - backend: + # NOFX Trading Backend + nofx: build: context: . dockerfile: Dockerfile - container_name: nofx-backend + container_name: nofx-trading restart: unless-stopped ports: - "8080:8080" volumes: - # 挂载配置文件(必须) - ./config.json:/app/config.json:ro - # 持久化决策日志 - ./decision_logs:/app/decision_logs - # 持久化币种池缓存 - - ./coin_pool_cache:/app/coin_pool_cache + - /etc/localtime:/etc/localtime:ro # 同步主机时间 environment: - - TZ=Asia/Shanghai + - TZ=Asia/Shanghai # 使用中国时区 networks: - nofx-network healthcheck: @@ -26,45 +23,23 @@ services: interval: 30s timeout: 10s retries: 3 - start_period: 10s - logging: - driver: "json-file" - options: - max-size: "10m" - max-file: "3" + start_period: 60s - # 前端服务 - frontend: - build: - context: ./web - dockerfile: Dockerfile + # Frontend (Nginx) + nofx-frontend: + image: nginx:alpine container_name: nofx-frontend restart: unless-stopped ports: - "3000:80" - depends_on: - backend: - condition: service_healthy + volumes: + - ./web/dist:/usr/share/nginx/html:ro + - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro networks: - nofx-network - environment: - - TZ=Asia/Shanghai - healthcheck: - test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost/health"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 5s - logging: - driver: "json-file" - options: - max-size: "10m" - max-file: "3" + depends_on: + - nofx networks: nofx-network: driver: bridge - -volumes: - decision_logs: - coin_pool_cache: diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 00000000..4c5abf81 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,49 @@ +server { + listen 80; + server_name localhost; + + # Frontend root + root /usr/share/nginx/html; + index index.html; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/javascript application/json; + + # Frontend routes (SPA) + location / { + try_files $uri $uri/ /index.html; + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + } + + # Proxy API requests to backend + location /api/ { + proxy_pass http://nofx-trading:8080; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Increase timeout for long-running API calls + proxy_connect_timeout 300s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + } + + # Health check endpoint + location /health { + return 200 "OK\n"; + add_header Content-Type text/plain; + } +} From 40190a2b8b64fa9a935f88d14944fdef9b06f8d2 Mon Sep 17 00:00:00 2001 From: PorunC <09982.misaka@gmail.com> Date: Wed, 29 Oct 2025 20:29:51 +0800 Subject: [PATCH 2/9] =?UTF-8?q?Feat:=20Add=20configurable=20leverage=20set?= =?UTF-8?q?tings=20for=20BTC/ETH=20and=20altcoins=20(v2.0.3)=20-=20Add=20L?= =?UTF-8?q?everageConfig=20struct=20with=20btc=5Feth=5Fleverage=20and=20al?= =?UTF-8?q?tcoin=5Fleverage=20fields=20-=20Set=20default=20leverage=20to?= =?UTF-8?q?=205x=20(safe=20for=20Binance=20subaccounts)=20-=20Add=20valida?= =?UTF-8?q?tion=20warnings=20for=20leverage=20>5x=20(subaccount=20restrict?= =?UTF-8?q?ions)=20-=20Update=20config.json.example=20with=20leverage=20co?= =?UTF-8?q?nfiguration=20Breaking=20changes:=20-=20None=20(backward=20comp?= =?UTF-8?q?atible=20with=20default=205x=20leverage)=20Migration:=20-=20Exi?= =?UTF-8?q?sting=20configs=20will=20auto-default=20to=205x=20leverage=20(s?= =?UTF-8?q?afe)=20-=20Main=20accounts=20can=20increase=20to=2020x=20(altco?= =?UTF-8?q?ins)=20or=2050x=20(BTC/ETH)=20in=20config.json=20-=20Subaccount?= =?UTF-8?q?s=20must=20keep=20leverage=20=E2=89=A45x=20to=20avoid=20trade?= =?UTF-8?q?=20failures=20Co-Authored-By:=20tinkle-community=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.json.example | 4 ++++ config/config.go | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/config.json.example b/config.json.example index eb817ea6..d1897508 100644 --- a/config.json.example +++ b/config.json.example @@ -13,6 +13,10 @@ "scan_interval_minutes": 3 } ], + "leverage": { + "btc_eth_leverage": 5, + "altcoin_leverage": 5 + }, "use_default_coins": true, "coin_pool_api_url": "", "oi_top_api_url": "", diff --git a/config/config.go b/config/config.go index efaf7a75..561f1fc6 100644 --- a/config/config.go +++ b/config/config.go @@ -20,6 +20,12 @@ type TraderConfig struct { ScanIntervalMinutes int `json:"scan_interval_minutes"` } +// LeverageConfig 杠杆配置 +type LeverageConfig struct { + BTCETHLeverage int `json:"btc_eth_leverage"` // BTC和ETH的杠杆倍数(主账户建议5-50,子账户≤5) + AltcoinLeverage int `json:"altcoin_leverage"` // 山寨币的杠杆倍数(主账户建议5-20,子账户≤5) +} + // Config 总配置 type Config struct { Traders []TraderConfig `json:"traders"` @@ -30,6 +36,7 @@ type Config struct { MaxDailyLoss float64 `json:"max_daily_loss"` MaxDrawdown float64 `json:"max_drawdown"` StopTradingMinutes int `json:"stop_trading_minutes"` + Leverage LeverageConfig `json:"leverage"` // 杠杆配置 } // LoadConfig 从文件加载配置 @@ -100,6 +107,20 @@ func (c *Config) Validate() error { c.APIServerPort = 8080 // 默认8080端口 } + // 设置杠杆默认值(适配币安子账户限制,最大5倍) + if c.Leverage.BTCETHLeverage <= 0 { + c.Leverage.BTCETHLeverage = 5 // 默认5倍(安全值,适配子账户) + } + if c.Leverage.BTCETHLeverage > 5 { + fmt.Printf("⚠️ 警告: BTC/ETH杠杆设置为%dx,如果使用子账户可能会失败(子账户限制≤5x)\n", c.Leverage.BTCETHLeverage) + } + if c.Leverage.AltcoinLeverage <= 0 { + c.Leverage.AltcoinLeverage = 5 // 默认5倍(安全值,适配子账户) + } + if c.Leverage.AltcoinLeverage > 5 { + fmt.Printf("⚠️ 警告: 山寨币杠杆设置为%dx,如果使用子账户可能会失败(子账户限制≤5x)\n", c.Leverage.AltcoinLeverage) + } + return nil } From cf9fdeafb2a6413aecc3ab60a871a60c51647370 Mon Sep 17 00:00:00 2001 From: PorunC <09982.misaka@gmail.com> Date: Wed, 29 Oct 2025 20:30:04 +0800 Subject: [PATCH 3/9] Feat: Integrate leverage configuration across trading system - Pass leverage config through TraderManager to AutoTrader - Add BTCETHLeverage and AltcoinLeverage fields to Context and AutoTraderConfig - Update decision validation to use configured leverage limits - Display configured leverage in startup message - Update error messages to show current leverage limits Changes: - main.go: Pass leverage config to AddTrader, update startup message - manager/trader_manager.go: Accept and forward leverage config - trader/auto_trader.go: Store leverage config, pass to Context - decision/engine.go: Use dynamic leverage limits in validation This completes the leverage configuration feature implementation. Co-Authored-By: tinkle-community --- decision/engine.go | 42 ++++++++++++++++++++------------------- main.go | 4 +++- manager/trader_manager.go | 4 +++- trader/auto_trader.go | 12 ++++++++--- 4 files changed, 37 insertions(+), 25 deletions(-) diff --git a/decision/engine.go b/decision/engine.go index 14a6101b..21967df9 100644 --- a/decision/engine.go +++ b/decision/engine.go @@ -55,15 +55,17 @@ type OITopData struct { // Context 交易上下文(传递给AI的完整信息) type Context struct { - CurrentTime string `json:"current_time"` - RuntimeMinutes int `json:"runtime_minutes"` - CallCount int `json:"call_count"` - Account AccountInfo `json:"account"` - Positions []PositionInfo `json:"positions"` - CandidateCoins []CandidateCoin `json:"candidate_coins"` - MarketDataMap map[string]*market.Data `json:"-"` // 不序列化,但内部使用 - OITopDataMap map[string]*OITopData `json:"-"` // OI Top数据映射 - Performance interface{} `json:"-"` // 历史表现分析(logger.PerformanceAnalysis) + CurrentTime string `json:"current_time"` + RuntimeMinutes int `json:"runtime_minutes"` + CallCount int `json:"call_count"` + Account AccountInfo `json:"account"` + Positions []PositionInfo `json:"positions"` + CandidateCoins []CandidateCoin `json:"candidate_coins"` + MarketDataMap map[string]*market.Data `json:"-"` // 不序列化,但内部使用 + OITopDataMap map[string]*OITopData `json:"-"` // OI Top数据映射 + Performance interface{} `json:"-"` // 历史表现分析(logger.PerformanceAnalysis) + BTCETHLeverage int `json:"-"` // BTC/ETH杠杆倍数(从配置读取) + AltcoinLeverage int `json:"-"` // 山寨币杠杆倍数(从配置读取) } // Decision AI的交易决策 @@ -105,7 +107,7 @@ func GetFullDecision(ctx *Context) (*FullDecision, error) { } // 4. 解析AI响应 - decision, err := parseFullDecisionResponse(aiResponse, ctx.Account.TotalEquity) + decision, err := parseFullDecisionResponse(aiResponse, ctx.Account.TotalEquity, ctx.BTCETHLeverage, ctx.AltcoinLeverage) if err != nil { return nil, fmt.Errorf("解析AI响应失败: %w", err) } @@ -415,7 +417,7 @@ func buildUserPrompt(ctx *Context) string { } // parseFullDecisionResponse 解析AI的完整决策响应 -func parseFullDecisionResponse(aiResponse string, accountEquity float64) (*FullDecision, error) { +func parseFullDecisionResponse(aiResponse string, accountEquity float64, btcEthLeverage, altcoinLeverage int) (*FullDecision, error) { // 1. 提取思维链 cotTrace := extractCoTTrace(aiResponse) @@ -429,7 +431,7 @@ func parseFullDecisionResponse(aiResponse string, accountEquity float64) (*FullD } // 3. 验证决策 - if err := validateDecisions(decisions, accountEquity); err != nil { + if err := validateDecisions(decisions, accountEquity, btcEthLeverage, altcoinLeverage); err != nil { return &FullDecision{ CoTTrace: cotTrace, Decisions: decisions, @@ -496,10 +498,10 @@ func fixMissingQuotes(jsonStr string) string { return jsonStr } -// validateDecisions 验证所有决策(需要账户信息) -func validateDecisions(decisions []Decision, accountEquity float64) error { +// validateDecisions 验证所有决策(需要账户信息和杠杆配置) +func validateDecisions(decisions []Decision, accountEquity float64, btcEthLeverage, altcoinLeverage int) error { for i, decision := range decisions { - if err := validateDecision(&decision, accountEquity); err != nil { + if err := validateDecision(&decision, accountEquity, btcEthLeverage, altcoinLeverage); err != nil { return fmt.Errorf("决策 #%d 验证失败: %w", i+1, err) } } @@ -529,7 +531,7 @@ func findMatchingBracket(s string, start int) int { } // validateDecision 验证单个决策的有效性 -func validateDecision(d *Decision, accountEquity float64) error { +func validateDecision(d *Decision, accountEquity float64, btcEthLeverage, altcoinLeverage int) error { // 验证action validActions := map[string]bool{ "open_long": true, @@ -546,16 +548,16 @@ func validateDecision(d *Decision, accountEquity float64) error { // 开仓操作必须提供完整参数 if d.Action == "open_long" || d.Action == "open_short" { - // 根据币种判断杠杆上限和仓位价值上限 - maxLeverage := 20 // 山寨币固定20倍 + // 根据币种使用配置的杠杆上限 + maxLeverage := altcoinLeverage // 山寨币使用配置的杠杆 maxPositionValue := accountEquity * 1.5 // 山寨币最多1.5倍账户净值 if d.Symbol == "BTCUSDT" || d.Symbol == "ETHUSDT" { - maxLeverage = 50 // BTC和ETH固定50倍 + maxLeverage = btcEthLeverage // BTC和ETH使用配置的杠杆 maxPositionValue = accountEquity * 10 // BTC/ETH最多10倍账户净值 } if d.Leverage <= 0 || d.Leverage > maxLeverage { - return fmt.Errorf("杠杆必须在1-%d之间(%s): %d", maxLeverage, d.Symbol, d.Leverage) + return fmt.Errorf("杠杆必须在1-%d之间(%s,当前配置上限%d倍): %d", maxLeverage, d.Symbol, maxLeverage, d.Leverage) } if d.PositionSizeUSD <= 0 { return fmt.Errorf("仓位大小必须大于0: %.2f", d.PositionSizeUSD) diff --git a/main.go b/main.go index b956209b..d1347535 100644 --- a/main.go +++ b/main.go @@ -64,6 +64,7 @@ func main() { cfg.MaxDailyLoss, cfg.MaxDrawdown, cfg.StopTradingMinutes, + cfg.Leverage, // 传递杠杆配置 ) if err != nil { log.Fatalf("❌ 初始化trader失败: %v", err) @@ -79,7 +80,8 @@ func main() { fmt.Println() fmt.Println("🤖 AI全权决策模式:") - fmt.Println(" • AI将自主决定每笔交易的杠杆倍数(山寨币1-20倍,BTC/ETH最高50倍)") + fmt.Printf(" • AI将自主决定每笔交易的杠杆倍数(山寨币最高%d倍,BTC/ETH最高%d倍)\n", + cfg.Leverage.AltcoinLeverage, cfg.Leverage.BTCETHLeverage) fmt.Println(" • AI将自主决定每笔交易的仓位大小") fmt.Println(" • AI将自主设置止损和止盈价格") fmt.Println(" • AI将基于市场数据、技术指标、账户状态做出全面分析") diff --git a/manager/trader_manager.go b/manager/trader_manager.go index 6c44c93b..b62f3fb9 100644 --- a/manager/trader_manager.go +++ b/manager/trader_manager.go @@ -23,7 +23,7 @@ func NewTraderManager() *TraderManager { } // AddTrader 添加一个trader -func (tm *TraderManager) AddTrader(cfg config.TraderConfig, coinPoolURL string, maxDailyLoss, maxDrawdown float64, stopTradingMinutes int) error { +func (tm *TraderManager) AddTrader(cfg config.TraderConfig, coinPoolURL string, maxDailyLoss, maxDrawdown float64, stopTradingMinutes int, leverage config.LeverageConfig) error { tm.mu.Lock() defer tm.mu.Unlock() @@ -44,6 +44,8 @@ func (tm *TraderManager) AddTrader(cfg config.TraderConfig, coinPoolURL string, QwenKey: cfg.QwenKey, ScanInterval: cfg.GetScanInterval(), InitialBalance: cfg.InitialBalance, + BTCETHLeverage: leverage.BTCETHLeverage, // 使用配置的杠杆倍数 + AltcoinLeverage: leverage.AltcoinLeverage, // 使用配置的杠杆倍数 MaxDailyLoss: maxDailyLoss, MaxDrawdown: maxDrawdown, StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute, diff --git a/trader/auto_trader.go b/trader/auto_trader.go index 34f49475..a1e81edc 100644 --- a/trader/auto_trader.go +++ b/trader/auto_trader.go @@ -36,6 +36,10 @@ type AutoTraderConfig struct { // 账户配置 InitialBalance float64 // 初始金额(用于计算盈亏,需手动设置) + // 杠杆配置 + BTCETHLeverage int // BTC和ETH的杠杆倍数 + AltcoinLeverage int // 山寨币的杠杆倍数 + // 风险控制(仅作为提示,AI可自主决定) MaxDailyLoss float64 // 最大日亏损百分比(提示) MaxDrawdown float64 // 最大回撤百分比(提示) @@ -459,9 +463,11 @@ func (at *AutoTrader) buildTradingContext() (*decision.Context, error) { // 6. 构建上下文 ctx := &decision.Context{ - CurrentTime: time.Now().Format("2006-01-02 15:04:05"), - RuntimeMinutes: int(time.Since(at.startTime).Minutes()), - CallCount: at.callCount, + CurrentTime: time.Now().Format("2006-01-02 15:04:05"), + RuntimeMinutes: int(time.Since(at.startTime).Minutes()), + CallCount: at.callCount, + BTCETHLeverage: at.config.BTCETHLeverage, // 使用配置的杠杆倍数 + AltcoinLeverage: at.config.AltcoinLeverage, // 使用配置的杠杆倍数 Account: decision.AccountInfo{ TotalEquity: totalEquity, AvailableBalance: availableBalance, From 36b75bbfc8361b7687c4bec81d322c5c4efec2e4 Mon Sep 17 00:00:00 2001 From: PorunC <09982.misaka@gmail.com> Date: Wed, 29 Oct 2025 20:30:17 +0800 Subject: [PATCH 4/9] Fix: Improve frontend code quality and null safety - Remove unused variables and imports - Add null/undefined safety checks for account data display - Use optional chaining and nullish coalescing for safer data access - Remove unused "Input Prompt" display section (keeping CoT trace) - Fix TypeScript warnings and improve type safety Changes: - App.tsx: Add null checks for account fields, remove unused stats prop and input prompt display - ComparisonChart.tsx: Remove unused imports and variables This improves code quality without affecting functionality. Co-Authored-By: tinkle-community --- web/src/App.tsx | 35 ++++++-------------------- web/src/components/ComparisonChart.tsx | 4 +-- 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/web/src/App.tsx b/web/src/App.tsx index 026c3e2a..57c2e2f5 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -290,7 +290,6 @@ function TraderDetailsPage({ account, positions, decisions, - stats, lastUpdate, language, }: { @@ -369,25 +368,25 @@ function TraderDetailsPage({
0 : false} + positive={(account?.total_pnl ?? 0) > 0} /> = 0 ? '+' : ''}${account?.total_pnl.toFixed(2) || '0.00'} USDT`} + value={`${account?.total_pnl !== undefined && account.total_pnl >= 0 ? '+' : ''}${account?.total_pnl?.toFixed(2) || '0.00'} USDT`} change={account?.total_pnl_pct || 0} - positive={account ? account.total_pnl >= 0 : false} + positive={(account?.total_pnl ?? 0) >= 0} />
@@ -559,7 +558,6 @@ function StatCard({ // Decision Card Component with CoT Trace - Binance Style function DecisionCard({ decision, language }: { decision: DecisionRecord; language: Language }) { - const [showInput, setShowInput] = useState(false); const [showCoT, setShowCoT] = useState(false); return ( @@ -583,25 +581,6 @@ function DecisionCard({ decision, language }: { decision: DecisionRecord; langua - {/* AI Input Prompt - Collapsible */} - {decision.input_prompt && ( -
- - {showInput && ( -
- {decision.input_prompt} -
- )} -
- )} - {/* AI Chain of Thought - Collapsible */} {decision.cot_trace && (
diff --git a/web/src/components/ComparisonChart.tsx b/web/src/components/ComparisonChart.tsx index 3fdc9dcc..ee94ef00 100644 --- a/web/src/components/ComparisonChart.tsx +++ b/web/src/components/ComparisonChart.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useMemo } from 'react'; +import { useMemo } from 'react'; import { LineChart, Line, @@ -275,7 +275,7 @@ export function ComparisonChart({ traders }: ComparisonChartProps) { }} /> - {traders.map((trader, index) => ( + {traders.map((trader) => ( Date: Wed, 29 Oct 2025 20:30:30 +0800 Subject: [PATCH 5/9] =?UTF-8?q?Docs:=20Add=20comprehensive=20leverage=20co?= =?UTF-8?q?nfiguration=20guide=20(v2.0.3)=20-=20Document=20new=20configura?= =?UTF-8?q?ble=20leverage=20feature=20in=20Risk=20Management=20section=20-?= =?UTF-8?q?=20Add=20detailed=20leverage=20configuration=20section=20with?= =?UTF-8?q?=20examples=20-=20Explain=20Binance=20subaccount=20restrictions?= =?UTF-8?q?=20(=E2=89=A45x=20leverage)=20-=20Provide=20recommended=20setti?= =?UTF-8?q?ngs=20for=20different=20account=20types=20and=20risk=20levels?= =?UTF-8?q?=20-=20Add=20leverage=20fields=20to=20configuration=20table=20-?= =?UTF-8?q?=20Include=20warnings=20and=20best=20practices=20Key=20document?= =?UTF-8?q?ation=20updates:=20-=20Clear=20distinction=20between=20subaccou?= =?UTF-8?q?nt=20(=E2=89=A45x)=20and=20main=20account=20limits=20-=20Exampl?= =?UTF-8?q?e=20configurations=20for=20safe,=20medium,=20high,=20and=20maxi?= =?UTF-8?q?mum=20risk=20levels=20-=20Explanation=20of=20how=20AI=20uses=20?= =?UTF-8?q?leverage=20within=20configured=20limits=20-=20Updated=20default?= =?UTF-8?q?=20values=20from=20fixed=2020x/50x=20to=20configurable=205x=20T?= =?UTF-8?q?his=20documentation=20helps=20users=20safely=20configure=20leve?= =?UTF-8?q?rage=20based=20on=20their=20account=20type.=20Co-Authored-By:?= =?UTF-8?q?=20tinkle-community=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 080e2293..dcd8df6d 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,11 @@ Join our Telegram developer community to discuss, share ideas, and get support: - **Per-Coin Position Limit**: - Altcoins ≤ 1.5x account equity - BTC/ETH ≤ 10x account equity -- **Fixed Leverage**: Altcoins 20x | BTC/ETH 50x +- **Configurable Leverage** (v2.0.3+): + - Set maximum leverage in config.json + - Default: 5x for all coins (safe for subaccounts) + - Main accounts can increase: Altcoins up to 20x, BTC/ETH up to 50x + - ⚠️ Binance subaccounts restricted to ≤5x leverage - **Margin Management**: Total usage ≤90%, AI autonomous decision on usage rate - **Risk-Reward Ratio**: Mandatory ≥1:2 (stop-loss:take-profit) - **Prevent Position Stacking**: No duplicate opening of same coin/direction @@ -331,6 +335,10 @@ cp config.json.example config.json "scan_interval_minutes": 3 } ], + "leverage": { + "btc_eth_leverage": 5, + "altcoin_leverage": 5 + }, "use_default_coins": true, "coin_pool_api_url": "", "oi_top_api_url": "", @@ -423,6 +431,9 @@ For running multiple AI traders competing against each other: | `qwen_key` | Qwen API key | `"sk-xxx"` | If using Qwen | | `initial_balance` | Starting balance for P/L calculation | `1000.0` | ✅ Yes | | `scan_interval_minutes` | How often to make decisions | `3` (3-5 recommended) | ✅ Yes | +| **`leverage`** | **Leverage configuration (v2.0.3+)** | See below | ✅ Yes | +| `btc_eth_leverage` | Maximum leverage for BTC/ETH
⚠️ Subaccounts: ≤5x | `5` (default, safe)
`50` (main account max) | ✅ Yes | +| `altcoin_leverage` | Maximum leverage for altcoins
⚠️ Subaccounts: ≤5x | `5` (default, safe)
`20` (main account max) | ✅ Yes | | `use_default_coins` | Use built-in coin list
**✨ Smart Default: `true`** (v2.0.2+)
Auto-enabled if no API URL provided | `true` or omit | ❌ No
(Optional, auto-defaults) | | `coin_pool_api_url` | Custom coin pool API
*Only needed when `use_default_coins: false`* | `""` (empty) | ❌ No | | `oi_top_api_url` | Open interest API
*Optional supplement data* | `""` (empty) | ❌ No | @@ -433,6 +444,63 @@ For running multiple AI traders competing against each other: --- +#### ⚙️ Leverage Configuration (v2.0.3+) + +**What is leverage configuration?** + +The leverage settings control the maximum leverage the AI can use for each trade. This is crucial for risk management, especially for Binance subaccounts which have leverage restrictions. + +**Configuration format:** + +```json +"leverage": { + "btc_eth_leverage": 5, // Maximum leverage for BTC and ETH + "altcoin_leverage": 5 // Maximum leverage for all other coins +} +``` + +**⚠️ Important: Binance Subaccount Restrictions** + +- **Subaccounts**: Limited to **≤5x leverage** by Binance +- **Main accounts**: Can use up to 20x (altcoins) or 50x (BTC/ETH) +- If you're using a subaccount and set leverage >5x, trades will **fail** with error: `Subaccounts are restricted from using leverage greater than 5x` + +**Recommended settings:** + +| Account Type | BTC/ETH Leverage | Altcoin Leverage | Risk Level | +|-------------|------------------|------------------|------------| +| **Subaccount** | `5` | `5` | ✅ Safe (default) | +| **Main (Conservative)** | `10` | `10` | 🟡 Medium | +| **Main (Aggressive)** | `20` | `15` | 🔴 High | +| **Main (Maximum)** | `50` | `20` | 🔴🔴 Very High | + +**Examples:** + +**Safe configuration (subaccount or conservative):** +```json +"leverage": { + "btc_eth_leverage": 5, + "altcoin_leverage": 5 +} +``` + +**Aggressive configuration (main account only):** +```json +"leverage": { + "btc_eth_leverage": 20, + "altcoin_leverage": 15 +} +``` + +**How AI uses leverage:** + +- AI can choose **any leverage from 1x up to your configured maximum** +- For example, with `altcoin_leverage: 20`, AI might decide to use 5x, 10x, or 20x based on market conditions +- The configuration sets the **upper limit**, not a fixed value +- AI considers volatility, risk-reward ratio, and account balance when choosing leverage + +--- + #### ⚠️ Important: `use_default_coins` Field **Smart Default Behavior (v2.0.2+):** From b145c4e2f61016658baccdbf8c35128c00cfa774 Mon Sep 17 00:00:00 2001 From: PorunC <09982.misaka@gmail.com> Date: Wed, 29 Oct 2025 20:34:43 +0800 Subject: [PATCH 6/9] =?UTF-8?q?Docs:=20Sync=20leverage=20configuration=20g?= =?UTF-8?q?uide=20to=20all=20language=20versions=20-=20Update=20Chinese=20?= =?UTF-8?q?(README.zh-CN.md)=20with=20leverage=20configuration=20documenta?= =?UTF-8?q?tion=20-=20Update=20Russian=20(README.ru.md)=20with=20leverage?= =?UTF-8?q?=20configuration=20documentation=20-=20Update=20Ukrainian=20(RE?= =?UTF-8?q?ADME.uk.md)=20with=20leverage=20configuration=20documentation?= =?UTF-8?q?=20All=20language=20versions=20now=20include:=20-=20Updated=20r?= =?UTF-8?q?isk=20management=20section=20(fixed=20=E2=86=92=20configurable?= =?UTF-8?q?=20leverage)=20-=20Leverage=20configuration=20in=20JSON=20examp?= =?UTF-8?q?les=20-=20New=20leverage=20fields=20in=20configuration=20tables?= =?UTF-8?q?=20-=20Comprehensive=20leverage=20configuration=20section=20wit?= =?UTF-8?q?h:=20=20=20-=20Configuration=20format=20and=20examples=20=20=20?= =?UTF-8?q?-=20Binance=20subaccount=20restriction=20warnings=20(=E2=89=A45?= =?UTF-8?q?x)=20=20=20-=20Recommended=20settings=20for=20different=20accou?= =?UTF-8?q?nt=20types=20=20=20-=20Safe=20and=20aggressive=20configuration?= =?UTF-8?q?=20examples=20=20=20-=20Explanation=20of=20AI=20leverage=20usag?= =?UTF-8?q?e=20This=20ensures=20all=20users=20can=20read=20the=20leverage?= =?UTF-8?q?=20documentation=20in=20their=20preferred=20language.=20Co-Auth?= =?UTF-8?q?ored-By:=20tinkle-community=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.ru.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++++- README.uk.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++++- README.zh-CN.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 207 insertions(+), 3 deletions(-) diff --git a/README.ru.md b/README.ru.md index f9737148..9febe179 100644 --- a/README.ru.md +++ b/README.ru.md @@ -50,7 +50,11 @@ - **Лимит позиции по монете**: - Альткоины ≤ 1.5x капитал счета - BTC/ETH ≤ 10x капитал счета -- **Фиксированное плечо**: Альткоины 20x | BTC/ETH 50x +- **Настраиваемое плечо** (v2.0.3+): + - Установите максимальное плечо в config.json + - По умолчанию: 5x для всех монет (безопасно для субаккаунтов) + - Основные аккаунты могут увеличить: Альткоины до 20x, BTC/ETH до 50x + - ⚠️ Субаккаунты Binance ограничены ≤5x плечом - **Управление маржой**: Общее использование ≤90%, AI принимает автономные решения - **Соотношение риск/доход**: Обязательное ≥1:2 (стоп-лосс:тейк-профит) - **Предотвращение накопления позиций**: Запрет дублирования открытия той же монеты/направления @@ -268,6 +272,10 @@ cp config.json.example config.json "scan_interval_minutes": 3 } ], + "leverage": { + "btc_eth_leverage": 5, + "altcoin_leverage": 5 + }, "use_default_coins": true, "coin_pool_api_url": "", "oi_top_api_url": "", @@ -360,6 +368,9 @@ cp config.json.example config.json | `qwen_key` | Qwen API ключ | `"sk-xxx"` | Требуется при использовании Qwen | | `initial_balance` | Начальный баланс для расчета P/L | `1000.0` | ✅ Да | | `scan_interval_minutes` | Частота решений (минуты) | `3` (рекомендуется 3-5) | ✅ Да | +| **`leverage`** | **Конфигурация плеча (v2.0.3+)** | См. ниже | ✅ Да | +| `btc_eth_leverage` | Максимальное плечо для BTC/ETH
⚠️ Субаккаунты: ≤5x | `5` (по умолчанию, безопасно)
`50` (максимум для основного аккаунта) | ✅ Да | +| `altcoin_leverage` | Максимальное плечо для альткоинов
⚠️ Субаккаунты: ≤5x | `5` (по умолчанию, безопасно)
`20` (максимум для основного аккаунта) | ✅ Да | | `use_default_coins` | Использовать встроенный список монет
**✨ Умное значение по умолчанию: `true`** (v2.0.2+)
Автоматически включается без API | `true` или опустить | ❌ Нет
(Опционально, авто) | | `coin_pool_api_url` | API пользовательского пула монет
*Требуется только при `use_default_coins: false`* | `""` (пусто) | ❌ Нет | | `oi_top_api_url` | API открытого интереса
*Опциональные дополнительные данные* | `""` (пусто) | ❌ Нет | @@ -370,6 +381,63 @@ cp config.json.example config.json --- +#### ⚙️ Конфигурация плеча (v2.0.3+) + +**Что такое конфигурация плеча?** + +Настройки плеча контролируют максимальное плечо, которое AI может использовать для каждой сделки. Это критически важно для управления рисками, особенно для субаккаунтов Binance, которые имеют ограничения по плечу. + +**Формат конфигурации:** + +```json +"leverage": { + "btc_eth_leverage": 5, // Максимальное плечо для BTC и ETH + "altcoin_leverage": 5 // Максимальное плечо для всех других монет +} +``` + +**⚠️ Важно: Ограничения субаккаунтов Binance** + +- **Субаккаунты**: Ограничены **≤5x плечом** от Binance +- **Основные аккаунты**: Могут использовать до 20x (альткоины) или 50x (BTC/ETH) +- Если вы используете субаккаунт и установите плечо >5x, сделки будут **завершаться с ошибкой**: `Subaccounts are restricted from using leverage greater than 5x` + +**Рекомендуемые настройки:** + +| Тип аккаунта | Плечо BTC/ETH | Плечо альткоинов | Уровень риска | +|--------------|---------------|------------------|---------------| +| **Субаккаунт** | `5` | `5` | ✅ Безопасно (по умолчанию) | +| **Основной (Консервативно)** | `10` | `10` | 🟡 Средний | +| **Основной (Агрессивно)** | `20` | `15` | 🔴 Высокий | +| **Основной (Максимум)** | `50` | `20` | 🔴🔴 Очень высокий | + +**Примеры:** + +**Безопасная конфигурация (субаккаунт или консервативная):** +```json +"leverage": { + "btc_eth_leverage": 5, + "altcoin_leverage": 5 +} +``` + +**Агрессивная конфигурация (только основной аккаунт):** +```json +"leverage": { + "btc_eth_leverage": 20, + "altcoin_leverage": 15 +} +``` + +**Как AI использует плечо:** + +- AI может выбрать **любое плечо от 1x до вашего настроенного максимума** +- Например, с `altcoin_leverage: 20`, AI может решить использовать 5x, 10x или 20x в зависимости от рыночных условий +- Конфигурация устанавливает **верхний лимит**, а не фиксированное значение +- AI учитывает волатильность, соотношение риск/доход и баланс аккаунта при выборе плеча + +--- + #### ⚠️ Важно: Поле `use_default_coins` **Умное поведение по умолчанию (v2.0.2+):** diff --git a/README.uk.md b/README.uk.md index 7af93d25..b5afe3fb 100644 --- a/README.uk.md +++ b/README.uk.md @@ -50,7 +50,11 @@ - **Ліміт позиції по монеті**: - Альткоїни ≤ 1.5x капітал рахунку - BTC/ETH ≤ 10x капітал рахунку -- **Фіксоване плече**: Альткоїни 20x | BTC/ETH 50x +- **Налаштовуване плече** (v2.0.3+): + - Встановіть максимальне плече в config.json + - За замовчуванням: 5x для всіх монет (безпечно для субакаунтів) + - Основні акаунти можуть збільшити: Альткоїни до 20x, BTC/ETH до 50x + - ⚠️ Субакаунти Binance обмежені ≤5x плечем - **Управління маржею**: Загальне використання ≤90%, AI приймає автономні рішення - **Співвідношення ризик/дохід**: Обов'язкове ≥1:2 (стоп-лосс:тейк-профіт) - **Запобігання накопиченню позицій**: Заборона дублювання відкриття тієї ж монети/напрямку @@ -268,6 +272,10 @@ cp config.json.example config.json "scan_interval_minutes": 3 } ], + "leverage": { + "btc_eth_leverage": 5, + "altcoin_leverage": 5 + }, "use_default_coins": true, "coin_pool_api_url": "", "oi_top_api_url": "", @@ -360,6 +368,9 @@ cp config.json.example config.json | `qwen_key` | Qwen API ключ | `"sk-xxx"` | Потрібно при використанні Qwen | | `initial_balance` | Початковий баланс для розрахунку P/L | `1000.0` | ✅ Так | | `scan_interval_minutes` | Частота рішень (хвилини) | `3` (рекомендується 3-5) | ✅ Так | +| **`leverage`** | **Конфігурація плеча (v2.0.3+)** | Див. нижче | ✅ Так | +| `btc_eth_leverage` | Максимальне плече для BTC/ETH
⚠️ Субакаунти: ≤5x | `5` (за замовчуванням, безпечно)
`50` (максимум для основного акаунта) | ✅ Так | +| `altcoin_leverage` | Максимальне плече для альткоїнів
⚠️ Субакаунти: ≤5x | `5` (за замовчуванням, безпечно)
`20` (максимум для основного акаунта) | ✅ Так | | `use_default_coins` | Використовувати вбудований список монет
**✨ Розумне значення за замовчуванням: `true`** (v2.0.2+)
Автоматично включається без API | `true` або опустити | ❌ Ні
(Опціонально, авто) | | `coin_pool_api_url` | API користувацького пулу монет
*Потрібно лише при `use_default_coins: false`* | `""` (пусто) | ❌ Ні | | `oi_top_api_url` | API відкритого інтересу
*Опціональні додаткові дані* | `""` (пусто) | ❌ Ні | @@ -370,6 +381,63 @@ cp config.json.example config.json --- +#### ⚙️ Конфігурація плеча (v2.0.3+) + +**Що таке конфігурація плеча?** + +Налаштування плеча контролюють максимальне плече, яке AI може використовувати для кожної угоди. Це критично важливо для управління ризиками, особливо для субакаунтів Binance, які мають обмеження по плечу. + +**Формат конфігурації:** + +```json +"leverage": { + "btc_eth_leverage": 5, // Максимальне плече для BTC та ETH + "altcoin_leverage": 5 // Максимальне плече для всіх інших монет +} +``` + +**⚠️ Важливо: Обмеження субакаунтів Binance** + +- **Субакаунти**: Обмежені **≤5x плечем** від Binance +- **Основні акаунти**: Можуть використовувати до 20x (альткоїни) або 50x (BTC/ETH) +- Якщо ви використовуєте субакаунт і встановите плече >5x, угоди будуть **завершуватися з помилкою**: `Subaccounts are restricted from using leverage greater than 5x` + +**Рекомендовані налаштування:** + +| Тип акаунта | Плече BTC/ETH | Плече альткоїнів | Рівень ризику | +|-------------|---------------|------------------|---------------| +| **Субакаунт** | `5` | `5` | ✅ Безпечно (за замовчуванням) | +| **Основний (Консервативно)** | `10` | `10` | 🟡 Середній | +| **Основний (Агресивно)** | `20` | `15` | 🔴 Високий | +| **Основний (Максимум)** | `50` | `20` | 🔴🔴 Дуже високий | + +**Приклади:** + +**Безпечна конфігурація (субакаунт або консервативна):** +```json +"leverage": { + "btc_eth_leverage": 5, + "altcoin_leverage": 5 +} +``` + +**Агресивна конфігурація (тільки основний акаунт):** +```json +"leverage": { + "btc_eth_leverage": 20, + "altcoin_leverage": 15 +} +``` + +**Як AI використовує плече:** + +- AI може вибрати **будь-яке плече від 1x до вашого налаштованого максимуму** +- Наприклад, з `altcoin_leverage: 20`, AI може вирішити використовувати 5x, 10x або 20x залежно від ринкових умов +- Конфігурація встановлює **верхню межу**, а не фіксоване значення +- AI враховує волатильність, співвідношення ризик/дохід та баланс акаунта при виборі плеча + +--- + #### ⚠️ Важливо: Поле `use_default_coins` **Розумна поведінка за замовчуванням (v2.0.2+):** diff --git a/README.zh-CN.md b/README.zh-CN.md index 66b4979e..4fbabdde 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -50,7 +50,11 @@ - **单币种仓位上限**: - 山寨币 ≤ 1.5倍账户净值 - BTC/ETH ≤ 10倍账户净值 -- **固定杠杆**: 山寨币20倍 | BTC/ETH 50倍 +- **可配置杠杆** (v2.0.3+): + - 在config.json中设置最大杠杆 + - 默认:所有币种5倍(子账户安全) + - 主账户可增加:山寨币最高20倍,BTC/ETH最高50倍 + - ⚠️ 币安子账户限制≤5倍杠杆 - **保证金管理**: 总使用率≤90%,AI自主决策使用率 - **风险回报比**: 强制≥1:2(止损:止盈) - **防止仓位叠加**: 同币种同方向不允许重复开仓 @@ -331,6 +335,10 @@ cp config.json.example config.json "scan_interval_minutes": 3 } ], + "leverage": { + "btc_eth_leverage": 5, + "altcoin_leverage": 5 + }, "use_default_coins": true, "coin_pool_api_url": "", "oi_top_api_url": "", @@ -423,6 +431,9 @@ cp config.json.example config.json | `qwen_key` | Qwen API密钥 | `"sk-xxx"` | 使用Qwen时必填 | | `initial_balance` | 用于P/L计算的起始余额 | `1000.0` | ✅ 是 | | `scan_interval_minutes` | 决策频率(分钟) | `3`(建议3-5) | ✅ 是 | +| **`leverage`** | **杠杆配置 (v2.0.3+)** | 见下文 | ✅ 是 | +| `btc_eth_leverage` | BTC/ETH最大杠杆
⚠️ 子账户:≤5倍 | `5`(默认,安全)
`50`(主账户最大) | ✅ 是 | +| `altcoin_leverage` | 山寨币最大杠杆
⚠️ 子账户:≤5倍 | `5`(默认,安全)
`20`(主账户最大) | ✅ 是 | | `use_default_coins` | 使用内置币种列表
**✨ 智能默认:`true`** (v2.0.2+)
未提供API时自动启用 | `true` 或省略 | ❌ 否
(可选,自动默认) | | `coin_pool_api_url` | 自定义币种池API
*仅当`use_default_coins: false`时需要* | `""`(空) | ❌ 否 | | `oi_top_api_url` | 持仓量API
*可选补充数据* | `""`(空) | ❌ 否 | @@ -433,6 +444,63 @@ cp config.json.example config.json --- +#### ⚙️ 杠杆配置 (v2.0.3+) + +**什么是杠杆配置?** + +杠杆设置控制AI每次交易可以使用的最大杠杆。这对于风险管理至关重要,特别是对于有杠杆限制的币安子账户。 + +**配置格式:** + +```json +"leverage": { + "btc_eth_leverage": 5, // BTC和ETH的最大杠杆 + "altcoin_leverage": 5 // 所有其他币种的最大杠杆 +} +``` + +**⚠️ 重要:币安子账户限制** + +- **子账户**:币安限制为**≤5倍杠杆** +- **主账户**:可使用最高20倍(山寨币)或50倍(BTC/ETH) +- 如果您使用子账户并设置杠杆>5倍,交易将**失败**,错误信息:`Subaccounts are restricted from using leverage greater than 5x` + +**推荐设置:** + +| 账户类型 | BTC/ETH杠杆 | 山寨币杠杆 | 风险级别 | +|---------|------------|-----------|---------| +| **子账户** | `5` | `5` | ✅ 安全(默认) | +| **主账户(保守)** | `10` | `10` | 🟡 中等 | +| **主账户(激进)** | `20` | `15` | 🔴 高 | +| **主账户(最大)** | `50` | `20` | 🔴🔴 非常高 | + +**示例:** + +**安全配置(子账户或保守):** +```json +"leverage": { + "btc_eth_leverage": 5, + "altcoin_leverage": 5 +} +``` + +**激进配置(仅主账户):** +```json +"leverage": { + "btc_eth_leverage": 20, + "altcoin_leverage": 15 +} +``` + +**AI如何使用杠杆:** + +- AI可以选择**从1倍到您配置的最大值之间的任何杠杆** +- 例如,当`altcoin_leverage: 20`时,AI可能根据市场情况决定使用5倍、10倍或20倍 +- 配置设置的是**上限**,而不是固定值 +- AI在选择杠杆时会考虑波动性、风险回报比和账户余额 + +--- + #### ⚠️ 重要:`use_default_coins` 字段 **智能默认行为(v2.0.2+):** From 6b82321e01bb7ac5d83828a9859fb187c9d63c33 Mon Sep 17 00:00:00 2001 From: PorunC <09982.misaka@gmail.com> Date: Wed, 29 Oct 2025 20:39:28 +0800 Subject: [PATCH 7/9] Fix: Add null safety checks to Debug Info section in frontend Previously fixed StatCard components but missed the Debug Info section, causing "Cannot read properties of undefined (reading 'total_pnl')" error when account data is loading or incomplete. Root cause: - Frontend uses SWR with async data fetching (5s refresh interval) - During initial load or API delays, account object may exist but fields undefined - Previous fix (93e331a) only covered StatCard section (lines 369-384) - Debug Info section (lines 360-362) still used direct property access Changes: - Add optional chaining (?.) to all account field accesses in Debug Info - Add fallback values ('0.00') for undefined fields - Ensures consistent null safety across all account data displays This prevents runtime errors during data loading and API failures. Fixes: TypeError: Cannot read properties of undefined (reading 'total_pnl') Co-Authored-By: tinkle-community --- web/src/App.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/App.tsx b/web/src/App.tsx index 57c2e2f5..70761155 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -357,9 +357,9 @@ function TraderDetailsPage({ {account && (
- 🔄 Last Update: {lastUpdate} | Total Equity: {account.total_equity.toFixed(2)} | - Available: {account.available_balance.toFixed(2)} | P&L: {account.total_pnl.toFixed(2)}{' '} - ({account.total_pnl_pct.toFixed(2)}%) + 🔄 Last Update: {lastUpdate} | Total Equity: {account.total_equity?.toFixed(2) || '0.00'} | + Available: {account.available_balance?.toFixed(2) || '0.00'} | P&L: {account.total_pnl?.toFixed(2) || '0.00'}{' '} + ({account.total_pnl_pct?.toFixed(2) || '0.00'}%)
)} From 2cb2366b012e851da8f10b82434cef909caa88fc Mon Sep 17 00:00:00 2001 From: PorunC <09982.misaka@gmail.com> Date: Wed, 29 Oct 2025 20:46:08 +0800 Subject: [PATCH 8/9] Fix: Add comprehensive null safety checks to CompetitionPage component Root cause analysis: The previous fix (0cb1f37) only addressed App.tsx Debug Info section, but missed CompetitionPage.tsx which also directly accesses trader data fields without null safety checks. When competition data is loading or incomplete, trader objects may exist but contain undefined fields (total_pnl, total_equity, etc.), causing: "TypeError: Cannot read properties of undefined (reading 'total_pnl')" Fixed locations in CompetitionPage.tsx: 1. Line 76-77: Leader display in header (total_pnl, total_pnl_pct) 2. Line 142: Leaderboard total_equity display 3. Line 151-157: Leaderboard P&L section (total_pnl checks and displays) 4. Line 229-230: Head-to-Head comparison (total_pnl display) Changes applied: - Replace direct property access with optional chaining (?.) - Use nullish coalescing (?? 0) for numeric comparisons - Add fallback values ('0.00') for undefined fields - Ensure consistent null safety across all trader data displays This completes the null safety coverage for the entire frontend. Fixes: TypeError in CompetitionPage at index-R21Yay1P.js:116:51447 Co-Authored-By: tinkle-community --- web/src/components/CompetitionPage.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/web/src/components/CompetitionPage.tsx b/web/src/components/CompetitionPage.tsx index a72f6dfb..bc549e38 100644 --- a/web/src/components/CompetitionPage.tsx +++ b/web/src/components/CompetitionPage.tsx @@ -73,8 +73,8 @@ export function CompetitionPage() {
{t('leader', language)}
{leader?.trader_name}
-
= 0 ? '#0ECB81' : '#F6465D' }}> - {leader.total_pnl >= 0 ? '+' : ''}{leader.total_pnl_pct.toFixed(2)}% +
= 0 ? '#0ECB81' : '#F6465D' }}> + {(leader?.total_pnl ?? 0) >= 0 ? '+' : ''}{leader?.total_pnl_pct?.toFixed(2) || '0.00'}%
@@ -139,7 +139,7 @@ export function CompetitionPage() {
{t('equity', language)}
- {trader.total_equity.toFixed(2)} + {trader.total_equity?.toFixed(2) || '0.00'}
@@ -148,13 +148,13 @@ export function CompetitionPage() {
{t('pnl', language)}
= 0 ? '#0ECB81' : '#F6465D' }} + style={{ color: (trader.total_pnl ?? 0) >= 0 ? '#0ECB81' : '#F6465D' }} > - {trader.total_pnl >= 0 ? '+' : ''} - {trader.total_pnl_pct.toFixed(2)}% + {(trader.total_pnl ?? 0) >= 0 ? '+' : ''} + {trader.total_pnl_pct?.toFixed(2) || '0.00'}%
- {trader.total_pnl >= 0 ? '+' : ''}{trader.total_pnl.toFixed(2)} + {(trader.total_pnl ?? 0) >= 0 ? '+' : ''}{trader.total_pnl?.toFixed(2) || '0.00'}
@@ -226,8 +226,8 @@ export function CompetitionPage() { > {trader.trader_name} -
= 0 ? '#0ECB81' : '#F6465D' }}> - {trader.total_pnl >= 0 ? '+' : ''}{trader.total_pnl_pct.toFixed(2)}% +
= 0 ? '#0ECB81' : '#F6465D' }}> + {(trader.total_pnl ?? 0) >= 0 ? '+' : ''}{trader.total_pnl_pct?.toFixed(2) || '0.00'}%
{isWinning && gap > 0 && (
From 0ec1c6a5501d9ec6bfb3e78baae3130d13b0513f Mon Sep 17 00:00:00 2001 From: PorunC <09982.misaka@gmail.com> Date: Wed, 29 Oct 2025 20:49:22 +0800 Subject: [PATCH 9/9] =?UTF-8?q?Fix:=20Resolve=20React=20Hook=20violation?= =?UTF-8?q?=20in=20ComparisonChart=20component=20Root=20cause:=20Compariso?= =?UTF-8?q?nChart=20was=20calling=20useSWR=20hooks=20dynamically=20inside?= =?UTF-8?q?=20a=20.map()=20loop,=20which=20violates=20React's=20Rules=20of?= =?UTF-8?q?=20Hooks.=20When=20the=20traders=20array=20length=20changed=20(?= =?UTF-8?q?e.g.,=20from=200=20to=202,=20or=20during=20initial=20load),=20t?= =?UTF-8?q?he=20number=20of=20hook=20calls=20would=20change=20between=20re?= =?UTF-8?q?nders,=20triggering=20React=20Error=20#310.=20Previous=20code:?= =?UTF-8?q?=20```tsx=20const=20traderHistories=20=3D=20traders.map((trader?= =?UTF-8?q?)=20=3D>=20{=20=20=20return=20useSWR(`equity-history-${trader.t?= =?UTF-8?q?rader=5Fid}`,=20...);=20=20//=20=E2=9D=8C=20Dynamic=20hooks=20}?= =?UTF-8?q?);=20```=20The=20eslint-disable=20comment=20on=20line=2024=20wa?= =?UTF-8?q?s=20masking=20this=20critical=20issue.=20Fix:=20-=20Always=20ca?= =?UTF-8?q?ll=20exactly=202=20useSWR=20hooks=20(trader1,=20trader2)=20unco?= =?UTF-8?q?nditionally=20-=20Pass=20null=20as=20the=20key=20when=20trader?= =?UTF-8?q?=20doesn't=20exist=20(SWR=20handles=20this=20gracefully)=20-=20?= =?UTF-8?q?Build=20traderHistories=20array=20from=20these=20fixed=20hooks?= =?UTF-8?q?=20-=20Ensures=20same=20number=20of=20hooks=20called=20on=20eve?= =?UTF-8?q?ry=20render=20This=20follows=20React's=20Rules=20of=20Hooks:=20?= =?UTF-8?q?=E2=9C=85=20Only=20call=20hooks=20at=20the=20top=20level=20?= =?UTF-8?q?=E2=9C=85=20Don't=20call=20hooks=20inside=20loops,=20conditions?= =?UTF-8?q?,=20or=20nested=20functions=20=E2=9C=85=20Call=20hooks=20in=20t?= =?UTF-8?q?he=20same=20order=20every=20render=20Fixes:=20React=20Error=20#?= =?UTF-8?q?310=20(Rendered=20more=20hooks=20than=20during=20previous=20ren?= =?UTF-8?q?der)=20Co-Authored-By:=20tinkle-community=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/ComparisonChart.tsx | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/web/src/components/ComparisonChart.tsx b/web/src/components/ComparisonChart.tsx index ee94ef00..3c0b5999 100644 --- a/web/src/components/ComparisonChart.tsx +++ b/web/src/components/ComparisonChart.tsx @@ -19,14 +19,24 @@ interface ComparisonChartProps { } export function ComparisonChart({ traders }: ComparisonChartProps) { - // 获取所有trader的历史数据 - const traderHistories = traders.map((trader) => { - // eslint-disable-next-line react-hooks/rules-of-hooks - return useSWR(`equity-history-${trader.trader_id}`, () => - api.getEquityHistory(trader.trader_id), - { refreshInterval: 10000 } - ); - }); + // 获取所有trader的历史数据 - 修复: 使用固定数量的Hook调用 + // 始终调用最多2个trader的useSWR,即使实际trader数量不同 + const trader1 = traders[0]; + const trader2 = traders[1]; + + const history1 = useSWR( + trader1 ? `equity-history-${trader1.trader_id}` : null, + trader1 ? () => api.getEquityHistory(trader1.trader_id) : null, + { refreshInterval: 10000 } + ); + + const history2 = useSWR( + trader2 ? `equity-history-${trader2.trader_id}` : null, + trader2 ? () => api.getEquityHistory(trader2.trader_id) : null, + { refreshInterval: 10000 } + ); + + const traderHistories = [history1, history2].slice(0, traders.length); // 使用useMemo自动处理数据合并,直接使用data对象作为依赖 const combinedData = useMemo(() => {