trangleCalc.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import crypto from 'crypto';
  2. import iorKeys from './iorKeys.js';
  3. import eventSolutions from './eventSolutions.js';
  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. * 盘口排序
  15. */
  16. const priority = { polymarket: 1, pinnacle: 2 };
  17. const getPriority = (p) => {
  18. return priority[p] ?? 99;
  19. }
  20. const sortCpr = (cpr) => {
  21. const temp = [...cpr];
  22. temp.sort((a, b) => getPriority(a.p) - getPriority(b.p));
  23. return temp;
  24. }
  25. /**
  26. * 获取平台类型
  27. */
  28. const getPlatformKey = (cpr) => {
  29. const platforms = sortCpr(cpr).map(item => item.p);
  30. return [...new Set(platforms)].join('_');
  31. }
  32. const cartesianOdds = (selection) => {
  33. const [a, b, c] = selection;
  34. return a.flatMap(itemA => b.flatMap(itemB => c.map(itemC => [itemA, itemB, itemC])));
  35. }
  36. const getOptimalSelections = (odds, rules) => {
  37. const results = [];
  38. rules.forEach((rule, ruleIndex) => {
  39. const validOptions = [];
  40. const selection = [];
  41. let isValid = true;
  42. for (let i = 0; i < 3; i++) {
  43. const key = rule[i];
  44. let item = odds[key];
  45. if (key === '-') {
  46. item = { no: { v: 1 } };
  47. }
  48. if (!item) {
  49. isValid = false;
  50. break;
  51. }
  52. const candidates = Object.keys(item);
  53. if (candidates.length === 0) {
  54. isValid = false;
  55. break;
  56. }
  57. selection.push(candidates.map(k => ({
  58. k: key,
  59. p: k,
  60. o: item,
  61. ...item[k],
  62. })));
  63. }
  64. if (isValid) {
  65. const cartesian = cartesianOdds(selection);
  66. cartesian.forEach(iors => {
  67. const iorsCount = new Set(iors.filter(ior => ior.p !== 'no').map(ior => ior.p));
  68. if (iorsCount.size > 1) {
  69. validOptions.push(iors);
  70. }
  71. });
  72. }
  73. validOptions.forEach(iors => {
  74. results.push({ rule, iors, ruleIndex });
  75. });
  76. });
  77. return results;
  78. }
  79. export const getPassableEvents = (relations) => {
  80. return relations.map(({ platforms }) => {
  81. const eventsMap = {};
  82. const oddsMap = {};
  83. Object.keys(platforms).forEach(platform => {
  84. const { id, leagueName, teamHomeName, teamAwayName, timestamp, evtime, odds } = platforms[platform] ?? {};
  85. if (!odds) {
  86. return;
  87. }
  88. if (platform === 'polymarket') {
  89. eventsMap['info'] = { id, leagueName, teamHomeName, teamAwayName, timestamp };
  90. }
  91. Object.keys(odds).forEach(ior => {
  92. if (!oddsMap[ior]) {
  93. oddsMap[ior] = {};
  94. }
  95. oddsMap[ior][platform] = odds[ior]
  96. });
  97. });
  98. eventsMap['odds'] = oddsMap;
  99. return eventsMap;
  100. })
  101. .filter(item => item?.info);
  102. }
  103. export const eventsCombination = (passableEvents) => {
  104. const solutions = [];
  105. passableEvents.forEach(events => {
  106. const { odds, info } = events;
  107. Object.keys(iorKeys).forEach(iorGroup => {
  108. const rules = iorKeys[iorGroup];
  109. const optimalSelections = getOptimalSelections(odds, rules);
  110. optimalSelections.forEach(selection => {
  111. const { iors, rule, ruleIndex } = selection;
  112. const [, , , crossType] = rule;
  113. const [ oddsSideA, oddsSideB, oddsSideC ] = iors;
  114. const baseIndex = iors.reduce((minIdx, cur, idx) => cur.v < iors[minIdx].v ? idx : minIdx, 0);
  115. if (!oddsSideA || !oddsSideB || !oddsSideC) {
  116. return;
  117. }
  118. if (oddsSideA.v <= 1 || oddsSideB.v <= 1) {
  119. return;
  120. }
  121. const cpr = [oddsSideA, oddsSideB, oddsSideC];
  122. const betInfo = {
  123. cross_type: crossType,
  124. base_index: baseIndex,
  125. base_stake: 10000,
  126. odds_side_a: fixFloat(oddsSideA.v - 1),
  127. odds_side_b: fixFloat(oddsSideB.v - 1),
  128. odds_side_c: fixFloat(oddsSideC.v - 1),
  129. };
  130. const sol = eventSolutions(betInfo, true);
  131. if (cpr[2].k == '-') {
  132. cpr.pop();
  133. }
  134. if (!isNaN(sol?.win_average)) {
  135. const id = info.id;
  136. const keys = cpr.map(item => `${item.k}-${item.p}`).join('##');
  137. const sid = crypto.createHash('sha1').update(`${id}-${keys}`).digest('hex');
  138. const hasLower = cpr.some(item => item.q === 0);
  139. const platformKey = getPlatformKey(cpr);
  140. const timestamp = Date.now();
  141. solutions.push({sid, sol, cpr, cross: platformKey, info, lower: hasLower, rule: `${iorGroup}:${ruleIndex}`, timestamp});
  142. }
  143. });
  144. });
  145. });
  146. return solutions.sort((a, b) => b.sol.win_profit_rate - a.sol.win_profit_rate);
  147. }