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