source: docs/P3-UseCaseModel/UseCase0005.md

main
Last change on this file was ef1c1c7, checked in by Stefan <trsunovstefan@…>, 5 days ago

Wiki docs, phase 6 and phase 7 added

  • Property mode set to 100644
File size: 5.6 KB
Line 
1# Use-case 0005 — Place market SELL order
2
3**Initiating actor:** Trader
4
5**Other actors:** Market Simulator (indirect — supplies the current price).
6
7A Trader sells part or all of a holding at the current market price. Cost basis is preserved so realised P/L can be reconstructed from the ledger.
8
9## Reserve, then settle
10
11The crypto being sold is **reserved** (`holdings.reserved_quantity`) before it
12is actually removed from the position, so the check a second sell order makes
13is always against what is truly still free (`quantity - reserved_quantity`),
14not against the raw `quantity`, which would also count crypto already
15promised to this order. Because only market orders are implemented, an order
16settles in the same database transaction it is placed in, so reserve and
17settle below are two statements inside one commit rather than two separate
18ones — the existing all-or-nothing guarantee (see
19[PrototypeImplementation](../P4-Prototype/PrototypeImplementation.md)) is
20kept. They stay logically distinct so that a future limit-order matcher —
21where an order really would sit `open` for a while before a *later*
22transaction settles it — needs only a second transaction where today there is
23one, not a schema change.
24
25## Scenario
26
271. Trader chooses "Place market SELL order".
282. System lists, numbered, the Trader's holdings that still have a quantity free to
29 sell (not reserved by an open sell order), with the quantity held, the free
30 quantity and the latest price:
31
32 ```sql
33 SELECT m.id, c.id, c.symbol, m.quote_currency,
34 h.quantity, h.quantity - h.reserved_quantity AS free,
35 COALESCE(lp.price, 0) AS price
36 FROM project.holdings h
37 JOIN project.crypto c ON c.id = h.crypto_id
38 JOIN project.markets m ON m.crypto_id = c.id AND m.is_active = true
39 LEFT JOIN project.v_latest_prices lp ON lp.market_id = m.id
40 WHERE h.user_id = $1
41 AND h.quantity - h.reserved_quantity > 0
42 ORDER BY c.symbol;
43 ```
443. Trader picks the holding by its number in the listed holdings, e.g. `2` (ETH), and
45 enters the quantity.
464. System takes the chosen row's market id and crypto id from the list (no lookup by
47 symbol) and looks up the latest price:
48
49 ```sql
50 SELECT price FROM project.v_latest_prices WHERE market_id = $1;
51 ```
525. System opens a transaction:
53
54 ```sql
55 BEGIN;
56
57 -- (a) record intent — no trade has happened yet.
58 INSERT INTO project.orders
59 (user_id, market_id, side, type, status, quantity, price)
60 VALUES
61 ($user_id, $market_id, 'sell', 'market', 'open', $qty, $price)
62 RETURNING id; -- $order_id
63
64 -- (b) lock the holding and check what is actually free to sell.
65 SELECT quantity, reserved_quantity, avg_price
66 FROM project.holdings
67 WHERE user_id = $user_id AND crypto_id = $crypto_id
68 FOR UPDATE;
69 -- available := quantity - reserved_quantity
70 -- abort if row missing or available < $qty
71 ```
72
736. If the check passes, system reserves the crypto, then — since this is a market order — settles it immediately, all inside the same transaction:
74
75 ```sql
76 -- (c) reserve: committed to this order, not yet removed from the position.
77 UPDATE project.holdings
78 SET reserved_quantity = reserved_quantity + $qty,
79 updated_at = now()
80 WHERE user_id = $user_id AND crypto_id = $crypto_id;
81
82 -- (d) settle: release the reservation and remove the asset in one step.
83 UPDATE project.holdings
84 SET quantity = quantity - $qty,
85 reserved_quantity = reserved_quantity - $qty,
86 updated_at = now()
87 WHERE user_id = $user_id AND crypto_id = $crypto_id;
88
89 UPDATE project.users
90 SET available_balance = available_balance + $notional,
91 invested_balance = GREATEST(invested_balance - ($avg_price * $qty), 0),
92 updated_at = now()
93 WHERE id = $user_id;
94
95 INSERT INTO project.transactions
96 (user_id, type, amount, currency, related_order, description)
97 VALUES
98 ($user_id, 'sell', $notional, 'USD', $order_id, 'Market sell ...');
99
100 INSERT INTO project.market_trades
101 (market_id, executed_at, price, quantity, side, source)
102 VALUES
103 ($market_id, now(), $price, $qty, 'sell', 'user');
104
105 -- (e) settle the order itself — it has now actually been filled.
106 UPDATE project.orders
107 SET status = 'executed', executed_at = now()
108 WHERE id = $order_id;
109
110 COMMIT;
111 ```
112
1137. System confirms: `Order executed: sell 0.5000 ETH @ 3520.000000 (notional 1760.0000 USD)`.
114
115### Alternate flow 5a — insufficient holding
116
117If the holding row is missing, or `quantity - reserved_quantity < $qty`, the
118entire transaction rolls back — including the `open` order from step 5, which
119was never committed — and system shows:
120`"Insufficient holding: trying to sell X, available Y (of Z held, W reserved)."`
121
122### Worked example — the case this fixes
123
124Alice holds 2 BTC, `reserved_quantity = 0`, and places `sell 0.5 BTC`:
125
126| | quantity | reserved_quantity | available |
127|---|---|---|---|
128| before | 2.0000 | 0.0000 | 2.0000 |
129| after step (c) — reserved | 2.0000 | 0.5000 | 1.5000 |
130| after step (d) — settled | 1.5000 | 0.0000 | 1.5000 |
131
132If a second sell for more than 1.5 BTC is placed concurrently, its own
133`SELECT … FOR UPDATE` in step 5b blocks until the first transaction commits,
134then sees the reduced `quantity` and correctly reports insufficient holding —
135proven under real concurrency in
136[UseCase0005Implementation](../P4-Prototype/UseCase0005Implementation.md).
137
138### Realised P/L (post-scenario)
139
140The realised P/L for a sell is `$notional - ($avg_price * $qty)`. It is not persisted explicitly but can be computed from the ledger and the holding at sell time.
Note: See TracBrowser for help on using the repository browser.