trangleCalc.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. const crypto = require('crypto');
  2. const Logs = require('../libs/logs');
  3. const IOR_KEYS_MAP = require('./iorKeys');
  4. const { getSetting } = require('./settings');
  5. const { eventSolutions } = require('./eventSolutions');
  6. /**
  7. * 筛选最优赔率
  8. */
  9. function getOptimalSelections(odds, rules) {
  10. const results = [];
  11. const { obRebateRatio, hgRebateRatio } = getSetting();
  12. const rebateMap = {
  13. ob: 1 + obRebateRatio / 100,
  14. hg: 1 + hgRebateRatio / 100,
  15. };
  16. rules.forEach((rule, ruleIndex) => {
  17. let validOptions = [];
  18. for (let i = 0; i < 3; i++) {
  19. const innerIndex = i;
  20. const selection = [];
  21. let isValid = true;
  22. for (let j = 0; j < 3; j++) {
  23. const key = rule[j];
  24. let item = odds[key];
  25. if (key == '-') {
  26. item = { no: { v: 1 } };
  27. }
  28. if (!item) {
  29. isValid = false;
  30. break;
  31. }
  32. if (j === innerIndex) {
  33. if (!('ps' in item)) {
  34. isValid = false;
  35. break;
  36. }
  37. selection.push({
  38. k: key,
  39. p: 'ps',
  40. v: item.ps.v,
  41. r: item.ps.r,
  42. s: item.ps.s,
  43. o: item
  44. });
  45. }
  46. else {
  47. const candidates = ['ob', 'hg', 'no'].filter((k) => k in item);
  48. if (candidates.length === 0) {
  49. isValid = false;
  50. break;
  51. }
  52. // Logs.out('candidates', candidates)
  53. const best = candidates.reduce((a, b) => {
  54. const aValue = (item[a].v-1)*rebateMap[a];
  55. const bValue = (item[b].v-1)*rebateMap[b];
  56. const seletcted = aValue > bValue ? a : b;
  57. return seletcted;
  58. });
  59. // Logs.out('best', item, best)
  60. selection.push({
  61. k: key,
  62. p: best,
  63. v: item[best].v,
  64. r: item[best].r,
  65. o: item
  66. });
  67. }
  68. }
  69. if (isValid) {
  70. validOptions.push(selection);
  71. }
  72. }
  73. validOptions.forEach(iors => {
  74. results.push({ rule, iors, ruleIndex });
  75. });
  76. // if (validOptions.length > 0) {
  77. // const iors = validOptions.reduce((a, b) => {
  78. // const sumA = a.reduce((sum, x) => sum + x.v, 0);
  79. // const sumB = b.reduce((sum, x) => sum + x.v, 0);
  80. // return sumA > sumB ? a : b;
  81. // });
  82. // results.push({ rule, iors, ruleIndex });
  83. // }
  84. });
  85. return results;
  86. }
  87. /**
  88. * 精确浮点数字
  89. * @param {number} number
  90. * @param {number} x
  91. * @returns {number}
  92. */
  93. const fixFloat = (number, x=2) => {
  94. return parseFloat(number.toFixed(x));
  95. }
  96. /**
  97. * 盘口排序
  98. */
  99. const priority = { ps: 1, ob: 2, hg: 3 };
  100. const sortCpr = (cpr) => {
  101. const temp = [...cpr];
  102. temp.sort((a, b) => priority[a.p] - priority[b.p]);
  103. return temp;
  104. }
  105. /**
  106. * 添加返佣
  107. */
  108. const attachRebate = (ior) => {
  109. const { obRebateRatio, hgRebateRatio } = getSetting();
  110. const { p } = ior;
  111. let rebate = 0;
  112. if (p == 'ps') {
  113. rebate = 0;
  114. }
  115. else if (p == 'ob') {
  116. rebate = obRebateRatio;
  117. }
  118. else if (p == 'hg') {
  119. rebate = hgRebateRatio;
  120. }
  121. return { ...ior, b: rebate };
  122. }
  123. const eventsCombination = (passableEvents) => {
  124. const { minProfitAmount, innerDefaultAmount, innerRebateRatio } = getSetting();
  125. const solutions = [];
  126. passableEvents.forEach(events => {
  127. const { odds, info } = events;
  128. Object.keys(IOR_KEYS_MAP).forEach(iorGroup => {
  129. const rules = IOR_KEYS_MAP[iorGroup];
  130. const optimalSelections = getOptimalSelections(odds, rules);
  131. optimalSelections.forEach(selection => {
  132. const { rule, iors, ruleIndex } = selection;
  133. const [, , , crossType] = rule;
  134. const oddsSideA = attachRebate(iors[0]);
  135. const oddsSideB = attachRebate(iors[1]);
  136. const oddsSideC = attachRebate(iors[2]);
  137. const innerIndex = iors.findIndex(item => item.p == 'ps');
  138. if (!oddsSideA || !oddsSideB || !oddsSideC) {
  139. return;
  140. }
  141. const cpr = [ oddsSideA, oddsSideB, oddsSideC ];
  142. const betInfo = {
  143. cross_type: crossType,
  144. inner_index: innerIndex,
  145. inner_base: innerDefaultAmount,
  146. inner_rebate: fixFloat(innerRebateRatio / 100, 3),
  147. odds_side_a: fixFloat(oddsSideA.v - 1),
  148. odds_side_b: fixFloat(oddsSideB.v - 1),
  149. odds_side_c: fixFloat(oddsSideC.v - 1),
  150. rebate_side_a: parseFloat((oddsSideA.b / 100).toFixed(4)),
  151. rebate_side_b: parseFloat((oddsSideB.b / 100).toFixed(4)),
  152. rebate_side_c: parseFloat((oddsSideC.b / 100).toFixed(4)),
  153. };
  154. const sol = eventSolutions(betInfo, true);
  155. if (cpr[2].k == '-') {
  156. cpr.pop();
  157. }
  158. if (sol?.win_average > minProfitAmount) {
  159. const id = info.id;
  160. const sortedCpr = sortCpr(cpr);
  161. const keys = sortedCpr.map(item => `${item.k}`).join('_');
  162. const sid = crypto.createHash('sha1').update(`${id}_${keys}`).digest('hex');
  163. const crpGroup = `${id}_${sortedCpr[0].k}`;
  164. const timestamp = Date.now();
  165. solutions.push({sid, sol, cpr, info, group: crpGroup, rule: `${iorGroup}:${ruleIndex}`, timestamp});
  166. }
  167. });
  168. });
  169. });
  170. return solutions.sort((a, b) => {
  171. return b.sol.win_average - a.sol.win_average;
  172. });
  173. }
  174. module.exports = { eventsCombination };