source: docs/P1-ConceptualModel/ERModel.md@ ef1c1c7

main
Last change on this file since ef1c1c7 was ef1c1c7, checked in by Stefan <trsunovstefan@…>, 6 days ago

Wiki docs, phase 6 and phase 7 added

  • Property mode set to 100644
File size: 17.7 KB
RevLine 
[ef1c1c7]1# Entity-Relationship Model v.04
[d8ce4e2]2
3## Diagram
4
[ef1c1c7]5![ERModel_v04](ERModel_v04.png)
[d8ce4e2]6
7Notation: Chen. Rectangles are entity sets, diamonds are relationships, ellipses
8are attributes, underlined ellipses are primary keys, the dashed ellipse is a
9derived attribute. A double line between an entity set and a relationship marks
10**total participation** (every instance of that entity set must participate); a
11single line marks partial participation.
12
13Two deliberate modeling decisions worth stating up front:
14
15- **No foreign keys appear in the diagram.** Connections between entity sets are
16 expressed as relationships, per the notation. Foreign-key columns appear only
17 in the relational model in [RelationalDesign](../P2-RelationalDesign/RelationalDesign.md).
18- **`Holds` and `Contains` are relationships, not entity sets.** Both are M:N and
19 both carry their own attributes, which is exactly what a Chen relationship is
20 for. They become tables (`holdings`, `watchlist_items`) only in P2.
21
22## Data requirements
23
[9577c79]24Each entity set is given as a short rationale for why it exists as its own set,
25its keys, and its attributes as a table. Each relationship is given as its
26cardinality and participation, a short rationale, and — where it carries data —
27an attribute table.
28
[d8ce4e2]29### Entity sets
30
31#### Users
32Registered participants of the platform. Every action in the simulation is
33attributed to a user, and the two balance attributes are what makes the
[9577c79]34simulation work: cash that is free to trade is tracked separately from cash
35that is currently committed to open positions, so the platform can refuse a
36purchase without having to recompute the whole portfolio first.
37
38**Keys:** candidates `{id}`, `{username}`, `{email}`; primary key **`id`**. A
39surrogate UUID was chosen because it is opaque and stable — `username` and
40`email` are both things a user may legitimately want to change later, and
41every relationship in the diagram points at `Users`, so a mutable key would
42propagate changes across the whole database.
43
44| Attribute | Type | Constraints |
45|---|---|---|
46| `id` | UUID | PK, required |
47| `username` | text(50) | required, unique |
48| `email` | text(255) | required, unique, contains `@` |
49| `full_name` | text(200) | optional |
50| `password_hash` | text(255) | required — never the password itself; the prototype stores a SHA-256 hex digest |
51| `available_balance` | numeric(18,4) | required, default 0, ≥ 0 |
52| `invested_balance` | numeric(18,4) | required, default 0, ≥ 0 |
[ef1c1c7]53| `reserved_balance` | numeric(18,4) | required, default 0, ≥ 0 — cash set aside for the user's open buy orders (added in v04, after P7) |
[9577c79]54| `created_at` | timestamptz | required, defaults to now |
55| `updated_at` | timestamptz | optional (null until first change) |
[d8ce4e2]56
57#### Cryptos
58The catalog of crypto assets the platform knows about. Kept separate from
59`Markets` because an asset exists independently of the pairs it is traded in —
60the same asset can be quoted against several currencies, and a user's holding is
61in the *asset*, not in a particular pair.
62
[9577c79]63**Keys:** candidates `{id}`, `{symbol}`; primary key **`id`**, for the same
64reason as in `Users`. `symbol` is kept as a unique natural key because that is
65what users type and see.
66
67| Attribute | Type | Constraints |
68|---|---|---|
69| `id` | UUID | PK, required |
70| `symbol` | text(20) | required, unique (e.g. `BTC`) |
71| `name` | text(255) | required (e.g. `Bitcoin`) |
72| `created_at` | timestamptz | required, defaults to now |
[d8ce4e2]73
74#### Markets
75A tradeable pair: one crypto asset quoted in one currency, e.g. BTC/USD. This is
76where prices live, and it is the thing an order is placed *on*. Modeled as its
77own entity set rather than an attribute of `Cryptos` because a market has its own
78lifecycle — it can be deactivated without deleting the asset — and because
79trades, candles and orders all reference the pair, not the asset.
80
[9577c79]81**Keys:** candidates `{id}`, `{crypto_id, quote_currency}` — that pair is
82unique by definition, since a given asset can only be quoted once per
83currency; primary key **`id`**, so that the many entity sets referencing a
84market carry one narrow column instead of a composite key.
85
86| Attribute | Type | Constraints |
87|---|---|---|
88| `id` | UUID | PK, required |
89| `quote_currency` | text(3) | required, default `USD` |
90| `is_active` | boolean | required, default true — inactive markets are hidden from the trading menus but keep their history |
91| `created_at` | timestamptz | required, defaults to now |
[d8ce4e2]92
93#### Orders
94A user's instruction to buy or sell on a market. Needed as a separate entity set
95because an order is a record of *intent* that outlives its execution: it keeps
96the requested quantity and price even after it has been filled, which is what
97makes the ledger auditable.
98
[ef1c1c7]99Placing an order is what triggers a **reservation** of whatever it commits:
100the crypto being sold (`Holds.reserved_quantity`, below) on a sell, and the
101cash (`Users.reserved_balance`) on a buy. Since v04 (after P7) an order can
102wait in the order book and be filled in parts, so `status` is a real
103lifecycle driven by `filled_quantity`: `open` (nothing filled yet),
104`partially_filled`, `executed` (completely filled), or `cancelled`, which
105releases what is still reserved. See
[9577c79]106[UseCase0005](../P3-UseCaseModel/UseCase0005.md) for the reserve-then-settle
[ef1c1c7]107sequence and
108[AdvancedDatabaseDevelopment](../P7-AdvancedDatabaseDevelopment/AdvancedDatabaseDevelopment.md)
109for the rules that keep it consistent.
[9577c79]110
111**Keys:** candidate `{id}` only — there is no natural key, since the same user
112can place two identical orders on the same market in the same second, and both
113are legitimately distinct; primary key **`id`**.
114
115| Attribute | Type | Constraints |
116|---|---|---|
117| `id` | UUID | PK, required |
118| `side` | text | required, `buy` or `sell` |
[ef1c1c7]119| `type` | text | required, `market` or `limit` (both executed since P7) |
120| `status` | text | required, `open`, `partially_filled`, `executed` or `cancelled` |
[9577c79]121| `quantity` | numeric(20,4) | required, > 0 |
[ef1c1c7]122| `filled_quantity` | numeric(20,4) | required, default 0, between 0 and `quantity` — how much has been traded; remaining = `quantity − filled_quantity` (added in v04, after P7) |
123| `price` | numeric(18,6) | the limit price; for a market order, the market price when it was placed |
[9577c79]124| `placed_at` | timestamptz | required, defaults to now |
125| `executed_at` | timestamptz | optional, set when the order settles |
[d8ce4e2]126
127#### Transactions
128The financial ledger: every movement of virtual cash, in one place. This exists
129so that a balance is never just a number someone edited — it is the sum of an
130auditable list of entries, which is also what the "explain every step" goal of
131the project needs.
132
[9577c79]133**Keys:** candidate `{id}` only; primary key **`id`**.
134
135| Attribute | Type | Constraints |
136|---|---|---|
137| `id` | UUID | PK, required |
138| `type` | text | required, `deposit`, `buy`, `sell` or `fee` |
139| `amount` | numeric(18,4) | required, signed — negative for money leaving the cash balance, positive for money arriving |
140| `currency` | text(3) | required, default `USD` |
141| `created_at` | timestamptz | required, defaults to now |
142| `description` | text | optional, free-form |
[d8ce4e2]143
144#### MarketTrades
145Individual executed trades on a market, from the user's own fills and from the
146market simulator. This is the single source of truth for the current price: the
147price of a market is the price of its most recent trade, never a column someone
148writes directly.
149
[9577c79]150**Keys:** candidate `{id}` — `{market_id, executed_at}` looks unique in
151principle, but two trades can share a timestamp, so it is not a safe key;
152primary key **`id`** (a plain auto-incrementing integer here rather than a
153UUID, because this is the highest-volume entity set and it is only ever read
154in timestamp order, never referenced by anything else).
155
156| Attribute | Type | Constraints |
157|---|---|---|
158| `id` | integer | PK, required, auto-generated |
159| `executed_at` | timestamptz | required |
160| `price` | numeric(18,6) | required, > 0 |
161| `quantity` | numeric(20,6) | required, > 0 |
162| `side` | text | optional, `buy` or `sell` |
163| `source` | text(50) | required, default `simulation` — distinguishes a simulated trade from a user's own fill (`user`) |
[d8ce4e2]164
[ef1c1c7]165Since v04 (after P7) a trade also records which orders it filled, through the
166relationships `FillsBuy` and `FillsSell` below.
167
168#### OrderEvents
169*Added in v04, after P7.* The audit trail of an order: one event for its
170placement, one for every (partial) fill, and one for a cancellation. The
171`Orders` row only holds the current state; this entity keeps the history of
172how the order got there. Events are recorded automatically by the database.
173
174**Keys:** candidate `{id}` only; primary key **`id`** (auto-incrementing
175integer, events are only read in order).
176
177| Attribute | Type | Constraints |
178|---|---|---|
179| `id` | integer | PK, required, auto-generated |
180| `event_type` | text | required, `placed`, `partially_filled`, `filled` or `cancelled` |
181| `quantity` | numeric(20,4) | required — the ordered quantity for `placed`, the filled amount for a fill, the unfilled rest for `cancelled` |
182| `price` | numeric(18,6) | optional — the order price, or the trade price for a fill |
183| `status_after` | text | required, the order's status after the event |
184| `created_at` | timestamptz | required, defaults to now |
185
[d8ce4e2]186#### MarketCandles
187OHLCV aggregates per market and timeframe — the data a price chart is drawn
188from. Stored rather than computed on the fly because the point of the project is
189a chart-driven interface, and re-aggregating the whole trade history for every
190screen refresh does not scale.
191
[9577c79]192**Keys:** candidates `{id}`, `{market_id, timeframe, candle_time}` — a market
193has exactly one candle per timeframe per time bucket; primary key **`id`**, the
194composite is enforced as a uniqueness rule because it is the real-world
195constraint and it is what prevents duplicate candles.
196
197| Attribute | Type | Constraints |
198|---|---|---|
199| `id` | integer | PK, required, auto-generated |
200| `timeframe` | text | required, `1m`, `5m`, `1h` or `1d` |
201| `open`, `high`, `low`, `close` | numeric(18,6) | all required |
202| `volume` | numeric(20,6) | required |
203| `candle_time` | timestamptz | required — the start of the bucket |
[d8ce4e2]204
205#### Watchlists
206A named list of assets a user wants to monitor. A separate entity set rather than
207a flag on the relationship between users and assets, because a user may want
208several lists ("long term", "watching today") and each needs its own name.
209
[9577c79]210**Keys:** candidate `{id}` — `{user_id, name}` would also work if list names
211were required to be unique per user, which the model does not impose, so it is
212not listed as a candidate key; primary key **`id`**.
213
214| Attribute | Type | Constraints |
215|---|---|---|
216| `id` | UUID | PK, required |
217| `name` | text(100) | required |
218| `created_at` | timestamptz | required, defaults to now |
[d8ce4e2]219
220### Relationships
221
222#### QuotedOn — Cryptos (1) : Markets (N), total on Markets
223Ties a market to the asset it trades. One asset can be quoted in many markets;
224every market must have exactly one asset, hence total participation on the
[9577c79]225`Markets` side. No attributes.
[d8ce4e2]226
227#### PlacedOn — Markets (1) : Orders (N), total on Orders
228Records which market an order was placed on. Every order must name a market;
229a market may have no orders yet. No attributes.
230
231#### Places — Users (1) : Orders (N), total on Orders
[9577c79]232Records who placed an order. Every order belongs to exactly one user; a new
233user has no orders. No attributes.
[d8ce4e2]234
235#### Records — Users (1) : Transactions (N), total on Transactions
[9577c79]236Attributes each ledger entry to a user. Every entry belongs to exactly one
237user. No attributes.
[d8ce4e2]238
239#### Settles — Orders (1) : Transactions (N), partial on both sides
[9577c79]240Links a ledger entry to the order that caused it. Partial on the
241`Transactions` side because deposits have no originating order, and partial on
242the `Orders` side because an order that never executes never produces a
243ledger entry — which is why the corresponding column is nullable in P2. No
244attributes.
[d8ce4e2]245
246#### Fills — Markets (1) : MarketTrades (N), total on MarketTrades
247Every executed trade happened on exactly one market. No attributes.
248
[ef1c1c7]249#### FillsBuy — Orders (1) : MarketTrades (N), partial on both sides
250*Added in v04, after P7.* The buy order a trade filled. An order can be
251filled by many trades (partial fills); a trade fills at most one buy order,
252and none when the simulated market was the buyer. No attributes.
253
254#### FillsSell — Orders (1) : MarketTrades (N), partial on both sides
255*Added in v04, after P7.* The sell order a trade filled, symmetric to
256`FillsBuy`. A trade between two users' orders participates in both. No
257attributes.
258
259#### Logs — Orders (1) : OrderEvents (N), total on OrderEvents
260*Added in v04, after P7.* Every event belongs to exactly one order. No
261attributes.
262
[d8ce4e2]263#### Aggregates — Markets (1) : MarketCandles (N), total on MarketCandles
264Every candle summarises trades of exactly one market. No attributes.
265
266#### Owns — Users (1) : Watchlists (N), total on Watchlists
267Every watchlist belongs to exactly one user. No attributes.
268
269#### Holds — Users (M) : Cryptos (N), partial on both sides, **with attributes**
270A user's position in an asset. M:N because one user holds many assets and one
271asset is held by many users, and partial on both sides because a user may hold
272nothing and an asset may be held by nobody. Modeled as a relationship rather
273than an entity set because a position has no identity of its own — it is
274entirely described by *which user*, *which asset*, and how much.
275
[9577c79]276`reserved_quantity` mirrors `available_balance`/`invested_balance` on `Users`:
277two independently updated stored numbers, with the amount actually free to use
278computed on demand rather than stored (`quantity − reserved_quantity` here,
279`available_balance` alone on the cash side). Without it, nothing stopped a
280user from placing a second sell order against crypto already promised to a
281first one — `quantity` alone cannot tell "owned" apart from "owned, but
282already committed elsewhere." See [history](#entity-relationship-model-history), v03.
283
284| Attribute | Type | Constraints |
285|---|---|---|
286| `quantity` | numeric(20,4) | required, ≥ 0 — total amount owned |
287| `reserved_quantity` | numeric(20,4) | required, default 0, `0 ≤ reserved_quantity ≤ quantity` — committed to the user's own open sell orders, not yet removed from the position |
288| `avg_price` | numeric(18,6) | required, ≥ 0, **derived** (dashed ellipse) — the weighted average of the prices at which the position was accumulated; derivable from the buy history, stored anyway so unrealised P/L can be shown without replaying the whole ledger |
289| `created_at` | timestamptz | required, defaults to now |
290| `updated_at` | timestamptz | optional |
[d8ce4e2]291
292#### Contains — Watchlists (M) : Cryptos (N), partial on both sides, **with attribute**
293Which assets are on which watchlist. M:N: a list holds many assets, an asset
294appears on many lists. Partial on both sides — an empty list is valid and an
295asset need not be on any list.
296
[9577c79]297| Attribute | Type | Constraints |
298|---|---|---|
299| `added_at` | timestamptz | required, defaults to now — recorded so a list can be shown in the order the user built it |
[d8ce4e2]300
301## Entity-Relationship Model History
302
303- **v01** — First complete version. Built from the entity notes in
304 [`ep-diagram.md`](ep-diagram.md) (the initial hand-written model), with three
305 changes made to that initial model while drawing it:
306 1. `Markets` was promoted from an implied attribute of the asset to its own
307 entity set, so that prices, orders, trades and candles can all reference a
308 pair rather than an asset.
309 2. `holdings` and `watchlist_items` were re-expressed as the M:N relationships
310 `Holds` and `Contains` with their own attributes, instead of entity sets
311 with foreign keys — the initial notes listed them as tables, which is a
312 relational concept that does not belong in a Chen ERD.
313 3. `avg_price` was marked as a derived attribute rather than a plain one, to
314 make the denormalisation explicit rather than hidden.
[9577c79]315- **v02** — Student review pass over the AI-generated v01 in the TerraER GUI.
316- **v03** — Added `reserved_quantity` to `Holds`, and reworded `Orders.status`
317 to state its reserve → settle → (cancel) lifecycle explicitly, instead of
318 leaving `open`/`cancelled` as unused enum values. Triggered by a design
319 review that pointed out the model had no way to stop a user from placing a
320 second sell order against crypto already promised to a first, unsettled one
321 — `quantity` alone cannot distinguish "owned" from "owned, but already
322 committed." Also redrawn more compactly: every entity and relationship (with
323 its own attributes moved along with it) was pulled proportionally toward the
324 diagram's centroid, shrinking the canvas by roughly 45% with the same
325 topology and no new overlaps. See [ERModelAIUsage](ERModelAIUsage.md) for
326 the reasoning and how the diagram file itself was produced, and
327 [RelationalDesign](../P2-RelationalDesign/RelationalDesign.md) and
328 [UseCase0005](../P3-UseCaseModel/UseCase0005.md) for how the new attribute
329 is enforced.
[ef1c1c7]330- **v04 — after P7.** Phase 7 (order, balance and trade consistency) needed
331 data the model did not have, so the model was extended to stay in line with
332 the database:
333 - `Users.reserved_balance`: cash reserved by open buy orders;
334 - `Orders.filled_quantity` and the status value `partially_filled`: orders
335 can now be filled in parts;
336 - the relationships `FillsBuy` and `FillsSell` between `Orders` and
337 `MarketTrades`: which orders a trade filled;
338 - the entity set `OrderEvents` with the relationship `Logs`: the
339 automatically recorded history of every order.
340
341 Nothing existing was removed or changed. See
342 [AdvancedDatabaseDevelopment](../P7-AdvancedDatabaseDevelopment/AdvancedDatabaseDevelopment.md).
343 The diagram files are `ERModel_v04.xml` / `ERModel_v04.png`; earlier versions
344 are kept.
[d8ce4e2]345
346Reasoning for the AI-assisted part of this phase, and the full interaction log,
347are on [ERModelAIUsage](ERModelAIUsage.md).
[9577c79]348
Note: See TracBrowser for help on using the repository browser.