mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
568 lines
13 KiB
Go
568 lines
13 KiB
Go
package lx
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// AlpacaSource provides low-latency market data from Alpaca Markets
|
|
// Alpaca offers real-time market data with sub-millisecond latency
|
|
type AlpacaSource struct {
|
|
apiKey string
|
|
apiSecret string
|
|
baseURL string
|
|
wsURL string
|
|
|
|
// Low-latency optimizations
|
|
client *http.Client
|
|
cache sync.Map // Lock-free cache for prices
|
|
lastUpdate map[string]time.Time
|
|
mu sync.RWMutex
|
|
|
|
// Price channels for streaming
|
|
priceStreams map[string]chan PriceUpdate
|
|
streamMu sync.RWMutex
|
|
}
|
|
|
|
// NYSEArcaSource provides direct market access to NYSE Arca
|
|
// NYSE Arca is the leading exchange for ETF trading with ultra-low latency
|
|
type NYSEArcaSource struct {
|
|
connectionID string
|
|
sessionKey string
|
|
|
|
// Direct market access connection
|
|
tcpConn interface{} // Would be actual TCP connection
|
|
lastHeartbeat time.Time
|
|
|
|
// Order book cache
|
|
books map[string]*OrderBook
|
|
bookMu sync.RWMutex
|
|
}
|
|
|
|
// IEXCloudSource provides real-time data from IEX Cloud
|
|
// IEX offers fair-access, low-latency market data
|
|
type IEXCloudSource struct {
|
|
token string
|
|
version string
|
|
environment string // sandbox or production
|
|
|
|
// WebSocket for streaming
|
|
wsConn interface{} // Would be actual WebSocket
|
|
subscribers map[string][]chan PriceUpdate
|
|
subMu sync.RWMutex
|
|
}
|
|
|
|
// PolygonIOSource provides real-time and historical market data
|
|
// Polygon.io offers microsecond-precision timestamps
|
|
type PolygonIOSource struct {
|
|
apiKey string
|
|
cluster string // stocks, crypto, forex, etc.
|
|
|
|
// Aggregated data cache
|
|
aggregates map[string][]Aggregate
|
|
aggMu sync.RWMutex
|
|
|
|
// WebSocket streaming
|
|
wsEndpoint string
|
|
streamConn interface{}
|
|
}
|
|
|
|
// Aggregate represents a price bar from Polygon
|
|
type Aggregate struct {
|
|
Symbol string
|
|
Open float64
|
|
High float64
|
|
Low float64
|
|
Close float64
|
|
Volume float64
|
|
VWAP float64
|
|
Timestamp int64 // Unix nanoseconds for precision
|
|
}
|
|
|
|
// CMEDataSource provides futures data from CME Group
|
|
// CME offers the most liquid futures markets globally
|
|
type CMEDataSource struct {
|
|
username string
|
|
password string
|
|
|
|
// FIX connection for ultra-low latency
|
|
fixSession interface{} // Would be QuickFIX session
|
|
|
|
// Market data
|
|
futuresData map[string]*FuturesContract
|
|
dataMu sync.RWMutex
|
|
}
|
|
|
|
// FuturesContract represents a CME futures contract
|
|
type FuturesContract struct {
|
|
Symbol string
|
|
ExpirationDate time.Time
|
|
ContractSize float64
|
|
TickSize float64
|
|
LastPrice float64
|
|
BidPrice float64
|
|
AskPrice float64
|
|
OpenInterest int64
|
|
LastUpdate time.Time
|
|
}
|
|
|
|
// RefinitivSource (formerly Thomson Reuters) for institutional data
|
|
type RefinitivSource struct {
|
|
appKey string
|
|
machineID string
|
|
position string // Trading desk position for entitlements
|
|
|
|
// Elektron Real-Time connection
|
|
ertEndpoint string
|
|
ertSession interface{}
|
|
|
|
// Cache for subscribed instruments
|
|
instruments map[string]*InstrumentData
|
|
instMu sync.RWMutex
|
|
}
|
|
|
|
// InstrumentData from Refinitiv
|
|
type InstrumentData struct {
|
|
RIC string // Reuters Instrument Code
|
|
LastPrice float64
|
|
BidPrice float64
|
|
AskPrice float64
|
|
BidSize float64
|
|
AskSize float64
|
|
LastUpdate time.Time
|
|
LastExchange string
|
|
}
|
|
|
|
// ICEDataSource for ICE Data Services (formerly IDC)
|
|
type ICEDataSource struct {
|
|
username string
|
|
password string
|
|
feedType string // consolidated, direct, etc.
|
|
|
|
// Low-latency multicast feed
|
|
multicastGroup string
|
|
multicastPort int
|
|
|
|
// Normalized data
|
|
marketData map[string]*ICEMarketData
|
|
mdMu sync.RWMutex
|
|
}
|
|
|
|
// ICEMarketData represents ICE market data
|
|
type ICEMarketData struct {
|
|
Symbol string
|
|
Exchange string
|
|
LastPrice float64
|
|
LastSize float64
|
|
TotalVolume int64
|
|
VWAP float64
|
|
LastUpdate time.Time
|
|
}
|
|
|
|
// BloombergBPIPE for Bloomberg Professional API
|
|
type BloombergBPIPE struct {
|
|
serverHost string
|
|
serverPort int
|
|
uuid string
|
|
|
|
// Session management
|
|
session interface{} // Would be blpapi.Session
|
|
|
|
// Subscription management
|
|
subscriptions map[string]*BloombergSubscription
|
|
subMu sync.RWMutex
|
|
}
|
|
|
|
// BloombergSubscription for real-time Bloomberg data
|
|
type BloombergSubscription struct {
|
|
Security string
|
|
Fields []string
|
|
LastData map[string]interface{}
|
|
LastUpdate time.Time
|
|
}
|
|
|
|
// NasdaqTotalView provides full NASDAQ depth of book
|
|
type NasdaqTotalView struct {
|
|
username string
|
|
password string
|
|
|
|
// ITCH protocol connection
|
|
itchConn interface{}
|
|
messageSeq uint64
|
|
|
|
// Full order book
|
|
orderBooks map[string]*NasdaqBook
|
|
bookMu sync.RWMutex
|
|
}
|
|
|
|
// NasdaqBook represents full NASDAQ order book
|
|
type NasdaqBook struct {
|
|
Symbol string
|
|
BidLevels []PriceLevel
|
|
AskLevels []PriceLevel
|
|
LastMatch float64
|
|
LastUpdate time.Time
|
|
}
|
|
|
|
// CoinbaseProSource for crypto markets
|
|
type CoinbaseProSource struct {
|
|
apiKey string
|
|
apiSecret string
|
|
passphrase string
|
|
|
|
// WebSocket feed
|
|
wsFeed interface{}
|
|
|
|
// Order book management
|
|
books map[string]*CryptoBook
|
|
bookMu sync.RWMutex
|
|
}
|
|
|
|
// CryptoBook for cryptocurrency order books
|
|
type CryptoBook struct {
|
|
Symbol string
|
|
Bids []OrderLevel
|
|
Asks []OrderLevel
|
|
LastTrade float64
|
|
Volume24h float64
|
|
LastUpdate time.Time
|
|
}
|
|
|
|
// NewAlpacaSource creates a new Alpaca market data source
|
|
func NewAlpacaSource(apiKey, apiSecret string) *AlpacaSource {
|
|
return &AlpacaSource{
|
|
apiKey: apiKey,
|
|
apiSecret: apiSecret,
|
|
baseURL: "https://data.alpaca.markets/v2",
|
|
wsURL: "wss://stream.data.alpaca.markets/v2",
|
|
client: &http.Client{Timeout: 100 * time.Millisecond}, // Ultra-low timeout
|
|
lastUpdate: make(map[string]time.Time),
|
|
priceStreams: make(map[string]chan PriceUpdate),
|
|
}
|
|
}
|
|
|
|
// GetLatestPrice returns the latest price with sub-millisecond latency
|
|
func (a *AlpacaSource) GetLatestPrice(symbol string) (float64, error) {
|
|
// Check cache first (lock-free)
|
|
if cached, ok := a.cache.Load(symbol); ok {
|
|
if price, ok := cached.(float64); ok {
|
|
return price, nil
|
|
}
|
|
}
|
|
|
|
// Fetch from API
|
|
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
|
|
defer cancel()
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET",
|
|
fmt.Sprintf("%s/stocks/%s/trades/latest", a.baseURL, symbol), nil)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
req.Header.Set("APCA-API-KEY-ID", a.apiKey)
|
|
req.Header.Set("APCA-API-SECRET-KEY", a.apiSecret)
|
|
|
|
resp, err := a.client.Do(req)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var result struct {
|
|
Trade struct {
|
|
Price float64 `json:"p"`
|
|
} `json:"trade"`
|
|
}
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Update cache
|
|
a.cache.Store(symbol, result.Trade.Price)
|
|
|
|
return result.Trade.Price, nil
|
|
}
|
|
|
|
// StreamPrices starts streaming real-time prices
|
|
func (a *AlpacaSource) StreamPrices(symbols []string) (<-chan PriceUpdate, error) {
|
|
updates := make(chan PriceUpdate, 1000) // Buffered for performance
|
|
|
|
// In production, this would establish WebSocket connection
|
|
// and stream real-time updates with nanosecond timestamps
|
|
|
|
go func() {
|
|
ticker := time.NewTicker(1 * time.Millisecond) // 1ms updates
|
|
defer ticker.Stop()
|
|
|
|
for range ticker.C {
|
|
for _, symbol := range symbols {
|
|
// Simulate real-time price updates
|
|
price, _ := a.GetLatestPrice(symbol)
|
|
select {
|
|
case updates <- PriceUpdate{
|
|
Symbol: symbol,
|
|
Price: price,
|
|
Timestamp: time.Now(),
|
|
Source: "alpaca",
|
|
}:
|
|
default:
|
|
// Skip if channel is full (non-blocking)
|
|
}
|
|
}
|
|
}
|
|
}()
|
|
|
|
return updates, nil
|
|
}
|
|
|
|
// GetOrderBook returns Level 2 order book data
|
|
func (a *AlpacaSource) GetOrderBook(symbol string) (*OrderBookSnapshot, error) {
|
|
// Alpaca doesn't provide full order book via REST API
|
|
// This would use WebSocket for real-time book updates
|
|
|
|
// For now, return a mock snapshot
|
|
return &OrderBookSnapshot{
|
|
Symbol: symbol,
|
|
Timestamp: time.Now(),
|
|
Bids: []OrderLevel{
|
|
{Price: 100.00, Size: 1000},
|
|
{Price: 99.99, Size: 2000},
|
|
{Price: 99.98, Size: 1500},
|
|
},
|
|
Asks: []OrderLevel{
|
|
{Price: 100.01, Size: 1000},
|
|
{Price: 100.02, Size: 2000},
|
|
{Price: 100.03, Size: 1500},
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// GetHistoricalBars returns historical price bars
|
|
func (a *AlpacaSource) GetHistoricalBars(symbol string, start, end time.Time, timeframe string) ([]Bar, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
|
defer cancel()
|
|
|
|
url := fmt.Sprintf("%s/stocks/%s/bars?start=%s&end=%s&timeframe=%s",
|
|
a.baseURL, symbol, start.Format(time.RFC3339), end.Format(time.RFC3339), timeframe)
|
|
|
|
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
req.Header.Set("APCA-API-KEY-ID", a.apiKey)
|
|
req.Header.Set("APCA-API-SECRET-KEY", a.apiSecret)
|
|
|
|
resp, err := a.client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var result struct {
|
|
Bars []Bar `json:"bars"`
|
|
}
|
|
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return result.Bars, nil
|
|
}
|
|
|
|
// Bar represents a price bar
|
|
type Bar struct {
|
|
Timestamp time.Time `json:"t"`
|
|
Open float64 `json:"o"`
|
|
High float64 `json:"h"`
|
|
Low float64 `json:"l"`
|
|
Close float64 `json:"c"`
|
|
Volume int64 `json:"v"`
|
|
VWAP float64 `json:"vw"`
|
|
}
|
|
|
|
// PriceUpdate represents a real-time price update
|
|
type PriceUpdate struct {
|
|
Symbol string
|
|
Price float64
|
|
Bid float64
|
|
Ask float64
|
|
BidSize float64
|
|
AskSize float64
|
|
Volume int64
|
|
Timestamp time.Time
|
|
Source string
|
|
Latency time.Duration // Network latency measurement
|
|
}
|
|
|
|
// MarketDataAggregator combines multiple low-latency sources
|
|
type MarketDataAggregator struct {
|
|
sources map[string]MarketDataSource
|
|
weights map[string]float64
|
|
cache sync.Map
|
|
lastUpdate sync.Map
|
|
|
|
// Latency tracking
|
|
latencies map[string]*LatencyTracker
|
|
latencyMu sync.RWMutex
|
|
}
|
|
|
|
// MarketDataSource interface for all data sources
|
|
type MarketDataSource interface {
|
|
GetLatestPrice(symbol string) (float64, error)
|
|
GetOrderBook(symbol string) (*OrderBookSnapshot, error)
|
|
StreamPrices(symbols []string) (<-chan PriceUpdate, error)
|
|
}
|
|
|
|
// LatencyTracker tracks network latency for each source
|
|
type LatencyTracker struct {
|
|
source string
|
|
samples []time.Duration
|
|
avgLatency time.Duration
|
|
minLatency time.Duration
|
|
maxLatency time.Duration
|
|
lastUpdate time.Time
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewMarketDataAggregator creates a new aggregator for multiple sources
|
|
func NewMarketDataAggregator() *MarketDataAggregator {
|
|
return &MarketDataAggregator{
|
|
sources: make(map[string]MarketDataSource),
|
|
weights: make(map[string]float64),
|
|
latencies: make(map[string]*LatencyTracker),
|
|
}
|
|
}
|
|
|
|
// AddSource adds a new market data source with weight
|
|
func (m *MarketDataAggregator) AddSource(name string, source MarketDataSource, weight float64) {
|
|
m.sources[name] = source
|
|
m.weights[name] = weight
|
|
m.latencies[name] = &LatencyTracker{
|
|
source: name,
|
|
samples: make([]time.Duration, 0, 1000),
|
|
minLatency: time.Hour, // Start with high value
|
|
}
|
|
}
|
|
|
|
// GetBestPrice returns the best aggregated price from all sources
|
|
func (m *MarketDataAggregator) GetBestPrice(symbol string) (float64, error) {
|
|
var (
|
|
prices []float64
|
|
weights []float64
|
|
wg sync.WaitGroup
|
|
mu sync.Mutex
|
|
)
|
|
|
|
// Query all sources in parallel
|
|
for name, source := range m.sources {
|
|
wg.Add(1)
|
|
go func(n string, s MarketDataSource) {
|
|
defer wg.Done()
|
|
|
|
start := time.Now()
|
|
price, err := s.GetLatestPrice(symbol)
|
|
latency := time.Since(start)
|
|
|
|
// Track latency
|
|
m.recordLatency(n, latency)
|
|
|
|
if err == nil && price > 0 {
|
|
mu.Lock()
|
|
prices = append(prices, price)
|
|
weights = append(weights, m.weights[n])
|
|
mu.Unlock()
|
|
}
|
|
}(name, source)
|
|
}
|
|
|
|
wg.Wait()
|
|
|
|
if len(prices) == 0 {
|
|
return 0, fmt.Errorf("no prices available")
|
|
}
|
|
|
|
// Calculate weighted average
|
|
var weightedSum, totalWeight float64
|
|
for i, price := range prices {
|
|
weightedSum += price * weights[i]
|
|
totalWeight += weights[i]
|
|
}
|
|
|
|
return weightedSum / totalWeight, nil
|
|
}
|
|
|
|
// recordLatency records latency measurement
|
|
func (m *MarketDataAggregator) recordLatency(source string, latency time.Duration) {
|
|
m.latencyMu.RLock()
|
|
tracker, exists := m.latencies[source]
|
|
m.latencyMu.RUnlock()
|
|
|
|
if !exists {
|
|
return
|
|
}
|
|
|
|
tracker.mu.Lock()
|
|
defer tracker.mu.Unlock()
|
|
|
|
tracker.samples = append(tracker.samples, latency)
|
|
if len(tracker.samples) > 1000 {
|
|
tracker.samples = tracker.samples[1:] // Keep last 1000 samples
|
|
}
|
|
|
|
// Update min/max
|
|
if latency < tracker.minLatency {
|
|
tracker.minLatency = latency
|
|
}
|
|
if latency > tracker.maxLatency {
|
|
tracker.maxLatency = latency
|
|
}
|
|
|
|
// Calculate average
|
|
var sum time.Duration
|
|
for _, l := range tracker.samples {
|
|
sum += l
|
|
}
|
|
tracker.avgLatency = sum / time.Duration(len(tracker.samples))
|
|
tracker.lastUpdate = time.Now()
|
|
}
|
|
|
|
// GetLatencyStats returns latency statistics for all sources
|
|
func (m *MarketDataAggregator) GetLatencyStats() map[string]LatencyStats {
|
|
stats := make(map[string]LatencyStats)
|
|
|
|
m.latencyMu.RLock()
|
|
defer m.latencyMu.RUnlock()
|
|
|
|
for name, tracker := range m.latencies {
|
|
tracker.mu.RLock()
|
|
stats[name] = LatencyStats{
|
|
Source: name,
|
|
AvgLatency: tracker.avgLatency,
|
|
MinLatency: tracker.minLatency,
|
|
MaxLatency: tracker.maxLatency,
|
|
Samples: len(tracker.samples),
|
|
LastUpdate: tracker.lastUpdate,
|
|
}
|
|
tracker.mu.RUnlock()
|
|
}
|
|
|
|
return stats
|
|
}
|
|
|
|
// LatencyStats represents latency statistics for a source
|
|
type LatencyStats struct {
|
|
Source string
|
|
AvgLatency time.Duration
|
|
MinLatency time.Duration
|
|
MaxLatency time.Duration
|
|
Samples int
|
|
LastUpdate time.Time
|
|
}
|