| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- const crypto = require('crypto');
- const IOR_KEYS_MAP = require('./iorKeys');
- const { getSetting } = require('./settings');
- const { eventSolutions } = require('./eventSolutions');
- 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 = Object.keys(itemRest);
- if (candidates.length === 0) {
- isValid = false;
- break;
- }
- selection.push(candidates.map(k => ({
- k: key,
- p: k,
- v: item[k].v,
- r: item[k].r,
- s: item[k].s,
- q: item[k].q,
- o: item
- })));
- }
- }
- if (isValid) {
- const cartesian = cartesianOdds(selection);
- cartesian.forEach(item => {
- validOptions.push(item);
- });
- }
- }
- validOptions.forEach(iors => {
- results.push({ rule, iors, ruleIndex });
- });
- });
- 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, pc: 4 };
- 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, hgRebateLower, hgRebateType, pcRebateRatio, pcRebateType } = getSetting();
- const { p, k } = ior;
- let rebate = 0, rebateType = 0;
- if (p == 'ps') {
- rebate = 0;
- rebateType = -1;
- }
- else if (p == 'pc') {
- rebate = pcRebateRatio;
- rebateType = pcRebateType;
- }
- else if (p == 'ob') {
- rebate = obRebateRatio;
- rebateType = obRebateType;
- }
- else if (p == 'hg') {
- rebate = hgRebateRatio;
- rebateType = hgRebateType;
- if (k.startsWith('ior_m')) {
- rebate = hgRebateLower;
- }
- }
- return { ...ior, b: rebate, t: rebateType };
- }
- /**
- * 提取盘口数据
- */
- const extractOdds = ({ evtime, events, sptime, special }) => {
- const { expireTimeEvents, expireTimeSpecial } = getSetting();
- const nowTime = Date.now();
- const expireTimeEv = nowTime - expireTimeEvents;
- const expireTimeSP = nowTime - expireTimeSpecial;
- const extractData = {
- odds: null,
- evExpire: false,
- spExpire: false,
- removeCount: 0,
- }
- let odds = {};
- if (evtime > expireTimeEv) {
- odds = { ...odds, ...events };
- }
- else if (events) {
- extractData.evExpire = true;
- }
- if (sptime > expireTimeSP) {
- odds = { ...odds, ...special };
- }
- else if (special) {
- extractData.spExpire = true;
- }
- Object.keys(odds).forEach(ior => {
- if (!odds[ior]?.v || odds[ior].v <= 0) {
- delete odds[ior];
- extractData.removeCount++;
- }
- });
- extractData.odds = odds;
- return extractData;
- }
- /**
- * 筛选有效盘口
- */
- const getPassableEvents = (relations, eventsLogsMap) => {
- return relations.map(({ id, rel, mk }) => {
- const eventsMap = {};
- const oddsMap = {};
- Object.keys(rel).forEach(platform => {
- const { leagueName, teamHomeName, teamAwayName, timestamp, stage, score, evtime, events, sptime, special } = rel[platform];
- if (!events && !special) {
- return;
- }
- if (platform == 'ps') {
- eventsMap.info = { leagueName, teamHomeName, teamAwayName, id, timestamp, stage, score };
- }
- const { odds, evExpire, spExpire, removeCount } = extractOdds({ evtime, events, sptime, special });
- /** 日志 盘口过期 */
- if (eventsLogsMap && (evExpire || spExpire)) {
- eventsLogsMap.expireEvents?.push({
- mk, platform,
- info: rel[platform],
- evExpire, spExpire,
- evtime, sptime,
- });
- }
- /** 日志 盘口移除 */
- if (eventsLogsMap && removeCount) {
- eventsLogsMap.removeEvents?.push({
- mk, platform,
- info: rel[platform],
- removeCount,
- });
- }
- /** 日志 End */
- Object.keys(odds).forEach(ior => {
- if (!oddsMap[ior]) {
- oddsMap[ior] = {};
- }
- oddsMap[ior][platform] = odds[ior];
- });
- });
- eventsMap.odds = oddsMap;
- return eventsMap;
- })
- .filter(item => item?.info);
- }
- /**
- * 盘口组合计算
- */
- const eventsCombination = (passableEvents, innerBase, innerRebate) => {
- 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: innerBase ?? innerDefaultAmount,
- inner_rebate: innerRebate ?? 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 keys = cpr.map(item => `${item.k}-${item.p}`).join('##');
- const sid = crypto.createHash('sha1').update(`${id}-${keys}`).digest('hex');
- const crpGroup = `${id}_${cpr.find(item => item.p == 'ps').k}`;
- const hasLower = cpr.some(item => item.q === 0);
- const platformKey = getPlatformKey(cpr);
- const timestamp = Date.now();
- solutions.push({sid, sol, cpr, cross: platformKey, info, group: crpGroup, lower: hasLower, rule: `${iorGroup}:${ruleIndex}`, timestamp});
- }
- });
- });
- });
- return solutions.sort((a, b) => {
- return b.sol.win_average - a.sol.win_average;
- });
- }
- module.exports = { eventsCombination, getPassableEvents };
|