| 1 | # Entity-Relationship Model v.01
|
|---|
| 2 |
|
|---|
| 3 | ## Diagram
|
|---|
| 4 |
|
|---|
| 5 | 
|
|---|
| 6 |
|
|---|
| 7 | Attachments for this page: `ERModel_v01.xml` (TerraER source) and `ERModel_v01.png`
|
|---|
| 8 | (exported image). Open the source with TerraER 3.11:
|
|---|
| 9 |
|
|---|
| 10 | ```sh
|
|---|
| 11 | java -jar TerraER3.11.jar # then File → Open → ERModel_v01.xml
|
|---|
| 12 | ```
|
|---|
| 13 |
|
|---|
| 14 | Notation: Chen. Rectangles are entity sets, diamonds are relationships, ellipses
|
|---|
| 15 | are attributes, underlined ellipses are primary keys, the dashed ellipse is a
|
|---|
| 16 | derived attribute. A double line between an entity set and a relationship marks
|
|---|
| 17 | **total participation** (every instance of that entity set must participate); a
|
|---|
| 18 | single line marks partial participation.
|
|---|
| 19 |
|
|---|
| 20 | Two deliberate modeling decisions worth stating up front:
|
|---|
| 21 |
|
|---|
| 22 | - **No foreign keys appear in the diagram.** Connections between entity sets are
|
|---|
| 23 | expressed as relationships, per the notation. Foreign-key columns appear only
|
|---|
| 24 | in the relational model in [RelationalDesign](../P2-RelationalDesign/RelationalDesign.md).
|
|---|
| 25 | - **`Holds` and `Contains` are relationships, not entity sets.** Both are M:N and
|
|---|
| 26 | both carry their own attributes, which is exactly what a Chen relationship is
|
|---|
| 27 | for. They become tables (`holdings`, `watchlist_items`) only in P2.
|
|---|
| 28 |
|
|---|
| 29 | ## Data requirements
|
|---|
| 30 |
|
|---|
| 31 | ### Entity sets
|
|---|
| 32 |
|
|---|
| 33 | #### Users
|
|---|
| 34 | Registered participants of the platform. Every action in the simulation is
|
|---|
| 35 | attributed to a user, and the two balance attributes are what makes the
|
|---|
| 36 | simulation work: cash that is free to trade is tracked separately from cash that
|
|---|
| 37 | is currently committed to open positions, so the platform can refuse a purchase
|
|---|
| 38 | without having to recompute the whole portfolio first.
|
|---|
| 39 |
|
|---|
| 40 | - **Candidate keys:** `{id}`, `{username}`, `{email}`. Primary key: **`id`**.
|
|---|
| 41 | A surrogate UUID was chosen because it is opaque and stable — `username` and
|
|---|
| 42 | `email` are both things a user may legitimately want to change later, and
|
|---|
| 43 | every relationship in the diagram points at `Users`, so a mutable key would
|
|---|
| 44 | propagate changes across the whole database.
|
|---|
| 45 | - **Attributes:**
|
|---|
| 46 | - `id` — UUID, required, primary key.
|
|---|
| 47 | - `username` — text, max 50, required, unique.
|
|---|
| 48 | - `email` — text, max 255, required, unique, must contain `@`.
|
|---|
| 49 | - `full_name` — text, max 200, optional.
|
|---|
| 50 | - `password_hash` — text, max 255, required. Never the password itself; the
|
|---|
| 51 | prototype stores a SHA-256 hex digest.
|
|---|
| 52 | - `available_balance` — numeric(18,4), required, default 0, must be ≥ 0.
|
|---|
| 53 | - `invested_balance` — numeric(18,4), required, default 0, must be ≥ 0.
|
|---|
| 54 | - `created_at` — timestamp with time zone, required, defaults to now.
|
|---|
| 55 | - `updated_at` — timestamp with time zone, optional (null until first change).
|
|---|
| 56 |
|
|---|
| 57 | #### Cryptos
|
|---|
| 58 | The 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 —
|
|---|
| 60 | the same asset can be quoted against several currencies, and a user's holding is
|
|---|
| 61 | in the *asset*, not in a particular pair.
|
|---|
| 62 |
|
|---|
| 63 | - **Candidate keys:** `{id}`, `{symbol}`. Primary key: **`id`**, for the same
|
|---|
| 64 | reason as in `Users`; `symbol` is kept as a unique natural key because that is
|
|---|
| 65 | what users type and see.
|
|---|
| 66 | - **Attributes:**
|
|---|
| 67 | - `id` — UUID, required, primary key.
|
|---|
| 68 | - `symbol` — text, max 20, required, unique (e.g. `BTC`).
|
|---|
| 69 | - `name` — text, max 255, required (e.g. `Bitcoin`).
|
|---|
| 70 | - `created_at` — timestamptz, required, defaults to now.
|
|---|
| 71 |
|
|---|
| 72 | #### Markets
|
|---|
| 73 | A tradeable pair: one crypto asset quoted in one currency, e.g. BTC/USD. This is
|
|---|
| 74 | where prices live, and it is the thing an order is placed *on*. Modeled as its
|
|---|
| 75 | own entity set rather than an attribute of `Cryptos` because a market has its own
|
|---|
| 76 | lifecycle — it can be deactivated without deleting the asset — and because
|
|---|
| 77 | trades, candles and orders all reference the pair, not the asset.
|
|---|
| 78 |
|
|---|
| 79 | - **Candidate keys:** `{id}`, `{crypto_id, quote_currency}` — that pair is
|
|---|
| 80 | unique by definition, since a given asset can only be quoted once per
|
|---|
| 81 | currency. Primary key: **`id`**, so that the many entity sets referencing a
|
|---|
| 82 | market carry one narrow column instead of a composite key.
|
|---|
| 83 | - **Attributes:**
|
|---|
| 84 | - `id` — UUID, required, primary key.
|
|---|
| 85 | - `quote_currency` — text, exactly 3 characters, required, default `USD`.
|
|---|
| 86 | - `is_active` — boolean, required, default true. Inactive markets are hidden
|
|---|
| 87 | from the trading menus but keep their history.
|
|---|
| 88 | - `created_at` — timestamptz, required, defaults to now.
|
|---|
| 89 |
|
|---|
| 90 | #### Orders
|
|---|
| 91 | A user's instruction to buy or sell on a market. Needed as a separate entity set
|
|---|
| 92 | because an order is a record of *intent* that outlives its execution: it keeps
|
|---|
| 93 | the requested quantity and price even after it has been filled, which is what
|
|---|
| 94 | makes the ledger auditable.
|
|---|
| 95 |
|
|---|
| 96 | - **Candidate keys:** `{id}` only. There is no natural key — the same user can
|
|---|
| 97 | place two identical orders on the same market in the same second, and both are
|
|---|
| 98 | legitimately distinct. Primary key: **`id`**.
|
|---|
| 99 | - **Attributes:**
|
|---|
| 100 | - `id` — UUID, required, primary key.
|
|---|
| 101 | - `side` — text, required, restricted to `buy` or `sell`.
|
|---|
| 102 | - `type` — text, required, restricted to `market` or `limit`. The prototype
|
|---|
| 103 | executes only `market` orders; `limit` exists so the model does not have to
|
|---|
| 104 | change when limit orders are implemented.
|
|---|
| 105 | - `status` — text, required, restricted to `open`, `executed`, `cancelled`.
|
|---|
| 106 | - `quantity` — numeric(20,4), required, must be > 0.
|
|---|
| 107 | - `price` — numeric(18,6), optional — null for a market order until it fills,
|
|---|
| 108 | then the fill price.
|
|---|
| 109 | - `placed_at` — timestamptz, required, defaults to now.
|
|---|
| 110 | - `executed_at` — timestamptz, optional, set when the order fills.
|
|---|
| 111 |
|
|---|
| 112 | #### Transactions
|
|---|
| 113 | The financial ledger: every movement of virtual cash, in one place. This exists
|
|---|
| 114 | so that a balance is never just a number someone edited — it is the sum of an
|
|---|
| 115 | auditable list of entries, which is also what the "explain every step" goal of
|
|---|
| 116 | the project needs.
|
|---|
| 117 |
|
|---|
| 118 | - **Candidate keys:** `{id}` only. Primary key: **`id`**.
|
|---|
| 119 | - **Attributes:**
|
|---|
| 120 | - `id` — UUID, required, primary key.
|
|---|
| 121 | - `type` — text, required, restricted to `deposit`, `buy`, `sell`, `fee`.
|
|---|
| 122 | - `amount` — numeric(18,4), required. Signed: negative for money leaving the
|
|---|
| 123 | cash balance, positive for money arriving.
|
|---|
| 124 | - `currency` — text, exactly 3 characters, required, default `USD`.
|
|---|
| 125 | - `created_at` — timestamptz, required, defaults to now.
|
|---|
| 126 | - `description` — text, optional, free-form human-readable explanation.
|
|---|
| 127 |
|
|---|
| 128 | #### MarketTrades
|
|---|
| 129 | Individual executed trades on a market, from the user's own fills and from the
|
|---|
| 130 | market simulator. This is the single source of truth for the current price: the
|
|---|
| 131 | price of a market is the price of its most recent trade, never a column someone
|
|---|
| 132 | writes directly.
|
|---|
| 133 |
|
|---|
| 134 | - **Candidate keys:** `{id}`. In principle `{market_id, executed_at}` looks
|
|---|
| 135 | unique, but two trades can share a timestamp, so it is not a safe key.
|
|---|
| 136 | Primary key: **`id`** (a plain auto-incrementing integer here rather than a
|
|---|
| 137 | UUID, because this is the highest-volume entity set and it is only ever read
|
|---|
| 138 | in timestamp order, never referenced by anything else).
|
|---|
| 139 | - **Attributes:**
|
|---|
| 140 | - `id` — integer, required, primary key, auto-generated.
|
|---|
| 141 | - `executed_at` — timestamptz, required.
|
|---|
| 142 | - `price` — numeric(18,6), required, must be > 0.
|
|---|
| 143 | - `quantity` — numeric(20,6), required, must be > 0.
|
|---|
| 144 | - `side` — text, optional, `buy` or `sell`.
|
|---|
| 145 | - `source` — text, max 50, required, default `simulation`. Distinguishes a
|
|---|
| 146 | simulated trade from a user's own fill (`user`).
|
|---|
| 147 |
|
|---|
| 148 | #### MarketCandles
|
|---|
| 149 | OHLCV aggregates per market and timeframe — the data a price chart is drawn
|
|---|
| 150 | from. Stored rather than computed on the fly because the point of the project is
|
|---|
| 151 | a chart-driven interface, and re-aggregating the whole trade history for every
|
|---|
| 152 | screen refresh does not scale.
|
|---|
| 153 |
|
|---|
| 154 | - **Candidate keys:** `{id}`, and `{market_id, timeframe, candle_time}` — a
|
|---|
| 155 | market has exactly one candle per timeframe per time bucket. Primary key:
|
|---|
| 156 | **`id`**; the composite is enforced as a uniqueness rule because it is the
|
|---|
| 157 | real-world constraint and it is what prevents duplicate candles.
|
|---|
| 158 | - **Attributes:**
|
|---|
| 159 | - `id` — integer, required, primary key, auto-generated.
|
|---|
| 160 | - `timeframe` — text, required, restricted to `1m`, `5m`, `1h`, `1d`.
|
|---|
| 161 | - `open`, `high`, `low`, `close` — numeric(18,6), all required.
|
|---|
| 162 | - `volume` — numeric(20,6), required.
|
|---|
| 163 | - `candle_time` — timestamptz, required — the start of the bucket.
|
|---|
| 164 |
|
|---|
| 165 | #### Watchlists
|
|---|
| 166 | A named list of assets a user wants to monitor. A separate entity set rather than
|
|---|
| 167 | a flag on the relationship between users and assets, because a user may want
|
|---|
| 168 | several lists ("long term", "watching today") and each needs its own name.
|
|---|
| 169 |
|
|---|
| 170 | - **Candidate keys:** `{id}`. `{user_id, name}` would also work if list names
|
|---|
| 171 | are required to be unique per user; the model does not impose that, so it is
|
|---|
| 172 | not listed as a candidate key. Primary key: **`id`**.
|
|---|
| 173 | - **Attributes:**
|
|---|
| 174 | - `id` — UUID, required, primary key.
|
|---|
| 175 | - `name` — text, max 100, required.
|
|---|
| 176 | - `created_at` — timestamptz, required, defaults to now.
|
|---|
| 177 |
|
|---|
| 178 | ### Relationships
|
|---|
| 179 |
|
|---|
| 180 | #### QuotedOn — Cryptos (1) : Markets (N), total on Markets
|
|---|
| 181 | Ties a market to the asset it trades. One asset can be quoted in many markets;
|
|---|
| 182 | every market must have exactly one asset, hence total participation on the
|
|---|
| 183 | `Markets` side. No attributes of its own.
|
|---|
| 184 |
|
|---|
| 185 | #### PlacedOn — Markets (1) : Orders (N), total on Orders
|
|---|
| 186 | Records which market an order was placed on. Every order must name a market;
|
|---|
| 187 | a market may have no orders yet. No attributes.
|
|---|
| 188 |
|
|---|
| 189 | #### Places — Users (1) : Orders (N), total on Orders
|
|---|
| 190 | Records who placed an order. Every order belongs to exactly one user; a new user
|
|---|
| 191 | has no orders. No attributes.
|
|---|
| 192 |
|
|---|
| 193 | #### Records — Users (1) : Transactions (N), total on Transactions
|
|---|
| 194 | Attributes each ledger entry to a user. Every entry belongs to exactly one user.
|
|---|
| 195 | No attributes.
|
|---|
| 196 |
|
|---|
| 197 | #### Settles — Orders (1) : Transactions (N), partial on both sides
|
|---|
| 198 | Links a ledger entry to the order that caused it. Partial on the `Transactions`
|
|---|
| 199 | side because deposits have no originating order, and partial on the `Orders` side
|
|---|
| 200 | because an order that never executes never produces a ledger entry. This is why
|
|---|
| 201 | the corresponding column is nullable in P2. No attributes.
|
|---|
| 202 |
|
|---|
| 203 | #### Fills — Markets (1) : MarketTrades (N), total on MarketTrades
|
|---|
| 204 | Every executed trade happened on exactly one market. No attributes.
|
|---|
| 205 |
|
|---|
| 206 | #### Aggregates — Markets (1) : MarketCandles (N), total on MarketCandles
|
|---|
| 207 | Every candle summarises trades of exactly one market. No attributes.
|
|---|
| 208 |
|
|---|
| 209 | #### Owns — Users (1) : Watchlists (N), total on Watchlists
|
|---|
| 210 | Every watchlist belongs to exactly one user. No attributes.
|
|---|
| 211 |
|
|---|
| 212 | #### Holds — Users (M) : Cryptos (N), partial on both sides, **with attributes**
|
|---|
| 213 | A user's position in an asset. M:N because one user holds many assets and one
|
|---|
| 214 | asset is held by many users, and partial on both sides because a user may hold
|
|---|
| 215 | nothing and an asset may be held by nobody. Modeled as a relationship rather
|
|---|
| 216 | than an entity set because a position has no identity of its own — it is
|
|---|
| 217 | entirely described by *which user*, *which asset*, and how much.
|
|---|
| 218 |
|
|---|
| 219 | - **Attributes:**
|
|---|
| 220 | - `quantity` — numeric(20,4), required, must be ≥ 0.
|
|---|
| 221 | - `avg_price` — numeric(18,6), required, ≥ 0, **derived** (dashed ellipse):
|
|---|
| 222 | the weighted average of the prices at which the position was accumulated.
|
|---|
| 223 | It is derivable from the buy history, and is stored anyway so that
|
|---|
| 224 | unrealised P/L can be shown without replaying the whole ledger.
|
|---|
| 225 | - `created_at` — timestamptz, required, defaults to now.
|
|---|
| 226 | - `updated_at` — timestamptz, optional.
|
|---|
| 227 |
|
|---|
| 228 | #### Contains — Watchlists (M) : Cryptos (N), partial on both sides, **with attribute**
|
|---|
| 229 | Which assets are on which watchlist. M:N: a list holds many assets, an asset
|
|---|
| 230 | appears on many lists. Partial on both sides — an empty list is valid and an
|
|---|
| 231 | asset need not be on any list.
|
|---|
| 232 |
|
|---|
| 233 | - **Attributes:**
|
|---|
| 234 | - `added_at` — timestamptz, required, defaults to now. Recorded so a list can
|
|---|
| 235 | be shown in the order the user built it.
|
|---|
| 236 |
|
|---|
| 237 | ## Entity-Relationship Model History
|
|---|
| 238 |
|
|---|
| 239 | - **v01** — First complete version. Built from the entity notes in
|
|---|
| 240 | [`ep-diagram.md`](ep-diagram.md) (the initial hand-written model), with three
|
|---|
| 241 | changes made to that initial model while drawing it:
|
|---|
| 242 | 1. `Markets` was promoted from an implied attribute of the asset to its own
|
|---|
| 243 | entity set, so that prices, orders, trades and candles can all reference a
|
|---|
| 244 | pair rather than an asset.
|
|---|
| 245 | 2. `holdings` and `watchlist_items` were re-expressed as the M:N relationships
|
|---|
| 246 | `Holds` and `Contains` with their own attributes, instead of entity sets
|
|---|
| 247 | with foreign keys — the initial notes listed them as tables, which is a
|
|---|
| 248 | relational concept that does not belong in a Chen ERD.
|
|---|
| 249 | 3. `avg_price` was marked as a derived attribute rather than a plain one, to
|
|---|
| 250 | make the denormalisation explicit rather than hidden.
|
|---|
| 251 |
|
|---|
| 252 | Reasoning for the AI-assisted part of this phase, and the full interaction log,
|
|---|
| 253 | are on [ERModelAIUsage](ERModelAIUsage.md).
|
|---|