Explorar o código

外盘内盘数据比较

flyzto hai 1 mes
pai
achega
1bddab896f

+ 17 - 1
pinnacle/main.js

@@ -499,8 +499,23 @@ const parseMoneyline = (moneyline) => {
   }
 }
 
+const parseTotals = (totals) => {
+  // 大小球盘
+  if (!totals?.length) {
+    return null;
+  }
+  const events = {};
+
+  totals.forEach(total => {
+    const { points, over, under } = total;
+    events[`ior_ouc_${ratioString(points)}`] = { v: over };
+    events[`ior_ouh_${ratioString(points)}`] = { v: under };
+  });
+  return events;
+}
+
 const parsePeriod = (period, wm) => {
-  const { cutoff='', status=0, spreads=[], moneyline={} } = period;
+  const { cutoff='', status=0, spreads=[], moneyline={}, totals=[] } = period;
   const cutoffTime = new Date(cutoff).getTime();
   const nowTime = Date.now();
 
@@ -511,6 +526,7 @@ const parsePeriod = (period, wm) => {
   const events = {};
   Object.assign(events, parseSpreads(spreads, wm));
   Object.assign(events, parseMoneyline(moneyline));
+  Object.assign(events, parseTotals(totals));
 
   return events;
 }

+ 87 - 45
server/models/GamesPs.js

@@ -307,20 +307,22 @@ const syncBaseEvents = ({ mk, games, outrights }) => {
       const isRb = (mk == 2);
       const isSubsidy = timestamp > nowTime && timestamp < nowTime + 1000*60*60*subsidyTime;
       Object.keys(events).forEach(ior => {
+        // 滚球胜平负补水
         if (isRb && (ior.startsWith('ior_r') || ior.startsWith('ior_m') || ior.startsWith('ior_wm')) && subsidyRbWmAmount) {
-          // 滚球胜平负
           const sourceOdds = events[ior].v;
           events[ior].v = fixFloat(sourceOdds * (1 + subsidyRbWmAmount), 3);
           events[ior].s = sourceOdds
         }
+        // 滚球进球数补水
+        // API采集特殊盘口用
         else if (isRb && ior.startsWith('ior_ot') && subsidyRbOtAmount) {
-          // 滚球进球数
           const sourceOdds = events[ior].v;
           events[ior].v = fixFloat(sourceOdds * (1 + subsidyRbOtAmount), 3);
           events[ior].s = sourceOdds
         }
+        // 赛前进球数补水
+        // API采集特殊盘口用
         else if (!isRb && isSubsidy && ior.startsWith('ior_ot') && subsidyAmount) {
-          // 赛前进球数
           const sourceOdds = events[ior].v;
           events[ior].v = fixFloat(sourceOdds * (1 + subsidyAmount), 3);
           events[ior].s = sourceOdds
@@ -336,45 +338,46 @@ const syncBaseEvents = ({ mk, games, outrights }) => {
     }
   });
 
-  outrights?.forEach(outright => {
-    const { parentId, sptime, special } = outright;
-    const baseGame = baseMap.get(parentId);
-    if (baseGame) { // 赛前特殊盘口
-      const { timestamp } = baseGame;
-      const isSubsidy = timestamp > nowTime && timestamp < nowTime + 1000*60*60*subsidyTime;
-      if (isSubsidy) {
-        Object.keys(special).forEach(ior => {
-          if (ior.startsWith('ior_ot') && subsidyAmount) {
-            const sourceOdds = special[ior].v;
-            special[ior].v = fixFloat(sourceOdds * (1 + subsidyAmount), 3);
-            special[ior].s = sourceOdds
-          }
-        });
-      }
-      baseGame.sptime = sptime;
-      baseGame.special = special;
-    }
-    else {
-      const originBaseMap = new Map(baseList[marketType].map(item => [item.originId, item]));
-      const originBaseGame = originBaseMap.get(parentId);
-      if (originBaseGame) { // 滚球特殊盘口
-        Object.keys(special).forEach(ior => {
-          if (ior.startsWith('ior_wm') && subsidyRbWmAmount) {
-            const sourceOdds = special[ior].v;
-            special[ior].v = fixFloat(sourceOdds * (1 + subsidyRbWmAmount), 3);
-            special[ior].s = sourceOdds
-          }
-          else if (ior.startsWith('ior_ot') && subsidyRbOtAmount) {
-            const sourceOdds = special[ior].v;
-            special[ior].v = fixFloat(sourceOdds * (1 + subsidyRbOtAmount), 3);
-            special[ior].s = sourceOdds
-          }
-        });
-        originBaseGame.sptime = sptime;
-        originBaseGame.special = special;
-      }
-    }
-  });
+  // 浏览器采集特殊盘口用的
+  // outrights?.forEach(outright => {
+  //   const { parentId, sptime, special } = outright;
+  //   const baseGame = baseMap.get(parentId);
+  //   if (baseGame) { // 赛前特殊盘口
+  //     const { timestamp } = baseGame;
+  //     const isSubsidy = timestamp > nowTime && timestamp < nowTime + 1000*60*60*subsidyTime;
+  //     if (isSubsidy) {
+  //       Object.keys(special).forEach(ior => {
+  //         if (ior.startsWith('ior_ot') && subsidyAmount) {
+  //           const sourceOdds = special[ior].v;
+  //           special[ior].v = fixFloat(sourceOdds * (1 + subsidyAmount), 3);
+  //           special[ior].s = sourceOdds
+  //         }
+  //       });
+  //     }
+  //     baseGame.sptime = sptime;
+  //     baseGame.special = special;
+  //   }
+  //   else {
+  //     const originBaseMap = new Map(baseList[marketType].map(item => [item.originId, item]));
+  //     const originBaseGame = originBaseMap.get(parentId);
+  //     if (originBaseGame) { // 滚球特殊盘口
+  //       Object.keys(special).forEach(ior => {
+  //         if (ior.startsWith('ior_wm') && subsidyRbWmAmount) {
+  //           const sourceOdds = special[ior].v;
+  //           special[ior].v = fixFloat(sourceOdds * (1 + subsidyRbWmAmount), 3);
+  //           special[ior].s = sourceOdds
+  //         }
+  //         else if (ior.startsWith('ior_ot') && subsidyRbOtAmount) {
+  //           const sourceOdds = special[ior].v;
+  //           special[ior].v = fixFloat(sourceOdds * (1 + subsidyRbOtAmount), 3);
+  //           special[ior].s = sourceOdds
+  //         }
+  //       });
+  //       originBaseGame.sptime = sptime;
+  //       originBaseGame.special = special;
+  //     }
+  //   }
+  // });
 
   if (games?.length) {
     const gamesList = baseList[marketType]?.map(game => {
@@ -488,6 +491,24 @@ const updateBaseEvents = ({ games, timestamp }) => {
   });
 }
 
+/**
+ * 比较外盘与内盘的赔率
+ */
+const compareOdds = (events, baseEvents, platform) => {
+  const setting = getSetting();
+  const maxDiff = setting[`${platform}MaxDiff`] ?? 0;
+  Object.keys(events).forEach(ior => {
+    const value = events[ior].v;
+    const baseValue = baseEvents[ior]?.s ?? baseEvents[ior]?.v ?? 1;
+    if (value - baseValue >= maxDiff) {
+      events[ior].q = 1;
+    }
+    else {
+      events[ior].q = 0;
+    }
+  });
+}
+
 const updateGamesEvents = ({ platform, mk, games, outrights, timestamp, tp }) => {
   return new Promise((resolve, reject) => {
     if (!platform || (!games && !outrights)) {
@@ -508,7 +529,16 @@ const updateGamesEvents = ({ platform, mk, games, outrights, timestamp, tp }) =>
       return resolve({ update });
     }
 
-    const relatedGames = Object.values(GAMES.Relations).map(item => item.rel?.[platform] ?? {});
+    const relatedGames = Object.values(GAMES.Relations).map(item => {
+      // item.rel?.[platform] ?? {}
+      const { rel } = item ?? {};
+      if (!rel) {
+        return {};
+      }
+      const game = rel[platform] ?? {};
+      game.baseId = rel['ps']?.eventId;
+      return game;
+    });
 
     if (!relatedGames.length) {
       return resolve({ update: 0 });
@@ -518,11 +548,16 @@ const updateGamesEvents = ({ platform, mk, games, outrights, timestamp, tp }) =>
       update: 0
     };
 
+    const marketType = getMarketType(mk);
+    const baseList = GAMES.Baselist[marketType] ?? [];
+    const baseMap = new Map(baseList.map(item => [item.eventId, item]));
     const relatedMap = new Map(relatedGames.map(item => [item.eventId, item]));
 
     games?.forEach(game => {
       const { eventId, evtime, events, stage, retime, score, wm } = game;
       const relatedGame = relatedMap.get(eventId);
+      const baseEvents = baseMap.get(relatedGame.baseId)?.events ?? {};
+      compareOdds(events, baseEvents, platform);
       if (relatedGame) {
         relatedGame.evtime = evtime;
         relatedGame.events = events;
@@ -538,6 +573,8 @@ const updateGamesEvents = ({ platform, mk, games, outrights, timestamp, tp }) =>
     outrights?.forEach(outright => {
       const { parentId, sptime, special } = outright;
       const relatedGame = relatedMap.get(parentId);
+      const baseEvents = baseMap.get(relatedGame.baseId)?.events ?? {};
+      compareOdds(special, baseEvents, platform);
       if (relatedGame) {
         relatedGame.sptime = sptime;
         relatedGame.special = special;
@@ -1179,8 +1216,9 @@ const syncQbossConfig = () => {
     }
     const {
       qboss_return_ratio,
-      ob_return_ratio, ob_return_type,
-      hg_return_ratio, hg_return_type,
+      ob_return_ratio, ob_return_type, ob_odds_more_than,
+      hg_return_ratio, hg_return_type, hg_odds_more_than,
+      pc_return_ratio, pc_return_type,
       qboss_jq_add_odds, qboss_jq_add_hours,
       qboss_gq_add_dy_odds, qboss_gq_add_jq_odds,
      } = data.data;
@@ -1188,8 +1226,12 @@ const syncQbossConfig = () => {
       innerRebateRatio: +qboss_return_ratio,
       obRebateRatio: +ob_return_ratio,
       obRebateType: +ob_return_type,
+      obMaxDiff: +ob_odds_more_than,
       hgRebateRatio: +hg_return_ratio,
       hgRebateType: +hg_return_type,
+      hgMaxDiff: +hg_odds_more_than,
+      pcRebateRatio: +pc_return_ratio,
+      pcRebateType: +pc_return_type,
       subsidyTime: +qboss_jq_add_hours,
       subsidyAmount: +qboss_jq_add_odds,
       subsidyRbWmAmount: +qboss_gq_add_dy_odds,

+ 20 - 0
server/models/Setting.js

@@ -36,6 +36,11 @@ const systemSettingSchema = new Schema({
     required: true,
     default: 0
   },
+  obMaxDiff: {
+    type: Number,
+    required: true,
+    default: 0
+  },
   hgRebateRatio: {
     type: Number,
     required: true,
@@ -46,6 +51,21 @@ const systemSettingSchema = new Schema({
     required: true,
     default: 0
   },
+  hgMaxDiff: {
+    type: Number,
+    required: true,
+    default: 0
+  },
+  pcRebateRatio: {
+    type: Number,
+    required: true,
+    default: 0
+  },
+  pcRebateType: {
+    type: Number,
+    required: true,
+    default: 0
+  },
   expireTimeEvents: {
     type: Number,
     required: true,

+ 4 - 0
server/triangle/settings.js

@@ -7,8 +7,12 @@ const SETTING = {
   innerRebateRatio: 0,
   obRebateRatio: 0,
   obRebateType: 0,
+  obMaxDiff: 0,
   hgRebateRatio: 0,
   hgRebateType: 0,
+  hgMaxDiff: 0,
+  pcRebateRatio: 0,
+  pcRebateType: 0,
   subsidyTime: 0,
   subsidyAmount: 0,
   subsidyRbWmAmount: 0,

+ 14 - 12
server/triangle/trangleCalc.js

@@ -9,20 +9,20 @@ const HG_REBATE_RATIO_LOWER = 0.75;
 /**
  * 筛选最优赔率
  */
-const oddRebateValue = (odds, platform, key) => {
-  const setting = getSetting();
-  let rebateRatio = setting[`${platform}RebateRatio`] ?? 0;
-  let rebateType = setting[`${platform}RebateType`] ?? 0;
+// const oddRebateValue = (odds, platform, key) => {
+//   const setting = getSetting();
+//   let rebateRatio = setting[`${platform}RebateRatio`] ?? 0;
+//   let rebateType = setting[`${platform}RebateType`] ?? 0;
 
-  if (platform == 'hg' && key.startsWith('ior_m')) {
-    rebateRatio -= HG_REBATE_RATIO_LOWER;
-  }
+//   if (platform == 'hg' && key.startsWith('ior_m')) {
+//     rebateRatio -= HG_REBATE_RATIO_LOWER;
+//   }
 
-  if (rebateType == 0) {
-    return odds * (1 + rebateRatio / 100);
-  }
-  return odds + rebateRatio / 100;
-}
+//   if (rebateType == 0) {
+//     return odds * (1 + rebateRatio / 100);
+//   }
+//   return odds + rebateRatio / 100;
+// }
 
 const cartesianOdds = (selection) => {
   const [a, b, c] = selection;
@@ -93,8 +93,10 @@ const getOptimalSelections = (odds, rules) => {
             v: item[k].v,
             r: item[k].r,
             s: item[k].s,
+            q: item[k].q,
             o: item
           })));
+          // .filter(item => item.q !== 0)
         }
       }
 

+ 7 - 3
web/apps/web-antd/src/views/match/components/match_card.vue

@@ -57,15 +57,15 @@ defineProps({
         <tr v-for="item in events">
           <th>{{ parseEventKey(item[0]) }}</th>
           <td>
-            <span :class="{'selected': selected.includes(item[1]?.key)}">{{ item[1]?.value ? item[1].value : '-' }}</span>
+            <span :class="{'selected': selected.includes(item[1]?.key), 'strikethrough': item[1]?.qualified === 0}">{{ item[1]?.value ? item[1].value : '-' }}</span>
             <em v-if="item[1]?.origin">{{ item[1].origin }}</em>
           </td>
           <td>
-            <span :class="{'selected': selected.includes(item[2]?.key)}">{{ item[2]?.value ? item[2].value : '-' }}</span>
+            <span :class="{'selected': selected.includes(item[2]?.key), 'qualified': item[2]?.qualified === 0}">{{ item[2]?.value ? item[2].value : '-' }}</span>
             <em v-if="item[2]?.origin">{{ item[2].origin }}</em>
           </td>
           <td>
-            <span :class="{'selected': selected.includes(item[3]?.key)}">{{ item[3]?.value ? item[3].value : '-' }}</span>
+            <span :class="{'selected': selected.includes(item[3]?.key), 'strikethrough': item[3]?.qualified === 0}">{{ item[3]?.value ? item[3].value : '-' }}</span>
             <em v-if="item[3]?.origin">{{ item[3].origin }}</em>
           </td>
         </tr>
@@ -145,6 +145,10 @@ defineProps({
       line-height: 20px;
       vertical-align: middle;
       font-size: 14px;
+      &.strikethrough {
+        text-decoration: line-through;
+        color: hsl(var(--foreground) / 0.35);
+      }
       &.selected {
         border-radius: 4px;
         background-color: hsl(var(--primary));

+ 2 - 1
web/apps/web-antd/src/views/match/components/solution_item.vue

@@ -155,7 +155,8 @@ const formatEvents = (events) => {
 
     const value = events[key]?.v ?? 0;
     const origin = events[key]?.r;
-    eventsMap[ratioKey][index] = { key, value, origin };
+    const qualified = events[key]?.q ?? 1;
+    eventsMap[ratioKey][index] = { key, value, origin, qualified };
   });
 
   return Object.keys(eventsMap).sort((a, b) => a.localeCompare(b)).map(key => {