parseMarkets.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  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. if (!outcomes || !clobTokenIds) {
  59. return {};
  60. }
  61. const keys = JSON.parse(outcomes);
  62. const ids = JSON.parse(clobTokenIds);
  63. return keys.reduce((obj, key, index) => {
  64. let best_ask;
  65. let best_bid;
  66. if (index === 0) {
  67. best_ask = (bestAsk).toFixed(3);
  68. best_bid = (bestBid).toFixed(3);
  69. }
  70. else if (index === 1) {
  71. best_ask = (1 - bestBid).toFixed(3);
  72. best_bid = (1 - bestAsk).toFixed(3);
  73. }
  74. obj[key] = { id: ids[index], best_ask, best_bid };
  75. return obj;
  76. }, {});
  77. }
  78. /**
  79. * 解析盘口数据
  80. * @param {*} markets
  81. * @returns {Object}
  82. */
  83. const ratioAccept = (ratio) => {
  84. if (ratio > 0) {
  85. return 'a';
  86. }
  87. return '';
  88. }
  89. const ratioString = (ratio) => {
  90. ratio = Math.abs(ratio);
  91. ratio = ratio.toString();
  92. ratio = ratio.replace(/\./, '');
  93. return ratio;
  94. }
  95. export const parseOdds = (markets) => {
  96. const odds = {};
  97. Object.keys(markets).forEach(key => {
  98. const marketData = markets[key];
  99. if (key === 'moneyline') {
  100. Object.keys(marketData).forEach(side => {
  101. const askYes = marketData[side].outcomes['Yes']['best_ask'];
  102. const tokenYes = marketData[side].outcomes['Yes']['id'];
  103. const askNo = marketData[side].outcomes['No']['best_ask'];
  104. const tokenNo = marketData[side].outcomes['No']['id'];
  105. const slug = marketData[side].market.slug;
  106. if (askYes <= 0.1 || askNo <= 0.1) {
  107. return;
  108. }
  109. const iorYes = fixFloat(1 / askYes);
  110. const iorNo = fixFloat(1 / askNo);
  111. let iorKeyYes = '';
  112. let iorKeyNo = '';
  113. switch (side) {
  114. case 'Home':
  115. iorKeyYes = 'ior_mh';
  116. iorKeyNo = 'ior_moh';
  117. break;
  118. case 'Draw':
  119. iorKeyYes = 'ior_mn';
  120. iorKeyNo = 'ior_mon';
  121. break;
  122. case 'Away':
  123. iorKeyYes = 'ior_mc';
  124. iorKeyNo = 'ior_moc';
  125. break;
  126. }
  127. odds[iorKeyYes] = { v: iorYes, ask: askYes, token: tokenYes, slug };
  128. odds[iorKeyNo] = { v: iorNo, ask: askNo, token: tokenNo, slug };
  129. });
  130. }
  131. else if (key === 'spreads') {
  132. Object.keys(marketData).forEach(handicap => {
  133. const ratio = +handicap;
  134. const askHome = marketData[handicap].outcomes['Home']['best_ask'];
  135. const tokenHome = marketData[handicap].outcomes['Home']['id'];
  136. const askAway = marketData[handicap].outcomes['Away']['best_ask'];
  137. const tokenAway = marketData[handicap].outcomes['Away']['id'];
  138. const slug = marketData[handicap].market.slug;
  139. if (askHome <= 0.1 || askAway <= 0.1) {
  140. return;
  141. }
  142. const iorHome = fixFloat(1 / askHome);
  143. const iorAway = fixFloat(1 / askAway);
  144. odds[`ior_r${ratioAccept(ratio)}h_${ratioString(ratio)}`] = { v: iorHome, ask: askHome, token: tokenHome, slug };
  145. odds[`ior_r${ratioAccept(-ratio)}c_${ratioString(ratio)}`] = { v: iorAway, ask: askAway, token: tokenAway, slug };
  146. });
  147. }
  148. else if (key === 'totals') {
  149. Object.keys(marketData).forEach(handicap => {
  150. const ratio = +handicap;
  151. const askOver = marketData[handicap].outcomes['Over']['best_ask'];
  152. const tokenOver = marketData[handicap].outcomes['Over']['id'];
  153. const askUnder = marketData[handicap].outcomes['Under']['best_ask'];
  154. const tokenUnder = marketData[handicap].outcomes['Under']['id'];
  155. const slug = marketData[handicap].market.slug;
  156. if (askOver <= 0.1 || askUnder <= 0.1) {
  157. return;
  158. }
  159. const iorOver = fixFloat(1 / askOver);
  160. const iorUnder = fixFloat(1 / askUnder);
  161. odds[`ior_ouc_${ratioString(ratio)}`] = { v: iorOver, ask: askOver, token: tokenOver, slug };
  162. odds[`ior_ouh_${ratioString(ratio)}`] = { v: iorUnder, ask: askUnder, token: tokenUnder, slug };
  163. });
  164. }
  165. });
  166. return odds;
  167. }
  168. /**
  169. * 解析胜平负盘口
  170. * @param {*} groupItemThreshold
  171. * @param {*} outcomesMap
  172. * @param {*} market
  173. * @returns {Object}
  174. */
  175. const parseMoneyline = (groupItemTitle, outcomesMap, teams, market) => {
  176. const { teamHomeName, teamAwayName } = teams;
  177. let key = '';
  178. if (groupItemTitle == teamHomeName) {
  179. key = 'Home';
  180. }
  181. else if (groupItemTitle == teamAwayName) {
  182. key = 'Away';
  183. }
  184. else if (groupItemTitle.startsWith('Draw')) {
  185. key = 'Draw';
  186. }
  187. if (!key) {
  188. return {};
  189. }
  190. return { [key]: { outcomes: outcomesMap, market } };
  191. };
  192. /**
  193. * 解析让球盘口
  194. * @param {*} groupItemTitle
  195. * @param {*} spreadsLine
  196. * @param {*} outcomesMap
  197. * @param {*} teams
  198. * @param {*} market
  199. * @returns {Object}
  200. */
  201. const parseSpreads = (groupItemTitle, spreadsLine, outcomesMap, teams, market) => {
  202. const { teamHomeName, teamAwayName } = teams;
  203. let spreadDirection = 0;
  204. if (groupItemTitle.includes(teamHomeName)) {
  205. spreadDirection = 1;
  206. }
  207. else if (groupItemTitle.includes(teamAwayName)) {
  208. spreadDirection = -1;
  209. }
  210. const spreads = spreadsLine * spreadDirection;
  211. const key = spreads > 0 ? `+${spreads}` : `${spreads}`;
  212. const spreadsMap = {};
  213. Object.keys(outcomesMap).forEach(key => {
  214. if (key == teamHomeName) {
  215. spreadsMap['Home'] = outcomesMap[key];
  216. }
  217. else if (key == teamAwayName) {
  218. spreadsMap['Away'] = outcomesMap[key];
  219. }
  220. });
  221. return { [key]: { outcomes: spreadsMap, market } };
  222. };
  223. /**
  224. * 解析大小盘口
  225. * @param {*} line
  226. * @param {*} outcomesMap
  227. * @param {*} market
  228. * @returns {Object}
  229. */
  230. const parseTotals = (line, outcomesMap, market) => {
  231. return { [line]: { outcomes: outcomesMap, market } };
  232. };
  233. /**
  234. * 解析市场数据
  235. * @param {*} markets
  236. * @param {*} teams
  237. * @returns {Object}
  238. */
  239. const parseMarketsData = (markets, teams) => {
  240. const marketsData = {};
  241. markets.forEach(market => {
  242. const { sportsMarketType, groupItemTitle, line, outcomes, clobTokenIds, bestBid=0, bestAsk=0 } = market;
  243. const outcomesMap = parseOutcomes(outcomes, clobTokenIds, bestBid, bestAsk);
  244. if (sportsMarketType === "moneyline") {
  245. if (!marketsData.moneyline) {
  246. marketsData.moneyline = {};
  247. }
  248. Object.assign(marketsData.moneyline, parseMoneyline(groupItemTitle, outcomesMap, teams, market))
  249. }
  250. else if (sportsMarketType === "spreads") {
  251. if (!marketsData.spreads) {
  252. marketsData.spreads = {};
  253. }
  254. Object.assign(marketsData.spreads, parseSpreads(groupItemTitle, line, outcomesMap, teams, market))
  255. }
  256. else if (sportsMarketType === "totals") {
  257. if (!marketsData.totals) {
  258. marketsData.totals = {};
  259. }
  260. Object.assign(marketsData.totals, parseTotals(line, outcomesMap, market));
  261. }
  262. });
  263. return marketsData;
  264. }
  265. /**
  266. * 解析赛事数据
  267. * @param {*} event
  268. * @returns {Object}
  269. */
  270. const parseEvent = (event) => {
  271. const { id, title, series, gameId, parentEventId, startTime } = event;
  272. const { teamHomeName, teamAwayName } = parseTeamData(title);
  273. const leagueId = series[0].id;
  274. const leagueName = series[0].title;
  275. const timestamp = new Date(startTime).getTime();
  276. return {
  277. id: +id,
  278. gameId: gameId ? +gameId : undefined,
  279. parentEventId: parentEventId ? +parentEventId : undefined,
  280. leagueId: +leagueId,
  281. teamHomeName, teamAwayName, leagueName,
  282. timestamp,
  283. startTime: getDateInTimezone('+8', timestamp, true),
  284. };
  285. }
  286. /**
  287. * 解析市场列表数据
  288. * @param {*} events
  289. * @returns {Object}
  290. */
  291. export const parseMarkets = (eventsData) => {
  292. const mergedMarketsData = {};
  293. Object.values(eventsData).map(event => {
  294. const item = parseEvent(event);
  295. const { markets } = event;
  296. const { teamHomeName, teamAwayName } = item;
  297. const marketsData = parseMarketsData(markets, { teamHomeName, teamAwayName });
  298. return { ...item, marketsData };
  299. }).sort((a, b) => a.id - b.id).forEach(item => {
  300. if (item.id && !item.parentEventId) {
  301. mergedMarketsData[item.id] = cleanUndefined(item);
  302. }
  303. else if (item.parentEventId && mergedMarketsData[item.parentEventId] && item.marketsData) {
  304. const { marketsData: parentEvents } = mergedMarketsData[item.parentEventId];
  305. Object.assign(parentEvents, item.marketsData);
  306. }
  307. });
  308. return mergedMarketsData;
  309. }
  310. // export default parseMarkets;