FAILURE MAP
← Case archive

FA-84706 / Betting odds conversion / Open access

Partial cash-out leaves the wrong remaining stake · case 01

After cashing out half the value, the remaining stake is the cashed portion or the stake minus cents.

Verified by executionVariant 1 · 9 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

The remaining stake is computed as stake minus the cashed amount.

VERIFIED REPAIR

Reduce the stake by the cashed fraction of the value.

Unsuccessful approach: Using the cashed proportion instead of the remaining proportion swaps the two parts.

Case contract

Cash-out offer for a single or multiple back bet. legs rows are [price taken, current] where current is a decimal price for an open leg, "won" or "lost". Any lost leg makes the offer [0, 0]. Otherwise value = stake * product of prices taken / product of current prices of open legs, reduced by margin_pct percent, rounded down to a cent. partial_cents 0 means full cash-out: return [value, 0]. A partial amount above the value returns "invalid"; otherwise return [partial, remaining stake] with remaining = floor(stake * (1 - partial / value)).

Why this case matters

Cash-out and partial cash-out offers are recomputed from live prices on every tick.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
from fractions import Fraction
import math
N = 1
observations = []
def solve(legs, stake_cents, margin_pct, partial_cents):
    value = Fraction(stake_cents)
    for price, cur in legs:
        if cur == 'lost':
            return [0, 0]
        value *= Fraction(price)
        if cur != 'won':
            value /= Fraction(cur)
    value = value * (100 - Fraction(margin_pct)) / 100
    full = math.floor(value)
    if partial_cents == 0:
        return [full, 0]
    if partial_cents > full:
        return 'invalid'
    remaining = stake_cents - partial_cents
    return [partial_cents, remaining]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
def run(args):
    try:
        return solve(*args)
    except Exception as exc:
        return 'raised ' + type(exc).__name__
