source: docs/P1-ConceptualModel/ERModel.md@ 9577c79

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

add reserved_quantity and modify the phases, add v_03.png and v_03.xml for P1

  • Property mode set to 100644
File size: 15.2 KB
RevLine 
[9577c79]1# Entity-Relationship Model v.03
[d8ce4e2]2
3## Diagram
4
[9577c79]5![ERModel_v03](ERModel_v03.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 |
53| `created_at` | timestamptz | required, defaults to now |
54| `updated_at` | timestamptz | optional (null until first change) |
[d8ce4e2]55
56#### Cryptos
57The catalog of crypto assets the platform knows about. Kept separate from
58`Markets` because an asset exists independently of the pairs it is traded in —
59the same asset can be quoted against several currencies, and a user's holding is
60in the *asset*, not in a particular pair.
61
[9577c79]62**Keys:** candidates `{id}`, `{symbol}`; primary key **`id`**, for the same
63reason as in `Users`. `symbol` is kept as a unique natural key because that is
64what users type and see.
65
66| Attribute | Type | Constraints |
67|---|---|---|
68| `id` | UUID | PK, required |
69| `symbol` | text(20) | required, unique (e.g. `BTC`) |
70| `name` | text(255) | required (e.g. `Bitcoin`) |
71| `created_at` | timestamptz | required, defaults to now |
[d8ce4e2]72
73#### Markets
74A tradeable pair: one crypto asset quoted in one currency, e.g. BTC/USD. This is
75where prices live, and it is the thing an order is placed *on*. Modeled as its
76own entity set rather than an attribute of `Cryptos` because a market has its own
77lifecycle — it can be deactivated without deleting the asset — and because
78trades, candles and orders all reference the pair, not the asset.
79
[9577c79]80**Keys:** candidates `{id}`, `{crypto_id, quote_currency}` — that pair is
81unique by definition, since a given asset can only be quoted once per
82currency; primary key **`id`**, so that the many entity sets referencing a
83market carry one narrow column instead of a composite key.
84
85| Attribute | Type | Constraints |
86|---|---|---|
87| `id` | UUID | PK, required |
88| `quote_currency` | text(3) | required, default `USD` |
89| `is_active` | boolean | required, default true — inactive markets are hidden from the trading menus but keep their history |
90| `created_at` | timestamptz | required, defaults to now |
[d8ce4e2]91
92#### Orders
93A user's instruction to buy or sell on a market. Needed as a separate entity set
94because an order is a record of *intent* that outlives its execution: it keeps
95the requested quantity and price even after it has been filled, which is what
96makes the ledger auditable.
97
[9577c79]98Placing an order is what triggers a **reservation** of whatever it commits —
99the crypto being sold (`Holds.reserved_quantity`, below) on a sell, cash
100already handled the same way on a buy via `available_balance` /
101`invested_balance`. `status` therefore has real meaning as a lifecycle, not
102just a label: `open` means reserved but not yet settled, `executed` means
103settled, `cancelled` would release the reservation without settling (not yet
104exercised by any use case, since only market orders — which settle
105immediately — are implemented). See
106[UseCase0005](../P3-UseCaseModel/UseCase0005.md) for the reserve-then-settle
107sequence.
108
109**Keys:** candidate `{id}` only — there is no natural key, since the same user
110can place two identical orders on the same market in the same second, and both
111are legitimately distinct; primary key **`id`**.
112
113| Attribute | Type | Constraints |
114|---|---|---|
115| `id` | UUID | PK, required |
116| `side` | text | required, `buy` or `sell` |
117| `type` | text | required, `market` or `limit` — the prototype executes only `market`; `limit` exists so the model does not have to change when limit orders are implemented |
118| `status` | text | required, `open`, `executed` or `cancelled` |
119| `quantity` | numeric(20,4) | required, > 0 |
120| `price` | numeric(18,6) | optional — null until the order settles, then the fill price |
121| `placed_at` | timestamptz | required, defaults to now |
122| `executed_at` | timestamptz | optional, set when the order settles |
[d8ce4e2]123
124#### Transactions
125The financial ledger: every movement of virtual cash, in one place. This exists
126so that a balance is never just a number someone edited — it is the sum of an
127auditable list of entries, which is also what the "explain every step" goal of
128the project needs.
129
[9577c79]130**Keys:** candidate `{id}` only; primary key **`id`**.
131
132| Attribute | Type | Constraints |
133|---|---|---|
134| `id` | UUID | PK, required |
135| `type` | text | required, `deposit`, `buy`, `sell` or `fee` |
136| `amount` | numeric(18,4) | required, signed — negative for money leaving the cash balance, positive for money arriving |
137| `currency` | text(3) | required, default `USD` |
138| `created_at` | timestamptz | required, defaults to now |
139| `description` | text | optional, free-form |
[d8ce4e2]140
141#### MarketTrades
142Individual executed trades on a market, from the user's own fills and from the
143market simulator. This is the single source of truth for the current price: the
144price of a market is the price of its most recent trade, never a column someone
145writes directly.
146
[9577c79]147**Keys:** candidate `{id}` — `{market_id, executed_at}` looks unique in
148principle, but two trades can share a timestamp, so it is not a safe key;
149primary key **`id`** (a plain auto-incrementing integer here rather than a
150UUID, because this is the highest-volume entity set and it is only ever read
151in timestamp order, never referenced by anything else).
152
153| Attribute | Type | Constraints |
154|---|---|---|
155| `id` | integer | PK, required, auto-generated |
156| `executed_at` | timestamptz | required |
157| `price` | numeric(18,6) | required, > 0 |
158| `quantity` | numeric(20,6) | required, > 0 |
159| `side` | text | optional, `buy` or `sell` |
160| `source` | text(50) | required, default `simulation` — distinguishes a simulated trade from a user's own fill (`user`) |
[d8ce4e2]161
162#### MarketCandles
163OHLCV aggregates per market and timeframe — the data a price chart is drawn
164from. Stored rather than computed on the fly because the point of the project is
165a chart-driven interface, and re-aggregating the whole trade history for every
166screen refresh does not scale.
167
[9577c79]168**Keys:** candidates `{id}`, `{market_id, timeframe, candle_time}` — a market
169has exactly one candle per timeframe per time bucket; primary key **`id`**, the
170composite is enforced as a uniqueness rule because it is the real-world
171constraint and it is what prevents duplicate candles.
172
173| Attribute | Type | Constraints |
174|---|---|---|
175| `id` | integer | PK, required, auto-generated |
176| `timeframe` | text | required, `1m`, `5m`, `1h` or `1d` |
177| `open`, `high`, `low`, `close` | numeric(18,6) | all required |
178| `volume` | numeric(20,6) | required |
179| `candle_time` | timestamptz | required — the start of the bucket |
[d8ce4e2]180
181#### Watchlists
182A named list of assets a user wants to monitor. A separate entity set rather than
183a flag on the relationship between users and assets, because a user may want
184several lists ("long term", "watching today") and each needs its own name.
185
[9577c79]186**Keys:** candidate `{id}` — `{user_id, name}` would also work if list names
187were required to be unique per user, which the model does not impose, so it is
188not listed as a candidate key; primary key **`id`**.
189
190| Attribute | Type | Constraints |
191|---|---|---|
192| `id` | UUID | PK, required |
193| `name` | text(100) | required |
194| `created_at` | timestamptz | required, defaults to now |
[d8ce4e2]195
196### Relationships
197
198#### QuotedOn — Cryptos (1) : Markets (N), total on Markets
199Ties a market to the asset it trades. One asset can be quoted in many markets;
200every market must have exactly one asset, hence total participation on the
[9577c79]201`Markets` side. No attributes.
[d8ce4e2]202
203#### PlacedOn — Markets (1) : Orders (N), total on Orders
204Records which market an order was placed on. Every order must name a market;
205a market may have no orders yet. No attributes.
206
207#### Places — Users (1) : Orders (N), total on Orders
[9577c79]208Records who placed an order. Every order belongs to exactly one user; a new
209user has no orders. No attributes.
[d8ce4e2]210
211#### Records — Users (1) : Transactions (N), total on Transactions
[9577c79]212Attributes each ledger entry to a user. Every entry belongs to exactly one
213user. No attributes.
[d8ce4e2]214
215#### Settles — Orders (1) : Transactions (N), partial on both sides
[9577c79]216Links a ledger entry to the order that caused it. Partial on the
217`Transactions` side because deposits have no originating order, and partial on
218the `Orders` side because an order that never executes never produces a
219ledger entry — which is why the corresponding column is nullable in P2. No
220attributes.
[d8ce4e2]221
222#### Fills — Markets (1) : MarketTrades (N), total on MarketTrades
223Every executed trade happened on exactly one market. No attributes.
224
225#### Aggregates — Markets (1) : MarketCandles (N), total on MarketCandles
226Every candle summarises trades of exactly one market. No attributes.
227
228#### Owns — Users (1) : Watchlists (N), total on Watchlists
229Every watchlist belongs to exactly one user. No attributes.
230
231#### Holds — Users (M) : Cryptos (N), partial on both sides, **with attributes**
232A user's position in an asset. M:N because one user holds many assets and one
233asset is held by many users, and partial on both sides because a user may hold
234nothing and an asset may be held by nobody. Modeled as a relationship rather
235than an entity set because a position has no identity of its own — it is
236entirely described by *which user*, *which asset*, and how much.
237
[9577c79]238`reserved_quantity` mirrors `available_balance`/`invested_balance` on `Users`:
239two independently updated stored numbers, with the amount actually free to use
240computed on demand rather than stored (`quantity − reserved_quantity` here,
241`available_balance` alone on the cash side). Without it, nothing stopped a
242user from placing a second sell order against crypto already promised to a
243first one — `quantity` alone cannot tell "owned" apart from "owned, but
244already committed elsewhere." See [history](#entity-relationship-model-history), v03.
245
246| Attribute | Type | Constraints |
247|---|---|---|
248| `quantity` | numeric(20,4) | required, ≥ 0 — total amount owned |
249| `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 |
250| `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 |
251| `created_at` | timestamptz | required, defaults to now |
252| `updated_at` | timestamptz | optional |
[d8ce4e2]253
254#### Contains — Watchlists (M) : Cryptos (N), partial on both sides, **with attribute**
255Which assets are on which watchlist. M:N: a list holds many assets, an asset
256appears on many lists. Partial on both sides — an empty list is valid and an
257asset need not be on any list.
258
[9577c79]259| Attribute | Type | Constraints |
260|---|---|---|
261| `added_at` | timestamptz | required, defaults to now — recorded so a list can be shown in the order the user built it |
[d8ce4e2]262
263## Entity-Relationship Model History
264
265- **v01** — First complete version. Built from the entity notes in
266 [`ep-diagram.md`](ep-diagram.md) (the initial hand-written model), with three
267 changes made to that initial model while drawing it:
268 1. `Markets` was promoted from an implied attribute of the asset to its own
269 entity set, so that prices, orders, trades and candles can all reference a
270 pair rather than an asset.
271 2. `holdings` and `watchlist_items` were re-expressed as the M:N relationships
272 `Holds` and `Contains` with their own attributes, instead of entity sets
273 with foreign keys — the initial notes listed them as tables, which is a
274 relational concept that does not belong in a Chen ERD.
275 3. `avg_price` was marked as a derived attribute rather than a plain one, to
276 make the denormalisation explicit rather than hidden.
[9577c79]277- **v02** — Student review pass over the AI-generated v01 in the TerraER GUI.
278- **v03** — Added `reserved_quantity` to `Holds`, and reworded `Orders.status`
279 to state its reserve → settle → (cancel) lifecycle explicitly, instead of
280 leaving `open`/`cancelled` as unused enum values. Triggered by a design
281 review that pointed out the model had no way to stop a user from placing a
282 second sell order against crypto already promised to a first, unsettled one
283 — `quantity` alone cannot distinguish "owned" from "owned, but already
284 committed." Also redrawn more compactly: every entity and relationship (with
285 its own attributes moved along with it) was pulled proportionally toward the
286 diagram's centroid, shrinking the canvas by roughly 45% with the same
287 topology and no new overlaps. See [ERModelAIUsage](ERModelAIUsage.md) for
288 the reasoning and how the diagram file itself was produced, and
289 [RelationalDesign](../P2-RelationalDesign/RelationalDesign.md) and
290 [UseCase0005](../P3-UseCaseModel/UseCase0005.md) for how the new attribute
291 is enforced.
[d8ce4e2]292
293Reasoning for the AI-assisted part of this phase, and the full interaction log,
294are on [ERModelAIUsage](ERModelAIUsage.md).
[9577c79]295
296> **Student action required.** Open `ERModel_v03.xml` in TerraER, read the
297> whole diagram — not just the new `reserved_quantity` ellipse — and change
298> anything you disagree with, including the compaction. The phase rules
299> require the model to be yours; this is a generated revision to review and
300> take over, not an answer to submit unread.
Note: See TracBrowser for help on using the repository browser.