const crypto = require('crypto'); const Logs = require('../libs/logs'); const IOR_KEYS_MAP = require('./iorKeys'); const { getSetting } = require('./settings'); const { eventSolutions } = require('./eventSolutions'); const HG_REBATE_RATIO_LOWER = 0.75; /** * 筛选最优赔率 */ const oddRebateValue = (odds, platform, key) => { const setting = getSetting(); let rebateRatio = setting[`${platform}RebateRatio`] ?? 0; let rebateType = setting[`${platform}RebateType`] ?? 0; if (platform == 'hg' && key.startsWith('ior_m')) { rebateRatio -= HG_REBATE_RATIO_LOWER; } if (rebateType == 0) { return odds * (1 + rebateRatio / 100); } return odds + rebateRatio / 100; } const cartesianOdds = (selection) => { const [a, b, c] = selection; return a.flatMap(itemA => b.flatMap(itemB => c.map(itemC => [itemA, itemB, itemC]))); } const getOptimalSelections = (odds, rules) => { const results = []; rules.forEach((rule, ruleIndex) => { 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]; let item = odds[key]; if (key == '-') { item = { no: { v: 1 } }; } if (!item) { isValid = false; break; } if (j === innerIndex) { if (!('ps' in item)) { isValid = false; break; } selection.push([{ k: key, p: 'ps', v: item.ps.v, r: item.ps.r, s: item.ps.s, o: item }]); } else { const { ps, ...itemRest } = item; // const candidates = ['ob', 'hg', 'no'].filter((k) => k in item); const candidates = Object.keys(itemRest); if (candidates.length === 0) { isValid = false; break; } // Logs.out('candidates', candidates) // const best = candidates.reduce((a, b) => { // const aValue = oddRebateValue(item[a].v-1, a, key); // const bValue = oddRebateValue(item[b].v-1, b, key); // const seletcted = aValue > bValue ? a : b; // return seletcted; // }); // Logs.out('best', item, best) // selection.push({ // k: key, // p: best, // v: item[best].v, // r: item[best].r, // s: item[best].s, // o: item // }); selection.push(candidates.map(k => ({ k: key, p: k, v: item[k].v, r: item[k].r, s: item[k].s, o: item }))); } } if (isValid) { const cartesian = cartesianOdds(selection); cartesian.forEach(item => { validOptions.push(item); }); } } validOptions.forEach(iors => { results.push({ rule, iors, ruleIndex }); }); // 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, ruleIndex }); // } }); // if (results.length) { // Logs.outDev('results', results); // } // return []; return results; } /** * 精确浮点数字 * @param {number} number * @param {number} x * @returns {number} */ const fixFloat = (number, x=2) => { return parseFloat(number.toFixed(x)); } /** * 盘口排序 */ 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 getPlatformKey = (cpr) => { const platforms = sortCpr(cpr).map(item => item.p); return [...new Set(platforms)].join('_'); } /** * 添加返佣 */ const attachRebate = (ior) => { const { obRebateRatio, obRebateType, hgRebateRatio, hgRebateType } = getSetting(); const { p, k } = ior; let rebate = 0, rebateType = 0; if (p == 'ps') { rebate = 0; rebateType = -1; } else if (p == 'ob') { rebate = obRebateRatio; rebateType = obRebateType; } else if (p == 'hg') { rebate = hgRebateRatio; rebateType = hgRebateType; if (k.startsWith('ior_m')) { rebate -= HG_REBATE_RATIO_LOWER; } } return { ...ior, b: rebate, t: rebateType }; } const eventsCombination = (passableEvents) => { // Logs.outDev('passableEvents', passableEvents); const { innerDefaultAmount, innerRebateRatio } = getSetting(); 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, ruleIndex } = 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 betInfo = { cross_type: crossType, inner_index: innerIndex, inner_base: innerDefaultAmount, inner_rebate: fixFloat(innerRebateRatio / 100, 3), odds_side_a: fixFloat(oddsSideA.v - 1), odds_side_b: fixFloat(oddsSideB.v - 1), odds_side_c: fixFloat(oddsSideC.v - 1), rebate_side_a: parseFloat((oddsSideA.b / 100).toFixed(4)), rebate_type_side_a: oddsSideA.t, rebate_side_b: parseFloat((oddsSideB.b / 100).toFixed(4)), rebate_type_side_b: oddsSideB.t, rebate_side_c: parseFloat((oddsSideC.b / 100).toFixed(4)), rebate_type_side_c: oddsSideC.t, }; const sol = eventSolutions(betInfo, true); if (cpr[2].k == '-') { cpr.pop(); } if (!isNaN(sol?.win_average)) { const id = info.id; // const sortedCpr = sortCpr(cpr); const keys = cpr.map(item => `${item.k}-${item.p}`).join('##'); // Logs.outDev('keys', `${id}##${keys}`) const sid = crypto.createHash('sha1').update(`${id}-${keys}`).digest('hex'); const crpGroup = `${id}_${cpr.find(item => item.p == 'ps').k}`; // Logs.outDev('crpGroup', crpGroup) const platformKey = getPlatformKey(cpr); // Logs.outDev('platformKey', platformKey, cpr) const timestamp = Date.now(); solutions.push({sid, sol, cpr, cross: platformKey, info, group: crpGroup, rule: `${iorGroup}:${ruleIndex}`, timestamp}); } }); }); }); return solutions.sort((a, b) => { return b.sol.win_average - a.sol.win_average; }); } module.exports = { eventsCombination };