mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-17 09:24:36 +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"`
|
AIModelID string `json:"ai_model_id" binding:"required"`
|
||||||
ExchangeID string `json:"exchange_id" binding:"required"`
|
ExchangeID string `json:"exchange_id" binding:"required"`
|
||||||
InitialBalance float64 `json:"initial_balance"`
|
InitialBalance float64 `json:"initial_balance"`
|
||||||
|
ScanIntervalMinutes int `json:"scan_interval_minutes"`
|
||||||
BTCETHLeverage int `json:"btc_eth_leverage"`
|
BTCETHLeverage int `json:"btc_eth_leverage"`
|
||||||
AltcoinLeverage int `json:"altcoin_leverage"`
|
AltcoinLeverage int `json:"altcoin_leverage"`
|
||||||
TradingSymbols string `json:"trading_symbols"`
|
TradingSymbols string `json:"trading_symbols"`
|
||||||
@@ -342,6 +343,12 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
|
|||||||
systemPromptTemplate = req.SystemPromptTemplate
|
systemPromptTemplate = req.SystemPromptTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置扫描间隔默认值
|
||||||
|
scanIntervalMinutes := req.ScanIntervalMinutes
|
||||||
|
if scanIntervalMinutes <= 0 {
|
||||||
|
scanIntervalMinutes = 3 // 默认3分钟
|
||||||
|
}
|
||||||
|
|
||||||
// 创建交易员配置(数据库实体)
|
// 创建交易员配置(数据库实体)
|
||||||
trader := &config.TraderRecord{
|
trader := &config.TraderRecord{
|
||||||
ID: traderID,
|
ID: traderID,
|
||||||
@@ -359,7 +366,7 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
|
|||||||
OverrideBasePrompt: req.OverrideBasePrompt,
|
OverrideBasePrompt: req.OverrideBasePrompt,
|
||||||
SystemPromptTemplate: systemPromptTemplate,
|
SystemPromptTemplate: systemPromptTemplate,
|
||||||
IsCrossMargin: isCrossMargin,
|
IsCrossMargin: isCrossMargin,
|
||||||
ScanIntervalMinutes: 3, // 默认3分钟
|
ScanIntervalMinutes: scanIntervalMinutes,
|
||||||
IsRunning: false,
|
IsRunning: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,16 +396,17 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
|
|||||||
|
|
||||||
// UpdateTraderRequest 更新交易员请求
|
// UpdateTraderRequest 更新交易员请求
|
||||||
type UpdateTraderRequest struct {
|
type UpdateTraderRequest struct {
|
||||||
Name string `json:"name" binding:"required"`
|
Name string `json:"name" binding:"required"`
|
||||||
AIModelID string `json:"ai_model_id" binding:"required"`
|
AIModelID string `json:"ai_model_id" binding:"required"`
|
||||||
ExchangeID string `json:"exchange_id" binding:"required"`
|
ExchangeID string `json:"exchange_id" binding:"required"`
|
||||||
InitialBalance float64 `json:"initial_balance"`
|
InitialBalance float64 `json:"initial_balance"`
|
||||||
BTCETHLeverage int `json:"btc_eth_leverage"`
|
ScanIntervalMinutes int `json:"scan_interval_minutes"`
|
||||||
AltcoinLeverage int `json:"altcoin_leverage"`
|
BTCETHLeverage int `json:"btc_eth_leverage"`
|
||||||
TradingSymbols string `json:"trading_symbols"`
|
AltcoinLeverage int `json:"altcoin_leverage"`
|
||||||
CustomPrompt string `json:"custom_prompt"`
|
TradingSymbols string `json:"trading_symbols"`
|
||||||
OverrideBasePrompt bool `json:"override_base_prompt"`
|
CustomPrompt string `json:"custom_prompt"`
|
||||||
IsCrossMargin *bool `json:"is_cross_margin"`
|
OverrideBasePrompt bool `json:"override_base_prompt"`
|
||||||
|
IsCrossMargin *bool `json:"is_cross_margin"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleUpdateTrader 更新交易员配置
|
// handleUpdateTrader 更新交易员配置
|
||||||
@@ -447,23 +455,30 @@ func (s *Server) handleUpdateTrader(c *gin.Context) {
|
|||||||
if altcoinLeverage <= 0 {
|
if altcoinLeverage <= 0 {
|
||||||
altcoinLeverage = existingTrader.AltcoinLeverage // 保持原值
|
altcoinLeverage = existingTrader.AltcoinLeverage // 保持原值
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置扫描间隔,允许更新
|
||||||
|
scanIntervalMinutes := req.ScanIntervalMinutes
|
||||||
|
if scanIntervalMinutes <= 0 {
|
||||||
|
scanIntervalMinutes = existingTrader.ScanIntervalMinutes // 保持原值
|
||||||
|
}
|
||||||
|
|
||||||
// 更新交易员配置
|
// 更新交易员配置
|
||||||
trader := &config.TraderRecord{
|
trader := &config.TraderRecord{
|
||||||
ID: traderID,
|
ID: traderID,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
AIModelID: req.AIModelID,
|
AIModelID: req.AIModelID,
|
||||||
ExchangeID: req.ExchangeID,
|
ExchangeID: req.ExchangeID,
|
||||||
InitialBalance: req.InitialBalance,
|
InitialBalance: req.InitialBalance,
|
||||||
BTCETHLeverage: btcEthLeverage,
|
BTCETHLeverage: btcEthLeverage,
|
||||||
AltcoinLeverage: altcoinLeverage,
|
AltcoinLeverage: altcoinLeverage,
|
||||||
TradingSymbols: req.TradingSymbols,
|
TradingSymbols: req.TradingSymbols,
|
||||||
CustomPrompt: req.CustomPrompt,
|
CustomPrompt: req.CustomPrompt,
|
||||||
OverrideBasePrompt: req.OverrideBasePrompt,
|
OverrideBasePrompt: req.OverrideBasePrompt,
|
||||||
IsCrossMargin: isCrossMargin,
|
SystemPromptTemplate: existingTrader.SystemPromptTemplate, // 保持原值
|
||||||
ScanIntervalMinutes: existingTrader.ScanIntervalMinutes, // 保持原值
|
IsCrossMargin: isCrossMargin,
|
||||||
IsRunning: existingTrader.IsRunning, // 保持原值
|
ScanIntervalMinutes: scanIntervalMinutes,
|
||||||
|
IsRunning: existingTrader.IsRunning, // 保持原值
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新数据库
|
// 更新数据库
|
||||||
@@ -825,30 +840,25 @@ func (s *Server) handleGetTraderConfig(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AIModelID 应该已经是 provider(如 "deepseek"),直接使用
|
// 返回完整的模型ID,不做转换,保持与前端模型列表一致
|
||||||
// 如果是旧数据格式(如 "admin_deepseek"),提取 provider 部分
|
|
||||||
aiModelID := traderConfig.AIModelID
|
aiModelID := traderConfig.AIModelID
|
||||||
// 兼容旧数据:如果包含下划线,提取最后一部分作为 provider
|
|
||||||
if strings.Contains(aiModelID, "_") {
|
|
||||||
parts := strings.Split(aiModelID, "_")
|
|
||||||
aiModelID = parts[len(parts)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
result := map[string]interface{}{
|
result := map[string]interface{}{
|
||||||
"trader_id": traderConfig.ID,
|
"trader_id": traderConfig.ID,
|
||||||
"trader_name": traderConfig.Name,
|
"trader_name": traderConfig.Name,
|
||||||
"ai_model": aiModelID,
|
"ai_model": aiModelID,
|
||||||
"exchange_id": traderConfig.ExchangeID,
|
"exchange_id": traderConfig.ExchangeID,
|
||||||
"initial_balance": traderConfig.InitialBalance,
|
"initial_balance": traderConfig.InitialBalance,
|
||||||
"btc_eth_leverage": traderConfig.BTCETHLeverage,
|
"scan_interval_minutes": traderConfig.ScanIntervalMinutes,
|
||||||
"altcoin_leverage": traderConfig.AltcoinLeverage,
|
"btc_eth_leverage": traderConfig.BTCETHLeverage,
|
||||||
"trading_symbols": traderConfig.TradingSymbols,
|
"altcoin_leverage": traderConfig.AltcoinLeverage,
|
||||||
"custom_prompt": traderConfig.CustomPrompt,
|
"trading_symbols": traderConfig.TradingSymbols,
|
||||||
"override_base_prompt": traderConfig.OverrideBasePrompt,
|
"custom_prompt": traderConfig.CustomPrompt,
|
||||||
"is_cross_margin": traderConfig.IsCrossMargin,
|
"override_base_prompt": traderConfig.OverrideBasePrompt,
|
||||||
"use_coin_pool": traderConfig.UseCoinPool,
|
"is_cross_margin": traderConfig.IsCrossMargin,
|
||||||
"use_oi_top": traderConfig.UseOITop,
|
"use_coin_pool": traderConfig.UseCoinPool,
|
||||||
"is_running": isRunning,
|
"use_oi_top": traderConfig.UseOITop,
|
||||||
|
"is_running": isRunning,
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, result)
|
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,
|
ai_model_id: data.ai_model_id,
|
||||||
exchange_id: data.exchange_id,
|
exchange_id: data.exchange_id,
|
||||||
initial_balance: data.initial_balance,
|
initial_balance: data.initial_balance,
|
||||||
|
scan_interval_minutes: data.scan_interval_minutes,
|
||||||
btc_eth_leverage: data.btc_eth_leverage,
|
btc_eth_leverage: data.btc_eth_leverage,
|
||||||
altcoin_leverage: data.altcoin_leverage,
|
altcoin_leverage: data.altcoin_leverage,
|
||||||
trading_symbols: data.trading_symbols,
|
trading_symbols: data.trading_symbols,
|
||||||
|
|||||||
@@ -31,11 +31,14 @@ export function ComparisonChart({ traders }: ComparisonChartProps) {
|
|||||||
const { data: allTraderHistories, isLoading } = useSWR(
|
const { data: allTraderHistories, isLoading } = useSWR(
|
||||||
traders.length > 0 ? `all-equity-histories-${tradersKey}` : null,
|
traders.length > 0 ? `all-equity-histories-${tradersKey}` : null,
|
||||||
async () => {
|
async () => {
|
||||||
// 并发请求所有trader的历史数据
|
// 使用批量API一次性获取所有trader的历史数据
|
||||||
const promises = traders.map(trader =>
|
const traderIds = traders.map(trader => trader.trader_id);
|
||||||
api.getEquityHistory(trader.trader_id)
|
const batchData = await api.getEquityHistoryBatch(traderIds);
|
||||||
);
|
|
||||||
return Promise.all(promises);
|
// 转换为原格式,保持与原有代码兼容
|
||||||
|
return traders.map(trader => {
|
||||||
|
return batchData.histories[trader.trader_id] || [];
|
||||||
|
});
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
refreshInterval: 30000, // 30秒刷新(对比图表数据更新频率较低)
|
refreshInterval: 30000, // 30秒刷新(对比图表数据更新频率较低)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import type { AIModel, Exchange, CreateTraderRequest } from '../types';
|
import type { AIModel, Exchange, CreateTraderRequest } from '../types';
|
||||||
|
import { useLanguage } from '../contexts/LanguageContext';
|
||||||
|
import { t } from '../i18n/translations';
|
||||||
|
|
||||||
// 提取下划线后面的名称部分
|
// 提取下划线后面的名称部分
|
||||||
function getShortName(fullName: string): string {
|
function getShortName(fullName: string): string {
|
||||||
@@ -22,6 +24,7 @@ interface TraderConfigData {
|
|||||||
use_coin_pool: boolean;
|
use_coin_pool: boolean;
|
||||||
use_oi_top: boolean;
|
use_oi_top: boolean;
|
||||||
initial_balance: number;
|
initial_balance: number;
|
||||||
|
scan_interval_minutes: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TraderConfigModalProps {
|
interface TraderConfigModalProps {
|
||||||
@@ -43,6 +46,7 @@ export function TraderConfigModal({
|
|||||||
availableExchanges = [],
|
availableExchanges = [],
|
||||||
onSave
|
onSave
|
||||||
}: TraderConfigModalProps) {
|
}: TraderConfigModalProps) {
|
||||||
|
const { language } = useLanguage();
|
||||||
const [formData, setFormData] = useState<TraderConfigData>({
|
const [formData, setFormData] = useState<TraderConfigData>({
|
||||||
trader_name: '',
|
trader_name: '',
|
||||||
ai_model: '',
|
ai_model: '',
|
||||||
@@ -57,6 +61,7 @@ export function TraderConfigModal({
|
|||||||
use_coin_pool: false,
|
use_coin_pool: false,
|
||||||
use_oi_top: false,
|
use_oi_top: false,
|
||||||
initial_balance: 1000,
|
initial_balance: 1000,
|
||||||
|
scan_interval_minutes: 3,
|
||||||
});
|
});
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [availableCoins, setAvailableCoins] = useState<string[]>([]);
|
const [availableCoins, setAvailableCoins] = useState<string[]>([]);
|
||||||
@@ -87,6 +92,7 @@ export function TraderConfigModal({
|
|||||||
use_coin_pool: false,
|
use_coin_pool: false,
|
||||||
use_oi_top: false,
|
use_oi_top: false,
|
||||||
initial_balance: 1000,
|
initial_balance: 1000,
|
||||||
|
scan_interval_minutes: 3,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// 确保旧数据也有默认的 system_prompt_template
|
// 确保旧数据也有默认的 system_prompt_template
|
||||||
@@ -181,6 +187,7 @@ export function TraderConfigModal({
|
|||||||
use_coin_pool: formData.use_coin_pool,
|
use_coin_pool: formData.use_coin_pool,
|
||||||
use_oi_top: formData.use_oi_top,
|
use_oi_top: formData.use_oi_top,
|
||||||
initial_balance: formData.initial_balance,
|
initial_balance: formData.initial_balance,
|
||||||
|
scan_interval_minutes: formData.scan_interval_minutes,
|
||||||
};
|
};
|
||||||
await onSave(saveData);
|
await onSave(saveData);
|
||||||
onClose();
|
onClose();
|
||||||
@@ -319,7 +326,25 @@ export function TraderConfigModal({
|
|||||||
</div>
|
</div>
|
||||||
</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 className="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm text-[#EAECEF] block mb-2">BTC/ETH 杠杆</label>
|
<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'
|
import AnimatedSection from './AnimatedSection'
|
||||||
|
|
||||||
interface CardProps {
|
interface CardProps {
|
||||||
quote: string
|
quote: string;
|
||||||
authorName: string
|
authorName: string;
|
||||||
handle: string
|
handle: string;
|
||||||
avatarUrl: string
|
avatarUrl: string;
|
||||||
tweetUrl: string
|
tweetUrl: string;
|
||||||
delay: number
|
delay: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function TestimonialCard({ quote, authorName, delay }: CardProps) {
|
function TestimonialCard({ quote, authorName, delay }: CardProps) {
|
||||||
|
|||||||
@@ -163,6 +163,8 @@ export const translations = {
|
|||||||
create: 'Create',
|
create: 'Create',
|
||||||
configureAIModels: 'Configure AI Models',
|
configureAIModels: 'Configure AI Models',
|
||||||
configureExchanges: 'Configure Exchanges',
|
configureExchanges: 'Configure Exchanges',
|
||||||
|
aiScanInterval: 'AI Scan Decision Interval (minutes)',
|
||||||
|
scanIntervalRecommend: 'Recommended: 3-10 minutes',
|
||||||
useTestnet: 'Use Testnet',
|
useTestnet: 'Use Testnet',
|
||||||
enabled: 'Enabled',
|
enabled: 'Enabled',
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
@@ -575,6 +577,8 @@ export const translations = {
|
|||||||
create: '创建',
|
create: '创建',
|
||||||
configureAIModels: '配置AI模型',
|
configureAIModels: '配置AI模型',
|
||||||
configureExchanges: '配置交易所',
|
configureExchanges: '配置交易所',
|
||||||
|
aiScanInterval: 'AI 扫描决策间隔 (分钟)',
|
||||||
|
scanIntervalRecommend: '建议: 3-10分钟',
|
||||||
useTestnet: '使用测试网',
|
useTestnet: '使用测试网',
|
||||||
enabled: '启用',
|
enabled: '启用',
|
||||||
save: '保存',
|
save: '保存',
|
||||||
|
|||||||
@@ -247,6 +247,33 @@ export const api = {
|
|||||||
return res.json();
|
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)
|
// 获取AI学习表现分析(支持trader_id)
|
||||||
async getPerformance(traderId?: string): Promise<any> {
|
async getPerformance(traderId?: string): Promise<any> {
|
||||||
const url = traderId
|
const url = traderId
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ export interface CreateTraderRequest {
|
|||||||
ai_model_id: string;
|
ai_model_id: string;
|
||||||
exchange_id: string;
|
exchange_id: string;
|
||||||
initial_balance: number;
|
initial_balance: number;
|
||||||
|
scan_interval_minutes?: number;
|
||||||
btc_eth_leverage?: number;
|
btc_eth_leverage?: number;
|
||||||
altcoin_leverage?: number;
|
altcoin_leverage?: number;
|
||||||
trading_symbols?: string;
|
trading_symbols?: string;
|
||||||
@@ -198,5 +199,6 @@ export interface TraderConfigData {
|
|||||||
use_coin_pool: boolean;
|
use_coin_pool: boolean;
|
||||||
use_oi_top: boolean;
|
use_oi_top: boolean;
|
||||||
initial_balance: number;
|
initial_balance: number;
|
||||||
|
scan_interval_minutes: number;
|
||||||
is_running: boolean;
|
is_running: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user