mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-05 20:11:13 +08:00
Merge pull request #345 from Icyoung/beta
Beta Dev Merge、Competition fix api
This commit is contained in:
104
api/server.go
104
api/server.go
@@ -220,6 +220,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"`
|
||||
@@ -342,6 +343,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,
|
||||
@@ -359,7 +366,7 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
|
||||
OverrideBasePrompt: req.OverrideBasePrompt,
|
||||
SystemPromptTemplate: systemPromptTemplate,
|
||||
IsCrossMargin: isCrossMargin,
|
||||
ScanIntervalMinutes: 3, // 默认3分钟
|
||||
ScanIntervalMinutes: scanIntervalMinutes,
|
||||
IsRunning: false,
|
||||
}
|
||||
|
||||
@@ -389,16 +396,17 @@ 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"`
|
||||
IsCrossMargin *bool `json:"is_cross_margin"`
|
||||
}
|
||||
|
||||
// handleUpdateTrader 更新交易员配置
|
||||
@@ -447,23 +455,30 @@ func (s *Server) handleUpdateTrader(c *gin.Context) {
|
||||
if altcoinLeverage <= 0 {
|
||||
altcoinLeverage = existingTrader.AltcoinLeverage // 保持原值
|
||||
}
|
||||
|
||||
|
||||
// 设置扫描间隔,允许更新
|
||||
scanIntervalMinutes := req.ScanIntervalMinutes
|
||||
if scanIntervalMinutes <= 0 {
|
||||
scanIntervalMinutes = existingTrader.ScanIntervalMinutes // 保持原值
|
||||
}
|
||||
|
||||
// 更新交易员配置
|
||||
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: existingTrader.SystemPromptTemplate, // 保持原值
|
||||
IsCrossMargin: isCrossMargin,
|
||||
ScanIntervalMinutes: scanIntervalMinutes,
|
||||
IsRunning: existingTrader.IsRunning, // 保持原值
|
||||
}
|
||||
|
||||
// 更新数据库
|
||||
@@ -825,30 +840,25 @@ func (s *Server) handleGetTraderConfig(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// AIModelID 应该已经是 provider(如 "deepseek"),直接使用
|
||||
// 如果是旧数据格式(如 "admin_deepseek"),提取 provider 部分
|
||||
// 返回完整的模型ID,不做转换,保持与前端模型列表一致
|
||||
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": 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)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 446 KiB |
@@ -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,
|
||||
|
||||
@@ -31,11 +31,14 @@ export function ComparisonChart({ traders }: ComparisonChartProps) {
|
||||
const { data: allTraderHistories, isLoading } = useSWR(
|
||||
traders.length > 0 ? `all-equity-histories-${tradersKey}` : null,
|
||||
async () => {
|
||||
// 并发请求所有trader的历史数据
|
||||
const promises = traders.map(trader =>
|
||||
api.getEquityHistory(trader.trader_id)
|
||||
);
|
||||
return Promise.all(promises);
|
||||
// 使用批量API一次性获取所有trader的历史数据
|
||||
const traderIds = traders.map(trader => trader.trader_id);
|
||||
const batchData = await api.getEquityHistoryBatch(traderIds);
|
||||
|
||||
// 转换为原格式,保持与原有代码兼容
|
||||
return traders.map(trader => {
|
||||
return batchData.histories[trader.trader_id] || [];
|
||||
});
|
||||
},
|
||||
{
|
||||
refreshInterval: 30000, // 30秒刷新(对比图表数据更新频率较低)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import type { AIModel, Exchange, CreateTraderRequest } from '../types';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import { t } from '../i18n/translations';
|
||||
|
||||
// 提取下划线后面的名称部分
|
||||
function getShortName(fullName: string): string {
|
||||
@@ -22,6 +24,7 @@ interface TraderConfigData {
|
||||
use_coin_pool: boolean;
|
||||
use_oi_top: boolean;
|
||||
initial_balance: number;
|
||||
scan_interval_minutes: number;
|
||||
}
|
||||
|
||||
interface TraderConfigModalProps {
|
||||
@@ -43,6 +46,7 @@ export function TraderConfigModal({
|
||||
availableExchanges = [],
|
||||
onSave
|
||||
}: TraderConfigModalProps) {
|
||||
const { language } = useLanguage();
|
||||
const [formData, setFormData] = useState<TraderConfigData>({
|
||||
trader_name: '',
|
||||
ai_model: '',
|
||||
@@ -57,6 +61,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 +92,7 @@ export function TraderConfigModal({
|
||||
use_coin_pool: false,
|
||||
use_oi_top: false,
|
||||
initial_balance: 1000,
|
||||
scan_interval_minutes: 3,
|
||||
});
|
||||
}
|
||||
// 确保旧数据也有默认的 system_prompt_template
|
||||
@@ -181,6 +187,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 +326,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">{t('aiScanInterval', language)}</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">{t('scanIntervalRecommend', language)}</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>
|
||||
|
||||
@@ -2,12 +2,12 @@ import { motion } from 'framer-motion'
|
||||
import AnimatedSection from './AnimatedSection'
|
||||
|
||||
interface CardProps {
|
||||
quote: string
|
||||
authorName: string
|
||||
handle: string
|
||||
avatarUrl: string
|
||||
tweetUrl: string
|
||||
delay: number
|
||||
quote: string;
|
||||
authorName: string;
|
||||
handle: string;
|
||||
avatarUrl: string;
|
||||
tweetUrl: string;
|
||||
delay: number;
|
||||
}
|
||||
|
||||
function TestimonialCard({ quote, authorName, delay }: CardProps) {
|
||||
|
||||
@@ -163,6 +163,8 @@ export const translations = {
|
||||
create: 'Create',
|
||||
configureAIModels: 'Configure AI Models',
|
||||
configureExchanges: 'Configure Exchanges',
|
||||
aiScanInterval: 'AI Scan Decision Interval (minutes)',
|
||||
scanIntervalRecommend: 'Recommended: 3-10 minutes',
|
||||
useTestnet: 'Use Testnet',
|
||||
enabled: 'Enabled',
|
||||
save: 'Save',
|
||||
@@ -575,6 +577,8 @@ export const translations = {
|
||||
create: '创建',
|
||||
configureAIModels: '配置AI模型',
|
||||
configureExchanges: '配置交易所',
|
||||
aiScanInterval: 'AI 扫描决策间隔 (分钟)',
|
||||
scanIntervalRecommend: '建议: 3-10分钟',
|
||||
useTestnet: '使用测试网',
|
||||
enabled: '启用',
|
||||
save: '保存',
|
||||
|
||||
@@ -247,6 +247,33 @@ export const api = {
|
||||
return res.json();
|
||||
},
|
||||
|
||||
// 批量获取多个交易员的历史数据(无需认证)
|
||||
async getEquityHistoryBatch(traderIds: string[]): Promise<any> {
|
||||
const res = await fetch(`${API_BASE}/equity-history-batch`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ trader_ids: traderIds }),
|
||||
});
|
||||
if (!res.ok) throw new Error('获取批量历史数据失败');
|
||||
return res.json();
|
||||
},
|
||||
|
||||
// 获取前10名交易员数据(无需认证)
|
||||
async getTopTraders(): Promise<any[]> {
|
||||
const res = await fetch(`${API_BASE}/top-traders`);
|
||||
if (!res.ok) throw new Error('获取前10名交易员失败');
|
||||
return res.json();
|
||||
},
|
||||
|
||||
// 获取公开交易员配置(无需认证)
|
||||
async getPublicTraderConfig(traderId: string): Promise<any> {
|
||||
const res = await fetch(`${API_BASE}/trader/${traderId}/config`);
|
||||
if (!res.ok) throw new Error('获取公开交易员配置失败');
|
||||
return res.json();
|
||||
},
|
||||
|
||||
// 获取AI学习表现分析(支持trader_id)
|
||||
async getPerformance(traderId?: string): Promise<any> {
|
||||
const url = traderId
|
||||
|
||||
@@ -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