totalProfitCalc.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  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 { 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. la_wa_rv() {
  167. return calcTemplate([
  168. () => {
  169. const x = g;
  170. const y = (k2 * x + t) / k3;
  171. const z = 0;
  172. return { x, y, z };
  173. },
  174. () => {
  175. const y = g;
  176. const x = (k4 * y + t) / k1;
  177. const z = 0;
  178. return { x, y, z };
  179. }
  180. ]);
  181. }
  182. }
  183. }
  184. /**
  185. * const z = g;
  186. const m = t + k6 * z;
  187. const x = (k3 + k4) * m / (k1 * k3 - k2 * k4);
  188. const y = (k1 + k2) * m / (k1 * k3 - k2 * k4);
  189. return { x, y, z };
  190. */
  191. /**
  192. * 根据预期盈利计算下注金额
  193. */
  194. const calcGoldsWithTarget = (data) => {
  195. const {
  196. inner_base: g,
  197. odds_side_a: a,
  198. odds_side_b: b,
  199. odds_side_c: c,
  200. rebate_side_a: A,
  201. rebate_side_b: B,
  202. rebate_side_c: C,
  203. inner_index: i,
  204. cross_type: t,
  205. win_target: w,
  206. loss_out: l = 0,
  207. } = data;
  208. const calc = new HandicapCalc({ i, g, a, b, c, A, B, C, w, l });
  209. const { x, y, z } = calc?.[t]() ?? {};
  210. return {
  211. gold_side_a: fixFloat(x),
  212. gold_side_b: fixFloat(y),
  213. gold_side_c: fixFloat(z),
  214. }
  215. }
  216. /**
  217. * 根据预期盈利计算金额和内盘盈亏
  218. */
  219. const calcWinResultWithTarget = (data) => {
  220. const { inner_base, inner_rebate, win_target, sol1, sol2 } = data;
  221. const {
  222. cross_type: crossType1,
  223. odds_side_a: oddsA1,
  224. odds_side_b: oddsB1,
  225. odds_side_c: oddsC1,
  226. rebate_side_a: rebateA1,
  227. rebate_side_b: rebateB1,
  228. rebate_side_c: rebateC1,
  229. inner_index: inner_index_1,
  230. } = sol1;
  231. const {
  232. gold_side_a: goldA1,
  233. gold_side_b: goldB1,
  234. gold_side_c: goldC1,
  235. } = calcGoldsWithTarget({ ...sol1, inner_base, win_target });
  236. let loss_out_1 = 0, win_inner_1 = 0;
  237. switch (inner_index_1) {
  238. case 0:
  239. loss_out_1 = goldB1 * (1 - rebateB1) + goldC1 * (1 - rebateC1);
  240. win_inner_1 = inner_base * (oddsA1 + 1);
  241. break;
  242. case 1:
  243. loss_out_1 = goldA1 * (1 - rebateA1) + goldC1 * (1 - rebateC1);
  244. win_inner_1 = inner_base * (oddsB1 + 1);
  245. break;
  246. case 2:
  247. const { loss_proportion_a: lpA1, loss_proportion_b: lpB1 } = lossProportion(sol1);
  248. loss_out_1 = goldA1 * lpA1 + goldB1 * lpB1;
  249. win_inner_1 = inner_base * (oddsC1 + 1)
  250. break;
  251. }
  252. win_inner_1 = fixFloat(win_inner_1);
  253. const {
  254. cross_type: crossType2,
  255. odds_side_a: oddsA2,
  256. odds_side_b: oddsB2,
  257. odds_side_c: oddsC2,
  258. rebate_side_a: rebateA2,
  259. rebate_side_b: rebateB2,
  260. rebate_side_c: rebateC2,
  261. inner_index: inner_index_2,
  262. } = sol2;
  263. const {
  264. gold_side_a: goldA2,
  265. gold_side_b: goldB2,
  266. gold_side_c: goldC2,
  267. } = calcGoldsWithTarget({ ...sol2, inner_base, win_target, loss_out: loss_out_1 });
  268. let loss_out_2 = 0, win_inner_2 = 0, inner_base_key;
  269. switch (inner_index_2) {
  270. case 0:
  271. inner_base_key = 'goldA2';
  272. loss_out_2 = inner_base + goldB2 * (1 - rebateB2) + goldC2 * (1 - rebateC2) + loss_out_1;
  273. win_inner_2 = win_inner_1 * (oddsA2 + 1);
  274. break;
  275. case 1:
  276. inner_base_key = 'goldB2';
  277. loss_out_2 = inner_base + goldA2 * (1 - rebateA2) + goldC2 * (1 - rebateC2) + loss_out_1;
  278. win_inner_2 = win_inner_1 * (oddsB2 + 1);
  279. break;
  280. case 2:
  281. const { loss_proportion_a: lpA2, loss_proportion_b: lpB2 } = lossProportion(sol2);
  282. inner_base_key = 'goldC2';
  283. loss_out_2 = inner_base + goldA2 * lpA2 + goldB2 * lpB2 + loss_out_1;
  284. win_inner_2 = win_inner_1 * (oddsC2 + 1);
  285. break;
  286. }
  287. const win_inner = fixFloat(win_inner_2 - loss_out_2);
  288. const goldsInfo = { goldA1, goldB1, goldC1, goldA2, goldB2, goldC2 }
  289. if (goldsInfo[inner_base_key]) {
  290. goldsInfo[inner_base_key] = win_inner_1;
  291. }
  292. const result = {
  293. bet_info: [
  294. {
  295. cross_type: crossType1,
  296. gold_side_a: goldsInfo.goldA1,
  297. gold_side_b: goldsInfo.goldB1,
  298. gold_side_c: goldsInfo.goldC1,
  299. odds_side_a: oddsA1,
  300. odds_side_b: oddsB1,
  301. odds_side_c: oddsC1,
  302. rebate_side_a: rebateA1,
  303. rebate_side_b: rebateB1,
  304. rebate_side_c: rebateC1,
  305. inner_index: inner_index_1,
  306. },
  307. {
  308. cross_type: crossType2,
  309. gold_side_a: goldsInfo.goldA2,
  310. gold_side_b: goldsInfo.goldB2,
  311. gold_side_c: goldsInfo.goldC2,
  312. odds_side_a: oddsA2,
  313. odds_side_b: oddsB2,
  314. odds_side_c: oddsC2,
  315. rebate_side_a: rebateA2,
  316. rebate_side_b: rebateB2,
  317. rebate_side_c: rebateC2,
  318. inner_index: inner_index_2,
  319. }
  320. ],
  321. win_inner,
  322. inner_base,
  323. inner_rebate,
  324. }
  325. return result;
  326. }
  327. /**
  328. * 根据单关盈亏计算综合利润
  329. */
  330. const calcTotalProfit = (sol1, sol2, inner_base, inner_rebate) => {
  331. const rebateInner = inner_base * inner_rebate;
  332. const winTarget1 = fixFloat(sol1.win_average - rebateInner);
  333. const winTarget2 = fixFloat(sol2.win_average - rebateInner);
  334. const winTarget = fixFloat(Math.min(winTarget1, winTarget2));
  335. const win1 = calcWinResultWithTarget({ inner_base, inner_rebate, win_target: winTarget1, sol1, sol2 })?.win_inner;
  336. const win2 = calcWinResultWithTarget({ inner_base, inner_rebate, win_target: winTarget2, sol1, sol2 })?.win_inner;
  337. const win_inner = fixFloat(Math.max(win1, win2), 2);
  338. const start = Math.max(winTarget, win_inner);
  339. const end = Math.min(winTarget, win_inner);
  340. const result = [];
  341. for (let i = start; i > end; i--) {
  342. const win_target = i;
  343. const goldsInfo = calcWinResultWithTarget({ inner_base, inner_rebate, win_target, sol1, sol2 });
  344. const { win_inner, ...goldsRest } = goldsInfo;
  345. const win_diff = Math.abs(fixFloat(win_target - goldsInfo.win_inner));
  346. const lastResult = result.at(-1);
  347. if (!lastResult?.win_diff || win_diff < lastResult.win_diff) {
  348. result.push({ win_target: fixFloat(win_target + rebateInner), win_diff, win_inner: fixFloat(win_inner + rebateInner), ...goldsRest });
  349. }
  350. else {
  351. break;
  352. }
  353. }
  354. return result.at(-1);
  355. }
  356. /**
  357. * ----------------------------
  358. * 计算第一关锁定之后的新利润
  359. * 第一关过关后,重新计算第二关金额
  360. * 结合第一关亏损计算第二关新利润
  361. * ----------------------------
  362. */
  363. /**
  364. * 计算第二关利润
  365. */
  366. const calcSecondProfit = (betInfo) => {
  367. const {
  368. inner_index, inner_odds_first,
  369. odds_side_a: a, odds_side_b: b, odds_side_c: c,
  370. } = betInfo;
  371. let odds_side_a, odds_side_b, odds_side_c;
  372. switch (inner_index) {
  373. case 0:
  374. odds_side_a = fixFloat((a+1) * (inner_odds_first+1) - 1);
  375. odds_side_b = b;
  376. odds_side_c = c;
  377. break;
  378. case 1:
  379. odds_side_a = a;
  380. odds_side_b = fixFloat((b+1) * (inner_odds_first+1) - 1);
  381. odds_side_c = c;
  382. break;
  383. case 2:
  384. odds_side_a = a;
  385. odds_side_b = b;
  386. odds_side_c = fixFloat((c+1) * (inner_odds_first+1) - 1);
  387. break;
  388. }
  389. return eventSolutions({ ...betInfo, odds_side_a, odds_side_b, odds_side_c }, true);
  390. }
  391. /**
  392. * 结合第一关亏损计算第二关新利润
  393. */
  394. const calcTotalProfitWithFixedFirst = (betInfo1, betInfo2, inner_base, inner_rebate) => {
  395. const {
  396. cross_type: crossType1,
  397. inner_index: inner_index_1,
  398. gold_side_a: goldA1,
  399. gold_side_b: goldB1,
  400. gold_side_c: goldC1,
  401. odds_side_a: oddsA1,
  402. odds_side_b: oddsB1,
  403. odds_side_c: oddsC1,
  404. rebate_side_a: rebateA1,
  405. rebate_side_b: rebateB1,
  406. rebate_side_c: rebateC1,
  407. } = betInfo1;
  408. let loss_out_1 = 0, inner_ref_value = 0, inner_odds_1 = 0;
  409. switch (inner_index_1) {
  410. case 0:
  411. loss_out_1 = goldB1 * (1 - rebateB1) + goldC1 * (1 - rebateC1);
  412. inner_ref_value = goldA1;
  413. inner_odds_1 = oddsA1;
  414. break;
  415. case 1:
  416. loss_out_1 = goldA1 * (1 - rebateA1) + goldC1 * (1 - rebateC1);
  417. inner_ref_value = goldB1;
  418. inner_odds_1 = oddsB1;
  419. break;
  420. case 2:
  421. const { loss_proportion_a: lpA1, loss_proportion_b: lpB1 } = lossProportion(betInfo1);
  422. loss_out_1 = goldA1 * lpA1 + goldB1 * lpB1;
  423. inner_ref_value = goldC1;
  424. inner_odds_1 = oddsC1;
  425. break;
  426. }
  427. if (inner_base && inner_base != inner_ref_value) {
  428. Logs.out('inner_base is not equal to inner_ref_value', inner_base, inner_ref_value);
  429. throw new Error('内盘基准额度和内盘索引额度不一致');
  430. }
  431. const profitInfo = calcSecondProfit({ ...betInfo2, inner_base, inner_odds_first: inner_odds_1, inner_rebate });
  432. // profitInfo.win_side_a = fixFloat(profitInfo.win_side_a - loss_out_1);
  433. // profitInfo.win_side_b = fixFloat(profitInfo.win_side_b - loss_out_1);
  434. // profitInfo.win_side_c = fixFloat(profitInfo.win_side_c - loss_out_1);
  435. profitInfo.win_average = fixFloat(profitInfo.win_average - loss_out_1);
  436. return profitInfo;
  437. }
  438. module.exports = { calcTotalProfit, calcTotalProfitWithFixedFirst };