fix: isStockSymbol misclassifies crypto symbols (BTC/ETH/SOL) as stock tickers

Critical bug: isStockSymbol('BTC') returned true because 'BTC' is 3
uppercase letters and the suffix check only catches 'BTCUSDT'-style pairs.
This caused crypto trade commands to be routed to Alpaca (stock trader)
instead of crypto exchanges.

Fix: add knownCryptoSymbols map with 60+ common crypto base symbols.
Check this map first before the heuristic letter-count check.

Also includes:
- Unit tests for isStockSymbol (crypto vs stock classification)
- Alpaca support in handleSyncBalance/handleClosePosition API handlers
- Cancel orphaned SL/TP orders before manual position close
- Clear peak PnL cache on position close (prevent stale data)
- Lighter FormatQuantity uses actual market precision instead of hardcoded 4
This commit is contained in:
shinchan-zhai
2026-03-23 20:18:24 +08:00
parent 7b1cff23bc
commit 06824915a4
5 changed files with 127 additions and 4 deletions

View File

@@ -336,6 +336,9 @@ func (at *AutoTrader) executeCloseLongWithRecord(decision *kernel.Decision, acti
// Record order to database and poll for confirmation
at.recordAndConfirmOrder(order, decision.Symbol, "close_long", quantity, marketData.CurrentPrice, 0, entryPrice)
// Clear peak PnL cache for this position (prevent stale data if same symbol is reopened)
at.ClearPeakPnLCache(decision.Symbol, "long")
logger.Infof(" ✓ Position closed successfully")
return nil
}
@@ -406,6 +409,9 @@ func (at *AutoTrader) executeCloseShortWithRecord(decision *kernel.Decision, act
// Record order to database and poll for confirmation
at.recordAndConfirmOrder(order, decision.Symbol, "close_short", quantity, marketData.CurrentPrice, 0, entryPrice)
// Clear peak PnL cache for this position (prevent stale data if same symbol is reopened)
at.ClearPeakPnLCache(decision.Symbol, "short")
logger.Infof(" ✓ Position closed successfully")
return nil
}

View File

@@ -351,9 +351,14 @@ func (t *LighterTraderV2) GetMarketPrice(symbol string) (float64, error) {
// FormatQuantity Format quantity to correct precision (implements Trader interface)
func (t *LighterTraderV2) FormatQuantity(symbol string, quantity float64) (string, error) {
// TODO: Get symbol precision from API
// Using default precision for now
return fmt.Sprintf("%.4f", quantity), nil
marketInfo, err := t.getMarketInfo(symbol)
if err != nil {
// Fallback to 4 decimal places if market info unavailable
logger.Infof("⚠️ FormatQuantity: failed to get market info for %s, using default precision: %v", symbol, err)
return fmt.Sprintf("%.4f", quantity), nil
}
format := fmt.Sprintf("%%.%df", marketInfo.SizeDecimals)
return fmt.Sprintf(format, quantity), nil
}
// GetOrderBook Get order book (implements GridTrader interface)