trangleCalc.js 9.7 KB

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