const Logs = require('../libs/logs'); const IOR_KEYS_MAP = require('./iorKeys'); const GOLD_BASE = 1000; const WIN_MIN = process.env.NODE_ENV == 'development' ? -30 : -10; const JC_REBATE_RATIO = 0.07; /** * 筛选最优赔率 */ function getOptimalSelections(data, combos) { const results = []; combos.forEach((rule, index) => { let validOptions = []; for (let i = 0; i < 3; i++) { const jcIndex = i; const selection = []; let isValid = true; for (let j = 0; j < 3; j++) { const key = rule[j]; const item = data[key]; if (!item) { isValid = false; break; } if (j === jcIndex) { if (!('jc' in item)) { isValid = false; break; } selection.push({ k: key, p: 'jc', v: item.jc, o: item, }); } else { const candidates = ['ps', 'ob'].filter((k) => k in item); if (candidates.length === 0) { isValid = false; break; } const best = candidates.reduce((a, b) => item[a] > item[b] ? a : b); selection.push({ k: key, p: best, v: item[best], o: item, }); } } if (isValid) { validOptions.push(selection); } } if (validOptions.length > 0) { const iors = validOptions.reduce((a, b) => { const sumA = a.reduce((sum, x) => sum + x.v, 0); const sumB = b.reduce((sum, x) => sum + x.v, 0); return sumA > sumB ? a : b; }); results.push({ rule, iors, index }); } }); return results; } /** * 精确浮点数字 * @param {number} number * @param {number} x * @returns {number} */ const fixFloat = (number, x=2) => { return parseFloat(number.toFixed(x)); } /** * 计算盈利 */ const triangleProfitCalc = (goldsInfo, oddsOption) => { const { gold_home: x, gold_away: y, gold_special: z, odds_home: a, odds_away: b, odds_special: c } = goldsInfo; const { crossType, jcIndex } = oddsOption; /** * crossType: * la: 全输 * wa: 全赢 * lh: 半输 * wh: 半赢 * dr: 和局 * la_wh_wa, la_dr_wa, la_lh_wa, lh_dr_wa, lh_lh_wa, la_la_wa */ let jc_rebate = 0; if (jcIndex == 0) { jc_rebate = x * JC_REBATE_RATIO; } else if (jcIndex == 1) { jc_rebate = y * JC_REBATE_RATIO; } else if (jcIndex == 2) { jc_rebate = z * JC_REBATE_RATIO; } let win_home = 0, win_away = 0, win_special = 0; win_home = fixFloat(a * x - y - z + jc_rebate); win_away = fixFloat(b * y - x - z + jc_rebate); switch (crossType) { case 'la_wh_wa': // 全输 半赢 全赢 win_special = c*z - x + b*y/2; break; case 'la_dr_wa': // 全输 和局 全赢 win_special = c*z - x; break; case 'la_lh_wa': // 全输 半输 全赢 win_special = c*z - x - y/2; break; case 'lh_dr_wa': // 半输 和局 全赢 win_special = c*z - x/2; break; case 'lh_lh_wa': // 半输 半输 全赢 win_special = c*z - x/2 - y/2; break; case 'la_la_wa': // 全输 全输 全赢 win_special = c*z - x - y; break; } win_special = fixFloat(win_special + jc_rebate); const win_average = fixFloat((win_home + win_away + win_special) / 3); return { win_home, win_away, win_special, win_average } } const triangleGoldCalc = (oddsInfo, oddsOption) => { const { odds_home: a, odds_away: b, odds_special: c } = oddsInfo; if (!a || !b || !c) { return; } const { crossType, jcIndex } = oddsOption; const x = GOLD_BASE; const y = (a + 1) * x / (b + 1); let z; switch (crossType) { case 'la_wh_wa': // 全输 半赢 全赢 z = b * y / 2 / (c + 1); break; case 'la_dr_wa': // 全输 和局 全赢 z = b * y / (c + 1); break; case 'la_lh_wa': // 全输 半输 全赢 z = (2 * b + 1) * y / 2 / (c + 1); break; case 'lh_dr_wa': // 半输 和局 全赢 z = (b * y - x / 2) / (c + 1); break; case 'lh_lh_wa': // 半输 半输 全赢 z = (b * y + y / 2 - x / 2) / (c + 1); break; case 'la_la_wa': // 全输 全输 全赢 z = (b + 1) * y / (c + 1); break; default: z = 0; } return { gold_home: x, gold_away: fixFloat(y), gold_special: fixFloat(z), odds_home: a, odds_away: b, odds_special: c }; } const eventSolutions = (oddsInfo, oddsOption) => { const { reverse } = oddsOption; const goldsInfo = triangleGoldCalc(oddsInfo, oddsOption); if (!goldsInfo) { return; } const profitInfo = triangleProfitCalc(goldsInfo, oddsOption); return { ...goldsInfo, ...profitInfo, reverse, } } const eventsCombination = (passableEvents) => { const solutions = []; passableEvents.forEach(events => { const { odds, info } = events; Object.keys(IOR_KEYS_MAP).forEach(group => { const rules = IOR_KEYS_MAP[group]; const optimalSelections = getOptimalSelections(odds, rules); optimalSelections.forEach(selection => { const { rule, iors, index } = selection; const [, , , crossType, reverse] = rule; const oddsHome = iors[0]; const oddsAway = iors[1]; const oddsSpecial = iors[2]; const jcIndex = iors.findIndex(item => item.p == 'jc'); if (!oddsHome || !oddsAway || !oddsSpecial) { return; } const cpr = [ oddsHome, oddsAway, oddsSpecial ]; const oddsInfo = { odds_home: fixFloat(oddsHome.v - 1), odds_away: fixFloat(oddsAway.v - 1), odds_special: fixFloat(oddsSpecial.v - 1) }; const oddsOption = { crossType, reverse, jcIndex }; const sol = eventSolutions(oddsInfo, oddsOption); if (sol?.win_average > WIN_MIN) { const id = info.id; const keys = cpr.map(item => item.k).join('_'); const sid = `${id}_${keys}`; const timestamp = Date.now(); solutions.push({sid, sol, cpr, info, rule: `${group}:${index}`, timestamp}); } }); }); }); return solutions; } module.exports = eventsCombination;