Changes between Initial Version and Version 1 of UC0005


Ignore:
Timestamp:
09/24/26 13:50:26 (4 days ago)
Author:
231285
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • UC0005

    v1 v1  
     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) 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 markets (same SQL as UC0004 step 2).
     29 3. Trader enters market symbol and quantity.
     30 4. System resolves the market and looks up the latest price (same SQL as UC0004 step 4).
     31 5. System opens a transaction:
     32
     33{{{
     34BEGIN;
     35
     36-- (a) record intent — no trade has happened yet.
     37INSERT INTO project.orders
     38    (user_id, market_id, side, type, status, quantity, price)
     39VALUES
     40    ($user_id, $market_id, 'sell', 'market', 'open', $qty, $price)
     41RETURNING id;   -- $order_id
     42
     43-- (b) lock the holding and check what is actually free to sell.
     44SELECT quantity, reserved_quantity, avg_price
     45  FROM project.holdings
     46 WHERE user_id = $user_id AND crypto_id = $crypto_id
     47 FOR UPDATE;
     48-- available := quantity - reserved_quantity
     49-- abort if row missing or available < $qty
     50}}}
     51
     52 6. If the check passes, system reserves the crypto, then — since this is a market order — settles it immediately, all inside the same transaction:
     53
     54{{{
     55-- (c) reserve: committed to this order, not yet removed from the position.
     56UPDATE project.holdings
     57   SET reserved_quantity = reserved_quantity + $qty,
     58       updated_at        = now()
     59 WHERE user_id = $user_id AND crypto_id = $crypto_id;
     60
     61-- (d) settle: release the reservation and remove the asset in one step.
     62UPDATE project.holdings
     63   SET quantity          = quantity - $qty,
     64       reserved_quantity = reserved_quantity - $qty,
     65       updated_at        = now()
     66 WHERE user_id = $user_id AND crypto_id = $crypto_id;
     67
     68UPDATE project.users
     69   SET available_balance = available_balance + $notional,
     70       invested_balance  = GREATEST(invested_balance - ($avg_price * $qty), 0),
     71       updated_at        = now()
     72 WHERE id = $user_id;
     73
     74INSERT INTO project.transactions
     75    (user_id, type, amount, currency, related_order, description)
     76VALUES
     77    ($user_id, 'sell', $notional, 'USD', $order_id, 'Market sell ...');
     78
     79INSERT INTO project.market_trades
     80    (market_id, executed_at, price, quantity, side, source)
     81VALUES
     82    ($market_id, now(), $price, $qty, 'sell', 'user');
     83
     84-- (e) settle the order itself — it has now actually been filled.
     85UPDATE project.orders
     86   SET status = 'executed', executed_at = now()
     87 WHERE id = $order_id;
     88
     89COMMIT;
     90}}}
     91
     92 7. System confirms: `Order executed: sell 0.5000 ETH @ 3520.000000 (notional 1760.0000 USD)`.
     93
     94=== Alternate flow 5a — insufficient holding ===
     95
     96If the holding row is missing, or `quantity - reserved_quantity < $qty`, the
     97entire transaction rolls back — including the `open` order from step 5, which
     98was never committed — and system shows:
     99`"Insufficient holding: trying to sell X, available Y (of Z held, W reserved)."`
     100
     101=== Worked example — the case this fixes ===
     102
     103Alice holds 2 BTC, `reserved_quantity = 0`, and places `sell 0.5 BTC`:
     104
     105||= =||= quantity =||= reserved_quantity =||= available =||
     106|| before || 2.0000 || 0.0000 || 2.0000 ||
     107|| after step (c) — reserved || 2.0000 || 0.5000 || 1.5000 ||
     108|| after step (d) — settled || 1.5000 || 0.0000 || 1.5000 ||
     109
     110If a second sell for more than 1.5 BTC is placed concurrently, its own
     111`SELECT … FOR UPDATE` in step 5b blocks until the first transaction commits,
     112then sees the reduced `quantity` and correctly reports insufficient holding —
     113proven under real concurrency in
     114UseCase0005Implementation.
     115
     116=== Realised P/L (post-scenario) ===
     117
     118The 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.