cases = [[('control single shortened', ([['3.00', '2.00']], 1000, '0', 0), [1500, 0]),
  ('control margin applied', ([['3.00', '2.00']], 1000, '5', 0), [1425, 0]),
  ('boundary won leg keeps price', ([['2.00', 'won'], ['2.00', '1.50']], 1000, '0', 0), [2666, 0]),
  ('boundary partial cash out', ([['3.00', '2.00']], 1000, '0', 500), [500, 666]),
  ('control lost leg', ([['2.00', 'lost'], ['2.00', '1.50']], 1000, '0', 0), [0, 0]),
  ('boundary partial above value', ([['2.00', '4.00']], 1000, '0', 600), 'invalid'),
  ('regression: partial remaining stake', ([['2.79', 'won']], 500, '5', 800), [800, 198]),
  ('variant scenario 1',
   ([['1.51', 'won'], ['3.49', 'won'], ['3.39', 'lost'], ['1.99', 'won']], 500, '7.5', 0),
   [0, 0]),
  ('variant scenario 2',
   ([['3.71', 'lost'], ['1.47', '4.60'], ['1.32', 'won'], ['1.57', '2.91']], 1000, '5', 200),
   [0, 0])],
 [('control single shortened', ([['3.00', '2.00']], 1000, '0', 0), [1500, 0]),
  ('control margin applied', ([['3.00', '2.00']], 1000, '5', 0), [1425, 0]),
  ('boundary won leg keeps price', ([['2.00', 'won'], ['2.00', '1.50']], 1000, '0', 0), [2666, 0]),
  ('boundary partial cash out', ([['3.00', '2.00']], 1000, '0', 500), [500, 666]),
  ('control lost leg', ([['2.00', 'lost'], ['2.00', '1.50']], 1000, '0', 0), [0, 0]),
  ('boundary partial above value', ([['2.00', '4.00']], 1000, '0', 600), 'invalid'),
  ('regression: partial remaining stake',
   ([['3.43', '4.22'], ['4.58', 'won'], ['2.70', 'won']], 1000, '5', 500),
   [500, 947]),
  ('variant scenario 1', ([['5.00', '5.10']], 500, '0', 0), [490, 0]),
  ('variant scenario 2',
   ([['4.09', '4.06'], ['1.44', 'lost'], ['4.51', '1.56'], ['4.68', 'won']], 1000, '0', 0),
   [0, 0])],
 [('control single shortened', ([['3.00', '2.00']], 1000, '0', 0), [1500, 0]),
  ('control margin applied', ([['3.00', '2.00']], 1000, '5', 0), [1425, 0]),
  ('boundary won leg keeps price', ([['2.00', 'won'], ['2.00', '1.50']], 1000, '0', 0), [2666, 0]),
  ('boundary partial cash out', ([['3.00', '2.00']], 1000, '0', 500), [500, 666]),
  ('control lost leg', ([['2.00', 'lost'], ['2.00', '1.50']], 1000, '0', 0), [0, 0]),
  ('boundary partial above value', ([['2.00', '4.00']], 1000, '0', 600), 'invalid'),
  ('regression: partial remaining stake', ([['3.79', '2.66']], 1000, '7.5', 500), [500, 620]),
  ('variant scenario 1', ([['4.71', 'won']], 1000, '0', 0), [4710, 0]),
  ('variant scenario 2', ([['1.46', '1.60']], 500, '5', 500), 'invalid')],
 [('control single shortened', ([['3.00', '2.00']], 1000, '0', 0), [1500, 0]),
  ('control margin applied', ([['3.00', '2.00']], 1000, '5', 0), [1425, 0]),
  ('boundary won leg keeps price', ([['2.00', 'won'], ['2.00', '1.50']], 1000, '0', 0), [2666, 0]),
  ('boundary partial cash out', ([['3.00', '2.00']], 1000, '0', 500), [500, 666]),
  ('control lost leg', ([['2.00', 'lost'], ['2.00', '1.50']], 1000, '0', 0), [0, 0]),
  ('boundary partial above value', ([['2.00', '4.00']], 1000, '0', 600), 'invalid'),
  ('regression: partial remaining stake',
   ([['3.65', 'won'], ['2.14', '1.31'], ['2.44', '2.91']], 1000, '5', 500),
   [500, 894]),
  ('variant scenario 1',
   ([['3.39', 'won'], ['2.49', '5.36'], ['1.49', '4.00'], ['3.15', '5.06']], 2500, '5', 0),
   [867, 0]),
  ('variant scenario 2', ([['2.52', 'lost'], ['4.68', '4.00']], 1000, '5', 0), [0, 0])],
 [('control single shortened', ([['3.00', '2.00']], 1000, '0', 0), [1500, 0]),
  ('control margin applied', ([['3.00', '2.00']], 1000, '5', 0), [1425, 0]),
  ('boundary won leg keeps price', ([['2.00', 'won'], ['2.00', '1.50']], 1000, '0', 0), [2666, 0]),
  ('boundary partial cash out', ([['3.00', '2.00']], 1000, '0', 500), [500, 666]),
  ('control lost leg', ([['2.00', 'lost'], ['2.00', '1.50']], 1000, '0', 0), [0, 0]),
  ('boundary partial above value', ([['2.00', '4.00']], 1000, '0', 600), 'invalid'),
  ('regression: partial remaining stake', ([['4.93', 'won']], 2500, '5', 500), [500, 2393]),
  ('variant scenario 1',
   ([['3.53', '1.91'], ['3.32', 'lost'], ['1.63', 'won'], ['2.97', '4.57']], 500, '0', 500),
   [0, 0]),
  ('variant scenario 2', ([['2.73', 'won'], ['2.11', '4.03']], 1000, '0', 0), [1429, 0])]]
