fix: Alpaca integration bugs — field mapping, market hours, stock symbol handling

- Fix critical bug: Alpaca GetBalance returns wrong field names (total_equity
  vs totalEquity) — auto_trader was getting 0 equity, breaking all decisions
- Fix critical bug: Alpaca GetPositions missing positionAmt/unRealizedProfit
  fields — positions invisible to trading AI
- Fix CancelAllOrders: was nuking ALL orders globally, now filters by symbol
- Implement GetClosedPnL: was returning nil, now returns filled sell orders
- Add IsMarketOpen: checks Alpaca clock endpoint for market hours
- Add market hours check in trading loop: skip cycles when US market closed
  (saves LLM tokens and prevents failed orders)
- Fix parseTradeCommand: 'BUY AAPL 10' no longer becomes 'AAPLUSDT'
- Fix toolGetMarketPrice: route stock symbols to Alpaca, crypto to others
- Add exchange field to toolGetPositions output for multi-exchange clarity
This commit is contained in:
shinchan-zhai
2026-03-23 19:05:25 +08:00
parent d0aebe9f18
commit bbaa109223
4 changed files with 138 additions and 20 deletions

View File

@@ -274,6 +274,7 @@ func (a *Agent) toolGetPositions() string {
}
positions = append(positions, map[string]any{
"trader": tid,
"exchange": t.GetExchange(),
"symbol": p["symbol"],
"side": p["side"],
"size": size,
@@ -339,11 +340,20 @@ func (a *Agent) toolGetMarketPrice(argsJSON string) string {
return `{"error": "no trader manager configured"}`
}
wantStock := isStockSymbol(sym)
for _, t := range a.traderManager.GetAllTraders() {
underlying := t.GetUnderlyingTrader()
if underlying == nil {
continue
}
// Route to correct exchange type (stock vs crypto)
isAlpaca := t.GetExchange() == "alpaca"
if wantStock && !isAlpaca {
continue
}
if !wantStock && isAlpaca {
continue
}
price, err := underlying.GetMarketPrice(sym)
if err == nil && price > 0 {
result, _ := json.Marshal(map[string]any{

View File

@@ -112,7 +112,8 @@ func parseTradeCommand(text string) *TradeAction {
return nil
}
symbol = words[1]
if !strings.HasSuffix(symbol, "USDT") {
// Only append USDT for crypto symbols, not stock tickers
if !isStockSymbol(symbol) && !strings.HasSuffix(symbol, "USDT") {
symbol += "USDT"
}
@@ -226,7 +227,10 @@ func formatTradeConfirmation(trade *TradeAction, lang string) string {
"close_short": "平空 (Close Short)",
}
symbol := strings.TrimSuffix(trade.Symbol, "USDT")
symbol := trade.Symbol
if strings.HasSuffix(symbol, "USDT") {
symbol = strings.TrimSuffix(symbol, "USDT")
}
actionName := actionNames[trade.Action]
if actionName == "" {
actionName = trade.Action
@@ -308,7 +312,10 @@ func (a *Agent) handleTradeConfirmation(ctx context.Context, userID int64, text,
}
trade.Status = "executed"
symbol := strings.TrimSuffix(trade.Symbol, "USDT")
symbol := trade.Symbol
if strings.HasSuffix(symbol, "USDT") {
symbol = strings.TrimSuffix(symbol, "USDT")
}
actionEmoji := "📈"
if strings.Contains(trade.Action, "short") {
actionEmoji = "📉"