const fixFloat = (number, x = 2) => { return parseFloat(number.toFixed(x)); } const HandicapCalc = function (data) { const { i, g, a, b, c, A, B, C, w, l } = data; const t = w + l; const k1 = a * (1 + A); const k2 = 1 - A; const k3 = b * (1 + B); const k4 = 1 - B; const k5 = c * (1 + C); const k6 = 1 - C; const calcTemplate = (handlers) => { if (i > 2 || i < 0) { return {}; } if (i === 2) { const z = g; const m = t + k6 * z; const x = (k3 + k4) * m / (k1 * k3 - k2 * k4); const y = (k1 + k2) * m / (k1 * k3 - k2 * k4); return { x, y, z }; }; return handlers[i]?.() ?? {}; }; return { la_wh_wa() { return calcTemplate([ () => { const x = g; const m = t + k2 * x; const z = m / (2 * k5 + k6); const y = (k5 + k6) * m / (k3 * k5 + k3 * k6 / 2); return { x, y, z }; }, () => { const y = g; const z = ((k1 + k2) * t + (k2 * k4 - k1 * k3 / 2) * y) / (k1 * k5 - k2 * k6); const x = ((k5 + k6) * t + (k4 * k5 - k3 * k6 / 2) * y) / (k1 * k5 - k2 * k6); return { x, y, z }; } ]); }, la_dr_wa() { return calcTemplate([ () => { const x = g; const m = t + k2 * x; const z = m / k5; const y = (k5 + k6) * m / (k3 * k5); return { x, y, z }; }, () => { const y = g; const z = ((k1 + k2) * t + k2 * k4 * y) / (k1 * k5 - k2 * k6); const x = ((k5 + k6) * t + k4 * k5 * y) / (k1 * k5 - k2 * k6); return { x, y, z }; } ]); }, la_lh_wa() { return calcTemplate([ () => { const x = g; const m = t + k2 * x; const z = (2 * k3 + k4) * m / (2 * k3 * k5 - k4 * k6); const y = (k5 + k6) * m / (k3 * k5 - k4 * k6 / 2); return { x, y, z }; }, () => { const y = g; const z = ((k1 + k2) * t + (k2 * k4 + k1 * k4 / 2) * y) / (k1 * k5 - k2 * k6); const x = ((k5 + k6) * t + (k4 * k5 + k4 * k6 / 2) * y) / (k1 * k5 - k2 * k6); return { x, y, z }; } ]); }, lh_dr_wa() { return calcTemplate([ () => { const x = g; const z = (t + k2 * x / 2) / k5; const y = ((k5 + k6) * t + (k2 * k5 + k2 * k4 / 2) * x) / (k3 * k5); return { x, y, z }; }, () => { const y = g; const z = ((2 * k1 + k2) * t + k2 * k4 * y) / (2 * k1 * k5 - k2 * k6); const x = ((k5 + k6) * t + k4 * k5 * y) / (k1 * k5 - k2 * k6 / 2); return { x, y, z }; } ]); }, lh_lh_wa() { return calcTemplate([ () => { const x = g; const z = ((2 * k3 + k4) * t + (k3 + k4) * k2 * x) / (2 * k3 * k5 - k4 * k6); const y = ((k5 + k6) * t + (k5 + k6 / 2) * k2 * x) / (k3 * k5 - k4 * k6 / 2); return { x, y, z }; }, () => { const y = g; const z = ((2 * k1 + k2) * t + (k1 + k2) * k4 * y) / (2 * k1 * k5 - k2 * k6); const x = ((k5 + k6) * t + (k5 + k6 / 2) * k4 * y) / (k1 * k5 - k2 * k6 / 2); return { x, y, z }; } ]); }, la_la_wa() { return calcTemplate([ () => { const x = g; const m = t + k2 * x; const z = (k3 + k4) * m / (k3 * k5 - k4 * k6); const y = (k5 + k6) * m / (k3 * k5 - k4 * k6); return { x, y, z }; }, () => { const y = g; const m = t + k2 * y; const z = (k1 + k2) * m / (k1 * k5 - k2 * k6); const x = (k5 + k6) * m / (k1 * k5 - k2 * k6); return { x, y, z }; } ]); } } } /** * 计算输赢比例 * 与其他方法中的输赢逻辑相反 * 正数为输 * 负数为赢 */ const CROSS_TYPE_MAP = { w: -1, l: 1, a: 1, h: 0.5, d: 0, r: 0 }; const lossProportion = (sol) => { const { cross_type, odds_side_a, odds_side_b, rebate_side_a, rebate_side_b } = sol; const typeList = cross_type.split('_').map(part => { return part.split('').map(key => CROSS_TYPE_MAP[key]); }).map(([a, b])=> a * b); let loss_proportion_a = 0, loss_proportion_b = 0; if (typeList[0] >= 0) { loss_proportion_a = typeList[0] * (1 - rebate_side_a); } else { loss_proportion_a = typeList[0] * odds_side_a * (1 + rebate_side_a); } if (typeList[1] >= 0) { loss_proportion_b = typeList[1] * (1 - rebate_side_b); } else { loss_proportion_b = typeList[1] * odds_side_b * (1 + rebate_side_b); } return { loss_proportion_a, loss_proportion_b }; } const calcExternalHandicap = (data) => { const { gold_side_inner: g, odds_side_a: a, odds_side_b: b, odds_side_c: c, rebate_side_a: A, rebate_side_b: B, rebate_side_c: C, inner_index: i, cross_type: t, win_target: w, loss_out: l = 0, } = data; const calc = new HandicapCalc({ i, g, a, b, c, A, B, C, w, l }); const { x, y, z } = calc?.[t]() ?? {}; return { gold_side_a: fixFloat(x), gold_side_b: fixFloat(y), gold_side_c: fixFloat(z), } } const calcGoldsWithWinTarget = (data) => { const { gold_side_inner, win_target, sol1, sol2 } = data; const { odds_side_a: oddsA1, odds_side_b: oddsB1, odds_side_c: oddsC1, inner_index: inner_index_1, } = sol1; const { gold_side_a: goldA1, gold_side_b: goldB1, gold_side_c: goldC1, } = calcExternalHandicap({ ...sol1, gold_side_inner, win_target }); let loss_out_1 = 0, win_inner_1 = 0; switch (inner_index_1) { case 0: loss_out_1 = goldB1 + goldC1; win_inner_1 = gold_side_inner * (oddsA1 + 1); break; case 1: loss_out_1 = goldA1 + goldC1; win_inner_1 = gold_side_inner * (oddsB1 + 1); break; case 2: const { loss_proportion_a: lpA1, loss_proportion_b: lpB1 } = lossProportion(sol1); loss_out_1 = goldA1 * lpA1 + goldB1 * lpB1 ; win_inner_1 = gold_side_inner * (oddsC1 + 1) break; } const { odds_side_a: oddsA2, odds_side_b: oddsB2, odds_side_c: oddsC2, inner_index: inner_index_2, } = sol2; const { gold_side_a: goldA2, gold_side_b: goldB2, gold_side_c: goldC2, } = calcExternalHandicap({ ...sol2, gold_side_inner, win_target, loss_out: loss_out_1 }); let loss_out_2 = 0, win_inner_2 = 0, inner_base_key; switch (inner_index_2) { case 0: inner_base_key = 'goldA2'; loss_out_2 = gold_side_inner +goldB2 + goldC2 + loss_out_1; win_inner_2 = win_inner_1 * (oddsA2 + 1); break; case 1: inner_base_key = 'goldB2'; loss_out_2 = gold_side_inner + goldA2 + goldC2 + loss_out_1; win_inner_2 = win_inner_1 * (oddsB2 + 1); break; case 2: const { loss_proportion_a: lpA2, loss_proportion_b: lpB2 } = lossProportion(sol2); inner_base_key = 'goldC2'; loss_out_2 = gold_side_inner + goldA2 * lpA2 + goldB2 * lpB2 + loss_out_1; win_inner_2 = win_inner_1 * (oddsC2 + 1); break; } const win_inner = fixFloat(win_inner_2 - loss_out_2); const result = { goldA1, goldB1, goldC1, goldA2, goldB2, goldC2, win_inner, inner_index_1, inner_index_2, inner_base: gold_side_inner, } if (result[inner_base_key]) { result[inner_base_key] = win_inner_1; } return result; } const calcTotalProfit = (sol1, sol2, gold_side_inner) => { const winTarget1 = sol1.win_average; const winTarget2 = sol2.win_average; const winTarget = fixFloat(Math.min(winTarget1, winTarget2), 2); const win1 = calcGoldsWithWinTarget({ gold_side_inner, win_target: winTarget1, sol1, sol2 })?.win_inner; const win2 = calcGoldsWithWinTarget({ gold_side_inner, win_target: winTarget2, sol1, sol2 })?.win_inner; const win_inner = fixFloat(Math.max(win1, win2), 2); const start = Math.max(winTarget, win_inner); const end = Math.min(winTarget, win_inner); const result = []; for (let i = start; i > end; i--) { const win_target = i; const goldsInfo = calcGoldsWithWinTarget({ gold_side_inner, win_target, sol1, sol2 }); const win_diff = Math.abs(fixFloat(win_target - goldsInfo.win_inner)); const lastResult = result.at(-1); if (!lastResult?.win_diff || win_diff < lastResult.win_diff) { result.push({ win_target: fixFloat(win_target), win_diff, ...goldsInfo }); } else { break; } } return result.at(-1); } module.exports = calcTotalProfit;