1"""Three market designs for Model C. Each takes a list of orders and returns executed trades.23Order: dict(agent=i, side='buy'|'sell', qty=float, limit=float|None, type='limit'|'market').4Trade: dict(buyer, seller, qty, price).56* `walrasian_call`: a single clearing price equates buy and sell quantities (limit orders define step demand/supply7 schedules; market orders are price-insensitive); rationing pro rata on the long side.8* `continuous_double_auction`: limit order book with price–time priority; orders arrive in a random sequence9 (seeded), marketable orders execute against resting ones at the resting price; records mid, spread, depth.10* `bilateral_bargaining`: random matching of a buyer and a seller for R rounds; trade if the buyer's limit >= the11 seller's limit at the Nash-bargaining price (weight `power` on the buyer), quantity = min of the two.12Feasibility (cash, holdings) is enforced by the ledger when trades are settled; designs only match.13"""14from __future__ import annotations1516import numpy as np171819def _split(orders):20 buys = [o for o in orders if o['side'] == 'buy' and o['qty'] > 0]21 sells = [o for o in orders if o['side'] == 'sell' and o['qty'] > 0]22 return buys, sells232425def walrasian_call(orders, p_ref: float, p_lo: float = 1e-6, p_hi: float = None):26 buys, sells = _split(orders)27 if not buys or not sells:28 return [], p_ref, dict(excess=0.0)29 p_hi = p_hi or 100 * p_ref3031 def demand(p):32 return sum(o['qty'] for o in buys if o['type'] == 'market' or o['limit'] >= p)3334 def supply(p):35 return sum(o['qty'] for o in sells if o['type'] == 'market' or o['limit'] <= p)3637 # price grid from limits: excess demand is a step function, find the price minimising |excess|38 cands = sorted(set([o['limit'] for o in buys + sells if o['limit'] is not None] + [p_ref]))39 best, best_ex = p_ref, float('inf')40 for p in cands:41 ex = demand(p) - supply(p)42 if abs(ex) < best_ex or (abs(ex) == best_ex and abs(p - p_ref) < abs(best - p_ref)):43 best, best_ex = p, abs(ex)44 p = best45 D, S = demand(p), supply(p)46 if D <= 0 or S <= 0:47 return [], p_ref, dict(excess=D - S)48 q = min(D, S)49 rb, rs = q / D, q / S50 bq = [(o['agent'], o['qty'] * rb) for o in buys if o['type'] == 'market' or o['limit'] >= p]51 sq = [(o['agent'], o['qty'] * rs) for o in sells if o['type'] == 'market' or o['limit'] <= p]52 trades = []53 i = j = 054 bq = [list(x) for x in bq]55 sq = [list(x) for x in sq]56 while i < len(bq) and j < len(sq):57 x = min(bq[i][1], sq[j][1])58 if x > 1e-12:59 trades.append(dict(buyer=bq[i][0], seller=sq[j][0], qty=x, price=p))60 bq[i][1] -= x61 sq[j][1] -= x62 if bq[i][1] <= 1e-12:63 i += 164 if sq[j][1] <= 1e-12:65 j += 166 return trades, p, dict(excess=D - S, demand=D, supply=S)676869def continuous_double_auction(orders, p_ref: float, rng: np.random.Generator):70 book_b, book_s = [], [] # resting orders: [price, time, agent, qty]71 trades = []72 seq = rng.permutation(len(orders))73 spreads, mids = [], []74 t = 075 for k in seq:76 o = dict(orders[k])77 t += 178 qty = o['qty']79 if qty <= 0:80 continue81 if o['side'] == 'buy':82 lim = np.inf if o['type'] == 'market' else o['limit']83 book_s.sort(key=lambda r: (r[0], r[1]))84 while qty > 1e-12 and book_s and book_s[0][0] <= lim:85 r = book_s[0]86 x = min(qty, r[3])87 trades.append(dict(buyer=o['agent'], seller=r[2], qty=x, price=r[0]))88 qty -= x89 r[3] -= x90 if r[3] <= 1e-12:91 book_s.pop(0)92 if qty > 1e-12 and o['type'] == 'limit':93 book_b.append([o['limit'], t, o['agent'], qty])94 else:95 lim = -np.inf if o['type'] == 'market' else o['limit']96 book_b.sort(key=lambda r: (-r[0], r[1]))97 while qty > 1e-12 and book_b and book_b[0][0] >= lim:98 r = book_b[0]99 x = min(qty, r[3])100 trades.append(dict(buyer=r[2], seller=o['agent'], qty=x, price=r[0]))101 qty -= x102 r[3] -= x103 if r[3] <= 1e-12:104 book_b.pop(0)105 if qty > 1e-12 and o['type'] == 'limit':106 book_s.append([o['limit'], t, o['agent'], qty])107 if book_b and book_s:108 bb = max(r[0] for r in book_b)109 ba = min(r[0] for r in book_s)110 if ba >= bb:111 spreads.append(ba - bb)112 mids.append(0.5 * (ba + bb))113 last = trades[-1]['price'] if trades else p_ref114 depth_b = sum(r[3] for r in book_b)115 depth_s = sum(r[3] for r in book_s)116 info = dict(spread=float(np.mean(spreads)) if spreads else np.nan, mid=float(np.mean(mids)) if mids else np.nan,117 depth=depth_b + depth_s, n_trades=len(trades))118 return trades, last, info119120121def bilateral_bargaining(orders, p_ref: float, rng: np.random.Generator, rounds: int = 3, power: float = 0.5):122 buys, sells = _split(orders)123 buys = [dict(o) for o in buys]124 sells = [dict(o) for o in sells]125 trades = []126 for _ in range(rounds):127 bl = [o for o in buys if o['qty'] > 1e-12]128 sl = [o for o in sells if o['qty'] > 1e-12]129 if not bl or not sl:130 break131 rng.shuffle(bl)132 rng.shuffle(sl)133 for b, s in zip(bl, sl):134 bl_lim = b['limit'] if b['type'] == 'limit' else p_ref * 1.5135 sl_lim = s['limit'] if s['type'] == 'limit' else p_ref * 0.5136 if bl_lim >= sl_lim and b['agent'] != s['agent']:137 price = power * sl_lim + (1 - power) * bl_lim # buyer with power `power` pulls price toward seller's ask138 x = min(b['qty'], s['qty'])139 trades.append(dict(buyer=b['agent'], seller=s['agent'], qty=x, price=price))140 b['qty'] -= x141 s['qty'] -= x142 last = float(np.mean([t['price'] for t in trades])) if trades else p_ref143 return trades, last, dict(n_trades=len(trades), dispersion=float(np.std([t['price'] for t in trades])) if trades else np.nan)144