import getDateInTimezone from "./getDateInTimezone.js"; /** * 精确浮点数字 * @param {number} number * @param {number} x * @returns {number} */ const fixFloat = (number, x=3) => { return parseFloat(number.toFixed(x)); } /** * 清理对象中的undefined值 * @param {*} obj * @returns {Object} */ const cleanUndefined = (obj) => { return Object.fromEntries( Object.entries(obj).filter(([, v]) => v !== undefined) ); } /** * 解析赛事标题 * @param {*} eventTitle * @returns {Object} */ const parseTeamData = (eventTitle) => { let titleSpliter; if (eventTitle.includes(' vs. ')) { titleSpliter = ' vs. '; } else if (eventTitle.includes(' vs ')) { titleSpliter = ' vs '; } if (!titleSpliter) { return { teamHomeName: '', teamAwayName: '', }; } const teamData = eventTitle.replace(' - More Markets', '').split(titleSpliter); return { teamHomeName: teamData[0], teamAwayName: teamData[1], }; } /** * 解析盘口键值对 * 示例: * { * "Yes": 0.525, * "No": 0.475 * } * @param {*} outcomes * @param {*} outcomePrices * @returns {Object} */ const parseOutcomes = (outcomes, clobTokenIds, bestBid=0, bestAsk=0) => { const keys = JSON.parse(outcomes); const ids = JSON.parse(clobTokenIds); return keys.reduce((obj, key, index) => { let best_ask; let best_bid; if (index === 0) { best_ask = (bestAsk).toFixed(3); best_bid = (bestBid).toFixed(3); } else if (index === 1) { best_ask = (1 - bestBid).toFixed(3); best_bid = (1 - bestAsk).toFixed(3); } obj[key] = { id: ids[index], best_ask, best_bid }; return obj; }, {}); } /** * 解析盘口数据 * @param {*} markets * @returns {Object} */ const ratioAccept = (ratio) => { if (ratio > 0) { return 'a'; } return ''; } const ratioString = (ratio) => { ratio = Math.abs(ratio); ratio = ratio.toString(); ratio = ratio.replace(/\./, ''); return ratio; } export const parseOdds = (markets) => { const odds = {}; Object.keys(markets).forEach(key => { const marketData = markets[key]; if (key === 'moneyline') { Object.keys(marketData).forEach(side => { const askYes = marketData[side].outcomes['Yes']['best_ask']; const tokenYes = marketData[side].outcomes['Yes']['id']; const askNo = marketData[side].outcomes['No']['best_ask']; const tokenNo = marketData[side].outcomes['No']['id']; const slug = marketData[side].market.slug; if (askYes <= 0.1 || askNo <= 0.1) { return; } const iorYes = fixFloat(1 / askYes); const iorNo = fixFloat(1 / askNo); let iorKeyYes = ''; let iorKeyNo = ''; switch (side) { case 'Home': iorKeyYes = 'ior_mh'; iorKeyNo = 'ior_moh'; break; case 'Draw': iorKeyYes = 'ior_mn'; iorKeyNo = 'ior_mon'; break; case 'Away': iorKeyYes = 'ior_mc'; iorKeyNo = 'ior_moc'; break; } odds[iorKeyYes] = { v: iorYes, ask: askYes, token: tokenYes, slug }; odds[iorKeyNo] = { v: iorNo, ask: askNo, token: tokenNo, slug }; }); } else if (key === 'spreads') { Object.keys(marketData).forEach(handicap => { const ratio = +handicap; const askHome = marketData[handicap].outcomes['Home']['best_ask']; const tokenHome = marketData[handicap].outcomes['Home']['id']; const askAway = marketData[handicap].outcomes['Away']['best_ask']; const tokenAway = marketData[handicap].outcomes['Away']['id']; const slug = marketData[handicap].market.slug; if (askHome <= 0.1 || askAway <= 0.1) { return; } const iorHome = fixFloat(1 / askHome); const iorAway = fixFloat(1 / askAway); odds[`ior_r${ratioAccept(ratio)}h_${ratioString(ratio)}`] = { v: iorHome, ask: askHome, token: tokenHome, slug }; odds[`ior_r${ratioAccept(-ratio)}c_${ratioString(ratio)}`] = { v: iorAway, ask: askAway, token: tokenAway, slug }; }); } else if (key === 'totals') { Object.keys(marketData).forEach(handicap => { const ratio = +handicap; const askOver = marketData[handicap].outcomes['Over']['best_ask']; const tokenOver = marketData[handicap].outcomes['Over']['id']; const askUnder = marketData[handicap].outcomes['Under']['best_ask']; const tokenUnder = marketData[handicap].outcomes['Under']['id']; const slug = marketData[handicap].market.slug; if (askOver <= 0.1 || askUnder <= 0.1) { return; } const iorOver = fixFloat(1 / askOver); const iorUnder = fixFloat(1 / askUnder); odds[`ior_ouc_${ratioString(ratio)}`] = { v: iorOver, ask: askOver, token: tokenOver, slug }; odds[`ior_ouh_${ratioString(ratio)}`] = { v: iorUnder, ask: askUnder, token: tokenUnder, slug }; }); } }); return odds; } /** * 解析胜平负盘口 * @param {*} groupItemThreshold * @param {*} outcomesMap * @param {*} market * @returns {Object} */ const parseMoneyline = (groupItemTitle, outcomesMap, teams, market) => { const { teamHomeName, teamAwayName } = teams; let key = ''; if (groupItemTitle == teamHomeName) { key = 'Home'; } else if (groupItemTitle == teamAwayName) { key = 'Away'; } else if (groupItemTitle.startsWith('Draw')) { key = 'Draw'; } if (!key) { return {}; } return { [key]: { outcomes: outcomesMap, market } }; }; /** * 解析让球盘口 * @param {*} groupItemTitle * @param {*} spreadsLine * @param {*} outcomesMap * @param {*} teams * @param {*} market * @returns {Object} */ const parseSpreads = (groupItemTitle, spreadsLine, outcomesMap, teams, market) => { const { teamHomeName, teamAwayName } = teams; let spreadDirection = 0; if (groupItemTitle.includes(teamHomeName)) { spreadDirection = 1; } else if (groupItemTitle.includes(teamAwayName)) { spreadDirection = -1; } const spreads = spreadsLine * spreadDirection; const key = spreads > 0 ? `+${spreads}` : `${spreads}`; const spreadsMap = {}; Object.keys(outcomesMap).forEach(key => { if (key == teamHomeName) { spreadsMap['Home'] = outcomesMap[key]; } else if (key == teamAwayName) { spreadsMap['Away'] = outcomesMap[key]; } }); return { [key]: { outcomes: spreadsMap, market } }; }; /** * 解析大小盘口 * @param {*} line * @param {*} outcomesMap * @param {*} market * @returns {Object} */ const parseTotals = (line, outcomesMap, market) => { return { [line]: { outcomes: outcomesMap, market } }; }; /** * 解析市场数据 * @param {*} markets * @param {*} teams * @returns {Object} */ const parseMarketsData = (markets, teams) => { const marketsData = {}; markets.forEach(market => { const { sportsMarketType, groupItemTitle, line, outcomes, clobTokenIds, bestBid=0, bestAsk=0 } = market; const outcomesMap = parseOutcomes(outcomes, clobTokenIds, bestBid, bestAsk); if (sportsMarketType === "moneyline") { if (!marketsData.moneyline) { marketsData.moneyline = {}; } Object.assign(marketsData.moneyline, parseMoneyline(groupItemTitle, outcomesMap, teams, market)) } else if (sportsMarketType === "spreads") { if (!marketsData.spreads) { marketsData.spreads = {}; } Object.assign(marketsData.spreads, parseSpreads(groupItemTitle, line, outcomesMap, teams, market)) } else if (sportsMarketType === "totals") { if (!marketsData.totals) { marketsData.totals = {}; } Object.assign(marketsData.totals, parseTotals(line, outcomesMap, market)); } }); return marketsData; } /** * 解析赛事数据 * @param {*} event * @returns {Object} */ const parseEvent = (event) => { const { id, title, series, gameId, parentEventId, startTime } = event; const { teamHomeName, teamAwayName } = parseTeamData(title); const leagueId = series[0].id; const leagueName = series[0].title; const timestamp = new Date(startTime).getTime(); return { id: +id, gameId: gameId ? +gameId : undefined, parentEventId: parentEventId ? +parentEventId : undefined, leagueId: +leagueId, teamHomeName, teamAwayName, leagueName, timestamp, startTime: getDateInTimezone('+8', timestamp, true), }; } /** * 解析市场列表数据 * @param {*} events * @returns {Object} */ export const parseMarkets = (eventsData) => { const mergedMarketsData = {}; Object.values(eventsData).map(event => { const item = parseEvent(event); const { markets } = event; const { teamHomeName, teamAwayName } = item; item.marketsData = parseMarketsData(markets, { teamHomeName, teamAwayName }); return item; }).sort((a, b) => a.id - b.id).forEach(item => { if (item.id && !item.parentEventId) { mergedMarketsData[item.id] = cleanUndefined(item); } else if (item.parentEventId && mergedMarketsData[item.parentEventId] && item.marketsData) { const { marketsData: parentEvents } = mergedMarketsData[item.parentEventId]; Object.assign(parentEvents, item.marketsData); } }); return mergedMarketsData; } // export default parseMarkets;