## Fixes
### 1. Typewriter Component - Missing First Character
- Fix character loss issue where first character of each line was missing
- Add proper state reset logic before starting typing animation
- Extract character before setState to avoid closure issues
- Add setTimeout(0) to ensure state is updated before typing starts
- Change dependency from `lines` to `sanitizedLines` for correct updates
- Use `??` instead of `||` for safer null handling
### 2. Chinese Translation - Leading Spaces
- Remove leading spaces from startupMessages1/2/3 in Chinese translations
- Ensures proper display of startup messages in terminal simulation
### 3. Dynamic GitHub Stats with Animation
- Add useGitHubStats hook to fetch real-time GitHub repository data
- Add useCounterAnimation hook with easeOutExpo easing for smooth number animation
- Display dynamic star count with smooth counter animation (2s duration)
- Display dynamic days count (static, no animation)
- Support bilingual display (EN/ZH) with proper formatting
## Changes
- web/src/components/Typewriter.tsx: Fix first character loss bug
- web/src/i18n/translations.ts: Remove leading spaces in Chinese messages
- web/src/components/landing/HeroSection.tsx: Add dynamic GitHub stats
- web/src/hooks/useGitHubStats.ts: New hook for GitHub API integration
- web/src/hooks/useCounterAnimation.ts: New hook for number animations
Fixes#365🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## Problem
AI responses were being truncated due to a hardcoded max_tokens limit of 2000,
causing JSON parsing failures. The error occurred when:
1. AI's thought process analysis was cut off mid-response
2. extractDecisions() incorrectly extracted MACD data arrays from the input prompt
3. Go failed to unmarshal numbers into Decision struct
Error message:
```
json: cannot unmarshal number into Go value of type decision.Decision
JSON内容: [-867.759, -937.406, -1020.435, ...]
```
## Solution
- Add MaxTokens field to mcp.Client struct
- Read AI_MAX_TOKENS from environment variable (default: 2000)
- Set AI_MAX_TOKENS=4000 in docker-compose.yml for production use
- This provides enough tokens for complete analysis with the 800-line trading strategy prompt
## Testing
- Verify environment variable is read correctly
- Confirm AI responses are no longer truncated
- Check decision logs for complete JSON output
- Created specialized PR templates for different change types:
- Backend template for Go/API changes
- Frontend template for UI/UX changes
- Documentation template for docs updates
- General template for mixed changes
- Simplified default template from 270 to 115 lines
- Added GitHub Action for automatic template suggestion based on file types
- Auto-labels PRs with appropriate categories (backend/frontend/documentation)
- Provides friendly suggestions when default template is used
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
These methods are required by the OpenLong/OpenShort validation but were
missing from upstream/dev.
Adds:
- GetMinNotional(): Returns minimum notional value (10 USDT default)
- CheckMinNotional(): Validates order meets minimum notional requirement
🐛 Problem:
- AI returns JSON with fullwidth characters: [{
- Regex \[ cannot match fullwidth [
- extractDecisions() fails with "无法找到JSON数组起始"
🔧 Root Cause:
- fixMissingQuotes() was called AFTER regex matching
- If regex fails to match fullwidth chars, fix function never executes
✅ Solution:
- Call fixMissingQuotes(s) BEFORE regex matching (line 461)
- Convert fullwidth to halfwidth first: [→[, {→{
- Then regex can successfully match the JSON array
📊 Impact:
- Fixes "无法找到JSON数组起始" error
- Supports AI responses with fullwidth JSON characters
- Backward compatible with halfwidth JSON
This fix is identical to z-dev commit 3676cc0
Critical bug: AI can output fullwidth space ( U+3000) between brackets:
Input: [ {"symbol":"BTCUSDT"}]
↑ ↑ fullwidth space
After previous fix:
[ {"symbol":"BTCUSDT"}]
↑ fullwidth space remained!
Result: validateJSONFormat failed because:
- Checks "[{" (no space) ❌
- Checks "[ {" (halfwidth space U+0020) ❌
- AI output "[ {" (fullwidth space U+3000) ❌
Solution: Replace fullwidth space → halfwidth space
- (U+3000) → space (U+0020)
This allows existing validation logic to work:
strings.HasPrefix(trimmed, "[ {") now matches ✅
Why fullwidth space?
- Common in CJK text editing
- AI trained on mixed CJK content
- Invisible to naked eye but breaks JSON parsing
Test case:
Input: [ {"symbol":"BTCUSDT"}]
Output: [ {"symbol":"BTCUSDT"}]
Validation: ✅ PASS
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Critical discovery: AI can output different types of "fullwidth" brackets:
- Fullwidth: []{}(U+FF3B/FF3D/FF5B/FF5D) ← Already handled
- CJK: 【】〔〕(U+3010/3011/3014/3015) ← Was missing!
Root cause of persistent errors:
User reported: "JSON 必须以【{开头"
The 【 character (U+3010) is NOT the same as [ (U+FF3B)!
Added CJK punctuation replacements:
- 【 → [ (U+3010 Left Black Lenticular Bracket)
- 】 → ] (U+3011 Right Black Lenticular Bracket)
- 〔 → [ (U+3014 Left Tortoise Shell Bracket)
- 〕 → ] (U+3015 Right Tortoise Shell Bracket)
- 、 → , (U+3001 Ideographic Comma)
Why this was missed:
AI uses different characters in different contexts. CJK brackets (U+3010-3017)
are distinct from Fullwidth Forms (U+FF00-FFEF) in Unicode.
Test case:
Input: 【{"symbol":"BTCUSDT"】
Output: [{"symbol":"BTCUSDT"}]
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Adds comprehensive JSON validation before parsing to catch common AI output errors:
1. Format validation: Ensures JSON starts with [{ (decision array)
2. Range symbol detection: Rejects ~ symbols (e.g., "leverage: 3~5")
3. Thousands separator detection: Rejects commas in numbers (e.g., "98,000")
Execution order (critical for fullwidth character fix):
1. Extract JSON from response
2. fixMissingQuotes - normalize fullwidth → halfwidth ✅
3. validateJSONFormat - check for common errors ✅
4. Parse JSON
This validation layer provides early error detection and clearer error messages
for debugging AI response issues.
Added helper function:
- min(a, b int) int - returns smaller of two integers
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Redirect to /traders instead of / after successful login/registration
- Make 'Get Started Now' button redirect logged-in users to /traders
- Prevent infinite loop where logged-in users are shown landing page repeatedly
Fixes issue where after login success, clicking "Get Started Now" would
show login modal again instead of entering the main application.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## Summary
- Add POST /traders/:id/sync-balance endpoint (Option B)
- Add smart detection showing balance change percentage (Option C)
- Fix balance display bug caused by commit 2b9c4d2
## Changes
### api/server.go
- Add handleSyncBalance() handler
- Query actual exchange balance via trader.GetBalance()
- Calculate change percentage for smart detection
- Update initial_balance in database
- Reload trader into memory after update
### config/database.go
- Add UpdateTraderInitialBalance() method
- Update traders.initial_balance field
## Root Cause
Commit 2b9c4d2 auto-queries exchange balance at trader creation time,
but never updates after user deposits more funds, causing:
- Wrong initial_balance (400 USDT vs actual 3000 USDT)
- Wrong P&L calculations (-2598.55 USDT instead of actual)
## Solution
Provides manual sync API + smart detection to update initial_balance
when user deposits funds after trader creation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
## Problem
When editing trader configuration, if `system_prompt_template` was set to an empty string (""), the UI would incorrectly treat it as falsy and overwrite it with 'default', losing the user's selection.
**Root cause:**
```tsx
if (traderData && !traderData.system_prompt_template) {
// ❌ This triggers for both undefined AND empty string ""
setFormData({ system_prompt_template: 'default' });
}
```
JavaScript falsy values that trigger `!` operator:
- `undefined` ✅ Should trigger default
- `null` ✅ Should trigger default
- `""` ❌ Should NOT trigger (user explicitly chose empty)
- `false`, `0`, `NaN` (less relevant here)
## Solution
Change condition to explicitly check for `undefined`:
```tsx
if (traderData && traderData.system_prompt_template === undefined) {
// ✅ Only triggers for truly missing field
setFormData({ system_prompt_template: 'default' });
}
```
## Impact
- ✅ Empty string selections are preserved
- ✅ Legacy data (undefined) still gets default value
- ✅ User's explicit choices are respected
- ✅ No breaking changes to existing functionality
## Testing
- ✅ Code compiles
- ⚠️ Requires manual UI testing:
- [ ] Edit trader with empty system_prompt_template
- [ ] Verify it doesn't reset to 'default'
- [ ] Create new trader → should default to 'default'
- [ ] Edit old trader (undefined field) → should default to 'default'
## Code Changes
```
web/src/components/TraderConfigModal.tsx:
- Line 99: Changed !traderData.system_prompt_template → === undefined
```
## Problem
Multiple partial_close actions on the same position were being counted as separate trades, inflating TotalTrades count and distorting win rate/profit factor statistics.
**Example of bug:**
- Open 1 BTC @ $100,000
- Partial close 30% @ $101,000 → Counted as trade #1❌
- Partial close 50% @ $102,000 → Counted as trade #2❌
- Close remaining 20% @ $103,000 → Counted as trade #3❌
- **Result:** 3 trades instead of 1 ❌
## Solution
### 1. Added tracking fields to openPositions map
- `remainingQuantity`: Tracks remaining position size
- `accumulatedPnL`: Accumulates PnL from all partial closes
- `partialCloseCount`: Counts number of partial close operations
- `partialCloseVolume`: Total volume closed partially
### 2. Modified partial_close handling logic
- Each partial_close:
- Accumulates PnL into `accumulatedPnL`
- Reduces `remainingQuantity`
- **Does NOT increment TotalTrades++**
- Keeps position in openPositions map
- Only when `remainingQuantity <= 0.0001`:
- Records ONE TradeOutcome with aggregated PnL
- Increments TotalTrades++ once
- Removes from openPositions map
### 3. Updated full close handling
- If position had prior partial closes:
- Adds `accumulatedPnL` to final close PnL
- Reports total PnL in TradeOutcome
### 4. Fixed GetStatistics()
- Removed `partial_close` from TotalClosePositions count
- Only `close_long/close_short/auto_close` count as close operations
## Impact
- ✅ Statistics now accurate: multiple partial closes = 1 trade
- ✅ Win rate calculated correctly
- ✅ Profit factor reflects true performance
- ✅ Backward compatible: handles positions without tracking fields
## Testing
- ✅ Compiles successfully
- ⚠️ Requires validation with live partial_close scenarios
## Code Changes
```
logger/decision_logger.go:
- Lines 420-430: Add tracking fields to openPositions
- Lines 441-534: Implement partial_close aggregation logic
- Lines 536-593: Update full close to include accumulated PnL
- Lines 246-250: Fix GetStatistics() to exclude partial_close
```
## Problem
AI was calculating position_size_usd incorrectly, treating it as margin requirement instead of notional value, causing code=-2019 errors (insufficient margin).
## Solution
### 1. Updated AI prompts with correct formula
- **prompts/adaptive.txt**: Added clear position sizing calculation steps
- **prompts/nof1.txt**: Added English version with example
- **prompts/default.txt**: Added Chinese version with example
**Correct formula:**
1. Available Margin = Available Cash × 0.95 × Allocation % (reserve 5% for fees)
2. Notional Value = Available Margin × Leverage
3. position_size_usd = Notional Value (this is the value for JSON)
**Example:** $500 cash, 5x leverage → position_size_usd = $2,375 (not $500)
### 2. Added code-level validation
- **trader/auto_trader.go**: Added margin checks in executeOpenLong/ShortWithRecord
- Validates required margin + fees ≤ available balance before opening position
- Returns clear error message if insufficient
## Impact
- Prevents code=-2019 errors
- AI now understands the difference between notional value and margin requirement
- Double validation: AI prompt + code check
## Testing
- ✅ Compiles successfully
- ⚠️ Requires live trading environment testing
- Fix compilation error on Alpine: off64_t type not defined in v1.14.16
- Remove unused pure-Go sqlite implementation (modernc.org/sqlite) and its dependencies
- v1.14.22 is the first version fixing Alpine/musl build issues (2024-02-02)
- Minimizes version jump (v1.14.16 → v1.14.22, 18 commits) to reduce risk
Reference: https://github.com/mattn/go-sqlite3/issues/1164
Verified: Builds successfully on golang:1.25-alpine