soma.cell

Brokerage fee calculator: tiered commission, bps fee with a floor, and a maker/taker exchange fee

Shows clamp/max for floors and caps, and match on a tier bucket.

A Soma program from the finance part of the verified corpus: 57 lines, cells FeeCalculator, FeeTests. It passes soma check with soma 3.2.4.

How it is built

The cell FeeCalculator declares 4 signals in its face: bps_fee(notional: Float, bps: Float, min_fee: Float) -> Float, tiered_commission(shares: Int) -> Float, exchange_fee(notional: Float, is_maker: Bool) -> Float and net_proceeds(notional: Float, bps: Float, min_fee: Float) -> Float. Handlers: bps_fee (line 14), tiered_commission (line 20), exchange_fee (line 31) and net_proceeds (line 37).

The file also holds 1 test cell (FeeTests) with 6 assertions, run by soma test.

Generated from soma describe on this file.

The source

fee_calculator.cell

// Brokerage fee calculator: tiered commission, bps fee with a floor,
// and a maker/taker exchange fee. Shows clamp/max for floors and caps,
// and match on a tier bucket.

cell FeeCalculator {
    face {
        signal bps_fee(notional: Float, bps: Float, min_fee: Float) -> Float
        signal tiered_commission(shares: Int) -> Float
        signal exchange_fee(notional: Float, is_maker: Bool) -> Float
        signal net_proceeds(notional: Float, bps: Float, min_fee: Float) -> Float
    }

    // fee = notional * bps/10000, but never below the minimum
    on bps_fee(notional: Float, bps: Float, min_fee: Float) {
        let raw = notional * bps / 10000.0
        return max(raw, min_fee)
    }

    // per-share commission that drops as volume grows
    on tiered_commission(shares: Int) {
        let rate = match shares {
            0..999       -> 0.010
            1000..9999   -> 0.005
            _            -> 0.002
        }
        let fee = shares * rate
        return max(fee, 1.0)
    }

    // makers get a rebate (negative fee), takers pay
    on exchange_fee(notional: Float, is_maker: Bool) {
        if is_maker { return notional * -0.0001 }
        return notional * 0.0003
    }

    // sell-side: what you keep after the fee
    on net_proceeds(notional: Float, bps: Float, min_fee: Float) {
        return notional - bps_fee(notional, bps, min_fee)
    }
}

cell test FeeTests {
    rules {
        // 1M notional at 2 bps = 200
        assert abs(bps_fee(1000000.0, 2.0, 1.0) - 200.0) < 0.0001
        // tiny trade hits the minimum floor
        assert abs(bps_fee(100.0, 2.0, 1.0) - 1.0) < 0.0001
        // mid tier: 5000 shares * 0.005 = 25
        assert abs(tiered_commission(5000) - 25.0) < 0.0001
        // maker rebate is negative
        assert exchange_fee(1000000.0, true) < 0.0
        // taker pays
        assert exchange_fee(1000000.0, false) > 0.0
        assert abs(net_proceeds(10000.0, 5.0, 1.0) - 9995.0) < 0.0001
    }
}

What the toolchain says

Run on a fresh copy of the program at build time; the outputs are pasted unedited.

$ soma check fee_calculator.cell
✓ All checks passed.
$ soma test fee_calculator.cell
test FeeTests ...
  ✓ assert abs(bps_fee(1000000.0, 2.0, 1.0) - 200.0) < 0.0001
  ✓ assert abs(bps_fee(100.0, 2.0, 1.0) - 1.0) < 0.0001
  ✓ assert abs(tiered_commission(5000) - 25.0) < 0.0001
  ✓ assert exchange_fee(1000000.0, true) < 0.0
  ✓ assert exchange_fee(1000000.0, false) > 0.0
  ✓ assert abs(net_proceeds(10000.0, 5.0, 1.0) - 9995.0) < 0.0001

6 tests: 6 passed, 0 failed
$ soma verify fee_calculator.cell
Verifying fee_calculator.cell...
No state machine in this program: nothing to prove beyond `soma check` (invariants and lifecycles are what verify proves).
VERIFY OK (vacuous — no state machine)

Builtins used

abs, max

Source file: /corpus/finance/fee_calculator.cell · examples/corpus/finance/fee_calculator.cell