import crypto from 'crypto'; import iorKeys from './iorKeys.js'; import eventSolutions from './eventSolutions.js'; /** * 精确浮点数字 * @param {number} number * @param {number} x * @returns {number} */ const fixFloat = (number, x=3) => { return parseFloat(number.toFixed(x)); } /** * 盘口排序 */ const priority = { polymarket: 1, pinnacle: 2, huanguan: 3, obsports: 4 }; const getPriority = (p) => { return priority[p] ?? 99; } const sortCpr = (cpr) => { const temp = [...cpr]; temp.sort((a, b) => getPriority(a.p) - getPriority(b.p)); return temp; } /** * 获取平台类型 */ const getPlatformKey = (cpr) => { const platforms = sortCpr(cpr).map(item => item.p); return [...new Set(platforms)].join('_'); } 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) => { const validOptions = []; const selection = []; let isValid = true; for (let i = 0; i < 3; i++) { const key = rule[i]; let item = odds[key]; if (key === '-') { item = { no: { v: 1 } }; } if (!item) { isValid = false; break; } const candidates = Object.keys(item); if (candidates.length === 0) { isValid = false; break; } selection.push(candidates.map(k => ({ k: key, p: k, o: item, ...item[k], }))); } if (isValid) { const cartesian = cartesianOdds(selection); cartesian.forEach(iors => { const hasPm = iors.some(ior => ior.p === 'polymarket'); const iorsCount = new Set(iors.filter(ior => ior.p !== 'no').map(ior => ior.p)); if (hasPm && iorsCount.size > 1) { validOptions.push(iors); } }); } validOptions.forEach(iors => { results.push({ rule, iors, ruleIndex }); }); }); return results; } export const getPassableEvents = (relations) => { return relations.map(({ id, platforms }) => { const eventsMap = {}; const oddsMap = {}; Object.keys(platforms).forEach(platform => { const { odds, evtime } = platforms[platform] ?? {}; if (!odds) { // console.log('odds not found', platform, odds); return; } if (evtime < Date.now() - 1000 * 15) { // console.log('odds is expired', platform, evtime); return; } Object.keys(odds).forEach(ior => { if (!oddsMap[ior]) { oddsMap[ior] = {}; } oddsMap[ior][platform] = odds[ior] }); }); eventsMap['odds'] = oddsMap; eventsMap['rid'] = id; return eventsMap; }) .filter(item => item?.rid); } export const eventsCombination = (passableEvents) => { const solutions = []; passableEvents.forEach(events => { const { odds, rid } = events; Object.keys(iorKeys).forEach(iorGroup => { const rules = iorKeys[iorGroup]; const optimalSelections = getOptimalSelections(odds, rules); optimalSelections.forEach(selection => { const { iors, rule, ruleIndex } = selection; const [, , , crossType] = rule; const [ oddsSideA, oddsSideB, oddsSideC ] = iors; const baseIndex = iors.reduce((minIdx, cur, idx) => cur.v < iors[minIdx].v ? idx : minIdx, 0); if (!oddsSideA || !oddsSideB || !oddsSideC) { return; } if (oddsSideA.v <= 1 || oddsSideB.v <= 1) { return; } const cpr = [oddsSideA, oddsSideB, oddsSideC]; const betInfo = { cross_type: crossType, base_index: baseIndex, base_stake: 10000, odds_side_a: fixFloat(oddsSideA.v - 1), odds_side_b: fixFloat(oddsSideB.v - 1), odds_side_c: fixFloat(oddsSideC.v - 1), rebate_side_a: fixFloat(((oddsSideA.b ?? 0) / 100), 4), rebate_side_b: fixFloat(((oddsSideB.b ?? 0) / 100), 4), rebate_side_c: fixFloat(((oddsSideC.b ?? 0) / 100), 4), rebate_type_side_a: oddsSideA.t ?? 0, rebate_type_side_b: oddsSideB.t ?? 0, rebate_type_side_c: oddsSideC.t ?? 0, }; const sol = eventSolutions(betInfo, true); if (cpr[2].k == '-') { cpr.pop(); } if (!isNaN(sol?.win_average)) { const keys = cpr.map(item => `${item.k}-${item.p}`).join('$$'); const sid = crypto.createHash('sha1').update(`${rid}-${keys}`).digest('hex'); const hasLower = cpr.some(item => item.q === 0); const platformKey = getPlatformKey(cpr); const timestamp = Date.now(); solutions.push({sid, sol, cpr, cross: platformKey, rid, lower: hasLower, rule: `${iorGroup}:${ruleIndex}`, timestamp}); } }); }); }); return solutions.sort((a, b) => b.sol.win_profit_rate - a.sol.win_profit_rate); }