feat: Alpaca US stock trader integration (Tasks 8-11)

- trader/alpaca/: Full Trader interface implementation for Alpaca
  - Paper/Live trading support (market orders, stop/limit)
  - GetBalance, GetPositions, GetMarketPrice via Alpaca API
  - Fractional share support (4 decimal places)
  - Commission-free trading

- Agent trade routing: stock symbols (AAPL, TSLA) → Alpaca
  - isStockSymbol() heuristic to distinguish crypto vs stock
  - execute_trade tool updated for stock buy/sell semantics
  - get_market_price works for both crypto and stocks

- TraderManager + API: Alpaca registered as exchange type
  - Factory case in auto_trader.go
  - Config mapping in trader_manager.go
  - Temp trader creation in handler_trader.go for balance query

- Frontend: PositionsPanel distinguishes stock vs crypto
  - US flag emoji for stock positions
  - Dollar prefix for stock PnL/prices
  - 'Shares' label instead of 'Qty' for stocks

- System prompts updated for stock trading capabilities
This commit is contained in:
shinchan-zhai
2026-03-23 17:57:14 +08:00
parent 76a7b88ba8
commit d0aebe9f18
9 changed files with 720 additions and 17 deletions

View File

@@ -63,7 +63,11 @@ export function PositionsPanel() {
const isProfit = pnl >= 0
const color = isProfit ? '#00e5a0' : '#F6465D'
const side = pos.side?.toUpperCase() || (pos.quantity > 0 ? 'LONG' : 'SHORT')
const symbol = (pos.symbol || '').replace('USDT', '')
const rawSymbol = pos.symbol || ''
// Stock symbols are pure letters (1-5 chars), crypto has USDT suffix
const isStock = /^[A-Z]{1,5}$/.test(rawSymbol) && !rawSymbol.endsWith('USDT')
const symbol = isStock ? rawSymbol : rawSymbol.replace('USDT', '')
const currencyPrefix = isStock ? '$' : ''
return (
<div
@@ -93,6 +97,9 @@ export function PositionsPanel() {
>
{symbol}
</span>
{isStock && (
<span style={{ fontSize: 10, color: '#8b8ba0' }}>🇺🇸</span>
)}
<span
style={{
fontSize: 10,
@@ -106,7 +113,7 @@ export function PositionsPanel() {
color: side === 'LONG' ? '#00e5a0' : '#F6465D',
}}
>
{side}
{isStock ? (side === 'LONG' ? 'HOLD' : 'SHORT') : side}
</span>
</div>
<div
@@ -125,7 +132,7 @@ export function PositionsPanel() {
<ArrowDownRight size={12} />
)}
{isProfit ? '+' : ''}
{pnl.toFixed(2)}
{currencyPrefix}{pnl.toFixed(2)}
</div>
</div>
<div
@@ -136,8 +143,8 @@ export function PositionsPanel() {
color: '#5c5c72',
}}
>
<span>Qty: {pos.quantity}</span>
<span>Entry: {pos.entry_price.toFixed(2)}</span>
<span>{isStock ? 'Shares' : 'Qty'}: {pos.quantity}</span>
<span>Entry: {currencyPrefix}{pos.entry_price.toFixed(2)}</span>
</div>
</div>
)