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/README.md b/README.md index c957998a..af6da581 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,11 @@ See [Configuration Guide](#-alternative-using-hyperliquid-exchange) for details. - **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 @@ -360,6 +364,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": "", @@ -494,6 +502,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 | @@ -504,6 +515,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+):** 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+):** diff --git a/config.json.example b/config.json.example index 3655f5a3..60a494ea 100644 --- a/config.json.example +++ b/config.json.example @@ -23,6 +23,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 67b5e559..2e26c925 100644 --- a/config/config.go +++ b/config/config.go @@ -32,6 +32,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"` @@ -42,6 +48,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 从文件加载配置 @@ -129,6 +136,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 } 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/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/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 5747572a..18373a13 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() @@ -47,6 +47,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/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; + } +} diff --git a/trader/auto_trader.go b/trader/auto_trader.go index 64ad0e65..c90ceef1 100644 --- a/trader/auto_trader.go +++ b/trader/auto_trader.go @@ -44,6 +44,10 @@ type AutoTraderConfig struct { // 账户配置 InitialBalance float64 // 初始金额(用于计算盈亏,需手动设置) + // 杠杆配置 + BTCETHLeverage int // BTC和ETH的杠杆倍数 + AltcoinLeverage int // 山寨币的杠杆倍数 + // 风险控制(仅作为提示,AI可自主决定) MaxDailyLoss float64 // 最大日亏损百分比(提示) MaxDrawdown float64 // 最大回撤百分比(提示) @@ -489,9 +493,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, diff --git a/web/src/App.tsx b/web/src/App.tsx index 026c3e2a..70761155 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -290,7 +290,6 @@ function TraderDetailsPage({ account, positions, decisions, - stats, lastUpdate, language, }: { @@ -358,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'}%)
)} @@ -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..3c0b5999 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, @@ -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(() => { @@ -275,7 +285,7 @@ export function ComparisonChart({ traders }: ComparisonChartProps) { }} /> - {traders.map((trader, index) => ( + {traders.map((trader) => (
{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 && (