Skip to content

Conversation

@the-dev-z
Copy link
Collaborator

@the-dev-z the-dev-z commented Nov 11, 2025

📝 Description | 描述

中文:

用户报告小账户(净值 6.97 USDT)无法交易 BTC/ETH,因为固定最小开仓金额(60 USDT)超过账户规模,造成"鸡生蛋问题"。本 PR 实施动态最小金额计算策略,根据账户规模自适应调整最小开仓金额,降低小账户交易门槛的同时保持大账户的安全边际。


🎯 Type of Change | 变更类型

  • 🐛 Bug fix | 修复 Bug
  • ✨ New feature | 新功能
  • 💥 Breaking change | 破坏性变更
  • ♻️ Refactoring | 重构
  • ⚡ Performance improvement | 性能优化
  • 🔒 Security fix | 安全修复
  • 🔧 Build/config change | 构建/配置变更

🔗 Related Issues | 相关 Issue


📋 Changes Made | 具体变更

中文:

1. 新增动态计算函数 calculateMinPositionSize()

根据账户净值和币种动态计算最小开仓金额:

账户规模 BTC/ETH 最小金额 策略说明
< 20 USDT 12 USDT 与山寨币相同,降低门槛
20-100 USDT 12-60 USDT 线性插值,平滑过渡
≥ 100 USDT 60 USDT 保持原策略
山寨币 12 USDT 无变化

示例:

  • 6.97 USDT 账户: BTC/ETH 最小 12 USDT ✅
  • 60 USDT 账户: BTC/ETH 最小 36 USDT
  • 100+ USDT 账户: BTC/ETH 最小 60 USDT

2. 更新验证逻辑 (validateDecision)

  • 替换固定常量(minPositionSizeBTCETH = 60.0)为动态计算
  • 添加小账户特殊错误提示,引导用户理解动态门槛

3. 更新 System Prompt (buildSystemPrompt)

根据账户规模动态提示 AI 最小开仓金额:

  • 小账户: "BTC/ETH≥12 USDT (⚠️ 小账户模式,降低门槛)"
  • 中型账户: "BTC/ETH≥XX USDT (根据账户规模动态调整)"
  • 大账户: "BTC/ETH≥60 USDT"

🧪 Testing | 测试

Test Environment | 测试环境

  • OS | 操作系统: macOS Darwin 24.5.0
  • Go Version | Go 版本: go1.23 (from go.mod)
  • Exchange | 交易所: Binance Futures (配置相关)

Manual Testing | 手动测试

  • Tested locally | 本地测试通过
  • Unit tests pass | 单元测试通过
  • Verified no existing functionality broke | 确认没有破坏现有功能
  • Tested on testnet | 测试网测试通过(需要用户实际测试)

Test Results | 测试结果

$ go test ./decision/... -v
=== RUN   TestPromptManager_LoadTemplates
--- PASS: TestPromptManager_LoadTemplates (0.00s)
=== RUN   TestLeverageFallback
--- PASS: TestLeverageFallback (0.00s)
=== RUN   TestPromptReloadEndToEnd
--- PASS: TestPromptReloadEndToEnd (0.00s)
PASS
ok      nofx/decision   0.294s

$ go fmt ./decision/...
decision/prompt_manager_test.go

测试覆盖

  • ✅ 杠杆回退逻辑测试通过
  • ✅ 提示词模板测试通过
  • ✅ 代码格式化完成
  • ✅ 无编译错误

🔒 Security Considerations | 安全考虑

  • No API keys or secrets hardcoded | 没有硬编码 API 密钥
  • User inputs properly validated | 用户输入已正确验证
  • No SQL injection vulnerabilities | 无 SQL 注入漏洞
  • Authentication/authorization properly handled | 认证/授权正确处理
  • Sensitive data is encrypted | 敏感数据已加密
  • N/A (not security-related) | 不适用(本次变更仅涉及业务逻辑验证)

安全影响分析

  • 降低小账户最小金额 → 不增加安全风险(仍需通过风险回报比≥1:3验证)
  • 动态计算纯数学运算 → 无注入或溢出风险
  • 不涉及用户输入处理或数据库操作

⚡ Performance Impact | 性能影响

  • No significant performance impact | 无显著性能影响
  • Performance improved | 性能提升
  • Performance may be impacted (explain below) | 性能可能受影响

性能分析

  • calculateMinPositionSize() 为纯计算函数,时间复杂度 O(1)
  • 每次验证决策时调用一次,无循环或递归
  • 相比原逻辑(两个 if-else 判断),计算量相同
  • 结论: 无性能影响

✅ Checklist | 检查清单

Code Quality | 代码质量

  • Code follows project style | 代码遵循项目风格
  • Self-review completed | 已完成代码自查
  • Comments added for complex logic | 已添加必要注释
  • Code compiles successfully | 代码编译成功 (go build)
  • Ran go fmt | 已运行 go fmt

Documentation | 文档

  • Updated relevant documentation | 已更新相关文档(commit message 包含完整上下文)
  • Added inline comments where necessary | 已添加必要的代码注释
  • Updated API documentation (if applicable) | N/A(不涉及 API 变更)

Git

  • Commits follow conventional format | 提交遵循 Conventional Commits 格式 (fix: 动态调整最小开仓金额...)
  • Rebased on latest dev branch | 已 rebase 到最新 dev 分支
  • No merge conflicts | 无合并冲突

📚 Additional Notes | 补充说明

中文:

⚠️ 已知风险与后续建议

风险控制:

  1. 交易所精度限制: 12 USDT 开仓 BTC/ETH 需验证 Binance LOT_SIZEMIN_NOTIONAL 是否支持
  2. 滑点风险: 小单可能面临较大滑点
  3. 建议: 小账户用户先用模拟盘测试,确认策略可行性

后续追踪:

  1. 监控小账户交易成功率(是否触发交易所拒单)
  2. 收集用户反馈(动态策略是否解决实际问题)
  3. 考虑配置化(将 20U/100U 阈值移至配置文件)

🎯 设计决策

为什么选择 20U 和 100U 作为阈值?

  • 20U: 接近 2x 最小开仓金额(12U),给小账户留出操作空间
  • 100U: 考虑主流用户账户规模分布,100U+ 用户适用标准策略
  • 线性插值: 避免阶跃变化,提升用户体验

为什么不直接全部改为 12U?

  • 保持大账户的安全边际(60U 最小金额是基于精度和滑点考虑)
  • 渐进式降低风险,观察小账户实际交易效果

By submitting this PR, I confirm | 提交此 PR,我确认:

  • I have read the Contributing Guidelines | 已阅读贡献指南
  • I agree to the Code of Conduct | 同意行为准则
  • My contribution is licensed under AGPL-3.0 | 贡献遵循 AGPL-3.0 许可证

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

