package market import ( "fmt" "time" "git.mleku.dev/mleku/dendrite/pkg/axiom" ) // CrossSpread is the spread between the same asset on two venues. // When this exceeds transaction costs, it is structural incoherence — // a price that exists in one place but not the other. type CrossSpread struct { Asset string VenueA Venue VenueB Venue BidA float64 // best bid on venue A AskB float64 // best ask on venue B BidB float64 // best bid on venue B AskA float64 // best ask on venue A Time time.Time } // SpreadAB returns the profit from buying on B and selling on A. // Positive means venue A's bid exceeds venue B's ask. func (cs *CrossSpread) SpreadAB() float64 { if cs.AskB == 0 { return 0 } return cs.BidA - cs.AskB } // SpreadBA returns the profit from buying on A and selling on B. // Positive means venue B's bid exceeds venue A's ask. func (cs *CrossSpread) SpreadBA() float64 { if cs.AskA == 0 { return 0 } return cs.BidB - cs.AskA } // BestSpread returns the best available cross-venue spread and its // direction. Positive return means profit is available. func (cs *CrossSpread) BestSpread() (spread float64, buyVenue, sellVenue Venue) { ab := cs.SpreadAB() ba := cs.SpreadBA() if ab >= ba { return ab, cs.VenueB, cs.VenueA } return ba, cs.VenueA, cs.VenueB } // CompareBooksForSpread compares two order books for the same asset on // different venues and returns the cross-venue spread. func CompareBooksForSpread(a, b *OrderBook) *CrossSpread { return &CrossSpread{ Asset: a.Asset, VenueA: a.Venue, VenueB: b.Venue, BidA: a.BestBid(), AskA: a.BestAsk(), BidB: b.BestBid(), AskB: b.BestAsk(), Time: time.Now(), } } // Dislocation is a mismatch between information structure and price // structure. The negative space between two views of the same asset — // the shape of what the market hasn't priced yet. type Dislocation struct { Asset string PriceMid float64 // midpoint from order book Sentiment float64 // derived from signed event chatter [-1, +1] Magnitude float64 // absolute divergence measure [0, 1] Direction int // +1 = price lags sentiment up, -1 = price lags sentiment down Time time.Time } // DislocationToElements decomposes a dislocation into typed lattice // elements. A dislocation is the structural gap between what the // signed chatter says and what the order book shows. func DislocationToElements(d *Dislocation) []axiom.Element { return []axiom.Element{ element{"asset", d.Asset}, element{"dislocation-magnitude", d.Magnitude}, element{"dislocation-direction", d.Direction}, element{"price-mid", d.PriceMid}, element{"sentiment", d.Sentiment}, element{"timestamp", fmt.Sprintf("%d", d.Time.Unix())}, } } // CrossSpreadToElements decomposes a cross-venue spread into lattice elements. func CrossSpreadToElements(cs *CrossSpread) []axiom.Element { spread, buyVenue, sellVenue := cs.BestSpread() return []axiom.Element{ element{"asset", cs.Asset}, element{"cross-spread", spread}, element{"buy-venue", string(buyVenue)}, element{"sell-venue", string(sellVenue)}, element{"venue", string(cs.VenueA)}, element{"venue", string(cs.VenueB)}, element{"timestamp", fmt.Sprintf("%d", cs.Time.Unix())}, } }