| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- const crypto = require('crypto');
- const Logs = require('../libs/logs');
- const IOR_KEYS_MAP = require('./iorKeys');
- const SETTING = {
- innerDefaultAmount: 10000,
- minProfitAmount: 0,
- innerRebateRatio: 0,
- obRebateRatio: 0,
- hgRebateRatio: 0,
- }
- /**
- * 筛选最优赔率
- */
- function getOptimalSelections(odds, rules) {
- const results = [];
- const { obRebateRatio, hgRebateRatio } = SETTING;
- 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 triangleProfitCalc = (goldsInfo, oddsOption) => {
- const { innerRebateRatio } = SETTING;
- const {
- gold_side_a: x,
- gold_side_b: y,
- gold_side_c: z,
- odds_side_a: a,
- odds_side_b: b,
- odds_side_c: c
- } = goldsInfo;
- const { crossType, innerIndex, rebateA: A = 0, rebateB: B = 0, rebateC: C = 0 } = 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 inner_rebate = 0;
- if (innerIndex == 0) {
- inner_rebate = x * innerRebateRatio / 100;
- }
- else if (innerIndex == 1) {
- inner_rebate = y * innerRebateRatio / 100;
- }
- else if (innerIndex == 2) {
- inner_rebate = z * innerRebateRatio / 100;
- }
- const k1 = a * (1 + A);
- const k2 = 1 - A;
- const k3 = b * (1 + B);
- const k4 = 1 - B;
- const k5 = c * (1 + C);
- const k6 = 1 - C;
- let win_side_a = 0, win_side_b = 0, win_side_c = 0;
- win_side_a = k1*x - k4*y - k6*z;
- win_side_b = k3*y - k2*x - k6*z;
- switch (crossType) {
- case 'la_wh_wa': // 全输 半赢 全赢
- win_side_c = k5*z - k2*x + k3*y/2;
- break;
- case 'la_dr_wa': // 全输 和局 全赢
- win_side_c = k5*z - k2*x;
- break;
- case 'la_lh_wa': // 全输 半输 全赢
- win_side_c = k5*z - k2*x - k4*y/2;
- break;
- case 'lh_dr_wa': // 半输 和局 全赢
- win_side_c = k5*z - k2*x/2;
- break;
- case 'lh_lh_wa': // 半输 半输 全赢
- win_side_c = k5*z - k2*x/2 - k4*y/2;
- break;
- case 'la_la_wa': // 全输 全输 全赢
- win_side_c = k5*z - k2*x - k4*y;
- break;
- }
- win_side_a = fixFloat(win_side_a + inner_rebate);
- win_side_b = fixFloat(win_side_b + inner_rebate);
- win_side_c = fixFloat(win_side_c + inner_rebate);
- const win_average = fixFloat((win_side_a + win_side_b + win_side_c) / 3);
- return { win_side_a, win_side_b, win_side_c, win_average }
- }
- const triangleGoldCalc = (oddsInfo, oddsOption) => {
- const { innerDefaultAmount } = SETTING;
- const { odds_side_a: a, odds_side_b: b, odds_side_c: c } = oddsInfo;
- if (!a || !b || !c) {
- return;
- }
- const { crossType, innerIndex, rebateA: A = 0, rebateB: B = 0, rebateC: C = 0 } = oddsOption;
- const k1 = a * (1 + A);
- const k2 = 1 - A;
- const k3 = b * (1 + B);
- const k4 = 1 - B;
- const k5 = c * (1 + C);
- const k6 = 1 - C;
- let x = innerDefaultAmount;
- let y = (k1 + k2) * x / (k3 + k4);
- let z;
- switch (crossType) {
- case 'la_wh_wa': // 全输 半赢 全赢
- z = k3 * y / 2 / (k5 + k6);
- break;
- case 'la_dr_wa': // 全输 和局 全赢
- z = k3 * y / (k5 + k6);
- break;
- case 'la_lh_wa': // 全输 半输 全赢
- z = (k3 + k4 / 2) * y / (k5 + k6);
- break;
- case 'lh_dr_wa': // 半输 和局 全赢
- z = (k3 * y - k2 * x / 2) / (k5 + k6);
- break;
- case 'lh_lh_wa': // 半输 半输 全赢
- z = ((k3 + k4/2) * y - k2 * x / 2) / (k5 + k6);
- break;
- case 'la_la_wa': // 全输 全输 全赢
- z = (k3 + k4) * y / (k5 + k6);
- break;
- default:
- z = 0;
- }
- if (innerIndex == 1) {
- const scale = innerDefaultAmount / y;
- x = x * scale;
- y = innerDefaultAmount;
- z = z * scale;
- }
- else if (innerIndex == 2) {
- const scale = innerDefaultAmount / z;
- x = x * scale;
- y = y * scale;
- z = innerDefaultAmount;
- }
- return {
- gold_side_a: fixFloat(x),
- gold_side_b: fixFloat(y),
- gold_side_c: fixFloat(z),
- odds_side_a: fixFloat(a),
- odds_side_b: fixFloat(b),
- odds_side_c: fixFloat(c),
- };
- }
- const eventSolutions = (oddsInfo, oddsOption) => {
- const { innerDefaultAmount, innerRebateRatio } = SETTING;
- const goldsInfo = triangleGoldCalc(oddsInfo, oddsOption);
- if (!goldsInfo) {
- return;
- }
- const profitInfo = triangleProfitCalc(goldsInfo, oddsOption);
- const { odds_side_a, odds_side_b, odds_side_c, gold_side_a, gold_side_b, gold_side_c } = goldsInfo;
- const { win_side_a, win_side_b, win_side_c, win_average } = profitInfo;
- let { crossType, innerIndex, rebateA, rebateB, rebateC } = oddsOption;
- if (innerIndex == 0) {
- rebateA = fixFloat(innerRebateRatio / 100, 3);
- }
- else if (innerIndex == 1) {
- rebateB = fixFloat(innerRebateRatio / 100, 3);
- }
- else if (innerIndex == 2) {
- rebateC = fixFloat(innerRebateRatio / 100, 3);
- }
- const win_profit = fixFloat(win_average / (gold_side_a + gold_side_b + gold_side_c) * 100);
- return {
- odds_side_a, odds_side_b, odds_side_c,
- // gold_side_a, gold_side_b, gold_side_c,
- // win_side_a, win_side_b, win_side_c,
- win_average, win_profit,
- rebate_side_a: rebateA,
- rebate_side_b: rebateB,
- rebate_side_c: rebateC,
- cross_type: crossType,
- inner_index: innerIndex,
- inner_base: innerDefaultAmount,
- }
- }
- /**
- * 盘口排序
- */
- 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 } = SETTING;
- 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, setting) => {
- Object.keys(setting).forEach(key => {
- if (key in SETTING && SETTING[key] !== setting[key]) {
- SETTING[key] = setting[key];
- Logs.out(`setting ${key} changed to ${setting[key]}`);
- }
- });
- 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 oddsInfo = {
- odds_side_a: fixFloat(oddsSideA.v - 1),
- odds_side_b: fixFloat(oddsSideB.v - 1),
- odds_side_c: fixFloat(oddsSideC.v - 1),
- };
- const oddsOption = {
- crossType, innerIndex,
- rebateA: parseFloat((oddsSideA.r / 100).toFixed(4)),
- rebateB: parseFloat((oddsSideB.r / 100).toFixed(4)),
- rebateC: parseFloat((oddsSideC.r / 100).toFixed(4)),
- };
- const sol = eventSolutions(oddsInfo, oddsOption);
- if (sol?.win_average > SETTING.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;
- });
- // return Object.values(solutions).map(item => {
- // return item.sort((a, b) => {
- // return b.sol.win_average - a.sol.win_average;
- // })
- // .slice(0, 2);
- // })
- // .sort((a, b) => {
- // return b[0].sol.win_average - a[0].sol.win_average;
- // });
- }
- module.exports = { eventsCombination };
|