mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-04 19:41:02 +08:00
Merge pull request #110 from Icyoung/dev
【Dev】新增仓位模式的配置、移除config配置的文档描述
This commit is contained in:
@@ -10,7 +10,9 @@
|
||||
|
||||
## 配置方式
|
||||
|
||||
在 `config.json` 中添加使用自定义 API 的 trader:
|
||||
~~在 `config.json` 中添加使用自定义 API 的 trader:~~
|
||||
|
||||
*注意:现在通过Web界面配置自定义API,不再使用config.json文件*
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -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 <PID>
|
||||
### 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
55
README.md
55
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<br>*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
|
||||
|
||||
@@ -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:**
|
||||
|
||||
@@ -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:**
|
||||
|
||||
@@ -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界面中的配置*
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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密钥
|
||||
|
||||
24
start.sh
24
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)
|
||||
|
||||
@@ -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{}{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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<TraderInfo[]>('traders', api.getTraders, {
|
||||
|
||||
@@ -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
|
||||
/>
|
||||
</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 */}
|
||||
<div className="mt-4">
|
||||
|
||||
@@ -22,13 +22,15 @@ export function Header({ simple = false }: HeaderProps) {
|
||||
<h1 className="text-xl font-bold" style={{ color: '#EAECEF' }}>
|
||||
{t('appTitle', language)}
|
||||
</h1>
|
||||
<p className="text-xs mono" style={{ color: '#848E9C' }}>
|
||||
{t('subtitle', language)}
|
||||
</p>
|
||||
{!simple && (
|
||||
<p className="text-xs mono" style={{ color: '#848E9C' }}>
|
||||
{t('subtitle', language)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right - Language Toggle */}
|
||||
{/* Right - Language Toggle (always show) */}
|
||||
<div className="flex gap-1 rounded p-1" style={{ background: '#1E2329' }}>
|
||||
<button
|
||||
onClick={() => setLanguage('zh')}
|
||||
|
||||
@@ -137,6 +137,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
localStorage.setItem('auth_token', data.token);
|
||||
localStorage.setItem('auth_user', JSON.stringify(userInfo));
|
||||
|
||||
// 跳转到首页
|
||||
window.history.pushState({}, '', '/');
|
||||
window.dispatchEvent(new PopStateEvent('popstate'));
|
||||
|
||||
return { success: true, message: data.message };
|
||||
} else {
|
||||
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_user', JSON.stringify(userInfo));
|
||||
|
||||
// 跳转到首页
|
||||
window.history.pushState({}, '', '/');
|
||||
window.dispatchEvent(new PopStateEvent('popstate'));
|
||||
|
||||
return { success: true, message: data.message };
|
||||
} else {
|
||||
return { success: false, message: data.error };
|
||||
|
||||
@@ -125,6 +125,7 @@ export interface CreateTraderRequest {
|
||||
initial_balance: number;
|
||||
custom_prompt?: string;
|
||||
override_base_prompt?: boolean;
|
||||
is_cross_margin?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateModelConfigRequest {
|
||||
|
||||
Reference in New Issue
Block a user