icyouo and others added 30 commits November 4, 2025 01:45
The chart was not showing data because the API response format changed.
Fixed the calculation of PnL percentage by computing it from total_pnl
and balance values (initial_balance = balance - total_pnl).

Now the AI competition chart should properly display performance comparison data.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add NOFX watermark to ComparisonChart (competition page)
- Add NOFX watermark to EquityChart (dashboard page)
- Fix empty state handling and internationalization in CompetitionPage

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fix(workflow): add title and size validation comments
fix(readme): update readme and pr reviewer
…ssing-fields

fix(database): GetTraderConfig missing critical fields causes edit to fail
Fix:fix the main branch history issue from November 3rd.
Merge pull request NoFxAiOS#395 from NoFxAiOS/beta
fix:fix the main branch history issue from November 3rd.
Enable automatic wallet address generation from private key for Hyperliquid
exchange, simplifying user onboarding and reducing configuration errors.

Backend Changes (trader/hyperliquid_trader.go):
- Import crypto/ecdsa package for ECDSA public key operations
- Enable wallet address auto-generation when walletAddr is empty
- Use crypto.PubkeyToAddress() to derive address from private key
- Add logging for both auto-generated and manually provided addresses

Frontend Changes (web/src/components/AITradersPage.tsx):
- Remove wallet address required validation (only private key required)
- Update button disabled state to only check private key
- Add "Optional" label to wallet address field
- Add dynamic placeholder with bilingual hint
- Show context-aware helper text based on input state
- Remove HTML required attribute from input field

Translation Updates (web/src/i18n/translations.ts):
- Add 'optional' translation (EN: "Optional", ZH: "可选")
- Add 'hyperliquidWalletAddressAutoGenerate' translation
  EN: "Leave blank to automatically generate wallet address from private key"
  ZH: "留空将自动从私钥生成钱包地址"

Benefits:
✅ Simplified UX - Users only need to provide private key
✅ Error prevention - Auto-generated address always matches private key
✅ Backward compatible - Manual address input still supported
✅ Better UX - Clear visual indicators for optional fields

Technical Details:
- Uses Ethereum standard ECDSA public key to address conversion
- Implementation was already present but commented out (lines 37-43)
- No database schema changes required (hyperliquid_wallet_addr already nullable)
- Fallback behavior: manual input > auto-generation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add Binance configuration tutorial image (guide.png)
- Implement "View Guide" button in exchange configuration modal
- Add tutorial display modal with image viewer
- Add i18n support for guide-related text (EN/ZH)
- Button only appears when configuring Binance exchange

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
 feat: Add Binance setup guide with tutorial modal
Problem:
- Users could input arbitrary initial balance when creating traders
- This didn't reflect the actual available balance in exchange account
- Could lead to incorrect position sizing and risk calculations

Solution:
- Before creating trader, query exchange API for actual balance
- Use GetBalance() from respective trader implementation:
  * Binance: NewFuturesTrader + GetBalance()
  * Hyperliquid: NewHyperliquidTrader + GetBalance()
  * Aster: NewAsterTrader + GetBalance()
- Extract 'available_balance' or 'balance' from response
- Override user input with actual balance
- Fallback to user input if query fails

Changes:
- Added 'nofx/trader' import
- Query GetExchanges() to find matching exchange config
- Create temporary trader instance based on exchange type
- Call GetBalance() to fetch actual available balance
- Use actualBalance instead of req.InitialBalance
- Comprehensive error handling with fallback logic

Benefits:
- ✅ Ensures accurate initial balance matches exchange account
- ✅ Prevents user errors in balance input
- ✅ Improves position sizing accuracy
- ✅ Maintains data integrity between system and exchange

Example logs:
✓ 查询到交易所实际余额: 150.00 USDT (用户输入: 100.00 USDT)
⚠️ 查询交易所余额失败,使用用户输入的初始资金: connection timeout

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fixed compilation error caused by variable name mismatch:
- Line 404: defined as 'trader'
- Line 425: was using 'traderRecord' (undefined)

This aligns with upstream dev branch naming convention.
新增功能:
- update_stop_loss: 调整止损价格(追踪止损)
- update_take_profit: 调整止盈价格(技术位优化)
- partial_close: 部分平仓(分批止盈)

实现细节:
- Decision struct 新增字段:NewStopLoss, NewTakeProfit, ClosePercentage
- 新增执行函数:executeUpdateStopLossWithRecord, executeUpdateTakeProfitWithRecord, executePartialCloseWithRecord
- 修复持仓字段获取 bug(使用 "side" 并转大写)
- 更新 adaptive.txt 文档,包含详细使用示例和策略建议
- 优先级排序:平仓 > 调整止盈止损 > 开仓

命名统一:
- 与社区 PR NoFxAiOS#197 保持一致,使用 update_* 而非 adjust_*
- 独有功能:partial_close(部分平仓)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
問題根因:
- auto_trader.go 已實現 update_stop_loss/update_take_profit/partial_close 處理
- adaptive.txt 已描述這些功能
- 但 validateDecision 的 validActions map 缺少這三個動作
- 導致 AI 生成的決策在驗證階段被拒絕:「无效的action:update_stop_loss」

修復內容:
1. validActions 添加三個新動作
2. 為每個新動作添加參數驗證:
   - update_stop_loss: 驗證 NewStopLoss > 0
   - update_take_profit: 驗證 NewTakeProfit > 0
   - partial_close: 驗證 ClosePercentage 在 0-100 之間
3. 修正註釋:adjust_* → update_*

測試狀態:feature 分支,等待測試確認
問題:
- 調整止損/止盈時,直接調用 SetStopLoss/SetTakeProfit 會創建新訂單
- 但舊的止損/止盈單仍然存在,導致多個訂單共存
- 可能造成意外觸發或訂單衝突

