const Logs = require('../libs/logs'); const { eventSolutions } = require('./eventSolutions'); /** * 精确浮点数字 * @param {number} number * @param {number} x * @returns {number} */ const fixFloat = (number, x = 2) => { return parseFloat(number.toFixed(x)); } /** * 计算输赢比例 * 与其他方法中的输赢逻辑相反 * 正数为输 * 负数为赢 */ 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 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 calcGoldsWithTarget = (data) => { const { inner_base: 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 calcWinResultWithTarget = (data) => { const { inner_base, inner_rebate, win_target, sol1, sol2 } = data; const { cross_type: crossType1, odds_side_a: oddsA1, odds_side_b: oddsB1, odds_side_c: oddsC1, rebate_side_a: rebateA1, rebate_side_b: rebateB1, rebate_side_c: rebateC1, inner_index: inner_index_1, } = sol1; const { gold_side_a: goldA1, gold_side_b: goldB1, gold_side_c: goldC1, } = calcGoldsWithTarget({ ...sol1, inner_base, win_target }); let loss_out_1 = 0, win_inner_1 = 0; switch (inner_index_1) { case 0: loss_out_1 = goldB1 * (1 - rebateB1) + goldC1 * (1 - rebateC1); win_inner_1 = inner_base * (oddsA1 + 1); break; case 1: loss_out_1 = goldA1 * (1 - rebateA1) + goldC1 * (1 - rebateC1); win_inner_1 = inner_base * (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 = inner_base * (oddsC1 + 1) break; } win_inner_1 = fixFloat(win_inner_1); const { cross_type: crossType2, odds_side_a: oddsA2, odds_side_b: oddsB2, odds_side_c: oddsC2, rebate_side_a: rebateA2, rebate_side_b: rebateB2, rebate_side_c: rebateC2, inner_index: inner_index_2, } = sol2; const { gold_side_a: goldA2, gold_side_b: goldB2, gold_side_c: goldC2, } = calcGoldsWithTarget({ ...sol2, inner_base, 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 = inner_base + goldB2 * (1 - rebateB2) + goldC2 * (1 - rebateC2) + loss_out_1; win_inner_2 = win_inner_1 * (oddsA2 + 1); break; case 1: inner_base_key = 'goldB2'; loss_out_2 = inner_base + goldA2 * (1 - rebateA2) + goldC2 * (1 - rebateC2) + 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 = inner_base + 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 goldsInfo = { goldA1, goldB1, goldC1, goldA2, goldB2, goldC2 } if (goldsInfo[inner_base_key]) { goldsInfo[inner_base_key] = win_inner_1; } const result = { bet_info: [ { cross_type: crossType1, gold_side_a: goldsInfo.goldA1, gold_side_b: goldsInfo.goldB1, gold_side_c: goldsInfo.goldC1, odds_side_a: oddsA1, odds_side_b: oddsB1, odds_side_c: oddsC1, rebate_side_a: rebateA1, rebate_side_b: rebateB1, rebate_side_c: rebateC1, inner_index: inner_index_1, }, { cross_type: crossType2, gold_side_a: goldsInfo.goldA2, gold_side_b: goldsInfo.goldB2, gold_side_c: goldsInfo.goldC2, odds_side_a: oddsA2, odds_side_b: oddsB2, odds_side_c: oddsC2, rebate_side_a: rebateA2, rebate_side_b: rebateB2, rebate_side_c: rebateC2, inner_index: inner_index_2, } ], win_inner, inner_base, inner_rebate, } return result; } /** * 根据单关盈亏计算综合利润 */ const calcTotalProfit = (sol1, sol2, inner_base, inner_rebate) => { const rebateInner = inner_base * inner_rebate; const winTarget1 = fixFloat(sol1.win_average - rebateInner); const winTarget2 = fixFloat(sol2.win_average - rebateInner); const winTarget = fixFloat(Math.min(winTarget1, winTarget2)); const win1 = calcWinResultWithTarget({ inner_base, inner_rebate, win_target: winTarget1, sol1, sol2 })?.win_inner; const win2 = calcWinResultWithTarget({ inner_base, inner_rebate, 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 = calcWinResultWithTarget({ inner_base, inner_rebate, win_target, sol1, sol2 }); const { win_inner, ...goldsRest } = goldsInfo; 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 + rebateInner), win_diff, win_inner: fixFloat(win_inner + rebateInner), ...goldsRest }); } else { break; } } return result.at(-1); } /** * ---------------------------- * 计算第一关锁定之后的新利润 * 第一关过关后,重新计算第二关金额 * 结合第一关亏损计算第二关新利润 * ---------------------------- */ /** * 计算第二关利润 */ const calcSecondProfit = (betInfo) => { const { inner_index, inner_odds_first, odds_side_a: a, odds_side_b: b, odds_side_c: c, } = betInfo; let odds_side_a, odds_side_b, odds_side_c; switch (inner_index) { case 0: odds_side_a = fixFloat((a+1) * (inner_odds_first+1) - 1); odds_side_b = b; odds_side_c = c; break; case 1: odds_side_a = a; odds_side_b = fixFloat((b+1) * (inner_odds_first+1) - 1); odds_side_c = c; break; case 2: odds_side_a = a; odds_side_b = b; odds_side_c = fixFloat((c+1) * (inner_odds_first+1) - 1); break; } return eventSolutions({ ...betInfo, odds_side_a, odds_side_b, odds_side_c }, true); } /** * 结合第一关亏损计算第二关新利润 */ const calcTotalProfitWithFixedFirst = (betInfo1, betInfo2, inner_base, inner_rebate) => { const { cross_type: crossType1, inner_index: inner_index_1, gold_side_a: goldA1, gold_side_b: goldB1, gold_side_c: goldC1, odds_side_a: oddsA1, odds_side_b: oddsB1, odds_side_c: oddsC1, rebate_side_a: rebateA1, rebate_side_b: rebateB1, rebate_side_c: rebateC1, } = betInfo1; let loss_out_1 = 0, inner_ref_value = 0, inner_odds_1 = 0; switch (inner_index_1) { case 0: loss_out_1 = goldB1 * (1 - rebateB1) + goldC1 * (1 - rebateC1); inner_ref_value = goldA1; inner_odds_1 = oddsA1; break; case 1: loss_out_1 = goldA1 * (1 - rebateA1) + goldC1 * (1 - rebateC1); inner_ref_value = goldB1; inner_odds_1 = oddsB1; break; case 2: const { loss_proportion_a: lpA1, loss_proportion_b: lpB1 } = lossProportion(betInfo1); loss_out_1 = goldA1 * lpA1 + goldB1 * lpB1; inner_ref_value = goldC1; inner_odds_1 = oddsC1; break; } if (inner_base && inner_base != inner_ref_value) { throw new Error('inner_base is not equal to inner_ref_value'); } const profitInfo = calcSecondProfit({ ...betInfo2, inner_base, inner_odds_first: inner_odds_1, inner_rebate }); // profitInfo.win_side_a = fixFloat(profitInfo.win_side_a - loss_out_1); // profitInfo.win_side_b = fixFloat(profitInfo.win_side_b - loss_out_1); // profitInfo.win_side_c = fixFloat(profitInfo.win_side_c - loss_out_1); profitInfo.win_average = fixFloat(profitInfo.win_average - loss_out_1); return profitInfo; } module.exports = { calcTotalProfit, calcTotalProfitWithFixedFirst };