hl_pr_utile.rs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. use std::collections::{BTreeMap, HashSet};
  2. use std::fs::File;
  3. use std::io::Write;
  4. use std::str::FromStr;
  5. use chrono::{NaiveDateTime, Utc};
  6. use handlebars::Handlebars;
  7. use rust_decimal::Decimal;
  8. use rust_decimal::prelude::ToPrimitive;
  9. use serde_json::{json, Value};
  10. use tracing::{error, info};
  11. use crate::export_template::template_ticker::ExportExchangeTickerInfo;
  12. use crate::handle_ticker;
  13. use crate::http::request::get;
  14. use crate::struct_standard::Trades;
  15. use crate::swap_okx::okx_swap_standard::SwapTrades;
  16. use crate::utils::utils::TickerConfigInfo;
  17. pub async fn export_ticker(symbol: &str, exchange: &str, time_str: &str) {
  18. //从今天算起
  19. let time_this = Utc::now().timestamp_millis();
  20. let mut name_html = "";
  21. let mut start_at = time_this - match time_str {
  22. "10M" => {
  23. name_html = "10分钟";
  24. (1000 * 60 * 10)
  25. }
  26. "20M" => {
  27. name_html = "20分钟";
  28. (1000 * 60 * 20)
  29. }
  30. "30M" => {
  31. name_html = "30分钟";
  32. (1000 * 60 * 30)
  33. }
  34. "1H" => {
  35. name_html = "1小时";
  36. (1000 * 60 * 60 * 1)
  37. }
  38. "10H" => {
  39. name_html = "10小时";
  40. (1000 * 60 * 60 * 10)
  41. }
  42. "1D" => {
  43. name_html = "1天";
  44. (1000 * 60 * 60 * 24)
  45. }
  46. "2D" => {
  47. name_html = "1天";
  48. (1000 * 60 * 60 * 24 * 2)
  49. }
  50. "3D" => {
  51. name_html = "3天";
  52. (1000 * 60 * 60 * 24 * 3)
  53. }
  54. "5D" => {
  55. name_html = "5天";
  56. (1000 * 60 * 60 * 24 * 5)
  57. }
  58. "1Z" => {
  59. name_html = "1周";
  60. (1000 * 60 * 60 * 24 * 7)
  61. }
  62. "2Z" => {
  63. name_html = "2周";
  64. (1000 * 60 * 60 * 24 * 7 * 2)
  65. }
  66. "4Z" => {
  67. name_html = "1个月";
  68. (1000 * 60 * 60 * 24 * 7 * 4)
  69. }
  70. _ => {
  71. info!("错误时间");
  72. return;
  73. }
  74. };
  75. start_at = start_at / 1000;
  76. let end_at = time_this.clone() / 1000;
  77. let recall_start_at = start_at;
  78. let mut exchange_list: BTreeMap<String, Vec<Value>> = BTreeMap::new();
  79. let exchange_up = exchange.to_uppercase();
  80. let exchange_result = match exchange_up.as_str() {
  81. "GATE" => {
  82. let mut start_at_ti = start_at;
  83. loop {
  84. let end_at_ti = start_at_ti + (20 * 60);
  85. if start_at_ti > end_at {
  86. break;
  87. }
  88. let time_str_s = NaiveDateTime::from_timestamp_millis(((start_at_ti) + 8 * 3600) * 1000).unwrap().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
  89. let time_str_e = NaiveDateTime::from_timestamp_millis(((end_at_ti) + 8 * 3600) * 1000).unwrap().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
  90. info!("----时间:{:?}---{:?}",time_str_s,time_str_e);
  91. //查询数据按照间隔20分钟
  92. let url = "https://api.gateio.ws/api/v4/futures/usdt/trades";
  93. let params = serde_json::json!({
  94. "contract": symbol,
  95. "from": start_at_ti,
  96. "to": end_at_ti,
  97. "to": end_at_ti,
  98. "limit": "1000"
  99. });
  100. let res_data = get(url, params.to_string(), None).await;
  101. if res_data.code == "200" {
  102. let res_data_str = res_data.data;
  103. // info!("body:{:?}",res_data_str);
  104. let json_value: serde_json::Value = serde_json::from_str(&res_data_str).unwrap();
  105. for rows in json_value.as_array() {
  106. let rows_c = rows.clone();
  107. info!("数据数量:{:?}",rows_c.len());
  108. for row in rows_c {
  109. // {"contract": String("XRP_USDT"), "create_time": Number(1703146260.4), "create_time_ms": Number(1703146260.4), "id": Number(24663517), "price": String("0.6163"), "size": Number(132)}
  110. // info!("数据:{:?}",row.clone());
  111. let row_c = row.clone();
  112. let create_time = format!("{:?}", row["create_time"].as_f64().unwrap().to_i64().unwrap_or(0)).clone();
  113. let k = create_time.as_str();
  114. let mut def: Vec<Value> = vec![];
  115. if exchange_list.contains_key(k) {
  116. match exchange_list.get(k) {
  117. None => {}
  118. Some(r) => {
  119. def = r.clone();
  120. }
  121. }
  122. }
  123. def.push(row_c);
  124. exchange_list.insert(k.parse().unwrap(), def);
  125. }
  126. }
  127. } else {
  128. info!("数据请求错误:");
  129. return;
  130. }
  131. start_at_ti = start_at_ti + (20 * 60);
  132. }
  133. // info!("数据:{:?}",exchange_list);
  134. info!("----横向图--交易量:");
  135. let mut exchange_list_sss: BTreeMap<String, Vec<Value>> = BTreeMap::new();
  136. let mut turnover_data: BTreeMap<String, Vec<Value>> = BTreeMap::new();
  137. let mut keys: Vec<String> = exchange_list.keys().cloned().collect();
  138. keys.sort_by(|&(ref a), &(ref b)| a.len().cmp(&b.len()));
  139. for k in keys.clone() {
  140. let time_str = NaiveDateTime::from_timestamp_millis((k.clone().parse::<i64>().unwrap() + 8 * 3600) * 1000).unwrap().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
  141. let data = exchange_list.get(k.clone().as_str()).unwrap();
  142. let mut str_array = vec![""];
  143. for d in data.clone() {
  144. str_array.push(" - ");
  145. }
  146. // info!("数据总数: {:?}: {:?}",time_str,str_array);
  147. let mut def: Vec<Value> = vec![];
  148. let size_k = format!("{:?}", data.len());
  149. if exchange_list_sss.contains_key(&size_k) {
  150. match exchange_list_sss.get(&size_k) {
  151. None => {}
  152. Some(r) => {
  153. def = r.clone();
  154. }
  155. }
  156. }
  157. def.extend(data.clone());
  158. exchange_list_sss.insert((str_array.len() - 1).to_string(), def);
  159. let mut def2: Vec<Value> = vec![];
  160. let size_k2 = format!("{:?}", time_str);
  161. if turnover_data.contains_key(&size_k2) {
  162. match turnover_data.get(&size_k2) {
  163. None => {}
  164. Some(r) => {
  165. def2 = r.clone();
  166. }
  167. }
  168. }
  169. def2.extend(data.clone());
  170. turnover_data.insert(String::from(&size_k2), def2);
  171. }
  172. //总结 交易量
  173. info!("总结:");
  174. let mut pancake_data_y: Vec<String> = vec![]; //{ value: 1048, name: 'Search Engine' },
  175. let mut pancake_data_x: Vec<usize> = vec![]; //{ value: 1048, name: 'Search Engine' },
  176. // let zzz = exchange_list_sss.keys();
  177. let mut keys: Vec<String> = exchange_list_sss.keys().cloned().collect();
  178. keys.sort_by(|&(ref a), &(ref b)| a.len().cmp(&b.len()));
  179. for k in keys {
  180. let data = exchange_list_sss.get(k.clone().as_str()).unwrap();
  181. let mut initiative_ticker_info = vec![];
  182. for d in data {
  183. let create_time = format!("{:?}", d["create_time"].as_f64().unwrap().to_i64().unwrap_or(0)).clone();
  184. let time_str = NaiveDateTime::from_timestamp_millis((create_time.clone().parse::<i64>().unwrap() + 8 * 3600) * 1000).unwrap().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
  185. initiative_ticker_info.push(time_str);
  186. };
  187. let mut seen = HashSet::new();
  188. initiative_ticker_info.retain(|item| seen.insert(item.clone()));
  189. let name = format!(" 交易量:{:?}", k.clone());
  190. pancake_data_y.push(name);
  191. pancake_data_x.push(initiative_ticker_info.len());
  192. info!("交易量:{:?},一共有{:?}个时间点:{:?}",k.clone(),initiative_ticker_info.len(),0);
  193. }
  194. //柱形图
  195. let mut turnover_data_y: Vec<f64> = vec![];
  196. let mut turnover_data_x: Vec<String> = vec![];
  197. let mut keys: Vec<String> = turnover_data.keys().cloned().collect();
  198. keys.sort_by(|&(ref a), &(ref b)| a.len().cmp(&b.len()));
  199. turnover_data_x = keys;
  200. for k in turnover_data_x.clone() {
  201. let rows = turnover_data.get(k.as_str()).unwrap();
  202. let mut val_y = 0.0;
  203. if rows.len() > 1 {
  204. let mut max_v = 0.0;
  205. let mut min_v = 0.0;
  206. for row in rows {
  207. let price = row["price"].as_str().unwrap().parse::<f64>().unwrap();
  208. // info!("price:{:?} " ,price);
  209. if max_v == 0.0{
  210. max_v = price
  211. }
  212. if min_v == 0.0{
  213. min_v = price
  214. }
  215. max_v = if price > max_v { price } else { max_v };
  216. min_v = if price < min_v { price } else { min_v };
  217. }
  218. val_y = format!("{:.5}", (max_v - min_v) / max_v).to_string().parse::<f64>().unwrap();
  219. }
  220. turnover_data_y.push(val_y);
  221. }
  222. export_html(name_html, pancake_data_y, pancake_data_x, turnover_data_y, turnover_data_x)
  223. }
  224. _ => {
  225. error!("交易所输入错误!");
  226. panic!("交易所输入错误!")
  227. }
  228. };
  229. }
  230. //生成html
  231. pub fn export_html(name_html: &str, pancake_data_y: Vec<String>, pancake_data_x: Vec<usize>, turnover_data_y: Vec<f64>, turnover_data_x: Vec<String>) {
  232. info!("正在生成网页,请稍后!");
  233. let path = format!("./数据分析图.html");
  234. // 创建 Handlebars 实例
  235. let mut handlebars = Handlebars::new();
  236. let json_data = json!({
  237. "pancake_data_y":pancake_data_y,
  238. "pancake_data_x":pancake_data_x,
  239. "turnover_data_y":turnover_data_y,
  240. "turnover_data_x":turnover_data_x,
  241. });
  242. info!("pancake_data:{:?}",json_data["pancake_data"].to_string());
  243. let data = serde_json::json!({
  244. "chart_title": format!("Ticker数据分析"),
  245. "name_html": name_html.to_string().clone(),
  246. });
  247. // HTML 模板
  248. let template = r#"
  249. <!DOCTYPE html>
  250. <html>
  251. <head>
  252. <meta charset="UTF-8">
  253. <title>{{chart_title}}</title>
  254. <script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.3/echarts.min.js"></script>
  255. <script src="https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.10/dayjs.min.js"></script>
  256. <style>
  257. * {
  258. margin: 0;
  259. padding: 0;
  260. }
  261. #main {
  262. margin: 50px auto 0;
  263. width: calc(100vw - 100px);
  264. height: calc(100vh - 100px);
  265. }
  266. #main2 {
  267. margin: 50px auto 0;
  268. width: calc(100vw - 100px);
  269. height: calc(100vh - 100px);
  270. }
  271. </style>
  272. </head>
  273. <body>
  274. <div id="main"></div>
  275. <div id="main2"></div>
  276. </body>
  277. <script>
  278. var exchangeColor = {binance: '#F4BC0C', gate: '#0068FF', okx: '#171F30'};
  279. var chartDom = document.getElementById('main');
  280. var chartDom2 = document.getElementById('main2');
  281. var myChart2 = echarts.init(chartDom2);
  282. var myChart = echarts.init(chartDom);
  283. var option,option2;
  284. option = {
  285. title: {
  286. text: '{{name_html}}帧交易量分析图'
  287. },
  288. tooltip: {
  289. axisPointer: {
  290. type: 'cross',
  291. lineStyle: {
  292. type: 'dashed',
  293. width: 1
  294. }
  295. }
  296. },
  297. legend: {},
  298. grid: {
  299. left: '3%',
  300. right: '4%',
  301. bottom: '3%',
  302. containLabel: true
  303. },
  304. xAxis: {
  305. type: 'value',
  306. boundaryGap: [0, 0.01]
  307. },
  308. yAxis: {
  309. type: 'category',
  310. data: "#.to_owned() + json_data["pancake_data_y"].to_string().as_str() + "
  311. },
  312. series: [
  313. {
  314. type: 'bar',
  315. data: " + json_data["pancake_data_x"].to_string().as_str() + "
  316. },
  317. ]
  318. };
  319. option && myChart.setOption(option);
  320. option2 = {
  321. title: {
  322. text: ' Data',
  323. left: 10
  324. },
  325. toolbox: {
  326. feature: {
  327. dataZoom: {
  328. yAxisIndex: false
  329. },
  330. saveAsImage: {
  331. pixelRatio: 2
  332. }
  333. }
  334. },
  335. tooltip: {
  336. axisPointer: {
  337. type: 'cross',
  338. lineStyle: {
  339. type: 'dashed',
  340. width: 1
  341. }
  342. }
  343. },
  344. grid: {
  345. bottom: 90
  346. },
  347. dataZoom: [
  348. {
  349. type: 'inside'
  350. },
  351. {
  352. type: 'slider'
  353. }
  354. ],
  355. xAxis: {
  356. data: " + json_data["turnover_data_x"].to_string().as_str() + ",
  357. silent: false,
  358. splitLine: {
  359. show: true
  360. },
  361. splitArea: {
  362. show: true
  363. }
  364. },
  365. yAxis: {
  366. splitArea: {
  367. show: false
  368. }
  369. },
  370. series: [
  371. {
  372. type: 'bar',
  373. data: " + json_data["turnover_data_y"].to_string().as_str() + ",
  374. large: true
  375. }
  376. ]
  377. };
  378. option2 && myChart2.setOption(option2);
  379. </script>
  380. </html>
  381. ";
  382. // 编译模板
  383. handlebars
  384. .register_template_string("page", template)
  385. .expect("编译模版失败!");
  386. // 渲染模板
  387. let output = handlebars
  388. .render("page", &data)
  389. .expect("渲染模版失败!");
  390. // 将 HTML 写入文件
  391. let mut file = File::create(&path).expect("创建文件失败!");
  392. file.write_all(output.as_bytes()).expect("写入文件到本地失败!");
  393. info!("Ticker信息网页生成成功!路径:{:?}\n\n", path);
  394. }