overtime-utils 0.1.74 → 0.1.75

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