Browse Source

支持hg ob

flyzto 4 days ago
parent
commit
a9ec28cfd6

+ 78 - 77
polymarket/main.js

@@ -25,23 +25,23 @@ const GLOBAL_DATA = {
   lastChangeTime: 0,
 };
 
-/**
- * 获取有效联赛
- * @param {*} marketsList
- * @param {*} soccerSports
- * @returns {Array}
- */
-const getLeagues = (marketsList, soccerSports) => {
-  const soccerSportsMap = new Map(soccerSports.map(item => [+item.series, item]));
-  const leaguesList = marketsList.map(item => {
-    const { leagueId: id, leagueName: name } = item;
-    const sport = soccerSportsMap.get(+id)?.sport;
-    return { id, name, sport };
-  });
-  // 去重并排序
-  const leaguesMap = new Map(leaguesList.map(item => [item.id, item]));
-  return Array.from(leaguesMap.values()).sort((a, b) => a.id - b.id);
-}
+// /**
+//  * 获取有效联赛
+//  * @param {*} marketsList
+//  * @param {*} soccerSports
+//  * @returns {Array}
+//  */
+// const getLeagues = (marketsList, soccerSports) => {
+//   const soccerSportsMap = new Map(soccerSports.map(item => [+item.series, item]));
+//   const leaguesList = marketsList.map(item => {
+//     const { leagueId: id, leagueName: name } = item;
+//     const sport = soccerSportsMap.get(+id)?.sport;
+//     return { id, name, sport };
+//   });
+//   // 去重并排序
+//   const leaguesMap = new Map(leaguesList.map(item => [item.id, item]));
+//   return Array.from(leaguesMap.values()).sort((a, b) => a.id - b.id);
+// }
 
 // /**
 //  * 获取足球联赛
