Ver Fonte

新增补单接口

flyzto há 4 semanas atrás
pai
commit
a7b5e73064

+ 5 - 0
server/init.js

@@ -10,8 +10,13 @@ const Logs = require('./libs/logs');
     innerRebateRatio: 0,
     obRebateRatio: 0,
     obRebateType: 0,
+    obMaxDiff: 0,
     hgRebateRatio: 0,
     hgRebateType: 0,
+    hgRebateLower: 0,
+    hgMaxDiff: 0,
+    pcRebateRatio: 0,
+    pcRebateType: 0,
     expireTimeEvents: 45000,
     expireTimeSpecial: 60000,
     subsidyTime: 0,

+ 126 - 14
server/models/GamesPs.js

@@ -3,6 +3,7 @@ const Logs = require('../libs/logs');
 const Cache = require('../libs/cache');
 const Setting = require('./Setting');
 const { eventSolutions } = require('../triangle/eventSolutions');
+const { getPassableEvents, eventsCombination } = require('../triangle/trangleCalc');
 const { calcTotalProfit, calcTotalProfitWithFixedFirst } = require('../triangle/totalProfitCalc');
 const { getSetting, updateSetting } = require('../triangle/settings');
 
@@ -621,7 +622,7 @@ const getGamesEvents = ({ platform, relIds = [] } = {}) => {
 }
 
 /**
- * 获取关联比赛
+ * 获取远程关联比赛列表
  */
 const fetchGamesRelation = async (mk='') => {
   return axios.get(`${BASE_API_URL}/p/getGameTast?mk=${mk}`, {
@@ -686,31 +687,87 @@ const fetchGamesRelation = async (mk='') => {
   });
 }
 
-const getGamesRelation = ({ mk=-1, ids, listEvents } = {}) => {
+/**
+ * 获取PC数据
+ */
+const getPCEvents = (events) => {
+  if (!events) {
+    return undefined;
+  }
+  const pcEvents = {};
+  Object.keys(events).forEach(ior => {
+    const { v, s, r } = events[ior];
+    pcEvents[ior] = {
+      v: s ?? v,
+      r: r
+    }
+  });
+  return pcEvents;
+}
+const getPCData = (id) => {
+  const baseList = Object.values(GAMES.Baselist).flat();
+  const baseMap = new Map(baseList.map(item => [item.eventId, item]));
+  const baseGame = baseMap.get(id);
+  if (!baseGame) {
+    return null;
+  }
+  const { matches, events, special, ...gameInfo } = baseGame;
+  return {
+    events: getPCEvents(events),
+    special: getPCEvents(special),
+    ...gameInfo
+  }
+}
+
+/**
+ * 获取关联比赛列表
+ * @param {object} options
+ * @property {number} mk
+ * @property {number[]} ids
+ * @property {boolean} listEvents
+ * @property {boolean} listPC
+ * @returns {object[]}
+ */
+const getGamesRelation = ({ mk=-1, ids, listEvents=false, listPC=false } = {}) => {
   let idsSet = null;
   if (ids?.length) {
     idsSet = new Set(ids);
   }
-  const relations = Object.values(GAMES.Relations).filter(item => {
+
+  let relations;
+  if (ids?.length) {
+    relations = ids.map(id => GAMES.Relations[id]);
+  }
+  else {
+    relations = Object.values(GAMES.Relations);
+  }
+
+  relations = relations.filter(item => {
     if (idsSet && !idsSet.has(item.id)) {
       return false;
     }
     return mk == -1 || item.mk == mk;
   }).sort((a, b) => a.timestamp - b.timestamp);
 
-  if (listEvents) {
+  if (listEvents && !listPC) {
     return relations;
   }
-  const gamesRelation = relations.map(item => {
+
+  return relations.map(item => {
     const { rel, ...relationInfo } = item;
     const tempRel = { ...rel };
-    Object.keys(tempRel).forEach(platform => {
-      const { events, evtime, sptime, special, ...gameInfo } = tempRel[platform];
-      tempRel[platform] = gameInfo;
-    });
+    const { id } = relationInfo;
+    if (listPC) {
+      tempRel.pc = getPCData(id);
+    }
+    if (!listEvents) {
+      Object.keys(tempRel).forEach(platform => {
+        const { events, evtime, special, sptime, ...gameInfo } = tempRel[platform];
+        tempRel[platform] = gameInfo;
+      });
+    }
     return { ...relationInfo, rel: tempRel };
   });
-  return gamesRelation;
 }
 
 /**
@@ -1184,8 +1241,8 @@ const getTotalProfitWithBetInfo = async (betInfo1, betInfo2, fixed=false, inner_
     return Promise.reject(new Error('第二个下注信息无效'));
   }
   const { innerDefaultAmount, innerRebateRatio } = getSetting();
-  inner_base = inner_base ? +inner_base : innerDefaultAmount;
-  inner_rebate = inner_rebate ? +inner_rebate : fixFloat(innerRebateRatio / 100, 3);
+  inner_base = typeof(inner_base) != 'undefined' ? +inner_base : innerDefaultAmount;
+  inner_rebate = typeof(inner_rebate) != 'undefined' ? +inner_rebate : fixFloat(innerRebateRatio / 100, 3);
 
   if (fixed) {
     return calcTotalProfitWithFixedFirst(betInfo1, betInfo2, inner_base, inner_rebate);
@@ -1195,6 +1252,62 @@ const getTotalProfitWithBetInfo = async (betInfo1, betInfo2, fixed=false, inner_
   return getTotalProfit(sol1, sol2, inner_base, inner_rebate);
 }
 
+/**
+ * 计算补单综合利润
+ */
+const getTotalReplacement = async (event_id, inner_base, inner_rebate, inner_ior, inner_odds, outer_golds) => {
+  // return Promise.resolve();
+  if (!event_id) {
+    return Promise.reject(new Error('event_id 无效'));
+  }
+  if (!inner_ior) {
+    return Promise.reject(new Error('inner_ior 无效'));
+  }
+  if (inner_odds?.length != 2) {
+    return Promise.reject(new Error('inner_odds 无效'));
+  }
+  if (!outer_golds?.length) {
+    return Promise.reject(new Error('outer_golds 无效'));
+  }
+
+  const { innerDefaultAmount, innerRebateRatio } = getSetting();
+  inner_base = typeof(inner_base) != 'undefined' ? +inner_base : innerDefaultAmount;
+  inner_rebate = typeof(inner_rebate) != 'undefined' ? +inner_rebate : fixFloat(innerRebateRatio / 100, 3);
+
+  const inner_odds_value = fixFloat(inner_odds[0] * inner_odds[1], 3);
+  const outer_golds_rebate = outer_golds.map(item => item.gold * item.rebate).reduce((sum, rebate) => sum + rebate, 0);
+  const outer_golds_loss = outer_golds.map(item => item.gold).reduce((sum, gold) => sum + gold, 0) - outer_golds_rebate;
+
+  const relation = getGamesRelation({ ids: [event_id], listEvents: true, listPC: true }).map(item => {
+    const { rel, ...relationInfo } = item;
+    const tempRel = { ...rel };
+    const events = {};
+    events[inner_ior] = { v: inner_odds_value };
+    tempRel.ps = { ...rel.ps, events }
+    return { ...relationInfo, rel: tempRel };
+  });
+
+  const passableEvents = getPassableEvents(relation, null);
+  const solutions = eventsCombination(passableEvents, inner_base, inner_rebate);
+
+  if (!solutions?.length) {
+    return Promise.reject(new Error('没有可用的解决方案'));
+  }
+
+  return solutions.filter(solution => !solution.lower).map(solution => {
+    const { sid, info, sol, ...solutionInfo } = solution;
+    const { win_profit_rate, win_average_rate, ...solInfo } = sol;
+    const { cross_type } = solInfo;
+    solInfo.win_side_a = typeof(solInfo.win_side_a) !== 'number' ? undefined : fixFloat(solInfo.win_side_a - outer_golds_loss);
+    solInfo.win_side_b = typeof(solInfo.win_side_b) !== 'number' ? undefined : fixFloat(solInfo.win_side_b - outer_golds_loss);
+    solInfo.win_side_c = cross_type == 'la_wa_rv' ? 0 : (typeof(solInfo.win_side_c) !== 'number' ? undefined : fixFloat(solInfo.win_side_c - outer_golds_loss));
+    solInfo.win_average = fixFloat(solInfo.win_average - outer_golds_loss);
+
+    return { ...solutionInfo, sol: solInfo };
+  });
+
+}
+
 /**
  * 同步Qboss平台配置
  */
@@ -1389,7 +1502,6 @@ module.exports = {
   updateGamesResult,
   updateOriginalData, getOriginalData,
   getSolutions, getGamesSolutions, getSolution, getSolutionsByIds,
-  getTotalProfitWithSid,
-  getTotalProfitWithBetInfo,
+  getTotalProfitWithSid, getTotalProfitWithBetInfo, getTotalReplacement,
   notifyException,
 }

+ 5 - 0
server/models/Setting.js

@@ -51,6 +51,11 @@ const systemSettingSchema = new Schema({
     required: true,
     default: 0
   },
+  hgRebateLower: {
+    type: Number,
+    required: true,
+    default: 0
+  },
   hgMaxDiff: {
     type: Number,
     required: true,

+ 41 - 5
server/routes/pstery.js

@@ -85,11 +85,12 @@ router.get('/get_original_data', (req, res) => {
 
 // 获取关联列表
 router.get('/get_games_relation', (req, res) => {
-  const { mk, ids, le } = req.query;
+  const { mk, ids, le, lp } = req.query;
   const gamesRelation = Games.getGamesRelation({
     ids: ids?.split(',').map(item => +item.trim()).filter(item => !!item) ?? [],
     listEvents: le === 'true',
-    mk: mk ? +mk : -1
+    listPC: lp === 'true',
+    mk: (/^-?\d$/).test(mk) ? +mk : -1
   });
   res.sendSuccess(gamesRelation);
 });
@@ -156,7 +157,18 @@ router.post('/solutions_by_ids', (req, res) => {
 
 // 获取综合利润方案
 router.post('/calc_total_profit', (req, res) => {
-  const [sid1, sid2, inner_base, inner_rebate] = req.body;
+  const data = req.body;
+  let sid1, sid2, inner_base, inner_rebate;
+  if (Array.isArray(data)) {
+    [sid1, sid2, inner_base, inner_rebate] = data;
+  }
+  else {
+    sid1 = data.sids[0];
+    sid2 = data.sids[1];
+    inner_base = data.inner_base;
+    inner_rebate = data.inner_rebate;
+  }
+  // const [sid1, sid2, inner_base, inner_rebate] = req.body;
   Games.getTotalProfitWithSid(sid1, sid2, inner_base, inner_rebate)
   .then(totalProfit => {
     res.sendSuccess(totalProfit);
@@ -168,8 +180,32 @@ router.post('/calc_total_profit', (req, res) => {
 
 // 获取自定义综合利润
 router.post('/calc_custom_total_profit', (req, res) => {
-  const [betInfo1, betInfo2, fixed, inner_base, inner_rebate] = req.body;
-  Games.getTotalProfitWithBetInfo(betInfo1, betInfo2, fixed, inner_base, inner_rebate)
+  // const [bet_info_1, bet_info_2, fixed, inner_base, inner_rebate] = req.body;
+  const data = req.body;
+  let bet_info_1, bet_info_2, fixed, inner_base, inner_rebate;
+  if (Array.isArray(data)) {
+    [bet_info_1, bet_info_2, fixed, inner_base, inner_rebate] = data;
+  }
+  else {
+    bet_info_1 = data.bet_info[0];
+    bet_info_2 = data.bet_info[1];
+    inner_base = data.inner_base;
+    inner_rebate = data.inner_rebate;
+    fixed = data.fixed;
+  }
+  Games.getTotalProfitWithBetInfo(bet_info_1, bet_info_2, fixed, inner_base, inner_rebate)
+  .then(totalProfit => {
+    res.sendSuccess(totalProfit);
+  })
+  .catch(err => {
+    res.badRequest(err.message);
+  });
+});
+
+// 补单计算
+router.post('/calc_total_replacement', (req, res) => {
+  const { event_id, inner_base, inner_rebate, inner_ior, inner_odds, outer_golds } = req.body;
+  Games.getTotalReplacement(event_id, inner_base, inner_rebate, inner_ior, inner_odds, outer_golds)
   .then(totalProfit => {
     res.sendSuccess(totalProfit);
   })

+ 2 - 80
server/triangle/eventsMatch.js

@@ -1,5 +1,5 @@
 const Logs = require('../libs/logs');
-const { eventsCombination } = require('./trangleCalc');
+const { getPassableEvents, eventsCombination } = require('./trangleCalc');
 const { getSetting, updateSetting } = require('./settings');
 
 const Request = {
@@ -75,40 +75,6 @@ const updateSolutions = (solutions, eventsLogsMap) => {
   postDataToParent('updateSolutions', { solutions, eventsLogsMap });
 }
 
-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 eventMatch = () => {
   getGamesRelation()
   .then(relations => {
@@ -131,51 +97,7 @@ const eventMatch = () => {
     };
     /** 日志 End */
 
-    const passableEvents = 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 (evExpire || spExpire) {
-          eventsLogsMap.expireEvents.push({
-            mk, platform,
-            info: rel[platform],
-            evExpire, spExpire,
-            evtime, sptime,
-          });
-        }
-        /** 日志 盘口移除 */
-        if (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 passableEvents = getPassableEvents(relations, eventsLogsMap);
     const solutions = eventsCombination(passableEvents);
 
     // Logs.out('eventMatch solutions', solutions);

+ 1 - 0
server/triangle/settings.js

@@ -10,6 +10,7 @@ const SETTING = {
   obMaxDiff: 0,
   hgRebateRatio: 0,
   hgRebateType: 0,
+  hgRebateLower: 0,
   hgMaxDiff: 0,
   pcRebateRatio: 0,
   pcRebateType: 0,

+ 10 - 3
server/triangle/totalProfitCalc.js

@@ -499,13 +499,20 @@ const calcTotalProfitWithFixedFirst = (betInfo1, betInfo2, inner_base, inner_reb
   }
 
   const profitInfo = calcSecondProfit({ ...betInfo2, inner_base, inner_odds_first: inner_odds_1, inner_rebate });
+  const { cross_type } = profitInfo;
 
   profitInfo.win_side_a = typeof(profitInfo.win_side_a) !== 'number' ? undefined : fixFloat(profitInfo.win_side_a - loss_out_1);
   profitInfo.win_side_b = typeof(profitInfo.win_side_b) !== 'number' ? undefined : fixFloat(profitInfo.win_side_b - loss_out_1);
-  profitInfo.win_side_c = typeof(profitInfo.win_side_c) !== 'number' ? undefined : fixFloat(profitInfo.win_side_c - loss_out_1);
+  profitInfo.win_side_c = cross_type == 'la_wa_rv' ? 0 : (typeof(profitInfo.win_side_c) !== 'number' ? undefined : fixFloat(profitInfo.win_side_c - loss_out_1));
   profitInfo.win_average = fixFloat(profitInfo.win_average - loss_out_1);
 
-  return profitInfo;
+  const { win_average_rate, win_profit_rate, ...profitInfoRest } = profitInfo;
+
+  return profitInfoRest;
 }
 
-module.exports = { calcTotalProfit, calcTotalProfitWithFixedFirst };
+
+module.exports = {
+  calcTotalProfit,
+  calcTotalProfitWithFixedFirst,
+};

+ 101 - 7
server/triangle/trangleCalc.js

@@ -141,7 +141,7 @@ const fixFloat = (number, x=2) => {
 /**
  * 盘口排序
  */
-const priority = { ps: 1, ob: 2, hg: 3 };
+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]);
@@ -156,18 +156,21 @@ const getPlatformKey = (cpr) => {
   return [...new Set(platforms)].join('_');
 }
 
-
 /**
  * 添加返佣
  */
 const attachRebate = (ior) => {
-  const { obRebateRatio, obRebateType, hgRebateRatio, hgRebateType } = getSetting();
+  const { obRebateRatio, obRebateType, hgRebateRatio, 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;
@@ -182,7 +185,98 @@ const attachRebate = (ior) => {
   return { ...ior, b: rebate, t: rebateType };
 }
 
-const eventsCombination = (passableEvents) => {
+/**
+ * 提取盘口数据
+ */
+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) => {
+  console.log('getPassableEvents', relations);
+  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) => {
   // Logs.outDev('passableEvents', passableEvents);
   const { innerDefaultAmount, innerRebateRatio } = getSetting();
   const solutions = [];
@@ -205,8 +299,8 @@ const eventsCombination = (passableEvents) => {
         const betInfo = {
           cross_type: crossType,
           inner_index: innerIndex,
-          inner_base: innerDefaultAmount,
-          inner_rebate: fixFloat(innerRebateRatio / 100, 3),
+          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),
@@ -243,4 +337,4 @@ const eventsCombination = (passableEvents) => {
   });
 }
 
-module.exports = { eventsCombination };
+module.exports = { eventsCombination, getPassableEvents };

+ 1 - 1
web/apps/web-antd/src/views/match/solutions/index.vue

@@ -69,7 +69,7 @@ const getSolutions = async () => {
 const calcTotalProfit = async () => {
   const sids = selectedSolutions.map(item => item.sid);
   try {
-    const totalProfit = await requestClient.post('/pstery/calc_total_profit', [...sids]);
+    const totalProfit = await requestClient.post('/pstery/calc_total_profit', {sids});
     return totalProfit;
   }
   catch (error) {