trangleCalc.js 4.8 KB

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