diff --git a/CUSTOM_API.md b/CUSTOM_API.md index e08b25e8..7b3578c1 100644 --- a/CUSTOM_API.md +++ b/CUSTOM_API.md @@ -10,7 +10,9 @@ ## 配置方式 -在 `config.json` 中添加使用自定义 API 的 trader: +~~在 `config.json` 中添加使用自定义 API 的 trader:~~ + +*注意:现在通过Web界面配置自定义API,不再使用config.json文件* ```json { diff --git a/DOCKER_DEPLOY.en.md b/DOCKER_DEPLOY.en.md index 6ddd57ea..cf114feb 100644 --- a/DOCKER_DEPLOY.en.md +++ b/DOCKER_DEPLOY.en.md @@ -49,11 +49,13 @@ docker compose --version # Docker 24+ includes this, no separate installation n ### Step 1: Prepare Configuration File ```bash -# Copy configuration template -cp config.example.jsonc config.json +# ~~Copy configuration template~~ +# ~~cp config.example.jsonc config.json~~ -# Edit configuration file with your API keys -nano config.json # or use any other editor +# ~~Edit configuration file with your API keys~~ +# ~~nano config.json # or use any other editor~~ + +⚠️ **Note**: Configuration is now done through the web interface, no longer using JSON files. ``` **Required fields:** @@ -216,7 +218,7 @@ The system automatically persists data to local directories: - `./decision_logs/`: AI decision logs - `./coin_pool_cache/`: Coin pool cache -- `./config.json`: Configuration file (mounted) +- ~~`./config.json`: Configuration file (mounted)~~ (Deprecated) **Data locations:** ```bash @@ -225,7 +227,7 @@ ls -la decision_logs/ ls -la coin_pool_cache/ # Backup data -tar -czf backup_$(date +%Y%m%d).tar.gz decision_logs/ coin_pool_cache/ config.json +tar -czf backup_$(date +%Y%m%d).tar.gz decision_logs/ coin_pool_cache/ ~~config.json~~ trading.db # Restore data tar -xzf backup_20241029.tar.gz @@ -261,11 +263,13 @@ kill -9 ### Configuration File Not Found ```bash -# Ensure config.json exists -ls -la config.json +# ~~Ensure config.json exists~~ +# ~~ls -la config.json~~ -# If not exists, copy template -cp config.example.jsonc config.json +# ~~If not exists, copy template~~ +# ~~cp config.example.jsonc config.json~~ + +*Note: Now using SQLite database for configuration storage, no longer need config.json* ``` ### Health Check Failing @@ -305,11 +309,13 @@ docker system prune -a --volumes ## 🔐 Security Recommendations -1. **Don't commit config.json to Git** +1. ~~**Don't commit config.json to Git**~~ ```bash - # Ensure config.json is in .gitignore - echo "config.json" >> .gitignore + # ~~Ensure config.json is in .gitignore~~ + # ~~echo "config.json" >> .gitignore~~ ``` + + *Note: Now using trading.db database, ensure not to commit sensitive data* 2. **Use environment variables for sensitive data** ```yaml diff --git a/DOCKER_DEPLOY.md b/DOCKER_DEPLOY.md index d4cb031f..cc268261 100644 --- a/DOCKER_DEPLOY.md +++ b/DOCKER_DEPLOY.md @@ -325,11 +325,13 @@ docker system prune -a --volumes - 在生产环境中绝不使用默认值 - 定期更换(会使现有用户需要重新登录) -2. **不要将 config.json 提交到 Git** +2. ~~**不要将 config.json 提交到 Git**~~ ```bash - # 确保 config.json 在 .gitignore 中 - echo "config.json" >> .gitignore + # ~~确保 config.json 在 .gitignore 中~~ + # ~~echo "config.json" >> .gitignore~~ ``` + + *注意:现在使用trading.db数据库,请确保不提交敏感数据* 3. **使用环境变量存储敏感信息** ```yaml diff --git a/PM2_DEPLOYMENT.md b/PM2_DEPLOYMENT.md index 79af7e21..c9806ea3 100644 --- a/PM2_DEPLOYMENT.md +++ b/PM2_DEPLOYMENT.md @@ -206,8 +206,11 @@ go build -o nofx ### 后端无法启动 ```bash -# 检查 config.json 是否存在 -ls -l config.json +# ~~检查 config.json 是否存在~~ +# ~~ls -l config.json~~ + +# 检查数据库文件是否存在 +ls -l trading.db # 检查权限 chmod +x nofx diff --git a/README.md b/README.md index 7ae81000..deb1e496 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ A Binance-compatible decentralized perpetual futures exchange! 1. Visit [Aster API Wallet](https://www.asterdex.com/en/api-wallet) 2. Connect your main wallet and create an API wallet 3. Copy the API Signer address and Private Key -4. Set `"exchange": "aster"` in config.json +4. ~~Set `"exchange": "aster"` in config.json~~ *Configure through web interface* 5. Add `"aster_user"`, `"aster_signer"`, and `"aster_private_key"` --- @@ -156,7 +156,7 @@ A Binance-compatible decentralized perpetual futures exchange! ``` nofx/ ├── main.go # Program entry (multi-trader manager) -├── config.json # Configuration file (API keys, multi-trader config) +├── ~~config.json~~ # ~~Configuration file (API keys, multi-trader config)~~ (Deprecated: Use web interface) │ ├── api/ # HTTP API service │ └── server.go # Gin framework, RESTful API @@ -232,7 +232,7 @@ Before using this system, you need a Binance Futures account. **Use our referral 5. **Create API Key**: - Go to Account → API Management - Create new API key, **enable "Futures" permission** - - Save API Key and Secret Key (needed for config.json) + - Save API Key and Secret Key (~~needed for config.json~~) *needed for web interface* - **Important**: Whitelist your IP address for security ### Fee Discount Benefits: @@ -251,14 +251,15 @@ Before using this system, you need a Binance Futures account. **Use our referral Docker automatically handles all dependencies (Go, Node.js, TA-Lib, SQLite) and environment setup. -#### Step 1: Prepare Configuration +#### ~~Step 1: Prepare Configuration~~ (Deprecated) ```bash -# Copy configuration template -cp config.example.jsonc config.json +# ~~Copy configuration template~~ +# ~~cp config.example.jsonc config.json~~ -# Edit and fill in your API keys -nano config.json # or use any editor +# ~~Edit and fill in your API keys~~ +# ~~nano config.json # or use any editor~~ ``` +⚠️ **Note**: Configuration is now done through the web interface, not JSON files. #### Step 2: One-Click Start ```bash @@ -471,7 +472,7 @@ Open your browser and visit: **🌐 http://localhost:3000** 3. **Remove the `0x` prefix** from the key 4. Fund your wallet on [Hyperliquid](https://hyperliquid.xyz) -**Step 2**: Configure `config.json` for Hyperliquid +**Step 2**: ~~Configure `config.json` for Hyperliquid~~ *Configure through web interface* ```json { @@ -525,7 +526,7 @@ Open your browser and visit: **🌐 http://localhost:3000** - API Wallet address (Signer) - API Wallet Private Key (⚠️ shown only once!) -**Step 2**: Configure `config.json` for Aster +**Step 2**: ~~Configure `config.json` for Aster~~ *Configure through web interface* ```json { @@ -643,8 +644,10 @@ For running multiple AI traders competing against each other: | `oi_top_api_url` | Open interest API
*Optional supplement data* | `""` (empty) | ❌ No | | `api_server_port` | Web dashboard port | `8080` | ✅ Yes | -**Default Trading Coins** (when `use_default_coins: true`): -- BTC, ETH, SOL, BNB, XRP, DOGE, ADA, HYPE +~~**Default Trading Coins** (when `use_default_coins: true`): +- BTC, ETH, SOL, BNB, XRP, DOGE, ADA, HYPE~~ + +*Note: Trading coins are now configured through the web interface* --- @@ -654,14 +657,16 @@ For running multiple AI traders competing against each other: 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:** +~~**Configuration format:**~~ -```json +~~```json "leverage": { "btc_eth_leverage": 5, // Maximum leverage for BTC and ETH "altcoin_leverage": 5 // Maximum leverage for all other coins } -``` +```~~ + +*Note: Leverage is now configured through the web interface* **⚠️ Important: Binance Subaccount Restrictions** @@ -680,21 +685,23 @@ The leverage settings control the maximum leverage the AI can use for each trade **Examples:** -**Safe configuration (subaccount or conservative):** -```json +~~**Safe configuration (subaccount or conservative):**~~ +~~```json "leverage": { "btc_eth_leverage": 5, "altcoin_leverage": 5 } -``` +```~~ -**Aggressive configuration (main account only):** -```json +~~**Aggressive configuration (main account only):**~~ +~~```json "leverage": { "btc_eth_leverage": 20, "altcoin_leverage": 15 } -``` +```~~ + +*Note: Leverage configuration is now done through the web interface* **How AI uses leverage:** @@ -777,7 +784,7 @@ go build -o nofx |--------------|----------| | `invalid API key` | Check your Binance API key in config.json | | `TA-Lib not found` | Run `brew install ta-lib` (macOS) | -| `port 8080 already in use` | Change `api_server_port` in config.json | +| `port 8080 already in use` | ~~Change `api_server_port` in config.json~~ *Change `API_PORT` in .env file* | | `DeepSeek API error` | Verify your DeepSeek API key and balance | **✅ Backend is running correctly when you see:** @@ -1177,7 +1184,7 @@ sudo apt-get install libta-lib0-dev **Solution**: - Coin pool API is optional - If API fails, system uses default mainstream coins (BTC, ETH, etc.) -- Check API URL and auth parameter in config.json +- ~~Check API URL and auth parameter in config.json~~ *Check configuration in web interface* --- @@ -1236,7 +1243,7 @@ This is a **major breaking update** that completely transforms NOFX from a stati - ✅ **Code Organization**: Better separation between database, API, and business logic **Migration Notes:** -- ⚠️ **Breaking Change**: Old `config.json` files are no longer used +- ⚠️ **Breaking Change**: Old ~~`config.json`~~ files are no longer used - ⚠️ **Fresh Start**: All configurations must be redone through web interface - ✅ **Easier Setup**: Web-based configuration is much more user-friendly - ✅ **Better UX**: No more server restarts for configuration changes diff --git a/README.ru.md b/README.ru.md index 92956ea9..53f9b82c 100644 --- a/README.ru.md +++ b/README.ru.md @@ -694,7 +694,7 @@ go build -o nofx |---------------------|---------| | `invalid API key` | Проверьте Binance API ключи в config.json | | `TA-Lib not found` | Выполните `brew install ta-lib` (macOS) | -| `port 8080 already in use` | Измените `api_server_port` в config.json | +| `port 8080 already in use` | ~~Измените `api_server_port` в config.json~~ *Измените `API_PORT` в файле .env* | | `DeepSeek API error` | Проверьте DeepSeek API ключ и баланс | **✅ Признаки работы Backend:** diff --git a/README.uk.md b/README.uk.md index db537817..27620409 100644 --- a/README.uk.md +++ b/README.uk.md @@ -694,7 +694,7 @@ go build -o nofx |--------------------------|---------| | `invalid API key` | Перевірте Binance API ключі в config.json | | `TA-Lib not found` | Виконайте `brew install ta-lib` (macOS) | -| `port 8080 already in use` | Змініть `api_server_port` в config.json | +| `port 8080 already in use` | ~~Змініть `api_server_port` в config.json~~ *Змініть `API_PORT` у файлі .env* | | `DeepSeek API error` | Перевірте DeepSeek API ключ та баланс | **✅ Ознаки роботи Backend:** diff --git a/README.zh-CN.md b/README.zh-CN.md index 277772c3..bd86de5c 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -46,7 +46,7 @@ NOFX现已支持**三大交易所**:Binance、Hyperliquid和Aster DEX! **快速开始:** 1. 获取你的MetaMask私钥(去掉`0x`前缀) -2. 在config.json中设置`"exchange": "hyperliquid"` +2. ~~在config.json中设置`"exchange": "hyperliquid"`~~ *通过Web界面配置* 3. 添加`"hyperliquid_private_key": "your_key"` 4. 开始交易! @@ -73,7 +73,7 @@ NOFX现已支持**三大交易所**:Binance、Hyperliquid和Aster DEX! 1. 访问[Aster API钱包](https://www.asterdex.com/en/api-wallet) 2. 连接你的主钱包并创建API钱包 3. 复制API Signer地址和私钥 -4. 在config.json中设置`"exchange": "aster"` +4. ~~在config.json中设置`"exchange": "aster"`~~ *通过Web界面配置* 5. 添加`"aster_user"`、`"aster_signer"`和`"aster_private_key"` --- @@ -150,7 +150,7 @@ NOFX现已支持**三大交易所**:Binance、Hyperliquid和Aster DEX! ``` nofx/ ├── main.go # 程序入口(多trader管理器) -├── config.json # 配置文件(API密钥、多trader配置) +├── ~~config.json~~ # ~~配置文件(API密钥、多trader配置)~~ (已弃用:使用Web界面) │ ├── api/ # HTTP API服务 │ └── server.go # Gin框架,RESTful API @@ -226,7 +226,7 @@ nofx/ 5. **创建API密钥**: - 进入账户 → API管理 - 创建新的API密钥,**务必勾选"合约"权限** - - 保存API Key和Secret Key(config.json中需要) + - 保存API Key和Secret Key(~~config.json中需要~~ *Web界面中需要*) - **重要**:添加IP白名单以确保安全 ### 手续费优惠说明: @@ -245,14 +245,15 @@ nofx/ Docker会自动处理所有依赖(Go、Node.js、TA-Lib)和环境配置,完美适合新手! -#### 步骤1:准备配置文件 +#### ~~步骤1:准备配置文件~~ (已弃用) ```bash -# 复制配置文件模板 -cp config.example.jsonc config.json +# ~~复制配置文件模板~~ +# ~~cp config.example.jsonc config.json~~ -# 编辑并填入你的API密钥 -nano config.json # 或使用其他编辑器 +# ~~编辑并填入你的API密钥~~ +# ~~nano config.json # 或使用其他编辑器~~ ``` +⚠️ **注意**: 现在通过Web界面进行配置,不再使用JSON文件。 #### 步骤2:一键启动 ```bash @@ -382,13 +383,15 @@ cd .. #### 🌟 新手模式配置(推荐) -**步骤1**:复制并重命名示例配置文件 +~~**步骤1**:复制并重命名示例配置文件~~ -```bash +~~```bash cp config.example.jsonc config.json -``` +```~~ -**步骤2**:编辑`config.json`填入您的API密钥 +~~**步骤2**:编辑`config.json`填入您的API密钥~~ + +*现在通过Web界面配置,无需编辑JSON文件* ```json { @@ -453,7 +456,7 @@ cp config.example.jsonc config.json 3. **去掉`0x`前缀** 4. 在[Hyperliquid](https://hyperliquid.xyz)上为钱包充值 -**步骤2**:为Hyperliquid配置`config.json` +~~**步骤2**:为Hyperliquid配置`config.json`~~ *通过Web界面配置* ```json { @@ -507,7 +510,7 @@ cp config.example.jsonc config.json - API钱包地址(Signer) - API钱包私钥(⚠️ 仅显示一次!) -**步骤2**:为Aster配置`config.json` +~~**步骤2**:为Aster配置`config.json`~~ *通过Web界面配置* ```json { @@ -755,9 +758,9 @@ go build -o nofx | 错误信息 | 解决方案 | |---------|---------| -| `invalid API key` | 检查config.json中的币安API密钥 | +| `invalid API key` | ~~检查config.json中的币安API密钥~~ *检查Web界面中的API密钥* | | `TA-Lib not found` | 运行`brew install ta-lib`(macOS) | -| `port 8080 already in use` | 修改config.json中的`api_server_port` | +| `port 8080 already in use` | ~~修改config.json中的`api_server_port`~~ *修改.env文件中的`API_PORT`* | | `DeepSeek API error` | 验证DeepSeek API密钥和余额 | **✅ 后端运行正常的标志:** @@ -1194,7 +1197,7 @@ sudo apt-get install libta-lib0-dev **解决**: - 币种池API是可选的 - 如果API失败,系统会使用默认主流币种(BTC、ETH等) -- 检查config.json中的API URL和auth参数 +- ~~检查config.json中的API URL和auth参数~~ *检查Web界面中的配置* --- diff --git a/api/server.go b/api/server.go index 47bf8f1b..ced629d7 100644 --- a/api/server.go +++ b/api/server.go @@ -170,6 +170,7 @@ type CreateTraderRequest struct { InitialBalance float64 `json:"initial_balance"` CustomPrompt string `json:"custom_prompt"` OverrideBasePrompt bool `json:"override_base_prompt"` + IsCrossMargin *bool `json:"is_cross_margin"` // 指针类型,nil表示使用默认值true } type ModelConfig struct { @@ -222,6 +223,12 @@ func (s *Server) handleCreateTrader(c *gin.Context) { // 生成交易员ID traderID := fmt.Sprintf("%s_%s_%d", req.ExchangeID, req.AIModelID, time.Now().Unix()) + // 设置默认值 + isCrossMargin := true // 默认为全仓模式 + if req.IsCrossMargin != nil { + isCrossMargin = *req.IsCrossMargin + } + // 创建交易员配置(数据库实体) trader := &config.TraderRecord{ ID: traderID, @@ -232,6 +239,7 @@ func (s *Server) handleCreateTrader(c *gin.Context) { InitialBalance: req.InitialBalance, CustomPrompt: req.CustomPrompt, OverrideBasePrompt: req.OverrideBasePrompt, + IsCrossMargin: isCrossMargin, ScanIntervalMinutes: 3, // 默认3分钟 IsRunning: false, } diff --git a/config.example.jsonc b/config.example.jsonc deleted file mode 100644 index 696bdedc..00000000 --- a/config.example.jsonc +++ /dev/null @@ -1,80 +0,0 @@ -{ - "traders": [ - { - "id": "hyperliquid_deepseek", - "name": "Hyperliquid DeepSeek Trader", - "ai_model": "deepseek", - "exchange": "hyperliquid", - "hyperliquid_private_key": "your_ethereum_private_key_without_0x_prefix", - "hyperliquid_wallet_addr": "your_ethereum_address", - "hyperliquid_testnet": false, - "deepseek_key": "your_deepseek_api_key", - "initial_balance": 1000, - "scan_interval_minutes": 3 - }, - { - "id": "binance_qwen", - "name": "Binance Qwen Trader", - "ai_model": "qwen", - "exchange": "binance", - "binance_api_key": "your_binance_api_key", - "binance_secret_key": "your_binance_secret_key", - "qwen_key": "your_qwen_api_key", - "initial_balance": 1000, - "scan_interval_minutes": 3 - }, - { - "id": "binance_custom", - "name": "Binance Custom API Trader", - "ai_model": "custom", - "exchange": "binance", - "binance_api_key": "your_binance_api_key", - "binance_secret_key": "your_binance_secret_key", - "custom_api_url": "https://api.openai.com/v1", - "custom_api_key": "sk-your-api-key", - "custom_model_name": "gpt-4o", - "initial_balance": 1000, - "scan_interval_minutes": 3 - }, - { - "id": "aster_deepseek", - "name": "Aster DeepSeek Trader", - "ai_model": "deepseek", - "exchange": "aster", - - // 注意请仔细阅读这三个提示 请进入https://www.asterdex.com/en/api-wallet网站 -> 选择专业api -> 创建新api获取以下信息 - // user: 主钱包地址 (登录地址/连接到aster的钱包地址) - // signer: API钱包地址 (点击生成地址后生成的地址) - // privateKey: API钱包私钥 (生成地址对应的私钥) - - "aster_user": "0x63DD5aCC6b1aa0f563956C0e534DD30B6dcF7C4e", - "aster_signer": "0x21cF8Ae13Bb72632562c6Fff438652Ba1a151bb0", - "aster_private_key": "your_aster_api_wallet_private_key_without_0x_prefix", - - "deepseek_key": "your_deepseek_api_key", - "initial_balance": 1000.0, - "scan_interval_minutes": 3 - } - ], - "leverage": { - "btc_eth_leverage": 5, - "altcoin_leverage": 5 - }, - "use_default_coins": true, - "default_coins": [ - "BTCUSDT", - "ETHUSDT", - "SOLUSDT", - "BNBUSDT", - "XRPUSDT", - "DOGEUSDT", - "ADAUSDT", - "HYPEUSDT" - ], - "coin_pool_api_url": "", - "oi_top_api_url": "", - "api_server_port": 8080, - "max_daily_loss": 10.0, - "max_drawdown": 20.0, - "stop_trading_minutes": 60 -} diff --git a/config/database.go b/config/database.go index 6890f25b..aeab9bbb 100644 --- a/config/database.go +++ b/config/database.go @@ -153,6 +153,7 @@ func (d *Database) createTables() error { `ALTER TABLE exchanges ADD COLUMN aster_private_key TEXT DEFAULT ''`, `ALTER TABLE traders ADD COLUMN custom_prompt TEXT DEFAULT ''`, `ALTER TABLE traders ADD COLUMN override_base_prompt BOOLEAN DEFAULT 0`, + `ALTER TABLE traders ADD COLUMN is_cross_margin BOOLEAN DEFAULT 1`, // 默认为全仓模式 } for _, query := range alterQueries { @@ -369,6 +370,7 @@ type TraderRecord struct { IsRunning bool `json:"is_running"` CustomPrompt string `json:"custom_prompt"` // 自定义交易策略prompt OverrideBasePrompt bool `json:"override_base_prompt"` // 是否覆盖基础prompt + IsCrossMargin bool `json:"is_cross_margin"` // 是否为全仓模式(true=全仓,false=逐仓) CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } @@ -656,9 +658,9 @@ func (d *Database) CreateExchange(userID, id, name, typ string, enabled bool, ap // CreateTrader 创建交易员 func (d *Database) CreateTrader(trader *TraderRecord) error { _, err := d.db.Exec(` - INSERT INTO traders (id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running, custom_prompt, override_base_prompt) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - `, trader.ID, trader.UserID, trader.Name, trader.AIModelID, trader.ExchangeID, trader.InitialBalance, trader.ScanIntervalMinutes, trader.IsRunning, trader.CustomPrompt, trader.OverrideBasePrompt) + INSERT INTO traders (id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running, custom_prompt, override_base_prompt, is_cross_margin) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `, trader.ID, trader.UserID, trader.Name, trader.AIModelID, trader.ExchangeID, trader.InitialBalance, trader.ScanIntervalMinutes, trader.IsRunning, trader.CustomPrompt, trader.OverrideBasePrompt, trader.IsCrossMargin) return err } @@ -666,7 +668,8 @@ func (d *Database) CreateTrader(trader *TraderRecord) error { func (d *Database) GetTraders(userID string) ([]*TraderRecord, error) { rows, err := d.db.Query(` SELECT id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running, - COALESCE(custom_prompt, '') as custom_prompt, COALESCE(override_base_prompt, 0) as override_base_prompt, created_at, updated_at + COALESCE(custom_prompt, '') as custom_prompt, COALESCE(override_base_prompt, 0) as override_base_prompt, + COALESCE(is_cross_margin, 1) as is_cross_margin, created_at, updated_at FROM traders WHERE user_id = ? ORDER BY created_at DESC `, userID) if err != nil { @@ -680,7 +683,8 @@ func (d *Database) GetTraders(userID string) ([]*TraderRecord, error) { err := rows.Scan( &trader.ID, &trader.UserID, &trader.Name, &trader.AIModelID, &trader.ExchangeID, &trader.InitialBalance, &trader.ScanIntervalMinutes, &trader.IsRunning, - &trader.CustomPrompt, &trader.OverrideBasePrompt, &trader.CreatedAt, &trader.UpdatedAt, + &trader.CustomPrompt, &trader.OverrideBasePrompt, &trader.IsCrossMargin, + &trader.CreatedAt, &trader.UpdatedAt, ) if err != nil { return nil, err diff --git a/manager/trader_manager.go b/manager/trader_manager.go index c6714526..f6253a5d 100644 --- a/manager/trader_manager.go +++ b/manager/trader_manager.go @@ -154,6 +154,7 @@ func (tm *TraderManager) addTraderFromDB(traderCfg *config.TraderRecord, aiModel MaxDailyLoss: maxDailyLoss, MaxDrawdown: maxDrawdown, StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute, + IsCrossMargin: traderCfg.IsCrossMargin, } // 根据交易所类型设置API密钥 @@ -228,6 +229,7 @@ func (tm *TraderManager) AddTraderFromDB(traderCfg *config.TraderRecord, aiModel MaxDailyLoss: maxDailyLoss, MaxDrawdown: maxDrawdown, StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute, + IsCrossMargin: traderCfg.IsCrossMargin, } // 根据交易所类型设置API密钥 @@ -560,6 +562,7 @@ func (tm *TraderManager) loadSingleTrader(traderCfg *config.TraderRecord, aiMode MaxDailyLoss: maxDailyLoss, MaxDrawdown: maxDrawdown, StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute, + IsCrossMargin: traderCfg.IsCrossMargin, } // 根据交易所类型设置API密钥 diff --git a/start.sh b/start.sh index 1d3d5afa..7f5a8d81 100755 --- a/start.sh +++ b/start.sh @@ -78,24 +78,14 @@ check_env() { } # ------------------------------------------------------------------------ -# Validation: Configuration File (config.json) +# Validation: Database File (trading.db) # ------------------------------------------------------------------------ -check_config() { - if [ ! -f "config.json" ]; then - print_warning "config.json 不存在,正在从 config.example.jsonc 生成..." - - # 生成 config.json(安全去除 JSONC 注释,不破坏 https://) - perl -0777 -pe 's:/\*.*?\*/::gs' config.example.jsonc \ - | sed -E 's/^[[:space:]]*\/\/.*$//' \ - | jq '.' > config.json - - print_success "已生成 config.json" - print_info "请编辑 config.json 填入你的 API 密钥" - print_info "运行: nano config.json 或使用其他编辑器" - exit 1 +check_database() { + if [ ! -f "trading.db" ]; then + print_info "数据库文件不存在,系统将在启动时自动创建" + else + print_success "数据库文件存在" fi - - print_success "配置文件存在" } # ------------------------------------------------------------------------ @@ -253,7 +243,7 @@ main() { case "${1:-start}" in start) check_env - check_config + check_database start "$2" ;; stop) diff --git a/trader/aster_trader.go b/trader/aster_trader.go index b821be61..e4d7f12d 100644 --- a/trader/aster_trader.go +++ b/trader/aster_trader.go @@ -819,6 +819,38 @@ func (t *AsterTrader) CloseShort(symbol string, quantity float64) (map[string]in return result, nil } +// SetMarginMode 设置仓位模式 +func (t *AsterTrader) SetMarginMode(symbol string, isCrossMargin bool) error { + // Aster支持仓位模式设置 + // API格式与币安相似:CROSSED(全仓) / ISOLATED(逐仓) + marginType := "CROSSED" + if !isCrossMargin { + marginType = "ISOLATED" + } + + params := map[string]interface{}{ + "symbol": symbol, + "marginType": marginType, + } + + // 使用request方法调用API + _, err := t.request("POST", "/fapi/v3/marginType", params) + if err != nil { + // 如果错误表示无需更改,忽略错误 + if strings.Contains(err.Error(), "No need to change") || + strings.Contains(err.Error(), "Margin type cannot be changed") { + log.Printf(" ✓ %s 仓位模式已是 %s 或有持仓无法更改", symbol, marginType) + return nil + } + log.Printf(" ⚠️ 设置仓位模式失败: %v", err) + // 不返回错误,让交易继续 + return nil + } + + log.Printf(" ✓ %s 仓位模式已设置为 %s", symbol, marginType) + return nil +} + // SetLeverage 设置杠杆倍数 func (t *AsterTrader) SetLeverage(symbol string, leverage int) error { params := map[string]interface{}{ diff --git a/trader/auto_trader.go b/trader/auto_trader.go index aed0dc79..93889f12 100644 --- a/trader/auto_trader.go +++ b/trader/auto_trader.go @@ -63,6 +63,9 @@ type AutoTraderConfig struct { MaxDailyLoss float64 // 最大日亏损百分比(提示) MaxDrawdown float64 // 最大回撤百分比(提示) StopTradingTime time.Duration // 触发风控后暂停时长 + + // 仓位模式 + IsCrossMargin bool // true=全仓模式, false=逐仓模式 } // AutoTrader 自动交易器 @@ -135,6 +138,13 @@ func NewAutoTrader(config AutoTraderConfig) (*AutoTrader, error) { var trader Trader var err error + // 记录仓位模式(通用) + marginModeStr := "全仓" + if !config.IsCrossMargin { + marginModeStr = "逐仓" + } + log.Printf("📊 [%s] 仓位模式: %s", config.Name, marginModeStr) + switch config.Exchange { case "binance": log.Printf("🏦 [%s] 使用币安合约交易", config.Name) @@ -589,6 +599,12 @@ func (at *AutoTrader) executeOpenLongWithRecord(decision *decision.Decision, act actionRecord.Quantity = quantity actionRecord.Price = marketData.CurrentPrice + // 设置仓位模式 + if err := at.trader.SetMarginMode(decision.Symbol, at.config.IsCrossMargin); err != nil { + log.Printf(" ⚠️ 设置仓位模式失败: %v", err) + // 继续执行,不影响交易 + } + // 开仓 order, err := at.trader.OpenLong(decision.Symbol, quantity, decision.Leverage) if err != nil { @@ -642,6 +658,12 @@ func (at *AutoTrader) executeOpenShortWithRecord(decision *decision.Decision, ac actionRecord.Quantity = quantity actionRecord.Price = marketData.CurrentPrice + // 设置仓位模式 + if err := at.trader.SetMarginMode(decision.Symbol, at.config.IsCrossMargin); err != nil { + log.Printf(" ⚠️ 设置仓位模式失败: %v", err) + // 继续执行,不影响交易 + } + // 开仓 order, err := at.trader.OpenShort(decision.Symbol, quantity, decision.Leverage) if err != nil { @@ -885,7 +907,7 @@ func (at *AutoTrader) GetPositions() ([]map[string]interface{}, error) { // 计算占用保证金 marginUsed := (quantity * markPrice) / float64(leverage) - + // 计算盈亏百分比(基于保证金) // 收益率 = 未实现盈亏 / 保证金 × 100% pnlPct := 0.0 diff --git a/trader/binance_futures.go b/trader/binance_futures.go index ae85afa4..c10fadeb 100644 --- a/trader/binance_futures.go +++ b/trader/binance_futures.go @@ -131,6 +131,46 @@ func (t *FuturesTrader) GetPositions() ([]map[string]interface{}, error) { return result, nil } +// SetMarginMode 设置仓位模式 +func (t *FuturesTrader) SetMarginMode(symbol string, isCrossMargin bool) error { + var marginType futures.MarginType + if isCrossMargin { + marginType = futures.MarginTypeCrossed + } else { + marginType = futures.MarginTypeIsolated + } + + // 尝试设置仓位模式 + err := t.client.NewChangeMarginTypeService(). + Symbol(symbol). + MarginType(marginType). + Do(context.Background()) + + marginModeStr := "全仓" + if !isCrossMargin { + marginModeStr = "逐仓" + } + + if err != nil { + // 如果错误信息包含"No need to change",说明仓位模式已经是目标值 + if contains(err.Error(), "No need to change margin type") { + log.Printf(" ✓ %s 仓位模式已是 %s", symbol, marginModeStr) + return nil + } + // 如果有持仓,无法更改仓位模式,但不影响交易 + if contains(err.Error(), "Margin type cannot be changed if there exists position") { + log.Printf(" ⚠️ %s 有持仓,无法更改仓位模式,继续使用当前模式", symbol) + return nil + } + log.Printf(" ⚠️ 设置仓位模式失败: %v", err) + // 不返回错误,让交易继续 + return nil + } + + log.Printf(" ✓ %s 仓位模式已设置为 %s", symbol, marginModeStr) + return nil +} + // SetLeverage 设置杠杆(智能判断+冷却期) func (t *FuturesTrader) SetLeverage(symbol string, leverage int) error { // 先尝试获取当前杠杆(从持仓信息) @@ -177,31 +217,6 @@ func (t *FuturesTrader) SetLeverage(symbol string, leverage int) error { return nil } -// SetMarginType 设置保证金模式 -func (t *FuturesTrader) SetMarginType(symbol string, marginType futures.MarginType) error { - err := t.client.NewChangeMarginTypeService(). - Symbol(symbol). - MarginType(marginType). - Do(context.Background()) - - if err != nil { - // 如果已经是该模式,不算错误 - if contains(err.Error(), "No need to change") { - log.Printf(" ✓ %s 保证金模式已是 %s", symbol, marginType) - return nil - } - return fmt.Errorf("设置保证金模式失败: %w", err) - } - - log.Printf(" ✓ %s 保证金模式已切换为 %s", symbol, marginType) - - // 切换保证金模式后等待3秒(避免冷却期错误) - log.Printf(" ⏱ 等待3秒冷却期...") - time.Sleep(3 * time.Second) - - return nil -} - // OpenLong 开多仓 func (t *FuturesTrader) OpenLong(symbol string, quantity float64, leverage int) (map[string]interface{}, error) { // 先取消该币种的所有委托单(清理旧的止损止盈单) @@ -214,10 +229,7 @@ func (t *FuturesTrader) OpenLong(symbol string, quantity float64, leverage int) return nil, err } - // 设置逐仓模式 - if err := t.SetMarginType(symbol, futures.MarginTypeIsolated); err != nil { - return nil, err - } + // 注意:仓位模式应该由调用方(AutoTrader)在开仓前通过 SetMarginMode 设置 // 格式化数量到正确精度 quantityStr, err := t.FormatQuantity(symbol, quantity) @@ -260,10 +272,7 @@ func (t *FuturesTrader) OpenShort(symbol string, quantity float64, leverage int) return nil, err } - // 设置逐仓模式 - if err := t.SetMarginType(symbol, futures.MarginTypeIsolated); err != nil { - return nil, err - } + // 注意:仓位模式应该由调用方(AutoTrader)在开仓前通过 SetMarginMode 设置 // 格式化数量到正确精度 quantityStr, err := t.FormatQuantity(symbol, quantity) diff --git a/trader/hyperliquid_trader.go b/trader/hyperliquid_trader.go index fdd646e0..3073b342 100644 --- a/trader/hyperliquid_trader.go +++ b/trader/hyperliquid_trader.go @@ -13,10 +13,11 @@ import ( // HyperliquidTrader Hyperliquid交易器 type HyperliquidTrader struct { - exchange *hyperliquid.Exchange - ctx context.Context - walletAddr string - meta *hyperliquid.Meta // 缓存meta信息(包含精度等) + exchange *hyperliquid.Exchange + ctx context.Context + walletAddr string + meta *hyperliquid.Meta // 缓存meta信息(包含精度等) + isCrossMargin bool // 是否为全仓模式 } // NewHyperliquidTrader 创建Hyperliquid交易器 @@ -63,10 +64,11 @@ func NewHyperliquidTrader(privateKeyHex string, walletAddr string, testnet bool) } return &HyperliquidTrader{ - exchange: exchange, - ctx: ctx, - walletAddr: walletAddr, - meta: meta, + exchange: exchange, + ctx: ctx, + walletAddr: walletAddr, + meta: meta, + isCrossMargin: true, // 默认使用全仓模式 }, nil } @@ -187,13 +189,26 @@ func (t *HyperliquidTrader) GetPositions() ([]map[string]interface{}, error) { return result, nil } +// SetMarginMode 设置仓位模式 (在SetLeverage时一并设置) +func (t *HyperliquidTrader) SetMarginMode(symbol string, isCrossMargin bool) error { + // Hyperliquid的仓位模式在SetLeverage时设置,这里只记录 + t.isCrossMargin = isCrossMargin + marginModeStr := "全仓" + if !isCrossMargin { + marginModeStr = "逐仓" + } + log.Printf(" ✓ %s 将使用 %s 模式", symbol, marginModeStr) + return nil +} + // SetLeverage 设置杠杆 func (t *HyperliquidTrader) SetLeverage(symbol string, leverage int) error { // Hyperliquid symbol格式(去掉USDT后缀) coin := convertSymbolToHyperliquid(symbol) // 调用UpdateLeverage (leverage int, name string, isCross bool) - _, err := t.exchange.UpdateLeverage(t.ctx, leverage, coin, false) // false = 逐仓模式 + // 第三个参数: true=全仓模式, false=逐仓模式 + _, err := t.exchange.UpdateLeverage(t.ctx, leverage, coin, t.isCrossMargin) if err != nil { return fmt.Errorf("设置杠杆失败: %w", err) } diff --git a/trader/interface.go b/trader/interface.go index 77ee6ea8..18d75ee7 100644 --- a/trader/interface.go +++ b/trader/interface.go @@ -24,6 +24,9 @@ type Trader interface { // SetLeverage 设置杠杆 SetLeverage(symbol string, leverage int) error + // SetMarginMode 设置仓位模式 (true=全仓, false=逐仓) + SetMarginMode(symbol string, isCrossMargin bool) error + // GetMarketPrice 获取市场价格 GetMarketPrice(symbol string) (float64, error) diff --git a/web/src/App.tsx b/web/src/App.tsx index 42a04b8f..ddc6d028 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -73,11 +73,11 @@ function App() { return () => window.removeEventListener('hashchange', handleHashChange); }, []); - // 切换页面时更新URL hash - const navigateToPage = (page: Page) => { - setCurrentPage(page); - window.location.hash = page === 'competition' ? '' : 'trader'; - }; + // 切换页面时更新URL hash (当前通过按钮直接调用setCurrentPage,这个函数暂时保留用于未来扩展) + // const navigateToPage = (page: Page) => { + // setCurrentPage(page); + // window.location.hash = page === 'competition' ? '' : 'trader'; + // }; // 获取trader列表 const { data: traders } = useSWR('traders', api.getTraders, { diff --git a/web/src/components/AITradersPage.tsx b/web/src/components/AITradersPage.tsx index 9f157652..b42a79bd 100644 --- a/web/src/components/AITradersPage.tsx +++ b/web/src/components/AITradersPage.tsx @@ -99,7 +99,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) { return traders?.some(t => t.exchange_id === exchangeId && t.is_running) || false; }; - const handleCreateTrader = async (modelId: string, exchangeId: string, name: string, initialBalance: number, customPrompt?: string, overrideBase?: boolean) => { + const handleCreateTrader = async (modelId: string, exchangeId: string, name: string, initialBalance: number, customPrompt?: string, overrideBase?: boolean, isCrossMargin?: boolean) => { try { const model = allModels?.find(m => m.id === modelId); const exchange = allExchanges?.find(e => e.id === exchangeId); @@ -120,7 +120,8 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) { exchange_id: exchangeId, initial_balance: initialBalance, custom_prompt: customPrompt, - override_base_prompt: overrideBase + override_base_prompt: overrideBase, + is_cross_margin: isCrossMargin }; await api.createTrader(request); @@ -663,7 +664,7 @@ function CreateTraderModal({ }: { enabledModels: AIModel[]; enabledExchanges: Exchange[]; - onCreate: (modelId: string, exchangeId: string, name: string, initialBalance: number, customPrompt?: string, overrideBase?: boolean) => void; + onCreate: (modelId: string, exchangeId: string, name: string, initialBalance: number, customPrompt?: string, overrideBase?: boolean, isCrossMargin?: boolean) => void; onClose: () => void; language: any; }) { @@ -679,12 +680,13 @@ function CreateTraderModal({ const [customPrompt, setCustomPrompt] = useState(''); const [showAdvanced, setShowAdvanced] = useState(false); const [overrideBase, setOverrideBase] = useState(false); + const [isCrossMargin, setIsCrossMargin] = useState(true); // 默认为全仓模式 const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!selectedModel || !selectedExchange || !traderName.trim()) return; - onCreate(selectedModel, selectedExchange, traderName.trim(), initialBalance, customPrompt.trim() || undefined, overrideBase); + onCreate(selectedModel, selectedExchange, traderName.trim(), initialBalance, customPrompt.trim() || undefined, overrideBase, isCrossMargin); }; return ( @@ -763,6 +765,44 @@ function CreateTraderModal({ required /> + + {/* Margin Mode Selection */} +
+ +
+ + +
+
+ {isCrossMargin + ? '全仓模式:所有仓位共享账户余额作为保证金' + : '逐仓模式:每个仓位独立管理保证金,风险隔离'} +
+
{/* Advanced Settings Toggle */}
diff --git a/web/src/components/Header.tsx b/web/src/components/Header.tsx index 1780a392..3fb1c693 100644 --- a/web/src/components/Header.tsx +++ b/web/src/components/Header.tsx @@ -22,13 +22,15 @@ export function Header({ simple = false }: HeaderProps) {

{t('appTitle', language)}

-

- {t('subtitle', language)} -

+ {!simple && ( +

+ {t('subtitle', language)} +

+ )}
- {/* Right - Language Toggle */} + {/* Right - Language Toggle (always show) */}