trangleCalc.js 7.9 KB

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