parseGameData.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. /**
  2. * 解析让球方
  3. */
  4. const ratioAccept = (ratio) => {
  5. if (ratio > 0) {
  6. return 'a';
  7. }
  8. return '';
  9. }
  10. /**
  11. * 将比率转换为字符串
  12. * @param {*} ratio
  13. * @returns
  14. */
  15. const ratioString = (ratio) => {
  16. ratio = Math.abs(ratio);
  17. ratio = ratio.toString();
  18. ratio = ratio.replace(/\./, '');
  19. return ratio;
  20. }
  21. /**
  22. * 解析胜平负盘口
  23. * @param {*} moneyline
  24. * @returns
  25. */
  26. const parseMoneyline = (moneyline) => {
  27. // 胜平负
  28. if (!moneyline) {
  29. return null;
  30. }
  31. const { home, away, draw } = moneyline;
  32. return {
  33. 'ior_mh': { v: home },
  34. 'ior_mc': { v: away },
  35. 'ior_mn': { v: draw },
  36. }
  37. }
  38. /**
  39. * 解析让分盘口
  40. * @param {*} spreads
  41. * @param {*} wm
  42. * @returns
  43. */
  44. const parseSpreads = (spreads, wm) => {
  45. // 让分盘
  46. if (!spreads?.length) {
  47. return null;
  48. }
  49. const events = {};
  50. spreads.forEach(spread => {
  51. const { hdp, home, away } = spread;
  52. // if (!(hdp % 1) || !!(hdp % 0.5)) {
  53. // // 整数或不能被0.5整除的让分盘不处理
  54. // return;
  55. // }
  56. const ratio_ro = hdp;
  57. const ratio_r = ratio_ro - wm;
  58. events[`ior_r${ratioAccept(ratio_r)}h_${ratioString(ratio_r)}`] = {
  59. v: home,
  60. r: wm != 0 ? `ior_r${ratioAccept(ratio_ro)}h_${ratioString(ratio_ro)}` : undefined
  61. };
  62. events[`ior_r${ratioAccept(-ratio_r)}c_${ratioString(ratio_r)}`] = {
  63. v: away,
  64. r: wm != 0 ? `ior_r${ratioAccept(-ratio_ro)}c_${ratioString(ratio_ro)}` : undefined
  65. };
  66. });
  67. return events;
  68. }
  69. /**
  70. * 解析大小球盘口
  71. * @param {*} totals
  72. * @returns
  73. */
  74. const parseTotals = (totals) => {
  75. // 大小球盘
  76. if (!totals?.length) {
  77. return null;
  78. }
  79. const events = {};
  80. totals.forEach(total => {
  81. const { points, over, under } = total;
  82. events[`ior_ouc_${ratioString(points)}`] = { v: over };
  83. events[`ior_ouh_${ratioString(points)}`] = { v: under };
  84. });
  85. return events;
  86. }
  87. /**
  88. * 解析直赛盘口
  89. * @param {*} straight
  90. * @param {*} wm
  91. * @returns
  92. */
  93. const parseStraight = (straight, wm) => {
  94. if (!straight) {
  95. return null;
  96. }
  97. const { cutoff='', status=0, spreads=[], moneyline={}, totals=[] } = straight;
  98. const cutoffTime = new Date(cutoff).getTime();
  99. const nowTime = Date.now();
  100. if (status != 1 || cutoffTime < nowTime) {
  101. return null;
  102. }
  103. const events = {};
  104. Object.assign(events, parseSpreads(spreads, wm));
  105. Object.assign(events, parseMoneyline(moneyline));
  106. Object.assign(events, parseTotals(totals));
  107. return events;
  108. }
  109. /**
  110. * 解析净胜球盘口
  111. * @param {*} winningMargin
  112. * @param {*} home
  113. * @param {*} away
  114. * @returns
  115. */
  116. const parseWinningMargin = (winningMargin, home, away) => {
  117. if (!winningMargin) {
  118. return null;
  119. }
  120. const { cutoff='', status='', contestants=[] } = winningMargin;
  121. const cutoffTime = new Date(cutoff).getTime();
  122. const nowTime = Date.now();
  123. if (status != 'O' || cutoffTime < nowTime || !contestants?.length) {
  124. return null;
  125. }
  126. const events = {};
  127. contestants.forEach(contestant => {
  128. const { name, price } = contestant;
  129. const nr = name.match(/\d+$/)?.[0];
  130. if (!nr) {
  131. return;
  132. }
  133. let side;
  134. if (name.startsWith(home)) {
  135. side = 'h';
  136. }
  137. else if (name.startsWith(away)) {
  138. side = 'c';
  139. }
  140. else {
  141. return;
  142. }
  143. events[`ior_wm${side}_${nr}`] = { v: price };
  144. });
  145. return events;
  146. }
  147. /**
  148. * 解析进球数盘口
  149. * @param {*} exactTotalGoals
  150. * @returns
  151. */
  152. const parseExactTotalGoals = (exactTotalGoals) => {
  153. if (!exactTotalGoals) {
  154. return null;
  155. }
  156. const { cutoff='', status='', contestants=[] } = exactTotalGoals;
  157. const cutoffTime = new Date(cutoff).getTime();
  158. const nowTime = Date.now();
  159. if (status != 'O' || cutoffTime < nowTime || !contestants?.length) {
  160. return null;
  161. }
  162. const events = {};
  163. contestants.forEach(contestant => {
  164. const { name, price } = contestant;
  165. if (+name >= 1 && +name <= 7) {
  166. events[`ior_ot_${name}`] = { v: price };
  167. }
  168. });
  169. return events;
  170. }
  171. /**
  172. * 解析滚球场次状态
  173. * @param {*} state
  174. * @returns
  175. */
  176. const parseRbState = (state) => {
  177. let stage = null;
  178. if (state == 1) {
  179. stage = '1H';
  180. }
  181. else if (state == 2) {
  182. stage = 'HT';
  183. }
  184. else if (state == 3) {
  185. stage = '2H';
  186. }
  187. else if (state >= 4) {
  188. stage = 'ET';
  189. }
  190. return stage;
  191. }
  192. /**
  193. * 解析比赛数据
  194. * @param {*} game
  195. * @returns
  196. */
  197. export const parseGame = (game, filtedGames) => {
  198. const { eventId=0, originId=0, periods={}, specials={}, home, away, marketType, state, elapsed, homeScore=0, awayScore=0 } = game;
  199. const { straight, straight1st } = periods;
  200. const { winningMargin={}, exactTotalGoals={}, winningMargin1st={}, exactTotalGoals1st={} } = specials;
  201. const filtedGamesSet = new Set(filtedGames);
  202. const wm = homeScore - awayScore;
  203. const score = `${homeScore}-${awayScore}`;
  204. const events = parseStraight(straight, wm) ?? {};
  205. const stage = parseRbState(state);
  206. const retime = elapsed ? `${elapsed}'` : '';
  207. const evtime = Date.now();
  208. Object.assign(events, parseWinningMargin(winningMargin, home, away));
  209. Object.assign(events, parseExactTotalGoals(exactTotalGoals));
  210. const gameInfos = [];
  211. gameInfos.push({ eventId, originId, events, evtime, stage, retime, score, wm, marketType });
  212. const halfEventId = eventId * -1;
  213. if (filtedGamesSet.has(halfEventId)) {
  214. const events = parseStraight(straight1st, wm) ?? {};
  215. Object.assign(events, parseWinningMargin(winningMargin1st, home, away));
  216. Object.assign(events, parseExactTotalGoals(exactTotalGoals1st));
  217. gameInfos.push({ eventId: halfEventId, originId, events, evtime, stage, retime, score, wm, marketType });
  218. }
  219. return gameInfos;
  220. }
  221. /*
  222. * 解析比率信息
  223. */
  224. const parseRatio = (ratioString) => {
  225. if (!ratioString) {
  226. return null;
  227. }
  228. return parseFloat(`${ratioString[0]}.${ratioString.slice(1)}`);
  229. }
  230. /**
  231. * 解析盘口信息
  232. * @param {*} ior
  233. * @returns
  234. */
  235. const parseIor = (ior) => {
  236. const iorMatch = ior.match(/ior_(m|r|ou|wm|ot)([ao])?([hcn])?_?(\d+)?/);
  237. if (!iorMatch) {
  238. return null;
  239. }
  240. const [, type, action, side, ratio] = iorMatch;
  241. return { type, action, side, ratio };
  242. }
  243. /**
  244. * 解析盘口详情
  245. * @param {*} ior
  246. * @param {*} id
  247. * @param {*} gamesMap
  248. * @returns
  249. */
  250. export const parseIorDetail = (ior, id, gamesMap) => {
  251. const gamesData = gamesMap[id];
  252. if (!gamesData) {
  253. return { ior, id, message: 'games data not found', cause: 400 };
  254. }
  255. const iorOptions = parseIor(ior);
  256. if (!iorOptions) {
  257. return { ior, id, message: 'ior options not found', cause: 400 };
  258. }
  259. const { type, action, side, ratio } = iorOptions;
  260. const { leagueId, id: eventId, home: homeTeamName, away: awayTeamName, periods={}, specials={}} = gamesData;
  261. const straightData = periods.straight ?? {};
  262. const { lineId: straightLineId, moneyline, spreads, totals } = straightData;
  263. const { winningMargin, exactTotalGoals } = specials;
  264. if (type === 'm' && moneyline && !action && !ratio) {
  265. const sideKey = side === 'h' ? 'home' : side === 'c' ? 'away' : 'draw';
  266. const team = side === 'h' ? 'TEAM1' : side === 'c' ? 'TEAM2' : 'DRAW';
  267. const odds = moneyline[sideKey];
  268. return { leagueId, eventId, betType: 'MONEYLINE', team, lineId: straightLineId, odds };
  269. }
  270. else if (type === 'r' && spreads) {
  271. let ratioDirection = 1;
  272. if (side === 'c' && action === 'a' || side === 'h' && !action) {
  273. ratioDirection = -1;
  274. }
  275. const ratioKey = parseRatio(ratio) * ratioDirection;
  276. const itemSpread = spreads.find(spread => spread.hdp == ratioKey);
  277. if (!itemSpread) {
  278. return { ior, id, message: 'item spread not found', cause: 400 };
  279. }
  280. const { altLineId=null, home, away } = itemSpread;
  281. const odds = side === 'h' ? home : away;
  282. const team = side === 'h' ? 'TEAM1' : 'TEAM2';
  283. const handicap = ratioKey * (side === 'h' ? 1 : -1);
  284. return { leagueId, eventId, handicap, betType: 'SPREAD', team, lineId: straightLineId, altLineId, odds };
  285. }
  286. else if (type === 'ou' && totals) {
  287. const ratioKey = parseRatio(ratio);
  288. const itemTotal = totals.find(total => total.points == ratioKey);
  289. if (!itemTotal) {
  290. return { ior, id, message: 'item total not found', cause: 400 };
  291. }
  292. const { altLineId=null, over, under } = itemTotal;
  293. const odds = side === 'c' ? over : under;
  294. const sideKey = side === 'c' ? 'OVER' : 'UNDER';
  295. return { leagueId, eventId, handicap: ratioKey, betType: 'TOTAL_POINTS', side: sideKey, lineId: straightLineId, altLineId, odds };
  296. }
  297. else if (type === 'wm' && winningMargin) {
  298. const ratioKey = parseRatio(ratio);
  299. const { id: specialId } = winningMargin;
  300. const wmName = side === 'h' ? `${homeTeamName} By ${ratioKey}` : side === 'c' ? `${awayTeamName} By ${ratioKey}` : '';
  301. const wmItem = winningMargin.contestants.find(contestant => contestant.name == wmName);
  302. if (!wmItem) {
  303. return { ior, id, message: 'item winning margin not found', cause: 400 };
  304. }
  305. const { id: contestantId, lineId, price } = wmItem;
  306. return { leagueId, eventId, specialId, contestantId, lineId, odds: price };
  307. }
  308. else if (type === 'ot' && exactTotalGoals) {
  309. const ratioKey = parseRatio(ratio);
  310. const { id: specialId } = exactTotalGoals;
  311. const otItem = exactTotalGoals.contestants.find(contestant => contestant.name == ratioKey);
  312. if (!otItem) {
  313. Logs.outDev('pinnacle item exact total goals not found', id, type, action, side, ratio);
  314. return { ior, id, message: 'item exact total goals not found', cause: 400 };
  315. }
  316. const { id: contestantId, lineId, price } = otItem;
  317. return { leagueId, eventId, specialId, contestantId, lineId, odds: price };
  318. }
  319. else {
  320. return { ior, id, message: 'ior type not found', cause: 400 };
  321. }
  322. }