| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- const Logs = require('../libs/logs');
- const IOR_KEYS_MAP = require('./iorKeys');
- const GOLD_BASE = 10000;
- const WIN_MIN = process.env.NODE_ENV == 'development' ? -10000 : -1000;
- const JC_REBATE_RATIO = 0;
- /**
- * 筛选最优赔率
- */
- function getOptimalSelections(data, combos) {
- const results = [];
- combos.forEach((rule, index) => {
- let validOptions = [];
- for (let i = 0; i < 3; i++) {
- const jcIndex = i;
- const selection = [];
- let isValid = true;
- for (let j = 0; j < 3; j++) {
- const key = rule[j];
- const item = data[key];
- if (!item) {
- isValid = false;
- break;
- }
- if (j === jcIndex) {
- if (!('jc' in item)) {
- isValid = false;
- break;
- }
- selection.push({
- k: key,
- p: 'jc',
- v: item.jc,
- o: item,
- });
- }
- else {
- const candidates = ['ps', 'ob'].filter((k) => k in item);
- if (candidates.length === 0) {
- isValid = false;
- break;
- }
- const best = candidates.reduce((a, b) => item[a] > item[b] ? a : b);
- 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 {
- gold_side_a: x,
- gold_side_b: y,
- gold_side_m: z,
- odds_side_a: a,
- odds_side_b: b,
- odds_side_m: c
- } = goldsInfo;
- const { crossType, jcIndex } = 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 jc_rebate = 0;
- if (jcIndex == 0) {
- jc_rebate = x * JC_REBATE_RATIO;
- }
- else if (jcIndex == 1) {
- jc_rebate = y * JC_REBATE_RATIO;
- }
- else if (jcIndex == 2) {
- jc_rebate = z * JC_REBATE_RATIO;
- }
- let win_side_a = 0, win_side_b = 0, win_side_m = 0;
- win_side_a = a * x - y - z;
- win_side_b = b * y - x - z;
- switch (crossType) {
- case 'la_wh_wa': // 全输 半赢 全赢
- win_side_m = c*z - x + b*y/2;
- break;
- case 'la_dr_wa': // 全输 和局 全赢
- win_side_m = c*z - x;
- break;
- case 'la_lh_wa': // 全输 半输 全赢
- win_side_m = c*z - x - y/2;
- break;
- case 'lh_dr_wa': // 半输 和局 全赢
- win_side_m = c*z - x/2;
- break;
- case 'lh_lh_wa': // 半输 半输 全赢
- win_side_m = c*z - x/2 - y/2;
- break;
- case 'la_la_wa': // 全输 全输 全赢
- win_side_m = c*z - x - y;
- break;
- }
- win_side_a = fixFloat(win_side_a + jc_rebate);
- win_side_b = fixFloat(win_side_b + jc_rebate);
- win_side_m = fixFloat(win_side_m + jc_rebate);
- const win_average = fixFloat((win_side_a + win_side_b + win_side_m) / 3);
- return { win_side_a, win_side_b, win_side_m, win_average }
- }
- const triangleGoldCalc = (oddsInfo, oddsOption) => {
- const { odds_side_a: a, odds_side_b: b, odds_side_m: c } = oddsInfo;
- if (!a || !b || !c) {
- return;
- }
- const { crossType, jcIndex } = oddsOption;
- let x = GOLD_BASE;
- let y = (a + 1) * x / (b + 1);
- let z;
- switch (crossType) {
- case 'la_wh_wa': // 全输 半赢 全赢
- z = b * y / 2 / (c + 1);
- break;
- case 'la_dr_wa': // 全输 和局 全赢
- z = b * y / (c + 1);
- break;
- case 'la_lh_wa': // 全输 半输 全赢
- z = (2 * b + 1) * y / 2 / (c + 1);
- break;
- case 'lh_dr_wa': // 半输 和局 全赢
- z = (b * y - x / 2) / (c + 1);
- break;
- case 'lh_lh_wa': // 半输 半输 全赢
- z = (b * y + y / 2 - x / 2) / (c + 1);
- break;
- case 'la_la_wa': // 全输 全输 全赢
- z = (b + 1) * y / (c + 1);
- break;
- default:
- z = 0;
- }
- if (jcIndex == 1) {
- const scale = GOLD_BASE / y;
- x = x * scale;
- y = GOLD_BASE;
- z = z * scale;
- }
- else if (jcIndex == 2) {
- const scale = GOLD_BASE / z;
- x = x * scale;
- y = y * scale;
- z = GOLD_BASE;
- }
- return {
- gold_side_a: x,
- gold_side_b: fixFloat(y),
- gold_side_m: fixFloat(z),
- odds_side_a: a,
- odds_side_b: b,
- odds_side_m: c,
- };
- }
- const eventSolutions = (oddsInfo, oddsOption) => {
- const goldsInfo = triangleGoldCalc(oddsInfo, oddsOption);
- if (!goldsInfo) {
- return;
- }
- const profitInfo = triangleProfitCalc(goldsInfo, oddsOption);
- // console.log(goldsInfo, profitInfo);
- return {
- ...goldsInfo,
- ...profitInfo,
- cross_type: oddsOption.crossType,
- jc_index: oddsOption.jcIndex,
- jc_base: GOLD_BASE,
- }
- }
- const eventsCombination = (passableEvents) => {
- const solutions = [];
- passableEvents.forEach(events => {
- const { odds, info } = events;
- Object.keys(IOR_KEYS_MAP).forEach(group => {
- const rules = IOR_KEYS_MAP[group];
- const optimalSelections = getOptimalSelections(odds, rules);
- optimalSelections.forEach(selection => {
- const { rule, iors, index } = selection;
- const [, , , crossType] = rule;
- const oddsSideA = iors[0];
- const oddsSideB = iors[1];
- const oddsSideM = iors[2];
- const jcIndex = iors.findIndex(item => item.p == 'jc');
- if (!oddsSideA || !oddsSideB || !oddsSideM) {
- return;
- }
- const cpr = [ oddsSideA, oddsSideB, oddsSideM ];
- const oddsInfo = {
- odds_side_a: fixFloat(oddsSideA.v - 1),
- odds_side_b: fixFloat(oddsSideB.v - 1),
- odds_side_m: fixFloat(oddsSideM.v - 1)
- };
- const oddsOption = { crossType, jcIndex };
- const sol = eventSolutions(oddsInfo, oddsOption);
- if (sol?.win_average > WIN_MIN) {
- const id = info.id;
- const keys = cpr.map(item => `${item.k}`).join('_');
- const sid = `${id}_${keys}`;
- const timestamp = Date.now();
- solutions.push({sid, sol, cpr, info, rule: `${group}:${index}`, timestamp});
- }
- });
- });
- });
- return solutions;
- }
- module.exports = eventsCombination;
|