trangleCalc.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. const crypto = require('crypto');
  2. const IOR_KEYS_MAP = require('./iorKeys');
  3. const { getSetting } = require('./settings');
  4. const { eventSolutions } = require('./eventSolutions');
  5. const cartesianOdds = (selection) => {
  6. const [a, b, c] = selection;
  7. return a.flatMap(itemA => b.flatMap(itemB => c.map(itemC => [itemA, itemB, itemC])));
  8. }
  9. const getOptimalSelections = (odds, rules) => {
  10. const results = [];
  11. rules.forEach((rule, ruleIndex) => {
  12. let validOptions = [];
  13. for (let i = 0; i < 3; i++) {
  14. const innerIndex = i;
  15. const selection = [];
  16. let isValid = true;
  17. for (let j = 0; j < 3; j++) {
  18. const key = rule[j];
  19. let item = odds[key];
  20. if (key == '-') {
  21. item = { no: { v: 1 } };
  22. }
  23. if (!item) {
  24. isValid = false;
  25. break;
  26. }
  27. if (j === innerIndex) {
  28. if (!('ps' in item)) {
  29. isValid = false;
  30. break;
  31. }
  32. selection.push([{
  33. k: key,
  34. p: 'ps',
  35. v: item.ps.v,
  36. r: item.ps.r,
  37. s: item.ps.s,
  38. q: item.ps.q,
  39. o: item
  40. }]);
  41. }
  42. else {
  43. const { ps, ...itemRest } = item;
  44. const candidates = Object.keys(itemRest);
  45. if (candidates.length === 0) {
  46. isValid = false;
  47. break;
  48. }
  49. selection.push(candidates.map(k => ({
  50. k: key,
  51. p: k,
  52. v: item[k].v,
  53. r: item[k].r,
  54. s: item[k].s,
  55. q: item[k].q,
  56. o: item
  57. })));
  58. }
  59. }
  60. if (isValid) {
  61. const cartesian = cartesianOdds(selection);
  62. cartesian.forEach(item => {
  63. validOptions.push(item);
  64. });
  65. }
  66. }
  67. validOptions.forEach(iors => {
  68. results.push({ rule, iors, ruleIndex });
  69. });
  70. });
  71. return results;
  72. }
  73. /**
  74. * 精确浮点数字
  75. * @param {number} number
  76. * @param {number} x
  77. * @returns {number}
  78. */
  79. const fixFloat = (number, x=2) => {
  80. return parseFloat(number.toFixed(x));
  81. }
  82. /**
  83. * 盘口排序
  84. */
  85. const priority = { ps: 1, ob: 2, hg: 3, im: 4, pc: 5 };
  86. const sortCpr = (cpr) => {
  87. const temp = [...cpr];
  88. temp.sort((a, b) => priority[a.p] - priority[b.p]);
  89. return temp;
  90. }
  91. /**
  92. * 获取平台类型
  93. */
  94. const getPlatformKey = (cpr) => {
  95. const platforms = sortCpr(cpr).map(item => item.p);
  96. return [...new Set(platforms)].join('_');
  97. }
  98. /**
  99. * 添加返佣
  100. */
  101. const attachRebate = (ior) => {
  102. const { obRebateRatio, obRebateType, hgRebateRatio, hgRebateLower, hgRebateType, pcRebateRatio, pcRebateType } = getSetting();
  103. const { p, k } = ior;
  104. let rebate = 0, rebateType = 0;
  105. if (p == 'ps') {
  106. rebate = 0;
  107. rebateType = -1;
  108. }
  109. else if (p == 'pc') {
  110. rebate = pcRebateRatio;
  111. rebateType = pcRebateType;
  112. }
  113. else if (p == 'ob') {
  114. rebate = obRebateRatio;
  115. rebateType = obRebateType;
  116. }
  117. else if (p == 'hg') {
  118. rebate = hgRebateRatio;
  119. rebateType = hgRebateType;
  120. if (k.startsWith('ior_m')) {
  121. rebate = hgRebateLower;
  122. }
  123. }
  124. return { ...ior, b: rebate, t: rebateType };
  125. }
  126. /**
  127. * 提取盘口数据
  128. */
  129. const extractOdds = ({ evtime, events, sptime, special }) => {
  130. const { expireTimeEvents, expireTimeSpecial } = getSetting();
  131. const nowTime = Date.now();
  132. const expireTimeEv = nowTime - expireTimeEvents;
  133. const expireTimeSP = nowTime - expireTimeSpecial;
  134. const extractData = {
  135. odds: null,
  136. evExpire: false,
  137. spExpire: false,
  138. removeCount: 0,
  139. }
  140. let odds = {};
  141. if (evtime > expireTimeEv) {
  142. odds = { ...odds, ...events };
  143. }
  144. else if (events) {
  145. extractData.evExpire = true;
  146. }
  147. if (sptime > expireTimeSP) {
  148. odds = { ...odds, ...special };
  149. }
  150. else if (special) {
  151. extractData.spExpire = true;
  152. }
  153. Object.keys(odds).forEach(ior => {
  154. if (!odds[ior]?.v || odds[ior].v <= 0) {
  155. delete odds[ior];
  156. extractData.removeCount++;
  157. }
  158. });
  159. extractData.odds = odds;
  160. return extractData;
  161. }
  162. /**
  163. * 筛选有效盘口
  164. */
  165. const getPassableEvents = (relations, eventsLogsMap) => {
  166. return relations.map(({ id, rel, mk }) => {
  167. const eventsMap = {};
  168. const oddsMap = {};
  169. Object.keys(rel).forEach(platform => {
  170. const { leagueName, teamHomeName, teamAwayName, timestamp, stage, score, retime, evtime, events, sptime, special } = rel[platform] ?? {};
  171. if (!events && !special) {
  172. return;
  173. }
  174. if (platform == 'ps') {
  175. eventsMap.info = { leagueName, teamHomeName, teamAwayName, id, timestamp, stage, score, retime };
  176. }
  177. const { odds, evExpire, spExpire, removeCount } = extractOdds({ evtime, events, sptime, special });
  178. /** 日志 盘口过期 */
  179. if (eventsLogsMap && (evExpire || spExpire)) {
  180. eventsLogsMap.expireEvents?.push({
  181. mk, platform,
  182. info: rel[platform],
  183. evExpire, spExpire,
  184. evtime, sptime,
  185. });
  186. }
  187. /** 日志 盘口移除 */
  188. if (eventsLogsMap && removeCount) {
  189. eventsLogsMap.removeEvents?.push({
  190. mk, platform,
  191. info: rel[platform],
  192. removeCount,
  193. });
  194. }
  195. /** 日志 End */
  196. Object.keys(odds).forEach(ior => {
  197. if (!oddsMap[ior]) {
  198. oddsMap[ior] = {};
  199. }
  200. oddsMap[ior][platform] = odds[ior];
  201. });
  202. });
  203. eventsMap.odds = oddsMap;
  204. return eventsMap;
  205. })
  206. .filter(item => item?.info);
  207. }
  208. /**
  209. * 盘口组合计算
  210. */
  211. const eventsCombination = (passableEvents, innerBase, innerRebate) => {
  212. const { innerDefaultAmount, innerRebateRatio } = getSetting();
  213. const solutions = [];
  214. passableEvents.forEach(events => {
  215. const { odds, info } = events;
  216. Object.keys(IOR_KEYS_MAP).forEach(iorGroup => {
  217. const rules = IOR_KEYS_MAP[iorGroup];
  218. const optimalSelections = getOptimalSelections(odds, rules);
  219. optimalSelections.forEach(selection => {
  220. const { rule, iors, ruleIndex } = selection;
  221. const [, , , crossType] = rule;
  222. const oddsSideA = attachRebate(iors[0]);
  223. const oddsSideB = attachRebate(iors[1]);
  224. const oddsSideC = attachRebate(iors[2]);
  225. const innerIndex = iors.findIndex(item => item.p == 'ps');
  226. if (!oddsSideA || !oddsSideB || !oddsSideC) {
  227. return;
  228. }
  229. const cpr = [ oddsSideA, oddsSideB, oddsSideC ];
  230. const betInfo = {
  231. cross_type: crossType,
  232. inner_index: innerIndex,
  233. inner_base: innerBase ?? innerDefaultAmount,
  234. inner_rebate: innerRebate ?? fixFloat(innerRebateRatio / 100, 3),
  235. odds_side_a: fixFloat(oddsSideA.v - 1),
  236. odds_side_b: fixFloat(oddsSideB.v - 1),
  237. odds_side_c: fixFloat(oddsSideC.v - 1),
  238. rebate_side_a: parseFloat((oddsSideA.b / 100).toFixed(4)),
  239. rebate_type_side_a: oddsSideA.t,
  240. rebate_side_b: parseFloat((oddsSideB.b / 100).toFixed(4)),
  241. rebate_type_side_b: oddsSideB.t,
  242. rebate_side_c: parseFloat((oddsSideC.b / 100).toFixed(4)),
  243. rebate_type_side_c: oddsSideC.t,
  244. };
  245. const sol = eventSolutions(betInfo, true);
  246. if (cpr[2].k == '-') {
  247. cpr.pop();
  248. }
  249. if (!isNaN(sol?.win_average)) {
  250. const id = info.id;
  251. const keys = cpr.map(item => `${item.k}-${item.p}`).join('##');
  252. const sid = crypto.createHash('sha1').update(`${id}-${keys}`).digest('hex');
  253. const crpGroup = `${id}_${cpr.find(item => item.p == 'ps').k}`;
  254. const hasLower = cpr.some(item => item.q === 0);
  255. const platformKey = getPlatformKey(cpr);
  256. const timestamp = Date.now();
  257. solutions.push({sid, sol, cpr, cross: platformKey, info, group: crpGroup, lower: hasLower, rule: `${iorGroup}:${ruleIndex}`, timestamp});
  258. }
  259. });
  260. });
  261. });
  262. return solutions.sort((a, b) => {
  263. return b.sol.win_average - a.sol.win_average;
  264. });
  265. }
  266. module.exports = { eventsCombination, getPassableEvents };