const crypto = require('crypto'); const Logs = require('../libs/logs'); const IOR_KEYS_MAP = require('./iorKeys'); const SETTING = { innerDefaultAmount: 10000, minProfitAmount: 0, innerRebateRatio: 0, obRebateRatio: 0, hgRebateRatio: 0, } /** * 筛选最优赔率 */ function getOptimalSelections(odds, rules) { const results = []; const { obRebateRatio, hgRebateRatio } = SETTING; const rebateMap = { ob: 1 + obRebateRatio / 100, hg: 1 + hgRebateRatio / 100, }; rules.forEach((rule, index) => { let validOptions = []; for (let i = 0; i < 3; i++) { const innerIndex = i; const selection = []; let isValid = true; for (let j = 0; j < 3; j++) { const key = rule[j]; const item = odds[key]; if (!item) { isValid = false; break; } if (j === innerIndex) { if (!('ps' in item)) { isValid = false; break; } selection.push({ k: key, p: 'ps', v: item.ps, o: item, }); } else { const candidates = ['ob', 'hg'].filter((k) => k in item); if (candidates.length === 0) { isValid = false; break; } // Logs.out('candidates', candidates) const best = candidates.reduce((a, b) => { const aValue = (item[a]-1)*rebateMap[a]; const bValue = (item[b]-1)*rebateMap[b]; const seletcted = aValue > bValue ? a : b; return seletcted; }); // Logs.out('best', item, best) 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_c: z, odds_side_a: a, odds_side_b: b, odds_side_c: c } = goldsInfo; const { crossType, innerIndex, rebateA: A = 0, rebateB: B = 0, rebateC: C = 0 } = 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 inner_rebate = 0; if (innerIndex == 0) { inner_rebate = x * innerRebateRatio; } else if (innerIndex == 1) { inner_rebate = y * innerRebateRatio; } else if (innerIndex == 2) { inner_rebate = z * innerRebateRatio; } let win_side_a = 0, win_side_b = 0, win_side_c = 0; win_side_a = a*x - y - z + a*A*x + B*y + C*z; win_side_b = b*y - x - z + b*B*y + A*x + C*z; switch (crossType) { case 'la_wh_wa': // 全输 半赢 全赢 win_side_c = c*z - x + b*y/2 + c*C*z + A*x + b*B*y/2; break; case 'la_dr_wa': // 全输 和局 全赢 win_side_c = c*z - x + c*C*z + A*x; break; case 'la_lh_wa': // 全输 半输 全赢 win_side_c = c*z - x - y/2 + c*C*z + A*x + B*y/2; break; case 'lh_dr_wa': // 半输 和局 全赢 win_side_c = c*z - x/2 + c*C*z + A*x/2; break; case 'lh_lh_wa': // 半输 半输 全赢 win_side_c = c*z - x/2 - y/2 + c*C*z + A*x/2 + B*y/2; break; case 'la_la_wa': // 全输 全输 全赢 win_side_c = c*z - x - y + c*C*z + A*x + B*y; break; } win_side_a = fixFloat(win_side_a + inner_rebate); win_side_b = fixFloat(win_side_b + inner_rebate); win_side_c = fixFloat(win_side_c + inner_rebate); const win_average = fixFloat((win_side_a + win_side_b + win_side_c) / 3); return { win_side_a, win_side_b, win_side_c, win_average } } const triangleGoldCalc = (oddsInfo, oddsOption) => { const { innerDefaultAmount } = SETTING; const { odds_side_a: a, odds_side_b: b, odds_side_c: c } = oddsInfo; if (!a || !b || !c) { return; } const { crossType, innerIndex, rebateA: A = 0, rebateB: B = 0, rebateC: C = 0 } = oddsOption; 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; let x = innerDefaultAmount; let y = (k1 + k2) * x / (k3 + k4); let z; switch (crossType) { case 'la_wh_wa': // 全输 半赢 全赢 z = k3 * y / 2 / (k5 + k6); break; case 'la_dr_wa': // 全输 和局 全赢 z = k3 * y / (k5 + k6); break; case 'la_lh_wa': // 全输 半输 全赢 z = (k3 + k4 / 2) * y / (k5 + k6); break; case 'lh_dr_wa': // 半输 和局 全赢 z = (k3 * y - k2 * x / 2) / (k5 + k6); break; case 'lh_lh_wa': // 半输 半输 全赢 z = ((k3 + k4/2) * y - k2 * x / 2) / (k5 + k6); break; case 'la_la_wa': // 全输 全输 全赢 z = (k3 + k4) * y / (k5 + k6); break; default: z = 0; } if (innerIndex == 1) { const scale = innerDefaultAmount / y; x = x * scale; y = innerDefaultAmount; z = z * scale; } else if (innerIndex == 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_c: fixFloat(z), odds_side_a: a, odds_side_b: b, odds_side_c: c, }; } const eventSolutions = (oddsInfo, oddsOption) => { const { innerDefaultAmount } = SETTING; const goldsInfo = triangleGoldCalc(oddsInfo, oddsOption); if (!goldsInfo) { return; } const profitInfo = triangleProfitCalc(goldsInfo, oddsOption); const { odds_side_a, odds_side_b, odds_side_c } = goldsInfo; const { win_side_a, win_side_b, win_side_c, win_average } = profitInfo; return { odds_side_a, odds_side_b, odds_side_c, win_side_a, win_side_b, win_side_c, win_average, rebate_side_a: oddsOption.rebateA, rebate_side_b: oddsOption.rebateB, rebate_side_c: oddsOption.rebateC, cross_type: oddsOption.crossType, inner_index: oddsOption.innerIndex, inner_base: innerDefaultAmount, } } /** * 盘口排序 */ const priority = { ps: 1, ob: 2, hg: 3 }; const sortCpr = (cpr) => { const temp = [...cpr]; temp.sort((a, b) => priority[a.p] - priority[b.p]); return temp; } /** * 添加返佣 */ const attachRebate = (ior) => { const { innerRebateRatio, obRebateRatio, hgRebateRatio } = SETTING; const { p } = ior; let rebate = 0; if (p == 'ps') { rebate = innerRebateRatio; } else if (p == 'ob') { rebate = obRebateRatio; } else if (p == 'hg') { rebate = hgRebateRatio; } return { ...ior, r: rebate }; } const eventsCombination = (passableEvents, setting) => { Object.keys(setting).forEach(key => { if (key in SETTING && SETTING[key] !== setting[key]) { SETTING[key] = setting[key]; Logs.out(`setting ${key} changed to ${setting[key]}`); } }); const solutions = []; passableEvents.forEach(events => { const { odds, info } = events; Object.keys(IOR_KEYS_MAP).forEach(iorGroup => { const rules = IOR_KEYS_MAP[iorGroup]; const optimalSelections = getOptimalSelections(odds, rules); optimalSelections.forEach(selection => { const { rule, iors, index } = selection; const [, , , crossType] = rule; const oddsSideA = attachRebate(iors[0]); const oddsSideB = attachRebate(iors[1]); const oddsSideC = attachRebate(iors[2]); const innerIndex = iors.findIndex(item => item.p == 'ps'); if (!oddsSideA || !oddsSideB || !oddsSideC) { return; } const cpr = [ oddsSideA, oddsSideB, oddsSideC ]; const oddsInfo = { odds_side_a: fixFloat(oddsSideA.v - 1), odds_side_b: fixFloat(oddsSideB.v - 1), odds_side_c: fixFloat(oddsSideC.v - 1), }; const oddsOption = { crossType, innerIndex, rebateA: parseFloat((oddsSideA.r / 100).toFixed(4)), rebateB: parseFloat((oddsSideB.r / 100).toFixed(4)), rebateC: parseFloat((oddsSideC.r / 100).toFixed(4)), }; const sol = eventSolutions(oddsInfo, oddsOption); if (sol?.win_average > SETTING.minProfitAmount) { const id = info.id; const sortedCpr = sortCpr(cpr); const keys = sortedCpr.map(item => `${item.k}`).join('_'); const sid = crypto.createHash('sha1').update(`${id}_${keys}`).digest('hex'); const crpGroup = `${id}_${sortedCpr[0].k}`; const timestamp = Date.now(); solutions.push({sid, sol, cpr, info, group: crpGroup, rule: `${iorGroup}:${index}`, timestamp}); } }); }); }); return solutions.sort((a, b) => { return b.sol.win_average - a.sol.win_average; }); // return Object.values(solutions).map(item => { // return item.sort((a, b) => { // return b.sol.win_average - a.sol.win_average; // }) // .slice(0, 2); // }) // .sort((a, b) => { // return b[0].sol.win_average - a[0].sol.win_average; // }); } module.exports = { eventsCombination };