parseMarkets.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. import getDateInTimezone from "./getDateInTimezone.js";
  2. const MAKER_FEE_RATE = 0.03;
  3. const MAKER_REBATE_RATE = 0.25;
  4. /**
  5. * 精确浮点数字
  6. * @param {number} number
  7. * @param {number} x
  8. * @returns {number}
  9. */
  10. const fixFloat = (number, x=3) => {
  11. return parseFloat(number.toFixed(x));
  12. }
  13. /**
  14. * 清理对象中的undefined值
  15. * @param {*} obj
  16. * @returns {Object}
  17. */
  18. const cleanUndefined = (obj) => {
  19. return Object.fromEntries(
  20. Object.entries(obj).filter(([, v]) => v !== undefined)
  21. );
  22. }
  23. /**
  24. * 解析赛事标题
  25. * @param {*} eventTitle
  26. * @returns {Object}
  27. */
  28. const parseTeamData = (eventTitle) => {
  29. let titleSpliter;
  30. if (eventTitle.includes(' vs. ')) {
  31. titleSpliter = ' vs. ';
  32. }
  33. else if (eventTitle.includes(' vs ')) {
  34. titleSpliter = ' vs ';
  35. }
  36. if (!titleSpliter) {
  37. return {
  38. teamHomeName: '',
  39. teamAwayName: '',
  40. };
  41. }
  42. const teamData = eventTitle.replace(' - More Markets', '').split(titleSpliter);
  43. return {
  44. teamHomeName: teamData[0],
  45. teamAwayName: teamData[1],
  46. };
  47. }
  48. /**
  49. * 解析盘口键值对
  50. * 示例:
  51. * {
  52. * "Yes": 0.525,
  53. * "No": 0.475
  54. * }
  55. * @param {*} outcomes
  56. * @param {*} outcomePrices
  57. * @returns {Object}
  58. */
  59. const parseOutcomes = (outcomes, clobTokenIds, bestBid=0, bestAsk=0) => {
  60. if (!outcomes || !clobTokenIds) {
  61. return {};
  62. }
  63. const keys = JSON.parse(outcomes);
  64. const ids = JSON.parse(clobTokenIds);
  65. return keys.reduce((obj, key, index) => {
  66. let best_ask;
  67. let best_bid;
  68. if (index === 0) {
  69. best_ask = (bestAsk).toFixed(3);
  70. best_bid = (bestBid).toFixed(3);
  71. }
  72. else if (index === 1) {
  73. best_ask = (1 - bestBid).toFixed(3);
  74. best_bid = (1 - bestAsk).toFixed(3);
  75. }
  76. obj[key] = { id: ids[index], best_ask, best_ask_size: 0, best_bid, best_bid_size: 0 };
  77. return obj;
  78. }, {});
  79. }
  80. /**
  81. * 解析让球方向
  82. * 让球方向为正数时,返回'a',否则返回''
  83. * @param {*} ratio
  84. * @returns {string}
  85. */
  86. const ratioAccept = (ratio) => {
  87. if (ratio > 0) {
  88. return 'a';
  89. }
  90. return '';
  91. }
  92. /**
  93. * 解析盘口比率
  94. * 比率取绝对值,并去掉小数点后的数字
  95. * @param {*} ratio
  96. * @returns {string}
  97. */
  98. const ratioString = (ratio) => {
  99. ratio = Math.abs(ratio);
  100. ratio = ratio.toString();
  101. ratio = ratio.replace(/\./, '');
  102. return ratio;
  103. }
  104. /**
  105. * 解析买单价格
  106. * 当卖单最低价格与买单最高价格差值超过最小变动价位时,返回买单最高价格+最小变动价位
  107. * 否则,返回买单最高价格
  108. * @param {*} ask
  109. * @param {*} bid
  110. * @param {*} tickSize
  111. * @returns {number}
  112. */
  113. const parseBidPrice = (ask, bid, tickSize) => {
  114. const askPrice = +ask;
  115. const bidPrice = +bid;
  116. const minTickSize = +tickSize;
  117. const bestBidPrice = fixFloat(askPrice - minTickSize);
  118. if (bestBidPrice > bidPrice) {
  119. return bestBidPrice;
  120. }
  121. return bidPrice;
  122. }
  123. /**
  124. * 解析吃单手续费比
  125. * 固定金额吃卖单时,手续费比约等于 费率*(1-价格)
  126. * @param {*} price
  127. * @returns {number}
  128. */
  129. const parseAskFee = (price) => {
  130. return fixFloat(100 * MAKER_FEE_RATE * (1 - price), 4);
  131. }
  132. /**
  133. * 解析挂单返佣比
  134. * 固定金额挂买单时,返佣比约等于 费率*(1-价格)*返佣比例
  135. * @param {*} price
  136. * @returns {number}
  137. */
  138. const parseBidRebate = (price) => {
  139. return fixFloat(100 * MAKER_FEE_RATE * (1 - price) * MAKER_REBATE_RATE, 4);
  140. }
  141. /**
  142. * 解析盘口数据
  143. * 使用卖单最优价格
  144. * @param {*} markets
  145. * @returns {Object}
  146. */
  147. export const parseOddsAsk = (markets) => {
  148. const odds = {};
  149. Object.keys(markets).forEach(key => {
  150. const marketData = markets[key];
  151. if (key === 'moneyline') {
  152. Object.keys(marketData).forEach(side => {
  153. const askYes = +marketData[side].outcomes['Yes']['best_ask'];
  154. const askSizeYes = +marketData[side].outcomes['Yes']['best_ask_size'];
  155. const maxYes = fixFloat(askYes * askSizeYes);
  156. // const tokenYes = marketData[side].outcomes['Yes']['id'];
  157. const askNo = +marketData[side].outcomes['No']['best_ask'];
  158. const askSizeNo = +marketData[side].outcomes['No']['best_ask_size'];
  159. const maxNo = fixFloat(askNo * askSizeNo);
  160. // const tokenNo = marketData[side].outcomes['No']['id'];
  161. // const slug = marketData[side].market.slug;
  162. if (askYes <= 0.1 || askNo <= 0.1) {
  163. return;
  164. }
  165. const iorYes = fixFloat(1 / askYes);
  166. const iorNo = fixFloat(1 / askNo);
  167. const feeYes = parseAskFee(askYes);
  168. const feeNo = parseAskFee(askNo);
  169. let iorKeyYes = '';
  170. let iorKeyNo = '';
  171. switch (side) {
  172. case 'Home':
  173. iorKeyYes = 'ior_mh';
  174. iorKeyNo = 'ior_moh';
  175. break;
  176. case 'Draw':
  177. iorKeyYes = 'ior_mn';
  178. iorKeyNo = 'ior_mon';
  179. break;
  180. case 'Away':
  181. iorKeyYes = 'ior_mc';
  182. iorKeyNo = 'ior_moc';
  183. break;
  184. }
  185. odds[iorKeyYes] = { v: iorYes, b: -feeYes, t: 1, ask: askYes, ask_size: askSizeYes, max: maxYes, /*token: tokenYes, slug */ };
  186. odds[iorKeyNo] = { v: iorNo, b: -feeNo, t: 1, ask: askNo, ask_size: askSizeNo, max: maxNo, /*token: tokenNo, slug */ };
  187. });
  188. }
  189. else if (key === 'spreads') {
  190. Object.keys(marketData).forEach(handicap => {
  191. const ratio = +handicap;
  192. const askHome = +marketData[handicap].outcomes['Home']['best_ask'];
  193. const askSizeHome = +marketData[handicap].outcomes['Home']['best_ask_size'];
  194. const maxHome = fixFloat(askHome * askSizeHome);
  195. // const tokenHome = marketData[handicap].outcomes['Home']['id'];
  196. const askAway = +marketData[handicap].outcomes['Away']['best_ask'];
  197. const askSizeAway = +marketData[handicap].outcomes['Away']['best_ask_size'];
  198. const maxAway = fixFloat(askAway * askSizeAway);
  199. // const tokenAway = marketData[handicap].outcomes['Away']['id'];
  200. // const slug = marketData[handicap].market.slug;
  201. if (askHome <= 0.1 || askAway <= 0.1) {
  202. return;
  203. }
  204. const iorHome = fixFloat(1 / askHome);
  205. const iorAway = fixFloat(1 / askAway);
  206. const feeHome = parseAskFee(askHome);
  207. const feeAway = parseAskFee(askAway);
  208. odds[`ior_r${ratioAccept(ratio)}h_${ratioString(ratio)}`] = { v: iorHome, b: -feeHome, t: 1, ask: askHome, ask_size: askSizeHome, max: maxHome, /*token: tokenHome, slug */ };
  209. odds[`ior_r${ratioAccept(-ratio)}c_${ratioString(ratio)}`] = { v: iorAway, b: -feeAway, t: 1, ask: askAway, ask_size: askSizeAway, max: maxAway, /*token: tokenAway, slug */ };
  210. });
  211. }
  212. else if (key === 'totals') {
  213. Object.keys(marketData).forEach(handicap => {
  214. const ratio = +handicap;
  215. const askOver = +marketData[handicap].outcomes['Over']['best_ask'];
  216. const askSizeOver = +marketData[handicap].outcomes['Over']['best_ask_size'];
  217. const maxOver = fixFloat(askOver * askSizeOver);
  218. // const tokenOver = marketData[handicap].outcomes['Over']['id'];
  219. const askUnder = +marketData[handicap].outcomes['Under']['best_ask'];
  220. const askSizeUnder = +marketData[handicap].outcomes['Under']['best_ask_size'];
  221. const maxUnder = fixFloat(askUnder * askSizeUnder);
  222. // const tokenUnder = marketData[handicap].outcomes['Under']['id'];
  223. const slug = marketData[handicap].market.slug;
  224. if (askOver <= 0.1 || askUnder <= 0.1) {
  225. return;
  226. }
  227. const iorOver = fixFloat(1 / askOver);
  228. const iorUnder = fixFloat(1 / askUnder);
  229. const feeOver = parseAskFee(askOver);
  230. const feeUnder = parseAskFee(askUnder);
  231. odds[`ior_ouc_${ratioString(ratio)}`] = { v: iorOver, b: -feeOver, t: 1, ask: askOver, ask_size: askSizeOver, max: maxOver, /*token: tokenOver, slug */ };
  232. odds[`ior_ouh_${ratioString(ratio)}`] = { v: iorUnder, b: -feeUnder, t: 1, ask: askUnder, ask_size: askSizeUnder, max: maxUnder, /*token: tokenUnder, slug */ };
  233. });
  234. }
  235. });
  236. return odds;
  237. }
  238. /**
  239. * 解析盘口数据
  240. * 当卖单最低价格与买单最高价格差值超过最小变动价位时
  241. * 创建新的买单价格,即买单最高价格+最小变动价位
  242. * 否则,使用买单最高价格
  243. * @param {*} markets
  244. * @returns {Object}
  245. */
  246. export const parseOddsBid = (markets) => {
  247. const odds = {};
  248. Object.keys(markets).forEach(key => {
  249. const marketData = markets[key];
  250. if (key === 'moneyline') {
  251. Object.keys(marketData).forEach(side => {
  252. const askYes = +marketData[side].outcomes['Yes']['best_ask'];
  253. const bidYes = +marketData[side].outcomes['Yes']['best_bid'];
  254. const bidSizeYes = +marketData[side].outcomes['Yes']['best_bid_size'];
  255. // const tokenYes = marketData[side].outcomes['Yes']['id'];
  256. const askNo = +marketData[side].outcomes['No']['best_ask'];
  257. const bidNo = +marketData[side].outcomes['No']['best_bid'];
  258. const bidSizeNo = +marketData[side].outcomes['No']['best_bid_size'];
  259. // const tokenNo = marketData[side].outcomes['No']['id'];
  260. // const slug = marketData[side].market.slug;
  261. const tick_size = marketData[side].market.orderPriceMinTickSize;
  262. if (askYes <= 0.1 || askNo <= 0.1) {
  263. return;
  264. }
  265. const bidPriceYes = parseBidPrice(askYes, bidYes, tick_size);
  266. const bidPriceNo = parseBidPrice(askNo, bidNo, tick_size);
  267. const iorYes = fixFloat(1 / bidPriceYes);
  268. const iorNo = fixFloat(1 / bidPriceNo);
  269. const rebateYes = parseBidRebate(bidPriceYes);
  270. const rebateNo = parseBidRebate(bidPriceNo);
  271. let iorKeyYes = '';
  272. let iorKeyNo = '';
  273. switch (side) {
  274. case 'Home':
  275. iorKeyYes = 'ior_mh';
  276. iorKeyNo = 'ior_moh';
  277. break;
  278. case 'Draw':
  279. iorKeyYes = 'ior_mn';
  280. iorKeyNo = 'ior_mon';
  281. break;
  282. case 'Away':
  283. iorKeyYes = 'ior_mc';
  284. iorKeyNo = 'ior_moc';
  285. break;
  286. }
  287. odds[iorKeyYes] = { v: iorYes, b: rebateYes, t: 1, ask: askYes, bid: bidYes, bid_size: bidSizeYes, bid_ex: bidPriceYes, tick_size, /*token: tokenYes, slug */ };
  288. odds[iorKeyNo] = { v: iorNo, b: rebateNo, t: 1, ask: askNo, bid: bidNo, bid_size: bidSizeNo, bid_ex: bidPriceNo, tick_size, /*token: tokenNo, slug */ };
  289. });
  290. }
  291. else if (key === 'spreads') {
  292. Object.keys(marketData).forEach(handicap => {
  293. const ratio = +handicap;
  294. const askHome = +marketData[handicap].outcomes['Home']['best_ask'];
  295. const bidHome = +marketData[handicap].outcomes['Home']['best_bid'];
  296. const bidSizeHome = +marketData[handicap].outcomes['Home']['best_bid_size'];
  297. // const tokenHome = marketData[handicap].outcomes['Home']['id'];
  298. const askAway = +marketData[handicap].outcomes['Away']['best_ask'];
  299. const bidAway = +marketData[handicap].outcomes['Away']['best_bid'];
  300. const bidSizeAway = +marketData[handicap].outcomes['Away']['best_bid_size'];
  301. // const tokenAway = marketData[handicap].outcomes['Away']['id'];
  302. // const slug = marketData[handicap].market.slug;
  303. const tick_size = marketData[handicap].market.orderPriceMinTickSize;
  304. if (askHome <= 0.1 || askAway <= 0.1) {
  305. return;
  306. }
  307. const bidPriceHome = parseBidPrice(askHome, bidHome, tick_size);
  308. const bidPriceAway = parseBidPrice(askAway, bidAway, tick_size);
  309. const iorHome = fixFloat(1 / bidPriceHome);
  310. const iorAway = fixFloat(1 / bidPriceAway);
  311. const rebateHome = parseBidRebate(bidPriceHome);
  312. const rebateAway = parseBidRebate(bidPriceAway);
  313. odds[`ior_r${ratioAccept(ratio)}h_${ratioString(ratio)}`] = { v: iorHome, b: rebateHome, t: 1, ask: askHome, bid: bidHome, bid_size: bidSizeHome, bid_ex: bidPriceHome, tick_size, /*token: tokenHome, slug */ };
  314. odds[`ior_r${ratioAccept(-ratio)}c_${ratioString(ratio)}`] = { v: iorAway, b: rebateAway, t: 1, ask: askAway, bid: bidAway, bid_size: bidSizeAway, bid_ex: bidPriceAway, tick_size, /*token: tokenAway, slug */ };
  315. });
  316. }
  317. else if (key === 'totals') {
  318. Object.keys(marketData).forEach(handicap => {
  319. const ratio = +handicap;
  320. const askOver = +marketData[handicap].outcomes['Over']['best_ask'];
  321. const bidOver = +marketData[handicap].outcomes['Over']['best_bid'];
  322. const bidSizeOver = +marketData[handicap].outcomes['Over']['best_bid_size'];
  323. // const tokenOver = marketData[handicap].outcomes['Over']['id'];
  324. const askUnder = +marketData[handicap].outcomes['Under']['best_ask'];
  325. const bidUnder = +marketData[handicap].outcomes['Under']['best_bid'];
  326. const bidSizeUnder = +marketData[handicap].outcomes['Under']['best_bid_size'];
  327. // const tokenUnder = marketData[handicap].outcomes['Under']['id'];
  328. // const slug = marketData[handicap].market.slug;
  329. const tick_size = marketData[handicap].market.orderPriceMinTickSize;
  330. if (askOver <= 0.1 || askUnder <= 0.1) {
  331. return;
  332. }
  333. const bidPriceOver = parseBidPrice(askOver, bidOver, tick_size);
  334. const bidPriceUnder = parseBidPrice(askUnder, bidUnder, tick_size);
  335. const iorOver = fixFloat(1 / bidPriceOver);
  336. const iorUnder = fixFloat(1 / bidPriceUnder);
  337. const rebateOver = parseBidRebate(bidPriceOver);
  338. const rebateUnder = parseBidRebate(bidPriceUnder);
  339. odds[`ior_ouc_${ratioString(ratio)}`] = { v: iorOver, b: rebateOver, t: 1, ask: askOver, bid: bidOver, bid_size: bidSizeOver, bid_ex: bidPriceOver, tick_size, /*token: tokenOver, slug */ };
  340. odds[`ior_ouh_${ratioString(ratio)}`] = { v: iorUnder, b: rebateUnder, t: 1, ask: askUnder, bid: bidUnder, bid_size: bidSizeUnder, bid_ex: bidPriceUnder, tick_size, /*token: tokenUnder, slug */ };
  341. });
  342. }
  343. });
  344. return odds;
  345. }
  346. /**
  347. * 解析胜平负盘口
  348. * @param {*} groupItemThreshold
  349. * @param {*} outcomesMap
  350. * @param {*} market
  351. * @returns {Object}
  352. */
  353. const parseMoneyline = (groupItemTitle, outcomesMap, teams, market) => {
  354. const { teamHomeName, teamAwayName } = teams;
  355. let key = '';
  356. if (groupItemTitle == teamHomeName) {
  357. key = 'Home';
  358. }
  359. else if (groupItemTitle == teamAwayName) {
  360. key = 'Away';
  361. }
  362. else if (groupItemTitle.startsWith('Draw')) {
  363. key = 'Draw';
  364. }
  365. if (!key) {
  366. return {};
  367. }
  368. return { [key]: { outcomes: outcomesMap, market } };
  369. };
  370. /**
  371. * 解析让球盘口
  372. * @param {*} groupItemTitle
  373. * @param {*} spreadsLine
  374. * @param {*} outcomesMap
  375. * @param {*} teams
  376. * @param {*} market
  377. * @returns {Object}
  378. */
  379. const parseSpreads = (groupItemTitle, spreadsLine, outcomesMap, teams, market) => {
  380. const { teamHomeName, teamAwayName } = teams;
  381. let spreadDirection = 0;
  382. if (groupItemTitle.includes(teamHomeName)) {
  383. spreadDirection = 1;
  384. }
  385. else if (groupItemTitle.includes(teamAwayName)) {
  386. spreadDirection = -1;
  387. }
  388. const spreads = spreadsLine * spreadDirection;
  389. const key = spreads > 0 ? `+${spreads}` : `${spreads}`;
  390. const spreadsMap = {};
  391. Object.keys(outcomesMap).forEach(key => {
  392. if (key == teamHomeName) {
  393. spreadsMap['Home'] = outcomesMap[key];
  394. }
  395. else if (key == teamAwayName) {
  396. spreadsMap['Away'] = outcomesMap[key];
  397. }
  398. });
  399. return { [key]: { outcomes: spreadsMap, market } };
  400. };
  401. /**
  402. * 解析大小盘口
  403. * @param {*} line
  404. * @param {*} outcomesMap
  405. * @param {*} market
  406. * @returns {Object}
  407. */
  408. const parseTotals = (line, outcomesMap, market) => {
  409. return { [line]: { outcomes: outcomesMap, market } };
  410. };
  411. /**
  412. * 解析市场数据
  413. * @param {*} markets
  414. * @param {*} teams
  415. * @returns {Object}
  416. */
  417. const parseMarketsData = (markets, teams) => {
  418. const marketsData = {};
  419. markets.forEach(market => {
  420. const { sportsMarketType, groupItemTitle, line, outcomes, clobTokenIds, bestBid=0, bestAsk=0 } = market;
  421. const outcomesMap = parseOutcomes(outcomes, clobTokenIds, bestBid, bestAsk);
  422. if (sportsMarketType === "moneyline") {
  423. if (!marketsData.moneyline) {
  424. marketsData.moneyline = {};
  425. }
  426. Object.assign(marketsData.moneyline, parseMoneyline(groupItemTitle, outcomesMap, teams, market))
  427. }
  428. else if (sportsMarketType === "spreads") {
  429. if (!marketsData.spreads) {
  430. marketsData.spreads = {};
  431. }
  432. Object.assign(marketsData.spreads, parseSpreads(groupItemTitle, line, outcomesMap, teams, market))
  433. }
  434. else if (sportsMarketType === "totals") {
  435. if (!marketsData.totals) {
  436. marketsData.totals = {};
  437. }
  438. Object.assign(marketsData.totals, parseTotals(line, outcomesMap, market));
  439. }
  440. });
  441. return marketsData;
  442. }
  443. /**
  444. * 解析赛事数据
  445. * @param {*} event
  446. * @returns {Object}
  447. */
  448. const parseEvent = (event) => {
  449. const { id, slug, title, series, gameId, parentEventId, startTime, sport: { sport } = {}} = event;
  450. const { teamHomeName, teamAwayName } = parseTeamData(title);
  451. const leagueId = series[0].id;
  452. const leagueName = series[0].title;
  453. const timestamp = new Date(startTime).getTime();
  454. return {
  455. id: +id,
  456. sport, slug,
  457. gameId: gameId ? +gameId : undefined,
  458. parentEventId: parentEventId ? +parentEventId : undefined,
  459. leagueId: +leagueId,
  460. teamHomeName, teamAwayName, leagueName,
  461. timestamp,
  462. startTime: getDateInTimezone('+8', timestamp, true),
  463. };
  464. }
  465. /**
  466. * 解析市场列表数据
  467. * @param {*} events
  468. * @returns {Object}
  469. */
  470. export const parseMarkets = (eventsData) => {
  471. const mergedMarketsData = {};
  472. Object.values(eventsData).map(event => {
  473. const item = parseEvent(event);
  474. const { markets } = event;
  475. const { teamHomeName, teamAwayName } = item;
  476. const marketsData = parseMarketsData(markets, { teamHomeName, teamAwayName });
  477. return { ...item, marketsData };
  478. }).sort((a, b) => a.id - b.id).forEach(item => {
  479. if (item.id && !item.parentEventId) {
  480. mergedMarketsData[item.id] = cleanUndefined(item);
  481. }
  482. else if (item.parentEventId && mergedMarketsData[item.parentEventId] && item.marketsData) {
  483. const { marketsData: parentEvents } = mergedMarketsData[item.parentEventId];
  484. Object.assign(parentEvents, item.marketsData);
  485. }
  486. });
  487. return mergedMarketsData;
  488. }
  489. /*
  490. * 解析比率信息
  491. */
  492. const parseRatio = (ratioString) => {
  493. if (!ratioString) {
  494. return null;
  495. }
  496. return parseFloat(`${ratioString[0]}.${ratioString.slice(1)}`);
  497. }
  498. /**
  499. * 解析盘口信息
  500. * @param {*} ior
  501. * @returns
  502. */
  503. const parseIor = (ior) => {
  504. const iorMatch = ior.match(/ior_(m|r|ou|wm|ot)([ao])?([hcn])?_?(\d+)?/);
  505. if (!iorMatch) {
  506. return null;
  507. }
  508. const [, type, action, side, ratio] = iorMatch;
  509. return { type, action, side, ratio };
  510. }
  511. /**
  512. * 解析盘口详情
  513. * @param {*} ior
  514. * @param {*} id
  515. * @param {*} marketsMap
  516. * @returns
  517. */
  518. export const parseIorDetail = (ior, id, marketsMap) => {
  519. const marketsData = marketsMap[id]?.marketsData;
  520. if (!marketsData) {
  521. return { ior, id, message: 'markets data not found', cause: 400 };
  522. }
  523. const iorOptions = parseIor(ior);
  524. if (!iorOptions) {
  525. return { ior, id, message: 'ior options not found', cause: 400 };
  526. }
  527. const { type, action, side, ratio } = iorOptions;
  528. let marketTypeData, outcomesSide;
  529. if (type === 'm' && !ratio) {
  530. const sideKey = side === 'h' ? 'Home' : side === 'c' ? 'Away' : 'Draw';
  531. const sideAction = action === 'o' ? 'No' : 'Yes';
  532. marketTypeData = marketsData.moneyline[sideKey];
  533. outcomesSide = sideAction;
  534. }
  535. else if (type === 'r') {
  536. const sideKey = side === 'h' ? 'Home' : side === 'c' ? 'Away' : '';
  537. let ratioDirection = 1;
  538. if (side === 'c' && action === 'a' || side === 'h' && !action) {
  539. ratioDirection = -1;
  540. }
  541. const ratioValue = parseRatio(ratio) * ratioDirection;
  542. const ratioKey = ratioValue > 0 ? `+${ratioValue}` : `${ratioValue}`;
  543. marketTypeData = marketsData.spreads?.[ratioKey];
  544. outcomesSide = sideKey;
  545. }
  546. else if (type === 'ou') {
  547. const sideKey = side === 'c' ? 'Over' : side === 'h' ? 'Under' : '';
  548. const ratioKey = parseRatio(ratio);
  549. marketTypeData = marketsData.totals[ratioKey];
  550. outcomesSide = sideKey;
  551. }
  552. const result = marketTypeData?.outcomes?.[outcomesSide];
  553. if (!result) {
  554. Logs.outDev('polymarket market type data not found', { ior, id, type, action, side, ratio, marketTypeData, outcomesSide });
  555. return { ior, id, message: 'market type data not found', cause: 400 };
  556. }
  557. return result;
  558. }