source: docs/P4-Prototype/UseCase0004Implementation.md@ df05838

main
Last change on this file since df05838 was b715712, checked in by Stefan <trsunovstefan@…>, 8 weeks ago

Add the server side and configuration

  • Property mode set to 100644
File size: 3.1 KB
Line 
1# Use-case 0004 Implementation — Buy
2
3**Initiating actor:** Trader. **Source file:** `server/trade.go`, function `PlaceOrder(s, "buy")`.
4
5## Scenario (implemented)
6
71. **User** chooses `[4] Place market BUY order`.
82. **System** lists markets with their latest price (same SQL as UC0006 — via the `v_latest_prices` view).
9
10
113. **User** enters `BTC`.
124. **System** resolves the market and fetches the latest price:
13
14 ```sql
15 SELECT m.id, c.id, c.symbol, m.quote_currency
16 FROM markets m
17 JOIN crypto c ON c.id = m.crypto_id
18 WHERE upper(c.symbol) = upper($1) AND m.is_active;
19
20 SELECT price FROM v_latest_prices WHERE market_id = $2;
21 ```
22
235. **User** enters quantity `0.01`.
246. **System** executes a single database transaction — *all or nothing*:
25
26 ```sql
27 BEGIN;
28
29 -- (a) record the order
30 INSERT INTO orders
31 (user_id, market_id, side, type, status, quantity, price, executed_at)
32 VALUES
33 ($1, $2, 'buy', 'market', 'executed', $3, $4, now())
34 RETURNING id;
35
36 -- (b) lock and check the user balance
37 SELECT available_balance FROM users WHERE id = $1 FOR UPDATE;
38
39 -- (c) move cash from available to invested
40 UPDATE users
41 SET available_balance = available_balance - $notional,
42 invested_balance = invested_balance + $notional,
43 updated_at = now()
44 WHERE id = $1;
45
46 -- (d) upsert the holding, recomputing the weighted-average entry price
47 -- in one statement. Every SET expression sees the pre-update row, so
48 -- holdings.quantity below is still the old quantity.
49 INSERT INTO holdings (user_id, crypto_id, quantity, avg_price, updated_at)
50 VALUES ($1, $c, $3, $4, now())
51 ON CONFLICT (user_id, crypto_id) DO UPDATE
52 SET avg_price = (holdings.quantity * holdings.avg_price
53 + EXCLUDED.quantity * EXCLUDED.avg_price)
54 / (holdings.quantity + EXCLUDED.quantity),
55 quantity = holdings.quantity + EXCLUDED.quantity,
56 updated_at = now();
57
58 -- (e) ledger entry
59 INSERT INTO transactions
60 (user_id, type, amount, currency, related_order, description)
61 VALUES
62 ($1, 'buy', -$notional, 'USD', $orderId, 'Market buy ...');
63
64 -- (f) record the resulting market trade
65 INSERT INTO market_trades
66 (market_id, executed_at, price, quantity, side, source)
67 VALUES
68 ($2, now(), $4, $3, 'buy', 'user');
69
70 COMMIT;
71 ```
72
737. **System** prints: `Order executed: buy 0.0100 BTC @ 67140.000000 (notional 671.4000 USD)`.
74
75 ![Market list, then a filled BUY order](screenshots/uc0004_buy.png)
76
77## Verified run (from actual prototype execution)
78
79With seed data loaded:
80
81- **Before:** alice.available_balance = 8250.00, portfolio = { ETH: 0.5 }.
82- **Command:** `buy 0.01 BTC`.
83- **After:** alice.available_balance = 7578.60 (= 8250 − 671.40), portfolio = { BTC: 0.01 @ 67140, ETH: 0.5 @ 3500 }, net worth = 10010.00 USD (the +10 is the ETH unrealised P/L from the price moving from 3500 → 3520).
84
85## Failure path — insufficient funds
86
87If `available_balance < notional`, the `defer tx.Rollback()` in `server/trade.go` reverts all six statements and the user sees:
88
89```
90Insufficient funds: need X, have Y
91```
Note: See TracBrowser for help on using the repository browser.