trangleCalc.js 9.2 KB

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