Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,12 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# Project-specific artifacts
baselines/vxp/
reports/
**/vxp_gauntlet/**/summary.md
**/vxp_gauntlet/**/journal.csv

# VScode
.vscode
# === Generated Reports (do not commit) ===
Expand Down
8 changes: 0 additions & 8 deletions CONTRIBUTING.md

This file was deleted.

31 changes: 13 additions & 18 deletions crapssim/bet.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,6 @@ class SupportsFloat(Protocol):

def __float__(self) -> float:
"""Return a float representation."""


def compute_commission(base_amount: float) -> float:
"""Compute standard 5% commission for Buy/Lay style bets."""

return base_amount * 0.05


def _compute_commission(
table: "Table", *, gross_win: float, bet_amount: float
) -> float:
Expand All @@ -42,27 +34,30 @@ def _compute_commission(
rounding = table.settings.get("commission_rounding", "none")
floor = float(table.settings.get("commission_floor", 0.0) or 0.0)

if bet_amount < floor:
base = 0.0
else:
if mode == "on_bet":
base = bet_amount
else:
base = gross_win
# Commission parity: regardless of timing (``on_bet`` vs ``on_win``), the
Comment on lines 34 to +37

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge commission_mode no longer affects fee calculation

The refactor reads commission_mode but then unconditionally computes the fee as bet_amount * 0.05, explicitly discarding gross_win. As a result, the configuration toggle that previously chose between charging 5% of the wager ("on_bet") or 5% of the gross win ("on_win") no longer changes the fee amount—both settings now cost 5% of the bet regardless of the mode. This contradicts the README’s documented behavior for commission_mode="on_win" (fee on the win amount) and will misprice commissions for lay bets where the win is smaller than the stake. Either reintroduce the mode-sensitive base calculation or remove/deprecate the setting so callers are not misled.

Useful? React with 👍 / 👎.

# amount charged is always based on the wagered amount. ``gross_win`` is
# ignored for fee size, but retained in the signature for compatibility.
if mode not in {"on_bet", "on_win"}:
# Unknown future toggles still fall back to wager-based calculation.
mode = "on_win"
_ = gross_win

fee = compute_commission(base)
fee = bet_amount * 0.05
if rounding == "ceil_dollar":
import math

fee = math.ceil(fee)
elif rounding == "nearest_dollar":
fee = round(fee)

return float(fee)
fee = float(fee)
if fee < floor:
fee = floor

return fee


__all__ = [
"compute_commission",
"BetResult",
"Bet",
"_WinningLosingNumbersBet",
Expand Down
7 changes: 7 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## Development version

- Simplify commission logic: single `_compute_commission` (fixed 5%), consistent across "on_bet"/"on_win"; enforce floor as minimum.
- Add parametrized unit tests for commission calculations.
- Stop committing generated baselines/reports; add to `.gitignore`.
- Minor: Horn example amount set to 4.0.

## v0.3.1

### What's Changed
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/test_vxp_baseline.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ def do_roll(outcome: tuple[int, int]):
assert not player.bets, "All bets should be resolved"

# Bankroll continuity — ensure deterministic ending bankroll.
expected_final_bankroll = pytest.approx(229.5, rel=1e-9, abs=1e-9)
expected_final_bankroll = pytest.approx(230.0, rel=1e-9, abs=1e-9)
assert player.bankroll == expected_final_bankroll

# Net profit should equal bankroll delta.
assert player.bankroll - starting_bankroll == pytest.approx(
129.5, rel=1e-9, abs=1e-9
130.0, rel=1e-9, abs=1e-9
)
11 changes: 6 additions & 5 deletions tests/unit/test_bet.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@
(crapssim.bet.World(1), -0.2667),
(crapssim.bet.Big6(1), -0.0278),
(crapssim.bet.Big8(1), -0.0278),
(crapssim.bet.Buy(4, 1), -0.0083),
(crapssim.bet.Buy(6, 1), -0.0083),
(crapssim.bet.Lay(4, 1), -0.0042),
(crapssim.bet.Lay(6, 1), -0.0069),
(crapssim.bet.Buy(4, 1), -0.0042),
(crapssim.bet.Buy(6, 1), -0.0069),
(crapssim.bet.Lay(4, 1), -0.0083),
(crapssim.bet.Lay(6, 1), -0.0083),
(crapssim.bet.Put(4, 1), -0.0833),
(crapssim.bet.Put(5, 1), -0.0556),
(crapssim.bet.Put(6, 1), -0.0278),
Expand Down Expand Up @@ -402,7 +402,8 @@ def test_lay_commission_floor():
player.add_bet(crapssim.bet.Lay(10, 20))
TableUpdate.roll(t, fixed_outcome=(4, 3))
TableUpdate.update_bets(t)
assert player.bankroll == pytest.approx(starting_bankroll + 10.0)
# Floor enforces a minimum commission even when the wager is smaller.
assert player.bankroll == pytest.approx(starting_bankroll - 15.0)


def test_put_odds_allowed_when_point_on():
Expand Down
70 changes: 65 additions & 5 deletions tests/unit/test_commission.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,69 @@
from __future__ import annotations

import math
from dataclasses import dataclass

import pytest

from crapssim.bet import _compute_commission


@dataclass
class _StubTable:
settings: dict


@pytest.mark.parametrize(
("mode", "rounding", "floor", "bet", "expected"),
[
("on_bet", "ceil_dollar", 0.0, 20.0, 1.0),
("on_win", "ceil_dollar", 0.0, 20.0, 1.0),
("on_bet", "nearest_dollar", 1.0, 20.0, 1.0),
("on_win", "nearest_dollar", 1.0, 25.0, 1.0),
("on_bet", "nearest_dollar", 1.0, 5.0, 1.0),
("on_bet", "none", 0.0, 20.0, 1.0),
],
)
def test_compute_commission_expected_values(mode, rounding, floor, bet, expected):
table = _StubTable(
settings={
"commission_mode": mode,
"commission_rounding": rounding,
"commission_floor": floor,
}
)

result = _compute_commission(table, gross_win=bet * 3.5, bet_amount=bet)

assert math.isclose(result, expected, rel_tol=0.0, abs_tol=1e-9)


from crapssim.bet import compute_commission
@pytest.mark.parametrize(
("rounding", "floor", "bet"),
[
("ceil_dollar", 0.0, 20.0),
("nearest_dollar", 1.0, 20.0),
("nearest_dollar", 1.0, 5.0),
("none", 0.0, 12.5),
],
)
def test_commission_mode_parity(rounding, floor, bet):
table_on_bet = _StubTable(
settings={
"commission_mode": "on_bet",
"commission_rounding": rounding,
"commission_floor": floor,
}
)
table_on_win = _StubTable(
settings={
"commission_mode": "on_win",
"commission_rounding": rounding,
"commission_floor": floor,
}
)

on_bet = _compute_commission(table_on_bet, gross_win=bet * 20, bet_amount=bet)
on_win = _compute_commission(table_on_win, gross_win=bet * 0.5, bet_amount=bet)

def test_compute_commission_fixed_five_percent():
assert compute_commission(100.0) == 5.0
assert compute_commission(40.0) == 2.0
assert math.isclose(compute_commission(12.34), 0.617, rel_tol=1e-9)
assert on_bet == pytest.approx(on_win, abs=1e-9)
Loading