mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-04 01:48:22 +08:00
Merge branch 'tinkle-community:main' into main
This commit is contained in:
+23
-3
@@ -40,7 +40,7 @@
|
||||
| 字段 | 类型 | 必需 | 说明 |
|
||||
|-----|------|------|------|
|
||||
| `ai_model` | string | ✅ | 设置为 `"custom"` 启用自定义 API |
|
||||
| `custom_api_url` | string | ✅ | API 的 Base URL (不含 `/chat/completions`) |
|
||||
| `custom_api_url` | string | ✅ | API 的 Base URL (不含 `/chat/completions`)。特殊用法:如果以 `#` 结尾,则使用完整 URL(不自动添加路径) |
|
||||
| `custom_api_key` | string | ✅ | API 密钥 |
|
||||
| `custom_model_name` | string | ✅ | 模型名称 (如 `gpt-4o`, `claude-3-5-sonnet` 等) |
|
||||
|
||||
@@ -90,11 +90,26 @@
|
||||
}
|
||||
```
|
||||
|
||||
### 5. 使用完整自定义路径(末尾添加 #)
|
||||
|
||||
对于某些特殊的 API 端点,如果已经包含完整路径(包括 `/chat/completions` 或其他自定义路径),可以在 URL 末尾添加 `#` 来强制使用完整 URL:
|
||||
|
||||
```json
|
||||
{
|
||||
"ai_model": "custom",
|
||||
"custom_api_url": "https://api.example.com/v2/ai/chat/completions#",
|
||||
"custom_api_key": "your-api-key",
|
||||
"custom_model_name": "custom-model"
|
||||
}
|
||||
```
|
||||
|
||||
**注意**:`#` 会被自动去除,实际请求会发送到 `https://api.example.com/v2/ai/chat/completions`
|
||||
|
||||
## 兼容性要求
|
||||
|
||||
自定义 API 必须:
|
||||
1. 支持 OpenAI Chat Completions 格式
|
||||
2. 接受 `POST /chat/completions` 端点
|
||||
2. 接受 `POST` 请求到 `/chat/completions` 端点(或在 URL 末尾添加 `#` 以使用自定义路径)
|
||||
3. 支持 `Authorization: Bearer {api_key}` 认证
|
||||
4. 返回标准的 OpenAI 响应格式
|
||||
|
||||
@@ -103,6 +118,9 @@
|
||||
1. **URL 格式**:`custom_api_url` 应该是 Base URL,系统会自动添加 `/chat/completions`
|
||||
- ✅ 正确:`https://api.openai.com/v1`
|
||||
- ❌ 错误:`https://api.openai.com/v1/chat/completions`
|
||||
- 🔧 **特殊用法**:如果需要使用完整的自定义路径(不自动添加 `/chat/completions`),可以在 URL 末尾添加 `#`
|
||||
- 例如:`https://api.example.com/custom/path/chat/completions#`
|
||||
- 系统会自动去掉 `#` 并直接使用该完整 URL
|
||||
|
||||
2. **模型名称**:确保 `custom_model_name` 与 API 提供商支持的模型名称完全一致
|
||||
|
||||
@@ -157,7 +175,9 @@
|
||||
### 问题:API 调用失败
|
||||
|
||||
**可能原因**:
|
||||
1. URL 格式错误(检查是否包含了 `/chat/completions`)
|
||||
1. URL 格式错误
|
||||
- 普通用法:不应包含 `/chat/completions`(系统会自动添加)
|
||||
- 特殊用法:如果需要完整路径,记得在 URL 末尾添加 `#`
|
||||
2. API 密钥无效
|
||||
3. 模型名称错误
|
||||
4. 网络连接问题
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ RUN go mod download
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -o nofx .
|
||||
RUN CGO_ENABLED=1 GOOS=linux go build -trimpath -ldflags="-s -w" -o nofx .
|
||||
|
||||
# Frontend build stage
|
||||
FROM node:18-alpine AS frontend-builder
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
---
|
||||
|
||||
An automated crypto futures trading system powered by **DeepSeek/Qwen AI**, supporting **Binance and Hyperliquid exchanges**, **multi-AI model live trading competition**, featuring comprehensive market analysis, AI decision-making, **self-learning mechanism**, and professional Web monitoring interface.
|
||||
An automated crypto futures trading system powered by **DeepSeek/Qwen AI**, supporting **Binance, Hyperliquid, and Aster DEX exchanges**, **multi-AI model live trading competition**, featuring comprehensive market analysis, AI decision-making, **self-learning mechanism**, and professional Web monitoring interface.
|
||||
|
||||
> ⚠️ **Risk Warning**: This system is experimental. AI auto-trading carries significant risks. Strongly recommended for learning/research purposes or testing with small amounts only!
|
||||
|
||||
@@ -23,9 +23,13 @@ Join our Telegram developer community to discuss, share ideas, and get support:
|
||||
|
||||
## 🆕 What's New (Latest Update)
|
||||
|
||||
### 🚀 Hyperliquid Exchange Support Added!
|
||||
### 🚀 Multi-Exchange Support!
|
||||
|
||||
NOFX now supports **Hyperliquid** - a high-performance decentralized perpetual futures exchange!
|
||||
NOFX now supports **three major exchanges**: Binance, Hyperliquid, and Aster DEX!
|
||||
|
||||
#### **Hyperliquid Exchange**
|
||||
|
||||
A high-performance decentralized perpetual futures exchange!
|
||||
|
||||
**Key Features:**
|
||||
- ✅ Full trading support (long/short, leverage, stop-loss/take-profit)
|
||||
@@ -48,6 +52,42 @@ NOFX now supports **Hyperliquid** - a high-performance decentralized perpetual f
|
||||
|
||||
See [Configuration Guide](#-alternative-using-hyperliquid-exchange) for details.
|
||||
|
||||
#### **Aster DEX Exchange** (NEW! v2.0.2)
|
||||
|
||||
A Binance-compatible decentralized perpetual futures exchange!
|
||||
|
||||
**Key Features:**
|
||||
- ✅ Binance-style API (easy migration from Binance)
|
||||
- ✅ Web3 wallet authentication (secure and decentralized)
|
||||
- ✅ Full trading support with automatic precision handling
|
||||
- ✅ Lower trading fees than CEX
|
||||
- ✅ EVM-compatible (Ethereum, BSC, Polygon, etc.)
|
||||
|
||||
**Why Aster?**
|
||||
- 🎯 **Binance-compatible API** - minimal code changes required
|
||||
- 🔐 **API Wallet System** - separate trading wallet for security
|
||||
- 💰 **Competitive fees** - lower than most centralized exchanges
|
||||
- 🌐 **Multi-chain support** - trade on your preferred EVM chain
|
||||
|
||||
**Quick Start:**
|
||||
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
|
||||
5. Add `"aster_user"`, `"aster_signer"`, and `"aster_private_key"`
|
||||
|
||||
---
|
||||
|
||||
## 📸 Screenshots
|
||||
|
||||
### 🏆 Competition Mode - Real-time AI Battle
|
||||

|
||||
*Multi-AI leaderboard with real-time performance comparison charts showing Qwen vs DeepSeek live trading battle*
|
||||
|
||||
### 📊 Trader Details - Complete Trading Dashboard
|
||||

|
||||
*Professional trading interface with equity curves, live positions, and AI decision logs with expandable input prompts & chain-of-thought reasoning*
|
||||
|
||||
---
|
||||
|
||||
## ✨ Core Features
|
||||
@@ -442,6 +482,71 @@ cp config.json.example config.json
|
||||
|
||||
---
|
||||
|
||||
#### 🔶 Alternative: Using Aster DEX Exchange
|
||||
|
||||
**NOFX also supports Aster DEX** - a Binance-compatible decentralized perpetual futures exchange!
|
||||
|
||||
**Why Choose Aster?**
|
||||
- 🎯 Binance-compatible API (easy migration)
|
||||
- 🔐 API Wallet security system
|
||||
- 💰 Lower trading fees
|
||||
- 🌐 Multi-chain support (ETH, BSC, Polygon)
|
||||
- 🌍 No KYC required
|
||||
|
||||
**Step 1**: Create Aster API Wallet
|
||||
|
||||
1. Visit [Aster API Wallet](https://www.asterdex.com/en/api-wallet)
|
||||
2. Connect your main wallet (MetaMask, WalletConnect, etc.)
|
||||
3. Click "Create API Wallet"
|
||||
4. **Save these 3 items immediately:**
|
||||
- Main Wallet address (User)
|
||||
- API Wallet address (Signer)
|
||||
- API Wallet Private Key (⚠️ shown only once!)
|
||||
|
||||
**Step 2**: Configure `config.json` for Aster
|
||||
|
||||
```json
|
||||
{
|
||||
"traders": [
|
||||
{
|
||||
"id": "aster_deepseek",
|
||||
"name": "Aster DeepSeek Trader",
|
||||
"ai_model": "deepseek",
|
||||
"exchange": "aster",
|
||||
|
||||
"aster_user": "0x63DD5aCC6b1aa0f563956C0e534DD30B6dcF7C4e",
|
||||
"aster_signer": "0x21cF8Ae13Bb72632562c6Fff438652Ba1a151bb0",
|
||||
"aster_private_key": "4fd0a42218f3eae43a6ce26d22544e986139a01e5b34a62db53757ffca81bae1",
|
||||
|
||||
"deepseek_key": "sk-xxxxxxxxxxxxx",
|
||||
"initial_balance": 1000.0,
|
||||
"scan_interval_minutes": 3
|
||||
}
|
||||
],
|
||||
"use_default_coins": true,
|
||||
"api_server_port": 8080,
|
||||
"leverage": {
|
||||
"btc_eth_leverage": 5,
|
||||
"altcoin_leverage": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Configuration Fields:**
|
||||
- `"exchange": "aster"` - Set exchange to Aster
|
||||
- `aster_user` - Your main wallet address
|
||||
- `aster_signer` - API wallet address (from Step 1)
|
||||
- `aster_private_key` - API wallet private key (without `0x` prefix)
|
||||
|
||||
**📖 For detailed setup instructions, see**: [Aster Integration Guide](ASTER_INTEGRATION.md)
|
||||
|
||||
**⚠️ Security Notes**:
|
||||
- API wallet is separate from your main wallet (extra security layer)
|
||||
- Never share your API private key
|
||||
- You can revoke API wallet access anytime at [asterdex.com](https://www.asterdex.com/en/api-wallet)
|
||||
|
||||
---
|
||||
|
||||
#### ⚔️ Expert Mode: Multi-Trader Competition
|
||||
|
||||
For running multiple AI traders competing against each other:
|
||||
@@ -1092,6 +1197,19 @@ This version fixes **critical calculation errors** in the historical trade recor
|
||||
|
||||
**Recommendation**: If you were running the system before this update, your historical statistics were inaccurate. After updating to v2.0.2, new trades will be calculated correctly.
|
||||
|
||||
### v2.0.2 (2025-10-29)
|
||||
|
||||
**Bug Fixes:**
|
||||
- ✅ Fixed Aster exchange precision error (code -1111: "Precision is over the maximum defined for this asset")
|
||||
- ✅ Improved price and quantity formatting to match exchange precision requirements
|
||||
- ✅ Added detailed precision processing logs for debugging
|
||||
- ✅ Enhanced all order functions (OpenLong, OpenShort, CloseLong, CloseShort, SetStopLoss, SetTakeProfit) with proper precision handling
|
||||
|
||||
**Technical Details:**
|
||||
- Added `formatFloatWithPrecision` function to convert float64 to strings with correct precision
|
||||
- Price and quantity parameters are now formatted according to exchange's `pricePrecision` and `quantityPrecision` specifications
|
||||
- Trailing zeros are removed from formatted values to optimize API requests
|
||||
|
||||
### v2.0.1 (2025-10-29)
|
||||
|
||||
**Bug Fixes:**
|
||||
@@ -1159,7 +1277,7 @@ Issues and Pull Requests are welcome!
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: 2025-10-29 (v2.0.2)
|
||||
**Last Updated**: 2025-10-29 (v2.0.3)
|
||||
|
||||
**⚡ Explore the possibilities of quantitative trading with the power of AI!**
|
||||
|
||||
|
||||
+175
-1
@@ -9,7 +9,7 @@
|
||||
|
||||
---
|
||||
|
||||
Автоматизированная система торговли фьючерсами Binance на базе **DeepSeek/Qwen AI**, поддерживающая **конкуренцию нескольких AI-моделей в реальной торговле**, с полным анализом рынка, принятием решений AI, **механизмом самообучения** и профессиональным веб-интерфейсом мониторинга.
|
||||
Автоматизированная система торговли криптовалютными фьючерсами на базе **DeepSeek/Qwen AI**, поддерживающая **Binance, Hyperliquid и Aster DEX биржи**, **конкуренцию нескольких AI-моделей в реальной торговле**, с полным анализом рынка, принятием решений AI, **механизмом самообучения** и профессиональным веб-интерфейсом мониторинга.
|
||||
|
||||
> ⚠️ **Предупреждение о рисках**: Эта система экспериментальная. Автоматическая торговля с AI несет значительные риски. Настоятельно рекомендуется использовать только для обучения/исследований или тестирования с небольшими суммами!
|
||||
|
||||
@@ -21,6 +21,75 @@
|
||||
|
||||
---
|
||||
|
||||
## 🆕 Последние обновления
|
||||
|
||||
### 🚀 Поддержка нескольких бирж!
|
||||
|
||||
NOFX теперь поддерживает **три основные биржи**: Binance, Hyperliquid и Aster DEX!
|
||||
|
||||
#### **Биржа Hyperliquid**
|
||||
|
||||
Высокопроизводительная децентрализованная биржа бессрочных фьючерсов!
|
||||
|
||||
**Ключевые особенности:**
|
||||
- ✅ Полная поддержка торговли (лонг/шорт, плечо, стоп-лосс/тейк-профит)
|
||||
- ✅ Автоматическая обработка точности (размер и цена ордера)
|
||||
- ✅ Единый интерфейс трейдера (бесшовное переключение бирж)
|
||||
- ✅ Поддержка мейннета и тестнета
|
||||
- ✅ Не нужны API ключи - только приватный ключ Ethereum
|
||||
|
||||
**Почему Hyperliquid?**
|
||||
- 🔥 Более низкие комиссии чем на централизованных биржах
|
||||
- 🔒 Без хранения - вы контролируете свои средства
|
||||
- ⚡ Быстрое исполнение с расчетом на цепи
|
||||
- 🌍 Не нужна KYC
|
||||
|
||||
**Быстрый старт:**
|
||||
1. Получите приватный ключ MetaMask (удалите префикс `0x`)
|
||||
2. Установите `"exchange": "hyperliquid"` в config.json
|
||||
3. Добавьте `"hyperliquid_private_key": "your_key"`
|
||||
4. Начните торговать!
|
||||
|
||||
См. [Руководство по конфигурации](#-альтернатива-использование-биржи-hyperliquid).
|
||||
|
||||
#### **Биржа Aster DEX** (НОВОЕ! v2.0.2)
|
||||
|
||||
Децентрализованная биржа бессрочных фьючерсов, совместимая с Binance!
|
||||
|
||||
**Ключевые особенности:**
|
||||
- ✅ API в стиле Binance (легкая миграция с Binance)
|
||||
- ✅ Web3 аутентификация кошелька (безопасно и децентрализованно)
|
||||
- ✅ Полная поддержка торговли с автоматической обработкой точности
|
||||
- ✅ Более низкие комиссии за торговлю чем CEX
|
||||
- ✅ Совместимость с EVM (Ethereum, BSC, Polygon и т.д.)
|
||||
|
||||
**Почему Aster?**
|
||||
- 🎯 **API совместимый с Binance** - нужны минимальные изменения кода
|
||||
- 🔐 **Система API кошелька** - отдельный торговый кошелек для безопасности
|
||||
- 💰 **Конкурентные комиссии** - ниже чем большинство централизованных бирж
|
||||
- 🌐 **Поддержка нескольких цепей** - торгуйте на вашей любимой EVM цепи
|
||||
|
||||
**Быстрый старт:**
|
||||
1. Посетите [Aster API Wallet](https://www.asterdex.com/en/api-wallet)
|
||||
2. Подключите основной кошелек и создайте API кошелек
|
||||
3. Скопируйте адрес API Signer и приватный ключ
|
||||
4. Установите `"exchange": "aster"` в config.json
|
||||
5. Добавьте `"aster_user"`, `"aster_signer"` и `"aster_private_key"`
|
||||
|
||||
---
|
||||
|
||||
## 📸 Скриншоты
|
||||
|
||||
### 🏆 Режим конкуренции - Битва AI в реальном времени
|
||||

|
||||
*Лидерборд с несколькими AI и графики сравнения производительности в реальном времени показывают битву Qwen против DeepSeek*
|
||||
|
||||
### 📊 Детали трейдера - Полная торговая панель
|
||||

|
||||
*Профессиональный торговый интерфейс с кривыми капитала, живыми позициями и логами решений AI с раскрываемыми входными промптами и цепочкой рассуждений*
|
||||
|
||||
---
|
||||
|
||||
## ✨ Основные возможности
|
||||
|
||||
### 🏆 Режим конкуренции нескольких AI
|
||||
@@ -308,6 +377,111 @@ cp config.json.example config.json
|
||||
|
||||
---
|
||||
|
||||
#### 🔷 Альтернатива: Использование биржи Hyperliquid
|
||||
|
||||
**NOFX также поддерживает Hyperliquid** - децентрализованную биржу бессрочных фьючерсов. Чтобы использовать Hyperliquid вместо Binance:
|
||||
|
||||
**Шаг 1**: Получите приватный ключ Ethereum (для аутентификации Hyperliquid)
|
||||
|
||||
1. Откройте **MetaMask** (или любой Ethereum кошелек)
|
||||
2. Экспортируйте приватный ключ
|
||||
3. **Удалите префикс `0x`** из ключа
|
||||
4. Пополните кошелек на [Hyperliquid](https://hyperliquid.xyz)
|
||||
|
||||
**Шаг 2**: Настройте `config.json` для Hyperliquid
|
||||
|
||||
```json
|
||||
{
|
||||
"traders": [
|
||||
{
|
||||
"id": "hyperliquid_trader",
|
||||
"name": "My Hyperliquid Trader",
|
||||
"ai_model": "deepseek",
|
||||
"exchange": "hyperliquid",
|
||||
"hyperliquid_private_key": "your_private_key_without_0x",
|
||||
"hyperliquid_testnet": false,
|
||||
"deepseek_key": "sk-xxxxxxxxxxxxx",
|
||||
"initial_balance": 1000.0,
|
||||
"scan_interval_minutes": 3
|
||||
}
|
||||
],
|
||||
"use_default_coins": true,
|
||||
"api_server_port": 8080
|
||||
}
|
||||
```
|
||||
|
||||
**Ключевые отличия от конфигурации Binance:**
|
||||
- Замените `binance_api_key` + `binance_secret_key` на `hyperliquid_private_key`
|
||||
- Добавьте поле `"exchange": "hyperliquid"`
|
||||
- Установите `hyperliquid_testnet: false` для мейннета (или `true` для тестнета)
|
||||
|
||||
**⚠️ Предупреждение безопасности**: Никогда не делитесь приватным ключом! Используйте отдельный кошелек для торговли, а не основной.
|
||||
|
||||
---
|
||||
|
||||
#### 🔶 Альтернатива: Использование биржи Aster DEX
|
||||
|
||||
**NOFX также поддерживает Aster DEX** - децентрализованную биржу бессрочных фьючерсов, совместимую с Binance!
|
||||
|
||||
**Почему выбрать Aster?**
|
||||
- 🎯 API совместимый с Binance (легкая миграция)
|
||||
- 🔐 Система безопасности API кошелька
|
||||
- 💰 Более низкие комиссии за торговлю
|
||||
- 🌐 Поддержка нескольких цепей (ETH, BSC, Polygon)
|
||||
- 🌍 Не нужна KYC
|
||||
|
||||
**Шаг 1**: Создайте Aster API кошелек
|
||||
|
||||
1. Посетите [Aster API Wallet](https://www.asterdex.com/en/api-wallet)
|
||||
2. Подключите основной кошелек (MetaMask, WalletConnect и т.д.)
|
||||
3. Нажмите "Создать API кошелек"
|
||||
4. **Сохраните эти 3 элемента немедленно:**
|
||||
- Адрес основного кошелька (User)
|
||||
- Адрес API кошелька (Signer)
|
||||
- Приватный ключ API кошелька (⚠️ показывается только один раз!)
|
||||
|
||||
**Шаг 2**: Настройте `config.json` для Aster
|
||||
|
||||
```json
|
||||
{
|
||||
"traders": [
|
||||
{
|
||||
"id": "aster_deepseek",
|
||||
"name": "Aster DeepSeek Trader",
|
||||
"ai_model": "deepseek",
|
||||
"exchange": "aster",
|
||||
|
||||
"aster_user": "0x63DD5aCC6b1aa0f563956C0e534DD30B6dcF7C4e",
|
||||
"aster_signer": "0x21cF8Ae13Bb72632562c6Fff438652Ba1a151bb0",
|
||||
"aster_private_key": "4fd0a42218f3eae43a6ce26d22544e986139a01e5b34a62db53757ffca81bae1",
|
||||
|
||||
"deepseek_key": "sk-xxxxxxxxxxxxx",
|
||||
"initial_balance": 1000.0,
|
||||
"scan_interval_minutes": 3
|
||||
}
|
||||
],
|
||||
"use_default_coins": true,
|
||||
"api_server_port": 8080,
|
||||
"leverage": {
|
||||
"btc_eth_leverage": 5,
|
||||
"altcoin_leverage": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Ключевые поля конфигурации:**
|
||||
- `"exchange": "aster"` - Установите биржу на Aster
|
||||
- `aster_user` - Адрес вашего основного кошелька
|
||||
- `aster_signer` - Адрес API кошелька (из Шага 1)
|
||||
- `aster_private_key` - Приватный ключ API кошелька (без префикса `0x`)
|
||||
|
||||
**⚠️ Примечания безопасности**:
|
||||
- API кошелек отдельный от основного (дополнительный уровень безопасности)
|
||||
- Никогда не делитесь приватным ключом API
|
||||
- Вы можете отозвать доступ API кошелька в любое время на [asterdex.com](https://www.asterdex.com/en/api-wallet)
|
||||
|
||||
---
|
||||
|
||||
#### ⚔️ Экспертный режим: Конкуренция нескольких трейдеров
|
||||
|
||||
Для запуска нескольких AI трейдеров, конкурирующих друг с другом:
|
||||
|
||||
+175
-1
@@ -9,7 +9,7 @@
|
||||
|
||||
---
|
||||
|
||||
Автоматизована система торгівлі ф'ючерсами Binance на базі **DeepSeek/Qwen AI**, що підтримує **змагання кількох AI-моделей у реальній торгівлі**, з повним аналізом ринку, прийняттям рішень AI, **механізмом самонавчання** та професійним веб-інтерфейсом моніторингу.
|
||||
Автоматизована система торгівлі криптовалютними ф'ючерсами на базі **DeepSeek/Qwen AI**, що підтримує **Binance, Hyperliquid та Aster DEX біржі**, **змагання кількох AI-моделей у реальній торгівлі**, з повним аналізом ринку, прийняттям рішень AI, **механізмом самонавчання** та професійним веб-інтерфейсом моніторингу.
|
||||
|
||||
> ⚠️ **Попередження про ризики**: Ця система експериментальна. Автоматична торгівля з AI несе значні ризики. Наполегливо рекомендується використовувати лише для навчання/досліджень або тестування з невеликими сумами!
|
||||
|
||||
@@ -21,6 +21,75 @@
|
||||
|
||||
---
|
||||
|
||||
## 🆕 Останні оновлення
|
||||
|
||||
### 🚀 Підтримка кількох бірж!
|
||||
|
||||
NOFX тепер підтримує **три основні біржі**: Binance, Hyperliquid та Aster DEX!
|
||||
|
||||
#### **Біржа Hyperliquid**
|
||||
|
||||
Високопродуктивна децентралізована біржа безстрокових ф'ючерсів!
|
||||
|
||||
**Ключові особливості:**
|
||||
- ✅ Повна підтримка торгівлі (лонг/шорт, плече, стоп-лосс/тейк-профіт)
|
||||
- ✅ Автоматична обробка точності (розмір та ціна ордера)
|
||||
- ✅ Єдиний інтерфейс трейдера (безшовне перемикання бірж)
|
||||
- ✅ Підтримка мейннету та тестнету
|
||||
- ✅ Не потрібні API ключі - тільки приватний ключ Ethereum
|
||||
|
||||
**Чому Hyperliquid?**
|
||||
- 🔥 Нижчі комісії ніж на централізованих біржах
|
||||
- 🔒 Без зберігання - ви контролюєте свої кошти
|
||||
- ⚡ Швидке виконання з розрахунком на ланцюзі
|
||||
- 🌍 Не потрібна KYC
|
||||
|
||||
**Швидкий старт:**
|
||||
1. Отримайте приватний ключ MetaMask (видаліть префікс `0x`)
|
||||
2. Встановіть `"exchange": "hyperliquid"` в config.json
|
||||
3. Додайте `"hyperliquid_private_key": "your_key"`
|
||||
4. Почніть торгувати!
|
||||
|
||||
Див. [Посібник з конфігурації](#-альтернатива-використання-біржі-hyperliquid).
|
||||
|
||||
#### **Біржа Aster DEX** (НОВЕ! v2.0.2)
|
||||
|
||||
Децентралізована біржа безстрокових ф'ючерсів, сумісна з Binance!
|
||||
|
||||
**Ключові особливості:**
|
||||
- ✅ API в стилі Binance (легка міграція з Binance)
|
||||
- ✅ Web3 автентифікація гаманця (безпечно та децентралізовано)
|
||||
- ✅ Повна підтримка торгівлі з автоматичною обробкою точності
|
||||
- ✅ Нижчі комісії за торгівлю ніж CEX
|
||||
- ✅ Сумісність з EVM (Ethereum, BSC, Polygon тощо)
|
||||
|
||||
**Чому Aster?**
|
||||
- 🎯 **API сумісний з Binance** - потрібні мінімальні зміни коду
|
||||
- 🔐 **Система API гаманця** - окремий торговий гаманець для безпеки
|
||||
- 💰 **Конкурентні комісії** - нижче ніж більшість централізованих бірж
|
||||
- 🌐 **Підтримка кількох ланцюгів** - торгуйте на вашому улюбленому EVM ланцюзі
|
||||
|
||||
**Швидкий старт:**
|
||||
1. Відвідайте [Aster API Wallet](https://www.asterdex.com/en/api-wallet)
|
||||
2. Підключіть основний гаманець і створіть API гаманець
|
||||
3. Скопіюйте адресу API Signer та приватний ключ
|
||||
4. Встановіть `"exchange": "aster"` в config.json
|
||||
5. Додайте `"aster_user"`, `"aster_signer"` та `"aster_private_key"`
|
||||
|
||||
---
|
||||
|
||||
## 📸 Скриншоти
|
||||
|
||||
### 🏆 Режим змагання - Битва AI в реальному часі
|
||||

|
||||
*Лідерборд з кількома AI та графіки порівняння продуктивності в реальному часі показують битву Qwen проти DeepSeek*
|
||||
|
||||
### 📊 Деталі трейдера - Повна торгова панель
|
||||

|
||||
*Професійний торговий інтерфейс з кривими капіталу, живими позиціями та логами рішень AI з розкриваємими вхідними промптами та ланцюгом міркувань*
|
||||
|
||||
---
|
||||
|
||||
## ✨ Основні можливості
|
||||
|
||||
### 🏆 Режим змагання кількох AI
|
||||
@@ -308,6 +377,111 @@ cp config.json.example config.json
|
||||
|
||||
---
|
||||
|
||||
#### 🔷 Альтернатива: Використання біржі Hyperliquid
|
||||
|
||||
**NOFX також підтримує Hyperliquid** - децентралізовану біржу безстрокових ф'ючерсів. Щоб використовувати Hyperliquid замість Binance:
|
||||
|
||||
**Крок 1**: Отримайте приватний ключ Ethereum (для автентифікації Hyperliquid)
|
||||
|
||||
1. Відкрийте **MetaMask** (або будь-який Ethereum гаманець)
|
||||
2. Експортуйте приватний ключ
|
||||
3. **Видаліть префікс `0x`** з ключа
|
||||
4. Поповніть гаманець на [Hyperliquid](https://hyperliquid.xyz)
|
||||
|
||||
**Крок 2**: Налаштуйте `config.json` для Hyperliquid
|
||||
|
||||
```json
|
||||
{
|
||||
"traders": [
|
||||
{
|
||||
"id": "hyperliquid_trader",
|
||||
"name": "My Hyperliquid Trader",
|
||||
"ai_model": "deepseek",
|
||||
"exchange": "hyperliquid",
|
||||
"hyperliquid_private_key": "your_private_key_without_0x",
|
||||
"hyperliquid_testnet": false,
|
||||
"deepseek_key": "sk-xxxxxxxxxxxxx",
|
||||
"initial_balance": 1000.0,
|
||||
"scan_interval_minutes": 3
|
||||
}
|
||||
],
|
||||
"use_default_coins": true,
|
||||
"api_server_port": 8080
|
||||
}
|
||||
```
|
||||
|
||||
**Ключові відмінності від конфігурації Binance:**
|
||||
- Замініть `binance_api_key` + `binance_secret_key` на `hyperliquid_private_key`
|
||||
- Додайте поле `"exchange": "hyperliquid"`
|
||||
- Встановіть `hyperliquid_testnet: false` для мейннету (або `true` для тестнету)
|
||||
|
||||
**⚠️ Попередження безпеки**: Ніколи не діліться приватним ключем! Використовуйте окремий гаманець для торгівлі, а не основний.
|
||||
|
||||
---
|
||||
|
||||
#### 🔶 Альтернатива: Використання біржі Aster DEX
|
||||
|
||||
**NOFX також підтримує Aster DEX** - децентралізовану біржу безстрокових ф'ючерсів, сумісну з Binance!
|
||||
|
||||
**Чому обрати Aster?**
|
||||
- 🎯 API сумісний з Binance (легка міграція)
|
||||
- 🔐 Система безпеки API гаманця
|
||||
- 💰 Нижчі комісії за торгівлю
|
||||
- 🌐 Підтримка кількох ланцюгів (ETH, BSC, Polygon)
|
||||
- 🌍 Не потрібна KYC
|
||||
|
||||
**Крок 1**: Створіть Aster API гаманець
|
||||
|
||||
1. Відвідайте [Aster API Wallet](https://www.asterdex.com/en/api-wallet)
|
||||
2. Підключіть основний гаманець (MetaMask, WalletConnect тощо)
|
||||
3. Натисніть "Створити API гаманець"
|
||||
4. **Збережіть ці 3 елементи негайно:**
|
||||
- Адреса основного гаманця (User)
|
||||
- Адреса API гаманця (Signer)
|
||||
- Приватний ключ API гаманця (⚠️ показується лише один раз!)
|
||||
|
||||
**Крок 2**: Налаштуйте `config.json` для Aster
|
||||
|
||||
```json
|
||||
{
|
||||
"traders": [
|
||||
{
|
||||
"id": "aster_deepseek",
|
||||
"name": "Aster DeepSeek Trader",
|
||||
"ai_model": "deepseek",
|
||||
"exchange": "aster",
|
||||
|
||||
"aster_user": "0x63DD5aCC6b1aa0f563956C0e534DD30B6dcF7C4e",
|
||||
"aster_signer": "0x21cF8Ae13Bb72632562c6Fff438652Ba1a151bb0",
|
||||
"aster_private_key": "4fd0a42218f3eae43a6ce26d22544e986139a01e5b34a62db53757ffca81bae1",
|
||||
|
||||
"deepseek_key": "sk-xxxxxxxxxxxxx",
|
||||
"initial_balance": 1000.0,
|
||||
"scan_interval_minutes": 3
|
||||
}
|
||||
],
|
||||
"use_default_coins": true,
|
||||
"api_server_port": 8080,
|
||||
"leverage": {
|
||||
"btc_eth_leverage": 5,
|
||||
"altcoin_leverage": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Ключові поля конфігурації:**
|
||||
- `"exchange": "aster"` - Встановіть біржу на Aster
|
||||
- `aster_user` - Адреса вашого основного гаманця
|
||||
- `aster_signer` - Адреса API гаманця (з Кроку 1)
|
||||
- `aster_private_key` - Приватний ключ API гаманця (без префікса `0x`)
|
||||
|
||||
**⚠️ Примітки безпеки**:
|
||||
- API гаманець окремий від основного (додатковий рівень безпеки)
|
||||
- Ніколи не діліться приватним ключем API
|
||||
- Ви можете відкликати доступ API гаманця в будь-який час на [asterdex.com](https://www.asterdex.com/en/api-wallet)
|
||||
|
||||
---
|
||||
|
||||
#### ⚔️ Експертний режим: Змагання кількох трейдерів
|
||||
|
||||
Для запуску кількох AI трейдерів, що змагаються один з одним:
|
||||
|
||||
+175
-1
@@ -9,7 +9,7 @@
|
||||
|
||||
---
|
||||
|
||||
一个基于 **DeepSeek/Qwen AI** 的币安合约自动交易系统,支持**多AI模型实盘竞赛**,具备完整的市场分析、AI决策、**自我学习机制**和专业的Web监控界面。
|
||||
一个基于 **DeepSeek/Qwen AI** 的加密货币期货自动交易系统,支持 **Binance、Hyperliquid和Aster DEX交易所**,**多AI模型实盘竞赛**,具备完整的市场分析、AI决策、**自我学习机制**和专业的Web监控界面。
|
||||
|
||||
> ⚠️ **风险提示**:本系统为实验性项目,AI自动交易存在重大风险,强烈建议仅用于学习研究或小额资金测试!
|
||||
|
||||
@@ -21,6 +21,75 @@
|
||||
|
||||
---
|
||||
|
||||
## 🆕 最新更新
|
||||
|
||||
### 🚀 多交易所支持!
|
||||
|
||||
NOFX现已支持**三大交易所**:Binance、Hyperliquid和Aster DEX!
|
||||
|
||||
#### **Hyperliquid交易所**
|
||||
|
||||
高性能的去中心化永续期货交易所!
|
||||
|
||||
**核心特性:**
|
||||
- ✅ 完整交易支持(做多/做空、杠杆、止损/止盈)
|
||||
- ✅ 自动精度处理(订单数量和价格)
|
||||
- ✅ 统一trader接口(无缝切换交易所)
|
||||
- ✅ 支持主网和测试网
|
||||
- ✅ 无需API密钥 - 只需以太坊私钥
|
||||
|
||||
**为什么选择Hyperliquid?**
|
||||
- 🔥 比中心化交易所手续费更低
|
||||
- 🔒 非托管 - 你掌控自己的资金
|
||||
- ⚡ 快速执行与链上结算
|
||||
- 🌍 无需KYC
|
||||
|
||||
**快速开始:**
|
||||
1. 获取你的MetaMask私钥(去掉`0x`前缀)
|
||||
2. 在config.json中设置`"exchange": "hyperliquid"`
|
||||
3. 添加`"hyperliquid_private_key": "your_key"`
|
||||
4. 开始交易!
|
||||
|
||||
详见[配置指南](#-备选使用hyperliquid交易所)。
|
||||
|
||||
#### **Aster DEX交易所**(新!v2.0.2)
|
||||
|
||||
兼容Binance的去中心化永续期货交易所!
|
||||
|
||||
**核心特性:**
|
||||
- ✅ Binance风格API(从Binance轻松迁移)
|
||||
- ✅ Web3钱包认证(安全且去中心化)
|
||||
- ✅ 完整交易支持,自动精度处理
|
||||
- ✅ 比中心化交易所手续费更低
|
||||
- ✅ 兼容EVM(以太坊、BSC、Polygon等)
|
||||
|
||||
**为什么选择Aster?**
|
||||
- 🎯 **兼容Binance API** - 需要最少的代码修改
|
||||
- 🔐 **API钱包系统** - 独立交易钱包提升安全性
|
||||
- 💰 **有竞争力的手续费** - 比大多数中心化交易所更低
|
||||
- 🌐 **多链支持** - 在你喜欢的EVM链上交易
|
||||
|
||||
**快速开始:**
|
||||
1. 访问[Aster API钱包](https://www.asterdex.com/en/api-wallet)
|
||||
2. 连接你的主钱包并创建API钱包
|
||||
3. 复制API Signer地址和私钥
|
||||
4. 在config.json中设置`"exchange": "aster"`
|
||||
5. 添加`"aster_user"`、`"aster_signer"`和`"aster_private_key"`
|
||||
|
||||
---
|
||||
|
||||
## 📸 系统截图
|
||||
|
||||
### 🏆 竞赛模式 - AI实时对战
|
||||

|
||||
*多AI排行榜和实时性能对比图表,展示Qwen vs DeepSeek实时交易对战*
|
||||
|
||||
### 📊 交易详情 - 完整交易仪表盘
|
||||

|
||||
*专业交易界面,包含权益曲线、实时持仓、AI决策日志,支持展开查看输入提示词和AI思维链推理过程*
|
||||
|
||||
---
|
||||
|
||||
## ✨ 核心特性
|
||||
|
||||
### 🏆 多AI竞赛模式
|
||||
@@ -371,6 +440,111 @@ cp config.json.example config.json
|
||||
|
||||
---
|
||||
|
||||
#### 🔷 备选:使用Hyperliquid交易所
|
||||
|
||||
**NOFX也支持Hyperliquid** - 去中心化永续期货交易所。使用Hyperliquid而非Binance:
|
||||
|
||||
**步骤1**:获取以太坊私钥(用于Hyperliquid身份验证)
|
||||
|
||||
1. 打开**MetaMask**(或任何以太坊钱包)
|
||||
2. 导出你的私钥
|
||||
3. **去掉`0x`前缀**
|
||||
4. 在[Hyperliquid](https://hyperliquid.xyz)上为钱包充值
|
||||
|
||||
**步骤2**:为Hyperliquid配置`config.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"traders": [
|
||||
{
|
||||
"id": "hyperliquid_trader",
|
||||
"name": "My Hyperliquid Trader",
|
||||
"ai_model": "deepseek",
|
||||
"exchange": "hyperliquid",
|
||||
"hyperliquid_private_key": "your_private_key_without_0x",
|
||||
"hyperliquid_testnet": false,
|
||||
"deepseek_key": "sk-xxxxxxxxxxxxx",
|
||||
"initial_balance": 1000.0,
|
||||
"scan_interval_minutes": 3
|
||||
}
|
||||
],
|
||||
"use_default_coins": true,
|
||||
"api_server_port": 8080
|
||||
}
|
||||
```
|
||||
|
||||
**与Binance配置的关键区别:**
|
||||
- 用`hyperliquid_private_key`替换`binance_api_key` + `binance_secret_key`
|
||||
- 添加`"exchange": "hyperliquid"`字段
|
||||
- 设置`hyperliquid_testnet: false`用于主网(或`true`用于测试网)
|
||||
|
||||
**⚠️ 安全警告**:切勿分享你的私钥!使用专门的钱包进行交易,而非主钱包。
|
||||
|
||||
---
|
||||
|
||||
#### 🔶 备选:使用Aster DEX交易所
|
||||
|
||||
**NOFX也支持Aster DEX** - 兼容Binance的去中心化永续期货交易所!
|
||||
|
||||
**为什么选择Aster?**
|
||||
- 🎯 兼容Binance API(轻松迁移)
|
||||
- 🔐 API钱包安全系统
|
||||
- 💰 更低的交易手续费
|
||||
- 🌐 多链支持(ETH、BSC、Polygon)
|
||||
- 🌍 无需KYC
|
||||
|
||||
**步骤1**:创建Aster API钱包
|
||||
|
||||
1. 访问[Aster API钱包](https://www.asterdex.com/en/api-wallet)
|
||||
2. 连接你的主钱包(MetaMask、WalletConnect等)
|
||||
3. 点击"创建API钱包"
|
||||
4. **立即保存这3项:**
|
||||
- 主钱包地址(User)
|
||||
- API钱包地址(Signer)
|
||||
- API钱包私钥(⚠️ 仅显示一次!)
|
||||
|
||||
**步骤2**:为Aster配置`config.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"traders": [
|
||||
{
|
||||
"id": "aster_deepseek",
|
||||
"name": "Aster DeepSeek Trader",
|
||||
"ai_model": "deepseek",
|
||||
"exchange": "aster",
|
||||
|
||||
"aster_user": "0x63DD5aCC6b1aa0f563956C0e534DD30B6dcF7C4e",
|
||||
"aster_signer": "0x21cF8Ae13Bb72632562c6Fff438652Ba1a151bb0",
|
||||
"aster_private_key": "4fd0a42218f3eae43a6ce26d22544e986139a01e5b34a62db53757ffca81bae1",
|
||||
|
||||
"deepseek_key": "sk-xxxxxxxxxxxxx",
|
||||
"initial_balance": 1000.0,
|
||||
"scan_interval_minutes": 3
|
||||
}
|
||||
],
|
||||
"use_default_coins": true,
|
||||
"api_server_port": 8080,
|
||||
"leverage": {
|
||||
"btc_eth_leverage": 5,
|
||||
"altcoin_leverage": 5
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关键配置字段:**
|
||||
- `"exchange": "aster"` - 设置交易所为Aster
|
||||
- `aster_user` - 你的主钱包地址
|
||||
- `aster_signer` - API钱包地址(来自步骤1)
|
||||
- `aster_private_key` - API钱包私钥(去掉`0x`前缀)
|
||||
|
||||
**⚠️ 安全提示**:
|
||||
- API钱包与主钱包分离(额外的安全层)
|
||||
- 切勿分享API私钥
|
||||
- 你可以随时在[asterdex.com](https://www.asterdex.com/en/api-wallet)撤销API钱包访问
|
||||
|
||||
---
|
||||
|
||||
#### ⚔️ 专家模式:多Trader竞赛
|
||||
|
||||
用于运行多个AI trader相互竞争:
|
||||
|
||||
@@ -34,6 +34,25 @@
|
||||
"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": {
|
||||
@@ -41,6 +60,16 @@
|
||||
"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,
|
||||
|
||||
+26
-2
@@ -24,6 +24,11 @@ type TraderConfig struct {
|
||||
HyperliquidPrivateKey string `json:"hyperliquid_private_key,omitempty"`
|
||||
HyperliquidTestnet bool `json:"hyperliquid_testnet,omitempty"`
|
||||
|
||||
// Aster配置
|
||||
AsterUser string `json:"aster_user,omitempty"` // Aster主钱包地址
|
||||
AsterSigner string `json:"aster_signer,omitempty"` // Aster API钱包地址
|
||||
AsterPrivateKey string `json:"aster_private_key,omitempty"` // Aster API钱包私钥
|
||||
|
||||
// AI配置
|
||||
QwenKey string `json:"qwen_key,omitempty"`
|
||||
DeepSeekKey string `json:"deepseek_key,omitempty"`
|
||||
@@ -47,6 +52,7 @@ type LeverageConfig struct {
|
||||
type Config struct {
|
||||
Traders []TraderConfig `json:"traders"`
|
||||
UseDefaultCoins bool `json:"use_default_coins"` // 是否使用默认主流币种列表
|
||||
DefaultCoins []string `json:"default_coins"` // 默认主流币种池
|
||||
CoinPoolAPIURL string `json:"coin_pool_api_url"`
|
||||
OITopAPIURL string `json:"oi_top_api_url"`
|
||||
APIServerPort int `json:"api_server_port"`
|
||||
@@ -73,6 +79,20 @@ func LoadConfig(filename string) (*Config, error) {
|
||||
config.UseDefaultCoins = true
|
||||
}
|
||||
|
||||
// 设置默认币种池
|
||||
if len(config.DefaultCoins) == 0 {
|
||||
config.DefaultCoins = []string{
|
||||
"BTCUSDT",
|
||||
"ETHUSDT",
|
||||
"SOLUSDT",
|
||||
"BNBUSDT",
|
||||
"XRPUSDT",
|
||||
"DOGEUSDT",
|
||||
"ADAUSDT",
|
||||
"HYPEUSDT",
|
||||
}
|
||||
}
|
||||
|
||||
// 验证配置
|
||||
if err := config.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("配置验证失败: %w", err)
|
||||
@@ -108,8 +128,8 @@ func (c *Config) Validate() error {
|
||||
if trader.Exchange == "" {
|
||||
trader.Exchange = "binance" // 默认使用币安
|
||||
}
|
||||
if trader.Exchange != "binance" && trader.Exchange != "hyperliquid" {
|
||||
return fmt.Errorf("trader[%d]: exchange必须是 'binance' 或 'hyperliquid'", i)
|
||||
if trader.Exchange != "binance" && trader.Exchange != "hyperliquid" && trader.Exchange != "aster" {
|
||||
return fmt.Errorf("trader[%d]: exchange必须是 'binance', 'hyperliquid' 或 'aster'", i)
|
||||
}
|
||||
|
||||
// 根据平台验证对应的密钥
|
||||
@@ -121,6 +141,10 @@ func (c *Config) Validate() error {
|
||||
if trader.HyperliquidPrivateKey == "" {
|
||||
return fmt.Errorf("trader[%d]: 使用Hyperliquid时必须配置hyperliquid_private_key", i)
|
||||
}
|
||||
} else if trader.Exchange == "aster" {
|
||||
if trader.AsterUser == "" || trader.AsterSigner == "" || trader.AsterPrivateKey == "" {
|
||||
return fmt.Errorf("trader[%d]: 使用Aster时必须配置aster_user, aster_signer和aster_private_key", i)
|
||||
}
|
||||
}
|
||||
|
||||
if trader.AIModel == "qwen" && trader.QwenKey == "" {
|
||||
|
||||
+5
-5
@@ -97,7 +97,7 @@ func GetFullDecision(ctx *Context) (*FullDecision, error) {
|
||||
}
|
||||
|
||||
// 2. 构建 System Prompt(固定规则)和 User Prompt(动态数据)
|
||||
systemPrompt := buildSystemPrompt(ctx.Account.TotalEquity)
|
||||
systemPrompt := buildSystemPrompt(ctx.Account.TotalEquity, ctx.BTCETHLeverage, ctx.AltcoinLeverage)
|
||||
userPrompt := buildUserPrompt(ctx)
|
||||
|
||||
// 3. 调用AI API(使用 system + user prompt)
|
||||
@@ -200,7 +200,7 @@ func calculateMaxCandidates(ctx *Context) int {
|
||||
}
|
||||
|
||||
// buildSystemPrompt 构建 System Prompt(固定规则,可缓存)
|
||||
func buildSystemPrompt(accountEquity float64) string {
|
||||
func buildSystemPrompt(accountEquity float64, btcEthLeverage, altcoinLeverage int) string {
|
||||
var sb strings.Builder
|
||||
|
||||
// === 核心使命 ===
|
||||
@@ -222,8 +222,8 @@ func buildSystemPrompt(accountEquity float64) string {
|
||||
sb.WriteString("# ⚖️ 硬约束(风险控制)\n\n")
|
||||
sb.WriteString("1. **风险回报比**: 必须 ≥ 1:3(冒1%风险,赚3%+收益)\n")
|
||||
sb.WriteString("2. **最多持仓**: 3个币种(质量>数量)\n")
|
||||
sb.WriteString(fmt.Sprintf("3. **单币仓位**: 山寨%.0f-%.0f U(20x杠杆) | BTC/ETH %.0f-%.0f U(50x杠杆)\n",
|
||||
accountEquity*0.8, accountEquity*1.5, accountEquity*5, accountEquity*10))
|
||||
sb.WriteString(fmt.Sprintf("3. **单币仓位**: 山寨%.0f-%.0f U(%dx杠杆) | BTC/ETH %.0f-%.0f U(%dx杠杆)\n",
|
||||
accountEquity*0.8, accountEquity*1.5, altcoinLeverage, accountEquity*5, accountEquity*10, btcEthLeverage))
|
||||
sb.WriteString("4. **保证金**: 总使用率 ≤ 90%\n\n")
|
||||
|
||||
// === 做空激励 ===
|
||||
@@ -296,7 +296,7 @@ func buildSystemPrompt(accountEquity float64) string {
|
||||
sb.WriteString("简洁分析你的思考过程\n\n")
|
||||
sb.WriteString("**第二步: JSON决策数组**\n\n")
|
||||
sb.WriteString("```json\n[\n")
|
||||
sb.WriteString(fmt.Sprintf(" {\"symbol\": \"BTCUSDT\", \"action\": \"open_short\", \"leverage\": 50, \"position_size_usd\": %.0f, \"stop_loss\": 97000, \"take_profit\": 91000, \"confidence\": 85, \"risk_usd\": 300, \"reasoning\": \"下跌趋势+MACD死叉\"},\n", accountEquity*5))
|
||||
sb.WriteString(fmt.Sprintf(" {\"symbol\": \"BTCUSDT\", \"action\": \"open_short\", \"leverage\": %d, \"position_size_usd\": %.0f, \"stop_loss\": 97000, \"take_profit\": 91000, \"confidence\": 85, \"risk_usd\": 300, \"reasoning\": \"下跌趋势+MACD死叉\"},\n", btcEthLeverage, accountEquity*5))
|
||||
sb.WriteString(" {\"symbol\": \"ETHUSDT\", \"action\": \"close_long\", \"reasoning\": \"止盈离场\"}\n")
|
||||
sb.WriteString("]\n```\n\n")
|
||||
sb.WriteString("**字段说明**:\n")
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ services:
|
||||
networks:
|
||||
- nofx-network
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"]
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
@@ -34,10 +34,13 @@ func main() {
|
||||
log.Printf("✓ 配置加载成功,共%d个trader参赛", len(cfg.Traders))
|
||||
fmt.Println()
|
||||
|
||||
// 设置默认主流币种列表
|
||||
pool.SetDefaultCoins(cfg.DefaultCoins)
|
||||
|
||||
// 设置是否使用默认主流币种
|
||||
pool.SetUseDefaultCoins(cfg.UseDefaultCoins)
|
||||
if cfg.UseDefaultCoins {
|
||||
log.Printf("✓ 已启用默认主流币种列表(BTC、ETH、SOL、BNB、XRP、DOGE、ADA、HYPE)")
|
||||
log.Printf("✓ 已启用默认主流币种列表(共%d个币种): %v", len(cfg.DefaultCoins), cfg.DefaultCoins)
|
||||
}
|
||||
|
||||
// 设置币种池API URL
|
||||
|
||||
@@ -41,6 +41,9 @@ func (tm *TraderManager) AddTrader(cfg config.TraderConfig, coinPoolURL string,
|
||||
BinanceSecretKey: cfg.BinanceSecretKey,
|
||||
HyperliquidPrivateKey: cfg.HyperliquidPrivateKey,
|
||||
HyperliquidTestnet: cfg.HyperliquidTestnet,
|
||||
AsterUser: cfg.AsterUser,
|
||||
AsterSigner: cfg.AsterSigner,
|
||||
AsterPrivateKey: cfg.AsterPrivateKey,
|
||||
CoinPoolAPIURL: coinPoolURL,
|
||||
UseQwen: cfg.AIModel == "qwen",
|
||||
DeepSeekKey: cfg.DeepSeekKey,
|
||||
|
||||
+19
-2
@@ -27,6 +27,7 @@ type Config struct {
|
||||
BaseURL string
|
||||
Model string
|
||||
Timeout time.Duration
|
||||
UseFullURL bool // 是否使用完整URL(不添加/chat/completions)
|
||||
}
|
||||
|
||||
// 默认配置
|
||||
@@ -58,7 +59,16 @@ func SetQwenAPIKey(apiKey, secretKey string) {
|
||||
func SetCustomAPI(apiURL, apiKey, modelName string) {
|
||||
defaultConfig.Provider = ProviderCustom
|
||||
defaultConfig.APIKey = apiKey
|
||||
defaultConfig.BaseURL = apiURL
|
||||
|
||||
// 检查URL是否以#结尾,如果是则使用完整URL(不添加/chat/completions)
|
||||
if strings.HasSuffix(apiURL, "#") {
|
||||
defaultConfig.BaseURL = strings.TrimSuffix(apiURL, "#")
|
||||
defaultConfig.UseFullURL = true
|
||||
} else {
|
||||
defaultConfig.BaseURL = apiURL
|
||||
defaultConfig.UseFullURL = false
|
||||
}
|
||||
|
||||
defaultConfig.Model = modelName
|
||||
defaultConfig.Timeout = 120 * time.Second
|
||||
}
|
||||
@@ -147,7 +157,14 @@ func callOnce(systemPrompt, userPrompt string) (string, error) {
|
||||
}
|
||||
|
||||
// 创建HTTP请求
|
||||
url := fmt.Sprintf("%s/chat/completions", defaultConfig.BaseURL)
|
||||
var url string
|
||||
if defaultConfig.UseFullURL {
|
||||
// 使用完整URL,不添加/chat/completions
|
||||
url = defaultConfig.BaseURL
|
||||
} else {
|
||||
// 默认行为:添加/chat/completions
|
||||
url = fmt.Sprintf("%s/chat/completions", defaultConfig.BaseURL)
|
||||
}
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建请求失败: %w", err)
|
||||
|
||||
+9
-1
@@ -12,7 +12,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// defaultMainstreamCoins 默认主流币种池(当AI500和OI Top都失败时使用)
|
||||
// defaultMainstreamCoins 默认主流币种池(从配置文件读取)
|
||||
var defaultMainstreamCoins = []string{
|
||||
"BTCUSDT",
|
||||
"ETHUSDT",
|
||||
@@ -83,6 +83,14 @@ func SetUseDefaultCoins(useDefault bool) {
|
||||
coinPoolConfig.UseDefaultCoins = useDefault
|
||||
}
|
||||
|
||||
// SetDefaultCoins 设置默认主流币种列表
|
||||
func SetDefaultCoins(coins []string) {
|
||||
if len(coins) > 0 {
|
||||
defaultMainstreamCoins = coins
|
||||
log.Printf("✓ 已设置默认币种池(共%d个币种): %v", len(coins), coins)
|
||||
}
|
||||
}
|
||||
|
||||
// GetCoinPool 获取币种池列表(带重试和缓存机制)
|
||||
func GetCoinPool() ([]CoinInfo, error) {
|
||||
// 优先检查是否启用默认币种列表
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 380 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 455 KiB |
@@ -0,0 +1,937 @@
|
||||
package trader
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"math/big"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
)
|
||||
|
||||
// AsterTrader Aster交易平台实现
|
||||
type AsterTrader struct {
|
||||
ctx context.Context
|
||||
user string // 主钱包地址 (ERC20)
|
||||
signer string // API钱包地址
|
||||
privateKey *ecdsa.PrivateKey // API钱包私钥
|
||||
client *http.Client
|
||||
baseURL string
|
||||
|
||||
// 缓存交易对精度信息
|
||||
symbolPrecision map[string]SymbolPrecision
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// SymbolPrecision 交易对精度信息
|
||||
type SymbolPrecision struct {
|
||||
PricePrecision int
|
||||
QuantityPrecision int
|
||||
TickSize float64 // 价格步进值
|
||||
StepSize float64 // 数量步进值
|
||||
}
|
||||
|
||||
// NewAsterTrader 创建Aster交易器
|
||||
// user: 主钱包地址 (登录地址)
|
||||
// signer: API钱包地址 (从 https://www.asterdex.com/en/api-wallet 获取)
|
||||
// privateKey: API钱包私钥 (从 https://www.asterdex.com/en/api-wallet 获取)
|
||||
func NewAsterTrader(user, signer, privateKeyHex string) (*AsterTrader, error) {
|
||||
// 解析私钥
|
||||
privKey, err := crypto.HexToECDSA(strings.TrimPrefix(privateKeyHex, "0x"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("解析私钥失败: %w", err)
|
||||
}
|
||||
|
||||
return &AsterTrader{
|
||||
ctx: context.Background(),
|
||||
user: user,
|
||||
signer: signer,
|
||||
privateKey: privKey,
|
||||
symbolPrecision: make(map[string]SymbolPrecision),
|
||||
client: &http.Client{
|
||||
Timeout: 30 * time.Second, // 增加到30秒
|
||||
Transport: &http.Transport{
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ResponseHeaderTimeout: 10 * time.Second,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
},
|
||||
},
|
||||
baseURL: "https://fapi.asterdex.com",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// genNonce 生成微秒时间戳
|
||||
func (t *AsterTrader) genNonce() uint64 {
|
||||
return uint64(time.Now().UnixMicro())
|
||||
}
|
||||
|
||||
// getPrecision 获取交易对精度信息
|
||||
func (t *AsterTrader) getPrecision(symbol string) (SymbolPrecision, error) {
|
||||
t.mu.RLock()
|
||||
if prec, ok := t.symbolPrecision[symbol]; ok {
|
||||
t.mu.RUnlock()
|
||||
return prec, nil
|
||||
}
|
||||
t.mu.RUnlock()
|
||||
|
||||
// 获取交易所信息
|
||||
resp, err := t.client.Get(t.baseURL + "/fapi/v3/exchangeInfo")
|
||||
if err != nil {
|
||||
return SymbolPrecision{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
var info struct {
|
||||
Symbols []struct {
|
||||
Symbol string `json:"symbol"`
|
||||
PricePrecision int `json:"pricePrecision"`
|
||||
QuantityPrecision int `json:"quantityPrecision"`
|
||||
Filters []map[string]interface{} `json:"filters"`
|
||||
} `json:"symbols"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &info); err != nil {
|
||||
return SymbolPrecision{}, err
|
||||
}
|
||||
|
||||
// 缓存所有交易对的精度
|
||||
t.mu.Lock()
|
||||
for _, s := range info.Symbols {
|
||||
prec := SymbolPrecision{
|
||||
PricePrecision: s.PricePrecision,
|
||||
QuantityPrecision: s.QuantityPrecision,
|
||||
}
|
||||
|
||||
// 解析filters获取tickSize和stepSize
|
||||
for _, filter := range s.Filters {
|
||||
filterType, _ := filter["filterType"].(string)
|
||||
switch filterType {
|
||||
case "PRICE_FILTER":
|
||||
if tickSizeStr, ok := filter["tickSize"].(string); ok {
|
||||
prec.TickSize, _ = strconv.ParseFloat(tickSizeStr, 64)
|
||||
}
|
||||
case "LOT_SIZE":
|
||||
if stepSizeStr, ok := filter["stepSize"].(string); ok {
|
||||
prec.StepSize, _ = strconv.ParseFloat(stepSizeStr, 64)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t.symbolPrecision[s.Symbol] = prec
|
||||
}
|
||||
t.mu.Unlock()
|
||||
|
||||
if prec, ok := t.symbolPrecision[symbol]; ok {
|
||||
return prec, nil
|
||||
}
|
||||
|
||||
return SymbolPrecision{}, fmt.Errorf("未找到交易对 %s 的精度信息", symbol)
|
||||
}
|
||||
|
||||
// roundToTickSize 将价格/数量四舍五入到tick size/step size的整数倍
|
||||
func roundToTickSize(value float64, tickSize float64) float64 {
|
||||
if tickSize <= 0 {
|
||||
return value
|
||||
}
|
||||
// 计算有多少个tick size
|
||||
steps := value / tickSize
|
||||
// 四舍五入到最近的整数
|
||||
roundedSteps := math.Round(steps)
|
||||
// 乘回tick size
|
||||
return roundedSteps * tickSize
|
||||
}
|
||||
|
||||
// formatPrice 格式化价格到正确精度和tick size
|
||||
func (t *AsterTrader) formatPrice(symbol string, price float64) (float64, error) {
|
||||
prec, err := t.getPrecision(symbol)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 优先使用tick size,确保价格是tick size的整数倍
|
||||
if prec.TickSize > 0 {
|
||||
return roundToTickSize(price, prec.TickSize), nil
|
||||
}
|
||||
|
||||
// 如果没有tick size,则按精度四舍五入
|
||||
multiplier := math.Pow10(prec.PricePrecision)
|
||||
return math.Round(price*multiplier) / multiplier, nil
|
||||
}
|
||||
|
||||
// formatQuantity 格式化数量到正确精度和step size
|
||||
func (t *AsterTrader) formatQuantity(symbol string, quantity float64) (float64, error) {
|
||||
prec, err := t.getPrecision(symbol)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 优先使用step size,确保数量是step size的整数倍
|
||||
if prec.StepSize > 0 {
|
||||
return roundToTickSize(quantity, prec.StepSize), nil
|
||||
}
|
||||
|
||||
// 如果没有step size,则按精度四舍五入
|
||||
multiplier := math.Pow10(prec.QuantityPrecision)
|
||||
return math.Round(quantity*multiplier) / multiplier, nil
|
||||
}
|
||||
|
||||
// formatFloatWithPrecision 将浮点数格式化为指定精度的字符串(去除末尾的0)
|
||||
func (t *AsterTrader) formatFloatWithPrecision(value float64, precision int) string {
|
||||
// 使用指定精度格式化
|
||||
formatted := strconv.FormatFloat(value, 'f', precision, 64)
|
||||
|
||||
// 去除末尾的0和小数点(如果有)
|
||||
formatted = strings.TrimRight(formatted, "0")
|
||||
formatted = strings.TrimRight(formatted, ".")
|
||||
|
||||
return formatted
|
||||
}
|
||||
|
||||
// normalizeAndStringify 对参数进行规范化并序列化为JSON字符串(按key排序)
|
||||
func (t *AsterTrader) normalizeAndStringify(params map[string]interface{}) (string, error) {
|
||||
normalized, err := t.normalize(params)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
bs, err := json.Marshal(normalized)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(bs), nil
|
||||
}
|
||||
|
||||
// normalize 递归规范化参数(按key排序,所有值转为字符串)
|
||||
func (t *AsterTrader) normalize(v interface{}) (interface{}, error) {
|
||||
switch val := v.(type) {
|
||||
case map[string]interface{}:
|
||||
keys := make([]string, 0, len(val))
|
||||
for k := range val {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
newMap := make(map[string]interface{}, len(keys))
|
||||
for _, k := range keys {
|
||||
nv, err := t.normalize(val[k])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newMap[k] = nv
|
||||
}
|
||||
return newMap, nil
|
||||
case []interface{}:
|
||||
out := make([]interface{}, 0, len(val))
|
||||
for _, it := range val {
|
||||
nv, err := t.normalize(it)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, nv)
|
||||
}
|
||||
return out, nil
|
||||
case string:
|
||||
return val, nil
|
||||
case int:
|
||||
return fmt.Sprintf("%d", val), nil
|
||||
case int64:
|
||||
return fmt.Sprintf("%d", val), nil
|
||||
case float64:
|
||||
return fmt.Sprintf("%v", val), nil
|
||||
case bool:
|
||||
return fmt.Sprintf("%v", val), nil
|
||||
default:
|
||||
// 其他类型转为字符串
|
||||
return fmt.Sprintf("%v", val), nil
|
||||
}
|
||||
}
|
||||
|
||||
// sign 对请求参数进行签名
|
||||
func (t *AsterTrader) sign(params map[string]interface{}, nonce uint64) error {
|
||||
// 添加时间戳和接收窗口
|
||||
params["recvWindow"] = "50000"
|
||||
params["timestamp"] = strconv.FormatInt(time.Now().UnixNano()/int64(time.Millisecond), 10)
|
||||
|
||||
// 规范化参数为JSON字符串
|
||||
jsonStr, err := t.normalizeAndStringify(params)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ABI编码: (string, address, address, uint256)
|
||||
addrUser := common.HexToAddress(t.user)
|
||||
addrSigner := common.HexToAddress(t.signer)
|
||||
nonceBig := new(big.Int).SetUint64(nonce)
|
||||
|
||||
tString, _ := abi.NewType("string", "", nil)
|
||||
tAddress, _ := abi.NewType("address", "", nil)
|
||||
tUint256, _ := abi.NewType("uint256", "", nil)
|
||||
|
||||
arguments := abi.Arguments{
|
||||
{Type: tString},
|
||||
{Type: tAddress},
|
||||
{Type: tAddress},
|
||||
{Type: tUint256},
|
||||
}
|
||||
|
||||
packed, err := arguments.Pack(jsonStr, addrUser, addrSigner, nonceBig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ABI编码失败: %w", err)
|
||||
}
|
||||
|
||||
// Keccak256哈希
|
||||
hash := crypto.Keccak256(packed)
|
||||
|
||||
// 以太坊签名消息前缀
|
||||
prefixedMsg := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(hash), hash)
|
||||
msgHash := crypto.Keccak256Hash([]byte(prefixedMsg))
|
||||
|
||||
// ECDSA签名
|
||||
sig, err := crypto.Sign(msgHash.Bytes(), t.privateKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("签名失败: %w", err)
|
||||
}
|
||||
|
||||
// 将v从0/1转换为27/28
|
||||
if len(sig) != 65 {
|
||||
return fmt.Errorf("签名长度异常: %d", len(sig))
|
||||
}
|
||||
sig[64] += 27
|
||||
|
||||
// 添加签名参数
|
||||
params["user"] = t.user
|
||||
params["signer"] = t.signer
|
||||
params["signature"] = "0x" + hex.EncodeToString(sig)
|
||||
params["nonce"] = nonce
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// request 发送HTTP请求(带重试机制)
|
||||
func (t *AsterTrader) request(method, endpoint string, params map[string]interface{}) ([]byte, error) {
|
||||
const maxRetries = 3
|
||||
var lastErr error
|
||||
|
||||
for attempt := 1; attempt <= maxRetries; attempt++ {
|
||||
// 每次重试都生成新的nonce和签名
|
||||
nonce := t.genNonce()
|
||||
paramsCopy := make(map[string]interface{})
|
||||
for k, v := range params {
|
||||
paramsCopy[k] = v
|
||||
}
|
||||
|
||||
// 签名
|
||||
if err := t.sign(paramsCopy, nonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
body, err := t.doRequest(method, endpoint, paramsCopy)
|
||||
if err == nil {
|
||||
return body, nil
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
|
||||
// 如果是网络超时或临时错误,重试
|
||||
if strings.Contains(err.Error(), "timeout") ||
|
||||
strings.Contains(err.Error(), "connection reset") ||
|
||||
strings.Contains(err.Error(), "EOF") {
|
||||
if attempt < maxRetries {
|
||||
waitTime := time.Duration(attempt) * time.Second
|
||||
time.Sleep(waitTime)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// 其他错误(如400/401等)不重试
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("请求失败(已重试%d次): %w", maxRetries, lastErr)
|
||||
}
|
||||
|
||||
// doRequest 执行实际的HTTP请求
|
||||
func (t *AsterTrader) doRequest(method, endpoint string, params map[string]interface{}) ([]byte, error) {
|
||||
fullURL := t.baseURL + endpoint
|
||||
method = strings.ToUpper(method)
|
||||
|
||||
switch method {
|
||||
case "POST":
|
||||
// POST请求:参数放在表单body中
|
||||
form := url.Values{}
|
||||
for k, v := range params {
|
||||
form.Set(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
req, err := http.NewRequest("POST", fullURL, strings.NewReader(form.Encode()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
resp, err := t.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
return body, nil
|
||||
|
||||
case "GET", "DELETE":
|
||||
// GET/DELETE请求:参数放在querystring中
|
||||
q := url.Values{}
|
||||
for k, v := range params {
|
||||
q.Set(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
u, _ := url.Parse(fullURL)
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
req, err := http.NewRequest(method, u.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := t.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
return body, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("不支持的HTTP方法: %s", method)
|
||||
}
|
||||
}
|
||||
|
||||
// GetBalance 获取账户余额
|
||||
func (t *AsterTrader) GetBalance() (map[string]interface{}, error) {
|
||||
params := make(map[string]interface{})
|
||||
body, err := t.request("GET", "/fapi/v3/balance", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var balances []map[string]interface{}
|
||||
if err := json.Unmarshal(body, &balances); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 查找USDT余额
|
||||
totalBalance := 0.0
|
||||
availableBalance := 0.0
|
||||
crossUnPnl := 0.0
|
||||
|
||||
for _, bal := range balances {
|
||||
if asset, ok := bal["asset"].(string); ok && asset == "USDT" {
|
||||
if wb, ok := bal["balance"].(string); ok {
|
||||
totalBalance, _ = strconv.ParseFloat(wb, 64)
|
||||
}
|
||||
if avail, ok := bal["availableBalance"].(string); ok {
|
||||
availableBalance, _ = strconv.ParseFloat(avail, 64)
|
||||
}
|
||||
if unpnl, ok := bal["crossUnPnl"].(string); ok {
|
||||
crossUnPnl, _ = strconv.ParseFloat(unpnl, 64)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 返回与Binance相同的字段名,确保AutoTrader能正确解析
|
||||
return map[string]interface{}{
|
||||
"totalWalletBalance": totalBalance,
|
||||
"availableBalance": availableBalance,
|
||||
"totalUnrealizedProfit": crossUnPnl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetPositions 获取持仓信息
|
||||
func (t *AsterTrader) GetPositions() ([]map[string]interface{}, error) {
|
||||
params := make(map[string]interface{})
|
||||
body, err := t.request("GET", "/fapi/v3/positionRisk", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var positions []map[string]interface{}
|
||||
if err := json.Unmarshal(body, &positions); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := []map[string]interface{}{}
|
||||
for _, pos := range positions {
|
||||
posAmtStr, ok := pos["positionAmt"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
posAmt, _ := strconv.ParseFloat(posAmtStr, 64)
|
||||
if posAmt == 0 {
|
||||
continue // 跳过空仓位
|
||||
}
|
||||
|
||||
entryPrice, _ := strconv.ParseFloat(pos["entryPrice"].(string), 64)
|
||||
markPrice, _ := strconv.ParseFloat(pos["markPrice"].(string), 64)
|
||||
unRealizedProfit, _ := strconv.ParseFloat(pos["unRealizedProfit"].(string), 64)
|
||||
leverageVal, _ := strconv.ParseFloat(pos["leverage"].(string), 64)
|
||||
liquidationPrice, _ := strconv.ParseFloat(pos["liquidationPrice"].(string), 64)
|
||||
|
||||
// 判断方向(与Binance一致)
|
||||
side := "long"
|
||||
if posAmt < 0 {
|
||||
side = "short"
|
||||
posAmt = -posAmt
|
||||
}
|
||||
|
||||
// 返回与Binance相同的字段名
|
||||
result = append(result, map[string]interface{}{
|
||||
"symbol": pos["symbol"],
|
||||
"side": side,
|
||||
"positionAmt": posAmt,
|
||||
"entryPrice": entryPrice,
|
||||
"markPrice": markPrice,
|
||||
"unRealizedProfit": unRealizedProfit,
|
||||
"leverage": leverageVal,
|
||||
"liquidationPrice": liquidationPrice,
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// OpenLong 开多单
|
||||
func (t *AsterTrader) OpenLong(symbol string, quantity float64, leverage int) (map[string]interface{}, error) {
|
||||
// 先设置杠杆
|
||||
if err := t.SetLeverage(symbol, leverage); err != nil {
|
||||
return nil, fmt.Errorf("设置杠杆失败: %w", err)
|
||||
}
|
||||
|
||||
// 获取当前价格
|
||||
price, err := t.GetMarketPrice(symbol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 使用限价单模拟市价单(价格设置得稍高一些以确保成交)
|
||||
limitPrice := price * 1.01
|
||||
|
||||
// 格式化价格和数量到正确精度
|
||||
formattedPrice, err := t.formatPrice(symbol, limitPrice)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
formattedQty, err := t.formatQuantity(symbol, quantity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取精度信息
|
||||
prec, err := t.getPrecision(symbol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为字符串,使用正确的精度格式
|
||||
priceStr := t.formatFloatWithPrecision(formattedPrice, prec.PricePrecision)
|
||||
qtyStr := t.formatFloatWithPrecision(formattedQty, prec.QuantityPrecision)
|
||||
|
||||
log.Printf(" 📏 精度处理: 价格 %.8f -> %s (精度=%d), 数量 %.8f -> %s (精度=%d)",
|
||||
limitPrice, priceStr, prec.PricePrecision, quantity, qtyStr, prec.QuantityPrecision)
|
||||
|
||||
params := map[string]interface{}{
|
||||
"symbol": symbol,
|
||||
"positionSide": "BOTH",
|
||||
"type": "LIMIT",
|
||||
"side": "BUY",
|
||||
"timeInForce": "GTC",
|
||||
"quantity": qtyStr,
|
||||
"price": priceStr,
|
||||
}
|
||||
|
||||
body, err := t.request("POST", "/fapi/v3/order", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// OpenShort 开空单
|
||||
func (t *AsterTrader) OpenShort(symbol string, quantity float64, leverage int) (map[string]interface{}, error) {
|
||||
// 先设置杠杆
|
||||
if err := t.SetLeverage(symbol, leverage); err != nil {
|
||||
return nil, fmt.Errorf("设置杠杆失败: %w", err)
|
||||
}
|
||||
|
||||
// 获取当前价格
|
||||
price, err := t.GetMarketPrice(symbol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 使用限价单模拟市价单(价格设置得稍低一些以确保成交)
|
||||
limitPrice := price * 0.99
|
||||
|
||||
// 格式化价格和数量到正确精度
|
||||
formattedPrice, err := t.formatPrice(symbol, limitPrice)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
formattedQty, err := t.formatQuantity(symbol, quantity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取精度信息
|
||||
prec, err := t.getPrecision(symbol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为字符串,使用正确的精度格式
|
||||
priceStr := t.formatFloatWithPrecision(formattedPrice, prec.PricePrecision)
|
||||
qtyStr := t.formatFloatWithPrecision(formattedQty, prec.QuantityPrecision)
|
||||
|
||||
log.Printf(" 📏 精度处理: 价格 %.8f -> %s (精度=%d), 数量 %.8f -> %s (精度=%d)",
|
||||
limitPrice, priceStr, prec.PricePrecision, quantity, qtyStr, prec.QuantityPrecision)
|
||||
|
||||
params := map[string]interface{}{
|
||||
"symbol": symbol,
|
||||
"positionSide": "BOTH",
|
||||
"type": "LIMIT",
|
||||
"side": "SELL",
|
||||
"timeInForce": "GTC",
|
||||
"quantity": qtyStr,
|
||||
"price": priceStr,
|
||||
}
|
||||
|
||||
body, err := t.request("POST", "/fapi/v3/order", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CloseLong 平多单
|
||||
func (t *AsterTrader) CloseLong(symbol string, quantity float64) (map[string]interface{}, error) {
|
||||
// 如果数量为0,获取当前持仓数量
|
||||
if quantity == 0 {
|
||||
positions, err := t.GetPositions()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, pos := range positions {
|
||||
if pos["symbol"] == symbol && pos["side"] == "long" {
|
||||
quantity = pos["positionAmt"].(float64)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if quantity == 0 {
|
||||
return nil, fmt.Errorf("没有找到 %s 的多仓", symbol)
|
||||
}
|
||||
log.Printf(" 📊 获取到多仓数量: %.8f", quantity)
|
||||
}
|
||||
|
||||
price, err := t.GetMarketPrice(symbol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
limitPrice := price * 0.99
|
||||
|
||||
// 格式化价格和数量到正确精度
|
||||
formattedPrice, err := t.formatPrice(symbol, limitPrice)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
formattedQty, err := t.formatQuantity(symbol, quantity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取精度信息
|
||||
prec, err := t.getPrecision(symbol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为字符串,使用正确的精度格式
|
||||
priceStr := t.formatFloatWithPrecision(formattedPrice, prec.PricePrecision)
|
||||
qtyStr := t.formatFloatWithPrecision(formattedQty, prec.QuantityPrecision)
|
||||
|
||||
log.Printf(" 📏 精度处理: 价格 %.8f -> %s (精度=%d), 数量 %.8f -> %s (精度=%d)",
|
||||
limitPrice, priceStr, prec.PricePrecision, quantity, qtyStr, prec.QuantityPrecision)
|
||||
|
||||
params := map[string]interface{}{
|
||||
"symbol": symbol,
|
||||
"positionSide": "BOTH",
|
||||
"type": "LIMIT",
|
||||
"side": "SELL",
|
||||
"timeInForce": "GTC",
|
||||
"quantity": qtyStr,
|
||||
"price": priceStr,
|
||||
}
|
||||
|
||||
body, err := t.request("POST", "/fapi/v3/order", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("✓ 平多仓成功: %s 数量: %s", symbol, qtyStr)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CloseShort 平空单
|
||||
func (t *AsterTrader) CloseShort(symbol string, quantity float64) (map[string]interface{}, error) {
|
||||
// 如果数量为0,获取当前持仓数量
|
||||
if quantity == 0 {
|
||||
positions, err := t.GetPositions()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, pos := range positions {
|
||||
if pos["symbol"] == symbol && pos["side"] == "short" {
|
||||
// Aster的GetPositions已经将空仓数量转换为正数,直接使用
|
||||
quantity = pos["positionAmt"].(float64)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if quantity == 0 {
|
||||
return nil, fmt.Errorf("没有找到 %s 的空仓", symbol)
|
||||
}
|
||||
log.Printf(" 📊 获取到空仓数量: %.8f", quantity)
|
||||
}
|
||||
|
||||
price, err := t.GetMarketPrice(symbol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
limitPrice := price * 1.01
|
||||
|
||||
// 格式化价格和数量到正确精度
|
||||
formattedPrice, err := t.formatPrice(symbol, limitPrice)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
formattedQty, err := t.formatQuantity(symbol, quantity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取精度信息
|
||||
prec, err := t.getPrecision(symbol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为字符串,使用正确的精度格式
|
||||
priceStr := t.formatFloatWithPrecision(formattedPrice, prec.PricePrecision)
|
||||
qtyStr := t.formatFloatWithPrecision(formattedQty, prec.QuantityPrecision)
|
||||
|
||||
log.Printf(" 📏 精度处理: 价格 %.8f -> %s (精度=%d), 数量 %.8f -> %s (精度=%d)",
|
||||
limitPrice, priceStr, prec.PricePrecision, quantity, qtyStr, prec.QuantityPrecision)
|
||||
|
||||
params := map[string]interface{}{
|
||||
"symbol": symbol,
|
||||
"positionSide": "BOTH",
|
||||
"type": "LIMIT",
|
||||
"side": "BUY",
|
||||
"timeInForce": "GTC",
|
||||
"quantity": qtyStr,
|
||||
"price": priceStr,
|
||||
}
|
||||
|
||||
body, err := t.request("POST", "/fapi/v3/order", params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("✓ 平空仓成功: %s 数量: %s", symbol, qtyStr)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// SetLeverage 设置杠杆倍数
|
||||
func (t *AsterTrader) SetLeverage(symbol string, leverage int) error {
|
||||
params := map[string]interface{}{
|
||||
"symbol": symbol,
|
||||
"leverage": leverage,
|
||||
}
|
||||
|
||||
_, err := t.request("POST", "/fapi/v3/leverage", params)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetMarketPrice 获取市场价格
|
||||
func (t *AsterTrader) GetMarketPrice(symbol string) (float64, error) {
|
||||
// 使用ticker接口获取当前价格
|
||||
resp, err := t.client.Get(fmt.Sprintf("%s/fapi/v3/ticker/price?symbol=%s", t.baseURL, symbol))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return 0, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
priceStr, ok := result["price"].(string)
|
||||
if !ok {
|
||||
return 0, errors.New("无法获取价格")
|
||||
}
|
||||
|
||||
return strconv.ParseFloat(priceStr, 64)
|
||||
}
|
||||
|
||||
// SetStopLoss 设置止损
|
||||
func (t *AsterTrader) SetStopLoss(symbol string, positionSide string, quantity, stopPrice float64) error {
|
||||
side := "SELL"
|
||||
if positionSide == "SHORT" {
|
||||
side = "BUY"
|
||||
}
|
||||
|
||||
// 格式化价格和数量到正确精度
|
||||
formattedPrice, err := t.formatPrice(symbol, stopPrice)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
formattedQty, err := t.formatQuantity(symbol, quantity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 获取精度信息
|
||||
prec, err := t.getPrecision(symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 转换为字符串,使用正确的精度格式
|
||||
priceStr := t.formatFloatWithPrecision(formattedPrice, prec.PricePrecision)
|
||||
qtyStr := t.formatFloatWithPrecision(formattedQty, prec.QuantityPrecision)
|
||||
|
||||
params := map[string]interface{}{
|
||||
"symbol": symbol,
|
||||
"positionSide": "BOTH",
|
||||
"type": "STOP_MARKET",
|
||||
"side": side,
|
||||
"stopPrice": priceStr,
|
||||
"quantity": qtyStr,
|
||||
"timeInForce": "GTC",
|
||||
}
|
||||
|
||||
_, err = t.request("POST", "/fapi/v3/order", params)
|
||||
return err
|
||||
}
|
||||
|
||||
// SetTakeProfit 设置止盈
|
||||
func (t *AsterTrader) SetTakeProfit(symbol string, positionSide string, quantity, takeProfitPrice float64) error {
|
||||
side := "SELL"
|
||||
if positionSide == "SHORT" {
|
||||
side = "BUY"
|
||||
}
|
||||
|
||||
// 格式化价格和数量到正确精度
|
||||
formattedPrice, err := t.formatPrice(symbol, takeProfitPrice)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
formattedQty, err := t.formatQuantity(symbol, quantity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 获取精度信息
|
||||
prec, err := t.getPrecision(symbol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 转换为字符串,使用正确的精度格式
|
||||
priceStr := t.formatFloatWithPrecision(formattedPrice, prec.PricePrecision)
|
||||
qtyStr := t.formatFloatWithPrecision(formattedQty, prec.QuantityPrecision)
|
||||
|
||||
params := map[string]interface{}{
|
||||
"symbol": symbol,
|
||||
"positionSide": "BOTH",
|
||||
"type": "TAKE_PROFIT_MARKET",
|
||||
"side": side,
|
||||
"stopPrice": priceStr,
|
||||
"quantity": qtyStr,
|
||||
"timeInForce": "GTC",
|
||||
}
|
||||
|
||||
_, err = t.request("POST", "/fapi/v3/order", params)
|
||||
return err
|
||||
}
|
||||
|
||||
// CancelAllOrders 取消所有订单
|
||||
func (t *AsterTrader) CancelAllOrders(symbol string) error {
|
||||
params := map[string]interface{}{
|
||||
"symbol": symbol,
|
||||
}
|
||||
|
||||
_, err := t.request("DELETE", "/fapi/v3/allOpenOrders", params)
|
||||
return err
|
||||
}
|
||||
|
||||
// FormatQuantity 格式化数量(实现Trader接口)
|
||||
func (t *AsterTrader) FormatQuantity(symbol string, quantity float64) (string, error) {
|
||||
formatted, err := t.formatQuantity(symbol, quantity)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%v", formatted), nil
|
||||
}
|
||||
+12
-1
@@ -21,7 +21,7 @@ type AutoTraderConfig struct {
|
||||
AIModel string // AI模型: "qwen" 或 "deepseek"
|
||||
|
||||
// 交易平台选择
|
||||
Exchange string // "binance" 或 "hyperliquid"
|
||||
Exchange string // "binance", "hyperliquid" 或 "aster"
|
||||
|
||||
// 币安API配置
|
||||
BinanceAPIKey string
|
||||
@@ -31,6 +31,11 @@ type AutoTraderConfig struct {
|
||||
HyperliquidPrivateKey string
|
||||
HyperliquidTestnet bool
|
||||
|
||||
// Aster配置
|
||||
AsterUser string // Aster主钱包地址
|
||||
AsterSigner string // Aster API钱包地址
|
||||
AsterPrivateKey string // Aster API钱包私钥
|
||||
|
||||
CoinPoolAPIURL string
|
||||
|
||||
// AI配置
|
||||
@@ -134,6 +139,12 @@ func NewAutoTrader(config AutoTraderConfig) (*AutoTrader, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("初始化Hyperliquid交易器失败: %w", err)
|
||||
}
|
||||
case "aster":
|
||||
log.Printf("🏦 [%s] 使用Aster交易", config.Name)
|
||||
trader, err = NewAsterTrader(config.AsterUser, config.AsterSigner, config.AsterPrivateKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("初始化Aster交易器失败: %w", err)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("不支持的交易平台: %s", config.Exchange)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import useSWR from 'swr';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import { t } from '../i18n/translations';
|
||||
import { api } from '../lib/api';
|
||||
|
||||
interface TradeOutcome {
|
||||
symbol: string;
|
||||
@@ -44,13 +45,11 @@ interface AILearningProps {
|
||||
traderId: string;
|
||||
}
|
||||
|
||||
const fetcher = (url: string) => fetch(url).then(res => res.json());
|
||||
|
||||
export default function AILearning({ traderId }: AILearningProps) {
|
||||
const { language } = useLanguage();
|
||||
const { data: performance, error } = useSWR<PerformanceAnalysis>(
|
||||
`http://localhost:8080/api/performance?trader_id=${traderId}`,
|
||||
fetcher,
|
||||
traderId ? `performance-${traderId}` : 'performance',
|
||||
() => api.getPerformance(traderId),
|
||||
{ refreshInterval: 10000 }
|
||||
);
|
||||
|
||||
|
||||
@@ -80,8 +80,10 @@ export function EquityChart({ traderId }: EquityChartProps) {
|
||||
? history.slice(-MAX_DISPLAY_POINTS)
|
||||
: history;
|
||||
|
||||
// 计算初始余额(使用第一个数据点)
|
||||
const initialBalance = history[0]?.total_equity || 1000;
|
||||
// 计算初始余额(使用第一个数据点,如果无数据则从account获取,最后才用默认值)
|
||||
const initialBalance = history[0]?.total_equity
|
||||
|| account?.total_equity
|
||||
|| 100; // 默认值改为100,与常见配置一致
|
||||
|
||||
// 转换数据格式
|
||||
const chartData = displayHistory.map((point) => {
|
||||
|
||||
@@ -100,4 +100,14 @@ export const api = {
|
||||
if (!res.ok) throw new Error('获取历史数据失败');
|
||||
return res.json();
|
||||
},
|
||||
|
||||
// 获取AI学习表现分析(支持trader_id)
|
||||
async getPerformance(traderId?: string): Promise<any> {
|
||||
const url = traderId
|
||||
? `${API_BASE}/performance?trader_id=${traderId}`
|
||||
: `${API_BASE}/performance`;
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) throw new Error('获取AI学习数据失败');
|
||||
return res.json();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# 常见问题
|
||||
|
||||
## 币安持仓模式错误 (code=-4061)
|
||||
|
||||
**错误信息**:`Order's position side does not match user's setting`
|
||||
|
||||
**原因**:系统需要使用双向持仓模式,但您的币安账户设置为单向持仓。
|
||||
|
||||
### 解决方法
|
||||
|
||||
1. 登录 [币安合约交易平台](https://www.binance.com/zh-CN/futures/BTCUSDT)
|
||||
|
||||
2. 点击右上角的 **⚙️ 偏好设置**
|
||||
|
||||
3. 选择 **持仓模式**
|
||||
|
||||
4. 切换为 **双向持仓** (Hedge Mode)
|
||||
|
||||
5. 确认切换
|
||||
|
||||
**注意**:切换前必须先平掉所有持仓。
|
||||
|
||||
---
|
||||
|
||||
更多问题请查看 [GitHub Issues](https://github.com/tinkle-community/nofx/issues)
|
||||
Reference in New Issue
Block a user