mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-02 02:21:19 +08:00
feat: 添加 AI 扫描决策间隔配置支持
## 功能描述 在创建和编辑 Trader 时,支持配置 AI 扫描决策间隔(scan_interval_minutes),允许用户自定义 AI 决策的频率。 ## 修改内容 ### 后端修改 (api/server.go) 1. **CreateTraderRequest** 添加 `ScanIntervalMinutes` 字段 2. **UpdateTraderRequest** 添加 `ScanIntervalMinutes` 字段和 `SystemPromptTemplate` 字段 3. **handleCreateTrader** 处理扫描间隔默认值(默认 3 分钟) 4. **handleUpdateTrader** 支持更新扫描间隔 5. **handleGetTraderConfig** 返回中添加 `scan_interval_minutes` 字段 ### 前端修改 #### web/src/types.ts - `CreateTraderRequest` 添加 `scan_interval_minutes?` 可选字段 - `TraderConfigData` 添加 `scan_interval_minutes` 必填字段 #### web/src/components/TraderConfigModal.tsx - 本地 `TraderConfigData` 接口添加 `scan_interval_minutes` - 初始状态设置默认值为 3 分钟 - 添加 UI 输入框(范围 1-60 分钟) - Label 优化为 "AI 扫描决策间隔 (分钟)" #### web/src/components/AITradersPage.tsx - `handleSaveEditTrader` 的更新请求中添加 `scan_interval_minutes` #### web/src/components/landing/CommunitySection.tsx - 修复 TypeScript 编译错误:定义 `CardProps` 接口 - 修正 `TestimonialCard` 组件的 prop 名称(author → authorName) ## 功能特性 - ✅ 支持 1-60 分钟的自定义间隔 - ✅ 默认值为 3 分钟 - ✅ UI 提示建议范围:3-10 分钟 - ✅ 创建和编辑时均支持配置 - ✅ 后端验证和处理默认值 ## 测试步骤 1. 创建新 Trader,设置自定义扫描间隔(如 10 分钟) 2. 验证 Trader 创建成功 3. 编辑现有 Trader,修改扫描间隔 4. 验证修改保存成功 5. 确认 AI 决策按照新的间隔执行
This commit is contained in:
103
api/server.go
103
api/server.go
@@ -210,6 +210,7 @@ type CreateTraderRequest struct {
|
||||
AIModelID string `json:"ai_model_id" binding:"required"`
|
||||
ExchangeID string `json:"exchange_id" binding:"required"`
|
||||
InitialBalance float64 `json:"initial_balance"`
|
||||
ScanIntervalMinutes int `json:"scan_interval_minutes"`
|
||||
BTCETHLeverage int `json:"btc_eth_leverage"`
|
||||
AltcoinLeverage int `json:"altcoin_leverage"`
|
||||
TradingSymbols string `json:"trading_symbols"`
|
||||
@@ -332,6 +333,12 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
|
||||
systemPromptTemplate = req.SystemPromptTemplate
|
||||
}
|
||||
|
||||
// 设置扫描间隔默认值
|
||||
scanIntervalMinutes := req.ScanIntervalMinutes
|
||||
if scanIntervalMinutes <= 0 {
|
||||
scanIntervalMinutes = 3 // 默认3分钟
|
||||
}
|
||||
|
||||
// 创建交易员配置(数据库实体)
|
||||
trader := &config.TraderRecord{
|
||||
ID: traderID,
|
||||
@@ -349,7 +356,7 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
|
||||
OverrideBasePrompt: req.OverrideBasePrompt,
|
||||
SystemPromptTemplate: systemPromptTemplate,
|
||||
IsCrossMargin: isCrossMargin,
|
||||
ScanIntervalMinutes: 3, // 默认3分钟
|
||||
ScanIntervalMinutes: scanIntervalMinutes,
|
||||
IsRunning: false,
|
||||
}
|
||||
|
||||
@@ -379,16 +386,18 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
|
||||
|
||||
// UpdateTraderRequest 更新交易员请求
|
||||
type UpdateTraderRequest 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"`
|
||||
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"`
|
||||
ScanIntervalMinutes int `json:"scan_interval_minutes"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// handleUpdateTrader 更新交易员配置
|
||||
@@ -437,23 +446,36 @@ func (s *Server) handleUpdateTrader(c *gin.Context) {
|
||||
if altcoinLeverage <= 0 {
|
||||
altcoinLeverage = existingTrader.AltcoinLeverage // 保持原值
|
||||
}
|
||||
|
||||
|
||||
// 设置扫描间隔,允许更新
|
||||
scanIntervalMinutes := req.ScanIntervalMinutes
|
||||
if scanIntervalMinutes <= 0 {
|
||||
scanIntervalMinutes = existingTrader.ScanIntervalMinutes // 保持原值
|
||||
}
|
||||
|
||||
// 设置系统提示词模板,允许更新
|
||||
systemPromptTemplate := req.SystemPromptTemplate
|
||||
if systemPromptTemplate == "" {
|
||||
systemPromptTemplate = existingTrader.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,
|
||||
CustomPrompt: req.CustomPrompt,
|
||||
OverrideBasePrompt: req.OverrideBasePrompt,
|
||||
IsCrossMargin: isCrossMargin,
|
||||
ScanIntervalMinutes: existingTrader.ScanIntervalMinutes, // 保持原值
|
||||
IsRunning: existingTrader.IsRunning, // 保持原值
|
||||
ID: traderID,
|
||||
UserID: userID,
|
||||
Name: req.Name,
|
||||
AIModelID: req.AIModelID,
|
||||
ExchangeID: req.ExchangeID,
|
||||
InitialBalance: req.InitialBalance,
|
||||
BTCETHLeverage: btcEthLeverage,
|
||||
AltcoinLeverage: altcoinLeverage,
|
||||
TradingSymbols: req.TradingSymbols,
|
||||
CustomPrompt: req.CustomPrompt,
|
||||
OverrideBasePrompt: req.OverrideBasePrompt,
|
||||
SystemPromptTemplate: systemPromptTemplate,
|
||||
IsCrossMargin: isCrossMargin,
|
||||
ScanIntervalMinutes: scanIntervalMinutes,
|
||||
IsRunning: existingTrader.IsRunning, // 保持原值
|
||||
}
|
||||
|
||||
// 更新数据库
|
||||
@@ -805,20 +827,21 @@ func (s *Server) handleGetTraderConfig(c *gin.Context) {
|
||||
aiModelID := traderConfig.AIModelID
|
||||
|
||||
result := map[string]interface{}{
|
||||
"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,
|
||||
"trader_id": traderConfig.ID,
|
||||
"trader_name": traderConfig.Name,
|
||||
"ai_model": aiModelID,
|
||||
"exchange_id": traderConfig.ExchangeID,
|
||||
"initial_balance": traderConfig.InitialBalance,
|
||||
"scan_interval_minutes": traderConfig.ScanIntervalMinutes,
|
||||
"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,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, result)
|
||||
|
||||
@@ -183,6 +183,7 @@ export function AITradersPage({ onTraderSelect }: AITradersPageProps) {
|
||||
ai_model_id: data.ai_model_id,
|
||||
exchange_id: data.exchange_id,
|
||||
initial_balance: data.initial_balance,
|
||||
scan_interval_minutes: data.scan_interval_minutes,
|
||||
btc_eth_leverage: data.btc_eth_leverage,
|
||||
altcoin_leverage: data.altcoin_leverage,
|
||||
trading_symbols: data.trading_symbols,
|
||||
|
||||
@@ -22,6 +22,7 @@ interface TraderConfigData {
|
||||
use_coin_pool: boolean;
|
||||
use_oi_top: boolean;
|
||||
initial_balance: number;
|
||||
scan_interval_minutes: number;
|
||||
}
|
||||
|
||||
interface TraderConfigModalProps {
|
||||
@@ -57,6 +58,7 @@ export function TraderConfigModal({
|
||||
use_coin_pool: false,
|
||||
use_oi_top: false,
|
||||
initial_balance: 1000,
|
||||
scan_interval_minutes: 3,
|
||||
});
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [availableCoins, setAvailableCoins] = useState<string[]>([]);
|
||||
@@ -87,6 +89,7 @@ export function TraderConfigModal({
|
||||
use_coin_pool: false,
|
||||
use_oi_top: false,
|
||||
initial_balance: 1000,
|
||||
scan_interval_minutes: 3,
|
||||
});
|
||||
}
|
||||
// 确保旧数据也有默认的 system_prompt_template
|
||||
@@ -181,6 +184,7 @@ export function TraderConfigModal({
|
||||
use_coin_pool: formData.use_coin_pool,
|
||||
use_oi_top: formData.use_oi_top,
|
||||
initial_balance: formData.initial_balance,
|
||||
scan_interval_minutes: formData.scan_interval_minutes,
|
||||
};
|
||||
await onSave(saveData);
|
||||
onClose();
|
||||
@@ -319,7 +323,25 @@ export function TraderConfigModal({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 第二行:杠杆设置 */}
|
||||
{/* 第二行:AI 扫描决策间隔 */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-[#EAECEF] block mb-2">AI 扫描决策间隔 (分钟)</label>
|
||||
<input
|
||||
type="number"
|
||||
value={formData.scan_interval_minutes}
|
||||
onChange={(e) => handleInputChange('scan_interval_minutes', Number(e.target.value))}
|
||||
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
|
||||
min="1"
|
||||
max="60"
|
||||
step="1"
|
||||
/>
|
||||
<p className="text-xs text-gray-500 mt-1">建议: 3-10分钟</p>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
|
||||
{/* 第三行:杠杆设置 */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm text-[#EAECEF] block mb-2">BTC/ETH 杠杆</label>
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { motion } from 'framer-motion'
|
||||
import AnimatedSection from './AnimatedSection'
|
||||
|
||||
function TestimonialCard({ quote, author, delay }: any) {
|
||||
interface CardProps {
|
||||
quote: string;
|
||||
authorName: string;
|
||||
handle: string;
|
||||
avatarUrl: string;
|
||||
tweetUrl: string;
|
||||
delay: number;
|
||||
}
|
||||
|
||||
function TestimonialCard({ quote, authorName, delay }: CardProps) {
|
||||
return (
|
||||
<motion.div
|
||||
className='p-6 rounded-xl'
|
||||
@@ -18,7 +27,7 @@ function TestimonialCard({ quote, author, delay }: any) {
|
||||
<div className='flex items-center gap-2'>
|
||||
<div className='w-8 h-8 rounded-full' style={{ background: 'var(--binance-yellow)' }} />
|
||||
<span className='text-sm font-semibold' style={{ color: 'var(--text-secondary)' }}>
|
||||
{author}
|
||||
{authorName}
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@@ -125,6 +125,7 @@ export interface CreateTraderRequest {
|
||||
ai_model_id: string;
|
||||
exchange_id: string;
|
||||
initial_balance: number;
|
||||
scan_interval_minutes?: number;
|
||||
btc_eth_leverage?: number;
|
||||
altcoin_leverage?: number;
|
||||
trading_symbols?: string;
|
||||
@@ -198,5 +199,6 @@ export interface TraderConfigData {
|
||||
use_coin_pool: boolean;
|
||||
use_oi_top: boolean;
|
||||
initial_balance: number;
|
||||
scan_interval_minutes: number;
|
||||
is_running: boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user