trangleCalc.js 4.9 KB

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