Ignore:
Timestamp:
09/16/26 23:37:15 (13 days ago)
Author:
Stefan <trsunovstefan@…>
Branches:
main
Children:
8b447ef
Parents:
df05838
Message:

add reserved_quantity and modify the phases, add v_03.png and v_03.xml for P1

Location:
docs/P3-UseCaseModel
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • docs/P3-UseCaseModel/UseCase0004.md

    rdf05838 r9577c79  
    3737   BEGIN;
    3838
     39   -- (a) record intent — no trade has happened yet.
    3940   INSERT INTO project.orders
    40        (user_id, market_id, side, type, status, quantity, price, executed_at)
     41       (user_id, market_id, side, type, status, quantity, price)
    4142   VALUES
    42        ($user_id, $market_id, 'buy', 'market', 'executed', $qty, $price, now())
     43       ($user_id, $market_id, 'buy', 'market', 'open', $qty, $price)
    4344   RETURNING id;  -- captured as $order_id
    4445
     46   -- (b) lock and check the user balance
    4547   SELECT available_balance FROM project.users WHERE id = $user_id FOR UPDATE;
    4648   -- abort if available_balance < notional
    4749
     50   -- (c) move cash from available to invested. A buy never reserves crypto
     51   --     the way a sell does — it only ever adds to the position, so there
     52   --     is nothing on the holdings side to commit before settling.
    4853   UPDATE project.users
    4954      SET available_balance = available_balance - $notional,
    … …  
    5257    WHERE id = $user_id;
    5358
    54    -- Upsert holding with running weighted-average price:
     59   -- (d) upsert holding with running weighted-average price:
    5560   SELECT quantity, avg_price
    5661     FROM project.holdings
    … …  
    6166   -- new_avg = (old_qty*old_avg + $qty*$price) / (old_qty + $qty)
    6267
     68   -- (e) ledger entry
    6369   INSERT INTO project.transactions
    6470       (user_id, type, amount, currency, related_order, description)
    … …  
    6672       ($user_id, 'buy', -$notional, 'USD', $order_id, 'Market buy ...');
    6773
     74   -- (f) record the resulting market trade
    6875   INSERT INTO project.market_trades
    6976       (market_id, executed_at, price, quantity, side, source)
    7077   VALUES
    7178       ($market_id, now(), $price, $qty, 'buy', 'user');
     79
     80   -- (g) settle the order itself — it has now actually been filled.
     81   UPDATE project.orders
     82      SET status = 'executed', executed_at = now()
     83    WHERE id = $order_id;
    7284
    7385   COMMIT;
  • docs/P3-UseCaseModel/UseCase0005.md

    rdf05838 r9577c79  
    66
    77A 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.
    824
    925## Scenario
    … …  
    1834   BEGIN;
    1935
     36   -- (a) record intent — no trade has happened yet.
    2037   INSERT INTO project.orders
    21        (user_id, market_id, side, type, status, quantity, price, executed_at)
     38       (user_id, market_id, side, type, status, quantity, price)
    2239   VALUES
    23        ($user_id, $market_id, 'sell', 'market', 'executed', $qty, $price, now())
     40       ($user_id, $market_id, 'sell', 'market', 'open', $qty, $price)
    2441   RETURNING id;   -- $order_id
    2542
    26    SELECT quantity, avg_price
     43   -- (b) lock the holding and check what is actually free to sell.
     44   SELECT quantity, reserved_quantity, avg_price
    2745     FROM project.holdings
    2846    WHERE user_id = $user_id AND crypto_id = $crypto_id
    2947    FOR UPDATE;
    30    -- abort if row missing or quantity < $qty
     48   -- available := quantity - reserved_quantity
     49   -- abort if row missing or available < $qty
    3150   ```
    32 6. If the holding check passes, system reduces the holding, credits cash and debits invested, and appends a ledger and a market trade:
     51
     526. If the check passes, system reserves the crypto, then — since this is a market order — settles it immediately, all inside the same transaction:
    3353
    3454   ```sql
     55   -- (c) reserve: committed to this order, not yet removed from the position.
    3556   UPDATE project.holdings
    36       SET quantity   = quantity - $qty,
    37           updated_at = now()
     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.
     62   UPDATE project.holdings
     63      SET quantity          = quantity - $qty,
     64          reserved_quantity = reserved_quantity - $qty,
     65          updated_at        = now()
    3866    WHERE user_id = $user_id AND crypto_id = $crypto_id;
    3967
    … …  
    5482       ($market_id, now(), $price, $qty, 'sell', 'user');
    5583
     84   -- (e) settle the order itself — it has now actually been filled.
     85   UPDATE project.orders
     86      SET status = 'executed', executed_at = now()
     87    WHERE id = $order_id;
     88
    5689   COMMIT;
    5790   ```
     91
    58927. System confirms: `Order executed: sell 0.5000 ETH @ 3520.000000 (notional 1760.0000 USD)`.
    5993
    6094### Alternate flow 5a — insufficient holding
    6195
    62 If the `SELECT ... FOR UPDATE` returns no row, or the held quantity is smaller than the sell quantity, the entire transaction rolls back and system shows "Insufficient holding: trying to sell X, hold Y."
     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|---|---|---|---|
     107| before | 2.0000 | 0.0000 | 2.0000 |
     108| after step (c) — reserved | 2.0000 | 0.5000 | 1.5000 |
     109| after step (d) — settled | 1.5000 | 0.0000 | 1.5000 |
     110
     111If a second sell for more than 1.5 BTC is placed concurrently, its own
     112`SELECT … FOR UPDATE` in step 5b blocks until the first transaction commits,
     113then sees the reduced `quantity` and correctly reports insufficient holding —
     114proven under real concurrency in
     115[UseCase0005Implementation](../P4-Prototype/UseCase0005Implementation.md).
    63116
    64117### Realised P/L (post-scenario)
  • docs/P3-UseCaseModel/UseCase0006.md

    rdf05838 r9577c79  
    1717   SELECT symbol,
    1818          quantity,
     19          COALESCE(reserved_quantity,  0),
     20          COALESCE(available_quantity, quantity),
    1921          COALESCE(avg_price,      0),
    2022          COALESCE(current_price,  0),
    … …  
    2628    ORDER BY symbol;
    2729   ```
     30
     31   `reserved_quantity` is the amount committed to the Trader's own open sell
     32   orders (see [UseCase0005](UseCase0005.md)); `available_quantity` is what is
     33   actually free to sell right now.
    28343. System displays the rows and a computed summary:
    2935
    … …  
    5460       c.symbol,
    5561       h.quantity,
     62       h.reserved_quantity,
     63       (h.quantity - h.reserved_quantity)      AS available_quantity,
    5664       h.avg_price,
    5765       lp.price                                AS current_price,
  • docs/P3-UseCaseModel/UseCaseModel.md

    rdf05838 r9577c79  
    3838 * [https://github.com/StefanTrsunov/bp/blob/main/docs/P3-UseCaseModel/UseCase0005.md UC0005] –
    3939   '''Place market SELL order''' – Trader sells part or all of a holding at the current market
    40    price, which credits cash and preserves the cost basis.
     40   price, which reserves the crypto being sold, credits cash and preserves the cost basis.
    4141 * [https://github.com/StefanTrsunov/bp/blob/main/docs/P3-UseCaseModel/UseCase0006.md UC0006] –
    4242   '''View portfolio and transaction history''' – Trader inspects current holdings, unrealised
    … …  
    8888||[https://github.com/StefanTrsunov/bp/blob/main/docs/P3-UseCaseModel/UseCase0003.md UC0003 – Deposit]||High||Shows a multi-row transaction: `UPDATE users` plus `INSERT INTO transactions`.||
    8989||[https://github.com/StefanTrsunov/bp/blob/main/docs/P3-UseCaseModel/UseCase0004.md UC0004 – Buy]||Very high||Core of the exchange: `INSERT orders`, `UPDATE users`, upsert `holdings`, ledger entry, market trade.||
    90 ||[https://github.com/StefanTrsunov/bp/blob/main/docs/P3-UseCaseModel/UseCase0005.md UC0005 – Sell]||Very high||Dual of Buy; demonstrates row-level `FOR UPDATE` locking and cost-basis bookkeeping.||
     90||[https://github.com/StefanTrsunov/bp/blob/main/docs/P3-UseCaseModel/UseCase0005.md UC0005 – Sell]||Very high||Dual of Buy; demonstrates row-level `FOR UPDATE` locking, reservation of committed crypto (`holdings.reserved_quantity`) and cost-basis bookkeeping.||
    9191||[https://github.com/StefanTrsunov/bp/blob/main/docs/P3-UseCaseModel/UseCase0006.md UC0006 – Portfolio]||High||Demonstrates joins over `holdings`, `markets` and `crypto`, and the `v_portfolio` view.||
    9292||[https://github.com/StefanTrsunov/bp/blob/main/docs/P3-UseCaseModel/UseCase0007.md UC0007 – Watchlist]||Medium||Demonstrates N–M relation handling and `ON CONFLICT` upsert semantics.||
    … …  
    104104   kept in one place. Direct links:
    105105   [https://github.com/StefanTrsunov/bp/blob/main/docs/P1-ConceptualModel/ERModelAIUsage.md#session-1--2026-04-21 Session 1 – 2026-04-21],
    106    [https://github.com/StefanTrsunov/bp/blob/main/docs/P1-ConceptualModel/ERModelAIUsage.md#session-2--2026-08-06--2026-08-07 Session 2 – 2026-08-06/07].
     106   [https://github.com/StefanTrsunov/bp/blob/main/docs/P1-ConceptualModel/ERModelAIUsage.md#session-2--2026-08-06--2026-08-07 Session 2 – 2026-08-06/07],
     107   [https://github.com/StefanTrsunov/bp/blob/main/docs/P1-ConceptualModel/ERModelAIUsage.md#session-3--2026-09-16 Session 3 – 2026-09-16].
    107108
    108109'''Service:''' Claude Code (Anthropic), https://claude.com/claude-code – Claude subscription,
    109 model Claude Opus 4.7 (1M context).
     110model Claude Opus 4.7 (1M context) in sessions 1–2, Claude Sonnet 5 in session 3.
    110111
    111112'''In short:''' the AI proposed the actor taxonomy and drafted the seven use cases with their SQL
    112113in session 1. In session 2 the use-case model itself was '''not''' changed – the only work was
    113114re-executing every scenario, including the failure paths, against a live PostgreSQL 16 database.
     115In session 3, UC0004 and UC0005 were revised to reserve the resource an order commits (crypto on
     116a sell) before settling it, closing a gap where nothing stopped a second sell order from being
     117granted crypto already promised to a first one; see
     118[UseCaseModelAIUsage](UseCaseModelAIUsage.md#session-3--2026-09-16).
  • docs/P3-UseCaseModel/UseCaseModelAIUsage.md

    rdf05838 r9577c79  
    5555duplicate registration, wrong password). The results are documented per use case
    5656on the `UseCaseXXXXImplementation` pages.
     57
     58### Session 3 — 2026-09-16
     59
     60Driven by the design review logged in full in
     61[ERModelAIUsage](../P1-ConceptualModel/ERModelAIUsage.md#session-3--2026-09-16):
     62placing a sell order checked `holdings.quantity` directly, with no way to
     63record that part of a position was already promised to another, unsettled
     64order.
     65
     66**What changed:**
     67
     68- [UseCase0005](UseCase0005.md) — the scenario now reserves the crypto
     69  (`holdings.reserved_quantity`) before removing it from the position, checks
     70  `quantity - reserved_quantity` rather than raw `quantity`, and adds a
     71  worked example and a note on why the reserve and settle steps stay inside
     72  one transaction rather than two (only market orders are implemented, and
     73  splitting into two commits would risk an order stuck `open` with no cancel
     74  use case to recover it).
     75- [UseCase0004](UseCase0004.md) — no change to the balance logic, but the
     76  order insert now goes through `status='open'` before a final
     77  `UPDATE ... SET status='executed'`, matching the sell side, so `Orders`
     78  genuinely has the lifecycle [ERModel](../P1-ConceptualModel/ERModel.md)
     79  describes for it rather than a status column that is only ever written
     80  once.
     81- [UseCase0006](UseCase0006.md) — the `v_portfolio` reference and its query
     82  gained `reserved_quantity`/`available_quantity`, since the portfolio screen
     83  is where a Trader would actually see the new field.
     84- The use-case importance table and UC0005's one-line description in
     85  [UseCaseModel](UseCaseModel.md) were reworded to mention the reservation.
     86
     87Every changed scenario's SQL was re-run against the live database, including a
     88two-concurrent-sells test that reproduces the exact bug being fixed: see
     89[UseCase0005Implementation](../P4-Prototype/UseCase0005Implementation.md).
     90
     91**What I decided:** to keep this a revision of the existing UC0004/UC0005
     92pages rather than a new use case (e.g. "cancel order") — `cancelled` remains
     93an unused status, same as before, since nothing in the prototype produces it
     94and inventing a cancel flow was not what the review asked for.
Note: See TracChangeset for help on using the changeset viewer.