import { describe, it, expect } from "vitest"; import { americanToImpliedProbability, impliedProbabilityToAmerican, normalizeOdds, } from "../playoff-match-odds"; describe("americanToImpliedProbability", () => { it("converts even odds (+100) to 0.5", () => { expect(americanToImpliedProbability(100)).toBeCloseTo(0.5); }); it("converts favourite negative odds (-110) correctly", () => { // 110 / (110 + 100) = 110/210 ≈ 0.5238 expect(americanToImpliedProbability(-110)).toBeCloseTo(110 / 210); }); it("converts heavy favourite (-400) correctly", () => { // 400 / 500 = 0.8 expect(americanToImpliedProbability(-400)).toBeCloseTo(0.8); }); it("converts underdog (+200) correctly", () => { // 100 / (200 + 100) = 100/300 ≈ 0.3333 expect(americanToImpliedProbability(200)).toBeCloseTo(1 / 3); }); it("converts heavy underdog (+500) correctly", () => { // 100 / 600 ≈ 0.1667 expect(americanToImpliedProbability(500)).toBeCloseTo(1 / 6); }); it("returns 0 for zero odds", () => { expect(americanToImpliedProbability(0)).toBe(0); }); it("implied probabilities for two-way market sum > 1 (vig included)", () => { // Standard -110/-110 line should sum to > 1 due to vig const p1 = americanToImpliedProbability(-110); const p2 = americanToImpliedProbability(-110); expect(p1 + p2).toBeGreaterThan(1); }); }); describe("impliedProbabilityToAmerican", () => { it("converts 0.5 to even money (+100)", () => { // At exactly 0.5, should be -100 (technically even both ways) const result = impliedProbabilityToAmerican(0.5); expect(result).toBe(-100); }); it("converts a favourite probability to negative odds", () => { // 0.8 probability → -(0.8/0.2)*100 = -400 expect(impliedProbabilityToAmerican(0.8)).toBe(-400); }); it("converts an underdog probability to positive odds", () => { // 1/3 ≈ 0.3333 → ((1 - 1/3) / (1/3)) * 100 = 200 expect(impliedProbabilityToAmerican(1 / 3)).toBeCloseTo(200, 0); }); it("converts a heavy underdog probability to large positive number", () => { // 1/6 ≈ 0.1667 → (5/6 / 1/6) * 100 = 500 expect(impliedProbabilityToAmerican(1 / 6)).toBeCloseTo(500, 0); }); it("throws for probability of 0", () => { expect(() => impliedProbabilityToAmerican(0)).toThrow(RangeError); }); it("throws for probability of 1", () => { expect(() => impliedProbabilityToAmerican(1)).toThrow(RangeError); }); it("throws for probability > 1", () => { expect(() => impliedProbabilityToAmerican(1.5)).toThrow(RangeError); }); it("throws for negative probability", () => { expect(() => impliedProbabilityToAmerican(-0.1)).toThrow(RangeError); }); it("roundtrips correctly for typical favourite", () => { // -200 → prob → back to -200 const prob = americanToImpliedProbability(-200); const back = impliedProbabilityToAmerican(prob); expect(back).toBe(-200); }); it("roundtrips correctly for typical underdog", () => { // +150 → prob → back to +150 const prob = americanToImpliedProbability(150); const back = impliedProbabilityToAmerican(prob); expect(back).toBe(150); }); }); describe("normalizeOdds", () => { it("normalized probabilities sum to 1", () => { const [p1, p2] = normalizeOdds(-110, -110); expect(p1 + p2).toBeCloseTo(1); }); it("equal moneylines give 50/50 after normalization", () => { const [p1, p2] = normalizeOdds(-110, -110); expect(p1).toBeCloseTo(0.5); expect(p2).toBeCloseTo(0.5); }); it("favourite has higher normalized probability", () => { const [favProb, underdogProb] = normalizeOdds(-200, +170); expect(favProb).toBeGreaterThan(underdogProb); }); it("normalized probabilities are between 0 and 1", () => { const [p1, p2] = normalizeOdds(-300, +250); expect(p1).toBeGreaterThan(0); expect(p1).toBeLessThan(1); expect(p2).toBeGreaterThan(0); expect(p2).toBeLessThan(1); }); it("removes vig - heavy favourite still less than 1 after normalization", () => { // Raw implied prob for -400 = 0.8. Normalized should be < 1 since opponent exists. const [p1] = normalizeOdds(-400, +300); expect(p1).toBeLessThan(1); expect(p1).toBeGreaterThan(0.5); }); it("ordering is consistent with input", () => { const [p1First, p2First] = normalizeOdds(-200, +170); const [p2Second, p1Second] = normalizeOdds(+170, -200); expect(p1First).toBeCloseTo(p1Second); expect(p2First).toBeCloseTo(p2Second); }); });