Files
zeekay bf09f371b2 fix: red team findings — 5 critical + 3 high from Alpaca sign-off review
C1: queryCountryInfos endpoint typo (-1 suffix removed)
C2: ValidateTIF wired into CreateOrder (was dead code)
C3: GetFixedIncomeQuote returns honest error (Moment ToS required)
C4: Agreement signing uses real client IP (was 0.0.0.0)
C5: Overnight trading uses America/New_York timezone (was UTC-4)
H1: Background asset sync goroutine (1-hour ticker)
H2: ACATS disclosure API endpoint
H3: Crypto $200K aggregate limit on split plans
2026-04-06 15:53:47 -07:00

195 lines
9.3 KiB
Go

package provider
import (
"context"
"fmt"
"sync"
"github.com/luxfi/broker/pkg/types"
)
// TradingExtended is an optional interface for providers that support
// advanced trading operations beyond the base Provider interface.
type TradingExtended interface {
ReplaceOrder(ctx context.Context, accountID, orderID string, req *types.ReplaceOrderRequest) (*types.Order, error)
CancelAllOrders(ctx context.Context, accountID string) error
GetPosition(ctx context.Context, accountID, symbol string) (*types.Position, error)
ClosePosition(ctx context.Context, accountID, symbol string, qty *float64) (*types.Order, error)
CloseAllPositions(ctx context.Context, accountID string) ([]*types.Order, error)
ListOrdersFiltered(ctx context.Context, accountID string, params *types.ListOrdersParams) ([]*types.Order, error)
}
// AccountManager is an optional interface for account lifecycle management.
type AccountManager interface {
UpdateAccount(ctx context.Context, accountID string, req *types.UpdateAccountRequest) (*types.Account, error)
CloseAccount(ctx context.Context, accountID string) error
GetAccountActivities(ctx context.Context, accountID string, params *types.ActivityParams) ([]*types.Activity, error)
}
// DocumentManager is an optional interface for document upload/retrieval.
type DocumentManager interface {
UploadDocument(ctx context.Context, accountID string, doc *types.DocumentUpload) (*types.Document, error)
ListDocuments(ctx context.Context, accountID string, params *types.DocumentParams) ([]*types.Document, error)
GetDocument(ctx context.Context, accountID, documentID string) (*types.Document, error)
DownloadDocument(ctx context.Context, accountID, documentID string) ([]byte, string, error)
}
// JournalManager is an optional interface for inter-account journal transfers.
type JournalManager interface {
CreateJournal(ctx context.Context, req *types.CreateJournalRequest) (*types.Journal, error)
ListJournals(ctx context.Context, params *types.JournalParams) ([]*types.Journal, error)
GetJournal(ctx context.Context, journalID string) (*types.Journal, error)
DeleteJournal(ctx context.Context, journalID string) error
CreateBatchJournal(ctx context.Context, req *types.BatchJournalRequest) ([]*types.Journal, error)
ReverseBatchJournal(ctx context.Context, req *types.ReverseBatchJournalRequest) ([]*types.Journal, error)
}
// TransferExtended is an optional interface for extended transfer operations.
type TransferExtended interface {
CancelTransfer(ctx context.Context, accountID, transferID string) error
DeleteACHRelationship(ctx context.Context, accountID, achID string) error
CreateRecipientBank(ctx context.Context, accountID string, req *types.CreateBankRequest) (*types.RecipientBank, error)
ListRecipientBanks(ctx context.Context, accountID string) ([]*types.RecipientBank, error)
DeleteRecipientBank(ctx context.Context, accountID, bankID string) error
}
// ACATSManager is an optional interface for ACATS (Automated Customer Account Transfer Service) operations.
type ACATSManager interface {
CreateACATSTransfer(ctx context.Context, accountID string, req *types.CreateACATSTransferRequest) (*types.ACATSTransfer, error)
GetACATSTransfer(ctx context.Context, accountID, transferID string) (*types.ACATSTransfer, error)
ListACATSTransfers(ctx context.Context, accountID string) ([]*types.ACATSTransfer, error)
CancelACATSTransfer(ctx context.Context, accountID, transferID string) error
}
// FixedIncomeTrader is an optional interface for fixed income order execution,
// positions, and asset listing (corporate bonds, treasuries).
type FixedIncomeTrader interface {
ListCorporateBonds(ctx context.Context) ([]*types.Asset, error)
ListTreasuryBonds(ctx context.Context) ([]*types.Asset, error)
CreateFixedIncomeOrder(ctx context.Context, accountID string, req *types.CreateOrderRequest) (*types.Order, error)
GetFixedIncomeOrder(ctx context.Context, accountID, orderID string) (*types.Order, error)
CancelFixedIncomeOrder(ctx context.Context, accountID, orderID string) error
GetFixedIncomeActivities(ctx context.Context, accountID string) ([]*types.Activity, error)
GetFixedIncomePositions(ctx context.Context, accountID string) ([]*types.Position, error)
CloseFixedIncomePosition(ctx context.Context, accountID, symbol string, qty *float64) (*types.Order, error)
}
// CryptoDataProvider is an optional interface for crypto-specific market data.
type CryptoDataProvider interface {
GetCryptoBars(ctx context.Context, req *types.CryptoBarsRequest) (*types.BarsResponse, error)
GetCryptoQuotes(ctx context.Context, req *types.CryptoQuotesRequest) (*types.QuotesResponse, error)
GetCryptoTrades(ctx context.Context, req *types.CryptoTradesRequest) (*types.TradesResponse, error)
GetCryptoSnapshots(ctx context.Context, symbols []string) (map[string]*types.CryptoSnapshot, error)
}
// EventStreamer is an optional interface for server-sent event streaming.
type EventStreamer interface {
StreamTradeEvents(ctx context.Context, since string) (<-chan *types.TradeEvent, error)
StreamAccountEvents(ctx context.Context, since string) (<-chan *types.AccountEvent, error)
StreamTransferEvents(ctx context.Context, since string) (<-chan *types.TransferEvent, error)
StreamJournalEvents(ctx context.Context, since string) (<-chan *types.JournalEvent, error)
}
// PortfolioAnalyzer is an optional interface for portfolio history.
type PortfolioAnalyzer interface {
GetPortfolioHistory(ctx context.Context, accountID string, params *types.HistoryParams) (*types.PortfolioHistory, error)
}
// WatchlistManager is an optional interface for watchlist operations.
type WatchlistManager interface {
CreateWatchlist(ctx context.Context, accountID string, req *types.CreateWatchlistRequest) (*types.Watchlist, error)
ListWatchlists(ctx context.Context, accountID string) ([]*types.Watchlist, error)
GetWatchlist(ctx context.Context, accountID, watchlistID string) (*types.Watchlist, error)
UpdateWatchlist(ctx context.Context, accountID, watchlistID string, req *types.UpdateWatchlistRequest) (*types.Watchlist, error)
DeleteWatchlist(ctx context.Context, accountID, watchlistID string) error
AddWatchlistAsset(ctx context.Context, accountID, watchlistID, symbol string) (*types.Watchlist, error)
RemoveWatchlistAsset(ctx context.Context, accountID, watchlistID, symbol string) error
}
// Provider is the unified interface every broker backend must implement.
type Provider interface {
// Name returns the provider identifier (e.g. "alpaca", "ibkr").
Name() string
// Accounts
CreateAccount(ctx context.Context, req *types.CreateAccountRequest) (*types.Account, error)
GetAccount(ctx context.Context, providerAccountID string) (*types.Account, error)
ListAccounts(ctx context.Context) ([]*types.Account, error)
// Portfolio & Positions
GetPortfolio(ctx context.Context, providerAccountID string) (*types.Portfolio, error)
// Orders
CreateOrder(ctx context.Context, providerAccountID string, req *types.CreateOrderRequest) (*types.Order, error)
ListOrders(ctx context.Context, providerAccountID string) ([]*types.Order, error)
GetOrder(ctx context.Context, providerAccountID, providerOrderID string) (*types.Order, error)
CancelOrder(ctx context.Context, providerAccountID, providerOrderID string) error
// Transfers
CreateTransfer(ctx context.Context, providerAccountID string, req *types.CreateTransferRequest) (*types.Transfer, error)
ListTransfers(ctx context.Context, providerAccountID string) ([]*types.Transfer, error)
// Bank Relationships
CreateBankRelationship(ctx context.Context, providerAccountID string, ownerName, accountType, accountNumber, routingNumber string) (*types.BankRelationship, error)
ListBankRelationships(ctx context.Context, providerAccountID string) ([]*types.BankRelationship, error)
// Assets
ListAssets(ctx context.Context, class string) ([]*types.Asset, error)
GetAsset(ctx context.Context, symbolOrID string) (*types.Asset, error)
// Market Data
GetSnapshot(ctx context.Context, symbol string) (*types.MarketSnapshot, error)
GetSnapshots(ctx context.Context, symbols []string) (map[string]*types.MarketSnapshot, error)
GetBars(ctx context.Context, symbol, timeframe, start, end string, limit int) ([]*types.Bar, error)
GetLatestTrades(ctx context.Context, symbols []string) (map[string]*types.Trade, error)
GetLatestQuotes(ctx context.Context, symbols []string) (map[string]*types.Quote, error)
GetClock(ctx context.Context) (*types.MarketClock, error)
GetCalendar(ctx context.Context, start, end string) ([]*types.MarketCalendarDay, error)
}
// Registry holds all registered providers.
type Registry struct {
mu sync.RWMutex
providers map[string]Provider
}
func NewRegistry() *Registry {
return &Registry{providers: make(map[string]Provider)}
}
func (r *Registry) Register(p Provider) {
r.mu.Lock()
defer r.mu.Unlock()
r.providers[p.Name()] = p
}
func (r *Registry) Get(name string) (Provider, error) {
r.mu.RLock()
defer r.mu.RUnlock()
p, ok := r.providers[name]
if !ok {
return nil, fmt.Errorf("provider %q not registered", name)
}
return p, nil
}
func (r *Registry) Unregister(name string) bool {
r.mu.Lock()
defer r.mu.Unlock()
if _, ok := r.providers[name]; !ok {
return false
}
delete(r.providers, name)
return true
}
func (r *Registry) List() []string {
r.mu.RLock()
defer r.mu.RUnlock()
names := make([]string, 0, len(r.providers))
for n := range r.providers {
names = append(names, n)
}
return names
}