* 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>
* 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
* 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
Fix setup_encryption.sh script that was causing JWT_SECRET values to wrap
onto multiple lines in .env file, leading to parse errors.
Root cause:
- openssl rand -base64 64 generates 88-character strings
- Using echo with / delimiter in sed caused conflicts with / in base64
- Long strings could wrap when written to .env
Changes:
- Changed sed delimiter from / to | to avoid conflicts with base64 chars
- Replaced echo with printf for consistent single-line output
- Added quotes around JWT_SECRET values for proper escaping
- Applied fix to all 3 locations that write JWT_SECRET
Co-authored-by: tinkle <tinkle@tinkle.community>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
Fixes#652
Previously, peakPnLCache used only 'symbol' as the key, causing LONG
and SHORT positions of the same symbol to share the same peak P&L value.
This led to incorrect drawdown calculations and emergency close triggers.
Changes:
- checkPositionDrawdown: use posKey (symbol_side) for cache access
- UpdatePeakPnL: add side parameter and use posKey internally
- ClearPeakPnLCache: add side parameter and use posKey internally
Example fix:
- Before: peakPnLCache["BTCUSDT"] shared by both LONG and SHORT
- After: peakPnLCache["BTCUSDT_long"] and peakPnLCache["BTCUSDT_short"]
Impact:
- Fixes incorrect drawdown monitoring for dual positions
- Prevents false emergency close triggers on profitable positions
- Simplified the logic for determining configured models and exchanges by removing reliance on sensitive fields like apiKey.
- Enhanced filtering criteria to check for enabled status and non-sensitive fields, improving clarity and security.
- Updated UI class bindings to reflect the new configuration checks without compromising functionality.
This refactor aims to streamline the configuration process while ensuring sensitive information is not exposed.
## Background
Hyperliquid official documentation recommends using Agent Wallet pattern for API trading:
- Agent Wallet is used for signing only
- Main Wallet Address is used for querying account data
- Agent Wallet should not hold significant funds
Reference: https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/nonces-and-api-wallets
## Current Implementation
Current implementation allows auto-generating wallet address from private key,
which simplifies user configuration but may lead to potential security concerns
if users accidentally use their main wallet private key.
## Enhancement
Following the proven pattern already used in Aster exchange implementation
(which uses dual-address mode), this enhancement upgrades Hyperliquid to
Agent Wallet mode:
### Core Changes
1. **Mandatory dual-address configuration**
- Agent Private Key (for signing)
- Main Wallet Address (holds funds)
2. **Multi-layer security checks**
- Detect if user accidentally uses main wallet private key
- Validate Agent wallet balance (reject if > 100 USDC)
- Provide detailed configuration guidance
3. **Design consistency**
- Align with Aster's dual-address pattern
- Follow Hyperliquid official best practices
### Code Changes
**config/database.go**:
- Add inline comments clarifying Agent Wallet security model
**trader/hyperliquid_trader.go**:
- Require explicit main wallet address (no auto-generation)
- Check if agent address matches main wallet address (security risk indicator)
- Query agent wallet balance and block if excessive
- Display both agent and main wallet addresses for transparency
**web/src/components/AITradersPage.tsx**:
- Add security alert banner explaining Agent Wallet mode
- Separate required inputs for Agent Private Key and Main Wallet Address
- Add field descriptions and validation
### Benefits
- ✅ Aligns with Hyperliquid official security recommendations
- ✅ Maintains design consistency with Aster implementation
- ✅ Multi-layer protection against configuration mistakes
- ✅ Detailed logging for troubleshooting
### Breaking Change
Users must now explicitly provide main wallet address (hyperliquid_wallet_addr).
Old configurations will receive clear error messages with migration guidance.
### Migration Guide
**Before** (single private key):
```json
{
"hyperliquid_private_key": "0x..."
}
```
**After** (Agent Wallet mode):
```json
{
"hyperliquid_private_key": "0x...", // Agent Wallet private key
"hyperliquid_wallet_addr": "0x..." // Main Wallet address
}
```
Users can create Agent Wallet on Hyperliquid official website:
https://app.hyperliquid.xyz/ → Settings → API Wallets
Co-authored-by: tinkle-community <tinklefund@gmail.com>
* Separate the AI's thought process from the instruction JSON using XML tags.
* Avoid committing encryption key related materials to Git.
* Removing adaptive series prompts, awaiting subsequent modifications for compatibility.
## 🎯 Motivation
Based on extensive production usage and user feedback, we've developed a more comprehensive prompt system with:
- Stronger risk management rules
- Better handling of partial_close and update_stop_loss
- Multiple strategy templates for different risk profiles
- Enhanced decision quality and consistency
## 📊 What's Changed
### 1. Prompt System v6.0.0
All prompts now follow a standardized format with:
- **Version header**: Clear versioning (v6.0.0)
- **Strategy positioning**: Conservative/Moderate/Relaxed/Altcoin
- **Core parameters**: Confidence thresholds, cooldown periods, BTC confirmation requirements
- **Unified structure**: Consistent across all templates
### 2. New Strategy Templates
Added two new templates to cover different trading scenarios:
- `adaptive_altcoin.txt` - Optimized for altcoin trading
- Higher leverage limits (10x-15x)
- More aggressive position sizing
- Faster decision cycles
- `adaptive_moderate.txt` - Balanced strategy
- Medium risk tolerance
- Flexible BTC confirmation
- Suitable for most traders
### 3. Enhanced Safety Rules
#### partial_close Safety (Addresses #301)
```
⚠️ Mandatory Check:
- Before partial_close, calculate: remaining_value = current_value × (1 - close_percentage/100)
- If remaining_value ≤ $10 → Must use close_long/close_short instead
- Prevents "Order must have minimum value of $10" exchange errors
```
#### update_stop_loss Threshold Rules
```
⚠️ Strict Rules:
- Profit <3% → FORBIDDEN to move stop-loss (avoid premature trailing)
- Profit 3-5% → Can move to breakeven
- Profit ≥10% → Can move to entry +5% (lock partial profit)
```
#### TP/SL Restoration After partial_close
```
⚠️ Important:
- Exchanges auto-cancel TP/SL orders when position size changes
- Must provide new_stop_loss + new_take_profit with partial_close
- Otherwise remaining position has NO protection (liquidation risk)
```
### 4. Files Changed
- `prompts/adaptive.txt` - Conservative strategy (v6.0.0)
- `prompts/adaptive_relaxed.txt` - Relaxed strategy (v6.0.0)
- `prompts/adaptive_altcoin.txt` - NEW: Altcoin-optimized strategy
- `prompts/adaptive_moderate.txt` - NEW: Balanced strategy
## 🔗 Related Issues
- Closes#301 (Prompt layer safety rules)
- Related to #418 (Same validation issue)
- Complements PR #415 (Backend implementation)
## ✅ Testing
- [x] All 4 templates follow v6.0.0 format
- [x] partial_close safety rules included
- [x] update_stop_loss threshold rules included
- [x] TP/SL restoration warnings included
- [x] Strategy-specific parameters validated
## 📝 Notes
This PR focuses on **prompt layer enhancements only**.
Backend safety checks (trader/auto_trader.go) will be submitted in a separate PR for easier review.
The two PRs can be merged independently or together - they complement each other:
- This PR: AI makes better decisions (prevent bad actions)
- Next PR: Backend validates and auto-corrects (safety net)
---
Co-authored-by: tinkle-community <tinklefund@gmail.com>
**Problem:**
Competition page shows "NaN%" for gap difference when trader P&L
percentages are null/undefined.
**Root Cause:**
Line 227: `const gap = trader.total_pnl_pct - opponent.total_pnl_pct`
- If either value is `undefined` or `null`, result is `NaN`
- Display shows "领先 NaN%" or "落后 NaN%"
**Solution:**
Add null coalescing to default undefined/null values to 0:
```typescript
const gap = (trader.total_pnl_pct ?? 0) - (opponent.total_pnl_pct ?? 0)
```
**Impact:**
- ✅ Gap calculation returns 0 when data is missing (shows 0.00%)
- ✅ Prevents confusing "NaN%" display
- ✅ Graceful degradation for incomplete data
Fixes#633
Co-authored-by: tinkle-community <tinklefund@gmail.com>