parseMarkets.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. import getDateInTimezone from "./getDateInTimezone.js";
  2. /**
  3. * 精确浮点数字
  4. * @param {number} number
  5. * @param {number} x
  6. * @returns {number}
  7. */
  8. const fixFloat = (number, x=3) => {
  9. return parseFloat(number.toFixed(x));
  10. }
  11. /**
  12. * 清理对象中的undefined值
  13. * @param {*} obj
  14. * @returns {Object}
  15. */
  16. const cleanUndefined = (obj) => {
  17. return Object.fromEntries(
  18. Object.entries(obj).filter(([, v]) => v !== undefined)
  19. );
  20. }
  21. /**
  22. * 解析赛事标题
  23. * @param {*} eventTitle
  24. * @returns {Object}
  25. */
  26. const parseTeamData = (eventTitle) => {
  27. let titleSpliter;
  28. if (eventTitle.includes(' vs. ')) {
  29. titleSpliter = ' vs. ';
  30. }
  31. else if (eventTitle.includes(' vs ')) {
  32. titleSpliter = ' vs ';
  33. }
  34. if (!titleSpliter) {
  35. return {
  36. teamHomeName: '',
  37. teamAwayName: '',
  38. };
  39. }
  40. const teamData = eventTitle.replace(' - More Markets', '').split(titleSpliter);
  41. return {
  42. teamHomeName: teamData[0],
  43. teamAwayName: teamData[1],
  44. };
  45. }
  46. /**
  47. * 解析盘口键值对
  48. * 示例:
  49. * {
  50. * "Yes": 0.525,
  51. * "No": 0.475
  52. * }
  53. * @param {*} outcomes
  54. * @param {*} outcomePrices
  55. * @returns {Object}
  56. */
  57. const parseOutcomes = (outcomes, clobTokenIds, bestBid=0, bestAsk=0) => {
  58. const keys = JSON.parse(outcomes);
  59. const ids = JSON.parse(clobTokenIds);
  60. return keys.reduce((obj, key, index) => {
  61. let best_ask;
  62. let best_bid;
  63. if (index === 0) {
  64. best_ask = (bestAsk).toFixed(3);
  65. best_bid = (bestBid).toFixed(3);
  66. }
  67. else if (index === 1) {
  68. best_ask = (1 - bestBid).toFixed(3);
  69. best_bid = (1 - bestAsk).toFixed(3);
  70. }
  71. obj[key] = { id: ids[index], best_ask, best_bid };
  72. return obj;
  73. }, {});
  74. }
  75. /**
  76. * 解析盘口数据
  77. * @param {*} markets
  78. * @returns {Object}
  79. */
  80. const ratioAccept = (ratio) => {
  81. if (ratio > 0) {
  82. return 'a';
  83. }
  84. return '';
  85. }
  86. const ratioString = (ratio) => {
  87. ratio = Math.abs(ratio);
  88. ratio = ratio.toString();
  89. ratio = ratio.replace(/\./, '');
  90. return ratio;
  91. }
  92. export const parseOdds = (markets) => {
  93. const odds = {};
  94. Object.keys(markets).forEach(key => {
  95. const marketData = markets[key];
  96. if (key === 'moneyline') {
  97. Object.keys(marketData).forEach(side => {
  98. const askYes = marketData[side].outcomes['Yes']['best_ask'];
  99. const askNo = marketData[side].outcomes['No']['best_ask'];
  100. if (askYes <= 0.1 || askNo <= 0.1) {
  101. return;
  102. }
  103. const iorYes = fixFloat(1 / askYes);
  104. const iorNo = fixFloat(1 / askNo);
  105. let iorKeyYes = '';
  106. let iorKeyNo = '';
  107. switch (side) {
  108. case 'Home':
  109. iorKeyYes = 'ior_mh';
  110. iorKeyNo = 'ior_moh';
  111. break;
  112. case 'Draw':
  113. iorKeyYes = 'ior_mn';
  114. iorKeyNo = 'ior_mon';
  115. break;
  116. case 'Away':
  117. iorKeyYes = 'ior_mc';
  118. iorKeyNo = 'ior_moc';
  119. break;
  120. }
  121. odds[iorKeyYes] = { v: iorYes, ask: askYes };
  122. odds[iorKeyNo] = { v: iorNo, ask: askNo };
  123. });
  124. }
  125. else if (key === 'spreads') {
  126. Object.keys(marketData).forEach(handicap => {
  127. const ratio = +handicap;
  128. const askHome = marketData[handicap].outcomes['Home']['best_ask'];
  129. const askAway = marketData[handicap].outcomes['Away']['best_ask'];
  130. if (askHome <= 0.1 || askAway <= 0.1) {
  131. return;
  132. }
  133. const iorHome = fixFloat(1 / askHome);
  134. const iorAway = fixFloat(1 / askAway);
  135. odds[`ior_r${ratioAccept(ratio)}h_${ratioString(ratio)}`] = { v: iorHome, ask: askHome };
  136. odds[`ior_r${ratioAccept(-ratio)}c_${ratioString(ratio)}`] = { v: iorAway, ask: askAway };
  137. });
  138. }
  139. else if (key === 'totals') {
  140. Object.keys(marketData).forEach(handicap => {
  141. const ratio = +handicap;
  142. const askOver = marketData[handicap].outcomes['Over']['best_ask'];
  143. const askUnder = marketData[handicap].outcomes['Under']['best_ask'];
  144. if (askOver <= 0.1 || askUnder <= 0.1) {
  145. return;
  146. }
  147. const iorOver = fixFloat(1 / askOver);
  148. const iorUnder = fixFloat(1 / askUnder);
  149. odds[`ior_ouc_${ratioString(ratio)}`] = { v: iorOver, ask: askOver };
  150. odds[`ior_ouh_${ratioString(ratio)}`] = { v: iorUnder, ask: askUnder };
  151. });
  152. }
  153. });
  154. return odds;
  155. }
  156. /**
  157. * 解析胜平负盘口
  158. * @param {*} groupItemThreshold
  159. * @param {*} outcomesMap
  160. * @param {*} market
  161. * @returns {Object}
  162. */
  163. const parseMoneyline = (groupItemTitle, outcomesMap, teams, market) => {
  164. const { teamHomeName, teamAwayName } = teams;
  165. let key = '';
  166. if (groupItemTitle == teamHomeName) {
  167. key = 'Home';
  168. }
  169. else if (groupItemTitle == teamAwayName) {
  170. key = 'Away';
  171. }
  172. else if (groupItemTitle.startsWith('Draw')) {
  173. key = 'Draw';
  174. }
  175. if (!key) {
  176. return {};
  177. }
  178. return { [key]: { outcomes: outcomesMap, market } };
  179. };
  180. /**
  181. * 解析让球盘口
  182. * @param {*} groupItemTitle
  183. * @param {*} spreadsLine
  184. * @param {*} outcomesMap
  185. * @param {*} teams
  186. * @param {*} market
  187. * @returns {Object}
  188. */
  189. const parseSpreads = (groupItemTitle, spreadsLine, outcomesMap, teams, market) => {
  190. const { teamHomeName, teamAwayName } = teams;
  191. let spreadDirection = 0;
  192. if (groupItemTitle.includes(teamHomeName)) {
  193. spreadDirection = 1;
  194. }
  195. else if (groupItemTitle.includes(teamAwayName)) {
  196. spreadDirection = -1;
  197. }
  198. const spreads = spreadsLine * spreadDirection;
  199. const key = spreads > 0 ? `+${spreads}` : `${spreads}`;
  200. const spreadsMap = {};
  201. Object.keys(outcomesMap).forEach(key => {
  202. if (key == teamHomeName) {
  203. spreadsMap['Home'] = outcomesMap[key];
  204. }
  205. else if (key == teamAwayName) {
  206. spreadsMap['Away'] = outcomesMap[key];
  207. }
  208. });
  209. return { [key]: { outcomes: spreadsMap, market } };
  210. };
  211. /**
  212. * 解析大小盘口
  213. * @param {*} line
  214. * @param {*} outcomesMap
  215. * @param {*} market
  216. * @returns {Object}
  217. */
  218. const parseTotals = (line, outcomesMap, market) => {
  219. return { [line]: { outcomes: outcomesMap, market } };
  220. };
  221. /**
  222. * 解析市场数据
  223. * @param {*} markets
  224. * @param {*} teams
  225. * @returns {Object}
  226. */
  227. const parseMarketsData = (markets, teams) => {
  228. const marketsData = {};
  229. markets.forEach(market => {
  230. const { sportsMarketType, groupItemTitle, line, outcomes, clobTokenIds, bestBid=0, bestAsk=0 } = market;
  231. const outcomesMap = parseOutcomes(outcomes, clobTokenIds, bestBid, bestAsk);
  232. if (sportsMarketType === "moneyline") {
  233. if (!marketsData.moneyline) {
  234. marketsData.moneyline = {};
  235. }
  236. Object.assign(marketsData.moneyline, parseMoneyline(groupItemTitle, outcomesMap, teams, market))
  237. }
  238. else if (sportsMarketType === "spreads") {
  239. if (!marketsData.spreads) {
  240. marketsData.spreads = {};
  241. }
  242. Object.assign(marketsData.spreads, parseSpreads(groupItemTitle, line, outcomesMap, teams, market))
  243. }
  244. else if (sportsMarketType === "totals") {
  245. if (!marketsData.totals) {
  246. marketsData.totals = {};
  247. }
  248. Object.assign(marketsData.totals, parseTotals(line, outcomesMap, market));
  249. }
  250. });
  251. return marketsData;
  252. }
  253. /**
  254. * 解析赛事数据
  255. * @param {*} event
  256. * @returns {Object}
  257. */
  258. const parseEvent = (event) => {
  259. const { id, title, series, gameId, parentEventId, startTime } = event;
  260. const { teamHomeName, teamAwayName } = parseTeamData(title);
  261. const leagueId = series[0].id;
  262. const leagueName = series[0].title;
  263. const timestamp = new Date(startTime).getTime();
  264. return {
  265. id: +id,
  266. gameId: gameId ? +gameId : undefined,
  267. parentEventId: parentEventId ? +parentEventId : undefined,
  268. leagueId: +leagueId,
  269. teamHomeName, teamAwayName, leagueName,
  270. timestamp,
  271. startTime: getDateInTimezone('+8', timestamp, true),
  272. };
  273. }
  274. /**
  275. * 解析市场列表数据
  276. * @param {*} events
  277. * @returns {Object}
  278. */
  279. export const parseMarkets = (eventsData) => {
  280. const mergedMarketsData = {};
  281. Object.values(eventsData).map(event => {
  282. const item = parseEvent(event);
  283. const { markets } = event;
  284. const { teamHomeName, teamAwayName } = item;
  285. item.marketsData = parseMarketsData(markets, { teamHomeName, teamAwayName });
  286. return item;
  287. }).sort((a, b) => a.id - b.id).forEach(item => {
  288. if (item.id && !item.parentEventId) {
  289. mergedMarketsData[item.id] = cleanUndefined(item);
  290. }
  291. else if (item.parentEventId && mergedMarketsData[item.parentEventId] && item.marketsData) {
  292. const { marketsData: parentEvents } = mergedMarketsData[item.parentEventId];
  293. Object.assign(parentEvents, item.marketsData);
  294. }
  295. });
  296. return mergedMarketsData;
  297. }
  298. // export default parseMarkets;