const crypto = require('crypto'); const Logs = require('../libs/logs'); const IOR_KEYS_MAP = require('./iorKeys'); const { getSetting } = require('./settings'); const { eventSolutions } = require('./eventSolutions'); /** * 筛选最优赔率 */ function getOptimalSelections(odds, rules) { const results = []; const { obRebateRatio, hgRebateRatio } = getSetting(); 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 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 { obRebateRatio, hgRebateRatio } = getSetting(); const { p } = ior; let rebate = 0; if (p == 'ps') { rebate = 0; } else if (p == 'ob') { rebate = obRebateRatio; } else if (p == 'hg') { rebate = hgRebateRatio; } return { ...ior, r: rebate }; } const eventsCombination = (passableEvents) => { const { minProfitAmount, 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, 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 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.r / 100).toFixed(4)), rebate_side_b: parseFloat((oddsSideB.r / 100).toFixed(4)), rebate_side_c: parseFloat((oddsSideC.r / 100).toFixed(4)), }; const sol = eventSolutions(betInfo); if (sol?.win_average > 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; }); } module.exports = { eventsCombination };