source: docs/P3-UseCaseModel/wiki/UseCase0005.md@ 0cee8ec

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

Wiki docs, phase 6 and phase 7 added

  • Property mode set to 100644
File size: 5.4 KB
RevLine 
[ef1c1c7]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[wiki:PrototypeImplementation]) 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
27 1. Trader chooses "Place market SELL order".
28 2. 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{{{
33SELECT 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}}}
44
45 3. Trader picks the holding by its number in the listed holdings, e.g. `2` (ETH), and
46 enters the quantity.
47 4. System takes the chosen row's market id and crypto id from the list (no lookup by
48 symbol) and looks up the latest price:
49
50{{{
51SELECT price FROM project.v_latest_prices WHERE market_id = $1;
52}}}
53
54 5. System opens a transaction:
55
56{{{
57BEGIN;
58
59-- (a) record intent — no trade has happened yet.
60INSERT INTO project.orders
61 (user_id, market_id, side, type, status, quantity, price)
62VALUES
63 ($user_id, $market_id, 'sell', 'market', 'open', $qty, $price)
64RETURNING id; -- $order_id
65
66-- (b) lock the holding and check what is actually free to sell.
67SELECT quantity, reserved_quantity, avg_price
68 FROM project.holdings
69 WHERE user_id = $user_id AND crypto_id = $crypto_id
70 FOR UPDATE;
71-- available := quantity - reserved_quantity
72-- abort if row missing or available < $qty
73}}}
74
75 6. If the check passes, system reserves the crypto, then — since this is a market order — settles it immediately, all inside the same transaction:
76
77{{{
78-- (c) reserve: committed to this order, not yet removed from the position.
79UPDATE project.holdings
80 SET reserved_quantity = reserved_quantity + $qty,
81 updated_at = now()
82 WHERE user_id = $user_id AND crypto_id = $crypto_id;
83
84-- (d) settle: release the reservation and remove the asset in one step.
85UPDATE project.holdings
86 SET quantity = quantity - $qty,
87 reserved_quantity = reserved_quantity - $qty,
88 updated_at = now()
89 WHERE user_id = $user_id AND crypto_id = $crypto_id;
90
91UPDATE project.users
92 SET available_balance = available_balance + $notional,
93 invested_balance = GREATEST(invested_balance - ($avg_price * $qty), 0),
94 updated_at = now()
95 WHERE id = $user_id;
96
97INSERT INTO project.transactions
98 (user_id, type, amount, currency, related_order, description)
99VALUES
100 ($user_id, 'sell', $notional, 'USD', $order_id, 'Market sell ...');
101
102INSERT INTO project.market_trades
103 (market_id, executed_at, price, quantity, side, source)
104VALUES
105 ($market_id, now(), $price, $qty, 'sell', 'user');
106
107-- (e) settle the order itself — it has now actually been filled.
108UPDATE project.orders
109 SET status = 'executed', executed_at = now()
110 WHERE id = $order_id;
111
112COMMIT;
113}}}
114
115 7. System confirms: `Order executed: sell 0.5000 ETH @ 3520.000000 (notional 1760.0000 USD)`.
116
117=== Alternate flow 5a — insufficient holding ===
118
119If the holding row is missing, or `quantity - reserved_quantity < $qty`, the
120entire transaction rolls back — including the `open` order from step 5, which
121was never committed — and system shows:
122`"Insufficient holding: trying to sell X, available Y (of Z held, W reserved)."`
123
124=== Worked example — the case this fixes ===
125
126Alice holds 2 BTC, `reserved_quantity = 0`, and places `sell 0.5 BTC`:
127
128||= =||= quantity =||= reserved_quantity =||= available =||
129|| before || 2.0000 || 0.0000 || 2.0000 ||
130|| after step (c) — reserved || 2.0000 || 0.5000 || 1.5000 ||
131|| after step (d) — settled || 1.5000 || 0.0000 || 1.5000 ||
132
133If a second sell for more than 1.5 BTC is placed concurrently, its own
134`SELECT … FOR UPDATE` in step 5b blocks until the first transaction commits,
135then sees the reduced `quantity` and correctly reports insufficient holding —
136proven under real concurrency in
137[wiki:UseCase0005Implementation].
138
139=== Realised P/L (post-scenario) ===
140
141The 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.