@@ -58,65 +58,65 @@ const getLeagues = (marketsList, soccerSports) => {
 //   });
 // }
 
-/**
- * 提交联赛和比赛数据
- * @param {string} platform - 平台名称
- * @param {string} url - 请求地址
- * @param {Object} data - 请求数据
- * @returns {Promise}
- */
-const submitLeaguesAndGames = async ({ leagues, games }) => {
-  const submitLeagues = platformPost('/api/platforms/update_leagues', { platform: 'polymarket', leagues });
-  const submitGames = platformPost('/api/platforms/update_games', { platform: 'polymarket', games });
-  return Promise.all([submitLeagues, submitGames]);
-}
+// /**
+//  * 提交联赛和比赛数据
+//  * @param {string} platform - 平台名称
+//  * @param {string} url - 请求地址
+//  * @param {Object} data - 请求数据
+//  * @returns {Promise}
+//  */
+// const submitLeaguesAndGames = async ({ leagues, games }) => {
+//   const submitLeagues = platformPost('/api/platforms/update_leagues', { platform: 'polymarket', leagues });
+//   const submitGames = platformPost('/api/platforms/update_games', { platform: 'polymarket', games });
+//   return Promise.all([submitLeagues, submitGames]);
+// }
 
-/**
- * 更新联赛和比赛数据
- */
-const updateLeaguesAndGames = (marketsData) => {
-  const marketsList = Object.values(marketsData);
-  getSoccerSports()
-  .then(soccerSports => {
-    const leagues = getLeagues(marketsList, soccerSports);
-    const games = marketsList.map(game => {
-      const { marketsData, ...rest } = game;
-      return rest;
-    }).sort((a, b) => a.timestamp - b.timestamp);
-    return { leagues, games };
-  })
-  .then(data => {
-    Logs.outDev('update leagues and games list', data.leagues.length, data.games.length);
-    return submitLeaguesAndGames(data);
-  })
-  .then(() => {
-    Logs.outDev('leagues and games list updated');
-  })
-  .catch(error => {
-    Logs.out('failed to update leagues and games list', error.message);
-  });
-}
+// /**
+//  * 更新联赛和比赛数据
+//  */
+// const updateLeaguesAndGames = (marketsData) => {
+//   const marketsList = Object.values(marketsData);
+//   getSoccerSports()
+//   .then(soccerSports => {
+//     const leagues = getLeagues(marketsList, soccerSports);
+//     const games = marketsList.map(game => {
+//       const { marketsData, ...rest } = game;
+//       return rest;
+//     }).sort((a, b) => a.timestamp - b.timestamp);
+//     return { leagues, games };
+//   })
+//   .then(data => {
+//     Logs.outDev('update leagues and games list', data.leagues.length, data.games.length);
+//     return submitLeaguesAndGames(data);
+//   })
+//   .then(() => {
+//     Logs.outDev('leagues and games list updated');
+//   })
+//   .catch(error => {
+//     Logs.out('failed to update leagues and games list', error.message);
+//   });
+// }
 
-/**
- * 获取过滤后的比赛数据
- * @returns
- */
-const updateRelatedGames = () => {
-  platformGet('/api/platforms/get_related_games', { platform: 'polymarket' })
-  .then(res => {
-    const { data: relatedGames } = res;
-    Logs.outDev('relatedGames updated', relatedGames.length);
-    GLOBAL_DATA.relatedGames = relatedGames.map(item => item.id);
-  })
-  .catch(error => {
-    Logs.out('failed to update related games', error.message);
-  })
-  .finally(() => {
-    setTimeout(() => {
-      updateRelatedGames();
-    }, 1000 * 10);
-  });
-}
+// /**
+//  * 获取过滤后的比赛数据
+//  * @returns
+//  */
+// const updateRelatedGames = () => {
+//   platformGet('/api/platforms/get_related_games', { platform: 'polymarket' })
+//   .then(res => {
+//     const { data: relatedGames } = res;
+//     Logs.outDev('relatedGames updated', relatedGames.length);
+//     GLOBAL_DATA.relatedGames = relatedGames.map(item => item.id);
+//   })
+//   .catch(error => {
+//     Logs.out('failed to update related games', error.message);
+//   })
+//   .finally(() => {
+//     setTimeout(() => {
+//       updateRelatedGames();
+//     }, 1000 * 10);
+//   });
+// }
 
 /**
  * 获取赛事数据
@@ -148,7 +148,7 @@ const getMarketsData = async () => {
   })
   .then(marketsData => {
 
-    updateLeaguesAndGames(marketsData);
+    // updateLeaguesAndGames(marketsData);
 
     const { marketsData: oldMarketsData } = GLOBAL_DATA;
     const newMarketsData = marketsData;
@@ -247,7 +247,7 @@ const syncMarketsData = () => {
  */
 const getGamesEvents = () => {
   const { marketsData, lastChangeTime } = GLOBAL_DATA;
-  Logs.outDev('getGamesEvents', marketsData, lastChangeTime);
+  // Logs.outDev('getGamesEvents', marketsData, lastChangeTime);
   const games = Object.values(marketsData).map(item => {
     const { marketsData, ...rest } = item;
     const odds = parseOdds(marketsData);
@@ -265,6 +265,7 @@ const updateOdds = async () => {
   if (!games.length || timestamp < expireTime ) {
     return Promise.resolve();
   }
+  // Logs.outDev('updateOdds', games, timestamp);
   return platformPost('/api/platforms/update_odds', { platform: 'polymarket', games, timestamp });
 }
 
@@ -331,7 +332,7 @@ const main = () => {
   GLOBAL_DATA.marketWsClient = marketWsClient;
   marketWsClient.connect();
   marketWsClient.on('open', () => {
-    updateRelatedGames();
+    // updateRelatedGames();
     updateGamesMarkets();
     syncMarketsData();
     updateOddsLoop();

+ 74 - 0
server/libs/getGamesRelations.js

@@ -0,0 +1,74 @@
+import Store from "../state/store.js";
+
+const getGameData = (game, hasOdds=false) => {
+  if (!game) {
+    return null;
+  }
+  const { id, leagueId, leagueName, teamHomeName, teamAwayName, timestamp, odds, evtime } = game;
+  const gameData = { id, leagueId, leagueName, teamHomeName, teamAwayName, timestamp };
+  if (hasOdds) {
+    gameData.odds = odds;
+    gameData.evtime = evtime;
+  }
+  return gameData;
+}
+
+export const getGamesRelationsMap = (hasOdds=false) => {
+  const gamesRelations = Store.get('gamesRelations') ?? {};
+  const pmOdds = Store.get('polymarket', 'odds')?.games ?? [];
+  const pmEvtime = Store.get('polymarket', 'odds')?.timestamp ?? 0;
+  const pcOdds = Store.get('pinnacle', 'odds')?.games ?? [];
+  const hgOdds = Store.get('huanguan', 'odds')?.games ?? [];
+  const obOdds = Store.get('obsports', 'odds')?.games ?? [];
+  const pmOddsMap = new Map(pmOdds.map(item => [item.id, item]));
+  const pcOddsMap = new Map(pcOdds.map(item => [item.id, item]));
+  const hgOddsMap = new Map(hgOdds.map(item => [item.id, item]));
+  const obOddsMap = new Map(obOdds.map(item => [item.id, item]));
+
+  const relationsMap = {};
+  Object.entries(gamesRelations).forEach(([id, relation]) => {
+    const { platforms } = relation;
+    const { polymarket, pinnacle, huanguan, obsports } = platforms;
+    const { id: pmId = 0 } = polymarket;
+    const { id: pcId = 0 } = pinnacle;
+    const { id: hgId = 0 } = huanguan;
+    const { id: obId = 0 } = obsports;
+    const pmGame = getGameData(pmOddsMap.get(pmId), hasOdds) ?? {};
+    const pcGame = getGameData(pcOddsMap.get(pcId), hasOdds) ?? {};
+    const hgGame = getGameData(hgOddsMap.get(hgId), hasOdds) ?? {};
+    const obGame = getGameData(obOddsMap.get(obId), hasOdds) ?? {};
+    relationsMap[id] = { ...relation, platforms: {
+      polymarket: { ...polymarket, ...pmGame, evtime: pmEvtime },
+      pinnacle: { ...pinnacle, ...pcGame },
+      huanguan: { ...huanguan, ...hgGame },
+      obsports: { ...obsports, ...obGame },
+    }};
+  });
+  return relationsMap;
+}
+
+export const getSolutionsWithRelations = async (solutionsList, maxLength=0) => {
+  const gamesRelations = getGamesRelationsMap(true);
+
+  const selectedRelations = {};
+  solutionsList.forEach(solution => {
+    const rid = solution.rid;
+    if (!gamesRelations[rid]) {
+      return;
+    }
+    if (!selectedRelations[rid]) {
+      selectedRelations[rid] = { ...gamesRelations[rid] };
+    }
+    if (!selectedRelations[rid]['solutions']) {
+      selectedRelations[rid]['solutions'] = [];
+    }
+    if (maxLength > 0 && selectedRelations[rid]['solutions'].length >= maxLength) {
+      return;
+    }
+    selectedRelations[rid]['solutions'].push(solution);
+  });
+  const relationsList = Object.values(selectedRelations).sort((a, b) => {
+    return b.solutions[0].sol.win_profit_rate - a.solutions[0].sol.win_profit_rate;
+  });
+  return Promise.resolve(relationsList);
+}

+ 0 - 23
server/libs/getSolutions.js

@@ -1,23 +0,0 @@
-export const getSolutionsWithRelations = async (solutionsList, gamesRelations, maxLength=0) => {
-  const selectedRelations = {};
-  solutionsList.forEach(solution => {
-    const { info: { id }, ...solutionRest } = solution;
-    if (!gamesRelations[id]) {
-      return;
-    }
-    if (!selectedRelations[id]) {
-      selectedRelations[id] = { ...gamesRelations[id] };
-    }
-    if (!selectedRelations[id]['solutions']) {
-      selectedRelations[id]['solutions'] = [];
-    }
-    if (maxLength > 0 && selectedRelations[id]['solutions'].length >= maxLength) {
-      return;
-    }
-    selectedRelations[id]['solutions'].push(solutionRest);
-  });
-  const relationsList = Object.values(selectedRelations).sort((a, b) => {
-    return b.solutions[0].sol.win_profit_rate - a.solutions[0].sol.win_profit_rate;
-  });
-  return Promise.resolve(relationsList);
-}

+ 25 - 18
server/models/Games.js

@@ -1,5 +1,5 @@
 import GetTranslation from "../libs/getTranslation.js";
-import { getSolutionsWithRelations } from "../libs/getSolutions.js";
+import { getSolutionsWithRelations, getGamesRelationsMap } from "../libs/getGamesRelations.js";
 import Store from "../state/store.js";
 
 import { getPlatformIorsDetailInfo, getSolutionByLatestIors, getSoulutionBetResult } from "./Markets.js";
@@ -103,21 +103,29 @@ export const removeGamesRelation = async (id) => {
 }
 
 export const getGamesRelations = async () => {
-  const storeRelations = Object.values(Store.get('gamesRelations') ?? {});
-  const polymarketNames = [ ...new Set(storeRelations.map(item => {
-    const { teamHomeName, teamAwayName, leagueName } = item.platforms.polymarket;
-    return [teamHomeName, teamAwayName, leagueName];
-  }).flat()) ];
-  const translatedNames = await GetTranslation(polymarketNames);
-  const newRelations = storeRelations.map(item => {
-    const { platforms: { polymarket, pinnacle } } = item;
-    const { teamHomeName, teamAwayName, leagueName } = polymarket;
-    const localesTeamHomeName = translatedNames[teamHomeName] ?? teamHomeName;
-    const localesTeamAwayName = translatedNames[teamAwayName] ?? teamAwayName;
-    const localesLeagueName = translatedNames[leagueName] ?? leagueName;
-    return { ...item, platforms: { polymarket: { ...polymarket, localesTeamHomeName, localesTeamAwayName, localesLeagueName }, pinnacle } };
-  }).sort((a, b) => a.timestamp - b.timestamp);
-  return Promise.resolve(newRelations);
+  // const storeData = Store.get('gamesRelations') ?? {};
+  // console.log('get games relations', storeData);
+  // const storeRelations = Object.values(storeData);
+  // console.log('store relations', storeRelations);
+  // const polymarketNames = [ ...new Set(storeRelations.map(item => {
+  //   const { teamHomeName, teamAwayName, leagueName } = item.platforms.polymarket;
+  //   return [teamHomeName, teamAwayName, leagueName];
+  // }).flat()) ];
+  // const translatedNames = await GetTranslation(polymarketNames);
+  // const newRelations = storeRelations.map(item => {
+  //   const { platforms: { polymarket, pinnacle } } = item;
+  //   const { teamHomeName, teamAwayName, leagueName } = polymarket;
+  //   const localesTeamHomeName = translatedNames[teamHomeName] ?? teamHomeName;
+  //   const localesTeamAwayName = translatedNames[teamAwayName] ?? teamAwayName;
+  //   const localesLeagueName = translatedNames[leagueName] ?? leagueName;
+  //   return { ...item, platforms: { polymarket: { ...polymarket, localesTeamHomeName, localesTeamAwayName, localesLeagueName }, pinnacle } };
+  // }).sort((a, b) => a.timestamp - b.timestamp);
+  // const newRelations = storeRelations.map(item => {
+  //   return { ...item };
+  // }).sort((a, b) => a.timestamp - b.timestamp);
+
+  const gamesRelations = Object.values(getGamesRelationsMap()).sort((a, b) => a.timestamp - b.timestamp);
+  return Promise.resolve(gamesRelations);
 }
 
 /**
@@ -133,8 +141,7 @@ export const getSolutions = async ({ min_profit_rate = 0 } = {}) => {
   }).sort((a, b) => {
     return b.sol.win_profit_rate - a.sol.win_profit_rate;
   });
-  const gamesRelations = Store.get('gamesRelations') ?? {};
-  return getSolutionsWithRelations(solutionsList, gamesRelations, 5);
+  return getSolutionsWithRelations(solutionsList, 5);
 }
 
 /**

+ 45 - 3
server/models/Partner.js

@@ -13,6 +13,8 @@ dotenv.config();
 const __filename = fileURLToPath(import.meta.url);
 const __dirname = path.dirname(__filename);
 
+const IS_DEV = process.env.NODE_ENV == 'development';
+
 const PARTNER_DATA_FILE = path.join(__dirname, '../data/partner.json');
 
 /**
@@ -23,6 +25,23 @@ const { PARTNER_APP_ID, PARTNER_APP_KEY } = process.env;
 
 const PARTNER_DATA = {};
 
+/**
+ * QBoss API environment variables
+ */
+const QBOSS_API_BASE = IS_DEV ? 'https://cb.long.bid/api' : 'http://172.27.74.243/api';
+
+
+/**
+ * axios instance
+ */
+const axiosInstance = axios.create({
+  proxy: false,
+});
+
+/**
+ *
+ */
+
 /**
  * 加载本地数据
  */
@@ -73,7 +92,7 @@ const isTimestampValid = (timestamp, window=300) => {
 /**
  * 请求Partner数据接口
  */
-export const requestData = async (action, data) => {
+export const requestData = async (action, data={}) => {
   const timestamp = getTimestamp();
   const nonce = generateNonce();
   const sign = generateSignature(PARTNER_APP_KEY, action, timestamp, nonce);
@@ -82,9 +101,14 @@ export const requestData = async (action, data) => {
   if (Object.keys(data).length > 0) {
     requestData.params = data;
   }
-  return axios.post(PARTNER_API_BASE, requestData).then(res => res.data);
+  return axiosInstance.post(PARTNER_API_BASE, requestData).then(res => res.data);
 }
 
+/**
+ * 更新策略
+ * @param {*} solutions
+ * @returns
+ */
 export const updateSolutions = async (solutions) => {
   if (process.env.NODE_ENV == 'development') {
     return Promise.resolve({ message: 'development mode' });
@@ -92,6 +116,24 @@ export const updateSolutions = async (solutions) => {
   return requestData('opps.soccer', solutions);
 }
 
+/**
+ * 获取关联比赛
+ * @returns
+ */
+export const getSoccerGames = async () => {
+  return requestData('game.soccer');
+}
+
+/**
+ * 获取QBoss赔率数据
+ * @returns
+ */
+export const getObossOdds = async () => {
+  return axiosInstance.get(`${QBOSS_API_BASE}/pstery/get_games_relation`, {
+    params: { lo: true, lp: true, hb: true, hh: true },
+  }).then(res => res.data);
+}
+
 /**
  * 接收Partner数据
  */
@@ -111,4 +153,4 @@ export const receivePartnerData = async (data) => {
   return Promise.resolve({ action, params });
 }
 
-export default { updateSolutions, receivePartnerData };
+export default { updateSolutions, getSoccerGames, getObossOdds, receivePartnerData };

+ 127 - 24
server/models/Platforms.js

@@ -4,8 +4,8 @@ import Store from "../state/store.js";
 import ProcessData from "../libs/processData.js";
 import Logs from "../libs/logs.js";
 
-import { getSolutionsWithRelations } from "../libs/getSolutions.js";
-import { updateSolutions } from "./Partner.js";
+import { getSolutionsWithRelations, getGamesRelationsMap } from "../libs/getGamesRelations.js";
+import { updateSolutions, getSoccerGames, getObossOdds } from "./Partner.js";
 
 // import { getPlatformIorInfo } from "./Markets.js";
 
@@ -22,26 +22,8 @@ const triangleProcess = fork("triangle/main.js", [], getChildOptions(9229));
 const triangleData = new ProcessData(triangleProcess, 'triangle');
 
 triangleData.registerResponse('gamesRelations', async () => {
-  const gamesRelations = Object.values(Store.get('gamesRelations') ?? {});
-  const polymarketOdds = Store.get('polymarket', 'odds') ?? {};
-  const pinnacleOdds = Store.get('pinnacle', 'odds') ?? {};
-  const expireTime = Date.now() - 1000 * 15;
-  const { games: polymarketGames = [], timestamp: polymarketTimestamp = 0 } = polymarketOdds;
-  const { games: pinnacleGames = [], timestamp: pinnacleTimestamp = 0 } = pinnacleOdds;
-  const polymarketOddsMap = polymarketTimestamp > expireTime ? new Map(polymarketGames.map(item => [item.id, item])) : new Map();
-  const pinnacleOddsMap = pinnacleTimestamp > expireTime ? new Map(pinnacleGames.map(item => [item.id, item])) : new Map();
-  const newRelations = gamesRelations.map(relation => {
-    const { platforms: { polymarket, pinnacle }, ...rest } = relation;
-    const polymarketId = polymarket.id;
-    const pinnacleId = pinnacle.id;
-    const polymarketOdds = polymarketOddsMap.get(polymarketId)?.odds;
-    const pinnacleOdds = pinnacleOddsMap.get(pinnacleId)?.odds;
-    return { ...rest, platforms: {
-      polymarket: { ...polymarket, odds: polymarketOdds, evtime: polymarketTimestamp },
-      pinnacle: { ...pinnacle, odds: pinnacleOdds, evtime: pinnacleTimestamp },
-    }};
-  });
-  return Promise.resolve(newRelations);
+  const gamesRelations = Object.values(getGamesRelationsMap(true));
+  return Promise.resolve(gamesRelations);
 });
 
 triangleData.registerRequest('solutions', solutions => {
@@ -67,8 +49,7 @@ triangleData.registerRequest('solutions', solutions => {
   });
   if (changed.update.length || changed.add.length || changed.remove.length) {
     Store.set('solutions', solutions);
-    const gamesRelations = Store.get('gamesRelations') ?? {};
-    getSolutionsWithRelations(solutions, gamesRelations, 5)
+    getSolutionsWithRelations(solutions, 5)
     .then(solutionsList => {
       Logs.outDev('get solutions with relations', solutionsList);
       return updateSolutions(solutionsList)
@@ -184,6 +165,128 @@ export const updateOdds = async ({ platform, games, timestamp }) => {
   return Promise.resolve();
 };
 
+/**
+ * 同步QBoss赔率数据
+ * @param {*} relationsData
+ */
+const syncObossOdds = (relationsData) => {
+  const timestamp = Date.now();
+  const pinnacle = [];
+  const obsports = [];
+  const huanguan = [];
+  relationsData.forEach(item => {
+    Object.entries(item.rel).forEach(([key, value]) => {
+      const { eventId: id, ...rest } = value;
+      const game = { id, ...rest }
+      if (key === 'pc') {
+        pinnacle.push(game);
+      }
+      else if (key === 'ob') {
+        obsports.push(game);
+      }
+      else if (key === 'hg') {
+        huanguan.push(game);
+      }
+    });
+  });
+  Promise.all([
+    updateOdds({ platform: 'pinnacle', games: pinnacle, timestamp }),
+    updateOdds({ platform: 'obsports', games: obsports, timestamp }),
+    updateOdds({ platform: 'huanguan', games: huanguan, timestamp }),
+  ]);
+}
+
+/**
+ * 定时更新QBoss赔率数据
+ */
+const updateObossOdds = () => {
+  getObossOdds()
+  .then(res => {
+    if (res.statusCode === 200) {
+      return syncObossOdds(res.data);
+    }
+    return Promise.reject(new Error(`status code ${res.statusCode}`));
+  })
+  .catch(error => {
+    Logs.err('failed to update oboss odds', error.message);
+  })
+  .finally(() => {
+    setTimeout(() => {
+      updateObossOdds();
+    }, 1000 * 2);
+  });
+}
+updateObossOdds();
+
+/**
+ * 同步关联比赛数据
+ * @param {*} relationsData
+ */
+const syncGamesRelations = (relationsData) => {
+  const storeRelations = Store.get('gamesRelations') ?? {};
+  const newRelations = relationsData.map(item => {
+    const {
+      event_id: pmId,
+      ps_event_id: pcId,
+      ob_event_id: obId,
+      hg_event_id: hgId,
+      start_time: startTime,
+    } = item;
+    const timestamp = new Date(startTime).getTime();
+    return {
+      id: pmId,
+      timestamp,
+      platforms: {
+        polymarket: { id: +pmId },
+        pinnacle: { id: +pcId },
+        huanguan: { id: +hgId },
+        obsports: { id: +obId }
+      }
+    }
+  });
+  const changed = { add: 0, update: 0, remove: 0 }
+  const newRelationsSet = new Set(newRelations.map(item => item.id));
+  Object.keys(storeRelations).forEach(id => {
+    if (!newRelationsSet.has(+id)) {
+      delete storeRelations[id];
+      changed.remove++;
+    }
+  });
+  newRelations.forEach(item => {
+    if (!storeRelations[item.id]) {
+      storeRelations[item.id] = item;
+      changed.add++;
+    }
+  });
+
+  if (changed.add || changed.update || changed.remove) {
+    Store.set('gamesRelations', storeRelations);
+    Logs.outDev('sync games relations', changed, storeRelations);
+  }
+}
+
+/**
+ * 定时更新关联比赛
+ */
+const updateGamesRelations = () => {
+  getSoccerGames()
+  .then(res => {
+    if (res.success) {
+      return syncGamesRelations(res.data);
+    }
+    return Promise.reject(new Error(res.message));
+  })
+  .catch(error => {
+    Logs.err('failed to update games relations', error.message);
+  })
+  .finally(() => {
+    setTimeout(() => {
+      updateGamesRelations();
+    }, 1000 * 30);
+  });
+}
+updateGamesRelations();
+
 export default {
   updateLeagues, getRelatedLeagues,
   updateGames, getRelatedGames,

+ 20 - 11
server/triangle/trangleCalc.js

@@ -70,8 +70,9 @@ const getOptimalSelections = (odds, rules) => {
     if (isValid) {
       const cartesian = cartesianOdds(selection);
       cartesian.forEach(iors => {
+        const hasPm = iors.some(ior => ior.p === 'polymarket');
         const iorsCount = new Set(iors.filter(ior => ior.p !== 'no').map(ior => ior.p));
-        if (iorsCount.size > 1) {
+        if (hasPm && iorsCount.size > 1) {
           validOptions.push(iors);
         }
       });
@@ -84,17 +85,19 @@ const getOptimalSelections = (odds, rules) => {
 }
 
 export const getPassableEvents = (relations) => {
-  return relations.map(({ platforms }) => {
+  return relations.map(({ id, platforms }) => {
     const eventsMap = {};
     const oddsMap = {};
 
     Object.keys(platforms).forEach(platform => {
-      const { id, leagueName, teamHomeName, teamAwayName, timestamp, evtime, odds } = platforms[platform] ?? {};
+      const { odds, evtime } = platforms[platform] ?? {};
       if (!odds) {
+        // console.log('odds not found', platform, odds);
         return;
       }
-      if (platform === 'polymarket') {
-        eventsMap['info'] = { id, leagueName, teamHomeName, teamAwayName, timestamp };
+      if (evtime < Date.now() - 1000 * 15) {
+        // console.log('odds is expired', platform, evtime);
+        return;
       }
       Object.keys(odds).forEach(ior => {
         if (!oddsMap[ior]) {
@@ -104,15 +107,16 @@ export const getPassableEvents = (relations) => {
       });
     });
     eventsMap['odds'] = oddsMap;
+    eventsMap['rid'] = id;
     return eventsMap;
   })
-  .filter(item => item?.info);
+  .filter(item => item?.rid);
 }
 
 export const eventsCombination = (passableEvents) => {
   const solutions = [];
   passableEvents.forEach(events => {
-    const { odds, info } = events;
+    const { odds, rid } = events;
     Object.keys(iorKeys).forEach(iorGroup => {
       const rules = iorKeys[iorGroup];
       const optimalSelections = getOptimalSelections(odds, rules);
@@ -135,19 +139,24 @@ export const eventsCombination = (passableEvents) => {
           odds_side_a: fixFloat(oddsSideA.v - 1),
           odds_side_b: fixFloat(oddsSideB.v - 1),
           odds_side_c: fixFloat(oddsSideC.v - 1),
+          rebate_side_a: fixFloat(((oddsSideA.b ?? 0) / 100), 4),
+          rebate_side_b: fixFloat(((oddsSideB.b ?? 0) / 100), 4),
+          rebate_side_c: fixFloat(((oddsSideC.b ?? 0) / 100), 4),
+          rebate_type_side_a: oddsSideA.t ?? 0,
+          rebate_type_side_b: oddsSideB.t ?? 0,
+          rebate_type_side_c: oddsSideC.t ?? 0,
         };
         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 keys = cpr.map(item => `${item.k}-${item.p}`).join('$$');
+          const sid = crypto.createHash('sha1').update(`${rid}-${keys}`).digest('hex');
           const hasLower = cpr.some(item => item.q === 0);
           const platformKey = getPlatformKey(cpr);
           const timestamp = Date.now();
-          solutions.push({sid, sol, cpr, cross: platformKey, info, lower: hasLower, rule: `${iorGroup}:${ruleIndex}`, timestamp});
+          solutions.push({sid, sol, cpr, cross: platformKey, rid, lower: hasLower, rule: `${iorGroup}:${ruleIndex}`, timestamp});
         }
       });
     });