totalProfitCalc.js 15 KB

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