trangleCalc.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  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 hasPm = iors.some(ior => ior.p === 'polymarket');
  68. const iorsCount = new Set(iors.filter(ior => ior.p !== 'no').map(ior => ior.p));
  69. if (hasPm && iorsCount.size > 1) {
  70. validOptions.push(iors);
  71. }
  72. });
  73. }
  74. validOptions.forEach(iors => {
  75. results.push({ rule, iors, ruleIndex });
  76. });
  77. });
  78. return results;
  79. }
  80. export const getPassableEvents = (relations) => {
  81. return relations.map(({ id, platforms }) => {
  82. const eventsMap = {};
  83. const oddsMap = {};
  84. Object.keys(platforms).forEach(platform => {
  85. const { odds, evtime } = platforms[platform] ?? {};
  86. if (!odds) {
  87. // console.log('odds not found', platform, odds);
  88. return;
  89. }
  90. if (evtime < Date.now() - 1000 * 15) {
  91. // console.log('odds is expired', platform, evtime);
  92. return;
  93. }
  94. Object.keys(odds).forEach(ior => {
  95. if (!oddsMap[ior]) {
  96. oddsMap[ior] = {};
  97. }
  98. oddsMap[ior][platform] = odds[ior]
  99. });
  100. });
  101. eventsMap['odds'] = oddsMap;
  102. eventsMap['rid'] = id;
  103. return eventsMap;
  104. })
  105. .filter(item => item?.rid);
  106. }
  107. export const eventsCombination = (passableEvents) => {
  108. const solutions = [];
  109. passableEvents.forEach(events => {
  110. const { odds, rid } = events;
  111. Object.keys(iorKeys).forEach(iorGroup => {
  112. const rules = iorKeys[iorGroup];
  113. const optimalSelections = getOptimalSelections(odds, rules);
  114. optimalSelections.forEach(selection => {
  115. const { iors, rule, ruleIndex } = selection;
  116. const [, , , crossType] = rule;
  117. const [ oddsSideA, oddsSideB, oddsSideC ] = iors;
  118. const baseIndex = iors.reduce((minIdx, cur, idx) => cur.v < iors[minIdx].v ? idx : minIdx, 0);
  119. if (!oddsSideA || !oddsSideB || !oddsSideC) {
  120. return;
  121. }
  122. if (oddsSideA.v <= 1 || oddsSideB.v <= 1) {
  123. return;
  124. }
  125. const cpr = [oddsSideA, oddsSideB, oddsSideC];
  126. const betInfo = {
  127. cross_type: crossType,
  128. base_index: baseIndex,
  129. base_stake: 10000,
  130. odds_side_a: fixFloat(oddsSideA.v - 1),
  131. odds_side_b: fixFloat(oddsSideB.v - 1),
  132. odds_side_c: fixFloat(oddsSideC.v - 1),
  133. rebate_side_a: fixFloat(((oddsSideA.b ?? 0) / 100), 4),
  134. rebate_side_b: fixFloat(((oddsSideB.b ?? 0) / 100), 4),
  135. rebate_side_c: fixFloat(((oddsSideC.b ?? 0) / 100), 4),
  136. rebate_type_side_a: oddsSideA.t ?? 0,
  137. rebate_type_side_b: oddsSideB.t ?? 0,
  138. rebate_type_side_c: oddsSideC.t ?? 0,
  139. };
  140. const sol = eventSolutions(betInfo, true);
  141. if (cpr[2].k == '-') {
  142. cpr.pop();
  143. }
  144. if (!isNaN(sol?.win_average)) {
  145. const keys = cpr.map(item => `${item.k}-${item.p}`).join('$$');
  146. const sid = crypto.createHash('sha1').update(`${rid}-${keys}`).digest('hex');
  147. const hasLower = cpr.some(item => item.q === 0);
  148. const platformKey = getPlatformKey(cpr);
  149. const timestamp = Date.now();
  150. solutions.push({sid, sol, cpr, cross: platformKey, rid, lower: hasLower, rule: `${iorGroup}:${ruleIndex}`, timestamp});
  151. }
  152. });
  153. });
  154. });
  155. return solutions.sort((a, b) => b.sol.win_profit_rate - a.sol.win_profit_rate);
  156. }