totalProfitCalc.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. const Logs = require('../libs/logs');
  2. const { eventSolutions } = require('./eventSolutions');
  3. /**
  4. * 精确浮点数字
  5. * @param {number} number
  6. * @param {number} x
  7. * @returns {number}
  8. */
  9. const fixFloat = (number, x = 2) => {
  10. return parseFloat(number.toFixed(x));
  11. }
  12. /**
  13. * 计算输赢比例
  14. * 与其他方法中的输赢逻辑相反
  15. * 正数为输
  16. * 负数为赢
  17. */
  18. const CROSS_TYPE_MAP = { w: -1, l: 1, a: 1, h: 0.5, d: 0, r: 0 };
  19. const lossProportion = (sol) => {
  20. const { cross_type, odds_side_a, odds_side_b, rebate_side_a, rebate_side_b } = sol;
  21. const typeList = cross_type.split('_').map(part => {
  22. return part.split('').map(key => CROSS_TYPE_MAP[key]);
  23. }).map(([a, b])=> a * b);
  24. let loss_proportion_a = 0, loss_proportion_b = 0;
  25. if (typeList[0] >= 0) {
  26. loss_proportion_a = typeList[0] * (1 - rebate_side_a);
  27. }
  28. else {
  29. loss_proportion_a = typeList[0] * odds_side_a * (1 + rebate_side_a);
  30. }
  31. if (typeList[1] >= 0) {
  32. loss_proportion_b = typeList[1] * (1 - rebate_side_b);
  33. }
  34. else {
  35. loss_proportion_b = typeList[1] * odds_side_b * (1 + rebate_side_b);
  36. }
  37. return { loss_proportion_a, loss_proportion_b };
  38. }
  39. /**
  40. * 不同组合的金额计算
  41. */
  42. const HandicapCalc = function (data) {
  43. const { i, g, a, b, c, A, B, C, w, l } = data;
  44. const t = w + l;
  45. const k1 = a * (1 + A);
  46. const k2 = 1 - A;
  47. const k3 = b * (1 + B);
  48. const k4 = 1 - B;
  49. const k5 = c * (1 + C);
  50. const k6 = 1 - C;
  51. const calcTemplate = (handlers) => {
  52. if (i > 2 || i < 0) {
  53. return {};
  54. }
  55. if (i === 2) {
  56. const z = g;
  57. const m = t + k6 * z;
  58. const x = (k3 + k4) * m / (k1 * k3 - k2 * k4);
  59. const y = (k1 + k2) * m / (k1 * k3 - k2 * k4);
  60. return { x, y, z };
  61. };
  62. return handlers[i]?.() ?? {};
  63. };
  64. return {
  65. la_wh_wa() {
  66. return calcTemplate([
  67. () => {
  68. const x = g;
  69. const m = t + k2 * x;
  70. const z = m / (2 * k5 + k6);
  71. const y = (k5 + k6) * m / (k3 * k5 + k3 * k6 / 2);
  72. return { x, y, z };
  73. },
  74. () => {
  75. const y = g;
  76. const z = ((k1 + k2) * t + (k2 * k4 - k1 * k3 / 2) * y) / (k1 * k5 - k2 * k6);
  77. const x = ((k5 + k6) * t + (k4 * k5 - k3 * k6 / 2) * y) / (k1 * k5 - k2 * k6);
  78. return { x, y, z };
  79. }
  80. ]);
  81. },
  82. la_dr_wa() {
  83. return calcTemplate([
  84. () => {
  85. const x = g;
  86. const m = t + k2 * x;
  87. const z = m / k5;
  88. const y = (k5 + k6) * m / (k3 * k5);
  89. return { x, y, z };
  90. },
  91. () => {
  92. const y = g;
  93. const z = ((k1 + k2) * t + k2 * k4 * y) / (k1 * k5 - k2 * k6);
  94. const x = ((k5 + k6) * t + k4 * k5 * y) / (k1 * k5 - k2 * k6);
  95. return { x, y, z };
  96. }
  97. ]);
  98. },
  99. la_lh_wa() {
  100. return calcTemplate([
  101. () => {
  102. const x = g;
  103. const m = t + k2 * x;
  104. const z = (2 * k3 + k4) * m / (2 * k3 * k5 - k4 * k6);
  105. const y = (k5 + k6) * m / (k3 * k5 - k4 * k6 / 2);
  106. return { x, y, z };
  107. },
  108. () => {
  109. const y = g;
  110. const z = ((k1 + k2) * t + (k2 * k4 + k1 * k4 / 2) * y) / (k1 * k5 - k2 * k6);
  111. const x = ((k5 + k6) * t + (k4 * k5 + k4 * k6 / 2) * y) / (k1 * k5 - k2 * k6);
  112. return { x, y, z };
  113. }
  114. ]);
  115. },
  116. lh_dr_wa() {
  117. return calcTemplate([
  118. () => {
  119. const x = g;
  120. const z = (t + k2 * x / 2) / k5;
  121. const y = ((k5 + k6) * t + (k2 * k5 + k2 * k4 / 2) * x) / (k3 * k5);
  122. return { x, y, z };
  123. },
  124. () => {
  125. const y = g;
  126. const z = ((2 * k1 + k2) * t + k2 * k4 * y) / (2 * k1 * k5 - k2 * k6);
  127. const x = ((k5 + k6) * t + k4 * k5 * y) / (k1 * k5 - k2 * k6 / 2);
  128. return { x, y, z };
  129. }
  130. ]);
  131. },
  132. lh_lh_wa() {
  133. return calcTemplate([
  134. () => {
  135. const x = g;
  136. const z = ((2 * k3 + k4) * t + (k3 + k4) * k2 * x) / (2 * k3 * k5 - k4 * k6);
  137. const y = ((k5 + k6) * t + (k5 + k6 / 2) * k2 * x) / (k3 * k5 - k4 * k6 / 2);
  138. return { x, y, z };
  139. },
  140. () => {
  141. const y = g;
  142. const z = ((2 * k1 + k2) * t + (k1 + k2) * k4 * y) / (2 * k1 * k5 - k2 * k6);
  143. const x = ((k5 + k6) * t + (k5 + k6 / 2) * k4 * y) / (k1 * k5 - k2 * k6 / 2);
  144. return { x, y, z };
  145. }
  146. ]);
  147. },
  148. la_la_wa() {
  149. return calcTemplate([
  150. () => {
  151. const x = g;
  152. const m = t + k2 * x;
  153. const z = (k3 + k4) * m / (k3 * k5 - k4 * k6);
  154. const y = (k5 + k6) * m / (k3 * k5 - k4 * k6);
  155. return { x, y, z };
  156. },
  157. () => {
  158. const y = g;
  159. const m = t + k4 * y;
  160. const z = (k1 + k2) * m / (k1 * k5 - k2 * k6);
  161. const x = (k5 + k6) * m / (k1 * k5 - k2 * k6);
  162. return { x, y, z };
  163. }
  164. ]);
  165. }
  166. }
  167. }
  168. /**
  169. * const z = g;
  170. const m = t + k6 * z;
  171. const x = (k3 + k4) * m / (k1 * k3 - k2 * k4);
  172. const y = (k1 + k2) * m / (k1 * k3 - k2 * k4);
  173. return { x, y, z };
  174. */
  175. /**
  176. * 根据预期盈利计算下注金额
  177. */
  178. const calcGoldsWithTarget = (data) => {
  179. const {
  180. inner_base: g,
  181. odds_side_a: a,
  182. odds_side_b: b,
  183. odds_side_c: c,
  184. rebate_side_a: A,
  185. rebate_side_b: B,
  186. rebate_side_c: C,
  187. inner_index: i,
  188. cross_type: t,
  189. win_target: w,
  190. loss_out: l = 0,
  191. } = data;
  192. const calc = new HandicapCalc({ i, g, a, b, c, A, B, C, w, l });
  193. const { x, y, z } = calc?.[t]() ?? {};
  194. return {
  195. gold_side_a: fixFloat(x),
  196. gold_side_b: fixFloat(y),
  197. gold_side_c: fixFloat(z),
  198. }
  199. }
  200. /**
  201. * 根据预期盈利计算金额和内盘盈亏
  202. */
  203. const calcWinResultWithTarget = (data) => {
  204. const { inner_base, inner_rebate, win_target, sol1, sol2 } = data;
  205. const {
  206. cross_type: crossType1,
  207. odds_side_a: oddsA1,
  208. odds_side_b: oddsB1,
  209. odds_side_c: oddsC1,
  210. rebate_side_a: rebateA1,
  211. rebate_side_b: rebateB1,
  212. rebate_side_c: rebateC1,
  213. inner_index: inner_index_1,
  214. } = sol1;
  215. const {
  216. gold_side_a: goldA1,
  217. gold_side_b: goldB1,
  218. gold_side_c: goldC1,
  219. } = calcGoldsWithTarget({ ...sol1, inner_base, win_target });
  220. let loss_out_1 = 0, win_inner_1 = 0;
  221. switch (inner_index_1) {
  222. case 0:
  223. loss_out_1 = goldB1 * (1 - rebateB1) + goldC1 * (1 - rebateC1);
  224. win_inner_1 = inner_base * (oddsA1 + 1);
  225. break;
  226. case 1:
  227. loss_out_1 = goldA1 * (1 - rebateA1) + goldC1 * (1 - rebateC1);
  228. win_inner_1 = inner_base * (oddsB1 + 1);
  229. break;
  230. case 2:
  231. const { loss_proportion_a: lpA1, loss_proportion_b: lpB1 } = lossProportion(sol1);
  232. loss_out_1 = goldA1 * lpA1 + goldB1 * lpB1;
  233. win_inner_1 = inner_base * (oddsC1 + 1)
  234. break;
  235. }
  236. win_inner_1 = fixFloat(win_inner_1);
  237. const {
  238. cross_type: crossType2,
  239. odds_side_a: oddsA2,
  240. odds_side_b: oddsB2,
  241. odds_side_c: oddsC2,
  242. rebate_side_a: rebateA2,
  243. rebate_side_b: rebateB2,
  244. rebate_side_c: rebateC2,
  245. inner_index: inner_index_2,
  246. } = sol2;
  247. const {
  248. gold_side_a: goldA2,
  249. gold_side_b: goldB2,
  250. gold_side_c: goldC2,
  251. } = calcGoldsWithTarget({ ...sol2, inner_base, win_target, loss_out: loss_out_1 });
  252. let loss_out_2 = 0, win_inner_2 = 0, inner_base_key;
  253. switch (inner_index_2) {
  254. case 0:
  255. inner_base_key = 'goldA2';
  256. loss_out_2 = inner_base + goldB2 * (1 - rebateB2) + goldC2 * (1 - rebateC2) + loss_out_1;
  257. win_inner_2 = win_inner_1 * (oddsA2 + 1);
  258. break;
  259. case 1:
  260. inner_base_key = 'goldB2';
  261. loss_out_2 = inner_base + goldA2 * (1 - rebateA2) + goldC2 * (1 - rebateC2) + loss_out_1;
  262. win_inner_2 = win_inner_1 * (oddsB2 + 1);
  263. break;
  264. case 2:
  265. const { loss_proportion_a: lpA2, loss_proportion_b: lpB2 } = lossProportion(sol2);
  266. inner_base_key = 'goldC2';
  267. loss_out_2 = inner_base + goldA2 * lpA2 + goldB2 * lpB2 + loss_out_1;
  268. win_inner_2 = win_inner_1 * (oddsC2 + 1);
  269. break;
  270. }
  271. const win_inner = fixFloat(win_inner_2 - loss_out_2);
  272. const goldsInfo = { goldA1, goldB1, goldC1, goldA2, goldB2, goldC2 }
  273. if (goldsInfo[inner_base_key]) {
  274. goldsInfo[inner_base_key] = win_inner_1;
  275. }
  276. const result = {
  277. bet_info: [
  278. {
  279. cross_type: crossType1,
  280. gold_side_a: goldsInfo.goldA1,
  281. gold_side_b: goldsInfo.goldB1,
  282. gold_side_c: goldsInfo.goldC1,
  283. odds_side_a: oddsA1,
  284. odds_side_b: oddsB1,
  285. odds_side_c: oddsC1,
  286. rebate_side_a: rebateA1,
  287. rebate_side_b: rebateB1,
  288. rebate_side_c: rebateC1,
  289. inner_index: inner_index_1,
  290. },
  291. {
  292. cross_type: crossType2,
  293. gold_side_a: goldsInfo.goldA2,
  294. gold_side_b: goldsInfo.goldB2,
  295. gold_side_c: goldsInfo.goldC2,
  296. odds_side_a: oddsA2,
  297. odds_side_b: oddsB2,
  298. odds_side_c: oddsC2,
  299. rebate_side_a: rebateA2,
  300. rebate_side_b: rebateB2,
  301. rebate_side_c: rebateC2,
  302. inner_index: inner_index_2,
  303. }
  304. ],
  305. win_inner,
  306. inner_base,
  307. inner_rebate,
  308. }
  309. return result;
  310. }
  311. /**
  312. * 根据单关盈亏计算综合利润
  313. */
  314. const calcTotalProfit = (sol1, sol2, inner_base, inner_rebate) => {
  315. const rebateInner = inner_base * inner_rebate;
  316. const winTarget1 = fixFloat(sol1.win_average - rebateInner);
  317. const winTarget2 = fixFloat(sol2.win_average - rebateInner);
  318. Logs.out('winTarget1', winTarget1);
  319. Logs.out('winTarget2', winTarget2);
  320. const winTarget = fixFloat(Math.min(winTarget1, winTarget2));
  321. const win1 = calcWinResultWithTarget({ inner_base, inner_rebate, win_target: winTarget1, sol1, sol2 })?.win_inner;
  322. const win2 = calcWinResultWithTarget({ inner_base, inner_rebate, win_target: winTarget2, sol1, sol2 })?.win_inner;
  323. Logs.out('win1', win1);
  324. Logs.out('win2', win2);
  325. const win_inner = fixFloat(Math.max(win1, win2), 2);
  326. const start = Math.max(winTarget, win_inner);
  327. const end = Math.min(winTarget, win_inner);
  328. Logs.out('start', start);
  329. Logs.out('end', end);
  330. const result = [];
  331. for (let i = start; i > end; i--) {
  332. const win_target = i;
  333. const goldsInfo = calcWinResultWithTarget({ inner_base, inner_rebate, win_target, sol1, sol2 });
  334. const { win_inner, ...goldsRest } = goldsInfo;
  335. const win_diff = Math.abs(fixFloat(win_target - goldsInfo.win_inner));
  336. const lastResult = result.at(-1);
  337. if (!lastResult?.win_diff || win_diff < lastResult.win_diff) {
  338. result.push({ win_target: fixFloat(win_target + rebateInner), win_diff, win_inner: fixFloat(win_inner + rebateInner), ...goldsRest });
  339. }
  340. else {
  341. break;
  342. }
  343. }
  344. return result.at(-1);
  345. }
  346. /**
  347. * ----------------------------
  348. * 计算第一关锁定之后的新利润
  349. * 第一关过关后,重新计算第二关金额
  350. * 结合第一关亏损计算第二关新利润
  351. * ----------------------------
  352. */
  353. /**
  354. * 计算第二关利润
  355. */
  356. const calcSecondProfit = (betInfo) => {
  357. const {
  358. inner_index, inner_odds_first,
  359. odds_side_a: a, odds_side_b: b, odds_side_c: c,
  360. } = betInfo;
  361. let odds_side_a, odds_side_b, odds_side_c;
  362. switch (inner_index) {
  363. case 0:
  364. odds_side_a = fixFloat((a+1) * (inner_odds_first+1) - 1);
  365. odds_side_b = b;
  366. odds_side_c = c;
  367. break;
  368. case 1:
  369. odds_side_a = a;
  370. odds_side_b = fixFloat((b+1) * (inner_odds_first+1) - 1);
  371. odds_side_c = c;
  372. break;
  373. case 2:
  374. odds_side_a = a;
  375. odds_side_b = b;
  376. odds_side_c = fixFloat((c+1) * (inner_odds_first+1) - 1);
  377. break;
  378. }
  379. return eventSolutions({ ...betInfo, odds_side_a, odds_side_b, odds_side_c }, true);
  380. }
  381. /**
  382. * 结合第一关亏损计算第二关新利润
  383. */
  384. const calcTotalProfitWithFixedFirst = (betInfo1, betInfo2, inner_base, inner_rebate) => {
  385. const {
  386. cross_type: crossType1,
  387. inner_index: inner_index_1,
  388. gold_side_a: goldA1,
  389. gold_side_b: goldB1,
  390. gold_side_c: goldC1,
  391. odds_side_a: oddsA1,
  392. odds_side_b: oddsB1,
  393. odds_side_c: oddsC1,
  394. rebate_side_a: rebateA1,
  395. rebate_side_b: rebateB1,
  396. rebate_side_c: rebateC1,
  397. } = betInfo1;
  398. let loss_out_1 = 0, inner_ref_value = 0, inner_odds_1 = 0;
  399. switch (inner_index_1) {
  400. case 0:
  401. loss_out_1 = goldB1 * (1 - rebateB1) + goldC1 * (1 - rebateC1);
  402. inner_ref_value = goldA1;
  403. inner_odds_1 = oddsA1;
  404. break;
  405. case 1:
  406. loss_out_1 = goldA1 * (1 - rebateA1) + goldC1 * (1 - rebateC1);
  407. inner_ref_value = goldB1;
  408. inner_odds_1 = oddsB1;
  409. break;
  410. case 2:
  411. const { loss_proportion_a: lpA1, loss_proportion_b: lpB1 } = lossProportion(betInfo1);
  412. loss_out_1 = goldA1 * lpA1 + goldB1 * lpB1;
  413. inner_ref_value = goldC1;
  414. inner_odds_1 = oddsC1;
  415. break;
  416. }
  417. if (inner_base && inner_base != inner_ref_value) {
  418. Logs.out('inner_base is not equal to inner_ref_value', inner_base, inner_ref_value);
  419. throw new Error('内盘基准额度和内盘索引额度不一致');
  420. }
  421. const profitInfo = calcSecondProfit({ ...betInfo2, inner_base, inner_odds_first: inner_odds_1, inner_rebate });
  422. // profitInfo.win_side_a = fixFloat(profitInfo.win_side_a - loss_out_1);
  423. // profitInfo.win_side_b = fixFloat(profitInfo.win_side_b - loss_out_1);
  424. // profitInfo.win_side_c = fixFloat(profitInfo.win_side_c - loss_out_1);
  425. profitInfo.win_average = fixFloat(profitInfo.win_average - loss_out_1);
  426. return profitInfo;
  427. }
  428. module.exports = { calcTotalProfit, calcTotalProfitWithFixedFirst };