Merge pull request #110 from Icyoung/dev

【Dev】新增仓位模式的配置、移除config配置的文档描述
This commit is contained in:
tinkle-community
2025-10-31 13:54:19 +08:00
committed by GitHub
23 changed files with 301 additions and 221 deletions

View File

@@ -10,7 +10,9 @@
## 配置方式 ## 配置方式
`config.json` 中添加使用自定义 API 的 trader ~~在 `config.json` 中添加使用自定义 API 的 trader~~
*注意现在通过Web界面配置自定义API不再使用config.json文件*
```json ```json
{ {

View File

@@ -49,11 +49,13 @@ docker compose --version # Docker 24+ includes this, no separate installation n
### Step 1: Prepare Configuration File ### Step 1: Prepare Configuration File
```bash ```bash
# Copy configuration template # ~~Copy configuration template~~
cp config.example.jsonc config.json # ~~cp config.example.jsonc config.json~~
# Edit configuration file with your API keys # ~~Edit configuration file with your API keys~~
nano config.json # or use any other editor # ~~nano config.json # or use any other editor~~
⚠️ **Note**: Configuration is now done through the web interface, no longer using JSON files.
``` ```
**Required fields:** **Required fields:**
@@ -216,7 +218,7 @@ The system automatically persists data to local directories:
- `./decision_logs/`: AI decision logs - `./decision_logs/`: AI decision logs
- `./coin_pool_cache/`: Coin pool cache - `./coin_pool_cache/`: Coin pool cache
- `./config.json`: Configuration file (mounted) - ~~`./config.json`: Configuration file (mounted)~~ (Deprecated)
**Data locations:** **Data locations:**
```bash ```bash
@@ -225,7 +227,7 @@ ls -la decision_logs/
ls -la coin_pool_cache/ ls -la coin_pool_cache/
# Backup data # 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 # Restore data
tar -xzf backup_20241029.tar.gz tar -xzf backup_20241029.tar.gz
@@ -261,11 +263,13 @@ kill -9 <PID>
### Configuration File Not Found ### Configuration File Not Found
```bash ```bash
# Ensure config.json exists # ~~Ensure config.json exists~~
ls -la config.json # ~~ls -la config.json~~
# If not exists, copy template # ~~If not exists, copy template~~
cp config.example.jsonc config.json # ~~cp config.example.jsonc config.json~~
*Note: Now using SQLite database for configuration storage, no longer need config.json*
``` ```
### Health Check Failing ### Health Check Failing
@@ -305,11 +309,13 @@ docker system prune -a --volumes
## 🔐 Security Recommendations ## 🔐 Security Recommendations
1. **Don't commit config.json to Git** 1. ~~**Don't commit config.json to Git**~~
```bash ```bash
# Ensure config.json is in .gitignore # ~~Ensure config.json is in .gitignore~~
echo "config.json" >> .gitignore # ~~echo "config.json" >> .gitignore~~
``` ```
*Note: Now using trading.db database, ensure not to commit sensitive data*
2. **Use environment variables for sensitive data** 2. **Use environment variables for sensitive data**
```yaml ```yaml

View File

@@ -325,11 +325,13 @@ docker system prune -a --volumes
- 在生产环境中绝不使用默认值 - 在生产环境中绝不使用默认值
- 定期更换(会使现有用户需要重新登录) - 定期更换(会使现有用户需要重新登录)
2. **不要将 config.json 提交到 Git** 2. ~~**不要将 config.json 提交到 Git**~~
```bash ```bash
# 确保 config.json 在 .gitignore 中 # ~~确保 config.json 在 .gitignore 中~~
echo "config.json" >> .gitignore # ~~echo "config.json" >> .gitignore~~
``` ```
*注意现在使用trading.db数据库请确保不提交敏感数据*
3. **使用环境变量存储敏感信息** 3. **使用环境变量存储敏感信息**
```yaml ```yaml

View File

@@ -206,8 +206,11 @@ go build -o nofx
### 后端无法启动 ### 后端无法启动
```bash ```bash
# 检查 config.json 是否存在 # ~~检查 config.json 是否存在~~
ls -l config.json # ~~ls -l config.json~~
# 检查数据库文件是否存在
ls -l trading.db
# 检查权限 # 检查权限
chmod +x nofx chmod +x nofx

View File

@@ -79,7 +79,7 @@ A Binance-compatible decentralized perpetual futures exchange!
1. Visit [Aster API Wallet](https://www.asterdex.com/en/api-wallet) 1. Visit [Aster API Wallet](https://www.asterdex.com/en/api-wallet)
2. Connect your main wallet and create an API wallet 2. Connect your main wallet and create an API wallet
3. Copy the API Signer address and Private Key 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"` 5. Add `"aster_user"`, `"aster_signer"`, and `"aster_private_key"`
--- ---
@@ -156,7 +156,7 @@ A Binance-compatible decentralized perpetual futures exchange!
``` ```
nofx/ nofx/
├── main.go # Program entry (multi-trader manager) ├── 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 ├── api/ # HTTP API service
│ └── server.go # Gin framework, RESTful API │ └── 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**: 5. **Create API Key**:
- Go to Account API Management - Go to Account API Management
- Create new API key, **enable "Futures" permission** - 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 - **Important**: Whitelist your IP address for security
### Fee Discount Benefits: ### 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. Docker automatically handles all dependencies (Go, Node.js, TA-Lib, SQLite) and environment setup.
#### Step 1: Prepare Configuration #### ~~Step 1: Prepare Configuration~~ (Deprecated)
```bash ```bash
# Copy configuration template # ~~Copy configuration template~~
cp config.example.jsonc config.json # ~~cp config.example.jsonc config.json~~
# Edit and fill in your API keys # ~~Edit and fill in your API keys~~
nano config.json # or use any editor # ~~nano config.json # or use any editor~~
``` ```
**Note**: Configuration is now done through the web interface, not JSON files.
#### Step 2: One-Click Start #### Step 2: One-Click Start
```bash ```bash
@@ -471,7 +472,7 @@ Open your browser and visit: **🌐 http://localhost:3000**
3. **Remove the `0x` prefix** from the key 3. **Remove the `0x` prefix** from the key
4. Fund your wallet on [Hyperliquid](https://hyperliquid.xyz) 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 ```json
{ {
@@ -525,7 +526,7 @@ Open your browser and visit: **🌐 http://localhost:3000**
- API Wallet address (Signer) - API Wallet address (Signer)
- API Wallet Private Key (⚠ shown only once!) - 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 ```json
{ {
@@ -643,8 +644,10 @@ For running multiple AI traders competing against each other:
| `oi_top_api_url` | Open interest API<br>*Optional supplement data* | `""` (empty) | ❌ No | | `oi_top_api_url` | Open interest API<br>*Optional supplement data* | `""` (empty) | ❌ No |
| `api_server_port` | Web dashboard port | `8080` | ✅ Yes | | `api_server_port` | Web dashboard port | `8080` | ✅ Yes |
**Default Trading Coins** (when `use_default_coins: true`): ~~**Default Trading Coins** (when `use_default_coins: true`):
- BTC, ETH, SOL, BNB, XRP, DOGE, ADA, HYPE - 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. 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": { "leverage": {
"btc_eth_leverage": 5, // Maximum leverage for BTC and ETH "btc_eth_leverage": 5, // Maximum leverage for BTC and ETH
"altcoin_leverage": 5 // Maximum leverage for all other coins "altcoin_leverage": 5 // Maximum leverage for all other coins
} }
``` ```~~
*Note: Leverage is now configured through the web interface*
**⚠️ Important: Binance Subaccount Restrictions** **⚠️ Important: Binance Subaccount Restrictions**
@@ -680,21 +685,23 @@ The leverage settings control the maximum leverage the AI can use for each trade
**Examples:** **Examples:**
**Safe configuration (subaccount or conservative):** ~~**Safe configuration (subaccount or conservative):**~~
```json ~~```json
"leverage": { "leverage": {
"btc_eth_leverage": 5, "btc_eth_leverage": 5,
"altcoin_leverage": 5 "altcoin_leverage": 5
} }
``` ```~~
**Aggressive configuration (main account only):** ~~**Aggressive configuration (main account only):**~~
```json ~~```json
"leverage": { "leverage": {
"btc_eth_leverage": 20, "btc_eth_leverage": 20,
"altcoin_leverage": 15 "altcoin_leverage": 15
} }
``` ```~~
*Note: Leverage configuration is now done through the web interface*
**How AI uses leverage:** **How AI uses leverage:**
@@ -777,7 +784,7 @@ go build -o nofx
|--------------|----------| |--------------|----------|
| `invalid API key` | Check your Binance API key in config.json | | `invalid API key` | Check your Binance API key in config.json |
| `TA-Lib not found` | Run `brew install ta-lib` (macOS) | | `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 | | `DeepSeek API error` | Verify your DeepSeek API key and balance |
**✅ Backend is running correctly when you see:** **✅ Backend is running correctly when you see:**
@@ -1177,7 +1184,7 @@ sudo apt-get install libta-lib0-dev
**Solution**: **Solution**:
- Coin pool API is optional - Coin pool API is optional
- If API fails, system uses default mainstream coins (BTC, ETH, etc.) - 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 -**Code Organization**: Better separation between database, API, and business logic
**Migration Notes:** **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 - ⚠️ **Fresh Start**: All configurations must be redone through web interface
-**Easier Setup**: Web-based configuration is much more user-friendly -**Easier Setup**: Web-based configuration is much more user-friendly
-**Better UX**: No more server restarts for configuration changes -**Better UX**: No more server restarts for configuration changes

View File

@@ -694,7 +694,7 @@ go build -o nofx
|---------------------|---------| |---------------------|---------|
| `invalid API key` | Проверьте Binance API ключи в config.json | | `invalid API key` | Проверьте Binance API ключи в config.json |
| `TA-Lib not found` | Выполните `brew install ta-lib` (macOS) | | `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 ключ и баланс | | `DeepSeek API error` | Проверьте DeepSeek API ключ и баланс |
**✅ Признаки работы Backend:** **✅ Признаки работы Backend:**

View File

@@ -694,7 +694,7 @@ go build -o nofx
|--------------------------|---------| |--------------------------|---------|
| `invalid API key` | Перевірте Binance API ключі в config.json | | `invalid API key` | Перевірте Binance API ключі в config.json |
| `TA-Lib not found` | Виконайте `brew install ta-lib` (macOS) | | `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 ключ та баланс | | `DeepSeek API error` | Перевірте DeepSeek API ключ та баланс |
**✅ Ознаки роботи Backend:** **✅ Ознаки роботи Backend:**

View File

@@ -46,7 +46,7 @@ NOFX现已支持**三大交易所**Binance、Hyperliquid和Aster DEX
**快速开始:** **快速开始:**
1. 获取你的MetaMask私钥去掉`0x`前缀) 1. 获取你的MetaMask私钥去掉`0x`前缀)
2. 在config.json中设置`"exchange": "hyperliquid"` 2. ~~在config.json中设置`"exchange": "hyperliquid"`~~ *通过Web界面配置*
3. 添加`"hyperliquid_private_key": "your_key"` 3. 添加`"hyperliquid_private_key": "your_key"`
4. 开始交易! 4. 开始交易!
@@ -73,7 +73,7 @@ NOFX现已支持**三大交易所**Binance、Hyperliquid和Aster DEX
1. 访问[Aster API钱包](https://www.asterdex.com/en/api-wallet) 1. 访问[Aster API钱包](https://www.asterdex.com/en/api-wallet)
2. 连接你的主钱包并创建API钱包 2. 连接你的主钱包并创建API钱包
3. 复制API Signer地址和私钥 3. 复制API Signer地址和私钥
4. 在config.json中设置`"exchange": "aster"` 4. ~~在config.json中设置`"exchange": "aster"`~~ *通过Web界面配置*
5. 添加`"aster_user"``"aster_signer"``"aster_private_key"` 5. 添加`"aster_user"``"aster_signer"``"aster_private_key"`
--- ---
@@ -150,7 +150,7 @@ NOFX现已支持**三大交易所**Binance、Hyperliquid和Aster DEX
``` ```
nofx/ nofx/
├── main.go # 程序入口多trader管理器 ├── main.go # 程序入口多trader管理器
├── config.json # 配置文件API密钥、多trader配置 ├── ~~config.json~~ # ~~配置文件API密钥、多trader配置~~ (已弃用使用Web界面)
├── api/ # HTTP API服务 ├── api/ # HTTP API服务
│ └── server.go # Gin框架RESTful API │ └── server.go # Gin框架RESTful API
@@ -226,7 +226,7 @@ nofx/
5. **创建API密钥** 5. **创建API密钥**
- 进入账户 API管理 - 进入账户 API管理
- 创建新的API密钥**务必勾选"合约"权限** - 创建新的API密钥**务必勾选"合约"权限**
- 保存API Key和Secret Keyconfig.json中需要 - 保存API Key和Secret Key~~config.json中需要~~ *Web界面中需要*
- **重要**添加IP白名单以确保安全 - **重要**添加IP白名单以确保安全
### 手续费优惠说明: ### 手续费优惠说明:
@@ -245,14 +245,15 @@ nofx/
Docker会自动处理所有依赖GoNode.jsTA-Lib和环境配置完美适合新手 Docker会自动处理所有依赖GoNode.jsTA-Lib和环境配置完美适合新手
#### 步骤1准备配置文件 #### ~~步骤1准备配置文件~~ (已弃用)
```bash ```bash
# 复制配置文件模板 # ~~复制配置文件模板~~
cp config.example.jsonc config.json # ~~cp config.example.jsonc config.json~~
# 编辑并填入你的API密钥 # ~~编辑并填入你的API密钥~~
nano config.json # 或使用其他编辑器 # ~~nano config.json # 或使用其他编辑器~~
``` ```
**注意**: 现在通过Web界面进行配置不再使用JSON文件
#### 步骤2一键启动 #### 步骤2一键启动
```bash ```bash
@@ -382,13 +383,15 @@ cd ..
#### 🌟 新手模式配置(推荐) #### 🌟 新手模式配置(推荐)
**步骤1**复制并重命名示例配置文件 ~~**步骤1**复制并重命名示例配置文件~~
```bash ~~```bash
cp config.example.jsonc config.json cp config.example.jsonc config.json
``` ```~~
**步骤2**编辑`config.json`填入您的API密钥 ~~**步骤2**:编辑`config.json`填入您的API密钥~~
*现在通过Web界面配置无需编辑JSON文件*
```json ```json
{ {
@@ -453,7 +456,7 @@ cp config.example.jsonc config.json
3. **去掉`0x`前缀** 3. **去掉`0x`前缀**
4. [Hyperliquid](https://hyperliquid.xyz)上为钱包充值 4. [Hyperliquid](https://hyperliquid.xyz)上为钱包充值
**步骤2**为Hyperliquid配置`config.json` ~~**步骤2**为Hyperliquid配置`config.json`~~ *通过Web界面配置*
```json ```json
{ {
@@ -507,7 +510,7 @@ cp config.example.jsonc config.json
- API钱包地址Signer - API钱包地址Signer
- API钱包私钥(⚠ 仅显示一次 - API钱包私钥(⚠ 仅显示一次
**步骤2**为Aster配置`config.json` ~~**步骤2**为Aster配置`config.json`~~ *通过Web界面配置*
```json ```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 | | `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密钥和余额 | | `DeepSeek API error` | 验证DeepSeek API密钥和余额 |
**✅ 后端运行正常的标志:** **✅ 后端运行正常的标志:**
@@ -1194,7 +1197,7 @@ sudo apt-get install libta-lib0-dev
**解决**: **解决**:
- 币种池API是可选的 - 币种池API是可选的
- 如果API失败系统会使用默认主流币种BTC、ETH等 - 如果API失败系统会使用默认主流币种BTC、ETH等
- 检查config.json中的API URL和auth参数 - ~~检查config.json中的API URL和auth参数~~ *检查Web界面中的配置*
--- ---

View File

@@ -170,6 +170,7 @@ type CreateTraderRequest struct {
InitialBalance float64 `json:"initial_balance"` InitialBalance float64 `json:"initial_balance"`
CustomPrompt string `json:"custom_prompt"` CustomPrompt string `json:"custom_prompt"`
OverrideBasePrompt bool `json:"override_base_prompt"` OverrideBasePrompt bool `json:"override_base_prompt"`
IsCrossMargin *bool `json:"is_cross_margin"` // 指针类型nil表示使用默认值true
} }
type ModelConfig struct { type ModelConfig struct {
@@ -222,6 +223,12 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
// 生成交易员ID // 生成交易员ID
traderID := fmt.Sprintf("%s_%s_%d", req.ExchangeID, req.AIModelID, time.Now().Unix()) 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{ trader := &config.TraderRecord{
ID: traderID, ID: traderID,
@@ -232,6 +239,7 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
InitialBalance: req.InitialBalance, InitialBalance: req.InitialBalance,
CustomPrompt: req.CustomPrompt, CustomPrompt: req.CustomPrompt,
OverrideBasePrompt: req.OverrideBasePrompt, OverrideBasePrompt: req.OverrideBasePrompt,
IsCrossMargin: isCrossMargin,
ScanIntervalMinutes: 3, // 默认3分钟 ScanIntervalMinutes: 3, // 默认3分钟
IsRunning: false, IsRunning: false,
} }

View File

@@ -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
}

View File

@@ -153,6 +153,7 @@ func (d *Database) createTables() error {
`ALTER TABLE exchanges ADD COLUMN aster_private_key TEXT DEFAULT ''`, `ALTER TABLE exchanges ADD COLUMN aster_private_key TEXT DEFAULT ''`,
`ALTER TABLE traders ADD COLUMN custom_prompt 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 override_base_prompt BOOLEAN DEFAULT 0`,
`ALTER TABLE traders ADD COLUMN is_cross_margin BOOLEAN DEFAULT 1`, // 默认为全仓模式
} }
for _, query := range alterQueries { for _, query := range alterQueries {
@@ -369,6 +370,7 @@ type TraderRecord struct {
IsRunning bool `json:"is_running"` IsRunning bool `json:"is_running"`
CustomPrompt string `json:"custom_prompt"` // 自定义交易策略prompt CustomPrompt string `json:"custom_prompt"` // 自定义交易策略prompt
OverrideBasePrompt bool `json:"override_base_prompt"` // 是否覆盖基础prompt OverrideBasePrompt bool `json:"override_base_prompt"` // 是否覆盖基础prompt
IsCrossMargin bool `json:"is_cross_margin"` // 是否为全仓模式true=全仓false=逐仓)
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
} }
@@ -656,9 +658,9 @@ func (d *Database) CreateExchange(userID, id, name, typ string, enabled bool, ap
// CreateTrader 创建交易员 // CreateTrader 创建交易员
func (d *Database) CreateTrader(trader *TraderRecord) error { func (d *Database) CreateTrader(trader *TraderRecord) error {
_, err := d.db.Exec(` _, 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) 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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, trader.ID, trader.UserID, trader.Name, trader.AIModelID, trader.ExchangeID, trader.InitialBalance, trader.ScanIntervalMinutes, trader.IsRunning, trader.CustomPrompt, trader.OverrideBasePrompt) `, trader.ID, trader.UserID, trader.Name, trader.AIModelID, trader.ExchangeID, trader.InitialBalance, trader.ScanIntervalMinutes, trader.IsRunning, trader.CustomPrompt, trader.OverrideBasePrompt, trader.IsCrossMargin)
return err return err
} }
@@ -666,7 +668,8 @@ func (d *Database) CreateTrader(trader *TraderRecord) error {
func (d *Database) GetTraders(userID string) ([]*TraderRecord, error) { func (d *Database) GetTraders(userID string) ([]*TraderRecord, error) {
rows, err := d.db.Query(` rows, err := d.db.Query(`
SELECT id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running, 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 FROM traders WHERE user_id = ? ORDER BY created_at DESC
`, userID) `, userID)
if err != nil { if err != nil {
@@ -680,7 +683,8 @@ func (d *Database) GetTraders(userID string) ([]*TraderRecord, error) {
err := rows.Scan( err := rows.Scan(
&trader.ID, &trader.UserID, &trader.Name, &trader.AIModelID, &trader.ExchangeID, &trader.ID, &trader.UserID, &trader.Name, &trader.AIModelID, &trader.ExchangeID,
&trader.InitialBalance, &trader.ScanIntervalMinutes, &trader.IsRunning, &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 { if err != nil {
return nil, err return nil, err

View File

@@ -154,6 +154,7 @@ func (tm *TraderManager) addTraderFromDB(traderCfg *config.TraderRecord, aiModel
MaxDailyLoss: maxDailyLoss, MaxDailyLoss: maxDailyLoss,
MaxDrawdown: maxDrawdown, MaxDrawdown: maxDrawdown,
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute, StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
IsCrossMargin: traderCfg.IsCrossMargin,
} }
// 根据交易所类型设置API密钥 // 根据交易所类型设置API密钥
@@ -228,6 +229,7 @@ func (tm *TraderManager) AddTraderFromDB(traderCfg *config.TraderRecord, aiModel
MaxDailyLoss: maxDailyLoss, MaxDailyLoss: maxDailyLoss,
MaxDrawdown: maxDrawdown, MaxDrawdown: maxDrawdown,
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute, StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
IsCrossMargin: traderCfg.IsCrossMargin,
} }
// 根据交易所类型设置API密钥 // 根据交易所类型设置API密钥
@@ -560,6 +562,7 @@ func (tm *TraderManager) loadSingleTrader(traderCfg *config.TraderRecord, aiMode
MaxDailyLoss: maxDailyLoss, MaxDailyLoss: maxDailyLoss,
MaxDrawdown: maxDrawdown, MaxDrawdown: maxDrawdown,
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute, StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
IsCrossMargin: traderCfg.IsCrossMargin,
} }
// 根据交易所类型设置API密钥 // 根据交易所类型设置API密钥

View File

@@ -78,24 +78,14 @@ check_env() {
} }
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
# Validation: Configuration File (config.json) # Validation: Database File (trading.db)
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
check_config() { check_database() {
if [ ! -f "config.json" ]; then if [ ! -f "trading.db" ]; then
print_warning "config.json 不存在,正在从 config.example.jsonc 生成..." print_info "数据库文件不存在,系统将在启动时自动创建"
else
# 生成 config.json安全去除 JSONC 注释,不破坏 https:// print_success "数据库文件存在"
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
fi fi
print_success "配置文件存在"
} }
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
@@ -253,7 +243,7 @@ main() {
case "${1:-start}" in case "${1:-start}" in
start) start)
check_env check_env
check_config check_database
start "$2" start "$2"
;; ;;
stop) stop)

View File

@@ -819,6 +819,38 @@ func (t *AsterTrader) CloseShort(symbol string, quantity float64) (map[string]in
return result, nil 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 设置杠杆倍数 // SetLeverage 设置杠杆倍数
func (t *AsterTrader) SetLeverage(symbol string, leverage int) error { func (t *AsterTrader) SetLeverage(symbol string, leverage int) error {
params := map[string]interface{}{ params := map[string]interface{}{

View File

@@ -63,6 +63,9 @@ type AutoTraderConfig struct {
MaxDailyLoss float64 // 最大日亏损百分比(提示) MaxDailyLoss float64 // 最大日亏损百分比(提示)
MaxDrawdown float64 // 最大回撤百分比(提示) MaxDrawdown float64 // 最大回撤百分比(提示)
StopTradingTime time.Duration // 触发风控后暂停时长 StopTradingTime time.Duration // 触发风控后暂停时长
// 仓位模式
IsCrossMargin bool // true=全仓模式, false=逐仓模式
} }
// AutoTrader 自动交易器 // AutoTrader 自动交易器
@@ -135,6 +138,13 @@ func NewAutoTrader(config AutoTraderConfig) (*AutoTrader, error) {
var trader Trader var trader Trader
var err error var err error
// 记录仓位模式(通用)
marginModeStr := "全仓"
if !config.IsCrossMargin {
marginModeStr = "逐仓"
}
log.Printf("📊 [%s] 仓位模式: %s", config.Name, marginModeStr)
switch config.Exchange { switch config.Exchange {
case "binance": case "binance":
log.Printf("🏦 [%s] 使用币安合约交易", config.Name) log.Printf("🏦 [%s] 使用币安合约交易", config.Name)
@@ -589,6 +599,12 @@ func (at *AutoTrader) executeOpenLongWithRecord(decision *decision.Decision, act
actionRecord.Quantity = quantity actionRecord.Quantity = quantity
actionRecord.Price = marketData.CurrentPrice 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) order, err := at.trader.OpenLong(decision.Symbol, quantity, decision.Leverage)
if err != nil { if err != nil {
@@ -642,6 +658,12 @@ func (at *AutoTrader) executeOpenShortWithRecord(decision *decision.Decision, ac
actionRecord.Quantity = quantity actionRecord.Quantity = quantity
actionRecord.Price = marketData.CurrentPrice 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) order, err := at.trader.OpenShort(decision.Symbol, quantity, decision.Leverage)
if err != nil { if err != nil {
@@ -885,7 +907,7 @@ func (at *AutoTrader) GetPositions() ([]map[string]interface{}, error) {
// 计算占用保证金 // 计算占用保证金
marginUsed := (quantity * markPrice) / float64(leverage) marginUsed := (quantity * markPrice) / float64(leverage)
// 计算盈亏百分比(基于保证金) // 计算盈亏百分比(基于保证金)
// 收益率 = 未实现盈亏 / 保证金 × 100% // 收益率 = 未实现盈亏 / 保证金 × 100%
pnlPct := 0.0 pnlPct := 0.0

View File

@@ -131,6 +131,46 @@ func (t *FuturesTrader) GetPositions() ([]map[string]interface{}, error) {
return result, nil 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 设置杠杆(智能判断+冷却期) // SetLeverage 设置杠杆(智能判断+冷却期)
func (t *FuturesTrader) SetLeverage(symbol string, leverage int) error { func (t *FuturesTrader) SetLeverage(symbol string, leverage int) error {
// 先尝试获取当前杠杆(从持仓信息) // 先尝试获取当前杠杆(从持仓信息)
@@ -177,31 +217,6 @@ func (t *FuturesTrader) SetLeverage(symbol string, leverage int) error {
return nil 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 开多仓 // OpenLong 开多仓
func (t *FuturesTrader) OpenLong(symbol string, quantity float64, leverage int) (map[string]interface{}, error) { 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 return nil, err
} }
// 设置逐仓模式 // 注意仓位模式应该由调用方AutoTrader在开仓前通过 SetMarginMode 设置
if err := t.SetMarginType(symbol, futures.MarginTypeIsolated); err != nil {
return nil, err
}
// 格式化数量到正确精度 // 格式化数量到正确精度
quantityStr, err := t.FormatQuantity(symbol, quantity) quantityStr, err := t.FormatQuantity(symbol, quantity)
@@ -260,10 +272,7 @@ func (t *FuturesTrader) OpenShort(symbol string, quantity float64, leverage int)
return nil, err return nil, err
} }
// 设置逐仓模式 // 注意仓位模式应该由调用方AutoTrader在开仓前通过 SetMarginMode 设置
if err := t.SetMarginType(symbol, futures.MarginTypeIsolated); err != nil {
return nil, err
}
// 格式化数量到正确精度 // 格式化数量到正确精度
quantityStr, err := t.FormatQuantity(symbol, quantity) quantityStr, err := t.FormatQuantity(symbol, quantity)

View File

@@ -13,10 +13,11 @@ import (
// HyperliquidTrader Hyperliquid交易器 // HyperliquidTrader Hyperliquid交易器
type HyperliquidTrader struct { type HyperliquidTrader struct {
exchange *hyperliquid.Exchange exchange *hyperliquid.Exchange
ctx context.Context ctx context.Context
walletAddr string walletAddr string
meta *hyperliquid.Meta // 缓存meta信息包含精度等 meta *hyperliquid.Meta // 缓存meta信息包含精度等
isCrossMargin bool // 是否为全仓模式
} }
// NewHyperliquidTrader 创建Hyperliquid交易器 // NewHyperliquidTrader 创建Hyperliquid交易器
@@ -63,10 +64,11 @@ func NewHyperliquidTrader(privateKeyHex string, walletAddr string, testnet bool)
} }
return &HyperliquidTrader{ return &HyperliquidTrader{
exchange: exchange, exchange: exchange,
ctx: ctx, ctx: ctx,
walletAddr: walletAddr, walletAddr: walletAddr,
meta: meta, meta: meta,
isCrossMargin: true, // 默认使用全仓模式
}, nil }, nil
} }
@@ -187,13 +189,26 @@ func (t *HyperliquidTrader) GetPositions() ([]map[string]interface{}, error) {
return result, nil 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 设置杠杆 // SetLeverage 设置杠杆
func (t *HyperliquidTrader) SetLeverage(symbol string, leverage int) error { func (t *HyperliquidTrader) SetLeverage(symbol string, leverage int) error {
// Hyperliquid symbol格式去掉USDT后缀 // Hyperliquid symbol格式去掉USDT后缀
coin := convertSymbolToHyperliquid(symbol) coin := convertSymbolToHyperliquid(symbol)
// 调用UpdateLeverage (leverage int, name string, isCross bool) // 调用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 { if err != nil {
return fmt.Errorf("设置杠杆失败: %w", err) return fmt.Errorf("设置杠杆失败: %w", err)
} }

View File

@@ -24,6 +24,9 @@ type Trader interface {
// SetLeverage 设置杠杆 // SetLeverage 设置杠杆
SetLeverage(symbol string, leverage int) error SetLeverage(symbol string, leverage int) error
// SetMarginMode 设置仓位模式 (true=全仓, false=逐仓)
SetMarginMode(symbol string, isCrossMargin bool) error
// GetMarketPrice 获取市场价格 // GetMarketPrice 获取市场价格
GetMarketPrice(symbol string) (float64, error) GetMarketPrice(symbol string) (float64, error)

View File

@@ -73,11 +73,11 @@ function App() {
return () => window.removeEventListener('hashchange', handleHashChange); return () => window.removeEventListener('hashchange', handleHashChange);
}, []); }, []);
// 切换页面时更新URL hash // 切换页面时更新URL hash (当前通过按钮直接调用setCurrentPage这个函数暂时保留用于未来扩展)
const navigateToPage = (page: Page) => { // const navigateToPage = (page: Page) => {
setCurrentPage(page); // setCurrentPage(page);
window.location.hash = page === 'competition' ? '' : 'trader'; // window.location.hash = page === 'competition' ? '' : 'trader';
}; // };
// 获取trader列表 // 获取trader列表
const { data: traders } = useSWR<TraderInfo[]>('traders', api.getTraders, { const { data: traders } = useSWR<TraderInfo[]>('traders', api.getTraders, {

View File

@@ -99,7 +99,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
return traders?.some(t => t.exchange_id === exchangeId && t.is_running) || false; 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 { try {
const model = allModels?.find(m => m.id === modelId); const model = allModels?.find(m => m.id === modelId);
const exchange = allExchanges?.find(e => e.id === exchangeId); const exchange = allExchanges?.find(e => e.id === exchangeId);
@@ -120,7 +120,8 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
exchange_id: exchangeId, exchange_id: exchangeId,
initial_balance: initialBalance, initial_balance: initialBalance,
custom_prompt: customPrompt, custom_prompt: customPrompt,
override_base_prompt: overrideBase override_base_prompt: overrideBase,
is_cross_margin: isCrossMargin
}; };
await api.createTrader(request); await api.createTrader(request);
@@ -663,7 +664,7 @@ function CreateTraderModal({
}: { }: {
enabledModels: AIModel[]; enabledModels: AIModel[];
enabledExchanges: Exchange[]; 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; onClose: () => void;
language: any; language: any;
}) { }) {
@@ -679,12 +680,13 @@ function CreateTraderModal({
const [customPrompt, setCustomPrompt] = useState(''); const [customPrompt, setCustomPrompt] = useState('');
const [showAdvanced, setShowAdvanced] = useState(false); const [showAdvanced, setShowAdvanced] = useState(false);
const [overrideBase, setOverrideBase] = useState(false); const [overrideBase, setOverrideBase] = useState(false);
const [isCrossMargin, setIsCrossMargin] = useState(true); // 默认为全仓模式
const handleSubmit = (e: React.FormEvent) => { const handleSubmit = (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
if (!selectedModel || !selectedExchange || !traderName.trim()) return; 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 ( return (
@@ -763,6 +765,44 @@ function CreateTraderModal({
required required
/> />
</div> </div>
{/* Margin Mode Selection */}
<div>
<label className="block text-sm font-semibold mb-2" style={{ color: '#EAECEF' }}>
</label>
<div className="grid grid-cols-2 gap-2">
<button
type="button"
onClick={() => setIsCrossMargin(true)}
className={`px-3 py-2 rounded text-sm font-semibold transition-all ${
isCrossMargin
? 'bg-yellow-500 text-black'
: 'bg-gray-700 text-gray-400 hover:bg-gray-600'
}`}
style={isCrossMargin ? { background: '#F0B90B', color: '#000' } : { background: '#2B3139', color: '#848E9C' }}
>
</button>
<button
type="button"
onClick={() => setIsCrossMargin(false)}
className={`px-3 py-2 rounded text-sm font-semibold transition-all ${
!isCrossMargin
? 'bg-yellow-500 text-black'
: 'bg-gray-700 text-gray-400 hover:bg-gray-600'
}`}
style={!isCrossMargin ? { background: '#F0B90B', color: '#000' } : { background: '#2B3139', color: '#848E9C' }}
>
</button>
</div>
<div className="text-xs mt-1" style={{ color: '#848E9C' }}>
{isCrossMargin
? '全仓模式:所有仓位共享账户余额作为保证金'
: '逐仓模式:每个仓位独立管理保证金,风险隔离'}
</div>
</div>
{/* Advanced Settings Toggle */} {/* Advanced Settings Toggle */}
<div className="mt-4"> <div className="mt-4">

View File

@@ -22,13 +22,15 @@ export function Header({ simple = false }: HeaderProps) {
<h1 className="text-xl font-bold" style={{ color: '#EAECEF' }}> <h1 className="text-xl font-bold" style={{ color: '#EAECEF' }}>
{t('appTitle', language)} {t('appTitle', language)}
</h1> </h1>
<p className="text-xs mono" style={{ color: '#848E9C' }}> {!simple && (
{t('subtitle', language)} <p className="text-xs mono" style={{ color: '#848E9C' }}>
</p> {t('subtitle', language)}
</p>
)}
</div> </div>
</div> </div>
{/* Right - Language Toggle */} {/* Right - Language Toggle (always show) */}
<div className="flex gap-1 rounded p-1" style={{ background: '#1E2329' }}> <div className="flex gap-1 rounded p-1" style={{ background: '#1E2329' }}>
<button <button
onClick={() => setLanguage('zh')} onClick={() => setLanguage('zh')}

View File

@@ -137,6 +137,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
localStorage.setItem('auth_token', data.token); localStorage.setItem('auth_token', data.token);
localStorage.setItem('auth_user', JSON.stringify(userInfo)); localStorage.setItem('auth_user', JSON.stringify(userInfo));
// 跳转到首页
window.history.pushState({}, '', '/');
window.dispatchEvent(new PopStateEvent('popstate'));
return { success: true, message: data.message }; return { success: true, message: data.message };
} else { } else {
return { success: false, message: data.error }; return { success: false, message: data.error };
@@ -166,6 +170,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
localStorage.setItem('auth_token', data.token); localStorage.setItem('auth_token', data.token);
localStorage.setItem('auth_user', JSON.stringify(userInfo)); localStorage.setItem('auth_user', JSON.stringify(userInfo));
// 跳转到首页
window.history.pushState({}, '', '/');
window.dispatchEvent(new PopStateEvent('popstate'));
return { success: true, message: data.message }; return { success: true, message: data.message };
} else { } else {
return { success: false, message: data.error }; return { success: false, message: data.error };

View File

@@ -125,6 +125,7 @@ export interface CreateTraderRequest {
initial_balance: number; initial_balance: number;
custom_prompt?: string; custom_prompt?: string;
override_base_prompt?: boolean; override_base_prompt?: boolean;
is_cross_margin?: boolean;
} }
export interface UpdateModelConfigRequest { export interface UpdateModelConfigRequest {