diff --git a/README.md b/README.md index 34ee2c0..95aa868 100644 --- a/README.md +++ b/README.md @@ -37,21 +37,21 @@ import ( func main() { randomOdd := 4.051 - index1, odd1, err := bfutils.OddShift(bfutils.RoundType_Floor, randomOdd, 10) + index1, odd1, err := bfutils.OddShift(randomOdd, 10, bfutils.RoundType_Floor) if err != nil { panic(err) } fmt.Printf("Odd1: %.2f - position in the ladder: %d\n", odd1, index1+1) - index2, odd2, err := bfutils.OddShift(bfutils.RoundType_Floor, randomOdd, -10) + index2, odd2, err := bfutils.OddShift(randomOdd, -10, bfutils.RoundType_Floor) if err != nil { panic(err) } fmt.Printf("Odd2: %.2f - position in the ladder: %d\n", odd2, index2+1) - ticksDiff, err := bfutils.OddsTicksDiff(bfutils.RoundType_Floor, odd1, odd2) + ticksDiff, err := bfutils.OddsTicksDiff(odd1, odd2, bfutils.RoundType_Floor) if err != nil { panic(err) } @@ -82,29 +82,29 @@ import ( ) func main() { - selection := betting.Selection{ - Bets: []betting.Bet{ - {Type: betting.BetType_Back, Odd: 4, Amount: 5}, - {Type: betting.BetType_Lay, Odd: 3, Amount: 5}, - {Type: betting.BetType_Back, Odd: 3.5, Amount: 10}, - {Type: betting.BetType_Lay, Odd: 3.2, Amount: 10}, - }, - CurrentBackOdd: 2.4, - CurrentLayOdd: 2.42, - } - - bet, err := betting.GreenBookSelection(selection) - if err != nil { - panic(err) - } - - fmt.Printf("In order to green book this selection, put a {%s} bet at {%.2f} for £%.2f.\n", - bet.Type, bet.Odd, bet.Amount) - - fmt.Printf("P&L\n") - fmt.Printf("---\n") - fmt.Printf("If this selection wins: £%.2f\n", bet.WinPL) - fmt.Printf("If this selection loses: £%.2f\n", bet.LosePL) + selection := betting.Selection{ + Bets: []betting.Bet{ + {Type: betting.BetType_Back, Odd: 4, Amount: 5}, + {Type: betting.BetType_Lay, Odd: 3, Amount: 5}, + {Type: betting.BetType_Back, Odd: 3.5, Amount: 10}, + {Type: betting.BetType_Lay, Odd: 3.2, Amount: 10}, + }, + CurrentBackOdd: 2.4, + CurrentLayOdd: 2.42, + } + + bet, err := betting.GreenBookSelection(selection) + if err != nil { + panic(err) + } + + fmt.Printf("In order to green book this selection, put a {%s} bet at {%.2f} for £%.2f.\n", + bet.Type, bet.Odd, bet.Amount) + + fmt.Printf("P&L\n") + fmt.Printf("---\n") + fmt.Printf("If this selection wins: £%.2f\n", bet.WinPL) + fmt.Printf("If this selection loses: £%.2f\n", bet.LosePL) } ``` @@ -127,16 +127,16 @@ import ( ) func main() { - raceBetfairName := "2m3f Hcap" + raceBetfairName := "2m3f Hcap" - name, distance := horserace.GetClassAndDistance(raceBetfairName) - class, err := horserace.GetClassificationFromAbbrev(name) - if err != nil { - panic(err) - } + name, distance := horserace.GetClassAndDistance(raceBetfairName) + class, err := horserace.GetClassificationFromAbbrev(name) + if err != nil { + panic(err) + } - fmt.Printf("Race classification: %s\n", class) - fmt.Printf("Race distance: %s\n", distance) + fmt.Printf("Race classification: %s\n", class) + fmt.Printf("Race distance: %s\n", distance) } ``` @@ -159,16 +159,16 @@ import ( ) func main() { - raceDistance := "2m5f100y" + raceDistance := "2m5f100y" - d, err := conversion.ParseDistance(raceDistance) - if err != nil { - panic(err) - } + d, err := conversion.ParseDistance(raceDistance) + if err != nil { + panic(err) + } - fmt.Printf("This race distance [%s] has %d miles, %d furlongs and %d yards.\n", - d, d.Miles, d.Furlongs, d.Yards) - fmt.Printf("This race distance in meters is: %.2f\n", d.ToMeters()) + fmt.Printf("This race distance [%s] has %d miles, %d furlongs and %d yards.\n", + d, d.Miles, d.Furlongs, d.Yards) + fmt.Printf("This race distance in meters is: %.2f\n", d.ToMeters()) } ``` diff --git a/betting/betting.go b/betting/betting.go index da99694..815676b 100644 --- a/betting/betting.go +++ b/betting/betting.go @@ -118,7 +118,7 @@ func SelectionIsEdged(bets []Bet) (bool, error) { return false, nil } -// GreenBookSelection computes what bet to make in order to greenbook a selection. +// GreenBookSelection computes what bet to make in order to achieve a greenbook in that selection. func GreenBookSelection(selection Selection) (bet Bet, err error) { layAvgOdd := 0.0 layAmount := 0.0 @@ -129,7 +129,7 @@ func GreenBookSelection(selection Selection) (bet Bet, err error) { currentBackOdd := selection.CurrentBackOdd currentLayOdd := selection.CurrentLayOdd - if bets == nil || len(bets) == 0 { + if len(bets) == 0 { return bet, fmt.Errorf("no bets in this selection") } @@ -203,7 +203,7 @@ func GreenBookSelection(selection Selection) (bet Bet, err error) { // return // } -// GreenBookAtAllOdds returns the ladder with P&L and volumed matched by bets. +// GreenBookAtAllOdds returns the ladder with P&L and volume matched by bets. func GreenBookAtAllOdds(bets []Bet) ([]LadderStep, error) { layAvgOdd := 0.0 layAmount := 0.0 diff --git a/betting/entities.go b/betting/entities.go index 23d4410..4398c2c 100644 --- a/betting/entities.go +++ b/betting/entities.go @@ -26,7 +26,7 @@ type Selection struct { CurrentLayOdd float64 } -// LadderStep represents a trading ladder. +// LadderStep represents a step in the trading ladder for a given selection. type LadderStep struct { // Odd in the market. Odd float64 diff --git a/conversion/example_test.go b/conversion/example_test.go index ea0c053..ea8f6a4 100644 --- a/conversion/example_test.go +++ b/conversion/example_test.go @@ -6,8 +6,8 @@ import ( "github.com/gustavooferreira/bfutils/conversion" ) -// This example parses a horse race's distance and then prints a human friendly -// message showing the distance. It also converts the distance into meters. +// This example parses a horse race's distance and then prints a human friendly message showing the distance. +// It also converts the distance into meters. func Example_a() { raceDistance := "2m5f100y" diff --git a/example_test.go b/example_test.go index 590b7b1..253971e 100644 --- a/example_test.go +++ b/example_test.go @@ -10,21 +10,21 @@ import ( func Example_a() { randomOdd := 4.051 - index1, odd1, err := bfutils.OddShift(bfutils.RoundType_Floor, randomOdd, 10) + index1, odd1, err := bfutils.OddShift(randomOdd, 10, bfutils.RoundType_Floor) if err != nil { panic(err) } fmt.Printf("Odd1: %.2f - position in the ladder: %d\n", odd1, index1+1) - index2, odd2, err := bfutils.OddShift(bfutils.RoundType_Floor, randomOdd, -10) + index2, odd2, err := bfutils.OddShift(randomOdd, -10, bfutils.RoundType_Floor) if err != nil { panic(err) } fmt.Printf("Odd2: %.2f - position in the ladder: %d\n", odd2, index2+1) - ticksDiff, err := bfutils.OddsTicksDiff(bfutils.RoundType_Floor, odd1, odd2) + ticksDiff, err := bfutils.OddsTicksDiff(odd1, odd2, bfutils.RoundType_Floor) if err != nil { panic(err) } diff --git a/horserace/classifications.go b/horserace/classifications.go index 65f22a2..4a67938 100644 --- a/horserace/classifications.go +++ b/horserace/classifications.go @@ -4,7 +4,7 @@ package horserace // disregard Novice, Beginners etc as using the three abbreviations will not fit. // Therefore Hcap Hrd, or Hcap Chs. -// classToAbbrev is a map from Classification to Abbreviation +// classToAbbrev is a map from Classification to Abbreviation. var classToAbbrev = map[string]string{ "Listed Race": "Listed", "Group 1": "Grp1", @@ -36,7 +36,7 @@ var classToAbbrev = map[string]string{ "(Non Of the Above)": "Stks", } -// abbrevToClass is a map from Abbreviation to Classification +// abbrevToClass is a map from Abbreviation to Classification. var abbrevToClass = map[string][]string{ "Listed": {"Listed Race"}, "Grp1": {"Group 1"}, diff --git a/horserace/example_test.go b/horserace/example_test.go index dc70d30..29b3a1e 100644 --- a/horserace/example_test.go +++ b/horserace/example_test.go @@ -6,8 +6,7 @@ import ( "github.com/gustavooferreira/bfutils/horserace" ) -// This example parses a horse race's betfair market name and returns the distance and -// the race classification. +// This example parses a horse race's betfair market name and returns the distance and the race classification. func Example_a() { raceBetfairName := "2m3f Hcap" diff --git a/horserace/racecourses.go b/horserace/racecourses.go index afc2699..df35a70 100644 --- a/horserace/racecourses.go +++ b/horserace/racecourses.go @@ -1,6 +1,6 @@ package horserace -// ukTrackToAbbrev is a map from Track to Abbreviation +// ukTrackToAbbrev is a map from Track to Abbreviation. var ukTrackToAbbrev = map[string]string{ "Aintree": "Aint", "Ascot": "Ascot", @@ -65,7 +65,7 @@ var ukTrackToAbbrev = map[string]string{ "York": "York", } -// ukAbbrevToTrack is a map from Abbreviation to Track +// ukAbbrevToTrack is a map from Abbreviation to Track. var ukAbbrevToTrack = map[string]string{ "Aint": "Aintree", "Ascot": "Ascot", @@ -130,7 +130,7 @@ var ukAbbrevToTrack = map[string]string{ "York": "York", } -// ireTrackToAbbrev is a map from Track to Abbreviation +// ireTrackToAbbrev is a map from Track to Abbreviation. var ireTrackToAbbrev = map[string]string{ "Ballinrobe": "Ballin", "Bellewstown": "Belle", @@ -162,7 +162,7 @@ var ireTrackToAbbrev = map[string]string{ "Wexford": "Wex", } -// ireAbbrevToTrack is a map from Abbreviation to Track +// ireAbbrevToTrack is a map from Abbreviation to Track. var ireAbbrevToTrack = map[string]string{ "Ballin": "Ballinrobe", "Belle": "Bellewstown", diff --git a/internal/internal.go b/internal/internal.go index c25eb14..912231e 100644 --- a/internal/internal.go +++ b/internal/internal.go @@ -2,7 +2,7 @@ package internal import "math" -// EqualWithTolerance is a helper function and constant to help estimate whether odd matches or not +// EqualWithTolerance is a helper function to help estimate whether a given odd matches or not (within a threshold). func EqualWithTolerance(a float64, b float64) bool { const float64EqualityThreshold = 1e-9 return math.Abs(a-b) <= float64EqualityThreshold diff --git a/internal/internal_test.go b/internal/internal_test.go index 17eb1b2..bb57cc6 100644 --- a/internal/internal_test.go +++ b/internal/internal_test.go @@ -3,27 +3,29 @@ package internal_test import ( "testing" - "github.com/gustavooferreira/bfutils/internal" "github.com/stretchr/testify/assert" + + "github.com/gustavooferreira/bfutils/internal" ) func TestEqualWithTolerance(t *testing.T) { - tests := map[string]struct { - a float64 - b float64 - expected bool + testCases := []struct { + name string + a float64 + b float64 + expectedResult bool }{ - "compare 1": {a: 0, b: 0, expected: true}, - "compare 2": {a: 10.0000000001, b: 10.0000000002, expected: true}, - "compare 3": {a: 9.999999999999, b: 10, expected: true}, - "compare 4": {a: 5, b: 10, expected: false}, - "compare 5": {a: 9.555555, b: 9.5555556, expected: false}, + {name: "compare 1", a: 0, b: 0, expectedResult: true}, + {name: "compare 2", a: 10.0000000001, b: 10.0000000002, expectedResult: true}, + {name: "compare 3", a: 9.999999999999, b: 10, expectedResult: true}, + {name: "compare 4", a: 5, b: 10, expectedResult: false}, + {name: "compare 5", a: 9.555555, b: 9.5555556, expectedResult: false}, } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - value := internal.EqualWithTolerance(test.a, test.b) - assert.Equal(t, test.expected, value, "expected boolean") + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + value := internal.EqualWithTolerance(tc.a, tc.b) + assert.Equal(t, tc.expectedResult, value) }) } } diff --git a/odds.go b/odds.go index d494498..a3a03e0 100644 --- a/odds.go +++ b/odds.go @@ -1,13 +1,17 @@ -// Package bfutils provides a set of utility functions that help with day to day automation in the Betfair exchange +// Package bfutils provides a set of utility functions that help with day to day automation in the Betfair exchange. package bfutils import ( + "errors" "fmt" "math" "github.com/gustavooferreira/bfutils/internal" ) +// ErrOddOutsideTradingRange is an error that is returned when the odd provided is outside of the trading range [1.01 - 1000]. +var ErrOddOutsideTradingRange = errors.New("odd is outside of the trading range") + // OddsCount is a constant defining the number of available odds in the ladder. const OddsCount = 350 @@ -44,7 +48,8 @@ var Odds = [...]float64{ 590, 600, 610, 620, 630, 640, 650, 660, 670, 680, 690, 700, 710, 720, 730, 740, 750, 760, 770, 780, 790, 800, 810, 820, 830, 840, 850, 860, 870, 880, 890, 900, 910, 920, 930, 940, - 950, 960, 970, 980, 990, 1000} + 950, 960, 970, 980, 990, 1000, +} // OddsStr is an array of strings holding the value of all tradable odds. var OddsStr = [...]string{ @@ -79,25 +84,34 @@ var OddsStr = [...]string{ "590", "600", "610", "620", "630", "640", "650", "660", "670", "680", "690", "700", "710", "720", "730", "740", "750", "760", "770", "780", "790", "800", "810", "820", "830", "840", "850", "860", "870", "880", "890", "900", "910", "920", "930", "940", - "950", "960", "970", "980", "990", "1000"} - -// OddsRange is a map[string]float64 with all the odd ranges available. -var OddsRange = []map[string]float64{ - {"begin": 1.01, "end": 2, "var": 0.01, "ticks": 100}, - {"begin": 2.02, "end": 3, "var": 0.02, "ticks": 50}, - {"begin": 3.05, "end": 4, "var": 0.05, "ticks": 20}, - {"begin": 4.1, "end": 6, "var": 0.1, "ticks": 20}, - {"begin": 6.2, "end": 10, "var": 0.2, "ticks": 20}, - {"begin": 10.5, "end": 20, "var": 0.5, "ticks": 20}, - {"begin": 21, "end": 30, "var": 1, "ticks": 10}, - {"begin": 32, "end": 50, "var": 2, "ticks": 10}, - {"begin": 55, "end": 100, "var": 5, "ticks": 10}, - {"begin": 110, "end": 1000, "var": 10, "ticks": 90}, + "950", "960", "970", "980", "990", "1000", +} + +type LadderStepOddsRange struct { + Begin float64 + End float64 + Increment float64 + Ticks int +} + +// OddsRangeTable is a table containing all the odds ranges available in the ladder. +var OddsRangeTable = []LadderStepOddsRange{ + {Begin: 1.01, End: 2, Increment: 0.01, Ticks: 100}, + {Begin: 2.02, End: 3, Increment: 0.02, Ticks: 50}, + {Begin: 3.05, End: 4, Increment: 0.05, Ticks: 20}, + {Begin: 4.1, End: 6, Increment: 0.1, Ticks: 20}, + {Begin: 6.2, End: 10, Increment: 0.2, Ticks: 20}, + {Begin: 10.5, End: 20, Increment: 0.5, Ticks: 20}, + {Begin: 21, End: 30, Increment: 1, Ticks: 10}, + {Begin: 32, End: 50, Increment: 2, Ticks: 10}, + {Begin: 55, End: 100, Increment: 5, Ticks: 10}, + {Begin: 110, End: 1000, Increment: 10, Ticks: 90}, } -// OddFloor returns the same odd input rounded towards 1.01. +// OddFloor returns the nearest available odd rounded towards 1.01. // If the odd supplied is one of the available odds in the ladder, than the same odd is returned. // index returns the index of the odd in the ladder. +// An error may be returned if the odd provided is outside of the tradable range [1.01 - 1000]. func OddFloor(odd float64) (index int, oddRounded float64, err error) { _, index, err = FindOdd(odd) if err != nil { @@ -107,9 +121,10 @@ func OddFloor(odd float64) (index int, oddRounded float64, err error) { return index, Odds[index], nil } -// OddCeil returns the same odd input rounded towards 1000. +// OddCeil returns the nearest available odd rounded towards 1000. // If the odd supplied is one of the available odds in the ladder, than the same odd is returned. // index returns the index of the odd in the ladder. +// An error may be returned if the odd provided is outside of the tradable range [1.01 - 1000]. func OddCeil(odd float64) (index int, oddRounded float64, err error) { match, index, err := FindOdd(odd) if err != nil { @@ -119,12 +134,15 @@ func OddCeil(odd float64) (index int, oddRounded float64, err error) { if match { return index, Odds[index], nil } + + // it's fine to do index+1, because we always get the odd to the left when finding the odd. return index + 1, Odds[index+1], nil } -// OddRound returns the same odd input rounded to the nearest odd in the ladder. +// OddRound returns the nearest available odd rounded according to normal rounding rules. // If the odd supplied is one of the available odds in the ladder, than the same odd is returned. // index returns the index of the odd in the ladder. +// An error may be returned if the odd provided is outside of the tradable range [1.01 - 1000]. func OddRound(odd float64) (index int, oddRounded float64, err error) { match, index, err := FindOdd(odd) if err != nil { @@ -142,6 +160,7 @@ func OddRound(odd float64) (index int, oddRounded float64, err error) { if delta1 <= delta2 { return index, Odds[index], nil } + return index + 1, Odds[index+1], nil } @@ -149,7 +168,8 @@ func OddRound(odd float64) (index int, oddRounded float64, err error) { // If shift is higher than zero, it shifts the odd towards 1000, if it's less than zero it shifts the odd towards 1.01. // roundType is the round method to be used. // shift represents the number of ticks to shift the odd. -func OddShift(roundType RoundType, odd float64, shift int) (index int, oddOut float64, err error) { +// An error may be returned if the odd provided is outside of the tradable range [1.01 - 1000]. +func OddShift(odd float64, shift int, roundType RoundType) (index int, oddOut float64, err error) { switch roundType { case RoundType_Ceil: index, _, err = OddCeil(odd) @@ -165,15 +185,17 @@ func OddShift(roundType RoundType, odd float64, shift int) (index int, oddOut fl index += shift - if (index >= OddsCount) || (index < 0) { - return 0, 0, fmt.Errorf("odd outside of tradable range") + if (index < 0) || (index >= OddsCount) { + return 0, 0, fmt.Errorf("computed invalid odd [idx: %d]: %w", index, ErrOddOutsideTradingRange) } + return index, Odds[index], nil } // OddsTicksDiff computes the number of ticks between two odds. // roundType is the round method to be used. -func OddsTicksDiff(roundType RoundType, odd1 float64, odd2 float64) (ticksDiff int, err error) { +// An error may be returned if the odd provided is outside of the tradable range [1.01 - 1000]. +func OddsTicksDiff(odd1 float64, odd2 float64, roundType RoundType) (ticksDiff int, err error) { var index1 int var index2 int var err1 error @@ -202,8 +224,7 @@ func OddsTicksDiff(roundType RoundType, odd1 float64, odd2 float64) (ticksDiff i return int(math.Abs(float64(index2 - index1))), nil } -// IsOddWithinBoundaries checks if odd is within trading range. -// I.e., odd is between 1.01 and 1000. +// IsOddWithinBoundaries checks if odd is within trading range, i.e., odd is between 1.01 and 1000. func IsOddWithinBoundaries(odd float64) bool { if internal.EqualWithTolerance(odd, 1000) { return true @@ -223,10 +244,10 @@ func IsOddWithinBoundaries(odd float64) bool { // FindOdd tries to find the odd in the ladder. // If it finds it, then index is the odd index in the ladder. // If it doesn't find it, it will return the index in the ladder that is the closest to the odd on the left side. +// An error may be returned if the odd provided is outside of the tradable range [1.01 - 1000]. func FindOdd(odd float64) (match bool, index int, err error) { - // Boundaries - if withinBoundary := IsOddWithinBoundaries(odd); !withinBoundary { - return false, 0, fmt.Errorf("odd provided [%f] is outside of trading range", odd) + if ok := IsOddWithinBoundaries(odd); !ok { + return false, 0, ErrOddOutsideTradingRange } if internal.EqualWithTolerance(odd, 1000) { @@ -235,6 +256,8 @@ func FindOdd(odd float64) (match bool, index int, err error) { return true, 0, nil } + // binary search + lo := 0 hi := OddsCount @@ -257,11 +280,11 @@ func FindOdd(odd float64) (match bool, index int, err error) { type RoundType uint const ( - // RoundType_Ceil round towards 1000. + // RoundType_Ceil rounds towards 1000. RoundType_Ceil = iota - // RoundType_Round round to the nearest odd in the ladder. + // RoundType_Round rounds to the nearest odd in the ladder. RoundType_Round - // RoundType_Floor round towards 1.01. + // RoundType_Floor rounds towards 1.01. RoundType_Floor ) diff --git a/odds_test.go b/odds_test.go index 102db86..e4fc42f 100644 --- a/odds_test.go +++ b/odds_test.go @@ -3,180 +3,212 @@ package bfutils_test import ( "testing" - "github.com/gustavooferreira/bfutils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/gustavooferreira/bfutils" ) func TestOddsSize(t *testing.T) { - OddsLen := len(bfutils.Odds) - OddsStrLen := len(bfutils.OddsStr) - t.Run("Odds array length equal to OddsCount", func(t *testing.T) { + OddsLen := len(bfutils.Odds) assert.Equal(t, bfutils.OddsCount, OddsLen) }) t.Run("OddsStr array length equal to OddsCount", func(t *testing.T) { + OddsStrLen := len(bfutils.OddsStr) assert.Equal(t, bfutils.OddsCount, OddsStrLen) }) } func TestOddExists(t *testing.T) { - tests := map[string]struct { + testCases := []struct { + name string index int expected float64 }{ - "odd[1.01] exists": {index: 0, expected: 1.01}, - "odd[1.1] exists": {index: 9, expected: 1.1}, - "odd[2] exists": {index: 99, expected: 2}, - "odd[510] exists": {index: 300, expected: 510}, + {name: "odd[1.01] exists", index: 0, expected: 1.01}, + {name: "odd[1.1] exists", index: 9, expected: 1.1}, + {name: "odd[2] exists", index: 99, expected: 2}, + {name: "odd[510] exists", index: 300, expected: 510}, } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - assert.Equal(t, test.expected, bfutils.Odds[test.index]) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expected, bfutils.Odds[tc.index]) }) } } func TestOddRange(t *testing.T) { - tests := map[string]struct { - index int - expectedBegin float64 - expectedEnd float64 + testCases := []struct { + name string + index int + expectedBegin float64 + expectedEnd float64 + expectedIncrement float64 + expectedTicks int }{ - "odd range [0] boundaries": {index: 0, expectedBegin: 1.01, expectedEnd: 2.0}, - "odd range [3] boundaries": {index: 3, expectedBegin: 4.1, expectedEnd: 6.0}, - "odd range [9] boundaries": {index: 9, expectedBegin: 110, expectedEnd: 1000}, + { + name: "odd range [idx: 0] boundaries", + index: 0, + expectedBegin: 1.01, + expectedEnd: 2.0, + expectedIncrement: 0.01, + expectedTicks: 100, + }, + { + name: "odd range [idx: 3] boundaries", + index: 3, + expectedBegin: 4.1, + expectedEnd: 6.0, + expectedIncrement: 0.1, + expectedTicks: 20, + }, + { + name: "odd range [idx: 9] boundaries", + index: 9, + expectedBegin: 110, + expectedEnd: 1000, + expectedIncrement: 10, + expectedTicks: 90, + }, } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - assert.Equal(t, test.expectedBegin, bfutils.OddsRange[test.index]["begin"]) - assert.Equal(t, test.expectedEnd, bfutils.OddsRange[test.index]["end"]) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.expectedBegin, bfutils.OddsRangeTable[tc.index].Begin) + assert.Equal(t, tc.expectedEnd, bfutils.OddsRangeTable[tc.index].End) + assert.Equal(t, tc.expectedIncrement, bfutils.OddsRangeTable[tc.index].Increment) + assert.Equal(t, tc.expectedTicks, bfutils.OddsRangeTable[tc.index].Ticks) }) } } func TestOddFloor(t *testing.T) { - tests := map[string]struct { + testCases := []struct { + name string odd float64 expectedIndex int expectedOdd float64 expectedErr bool }{ // Boundary tests - "odd[-1] match": {odd: -1, expectedErr: true}, - "odd[0] match": {odd: 0, expectedErr: true}, - "odd[1] match": {odd: 1, expectedErr: true}, - "odd[99999] match": {odd: 99999, expectedErr: true}, + {name: "odd[-1] match", odd: -1, expectedErr: true}, + {name: "odd[0] match", odd: 0, expectedErr: true}, + {name: "odd[1] match", odd: 1, expectedErr: true}, + {name: "odd[99999] match", odd: 99999, expectedErr: true}, - "odd[1.01] match": {odd: 1.01, expectedIndex: 0, expectedOdd: 1.01}, - "odd[1000] match": {odd: 1000, expectedIndex: 349, expectedOdd: 1000}, + {name: "odd[1.01] match", odd: 1.01, expectedIndex: 0, expectedOdd: 1.01}, + {name: "odd[1000] match", odd: 1000, expectedIndex: 349, expectedOdd: 1000}, - "odd[3.2552321] match": {odd: 3.2552321, expectedIndex: 154, expectedOdd: 3.25}, - "odd[3.299999999999] match": {odd: 3.299999999999, expectedIndex: 155, expectedOdd: 3.3}, + {name: "odd[3.2552321] match", odd: 3.2552321, expectedIndex: 154, expectedOdd: 3.25}, + {name: "odd[3.299999999999] match", odd: 3.299999999999, expectedIndex: 155, expectedOdd: 3.3}, - "odd[2.0] match": {odd: 2.0, expectedIndex: 99, expectedOdd: 2.0}, - "odd[4.05] match": {odd: 4.05, expectedIndex: 169, expectedOdd: 4.0}, - "odd[33] match": {odd: 33, expectedIndex: 240, expectedOdd: 32}, + {name: "odd[3.29] match", odd: 3.29, expectedIndex: 154, expectedOdd: 3.25}, + {name: "odd[2.0] match", odd: 2.0, expectedIndex: 99, expectedOdd: 2.0}, + {name: "odd[4.05] match", odd: 4.05, expectedIndex: 169, expectedOdd: 4.0}, + {name: "odd[33] match", odd: 33, expectedIndex: 240, expectedOdd: 32}, } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - var errBool bool - index, odd, err := bfutils.OddFloor(test.odd) - if err != nil { - errBool = true + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + index, odd, err := bfutils.OddFloor(tc.odd) + if tc.expectedErr { + require.Error(t, err) + return // end of test } - require.Equal(t, test.expectedErr, errBool) - assert.Equal(t, test.expectedIndex, index) - assert.Equal(t, test.expectedOdd, odd) + require.NoError(t, err) + assert.Equal(t, tc.expectedIndex, index) + assert.Equal(t, tc.expectedOdd, odd) }) } } func TestOddCeil(t *testing.T) { - tests := map[string]struct { + testCases := []struct { + name string odd float64 expectedIndex int expectedOdd float64 expectedErr bool }{ // Boundary tests - "odd[-1] match": {odd: -1, expectedErr: true}, - "odd[0] match": {odd: 0, expectedErr: true}, - "odd[1] match": {odd: 1, expectedErr: true}, - "odd[99999] match": {odd: 99999, expectedErr: true}, + {name: "odd[-1] match", odd: -1, expectedErr: true}, + {name: "odd[0] match", odd: 0, expectedErr: true}, + {name: "odd[1] match", odd: 1, expectedErr: true}, + {name: "odd[99999] match", odd: 99999, expectedErr: true}, - "odd[1.01] match": {odd: 1.01, expectedIndex: 0, expectedOdd: 1.01}, - "odd[1000] match": {odd: 1000, expectedIndex: 349, expectedOdd: 1000}, + {name: "odd[1.01] match", odd: 1.01, expectedIndex: 0, expectedOdd: 1.01}, + {name: "odd[1000] match", odd: 1000, expectedIndex: 349, expectedOdd: 1000}, - "odd[3.2552321] match": {odd: 3.2552321, expectedIndex: 155, expectedOdd: 3.3}, - "odd[3.249999999999] match": {odd: 3.249999999999, expectedIndex: 154, expectedOdd: 3.25}, + {name: "odd[3.2552321] match", odd: 3.2552321, expectedIndex: 155, expectedOdd: 3.3}, + {name: "odd[3.249999999999] match", odd: 3.249999999999, expectedIndex: 154, expectedOdd: 3.25}, - "odd[2.0] match": {odd: 2.0, expectedIndex: 99, expectedOdd: 2.0}, - "odd[4.05] match": {odd: 4.05, expectedIndex: 170, expectedOdd: 4.1}, - "odd[33] match": {odd: 33, expectedIndex: 241, expectedOdd: 34}, + {name: "odd[2.0] match", odd: 2.0, expectedIndex: 99, expectedOdd: 2.0}, + {name: "odd[4.05] match", odd: 4.05, expectedIndex: 170, expectedOdd: 4.1}, + {name: "odd[33] match", odd: 33, expectedIndex: 241, expectedOdd: 34}, } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - var errBool bool - index, odd, err := bfutils.OddCeil(test.odd) - if err != nil { - errBool = true + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + index, odd, err := bfutils.OddCeil(tc.odd) + if tc.expectedErr { + require.Error(t, err) + return // end of test } - require.Equal(t, test.expectedErr, errBool) - assert.Equal(t, test.expectedIndex, index) - assert.Equal(t, test.expectedOdd, odd) + require.NoError(t, err) + assert.Equal(t, tc.expectedIndex, index) + assert.Equal(t, tc.expectedOdd, odd) }) } } func TestOddRound(t *testing.T) { - tests := map[string]struct { + testCases := []struct { + name string odd float64 expectedIndex int expectedOdd float64 expectedErr bool }{ // Boundary tests - "odd[-1] match": {odd: -1, expectedErr: true}, - "odd[0] match": {odd: 0, expectedErr: true}, - "odd[1] match": {odd: 1, expectedErr: true}, - "odd[99999] match": {odd: 99999, expectedErr: true}, + {name: "odd[-1] match", odd: -1, expectedErr: true}, + {name: "odd[0] match", odd: 0, expectedErr: true}, + {name: "odd[1] match", odd: 1, expectedErr: true}, + {name: "odd[99999] match", odd: 99999, expectedErr: true}, - "odd[1.01] match": {odd: 1.01, expectedIndex: 0, expectedOdd: 1.01}, - "odd[1000] match": {odd: 1000, expectedIndex: 349, expectedOdd: 1000}, + {name: "odd[1.01] match", odd: 1.01, expectedIndex: 0, expectedOdd: 1.01}, + {name: "odd[1000] match", odd: 1000, expectedIndex: 349, expectedOdd: 1000}, - "odd[3.2552321] match": {odd: 3.2552321, expectedIndex: 154, expectedOdd: 3.25}, - "odd[3.249999999999] match": {odd: 3.249999999999, expectedIndex: 154, expectedOdd: 3.25}, + {name: "odd[3.2552321] match", odd: 3.2552321, expectedIndex: 154, expectedOdd: 3.25}, + {name: "odd[3.249999999999] match", odd: 3.249999999999, expectedIndex: 154, expectedOdd: 3.25}, - "odd[2.0] match": {odd: 2.0, expectedIndex: 99, expectedOdd: 2.0}, - "odd[4.077] match": {odd: 4.077, expectedIndex: 170, expectedOdd: 4.1}, - "odd[32.9] match": {odd: 32.9, expectedIndex: 240, expectedOdd: 32}, + {name: "odd[2.0] match", odd: 2.0, expectedIndex: 99, expectedOdd: 2.0}, + {name: "odd[4.077] match", odd: 4.077, expectedIndex: 170, expectedOdd: 4.1}, + {name: "odd[32.9] match", odd: 32.9, expectedIndex: 240, expectedOdd: 32}, } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - var errBool bool - index, odd, err := bfutils.OddRound(test.odd) - if err != nil { - errBool = true + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + index, odd, err := bfutils.OddRound(tc.odd) + if tc.expectedErr { + require.Error(t, err) + return // end of test } - require.Equal(t, test.expectedErr, errBool) - assert.Equal(t, test.expectedIndex, index) - assert.Equal(t, test.expectedOdd, odd) + require.NoError(t, err) + assert.Equal(t, tc.expectedIndex, index) + assert.Equal(t, tc.expectedOdd, odd) }) } } func TestOddShift(t *testing.T) { - tests := map[string]struct { + testCases := []struct { + name string roundType bfutils.RoundType odd float64 shift int @@ -185,34 +217,56 @@ func TestOddShift(t *testing.T) { expectedErr bool }{ // Boundary tests - "odd[-1] shift": {odd: -1, expectedErr: true}, - "odd[0] shift": {odd: 0, expectedErr: true}, - "odd[1] shift": {odd: 1, expectedErr: true}, - "odd[99999] shift": {odd: 99999, expectedErr: true}, - "odd[10, 1000] shift": {roundType: bfutils.RoundType_Ceil, odd: 10, shift: 1000, expectedErr: true}, - - "odd[1.01, 3] shift": {roundType: bfutils.RoundType_Round, odd: 1.01, shift: 3, expectedIndex: 3, expectedOdd: 1.04}, - "odd[4, -10] shift": {roundType: bfutils.RoundType_Ceil, odd: 4, shift: -10, expectedIndex: 159, expectedOdd: 3.5}, - "odd[10, 5] shift": {roundType: bfutils.RoundType_Floor, odd: 10, shift: 5, expectedIndex: 214, expectedOdd: 12.5}, + {name: "odd[-1] shift", odd: -1, expectedErr: true}, + {name: "odd[0] shift", odd: 0, expectedErr: true}, + {name: "odd[1] shift", odd: 1, expectedErr: true}, + {name: "odd[99999] shift", odd: 99999, expectedErr: true}, + {name: "odd[10, 1000] shift", roundType: bfutils.RoundType_Ceil, odd: 10, shift: 1000, expectedErr: true}, + + { + name: "odd[1.01, 3] shift", + roundType: bfutils.RoundType_Round, + odd: 1.01, + shift: 3, + expectedIndex: 3, + expectedOdd: 1.04, + }, + { + name: "odd[4, -10] shift", + roundType: bfutils.RoundType_Ceil, + odd: 4, + shift: -10, + expectedIndex: 159, + expectedOdd: 3.5, + }, + { + name: "odd[10, 5] shift", + roundType: bfutils.RoundType_Floor, + odd: 10, + shift: 5, + expectedIndex: 214, + expectedOdd: 12.5, + }, } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - var errBool bool - index, odd, err := bfutils.OddShift(test.roundType, test.odd, test.shift) - if err != nil { - errBool = true + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + index, odd, err := bfutils.OddShift(tc.odd, tc.shift, tc.roundType) + if tc.expectedErr { + require.Error(t, err) + return // end of test } - require.Equal(t, test.expectedErr, errBool) - assert.Equal(t, test.expectedIndex, index) - assert.Equal(t, test.expectedOdd, odd) + require.NoError(t, err) + assert.Equal(t, tc.expectedIndex, index) + assert.Equal(t, tc.expectedOdd, odd) }) } } func TestOddsTicksDiff(t *testing.T) { - tests := map[string]struct { + testCases := []struct { + name string roundType bfutils.RoundType odd1 float64 odd2 float64 @@ -220,54 +274,101 @@ func TestOddsTicksDiff(t *testing.T) { expectedErr bool }{ // Boundary tests - "odds[-1, 10] tick diff": {odd1: -1, odd2: 10, expectedErr: true}, - "odds[0, 5] tick diff": {odd1: 0, odd2: 5, expectedErr: true}, - "odds[50, 1] tick diff": {odd1: 50, odd2: 1, expectedErr: true}, - "odds[10, 99999] tick diff": {odd1: 10, odd2: 99999, expectedErr: true}, - - "odds[1.01, 3] tick diff": {roundType: bfutils.RoundType_Round, odd1: 1.01, odd2: 3, expectedDiff: 149}, - "odds[4, 4.5] tick diff": {roundType: bfutils.RoundType_Ceil, odd1: 4, odd2: 4.5, expectedDiff: 5}, - "odds[10, 5] tick diff": {roundType: bfutils.RoundType_Floor, odd1: 10, odd2: 5, expectedDiff: 30}, + {name: "odds[-1, 10] tick diff", odd1: -1, odd2: 10, expectedErr: true}, + {name: "odds[0, 5] tick diff", odd1: 0, odd2: 5, expectedErr: true}, + {name: "odds[50, 1] tick diff", odd1: 50, odd2: 1, expectedErr: true}, + {name: "odds[10, 99999] tick diff", odd1: 10, odd2: 99999, expectedErr: true}, + + {name: "odds[1.01, 3] tick diff", roundType: bfutils.RoundType_Round, odd1: 1.01, odd2: 3, expectedDiff: 149}, + {name: "odds[4, 4.5] tick diff", roundType: bfutils.RoundType_Ceil, odd1: 4, odd2: 4.5, expectedDiff: 5}, + {name: "odds[10, 5] tick diff", roundType: bfutils.RoundType_Floor, odd1: 10, odd2: 5, expectedDiff: 30}, } - for name, test := range tests { - t.Run(name, func(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { var errBool bool - diff, err := bfutils.OddsTicksDiff(test.roundType, test.odd1, test.odd2) + diff, err := bfutils.OddsTicksDiff(tc.odd1, tc.odd2, tc.roundType) if err != nil { errBool = true } - require.Equal(t, test.expectedErr, errBool) - assert.Equal(t, test.expectedDiff, diff) + require.Equal(t, tc.expectedErr, errBool) + assert.Equal(t, tc.expectedDiff, diff) }) } } func TestOddWithinBoundaries(t *testing.T) { - tests := map[string]struct { - odd float64 - expected bool + testCases := []struct { + name string + odd float64 + expectedResult bool }{ // Boundary tests - "odds[-1] boundary check": {odd: -1, expected: false}, - "odds[0] boundary check": {odd: 0, expected: false}, - "odds[1] boundary check": {odd: 1, expected: false}, - "odds[1001] boundary check": {odd: 1001, expected: false}, + {name: "odds[-1] boundary check", odd: -1, expectedResult: false}, + {name: "odds[0] boundary check", odd: 0, expectedResult: false}, + {name: "odds[1] boundary check", odd: 1, expectedResult: false}, + {name: "odds[1.01] boundary check", odd: 1.01, expectedResult: true}, + {name: "odds[1001] boundary check", odd: 1001, expectedResult: false}, + + {name: "odds[2.3] boundary check", odd: 2.3, expectedResult: true}, + {name: "odds[50] boundary check", odd: 50, expectedResult: true}, + } - "odds[2.3] boundary check": {odd: 2.3, expected: true}, - "odds[50] boundary check": {odd: 50, expected: true}, + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + value := bfutils.IsOddWithinBoundaries(tc.odd) + assert.Equal(t, tc.expectedResult, value) + }) + } +} + +func TestFindOdd(t *testing.T) { + testCases := []struct { + name string + odd float64 + expectedMatch bool + expectedIndex int + expectedErr bool + }{ + {name: "find odd [-1]", odd: -1, expectedErr: true}, + {name: "find odd [0]", odd: 0, expectedErr: true}, + {name: "find odd [1]", odd: 1, expectedErr: true}, + + {name: "find odd [1.01]", odd: 1.01, expectedErr: false, expectedIndex: 0, expectedMatch: true}, + {name: "find odd [1000]", odd: 1000, expectedErr: false, expectedIndex: 349, expectedMatch: true}, + + {name: "find odd [2]", odd: 2, expectedErr: false, expectedIndex: 99, expectedMatch: true}, + + {name: "find odd [6.1]", odd: 6.1, expectedErr: false, expectedIndex: 189, expectedMatch: false}, + {name: "find odd [1.0123]", odd: 1.0123, expectedErr: false, expectedIndex: 0, expectedMatch: false}, + {name: "find odd [999.1]", odd: 999.1, expectedErr: false, expectedIndex: 348, expectedMatch: false}, } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - value := bfutils.IsOddWithinBoundaries(test.odd) - assert.Equal(t, test.expected, value) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + match, index, err := bfutils.FindOdd(tc.odd) + if tc.expectedErr { + require.Error(t, err) + return // end of test + } + + require.NoError(t, err) + require.Equal(t, tc.expectedMatch, match) + assert.Equal(t, tc.expectedIndex, index) + + t.Logf("Odd at index [%d]: %.2f", index, bfutils.Odds[index]) }) } } func TestRoundTypeEnum(t *testing.T) { - var enum bfutils.RoundType = bfutils.RoundType_Floor - assert.Equal(t, "Floor", enum.String()) + var enum1 bfutils.RoundType = bfutils.RoundType_Ceil + assert.Equal(t, "Ceil", enum1.String()) + + var enum2 bfutils.RoundType = bfutils.RoundType_Round + assert.Equal(t, "Round", enum2.String()) + + var enum3 bfutils.RoundType = bfutils.RoundType_Floor + assert.Equal(t, "Floor", enum3.String()) }