overtime-utils 0.1.74-rc.0 → 0.1.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.ts +0 -1
- package/main.js +1 -1
- package/package.json +1 -1
- package/src/constants/marketTypes.ts +1 -1
- package/src/utils/odds.ts +0 -579
package/package.json
CHANGED
|
@@ -3836,7 +3836,6 @@ export const MarketTypeMap: Record<MarketType, MarketTypeInfo> = {
|
|
|
3836
3836
|
name: 'Total fouls',
|
|
3837
3837
|
resultType: ResultType.OVER_UNDER,
|
|
3838
3838
|
},
|
|
3839
|
-
|
|
3840
3839
|
};
|
|
3841
3840
|
|
|
3842
3841
|
export const WINNER_MARKET_TYPES = [
|
|
@@ -4431,6 +4430,7 @@ export const PLAYER_PROPS_MARKET_TYPES = [
|
|
|
4431
4430
|
MarketType.PLAYER_PROPS_MAN_OF_THE_MATCH,
|
|
4432
4431
|
MarketType.PLAYER_PROPS_WICKETS_TAKEN,
|
|
4433
4432
|
MarketType.PLAYER_PROPS_SIXES,
|
|
4433
|
+
MarketType.PLAYER_PROPS_FOURS,
|
|
4434
4434
|
MarketType.PLAYER_PROPS_THREE_DART_AVERAGE,
|
|
4435
4435
|
MarketType.PLAYER_PROPS_180,
|
|
4436
4436
|
MarketType.PLAYER_PROPS_100_PLUS_CHECKOUTS,
|
package/src/utils/odds.ts
DELETED
|
@@ -1,579 +0,0 @@
|
|
|
1
|
-
const ZERO = 0;
|
|
2
|
-
const MULTIPLIER_100 = 100;
|
|
3
|
-
const MINIMAL_PROBABILITY_SUM = 1.02;
|
|
4
|
-
|
|
5
|
-
const log = (...args: any[]) => {
|
|
6
|
-
const isTest = process.env.NODE_ENV === 'test';
|
|
7
|
-
if (isTest) {
|
|
8
|
-
console.log(...args);
|
|
9
|
-
}
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Checks if the sum of odds is greater than MINIMAL_PROBABILITY_SUM.
|
|
14
|
-
* @param {Array<number>} odds - Array of odds.
|
|
15
|
-
* @returns {{ odds: Array<number>, isValid: boolean, sumOfOdds: number }}
|
|
16
|
-
* - odds: adjusted odds or array of zeros
|
|
17
|
-
* - isValid: true if sum >= MINIMAL_PROBABILITY_SUM, false otherwise
|
|
18
|
-
* - sumOfOdds: sum of all odds
|
|
19
|
-
*/
|
|
20
|
-
function checkOddsIfGreaterThanMinimalSum(
|
|
21
|
-
odds: number[],
|
|
22
|
-
minimal = MINIMAL_PROBABILITY_SUM
|
|
23
|
-
): { odds: Array<number>; isValid: boolean; sumOfOdds: number } {
|
|
24
|
-
const sumOfOdds = odds.reduce((sum, odd) => sum + odd, ZERO);
|
|
25
|
-
const isValid = sumOfOdds >= minimal;
|
|
26
|
-
|
|
27
|
-
if (!isValid) {
|
|
28
|
-
log('Odds sum invalid: sum=%s, expected >= %s, odds=%o -> returning zeros.', sumOfOdds, minimal, odds);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
odds: isValid ? odds : new Array(odds.length).fill(ZERO),
|
|
33
|
-
isValid,
|
|
34
|
-
sumOfOdds,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Rebalances the given odds based on provided cap and risk values for two positions.
|
|
40
|
-
*
|
|
41
|
-
* @param {Array<number>} currentOddsArray - The array of odds to be rebalanced.
|
|
42
|
-
* @param {number} cap - The cap value used for adjustments.
|
|
43
|
-
* @param {number} positionOneRisk - The risk adjustment for position one.
|
|
44
|
-
* @param {number} positionTwoRisk - The risk adjustment for position two.
|
|
45
|
-
* @param {number} [lowerPercentageRisk=33] - The lower percentage risk threshold.
|
|
46
|
-
* @param {number} [middlePercentageRisk=50] - The middle percentage risk threshold.
|
|
47
|
-
* @param {number} [higherPercentageRisk=66] - The higher percentage risk threshold.
|
|
48
|
-
* @param {number} [lowerPercentageRiskRebalance=1] - Rebalance % when in (lower, middle].
|
|
49
|
-
* @param {number} [middlePercentageRiskRebalance=1.5]- Rebalance % when in (middle, higher].
|
|
50
|
-
* @param {number} [higherPercentageRiskRebalance=2] - Rebalance % when > higher.
|
|
51
|
-
* @param {boolean} [rebalanceOnRiskSideOnly=true] - If true, only the higher-risk side is boosted and
|
|
52
|
-
* the other side is left as-is; if false, the opposite side is reduced as well.
|
|
53
|
-
* @returns {{ odds: number[], isRebalanced: boolean, minPercantage: number, adjustedPercantage: number }}
|
|
54
|
-
*/
|
|
55
|
-
function rebalanceOdds(
|
|
56
|
-
currentOddsArray: number[],
|
|
57
|
-
cap: number,
|
|
58
|
-
positionOneRisk: number,
|
|
59
|
-
positionTwoRisk: number,
|
|
60
|
-
lowerPercentageRisk: number = 33,
|
|
61
|
-
middlePercentageRisk: number = 50,
|
|
62
|
-
higherPercentageRisk: number = 66,
|
|
63
|
-
lowerPercentageRiskRebalance: number = 1, // %
|
|
64
|
-
middlePercentageRiskRebalance: number = 1.5, // %
|
|
65
|
-
higherPercentageRiskRebalance: number = 2, // %
|
|
66
|
-
rebalanceOnRiskSideOnly: boolean = true
|
|
67
|
-
): { odds: number[]; isRebalanced: boolean; minPercantage: number; adjustedPercantage: number } {
|
|
68
|
-
// Validate input
|
|
69
|
-
if (!Array.isArray(currentOddsArray) || currentOddsArray.length !== 2) {
|
|
70
|
-
log('Invalid odds array. Returning [0, 0].');
|
|
71
|
-
return {
|
|
72
|
-
odds: [ZERO, ZERO],
|
|
73
|
-
isRebalanced: false,
|
|
74
|
-
minPercantage: 0,
|
|
75
|
-
adjustedPercantage: 0,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (cap <= ZERO) {
|
|
80
|
-
log('Invalid cap value. Returning current odds.');
|
|
81
|
-
return {
|
|
82
|
-
odds: currentOddsArray,
|
|
83
|
-
isRebalanced: false,
|
|
84
|
-
minPercantage: 0,
|
|
85
|
-
adjustedPercantage: 0,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const [odds1, odds2] = currentOddsArray;
|
|
90
|
-
|
|
91
|
-
// Check if odds are within valid range
|
|
92
|
-
if (odds1 <= ZERO || odds1 >= 1 || odds2 <= ZERO || odds2 >= 1 || odds1 + odds2 <= 1) {
|
|
93
|
-
log('Invalid odds: either individual odds are out of range or their sum is less than 1. Returning [0, 0].');
|
|
94
|
-
return {
|
|
95
|
-
odds: [ZERO, ZERO],
|
|
96
|
-
isRebalanced: false,
|
|
97
|
-
minPercantage: 0,
|
|
98
|
-
adjustedPercantage: 0,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Find the maximum risk and its associated position
|
|
103
|
-
const maxRisk = Math.max(positionOneRisk, positionTwoRisk);
|
|
104
|
-
const riskPercentage = (Math.abs(maxRisk) / cap) * MULTIPLIER_100;
|
|
105
|
-
|
|
106
|
-
log('Max Risk: %s, Risk Percentage: %s%%', maxRisk, riskPercentage);
|
|
107
|
-
|
|
108
|
-
// Determine the adjustment factor
|
|
109
|
-
let adjustment = ZERO;
|
|
110
|
-
if (riskPercentage > lowerPercentageRisk && riskPercentage <= middlePercentageRisk) {
|
|
111
|
-
adjustment = lowerPercentageRiskRebalance / MULTIPLIER_100;
|
|
112
|
-
} else if (riskPercentage > middlePercentageRisk && riskPercentage <= higherPercentageRisk) {
|
|
113
|
-
adjustment = middlePercentageRiskRebalance / MULTIPLIER_100;
|
|
114
|
-
} else if (riskPercentage > higherPercentageRisk) {
|
|
115
|
-
adjustment = higherPercentageRiskRebalance / MULTIPLIER_100;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// If no adjustment is needed, return the current odds
|
|
119
|
-
if (adjustment === ZERO) {
|
|
120
|
-
log('Risk percentage is within acceptable limits. No rebalancing needed. Current odds: %o', currentOddsArray);
|
|
121
|
-
return {
|
|
122
|
-
odds: currentOddsArray,
|
|
123
|
-
isRebalanced: false,
|
|
124
|
-
minPercantage: 0,
|
|
125
|
-
adjustedPercantage: 0,
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Log original odds before rebalancing
|
|
130
|
-
log('Original Odds: [%s, %s]', odds1, odds2);
|
|
131
|
-
|
|
132
|
-
// Adjust the odds by multiplying with (1 ± adjustment)
|
|
133
|
-
let newOdds1, newOdds2;
|
|
134
|
-
|
|
135
|
-
if (maxRisk === positionOneRisk) {
|
|
136
|
-
newOdds1 = odds1 * (1 + adjustment); // Increase odds1
|
|
137
|
-
// do not change or decrease odds2 depending on a rebalanceOnRiskSideOnly flag
|
|
138
|
-
newOdds2 = rebalanceOnRiskSideOnly ? odds2 : odds2 * (1 - adjustment);
|
|
139
|
-
} else {
|
|
140
|
-
// do not change or decrease odds1 depending on a rebalanceOnRiskSideOnly flag
|
|
141
|
-
newOdds1 = rebalanceOnRiskSideOnly ? odds1 : odds1 * (1 - adjustment);
|
|
142
|
-
newOdds2 = odds2 * (1 + adjustment); // Increase odds2
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
log('Rebalanced Odds: [%s, %s]', newOdds1, newOdds2);
|
|
146
|
-
|
|
147
|
-
// Check if rebalanced odds are valid
|
|
148
|
-
const { odds: sumCheckedOdds, isValid, sumOfOdds } = checkOddsIfGreaterThanMinimalSum([newOdds1, newOdds2]);
|
|
149
|
-
|
|
150
|
-
// Check if rebalanced odds are valid
|
|
151
|
-
if (newOdds1 <= ZERO || newOdds2 <= ZERO || newOdds1 >= 1 || newOdds2 >= 1 || !isValid) {
|
|
152
|
-
log(
|
|
153
|
-
'Rebalanced odds invalid: sum=%s, expected >= %s, odds=[%s, %s] -> returning [0, 0].',
|
|
154
|
-
sumOfOdds,
|
|
155
|
-
MINIMAL_PROBABILITY_SUM,
|
|
156
|
-
newOdds1,
|
|
157
|
-
newOdds2
|
|
158
|
-
);
|
|
159
|
-
return {
|
|
160
|
-
odds: [ZERO, ZERO],
|
|
161
|
-
isRebalanced: false,
|
|
162
|
-
minPercantage: 0,
|
|
163
|
-
adjustedPercantage: 0,
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Return valid rebalanced odds
|
|
168
|
-
return {
|
|
169
|
-
odds: sumCheckedOdds,
|
|
170
|
-
isRebalanced: true,
|
|
171
|
-
minPercantage: lowerPercentageRisk,
|
|
172
|
-
adjustedPercantage: riskPercentage,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Rebalances multi-way (≥3) implied odds by shifting probability toward the
|
|
178
|
-
* highest-risk position and reducing the rest (implicitly via sum check).
|
|
179
|
-
*
|
|
180
|
-
* @param {number[]} currentOddsArray - Implied probabilities per position (each in (0,1)), N ≥ 3.
|
|
181
|
-
* @param {number} cap - Absolute cap used to normalize risk to percentage (must be > 0).
|
|
182
|
-
* @param {number[]} positionRisks - Risks per position; only first N used; missing padded with 0.
|
|
183
|
-
* @param {number} [lowerPercentageRisk=33] - Lower risk % threshold.
|
|
184
|
-
* @param {number} [middlePercentageRisk=50] - Middle risk % threshold.
|
|
185
|
-
* @param {number} [higherPercentageRisk=66] - Higher risk % threshold.
|
|
186
|
-
* @param {number} [lowerPercentageRiskRebalance=1] - % increase when in (lower, middle].
|
|
187
|
-
* @param {number} [middlePercentageRiskRebalance=1.5]- % increase when in (middle, higher].
|
|
188
|
-
* @param {number} [higherPercentageRiskRebalance=2] - % increase when > higher.
|
|
189
|
-
* @param {boolean} [rebalanceOnRiskSideOnly=true] - If true, only the highest-risk side is boosted and
|
|
190
|
-
* all other positions stay unchanged. If false, other positions are slightly reduced so that
|
|
191
|
-
* the overall probability mass is approximately conserved.
|
|
192
|
-
* @returns {{ odds: number[], isRebalanced: boolean, minPercantage: number, adjustedPercantage: number }} Rebalanced odds and flag.
|
|
193
|
-
*/
|
|
194
|
-
function rebalanceMultiOdds(
|
|
195
|
-
currentOddsArray: number[],
|
|
196
|
-
cap: number,
|
|
197
|
-
positionRisks: number[],
|
|
198
|
-
lowerPercentageRisk: number = 33,
|
|
199
|
-
middlePercentageRisk: number = 50,
|
|
200
|
-
higherPercentageRisk: number = 66,
|
|
201
|
-
lowerPercentageRiskRebalance: number = 1, // %
|
|
202
|
-
middlePercentageRiskRebalance: number = 1.5, // %
|
|
203
|
-
higherPercentageRiskRebalance: number = 2, // %
|
|
204
|
-
rebalanceOnRiskSideOnly: boolean = true
|
|
205
|
-
): { odds: number[]; isRebalanced: boolean; minPercantage: number; adjustedPercantage: number } {
|
|
206
|
-
// Basic validation
|
|
207
|
-
if (!Array.isArray(currentOddsArray)) {
|
|
208
|
-
log('Invalid odds array (not an array). Returning zeros.');
|
|
209
|
-
return {
|
|
210
|
-
odds: new Array(3).fill(ZERO), // same behavior as before when not an array
|
|
211
|
-
isRebalanced: false,
|
|
212
|
-
minPercantage: 0,
|
|
213
|
-
adjustedPercantage: 0,
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (currentOddsArray.length < 3) {
|
|
218
|
-
log('Multi-way rebalancing needs >= 3 positions. Returning current odds.');
|
|
219
|
-
return {
|
|
220
|
-
odds: currentOddsArray,
|
|
221
|
-
isRebalanced: false,
|
|
222
|
-
minPercantage: 0,
|
|
223
|
-
adjustedPercantage: 0,
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (cap <= ZERO) {
|
|
228
|
-
log('Invalid cap value. Returning current odds.');
|
|
229
|
-
return {
|
|
230
|
-
odds: currentOddsArray,
|
|
231
|
-
isRebalanced: false,
|
|
232
|
-
minPercantage: 0,
|
|
233
|
-
adjustedPercantage: 0,
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
const N = currentOddsArray.length;
|
|
238
|
-
|
|
239
|
-
const sum = currentOddsArray.reduce((s, o) => s + o, ZERO);
|
|
240
|
-
if (currentOddsArray.some((o) => o < ZERO || o >= 1) || sum <= 1) {
|
|
241
|
-
log('Invalid odds for multi-way. Returning zeros.');
|
|
242
|
-
return {
|
|
243
|
-
odds: new Array(N).fill(ZERO),
|
|
244
|
-
isRebalanced: false,
|
|
245
|
-
minPercantage: 0,
|
|
246
|
-
adjustedPercantage: 0,
|
|
247
|
-
};
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Use risks for the first N positions (pad with 0)
|
|
251
|
-
const risks = Array.from({ length: N }, (_, i) => Number(positionRisks?.[i] ?? ZERO));
|
|
252
|
-
|
|
253
|
-
// Find index of max risk (consistent with 2-way: Math.max, then abs() for %)
|
|
254
|
-
let maxIdx = 0;
|
|
255
|
-
for (let i = 1; i < N; i++) if (risks[i] > risks[maxIdx]) maxIdx = i;
|
|
256
|
-
const maxRisk = risks[maxIdx];
|
|
257
|
-
const riskPercentage = (Math.abs(maxRisk) / cap) * MULTIPLIER_100;
|
|
258
|
-
|
|
259
|
-
let adjustment = ZERO;
|
|
260
|
-
if (riskPercentage > lowerPercentageRisk && riskPercentage <= middlePercentageRisk) {
|
|
261
|
-
adjustment = lowerPercentageRiskRebalance / MULTIPLIER_100;
|
|
262
|
-
} else if (riskPercentage > middlePercentageRisk && riskPercentage <= higherPercentageRisk) {
|
|
263
|
-
adjustment = middlePercentageRiskRebalance / MULTIPLIER_100;
|
|
264
|
-
} else if (riskPercentage > higherPercentageRisk) {
|
|
265
|
-
adjustment = higherPercentageRiskRebalance / MULTIPLIER_100;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (adjustment === ZERO) {
|
|
269
|
-
log('Risk %% within limits (multi-way). No rebalancing. Current odds: %o', currentOddsArray);
|
|
270
|
-
return {
|
|
271
|
-
odds: currentOddsArray,
|
|
272
|
-
isRebalanced: false,
|
|
273
|
-
minPercantage: 0,
|
|
274
|
-
adjustedPercantage: 0,
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
const downFactor = adjustment / (N - 1); // e.g., +2% on one => -1% on each of two
|
|
279
|
-
const newOdds = currentOddsArray.map((o, i) => {
|
|
280
|
-
if (i === maxIdx) return o * (1 + adjustment);
|
|
281
|
-
if (rebalanceOnRiskSideOnly) return o;
|
|
282
|
-
return o * (1 - downFactor);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
// Validate new odds using the helper
|
|
286
|
-
const { odds: sumCheckedOdds, isValid, sumOfOdds } = checkOddsIfGreaterThanMinimalSum(newOdds);
|
|
287
|
-
|
|
288
|
-
// fail only on negatives or failed sum check
|
|
289
|
-
if (newOdds.some((o) => o < ZERO) || !isValid) {
|
|
290
|
-
log(
|
|
291
|
-
'Rebalanced multi-way odds invalid: sum=%s, expected >= %s, odds=%o -> returning zeros.',
|
|
292
|
-
sumOfOdds,
|
|
293
|
-
MINIMAL_PROBABILITY_SUM,
|
|
294
|
-
newOdds
|
|
295
|
-
);
|
|
296
|
-
return {
|
|
297
|
-
odds: new Array(N).fill(ZERO),
|
|
298
|
-
isRebalanced: false,
|
|
299
|
-
minPercantage: 0,
|
|
300
|
-
adjustedPercantage: 0,
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// valid → clamp any >= 1 to 0 (don’t revalidate)
|
|
305
|
-
const overOneIdx: number[] = [];
|
|
306
|
-
const finalOdds = sumCheckedOdds.map((o, i) => {
|
|
307
|
-
if (o >= 1) {
|
|
308
|
-
overOneIdx.push(i);
|
|
309
|
-
return ZERO;
|
|
310
|
-
}
|
|
311
|
-
return o;
|
|
312
|
-
});
|
|
313
|
-
if (overOneIdx.length) {
|
|
314
|
-
log('Odds >= 1 found at indexes %o; set to 0.', overOneIdx);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
return {
|
|
318
|
-
odds: finalOdds,
|
|
319
|
-
isRebalanced: true,
|
|
320
|
-
minPercantage: lowerPercentageRisk,
|
|
321
|
-
adjustedPercantage: riskPercentage,
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* Adjusts a single odds value based on risk exposure and predefined rebalancing thresholds.
|
|
327
|
-
*
|
|
328
|
-
* @param {number[]} currentOdds - An array containing a single odds value to be rebalanced.
|
|
329
|
-
* @param {number} cap - The maximum risk exposure allowed.
|
|
330
|
-
* @param {number} risk - The risk associated with the odds position.
|
|
331
|
-
* @param {number} [lowerPercentageRisk=33] - Lower threshold.
|
|
332
|
-
* @param {number} [middlePercentageRisk=50] - Middle threshold.
|
|
333
|
-
* @param {number} [higherPercentageRisk=66] - Higher threshold.
|
|
334
|
-
* @param {number} [lowerPercentageRiskRebalance=1] - % when in (lower, middle].
|
|
335
|
-
* @param {number} [middlePercentageRiskRebalance=1.5]- % when in (middle, higher].
|
|
336
|
-
* @param {number} [higherPercentageRiskRebalance=2] - % when > higher.
|
|
337
|
-
* @returns {{ odds: number[], isRebalanced: boolean, minPercantage: number, adjustedPercantage: number }}
|
|
338
|
-
*/
|
|
339
|
-
function rebalanceSingleOdd(
|
|
340
|
-
currentOdds: number[],
|
|
341
|
-
cap: number,
|
|
342
|
-
risk: number,
|
|
343
|
-
lowerPercentageRisk: number = 33,
|
|
344
|
-
middlePercentageRisk: number = 50,
|
|
345
|
-
higherPercentageRisk: number = 66,
|
|
346
|
-
lowerPercentageRiskRebalance: number = 1, // %
|
|
347
|
-
middlePercentageRiskRebalance: number = 1.5, // %
|
|
348
|
-
higherPercentageRiskRebalance: number = 2 // %
|
|
349
|
-
): { odds: number[]; isRebalanced: boolean; minPercantage: number; adjustedPercantage: number } {
|
|
350
|
-
// Validate input
|
|
351
|
-
if (!Array.isArray(currentOdds) || currentOdds.length !== 1) {
|
|
352
|
-
log('Invalid odds input. Expected an array with one element. Returning [ZERO].');
|
|
353
|
-
return {
|
|
354
|
-
odds: [ZERO],
|
|
355
|
-
isRebalanced: false,
|
|
356
|
-
minPercantage: 0,
|
|
357
|
-
adjustedPercantage: 0,
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
const [odd] = currentOdds;
|
|
362
|
-
|
|
363
|
-
if (odd <= ZERO || odd >= 1) {
|
|
364
|
-
log('Invalid odd value. Returning [ZERO].');
|
|
365
|
-
return {
|
|
366
|
-
odds: [ZERO],
|
|
367
|
-
isRebalanced: false,
|
|
368
|
-
minPercantage: 0,
|
|
369
|
-
adjustedPercantage: 0,
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
if (cap <= ZERO) {
|
|
374
|
-
log('Invalid cap value. Returning current odds.');
|
|
375
|
-
return {
|
|
376
|
-
odds: currentOdds,
|
|
377
|
-
isRebalanced: false,
|
|
378
|
-
minPercantage: 0,
|
|
379
|
-
adjustedPercantage: 0,
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Calculate risk percentage
|
|
384
|
-
const riskPercentage = (Math.abs(risk) / cap) * MULTIPLIER_100;
|
|
385
|
-
|
|
386
|
-
log('Risk: %s, Risk Percentage: %s%%', risk, riskPercentage);
|
|
387
|
-
|
|
388
|
-
// Determine the adjustment factor
|
|
389
|
-
let adjustment = ZERO;
|
|
390
|
-
if (riskPercentage > lowerPercentageRisk && riskPercentage <= middlePercentageRisk) {
|
|
391
|
-
adjustment = lowerPercentageRiskRebalance / MULTIPLIER_100;
|
|
392
|
-
} else if (riskPercentage > middlePercentageRisk && riskPercentage <= higherPercentageRisk) {
|
|
393
|
-
adjustment = middlePercentageRiskRebalance / MULTIPLIER_100;
|
|
394
|
-
} else if (riskPercentage > higherPercentageRisk) {
|
|
395
|
-
adjustment = higherPercentageRiskRebalance / MULTIPLIER_100;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// If no adjustment is needed, return the current odds
|
|
399
|
-
if (adjustment === ZERO) {
|
|
400
|
-
log('No rebalancing needed. Current odds: %o', currentOdds);
|
|
401
|
-
return {
|
|
402
|
-
odds: currentOdds,
|
|
403
|
-
isRebalanced: false,
|
|
404
|
-
minPercantage: 0,
|
|
405
|
-
adjustedPercantage: 0,
|
|
406
|
-
};
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// Log original odds before rebalancing
|
|
410
|
-
log('Original Odds: %s', odd);
|
|
411
|
-
|
|
412
|
-
// Adjust the odd based on risk direction (only positive adjustment)
|
|
413
|
-
let newOdd = odd;
|
|
414
|
-
let isRebalanced = false;
|
|
415
|
-
if (risk >= ZERO) {
|
|
416
|
-
newOdd = odd * (1 + adjustment);
|
|
417
|
-
isRebalanced = true;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
log('Rebalanced Odd: %s', newOdd);
|
|
421
|
-
|
|
422
|
-
// Validate rebalanced odd
|
|
423
|
-
if (newOdd <= ZERO || newOdd >= 1) {
|
|
424
|
-
log('Rebalanced odd is invalid (out of range). Returning [ZERO].');
|
|
425
|
-
return {
|
|
426
|
-
odds: [ZERO],
|
|
427
|
-
isRebalanced: false,
|
|
428
|
-
minPercantage: 0,
|
|
429
|
-
adjustedPercantage: 0,
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
return {
|
|
434
|
-
odds: [newOdd],
|
|
435
|
-
isRebalanced,
|
|
436
|
-
minPercantage: lowerPercentageRisk,
|
|
437
|
-
adjustedPercantage: riskPercentage,
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
/**
|
|
442
|
-
* Rebalances odds based on risk data for a specific type ID.
|
|
443
|
-
*
|
|
444
|
-
* @param {Array<number>} currentOddsArray - The array of odds to be rebalanced.
|
|
445
|
-
* @param {number} typeId - The type ID used to filter risk data.
|
|
446
|
-
* @param {Object} riskData - The risk data object containing risk information.
|
|
447
|
-
* @param {number|null} [playerId=null] - Optional player ID for player-specific risk filtering.
|
|
448
|
-
* @returns {{ odds: number[], isRebalanced: boolean, minPercantage: number, adjustedPercantage: number }} - The rebalanced odds array and a flag indicating whether rebalancing was applied.
|
|
449
|
-
*/
|
|
450
|
-
export function rebalanceOddsBasedOnRisk(
|
|
451
|
-
currentOddsArray: number[],
|
|
452
|
-
typeId: number,
|
|
453
|
-
riskData: { fileExists: boolean; riskObject: any[] },
|
|
454
|
-
playerId: number | null = null
|
|
455
|
-
): { odds: number[]; isRebalanced: boolean; minPercantage: number; adjustedPercantage: number } {
|
|
456
|
-
// Step 1: Check if riskData is missing or invalid
|
|
457
|
-
if (!riskData || !riskData.fileExists || !Array.isArray(riskData.riskObject)) {
|
|
458
|
-
log('Invalid or missing risk data. Return odds as it is.');
|
|
459
|
-
return {
|
|
460
|
-
odds: currentOddsArray,
|
|
461
|
-
isRebalanced: false,
|
|
462
|
-
minPercantage: 0,
|
|
463
|
-
adjustedPercantage: 0,
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// Step 2: Find risk configuration for the given typeId (and playerId if provided)
|
|
468
|
-
const filteredRisk = riskData.riskObject.find(
|
|
469
|
-
(entry) => entry.typeId === typeId && (playerId === null || entry.playerId === Number(playerId))
|
|
470
|
-
);
|
|
471
|
-
|
|
472
|
-
if (!filteredRisk) {
|
|
473
|
-
log(
|
|
474
|
-
'No risk entry found for typeId: %s%s. Returning original odds array: %o',
|
|
475
|
-
typeId,
|
|
476
|
-
playerId !== null && playerId !== undefined ? ` and playerId: ${playerId}` : '',
|
|
477
|
-
currentOddsArray
|
|
478
|
-
);
|
|
479
|
-
return {
|
|
480
|
-
odds: currentOddsArray,
|
|
481
|
-
isRebalanced: false,
|
|
482
|
-
minPercantage: 0,
|
|
483
|
-
adjustedPercantage: 0,
|
|
484
|
-
};
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// Step 3: Validate the odds array
|
|
488
|
-
if (!Array.isArray(currentOddsArray)) {
|
|
489
|
-
log('Invalid odds array. Returning all zeros.');
|
|
490
|
-
return {
|
|
491
|
-
odds: new Array(filteredRisk.numberOfPositions).fill(ZERO),
|
|
492
|
-
isRebalanced: false,
|
|
493
|
-
minPercantage: 0,
|
|
494
|
-
adjustedPercantage: 0,
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
const oddsLength = currentOddsArray.length;
|
|
499
|
-
|
|
500
|
-
// Step 4: Check the total implied probability
|
|
501
|
-
const { odds: adjustedOdds, isValid, sumOfOdds } = checkOddsIfGreaterThanMinimalSum(currentOddsArray);
|
|
502
|
-
|
|
503
|
-
if (!isValid && adjustedOdds.length > 1) {
|
|
504
|
-
log(
|
|
505
|
-
'Total implied probabilities too low: sum=%s, expected >= %s. Odds=%o -> returning zeros.',
|
|
506
|
-
sumOfOdds,
|
|
507
|
-
MINIMAL_PROBABILITY_SUM,
|
|
508
|
-
currentOddsArray
|
|
509
|
-
);
|
|
510
|
-
return {
|
|
511
|
-
odds: new Array(oddsLength).fill(ZERO),
|
|
512
|
-
isRebalanced: false,
|
|
513
|
-
minPercantage: 0,
|
|
514
|
-
adjustedPercantage: 0,
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
// Step 4: Extract risk values
|
|
519
|
-
const {
|
|
520
|
-
cap,
|
|
521
|
-
positionRisk = [],
|
|
522
|
-
lowerPercentageRisk,
|
|
523
|
-
middlePercentageRisk,
|
|
524
|
-
higherPercentageRisk,
|
|
525
|
-
lowerPercentageRiskRebalance,
|
|
526
|
-
middlePercentageRiskRebalance,
|
|
527
|
-
higherPercentageRiskRebalance,
|
|
528
|
-
rebalanceOnRiskSideOnly,
|
|
529
|
-
} = filteredRisk;
|
|
530
|
-
|
|
531
|
-
// Step 5: Handle one-positional, two-positional, or multi-positional odds
|
|
532
|
-
if (oddsLength === 2) {
|
|
533
|
-
log('Rebalancing two-positional odds for typeId: %s', typeId);
|
|
534
|
-
return rebalanceOdds(
|
|
535
|
-
currentOddsArray,
|
|
536
|
-
cap,
|
|
537
|
-
Number(positionRisk[0] ?? ZERO),
|
|
538
|
-
Number(positionRisk[1] ?? ZERO),
|
|
539
|
-
lowerPercentageRisk,
|
|
540
|
-
middlePercentageRisk,
|
|
541
|
-
higherPercentageRisk,
|
|
542
|
-
lowerPercentageRiskRebalance,
|
|
543
|
-
middlePercentageRiskRebalance,
|
|
544
|
-
higherPercentageRiskRebalance,
|
|
545
|
-
rebalanceOnRiskSideOnly
|
|
546
|
-
);
|
|
547
|
-
} else if (oddsLength === 1) {
|
|
548
|
-
log('Rebalancing single odd for typeId: %s', typeId);
|
|
549
|
-
return rebalanceSingleOdd(
|
|
550
|
-
currentOddsArray,
|
|
551
|
-
cap,
|
|
552
|
-
Number(positionRisk[0] ?? ZERO),
|
|
553
|
-
lowerPercentageRisk,
|
|
554
|
-
middlePercentageRisk,
|
|
555
|
-
higherPercentageRisk,
|
|
556
|
-
lowerPercentageRiskRebalance,
|
|
557
|
-
middlePercentageRiskRebalance,
|
|
558
|
-
higherPercentageRiskRebalance
|
|
559
|
-
);
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// Step 7: Handle N-positional odds
|
|
563
|
-
log('Rebalancing %d-positional odds for typeId: %s', oddsLength, typeId);
|
|
564
|
-
|
|
565
|
-
const positionRisks = Array.from({ length: oddsLength }, (_, i) => Number(positionRisk[i] ?? ZERO));
|
|
566
|
-
|
|
567
|
-
return rebalanceMultiOdds(
|
|
568
|
-
currentOddsArray,
|
|
569
|
-
cap,
|
|
570
|
-
positionRisks,
|
|
571
|
-
lowerPercentageRisk,
|
|
572
|
-
middlePercentageRisk,
|
|
573
|
-
higherPercentageRisk,
|
|
574
|
-
lowerPercentageRiskRebalance,
|
|
575
|
-
middlePercentageRiskRebalance,
|
|
576
|
-
higherPercentageRiskRebalance,
|
|
577
|
-
rebalanceOnRiskSideOnly
|
|
578
|
-
);
|
|
579
|
-
}
|