trangleCalc.js 5.9 KB

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