mirror of
https://github.com/luxfi/broker.git
synced 2026-10-30 14:45:05 +00:00
Security fixes: - Fix auth bypass: empty API key now returns 401 (unless BROKER_DEV_MODE=true) - Fix risk engine recording $0 for all orders (now calculates qty*price) - Fix hardcoded compliance fields in CreateAccount (pass-through from request) P0 Trading Core: - ReplaceOrder (PATCH), CancelAllOrders, GetPosition, ClosePosition, CloseAllPositions - ListOrdersFiltered with status/limit/after/until/direction/nested params - Extended CreateOrderRequest: trailing stops, bracket orders, extended hours P1-P5 Complete API via composable interfaces: - AccountManager: UpdateAccount, CloseAccount, GetAccountActivities - DocumentManager: Upload, List, Get, Download documents - JournalManager: Create, List, Get, Delete, Batch, ReverseBatch journals - TransferExtended: CancelTransfer, DeleteACH, recipient bank CRUD - CryptoDataProvider: GetCryptoBars/Quotes/Trades/Snapshots - EventStreamer: SSE streaming for trade/account/transfer/journal events - PortfolioAnalyzer: GetPortfolioHistory - WatchlistManager: Full CRUD with asset management Architecture: 8 optional capability interfaces on single Provider abstraction. Providers implement what they support; router detects via type assertion. Market data fixes: - Crypto symbols now route to /v1beta3/crypto/us/ (not /v2/stocks/) - GetBars pagination via next_page_token loop
129 lines
3.5 KiB
Go
129 lines
3.5 KiB
Go
package alpaca
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"github.com/luxfi/broker/pkg/types"
|
|
)
|
|
|
|
// --- WatchlistManager implementation ---
|
|
|
|
func (p *Provider) CreateWatchlist(ctx context.Context, accountID string, req *types.CreateWatchlistRequest) (*types.Watchlist, error) {
|
|
body := map[string]interface{}{
|
|
"name": req.Name,
|
|
}
|
|
if len(req.Symbols) > 0 {
|
|
body["symbols"] = req.Symbols
|
|
}
|
|
|
|
data, _, err := p.do(ctx, http.MethodPost, "/v1/trading/accounts/"+accountID+"/watchlists", body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return p.parseWatchlist(data)
|
|
}
|
|
|
|
func (p *Provider) ListWatchlists(ctx context.Context, accountID string) ([]*types.Watchlist, error) {
|
|
data, _, err := p.do(ctx, http.MethodGet, "/v1/trading/accounts/"+accountID+"/watchlists", nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var raw []json.RawMessage
|
|
if err := json.Unmarshal(data, &raw); err != nil {
|
|
return nil, err
|
|
}
|
|
watchlists := make([]*types.Watchlist, 0, len(raw))
|
|
for _, r := range raw {
|
|
w, err := p.parseWatchlist(r)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
watchlists = append(watchlists, w)
|
|
}
|
|
return watchlists, nil
|
|
}
|
|
|
|
func (p *Provider) GetWatchlist(ctx context.Context, accountID, watchlistID string) (*types.Watchlist, error) {
|
|
data, _, err := p.do(ctx, http.MethodGet, "/v1/trading/accounts/"+accountID+"/watchlists/"+watchlistID, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return p.parseWatchlist(data)
|
|
}
|
|
|
|
func (p *Provider) UpdateWatchlist(ctx context.Context, accountID, watchlistID string, req *types.UpdateWatchlistRequest) (*types.Watchlist, error) {
|
|
body := make(map[string]interface{})
|
|
if req.Name != "" {
|
|
body["name"] = req.Name
|
|
}
|
|
if len(req.Symbols) > 0 {
|
|
body["symbols"] = req.Symbols
|
|
}
|
|
|
|
data, _, err := p.do(ctx, http.MethodPut, "/v1/trading/accounts/"+accountID+"/watchlists/"+watchlistID, body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return p.parseWatchlist(data)
|
|
}
|
|
|
|
func (p *Provider) DeleteWatchlist(ctx context.Context, accountID, watchlistID string) error {
|
|
_, _, err := p.do(ctx, http.MethodDelete, "/v1/trading/accounts/"+accountID+"/watchlists/"+watchlistID, nil)
|
|
return err
|
|
}
|
|
|
|
func (p *Provider) AddWatchlistAsset(ctx context.Context, accountID, watchlistID, symbol string) (*types.Watchlist, error) {
|
|
body := map[string]interface{}{
|
|
"symbol": symbol,
|
|
}
|
|
data, _, err := p.do(ctx, http.MethodPost, "/v1/trading/accounts/"+accountID+"/watchlists/"+watchlistID, body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return p.parseWatchlist(data)
|
|
}
|
|
|
|
func (p *Provider) RemoveWatchlistAsset(ctx context.Context, accountID, watchlistID, symbol string) error {
|
|
_, _, err := p.do(ctx, http.MethodDelete, "/v1/trading/accounts/"+accountID+"/watchlists/"+watchlistID+"/"+symbol, nil)
|
|
return err
|
|
}
|
|
|
|
func (p *Provider) parseWatchlist(data []byte) (*types.Watchlist, error) {
|
|
var raw struct {
|
|
ID string `json:"id"`
|
|
AccountID string `json:"account_id"`
|
|
Name string `json:"name"`
|
|
Assets []struct {
|
|
ID string `json:"id"`
|
|
Symbol string `json:"symbol"`
|
|
Name string `json:"name"`
|
|
Class string `json:"class"`
|
|
} `json:"assets"`
|
|
CreatedAt string `json:"created_at"`
|
|
UpdatedAt string `json:"updated_at"`
|
|
}
|
|
if err := json.Unmarshal(data, &raw); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
w := &types.Watchlist{
|
|
ID: raw.ID,
|
|
AccountID: raw.AccountID,
|
|
Name: raw.Name,
|
|
CreatedAt: raw.CreatedAt,
|
|
UpdatedAt: raw.UpdatedAt,
|
|
}
|
|
for _, a := range raw.Assets {
|
|
w.Assets = append(w.Assets, types.WatchlistAsset{
|
|
ID: a.ID,
|
|
Symbol: a.Symbol,
|
|
Name: a.Name,
|
|
Class: a.Class,
|
|
})
|
|
}
|
|
return w, nil
|
|
}
|