utils.rs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. use std::collections::VecDeque;
  2. use std::ops::{Div, Mul};
  3. use std::path::Path;
  4. use chrono::Utc;
  5. use rand::Rng;
  6. use rust_decimal::Decimal;
  7. use tokio::fs;
  8. use tokio::fs::File;
  9. use tokio::io::AsyncWriteExt;
  10. use tracing::{error};
  11. use global::public_params;
  12. // 生成订单的id,可以根据交易所名字来
  13. pub fn generate_client_id(exchange_name_some: Option<String>) -> String {
  14. // 交易所名字获取
  15. let mut exchange_name = exchange_name_some.unwrap_or("".to_string());
  16. // 0-1000的随机数
  17. let rand_num = rand::thread_rng().gen_range(1..1000);
  18. // 随机时间戳
  19. let in_ms = Utc::now().timestamp_micros();
  20. let time_str = in_ms.to_string();
  21. let time_slice = &time_str[10..];
  22. // 火币只能纯数字
  23. if exchange_name == "htx" {
  24. let mut rng = rand::thread_rng(); // 获取一个随机数生成器
  25. let random_number = rng.gen_range(1000..10000); // 生成一个四位随机数
  26. exchange_name = random_number.to_string();
  27. }
  28. let result = format!("{}{}{}{}", "1", time_slice, rand_num, exchange_name);
  29. return result;
  30. }
  31. // 大小限定:将num限定在lower_limit和upper_limit范围内
  32. pub fn clip(num: Decimal, lower_limit: Decimal, upper_limit: Decimal) -> Decimal {
  33. if num.ge(&upper_limit) {
  34. return upper_limit;
  35. }
  36. if num.le(&lower_limit) {
  37. return lower_limit;
  38. }
  39. num
  40. }
  41. // 步长修复数量
  42. pub fn fix_amount(amount: Decimal, step_size: Decimal) -> Decimal {
  43. // 思路:做除法后取整,然后使用这个值乘以步长
  44. amount.div(step_size).floor().mul(step_size)
  45. }
  46. // 根据最小下单价值获取下单数量
  47. // pub fn get_amount_by_min_amount_value(min_amount_value: Decimal,
  48. // order_price: Decimal,
  49. // step_size: Decimal) -> Decimal {
  50. // // ceil可以向上取整
  51. // let amount_unit = ((min_amount_value / order_price) / step_size).ceil();
  52. //
  53. // amount_unit * step_size
  54. // }
  55. // 步长修复价格
  56. pub fn fix_price(amount: Decimal, tick_size: Decimal) -> Decimal {
  57. // 思路:做除法后四舍五入,然后使用这个值乘以步长
  58. amount.div(tick_size).round().mul(tick_size)
  59. }
  60. // 每秒请求频率
  61. pub fn get_limit_requests_num_per_second(exchange: String) -> i64 {
  62. if exchange.eq("kucoin_spot") {
  63. return public_params::KUCOIN_SPOT_LIMIT * public_params::RATIO;
  64. } else if exchange.eq("kucoin_usdt_swap") {
  65. return public_params::KUCOIN_USDT_SWAP_LIMIT * public_params::RATIO;
  66. } else if exchange.eq("binance_usdt_swap") {
  67. return public_params::BINANCE_USDT_SWAP_LIMIT * public_params::RATIO;
  68. } else if exchange.eq("binance_spot") {
  69. return public_params::BINANCE_SPOT_LIMIT * public_params::RATIO;
  70. } else if exchange.eq("gate_spot") {
  71. return public_params::GATE_SPOT_LIMIT * public_params::RATIO;
  72. } else if exchange.eq("gate_usdt_swap") {
  73. return public_params::GATE_USDT_SWAP_LIMIT * public_params::RATIO;
  74. } else if exchange.eq("coinex_usdt_swap") {
  75. return public_params::COINEX_USDT_SWAP_LIMIT * public_params::RATIO;
  76. } else if exchange.eq("coinex_spot") {
  77. return public_params::COINEX_SPOT_LIMIT * public_params::RATIO;
  78. } else if exchange.eq("okex_usdt_swap") {
  79. return public_params::OKEX_USDT_SWAP_LIMIT * public_params::RATIO;
  80. } else if exchange.eq("bitget_spot") {
  81. return public_params::BITGET_USDT_SPOT_LIMIT * public_params::RATIO;
  82. } else if exchange.eq("bitget_usdt_swap") {
  83. return public_params::BITGET_USDT_SWAP_LIMIT * public_params::RATIO;
  84. } else if exchange.eq("bybit_usdt_swap"){
  85. return public_params::BYBIT_USDT_SWAP_LIMIT * public_params::RATIO;
  86. } else if exchange.eq("htx_usdt_swap") {
  87. return public_params::HTX_USDT_SWAP_LIMIT * public_params::RATIO;
  88. } else if exchange.eq("mexc_usdt_swap") {
  89. return public_params::MEXC_USDT_SWAP_LIMIT * public_params::RATIO;
  90. } else {
  91. error!("限频规则(ratio)未找到,请检查配置!");
  92. panic!("限频规则(ratio)未找到,请检查配置!");
  93. }
  94. }
  95. // 每秒下单请求频率
  96. pub fn get_limit_order_requests_num_per_second(exchange: String) -> i64 {
  97. if exchange.eq("kucoin_spot") {
  98. return public_params::KUCOIN_SPOT_LIMIT
  99. } else if exchange.eq("kucoin_usdt_swap") {
  100. return public_params::KUCOIN_USDT_SWAP_LIMIT
  101. } else if exchange.eq("binance_usdt_swap") {
  102. return public_params::BINANCE_USDT_SWAP_LIMIT
  103. } else if exchange.eq("binance_spot") {
  104. return public_params::BINANCE_SPOT_LIMIT
  105. } else if exchange.eq("gate_spot") {
  106. return public_params::GATE_SPOT_LIMIT
  107. } else if exchange.eq("gate_usdt_swap") {
  108. return public_params::GATE_USDT_SWAP_LIMIT
  109. } else if exchange.eq("coinex_usdt_swap") {
  110. return public_params::COINEX_USDT_SWAP_LIMIT
  111. } else if exchange.eq("coinex_spot") {
  112. return public_params::COINEX_SPOT_LIMIT
  113. } else if exchange.eq("okex_usdt_swap") {
  114. return public_params::OKEX_USDT_SWAP_LIMIT
  115. } else if exchange.eq("bitget_spot") {
  116. return public_params::BITGET_USDT_SPOT_LIMIT
  117. } else if exchange.eq("bitget_usdt_swap") {
  118. return public_params::BITGET_USDT_SWAP_LIMIT
  119. } else if exchange.eq("bybit_usdt_swap") {
  120. return public_params::BYBIT_USDT_SWAP_LIMIT
  121. } else if exchange.eq("htx_usdt_swap") {
  122. return public_params::HTX_USDT_SWAP_LIMIT
  123. } else if exchange.eq("mexc_usdt_swap") {
  124. return public_params::MEXC_USDT_SWAP_LIMIT
  125. } else {
  126. error!("限频规则(limit)未找到,请检查配置!");
  127. panic!("限频规则(limit)未找到,请检查配置!");
  128. }
  129. }
  130. pub async fn write_to_file(json_data: &String, file_path: String) {
  131. // 尝试创建文件路径
  132. if let Err(e) = fs::create_dir_all(
  133. // 获取文件目录路径
  134. Path::new(&file_path)
  135. .parent() // 获取父目录(即文件路径除去文件名后的部分)
  136. .unwrap_or_else(|| Path::new("")), // 如果没有父目录,使用当前目录
  137. )
  138. .await
  139. {
  140. // 如果创建路径失败,打印错误日志
  141. error!("创建目录错误: {:?}", e);
  142. return; // 结束任务
  143. }
  144. // 异步地执行文件写入操作
  145. if let Err(e) = async {
  146. let mut file = File::create(&file_path).await?;
  147. file.write_all(json_data.as_bytes()).await?;
  148. Result::<(), std::io::Error>::Ok(())
  149. }
  150. .await
  151. {
  152. // 如果发生错误,只打印错误日志
  153. error!("json db写入错误: {:?}", e);
  154. }
  155. }
  156. pub fn build_html_file(data_c: &Vec<VecDeque<Option<Decimal>>>) -> String {
  157. let temp_json_str = serde_json::to_string(&data_c).unwrap();
  158. let str1 = r##"
  159. <!DOCTYPE html>
  160. <html lang="en">
  161. <head>
  162. <meta charset="UTF-8" />
  163. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  164. <title>Document</title>
  165. <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
  166. <script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.3/echarts.min.js"></script>
  167. <script src="https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.10/dayjs.min.js"></script>
  168. <style>
  169. * {
  170. margin: 0;
  171. padding: 0;
  172. }
  173. #echarts {
  174. margin: 50px auto 0;
  175. width: calc(100vw - 100px);
  176. height: 1030px;
  177. }
  178. .page-group-wp {
  179. padding: 20px 40px;
  180. display: flex;
  181. justify-content: space-between;
  182. }
  183. .page-button {
  184. cursor: pointer;
  185. border: 1px solid #16b777;
  186. color: #16b777;
  187. padding: 2px 10px;
  188. user-select: none;
  189. }
  190. .disabled {
  191. border: 1px solid #ccc;
  192. color: #ccc;
  193. cursor: not-allowed;
  194. }
  195. </style>
  196. </head>
  197. <body>
  198. <div class="page-group-wp">
  199. <div class="page-button upper-btn">上一页</div>
  200. <div class="show-page">第1/1页</div>
  201. <div class="page-button next-btn">下一页</div>
  202. </div>
  203. <div id="echarts"></div>
  204. </body>
  205. <script>
  206. let initData = "##;
  207. let str2 = r##";
  208. let init_echarts_data = (data) => {
  209. const xData = data[0].map((item) => dayjs(item * 1).format("MM-DD HH:mm:ss:SSS"));
  210. let inventory = 0;
  211. let inventoryList = [];
  212. data[10].map((item, index) => {
  213. if (inventory != item) {
  214. inventory = item;
  215. let color = "";
  216. if(item > 0) color = "#F2495E"
  217. if(item < 0) color = "#13BF86"
  218. inventoryList.push({ name: "inventory",itemStyle:{color: color}, value: item, xAxis: index, yAxis: data[1][index] });
  219. }
  220. });
  221. return {
  222. dataZoom: [
  223. {
  224. type: "inside",
  225. xAxisIndex: [0, 1, 2, 3, 4],
  226. start: 0,
  227. end: 100,
  228. },
  229. {
  230. xAxisIndex: [0, 1, 2, 3, 4],
  231. start: 0,
  232. end: 100,
  233. },
  234. ],
  235. tooltip: {
  236. trigger: "axis",
  237. formatter: (value) => {
  238. return `时间:${value[0].name}<br />${value.map((item) => `${item.marker}${item.seriesName}:${item.value}`).join("<br/>")}`;
  239. },
  240. axisPointer: {
  241. animation: false,
  242. },
  243. },
  244. toolbox: {
  245. feature: {
  246. dataZoom: {},
  247. brush: {
  248. type: ["rect", "clear"],
  249. },
  250. },
  251. },
  252. legend: [
  253. {
  254. data: ["mid_price", "ask_price", "bid_price", "optimal_ask_price", "optimal_bid_price", "fair_price"],
  255. top: "230px",
  256. },
  257. {
  258. data: ["mp", "ap", "bp"],
  259. top: "480px",
  260. },
  261. {
  262. data: ["sigma_square"],
  263. top: "630px",
  264. },
  265. {
  266. data: ["delay"],
  267. top: "780px",
  268. },
  269. {
  270. data: ["kappa"],
  271. top: "930px",
  272. },
  273. ],
  274. grid: [
  275. {
  276. top: "50px",
  277. left: "60px",
  278. right: "60px",
  279. height: "150px", // 主图高度
  280. },
  281. {
  282. top: "300px",
  283. left: "60px",
  284. right: "60px",
  285. height: "150px",
  286. },
  287. {
  288. top: "550px",
  289. left: "60px",
  290. right: "60px",
  291. height: "50px",
  292. },
  293. {
  294. top: "700px",
  295. left: "60px",
  296. right: "60px",
  297. height: "50px",
  298. },
  299. {
  300. top: "850px",
  301. left: "60px",
  302. right: "60px",
  303. height: "50px",
  304. },
  305. ],
  306. xAxis: [
  307. {
  308. type: "category",
  309. data: xData,
  310. },
  311. {
  312. gridIndex: 1, // 第1个网格的 x 轴
  313. type: "category",
  314. data: xData,
  315. },
  316. {
  317. gridIndex: 2, // 第2个网格的 x 轴
  318. type: "category",
  319. data: xData,
  320. },
  321. {
  322. gridIndex: 3, // 第3个网格的 x 轴
  323. type: "category",
  324. data: xData,
  325. },
  326. {
  327. gridIndex: 4, // 第4个网格的 x 轴
  328. type: "category",
  329. data: xData,
  330. },
  331. ],
  332. yAxis: [
  333. {
  334. type: "value",
  335. min: "dataMin",
  336. },
  337. {
  338. gridIndex: 1, // 第1个网格的 y 轴
  339. type: "value",
  340. min: "dataMin",
  341. },
  342. {
  343. gridIndex: 2, // 第2个网格的 y 轴
  344. type: "value",
  345. min: "dataMin",
  346. },
  347. {
  348. gridIndex: 3, // 第3个网格的 y 轴
  349. type: "value",
  350. min: "dataMin",
  351. },
  352. {
  353. gridIndex: 4, // 第4个网格的 y 轴
  354. type: "value",
  355. min: "dataMin",
  356. },
  357. ],
  358. series: [
  359. {
  360. name: "mid_price",
  361. type: "line",
  362. showSymbol: false,
  363. data: data[1],
  364. markPoint: {
  365. symbolSize: 20,
  366. data: inventoryList,
  367. },
  368. },
  369. {
  370. name: "ask_price",
  371. type: "line",
  372. showSymbol: false,
  373. // data: data[2],
  374. data: [],
  375. },
  376. {
  377. name: "bid_price",
  378. type: "line",
  379. showSymbol: false,
  380. // data: data[3],
  381. data: [],
  382. },
  383. {
  384. name: "optimal_ask_price",
  385. type: "line",
  386. showSymbol: false,
  387. // data: data[8],
  388. data: [],
  389. lineStyle: {
  390. type: "dashed",
  391. },
  392. },
  393. {
  394. name: "optimal_bid_price",
  395. type: "line",
  396. showSymbol: false,
  397. // data: data[9],
  398. data: [],
  399. lineStyle: {
  400. type: "dashed",
  401. },
  402. },
  403. {
  404. name: "fair_price",
  405. type: "line",
  406. showSymbol: false,
  407. data: data[15],
  408. },
  409. {
  410. name: "mp",
  411. type: "line",
  412. xAxisIndex: 1,
  413. yAxisIndex: 1,
  414. showSymbol: false,
  415. data: data[5],
  416. },
  417. {
  418. name: "ap",
  419. type: "line",
  420. xAxisIndex: 1,
  421. yAxisIndex: 1,
  422. showSymbol: false,
  423. data: data[6],
  424. },
  425. {
  426. name: "bp",
  427. type: "line",
  428. xAxisIndex: 1,
  429. yAxisIndex: 1,
  430. showSymbol: false,
  431. data: data[7],
  432. },
  433. {
  434. name: "sigma_square",
  435. type: "line",
  436. xAxisIndex: 2,
  437. yAxisIndex: 2,
  438. showSymbol: false,
  439. data: data[11],
  440. },
  441. {
  442. name: "delay",
  443. type: "line",
  444. xAxisIndex: 3,
  445. yAxisIndex: 3,
  446. showSymbol: false,
  447. data: data[12],
  448. },
  449. {
  450. name: "kappa",
  451. type: "line",
  452. xAxisIndex: 4,
  453. yAxisIndex: 4,
  454. showSymbol: false,
  455. data: data[13],
  456. },
  457. ],
  458. };
  459. };
  460. let update_echarts_data = (data) => {
  461. const xData = data[0].map((item) => dayjs(item * 1).format("MM-DD HH:mm:ss:SSS"));
  462. let inventory = 0;
  463. let inventoryList = [];
  464. data[10].map((item, index) => {
  465. if (inventory != item) {
  466. inventory = item;
  467. let color = "";
  468. if(item > 0) color = "#F2495E"
  469. if(item < 0) color = "#13BF86"
  470. inventoryList.push({ name: "inventory",itemStyle:{color: color}, value: item, xAxis: index, yAxis: data[1][index] });
  471. }
  472. });
  473. return {
  474. xAxis: [{ data: xData }, { data: xData }, { data: xData }, { data: xData }, { data: xData }],
  475. series: [
  476. {
  477. data: data[1],
  478. markPoint: {
  479. data: inventoryList,
  480. },
  481. },
  482. // { data: data[2] },
  483. // { data: data[3] },
  484. // { data: data[8] },
  485. // { data: data[9] },
  486. { data: [] },
  487. { data: [] },
  488. { data: [] },
  489. { data: [] },
  490. { data: data[15] },
  491. { data: data[5] },
  492. { data: data[6] },
  493. { data: data[7] },
  494. { data: data[11] },
  495. { data: data[12] },
  496. { data: data[13] },
  497. ],
  498. };
  499. };
  500. let page = 1;
  501. let maxPage = initData[0].length % 100000 > 0 ? Math.ceil(initData[0].length / 100000) : initData[0].length / 100000 || 1;
  502. let showData = initData.map((item) => item.slice((page - 1) * 100000, page * 100000 - 1));
  503. let chartDom = document.getElementById("echarts");
  504. let myChart = echarts.init(chartDom);
  505. let initOption = init_echarts_data(showData);
  506. initOption && myChart.setOption(initOption);
  507. let updata_page = () => {
  508. $(".show-page").eq(0).html(`第${page}/${maxPage}页`);
  509. $(".next-btn").eq(0).removeClass("disabled");
  510. $(".upper-btn").eq(0).removeClass("disabled");
  511. if (page >= maxPage) $(".next-btn").eq(0).addClass("disabled");
  512. if (page <= 1) $(".upper-btn").eq(0).addClass("disabled");
  513. };
  514. updata_page();
  515. let update_echarts = () => {
  516. showData = initData.map((item) => item.slice((page - 1) * 100000, page * 100000 + 99999));
  517. let option = update_echarts_data(showData);
  518. myChart.setOption(option, false, true);
  519. };
  520. $(".next-btn")
  521. .eq(0)
  522. .click(() => {
  523. if (page < maxPage) {
  524. page += 1;
  525. update_echarts();
  526. updata_page();
  527. }
  528. });
  529. $(".upper-btn")
  530. .eq(0)
  531. .click(() => {
  532. if (page > 1) {
  533. page -= 1;
  534. update_echarts();
  535. updata_page();
  536. }
  537. });
  538. </script>
  539. </html>"##;
  540. format!("{}{}{}", str1, temp_json_str, str2)
  541. }
  542. #[cfg(test)]
  543. mod tests {
  544. use chrono::Utc;
  545. use rust_decimal_macros::dec;
  546. use crate::utils::{clip, fix_amount, fix_price, generate_client_id};
  547. #[test]
  548. fn clip_test() {
  549. let num = dec!(11);
  550. // let num2 = dec!(2);
  551. let lower_limit = dec!(3);
  552. let upper_limit = dec!(6);
  553. println!("mum: {}", clip(num, lower_limit, upper_limit));
  554. println!("mum2: {}", clip(num, lower_limit, upper_limit));
  555. }
  556. #[test]
  557. fn generate_client_id_test() {
  558. println!("{}", generate_client_id(None));
  559. println!("{}", generate_client_id(Some("binance".to_string())));
  560. }
  561. #[test]
  562. fn fix_amount_test() {
  563. println!("{}", fix_amount(dec!(0.9), dec!(0.04)));
  564. println!("{}", fix_amount(dec!(1.0), dec!(0.1)));
  565. println!("{}", fix_amount(dec!(0.9), dec!(0.05)));
  566. println!("{}", fix_amount(dec!(1), dec!(0.1)));
  567. println!("{}", fix_amount(dec!(0.01), dec!(0.05)));
  568. }
  569. #[test]
  570. fn fix_price_test() {
  571. println!("{}", fix_price(dec!(1), dec!(0.1)));
  572. println!("{}", fix_price(dec!(0.9), dec!(2.0)));
  573. println!("{}", fix_price(dec!(1.1), dec!(0.1)));
  574. println!("{}", fix_price(dec!(1.2), dec!(0.5)));
  575. println!("{}", fix_price(dec!(4999.99), dec!(0.5)));
  576. }
  577. #[test]
  578. fn utc_timestamp_test() {
  579. let now = Utc::now();
  580. println!("timestamp: {}", now.timestamp());
  581. println!("timestamp_millis: {}", now.timestamp_millis());
  582. println!("timestamp_micros: {}", now.timestamp_micros());
  583. println!("timestamp_nanos: {}", now.timestamp_nanos_opt().unwrap());
  584. }
  585. }