解決方案(參考 PR NoFxAiOS#197):
1. 在 Trader 接口添加 CancelStopOrders 方法
2. 為三個交易所實現:
   - binance_futures.go: 過濾 STOP_MARKET/TAKE_PROFIT_MARKET 類型
   - aster_trader.go: 同樣邏輯
   - hyperliquid_trader.go: 過濾 trigger 訂單(有 triggerPx)
3. 在 executeUpdateStopLossWithRecord 和 executeUpdateTakeProfitWithRecord 中:
   - 先調用 CancelStopOrders 取消舊單
   - 然後設置新止損/止盈
   - 取消失敗不中斷執行(記錄警告)

優勢:
- ✅ 避免多個止損單同時存在
- ✅ 保留我們的價格驗證邏輯
- ✅ 保留執行價格記錄
- ✅ 詳細錯誤信息
- ✅ 取消失敗時繼續執行(更健壯)

測試建議:
- 開倉後調整止損,檢查舊止損單是否被取消
- 連續調整兩次,確認只有最新止損單存在

致謝:參考 PR NoFxAiOS#197 的實現思路
问题:部分平仓时,历史记录显示的是全仓位盈利,而非实际平仓部分的盈利

根本原因:
- AnalyzePerformance 使用开仓总数量计算部分平仓的盈利
- 应该使用 action.Quantity(实际平仓数量)而非 openPos["quantity"](总数量)

修复:
- 添加 actualQuantity 变量区分完整平仓和部分平仓
- partial_close 使用 action.Quantity
- 所有相关计算(PnL、PositionValue、MarginUsed)都使用 actualQuantity

影响范围:logger/decision_logger.go:428-465

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- OpenOrder 結構不暴露 trigger 字段
- 改為取消該幣種的所有掛單(安全做法)
the-dev-z and others added 9 commits November 11, 2025 20:59
…rliquid) (NoFxAiOS#921)

* fix(ui): remove duplicate Aster exchange form rendering

修復 Aster 交易所配置表單重複渲染問題。

Issue:
- Aster 表單代碼在 AITradersPage.tsx 中出現兩次(lines 2334 和 2559)
- 導致用戶界面顯示 6 個輸入欄位(應該是 3 個)
- 用戶體驗混亂

Fix:
- 刪除重複的 Aster 表單代碼塊(lines 2559-2710,共 153 行)
- 保留第一個表單塊(lines 2334-2419)
- 修復 prettier 格式問題

Result:
- Aster 配置現在正確顯示 3 個欄位:user, signer, private key
- Lint 檢查通過
- Hyperliquid Agent Wallet 翻譯已存在無需修改

Technical:
- 刪除了完全重複的 JSX 條件渲染塊
- 移除空白行以符合 prettier 規範

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ui): remove legacy Hyperliquid single private key field

修復 Hyperliquid 配置頁面顯示舊版私鑰欄位的問題。

Issue:
- Hyperliquid 配置同時顯示舊版和新版欄位
- 舊版:單一「私钥」欄位(不安全,已廢棄)
- 新版:「代理私钥」+「主钱包地址」(Agent Wallet 安全模式)
- 用戶看到重複的欄位配置,造成混淆

Root Cause:
- AITradersPage.tsx 存在兩個 Hyperliquid 條件渲染塊
- Lines 2302-2332: 舊版單私鑰模式(應刪除)
- Lines 2424-2557: 新版 Agent Wallet 模式(正確)

Fix:
- 刪除舊版 Hyperliquid 單私鑰欄位代碼塊(lines 2302-2332,共 32 行)
- 保留新版 Agent Wallet 配置(代理私鑰 + 主錢包地址)
- 移除 `t('privateKey')` 和 `t('hyperliquidPrivateKeyDesc')` 舊版翻譯引用

Result:
- Hyperliquid 配置現在只顯示正確的 Agent Wallet 欄位
- 安全提示 banner 正確顯示
- 用戶體驗改善,不再混淆

Technical Details:
- 新版使用 `apiKey` 儲存 Agent Private Key
- 新版使用 `hyperliquidWalletAddr` 儲存 Main Wallet Address
- 符合 Hyperliquid Agent Wallet 最佳安全實踐

Related:
- 之前已修復 Aster 重複渲染問題(commit 5462eba)
- Hyperliquid 翻譯 key 已存在於 translations.ts (lines 206-216, 1017-1027)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(i18n): add missing Hyperliquid Agent Wallet translation keys

補充 Hyperliquid 代理錢包配置的翻譯文本,修復前端顯示 key 名稱的問題。

Changes:
- 新增 8 個英文翻譯 key (Agent Wallet 配置說明)
- 新增 8 個中文翻譯 key (代理錢包配置說明)
- 修正 Hyperliquid 配置頁面顯示問題(從顯示 key 名稱改為顯示翻譯文本)

Technical Details:
- hyperliquidAgentWalletTitle: Banner 標題
- hyperliquidAgentWalletDesc: 安全說明文字
- hyperliquidAgentPrivateKey: 代理私鑰欄位標籤
- hyperliquidMainWalletAddress: 主錢包地址欄位標籤
- 相應的 placeholder 和 description 文本

