Skip to content

Commit d90b8cd

Browse files
the-dev-zclaude
andcommitted
fix: comprehensive bug fixes and UX improvements for z-dev-v2
This commit includes multiple critical fixes and improvements: **P0 Fixes (Critical):** - ✅ Fix trader deletion logic order (manager/trader_manager.go, api/server.go) - Added RemoveTrader() method to safely remove traders from memory - Fixed order: stop & remove from memory → delete from DB - Prevents "ghost traders" running without DB records - Clears competition cache on deletion **P1 Fixes (Important):** - ✅ Fix two-stage private key input validation (web/src/components/TwoStageKeyModal.tsx) - Normalize "0x" prefix before length validation - Support keys with or without "0x" prefix - ✅ Fix competition page showing deleted traders - RemoveTrader() now clears competition cache - Deleted traders no longer appear in rankings **P2 Improvements (General):** - ✅ Add duplicate trader name check (api/server.go) - Prevents creating traders with identical names - Returns clear error message - ✅ Improve Dashboard empty state messages (web/src/i18n/translations.ts) - More welcoming title: "Let's Get Started!" / "開始使用吧!" - Specific action guidance: connect exchange, choose AI model - Better call-to-action button text - ✅ Improve login error messages (web/src/i18n/translations.ts) - More helpful error hints - Clearer guidance for users **Cherry-picked from upstream:** - ✅ Initial balance and equity calculation fixes (NoFxAiOS#901, 4 commits) - ✅ Dynamic minimum position size for small accounts (NoFxAiOS#902, 1 commit) Related: NoFxAiOS#787, NoFxAiOS#807, NoFxAiOS#790, NoFxAiOS#813, NoFxAiOS#883 Files changed: 4 (backend: 2, frontend: 2) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 8834c88 commit d90b8cd

File tree

4 files changed

+85
-26
lines changed

4 files changed

+85
-26
lines changed

api/server.go

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,19 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
486486
}
487487
}
488488

489+
// ✅ 检查交易员名称是否重复
490+
existingTraders, err := s.database.GetTraders(userID)
491+
if err != nil {
492+
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("检查交易员名称失败: %v", err)})
493+
return
494+
}
495+
for _, existing := range existingTraders {
496+
if existing.Name == req.Name {
497+
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("交易员名称 '%s' 已存在,请使用其他名称", req.Name)})
498+
return
499+
}
500+
}
501+
489502
// 生成交易员ID
490503
traderID := fmt.Sprintf("%s_%s_%d", req.ExchangeID, req.AIModelID, time.Now().Unix())
491504

@@ -794,23 +807,20 @@ func (s *Server) handleDeleteTrader(c *gin.Context) {
794807
userID := c.GetString("user_id")
795808
traderID := c.Param("id")
796809

797-
// 从数据库删除
810+
// ✅ 步骤1:先从内存中停止并移除交易员(RemoveTrader会处理停止逻辑和竞赛缓存清除)
811+
if err := s.traderManager.RemoveTrader(traderID); err != nil {
812+
// 交易员不在内存中也不是错误,可能已经被移除或从未加载
813+
log.Printf("⚠️ 从内存中移除交易员时出现警告: %v", err)
814+
}
815+
816+
// ✅ 步骤2:最后才从数据库删除
798817
err := s.database.DeleteTrader(userID, traderID)
799818
if err != nil {
800819
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("删除交易员失败: %v", err)})
801820
return
802821
}
803822

804-
// 如果交易员正在运行,先停止它
805-
if trader, err := s.traderManager.GetTrader(traderID); err == nil {
806-
status := trader.GetStatus()
807-
if isRunning, ok := status["is_running"].(bool); ok && isRunning {
808-
trader.Stop()
809-
log.Printf("⏹ 已停止运行中的交易员: %s", traderID)
810-
}
811-
}
812-
813-
log.Printf("✓ 交易员已删除: %s", traderID)
823+
log.Printf("✓ 交易员已完全删除: %s", traderID)
814824
c.JSON(http.StatusOK, gin.H{"message": "交易员已删除"})
815825
}
816826

manager/trader_manager.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,48 @@ func (tm *TraderManager) GetTraderIDs() []string {
425425
return ids
426426
}
427427

