Initial commit
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TrackedCoin represents a coin we monitor across exchanges.
|
||||
type TrackedCoin struct {
|
||||
Name string // Display name (BTC, ETH, etc.)
|
||||
BN string // Binance symbol (BTCUSDT)
|
||||
BG string // Bitget symbol (BTCUSDT)
|
||||
HL string // HyperLiquid symbol (BTC)
|
||||
}
|
||||
|
||||
// PriceTick holds a price update with optional bid/ask.
|
||||
type PriceTick struct {
|
||||
Price float64
|
||||
Bid float64 // 0 if unknown
|
||||
Ask float64 // 0 if unknown
|
||||
}
|
||||
|
||||
// Spread holds bid/ask data for one exchange+coin.
|
||||
type Spread struct {
|
||||
Bid float64
|
||||
Ask float64
|
||||
Updated int64 // unix nano
|
||||
}
|
||||
|
||||
// PriceStore holds the latest prices from all exchanges, thread-safe.
|
||||
type PriceStore struct {
|
||||
mu sync.RWMutex
|
||||
prices map[string]map[string]float64 // coin -> exchange -> price
|
||||
spreads map[string]map[string]*Spread // coin -> exchange -> spread
|
||||
}
|
||||
|
||||
func NewPriceStore() *PriceStore {
|
||||
return &PriceStore{
|
||||
prices: make(map[string]map[string]float64),
|
||||
spreads: make(map[string]map[string]*Spread),
|
||||
}
|
||||
}
|
||||
|
||||
// Set stores a price update. If bid/ask are non-zero, also stores spread.
|
||||
func (s *PriceStore) Set(coin, exchange string, price float64) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.prices[coin] == nil {
|
||||
s.prices[coin] = make(map[string]float64)
|
||||
}
|
||||
s.prices[coin][exchange] = price
|
||||
}
|
||||
|
||||
// SetWithSpread stores price + bid/ask spread.
|
||||
func (s *PriceStore) SetWithSpread(coin, exchange string, price, bid, ask float64) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.prices[coin] == nil {
|
||||
s.prices[coin] = make(map[string]float64)
|
||||
}
|
||||
s.prices[coin][exchange] = price
|
||||
|
||||
if bid > 0 && ask > 0 {
|
||||
if s.spreads[coin] == nil {
|
||||
s.spreads[coin] = make(map[string]*Spread)
|
||||
}
|
||||
s.spreads[coin][exchange] = &Spread{
|
||||
Bid: bid, Ask: ask,
|
||||
Updated: time.Now().UnixNano(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PriceStore) Get(coin, exchange string) (float64, bool) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
p, ok := s.prices[coin][exchange]
|
||||
return p, ok
|
||||
}
|
||||
|
||||
// GetSpread returns the current bid-ask spread (as percentage of mid price).
|
||||
// Returns 0 if no spread data available.
|
||||
func (s *PriceStore) GetSpread(coin, exchange string) float64 {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
sp, ok := s.spreads[coin][exchange]
|
||||
if !ok || sp.Bid <= 0 || sp.Ask <= 0 {
|
||||
return 0
|
||||
}
|
||||
mid := (sp.Bid + sp.Ask) / 2
|
||||
if mid <= 0 {
|
||||
return 0
|
||||
}
|
||||
return (sp.Ask - sp.Bid) / mid * 100
|
||||
}
|
||||
|
||||
// GetAll returns a snapshot of all prices.
|
||||
func (s *PriceStore) GetAll() map[string]map[string]float64 {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
snap := make(map[string]map[string]float64)
|
||||
for coin, exMap := range s.prices {
|
||||
snap[coin] = make(map[string]float64)
|
||||
for ex, p := range exMap {
|
||||
snap[coin][ex] = p
|
||||
}
|
||||
}
|
||||
return snap
|
||||
}
|
||||
|
||||
// ArbOpportunity represents a profitable arbitrage route.
|
||||
type ArbOpportunity struct {
|
||||
Coin string
|
||||
Direction string // e.g. "BN->HL"
|
||||
BuyEx string
|
||||
SellEx string
|
||||
BuyPrice float64
|
||||
SellPrice float64
|
||||
NetProfit float64 // percentage after fees
|
||||
GrossBasis float64 // raw price difference %
|
||||
}
|
||||
Reference in New Issue
Block a user