| [ef1c1c7] | 1 | = Advanced Reports =
|
|---|
| 2 |
|
|---|
| 3 | This is a solo project (see [wiki:UseCaseModel]),
|
|---|
| 4 | so the rubric's "2 per team member" is 2 reports total. Both are implemented as
|
|---|
| 5 | single SQL statements, wrapped as callable SQL functions in
|
|---|
| 6 | `schema_creation.sql` (`report_top_traders`,
|
|---|
| 7 | `report_market_performance`) so they are actual reports inside the prototype — menu
|
|---|
| 8 | options `[10]` and `[11]` in `server/reports.go` — not just documentation. No change to
|
|---|
| 9 | [wiki:ERModel] or [wiki:RelationalDesign]
|
|---|
| 10 | was needed: both reports read `transactions`, `market_trades` and `orders`, all of which
|
|---|
| 11 | already carry everything required.
|
|---|
| 12 |
|
|---|
| 13 | === Notation used below ===
|
|---|
| 14 |
|
|---|
| 15 | Both solutions need grouping, aggregation and computed attributes that plain relational
|
|---|
| 16 | algebra has no notation for, so the relational-algebra sections use the standard ''extended''
|
|---|
| 17 | operators:
|
|---|
| 18 |
|
|---|
| 19 | ||= Symbol =||= Meaning =||
|
|---|
| 20 | || `σ_cond(R)` || selection ||
|
|---|
| 21 | || `π_list(R)` || projection — a list entry `expr → name` is a '''generalized projection''': a computed attribute, not just a column reference ||
|
|---|
| 22 | || `ρ_name(R)` || rename ||
|
|---|
| 23 | || `R ⋈_cond S` || inner join ||
|
|---|
| 24 | || `R ⟕_cond S` || left outer join (needed wherever a group can legitimately have zero matching rows on the other side, e.g. zero profitable periods, zero participating users) ||
|
|---|
| 25 | || `γ_{grouping; agg → name, …}(R)` || grouping/aggregation ||
|
|---|
| 26 | || `τ_attr(R)` || sort, for the presentation order only ||
|
|---|
| 27 |
|
|---|
| 28 | == Top traders by realized performance ==
|
|---|
| 29 |
|
|---|
| 30 | === Data requirements description ===
|
|---|
| 31 |
|
|---|
| 32 | ''"Which users actually made money, how much, how efficiently, and how consistently — over
|
|---|
| 33 | a quarter, a year, or several years?"'' This is the natural crypto-exchange analogue of "which
|
|---|
| 34 | customers bring the most profit" from the phase brief: a Trader's `available_balance` and
|
|---|
| 35 | `invested_balance` (P1 `Users`) show a live snapshot, but they say nothing about performance
|
|---|
| 36 | ''over a chosen window'', and nothing at all about whether a user's results are one lucky
|
|---|
| 37 | quarter or a repeatable pattern. All of it is derivable from
|
|---|
| 38 | `transactions` (defined in `schema_creation.sql`) as it already exists: every buy, sell
|
|---|
| 39 | and fee is one signed row there (see [wiki:UseCase0004] and
|
|---|
| 40 | [wiki:UseCase0005] for how each row is produced), so no new
|
|---|
| 41 | column or table is needed.
|
|---|
| 42 |
|
|---|
| 43 | The `transactions` table, from `schema_creation.sql`:
|
|---|
| 44 |
|
|---|
| 45 | {{{
|
|---|
| 46 | CREATE TABLE project.transactions (
|
|---|
| 47 | id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|---|
| 48 | user_id uuid NOT NULL REFERENCES project.users(id) ON DELETE CASCADE,
|
|---|
| 49 | type varchar(50) NOT NULL CHECK (type IN ('deposit', 'buy', 'sell', 'fee')),
|
|---|
| 50 | amount numeric(18,4) NOT NULL,
|
|---|
| 51 | currency char(3) NOT NULL DEFAULT 'USD',
|
|---|
| 52 | related_order uuid REFERENCES project.orders(id),
|
|---|
| 53 | created_at timestamptz NOT NULL DEFAULT now(),
|
|---|
| 54 | description text
|
|---|
| 55 | );
|
|---|
| 56 | }}}
|
|---|
| 57 |
|
|---|
| 58 | Given a period `[from, to)`:
|
|---|
| 59 |
|
|---|
| 60 | * '''Realized P/L''' = `SUM(amount)` over that user's `buy`, `sell` and `fee` transactions in
|
|---|
| 61 | the period (deposits excluded — they are not trading results).
|
|---|
| 62 | * '''Total invested''' = absolute value of the sum of that user's `buy` transactions in the
|
|---|
| 63 | period (buy amounts are stored negative, per
|
|---|
| 64 | [wiki:ERModel]).
|
|---|
| 65 | * '''ROI %''' = realized P/L ÷ total invested × 100.
|
|---|
| 66 | * The period is additionally bucketed into '''quarters''' internally, regardless of how wide
|
|---|
| 67 | `[from, to)` is, to measure:
|
|---|
| 68 | * '''Profitable / losing periods''' — how many quarters inside the window had positive vs.
|
|---|
| 69 | negative P/L.
|
|---|
| 70 | * '''Consistency %''' = profitable periods ÷ total periods with any activity × 100 — two
|
|---|
| 71 | users can have the same total P/L with very different risk profiles, and this is the
|
|---|
| 72 | number that tells them apart.
|
|---|
| 73 |
|
|---|
| 74 | === Solution SQL ===
|
|---|
| 75 |
|
|---|
| 76 | Implemented as `project.report_top_traders(p_from, p_to)` in
|
|---|
| 77 | `schema_creation.sql`:
|
|---|
| 78 |
|
|---|
| 79 | {{{
|
|---|
| 80 | CREATE OR REPLACE FUNCTION project.report_top_traders(p_from timestamptz, p_to timestamptz)
|
|---|
| 81 | RETURNS TABLE (
|
|---|
| 82 | username varchar,
|
|---|
| 83 | realized_pl numeric,
|
|---|
| 84 | total_invested numeric,
|
|---|
| 85 | roi_pct numeric,
|
|---|
| 86 | profitable_periods bigint,
|
|---|
| 87 | losing_periods bigint,
|
|---|
| 88 | total_periods bigint,
|
|---|
| 89 | consistency_pct numeric
|
|---|
| 90 | )
|
|---|
| 91 | LANGUAGE sql STABLE AS $$
|
|---|
| 92 | WITH period_pl AS (
|
|---|
| 93 | SELECT
|
|---|
| 94 | t.user_id,
|
|---|
| 95 | date_trunc('quarter', t.created_at) AS period,
|
|---|
| 96 | SUM(t.amount) AS period_pl,
|
|---|
| 97 | SUM(t.amount) FILTER (WHERE t.type = 'buy') AS period_buy
|
|---|
| 98 | FROM project.transactions t
|
|---|
| 99 | WHERE t.type IN ('buy', 'sell', 'fee')
|
|---|
| 100 | AND t.created_at >= p_from
|
|---|
| 101 | AND t.created_at < p_to
|
|---|
| 102 | GROUP BY t.user_id, date_trunc('quarter', t.created_at)
|
|---|
| 103 | )
|
|---|
| 104 | SELECT
|
|---|
| 105 | u.username,
|
|---|
| 106 | SUM(pp.period_pl) AS realized_pl,
|
|---|
| 107 | ABS(SUM(pp.period_buy)) AS total_invested,
|
|---|
| 108 | ROUND(SUM(pp.period_pl) / NULLIF(ABS(SUM(pp.period_buy)), 0) * 100, 2) AS roi_pct,
|
|---|
| 109 | COUNT(*) FILTER (WHERE pp.period_pl > 0) AS profitable_periods,
|
|---|
| 110 | COUNT(*) FILTER (WHERE pp.period_pl < 0) AS losing_periods,
|
|---|
| 111 | COUNT(*) AS total_periods,
|
|---|
| 112 | ROUND(COUNT(*) FILTER (WHERE pp.period_pl > 0)::numeric
|
|---|
| 113 | / NULLIF(COUNT(*), 0) * 100, 2) AS consistency_pct
|
|---|
| 114 | FROM period_pl pp
|
|---|
| 115 | JOIN project.users u ON u.id = pp.user_id
|
|---|
| 116 | GROUP BY u.id, u.username
|
|---|
| 117 | ORDER BY realized_pl DESC;
|
|---|
| 118 | $$;
|
|---|
| 119 | }}}
|
|---|
| 120 |
|
|---|
| 121 | One `SELECT`, one `WITH` CTE — the CTE does the quarter bucketing per user, the outer query
|
|---|
| 122 | rolls those buckets up into the totals, the ROI/consistency percentages and the ranking.
|
|---|
| 123 |
|
|---|
| 124 | '''Verified run.''' `reports_demo_data.sql` adds five
|
|---|
| 125 | quarters of round-trip trades (2025-07 through 2026-07) on top of the normal seed data
|
|---|
| 126 | specifically so this report has more than one period to work with — see that file's header
|
|---|
| 127 | (shown in full in the Demonstration data section below)
|
|---|
| 128 | for exactly what it inserts and why it is optional rather than part of `-init`. Run against
|
|---|
| 129 | PostgreSQL 16 with `data_load.sql` + `reports_demo_data.sql` loaded, through the actual CLI
|
|---|
| 130 | (`[10] Report: top traders`, range `2025-01-01` to `2026-09-17`):
|
|---|
| 131 |
|
|---|
| 132 | {{{
|
|---|
| 133 | Username Realized P/L Invested ROI % Prof. Loss Total Consist. %
|
|---|
| 134 | ------------------------------------------------------------------------------------------
|
|---|
| 135 | bob +991.0000 6300.0000 15.73 3 0 3 100.00
|
|---|
| 136 | alice -475.0000 24250.0000 -1.96 2 3 5 40.00
|
|---|
| 137 | }}}
|
|---|
| 138 |
|
|---|
| 139 | Sorting by raw P/L alone would rank alice above bob if alice's numbers were all positive; here
|
|---|
| 140 | it does the opposite, and that is the point of the report — alice traded a much larger total
|
|---|
| 141 | (and one of her seeded round trips landed in the same quarter as the ETH buy already in
|
|---|
| 142 | `data_load.sql`, tipping that quarter into a loss), while bob's three quarters were smaller
|
|---|
| 143 | but every one of them profitable, giving him both the better ROI and a perfect consistency
|
|---|
| 144 | score. A single "total profit" column would have hidden that difference completely.
|
|---|
| 145 |
|
|---|
| 146 | === Solution Relational Algebra ===
|
|---|
| 147 |
|
|---|
| 148 | {{{
|
|---|
| 149 | T_period = σ_{type ∈ {buy,sell,fee} ∧ created_at ≥ from ∧ created_at < to} (Transactions)
|
|---|
| 150 |
|
|---|
| 151 | T_tagged = π_{user_id, created_at, amount,
|
|---|
| 152 | (type = 'buy' ? amount : 0) → buy_amt} (T_period)
|
|---|
| 153 |
|
|---|
| 154 | Periods = γ_{user_id, quarter(created_at) → period ;
|
|---|
| 155 | SUM(amount) → period_pl, SUM(buy_amt) → period_buy} (T_tagged)
|
|---|
| 156 |
|
|---|
| 157 | Totals = γ_{user_id ; SUM(period_pl) → realized_pl,
|
|---|
| 158 | ABS(SUM(period_buy)) → total_invested,
|
|---|
| 159 | COUNT(*) → total_periods} (Periods)
|
|---|
| 160 | Profitable = γ_{user_id ; COUNT(*) → profitable_periods} (σ_{period_pl > 0} (Periods))
|
|---|
| 161 | Losing = γ_{user_id ; COUNT(*) → losing_periods} (σ_{period_pl < 0} (Periods))
|
|---|
| 162 |
|
|---|
| 163 | Combined = (Totals ⟕_{user_id} Profitable) ⟕_{user_id} Losing
|
|---|
| 164 |
|
|---|
| 165 | Ranked = π_{user_id, realized_pl, total_invested,
|
|---|
| 166 | (realized_pl / total_invested × 100) → roi_pct,
|
|---|
| 167 | COALESCE(profitable_periods, 0) → profitable_periods,
|
|---|
| 168 | COALESCE(losing_periods, 0) → losing_periods,
|
|---|
| 169 | total_periods,
|
|---|
| 170 | (COALESCE(profitable_periods, 0) / total_periods × 100) → consistency_pct}
|
|---|
| 171 | (Combined)
|
|---|
| 172 |
|
|---|
| 173 | Result = τ_{realized_pl ↓} (π_{username, realized_pl, total_invested, roi_pct,
|
|---|
| 174 | profitable_periods, losing_periods, total_periods, consistency_pct}
|
|---|
| 175 | (Ranked ⋈_{user_id = id} Users))
|
|---|
| 176 | }}}
|
|---|
| 177 |
|
|---|
| 178 | `Totals`/`Profitable`/`Losing` are three separate groupings of the same `Periods` relation
|
|---|
| 179 | because plain aggregation has no built-in "count only where X" operator; the two outer joins
|
|---|
| 180 | recombine them (`⟕`, not `⋈`, because a user with zero losing quarters must still appear with
|
|---|
| 181 | `losing_periods = 0`, not disappear from the result).
|
|---|
| 182 |
|
|---|
| 183 | == Market performance leaderboard ==
|
|---|
| 184 |
|
|---|
| 185 | === Data requirements description ===
|
|---|
| 186 |
|
|---|
| 187 | ''"Which markets were actually worth making — high volume, real price movement, real user
|
|---|
| 188 | interest — over a chosen period?"'' This is the "products that bring the most profit" /
|
|---|
| 189 | "good locations" family of question from the phase brief, translated to markets instead of
|
|---|
| 190 | physical products: a market with heavy volume but a dead price, or a big price swing nobody
|
|---|
| 191 | actually traded, are both misleading on their own; this report puts volume, trade count,
|
|---|
| 192 | price return and user participation side by side so a market's performance over a
|
|---|
| 193 | quarter/year/multi-year window can be judged as a whole, not from one number in isolation.
|
|---|
| 194 | Everything needed already exists: `market_trades` is the single source of truth for price and
|
|---|
| 195 | volume for every market ([wiki:PrototypeImplementation]),
|
|---|
| 196 | and `orders` is the only place a specific user is tied to a specific market
|
|---|
| 197 | ([wiki:ERModel]) —
|
|---|
| 198 | `market_trades` deliberately has no `user_id` column, since it also records the market
|
|---|
| 199 | simulator's own fills.
|
|---|
| 200 |
|
|---|
| 201 | Given a period `[from, to)`, per market:
|
|---|
| 202 |
|
|---|
| 203 | * '''Total volume''' = `SUM(quantity)` over its trades in the period.
|
|---|
| 204 | * '''Trade count''' = `COUNT(*)` over the same trades (real fills and simulated fills alike —
|
|---|
| 205 | this is activity, not just user activity).
|
|---|
| 206 | * '''Average trading price''' = `AVG(price)` over the same trades.
|
|---|
| 207 | * '''Market return %''' = `(last trade price − first trade price) ÷ first trade price × 100`,
|
|---|
| 208 | ordering trades by `executed_at` inside the period.
|
|---|
| 209 | * '''Participating users''' = `COUNT(DISTINCT user_id)` from that market's '''executed orders'''
|
|---|
| 210 | in the period — the only correct source, since `market_trades` cannot answer this question
|
|---|
| 211 | at all.
|
|---|
| 212 |
|
|---|
| 213 | === Solution SQL ===
|
|---|
| 214 |
|
|---|
| 215 | Implemented as `project.report_market_performance(p_from, p_to)` in
|
|---|
| 216 | `schema_creation.sql`:
|
|---|
| 217 |
|
|---|
| 218 | {{{
|
|---|
| 219 | CREATE OR REPLACE FUNCTION project.report_market_performance(p_from timestamptz, p_to timestamptz)
|
|---|
| 220 | RETURNS TABLE (
|
|---|
| 221 | symbol varchar,
|
|---|
| 222 | quote_currency char(3),
|
|---|
| 223 | total_volume numeric,
|
|---|
| 224 | trade_count bigint,
|
|---|
| 225 | avg_price numeric,
|
|---|
| 226 | market_return_pct numeric,
|
|---|
| 227 | participating_users bigint
|
|---|
| 228 | )
|
|---|
| 229 | LANGUAGE sql STABLE AS $$
|
|---|
| 230 | WITH trades AS (
|
|---|
| 231 | SELECT
|
|---|
| 232 | market_id, price, quantity, executed_at,
|
|---|
| 233 | FIRST_VALUE(price) OVER w AS first_price,
|
|---|
| 234 | LAST_VALUE(price) OVER (PARTITION BY market_id ORDER BY executed_at
|
|---|
| 235 | ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS last_price
|
|---|
| 236 | FROM project.market_trades
|
|---|
| 237 | WHERE executed_at >= p_from AND executed_at < p_to
|
|---|
| 238 | WINDOW w AS (PARTITION BY market_id ORDER BY executed_at)
|
|---|
| 239 | ),
|
|---|
| 240 | market_stats AS (
|
|---|
| 241 | SELECT
|
|---|
| 242 | market_id,
|
|---|
| 243 | SUM(quantity) AS total_volume,
|
|---|
| 244 | COUNT(*) AS trade_count,
|
|---|
| 245 | AVG(price) AS avg_price,
|
|---|
| 246 | MAX(first_price) AS first_price,
|
|---|
| 247 | MAX(last_price) AS last_price
|
|---|
| 248 | FROM trades
|
|---|
| 249 | GROUP BY market_id
|
|---|
| 250 | ),
|
|---|
| 251 | participation AS (
|
|---|
| 252 | SELECT market_id, COUNT(DISTINCT user_id) AS participating_users
|
|---|
| 253 | FROM project.orders
|
|---|
| 254 | WHERE status = 'executed' AND executed_at >= p_from AND executed_at < p_to
|
|---|
| 255 | GROUP BY market_id
|
|---|
| 256 | )
|
|---|
| 257 | SELECT
|
|---|
| 258 | c.symbol,
|
|---|
| 259 | m.quote_currency,
|
|---|
| 260 | ms.total_volume,
|
|---|
| 261 | ms.trade_count,
|
|---|
| 262 | ROUND(ms.avg_price, 6) AS avg_price,
|
|---|
| 263 | ROUND((ms.last_price - ms.first_price) / NULLIF(ms.first_price, 0) * 100, 2) AS market_return_pct,
|
|---|
| 264 | COALESCE(p.participating_users, 0) AS participating_users
|
|---|
| 265 | FROM market_stats ms
|
|---|
| 266 | JOIN project.markets m ON m.id = ms.market_id
|
|---|
| 267 | JOIN project.crypto c ON c.id = m.crypto_id
|
|---|
| 268 | LEFT JOIN participation p ON p.market_id = ms.market_id
|
|---|
| 269 | ORDER BY ms.total_volume DESC;
|
|---|
| 270 | $$;
|
|---|
| 271 | }}}
|
|---|
| 272 |
|
|---|
| 273 | `FIRST_VALUE`/`LAST_VALUE` pick the period's opening and closing price per market without a
|
|---|
| 274 | self-join; `LEFT JOIN participation` is required, not optional — a market can have trades
|
|---|
| 275 | from the simulator alone and legitimately zero participating users, and it must still show
|
|---|
| 276 | `0`, not disappear from the report.
|
|---|
| 277 |
|
|---|
| 278 | '''Verified run.''' Same seed as above (`data_load.sql` + `reports_demo_data.sql`, which also
|
|---|
| 279 | adds a BTC/USD uptrend and an ETH/USD downtrend across the same five quarters — see that
|
|---|
| 280 | file in the Demonstration data section below). Run through the CLI (`[11] Report: market performance`, `2025-01-01` to `2026-09-17`):
|
|---|
| 281 |
|
|---|
| 282 | {{{
|
|---|
| 283 | Symbol Quote Volume Trades Avg Price Return % Users
|
|---|
| 284 | -----------------------------------------------------------------------------
|
|---|
| 285 | DOGE USD 29500.0000 3 0.120583 +2.95 0
|
|---|
| 286 | ADA USD 2500.0000 3 0.450750 +1.62 0
|
|---|
| 287 | SOL USD 23.5000 3 165.283333 +1.13 0
|
|---|
| 288 | ETH USD 14.3500 8 3622.312500 -12.00 2
|
|---|
| 289 | BTC USD 3.9750 9 59447.400000 +67.85 2
|
|---|
| 290 | }}}
|
|---|
| 291 |
|
|---|
| 292 | BTC/USD and ETH/USD are the only two markets with historical (multi-quarter) data seeded, and
|
|---|
| 293 | they show it: BTC's price nearly tripled over the period (`+67.85%`), while ETH quietly lost
|
|---|
| 294 | `12%`. ADA/SOL/DOGE only have the few minutes of `data_load.sql`'s own recent seed trades, so
|
|---|
| 295 | their return numbers reflect that narrow window, and their `0` participating users is correct
|
|---|
| 296 | — `data_load.sql` seeds trade history for every market but only ever places an ''order'' on ETH.
|
|---|
| 297 |
|
|---|
| 298 | A price-volatility column (standard deviation of trade price) was dropped from this report
|
|---|
| 299 | after review — with only a handful of trades per market in most periods it read as noise
|
|---|
| 300 | rather than signal, and total volume plus return already carry the useful information.
|
|---|
| 301 |
|
|---|
| 302 | === Solution Relational Algebra ===
|
|---|
| 303 |
|
|---|
| 304 | {{{
|
|---|
| 305 | MT_period = σ_{executed_at ≥ from ∧ executed_at < to} (MarketTrades)
|
|---|
| 306 |
|
|---|
| 307 | Bounds = γ_{market_id ; MIN(executed_at) → t_first, MAX(executed_at) → t_last} (MT_period)
|
|---|
| 308 |
|
|---|
| 309 | FirstPx = π_{market_id, price → first_price}
|
|---|
| 310 | (MT_period ⋈_{MT_period.market_id = Bounds.market_id
|
|---|
| 311 | ∧ executed_at = t_first} Bounds)
|
|---|
| 312 | LastPx = π_{market_id, price → last_price}
|
|---|
| 313 | (MT_period ⋈_{MT_period.market_id = Bounds.market_id
|
|---|
| 314 | ∧ executed_at = t_last} Bounds)
|
|---|
| 315 |
|
|---|
| 316 | Stats = γ_{market_id ; SUM(quantity) → total_volume, COUNT(*) → trade_count,
|
|---|
| 317 | AVG(price) → avg_price} (MT_period)
|
|---|
| 318 |
|
|---|
| 319 | MarketStats = (Stats ⋈_{market_id} FirstPx) ⋈_{market_id} LastPx
|
|---|
| 320 |
|
|---|
| 321 | O_period = σ_{status = 'executed' ∧ executed_at ≥ from ∧ executed_at < to} (Orders)
|
|---|
| 322 | Participation = γ_{market_id ; COUNT_DISTINCT(user_id) → participating_users} (O_period)
|
|---|
| 323 |
|
|---|
| 324 | Joined = ((MarketStats ⟕_{market_id} Participation)
|
|---|
| 325 | ⋈_{market_id = id} Markets) ⋈_{crypto_id = id} Crypto
|
|---|
| 326 |
|
|---|
| 327 | Result = τ_{total_volume ↓} (
|
|---|
| 328 | π_{symbol, quote_currency, total_volume, trade_count, avg_price,
|
|---|
| 329 | (last_price − first_price) / first_price × 100 → market_return_pct,
|
|---|
| 330 | COALESCE(participating_users, 0) → participating_users}
|
|---|
| 331 | (Joined) )
|
|---|
| 332 | }}}
|
|---|
| 333 |
|
|---|
| 334 | `FirstPx`/`LastPx` express `FIRST_VALUE`/`LAST_VALUE` — which have no classical relational-
|
|---|
| 335 | algebra equivalent — as an aggregation for the boundary timestamp per market followed by a
|
|---|
| 336 | self-join back to `MarketTrades` to recover the price at that timestamp; this is the standard
|
|---|
| 337 | way to express "value at the extreme of a group" in extended relational algebra.
|
|---|
| 338 |
|
|---|
| 339 | == Demonstration data ==
|
|---|
| 340 |
|
|---|
| 341 | `reports_demo_data.sql`, the optional script both verified runs above were produced with:
|
|---|
| 342 |
|
|---|
| 343 | {{{
|
|---|
| 344 | -- reports_demo_data.sql
|
|---|
| 345 | -- EduBerza - optional historical data for the P6 reports
|
|---|
| 346 | -- Course: Databases 2025/2026 Winter, FINKI UKIM
|
|---|
| 347 | --
|
|---|
| 348 | -- data_load.sql only seeds ~10 minutes of trade history, which is enough to
|
|---|
| 349 | -- demonstrate UC0001-UC0007 but not enough to show report_top_traders() or
|
|---|
| 350 | -- report_market_performance() doing anything interesting: everything falls
|
|---|
| 351 | -- into a single quarter, so "number of profitable periods" and "consistency"
|
|---|
| 352 | -- are trivial and "market return" has almost no history to work with.
|
|---|
| 353 | --
|
|---|
| 354 | -- This script adds five quarters of synthetic transactions, market trades and
|
|---|
| 355 | -- executed orders on top of an already-loaded data_load.sql, spanning
|
|---|
| 356 | -- 2025-07 to 2026-07, so the two P6 reports have several periods and two
|
|---|
| 357 | -- markets with opposite price trends to actually compare.
|
|---|
| 358 | --
|
|---|
| 359 | -- Deliberately NOT part of -init / -load-data: it only inserts into
|
|---|
| 360 | -- transactions, market_trades and orders, and does not touch
|
|---|
| 361 | -- users.available_balance/invested_balance or holdings, so it does not
|
|---|
| 362 | -- disturb the balances the other use cases' documented "verified run"
|
|---|
| 363 | -- sections depend on. Run it by hand, after data_load.sql, only to exercise
|
|---|
| 364 | -- the two reports:
|
|---|
| 365 | --
|
|---|
| 366 | -- psql "$DATABASE_URL" -f server/db/schema_creation.sql
|
|---|
| 367 | -- psql "$DATABASE_URL" -f server/db/data_load.sql
|
|---|
| 368 | -- psql "$DATABASE_URL" -f server/db/reports_demo_data.sql
|
|---|
| 369 | --
|
|---|
| 370 | -- Idempotent: deletes its own previously-inserted rows (tagged via
|
|---|
| 371 | -- description/source) before re-inserting.
|
|---|
| 372 |
|
|---|
| 373 | SET search_path TO project, public;
|
|---|
| 374 |
|
|---|
| 375 | DELETE FROM transactions WHERE description = 'P6 demo data';
|
|---|
| 376 | DELETE FROM orders WHERE id IN (
|
|---|
| 377 | 'e1111111-1111-1111-1111-111111111111', 'e2222222-2222-2222-2222-222222222222',
|
|---|
| 378 | 'e3333333-3333-3333-3333-333333333333', 'e4444444-4444-4444-4444-444444444444',
|
|---|
| 379 | 'e5555555-5555-5555-5555-555555555555'
|
|---|
| 380 | );
|
|---|
| 381 | DELETE FROM market_trades WHERE source = 'p6_demo';
|
|---|
| 382 |
|
|---|
| 383 | -- ============================================================================
|
|---|
| 384 | -- Alice: five quarterly round trips, 3 profitable / 2 losing (60% consistency)
|
|---|
| 385 | -- ============================================================================
|
|---|
| 386 | INSERT INTO transactions (user_id, type, amount, currency, created_at, description) VALUES
|
|---|
| 387 | ('b1111111-1111-1111-1111-111111111111', 'buy', -5000.0000, 'USD', '2025-07-15 10:00', 'P6 demo data'),
|
|---|
| 388 | ('b1111111-1111-1111-1111-111111111111', 'sell', 5800.0000, 'USD', '2025-07-20 10:00', 'P6 demo data'),
|
|---|
| 389 | ('b1111111-1111-1111-1111-111111111111', 'fee', -5.0000, 'USD', '2025-07-20 10:00', 'P6 demo data'),
|
|---|
| 390 |
|
|---|
| 391 | ('b1111111-1111-1111-1111-111111111111', 'buy', -4000.0000, 'USD', '2025-10-15 10:00', 'P6 demo data'),
|
|---|
| 392 | ('b1111111-1111-1111-1111-111111111111', 'sell', 3500.0000, 'USD', '2025-10-20 10:00', 'P6 demo data'),
|
|---|
| 393 | ('b1111111-1111-1111-1111-111111111111', 'fee', -5.0000, 'USD', '2025-10-20 10:00', 'P6 demo data'),
|
|---|
| 394 |
|
|---|
| 395 | ('b1111111-1111-1111-1111-111111111111', 'buy', -6000.0000, 'USD', '2026-01-15 10:00', 'P6 demo data'),
|
|---|
| 396 | ('b1111111-1111-1111-1111-111111111111', 'sell', 6700.0000, 'USD', '2026-01-20 10:00', 'P6 demo data'),
|
|---|
| 397 | ('b1111111-1111-1111-1111-111111111111', 'fee', -5.0000, 'USD', '2026-01-20 10:00', 'P6 demo data'),
|
|---|
| 398 |
|
|---|
| 399 | ('b1111111-1111-1111-1111-111111111111', 'buy', -3000.0000, 'USD', '2026-04-15 10:00', 'P6 demo data'),
|
|---|
| 400 | ('b1111111-1111-1111-1111-111111111111', 'sell', 2600.0000, 'USD', '2026-04-20 10:00', 'P6 demo data'),
|
|---|
| 401 | ('b1111111-1111-1111-1111-111111111111', 'fee', -5.0000, 'USD', '2026-04-20 10:00', 'P6 demo data'),
|
|---|
| 402 |
|
|---|
| 403 | ('b1111111-1111-1111-1111-111111111111', 'buy', -4500.0000, 'USD', '2026-07-15 10:00', 'P6 demo data'),
|
|---|
| 404 | ('b1111111-1111-1111-1111-111111111111', 'sell', 5200.0000, 'USD', '2026-07-20 10:00', 'P6 demo data'),
|
|---|
| 405 | ('b1111111-1111-1111-1111-111111111111', 'fee', -5.0000, 'USD', '2026-07-20 10:00', 'P6 demo data');
|
|---|
| 406 |
|
|---|
| 407 | -- ============================================================================
|
|---|
| 408 | -- Bob: three quarterly round trips, all profitable (100% consistency),
|
|---|
| 409 | -- smaller total P/L than Alice but a higher ROI.
|
|---|
| 410 | -- ============================================================================
|
|---|
| 411 | INSERT INTO transactions (user_id, type, amount, currency, created_at, description) VALUES
|
|---|
| 412 | ('b2222222-2222-2222-2222-222222222222', 'buy', -2000.0000, 'USD', '2025-10-10 10:00', 'P6 demo data'),
|
|---|
| 413 | ('b2222222-2222-2222-2222-222222222222', 'sell', 2300.0000, 'USD', '2025-10-12 10:00', 'P6 demo data'),
|
|---|
| 414 | ('b2222222-2222-2222-2222-222222222222', 'fee', -3.0000, 'USD', '2025-10-12 10:00', 'P6 demo data'),
|
|---|
| 415 |
|
|---|
| 416 | ('b2222222-2222-2222-2222-222222222222', 'buy', -2500.0000, 'USD', '2026-01-10 10:00', 'P6 demo data'),
|
|---|
| 417 | ('b2222222-2222-2222-2222-222222222222', 'sell', 2900.0000, 'USD', '2026-01-12 10:00', 'P6 demo data'),
|
|---|
| 418 | ('b2222222-2222-2222-2222-222222222222', 'fee', -3.0000, 'USD', '2026-01-12 10:00', 'P6 demo data'),
|
|---|
| 419 |
|
|---|
| 420 | ('b2222222-2222-2222-2222-222222222222', 'buy', -1800.0000, 'USD', '2026-04-10 10:00', 'P6 demo data'),
|
|---|
| 421 | ('b2222222-2222-2222-2222-222222222222', 'sell', 2100.0000, 'USD', '2026-04-12 10:00', 'P6 demo data'),
|
|---|
| 422 | ('b2222222-2222-2222-2222-222222222222', 'fee', -3.0000, 'USD', '2026-04-12 10:00', 'P6 demo data');
|
|---|
| 423 |
|
|---|
| 424 | -- ============================================================================
|
|---|
| 425 | -- Market trades: BTC/USD trending up, ETH/USD trending down, five quarters.
|
|---|
| 426 | -- source='p6_demo' keeps these separate from data_load.sql's own rows and
|
|---|
| 427 | -- from live user/bot fills so this script can clean up after itself.
|
|---|
| 428 | -- ============================================================================
|
|---|
| 429 | INSERT INTO market_trades (market_id, executed_at, price, quantity, side, source) VALUES
|
|---|
| 430 | ('a1111111-1111-1111-1111-111111111111', '2025-07-15 10:00', 40000.000000, 0.500000, 'buy', 'p6_demo'),
|
|---|
| 431 | ('a1111111-1111-1111-1111-111111111111', '2025-10-15 10:00', 45000.000000, 0.800000, 'buy', 'p6_demo'),
|
|---|
| 432 | ('a1111111-1111-1111-1111-111111111111', '2026-01-15 10:00', 55000.000000, 1.200000, 'buy', 'p6_demo'),
|
|---|
| 433 | ('a1111111-1111-1111-1111-111111111111', '2026-04-15 10:00', 60000.000000, 1.000000, 'buy', 'p6_demo'),
|
|---|
| 434 |
|
|---|
| 435 | ('a2222222-2222-2222-2222-222222222222', '2025-07-15 10:00', 4000.000000, 3.000000, 'sell', 'p6_demo'),
|
|---|
| 436 | ('a2222222-2222-2222-2222-222222222222', '2025-10-15 10:00', 3800.000000, 2.500000, 'sell', 'p6_demo'),
|
|---|
| 437 | ('a2222222-2222-2222-2222-222222222222', '2026-01-15 10:00', 3600.000000, 2.000000, 'sell', 'p6_demo'),
|
|---|
| 438 | ('a2222222-2222-2222-2222-222222222222', '2026-04-15 10:00', 3550.000000, 1.800000, 'sell', 'p6_demo');
|
|---|
| 439 |
|
|---|
| 440 | -- ============================================================================
|
|---|
| 441 | -- Executed orders: who participated in which market, across the same quarters.
|
|---|
| 442 | -- ============================================================================
|
|---|
| 443 | INSERT INTO orders (id, user_id, market_id, side, type, status, quantity, price, placed_at, executed_at) VALUES
|
|---|
| 444 | ('e1111111-1111-1111-1111-111111111111', 'b1111111-1111-1111-1111-111111111111',
|
|---|
| 445 | 'a1111111-1111-1111-1111-111111111111', 'buy', 'market', 'executed', 0.5000, 40000.000000,
|
|---|
| 446 | '2025-07-15 10:00', '2025-07-15 10:00'),
|
|---|
| 447 | ('e2222222-2222-2222-2222-222222222222', 'b1111111-1111-1111-1111-111111111111',
|
|---|
| 448 | 'a2222222-2222-2222-2222-222222222222', 'sell', 'market', 'executed', 3.0000, 4000.000000,
|
|---|
| 449 | '2025-10-15 10:00', '2025-10-15 10:00'),
|
|---|
| 450 | ('e3333333-3333-3333-3333-333333333333', 'b2222222-2222-2222-2222-222222222222',
|
|---|
| 451 | 'a1111111-1111-1111-1111-111111111111', 'buy', 'market', 'executed', 1.2000, 55000.000000,
|
|---|
| 452 | '2026-01-15 10:00', '2026-01-15 10:00'),
|
|---|
| 453 | ('e4444444-4444-4444-4444-444444444444', 'b2222222-2222-2222-2222-222222222222',
|
|---|
| 454 | 'a1111111-1111-1111-1111-111111111111', 'buy', 'market', 'executed', 1.0000, 60000.000000,
|
|---|
| 455 | '2026-04-15 10:00', '2026-04-15 10:00'),
|
|---|
| 456 | ('e5555555-5555-5555-5555-555555555555', 'b3333333-3333-3333-3333-333333333333',
|
|---|
| 457 | 'a2222222-2222-2222-2222-222222222222', 'sell', 'market', 'executed', 2.0000, 3600.000000,
|
|---|
| 458 | '2026-01-15 10:00', '2026-01-15 10:00');
|
|---|
| 459 | }}}
|
|---|
| 460 |
|
|---|
| 461 | == AI usage ==
|
|---|
| 462 |
|
|---|
| 463 | AI was used in this phase and is logged in full, per the course rule for P1 onward.
|
|---|
| 464 |
|
|---|
| 465 | * '''Phase log:''' [wiki:AdvancedReportsAIUsage] — service used, what
|
|---|
| 466 | the AI produced, and what I decided myself.
|
|---|
| 467 |
|
|---|
| 468 | '''Service:''' Claude Code (Anthropic), `https://claude.com/claude-code` — Claude subscription,
|
|---|
| 469 | model Claude Sonnet 5.
|
|---|
| 470 |
|
|---|
| 471 | '''In short:''' I specified both report questions in full — including the exact formulas for
|
|---|
| 472 | P/L, ROI, consistency, market return, volatility and user participation — and asked the AI to
|
|---|
| 473 | turn them into working SQL, wire them into the prototype as real reports, build the
|
|---|
| 474 | relational-algebra equivalents, and produce demonstration data rich enough to show the
|
|---|
| 475 | reports doing something non-trivial. In a follow-up, I asked for the price-volatility column
|
|---|
| 476 | to be dropped from the market performance report — see the "Follow-up — 2026-09-17" section of
|
|---|
| 477 | [wiki:AdvancedReportsAIUsage] for that change.
|
|---|