| 1 | = Prototype Implementation =
|
|---|
| 2 |
|
|---|
| 3 | == Implemented use-cases ==
|
|---|
| 4 |
|
|---|
| 5 | * [wiki:UseCase0001Implementation] — Register new account
|
|---|
| 6 | * [wiki:UseCase0002Implementation] — Log in
|
|---|
| 7 | * [wiki:UseCase0003Implementation] — Deposit virtual funds
|
|---|
| 8 | * [wiki:UseCase0004Implementation] — Place market BUY order
|
|---|
| 9 | * [wiki:UseCase0005Implementation] — Place market SELL order
|
|---|
| 10 | * [wiki:UseCase0006Implementation] — View portfolio and transaction history
|
|---|
| 11 | * [wiki:UseCase0007Implementation] — Manage watchlist
|
|---|
| 12 |
|
|---|
| 13 | Each page follows its P3 use case step by step. It adds the exact SQL the Go code runs in that
|
|---|
| 14 | step and a screenshot of the step from a real run against the database.
|
|---|
| 15 |
|
|---|
| 16 | * How to build, configure, run and test the prototype: [wiki:BuildInstructions]
|
|---|
| 17 | * AI usage for this phase: [wiki:PrototypeImplementationAIUsage]
|
|---|
| 18 |
|
|---|
| 19 | == Overview ==
|
|---|
| 20 |
|
|---|
| 21 | !EduBerza's P4 prototype is a Go command-line program. It works against the `project` schema in
|
|---|
| 22 | PostgreSQL. It implements all seven use cases from [wiki:UseCaseModel]; the course asks for at
|
|---|
| 23 | least three. Every database access is real SQL that was executed and tested. A second program,
|
|---|
| 24 | the market bot, simulates a live market so prices move while the prototype runs.
|
|---|
| 25 |
|
|---|
| 26 | The source code is in the project's git repository: the CLI in `server/`, the bot in `bots/`,
|
|---|
| 27 | and the SQL scripts in `server/db/`.
|
|---|
| 28 |
|
|---|
| 29 | == Technology and architecture ==
|
|---|
| 30 |
|
|---|
| 31 | * '''Language:''' Go (module `bp_project`, `go 1.25` in `go.mod`). The only third-party
|
|---|
| 32 | library is the PostgreSQL driver `github.com/lib/pq`.
|
|---|
| 33 | * '''Database:''' PostgreSQL. Every table, view and function is in the `project` schema. The
|
|---|
| 34 | DDL is `schema_creation.sql` and the sample data is `data_load.sql`. Both scripts are
|
|---|
| 35 | compiled into the binary and run by `./eduberza -init`.
|
|---|
| 36 | * '''Interface:''' plain text menus on standard input and output. There are no web server,
|
|---|
| 37 | frameworks, images or styles.
|
|---|
| 38 | * '''Structure:''' one source file per area of the application.
|
|---|
| 39 |
|
|---|
| 40 | ||= File =||= Responsibility =||= Use cases =||
|
|---|
| 41 | || `server/main.go` || Flags `-init` / `-load-data`, then starts the menu loop || — ||
|
|---|
| 42 | || `server/cli.go` || The two menus (before and after login), input reading || all ||
|
|---|
| 43 | || `server/db/db.go` || Connection from `.env` / environment variables, embedded SQL scripts || — ||
|
|---|
| 44 | || `server/auth.go` || Register, log in (SHA-256 password hash) || UC0001, UC0002 ||
|
|---|
| 45 | || `server/account.go` || Balance, deposit, transaction history || UC0003, UC0006 ||
|
|---|
| 46 | || `server/market.go` || Market list, choosing a market or a holding by number, latest price || UC0004, UC0005 ||
|
|---|
| 47 | || `server/trade.go` || Market buy and sell orders, each in one transaction || UC0004, UC0005 ||
|
|---|
| 48 | || `server/portfolio.go` || Portfolio with current value and unrealised P/L || UC0006 ||
|
|---|
| 49 | || `server/watchlist.go` || List, add and remove watchlist items || UC0007 ||
|
|---|
| 50 | || `bots/main.go` || Market bot: random-walk price ticks into `market_trades`, 1-minute candles || — ||
|
|---|
| 51 |
|
|---|
| 52 | == No identifiers to remember ==
|
|---|
| 53 |
|
|---|
| 54 | The user never has to type or remember an id, a code or a symbol:
|
|---|
| 55 |
|
|---|
| 56 | * Every menu is numbered, and the user answers with the number of an option.
|
|---|
| 57 | * '''Buying:''' all active markets are listed with their latest price, numbered 1…n. The user
|
|---|
| 58 | enters the market's number at `Market #:` (`ChooseMarket` in `market.go`).
|
|---|
| 59 | * '''Selling:''' only the cryptos the user actually holds are listed, each with the quantity
|
|---|
| 60 | held and the quantity still free to sell. The user enters the holding's number at
|
|---|
| 61 | `Holding #:` (`ChooseHolding`). A user who holds nothing free to sell gets
|
|---|
| 62 | `you hold no crypto that is free to sell` and is never asked to choose.
|
|---|
| 63 | * '''Watchlist:''' ''Add'' lists the cryptos that are not on the watchlist yet. ''Remove''
|
|---|
| 64 | lists the ones that are on it. Both are numbered, and the user enters the number.
|
|---|
| 65 | * A number outside the list is refused with `Invalid choice, enter a number from 1 to N.` and
|
|---|
| 66 | nothing is changed.
|
|---|
| 67 |
|
|---|
| 68 | The only things the user types are their own data: username, e-mail, full name, password, the
|
|---|
| 69 | amount to deposit, the quantity to buy or sell, and the date range of the two P6 reports.
|
|---|
| 70 |
|
|---|
| 71 | == What the prototype demonstrates about the database design ==
|
|---|
| 72 |
|
|---|
| 73 | * '''The current price is never stored as a column.''' It is always the price of the most
|
|---|
| 74 | recent row in `market_trades`, read through the `v_latest_prices` view. The user's own fills
|
|---|
| 75 | and the bot's simulated trades go into the same table, so there is only one definition of
|
|---|
| 76 | "the price".
|
|---|
| 77 | * '''Money movements are transactional.''' A buy touches five tables (`orders`, `users`,
|
|---|
| 78 | `holdings`, `transactions`, `market_trades`) inside one transaction. If the balance check
|
|---|
| 79 | fails, the whole transaction is rolled back: after a rejected purchase there is no order
|
|---|
| 80 | row, no ledger entry and no holding. The failure-path tests in [wiki:BuildInstructions]
|
|---|
| 81 | check this.
|
|---|
| 82 | * '''Constraints do real work.''' `UNIQUE (user_id, crypto_id)` on `holdings` is what makes
|
|---|
| 83 | the `INSERT … ON CONFLICT DO UPDATE` upsert possible, so the database recomputes the
|
|---|
| 84 | weighted-average entry price in one statement, instead of the application reading, changing
|
|---|
| 85 | and writing the row. `CHECK (reserved_quantity >= 0 AND reserved_quantity <= quantity)` does
|
|---|
| 86 | the same for the sell path: the database itself makes an inconsistent reservation
|
|---|
| 87 | impossible, and it does not rely only on `trade.go` being careful.
|
|---|
| 88 | * '''Selling reserves before it removes.''' A sell order locks the holding row with
|
|---|
| 89 | `SELECT … FOR UPDATE`, reserves the quantity being sold, then settles by removing it (see
|
|---|
| 90 | [wiki:UseCase0005Implementation]). Two sell orders for more than the free quantity, placed
|
|---|
| 91 | at the same moment from two separate processes, are serialised by the row lock. Exactly one
|
|---|
| 92 | of them succeeds. This was tested with two concurrent processes in session 3 (see
|
|---|
| 93 | [wiki:PrototypeImplementationAIUsage]).
|
|---|
| 94 |
|
|---|
| 95 | == Known limitations ==
|
|---|
| 96 |
|
|---|
| 97 | These were left out on purpose for a first prototype. They belong to the later phases:
|
|---|
| 98 |
|
|---|
| 99 | * Only `market` orders execute. The schema accepts `limit` (`orders.type`), but there is no
|
|---|
| 100 | matching logic for it.
|
|---|
| 101 | * Passwords are hashed with SHA-256 and no salt. That shows the password itself is never
|
|---|
| 102 | stored, but it is not good enough for real use. A proper password hash belongs in P9
|
|---|
| 103 | (security).
|
|---|
| 104 | * Money is `float64` in Go, while the database columns are `numeric`. For that reason, all
|
|---|
| 105 | arithmetic that must be exact (the weighted average) is done in SQL. Real use would need a
|
|---|
| 106 | decimal type on the Go side too.
|
|---|
| 107 | * The prototype sets no connection pool and no explicit isolation level. Both are P8 topics.
|
|---|
| 108 | * A reservation only exists inside one transaction, because the prototype only has market
|
|---|
| 109 | orders, and they settle immediately. A real limit-order matcher would leave
|
|---|
| 110 | `holdings.reserved_quantity` set and `orders.status = 'open'` between two separate commits.
|
|---|
| 111 | It would also need a way to cancel an order and release the reservation. Neither is
|
|---|
| 112 | implemented, because nothing in the prototype creates an order that stays open.
|
|---|
| 113 |
|
|---|
| 114 | == History of changes ==
|
|---|
| 115 |
|
|---|
| 116 | The code started as my own Go backend: HTTP handlers, a draft schema, and a `db.go` that
|
|---|
| 117 | recreated the tables on every start. It changed as follows. The AI's share of each change is
|
|---|
| 118 | logged in [wiki:PrototypeImplementationAIUsage].
|
|---|
| 119 |
|
|---|
| 120 | ||= Date =||= Change =||= Origin =||
|
|---|
| 121 | || 2026-04-21 || My HTTP backend rewritten as the CLI prototype covering UC0001–UC0007. The schema errors in my draft were corrected. The market bot was added. || My code and decisions (CLI instead of web, drop the frontend, keep a simulator); rewrite by AI (session 1) ||
|
|---|
| 122 | || 2026-08-06/07 || Three bugs fixed: path resolution of `.env` and the SQL scripts, an endless loop at end of input, and an error check in the wrong order on the sell path. The holding update became one `INSERT … ON CONFLICT DO UPDATE`. || I asked for a code review; fixes by AI (session 2) ||
|
|---|
| 123 | || 2026-09-16 || `holdings.reserved_quantity` added. The sell path now reserves, then settles. Orders go from `open` to `executed`. || The edge case was mine; implementation by AI (session 3) ||
|
|---|
| 124 | || 2026-09-24 || Every choice is picked from a numbered list: markets by number, a sell lists only the user's holdings, the watchlist lists the cryptos. All screenshots were retaken, one per step. || I asked for a check against the P4 rules; implementation by AI (session 4) ||
|
|---|
| 125 |
|
|---|
| 126 | '''Service:''' Claude Code (Anthropic), Claude subscription. Session 1 used Claude Opus 4.7 (1M
|
|---|
| 127 | context), session 2 Claude Opus 5 (1M context), session 3 Claude Sonnet 5, and session 4
|
|---|
| 128 | Claude Opus 5.5 (1M context).
|
|---|