File size: 5,278 Bytes
f5e8b69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
from collections import defaultdict


class OrderBook:
    def __init__(self, arrival_price=100.0):
        self.bids = defaultdict(list)
        self.asks = defaultdict(list)
        self.orders = {}
        self.arrival_price = arrival_price
        self.next_id = 0
        self.total_traded_volume = 0.0

    def add_order(self, side, price, size, agent_id):
        """
        Add a limit order and immediately match it against the opposite side.
        Returns (order_id, fills) where:
        - order_id is the ID of the resting order (or None if fully filled)
        - fills is a list of fill dicts with keys: price, size, buyer, seller
        """
        order_id = self.next_id
        self.next_id += 1
        order = {
            "id": order_id,
            "side": side,
            "price": price,
            "size": size,
            "agent_id": agent_id,
        }
        self.orders[order_id] = order
        remaining = size
        fills = []

        if side == "bid":
            while (
                remaining > 0 and self.best_ask is not None and price >= self.best_ask
            ):
                current_best_ask = self.best_ask

                if current_best_ask not in self.asks or not self.asks[current_best_ask]:
                    if current_best_ask in self.asks:
                        del self.asks[current_best_ask]
                    continue

                ask_order = self.asks[current_best_ask][0]
                fill = min(remaining, ask_order["size"])

                fills.append(
                    {
                        "price": ask_order["price"],
                        "size": fill,
                        "buyer": agent_id,
                        "seller": ask_order["agent_id"],
                    }
                )

                self.total_traded_volume += fill
                remaining -= fill
                ask_order["size"] -= fill

                if ask_order["size"] == 0:
                    self.cancel_order(ask_order["id"])
                    if not self.asks[current_best_ask]:
                        del self.asks[current_best_ask]

            if remaining > 0:
                order["size"] = remaining
                if price not in self.bids:
                    self.bids[price] = []
                self.bids[price].append(order)
                return order_id, fills
            else:
                if order_id in self.orders:
                    del self.orders[order_id]
                return None, fills

        else:  # side == 'ask'
            while (
                remaining > 0 and self.best_bid is not None and price <= self.best_bid
            ):
                current_best_bid = self.best_bid

                if current_best_bid not in self.bids or not self.bids[current_best_bid]:
                    if current_best_bid in self.bids:
                        del self.bids[current_best_bid]
                    continue

                bid_order = self.bids[current_best_bid][0]
                fill = min(remaining, bid_order["size"])

                fills.append(
                    {
                        "price": bid_order["price"],
                        "size": fill,
                        "buyer": bid_order["agent_id"],
                        "seller": agent_id,
                    }
                )

                self.total_traded_volume += fill
                remaining -= fill
                bid_order["size"] -= fill

                if bid_order["size"] == 0:
                    self.cancel_order(bid_order["id"])
                    if not self.bids[current_best_bid]:
                        del self.bids[current_best_bid]

            if remaining > 0:
                order["size"] = remaining
                if price not in self.asks:
                    self.asks[price] = []
                self.asks[price].append(order)
                return order_id, fills
            else:
                if order_id in self.orders:
                    del self.orders[order_id]
                return None, fills

    def cancel_order(self, order_id):
        if order_id not in self.orders:
            return False
        order = self.orders.pop(order_id)
        level = self.bids if order["side"] == "bid" else self.asks
        level[order["price"]] = [
            o for o in level[order["price"]] if o["id"] != order_id
        ]
        if not level[order["price"]]:
            del level[order["price"]]

    @property
    def best_bid(self):
        return max(self.bids.keys()) if self.bids else None

    @property
    def best_ask(self):
        return min(self.asks.keys()) if self.asks else None

    @property
    def mid(self):
        if self.best_bid and self.best_ask:
            return (self.best_bid + self.best_ask) / 2
        return self.arrival_price

    def get_depth(self, levels=5):
        bid_levels = sorted(self.bids.keys(), reverse=True)[:levels]
        ask_levels = sorted(self.asks.keys())[:levels]
        return {
            "bid_prices": bid_levels,
            "bid_sizes": [sum(o["size"] for o in self.bids[p]) for p in bid_levels],
            "ask_prices": ask_levels,
            "ask_sizes": [sum(o["size"] for o in self.asks[p]) for p in ask_levels],
        }