Changes between Initial Version and Version 1 of ErModel


Ignore:
Timestamp:
08/07/26 10:56:04 (9 days ago)
Author:
231285
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • ErModel

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