mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-09 14:00:57 +08:00
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:
@@ -9,6 +9,7 @@ import (
|
||||
"nofx/logger"
|
||||
"nofx/store"
|
||||
"nofx/trader"
|
||||
"nofx/trader/alpaca"
|
||||
"nofx/trader/aster"
|
||||
"nofx/trader/binance"
|
||||
"nofx/trader/bitget"
|
||||
@@ -120,6 +121,12 @@ func (s *Server) handleSyncBalance(c *gin.Context) {
|
||||
} else {
|
||||
createErr = fmt.Errorf("Lighter requires wallet address and API Key private key")
|
||||
}
|
||||
case "alpaca":
|
||||
tempTrader = alpaca.NewAlpacaTrader(
|
||||
string(exchangeCfg.APIKey),
|
||||
string(exchangeCfg.SecretKey),
|
||||
exchangeCfg.Testnet,
|
||||
)
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Unsupported exchange type"})
|
||||
return
|
||||
@@ -284,6 +291,12 @@ func (s *Server) handleClosePosition(c *gin.Context) {
|
||||
} else {
|
||||
createErr = fmt.Errorf("Lighter requires wallet address and API Key private key")
|
||||
}
|
||||
case "alpaca":
|
||||
tempTrader = alpaca.NewAlpacaTrader(
|
||||
string(exchangeCfg.APIKey),
|
||||
string(exchangeCfg.SecretKey),
|
||||
exchangeCfg.Testnet,
|
||||
)
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Unsupported exchange type"})
|
||||
return
|
||||
@@ -318,6 +331,14 @@ func (s *Server) handleClosePosition(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel existing stop-loss and take-profit orders BEFORE closing position
|
||||
// Critical: orphaned SL/TP orders could trigger after position is closed and create unintended positions
|
||||
if err := tempTrader.CancelStopOrders(req.Symbol); err != nil {
|
||||
logger.Warnf(" ⚠️ Failed to cancel stop orders for %s: %v (proceeding with close)", req.Symbol, err)
|
||||
} else {
|
||||
logger.Infof(" 🗑️ Cancelled stop/TP orders for %s before manual close", req.Symbol)
|
||||
}
|
||||
|
||||
// Execute close position operation
|
||||
var result map[string]interface{}
|
||||
var closeErr error
|
||||
|
||||
Reference in New Issue
Block a user