mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-02 18:41:01 +08:00
Merge branch 'dev' into dev
This commit is contained in:
152
README.md
152
README.md
@@ -1,16 +1,43 @@
|
||||
# 🤖 NOFX - Multi-AI Model Automated Trading Platform
|
||||
# 🤖 NOFX - Agentic Trading OS
|
||||
|
||||
[](https://golang.org/)
|
||||
[](https://reactjs.org/)
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](https://sqlite.org/)
|
||||
[](LICENSE)
|
||||
[](https://amber.ac)
|
||||
|
||||
**Languages:** [English](README.md) | [中文](README.zh-CN.md) | [Українська](README.uk.md) | [Русский](README.ru.md)
|
||||
|
||||
**Official Twitter:** [@nofx_ai](https://x.com/nofx_ai)
|
||||
|
||||
---
|
||||
|
||||
A modern automated crypto futures trading platform powered by **DeepSeek/Qwen AI**, supporting **Binance, Hyperliquid, and Aster DEX exchanges**. Create and manage multiple AI traders with dynamic configuration through a web interface. Features comprehensive market analysis, AI decision-making, **multi-AI model live trading competition**, **self-learning mechanism**, and professional monitoring dashboard.
|
||||
## 🚀 Universal AI Trading Operating System
|
||||
|
||||
**NOFX** is a **universal Agentic Trading OS** built on a unified architecture. We've successfully closed the loop in crypto markets: **"Multi-Agent Decision → Unified Risk Control → Low-Latency Execution → Live/Paper Account Backtesting"**, and are now expanding this same technology stack to **stocks, futures, options, forex, and all financial markets**.
|
||||
|
||||
### 🎯 Core Features
|
||||
|
||||
- **Universal Data & Backtesting Layer**: Cross-market, cross-timeframe, cross-exchange unified representation and factor library, accumulating transferable "strategy memory"
|
||||
- **Multi-Agent Self-Play & Self-Evolution**: Strategies automatically compete and select the best, continuously iterating based on account-level PnL and risk constraints
|
||||
- **Integrated Execution & Risk Control**: Low-latency routing, slippage/risk control sandbox, account-level limits, one-click market switching
|
||||
|
||||
### 🏢 Backed by [Amber.ac](https://amber.ac)
|
||||
|
||||
### 👥 Core Team
|
||||
|
||||
- **Tinkle** - [@Web3Tinkle](https://x.com/Web3Tinkle)
|
||||
- **Zack** - [@0x_ZackH](https://x.com/0x_ZackH)
|
||||
|
||||
### 💼 Seed Funding Round Open
|
||||
|
||||
We are currently raising our **seed round**.
|
||||
|
||||
**For investment inquiries**, please DM **Tinkle** or **Zack** via Twitter.
|
||||
|
||||
**For partnerships and collaborations**, please DM our official Twitter [@nofx_ai](https://x.com/nofx_ai).
|
||||
|
||||
---
|
||||
|
||||
> ⚠️ **Risk Warning**: This system is experimental. AI auto-trading carries significant risks. Strongly recommended for learning/research purposes or testing with small amounts only!
|
||||
|
||||
@@ -24,11 +51,7 @@ Join our Telegram developer community to discuss, share ideas, and get support:
|
||||
|
||||
## 🆕 What's New (Latest Update)
|
||||
|
||||
### 🚀 Complete System Transformation - Web-Based Configuration!
|
||||
|
||||
NOFX has been **completely transformed** from a static config-based system to a **dynamic web-based trading platform** with **multi-exchange support**!
|
||||
|
||||
#### **Multi-Exchange Support**
|
||||
### 🚀 Multi-Exchange Support!
|
||||
|
||||
NOFX now supports **three major exchanges**: Binance, Hyperliquid, and Aster DEX!
|
||||
|
||||
@@ -36,13 +59,12 @@ NOFX now supports **three major exchanges**: Binance, Hyperliquid, and Aster DEX
|
||||
|
||||
A high-performance decentralized perpetual futures exchange!
|
||||
|
||||
**Major Changes:**
|
||||
- ✅ **Web-Based Configuration**: Create and manage AI traders through a modern web interface
|
||||
- ✅ **Database-Driven Architecture**: SQLite database replaces static JSON configuration
|
||||
- ✅ **Separate AI Models & Exchanges**: Configure AI models and exchanges independently
|
||||
- ✅ **Dynamic Trader Creation**: Create traders by combining configured AI models and exchanges
|
||||
- ✅ **Real-Time Management**: Start/stop traders, update configurations without restart
|
||||
- ✅ **No Default Traders**: Clean slate - create only the traders you need
|
||||
**Key Features:**
|
||||
- ✅ Full trading support (long/short, leverage, stop-loss/take-profit)
|
||||
- ✅ Automatic precision handling (order size & price)
|
||||
- ✅ Unified trader interface (seamless exchange switching)
|
||||
- ✅ Support for both mainnet and testnet
|
||||
- ✅ No API keys needed - just your Ethereum private key
|
||||
|
||||
**New Workflow:**
|
||||
1. **Configure AI Models**: Add your DeepSeek/Qwen API keys through the web interface
|
||||
@@ -96,58 +118,64 @@ A Binance-compatible decentralized perpetual futures exchange!
|
||||
|
||||
---
|
||||
|
||||
## ✨ Core Features
|
||||
## ✨ Current Implementation - Crypto Markets
|
||||
|
||||
### 🎛️ Web-Based Configuration Management
|
||||
- **Dynamic AI Model Setup**: Configure DeepSeek and Qwen API keys through web interface
|
||||
- **Exchange Management**: Set up Binance and Hyperliquid credentials independently
|
||||
- **Flexible Trader Creation**: Mix any AI model with any exchange
|
||||
- **Real-Time Control**: Start/stop traders without system restart
|
||||
NOFX is currently **fully operational in cryptocurrency markets** with the following proven capabilities:
|
||||
|
||||
### 🧠 AI Self-Learning Mechanism (NEW!)
|
||||
- **Historical Feedback**: Analyzes last 20 cycles of trading performance before each decision
|
||||
- **Smart Optimization**:
|
||||
- Identifies best/worst performing coins
|
||||
- Calculates win rate, profit/loss ratio, average profit
|
||||
- Avoids repeating mistakes (consecutive losing coins)
|
||||
### 🏆 Multi-Agent Competition Framework
|
||||
- **Live Agent Battle**: Qwen vs DeepSeek models compete in real-time trading
|
||||
- **Independent Account Management**: Each agent maintains its own decision logs and performance metrics
|
||||
- **Real-time Performance Comparison**: Live ROI tracking, win rate statistics, and head-to-head analysis
|
||||
- **Self-Evolution Loop**: Agents learn from their historical performance and continuously improve
|
||||
|
||||
### 🧠 AI Self-Learning & Optimization
|
||||
- **Historical Feedback System**: Analyzes last 20 trading cycles before each decision
|
||||
- **Smart Performance Analysis**:
|
||||
- Identifies best/worst performing assets
|
||||
- Calculates win rate, profit/loss ratio, average profit in real USDT terms
|
||||
- Avoids repeating mistakes (consecutive losing patterns)
|
||||
- Reinforces successful strategies (high win rate patterns)
|
||||
- **Dynamic Adjustment**: AI autonomously adjusts trading style based on historical performance
|
||||
- **Dynamic Strategy Adjustment**: AI autonomously adapts trading style based on backtest results
|
||||
|
||||
### 📊 Intelligent Market Analysis
|
||||
- **3-minute K-line**: Real-time price, EMA20, MACD, RSI(7)
|
||||
- **4-hour K-line**: Long-term trend, EMA20/50, ATR, RSI(14)
|
||||
- **Open Interest Analysis**: Market sentiment, capital flow judgment
|
||||
- **OI Top Tracking**: Top 20 coins with fastest growing open interest
|
||||
- **AI500 Coin Pool**: Automatic high-score coin screening
|
||||
- **Liquidity Filter**: Auto-filters low liquidity coins (<15M USD position value)
|
||||
### 📊 Universal Market Data Layer (Crypto Implementation)
|
||||
- **Multi-Timeframe Analysis**: 3-minute real-time + 4-hour trend data
|
||||
- **Technical Indicators**: EMA20/50, MACD, RSI(7/14), ATR
|
||||
- **Open Interest Tracking**: Market sentiment, capital flow analysis
|
||||
- **Liquidity Filtering**: Auto-filters low liquidity assets (<15M USD)
|
||||
- **Cross-Exchange Support**: Binance, Hyperliquid, Aster DEX with unified data interface
|
||||
|
||||
### 🎯 Professional Risk Control
|
||||
- **Per-Coin Position Limit**:
|
||||
- Altcoins ≤ 1.5x account equity
|
||||
- BTC/ETH ≤ 10x account equity
|
||||
- **Configurable Leverage** (v2.0.3+):
|
||||
- Set maximum leverage in config.json
|
||||
- Default: 5x for all coins (safe for subaccounts)
|
||||
- Main accounts can increase: Altcoins up to 20x, BTC/ETH up to 50x
|
||||
- ⚠️ Binance subaccounts restricted to ≤5x leverage
|
||||
- **Margin Management**: Total usage ≤90%, AI autonomous decision on usage rate
|
||||
- **Risk-Reward Ratio**: Mandatory ≥1:2 (stop-loss:take-profit)
|
||||
- **Prevent Position Stacking**: No duplicate opening of same coin/direction
|
||||
### 🎯 Unified Risk Control System
|
||||
- **Position Limits**: Per-asset limits (Altcoins ≤1.5x equity, BTC/ETH ≤10x equity)
|
||||
- **Configurable Leverage**: Dynamic leverage from 1x to 50x based on asset class and account type
|
||||
- **Margin Management**: Total usage ≤90%, AI-controlled allocation
|
||||
- **Risk-Reward Enforcement**: Mandatory ≥1:2 stop-loss to take-profit ratio
|
||||
- **Anti-Stacking Protection**: Prevents duplicate positions in same asset/direction
|
||||
|
||||
### 🎨 Professional UI
|
||||
- **Professional Trading Interface**: Binance-style visual design
|
||||
- **Dark Theme**: Classic color scheme (Gold #F0B90B + dark background)
|
||||
- **Real-time Data**: 5-second refresh for accounts, positions, charts
|
||||
- **Equity Curve**: Historical account value trend (USD/percentage toggle)
|
||||
- **Performance Comparison Chart**: Real-time multi-AI ROI comparison
|
||||
- **Smooth Animations**: Fluid hover, transition, and loading effects
|
||||
### ⚡ Low-Latency Execution Engine
|
||||
- **Multi-Exchange API Integration**: Binance Futures, Hyperliquid DEX, Aster DEX
|
||||
- **Automatic Precision Handling**: Smart order size & price formatting per exchange
|
||||
- **Priority Execution**: Close existing positions first, then open new ones
|
||||
- **Slippage Control**: Pre-execution validation, real-time precision checks
|
||||
|
||||
### 📝 Complete Decision Recording
|
||||
- **Chain of Thought**: AI's complete reasoning process (CoT)
|
||||
- **Historical Performance**: Overall win rate, average profit, profit/loss ratio
|
||||
- **Recent Trades**: Last 5 trade details (entry price → exit price → P/L%)
|
||||
- **Coin Statistics**: Per-coin performance (win rate, average P/L)
|
||||
- **JSON Logs**: Complete decision records for post-trade analysis
|
||||
### 🎨 Professional Monitoring Interface
|
||||
- **Binance-Style Dashboard**: Professional dark theme with real-time updates
|
||||
- **Equity Curves**: Historical account value tracking (USD/percentage toggle)
|
||||
- **Performance Charts**: Multi-agent ROI comparison with live updates
|
||||
- **Complete Decision Logs**: Full Chain of Thought (CoT) reasoning for every trade
|
||||
- **5-Second Data Refresh**: Real-time account, position, and P/L updates
|
||||
|
||||
---
|
||||
|
||||
## 🔮 Roadmap - Universal Market Expansion
|
||||
|
||||
Our proven crypto infrastructure is being extended to:
|
||||
|
||||
- **📈 Stock Markets**: US equities, A-shares, Hong Kong stocks
|
||||
- **📊 Futures Markets**: Commodity futures, index futures
|
||||
- **🎯 Options Trading**: Equity options, crypto options
|
||||
- **💱 Forex Markets**: Major currency pairs, cross rates
|
||||
|
||||
**Same architecture. Same agent framework. All markets.**
|
||||
|
||||
---
|
||||
|
||||
@@ -1365,8 +1393,10 @@ Issues and Pull Requests are welcome!
|
||||
|
||||
## 📬 Contact
|
||||
|
||||
- **Twitter/X**: [@Web3Tinkle](https://x.com/Web3Tinkle)
|
||||
|
||||
### 🐛 Technical Support
|
||||
- **GitHub Issues**: [Submit an Issue](https://github.com/tinkle-community/nofx/issues)
|
||||
- **Developer Community**: [Telegram Group](https://t.me/nofx_dev_community)
|
||||
|
||||
---
|
||||
|
||||
|
||||
32
README.ru.md
32
README.ru.md
@@ -1,15 +1,43 @@
|
||||
# 🤖 NOFX - AI-управляемая система автоматической торговли фьючерсами Binance
|
||||
# 🤖 NOFX - Agentic Trading OS
|
||||
|
||||
[](https://golang.org/)
|
||||
[](https://reactjs.org/)
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](LICENSE)
|
||||
[](https://amber.ac)
|
||||
|
||||
**Языки / Languages:** [English](README.md) | [中文](README.zh-CN.md) | [Українська](README.uk.md) | [Русский](README.ru.md)
|
||||
|
||||
**Официальный Twitter:** [@nofx_ai](https://x.com/nofx_ai)
|
||||
|
||||
---
|
||||
|
||||
Автоматизированная система торговли криптовалютными фьючерсами на базе **DeepSeek/Qwen AI**, поддерживающая **Binance, Hyperliquid и Aster DEX биржи**, **конкуренцию нескольких AI-моделей в реальной торговле**, с полным анализом рынка, принятием решений AI, **механизмом самообучения** и профессиональным веб-интерфейсом мониторинга.
|
||||
## 🚀 Универсальная AI Торговая Операционная Система
|
||||
|
||||
**NOFX** - это **универсальная Agentic Trading OS**, построенная на единой архитектуре. Мы успешно замкнули цикл на криптовалютных рынках: **"Решение Multi-Agent → Единый Контроль Рисков → Исполнение с Низкой Задержкой → Бэктестинг Реальных/Бумажных Счетов"**, и сейчас расширяем этот же технологический стек на **акции, фьючерсы, опционы, форекс и все финансовые рынки**.
|
||||
|
||||
### 🎯 Основные Возможности
|
||||
|
||||
- **Универсальный Слой Данных и Бэктестинга**: Кросс-рыночное, кросс-таймфреймовое, кросс-биржевое единое представление и библиотека факторов, накапливающая переносимую "память стратегий"
|
||||
- **Multi-Agent Самоигра и Самоэволюция**: Стратегии автоматически конкурируют и выбирают лучшие, непрерывно итерируясь на основе PnL на уровне счета и ограничений рисков
|
||||
- **Интегрированное Исполнение и Контроль Рисков**: Маршрутизация с низкой задержкой, песочница проскальзывания/контроля рисков, лимиты на уровне счета, переключение рынков одним кликом
|
||||
|
||||
### 🏢 При поддержке [Amber.ac](https://amber.ac)
|
||||
|
||||
### 👥 Основная Команда
|
||||
|
||||
- **Tinkle** - [@Web3Tinkle](https://x.com/Web3Tinkle)
|
||||
- **Zack** - [@0x_ZackH](https://x.com/0x_ZackH)
|
||||
|
||||
### 💼 Открыт Посевной Раунд Финансирования
|
||||
|
||||
Мы в настоящее время привлекаем **посевной раунд**.
|
||||
|
||||
**По вопросам инвестиций**, пишите в DM **Tinkle** или **Zack** в Twitter.
|
||||
|
||||
**По вопросам партнерства и сотрудничества**, пишите в DM нашего официального Twitter [@nofx_ai](https://x.com/nofx_ai).
|
||||
|
||||
---
|
||||
|
||||
> ⚠️ **Предупреждение о рисках**: Эта система экспериментальная. Автоматическая торговля с AI несет значительные риски. Настоятельно рекомендуется использовать только для обучения/исследований или тестирования с небольшими суммами!
|
||||
|
||||
|
||||
32
README.uk.md
32
README.uk.md
@@ -1,15 +1,43 @@
|
||||
# 🤖 NOFX - AI-керована система автоматичної торгівлі ф'ючерсами Binance
|
||||
# 🤖 NOFX - Agentic Trading OS
|
||||
|
||||
[](https://golang.org/)
|
||||
[](https://reactjs.org/)
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](LICENSE)
|
||||
[](https://amber.ac)
|
||||
|
||||
**Мови / Languages:** [English](README.md) | [中文](README.zh-CN.md) | [Українська](README.uk.md) | [Русский](README.ru.md)
|
||||
|
||||
**Офіційний Twitter:** [@nofx_ai](https://x.com/nofx_ai)
|
||||
|
||||
---
|
||||
|
||||
Автоматизована система торгівлі криптовалютними ф'ючерсами на базі **DeepSeek/Qwen AI**, що підтримує **Binance, Hyperliquid та Aster DEX біржі**, **змагання кількох AI-моделей у реальній торгівлі**, з повним аналізом ринку, прийняттям рішень AI, **механізмом самонавчання** та професійним веб-інтерфейсом моніторингу.
|
||||
## 🚀 Універсальна AI Торгова Операційна Система
|
||||
|
||||
**NOFX** - це **універсальна Agentic Trading OS**, побудована на єдиній архітектурі. Ми успішно замкнули цикл на криптовалютних ринках: **"Рішення Multi-Agent → Єдиний Контроль Ризиків → Виконання з Низькою Затримкою → Бектестинг Реальних/Паперових Рахунків"**, і зараз розширюємо цей же технологічний стек на **акції, ф'ючерси, опціони, форекс та всі фінансові ринки**.
|
||||
|
||||
### 🎯 Основні Можливості
|
||||
|
||||
- **Універсальний Шар Даних та Бектестингу**: Крос-ринкове, крос-таймфреймове, крос-біржеве єдине представлення та бібліотека факторів, що накопичує переносиму "пам'ять стратегій"
|
||||
- **Multi-Agent Самогра та Самоеволюція**: Стратегії автоматично конкурують і вибирають кращі, безперервно ітеруючись на основі PnL на рівні рахунку та обмежень ризиків
|
||||
- **Інтегроване Виконання та Контроль Ризиків**: Маршрутизація з низькою затримкою, пісочниця прослизання/контролю ризиків, ліміти на рівні рахунку, перемикання ринків одним кліком
|
||||
|
||||
### 🏢 За підтримки [Amber.ac](https://amber.ac)
|
||||
|
||||
### 👥 Основна Команда
|
||||
|
||||
- **Tinkle** - [@Web3Tinkle](https://x.com/Web3Tinkle)
|
||||
- **Zack** - [@0x_ZackH](https://x.com/0x_ZackH)
|
||||
|
||||
### 💼 Відкритий Посівний Раунд Фінансування
|
||||
|
||||
Ми зараз залучаємо **посівний раунд**.
|
||||
|
||||
**З питань інвестицій**, пишіть в DM **Tinkle** або **Zack** в Twitter.
|
||||
|
||||
**З питань партнерства та співпраці**, пишіть в DM нашого офіційного Twitter [@nofx_ai](https://x.com/nofx_ai).
|
||||
|
||||
---
|
||||
|
||||
> ⚠️ **Попередження про ризики**: Ця система експериментальна. Автоматична торгівля з AI несе значні ризики. Наполегливо рекомендується використовувати лише для навчання/досліджень або тестування з невеликими сумами!
|
||||
|
||||
|
||||
133
README.zh-CN.md
133
README.zh-CN.md
@@ -1,15 +1,43 @@
|
||||
# 🤖 NOFX - AI驱动的币安合约自动交易竞赛系统
|
||||
# 🤖 NOFX - AI交易操作系统
|
||||
|
||||
[](https://golang.org/)
|
||||
[](https://reactjs.org/)
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](LICENSE)
|
||||
[](https://amber.ac)
|
||||
|
||||
**语言 / Languages:** [English](README.md) | [中文](README.zh-CN.md) | [Українська](README.uk.md) | [Русский](README.ru.md)
|
||||
|
||||
**官方推特:** [@nofx_ai](https://x.com/nofx_ai)
|
||||
|
||||
---
|
||||
|
||||
一个基于 **DeepSeek/Qwen AI** 的加密货币期货自动交易系统,支持 **Binance、Hyperliquid和Aster DEX交易所**,**多AI模型实盘竞赛**,具备完整的市场分析、AI决策、**自我学习机制**和专业的Web监控界面。
|
||||
## 🚀 通用AI交易操作系统
|
||||
|
||||
**NOFX** 是通用架构的 **AI交易操作系统(Agentic Trading OS)**。我们已在加密市场打通"**多智能体决策 → 统一风控 → 低延迟执行 → 真实/纸面账户复盘**"的闭环,正按同一技术栈扩展到**股票、期货、期权、外汇等所有市场**。
|
||||
|
||||
### 🎯 核心特性
|
||||
|
||||
- **通用数据与回测层**:跨市场、跨周期、跨交易所统一表示与因子库,沉淀可迁移的"策略记忆"
|
||||
- **多智能体自博弈与自进化**:策略自动对战择优,按账户级 PnL 与风险约束持续迭代
|
||||
- **执行与风控一体化**:低延迟路由、滑点/风控沙箱、账户级限额,一键切换市场
|
||||
|
||||
### 🏢 由 [Amber.ac](https://amber.ac) 背书
|
||||
|
||||
### 👥 核心团队
|
||||
|
||||
- **Tinkle** - [@Web3Tinkle](https://x.com/Web3Tinkle)
|
||||
- **Zack** - [@0x_ZackH](https://x.com/0x_ZackH)
|
||||
|
||||
### 💼 种子轮融资进行中
|
||||
|
||||
我们正在进行**种子轮融资**。
|
||||
|
||||
**投资咨询**,请通过 Twitter 私信联系 **Tinkle** 或 **Zack**。
|
||||
|
||||
**商务合作**,请私信官方推特 [@nofx_ai](https://x.com/nofx_ai)。
|
||||
|
||||
---
|
||||
|
||||
> ⚠️ **风险提示**:本系统为实验性项目,AI自动交易存在重大风险,强烈建议仅用于学习研究或小额资金测试!
|
||||
|
||||
@@ -90,58 +118,64 @@ NOFX现已支持**三大交易所**:Binance、Hyperliquid和Aster DEX!
|
||||
|
||||
---
|
||||
|
||||
## ✨ 核心特性
|
||||
## ✨ 当前实现 - 加密货币市场
|
||||
|
||||
### 🏆 多AI竞赛模式
|
||||
- **Qwen vs DeepSeek** 实盘对抗
|
||||
- 独立账户管理,独立决策日志
|
||||
- 实时性能对比图表
|
||||
- 收益率PK,胜率统计
|
||||
NOFX 目前已在**加密货币市场全面运行**,具备以下经过验证的能力:
|
||||
|
||||
### 🧠 AI自我学习机制(NEW!)
|
||||
- **历史反馈**: 每次决策前分析最近20个周期的交易表现
|
||||
- **智能优化**:
|
||||
- 识别表现最佳/最差币种
|
||||
- 计算胜率、盈亏比、平均盈利
|
||||
- 避免重复错误(连续亏损的币种)
|
||||
- 强化成功策略(高胜率的交易模式)
|
||||
- **动态调整**: AI根据历史表现自主调整交易风格
|
||||
### 🏆 多智能体竞赛框架
|
||||
- **实时智能体对战**:Qwen vs DeepSeek 模型实时交易竞赛
|
||||
- **独立账户管理**:每个智能体维护独立的决策日志和性能指标
|
||||
- **实时性能对比**:实时 ROI 追踪、胜率统计、正面对抗分析
|
||||
- **自进化循环**:智能体从历史表现中学习,持续改进
|
||||
|
||||
### 📊 智能市场分析
|
||||
- **3分钟K线**: 实时价格、EMA20、MACD、RSI(7)
|
||||
- **4小时K线**: 长期趋势、EMA20/50、ATR、RSI(14)
|
||||
- **持仓量分析**: 市场情绪、资金流向判断
|
||||
- **OI Top追踪**: 持仓量增长最快的20个币种
|
||||
- **AI500币种池**: 高评分币种自动筛选
|
||||
- **流动性过滤**: 自动过滤持仓价值<15M USD的低流动性币种
|
||||
### 🧠 AI 自学习与优化
|
||||
- **历史反馈系统**:每次决策前分析最近 20 个交易周期
|
||||
- **智能性能分析**:
|
||||
- 识别表现最佳/最差资产
|
||||
- 计算胜率、盈亏比、以真实 USDT 计的平均盈利
|
||||
- 避免重复错误(连续亏损模式)
|
||||
- 强化成功策略(高胜率模式)
|
||||
- **动态策略调整**:AI 根据回测结果自主调整交易风格
|
||||
|
||||
### 🎯 专业风险控制
|
||||
- **单币种仓位上限**:
|
||||
- 山寨币 ≤ 1.5倍账户净值
|
||||
- BTC/ETH ≤ 10倍账户净值
|
||||
- **可配置杠杆** (v2.0.3+):
|
||||
- 在config.json中设置最大杠杆
|
||||
- 默认:所有币种5倍(子账户安全)
|
||||
- 主账户可增加:山寨币最高20倍,BTC/ETH最高50倍
|
||||
- ⚠️ 币安子账户限制≤5倍杠杆
|
||||
- **保证金管理**: 总使用率≤90%,AI自主决策使用率
|
||||
- **风险回报比**: 强制≥1:2(止损:止盈)
|
||||
- **防止仓位叠加**: 同币种同方向不允许重复开仓
|
||||
### 📊 通用市场数据层(加密货币实现)
|
||||
- **多时间框架分析**:3分钟实时 + 4小时趋势数据
|
||||
- **技术指标**:EMA20/50、MACD、RSI(7/14)、ATR
|
||||
- **持仓量追踪**:市场情绪、资金流向分析
|
||||
- **流动性过滤**:自动过滤低流动性资产(<15M USD)
|
||||
- **跨交易所支持**:Binance、Hyperliquid、Aster DEX,统一数据接口
|
||||
|
||||
### 🎨 风格UI
|
||||
- **专业交易界面**: 视觉设计
|
||||
- **暗色主题**: 经典配色(金色#F0B90B + 深色背景)
|
||||
- **实时数据**: 5秒刷新账户、持仓、图表
|
||||
- **收益率曲线**: 账户净值历史走势(美元/百分比切换)
|
||||
- **性能对比图**: 多AI收益率实时对比
|
||||
- **动画效果**: 流畅的hover、过渡、加载动画
|
||||
### 🎯 统一风控系统
|
||||
- **仓位限制**:单资产限制(山寨币≤1.5x净值,BTC/ETH≤10x净值)
|
||||
- **可配置杠杆**:根据资产类别和账户类型动态调整 1x 到 50x
|
||||
- **保证金管理**:总使用率≤90%,AI 控制分配
|
||||
- **风险回报强制执行**:强制≥1:2 的止损止盈比
|
||||
- **防叠加保护**:防止同一资产/方向的重复仓位
|
||||
|
||||
### 📝 完整决策记录
|
||||
- **思维链记录**: AI的完整推理过程(CoT)
|
||||
- **历史表现**: 整体胜率、平均盈利、盈亏比
|
||||
- **最近交易**: 最近5笔交易详情(开仓价→平仓价→盈亏%)
|
||||
- **币种统计**: 各币种表现(胜率、平均盈亏)
|
||||
- **JSON日志**: 每次决策完整记录,便于复盘分析
|
||||
### ⚡ 低延迟执行引擎
|
||||
- **多交易所 API 集成**:Binance Futures、Hyperliquid DEX、Aster DEX
|
||||
- **自动精度处理**:每个交易所智能订单大小和价格格式化
|
||||
- **优先级执行**:先平仓现有持仓,再开新仓
|
||||
- **滑点控制**:执行前验证,实时精度检查
|
||||
|
||||
### 🎨 专业监控界面
|
||||
- **币安风格仪表板**:专业暗色主题,实时更新
|
||||
- **净值曲线**:历史账户价值追踪(USD/百分比切换)
|
||||
- **性能图表**:多智能体 ROI 对比,实时更新
|
||||
- **完整决策日志**:每笔交易的完整思维链(CoT)推理
|
||||
- **5秒数据刷新**:实时账户、持仓和盈亏更新
|
||||
|
||||
---
|
||||
|
||||
## 🔮 路线图 - 通用市场扩展
|
||||
|
||||
我们经过验证的加密货币基础设施正在扩展到:
|
||||
|
||||
- **📈 股票市场**:美股、A股、港股
|
||||
- **📊 期货市场**:商品期货、指数期货
|
||||
- **🎯 期权交易**:股票期权、加密期权
|
||||
- **💱 外汇市场**:主要货币对、交叉盘
|
||||
|
||||
**相同架构。相同智能体框架。所有市场。**
|
||||
|
||||
---
|
||||
|
||||
@@ -1310,8 +1344,9 @@ MIT License - 详见 [LICENSE](LICENSE) 文件
|
||||
|
||||
## 📬 联系方式
|
||||
|
||||
- **Twitter/X**: [@Web3Tinkle](https://x.com/Web3Tinkle)
|
||||
### 🐛 技术支持
|
||||
- **GitHub Issues**: [提交Issue](https://github.com/tinkle-community/nofx/issues)
|
||||
- **开发者社区**: [Telegram群组](https://t.me/nofx_dev_community)
|
||||
|
||||
---
|
||||
|
||||
|
||||
186
api/server.go
186
api/server.go
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
"nofx/auth"
|
||||
"nofx/config"
|
||||
"nofx/decision"
|
||||
"nofx/manager"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -109,6 +110,10 @@ func (s *Server) setupRoutes() {
|
||||
protected.GET("/user/signal-sources", s.handleGetUserSignalSource)
|
||||
protected.POST("/user/signal-sources", s.handleSaveUserSignalSource)
|
||||
|
||||
// 系统提示词模板管理
|
||||
protected.GET("/prompt-templates", s.handleGetPromptTemplates)
|
||||
protected.GET("/prompt-templates/:name", s.handleGetPromptTemplate)
|
||||
|
||||
// 竞赛总览
|
||||
protected.GET("/competition", s.handleCompetition)
|
||||
|
||||
@@ -200,18 +205,19 @@ func (s *Server) getTraderFromQuery(c *gin.Context) (*manager.TraderManager, str
|
||||
|
||||
// AI交易员管理相关结构体
|
||||
type CreateTraderRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
AIModelID string `json:"ai_model_id" binding:"required"`
|
||||
ExchangeID string `json:"exchange_id" binding:"required"`
|
||||
InitialBalance float64 `json:"initial_balance"`
|
||||
BTCETHLeverage int `json:"btc_eth_leverage"`
|
||||
AltcoinLeverage int `json:"altcoin_leverage"`
|
||||
TradingSymbols string `json:"trading_symbols"`
|
||||
CustomPrompt string `json:"custom_prompt"`
|
||||
OverrideBasePrompt bool `json:"override_base_prompt"`
|
||||
IsCrossMargin *bool `json:"is_cross_margin"` // 指针类型,nil表示使用默认值true
|
||||
UseCoinPool bool `json:"use_coin_pool"`
|
||||
UseOITop bool `json:"use_oi_top"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
AIModelID string `json:"ai_model_id" binding:"required"`
|
||||
ExchangeID string `json:"exchange_id" binding:"required"`
|
||||
InitialBalance float64 `json:"initial_balance"`
|
||||
BTCETHLeverage int `json:"btc_eth_leverage"`
|
||||
AltcoinLeverage int `json:"altcoin_leverage"`
|
||||
TradingSymbols string `json:"trading_symbols"`
|
||||
CustomPrompt string `json:"custom_prompt"`
|
||||
OverrideBasePrompt bool `json:"override_base_prompt"`
|
||||
SystemPromptTemplate string `json:"system_prompt_template"` // 系统提示词模板名称
|
||||
IsCrossMargin *bool `json:"is_cross_margin"` // 指针类型,nil表示使用默认值true
|
||||
UseCoinPool bool `json:"use_coin_pool"`
|
||||
UseOITop bool `json:"use_oi_top"`
|
||||
}
|
||||
|
||||
type ModelConfig struct {
|
||||
@@ -235,9 +241,10 @@ type ExchangeConfig struct {
|
||||
|
||||
type UpdateModelConfigRequest struct {
|
||||
Models map[string]struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
APIKey string `json:"api_key"`
|
||||
CustomAPIURL string `json:"custom_api_url"`
|
||||
Enabled bool `json:"enabled"`
|
||||
APIKey string `json:"api_key"`
|
||||
CustomAPIURL string `json:"custom_api_url"`
|
||||
CustomModelName string `json:"custom_model_name"`
|
||||
} `json:"models"`
|
||||
}
|
||||
|
||||
@@ -318,23 +325,30 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// 设置系统提示词模板默认值
|
||||
systemPromptTemplate := "default"
|
||||
if req.SystemPromptTemplate != "" {
|
||||
systemPromptTemplate = req.SystemPromptTemplate
|
||||
}
|
||||
|
||||
// 创建交易员配置(数据库实体)
|
||||
trader := &config.TraderRecord{
|
||||
ID: traderID,
|
||||
UserID: userID,
|
||||
Name: req.Name,
|
||||
AIModelID: req.AIModelID,
|
||||
ExchangeID: req.ExchangeID,
|
||||
InitialBalance: req.InitialBalance,
|
||||
BTCETHLeverage: btcEthLeverage,
|
||||
AltcoinLeverage: altcoinLeverage,
|
||||
TradingSymbols: req.TradingSymbols,
|
||||
UseCoinPool: req.UseCoinPool,
|
||||
UseOITop: req.UseOITop,
|
||||
CustomPrompt: req.CustomPrompt,
|
||||
OverrideBasePrompt: req.OverrideBasePrompt,
|
||||
IsCrossMargin: isCrossMargin,
|
||||
ScanIntervalMinutes: 3, // 默认3分钟
|
||||
ID: traderID,
|
||||
UserID: userID,
|
||||
Name: req.Name,
|
||||
AIModelID: req.AIModelID,
|
||||
ExchangeID: req.ExchangeID,
|
||||
InitialBalance: req.InitialBalance,
|
||||
BTCETHLeverage: btcEthLeverage,
|
||||
AltcoinLeverage: altcoinLeverage,
|
||||
TradingSymbols: req.TradingSymbols,
|
||||
UseCoinPool: req.UseCoinPool,
|
||||
UseOITop: req.UseOITop,
|
||||
CustomPrompt: req.CustomPrompt,
|
||||
OverrideBasePrompt: req.OverrideBasePrompt,
|
||||
SystemPromptTemplate: systemPromptTemplate,
|
||||
IsCrossMargin: isCrossMargin,
|
||||
ScanIntervalMinutes: 3, // 默认3分钟
|
||||
IsRunning: false,
|
||||
}
|
||||
|
||||
@@ -612,16 +626,23 @@ func (s *Server) handleUpdateModelConfigs(c *gin.Context) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 更新每个模型的配置
|
||||
for modelID, modelData := range req.Models {
|
||||
err := s.database.UpdateAIModel(userID, modelID, modelData.Enabled, modelData.APIKey, modelData.CustomAPIURL)
|
||||
err := s.database.UpdateAIModel(userID, modelID, modelData.Enabled, modelData.APIKey, modelData.CustomAPIURL, modelData.CustomModelName)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("更新模型 %s 失败: %v", modelID, err)})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 重新加载该用户的所有交易员,使新配置立即生效
|
||||
err := s.traderManager.LoadUserTraders(s.database, userID)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 重新加载用户交易员到内存失败: %v", err)
|
||||
// 这里不返回错误,因为模型配置已经成功更新到数据库
|
||||
}
|
||||
|
||||
log.Printf("✓ AI模型配置已更新: %+v", req.Models)
|
||||
c.JSON(http.StatusOK, gin.H{"message": "模型配置已更新"})
|
||||
}
|
||||
@@ -649,7 +670,7 @@ func (s *Server) handleUpdateExchangeConfigs(c *gin.Context) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 更新每个交易所的配置
|
||||
for exchangeID, exchangeData := range req.Exchanges {
|
||||
err := s.database.UpdateExchange(userID, exchangeID, exchangeData.Enabled, exchangeData.APIKey, exchangeData.SecretKey, exchangeData.Testnet, exchangeData.HyperliquidWalletAddr, exchangeData.AsterUser, exchangeData.AsterSigner, exchangeData.AsterPrivateKey)
|
||||
@@ -658,7 +679,14 @@ func (s *Server) handleUpdateExchangeConfigs(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 重新加载该用户的所有交易员,使新配置立即生效
|
||||
err := s.traderManager.LoadUserTraders(s.database, userID)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 重新加载用户交易员到内存失败: %v", err)
|
||||
// 这里不返回错误,因为交易所配置已经成功更新到数据库
|
||||
}
|
||||
|
||||
log.Printf("✓ 交易所配置已更新: %+v", req.Exchanges)
|
||||
c.JSON(http.StatusOK, gin.H{"message": "交易所配置已更新"})
|
||||
}
|
||||
@@ -725,12 +753,21 @@ func (s *Server) handleTraderList(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// AIModelID 应该已经是 provider(如 "deepseek"),直接使用
|
||||
// 如果是旧数据格式(如 "admin_deepseek"),提取 provider 部分
|
||||
aiModelID := trader.AIModelID
|
||||
// 兼容旧数据:如果包含下划线,提取最后一部分作为 provider
|
||||
if strings.Contains(aiModelID, "_") {
|
||||
parts := strings.Split(aiModelID, "_")
|
||||
aiModelID = parts[len(parts)-1]
|
||||
}
|
||||
|
||||
result = append(result, map[string]interface{}{
|
||||
"trader_id": trader.ID,
|
||||
"trader_name": trader.Name,
|
||||
"ai_model": trader.AIModelID,
|
||||
"exchange_id": trader.ExchangeID,
|
||||
"is_running": isRunning,
|
||||
"trader_id": trader.ID,
|
||||
"trader_name": trader.Name,
|
||||
"ai_model": aiModelID,
|
||||
"exchange_id": trader.ExchangeID,
|
||||
"is_running": isRunning,
|
||||
"initial_balance": trader.InitialBalance,
|
||||
})
|
||||
}
|
||||
@@ -763,21 +800,30 @@ func (s *Server) handleGetTraderConfig(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// AIModelID 应该已经是 provider(如 "deepseek"),直接使用
|
||||
// 如果是旧数据格式(如 "admin_deepseek"),提取 provider 部分
|
||||
aiModelID := traderConfig.AIModelID
|
||||
// 兼容旧数据:如果包含下划线,提取最后一部分作为 provider
|
||||
if strings.Contains(aiModelID, "_") {
|
||||
parts := strings.Split(aiModelID, "_")
|
||||
aiModelID = parts[len(parts)-1]
|
||||
}
|
||||
|
||||
result := map[string]interface{}{
|
||||
"trader_id": traderConfig.ID,
|
||||
"trader_name": traderConfig.Name,
|
||||
"ai_model": traderConfig.AIModelID,
|
||||
"exchange_id": traderConfig.ExchangeID,
|
||||
"initial_balance": traderConfig.InitialBalance,
|
||||
"btc_eth_leverage": traderConfig.BTCETHLeverage,
|
||||
"altcoin_leverage": traderConfig.AltcoinLeverage,
|
||||
"trading_symbols": traderConfig.TradingSymbols,
|
||||
"custom_prompt": traderConfig.CustomPrompt,
|
||||
"trader_id": traderConfig.ID,
|
||||
"trader_name": traderConfig.Name,
|
||||
"ai_model": aiModelID,
|
||||
"exchange_id": traderConfig.ExchangeID,
|
||||
"initial_balance": traderConfig.InitialBalance,
|
||||
"btc_eth_leverage": traderConfig.BTCETHLeverage,
|
||||
"altcoin_leverage": traderConfig.AltcoinLeverage,
|
||||
"trading_symbols": traderConfig.TradingSymbols,
|
||||
"custom_prompt": traderConfig.CustomPrompt,
|
||||
"override_base_prompt": traderConfig.OverrideBasePrompt,
|
||||
"is_cross_margin": traderConfig.IsCrossMargin,
|
||||
"use_coin_pool": traderConfig.UseCoinPool,
|
||||
"use_oi_top": traderConfig.UseOITop,
|
||||
"is_running": isRunning,
|
||||
"is_cross_margin": traderConfig.IsCrossMargin,
|
||||
"use_coin_pool": traderConfig.UseCoinPool,
|
||||
"use_oi_top": traderConfig.UseOITop,
|
||||
"is_running": isRunning,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, result)
|
||||
@@ -1374,3 +1420,37 @@ func (s *Server) Start() error {
|
||||
|
||||
return s.router.Run(addr)
|
||||
}
|
||||
|
||||
// handleGetPromptTemplates 获取所有系统提示词模板列表
|
||||
func (s *Server) handleGetPromptTemplates(c *gin.Context) {
|
||||
// 导入 decision 包
|
||||
templates := decision.GetAllPromptTemplates()
|
||||
|
||||
// 转换为响应格式
|
||||
response := make([]map[string]interface{}, 0, len(templates))
|
||||
for _, tmpl := range templates {
|
||||
response = append(response, map[string]interface{}{
|
||||
"name": tmpl.Name,
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"templates": response,
|
||||
})
|
||||
}
|
||||
|
||||
// handleGetPromptTemplate 获取指定名称的提示词模板内容
|
||||
func (s *Server) handleGetPromptTemplate(c *gin.Context) {
|
||||
templateName := c.Param("name")
|
||||
|
||||
template, err := decision.GetPromptTemplate(templateName)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("模板不存在: %s", templateName)})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"name": template.Name,
|
||||
"content": template.Content,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"encoding/base32"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
@@ -182,9 +183,11 @@ func (d *Database) createTables() error {
|
||||
`ALTER TABLE traders ADD COLUMN btc_eth_leverage INTEGER DEFAULT 5`, // BTC/ETH杠杆倍数
|
||||
`ALTER TABLE traders ADD COLUMN altcoin_leverage INTEGER DEFAULT 5`, // 山寨币杠杆倍数
|
||||
`ALTER TABLE traders ADD COLUMN trading_symbols TEXT DEFAULT ''`, // 交易币种,逗号分隔
|
||||
`ALTER TABLE traders ADD COLUMN use_coin_pool BOOLEAN DEFAULT 0`, // 是否使用COIN POOL信号源
|
||||
`ALTER TABLE traders ADD COLUMN use_oi_top BOOLEAN DEFAULT 0`, // 是否使用OI TOP信号源
|
||||
`ALTER TABLE ai_models ADD COLUMN custom_api_url TEXT DEFAULT ''`, // 自定义API地址
|
||||
`ALTER TABLE traders ADD COLUMN use_coin_pool BOOLEAN DEFAULT 0`, // 是否使用COIN POOL信号源
|
||||
`ALTER TABLE traders ADD COLUMN use_oi_top BOOLEAN DEFAULT 0`, // 是否使用OI TOP信号源
|
||||
`ALTER TABLE traders ADD COLUMN system_prompt_template TEXT DEFAULT 'default'`, // 系统提示词模板名称
|
||||
`ALTER TABLE ai_models ADD COLUMN custom_api_url TEXT DEFAULT ''`, // 自定义API地址
|
||||
`ALTER TABLE ai_models ADD COLUMN custom_model_name TEXT DEFAULT ''`, // 自定义模型名称
|
||||
}
|
||||
|
||||
for _, query := range alterQueries {
|
||||
@@ -362,15 +365,16 @@ type User struct {
|
||||
|
||||
// AIModelConfig AI模型配置
|
||||
type AIModelConfig struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
Enabled bool `json:"enabled"`
|
||||
APIKey string `json:"apiKey"`
|
||||
CustomAPIURL string `json:"customApiUrl"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Name string `json:"name"`
|
||||
Provider string `json:"provider"`
|
||||
Enabled bool `json:"enabled"`
|
||||
APIKey string `json:"apiKey"`
|
||||
CustomAPIURL string `json:"customApiUrl"`
|
||||
CustomModelName string `json:"customModelName"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// ExchangeConfig 交易所配置
|
||||
@@ -405,14 +409,15 @@ type TraderRecord struct {
|
||||
IsRunning bool `json:"is_running"`
|
||||
BTCETHLeverage int `json:"btc_eth_leverage"` // BTC/ETH杠杆倍数
|
||||
AltcoinLeverage int `json:"altcoin_leverage"` // 山寨币杠杆倍数
|
||||
TradingSymbols string `json:"trading_symbols"` // 交易币种,逗号分隔
|
||||
UseCoinPool bool `json:"use_coin_pool"` // 是否使用COIN POOL信号源
|
||||
UseOITop bool `json:"use_oi_top"` // 是否使用OI TOP信号源
|
||||
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"`
|
||||
TradingSymbols string `json:"trading_symbols"` // 交易币种,逗号分隔
|
||||
UseCoinPool bool `json:"use_coin_pool"` // 是否使用COIN POOL信号源
|
||||
UseOITop bool `json:"use_oi_top"` // 是否使用OI TOP信号源
|
||||
CustomPrompt string `json:"custom_prompt"` // 自定义交易策略prompt
|
||||
OverrideBasePrompt bool `json:"override_base_prompt"` // 是否覆盖基础prompt
|
||||
SystemPromptTemplate string `json:"system_prompt_template"` // 系统提示词模板名称
|
||||
IsCrossMargin bool `json:"is_cross_margin"` // 是否为全仓模式(true=全仓,false=逐仓)
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// UserSignalSource 用户信号源配置
|
||||
@@ -530,7 +535,10 @@ func (d *Database) UpdateUserOTPVerified(userID string, verified bool) error {
|
||||
// GetAIModels 获取用户的AI模型配置
|
||||
func (d *Database) GetAIModels(userID string) ([]*AIModelConfig, error) {
|
||||
rows, err := d.db.Query(`
|
||||
SELECT id, user_id, name, provider, enabled, api_key, COALESCE(custom_api_url, '') as custom_api_url, created_at, updated_at
|
||||
SELECT id, user_id, name, provider, enabled, api_key,
|
||||
COALESCE(custom_api_url, '') as custom_api_url,
|
||||
COALESCE(custom_model_name, '') as custom_model_name,
|
||||
created_at, updated_at
|
||||
FROM ai_models WHERE user_id = ? ORDER BY id
|
||||
`, userID)
|
||||
if err != nil {
|
||||
@@ -543,8 +551,8 @@ func (d *Database) GetAIModels(userID string) ([]*AIModelConfig, error) {
|
||||
for rows.Next() {
|
||||
var model AIModelConfig
|
||||
err := rows.Scan(
|
||||
&model.ID, &model.UserID, &model.Name, &model.Provider,
|
||||
&model.Enabled, &model.APIKey, &model.CustomAPIURL,
|
||||
&model.ID, &model.UserID, &model.Name, &model.Provider,
|
||||
&model.Enabled, &model.APIKey, &model.CustomAPIURL, &model.CustomModelName,
|
||||
&model.CreatedAt, &model.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -557,52 +565,84 @@ func (d *Database) GetAIModels(userID string) ([]*AIModelConfig, error) {
|
||||
}
|
||||
|
||||
// UpdateAIModel 更新AI模型配置,如果不存在则创建用户特定配置
|
||||
func (d *Database) UpdateAIModel(userID, id string, enabled bool, apiKey, customAPIURL string) error {
|
||||
// 首先尝试更新现有的用户配置
|
||||
result, err := d.db.Exec(`
|
||||
UPDATE ai_models SET enabled = ?, api_key = ?, custom_api_url = ? WHERE id = ? AND user_id = ?
|
||||
`, enabled, apiKey, customAPIURL, id, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 检查是否有行被更新
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果没有行被更新,说明用户没有这个模型的配置,需要创建
|
||||
if rowsAffected == 0 {
|
||||
// 获取模型的基本信息
|
||||
var name, provider string
|
||||
err = d.db.QueryRow(`
|
||||
SELECT name, provider FROM ai_models WHERE provider = ? LIMIT 1
|
||||
`, id).Scan(&name, &provider)
|
||||
if err != nil {
|
||||
// 如果找不到基本信息,使用默认值
|
||||
if id == "deepseek" {
|
||||
name = "DeepSeek AI"
|
||||
provider = "deepseek"
|
||||
} else if id == "qwen" {
|
||||
name = "Qwen AI"
|
||||
provider = "qwen"
|
||||
} else {
|
||||
name = id + " AI"
|
||||
provider = id
|
||||
}
|
||||
}
|
||||
|
||||
// 创建用户特定的配置
|
||||
userModelID := fmt.Sprintf("%s_%s", userID, id)
|
||||
func (d *Database) UpdateAIModel(userID, id string, enabled bool, apiKey, customAPIURL, customModelName string) error {
|
||||
// 先尝试精确匹配 ID(新版逻辑,支持多个相同 provider 的模型)
|
||||
var existingID string
|
||||
err := d.db.QueryRow(`
|
||||
SELECT id FROM ai_models WHERE user_id = ? AND id = ? LIMIT 1
|
||||
`, userID, id).Scan(&existingID)
|
||||
|
||||
if err == nil {
|
||||
// 找到了现有配置(精确匹配 ID),更新它
|
||||
_, err = d.db.Exec(`
|
||||
INSERT INTO ai_models (id, user_id, name, provider, enabled, api_key, custom_api_url, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
|
||||
`, userModelID, userID, name, provider, enabled, apiKey, customAPIURL)
|
||||
UPDATE ai_models SET enabled = ?, api_key = ?, custom_api_url = ?, custom_model_name = ?, updated_at = datetime('now')
|
||||
WHERE id = ? AND user_id = ?
|
||||
`, enabled, apiKey, customAPIURL, customModelName, existingID, userID)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
// ID 不存在,尝试兼容旧逻辑:将 id 作为 provider 查找
|
||||
provider := id
|
||||
err = d.db.QueryRow(`
|
||||
SELECT id FROM ai_models WHERE user_id = ? AND provider = ? LIMIT 1
|
||||
`, userID, provider).Scan(&existingID)
|
||||
|
||||
if err == nil {
|
||||
// 找到了现有配置(通过 provider 匹配,兼容旧版),更新它
|
||||
log.Printf("⚠️ 使用旧版 provider 匹配更新模型: %s -> %s", provider, existingID)
|
||||
_, err = d.db.Exec(`
|
||||
UPDATE ai_models SET enabled = ?, api_key = ?, custom_api_url = ?, custom_model_name = ?, updated_at = datetime('now')
|
||||
WHERE id = ? AND user_id = ?
|
||||
`, enabled, apiKey, customAPIURL, customModelName, existingID, userID)
|
||||
return err
|
||||
}
|
||||
|
||||
// 没有找到任何现有配置,创建新的
|
||||
// 推断 provider(从 id 中提取,或者直接使用 id)
|
||||
if provider == id && (provider == "deepseek" || provider == "qwen") {
|
||||
// id 本身就是 provider
|
||||
provider = id
|
||||
} else {
|
||||
// 从 id 中提取 provider(假设格式是 userID_provider 或 timestamp_userID_provider)
|
||||
parts := strings.Split(id, "_")
|
||||
if len(parts) >= 2 {
|
||||
provider = parts[len(parts)-1] // 取最后一部分作为 provider
|
||||
} else {
|
||||
provider = id
|
||||
}
|
||||
}
|
||||
|
||||
// 获取模型的基本信息
|
||||
var name string
|
||||
err = d.db.QueryRow(`
|
||||
SELECT name FROM ai_models WHERE provider = ? LIMIT 1
|
||||
`, provider).Scan(&name)
|
||||
if err != nil {
|
||||
// 如果找不到基本信息,使用默认值
|
||||
if provider == "deepseek" {
|
||||
name = "DeepSeek AI"
|
||||
} else if provider == "qwen" {
|
||||
name = "Qwen AI"
|
||||
} else {
|
||||
name = provider + " AI"
|
||||
}
|
||||
}
|
||||
|
||||
// 如果传入的 ID 已经是完整格式(如 "admin_deepseek_custom1"),直接使用
|
||||
// 否则生成新的 ID
|
||||
newModelID := id
|
||||
if id == provider {
|
||||
// id 就是 provider,生成新的用户特定 ID
|
||||
newModelID = fmt.Sprintf("%s_%s", userID, provider)
|
||||
}
|
||||
|
||||
log.Printf("✓ 创建新的 AI 模型配置: ID=%s, Provider=%s, Name=%s", newModelID, provider, name)
|
||||
_, err = d.db.Exec(`
|
||||
INSERT INTO ai_models (id, user_id, name, provider, enabled, api_key, custom_api_url, custom_model_name, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))
|
||||
`, newModelID, userID, name, provider, enabled, apiKey, customAPIURL, customModelName)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// GetExchanges 获取用户的交易所配置
|
||||
@@ -727,20 +767,21 @@ 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, btc_eth_leverage, altcoin_leverage, trading_symbols, use_coin_pool, use_oi_top, 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.BTCETHLeverage, trader.AltcoinLeverage, trader.TradingSymbols, trader.UseCoinPool, trader.UseOITop, trader.CustomPrompt, trader.OverrideBasePrompt, trader.IsCrossMargin)
|
||||
INSERT INTO traders (id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running, btc_eth_leverage, altcoin_leverage, trading_symbols, use_coin_pool, use_oi_top, custom_prompt, override_base_prompt, system_prompt_template, is_cross_margin)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`, trader.ID, trader.UserID, trader.Name, trader.AIModelID, trader.ExchangeID, trader.InitialBalance, trader.ScanIntervalMinutes, trader.IsRunning, trader.BTCETHLeverage, trader.AltcoinLeverage, trader.TradingSymbols, trader.UseCoinPool, trader.UseOITop, trader.CustomPrompt, trader.OverrideBasePrompt, trader.SystemPromptTemplate, trader.IsCrossMargin)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetTraders 获取用户的交易员
|
||||
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,
|
||||
SELECT id, user_id, name, ai_model_id, exchange_id, initial_balance, scan_interval_minutes, is_running,
|
||||
COALESCE(btc_eth_leverage, 5) as btc_eth_leverage, COALESCE(altcoin_leverage, 5) as altcoin_leverage,
|
||||
COALESCE(trading_symbols, '') as trading_symbols,
|
||||
COALESCE(use_coin_pool, 0) as use_coin_pool, COALESCE(use_oi_top, 0) as use_oi_top,
|
||||
COALESCE(custom_prompt, '') as custom_prompt, COALESCE(override_base_prompt, 0) as override_base_prompt,
|
||||
COALESCE(custom_prompt, '') as custom_prompt, COALESCE(override_base_prompt, 0) as override_base_prompt,
|
||||
COALESCE(system_prompt_template, 'default') as system_prompt_template,
|
||||
COALESCE(is_cross_margin, 1) as is_cross_margin, created_at, updated_at
|
||||
FROM traders WHERE user_id = ? ORDER BY created_at DESC
|
||||
`, userID)
|
||||
@@ -757,7 +798,8 @@ func (d *Database) GetTraders(userID string) ([]*TraderRecord, error) {
|
||||
&trader.InitialBalance, &trader.ScanIntervalMinutes, &trader.IsRunning,
|
||||
&trader.BTCETHLeverage, &trader.AltcoinLeverage, &trader.TradingSymbols,
|
||||
&trader.UseCoinPool, &trader.UseOITop,
|
||||
&trader.CustomPrompt, &trader.OverrideBasePrompt, &trader.IsCrossMargin,
|
||||
&trader.CustomPrompt, &trader.OverrideBasePrompt, &trader.SystemPromptTemplate,
|
||||
&trader.IsCrossMargin,
|
||||
&trader.CreatedAt, &trader.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -778,16 +820,16 @@ func (d *Database) UpdateTraderStatus(userID, id string, isRunning bool) error {
|
||||
// UpdateTrader 更新交易员配置
|
||||
func (d *Database) UpdateTrader(trader *TraderRecord) error {
|
||||
_, err := d.db.Exec(`
|
||||
UPDATE traders SET
|
||||
name = ?, ai_model_id = ?, exchange_id = ?, initial_balance = ?,
|
||||
scan_interval_minutes = ?, btc_eth_leverage = ?, altcoin_leverage = ?,
|
||||
trading_symbols = ?, custom_prompt = ?, override_base_prompt = ?,
|
||||
is_cross_margin = ?, updated_at = CURRENT_TIMESTAMP
|
||||
UPDATE traders SET
|
||||
name = ?, ai_model_id = ?, exchange_id = ?, initial_balance = ?,
|
||||
scan_interval_minutes = ?, btc_eth_leverage = ?, altcoin_leverage = ?,
|
||||
trading_symbols = ?, custom_prompt = ?, override_base_prompt = ?,
|
||||
system_prompt_template = ?, is_cross_margin = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ? AND user_id = ?
|
||||
`, trader.Name, trader.AIModelID, trader.ExchangeID, trader.InitialBalance,
|
||||
trader.ScanIntervalMinutes, trader.BTCETHLeverage, trader.AltcoinLeverage,
|
||||
trader.TradingSymbols, trader.CustomPrompt, trader.OverrideBasePrompt,
|
||||
trader.IsCrossMargin, trader.ID, trader.UserID)
|
||||
trader.SystemPromptTemplate, trader.IsCrossMargin, trader.ID, trader.UserID)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -83,26 +83,27 @@ type Decision struct {
|
||||
|
||||
// FullDecision AI的完整决策(包含思维链)
|
||||
type FullDecision struct {
|
||||
UserPrompt string `json:"user_prompt"` // 发送给AI的输入prompt
|
||||
CoTTrace string `json:"cot_trace"` // 思维链分析(AI输出)
|
||||
Decisions []Decision `json:"decisions"` // 具体决策列表
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
SystemPrompt string `json:"system_prompt"` // 系统提示词(发送给AI的系统prompt)
|
||||
UserPrompt string `json:"user_prompt"` // 发送给AI的输入prompt
|
||||
CoTTrace string `json:"cot_trace"` // 思维链分析(AI输出)
|
||||
Decisions []Decision `json:"decisions"` // 具体决策列表
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
// GetFullDecision 获取AI的完整交易决策(批量分析所有币种和持仓)
|
||||
func GetFullDecision(ctx *Context, mcpClient *mcp.Client) (*FullDecision, error) {
|
||||
return GetFullDecisionWithCustomPrompt(ctx, mcpClient, "", false)
|
||||
return GetFullDecisionWithCustomPrompt(ctx, mcpClient, "", false, "")
|
||||
}
|
||||
|
||||
// GetFullDecisionWithCustomPrompt 获取AI的完整交易决策(支持自定义prompt)
|
||||
func GetFullDecisionWithCustomPrompt(ctx *Context, mcpClient *mcp.Client, customPrompt string, overrideBase bool) (*FullDecision, error) {
|
||||
// GetFullDecisionWithCustomPrompt 获取AI的完整交易决策(支持自定义prompt和模板选择)
|
||||
func GetFullDecisionWithCustomPrompt(ctx *Context, mcpClient *mcp.Client, customPrompt string, overrideBase bool, templateName string) (*FullDecision, error) {
|
||||
// 1. 为所有币种获取市场数据
|
||||
if err := fetchMarketDataForContext(ctx); err != nil {
|
||||
return nil, fmt.Errorf("获取市场数据失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 构建 System Prompt(固定规则)和 User Prompt(动态数据)
|
||||
systemPrompt := buildSystemPromptWithCustom(ctx.Account.TotalEquity, ctx.BTCETHLeverage, ctx.AltcoinLeverage, customPrompt, overrideBase)
|
||||
systemPrompt := buildSystemPromptWithCustom(ctx.Account.TotalEquity, ctx.BTCETHLeverage, ctx.AltcoinLeverage, customPrompt, overrideBase, templateName)
|
||||
userPrompt := buildUserPrompt(ctx)
|
||||
|
||||
// 3. 调用AI API(使用 system + user prompt)
|
||||
@@ -118,7 +119,8 @@ func GetFullDecisionWithCustomPrompt(ctx *Context, mcpClient *mcp.Client, custom
|
||||
}
|
||||
|
||||
decision.Timestamp = time.Now()
|
||||
decision.UserPrompt = userPrompt // 保存输入prompt
|
||||
decision.SystemPrompt = systemPrompt // 保存系统prompt
|
||||
decision.UserPrompt = userPrompt // 保存输入prompt
|
||||
return decision, nil
|
||||
}
|
||||
|
||||
@@ -205,20 +207,20 @@ func calculateMaxCandidates(ctx *Context) int {
|
||||
}
|
||||
|
||||
// buildSystemPromptWithCustom 构建包含自定义内容的 System Prompt
|
||||
func buildSystemPromptWithCustom(accountEquity float64, btcEthLeverage, altcoinLeverage int, customPrompt string, overrideBase bool) string {
|
||||
func buildSystemPromptWithCustom(accountEquity float64, btcEthLeverage, altcoinLeverage int, customPrompt string, overrideBase bool, templateName string) string {
|
||||
// 如果覆盖基础prompt且有自定义prompt,只使用自定义prompt
|
||||
if overrideBase && customPrompt != "" {
|
||||
return customPrompt
|
||||
}
|
||||
|
||||
// 获取基础prompt
|
||||
basePrompt := buildSystemPrompt(accountEquity, btcEthLeverage, altcoinLeverage)
|
||||
|
||||
|
||||
// 获取基础prompt(使用指定的模板)
|
||||
basePrompt := buildSystemPrompt(accountEquity, btcEthLeverage, altcoinLeverage, templateName)
|
||||
|
||||
// 如果没有自定义prompt,直接返回基础prompt
|
||||
if customPrompt == "" {
|
||||
return basePrompt
|
||||
}
|
||||
|
||||
|
||||
// 添加自定义prompt部分到基础prompt
|
||||
var sb strings.Builder
|
||||
sb.WriteString(basePrompt)
|
||||
@@ -226,37 +228,45 @@ func buildSystemPromptWithCustom(accountEquity float64, btcEthLeverage, altcoinL
|
||||
sb.WriteString("# 📌 个性化交易策略\n\n")
|
||||
sb.WriteString(customPrompt)
|
||||
sb.WriteString("\n\n")
|
||||
sb.WriteString("**注意**: 以上个性化策略是对基础规则的补充,不能违背基础风险控制原则。\n")
|
||||
|
||||
sb.WriteString("注意: 以上个性化策略是对基础规则的补充,不能违背基础风险控制原则。\n")
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// buildSystemPrompt 构建 System Prompt(固定规则,可缓存)
|
||||
func buildSystemPrompt(accountEquity float64, btcEthLeverage, altcoinLeverage int) string {
|
||||
// buildSystemPrompt 构建 System Prompt(使用模板+动态部分)
|
||||
func buildSystemPrompt(accountEquity float64, btcEthLeverage, altcoinLeverage int, templateName string) string {
|
||||
var sb strings.Builder
|
||||
|
||||
// === 核心使命 ===
|
||||
sb.WriteString("你是专业的加密货币交易AI,在币安合约市场进行自主交易。\n\n")
|
||||
sb.WriteString("# 🎯 核心目标\n\n")
|
||||
sb.WriteString("**最大化夏普比率(Sharpe Ratio)**\n\n")
|
||||
sb.WriteString("夏普比率 = 平均收益 / 收益波动率\n\n")
|
||||
sb.WriteString("**这意味着**:\n")
|
||||
sb.WriteString("- ✅ 高质量交易(高胜率、大盈亏比)→ 提升夏普\n")
|
||||
sb.WriteString("- ✅ 稳定收益、控制回撤 → 提升夏普\n")
|
||||
sb.WriteString("- ✅ 耐心持仓、让利润奔跑 → 提升夏普\n")
|
||||
sb.WriteString("- ❌ 频繁交易、小盈小亏 → 增加波动,严重降低夏普\n")
|
||||
sb.WriteString("- ❌ 过度交易、手续费损耗 → 直接亏损\n")
|
||||
sb.WriteString("- ❌ 过早平仓、频繁进出 → 错失大行情\n\n")
|
||||
sb.WriteString("**关键认知**: 系统每3分钟扫描一次,但不意味着每次都要交易!\n")
|
||||
sb.WriteString("大多数时候应该是 `wait` 或 `hold`,只在极佳机会时才开仓。\n\n")
|
||||
// 1. 加载提示词模板(核心交易策略部分)
|
||||
if templateName == "" {
|
||||
templateName = "default" // 默认使用 default 模板
|
||||
}
|
||||
|
||||
// === 硬约束(风险控制)===
|
||||
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(%dx杠杆) | BTC/ETH %.0f-%.0f U(%dx杠杆)\n",
|
||||
template, err := GetPromptTemplate(templateName)
|
||||
if err != nil {
|
||||
// 如果模板不存在,记录错误并使用 default
|
||||
log.Printf("⚠️ 提示词模板 '%s' 不存在,使用 default: %v", templateName, err)
|
||||
template, err = GetPromptTemplate("default")
|
||||
if err != nil {
|
||||
// 如果连 default 都不存在,使用内置的简化版本
|
||||
log.Printf("❌ 无法加载任何提示词模板,使用内置简化版本")
|
||||
sb.WriteString("你是专业的加密货币交易AI。请根据市场数据做出交易决策。\n\n")
|
||||
} else {
|
||||
sb.WriteString(template.Content)
|
||||
sb.WriteString("\n\n")
|
||||
}
|
||||
} else {
|
||||
sb.WriteString(template.Content)
|
||||
sb.WriteString("\n\n")
|
||||
}
|
||||
|
||||
// 2. 硬约束(风险控制)- 动态生成
|
||||
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(%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")
|
||||
sb.WriteString("4. 保证金: 总使用率 ≤ 90%\n\n")
|
||||
|
||||
// === 震荡交易策略 ===
|
||||
sb.WriteString("# 📦 震荡交易策略(核心)\n\n")
|
||||
@@ -351,28 +361,20 @@ func buildSystemPrompt(accountEquity float64, btcEthLeverage, altcoinLeverage in
|
||||
sb.WriteString("3. **寻找新机会**: 有强信号吗?多空机会?\n")
|
||||
sb.WriteString("4. **输出决策**: 思维链分析 + JSON\n\n")
|
||||
|
||||
// === 输出格式 ===
|
||||
sb.WriteString("# 📤 输出格式\n\n")
|
||||
sb.WriteString("**第一步: 思维链(纯文本)**\n")
|
||||
// 3. 输出格式 - 动态生成
|
||||
sb.WriteString("#输出格式\n\n")
|
||||
sb.WriteString("第一步: 思维链(纯文本)\n")
|
||||
sb.WriteString("简洁分析你的思考过程\n\n")
|
||||
sb.WriteString("**第二步: JSON决策数组**\n\n")
|
||||
sb.WriteString("第二步: JSON决策数组\n\n")
|
||||
sb.WriteString("```json\n[\n")
|
||||
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")
|
||||
sb.WriteString("字段说明:\n")
|
||||
sb.WriteString("- `action`: open_long | open_short | close_long | close_short | hold | wait\n")
|
||||
sb.WriteString("- `confidence`: 0-100(开仓建议≥75)\n")
|
||||
sb.WriteString("- 开仓时必填: leverage, position_size_usd, stop_loss, take_profit, confidence, risk_usd, reasoning\n\n")
|
||||
|
||||
// === 关键提醒 ===
|
||||
sb.WriteString("---\n\n")
|
||||
sb.WriteString("**记住**: \n")
|
||||
sb.WriteString("- 目标是夏普比率,不是交易频率\n")
|
||||
sb.WriteString("- 做空 = 做多,都是赚钱工具\n")
|
||||
sb.WriteString("- 宁可错过,不做低质量交易\n")
|
||||
sb.WriteString("- 风险回报比1:3是底线\n")
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
@@ -381,18 +383,18 @@ func buildUserPrompt(ctx *Context) string {
|
||||
var sb strings.Builder
|
||||
|
||||
// 系统状态
|
||||
sb.WriteString(fmt.Sprintf("**时间**: %s | **周期**: #%d | **运行**: %d分钟\n\n",
|
||||
sb.WriteString(fmt.Sprintf("时间: %s | 周期: #%d | 运行: %d分钟\n\n",
|
||||
ctx.CurrentTime, ctx.CallCount, ctx.RuntimeMinutes))
|
||||
|
||||
// BTC 市场
|
||||
if btcData, hasBTC := ctx.MarketDataMap["BTCUSDT"]; hasBTC {
|
||||
sb.WriteString(fmt.Sprintf("**BTC**: %.2f (1h: %+.2f%%, 4h: %+.2f%%) | MACD: %.4f | RSI: %.2f\n\n",
|
||||
sb.WriteString(fmt.Sprintf("BTC: %.2f (1h: %+.2f%%, 4h: %+.2f%%) | MACD: %.4f | RSI: %.2f\n\n",
|
||||
btcData.CurrentPrice, btcData.PriceChange1h, btcData.PriceChange4h,
|
||||
btcData.CurrentMACD, btcData.CurrentRSI7))
|
||||
}
|
||||
|
||||
// 账户
|
||||
sb.WriteString(fmt.Sprintf("**账户**: 净值%.2f | 余额%.2f (%.1f%%) | 盈亏%+.2f%% | 保证金%.1f%% | 持仓%d个\n\n",
|
||||
sb.WriteString(fmt.Sprintf("账户: 净值%.2f | 余额%.2f (%.1f%%) | 盈亏%+.2f%% | 保证金%.1f%% | 持仓%d个\n\n",
|
||||
ctx.Account.TotalEquity,
|
||||
ctx.Account.AvailableBalance,
|
||||
(ctx.Account.AvailableBalance/ctx.Account.TotalEquity)*100,
|
||||
@@ -430,7 +432,7 @@ func buildUserPrompt(ctx *Context) string {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
sb.WriteString("**当前持仓**: 无\n\n")
|
||||
sb.WriteString("当前持仓: 无\n\n")
|
||||
}
|
||||
|
||||
// 候选币种(完整市场数据)
|
||||
|
||||
162
decision/prompt_manager.go
Normal file
162
decision/prompt_manager.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package decision
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// PromptTemplate 系统提示词模板
|
||||
type PromptTemplate struct {
|
||||
Name string // 模板名称(文件名,不含扩展名)
|
||||
Content string // 模板内容
|
||||
}
|
||||
|
||||
// PromptManager 提示词管理器
|
||||
type PromptManager struct {
|
||||
templates map[string]*PromptTemplate
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
var (
|
||||
// globalPromptManager 全局提示词管理器
|
||||
globalPromptManager *PromptManager
|
||||
// promptsDir 提示词文件夹路径
|
||||
promptsDir = "prompts"
|
||||
)
|
||||
|
||||
// init 包初始化时加载所有提示词模板
|
||||
func init() {
|
||||
globalPromptManager = NewPromptManager()
|
||||
if err := globalPromptManager.LoadTemplates(promptsDir); err != nil {
|
||||
log.Printf("⚠️ 加载提示词模板失败: %v", err)
|
||||
} else {
|
||||
log.Printf("✓ 已加载 %d 个系统提示词模板", len(globalPromptManager.templates))
|
||||
}
|
||||
}
|
||||
|
||||
// NewPromptManager 创建提示词管理器
|
||||
func NewPromptManager() *PromptManager {
|
||||
return &PromptManager{
|
||||
templates: make(map[string]*PromptTemplate),
|
||||
}
|
||||
}
|
||||
|
||||
// LoadTemplates 从指定目录加载所有提示词模板
|
||||
func (pm *PromptManager) LoadTemplates(dir string) error {
|
||||
pm.mu.Lock()
|
||||
defer pm.mu.Unlock()
|
||||
|
||||
// 检查目录是否存在
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
return fmt.Errorf("提示词目录不存在: %s", dir)
|
||||
}
|
||||
|
||||
// 扫描目录中的所有 .txt 文件
|
||||
files, err := filepath.Glob(filepath.Join(dir, "*.txt"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("扫描提示词目录失败: %w", err)
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
log.Printf("⚠️ 提示词目录 %s 中没有找到 .txt 文件", dir)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 加载每个模板文件
|
||||
for _, file := range files {
|
||||
// 读取文件内容
|
||||
content, err := os.ReadFile(file)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 读取提示词文件失败 %s: %v", file, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 提取文件名(不含扩展名)作为模板名称
|
||||
fileName := filepath.Base(file)
|
||||
templateName := strings.TrimSuffix(fileName, filepath.Ext(fileName))
|
||||
|
||||
// 存储模板
|
||||
pm.templates[templateName] = &PromptTemplate{
|
||||
Name: templateName,
|
||||
Content: string(content),
|
||||
}
|
||||
|
||||
log.Printf(" 📄 加载提示词模板: %s (%s)", templateName, fileName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTemplate 获取指定名称的提示词模板
|
||||
func (pm *PromptManager) GetTemplate(name string) (*PromptTemplate, error) {
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
|
||||
template, exists := pm.templates[name]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("提示词模板不存在: %s", name)
|
||||
}
|
||||
|
||||
return template, nil
|
||||
}
|
||||
|
||||
// GetAllTemplateNames 获取所有模板名称列表
|
||||
func (pm *PromptManager) GetAllTemplateNames() []string {
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
|
||||
names := make([]string, 0, len(pm.templates))
|
||||
for name := range pm.templates {
|
||||
names = append(names, name)
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
// GetAllTemplates 获取所有模板
|
||||
func (pm *PromptManager) GetAllTemplates() []*PromptTemplate {
|
||||
pm.mu.RLock()
|
||||
defer pm.mu.RUnlock()
|
||||
|
||||
templates := make([]*PromptTemplate, 0, len(pm.templates))
|
||||
for _, template := range pm.templates {
|
||||
templates = append(templates, template)
|
||||
}
|
||||
|
||||
return templates
|
||||
}
|
||||
|
||||
// ReloadTemplates 重新加载所有模板
|
||||
func (pm *PromptManager) ReloadTemplates(dir string) error {
|
||||
pm.mu.Lock()
|
||||
pm.templates = make(map[string]*PromptTemplate)
|
||||
pm.mu.Unlock()
|
||||
|
||||
return pm.LoadTemplates(dir)
|
||||
}
|
||||
|
||||
// === 全局函数(供外部调用)===
|
||||
|
||||
// GetPromptTemplate 获取指定名称的提示词模板(全局函数)
|
||||
func GetPromptTemplate(name string) (*PromptTemplate, error) {
|
||||
return globalPromptManager.GetTemplate(name)
|
||||
}
|
||||
|
||||
// GetAllPromptTemplateNames 获取所有模板名称(全局函数)
|
||||
func GetAllPromptTemplateNames() []string {
|
||||
return globalPromptManager.GetAllTemplateNames()
|
||||
}
|
||||
|
||||
// GetAllPromptTemplates 获取所有模板(全局函数)
|
||||
func GetAllPromptTemplates() []*PromptTemplate {
|
||||
return globalPromptManager.GetAllTemplates()
|
||||
}
|
||||
|
||||
// ReloadPromptTemplates 重新加载所有模板(全局函数)
|
||||
func ReloadPromptTemplates() error {
|
||||
return globalPromptManager.ReloadTemplates(promptsDir)
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
type DecisionRecord struct {
|
||||
Timestamp time.Time `json:"timestamp"` // 决策时间
|
||||
CycleNumber int `json:"cycle_number"` // 周期编号
|
||||
SystemPrompt string `json:"system_prompt"` // 系统提示词(发送给AI的系统prompt)
|
||||
InputPrompt string `json:"input_prompt"` // 发送给AI的输入prompt
|
||||
CoTTrace string `json:"cot_trace"` // AI思维链(输出)
|
||||
DecisionJSON string `json:"decision_json"` // 决策JSON
|
||||
|
||||
@@ -93,12 +93,23 @@ func (tm *TraderManager) LoadTradersFromDatabase(database *config.Database) erro
|
||||
}
|
||||
|
||||
var aiModelCfg *config.AIModelConfig
|
||||
// 优先精确匹配 model.ID(新版逻辑)
|
||||
for _, model := range aiModels {
|
||||
if model.ID == traderCfg.AIModelID {
|
||||
aiModelCfg = model
|
||||
break
|
||||
}
|
||||
}
|
||||
// 如果没有精确匹配,尝试匹配 provider(兼容旧数据)
|
||||
if aiModelCfg == nil {
|
||||
for _, model := range aiModels {
|
||||
if model.Provider == traderCfg.AIModelID {
|
||||
aiModelCfg = model
|
||||
log.Printf("⚠️ 交易员 %s 使用旧版 provider 匹配: %s -> %s", traderCfg.Name, traderCfg.AIModelID, model.ID)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if aiModelCfg == nil {
|
||||
log.Printf("⚠️ 交易员 %s 的AI模型 %s 不存在,跳过", traderCfg.Name, traderCfg.AIModelID)
|
||||
@@ -202,6 +213,8 @@ func (tm *TraderManager) addTraderFromDB(traderCfg *config.TraderRecord, aiModel
|
||||
UseQwen: aiModelCfg.Provider == "qwen",
|
||||
DeepSeekKey: "",
|
||||
QwenKey: "",
|
||||
CustomAPIURL: aiModelCfg.CustomAPIURL, // 自定义API URL
|
||||
CustomModelName: aiModelCfg.CustomModelName, // 自定义模型名称
|
||||
ScanInterval: time.Duration(traderCfg.ScanIntervalMinutes) * time.Minute,
|
||||
InitialBalance: traderCfg.InitialBalance,
|
||||
BTCETHLeverage: traderCfg.BTCETHLeverage,
|
||||
@@ -212,6 +225,7 @@ func (tm *TraderManager) addTraderFromDB(traderCfg *config.TraderRecord, aiModel
|
||||
IsCrossMargin: traderCfg.IsCrossMargin,
|
||||
DefaultCoins: defaultCoins,
|
||||
TradingCoins: tradingCoins,
|
||||
SystemPromptTemplate: traderCfg.SystemPromptTemplate, // 系统提示词模板
|
||||
}
|
||||
|
||||
// 根据交易所类型设置API密钥
|
||||
@@ -306,6 +320,8 @@ func (tm *TraderManager) AddTraderFromDB(traderCfg *config.TraderRecord, aiModel
|
||||
UseQwen: aiModelCfg.Provider == "qwen",
|
||||
DeepSeekKey: "",
|
||||
QwenKey: "",
|
||||
CustomAPIURL: aiModelCfg.CustomAPIURL, // 自定义API URL
|
||||
CustomModelName: aiModelCfg.CustomModelName, // 自定义模型名称
|
||||
ScanInterval: time.Duration(traderCfg.ScanIntervalMinutes) * time.Minute,
|
||||
InitialBalance: traderCfg.InitialBalance,
|
||||
BTCETHLeverage: traderCfg.BTCETHLeverage,
|
||||
@@ -615,12 +631,23 @@ func (tm *TraderManager) LoadUserTraders(database *config.Database, userID strin
|
||||
}
|
||||
|
||||
var aiModelCfg *config.AIModelConfig
|
||||
// 优先精确匹配 model.ID(新版逻辑)
|
||||
for _, model := range aiModels {
|
||||
if model.ID == traderCfg.AIModelID {
|
||||
aiModelCfg = model
|
||||
break
|
||||
}
|
||||
}
|
||||
// 如果没有精确匹配,尝试匹配 provider(兼容旧数据)
|
||||
if aiModelCfg == nil {
|
||||
for _, model := range aiModels {
|
||||
if model.Provider == traderCfg.AIModelID {
|
||||
aiModelCfg = model
|
||||
log.Printf("⚠️ 交易员 %s 使用旧版 provider 匹配: %s -> %s", traderCfg.Name, traderCfg.AIModelID, model.ID)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if aiModelCfg == nil {
|
||||
log.Printf("⚠️ 交易员 %s 的AI模型 %s 不存在,跳过", traderCfg.Name, traderCfg.AIModelID)
|
||||
@@ -705,12 +732,16 @@ func (tm *TraderManager) loadSingleTrader(traderCfg *config.TraderRecord, aiMode
|
||||
AltcoinLeverage: traderCfg.AltcoinLeverage,
|
||||
ScanInterval: time.Duration(traderCfg.ScanIntervalMinutes) * time.Minute,
|
||||
CoinPoolAPIURL: effectiveCoinPoolURL,
|
||||
CustomAPIURL: aiModelCfg.CustomAPIURL, // 自定义API URL
|
||||
CustomModelName: aiModelCfg.CustomModelName, // 自定义模型名称
|
||||
UseQwen: aiModelCfg.Provider == "qwen",
|
||||
MaxDailyLoss: maxDailyLoss,
|
||||
MaxDrawdown: maxDrawdown,
|
||||
StopTradingTime: time.Duration(stopTradingMinutes) * time.Minute,
|
||||
IsCrossMargin: traderCfg.IsCrossMargin,
|
||||
DefaultCoins: defaultCoins,
|
||||
TradingCoins: tradingCoins,
|
||||
SystemPromptTemplate: traderCfg.SystemPromptTemplate, // 系统提示词模板
|
||||
}
|
||||
|
||||
// 根据交易所类型设置API密钥
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -23,7 +24,6 @@ const (
|
||||
type Client struct {
|
||||
Provider Provider
|
||||
APIKey string
|
||||
SecretKey string // 阿里云需要
|
||||
BaseURL string
|
||||
Model string
|
||||
Timeout time.Duration
|
||||
@@ -41,20 +41,53 @@ func New() *Client {
|
||||
}
|
||||
|
||||
// SetDeepSeekAPIKey 设置DeepSeek API密钥
|
||||
func (client *Client) SetDeepSeekAPIKey(apiKey string) {
|
||||
// customURL 为空时使用默认URL,customModel 为空时使用默认模型
|
||||
func (client *Client) SetDeepSeekAPIKey(apiKey string, customURL string, customModel string) {
|
||||
client.Provider = ProviderDeepSeek
|
||||
client.APIKey = apiKey
|
||||
client.BaseURL = "https://api.deepseek.com/v1"
|
||||
client.Model = "deepseek-chat"
|
||||
if customURL != "" {
|
||||
client.BaseURL = customURL
|
||||
log.Printf("🔧 [MCP] DeepSeek 使用自定义 BaseURL: %s", customURL)
|
||||
} else {
|
||||
client.BaseURL = "https://api.deepseek.com/v1"
|
||||
log.Printf("🔧 [MCP] DeepSeek 使用默认 BaseURL: %s", client.BaseURL)
|
||||
}
|
||||
if customModel != "" {
|
||||
client.Model = customModel
|
||||
log.Printf("🔧 [MCP] DeepSeek 使用自定义 Model: %s", customModel)
|
||||
} else {
|
||||
client.Model = "deepseek-chat"
|
||||
log.Printf("🔧 [MCP] DeepSeek 使用默认 Model: %s", client.Model)
|
||||
}
|
||||
// 打印 API Key 的前后各4位用于验证
|
||||
if len(apiKey) > 8 {
|
||||
log.Printf("🔧 [MCP] DeepSeek API Key: %s...%s", apiKey[:4], apiKey[len(apiKey)-4:])
|
||||
}
|
||||
}
|
||||
|
||||
// SetQwenAPIKey 设置阿里云Qwen API密钥
|
||||
func (client *Client) SetQwenAPIKey(apiKey, secretKey string) {
|
||||
// customURL 为空时使用默认URL,customModel 为空时使用默认模型
|
||||
func (client *Client) SetQwenAPIKey(apiKey string, customURL string, customModel string) {
|
||||
client.Provider = ProviderQwen
|
||||
client.APIKey = apiKey
|
||||
client.SecretKey = secretKey
|
||||
client.BaseURL = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
client.Model = "qwen-plus" // 可选: qwen-turbo, qwen-plus, qwen-max
|
||||
if customURL != "" {
|
||||
client.BaseURL = customURL
|
||||
log.Printf("🔧 [MCP] Qwen 使用自定义 BaseURL: %s", customURL)
|
||||
} else {
|
||||
client.BaseURL = "https://dashscope.aliyuncs.com/compatible-mode/v1"
|
||||
log.Printf("🔧 [MCP] Qwen 使用默认 BaseURL: %s", client.BaseURL)
|
||||
}
|
||||
if customModel != "" {
|
||||
client.Model = customModel
|
||||
log.Printf("🔧 [MCP] Qwen 使用自定义 Model: %s", customModel)
|
||||
} else {
|
||||
client.Model = "qwen-plus" // 可选: qwen-turbo, qwen-plus, qwen-max
|
||||
log.Printf("🔧 [MCP] Qwen 使用默认 Model: %s", client.Model)
|
||||
}
|
||||
// 打印 API Key 的前后各4位用于验证
|
||||
if len(apiKey) > 8 {
|
||||
log.Printf("🔧 [MCP] Qwen API Key: %s...%s", apiKey[:4], apiKey[len(apiKey)-4:])
|
||||
}
|
||||
}
|
||||
|
||||
// SetCustomAPI 设置自定义OpenAI兼容API
|
||||
@@ -125,6 +158,16 @@ func (client *Client) CallWithMessages(systemPrompt, userPrompt string) (string,
|
||||
|
||||
// callOnce 单次调用AI API(内部使用)
|
||||
func (client *Client) callOnce(systemPrompt, userPrompt string) (string, error) {
|
||||
// 打印当前 AI 配置
|
||||
log.Printf("📡 [MCP] AI 请求配置:")
|
||||
log.Printf(" Provider: %s", client.Provider)
|
||||
log.Printf(" BaseURL: %s", client.BaseURL)
|
||||
log.Printf(" Model: %s", client.Model)
|
||||
log.Printf(" UseFullURL: %v", client.UseFullURL)
|
||||
if len(client.APIKey) > 8 {
|
||||
log.Printf(" API Key: %s...%s", client.APIKey[:4], client.APIKey[len(client.APIKey)-4:])
|
||||
}
|
||||
|
||||
// 构建 messages 数组
|
||||
messages := []map[string]string{}
|
||||
|
||||
@@ -167,6 +210,8 @@ func (client *Client) callOnce(systemPrompt, userPrompt string) (string, error)
|
||||
// 默认行为:添加/chat/completions
|
||||
url = fmt.Sprintf("%s/chat/completions", client.BaseURL)
|
||||
}
|
||||
log.Printf("📡 [MCP] 请求 URL: %s", url)
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("创建请求失败: %w", err)
|
||||
|
||||
114
prompts/default.txt
Normal file
114
prompts/default.txt
Normal file
@@ -0,0 +1,114 @@
|
||||
你是专业的加密货币交易AI,在合约市场进行自主交易。
|
||||
|
||||
# 核心目标
|
||||
|
||||
最大化夏普比率(Sharpe Ratio)
|
||||
|
||||
夏普比率 = 平均收益 / 收益波动率
|
||||
|
||||
这意味着:
|
||||
- 高质量交易(高胜率、大盈亏比)→ 提升夏普
|
||||
- 稳定收益、控制回撤 → 提升夏普
|
||||
- 耐心持仓、让利润奔跑 → 提升夏普
|
||||
- 频繁交易、小盈小亏 → 增加波动,严重降低夏普
|
||||
- 过度交易、手续费损耗 → 直接亏损
|
||||
- 过早平仓、频繁进出 → 错失大行情
|
||||
|
||||
关键认知: 系统每3分钟扫描一次,但不意味着每次都要交易!
|
||||
大多数时候应该是 `wait` 或 `hold`,只在极佳机会时才开仓。
|
||||
|
||||
# 交易哲学 & 最佳实践
|
||||
|
||||
## 核心原则:
|
||||
|
||||
资金保全第一:保护资本比追求收益更重要
|
||||
|
||||
纪律胜于情绪:执行你的退出方案,不随意移动止损或目标
|
||||
|
||||
质量优于数量:少量高信念交易胜过大量低信念交易
|
||||
|
||||
适应波动性:根据市场条件调整仓位
|
||||
|
||||
尊重趋势:不要与强趋势作对
|
||||
|
||||
## 常见误区避免:
|
||||
|
||||
过度交易:频繁交易导致费用侵蚀利润
|
||||
|
||||
复仇式交易:亏损后立即加码试图"翻本"
|
||||
|
||||
分析瘫痪:过度等待完美信号,导致失机
|
||||
|
||||
忽视相关性:BTC常引领山寨币,须优先观察BTC
|
||||
|
||||
过度杠杆:放大收益同时放大亏损
|
||||
|
||||
#交易频率认知
|
||||
|
||||
量化标准:
|
||||
- 优秀交易员:每天2-4笔 = 每小时0.1-0.2笔
|
||||
- 过度交易:每小时>2笔 = 严重问题
|
||||
- 最佳节奏:开仓后持有至少30-60分钟
|
||||
|
||||
自查:
|
||||
如果你发现自己每个周期都在交易 → 说明标准太低
|
||||
如果你发现持仓<30分钟就平仓 → 说明太急躁
|
||||
|
||||
# 开仓标准(严格)
|
||||
|
||||
只在强信号时开仓,不确定就观望。
|
||||
|
||||
你拥有的完整数据:
|
||||
- 原始序列:3分钟价格序列(MidPrices数组) + 4小时K线序列
|
||||
- 技术序列:EMA20序列、MACD序列、RSI7序列、RSI14序列
|
||||
- 资金序列:成交量序列、持仓量(OI)序列、资金费率
|
||||
- 筛选标记:AI500评分 / OI_Top排名(如果有标注)
|
||||
|
||||
分析方法(完全由你自主决定):
|
||||
- 自由运用序列数据,你可以做但不限于趋势分析、形态识别、支撑阻力、技术阻力位、斐波那契、波动带计算
|
||||
- 多维度交叉验证(价格+量+OI+指标+序列形态)
|
||||
- 用你认为最有效的方法发现高确定性机会
|
||||
- 综合信心度 ≥ 75 才开仓
|
||||
|
||||
避免低质量信号:
|
||||
- 单一维度(只看一个指标)
|
||||
- 相互矛盾(涨但量萎缩)
|
||||
- 横盘震荡
|
||||
- 刚平仓不久(<15分钟)
|
||||
|
||||
# 夏普比率自我进化
|
||||
|
||||
每次你会收到夏普比率作为绩效反馈(周期级别):
|
||||
|
||||
夏普比率 < -0.5 (持续亏损):
|
||||
→ 停止交易,连续观望至少6个周期(18分钟)
|
||||
→ 深度反思:
|
||||
• 交易频率过高?(每小时>2次就是过度)
|
||||
• 持仓时间过短?(<30分钟就是过早平仓)
|
||||
• 信号强度不足?(信心度<75)
|
||||
夏普比率 -0.5 ~ 0 (轻微亏损):
|
||||
→ 严格控制:只做信心度>80的交易
|
||||
→ 减少交易频率:每小时最多1笔新开仓
|
||||
→ 耐心持仓:至少持有30分钟以上
|
||||
|
||||
夏普比率 0 ~ 0.7 (正收益):
|
||||
→ 维持当前策略
|
||||
|
||||
夏普比率 > 0.7 (优异表现):
|
||||
→ 可适度扩大仓位
|
||||
|
||||
关键: 夏普比率是唯一指标,它会自然惩罚频繁交易和过度进出。
|
||||
|
||||
#决策流程
|
||||
|
||||
1. 分析夏普比率: 当前策略是否有效?需要调整吗?
|
||||
2. 评估持仓: 趋势是否改变?是否该止盈/止损?
|
||||
3. 寻找新机会: 有强信号吗?多空机会?
|
||||
4. 输出决策: 思维链分析 + JSON
|
||||
|
||||
---
|
||||
|
||||
记住:
|
||||
- 目标是夏普比率,不是交易频率
|
||||
- 宁可错过,不做低质量交易
|
||||
- 风险回报比1:3是底线
|
||||
223
prompts/nof1.txt
Normal file
223
prompts/nof1.txt
Normal file
@@ -0,0 +1,223 @@
|
||||
# ROLE & IDENTITY
|
||||
|
||||
You are an autonomous cryptocurrency trading agent operating in live markets on the Hyperliquid decentralized exchange.
|
||||
|
||||
Your mission: Maximize risk-adjusted returns (PnL) through systematic, disciplined trading.
|
||||
|
||||
---
|
||||
|
||||
# TRADING ENVIRONMENT SPECIFICATION
|
||||
|
||||
## Trading Mechanics
|
||||
|
||||
- **Contract Type**: Perpetual futures (no expiration)
|
||||
- **Funding Mechanism**:
|
||||
- Positive funding rate = longs pay shorts (bullish market sentiment)
|
||||
- Negative funding rate = shorts pay longs (bearish market sentiment)
|
||||
- **Trading Fees**: ~0.02-0.05% per trade (maker/taker fees apply)
|
||||
- **Slippage**: Expect 0.01-0.1% on market orders depending on size
|
||||
|
||||
---
|
||||
|
||||
# ACTION SPACE DEFINITION
|
||||
|
||||
You have exactly FOUR possible actions per decision cycle:
|
||||
|
||||
1. **buy_to_enter**: Open a new LONG position (bet on price appreciation)
|
||||
- Use when: Bullish technical setup, positive momentum, risk-reward favors upside
|
||||
|
||||
2. **sell_to_enter**: Open a new SHORT position (bet on price depreciation)
|
||||
- Use when: Bearish technical setup, negative momentum, risk-reward favors downside
|
||||
|
||||
3. **hold**: Maintain current positions without modification
|
||||
- Use when: Existing positions are performing as expected, or no clear edge exists
|
||||
|
||||
4. **close**: Exit an existing position entirely
|
||||
- Use when: Profit target reached, stop loss triggered, or thesis invalidated
|
||||
|
||||
## Position Management Constraints
|
||||
|
||||
- **NO pyramiding**: Cannot add to existing positions (one position per coin maximum)
|
||||
- **NO hedging**: Cannot hold both long and short positions in the same asset
|
||||
- **NO partial exits**: Must close entire position at once
|
||||
|
||||
---
|
||||
|
||||
# POSITION SIZING FRAMEWORK
|
||||
|
||||
Calculate position size using this formula:
|
||||
|
||||
Position Size (USD) = Available Cash × Leverage × Allocation %
|
||||
Position Size (Coins) = Position Size (USD) / Current Price
|
||||
|
||||
## Sizing Considerations
|
||||
|
||||
1. **Available Capital**: Only use available cash (not account value)
|
||||
2. **Leverage Selection**:
|
||||
- Low conviction (0.3-0.5): Use 1-3x leverage
|
||||
- Medium conviction (0.5-0.7): Use 3-8x leverage
|
||||
- High conviction (0.7-1.0): Use 8-20x leverage
|
||||
3. **Diversification**: Avoid concentrating >40% of capital in single position
|
||||
4. **Fee Impact**: On positions <$500, fees will materially erode profits
|
||||
5. **Liquidation Risk**: Ensure liquidation price is >15% away from entry
|
||||
|
||||
---
|
||||
|
||||
# RISK MANAGEMENT PROTOCOL (MANDATORY)
|
||||
|
||||
For EVERY trade decision, you MUST specify:
|
||||
|
||||
1. **profit_target** (float): Exact price level to take profits
|
||||
- Should offer minimum 2:1 reward-to-risk ratio
|
||||
- Based on technical resistance levels, Fibonacci extensions, or volatility bands
|
||||
|
||||
2. **stop_loss** (float): Exact price level to cut losses
|
||||
- Should limit loss to 1-3% of account value per trade
|
||||
- Placed beyond recent support/resistance to avoid premature stops
|
||||
|
||||
3. **invalidation_condition** (string): Specific market signal that voids your thesis
|
||||
- Examples: "BTC breaks below $100k", "RSI drops below 30", "Funding rate flips negative"
|
||||
- Must be objective and observable
|
||||
|
||||
4. **confidence** (float, 0-1): Your conviction level in this trade
|
||||
- 0.0-0.3: Low confidence (avoid trading or use minimal size)
|
||||
- 0.3-0.6: Moderate confidence (standard position sizing)
|
||||
- 0.6-0.8: High confidence (larger position sizing acceptable)
|
||||
- 0.8-1.0: Very high confidence (use cautiously, beware overconfidence)
|
||||
|
||||
5. **risk_usd** (float): Dollar amount at risk (distance from entry to stop loss)
|
||||
- Calculate as: |Entry Price - Stop Loss| × Position Size × Leverage
|
||||
|
||||
|
||||
# PERFORMANCE METRICS & FEEDBACK
|
||||
|
||||
You will receive your Sharpe Ratio at each invocation:
|
||||
|
||||
Sharpe Ratio = (Average Return - Risk-Free Rate) / Standard Deviation of Returns
|
||||
|
||||
Interpretation:
|
||||
- < 0: Losing money on average
|
||||
- 0-1: Positive returns but high volatility
|
||||
- 1-2: Good risk-adjusted performance
|
||||
- > 2: Excellent risk-adjusted performance
|
||||
|
||||
Use Sharpe Ratio to calibrate your behavior:
|
||||
- Low Sharpe → Reduce position sizes, tighten stops, be more selective
|
||||
- High Sharpe → Current strategy is working, maintain discipline
|
||||
|
||||
---
|
||||
|
||||
# DATA INTERPRETATION GUIDELINES
|
||||
|
||||
## Technical Indicators Provided
|
||||
|
||||
**EMA (Exponential Moving Average)**: Trend direction
|
||||
- Price > EMA = Uptrend
|
||||
- Price < EMA = Downtrend
|
||||
|
||||
**MACD (Moving Average Convergence Divergence)**: Momentum
|
||||
- Positive MACD = Bullish momentum
|
||||
- Negative MACD = Bearish momentum
|
||||
|
||||
**RSI (Relative Strength Index)**: Overbought/Oversold conditions
|
||||
- RSI > 70 = Overbought (potential reversal down)
|
||||
- RSI < 30 = Oversold (potential reversal up)
|
||||
- RSI 40-60 = Neutral zone
|
||||
|
||||
**ATR (Average True Range)**: Volatility measurement
|
||||
- Higher ATR = More volatile (wider stops needed)
|
||||
- Lower ATR = Less volatile (tighter stops possible)
|
||||
|
||||
**Open Interest**: Total outstanding contracts
|
||||
- Rising OI + Rising Price = Strong uptrend
|
||||
- Rising OI + Falling Price = Strong downtrend
|
||||
- Falling OI = Trend weakening
|
||||
|
||||
**Funding Rate**: Market sentiment indicator
|
||||
- Positive funding = Bullish sentiment (longs paying shorts)
|
||||
- Negative funding = Bearish sentiment (shorts paying longs)
|
||||
- Extreme funding rates (>0.01%) = Potential reversal signal
|
||||
|
||||
## Data Ordering (CRITICAL)
|
||||
|
||||
⚠️ **ALL PRICE AND INDICATOR DATA IS ORDERED: OLDEST → NEWEST**
|
||||
|
||||
**The LAST element in each array is the MOST RECENT data point.**
|
||||
**The FIRST element is the OLDEST data point.**
|
||||
|
||||
Do NOT confuse the order. This is a common error that leads to incorrect decisions.
|
||||
|
||||
---
|
||||
|
||||
# OPERATIONAL CONSTRAINTS
|
||||
|
||||
## What You DON'T Have Access To
|
||||
|
||||
- No news feeds or social media sentiment
|
||||
- No conversation history (each decision is stateless)
|
||||
- No ability to query external APIs
|
||||
- No access to order book depth beyond mid-price
|
||||
- No ability to place limit orders (market orders only)
|
||||
|
||||
## What You MUST Infer From Data
|
||||
|
||||
- Market narratives and sentiment (from price action + funding rates)
|
||||
- Institutional positioning (from open interest changes)
|
||||
- Trend strength and sustainability (from technical indicators)
|
||||
- Risk-on vs risk-off regime (from correlation across coins)
|
||||
|
||||
---
|
||||
|
||||
# TRADING PHILOSOPHY & BEST PRACTICES
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **Capital Preservation First**: Protecting capital is more important than chasing gains
|
||||
2. **Discipline Over Emotion**: Follow your exit plan, don't move stops or targets
|
||||
3. **Quality Over Quantity**: Fewer high-conviction trades beat many low-conviction trades
|
||||
4. **Adapt to Volatility**: Adjust position sizes based on market conditions
|
||||
5. **Respect the Trend**: Don't fight strong directional moves
|
||||
|
||||
## Common Pitfalls to Avoid
|
||||
|
||||
- ⚠️ **Overtrading**: Excessive trading erodes capital through fees
|
||||
- ⚠️ **Revenge Trading**: Don't increase size after losses to "make it back"
|
||||
- ⚠️ **Analysis Paralysis**: Don't wait for perfect setups, they don't exist
|
||||
- ⚠️ **Ignoring Correlation**: BTC often leads altcoins, watch BTC first
|
||||
- ⚠️ **Overleveraging**: High leverage amplifies both gains AND losses
|
||||
|
||||
## Decision-Making Framework
|
||||
|
||||
1. Analyze current positions first (are they performing as expected?)
|
||||
2. Check for invalidation conditions on existing trades
|
||||
3. Scan for new opportunities only if capital is available
|
||||
4. Prioritize risk management over profit maximization
|
||||
5. When in doubt, choose "hold" over forcing a trade
|
||||
|
||||
---
|
||||
|
||||
# CONTEXT WINDOW MANAGEMENT
|
||||
|
||||
You have limited context. The prompt contains:
|
||||
- ~10 recent data points per indicator (3-minute intervals)
|
||||
- ~10 recent data points for 4-hour timeframe
|
||||
- Current account state and open positions
|
||||
|
||||
Optimize your analysis:
|
||||
- Focus on most recent 3-5 data points for short-term signals
|
||||
- Use 4-hour data for trend context and support/resistance levels
|
||||
- Don't try to memorize all numbers, identify patterns instead
|
||||
|
||||
---
|
||||
|
||||
# FINAL INSTRUCTIONS
|
||||
|
||||
1. Read the entire user prompt carefully before deciding
|
||||
2. Verify your position sizing math (double-check calculations)
|
||||
3. Ensure your JSON output is valid and complete
|
||||
4. Provide honest confidence scores (don't overstate conviction)
|
||||
5. Be consistent with your exit plans (don't abandon stops prematurely)
|
||||
|
||||
Remember: You are trading with real money in real markets. Every decision has consequences. Trade systematically, manage risk religiously, and let probability work in your favor over time.
|
||||
|
||||
Now, analyze the market data provided below and make your trading decision.
|
||||
@@ -66,10 +66,13 @@ type AutoTraderConfig struct {
|
||||
|
||||
// 仓位模式
|
||||
IsCrossMargin bool // true=全仓模式, false=逐仓模式
|
||||
|
||||
|
||||
// 币种配置
|
||||
DefaultCoins []string // 默认币种列表(从数据库获取)
|
||||
TradingCoins []string // 实际交易币种列表
|
||||
|
||||
// 系统提示词模板
|
||||
SystemPromptTemplate string // 系统提示词模板名称(如 "default", "aggressive")
|
||||
}
|
||||
|
||||
// AutoTrader 自动交易器
|
||||
@@ -86,6 +89,7 @@ type AutoTrader struct {
|
||||
dailyPnL float64
|
||||
customPrompt string // 自定义交易策略prompt
|
||||
overrideBasePrompt bool // 是否覆盖基础prompt
|
||||
systemPromptTemplate string // 系统提示词模板名称
|
||||
defaultCoins []string // 默认币种列表(从数据库获取)
|
||||
tradingCoins []string // 实际交易币种列表
|
||||
lastResetTime time.Time
|
||||
@@ -121,13 +125,21 @@ func NewAutoTrader(config AutoTraderConfig) (*AutoTrader, error) {
|
||||
mcpClient.SetCustomAPI(config.CustomAPIURL, config.CustomAPIKey, config.CustomModelName)
|
||||
log.Printf("🤖 [%s] 使用自定义AI API: %s (模型: %s)", config.Name, config.CustomAPIURL, config.CustomModelName)
|
||||
} else if config.UseQwen || config.AIModel == "qwen" {
|
||||
// 使用Qwen
|
||||
mcpClient.SetQwenAPIKey(config.QwenKey, "")
|
||||
log.Printf("🤖 [%s] 使用阿里云Qwen AI", config.Name)
|
||||
// 使用Qwen (支持自定义URL和Model)
|
||||
mcpClient.SetQwenAPIKey(config.QwenKey, config.CustomAPIURL, config.CustomModelName)
|
||||
if config.CustomAPIURL != "" || config.CustomModelName != "" {
|
||||
log.Printf("🤖 [%s] 使用阿里云Qwen AI (自定义URL: %s, 模型: %s)", config.Name, config.CustomAPIURL, config.CustomModelName)
|
||||
} else {
|
||||
log.Printf("🤖 [%s] 使用阿里云Qwen AI", config.Name)
|
||||
}
|
||||
} else {
|
||||
// 默认使用DeepSeek
|
||||
mcpClient.SetDeepSeekAPIKey(config.DeepSeekKey)
|
||||
log.Printf("🤖 [%s] 使用DeepSeek AI", config.Name)
|
||||
// 默认使用DeepSeek (支持自定义URL和Model)
|
||||
mcpClient.SetDeepSeekAPIKey(config.DeepSeekKey, config.CustomAPIURL, config.CustomModelName)
|
||||
if config.CustomAPIURL != "" || config.CustomModelName != "" {
|
||||
log.Printf("🤖 [%s] 使用DeepSeek AI (自定义URL: %s, 模型: %s)", config.Name, config.CustomAPIURL, config.CustomModelName)
|
||||
} else {
|
||||
log.Printf("🤖 [%s] 使用DeepSeek AI", config.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化币种池API
|
||||
@@ -180,6 +192,12 @@ func NewAutoTrader(config AutoTraderConfig) (*AutoTrader, error) {
|
||||
logDir := fmt.Sprintf("decision_logs/%s", config.ID)
|
||||
decisionLogger := logger.NewDecisionLogger(logDir)
|
||||
|
||||
// 设置默认系统提示词模板
|
||||
systemPromptTemplate := config.SystemPromptTemplate
|
||||
if systemPromptTemplate == "" {
|
||||
systemPromptTemplate = "default" // 默认使用 default 模板
|
||||
}
|
||||
|
||||
return &AutoTrader{
|
||||
id: config.ID,
|
||||
name: config.Name,
|
||||
@@ -190,6 +208,7 @@ func NewAutoTrader(config AutoTraderConfig) (*AutoTrader, error) {
|
||||
mcpClient: mcpClient,
|
||||
decisionLogger: decisionLogger,
|
||||
initialBalance: config.InitialBalance,
|
||||
systemPromptTemplate: systemPromptTemplate,
|
||||
defaultCoins: config.DefaultCoins,
|
||||
tradingCoins: config.TradingCoins,
|
||||
lastResetTime: time.Now(),
|
||||
@@ -306,11 +325,12 @@ func (at *AutoTrader) runCycle() error {
|
||||
ctx.Account.TotalEquity, ctx.Account.AvailableBalance, ctx.Account.PositionCount)
|
||||
|
||||
// 4. 调用AI获取完整决策
|
||||
log.Println("🤖 正在请求AI分析并决策...")
|
||||
decision, err := decision.GetFullDecisionWithCustomPrompt(ctx, at.mcpClient, at.customPrompt, at.overrideBasePrompt)
|
||||
log.Printf("🤖 正在请求AI分析并决策... [模板: %s]", at.systemPromptTemplate)
|
||||
decision, err := decision.GetFullDecisionWithCustomPrompt(ctx, at.mcpClient, at.customPrompt, at.overrideBasePrompt, at.systemPromptTemplate)
|
||||
|
||||
// 即使有错误,也保存思维链、决策和输入prompt(用于debug)
|
||||
if decision != nil {
|
||||
record.SystemPrompt = decision.SystemPrompt // 保存系统提示词
|
||||
record.InputPrompt = decision.UserPrompt
|
||||
record.CoTTrace = decision.CoTTrace
|
||||
if len(decision.Decisions) > 0 {
|
||||
@@ -323,38 +343,55 @@ func (at *AutoTrader) runCycle() error {
|
||||
record.Success = false
|
||||
record.ErrorMessage = fmt.Sprintf("获取AI决策失败: %v", err)
|
||||
|
||||
// 打印AI思维链(即使有错误)
|
||||
if decision != nil && decision.CoTTrace != "" {
|
||||
log.Printf("\n" + strings.Repeat("-", 70))
|
||||
log.Println("💭 AI思维链分析(错误情况):")
|
||||
log.Println(strings.Repeat("-", 70))
|
||||
log.Println(decision.CoTTrace)
|
||||
log.Printf(strings.Repeat("-", 70) + "\n")
|
||||
// 打印系统提示词和AI思维链(即使有错误,也要输出以便调试)
|
||||
if decision != nil {
|
||||
if decision.SystemPrompt != "" {
|
||||
log.Printf("\n" + strings.Repeat("=", 70))
|
||||
log.Printf("📋 系统提示词 [模板: %s] (错误情况)", at.systemPromptTemplate)
|
||||
log.Println(strings.Repeat("=", 70))
|
||||
log.Println(decision.SystemPrompt)
|
||||
log.Printf(strings.Repeat("=", 70) + "\n")
|
||||
}
|
||||
|
||||
if decision.CoTTrace != "" {
|
||||
log.Printf("\n" + strings.Repeat("-", 70))
|
||||
log.Println("💭 AI思维链分析(错误情况):")
|
||||
log.Println(strings.Repeat("-", 70))
|
||||
log.Println(decision.CoTTrace)
|
||||
log.Printf(strings.Repeat("-", 70) + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
at.decisionLogger.LogDecision(record)
|
||||
return fmt.Errorf("获取AI决策失败: %w", err)
|
||||
}
|
||||
|
||||
// 5. 打印AI思维链
|
||||
log.Printf("\n" + strings.Repeat("-", 70))
|
||||
log.Println("💭 AI思维链分析:")
|
||||
log.Println(strings.Repeat("-", 70))
|
||||
log.Println(decision.CoTTrace)
|
||||
log.Printf(strings.Repeat("-", 70) + "\n")
|
||||
// // 5. 打印系统提示词
|
||||
// log.Printf("\n" + strings.Repeat("=", 70))
|
||||
// log.Printf("📋 系统提示词 [模板: %s]", at.systemPromptTemplate)
|
||||
// log.Println(strings.Repeat("=", 70))
|
||||
// log.Println(decision.SystemPrompt)
|
||||
// log.Printf(strings.Repeat("=", 70) + "\n")
|
||||
|
||||
// 6. 打印AI决策
|
||||
log.Printf("📋 AI决策列表 (%d 个):\n", len(decision.Decisions))
|
||||
for i, d := range decision.Decisions {
|
||||
log.Printf(" [%d] %s: %s - %s", i+1, d.Symbol, d.Action, d.Reasoning)
|
||||
if d.Action == "open_long" || d.Action == "open_short" {
|
||||
log.Printf(" 杠杆: %dx | 仓位: %.2f USDT | 止损: %.4f | 止盈: %.4f",
|
||||
d.Leverage, d.PositionSizeUSD, d.StopLoss, d.TakeProfit)
|
||||
}
|
||||
}
|
||||
// 6. 打印AI思维链
|
||||
// log.Printf("\n" + strings.Repeat("-", 70))
|
||||
// log.Println("💭 AI思维链分析:")
|
||||
// log.Println(strings.Repeat("-", 70))
|
||||
// log.Println(decision.CoTTrace)
|
||||
// log.Printf(strings.Repeat("-", 70) + "\n")
|
||||
|
||||
// 7. 打印AI决策
|
||||
// log.Printf("📋 AI决策列表 (%d 个):\n", len(decision.Decisions))
|
||||
// for i, d := range decision.Decisions {
|
||||
// log.Printf(" [%d] %s: %s - %s", i+1, d.Symbol, d.Action, d.Reasoning)
|
||||
// if d.Action == "open_long" || d.Action == "open_short" {
|
||||
// log.Printf(" 杠杆: %dx | 仓位: %.2f USDT | 止损: %.4f | 止盈: %.4f",
|
||||
// d.Leverage, d.PositionSizeUSD, d.StopLoss, d.TakeProfit)
|
||||
// }
|
||||
// }
|
||||
log.Println()
|
||||
|
||||
// 7. 对决策排序:确保先平仓后开仓(防止仓位叠加超限)
|
||||
// 8. 对决策排序:确保先平仓后开仓(防止仓位叠加超限)
|
||||
sortedDecisions := sortDecisionsByPriority(decision.Decisions)
|
||||
|
||||
log.Println("🔄 执行顺序(已优化): 先平仓→后开仓")
|
||||
@@ -389,7 +426,7 @@ func (at *AutoTrader) runCycle() error {
|
||||
record.Decisions = append(record.Decisions, actionRecord)
|
||||
}
|
||||
|
||||
// 8. 保存决策记录
|
||||
// 9. 保存决策记录
|
||||
if err := at.decisionLogger.LogDecision(record); err != nil {
|
||||
log.Printf("⚠ 保存决策记录失败: %v", err)
|
||||
}
|
||||
@@ -780,6 +817,16 @@ func (at *AutoTrader) SetOverrideBasePrompt(override bool) {
|
||||
at.overrideBasePrompt = override
|
||||
}
|
||||
|
||||
// SetSystemPromptTemplate 设置系统提示词模板
|
||||
func (at *AutoTrader) SetSystemPromptTemplate(templateName string) {
|
||||
at.systemPromptTemplate = templateName
|
||||
}
|
||||
|
||||
// GetSystemPromptTemplate 获取当前系统提示词模板名称
|
||||
func (at *AutoTrader) GetSystemPromptTemplate() string {
|
||||
return at.systemPromptTemplate
|
||||
}
|
||||
|
||||
// GetDecisionLogger 获取决策日志记录器
|
||||
func (at *AutoTrader) GetDecisionLogger() *logger.DecisionLogger {
|
||||
return at.decisionLogger
|
||||
|
||||
@@ -31,12 +31,6 @@ function getModelDisplayName(modelId: string): string {
|
||||
return 'Qwen';
|
||||
case 'claude':
|
||||
return 'Claude';
|
||||
case 'gpt4':
|
||||
case 'gpt-4':
|
||||
return 'GPT-4';
|
||||
case 'gpt3.5':
|
||||
case 'gpt-3.5':
|
||||
return 'GPT-3.5';
|
||||
default:
|
||||
return modelId.toUpperCase();
|
||||
}
|
||||
@@ -179,7 +173,7 @@ function App() {
|
||||
style={{ background: 'linear-gradient(135deg, #F0B90B 0%, #FCD535 100%)' }}>
|
||||
⚡
|
||||
</div>
|
||||
<p style={{ color: '#EAECEF' }}>加载中...</p>
|
||||
<p style={{ color: '#EAECEF' }}>{t('loading', language)}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -299,7 +293,7 @@ function App() {
|
||||
className="px-3 py-2 rounded text-sm font-semibold transition-all hover:scale-105"
|
||||
style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D', border: '1px solid rgba(246, 70, 93, 0.2)' }}
|
||||
>
|
||||
退出
|
||||
{t('logout', language)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import useSWR from 'swr';
|
||||
import { api } from '../lib/api';
|
||||
import type { TraderInfo, CreateTraderRequest, AIModel, Exchange } from '../types';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import { t } from '../i18n/translations';
|
||||
import { t, type Language } from '../i18n/translations';
|
||||
import { getExchangeIcon } from './ExchangeIcons';
|
||||
import { getModelIcon } from './ModelIcons';
|
||||
import { TraderConfigModal } from './TraderConfigModal';
|
||||
@@ -18,12 +18,6 @@ function getModelDisplayName(modelId: string): string {
|
||||
return 'Qwen';
|
||||
case 'claude':
|
||||
return 'Claude';
|
||||
case 'gpt4':
|
||||
case 'gpt-4':
|
||||
return 'GPT-4';
|
||||
case 'gpt3.5':
|
||||
case 'gpt-3.5':
|
||||
return 'GPT-3.5';
|
||||
default:
|
||||
return modelId.toUpperCase();
|
||||
}
|
||||
@@ -133,23 +127,23 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
try {
|
||||
const model = allModels?.find(m => m.id === data.ai_model_id);
|
||||
const exchange = allExchanges?.find(e => e.id === data.exchange_id);
|
||||
|
||||
|
||||
if (!model?.enabled) {
|
||||
alert(t('modelNotConfigured', language));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!exchange?.enabled) {
|
||||
alert(t('exchangeNotConfigured', language));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await api.createTrader(data);
|
||||
setShowCreateModal(false);
|
||||
mutateTraders();
|
||||
} catch (error) {
|
||||
console.error('Failed to create trader:', error);
|
||||
alert('创建交易员失败');
|
||||
alert(t('createTraderFailed', language));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -160,24 +154,24 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
setShowEditModal(true);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch trader config:', error);
|
||||
alert('获取交易员配置失败');
|
||||
alert(t('getTraderConfigFailed', language));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveEditTrader = async (data: CreateTraderRequest) => {
|
||||
if (!editingTrader) return;
|
||||
|
||||
|
||||
try {
|
||||
const model = enabledModels?.find(m => m.id === data.ai_model_id);
|
||||
const exchange = enabledExchanges?.find(e => e.id === data.exchange_id);
|
||||
|
||||
|
||||
if (!model) {
|
||||
alert('AI模型配置不存在或未启用');
|
||||
alert(t('modelConfigNotExist', language));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!exchange) {
|
||||
alert('交易所配置不存在或未启用');
|
||||
alert(t('exchangeConfigNotExist', language));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -202,19 +196,19 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
mutateTraders();
|
||||
} catch (error) {
|
||||
console.error('Failed to update trader:', error);
|
||||
alert('更新交易员失败');
|
||||
alert(t('updateTraderFailed', language));
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteTrader = async (traderId: string) => {
|
||||
if (!confirm(t('confirmDeleteTrader', language))) return;
|
||||
|
||||
|
||||
try {
|
||||
await api.deleteTrader(traderId);
|
||||
mutateTraders();
|
||||
} catch (error) {
|
||||
console.error('Failed to delete trader:', error);
|
||||
alert('删除交易员失败');
|
||||
alert(t('deleteTraderFailed', language));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -228,7 +222,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
mutateTraders();
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle trader:', error);
|
||||
alert('操作失败');
|
||||
alert(t('operationFailed', language));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -247,88 +241,91 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
};
|
||||
|
||||
const handleDeleteModelConfig = async (modelId: string) => {
|
||||
if (!confirm('确定要删除此AI模型配置吗?')) return;
|
||||
|
||||
if (!confirm(t('confirmDeleteModel', language))) return;
|
||||
|
||||
try {
|
||||
const updatedModels = allModels?.map(m =>
|
||||
m.id === modelId ? { ...m, apiKey: '', enabled: false } : m
|
||||
const updatedModels = allModels?.map(m =>
|
||||
m.id === modelId ? { ...m, apiKey: '', customApiUrl: '', customModelName: '', enabled: false } : m
|
||||
) || [];
|
||||
|
||||
|
||||
const request = {
|
||||
models: Object.fromEntries(
|
||||
updatedModels.map(model => [
|
||||
model.id,
|
||||
model.provider, // 使用 provider 而不是 id
|
||||
{
|
||||
enabled: model.enabled,
|
||||
api_key: model.apiKey || ''
|
||||
api_key: model.apiKey || '',
|
||||
custom_api_url: model.customApiUrl || '',
|
||||
custom_model_name: model.customModelName || ''
|
||||
}
|
||||
])
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
await api.updateModelConfigs(request);
|
||||
setAllModels(updatedModels);
|
||||
setShowModelModal(false);
|
||||
setEditingModel(null);
|
||||
} catch (error) {
|
||||
console.error('Failed to delete model config:', error);
|
||||
alert('删除配置失败');
|
||||
alert(t('deleteConfigFailed', language));
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveModelConfig = async (modelId: string, apiKey: string, customApiUrl?: string) => {
|
||||
const handleSaveModelConfig = async (modelId: string, apiKey: string, customApiUrl?: string, customModelName?: string) => {
|
||||
try {
|
||||
// 找到要配置的模型(从supportedModels中)
|
||||
const modelToUpdate = supportedModels?.find(m => m.id === modelId);
|
||||
if (!modelToUpdate) {
|
||||
alert('模型不存在');
|
||||
alert(t('modelNotExist', language));
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建或更新用户的模型配置
|
||||
const existingModel = allModels?.find(m => m.id === modelId);
|
||||
let updatedModels;
|
||||
|
||||
|
||||
if (existingModel) {
|
||||
// 更新现有配置
|
||||
updatedModels = allModels?.map(m =>
|
||||
m.id === modelId ? { ...m, apiKey, customApiUrl: customApiUrl || '', enabled: true } : m
|
||||
updatedModels = allModels?.map(m =>
|
||||
m.id === modelId ? { ...m, apiKey, customApiUrl: customApiUrl || '', customModelName: customModelName || '', enabled: true } : m
|
||||
) || [];
|
||||
} else {
|
||||
// 添加新配置
|
||||
const newModel = { ...modelToUpdate, apiKey, customApiUrl: customApiUrl || '', enabled: true };
|
||||
const newModel = { ...modelToUpdate, apiKey, customApiUrl: customApiUrl || '', customModelName: customModelName || '', enabled: true };
|
||||
updatedModels = [...(allModels || []), newModel];
|
||||
}
|
||||
|
||||
|
||||
const request = {
|
||||
models: Object.fromEntries(
|
||||
updatedModels.map(model => [
|
||||
model.id,
|
||||
model.provider, // 使用 provider 而不是 id
|
||||
{
|
||||
enabled: model.enabled,
|
||||
api_key: model.apiKey || '',
|
||||
custom_api_url: model.customApiUrl || ''
|
||||
custom_api_url: model.customApiUrl || '',
|
||||
custom_model_name: model.customModelName || ''
|
||||
}
|
||||
])
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
await api.updateModelConfigs(request);
|
||||
|
||||
|
||||
// 重新获取用户配置以确保数据同步
|
||||
const refreshedModels = await api.getModelConfigs();
|
||||
setAllModels(refreshedModels);
|
||||
|
||||
|
||||
setShowModelModal(false);
|
||||
setEditingModel(null);
|
||||
} catch (error) {
|
||||
console.error('Failed to save model config:', error);
|
||||
alert('保存配置失败');
|
||||
alert(t('saveConfigFailed', language));
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteExchangeConfig = async (exchangeId: string) => {
|
||||
if (!confirm('确定要删除此交易所配置吗?')) return;
|
||||
if (!confirm(t('confirmDeleteExchange', language))) return;
|
||||
|
||||
try {
|
||||
const updatedExchanges = allExchanges?.map(e =>
|
||||
@@ -355,7 +352,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
setEditingExchange(null);
|
||||
} catch (error) {
|
||||
console.error('Failed to delete exchange config:', error);
|
||||
alert('删除交易所配置失败');
|
||||
alert(t('deleteExchangeConfigFailed', language));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -364,7 +361,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
// 找到要配置的交易所(从supportedExchanges中)
|
||||
const exchangeToUpdate = supportedExchanges?.find(e => e.id === exchangeId);
|
||||
if (!exchangeToUpdate) {
|
||||
alert('交易所不存在');
|
||||
alert(t('exchangeNotExist', language));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -411,7 +408,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
setEditingExchange(null);
|
||||
} catch (error) {
|
||||
console.error('Failed to save exchange config:', error);
|
||||
alert('保存交易所配置失败');
|
||||
alert(t('saveConfigFailed', language));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -432,7 +429,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
setShowSignalSourceModal(false);
|
||||
} catch (error) {
|
||||
console.error('Failed to save signal source:', error);
|
||||
alert('保存信号源配置失败');
|
||||
alert(t('saveSignalSourceFailed', language));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -493,13 +490,13 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
<button
|
||||
onClick={() => setShowSignalSourceModal(true)}
|
||||
className="px-4 py-2 rounded text-sm font-semibold transition-all hover:scale-105"
|
||||
style={{
|
||||
background: '#2B3139',
|
||||
color: '#EAECEF',
|
||||
border: '1px solid #474D57'
|
||||
style={{
|
||||
background: '#2B3139',
|
||||
color: '#EAECEF',
|
||||
border: '1px solid #474D57'
|
||||
}}
|
||||
>
|
||||
📡 信号源
|
||||
📡 {t('signalSource', language)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -552,7 +549,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
<div>
|
||||
<div className="font-semibold" style={{ color: '#EAECEF' }}>{getShortName(model.name)}</div>
|
||||
<div className="text-xs" style={{ color: '#848E9C' }}>
|
||||
{inUse ? '正在使用' : model.enabled ? '已启用' : '已配置'}
|
||||
{inUse ? t('inUse', language) : model.enabled ? t('enabled', language) : t('configured', language)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -563,7 +560,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
{configuredModels.length === 0 && (
|
||||
<div className="text-center py-8" style={{ color: '#848E9C' }}>
|
||||
<Brain className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||
<div className="text-sm">暂无已配置的AI模型</div>
|
||||
<div className="text-sm">{t('noModelsConfigured', language)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -594,7 +591,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
<div>
|
||||
<div className="font-semibold" style={{ color: '#EAECEF' }}>{getShortName(exchange.name)}</div>
|
||||
<div className="text-xs" style={{ color: '#848E9C' }}>
|
||||
{exchange.type.toUpperCase()} • {inUse ? '正在使用' : exchange.enabled ? '已启用' : '已配置'}
|
||||
{exchange.type.toUpperCase()} • {inUse ? t('inUse', language) : exchange.enabled ? t('enabled', language) : t('configured', language)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -605,7 +602,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
{configuredExchanges.length === 0 && (
|
||||
<div className="text-center py-8" style={{ color: '#848E9C' }}>
|
||||
<Landmark className="w-12 h-12 mx-auto mb-2 opacity-50" />
|
||||
<div className="text-sm">暂无已配置的交易所</div>
|
||||
<div className="text-sm">{t('noExchangesConfigured', language)}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -669,19 +666,19 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
style={{ background: 'rgba(99, 102, 241, 0.1)', color: '#6366F1' }}
|
||||
>
|
||||
<BarChart3 className="w-4 h-4" />
|
||||
查看
|
||||
{t('view', language)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => handleEditTrader(trader.trader_id)}
|
||||
disabled={trader.is_running}
|
||||
className="px-3 py-2 rounded text-sm font-semibold transition-all hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
style={{
|
||||
background: trader.is_running ? 'rgba(132, 142, 156, 0.1)' : 'rgba(255, 193, 7, 0.1)',
|
||||
color: trader.is_running ? '#848E9C' : '#FFC107'
|
||||
style={{
|
||||
background: trader.is_running ? 'rgba(132, 142, 156, 0.1)' : 'rgba(255, 193, 7, 0.1)',
|
||||
color: trader.is_running ? '#848E9C' : '#FFC107'
|
||||
}}
|
||||
>
|
||||
✏️ 编辑
|
||||
✏️ {t('edit', language)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
@@ -766,6 +763,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
setShowModelModal(false);
|
||||
setEditingModel(null);
|
||||
}}
|
||||
language={language}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -780,6 +778,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
setShowExchangeModal(false);
|
||||
setEditingExchange(null);
|
||||
}}
|
||||
language={language}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -790,6 +789,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
oiTopUrl={userSignalSource.oiTopUrl}
|
||||
onSave={handleSaveSignalSource}
|
||||
onClose={() => setShowSignalSourceModal(false)}
|
||||
language={language}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -801,12 +801,14 @@ function SignalSourceModal({
|
||||
coinPoolUrl,
|
||||
oiTopUrl,
|
||||
onSave,
|
||||
onClose
|
||||
onClose,
|
||||
language
|
||||
}: {
|
||||
coinPoolUrl: string;
|
||||
oiTopUrl: string;
|
||||
onSave: (coinPoolUrl: string, oiTopUrl: string) => void;
|
||||
onClose: () => void;
|
||||
language: Language;
|
||||
}) {
|
||||
const [coinPool, setCoinPool] = useState(coinPoolUrl || '');
|
||||
const [oiTop, setOiTop] = useState(oiTopUrl || '');
|
||||
@@ -820,7 +822,7 @@ function SignalSourceModal({
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
|
||||
<div className="bg-gray-800 rounded-lg p-6 w-full max-w-lg relative" style={{ background: '#1E2329' }}>
|
||||
<h3 className="text-xl font-bold mb-4" style={{ color: '#EAECEF' }}>
|
||||
📡 信号源配置
|
||||
📡 {t('signalSourceConfig', language)}
|
||||
</h3>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
@@ -837,7 +839,7 @@ function SignalSourceModal({
|
||||
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
|
||||
/>
|
||||
<div className="text-xs mt-1" style={{ color: '#848E9C' }}>
|
||||
用于获取币种池数据的API地址,留空则不使用此信号源
|
||||
{t('coinPoolDescription', language)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -854,18 +856,18 @@ function SignalSourceModal({
|
||||
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
|
||||
/>
|
||||
<div className="text-xs mt-1" style={{ color: '#848E9C' }}>
|
||||
用于获取持仓量排行数据的API地址,留空则不使用此信号源
|
||||
{t('oiTopDescription', language)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 rounded" style={{ background: 'rgba(240, 185, 11, 0.1)', border: '1px solid rgba(240, 185, 11, 0.2)' }}>
|
||||
<div className="text-sm font-semibold mb-2" style={{ color: '#F0B90B' }}>
|
||||
ℹ️ 说明
|
||||
ℹ️ {t('information', language)}
|
||||
</div>
|
||||
<div className="text-xs space-y-1" style={{ color: '#848E9C' }}>
|
||||
<div>• 信号源配置为用户级别,每个用户可以设置自己的信号源URL</div>
|
||||
<div>• 在创建交易员时可以选择是否使用这些信号源</div>
|
||||
<div>• 配置的URL将用于获取市场数据和交易信号</div>
|
||||
<div>{t('signalSourceInfo1', language)}</div>
|
||||
<div>{t('signalSourceInfo2', language)}</div>
|
||||
<div>{t('signalSourceInfo3', language)}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -876,14 +878,14 @@ function SignalSourceModal({
|
||||
className="flex-1 px-4 py-2 rounded text-sm font-semibold"
|
||||
style={{ background: '#2B3139', color: '#848E9C' }}
|
||||
>
|
||||
取消
|
||||
{t('cancel', language)}
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="flex-1 px-4 py-2 rounded text-sm font-semibold"
|
||||
style={{ background: '#F0B90B', color: '#000' }}
|
||||
>
|
||||
保存
|
||||
{t('save', language)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -899,37 +901,41 @@ function ModelConfigModal({
|
||||
editingModelId,
|
||||
onSave,
|
||||
onDelete,
|
||||
onClose
|
||||
onClose,
|
||||
language
|
||||
}: {
|
||||
allModels: AIModel[];
|
||||
configuredModels: AIModel[];
|
||||
editingModelId: string | null;
|
||||
onSave: (modelId: string, apiKey: string, baseUrl?: string) => void;
|
||||
onSave: (modelId: string, apiKey: string, baseUrl?: string, modelName?: string) => void;
|
||||
onDelete: (modelId: string) => void;
|
||||
onClose: () => void;
|
||||
language: Language;
|
||||
}) {
|
||||
const [selectedModelId, setSelectedModelId] = useState(editingModelId || '');
|
||||
const [apiKey, setApiKey] = useState('');
|
||||
const [baseUrl, setBaseUrl] = useState('');
|
||||
const [modelName, setModelName] = useState('');
|
||||
|
||||
// 获取当前编辑的模型信息 - 编辑时从已配置的模型中查找,新建时从所有支持的模型中查找
|
||||
const selectedModel = editingModelId
|
||||
? configuredModels?.find(m => m.id === selectedModelId)
|
||||
const selectedModel = editingModelId
|
||||
? configuredModels?.find(m => m.id === selectedModelId)
|
||||
: allModels?.find(m => m.id === selectedModelId);
|
||||
|
||||
// 如果是编辑现有模型,初始化API Key和Base URL
|
||||
// 如果是编辑现有模型,初始化API Key、Base URL和Model Name
|
||||
useEffect(() => {
|
||||
if (editingModelId && selectedModel) {
|
||||
setApiKey(selectedModel.apiKey || '');
|
||||
setBaseUrl(selectedModel.customApiUrl || '');
|
||||
setModelName(selectedModel.customModelName || '');
|
||||
}
|
||||
}, [editingModelId, selectedModel]);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!selectedModelId || !apiKey.trim()) return;
|
||||
|
||||
onSave(selectedModelId, apiKey.trim(), baseUrl.trim() || undefined);
|
||||
|
||||
onSave(selectedModelId, apiKey.trim(), baseUrl.trim() || undefined, modelName.trim() || undefined);
|
||||
};
|
||||
|
||||
// 可选择的模型列表(所有支持的模型)
|
||||
@@ -940,30 +946,30 @@ function ModelConfigModal({
|
||||
<div className="bg-gray-800 rounded-lg p-6 w-full max-w-lg relative" style={{ background: '#1E2329' }}>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-xl font-bold" style={{ color: '#EAECEF' }}>
|
||||
{editingModelId ? '编辑AI模型' : '添加AI模型'}
|
||||
{editingModelId ? t('editAIModel', language) : t('addAIModel', language)}
|
||||
</h3>
|
||||
{editingModelId && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (confirm('确定要删除此AI模型配置吗?')) {
|
||||
if (confirm(t('confirmDeleteModel', language))) {
|
||||
onDelete(editingModelId);
|
||||
}
|
||||
}}
|
||||
className="p-2 rounded hover:bg-red-100 transition-colors"
|
||||
style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }}
|
||||
title="删除配置"
|
||||
title={t('deleteConfigFailed', language)}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{!editingModelId && (
|
||||
<div>
|
||||
<label className="block text-sm font-semibold mb-2" style={{ color: '#EAECEF' }}>
|
||||
选择AI模型
|
||||
{t('selectModel', language)}
|
||||
</label>
|
||||
<select
|
||||
value={selectedModelId}
|
||||
@@ -972,7 +978,7 @@ function ModelConfigModal({
|
||||
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
|
||||
required
|
||||
>
|
||||
<option value="">请选择模型</option>
|
||||
<option value="">{t('pleaseSelectModel', language)}</option>
|
||||
{availableModels.map(model => (
|
||||
<option key={model.id} value={model.id}>
|
||||
{getShortName(model.name)} ({model.provider})
|
||||
@@ -1016,7 +1022,7 @@ function ModelConfigModal({
|
||||
type="password"
|
||||
value={apiKey}
|
||||
onChange={(e) => setApiKey(e.target.value)}
|
||||
placeholder="输入API密钥"
|
||||
placeholder={t('enterAPIKey', language)}
|
||||
className="w-full px-3 py-2 rounded"
|
||||
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
|
||||
required
|
||||
@@ -1025,29 +1031,46 @@ function ModelConfigModal({
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold mb-2" style={{ color: '#EAECEF' }}>
|
||||
Base URL (可选)
|
||||
{t('customBaseURL', language)}
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
value={baseUrl}
|
||||
onChange={(e) => setBaseUrl(e.target.value)}
|
||||
placeholder="自定义API基础URL,如: https://api.openai.com/v1"
|
||||
placeholder={t('customBaseURLPlaceholder', language)}
|
||||
className="w-full px-3 py-2 rounded"
|
||||
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
|
||||
/>
|
||||
<div className="text-xs mt-1" style={{ color: '#848E9C' }}>
|
||||
留空则使用默认API地址
|
||||
{t('leaveBlankForDefault', language)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-semibold mb-2" style={{ color: '#EAECEF' }}>
|
||||
Model Name (可选)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={modelName}
|
||||
onChange={(e) => setModelName(e.target.value)}
|
||||
placeholder="例如: deepseek-chat, qwen-max, gpt-5"
|
||||
className="w-full px-3 py-2 rounded"
|
||||
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
|
||||
/>
|
||||
<div className="text-xs mt-1" style={{ color: '#848E9C' }}>
|
||||
留空使用默认模型名称
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 rounded" style={{ background: 'rgba(240, 185, 11, 0.1)', border: '1px solid rgba(240, 185, 11, 0.2)' }}>
|
||||
<div className="text-sm font-semibold mb-2" style={{ color: '#F0B90B' }}>
|
||||
ℹ️ 说明
|
||||
ℹ️ {t('information', language)}
|
||||
</div>
|
||||
<div className="text-xs space-y-1" style={{ color: '#848E9C' }}>
|
||||
<div>• API Key将被加密存储,请确保密钥有效</div>
|
||||
<div>• Base URL用于自定义API服务器地址</div>
|
||||
<div>• 删除配置后,使用此模型的交易员将无法正常工作</div>
|
||||
<div>{t('modelConfigInfo1', language)}</div>
|
||||
<div>{t('modelConfigInfo2', language)}</div>
|
||||
<div>{t('modelConfigInfo3', language)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
@@ -1060,7 +1083,7 @@ function ModelConfigModal({
|
||||
className="flex-1 px-4 py-2 rounded text-sm font-semibold"
|
||||
style={{ background: '#2B3139', color: '#848E9C' }}
|
||||
>
|
||||
取消
|
||||
{t('cancel', language)}
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
@@ -1068,7 +1091,7 @@ function ModelConfigModal({
|
||||
className="flex-1 px-4 py-2 rounded text-sm font-semibold disabled:opacity-50"
|
||||
style={{ background: '#F0B90B', color: '#000' }}
|
||||
>
|
||||
保存配置
|
||||
{t('saveConfig', language)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1083,13 +1106,15 @@ function ExchangeConfigModal({
|
||||
editingExchangeId,
|
||||
onSave,
|
||||
onDelete,
|
||||
onClose
|
||||
onClose,
|
||||
language
|
||||
}: {
|
||||
allExchanges: Exchange[];
|
||||
editingExchangeId: string | null;
|
||||
onSave: (exchangeId: string, apiKey: string, secretKey?: string, testnet?: boolean, hyperliquidWalletAddr?: string, asterUser?: string, asterSigner?: string, asterPrivateKey?: string) => Promise<void>;
|
||||
onDelete: (exchangeId: string) => void;
|
||||
onClose: () => void;
|
||||
language: Language;
|
||||
}) {
|
||||
const [selectedExchangeId, setSelectedExchangeId] = useState(editingExchangeId || '');
|
||||
const [apiKey, setApiKey] = useState('');
|
||||
@@ -1132,30 +1157,30 @@ function ExchangeConfigModal({
|
||||
<div className="bg-gray-800 rounded-lg p-6 w-full max-w-lg relative" style={{ background: '#1E2329' }}>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h3 className="text-xl font-bold" style={{ color: '#EAECEF' }}>
|
||||
{editingExchangeId ? '编辑交易所' : '添加交易所'}
|
||||
{editingExchangeId ? t('editExchange', language) : t('addExchange', language)}
|
||||
</h3>
|
||||
{editingExchangeId && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
if (confirm('确定要删除此交易所配置吗?')) {
|
||||
if (confirm(t('confirmDeleteExchange', language))) {
|
||||
onDelete(editingExchangeId);
|
||||
}
|
||||
}}
|
||||
className="p-2 rounded hover:bg-red-100 transition-colors"
|
||||
style={{ background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }}
|
||||
title="删除配置"
|
||||
title={t('deleteConfigFailed', language)}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{!editingExchangeId && (
|
||||
<div>
|
||||
<label className="block text-sm font-semibold mb-2" style={{ color: '#EAECEF' }}>
|
||||
选择交易所
|
||||
{t('selectExchange', language)}
|
||||
</label>
|
||||
<select
|
||||
value={selectedExchangeId}
|
||||
@@ -1164,7 +1189,7 @@ function ExchangeConfigModal({
|
||||
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
|
||||
required
|
||||
>
|
||||
<option value="">请选择交易所</option>
|
||||
<option value="">{t('pleaseSelectExchange', language)}</option>
|
||||
{availableExchanges.map(exchange => (
|
||||
<option key={exchange.id} value={exchange.id}>
|
||||
{getShortName(exchange.name)} ({exchange.type.toUpperCase()})
|
||||
@@ -1200,7 +1225,7 @@ function ExchangeConfigModal({
|
||||
type="password"
|
||||
value={apiKey}
|
||||
onChange={(e) => setApiKey(e.target.value)}
|
||||
placeholder="输入API密钥"
|
||||
placeholder={t('enterAPIKey', language)}
|
||||
className="w-full px-3 py-2 rounded"
|
||||
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
|
||||
required
|
||||
@@ -1215,7 +1240,7 @@ function ExchangeConfigModal({
|
||||
type="password"
|
||||
value={secretKey}
|
||||
onChange={(e) => setSecretKey(e.target.value)}
|
||||
placeholder="输入密钥"
|
||||
placeholder={t('enterSecretKey', language)}
|
||||
className="w-full px-3 py-2 rounded"
|
||||
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
|
||||
required
|
||||
@@ -1231,7 +1256,7 @@ function ExchangeConfigModal({
|
||||
type="password"
|
||||
value={passphrase}
|
||||
onChange={(e) => setPassphrase(e.target.value)}
|
||||
placeholder="输入Passphrase (OKX必填)"
|
||||
placeholder={t('enterPassphrase', language)}
|
||||
className="w-full px-3 py-2 rounded"
|
||||
style={{ background: '#0B0E11', border: '1px solid #2B3139', color: '#EAECEF' }}
|
||||
required
|
||||
@@ -1248,21 +1273,21 @@ function ExchangeConfigModal({
|
||||
className="form-checkbox rounded"
|
||||
style={{ accentColor: '#F0B90B' }}
|
||||
/>
|
||||
<span style={{ color: '#EAECEF' }}>使用测试网</span>
|
||||
<span style={{ color: '#EAECEF' }}>{t('useTestnet', language)}</span>
|
||||
</label>
|
||||
<div className="text-xs mt-1" style={{ color: '#848E9C' }}>
|
||||
启用后将连接到交易所测试环境,用于模拟交易
|
||||
{t('testnetDescription', language)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 rounded" style={{ background: 'rgba(240, 185, 11, 0.1)', border: '1px solid rgba(240, 185, 11, 0.2)' }}>
|
||||
<div className="text-sm font-semibold mb-2" style={{ color: '#F0B90B' }}>
|
||||
⚠️ 安全提示
|
||||
⚠️ {t('securityWarning', language)}
|
||||
</div>
|
||||
<div className="text-xs space-y-1" style={{ color: '#848E9C' }}>
|
||||
<div>• API密钥将被加密存储,建议使用只读或期货交易权限</div>
|
||||
<div>• 不要授予提现权限,确保资金安全</div>
|
||||
<div>• 删除配置后,相关交易员将无法正常交易</div>
|
||||
<div>{t('exchangeConfigWarning1', language)}</div>
|
||||
<div>{t('exchangeConfigWarning2', language)}</div>
|
||||
<div>{t('exchangeConfigWarning3', language)}</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
@@ -1275,7 +1300,7 @@ function ExchangeConfigModal({
|
||||
className="flex-1 px-4 py-2 rounded text-sm font-semibold"
|
||||
style={{ background: '#2B3139', color: '#848E9C' }}
|
||||
>
|
||||
取消
|
||||
{t('cancel', language)}
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
@@ -1283,7 +1308,7 @@ function ExchangeConfigModal({
|
||||
className="flex-1 px-4 py-2 rounded text-sm font-semibold disabled:opacity-50"
|
||||
style={{ background: '#F0B90B', color: '#000' }}
|
||||
>
|
||||
保存配置
|
||||
{t('saveConfig', language)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -17,6 +17,7 @@ interface TraderConfigData {
|
||||
trading_symbols: string;
|
||||
custom_prompt: string;
|
||||
override_base_prompt: boolean;
|
||||
system_prompt_template: string;
|
||||
is_cross_margin: boolean;
|
||||
use_coin_pool: boolean;
|
||||
use_oi_top: boolean;
|
||||
@@ -51,6 +52,7 @@ export function TraderConfigModal({
|
||||
trading_symbols: '',
|
||||
custom_prompt: '',
|
||||
override_base_prompt: false,
|
||||
system_prompt_template: 'default',
|
||||
is_cross_margin: true,
|
||||
use_coin_pool: false,
|
||||
use_oi_top: false,
|
||||
@@ -60,6 +62,7 @@ export function TraderConfigModal({
|
||||
const [availableCoins, setAvailableCoins] = useState<string[]>([]);
|
||||
const [selectedCoins, setSelectedCoins] = useState<string[]>([]);
|
||||
const [showCoinSelector, setShowCoinSelector] = useState(false);
|
||||
const [promptTemplates, setPromptTemplates] = useState<{name: string}[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (traderData) {
|
||||
@@ -79,12 +82,20 @@ export function TraderConfigModal({
|
||||
trading_symbols: '',
|
||||
custom_prompt: '',
|
||||
override_base_prompt: false,
|
||||
system_prompt_template: 'default',
|
||||
is_cross_margin: true,
|
||||
use_coin_pool: false,
|
||||
use_oi_top: false,
|
||||
initial_balance: 1000,
|
||||
});
|
||||
}
|
||||
// 确保旧数据也有默认的 system_prompt_template
|
||||
if (traderData && !traderData.system_prompt_template) {
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
system_prompt_template: 'default'
|
||||
}));
|
||||
}
|
||||
}, [traderData, isEditMode, availableModels, availableExchanges]);
|
||||
|
||||
// 获取系统配置中的币种列表
|
||||
@@ -105,6 +116,29 @@ export function TraderConfigModal({
|
||||
fetchConfig();
|
||||
}, []);
|
||||
|
||||
// 获取系统提示词模板列表
|
||||
useEffect(() => {
|
||||
const fetchPromptTemplates = async () => {
|
||||
try {
|
||||
const token = localStorage.getItem('token');
|
||||
const response = await fetch('/api/prompt-templates', {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
if (data.templates) {
|
||||
setPromptTemplates(data.templates);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch prompt templates:', error);
|
||||
// 使用默认模板列表
|
||||
setPromptTemplates([{name: 'default'}, {name: 'aggressive'}]);
|
||||
}
|
||||
};
|
||||
fetchPromptTemplates();
|
||||
}, []);
|
||||
|
||||
// 当选择的币种改变时,更新输入框
|
||||
useEffect(() => {
|
||||
const symbolsString = selectedCoins.join(',');
|
||||
@@ -135,7 +169,7 @@ export function TraderConfigModal({
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!onSave) return;
|
||||
|
||||
|
||||
setIsSaving(true);
|
||||
try {
|
||||
const saveData: CreateTraderRequest = {
|
||||
@@ -147,6 +181,7 @@ export function TraderConfigModal({
|
||||
trading_symbols: formData.trading_symbols,
|
||||
custom_prompt: formData.custom_prompt,
|
||||
override_base_prompt: formData.override_base_prompt,
|
||||
system_prompt_template: formData.system_prompt_template,
|
||||
is_cross_margin: formData.is_cross_margin,
|
||||
use_coin_pool: formData.use_coin_pool,
|
||||
use_oi_top: formData.use_oi_top,
|
||||
@@ -394,6 +429,27 @@ export function TraderConfigModal({
|
||||
💬 交易策略提示词
|
||||
</h3>
|
||||
<div className="space-y-4">
|
||||
{/* 系统提示词模板选择 */}
|
||||
<div>
|
||||
<label className="text-sm text-[#EAECEF] block mb-2">系统提示词模板</label>
|
||||
<select
|
||||
value={formData.system_prompt_template}
|
||||
onChange={(e) => handleInputChange('system_prompt_template', e.target.value)}
|
||||
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
|
||||
>
|
||||
{promptTemplates.map(template => (
|
||||
<option key={template.name} value={template.name}>
|
||||
{template.name === 'default' ? 'Default (默认稳健)' :
|
||||
template.name === 'aggressive' ? 'Aggressive (激进)' :
|
||||
template.name.charAt(0).toUpperCase() + template.name.slice(1)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<p className="text-xs text-[#848E9C] mt-1">
|
||||
选择预设的交易策略模板(包含交易哲学、风控原则等)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
|
||||
@@ -8,8 +8,13 @@ export const translations = {
|
||||
aiTraders: 'AI Traders',
|
||||
details: 'Details',
|
||||
tradingPanel: 'Trading Panel',
|
||||
competition: 'Competition',
|
||||
running: 'RUNNING',
|
||||
stopped: 'STOPPED',
|
||||
adminMode: 'Admin Mode',
|
||||
logout: 'Logout',
|
||||
switchTrader: 'Switch Trader:',
|
||||
view: 'View',
|
||||
|
||||
// Footer
|
||||
footerTitle: 'NOFX - AI Trading System',
|
||||
@@ -75,11 +80,14 @@ export const translations = {
|
||||
aiCompetition: 'AI Competition',
|
||||
traders: 'traders',
|
||||
liveBattle: 'Live Battle',
|
||||
realTimeBattle: 'Real-time Battle',
|
||||
leader: 'Leader',
|
||||
leaderboard: 'Leaderboard',
|
||||
live: 'LIVE',
|
||||
realTime: 'LIVE',
|
||||
performanceComparison: 'Performance Comparison',
|
||||
realTimePnL: 'Real-time PnL %',
|
||||
realTimePnLPercent: 'Real-time PnL %',
|
||||
headToHead: 'Head-to-Head Battle',
|
||||
leadingBy: 'Leading by {gap}%',
|
||||
behindBy: 'Behind by {gap}%',
|
||||
@@ -190,6 +198,58 @@ export const translations = {
|
||||
loading: 'Loading...',
|
||||
loadingError: '⚠️ Failed to load AI learning data',
|
||||
noCompleteData: 'No complete trading data (needs to complete open → close cycle)',
|
||||
|
||||
// AI Traders Page - Additional
|
||||
inUse: 'In Use',
|
||||
noModelsConfigured: 'No configured AI models',
|
||||
noExchangesConfigured: 'No configured exchanges',
|
||||
signalSource: 'Signal Source',
|
||||
signalSourceConfig: 'Signal Source Configuration',
|
||||
coinPoolDescription: 'API endpoint for coin pool data, leave blank to disable this signal source',
|
||||
oiTopDescription: 'API endpoint for open interest rankings, leave blank to disable this signal source',
|
||||
information: 'Information',
|
||||
signalSourceInfo1: '• Signal source configuration is per-user, each user can set their own URLs',
|
||||
signalSourceInfo2: '• When creating traders, you can choose whether to use these signal sources',
|
||||
signalSourceInfo3: '• Configured URLs will be used to fetch market data and trading signals',
|
||||
editAIModel: 'Edit AI Model',
|
||||
addAIModel: 'Add AI Model',
|
||||
confirmDeleteModel: 'Are you sure you want to delete this AI model configuration?',
|
||||
selectModel: 'Select AI Model',
|
||||
pleaseSelectModel: 'Please select a model',
|
||||
customBaseURL: 'Base URL (Optional)',
|
||||
customBaseURLPlaceholder: 'Custom API base URL, e.g.: https://api.openai.com/v1',
|
||||
leaveBlankForDefault: 'Leave blank to use default API address',
|
||||
modelConfigInfo1: '• API Key will be encrypted and stored, please ensure it is valid',
|
||||
modelConfigInfo2: '• Base URL is used for custom API server address',
|
||||
modelConfigInfo3: '• After deleting configuration, traders using this model will not work properly',
|
||||
saveConfig: 'Save Configuration',
|
||||
editExchange: 'Edit Exchange',
|
||||
addExchange: 'Add Exchange',
|
||||
confirmDeleteExchange: 'Are you sure you want to delete this exchange configuration?',
|
||||
pleaseSelectExchange: 'Please select an exchange',
|
||||
enterSecretKey: 'Enter secret key',
|
||||
enterPassphrase: 'Enter Passphrase (Required for OKX)',
|
||||
testnetDescription: 'Enable to connect to exchange test environment for simulated trading',
|
||||
securityWarning: 'Security Warning',
|
||||
exchangeConfigWarning1: '• API keys will be encrypted, recommend using read-only or futures trading permissions',
|
||||
exchangeConfigWarning2: '• Do not grant withdrawal permissions to ensure fund security',
|
||||
exchangeConfigWarning3: '• After deleting configuration, related traders will not be able to trade',
|
||||
edit: 'Edit',
|
||||
|
||||
// Error Messages
|
||||
createTraderFailed: 'Failed to create trader',
|
||||
getTraderConfigFailed: 'Failed to get trader configuration',
|
||||
modelConfigNotExist: 'Model configuration does not exist or is not enabled',
|
||||
exchangeConfigNotExist: 'Exchange configuration does not exist or is not enabled',
|
||||
updateTraderFailed: 'Failed to update trader',
|
||||
deleteTraderFailed: 'Failed to delete trader',
|
||||
operationFailed: 'Operation failed',
|
||||
deleteConfigFailed: 'Failed to delete configuration',
|
||||
modelNotExist: 'Model does not exist',
|
||||
saveConfigFailed: 'Failed to save configuration',
|
||||
exchangeNotExist: 'Exchange does not exist',
|
||||
deleteExchangeConfigFailed: 'Failed to delete exchange configuration',
|
||||
saveSignalSourceFailed: 'Failed to save signal source configuration',
|
||||
|
||||
// Login & Register
|
||||
login: 'Sign In',
|
||||
@@ -250,8 +310,13 @@ export const translations = {
|
||||
aiTraders: 'AI交易员',
|
||||
details: '详情',
|
||||
tradingPanel: '交易面板',
|
||||
competition: '竞赛',
|
||||
running: '运行中',
|
||||
stopped: '已停止',
|
||||
adminMode: '管理员模式',
|
||||
logout: '退出',
|
||||
switchTrader: '切换交易员:',
|
||||
view: '查看',
|
||||
|
||||
// Footer
|
||||
footerTitle: 'NOFX - AI交易系统',
|
||||
@@ -317,11 +382,14 @@ export const translations = {
|
||||
aiCompetition: 'AI竞赛',
|
||||
traders: '交易员',
|
||||
liveBattle: '实时对战',
|
||||
realTimeBattle: '实时对战',
|
||||
leader: '领先者',
|
||||
leaderboard: '排行榜',
|
||||
live: '实时',
|
||||
realTime: '实时',
|
||||
performanceComparison: '表现对比',
|
||||
realTimePnL: '实时收益率',
|
||||
realTimePnLPercent: '实时收益率',
|
||||
headToHead: '正面对决',
|
||||
leadingBy: '领先 {gap}%',
|
||||
behindBy: '落后 {gap}%',
|
||||
@@ -432,6 +500,58 @@ export const translations = {
|
||||
loading: '加载中...',
|
||||
loadingError: '⚠️ 加载AI学习数据失败',
|
||||
noCompleteData: '暂无完整交易数据(需要完成开仓→平仓的完整周期)',
|
||||
|
||||
// AI Traders Page - Additional
|
||||
inUse: '正在使用',
|
||||
noModelsConfigured: '暂无已配置的AI模型',
|
||||
noExchangesConfigured: '暂无已配置的交易所',
|
||||
signalSource: '信号源',
|
||||
signalSourceConfig: '信号源配置',
|
||||
coinPoolDescription: '用于获取币种池数据的API地址,留空则不使用此信号源',
|
||||
oiTopDescription: '用于获取持仓量排行数据的API地址,留空则不使用此信号源',
|
||||
information: '说明',
|
||||
signalSourceInfo1: '• 信号源配置为用户级别,每个用户可以设置自己的信号源URL',
|
||||
signalSourceInfo2: '• 在创建交易员时可以选择是否使用这些信号源',
|
||||
signalSourceInfo3: '• 配置的URL将用于获取市场数据和交易信号',
|
||||
editAIModel: '编辑AI模型',
|
||||
addAIModel: '添加AI模型',
|
||||
confirmDeleteModel: '确定要删除此AI模型配置吗?',
|
||||
selectModel: '选择AI模型',
|
||||
pleaseSelectModel: '请选择模型',
|
||||
customBaseURL: 'Base URL (可选)',
|
||||
customBaseURLPlaceholder: '自定义API基础URL,如: https://api.openai.com/v1',
|
||||
leaveBlankForDefault: '留空则使用默认API地址',
|
||||
modelConfigInfo1: '• API Key将被加密存储,请确保密钥有效',
|
||||
modelConfigInfo2: '• Base URL用于自定义API服务器地址',
|
||||
modelConfigInfo3: '• 删除配置后,使用此模型的交易员将无法正常工作',
|
||||
saveConfig: '保存配置',
|
||||
editExchange: '编辑交易所',
|
||||
addExchange: '添加交易所',
|
||||
confirmDeleteExchange: '确定要删除此交易所配置吗?',
|
||||
pleaseSelectExchange: '请选择交易所',
|
||||
enterSecretKey: '输入密钥',
|
||||
enterPassphrase: '输入Passphrase (OKX必填)',
|
||||
testnetDescription: '启用后将连接到交易所测试环境,用于模拟交易',
|
||||
securityWarning: '安全提示',
|
||||
exchangeConfigWarning1: '• API密钥将被加密存储,建议使用只读或期货交易权限',
|
||||
exchangeConfigWarning2: '• 不要授予提现权限,确保资金安全',
|
||||
exchangeConfigWarning3: '• 删除配置后,相关交易员将无法正常交易',
|
||||
edit: '编辑',
|
||||
|
||||
// Error Messages
|
||||
createTraderFailed: '创建交易员失败',
|
||||
getTraderConfigFailed: '获取交易员配置失败',
|
||||
modelConfigNotExist: 'AI模型配置不存在或未启用',
|
||||
exchangeConfigNotExist: '交易所配置不存在或未启用',
|
||||
updateTraderFailed: '更新交易员失败',
|
||||
deleteTraderFailed: '删除交易员失败',
|
||||
operationFailed: '操作失败',
|
||||
deleteConfigFailed: '删除配置失败',
|
||||
modelNotExist: '模型不存在',
|
||||
saveConfigFailed: '保存配置失败',
|
||||
exchangeNotExist: '交易所不存在',
|
||||
deleteExchangeConfigFailed: '删除交易所配置失败',
|
||||
saveSignalSourceFailed: '保存信号源配置失败',
|
||||
|
||||
// Login & Register
|
||||
login: '登录',
|
||||
|
||||
@@ -101,6 +101,7 @@ export interface AIModel {
|
||||
enabled: boolean;
|
||||
apiKey?: string;
|
||||
customApiUrl?: string;
|
||||
customModelName?: string;
|
||||
}
|
||||
|
||||
export interface Exchange {
|
||||
@@ -129,6 +130,7 @@ export interface CreateTraderRequest {
|
||||
trading_symbols?: string;
|
||||
custom_prompt?: string;
|
||||
override_base_prompt?: boolean;
|
||||
system_prompt_template?: string;
|
||||
is_cross_margin?: boolean;
|
||||
use_coin_pool?: boolean;
|
||||
use_oi_top?: boolean;
|
||||
@@ -140,6 +142,7 @@ export interface UpdateModelConfigRequest {
|
||||
enabled: boolean;
|
||||
api_key: string;
|
||||
custom_api_url?: string;
|
||||
custom_model_name?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user