Commit Graph

666 Commits

Author SHA1 Message Date
Icy 95fa1263f5 merge dev 2025-11-12 23:40:58 +08:00
Icy 9933e3164d Merge branch 'dev' into beta
# Conflicts:
#	.github/workflows/docker-build.yml
#	.gitignore
#	api/server.go
#	config/config.go
#	config/database.go
#	decision/engine.go
#	docker-compose.yml
#	go.mod
#	go.sum
#	logger/telegram_sender.go
#	main.go
#	mcp/client.go
#	prompts/adaptive.txt
#	prompts/default.txt
#	prompts/nof1.txt
#	start.sh
#	trader/aster_trader.go
#	trader/auto_trader.go
#	trader/binance_futures.go
#	trader/hyperliquid_trader.go
#	web/package-lock.json
#	web/package.json
#	web/src/App.tsx
#	web/src/components/AILearning.tsx
#	web/src/components/AITradersPage.tsx
#	web/src/components/CompetitionPage.tsx
#	web/src/components/EquityChart.tsx
#	web/src/components/Header.tsx
#	web/src/components/LoginPage.tsx
#	web/src/components/RegisterPage.tsx
#	web/src/components/TraderConfigModal.tsx
#	web/src/components/TraderConfigViewModal.tsx
#	web/src/components/landing/FooterSection.tsx
#	web/src/components/landing/HeaderBar.tsx
#	web/src/contexts/AuthContext.tsx
#	web/src/i18n/translations.ts
#	web/src/lib/api.ts
#	web/src/lib/config.ts
#	web/src/types.ts
2025-11-12 23:20:25 +08:00
Ember bfb409e8a1 fix(web): unify password validation logic in RegisterPage (#943)
Remove duplicate password validation logic to ensure consistency.
Changes:
- Remove custom isStrongPassword function (RegisterPage.tsx:569-576)
- Use PasswordChecklist validation result (passwordValid state) instead
- Add comprehensive test suite with 28 test cases
- Configure Vitest with jsdom environment and setup file
Test Coverage:
- Password validation rules (length, uppercase, lowercase, number, special chars)
- Special character consistency (/[@#$%!&*?]/)
- Edge cases and boundary conditions
- Refactoring consistency verification
All 78 tests passing (25 + 25 + 28).
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-12 21:54:54 +08:00
0xYYBB | ZYY | Bobo 9e5688609e fix: improve two-stage private key input UX (32+32 → 58+6 split) (#942)
## Problem
Users reported that the 32+32 character split design is not user-friendly:
1.  Second stage still requires entering 32 characters - hard to count
2.  Need to count many characters in both stages
3.  Easy to make mistakes when counting
## Solution
Change the split from 32+32 to **58+6**
**Stage 1**: 58 characters
- Enter the majority of the key (90%)
- Easy to copy/paste the prefix
**Stage 2**: 6 characters
-  Only need to count last 6 chars (very easy)
-  Quick verification of key suffix
-  Reduces user errors
## Changes
```typescript
// Old: Equal split
const expectedPart1Length = Math.ceil(expectedLength / 2)  // 32
const expectedPart2Length = expectedLength - expectedPart1Length  // 32
// New: Most of key + last 6 chars
const expectedPart1Length = expectedLength - 6  // 58
const expectedPart2Length = 6  // Last 6 characters
```
## Test plan
 Frontend builds successfully (npm run build)
 User-friendly: Only need to count 6 characters
 Maintains security: Two-stage input logic unchanged
Co-authored-by: the-dev-z <the-dev-z@users.noreply.github.com>
2025-11-12 21:37:55 +08:00
0xYYBB | ZYY | Bobo a8c87125fa fix(web): fix button disabled validation to normalize 0x prefix (#937)
## Problem
PR #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 #917
Co-authored-by: the-dev-z <the-dev-z@users.noreply.github.com>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-12 19:43:00 +08:00
tinkle-community 9385547937 Feat/hyperliquid agent wallet docs (#936)
* 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
Co-Authored-By: tinkle-community <tinklefund@gmail.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
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
---------
Co-authored-by: tinkle <tinkle@tinkle.community>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-12 19:05:04 +08:00
tinkle-community 24ed1999d9 feat(docs): add Hyperliquid Agent Wallet tutorial for all languages (#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
Co-authored-by: tinkle <tinkle@tinkle.community>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-12 18:29:37 +08:00
0xYYBB | ZYY | Bobo 5fec086434 fix(web): add auth guards to prevent unauthorized API calls (#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 #881, which had conflicts due to PR #872
(frontend refactor with React Router). This implementation is based on
the latest upstream/dev with the new architecture.
Related: PR #881 (closed), PR #872 (Frontend Refactor)
Co-authored-by: the-dev-z <the-dev-z@users.noreply.github.com>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-12 17:56:36 +08:00
0xYYBB | ZYY | Bobo 21cc6e0bcd fix(docker): fix healthcheck failures in docker-compose.yml (#906) 2025-11-12 15:35:25 +08:00
0xYYBB | ZYY | Bobo ea3be2e916 chore: fix go formatting for test files (#931) 2025-11-12 15:33:43 +08:00
0xYYBB | ZYY | Bobo e0b4d026d3 feat(market): add data staleness detection (Part 2/3) (#800)
* feat(market): add data staleness detection
## 問題背景
解決 PR #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 #703** (Part 2/3)
- 基於最新 upstream/dev (3112250)
- 依賴: 無
- 前置: Part 1 (OI 時間序列) - 已提交 PR #798
- 後續: Part 3 (手續費率傳遞)
Co-Authored-By: tinkle-community <tinklefund@gmail.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.
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
---------
Co-authored-by: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
Co-authored-by: Shui <88711385+hzb1115@users.noreply.github.com>
2025-11-11 21:41:26 -05:00
Ember dbb05f7fde feat(ui): Add an automated Web Crypto environment check (#908)
* feat: add web crypto environment check
* fix: auto check env
* refactor:  WebCryptoEnvironmentCheck  swtich to map
2025-11-11 21:22:55 -05:00
0xYYBB | ZYY | Bobo 7afe1f1bad improve(web): improve UX messages for empty states and error feedback (#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.
Co-authored-by: the-dev-z <the-dev-z@users.noreply.github.com>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-11 21:21:07 -05:00
0xYYBB | ZYY | Bobo 79f625ace2 fix(web): restore missing system_prompt_template field in trader edit request (#922)
* fix(web): restore missing system_prompt_template in handleSaveEditTrader
修復編輯交易員時策略模板無法保存的問題。
Issue:
- 用戶編輯交易員時,選擇的策略模板(system_prompt_template)沒有被保存
- 重新打開編輯窗口,總是顯示默認值
- 用戶困惑為什麼策略模板無法持久化
Root Cause:
- PR #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 #872: UI 重構時遺漏
- commit c1f080f5: 原始添加 system_prompt_template 支持
- commit e58fc3c2: 修復 types.ts 缺失字段
Co-Authored-By: tinkle-community <tinklefund@gmail.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 字段對應
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
---------
Co-authored-by: the-dev-z <the-dev-z@users.noreply.github.com>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-11 21:00:42 -05:00
0xYYBB | ZYY | Bobo 70a6218704 fix(ui): remove duplicate exchange configuration fields (Aster & Hyperliquid) (#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 規範
Co-Authored-By: tinkle-community <tinklefund@gmail.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 5462eba0)
- Hyperliquid 翻譯 key 已存在於 translations.ts (lines 206-216, 1017-1027)
Co-Authored-By: tinkle-community <tinklefund@gmail.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 名稱而非翻譯文本
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
---------
Co-authored-by: the-dev-z <the-dev-z@users.noreply.github.com>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-11 20:59:57 -05:00
0xYYBB | ZYY | Bobo 1e0da2ee39 fix(web): fix two-stage private key input validation to support 0x prefix (#917)
## Problem
Users entering private keys with "0x" prefix failed validation incorrectly:
**Scenario:**
- User inputs: `0x1234...` (34 characters including "0x")
- Expected part1 length: 32 characters
- **Bug**: Code checks `part1.length < 32` → `34 < 32` →  FALSE → "Key too long" error
- **Actual**: Should normalize to `1234...` (32 chars) →  Valid
**Impact:**
- Users cannot paste keys from wallets (most include "0x")
- Confusing UX - valid keys rejected
- Forces manual "0x" removal
## Root Cause
**File**: `web/src/components/TwoStageKeyModal.tsx`
**Lines 77-84** (handleStage1Next):
```typescript
//  Bug: Checks length before normalizing
if (part1.length < expectedPart1Length) {
  // Fails for "0x..." inputs
}
```
**Lines 132-143** (handleStage2Complete):
```typescript
//  Bug: Same issue
if (part2.length < expectedPart2Length) {
  // Fails for "0x..." inputs
}
//  Bug: Concatenates without normalizing part1
const fullKey = part1 + part2 // May have double "0x"
```
## Solution
### Fix 1: Normalize before validation
**Lines 77-79**:
```typescript
//  Normalize first, then validate
const normalized1 = part1.startsWith('0x') ? part1.slice(2) : part1
if (normalized1.length < expectedPart1Length) {
  // Now correctly handles both "0x..." and "1234..."
}
```
**Lines 134-136**:
```typescript
//  Same for part2
const normalized2 = part2.startsWith('0x') ? part2.slice(2) : part2
if (normalized2.length < expectedPart2Length) {
  // ...
}
```
### Fix 2: Normalize before concatenation
**Lines 145-147**:
```typescript
//  Remove "0x" from both parts before concatenating
const normalized1 = part1.startsWith('0x') ? part1.slice(2) : part1
const fullKey = normalized1 + normalized2
// Result: Always 64 characters without "0x"
```
## Testing
**Manual Test Cases:**
| Input Type | Part 1 | Part 2 | Before | After |
|------------|--------|--------|--------|-------|
| **No prefix** | `1234...` (32) | `5678...` (32) |  Pass |  Pass |
| **With prefix** | `0x1234...` (34) | `0x5678...` (34) |  Fail |  Pass |
| **Mixed** | `0x1234...` (34) | `5678...` (32) |  Fail |  Pass |
| **Both prefixed** | `0x1234...` (34) | `0x5678...` (34) |  Fail |  Pass |
**Validation consistency:**
- Before: `validatePrivateKeyFormat` normalizes, but input checks don't 
- After: Both normalize the same way 
## Impact
-  Users can paste keys directly from wallets
-  Supports both `0x1234...` and `1234...` formats
-  Consistent with `validatePrivateKeyFormat` logic
-  Better UX - no manual "0x" removal needed
**Files changed**: 1 frontend file
- web/src/components/TwoStageKeyModal.tsx (+6 lines, -2 lines)
Co-authored-by: the-dev-z <the-dev-z@users.noreply.github.com>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-11 20:52:14 -05:00
0xYYBB | ZYY | Bobo 1d1b31f1f1 fix(trader): add backend safety checks for partial_close (#713)
* fix(trader): add backend safety checks for partial_close
After PR #415 added partial_close functionality, production users reported two critical issues:
1. **Exchange minimum value error**: "Order must have minimum value of $10" when remaining position value falls below exchange threshold
2. **Unprotected positions after partial close**: Exchanges auto-cancel TP/SL orders when position size changes, leaving remaining position exposed to liquidation risk
This PR adds **backend safety checks** as a safety net layer that complements the prompt-based rules from PR #712.
**Protection**: Before executing partial_close, verify remaining position value > $10
```go
const MIN_POSITION_VALUE = 10.0 // Exchange底线
remainingValue := remainingQuantity * markPrice
if remainingValue > 0 && remainingValue <= MIN_POSITION_VALUE {
    // 🔄 Auto-correct to full close
    decision.Action = "close_long" // or "close_short"
    return at.executeCloseLongWithRecord(decision, actionRecord)
}
```
**Behavior**:
- Position $20 → partial_close 50% → remaining $10 ≤ $10 → Auto full close 
- Position $30 → partial_close 50% → remaining $15 > $10 → Allow partial close 
**Protection**: Restore TP/SL orders for remaining position if AI provides new_stop_loss/new_take_profit
```go
// Exchanges auto-cancel TP/SL when position size changes
if decision.NewStopLoss > 0 {
    at.trader.SetStopLoss(symbol, side, remainingQuantity, decision.NewStopLoss)
}
if decision.NewTakeProfit > 0 {
    at.trader.SetTakeProfit(symbol, side, remainingQuantity, decision.NewTakeProfit)
}
// Warning if AI didn't provide new TP/SL
if decision.NewStopLoss <= 0 && decision.NewTakeProfit <= 0 {
    log.Printf("⚠️⚠️⚠️ Warning: Remaining position has NO TP/SL protection")
}
```
**Improvement**: Show position quantity and value to help AI make better decisions
```
Before: | 入场价100.00 当前价105.00 | 盈亏+5.00% | ...
After:  | 入场价100.00 当前价105.00 | 数量0.5000 | 仓位价值52.50 USDT | 盈亏+5.00% | ...
```
**Benefits**:
- AI can now calculate: remaining_value = current_value × (1 - close_percentage/100)
- AI can proactively avoid decisions that would violate $10 threshold
- Added MIN_POSITION_VALUE check before execution
- Auto-correct to full close if remaining value ≤ $10
- Restore TP/SL for remaining position
- Warning logs when AI doesn't provide new TP/SL
- Import "math" package
- Calculate and display position value
- Add quantity and position value to prompt
- Complements PR #712 (Prompt v6.0.0 safety rules)
- Addresses #301 (Backend layer)
- Based on PR #415 (Core functionality)
| Layer | Location | Purpose |
|-------|----------|---------|
| **Layer 1: AI Prompt** | PR #712 | Prevent bad decisions before they happen |
| **Layer 2: Backend** | This PR | Auto-correct and safety net |
**Together they provide**:
-  AI makes better decisions (sees position value, knows rules)
-  Backend catches edge cases (auto-corrects violations)
-  User-friendly warnings (explains what happened)
- [x] Compiles successfully (`go build ./...`)
- [x] MIN_POSITION_VALUE logic correct
- [x] TP/SL restoration logic correct
- [x] Position value display format validated
- [x] Auto-correction flow tested
This PR can be merged **independently** of PR #712, or together.
Suggested merge order:
1. PR #712 (Prompt v6.0.0) - AI layer improvements
2. This PR (Backend safety) - Safety net layer
Or merge together for complete two-layer protection.
---
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* fix: add error handling for markPrice type assertion
- Check type assertion success before using markPrice
- Return error if markPrice is invalid or <= 0
- Addresses code review feedback from @xqliu in PR #713
* test(trader): add comprehensive unit tests for partial_close safety checks
- Test minimum position value check (< 10 USDT triggers full close)
- Test boundary condition (exactly 10 USDT also triggers full close)
- Test stop-loss/take-profit recovery after partial close
- Test edge cases (invalid close percentages)
- Test integration scenarios with mock trader
All 14 test cases passed, covering:
1. MinPositionCheck (5 cases): normal, small remainder, boundary, edge cases
2. StopLossTakeProfitRecovery (4 cases): both/SL only/TP only/none
3. EdgeCases (4 cases): zero/over 100/negative/normal percentages
4. Integration (2 cases): LONG with SL/TP, SHORT with auto full close
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* style: apply go fmt after rebase
Only formatting changes:
- api/server.go: fix indentation
- manager/trader_manager.go: add blank line
- trader/partial_close_test.go: align struct fields
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* fix(test): rename MockTrader to MockPartialCloseTrader to avoid conflict
Problem:
- trader/partial_close_test.go defined MockTrader
- trader/auto_trader_test.go already has MockTrader
- Methods CloseLong, CloseShort, SetStopLoss, SetTakeProfit were declared twice
- Compilation failed with 'already declared' errors
Solution:
- Rename MockTrader to MockPartialCloseTrader in partial_close_test.go
- This avoids naming conflict while keeping test logic independent
Test Results:
- All partial close tests pass
- All trader tests pass
Related: PR #713
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
---------
Co-authored-by: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
Co-authored-by: the-dev-z <the-dev-z@users.noreply.github.com>
2025-11-11 20:36:16 -05:00
Lawrence Liu 9d721621f2 feat: Add decision limit selector with 5/10/20/50 options (#638)
## Summary
Allow users to select the number of decision records to display (5/10/20/50)
in the Web UI, with persistent storage in localStorage.
## Changes
### Backend
- api/server.go: Add 'limit' query parameter support to /api/decisions/latest
  - Default: 5 (maintains current behavior)
  - Max: 50 (prevents excessive data loading)
  - Fully backward compatible
### Frontend
- web/src/lib/api.ts: Update getLatestDecisions() to accept limit parameter
- web/src/pages/TraderDashboard.tsx:
  - Add decisionLimit state management with localStorage persistence
  - Add dropdown selector UI (5/10/20/50 options)
  - Pass limit to API calls and update SWR cache key
## Time Coverage
- 5 records = 15 minutes (default, quick check)
- 10 records = 30 minutes (short-term review)
- 20 records = 1 hour (medium-term analysis)
- 50 records = 2.5 hours (deep pattern analysis)
2025-11-11 20:34:29 -05:00
Ember 4920c28cc6 fix: fix build error (#895) 2025-11-11 15:36:12 +08:00
Ember 3bf69b758b Refactor(UI) : Refactor Frontend: Unified Toasts with Sonner, Introduced Layout System, and Integrated React Router (#872) 2025-11-10 23:19:17 -05:00
Ember ddc4cdeb60 fix: 修复小屏幕设备上对话框高度过高无法滚动的问题 (#681) 2025-11-10 23:17:12 -05:00
zpng a010cc2190 FIX: 编译错误,strconv imported and not used (#891) 2025-11-10 22:23:46 -05:00
Lawrence Liu 6efe733127 feat(decision): auto-reload prompt templates when starting trader (#833)
* feat: 启动交易员时自动重新加载系统提示词模板
## 改动内容
- 在 handleStartTrader 中调用 decision.ReloadPromptTemplates()
- 每次启动交易员时从硬盘重新加载 prompts/ 目录下的所有 .txt 模板文件
- 添加完整的单元测试和端到端集成测试
## 测试覆盖
- 单元测试:模板加载、获取、重新加载功能
- 集成测试:文件修改 → 重新加载 → 决策引擎使用新内容的完整流程
- 并发测试:验证多 goroutine 场景下的线程安全性
- Race detector 测试通过
## 用户体验改进
- 修改 prompt 文件后无需重启服务
- 只需停止交易员再启动即可应用新的 prompt
- 控制台会输出重新加载成功的日志提示
* feat: 在重新加载日志中显示当前使用的模板名称
* feat: fallback 到 default 模板时明确显示原因
* fix: correct GetTraderConfig return type to get SystemPromptTemplate
* refactor: extract reloadPromptTemplatesWithLog as reusable method
2025-11-10 21:37:46 -05:00
0xYYBB | ZYY | Bobo 57e31b2ace fix(trader): add mutex to prevent race condition in Meta refresh (#796)
* fix(trader): add mutex to prevent race condition in Meta refresh (issue #742)
**問題**:
根據 issue #742 審查標準,發現 BLOCKING 級別的並發安全問題:
- refreshMetaIfNeeded() 中的 `t.meta = meta` 缺少並發保護
- 多個 goroutine 同時調用 OpenLong/OpenShort 會造成競態條件
**修復**:
1. 添加 sync.RWMutex 保護 meta 字段
2. refreshMetaIfNeeded() 使用寫鎖保護 meta 更新
3. getSzDecimals() 使用讀鎖保護 meta 訪問
**符合標準**:
- issue #742: "並發安全問題需使用 sync.Once 等機制"
- 使用 RWMutex 實現讀寫分離,提升並發性能
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* test(trader): add comprehensive race condition tests for meta field mutex protection
- Test concurrent reads (100 goroutines accessing getSzDecimals)
- Test concurrent read/write (50 readers + 10 writers simulating meta refresh)
- Test nil meta edge case (returns default value 4)
- Test valid meta with multiple coins (BTC, ETH, SOL)
- Test massive concurrency (1000 iterations with race detector)
All 5 test cases passed, including -race verification with no data races detected.
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
---------
Co-authored-by: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-10 20:50:56 -05:00
0xYYBB | ZYY | Bobo 6a66913194 fix(decision): 添加槓桿超限 fallback 機制並澄清盈虧計算說明 (#716)
* fix(decision): 添加槓桿超限 fallback 機制並澄清盈虧計算說明
1. AI 決策輸出超限槓桿時(如 20x),驗證直接拒絕導致整個交易週期失敗
2. Prompt 未明確說明盈虧百分比已包含槓桿效應,導致 AI 思維鏈中誤用價格變動%
- **Before**: 超限直接報錯 → 決策失敗
- **After**: 自動降級為配置上限 → 決策繼續執行
- **效果**: SOLUSDT 20x → 自動修正為 5x(配置上限)
- 明確告知 AI:系統提供的「盈虧%」已包含槓桿效應
- 公式: 盈虧% = (未實現盈虧 / 保證金) × 100
- 示例: 5x 槓桿,價格漲 2% = 實際盈利 10%
- 測試山寨幣超限修正(20x → 5x)
- 測試 BTC/ETH 超限修正(20x → 10x)
- 測試正常範圍不修正
- 測試無效槓桿拒絕
```
PASS: TestLeverageFallback/山寨币杠杆超限_自动修正为上限
PASS: TestLeverageFallback/BTC杠杆超限_自动修正为上限
PASS: TestLeverageFallback/杠杆在上限内_不修正
PASS: TestLeverageFallback/杠杆为0_应该报错
```
-  向後兼容:正常槓桿範圍不受影響
-  容錯性增強:AI 輸出超限時系統自動修正
-  決策質量提升:AI 對槓桿收益有正確認知
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* style: apply go fmt after rebase
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
---------
Co-authored-by: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-10 20:47:46 -05:00
Sue e49aa09de1 fix: 修复币安白名单IP复制功能失效问题 (#680)
## 🐛 问题描述
币安交易所配置页面中的服务器IP复制功能无法正常工作
## 🔍 根因分析
原始实现仅使用 navigator.clipboard.writeText() API:
- 在某些浏览器环境下不可用或被阻止
- 需要 HTTPS 或 localhost 环境
- 缺少错误处理和用户反馈
##  修复方案
1. **双重降级机制**:
   - 优先使用现代 Clipboard API
   - 降级到传统 execCommand 方法
2. **错误处理**:
   - 添加 try-catch 错误捕获
   - 失败时显示友好的错误提示
   - 提供IP地址供用户手动复制
3. **多语言支持**:
   - 添加 copyIPFailed 翻译键(中英文)
## 📝 修改文件
- web/src/components/AITradersPage.tsx
  - handleCopyIP 函数重构为异步函数
  - 添加双重复制机制和错误处理
- web/src/i18n/translations.ts
  - 添加 copyIPFailed 错误提示翻译
## 🧪 测试验证
 TypeScript 编译通过
 Vite 构建成功
 支持现代和传统浏览器环境
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-10 20:34:22 -05:00
0xYYBB | ZYY | Bobo e6689eeb5b fix(web): display '—' for missing data instead of NaN% or 0% (#678)
* fix(web): display '—' for missing data instead of NaN% or 0% (#633)
- Add hasValidData validation for null/undefined/NaN
- Display '—' for invalid trader.total_pnl_pct
- Only show gap calculations when both values are valid
- Prevents misleading users with 0% when data is missing
Fixes #633
* test(web): add comprehensive unit tests for CompetitionPage NaN handling
- Test data validation logic (null/undefined/NaN detection)
- Test gap calculation with valid and invalid data
- Test display formatting (shows '—' instead of 'NaN%')
- Test leading/trailing message display conditions
- Test edge cases (Infinity, very small/large numbers)
All 25 test cases passed, covering:
1. hasValidData check (7 cases): valid/null/undefined/NaN/zero/negative
2. gap calculation (3 cases): valid data, invalid data, negative gap
3. display formatting (6 cases): positive/negative/null/undefined/NaN/zero
4. leading/trailing messages (5 cases): conditional display logic
5. edge cases (4 cases): Infinity, -Infinity, very small/large numbers
Related to PR #678 - ensures missing data displays as '—' instead of 'NaN%'.
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
---------
Co-authored-by: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-10 20:30:03 -05:00
0xYYBB | ZYY | Bobo bb2d81cbd4 fix(auth): allow re-fetching OTP for unverified users (#653)
* fix(auth): allow re-fetching OTP for unverified users
**Problem:**
- User registers but interrupts OTP setup
- Re-registration returns "邮箱已被注册" error
- User stuck, cannot retrieve QR code to complete setup
**Root Cause:**
- handleRegister rejects all existing emails without checking OTPVerified status
- No way for users to recover from interrupted registration
**Fix:**
- Check if existing user has OTPVerified=false
- If unverified, return original OTP QR code instead of error
- User can continue completing registration with same user_id
- If verified, still reject with "邮箱已被注册" (existing behavior)
**Code Changes:**
```go
// Before:
_, err := s.database.GetUserByEmail(req.Email)
if err == nil {
    c.JSON(http.StatusConflict, gin.H{"error": "邮箱已被注册"})
    return
}
// After:
existingUser, err := s.database.GetUserByEmail(req.Email)
if err == nil {
    if !existingUser.OTPVerified {
        // Return OTP to complete registration
        qrCodeURL := auth.GetOTPQRCodeURL(existingUser.OTPSecret, req.Email)
        c.JSON(http.StatusOK, gin.H{
            "user_id": existingUser.ID,
            "otp_secret": existingUser.OTPSecret,
            "qr_code_url": qrCodeURL,
            "message": "检测到未完成的注册,请继续完成OTP设置",
        })
        return
    }
    c.JSON(http.StatusConflict, gin.H{"error": "邮箱已被注册"})
    return
}
```
**Testing Scenario:**
1. User POST /api/register with email + password
2. User receives OTP QR code but closes browser (interrupts)
3. User POST /api/register again with same email + password
4.  Now returns original OTP instead of error
5. User can complete registration via /api/complete-registration
**Security:**
 No security issue - still requires OTP verification
 Only returns OTP for unverified accounts
 Password not validated on re-fetch (same as initial registration)
**Impact:**
 Users can recover from interrupted registration
 Better UX for registration flow
 No breaking changes to existing verified users
**API Changes:**
- POST /api/register response for unverified users:
  - Status: 200 OK (was: 409 Conflict)
  - Body includes: user_id, otp_secret, qr_code_url, message
Fixes #615
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* test(api): add comprehensive unit tests for OTP re-fetch logic
- Test OTP re-fetch logic for unverified users
- Test OTP verification state handling
- Test complete registration flow scenarios
- Test edge cases (ID=0, empty OTPSecret, verified users)
All 11 test cases passed, covering:
1. OTPRefetchLogic (3 cases): new user, unverified refetch, verified rejection
2. OTPVerificationStates (2 cases): verified/unverified states
3. RegistrationFlow (3 cases): first registration, interrupted resume, duplicate attempt
4. EdgeCases (3 cases): validates behavior with edge conditions
Related to PR #653 - ensures proper OTP re-fetch behavior for unverified users.
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* style: apply go fmt after rebase
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
---------
Co-authored-by: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-10 20:29:02 -05:00
darkedge 463a092a64 feat: 添加AI请求耗时记录,优化性能评估 (#587)
# Conflicts:
#	decision/engine.go
2025-11-10 20:18:39 -05:00
Deloz e06f7517a6 fix(auth): 修复TraderConfigModal使用错误的token key (#882) 2025-11-10 12:44:34 -05:00
Lawrence Liu 4fde70caa3 fix(market): add 3m volume and ATR14 indicators to AI data (#830)
* Support 3m volume and ATR4
* test(market): add unit tests for Volume and ATR14 indicators
- Add comprehensive tests for calculateIntradaySeries Volume collection
- Add tests for ATR14 calculation with various data scenarios
- Add edge case tests for insufficient data
- Test Volume value precision and consistency with other indicators
- All 8 test cases pass successfully
Resolves code review blocking issue from PR #830
2025-11-10 12:13:09 -05:00
CoderMageFox c8684bc6e7 fix(auth): align PasswordChecklist special chars with validation logic (#860)
修复密码验证UI组件与验证逻辑之间的特殊字符不一致问题。
问题描述:
- PasswordChecklist组件默认接受所有特殊字符(如^_-~等)
- 实际验证函数isStrongPassword()仅接受@#$%!&*?共8个特殊字符
- 导致用户输入包含其他特殊字符时,UI显示绿色勾选但注册按钮仍禁用
修改内容:
- 在RegisterPage.tsx的PasswordChecklist组件添加specialCharsRegex属性
- 限制特殊字符为/[@#$%!&*?]/,与isStrongPassword()保持一致
影响范围:
- 仅影响注册页面的密码验证UI显示
- 不影响后端验证逻辑
- 提升用户体验,避免误导性的UI反馈
Closes #859
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-10 08:08:52 -07:00
CoderMageFox 2b3d064e6c docs: improve override_base_prompt explanation and update maintainer (#852)
- Add visual diagrams to explain override_base_prompt behavior
- Clarify the difference between "append" (false) and "replace" (true) modes
- Add warning messages for advanced users about risks
- Update maintainer to "Nofx Team CoderMageFox"
- Improve both English and Chinese documentation
This change addresses user confusion about the override_base_prompt setting
by providing clear visual explanations and practical examples.
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-10 14:03:17 +08:00
Diego 9f2c62d692 should not load all user traders when create/update trader (#854) 2025-11-10 14:02:36 +08:00
0xYYBB | ZYY | Bobo 0008c9e188 fix(web): remove circular dependency causing trading symbols input bug (#632) (#671)
**Problem:**
Unable to type comma-separated trading symbols in the input field.
When typing "BTCUSDT," → comma immediately disappears → cannot add more symbols.
**Root Cause:**
Circular state dependency between `useEffect` and `handleInputChange`:
```typescript
//  Lines 146-149: useEffect syncs selectedCoins → formData
useEffect(() => {
  const symbolsString = selectedCoins.join(',')
  setFormData(prev => ({ ...prev, trading_symbols: symbolsString }))
}, [selectedCoins])
// Lines 150-153: handleInputChange syncs formData → selectedCoins
if (field === 'trading_symbols') {
  const coins = value.split(',').map(...).filter(...)
  setSelectedCoins(coins)
}
```
**Execution Flow:**
1. User types: `"BTCUSDT,"`
2. `handleInputChange` fires → splits by comma → filters empty → `selectedCoins = ["BTCUSDT"]`
3. `useEffect` fires → joins → overwrites input to `"BTCUSDT"`  **Trailing comma removed!**
4. User cannot continue typing
**Solution:**
Remove the redundant `useEffect` (lines 146-149) and update `handleCoinToggle` to directly sync both states:
```typescript
//  handleCoinToggle now updates both states
const handleCoinToggle = (coin: string) => {
  setSelectedCoins(prev => {
    const newCoins = prev.includes(coin) ? ... : ...
    // Directly update formData.trading_symbols
    const symbolsString = newCoins.join(',')
    setFormData(current => ({ ...current, trading_symbols: symbolsString }))
    return newCoins
  })
}
```
**Why This Works:**
- **Quick selector buttons** (`handleCoinToggle`): Now updates both states 
- **Manual input** (`handleInputChange`): Already updates both states 
- **No useEffect interference**: User can type freely 
**Impact:**
-  Manual typing of comma-separated symbols now works
-  Quick selector buttons still work correctly
-  No circular dependency
-  Cleaner unidirectional data flow
Fixes #632
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-10 11:57:55 +08:00
0xYYBB | ZYY | Bobo a1f015d45c feat(web): improve trader config UX for initial balance and prompt templates (#629 #630) (#673)
- Add onBlur validation for initial_balance input to enforce minimum of 100
- Add detailed prompt template descriptions with i18n support
- Fix Traditional Chinese to Simplified Chinese
- Extract hardcoded Chinese text to i18n translation system
- Add translation keys for all prompt templates and descriptions
Fixes #629, Fixes #630
2025-11-10 11:55:40 +08:00
CoderMageFox f7498ef346 docs(prompt): add comprehensive prompt writing guide (#837)
* docs: 添加 Prompt 编写指南并更新 README 导航
为 NoFx 系统创建完整的 Prompt 编写指南文档,帮助用户编写高质量的自定义 AI 交易策略提示词。
主要内容:
- 📚 快速开始指南(5分钟上手)
- 💡 核心概念和工作原理
- 📋 完整的可用字段参考(系统状态、账户信息、持仓信息等)
- ⚖️ 系统约束和规则说明
- 📦 三种官方策略模板(保守型/平衡型/激进型)
-  质量检查清单(20+ 检查项)
-  10个常见错误案例和最佳实践
- 🎓 高级话题(完全自定义、调试指南)
文档特点:
- 基于实际代码(decision/engine.go)确保字段准确性
- 三层用户分级(新手/进阶/高级)
- 完整的策略模板即拿即用
-  对比示例避免常见错误
- 20,000+ 字完整覆盖所有关键知识点
同时更新 README.md 添加文档导航链接,方便用户快速访问。
Fixes #654
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* docs(prompt): add i18n support with complete English translation
完善 Prompt 编写指南的国际化支持,添加完整的英文翻译版本。
主要更改:
- 📝 新增英文版本:docs/prompt-guide.md
- 🇨🇳 中文版本重命名:docs/prompt-guide.zh-CN.md
- 🔗 更新 README.md 文档链接,标注语言选项
文档特点:
- 完整翻译所有章节(20,000+ 字)
- 保持中英文结构完全一致
- 遵循项目 i18n 惯例(参考 docs/guides/)
- 便于不同语言用户使用
Related to #654
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
---------
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-10 11:44:17 +08:00
Lawrence Liu 486ff5e909 fix(trader): 修复编辑交易员时系统提示词模板无法更新和回显的问题 (#841)
## 问题描述
1. ⚠️ **无法更新**(最严重):用户修改系统提示词模板并保存后,更新被忽略,仍保持旧值
2. 编辑时显示错误的默认值:打开编辑对话框时该字段显示为 Default 而非实际保存的值(如 nof1)
## 根本原因
1. UpdateTraderRequest 结构体缺少 SystemPromptTemplate 字段 - 后端无法接收更新请求
2. handleGetTraderConfig 返回值中缺少 system_prompt_template 字段 - 前端无法获取实际值
3. handleUpdateTrader 强制使用原值,不接受请求中的更新 - 即使前端发送也被忽略
## 修复内容
1. 在 UpdateTraderRequest 中添加 SystemPromptTemplate 字段 - 现在可以接收更新
2. 在 handleUpdateTrader 中支持从请求读取并更新该字段 - 用户可以修改了
3. 在 handleGetTraderConfig 返回值中添加 system_prompt_template 字段 - 前端可以正确显示
## 测试
- 添加 3 个单元测试验证修复
- 所有测试通过,无回归
- 覆盖 nof1, default, custom 等不同模板场景
## 影响范围
- api/server.go: UpdateTraderRequest, handleUpdateTrader, handleGetTraderConfig
- 新增 api/server_test.go: 3 个单元测试
Closes #838
2025-11-10 01:20:30 +08:00
WquGuru befc887a89 fix(ci): add test encryption key for CI environment (#826)
* fix(ci): add test encryption key for CI environment
- Add DATA_ENCRYPTION_KEY environment variable to PR test workflow
- Add test RSA public key for encryption tests in CI
- Ensures unit tests pass in CI without production credentials
Co-authored-by: tinkle-community <tinklefund@gmail.com>
* fix(ci): install Go cover tool to eliminate covdata warnings
- Add step to install golang.org/x/tools/cmd/cover in CI workflow
- Use || true to prevent installation failure from breaking CI
- Eliminates "no such tool covdata" warnings during test execution
- Apply go fmt to multiple files for consistency
Co-authored-by: tinkle-community <tinklefund@gmail.com>
* fix(ci): install covdata tool for Go 1.23 coverage
The CI was failing with "go: no such tool 'covdata'" error.
This is because Go 1.23 requires the covdata tool to be installed
for coverage reporting.
Changes:
- Install golang.org/x/tools/cmd/covdata in CI workflow
- Update step name to reflect both coverage tools being installed
Fixes the unit test failures in CI pipeline.
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* fix(ci): remove unnecessary covdata installation and use builtin go tool cover
The previous attempt to install golang.org/x/tools/cmd/covdata was failing
because the package structure changed in Go 1.23 and tools v0.38.0.
The covdata tool is not needed for this project since we only use simple
coverage reporting with go test -coverprofile. The go tool cover command
is built into the Go toolchain and requires no additional installation.
Changes:
- Remove failed covdata and cover installation attempts
- Add verification step for go tool cover availability
- Simplify CI pipeline by eliminating unnecessary dependencies
Co-authored-by: tinkle-community <tinklefund@gmail.com>
* fix(ci): upgrade Go version to 1.25 to match go.mod declaration
The CI was using Go 1.23 while go.mod declares go 1.25.0, causing
"no such tool covdata" errors during coverage test compilation.
Go 1.25's coverage infrastructure requires toolchain features not
available in Go 1.23.
This change aligns the CI Go version with the project's declared
version requirement, ensuring the full Go 1.25 toolchain (including
the covdata tool) is available for coverage testing.
Co-authored-by: tinkle-community <tinklefund@gmail.com>
---------
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-09 18:40:03 +08:00
WquGuru 295124c1fa test(trader): add comprehensive unit tests and CI coverage reporting (#823)
* chore(config): add Python and uv support to project
- Add comprehensive Python .gitignore rules (pycache, venv, pytest, etc.)
- Add uv package manager specific ignores (.uv/, uv.lock)
- Initialize pyproject.toml for Python tooling
Co-authored-by: tinkle-community <tinklefund@gmail.com>
* chore(deps): add testing dependencies
- Add github.com/stretchr/testify v1.11.1 for test assertions
- Add github.com/agiledragon/gomonkey/v2 v2.13.0 for mocking
- Promote github.com/rs/zerolog to direct dependency
Co-authored-by: tinkle-community <tinklefund@gmail.com>
* ci(workflow): add PR test coverage reporting
Add GitHub Actions workflow to run unit tests and report coverage on PRs:
- Run Go tests with race detection and coverage profiling
- Calculate coverage statistics and generate detailed reports
- Post coverage results as PR comments with visual indicators
- Fix Go version to 1.23 (was incorrectly set to 1.25.0)
Coverage guidelines:
- Green (>=80%): excellent
- Yellow (>=60%): good
- Orange (>=40%): fair
- Red (<40%): needs improvement
This workflow is advisory only and does not block PR merging.
Co-authored-by: tinkle-community <tinklefund@gmail.com>
* test(trader): add comprehensive unit tests for trader modules
Add unit test suites for multiple trader implementations:
- aster_trader_test.go: AsterTrader functionality tests
- auto_trader_test.go: AutoTrader lifecycle and operations tests
- binance_futures_test.go: Binance futures trader tests
- hyperliquid_trader_test.go: Hyperliquid trader tests
- trader_test_suite.go: Common test suite utilities and helpers
Also fix minor formatting issue in auto_trader.go (trailing whitespace)
Co-authored-by: tinkle-community <tinklefund@gmail.com>
* test(trader): preserve existing calculatePnLPercentage unit tests
Merge existing calculatePnLPercentage tests with incoming comprehensive test suite:
- Preserve TestCalculatePnLPercentage with 9 test cases covering edge cases
- Preserve TestCalculatePnLPercentage_RealWorldScenarios with 3 trading scenarios
- Add math package import for floating-point precision comparison
- All tests validate PnL percentage calculation with different leverage scenarios
Co-authored-by: tinkle-community <tinklefund@gmail.com>
---------
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-09 17:43:28 +08:00
Lawrence Liu 8107667796 fix(database): prevent data loss on Docker restart with WAL mode and graceful shutdown (#817)
* fix(database): prevent data loss on Docker restart with WAL mode and graceful shutdown
Fixes #816
## Problem
Exchange API keys and private keys were being lost after `docker compose restart`.
This P0 bug posed critical security and operational risks.
### Root Cause
1. **SQLite journal_mode=delete**: Traditional rollback journal doesn't protect
   against data loss during non-graceful shutdowns
2. **Incomplete graceful shutdown**: Application relied on `defer database.Close()`
   which may not execute before process termination
3. **Docker grace period**: Default 10s may not be sufficient for cleanup
### Data Loss Scenario
```
User updates exchange config → Backend writes to SQLite → Data in buffer (not fsynced)
→ Docker restart (SIGTERM) → App exits → SQLite never flushes → Data lost
```
## Solution
### 1. Enable WAL Mode (Primary Fix)
- **Before**: `journal_mode=delete` (rollback journal)
- **After**: `journal_mode=WAL` (Write-Ahead Logging)
**Benefits:**
-  Crash-safe even during power loss
-  Better concurrent write performance
-  Atomic commits with durability guarantees
### 2. Improve Graceful Shutdown
**Before:**
```go
<-sigChan
traderManager.StopAll()
// defer database.Close() may not execute in time
```
**After:**
```go
<-sigChan
traderManager.StopAll()    // Step 1: Stop traders
server.Shutdown()          // Step 2: Stop HTTP server (new)
database.Close()           // Step 3: Explicit database close (new)
```
### 3. Increase Docker Grace Period
```yaml
stop_grace_period: 30s  # Allow 30s for graceful shutdown
```
## Changes
### config/database.go
- Enable `PRAGMA journal_mode=WAL` on database initialization
- Set `PRAGMA synchronous=FULL` for data durability
- Add log message confirming WAL mode activation
### api/server.go
- Add `httpServer *http.Server` field to Server struct
- Implement `Shutdown()` method with 5s timeout
- Replace `router.Run()` with `httpServer.ListenAndServe()` for graceful shutdown support
- Add `context` import for shutdown context
### main.go
- Add explicit shutdown sequence:
  1. Stop all traders
  2. Shutdown HTTP server (new)
  3. Close database connection (new)
- Add detailed logging for each shutdown step
### docker-compose.yml
- Add `stop_grace_period: 30s` to backend service
### config/database_test.go (TDD)
- `TestWALModeEnabled`: Verify WAL mode is active
- `TestSynchronousMode`: Verify synchronous=FULL setting
- `TestDataPersistenceAcrossReopen`: Simulate Docker restart scenario
- `TestConcurrentWritesWithWAL`: Verify concurrent write handling
## Test Results
```bash
$ go test -v ./config
=== RUN   TestWALModeEnabled
--- PASS: TestWALModeEnabled (0.25s)
=== RUN   TestSynchronousMode
--- PASS: TestSynchronousMode (0.06s)
=== RUN   TestDataPersistenceAcrossReopen
--- PASS: TestDataPersistenceAcrossReopen (0.05s)
=== RUN   TestConcurrentWritesWithWAL
--- PASS: TestConcurrentWritesWithWAL (0.09s)
PASS
```
All 16 tests pass (including 9 existing + 4 new WAL tests + 3 concurrent tests).
## Impact
**Before:**
- 🔴 Exchange credentials lost on restart
- 🔴 Trading operations disrupted
- 🔴 Security risk from credential re-entry
**After:**
-  Data persistence guaranteed
-  No credential loss after restart
-  Safe graceful shutdown in all scenarios
-  Better concurrent performance
## Acceptance Criteria
- [x] WAL mode enabled in database initialization
- [x] Graceful shutdown explicitly closes database
- [x] Unit tests verify data persistence across restarts
- [x] Docker grace period increased to 30s
- [x] All tests pass
## Deployment Notes
After deploying this fix:
1. Rebuild Docker image: `./start.sh start --build`
2. Existing `config.db` will be automatically converted to WAL mode
3. WAL files (`config.db-wal`, `config.db-shm`) will be created
4. No manual intervention required
## References
- SQLite WAL Mode: https://www.sqlite.org/wal.html
- Go http.Server Graceful Shutdown: https://pkg.go.dev/net/http#Server.Shutdown
* Add config.db* to gitignore
2025-11-09 16:23:00 +08:00
Lawrence Liu 146d2ad9a7 fix: 修复 AI 决策时收到的持仓盈亏百分比未考虑杠杆 (#819)
Fixes #818
## 问题
传递给 AI 决策的持仓盈亏百分比只计算价格变动,未考虑杠杆倍数。
例如:10倍杠杆,价格上涨1%,AI看到的是1%而非实际的10%收益率。
## 改动
1. 修复 buildTradingContext 中的盈亏百分比计算
   - 从基于价格变动改为基于保证金计算
   - 收益率 = 未实现盈亏 / 保证金 × 100%
2. 抽取公共函数 calculatePnLPercentage
   - 消除 buildTradingContext 和 GetPositions 的重复代码
   - 确保两处使用相同的计算逻辑
3. 新增单元测试 (trader/auto_trader_test.go)
   - 9个基础测试用例(正常、边界、异常)
   - 3个真实场景测试(BTC/ETH/SOL不同杠杆)
   - 测试覆盖率:100%
4. 更新 .gitignore
   - 添加 SQLite WAL 相关文件 (config.db-shm, config.db-wal, nofx.db)
## 测试结果
 所有 12 个单元测试通过
 代码编译通过
 与 GetPositions 函数保持一致
## 影响
- AI 现在能够准确评估持仓真实收益率
- 避免因错误数据导致的过早止盈或延迟止损
2025-11-09 16:21:31 +08:00
Lawrence Liu fd3fc654cb Fix 历史最高收益率(百分比), 盈亏金额 USDT, 最高收益率 没有传递给 AI 作决策,无法在 prompt 中使用 (#651) 2025-11-09 16:20:52 +08:00
Ember 576dd26b8b bugfix dashboard empty state (#709) 2025-11-09 14:44:42 +08:00
Deloz 17f4baa6da fix: 支持 NOFX_BACKEND_PORT 环境变量配置端口 (#764)
- 优先级:环境变量 > 数据库配置 > 默认值 8080
- 修复 .env 中端口配置不生效的问题
- 添加端口来源日志输出
2025-11-08 23:21:36 -05:00
Lawrence Liu 594116f141 fix: 修复token过期未重新登录的问题 (#803)
* fix: 修复token过期未重新登录的问题
实现统一的401错误处理机制:
- 创建httpClient封装fetch API,添加响应拦截器
- 401时自动清理localStorage和React状态
- 显示"请先登录"提示并延迟1.5秒后跳转登录页
- 保存当前URL到sessionStorage用于登录后返回
- 改造所有API调用使用httpClient统一处理
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* fix: 添加401处理的单例保护防止并发竞态
问题:
- 多个API同时返回401会导致多个通知叠加
- 多个style元素被添加到DOM造成内存泄漏
- 可能触发多次登录页跳转
解决方案:
- 添加静态标志位 isHandling401 防止重复处理
- 第一个401触发完整处理流程
- 后续401直接抛出错误,避免重复操作
- 确保只显示一次通知和一次跳转
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* fix: 修复isHandling401标志永不重置的问题
问题:
- isHandling401标志在401处理后永不重置
- 导致用户重新登录后,后续401会被静默忽略
- 页面刷新或取消重定向后标志仍为true
解决方案:
- 在HttpClient中添加reset401Flag()公开方法
- 登录成功后调用reset401Flag()重置标志
- 页面加载时调用reset401Flag()确保新会话正常
影响范围:
- web/src/lib/httpClient.ts: 添加reset方法和导出函数
- web/src/contexts/AuthContext.tsx: 在登录和页面加载时重置
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
* fix(auth): consume returnUrl after successful login (BLOCKING-1)
修复登录后未跳转回原页面的问题。
问题:
- httpClient在401时保存returnUrl到sessionStorage
- 但登录成功后没有读取和使用returnUrl
- 导致用户登录后停留在登录页,无法回到原页面
修复:
- 在loginAdmin、verifyOTP、completeRegistration三个登录方法中
- 添加returnUrl检查和跳转逻辑
- 登录成功后优先跳转到returnUrl,如果没有则使用默认页面
影响:
- 用户token过期后重新登录,会自动返回之前访问的页面
- 提升用户体验,避免手动导航
测试场景:
1. 用户访问/traders → token过期 → 登录 → 自动回到/traders 
2. 用户直接访问/login → 登录 → 跳转到默认页面(/dashboard或/traders) 
Related: BLOCKING-1 in PR #803 code review
---------
Co-authored-by: sue <177699783@qq.com>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-09 12:18:47 +08:00
Diego f4f28059c0 fix(config):enforce encryption setup (#808)
* enforce encryption setup
* comment
2025-11-08 23:04:23 -05:00
Lawrence Liu 9d41d5ab39 Fix(security): 增强日志文件安全权限和管理 (#757)
## 问题
决策日志包含敏感的交易数据(API密钥、仓位、PnL等),但使用了不安全的默认权限:
- 目录权限 0755(所有用户可读可执行)
- 文件权限 0644(所有用户可读)
这可能导致同一系统其他用户访问敏感交易数据。
## 解决方案
### 1. 代码层面安全加固(Secure by Default)
**logger/decision_logger.go**:
- 目录创建权限:0755 → 0700(只有所有者可访问)
- 文件创建权限:0644 → 0600(只有所有者可读写)
- **新增:强制修复已存在目录/文件权限**(修复升级场景)
  - `NewDecisionLogger` 启动时强制设置目录权限
  - 遍历并修复已存在的 *.json 文件权限
  - 覆盖 PM2/手动部署场景
### 2. 运行时安全检查(Defense in Depth)
**start.sh**:
- 新增 `setup_secure_permissions()` 函数
- 启动时自动修正敏感文件/目录权限:
  - 目录 (.secrets, secrets, logs, decision_logs): 700
  - 文件 (.env, config.db, 密钥文件, 日志): 600
- **修复:使用 `install -m MODE` 创建文件/目录**
  - `touch config.db` → `install -m 600 /dev/null config.db`
  - `mkdir -p decision_logs` → `install -m 700 -d decision_logs`
  - 确保首次启动就是安全权限(修复 fresh install 漏洞)
### 3. 日志轮转和清理
**scripts/cleanup_logs.sh**:
- 提供日志清理功能(默认保留7天)
- 支持 dry-run 模式预览
- 可通过 crontab 定期执行
## 测试要点
1.  验证新创建的日志文件权限为 0600
2.  验证新创建的日志目录权限为 0700
3.  验证 start.sh 正确设置所有敏感文件权限
4.  验证 Docker 部署(root)可正常访问
5.  验证 PM2 部署(owner)可正常访问
6.  验证清理脚本正确删除旧日志
7.  验证首次安装时 config.db 权限为 600(不是 644)
8.  验证升级后已存在的日志文件权限被修正为 600
## 安全影响
- 防止同一系统其他用户读取敏感交易数据
- 采用深度防御策略:代码默认安全 + 运行时检查 + 升级修复
- 对 Docker(root)和 PM2(owner)部署均兼容
- 修复首次安装和升级场景的权限漏洞
2025-11-09 09:47:24 +08:00
Lawrence Liu f656a9c0fe Fix API call uses wrong API key configured (#751) 2025-11-09 09:46:03 +08:00
Lawrence Liu 2bb4f9ec99 fix(database): prevent empty values from overwriting exchange private keys (#785)
* fix(database): prevent empty values from overwriting exchange private keys
Fixes #781
## Problem
- Empty values were overwriting existing private keys during exchange config updates
- INSERT operations were storing plaintext instead of encrypted values
- Caused data loss when users edited exchange configurations via web UI
## Solution
1. **Dynamic UPDATE**: Only update sensitive fields (api_key, secret_key, aster_private_key) when non-empty
2. **Encrypted INSERT**: Use encrypted values for all sensitive fields during INSERT
3. **Comprehensive tests**: Added 9 unit tests with 90.2% coverage
## Changes
- config/database.go (UpdateExchange): Refactored to use dynamic SQL building
- config/database_test.go (new): Added comprehensive test suite
## Test Results
 All 9 tests pass
 Coverage: 90.2% of UpdateExchange function (100% of normal paths)
 Verified empty values no longer overwrite existing keys
 Verified INSERT uses encrypted storage
## Impact
- 🔒 Protects user's exchange API keys and private keys from accidental deletion
- 🔒 Ensures all sensitive data is encrypted at rest
-  Backward compatible: non-empty updates work as before
* revert: remove incorrect INSERT encryption fix - out of scope
2025-11-09 09:42:47 +08:00