const Logs = require('../libs/logs'); const IOR_KEYS_MAP = require('./iorKeys'); const SETTING = { innerDefaultAmount: 10000, minProfitAmount: 0, innerRebateRatio: 0, } /** * 筛选最优赔率 */ 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 { innerRebateRatio } = SETTING; const { gold_side_a: x, gold_side_b: y, gold_side_m: z, odds_side_a: a, odds_side_b: b, odds_side_m: 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 * innerRebateRatio; } else if (jcIndex == 1) { jc_rebate = y * innerRebateRatio; } else if (jcIndex == 2) { jc_rebate = z * innerRebateRatio; } let win_side_a = 0, win_side_b = 0, win_side_m = 0; win_side_a = a * x - y - z; win_side_b = b * y - x - z; switch (crossType) { case 'la_wh_wa': // 全输 半赢 全赢 win_side_m = c*z - x + b*y/2; break; case 'la_dr_wa': // 全输 和局 全赢 win_side_m = c*z - x; break; case 'la_lh_wa': // 全输 半输 全赢 win_side_m = c*z - x - y/2; break; case 'lh_dr_wa': // 半输 和局 全赢 win_side_m = c*z - x/2; break; case 'lh_lh_wa': // 半输 半输 全赢 win_side_m = c*z - x/2 - y/2; break; case 'la_la_wa': // 全输 全输 全赢 win_side_m = c*z - x - y; break; } win_side_a = fixFloat(win_side_a + jc_rebate); win_side_b = fixFloat(win_side_b + jc_rebate); win_side_m = fixFloat(win_side_m + jc_rebate); const win_average = fixFloat((win_side_a + win_side_b + win_side_m) / 3); return { win_side_a, win_side_b, win_side_m, win_average } } const triangleGoldCalc = (oddsInfo, oddsOption) => { const { innerDefaultAmount } = SETTING; const { odds_side_a: a, odds_side_b: b, odds_side_m: c } = oddsInfo; if (!a || !b || !c) { return; } const { crossType, jcIndex } = oddsOption; let x = innerDefaultAmount; let 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; } if (jcIndex == 1) { const scale = innerDefaultAmount / y; x = x * scale; y = innerDefaultAmount; z = z * scale; } else if (jcIndex == 2) { const scale = innerDefaultAmount / z; x = x * scale; y = y * scale; z = innerDefaultAmount; } return { gold_side_a: x, gold_side_b: fixFloat(y), gold_side_m: fixFloat(z), odds_side_a: a, odds_side_b: b, odds_side_m: c, }; } const eventSolutions = (oddsInfo, oddsOption) => { const { innerDefaultAmount } = SETTING; const goldsInfo = triangleGoldCalc(oddsInfo, oddsOption); if (!goldsInfo) { return; } const profitInfo = triangleProfitCalc(goldsInfo, oddsOption); // console.log(goldsInfo, profitInfo); return { ...goldsInfo, ...profitInfo, cross_type: oddsOption.crossType, jc_index: oddsOption.jcIndex, jc_base: innerDefaultAmount, } } const eventsCombination = (passableEvents, setting) => { Object.keys(setting).forEach(key => { SETTING[key] = setting[key]; }); 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] = rule; const oddsSideA = iors[0]; const oddsSideB = iors[1]; const oddsSideM = iors[2]; const jcIndex = iors.findIndex(item => item.p == 'jc'); if (!oddsSideA || !oddsSideB || !oddsSideM) { return; } const cpr = [ oddsSideA, oddsSideB, oddsSideM ]; const oddsInfo = { odds_side_a: fixFloat(oddsSideA.v - 1), odds_side_b: fixFloat(oddsSideB.v - 1), odds_side_m: fixFloat(oddsSideM.v - 1) }; const oddsOption = { crossType, jcIndex }; const sol = eventSolutions(oddsInfo, oddsOption); if (sol?.win_average > SETTING.minProfitAmount) { 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 };