market.go raw

   1  // Package market implements financial primitives as lattice elements.
   2  //
   3  // An order book entry is structure. A spread is structure. A dislocation
   4  // between price and information is structure. The lattice doesn't
   5  // distinguish domain — Element is Element. Price is structure. Events
   6  // are structure. The organism sees structure.
   7  package market
   8  
   9  import "time"
  10  
  11  // Side is bid or ask.
  12  type Side int
  13  
  14  const (
  15  	Bid Side = iota
  16  	Ask
  17  )
  18  
  19  func (s Side) String() string {
  20  	if s == Bid {
  21  		return "bid"
  22  	}
  23  	return "ask"
  24  }
  25  
  26  // Venue identifies an exchange or trading platform.
  27  type Venue string
  28  
  29  // OrderEntry is a single price level in an order book.
  30  // It implements no special interface — it becomes a lattice element
  31  // through enzymatic decomposition, same as a Nostr event.
  32  type OrderEntry struct {
  33  	Price  float64
  34  	Volume float64
  35  	Side   Side
  36  	Venue  Venue
  37  	Time   time.Time
  38  }
  39  
  40  // OrderBook is a snapshot of bids and asks for one asset on one venue.
  41  type OrderBook struct {
  42  	Asset string
  43  	Venue Venue
  44  	Bids  []OrderEntry
  45  	Asks  []OrderEntry
  46  	Time  time.Time
  47  }
  48  
  49  // BestBid returns the highest bid, or zero if empty.
  50  func (ob *OrderBook) BestBid() float64 {
  51  	best := 0.0
  52  	for _, e := range ob.Bids {
  53  		if e.Price > best {
  54  			best = e.Price
  55  		}
  56  	}
  57  	return best
  58  }
  59  
  60  // BestAsk returns the lowest ask, or zero if empty.
  61  func (ob *OrderBook) BestAsk() float64 {
  62  	if len(ob.Asks) == 0 {
  63  		return 0
  64  	}
  65  	best := ob.Asks[0].Price
  66  	for _, e := range ob.Asks[1:] {
  67  		if e.Price < best {
  68  			best = e.Price
  69  		}
  70  	}
  71  	return best
  72  }
  73  
  74  // Spread returns the difference between best ask and best bid.
  75  // Negative or zero spread indicates a crossed book.
  76  func (ob *OrderBook) Spread() float64 {
  77  	ask := ob.BestAsk()
  78  	bid := ob.BestBid()
  79  	if ask == 0 || bid == 0 {
  80  		return 0
  81  	}
  82  	return ask - bid
  83  }
  84  
  85  // MidPrice returns the midpoint between best bid and best ask.
  86  func (ob *OrderBook) MidPrice() float64 {
  87  	ask := ob.BestAsk()
  88  	bid := ob.BestBid()
  89  	if ask == 0 || bid == 0 {
  90  		return 0
  91  	}
  92  	return (ask + bid) / 2
  93  }
  94  
  95  // DepthImbalance returns the ratio of bid volume to total volume
  96  // within the top N levels. Range [0,1]: 0 = all asks, 1 = all bids.
  97  // Returns 0.5 if both sides are empty.
  98  func (ob *OrderBook) DepthImbalance(levels int) float64 {
  99  	bidVol := topVolume(ob.Bids, levels)
 100  	askVol := topVolume(ob.Asks, levels)
 101  	total := bidVol + askVol
 102  	if total == 0 {
 103  		return 0.5
 104  	}
 105  	return bidVol / total
 106  }
 107  
 108  func topVolume(entries []OrderEntry, n int) float64 {
 109  	vol := 0.0
 110  	for i, e := range entries {
 111  		if i >= n {
 112  			break
 113  		}
 114  		vol += e.Volume
 115  	}
 116  	return vol
 117  }
 118