trangleCalc.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. const crypto = require('crypto');
  2. const Logs = require('../libs/logs');
  3. const IOR_KEYS_MAP = require('./iorKeys');
  4. const SETTING = {
  5. innerDefaultAmount: 10000,
  6. minProfitAmount: 0,
  7. innerRebateRatio: 0,
  8. obRebateRatio: 0,
  9. hgRebateRatio: 0,
  10. }
  11. /**
  12. * 筛选最优赔率
  13. */
  14. function getOptimalSelections(odds, rules) {
  15. const results = [];
  16. const { obRebateRatio, hgRebateRatio } = SETTING;
  17. const rebateMap = {
  18. ob: 1 + obRebateRatio / 100,
  19. hg: 1 + hgRebateRatio / 100,
  20. };
  21. rules.forEach((rule, index) => {
  22. let validOptions = [];
  23. for (let i = 0; i < 3; i++) {
  24. const innerIndex = i;
  25. const selection = [];
  26. let isValid = true;
  27. for (let j = 0; j < 3; j++) {
  28. const key = rule[j];
  29. const item = odds[key];
  30. if (!item) {
  31. isValid = false;
  32. break;
  33. }
  34. if (j === innerIndex) {
  35. if (!('ps' in item)) {
  36. isValid = false;
  37. break;
  38. }
  39. selection.push({
  40. k: key,
  41. p: 'ps',
  42. v: item.ps,
  43. o: item,
  44. });
  45. }
  46. else {
  47. const candidates = ['ob', 'hg'].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]-1)*rebateMap[a];
  55. const bValue = (item[b]-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],
  64. o: item,
  65. });
  66. }
  67. }
  68. if (isValid) {
  69. validOptions.push(selection);
  70. }
  71. }
  72. if (validOptions.length > 0) {
  73. const iors = validOptions.reduce((a, b) => {
  74. const sumA = a.reduce((sum, x) => sum + x.v, 0);
  75. const sumB = b.reduce((sum, x) => sum + x.v, 0);
  76. return sumA > sumB ? a : b;
  77. });
  78. results.push({ rule, iors, index });
  79. }
  80. });
  81. return results;
  82. }
  83. /**
  84. * 精确浮点数字
  85. * @param {number} number
  86. * @param {number} x
  87. * @returns {number}
  88. */
  89. const fixFloat = (number, x=2) => {
  90. return parseFloat(number.toFixed(x));
  91. }
  92. /**
  93. * 计算盈利
  94. */
  95. const triangleProfitCalc = (goldsInfo, oddsOption) => {
  96. const { innerRebateRatio } = SETTING;
  97. const {
  98. gold_side_a: x,
  99. gold_side_b: y,
  100. gold_side_c: z,
  101. odds_side_a: a,
  102. odds_side_b: b,
  103. odds_side_c: c
  104. } = goldsInfo;
  105. const { crossType, innerIndex, rebateA: A = 0, rebateB: B = 0, rebateC: C = 0 } = oddsOption;
  106. /**
  107. * crossType:
  108. * la: 全输
  109. * wa: 全赢
  110. * lh: 半输
  111. * wh: 半赢
  112. * dr: 和局
  113. * la_wh_wa, la_dr_wa, la_lh_wa, lh_dr_wa, lh_lh_wa, la_la_wa
  114. */
  115. let inner_rebate = 0;
  116. if (innerIndex == 0) {
  117. inner_rebate = x * innerRebateRatio / 100;
  118. }
  119. else if (innerIndex == 1) {
  120. inner_rebate = y * innerRebateRatio / 100;
  121. }
  122. else if (innerIndex == 2) {
  123. inner_rebate = z * innerRebateRatio / 100;
  124. }
  125. const k1 = a * (1 + A);
  126. const k2 = 1 - A;
  127. const k3 = b * (1 + B);
  128. const k4 = 1 - B;
  129. const k5 = c * (1 + C);
  130. const k6 = 1 - C;
  131. let win_side_a = 0, win_side_b = 0, win_side_c = 0;
  132. win_side_a = k1*x - k4*y - k6*z;
  133. win_side_b = k3*y - k2*x - k6*z;
  134. switch (crossType) {
  135. case 'la_wh_wa': // 全输 半赢 全赢
  136. win_side_c = k5*z - k2*x + k3*y/2;
  137. break;
  138. case 'la_dr_wa': // 全输 和局 全赢
  139. win_side_c = k5*z - k2*x;
  140. break;
  141. case 'la_lh_wa': // 全输 半输 全赢
  142. win_side_c = k5*z - k2*x - k4*y/2;
  143. break;
  144. case 'lh_dr_wa': // 半输 和局 全赢
  145. win_side_c = k5*z - k2*x/2;
  146. break;
  147. case 'lh_lh_wa': // 半输 半输 全赢
  148. win_side_c = k5*z - k2*x/2 - k4*y/2;
  149. break;
  150. case 'la_la_wa': // 全输 全输 全赢
  151. win_side_c = k5*z - k2*x - k4*y;
  152. break;
  153. }
  154. win_side_a = fixFloat(win_side_a + inner_rebate);
  155. win_side_b = fixFloat(win_side_b + inner_rebate);
  156. win_side_c = fixFloat(win_side_c + inner_rebate);
  157. const win_average = fixFloat((win_side_a + win_side_b + win_side_c) / 3);
  158. return { win_side_a, win_side_b, win_side_c, win_average }
  159. }
  160. const triangleGoldCalc = (oddsInfo, oddsOption) => {
  161. const { innerDefaultAmount } = SETTING;
  162. const { odds_side_a: a, odds_side_b: b, odds_side_c: c } = oddsInfo;
  163. if (!a || !b || !c) {
  164. return;
  165. }
  166. const { crossType, innerIndex, rebateA: A = 0, rebateB: B = 0, rebateC: C = 0 } = oddsOption;
  167. const k1 = a * (1 + A);
  168. const k2 = 1 - A;
  169. const k3 = b * (1 + B);
  170. const k4 = 1 - B;
  171. const k5 = c * (1 + C);
  172. const k6 = 1 - C;
  173. let x = innerDefaultAmount;
  174. let y = (k1 + k2) * x / (k3 + k4);
  175. let z;
  176. switch (crossType) {
  177. case 'la_wh_wa': // 全输 半赢 全赢
  178. z = k3 * y / 2 / (k5 + k6);
  179. break;
  180. case 'la_dr_wa': // 全输 和局 全赢
  181. z = k3 * y / (k5 + k6);
  182. break;
  183. case 'la_lh_wa': // 全输 半输 全赢
  184. z = (k3 + k4 / 2) * y / (k5 + k6);
  185. break;
  186. case 'lh_dr_wa': // 半输 和局 全赢
  187. z = (k3 * y - k2 * x / 2) / (k5 + k6);
  188. break;
  189. case 'lh_lh_wa': // 半输 半输 全赢
  190. z = ((k3 + k4/2) * y - k2 * x / 2) / (k5 + k6);
  191. break;
  192. case 'la_la_wa': // 全输 全输 全赢
  193. z = (k3 + k4) * y / (k5 + k6);
  194. break;
  195. default:
  196. z = 0;
  197. }
  198. if (innerIndex == 1) {
  199. const scale = innerDefaultAmount / y;
  200. x = x * scale;
  201. y = innerDefaultAmount;
  202. z = z * scale;
  203. }
  204. else if (innerIndex == 2) {
  205. const scale = innerDefaultAmount / z;
  206. x = x * scale;
  207. y = y * scale;
  208. z = innerDefaultAmount;
  209. }
  210. return {
  211. gold_side_a: fixFloat(x),
  212. gold_side_b: fixFloat(y),
  213. gold_side_c: fixFloat(z),
  214. odds_side_a: fixFloat(a),
  215. odds_side_b: fixFloat(b),
  216. odds_side_c: fixFloat(c),
  217. };
  218. }
  219. const eventSolutions = (oddsInfo, oddsOption) => {
  220. const { innerDefaultAmount, innerRebateRatio } = SETTING;
  221. const goldsInfo = triangleGoldCalc(oddsInfo, oddsOption);
  222. if (!goldsInfo) {
  223. return;
  224. }
  225. const profitInfo = triangleProfitCalc(goldsInfo, oddsOption);
  226. const { odds_side_a, odds_side_b, odds_side_c, gold_side_a, gold_side_b, gold_side_c } = goldsInfo;
  227. const { win_side_a, win_side_b, win_side_c, win_average } = profitInfo;
  228. let { crossType, innerIndex, rebateA, rebateB, rebateC } = oddsOption;
  229. if (innerIndex == 0) {
  230. rebateA = fixFloat(innerRebateRatio / 100, 3);
  231. }
  232. else if (innerIndex == 1) {
  233. rebateB = fixFloat(innerRebateRatio / 100, 3);
  234. }
  235. else if (innerIndex == 2) {
  236. rebateC = fixFloat(innerRebateRatio / 100, 3);
  237. }
  238. const win_profit = fixFloat(win_average / (gold_side_a + gold_side_b + gold_side_c) * 100);
  239. return {
  240. odds_side_a, odds_side_b, odds_side_c,
  241. // gold_side_a, gold_side_b, gold_side_c,
  242. // win_side_a, win_side_b, win_side_c,
  243. win_average, win_profit,
  244. rebate_side_a: rebateA,
  245. rebate_side_b: rebateB,
  246. rebate_side_c: rebateC,
  247. cross_type: crossType,
  248. inner_index: innerIndex,
  249. inner_base: innerDefaultAmount,
  250. }
  251. }
  252. /**
  253. * 盘口排序
  254. */
  255. const priority = { ps: 1, ob: 2, hg: 3 };
  256. const sortCpr = (cpr) => {
  257. const temp = [...cpr];
  258. temp.sort((a, b) => priority[a.p] - priority[b.p]);
  259. return temp;
  260. }
  261. /**
  262. * 添加返佣
  263. */
  264. const attachRebate = (ior) => {
  265. const { obRebateRatio, hgRebateRatio } = SETTING;
  266. const { p } = ior;
  267. let rebate = 0;
  268. if (p == 'ps') {
  269. rebate = 0;
  270. }
  271. else if (p == 'ob') {
  272. rebate = obRebateRatio;
  273. }
  274. else if (p == 'hg') {
  275. rebate = hgRebateRatio;
  276. }
  277. return { ...ior, r: rebate };
  278. }
  279. const eventsCombination = (passableEvents, setting) => {
  280. Object.keys(setting).forEach(key => {
  281. if (key in SETTING && SETTING[key] !== setting[key]) {
  282. SETTING[key] = setting[key];
  283. Logs.out(`setting ${key} changed to ${setting[key]}`);
  284. }
  285. });
  286. const solutions = [];
  287. passableEvents.forEach(events => {
  288. const { odds, info } = events;
  289. Object.keys(IOR_KEYS_MAP).forEach(iorGroup => {
  290. const rules = IOR_KEYS_MAP[iorGroup];
  291. const optimalSelections = getOptimalSelections(odds, rules);
  292. optimalSelections.forEach(selection => {
  293. const { rule, iors, index } = selection;
  294. const [, , , crossType] = rule;
  295. const oddsSideA = attachRebate(iors[0]);
  296. const oddsSideB = attachRebate(iors[1]);
  297. const oddsSideC = attachRebate(iors[2]);
  298. const innerIndex = iors.findIndex(item => item.p == 'ps');
  299. if (!oddsSideA || !oddsSideB || !oddsSideC) {
  300. return;
  301. }
  302. const cpr = [ oddsSideA, oddsSideB, oddsSideC ];
  303. const oddsInfo = {
  304. odds_side_a: fixFloat(oddsSideA.v - 1),
  305. odds_side_b: fixFloat(oddsSideB.v - 1),
  306. odds_side_c: fixFloat(oddsSideC.v - 1),
  307. };
  308. const oddsOption = {
  309. crossType, innerIndex,
  310. rebateA: parseFloat((oddsSideA.r / 100).toFixed(4)),
  311. rebateB: parseFloat((oddsSideB.r / 100).toFixed(4)),
  312. rebateC: parseFloat((oddsSideC.r / 100).toFixed(4)),
  313. };
  314. const sol = eventSolutions(oddsInfo, oddsOption);
  315. if (sol?.win_average > SETTING.minProfitAmount) {
  316. const id = info.id;
  317. const sortedCpr = sortCpr(cpr);
  318. const keys = sortedCpr.map(item => `${item.k}`).join('_');
  319. const sid = crypto.createHash('sha1').update(`${id}_${keys}`).digest('hex');
  320. const crpGroup = `${id}_${sortedCpr[0].k}`;
  321. const timestamp = Date.now();
  322. solutions.push({sid, sol, cpr, info, group: crpGroup, rule: `${iorGroup}:${index}`, timestamp});
  323. }
  324. });
  325. });
  326. });
  327. return solutions.sort((a, b) => {
  328. return b.sol.win_average - a.sol.win_average;
  329. });
  330. // return Object.values(solutions).map(item => {
  331. // return item.sort((a, b) => {
  332. // return b.sol.win_average - a.sol.win_average;
  333. // })
  334. // .slice(0, 2);
  335. // })
  336. // .sort((a, b) => {
  337. // return b[0].sol.win_average - a[0].sol.win_average;
  338. // });
  339. }
  340. module.exports = { eventsCombination };