Related Issue: 用戶反饋前端顯示 key 名稱而非翻譯文本

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: the-dev-z <the-dev-z@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
… request (NoFxAiOS#922)

* fix(web): restore missing system_prompt_template in handleSaveEditTrader

修復編輯交易員時策略模板無法保存的問題。

Issue:
- 用戶編輯交易員時,選擇的策略模板(system_prompt_template)沒有被保存
- 重新打開編輯窗口,總是顯示默認值
- 用戶困惑為什麼策略模板無法持久化

Root Cause:
- PR NoFxAiOS#872 在 UI 重構時遺漏了 system_prompt_template 字段
- handleSaveEditTrader 的 request 對象缺少 system_prompt_template
- 導致更新請求不包含策略模板信息

Fix:
- 在 handleSaveEditTrader 的 request 對象中添加 system_prompt_template 字段
- 位置:override_base_prompt 之後,is_cross_margin 之前
- 與後端 API 和 TraderConfigModal 保持一致

Result:
- 編輯交易員時,策略模板正確保存
- 重新打開編輯窗口,顯示正確的已保存值
- 用戶可以成功切換和保存不同的策略模板

Technical Details:
- web/src/types.ts TraderConfigData 接口已有 system_prompt_template ✓
- Backend handleUpdateTrader 接收並保存 SystemPromptTemplate ✓
- Frontend TraderConfigModal 表單提交包含 system_prompt_template ✓
- Frontend handleSaveEditTrader request 缺失此字段 ✗ → ✓ (已修復)

Related:
- PR NoFxAiOS#872: UI 重構時遺漏
- commit c1f080f: 原始添加 system_prompt_template 支持
- commit e58fc3c: 修復 types.ts 缺失字段

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(types): add missing system_prompt_template field to TraderConfigData

補充完整修復:確保 TypeScript 類型定義與 API 使用一致。

Issue:
- AITradersPage.tsx 提交時包含 system_prompt_template 字段
- 但 TraderConfigData 接口缺少此字段定義
- TypeScript 類型不匹配

Fix:
- 在 TraderConfigData 接口添加 system_prompt_template: string
- 位置:override_base_prompt 之後,is_cross_margin 之前
- 與 CreateTraderRequest 保持一致

Result:
- TypeScript 類型完整
- 編輯交易員時正確加載和保存策略模板
- 無類型錯誤

Technical:
- web/src/types.ts Line 200
- 與後端 SystemPromptTemplate 字段對應

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: the-dev-z <the-dev-z@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
…NoFxAiOS#918)

## Problem

User-facing messages were too generic and uninformative:

1. **Dashboard empty state**:
   - Title: "No Traders Configured" (cold, technical)
   - Description: Generic message with no action guidance
   - Button: "Go to Traders Page" (unclear what happens next)

2. **Login error messages**:
   - "Login failed" (too vague - why did it fail?)
   - "Registration failed" (no guidance on what to do)
   - "OTP verification failed" (users don't know how to fix)

**Impact**: Users felt confused and frustrated, no clear next steps.

## Solution

### 1. Improve Dashboard Empty State
**File**: `web/src/i18n/translations.ts`

**Before**:
```typescript
dashboardEmptyTitle: 'No Traders Configured'
dashboardEmptyDescription: "You haven't created any AI traders yet..."
goToTradersPage: 'Go to Traders Page'
```

**After**:
```typescript
dashboardEmptyTitle: "Let's Get Started!"  // ✅ Welcoming, encouraging
dashboardEmptyDescription: 'Create your first AI trader to automate your trading strategy. Connect an exchange, choose an AI model, and start trading in minutes!'  // ✅ Clear steps
goToTradersPage: 'Create Your First Trader'  // ✅ Clear action
```

**Changes**:
- ✅ More welcoming tone ("Let's Get Started!")
- ✅ Specific action steps (connect → choose → trade)
- ✅ Time expectation ("in minutes")
- ✅ Clear call-to-action button

---

### 2. Improve Error Messages
**File**: `web/src/i18n/translations.ts`

**Before**:
```typescript
loginFailed: 'Login failed'  // ❌ No guidance
registrationFailed: 'Registration failed'  // ❌ No guidance
verificationFailed: 'OTP verification failed'  // ❌ No guidance
```

**After**:
```typescript
loginFailed: 'Login failed. Please check your email and password.'  // ✅ Clear hint
registrationFailed: 'Registration failed. Please try again.'  // ✅ Clear action
verificationFailed: 'OTP verification failed. Please check the code and try again.'  // ✅ Clear steps
```

**Changes**:
- ✅ Specific error hints (check email/password)
- ✅ Clear remediation steps (try again, check code)
- ✅ User-friendly tone

---

### 3. Chinese Translations
All improvements mirrored in Chinese:

**Dashboard**:
- Title: "开始使用吧!" (Let's get started!)
- Description: Clear 3-step guidance
- Button: "创建您的第一个交易员" (Create your first trader)

**Errors**:
- "登录失败,请检查您的邮箱和密码。"
- "注册失败,请重试。"
- "OTP 验证失败,请检查验证码后重试。"

---

## Impact

### User Experience Improvements

| Message Type | Before | After | Benefit |
|--------------|--------|-------|---------|
| **Empty dashboard** | Cold, technical | Welcoming, actionable | ✅ Reduces confusion |
| **Login errors** | Vague | Specific hints | ✅ Faster problem resolution |
| **Registration errors** | No guidance | Clear next steps | ✅ Lower support burden |
| **OTP errors** | Confusing | Actionable | ✅ Higher success rate |

### Tone Shift

**Before**: Technical, system-centric
- "No Traders Configured"
- "Login failed"

**After**: User-centric, helpful
- "Let's Get Started!"
- "Login failed. Please check your email and password."

---

## Testing

**Manual Testing**:
- [x] Empty dashboard displays new messages correctly
- [x] Login error shows improved message
- [x] Registration error shows improved message
- [x] OTP error shows improved message
- [x] Chinese translations display correctly
- [x] Button text updated appropriately

**Language Coverage**:
- [x] English ✅
- [x] Chinese ✅

---

## Files Changed

**1 frontend file**:
- `web/src/i18n/translations.ts` (+12 lines, -6 lines)

**Lines affected**:
- English: Lines 149-152, 461-464
- Chinese: Lines 950-953, 1227-1229

---

**By submitting this PR, I confirm:**

- [x] I have read the Contributing Guidelines
- [x] I agree to the Code of Conduct
- [x] My contribution is licensed under AGPL-3.0

---

🌟 **Thank you for reviewing!**

This PR improves user experience with clearer, more helpful messages.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: the-dev-z <the-dev-z@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
* feat: add web crypto environment check

* fix: auto check env

* refactor:  WebCryptoEnvironmentCheck  swtich to map
* feat(market): add data staleness detection

## 問題背景
解決 PR NoFxAiOS#703 Part 2: 數據陳舊性檢測
- 修復 DOGEUSDT 式問題:連續價格不變表示數據源異常
- 防止系統處理僵化/過期的市場數據

## 技術方案

### 數據陳舊性檢測 (market/data.go)
- **函數**: `isStaleData(klines []Kline, symbol string) bool`
- **檢測邏輯**:
  - 連續 5 個 3 分鐘週期價格完全不變(15 分鐘無波動)
  - 價格波動容忍度:0.01%(避免誤報)
  - 成交量檢查:價格凍結 + 成交量為 0 → 確認陳舊

- **處理策略**:
  - 數據陳舊確認:跳過該幣種,返回錯誤
  - 極低波動市場:記錄警告但允許通過(價格穩定但有成交量)

### 調用時機
- 在 `Get()` 函數中,獲取 3m K線後立即檢測
- 早期返回:避免後續無意義的計算和 API 調用

## 實現細節
- **檢測閾值**: 5 個連續週期
- **容忍度**: 0.01% 價格波動
- **日誌**: 英文國際化版本
- **並發安全**: 函數無狀態,安全

## 影響範圍
- ✅ 修改 market/data.go: 新增 isStaleData() + 調用邏輯
- ✅ 新增 log 包導入
- ✅ 50 行新增代碼

## 測試建議
1. 模擬 DOGEUSDT 場景:連續價格不變 + 成交量為 0
2. 驗證日誌輸出:`stale data confirmed: price freeze + zero volume`
3. 正常市場:極低波動但有成交量,應允許通過並記錄警告

## 相關 Issue/PR
- 拆分自 **PR NoFxAiOS#703** (Part 2/3)
- 基於最新 upstream/dev (3112250)
- 依賴: 無
- 前置: Part 1 (OI 時間序列) - 已提交 PR NoFxAiOS#798
- 後續: Part 3 (手續費率傳遞)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* test(market): add comprehensive unit tests for isStaleData function

- Test normal fluctuating data (expects non-stale)
- Test price freeze with zero volume (expects stale)
- Test price freeze with volume (low volatility market)
- Test insufficient data edge case (<5 klines)
- Test boundary conditions (exactly 5 klines)
- Test tolerance threshold (0.01% price change)
- Test mixed scenario (normal → freeze transition)
- Test empty klines edge case

All 8 test cases passed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Shui <88711385+hzb1115@users.noreply.github.com>
…#934)

Add `user && token` guard to all authenticated SWR calls to prevent
requests with `Authorization: Bearer null` when users refresh the page
before AuthContext finishes loading the token from localStorage.

## Problem

When users refresh the page:
1. React components mount immediately
2. SWR hooks fire API requests
3. AuthContext is still loading token from localStorage
4. Requests sent with `Authorization: Bearer null`
5. Backend returns 401 errors

This causes:
- Unnecessary 401 errors in backend logs
- Error messages in browser console
- Poor user experience on page refresh

## Solution

Add auth check to SWR key conditions using pattern:
```typescript
user && token && condition ? key : null
```

When `user` or `token` is null, SWR key becomes `null`, preventing the request.
Once AuthContext loads, SWR automatically revalidates and fetches data.

## Changes

**TraderDashboard.tsx** (5 auth guards added):
- status: `user && token && selectedTraderId ? 'status-...' : null`
- account: `user && token && selectedTraderId ? 'account-...' : null`
- positions: `user && token && selectedTraderId ? 'positions-...' : null`
- decisions: `user && token && selectedTraderId ? 'decisions/...' : null`
- stats: `user && token && selectedTraderId ? 'statistics-...' : null`

**EquityChart.tsx** (2 auth guards added + useAuth import):
- Import `useAuth` from '../contexts/AuthContext'
- Add `const { user, token } = useAuth()`
- history: `user && token && traderId ? 'equity-history-...' : null`
- account: `user && token && traderId ? 'account-...' : null`

**apiGuard.test.ts** (new file, 370 lines):
- Comprehensive unit tests covering all auth guard scenarios
- Tests for null user, null token, valid auth states
- Tests for all 7 SWR calls (5 in TraderDashboard + 2 in EquityChart)

## Testing

- ✅ TypeScript compilation passed
- ✅ Vite build passed (2.81s)
- ✅ All modifications are additive (no logic changes)
- ✅ SWR auto-revalidation ensures data loads after auth completes

## Benefits

1. **No more 401 errors on refresh**: Auth guards prevent premature requests
2. **Cleaner logs**: Backend no longer receives invalid Bearer null requests
3. **Better UX**: No error flashes in console on page load
4. **Consistent pattern**: All authenticated endpoints use same guard logic

## Context

This PR supersedes closed PR NoFxAiOS#881, which had conflicts due to PR NoFxAiOS#872
(frontend refactor with React Router). This implementation is based on
the latest upstream/dev with the new architecture.

Related: PR NoFxAiOS#881 (closed), PR NoFxAiOS#872 (Frontend Refactor)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: the-dev-z <the-dev-z@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
…oFxAiOS#935)

- Add comprehensive Hyperliquid Agent Wallet setup guide with referral link
- Update all 5 language versions (EN, ZH, JA, RU, UK)
- Remove "Alternative" prefix from Hyperliquid and Aster DEX section titles
- Remove direct wallet method, only recommend secure Agent Wallet approach
- Include step-by-step registration, funding, and agent wallet creation
- Add security warnings and best practices for all languages

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: tinkle <tinkle@tinkle.community>
Co-authored-by: Claude <noreply@anthropic.com>
the-dev-z added a commit to the-dev-z/nofx that referenced this pull request Nov 12, 2025
根據 @xqliu 的 code review 建議進行重構:

**Changes:**
1. 將硬編碼的幣種判斷 (symbol == "BTCUSDT" || symbol == "ETHUSDT")
   改為 map 配置 (symbolSizeRules)
2. 添加 altcoinSizeRules 專門處理山寨幣規則
3. 添加 TestCalculateMinPositionSize_MapConfiguration 測試
   - 測試 BTC/ETH 配置邏輯
   - 測試未配置幣種的默認行為 (SOL, BNB, MATIC, AVAX, DOT, LINK)
4. 運行 go fmt 修復格式問題

**Benefits:**
- ✅ 易於擴展:添加新幣種規則只需在 map 中加一行
- ✅ 易於維護:幣種分類集中管理
- ✅ 易於測試:可以輕鬆 mock 或替換配置
- ✅ 類型安全:避免字符串比較錯誤

**Test Results:**
- ✅ All decision tests passed (包括新增的 8 個 map 配置測試)
- ✅ go fmt ./... 完成
- ✅ go vet ./... 通過

Addresses code review feedback from PR NoFxAiOS#907:
- NoFxAiOS#907 (comment)

Co-authored-by: xqliu
@github-actions
Copy link

🤖 Advisory Check Results

These are advisory checks to help improve code quality. They won't block your PR from being merged.

📋 PR Information

Title Format: ✅ Good - Follows Conventional Commits
PR Size: 🟡 Medium (428 lines: +395 -33)

🔧 Backend Checks

Go Formatting: ✅ Good
Go Vet: ✅ Good
Tests: ✅ Passed

Fix locally:

go fmt ./...      # Format code
go vet ./...      # Check for issues
go test ./...     # Run tests

⚛️ Frontend Checks

Build & Type Check: ✅ Success

Fix locally:

cd web
npm run build  # Test build (includes type checking)

📖 Resources

Questions? Feel free to ask in the comments! 🙏


These checks are advisory and won't block your PR from being merged. This comment is automatically generated from pr-checks-run.yml.

@the-dev-z
Copy link
Collaborator Author

✅ Code Review 反饋已修復

感謝 @xqliu 的專業建議!已完成所有要求的修改:

📝 修改內容

1️⃣ 重構 if-else 為 map 配置 (decision/engine.go:740)

Before:

isBTCETH := symbol == "BTCUSDT" || symbol == "ETHUSDT"
if !isBTCETH {
    return absoluteMinimum
}
// ... 硬編碼邏輯

After:

// 幣種規則映射表(易於擴展,添加新幣種只需在此添加一行)
var symbolSizeRules = map[string][]positionSizeConfig{
    "BTCUSDT": btcEthSizeRules,
    "ETHUSDT": btcEthSizeRules,
    // 未來可添加更多幣種的特殊規則,例如:
    // "BNBUSDT": bnbSizeRules,
    // "SOLUSDT": solSizeRules,
}

func calculateMinPositionSize(symbol string, accountEquity float64) float64 {
    // 從配置映射表中獲取幣種規則
    rules, exists := symbolSizeRules[symbol]
    if !exists {
        rules = altcoinSizeRules  // 默認使用山寨幣規則
    }
    // ... 統一的動態計算邏輯
}

2️⃣ 添加單元測試覆蓋所有分支

新增 TestCalculateMinPositionSize_MapConfiguration 測試函數:

  • ✅ 測試 BTC/ETH 配置邏輯(2 cases)
  • ✅ 測試未配置幣種的默認行為(6 cases:SOL, BNB, MATIC, AVAX, DOT, LINK)
  • ✅ 驗證小/中/大賬戶在不同幣種下的行為

3️⃣ 運行 go fmt 修復格式問題

✅ go fmt ./...      # 格式化完成
✅ go vet ./...      # 無警告
✅ go test ./decision -v  # 所有測試通過

🎯 改進效果

可擴展性

  • 未來添加新幣種規則(如 BNB、SOL)只需在 symbolSizeRules map 中添加一行
  • 無需修改函數邏輯,符合開放-封閉原則

可維護性

  • 幣種分類集中管理,規則清晰可見
  • 配置與邏輯分離,職責單一

可測試性

  • 可以輕鬆 mock 或替換配置進行測試
  • 測試覆蓋率增加(新增 8 個測試用例)

📊 測試結果

=== RUN   TestCalculateMinPositionSize_MapConfiguration
=== RUN   TestCalculateMinPositionSize_MapConfiguration/Map配置_BTC_小賬戶
=== RUN   TestCalculateMinPositionSize_MapConfiguration/Map配置_ETH_大賬戶
=== RUN   TestCalculateMinPositionSize_MapConfiguration/未配置_SOL_小賬戶
=== RUN   TestCalculateMinPositionSize_MapConfiguration/未配置_BNB_大賬戶
=== RUN   TestCalculateMinPositionSize_MapConfiguration/未配置_MATIC_中型賬戶
=== RUN   TestCalculateMinPositionSize_MapConfiguration/未配置_AVAX
=== RUN   TestCalculateMinPositionSize_MapConfiguration/未配置_DOT
=== RUN   TestCalculateMinPositionSize_MapConfiguration/未配置_LINK
--- PASS: TestCalculateMinPositionSize_MapConfiguration (0.00s)

所有 decision 包測試通過:✅ 26/26 tests passed

Commit: 8d6ef99
Ready for re-review! 🙏

the-dev-z added a commit to the-dev-z/nofx that referenced this pull request Nov 12, 2025
… with config

完成 @xqliu 的第二處 code review 建議(line 337):

**Changes:**

將 buildSystemPrompt 中的硬編碼閾值改為使用配置規則:

**Before (硬編碼):**
```go
if accountEquity < 20.0 {  // ❌ 硬編碼
    sb.WriteString(" | BTC/ETH≥12 USDT (⚠️ 小账户模式,降低门槛)")
} else if accountEquity < 100.0 {  // ❌ 硬編碼
    minBTCETH := calculateMinPositionSize("BTCUSDT", accountEquity)
    sb.WriteString(fmt.Sprintf(" | BTC/ETH≥%.0f USDT (根据账户规模动态调整)", minBTCETH))
} else {
    sb.WriteString(" | BTC/ETH≥60 USDT")  // ❌ 硬編碼
}
```

**After (使用配置):**
```go
minBTCETH := calculateMinPositionSize("BTCUSDT", accountEquity)

// 使用配置規則的閾值(btcEthSizeRules[1].MinEquity, btcEthSizeRules[2].MinEquity)
if accountEquity < btcEthSizeRules[1].MinEquity {  // ✅ 從配置讀取
    btcEthHint = fmt.Sprintf(" | BTC/ETH≥%.0f USDT (⚠️ 小账户模式,降低门槛)", minBTCETH)
} else if accountEquity < btcEthSizeRules[2].MinEquity {  // ✅ 從配置讀取
    btcEthHint = fmt.Sprintf(" | BTC/ETH≥%.0f USDT (根据账户规模动态调整)", minBTCETH)
} else {
    btcEthHint = fmt.Sprintf(" | BTC/ETH≥%.0f USDT", minBTCETH)  // ✅ 動態計算
}
```

**Benefits:**

- ✅ **Single Source of Truth**: 閾值統一從 `btcEthSizeRules` 配置讀取
- ✅ **一致性**: buildSystemPrompt 和 calculateMinPositionSize 使用相同配置
- ✅ **易於維護**: 修改閾值只需改一處配置
- ✅ **避免不一致**: 消除硬編碼數字不匹配的風險

**Test Results:**
```bash
✅ go test ./decision -v  # 所有測試通過 (26 cases)
✅ go fmt ./...           # 格式化完成
```

**現在兩處都已使用 map 配置:**
1. ✅ Line 337 (buildSystemPrompt) - 本次修復
2. ✅ Line 739 (calculateMinPositionSize) - 上次已修復

Addresses code review feedback from PR NoFxAiOS#907 (完整修復):
- Line 337: "Can we have a map for this instead of Ashoc if else?" ✅
- Line 739: "Also a map would be better here." ✅

Co-authored-by: xqliu
Copy link
Contributor

@xqliu xqliu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍👍

@the-dev-z
Copy link
Collaborator Author

🔧 補充修改:完整修復兩處硬編碼

剛才發現我遺漏了一處!現已補充修復 line 337 的硬編碼閾值。


✅ 現在兩處都已使用 map 配置

1️⃣ Line 739 (calculateMinPositionSize 函數) - ✅ 已修復

Before:

isBTCETH := symbol == "BTCUSDT" || symbol == "ETHUSDT"  // ❌ 硬編碼

After:

rules, exists := symbolSizeRules[symbol]  // ✅ map 配置
if !exists {
    rules = altcoinSizeRules
}

2️⃣ Line 337 (buildSystemPrompt 函數) - ✅ 剛補充修復

Before (硬編碼閾值):

if accountEquity < 20.0 {  // ❌ 硬編碼
    sb.WriteString(" | BTC/ETH≥12 USDT (⚠️ 小账户模式,降低门槛)")
} else if accountEquity < 100.0 {  // ❌ 硬編碼
    minBTCETH := calculateMinPositionSize("BTCUSDT", accountEquity)
    sb.WriteString(fmt.Sprintf(" | BTC/ETH≥%.0f USDT (根据账户规模动态调整)", minBTCETH))
} else {
    sb.WriteString(" | BTC/ETH≥60 USDT")  // ❌ 硬編碼
}

After (使用配置規則):

minBTCETH := calculateMinPositionSize("BTCUSDT", accountEquity)

if accountEquity < btcEthSizeRules[1].MinEquity {  // ✅ 從配置讀取 (20.0)
    btcEthHint = fmt.Sprintf(" | BTC/ETH≥%.0f USDT (⚠️ 小账户模式,降低门槛)", minBTCETH)
} else if accountEquity < btcEthSizeRules[2].MinEquity {  // ✅ 從配置讀取 (100.0)
    btcEthHint = fmt.Sprintf(" | BTC/ETH≥%.0f USDT (根据账户规模动态调整)", minBTCETH)
} else {
    btcEthHint = fmt.Sprintf(" | BTC/ETH≥%.0f USDT", minBTCETH)  // ✅ 動態計算
}

sb.WriteString("6. 开仓金额: 山寨币≥12 USDT")
sb.WriteString(btcEthHint)

🎯 改進效果

Single Source of Truth

  • 閾值統一從 btcEthSizeRules 配置讀取
  • buildSystemPromptcalculateMinPositionSize 使用相同配置
  • 避免硬編碼數字不一致的風險

易於維護

  • 修改閾值(20.0 → 30.0 或 100.0 → 150.0)只需改一處
  • 未來添加新規則(如 VIP 用戶特殊閾值)只需擴展配置

📊 測試結果

✅ go test ./decision -v  # 26/26 tests passed
✅ go fmt ./...           # 格式化完成

Commits:

  • 8d6ef99: 修復 line 739 (calculateMinPositionSize)
  • ed48fed: 修復 line 337 (buildSystemPrompt) ← 新增

現在完整修復了 @xqliu 指出的兩處硬編碼!🎉

@github-actions
Copy link

🤖 Advisory Check Results

These are advisory checks to help improve code quality. They won't block your PR from being merged.

📋 PR Information

Title Format: ✅ Good - Follows Conventional Commits
PR Size: 🟡 Medium (433 lines: +400 -33)

🔧 Backend Checks

Go Formatting: ✅ Good
Go Vet: ✅ Good
Tests: ✅ Passed

Fix locally:

go fmt ./...      # Format code
go vet ./...      # Check for issues
go test ./...     # Run tests

⚛️ Frontend Checks

Build & Type Check: ✅ Success

Fix locally:

cd web
npm run build  # Test build (includes type checking)

📖 Resources

Questions? Feel free to ask in the comments! 🙏


These checks are advisory and won't block your PR from being merged. This comment is automatically generated from pr-checks-run.yml.

* feat(docs): add Hyperliquid Agent Wallet tutorial for all languages

- Add comprehensive Hyperliquid Agent Wallet setup guide with referral link
- Update all 5 language versions (EN, ZH, JA, RU, UK)
- Remove "Alternative" prefix from Hyperliquid and Aster DEX section titles
- Remove direct wallet method, only recommend secure Agent Wallet approach
- Include step-by-step registration, funding, and agent wallet creation
- Add security warnings and best practices for all languages

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(docs): add Hyperliquid and Aster DEX to Table of Contents in all languages

- Add navigation links for Hyperliquid and Aster DEX registration in all 5 README versions
- Ensure all three exchanges (Binance, Hyperliquid, Aster) have equal visibility in TOC
- Add complete TOC structure to Japanese README (was missing before)
- Maintain consistency across English, Chinese, Japanese, Russian, and Ukrainian versions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: tinkle <tinkle@tinkle.community>
Co-authored-by: Claude <noreply@anthropic.com>
the-dev-z added a commit to the-dev-z/nofx that referenced this pull request Nov 12, 2025
根據 @xqliu 的 code review 建議進行重構:

**Changes:**
1. 將硬編碼的幣種判斷 (symbol == "BTCUSDT" || symbol == "ETHUSDT")
   改為 map 配置 (symbolSizeRules)
2. 添加 altcoinSizeRules 專門處理山寨幣規則
3. 添加 TestCalculateMinPositionSize_MapConfiguration 測試
   - 測試 BTC/ETH 配置邏輯
   - 測試未配置幣種的默認行為 (SOL, BNB, MATIC, AVAX, DOT, LINK)
4. 運行 go fmt 修復格式問題

**Benefits:**
- ✅ 易於擴展:添加新幣種規則只需在 map 中加一行
- ✅ 易於維護:幣種分類集中管理
- ✅ 易於測試:可以輕鬆 mock 或替換配置
- ✅ 類型安全:避免字符串比較錯誤

**Test Results:**
- ✅ All decision tests passed (包括新增的 8 個 map 配置測試)
- ✅ go fmt ./... 完成
- ✅ go vet ./... 通過

Addresses code review feedback from PR NoFxAiOS#907:
- NoFxAiOS#907 (comment)

Co-authored-by: xqliu
the-dev-z added a commit to the-dev-z/nofx that referenced this pull request Nov 12, 2025
… with config

完成 @xqliu 的第二處 code review 建議(line 337):

**Changes:**

將 buildSystemPrompt 中的硬編碼閾值改為使用配置規則:

**Before (硬編碼):**
```go
if accountEquity < 20.0 {  // ❌ 硬編碼
    sb.WriteString(" | BTC/ETH≥12 USDT (⚠️ 小账户模式,降低门槛)")
} else if accountEquity < 100.0 {  // ❌ 硬編碼
    minBTCETH := calculateMinPositionSize("BTCUSDT", accountEquity)
    sb.WriteString(fmt.Sprintf(" | BTC/ETH≥%.0f USDT (根据账户规模动态调整)", minBTCETH))
} else {
    sb.WriteString(" | BTC/ETH≥60 USDT")  // ❌ 硬編碼
}
```

**After (使用配置):**
```go
minBTCETH := calculateMinPositionSize("BTCUSDT", accountEquity)

// 使用配置規則的閾值(btcEthSizeRules[1].MinEquity, btcEthSizeRules[2].MinEquity)
if accountEquity < btcEthSizeRules[1].MinEquity {  // ✅ 從配置讀取
    btcEthHint = fmt.Sprintf(" | BTC/ETH≥%.0f USDT (⚠️ 小账户模式,降低门槛)", minBTCETH)
} else if accountEquity < btcEthSizeRules[2].MinEquity {  // ✅ 從配置讀取
    btcEthHint = fmt.Sprintf(" | BTC/ETH≥%.0f USDT (根据账户规模动态调整)", minBTCETH)
} else {
    btcEthHint = fmt.Sprintf(" | BTC/ETH≥%.0f USDT", minBTCETH)  // ✅ 動態計算
}
```

**Benefits:**

- ✅ **Single Source of Truth**: 閾值統一從 `btcEthSizeRules` 配置讀取
- ✅ **一致性**: buildSystemPrompt 和 calculateMinPositionSize 使用相同配置
- ✅ **易於維護**: 修改閾值只需改一處配置
- ✅ **避免不一致**: 消除硬編碼數字不匹配的風險

**Test Results:**
```bash
✅ go test ./decision -v  # 所有測試通過 (26 cases)
✅ go fmt ./...           # 格式化完成
```

**現在兩處都已使用 map 配置:**
1. ✅ Line 337 (buildSystemPrompt) - 本次修復
2. ✅ Line 739 (calculateMinPositionSize) - 上次已修復

Addresses code review feedback from PR NoFxAiOS#907 (完整修復):
- Line 337: "Can we have a map for this instead of Ashoc if else?" ✅
- Line 739: "Also a map would be better here." ✅

Co-authored-by: xqliu
the-dev-z added a commit to the-dev-z/nofx that referenced this pull request Nov 12, 2025
the-dev-z and others added 2 commits November 12, 2025 19:43
…AiOS#937)

## Problem

PR NoFxAiOS#917 fixed the validation logic but missed fixing the button disabled state:

**Issue:**
- Button enabled/disabled check uses raw input length (includes "0x")
- Validation logic uses normalized length (excludes "0x")
- **Result:** Button can be enabled with insufficient hex characters

**Example scenario:**
1. User inputs: `0x` + 30 hex chars = 32 total chars
2. Button check: `32 < 32` → false → ✅ Button enabled
3. User clicks button
4. Validation: normalized to 30 hex chars → `30 < 32` → ❌ Error
5. Error message: "需要至少 32 個字符" (confusing!)

## Root Cause

**Lines 230 & 301**: Button disabled conditions don't normalize input

```typescript
// ❌ Before: Checks raw length including "0x"
disabled={part1.length < expectedPart1Length || processing}
disabled={part2.length < expectedPart2Length}
```

## Solution

Normalize input before checking length in disabled conditions:

```typescript
// ✅ After: Normalize before checking
disabled={
  (part1.startsWith('0x') ? part1.slice(2) : part1).length <
    expectedPart1Length || processing
}
disabled={
  (part2.startsWith('0x') ? part2.slice(2) : part2).length <
  expectedPart2Length
}
```

## Testing

| Input | Total Length | Normalized Length | Button (Before) | Button (After) | Click Result |
|-------|--------------|-------------------|-----------------|----------------|--------------|
| `0x` + 30 hex | 32 | 30 | ✅ Enabled (bug) | ❌ Disabled | N/A |
| `0x` + 32 hex | 34 | 32 | ✅ Enabled | ✅ Enabled | ✅ Valid |
| 32 hex | 32 | 32 | ✅ Enabled | ✅ Enabled | ✅ Valid |

## Impact

- ✅ Button state now consistent with validation logic
- ✅ Users won't see confusing "need 32 chars" errors when button is enabled
- ✅ Better UX - button only enabled when input is truly valid

**Related:** Follow-up to PR NoFxAiOS#917

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: the-dev-z <the-dev-z@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
## Problem

Small accounts (<20 USDT) cannot trade BTC/ETH due to fixed minimum amount (60 USDT) exceeding account size.

**User Report:**
- Net worth: 6.97 USDT
- AI suggested: 30 USDT position
- Validation failed: "Minimum 60 USDT required"
- Creates "chicken-and-egg" problem: account too small for mainstream coins

## Solution

Implement dynamic minimum amount calculation strategy:
- Small accounts (<20U): BTC/ETH minimum 12 USDT (same as altcoins)
- Medium accounts (20-100U): Linear interpolation 12-60 USDT (smooth transition)
- Large accounts (≥100U): Standard 60 USDT (maintain original strategy)
- Altcoins: Fixed 12 USDT (unchanged)

## Changes

### 1. New Function: `calculateMinPositionSize()`
- Dynamically calculates minimum position size based on account equity and symbol
- Example: 6.97U account → BTC/ETH minimum 12U, 60U account → 36U
- Uses configuration-driven approach with `positionSizeConfig` struct

### 2. Configuration System
- `btcEthSizeRules`: BTC/ETH tiered rules by account size
- `altcoinSizeRules`: Altcoin rules (always absolute minimum)
- `symbolSizeRules`: Map for symbol-specific rules (easy to extend)

### 3. Updated Validation Logic (`validateDecision`)
- Replaced fixed constants with dynamic calculation
- Special error messages for small accounts
- Maintains safety margins for all account sizes

### 4. Updated AI Prompt (`buildSystemPrompt`)
- Dynamic prompts based on account size
- Small accounts: "BTC/ETH≥12 USDT (⚠️ Small account mode)"
- Medium accounts: "BTC/ETH≥XX USDT (Dynamically adjusted)"
- Large accounts: "BTC/ETH≥60 USDT"

## Testing

**Comprehensive unit tests added (286 lines):**
- ✅ All branches covered (altcoins, BTC/ETH 3 size tiers)
- ✅ Boundary tests (0U, 19.99U, 20U, 99.99U, 100U)
- ✅ Linear interpolation verification (monotonicity, accuracy)
- ✅ Edge cases (zero equity, negative equity, unknown symbols)
- ✅ Performance benchmarks
- Total: 16 test cases, all passing

**Test Results:**
```
$ go test ./decision/... -v -run TestCalculateMinPositionSize
=== RUN   TestCalculateMinPositionSize
--- PASS: TestCalculateMinPositionSize (0.00s)
=== RUN   TestCalculateMinPositionSize_EdgeCases
--- PASS: TestCalculateMinPositionSize_EdgeCases (0.00s)
=== RUN   TestCalculateMinPositionSize_LinearInterpolation
--- PASS: TestCalculateMinPositionSize_LinearInterpolation (0.00s)
=== RUN   TestCalculateMinPositionSize_MapConfiguration
--- PASS: TestCalculateMinPositionSize_MapConfiguration (0.00s)
PASS
ok      nofx/decision   0.314s
```

## Benefits

- ✅ **Lowers barrier**: Small accounts can now trade BTC/ETH
- ✅ **Smooth transition**: Linear interpolation avoids hard cutoffs
- ✅ **Configurable**: Easy to adjust thresholds via configuration
- ✅ **Extensible**: Simple to add new symbols or size tiers
- ✅ **Well-tested**: Comprehensive test coverage for all branches
- ✅ **Backward compatible**: Large accounts maintain original behavior

## Impact

- Only affects position opening validation logic
- Does not affect existing positions
- Improves UX for small accounts
- Maintains safety margins for large accounts

## Files Changed

```
decision/engine.go                    | 113 +++++++++++---
decision/engine_position_size_test.go | 286 ++++++++++++++++++++++++++++++
2 files changed, 381 insertions(+), 18 deletions(-)
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@the-dev-z the-dev-z force-pushed the fix/dynamic-min-position-size branch from ed48fed to 393d0ad Compare November 12, 2025 12:10
@github-actions
Copy link

🤖 Advisory Check Results

These are advisory checks to help improve code quality. They won't block your PR from being merged.

📋 PR Information

Title Format: ✅ Good - Follows Conventional Commits
PR Size: 🟡 Medium (399 lines: +381 -18)

🔧 Backend Checks

Go Formatting: ✅ Good
Go Vet: ✅ Good
Tests: ✅ Passed

Fix locally:

go fmt ./...      # Format code
go vet ./...      # Check for issues
go test ./...     # Run tests

⚛️ Frontend Checks

Build & Type Check: ✅ Success

Fix locally:

cd web
npm run build  # Test build (includes type checking)

📖 Resources

Questions? Feel free to ask in the comments! 🙏


These checks are advisory and won't block your PR from being merged. This comment is automatically generated from pr-checks-run.yml.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.