trangleCalc.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. const Logs = require('../libs/logs');
  2. const IOR_KEYS_MAP = require('./iorKeys');
  3. const GOLD_BASE = 10000;
  4. const WIN_MIN = process.env.NODE_ENV == 'development' ? -10000 : -1000;
  5. const JC_REBATE_RATIO = 0;
  6. /**
  7. * 筛选最优赔率
  8. */
  9. function getOptimalSelections(data, combos) {
  10. const results = [];
  11. combos.forEach((rule, index) => {
  12. let validOptions = [];
  13. for (let i = 0; i < 3; i++) {
  14. const jcIndex = i;
  15. const selection = [];
  16. let isValid = true;
  17. for (let j = 0; j < 3; j++) {
  18. const key = rule[j];
  19. const item = data[key];
  20. if (!item) {
  21. isValid = false;
  22. break;
  23. }
  24. if (j === jcIndex) {
  25. if (!('jc' in item)) {
  26. isValid = false;
  27. break;
  28. }
  29. selection.push({
  30. k: key,
  31. p: 'jc',
  32. v: item.jc,
  33. o: item,
  34. });
  35. }
  36. else {
  37. const candidates = ['ps', 'ob'].filter((k) => k in item);
  38. if (candidates.length === 0) {
  39. isValid = false;
  40. break;
  41. }
  42. const best = candidates.reduce((a, b) => item[a] > item[b] ? a : b);
  43. selection.push({
  44. k: key,
  45. p: best,
  46. v: item[best],
  47. o: item,
  48. });
  49. }
  50. }
  51. if (isValid) {
  52. validOptions.push(selection);
  53. }
  54. }
  55. if (validOptions.length > 0) {
  56. const iors = validOptions.reduce((a, b) => {
  57. const sumA = a.reduce((sum, x) => sum + x.v, 0);
  58. const sumB = b.reduce((sum, x) => sum + x.v, 0);
  59. return sumA > sumB ? a : b;
  60. });
  61. results.push({ rule, iors, index });
  62. }
  63. });
  64. return results;
  65. }
  66. /**
  67. * 精确浮点数字
  68. * @param {number} number
  69. * @param {number} x
  70. * @returns {number}
  71. */
  72. const fixFloat = (number, x=2) => {
  73. return parseFloat(number.toFixed(x));
  74. }
  75. /**
  76. * 计算盈利
  77. */
  78. const triangleProfitCalc = (goldsInfo, oddsOption) => {
  79. const {
  80. gold_side_a: x,
  81. gold_side_b: y,
  82. gold_side_m: z,
  83. odds_side_a: a,
  84. odds_side_b: b,
  85. odds_side_m: c
  86. } = goldsInfo;
  87. const { crossType, jcIndex } = oddsOption;
  88. /**
  89. * crossType:
  90. * la: 全输
  91. * wa: 全赢
  92. * lh: 半输
  93. * wh: 半赢
  94. * dr: 和局
  95. * la_wh_wa, la_dr_wa, la_lh_wa, lh_dr_wa, lh_lh_wa, la_la_wa
  96. */
  97. let jc_rebate = 0;
  98. if (jcIndex == 0) {
  99. jc_rebate = x * JC_REBATE_RATIO;
  100. }
  101. else if (jcIndex == 1) {
  102. jc_rebate = y * JC_REBATE_RATIO;
  103. }
  104. else if (jcIndex == 2) {
  105. jc_rebate = z * JC_REBATE_RATIO;
  106. }
  107. let win_side_a = 0, win_side_b = 0, win_side_m = 0;
  108. win_side_a = a * x - y - z;
  109. win_side_b = b * y - x - z;
  110. switch (crossType) {
  111. case 'la_wh_wa': // 全输 半赢 全赢
  112. win_side_m = c*z - x + b*y/2;
  113. break;
  114. case 'la_dr_wa': // 全输 和局 全赢
  115. win_side_m = c*z - x;
  116. break;
  117. case 'la_lh_wa': // 全输 半输 全赢
  118. win_side_m = c*z - x - y/2;
  119. break;
  120. case 'lh_dr_wa': // 半输 和局 全赢
  121. win_side_m = c*z - x/2;
  122. break;
  123. case 'lh_lh_wa': // 半输 半输 全赢
  124. win_side_m = c*z - x/2 - y/2;
  125. break;
  126. case 'la_la_wa': // 全输 全输 全赢
  127. win_side_m = c*z - x - y;
  128. break;
  129. }
  130. win_side_a = fixFloat(win_side_a + jc_rebate);
  131. win_side_b = fixFloat(win_side_b + jc_rebate);
  132. win_side_m = fixFloat(win_side_m + jc_rebate);
  133. const win_average = fixFloat((win_side_a + win_side_b + win_side_m) / 3);
  134. return { win_side_a, win_side_b, win_side_m, win_average }
  135. }
  136. const triangleGoldCalc = (oddsInfo, oddsOption) => {
  137. const { odds_side_a: a, odds_side_b: b, odds_side_m: c } = oddsInfo;
  138. if (!a || !b || !c) {
  139. return;
  140. }
  141. const { crossType, jcIndex } = oddsOption;
  142. let x = GOLD_BASE;
  143. let y = (a + 1) * x / (b + 1);
  144. let z;
  145. switch (crossType) {
  146. case 'la_wh_wa': // 全输 半赢 全赢
  147. z = b * y / 2 / (c + 1);
  148. break;
  149. case 'la_dr_wa': // 全输 和局 全赢
  150. z = b * y / (c + 1);
  151. break;
  152. case 'la_lh_wa': // 全输 半输 全赢
  153. z = (2 * b + 1) * y / 2 / (c + 1);
  154. break;
  155. case 'lh_dr_wa': // 半输 和局 全赢
  156. z = (b * y - x / 2) / (c + 1);
  157. break;
  158. case 'lh_lh_wa': // 半输 半输 全赢
  159. z = (b * y + y / 2 - x / 2) / (c + 1);
  160. break;
  161. case 'la_la_wa': // 全输 全输 全赢
  162. z = (b + 1) * y / (c + 1);
  163. break;
  164. default:
  165. z = 0;
  166. }
  167. if (jcIndex == 1) {
  168. const scale = GOLD_BASE / y;
  169. x = x * scale;
  170. y = GOLD_BASE;
  171. z = z * scale;
  172. }
  173. else if (jcIndex == 2) {
  174. const scale = GOLD_BASE / z;
  175. x = x * scale;
  176. y = y * scale;
  177. z = GOLD_BASE;
  178. }
  179. return {
  180. gold_side_a: x,
  181. gold_side_b: fixFloat(y),
  182. gold_side_m: fixFloat(z),
  183. odds_side_a: a,
  184. odds_side_b: b,
  185. odds_side_m: c,
  186. };
  187. }
  188. const eventSolutions = (oddsInfo, oddsOption) => {
  189. const goldsInfo = triangleGoldCalc(oddsInfo, oddsOption);
  190. if (!goldsInfo) {
  191. return;
  192. }
  193. const profitInfo = triangleProfitCalc(goldsInfo, oddsOption);
  194. // console.log(goldsInfo, profitInfo);
  195. return {
  196. ...goldsInfo,
  197. ...profitInfo,
  198. cross_type: oddsOption.crossType,
  199. jc_index: oddsOption.jcIndex,
  200. }
  201. }
  202. const eventsCombination = (passableEvents) => {
  203. const solutions = [];
  204. passableEvents.forEach(events => {
  205. const { odds, info } = events;
  206. Object.keys(IOR_KEYS_MAP).forEach(group => {
  207. const rules = IOR_KEYS_MAP[group];
  208. const optimalSelections = getOptimalSelections(odds, rules);
  209. optimalSelections.forEach(selection => {
  210. const { rule, iors, index } = selection;
  211. const [, , , crossType] = rule;
  212. const oddsSideA = iors[0];
  213. const oddsSideB = iors[1];
  214. const oddsSideM = iors[2];
  215. const jcIndex = iors.findIndex(item => item.p == 'jc');
  216. if (!oddsSideA || !oddsSideB || !oddsSideM) {
  217. return;
  218. }
  219. const cpr = [ oddsSideA, oddsSideB, oddsSideM ];
  220. const oddsInfo = {
  221. odds_side_a: fixFloat(oddsSideA.v - 1),
  222. odds_side_b: fixFloat(oddsSideB.v - 1),
  223. odds_side_m: fixFloat(oddsSideM.v - 1)
  224. };
  225. const oddsOption = { crossType, jcIndex };
  226. const sol = eventSolutions(oddsInfo, oddsOption);
  227. if (sol?.win_average > WIN_MIN) {
  228. const id = info.id;
  229. const keys = cpr.map(item => `${item.k}`).join('_');
  230. const sid = `${id}_${keys}`;
  231. const timestamp = Date.now();
  232. solutions.push({sid, sol, cpr, info, rule: `${group}:${index}`, timestamp});
  233. }
  234. });
  235. });
  236. });
  237. return solutions;
  238. }
  239. module.exports = eventsCombination;