428+
// RemoveTrader 从内存中移除交易员(删除前必须先停止)
429+
func (tm *TraderManager) RemoveTrader(traderID string) error {
430+
tm.mu.Lock()
431+
defer tm.mu.Unlock()
432+
433+
trader, exists := tm.traders[traderID]
434+
if !exists {
435+
return fmt.Errorf("trader ID '%s' 不存在", traderID)
436+
}
437+
438+
// 确保交易员已停止
439+
status := trader.GetStatus()
440+
if status != nil {
441+
if isRunning, ok := status["is_running"].(bool); ok && isRunning {
442+
log.Printf("⚠️ 交易员 %s 仍在运行,正在停止...", traderID)
443+
trader.Stop()
444+
445+
// 等待停止(最多5秒)
446+
for i := 0; i < 50; i++ {
447+
time.Sleep(100 * time.Millisecond)
448+
status := trader.GetStatus()
449+
if running, ok := status["is_running"].(bool); !ok || !running {
450+
break
451+
}
452+
}
453+
}
454+
}
455+
456+
// 从map中删除
457+
delete(tm.traders, traderID)
458+
log.Printf("✅ 已从内存中移除交易员: %s", traderID)
459+
460+
// 清除竞赛缓存,强制下次重新计算
461+
tm.competitionCache.mu.Lock()
462+
tm.competitionCache.data = nil
463+
tm.competitionCache.timestamp = time.Time{}
464+
tm.competitionCache.mu.Unlock()
465+
log.Printf("🔄 已清除竞赛缓存")
466+
467+
return nil
468+
}
469+
428470
// StartAll 启动所有trader
429471
func (tm *TraderManager) StartAll() {
430472
tm.mu.RLock()

web/src/components/TwoStageKeyModal.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ export function TwoStageKeyModal({
7474
}, [isOpen, stage])
7575

7676
const handleStage1Next = async () => {
77-
if (part1.length < expectedPart1Length) {
77+
// ✅ 標準化輸入(移除可能的 0x 前綴)再驗證長度
78+
const normalized1 = part1.startsWith('0x') ? part1.slice(2) : part1
79+
if (normalized1.length < expectedPart1Length) {
7880
setError(
7981
t('errors.privatekeyIncomplete', language, {
8082
expected: expectedPart1Length,
@@ -129,7 +131,9 @@ export function TwoStageKeyModal({
129131
}
130132

131133
const handleStage2Complete = () => {
132-
if (part2.length < expectedPart2Length) {
134+
// ✅ 標準化輸入(移除可能的 0x 前綴)再驗證長度
135+
const normalized2 = part2.startsWith('0x') ? part2.slice(2) : part2
136+
if (normalized2.length < expectedPart2Length) {
133137
setError(
134138
t('errors.privatekeyIncomplete', language, {
135139
expected: expectedPart2Length,
@@ -138,7 +142,9 @@ export function TwoStageKeyModal({
138142
return
139143
}
140144

141-
const fullKey = part1 + part2
145+
// ✅ 拼接時移除可能的 0x 前綴
146+
const normalized1 = part1.startsWith('0x') ? part1.slice(2) : part1
147+
const fullKey = normalized1 + normalized2
142148
if (!validatePrivateKeyFormat(fullKey, expectedLength)) {
143149
setError(t('errors.privatekeyInvalidFormat', language))
144150
return

web/src/i18n/translations.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,10 @@ export const translations = {
146146
currentTraders: 'Current Traders',
147147
noTraders: 'No AI Traders',
148148
createFirstTrader: 'Create your first AI trader to get started',
149-
dashboardEmptyTitle: 'No Traders Configured',
149+
dashboardEmptyTitle: "Let's Get Started!",
150150
dashboardEmptyDescription:
151-
"You haven't created any AI traders yet. Create your first trader to start automated trading.",
152-
goToTradersPage: 'Go to Traders Page',
151+
'Create your first AI trader to automate your trading strategy. Connect an exchange, choose an AI model, and start trading in minutes!',
152+
goToTradersPage: 'Create Your First Trader',
153153
configureModelsFirst: 'Please configure AI models first',
154154
configureExchangesFirst: 'Please configure exchanges first',
155155
configureModelsAndExchangesFirst:
@@ -458,9 +458,10 @@ export const translations = {
458458
completeRegistrationSubtitle: 'to complete registration',
459459
loginSuccess: 'Login successful',
460460
registrationSuccess: 'Registration successful',
461-
loginFailed: 'Login failed',
462-
registrationFailed: 'Registration failed',
463-
verificationFailed: 'OTP verification failed',
461+
loginFailed: 'Login failed. Please check your email and password.',
462+
registrationFailed: 'Registration failed. Please try again.',
463+
verificationFailed:
464+
'OTP verification failed. Please check the code and try again.',
464465
invalidCredentials: 'Invalid email or password',
465466
weak: 'Weak',
466467
medium: 'Medium',
@@ -947,10 +948,10 @@ export const translations = {
947948
currentTraders: '当前交易员',
948949
noTraders: '暂无AI交易员',
949950
createFirstTrader: '创建您的第一个AI交易员开始使用',
950-
dashboardEmptyTitle: '暂无交易员',
951+
dashboardEmptyTitle: '开始使用吧!',
951952
dashboardEmptyDescription:
952-
'您还未创建任何AI交易员,创建您的第一个交易员以开始自动化交易。',
953-
goToTradersPage: '前往交易员页面',
953+
'创建您的第一个 AI 交易员,自动化您的交易策略。连接交易所、选择 AI 模型,几分钟内即可开始交易!',
954+
goToTradersPage: '创建您的第一个交易员',
954955
configureModelsFirst: '请先配置AI模型',
955956
configureExchangesFirst: '请先配置交易所',
956957
configureModelsAndExchangesFirst: '请先配置AI模型和交易所',
@@ -1223,9 +1224,9 @@ export const translations = {
12231224
completeRegistrationSubtitle: '以完成注册',
12241225
loginSuccess: '登录成功',
12251226
registrationSuccess: '注册成功',
1226-
loginFailed: '登录失败',
1227-
registrationFailed: '注册失败',
1228-
verificationFailed: 'OTP验证失败',
1227+
loginFailed: '登录失败,请检查您的邮箱和密码。',
1228+
registrationFailed: '注册失败,请重试。',
1229+
verificationFailed: 'OTP 验证失败,请检查验证码后重试。',
12291230
invalidCredentials: '邮箱或密码错误',
12301231
weak: '弱',
12311232
medium: '中',

0 commit comments

Comments
 (0)