for label, args, expected in cases[N - 1]:
    check(label, run(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
control single shortened[1500, 0][1500, 0]Passed
control margin applied[1425, 0][1425, 0]Passed
boundary won leg keeps price[2666, 0][2666, 0]Passed
boundary partial cash out[500, 500][500, 666]Failed
control lost leg[0, 0][0, 0]Passed
boundary partial above valueinvalidinvalidPassed
regression: partial remaining stake[800, -300][800, 198]Failed
variant scenario 1[0, 0][0, 0]Passed
variant scenario 2[0, 0][0, 0]Passed

SHA-256 / 3f0213d417d3bb65b230cdf24b360a97ff42082fd2ce223d80ab528dd47b6c8e

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
from fractions import Fraction
import math
N = 1
observations = []
def solve(legs, stake_cents, margin_pct, partial_cents):
    value = Fraction(stake_cents)
    for price, cur in legs:
        if cur == 'lost':
            return [0, 0]
        value *= Fraction(price)
        if cur != 'won':
            value /= Fraction(cur)
    value = value * (100 - Fraction(margin_pct)) / 100
    full = math.floor(value)
    if partial_cents == 0:
        return [full, 0]
    if partial_cents > full:
        return 'invalid'
    remaining = math.floor(stake_cents * Fraction(partial_cents, full))
    return [partial_cents, remaining]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
def run(args):
    try:
        return solve(*args)
    except Exception as exc:
        return 'raised ' + type(exc).__name__
cases = [[('control single shortened', ([['3.00', '2.00']], 1000, '0', 0), [1500, 0]),
  ('control margin applied', ([['3.00', '2.00']], 1000, '5', 0), [1425, 0]),
  ('boundary won leg keeps price', ([['2.00', 'won'], ['2.00', '1.50']], 1000, '0', 0), [2666, 0]),
  ('boundary partial cash out', ([['3.00', '2.00']], 1000, '0', 500), [500, 666]),
  ('control lost leg', ([['2.00', 'lost'], ['2.00', '1.50']], 1000, '0', 0), [0, 0]),
  ('boundary partial above value', ([['2.00', '4.00']], 1000, '0', 600), 'invalid'),
  ('regression: partial remaining stake', ([['2.79', 'won']], 500, '5', 800), [800, 198]),
  ('variant scenario 1',
   ([['1.51', 'won'], ['3.49', 'won'], ['3.39', 'lost'], ['1.99', 'won']], 500, '7.5', 0),
   [0, 0]),
  ('variant scenario 2',
   ([['3.71', 'lost'], ['1.47', '4.60'], ['1.32', 'won'], ['1.57', '2.91']], 1000, '5', 200),
   [0, 0])],
 [('control single shortened', ([['3.00', '2.00']], 1000, '0', 0), [1500, 0]),
  ('control margin applied', ([['3.00', '2.00']], 1000, '5', 0), [1425, 0]),
  ('boundary won leg keeps price', ([['2.00', 'won'], ['2.00', '1.50']], 1000, '0', 0), [2666, 0]),
  ('boundary partial cash out', ([['3.00', '2.00']], 1000, '0', 500), [500, 666]),
  ('control lost leg', ([['2.00', 'lost'], ['2.00', '1.50']], 1000, '0', 0), [0, 0]),
  ('boundary partial above value', ([['2.00', '4.00']], 1000, '0', 600), 'invalid'),
  ('regression: partial remaining stake',
   ([['3.43', '4.22'], ['4.58', 'won'], ['2.70', 'won']], 1000, '5', 500),
   [500, 947]),
  ('variant scenario 1', ([['5.00', '5.10']], 500, '0', 0), [490, 0]),
  ('variant scenario 2',
   ([['4.09', '4.06'], ['1.44', 'lost'], ['4.51', '1.56'], ['4.68', 'won']], 1000, '0', 0),
   [0, 0])],
 [('control single shortened', ([['3.00', '2.00']], 1000, '0', 0), [1500, 0]),
  ('control margin applied', ([['3.00', '2.00']], 1000, '5', 0), [1425, 0]),
  ('boundary won leg keeps price', ([['2.00', 'won'], ['2.00', '1.50']], 1000, '0', 0), [2666, 0]),
  ('boundary partial cash out', ([['3.00', '2.00']], 1000, '0', 500), [500, 666]),
  ('control lost leg', ([['2.00', 'lost'], ['2.00', '1.50']], 1000, '0', 0), [0, 0]),
  ('boundary partial above value', ([['2.00', '4.00']], 1000, '0', 600), 'invalid'),
  ('regression: partial remaining stake', ([['3.79', '2.66']], 1000, '7.5', 500), [500, 620]),
  ('variant scenario 1', ([['4.71', 'won']], 1000, '0', 0), [4710, 0]),
  ('variant scenario 2', ([['1.46', '1.60']], 500, '5', 500), 'invalid')],
 [('control single shortened', ([['3.00', '2.00']], 1000, '0', 0), [1500, 0]),
  ('control margin applied', ([['3.00', '2.00']], 1000, '5', 0), [1425, 0]),
  ('boundary won leg keeps price', ([['2.00', 'won'], ['2.00', '1.50']], 1000, '0', 0), [2666, 0]),
  ('boundary partial cash out', ([['3.00', '2.00']], 1000, '0', 500), [500, 666]),
  ('control lost leg', ([['2.00', 'lost'], ['2.00', '1.50']], 1000, '0', 0), [0, 0]),
  ('boundary partial above value', ([['2.00', '4.00']], 1000, '0', 600), 'invalid'),
  ('regression: partial remaining stake',
   ([['3.65', 'won'], ['2.14', '1.31'], ['2.44', '2.91']], 1000, '5', 500),
   [500, 894]),
  ('variant scenario 1',
   ([['3.39', 'won'], ['2.49', '5.36'], ['1.49', '4.00'], ['3.15', '5.06']], 2500, '5', 0),
   [867, 0]),
  ('variant scenario 2', ([['2.52', 'lost'], ['4.68', '4.00']], 1000, '5', 0), [0, 0])],
 [('control single shortened', ([['3.00', '2.00']], 1000, '0', 0), [1500, 0]),
  ('control margin applied', ([['3.00', '2.00']], 1000, '5', 0), [1425, 0]),
  ('boundary won leg keeps price', ([['2.00', 'won'], ['2.00', '1.50']], 1000, '0', 0), [2666, 0]),
  ('boundary partial cash out', ([['3.00', '2.00']], 1000, '0', 500), [500, 666]),
  ('control lost leg', ([['2.00', 'lost'], ['2.00', '1.50']], 1000, '0', 0), [0, 0]),
  ('boundary partial above value', ([['2.00', '4.00']], 1000, '0', 600), 'invalid'),
  ('regression: partial remaining stake', ([['4.93', 'won']], 2500, '5', 500), [500, 2393]),
  ('variant scenario 1',
   ([['3.53', '1.91'], ['3.32', 'lost'], ['1.63', 'won'], ['2.97', '4.57']], 500, '0', 500),
   [0, 0]),
  ('variant scenario 2', ([['2.73', 'won'], ['2.11', '4.03']], 1000, '0', 0), [1429, 0])]]
for label, args, expected in cases[N - 1]:
    check(label, run(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
control single shortened[1500, 0][1500, 0]Passed
control margin applied[1425, 0][1425, 0]Passed
boundary won leg keeps price[2666, 0][2666, 0]Passed
boundary partial cash out[500, 333][500, 666]Failed
control lost leg[0, 0][0, 0]Passed
boundary partial above valueinvalidinvalidPassed
regression: partial remaining stake[800, 301][800, 198]Failed
variant scenario 1[0, 0][0, 0]Passed
variant scenario 2[0, 0][0, 0]Passed

SHA-256 / 3afc395fa1325b9e09aba30aacb3ba3863ce11bf274f518c0e43313ecdeeb5f8

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
from fractions import Fraction
import math
N = 1
observations = []
def solve(legs, stake_cents, margin_pct, partial_cents):
    value = Fraction(stake_cents)
    for price, cur in legs:
        if cur == 'lost':
            return [0, 0]
        value *= Fraction(price)
        if cur != 'won':
            value /= Fraction(cur)
    value = value * (100 - Fraction(margin_pct)) / 100
    full = math.floor(value)
    if partial_cents == 0:
        return [full, 0]
    if partial_cents > full:
        return 'invalid'
    remaining = math.floor(stake_cents * (1 - Fraction(partial_cents, full)))
    return [partial_cents, remaining]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
def run(args):
    try:
        return solve(*args)
    except Exception as exc:
        return 'raised ' + type(exc).__name__
cases = [[('control single shortened', ([['3.00', '2.00']], 1000, '0', 0), [1500, 0]),
  ('control margin applied', ([['3.00', '2.00']], 1000, '5', 0), [1425, 0]),
  ('boundary won leg keeps price', ([['2.00', 'won'], ['2.00', '1.50']], 1000, '0', 0), [2666, 0]),
  ('boundary partial cash out', ([['3.00', '2.00']], 1000, '0', 500), [500, 666]),
  ('control lost leg', ([['2.00', 'lost'], ['2.00', '1.50']], 1000, '0', 0), [0, 0]),
  ('boundary partial above value', ([['2.00', '4.00']], 1000, '0', 600), 'invalid'),
  ('regression: partial remaining stake', ([['2.79', 'won']], 500, '5', 800), [800, 198]),
  ('variant scenario 1',
   ([['1.51', 'won'], ['3.49', 'won'], ['3.39', 'lost'], ['1.99', 'won']], 500, '7.5', 0),
   [0, 0]),
  ('variant scenario 2',
   ([['3.71', 'lost'], ['1.47', '4.60'], ['1.32', 'won'], ['1.57', '2.91']], 1000, '5', 200),
   [0, 0])],
 [('control single shortened', ([['3.00', '2.00']], 1000, '0', 0), [1500, 0]),
  ('control margin applied', ([['3.00', '2.00']], 1000, '5', 0), [1425, 0]),
  ('boundary won leg keeps price', ([['2.00', 'won'], ['2.00', '1.50']], 1000, '0', 0), [2666, 0]),
  ('boundary partial cash out', ([['3.00', '2.00']], 1000, '0', 500), [500, 666]),
  ('control lost leg', ([['2.00', 'lost'], ['2.00', '1.50']], 1000, '0', 0), [0, 0]),
  ('boundary partial above value', ([['2.00', '4.00']], 1000, '0', 600), 'invalid'),
  ('regression: partial remaining stake',
   ([['3.43', '4.22'], ['4.58', 'won'], ['2.70', 'won']], 1000, '5', 500),
   [500, 947]),
  ('variant scenario 1', ([['5.00', '5.10']], 500, '0', 0), [490, 0]),
  ('variant scenario 2',
   ([['4.09', '4.06'], ['1.44', 'lost'], ['4.51', '1.56'], ['4.68', 'won']], 1000, '0', 0),
   [0, 0])],
 [('control single shortened', ([['3.00', '2.00']], 1000, '0', 0), [1500, 0]),
  ('control margin applied', ([['3.00', '2.00']], 1000, '5', 0), [1425, 0]),
  ('boundary won leg keeps price', ([['2.00', 'won'], ['2.00', '1.50']], 1000, '0', 0), [2666, 0]),
  ('boundary partial cash out', ([['3.00', '2.00']], 1000, '0', 500), [500, 666]),
  ('control lost leg', ([['2.00', 'lost'], ['2.00', '1.50']], 1000, '0', 0), [0, 0]),
  ('boundary partial above value', ([['2.00', '4.00']], 1000, '0', 600), 'invalid'),
  ('regression: partial remaining stake', ([['3.79', '2.66']], 1000, '7.5', 500), [500, 620]),
  ('variant scenario 1', ([['4.71', 'won']], 1000, '0', 0), [4710, 0]),
  ('variant scenario 2', ([['1.46', '1.60']], 500, '5', 500), 'invalid')],
 [('control single shortened', ([['3.00', '2.00']], 1000, '0', 0), [1500, 0]),
  ('control margin applied', ([['3.00', '2.00']], 1000, '5', 0), [1425, 0]),
  ('boundary won leg keeps price', ([['2.00', 'won'], ['2.00', '1.50']], 1000, '0', 0), [2666, 0]),
  ('boundary partial cash out', ([['3.00', '2.00']], 1000, '0', 500), [500, 666]),
  ('control lost leg', ([['2.00', 'lost'], ['2.00', '1.50']], 1000, '0', 0), [0, 0]),
  ('boundary partial above value', ([['2.00', '4.00']], 1000, '0', 600), 'invalid'),
  ('regression: partial remaining stake',
   ([['3.65', 'won'], ['2.14', '1.31'], ['2.44', '2.91']], 1000, '5', 500),
   [500, 894]),
  ('variant scenario 1',
   ([['3.39', 'won'], ['2.49', '5.36'], ['1.49', '4.00'], ['3.15', '5.06']], 2500, '5', 0),
   [867, 0]),
  ('variant scenario 2', ([['2.52', 'lost'], ['4.68', '4.00']], 1000, '5', 0), [0, 0])],
 [('control single shortened', ([['3.00', '2.00']], 1000, '0', 0), [1500, 0]),
  ('control margin applied', ([['3.00', '2.00']], 1000, '5', 0), [1425, 0]),
  ('boundary won leg keeps price', ([['2.00', 'won'], ['2.00', '1.50']], 1000, '0', 0), [2666, 0]),
  ('boundary partial cash out', ([['3.00', '2.00']], 1000, '0', 500), [500, 666]),
  ('control lost leg', ([['2.00', 'lost'], ['2.00', '1.50']], 1000, '0', 0), [0, 0]),
  ('boundary partial above value', ([['2.00', '4.00']], 1000, '0', 600), 'invalid'),
  ('regression: partial remaining stake', ([['4.93', 'won']], 2500, '5', 500), [500, 2393]),
  ('variant scenario 1',
   ([['3.53', '1.91'], ['3.32', 'lost'], ['1.63', 'won'], ['2.97', '4.57']], 500, '0', 500),
   [0, 0]),
  ('variant scenario 2', ([['2.73', 'won'], ['2.11', '4.03']], 1000, '0', 0), [1429, 0])]]
for label, args, expected in cases[N - 1]:
    check(label, run(args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
control single shortened[1500, 0][1500, 0]Passed
control margin applied[1425, 0][1425, 0]Passed
boundary won leg keeps price[2666, 0][2666, 0]Passed
boundary partial cash out[500, 666][500, 666]Passed
control lost leg[0, 0][0, 0]Passed
boundary partial above valueinvalidinvalidPassed
regression: partial remaining stake[800, 198][800, 198]Passed
variant scenario 1[0, 0][0, 0]Passed
variant scenario 2[0, 0][0, 0]Passed

SHA-256 / 4c4de9e9f848a291031d8594401d680559c3e9351a97b0ca4057956b3f2845c7

Verification & scope

Stipulated, bounded toy contract stated in the contract field; not a claim of conformance with any operator, exchange or regulator rule set. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:50:33.495728+00:00.

Case digest / f811913df339d15bfc96358628de340fd7b59ff813aa00a3311484d7e3622c2d