Changeset 9577c79 for server/trade.go


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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • server/trade.go

    rdf05838 r9577c79  
    1313// Runs inside a single database transaction so the orders, holdings,
    1414// users.balance and transactions tables always agree.
     15//
     16// The order still passes through 'open' before 'executed'. Placing it
     17// reserves whatever it commits — on a sell, the crypto being sold, tracked in
     18// holdings.reserved_quantity — before anything is actually moved, so a
     19// second order against the same holding can never be granted the same units
     20// twice. Because only market orders are implemented, reserve and settle
     21// happen inside this one transaction rather than across two commits; a
     22// future limit-order matcher would split them into a second transaction
     23// later, without needing a schema change.
    1524func PlaceOrder(s *Session, side string) {
    1625        if side != "buy" && side != "sell" {
    … …  
    4756        defer tx.Rollback()
    4857
    49         // 1. create the order (status='executed' since we fill immediately)
     58        // 1. record the order as 'open' — no trade has happened yet.
    5059        var orderID string
    5160        err = tx.QueryRow(
    52                 `INSERT INTO orders (user_id, market_id, side, type, status, quantity, price, executed_at)
    53                  VALUES ($1, $2, $3, 'market', 'executed', $4, $5, now())
     61                `INSERT INTO orders (user_id, market_id, side, type, status, quantity, price)
     62                 VALUES ($1, $2, $3, 'market', 'open', $4, $5)
    5463                 RETURNING id`,
    5564                s.UserID, m.ID, side, qty, price,
    … …  
    8796                }
    8897
    89                 // upsert holding with running weighted average
     98                // a buy never reserves crypto, only ever adds it — upsert holding
     99                // with running weighted average
    90100                if err := upsertHoldingOnBuy(tx, s.UserID, m.CryptoID, qty, price); err != nil {
    91101                        fmt.Println("Error updating holding:", err)
    … …  
    104114                }
    105115        } else {
    106                 // sell: check holding
    107                 var held, avgPrice float64
     116                // sell: lock the holding and check what is actually free to sell —
     117                // quantity minus whatever another open order has already reserved.
     118                var held, reserved, avgPrice float64
    108119                err := tx.QueryRow(
    109                         `SELECT quantity, avg_price FROM holdings
     120                        `SELECT quantity, reserved_quantity, avg_price FROM holdings
    110121                          WHERE user_id = $1 AND crypto_id = $2 FOR UPDATE`,
    111122                        s.UserID, m.CryptoID,
    112                 ).Scan(&held, &avgPrice)
     123                ).Scan(&held, &reserved, &avgPrice)
    113124                if err != nil && err != sql.ErrNoRows {
    114125                        fmt.Println("Error:", err)
    115126                        return
    116127                }
    117                 if err == sql.ErrNoRows || held < qty {
    118                         fmt.Printf("Insufficient holding: trying to sell %.4f, hold %.4f\n", qty, held)
    119                         return
    120                 }
    121 
    122                 // reduce holding
     128                available := held - reserved
     129                if err == sql.ErrNoRows || available < qty {
     130                        fmt.Printf("Insufficient holding: trying to sell %.4f, available %.4f (of %.4f held, %.4f reserved)\n",
     131                                qty, available, held, reserved)
     132                        return
     133                }
     134
     135                // reserve: committed to this order, not yet removed from the position.
    123136                if _, err := tx.Exec(
    124137                        `UPDATE holdings
    125                             SET quantity   = quantity - $1,
    126                                 updated_at = now()
     138                            SET reserved_quantity = reserved_quantity + $1,
     139                                updated_at        = now()
     140                          WHERE user_id = $2 AND crypto_id = $3`,
     141                        qty, s.UserID, m.CryptoID,
     142                ); err != nil {
     143                        fmt.Println("Error:", err)
     144                        return
     145                }
     146
     147                // settle: a market order fills immediately, so release the
     148                // reservation and remove the asset from the position in one step.
     149                if _, err := tx.Exec(
     150                        `UPDATE holdings
     151                            SET quantity          = quantity - $1,
     152                                reserved_quantity = reserved_quantity - $1,
     153                                updated_at        = now()
    127154                          WHERE user_id = $2 AND crypto_id = $3`,
    128155                        qty, s.UserID, m.CryptoID,
    … …  
    163190                 VALUES ($1, now(), $2, $3, $4, 'user')`,
    164191                m.ID, price, qty, side,
     192        ); err != nil {
     193                fmt.Println("Error:", err)
     194                return
     195        }
     196
     197        // settle the order itself: it has now actually been filled.
     198        if _, err := tx.Exec(
     199                `UPDATE orders SET status = 'executed', executed_at = now() WHERE id = $1`,
     200                orderID,
    165201        ); err != nil {
    166202                fmt.Println("Error:", err)
Note: See TracChangeset for help on using the changeset viewer.