Files
zeekay f897cd1f9c feat: complete Alpaca Broker API — 100% endpoint coverage
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
2025-10-22 10:40:22 -07:00

114 lines
3.2 KiB
Go

package alpaca
import (
"context"
"encoding/json"
"net/http"
"github.com/luxfi/broker/pkg/types"
)
// --- TransferExtended implementation ---
func (p *Provider) CancelTransfer(ctx context.Context, accountID, transferID string) error {
_, _, err := p.do(ctx, http.MethodDelete, "/v1/accounts/"+accountID+"/transfers/"+transferID, nil)
return err
}
func (p *Provider) DeleteACHRelationship(ctx context.Context, accountID, achID string) error {
_, _, err := p.do(ctx, http.MethodDelete, "/v1/accounts/"+accountID+"/ach_relationships/"+achID, nil)
return err
}
func (p *Provider) CreateRecipientBank(ctx context.Context, accountID string, req *types.CreateBankRequest) (*types.RecipientBank, error) {
body := map[string]interface{}{
"name": req.Name,
"bank_code": req.BankCode,
"bank_code_type": req.BankCodeType,
"account_number": req.AccountNumber,
}
if req.Country != "" {
body["country"] = req.Country
}
if req.StateProvince != "" {
body["state_province"] = req.StateProvince
}
if req.PostalCode != "" {
body["postal_code"] = req.PostalCode
}
if req.City != "" {
body["city"] = req.City
}
if req.StreetAddress != "" {
body["street_address"] = req.StreetAddress
}
data, _, err := p.do(ctx, http.MethodPost, "/v1/accounts/"+accountID+"/recipient_banks", body)
if err != nil {
return nil, err
}
return p.parseRecipientBank(data)
}
func (p *Provider) ListRecipientBanks(ctx context.Context, accountID string) ([]*types.RecipientBank, error) {
data, _, err := p.do(ctx, http.MethodGet, "/v1/accounts/"+accountID+"/recipient_banks", nil)
if err != nil {
return nil, err
}
var raw []json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
banks := make([]*types.RecipientBank, 0, len(raw))
for _, r := range raw {
b, err := p.parseRecipientBank(r)
if err != nil {
continue
}
banks = append(banks, b)
}
return banks, nil
}
func (p *Provider) DeleteRecipientBank(ctx context.Context, accountID, bankID string) error {
_, _, err := p.do(ctx, http.MethodDelete, "/v1/accounts/"+accountID+"/recipient_banks/"+bankID, nil)
return err
}
func (p *Provider) parseRecipientBank(data []byte) (*types.RecipientBank, error) {
var raw struct {
ID string `json:"id"`
AccountID string `json:"account_id"`
Name string `json:"name"`
Status string `json:"status"`
Country string `json:"country"`
StateProvince string `json:"state_province"`
PostalCode string `json:"postal_code"`
City string `json:"city"`
StreetAddress string `json:"street_address"`
AccountNumber string `json:"account_number"`
BankCode string `json:"bank_code"`
BankCodeType string `json:"bank_code_type"`
CreatedAt string `json:"created_at"`
}
if err := json.Unmarshal(data, &raw); err != nil {
return nil, err
}
return &types.RecipientBank{
ID: raw.ID,
AccountID: raw.AccountID,
Name: raw.Name,
Status: raw.Status,
Country: raw.Country,
StateProvince: raw.StateProvince,
PostalCode: raw.PostalCode,
City: raw.City,
StreetAddress: raw.StreetAddress,
AccountNumber: raw.AccountNumber,
BankCode: raw.BankCode,
BankCodeType: raw.BankCodeType,
CreatedAt: raw.CreatedAt,
}, nil
}