Преглед изворни кода

新增 成交量订单 柱形图,该功能试用 可行再优化

hl пре 1 година
родитељ
комит
0453a56ad7
2 измењених фајлова са 456 додато и 9 уклоњено
  1. 420 0
      src/hl_pr_utile.rs
  2. 36 9
      src/main.rs

+ 420 - 0
src/hl_pr_utile.rs

@@ -0,0 +1,420 @@
+use std::collections::{BTreeMap, HashSet};
+use std::fs::File;
+use std::io::Write;
+use std::str::FromStr;
+use chrono::{NaiveDateTime, Utc};
+use handlebars::Handlebars;
+use rust_decimal::Decimal;
+use rust_decimal::prelude::ToPrimitive;
+use serde_json::{json, Value};
+use tracing::{error, info};
+use crate::export_template::template_ticker::ExportExchangeTickerInfo;
+use crate::handle_ticker;
+use crate::http::request::get;
+use crate::struct_standard::Trades;
+use crate::swap_okx::okx_swap_standard::SwapTrades;
+use crate::utils::utils::TickerConfigInfo;
+
+pub async fn export_ticker(symbol: &str, exchange: &str, time_str: &str) {
+
+    //从今天算起
+    let time_this = Utc::now().timestamp_millis();
+    let mut name_html = "";
+    let mut start_at = time_this - match time_str {
+        "10M" => {
+            name_html = "10分钟";
+            (1000 * 60 * 10)
+        }
+        "20M" => {
+            name_html = "20分钟";
+            (1000 * 60 * 20)
+        }
+        "30M" => {
+            name_html = "30分钟";
+            (1000 * 60 * 30)
+        }
+        "1H" => {
+            name_html = "1小时";
+            (1000 * 60 * 60 * 1)
+        }
+        "10H" => {
+            name_html = "10小时";
+            (1000 * 60 * 60 * 10)
+        }
+        "1D" => {
+            name_html = "1天";
+            (1000 * 60 * 60 * 24)
+        }
+        "2D" => {
+            name_html = "1天";
+            (1000 * 60 * 60 * 24 * 2)
+        }
+        "3D" => {
+            name_html = "3天";
+            (1000 * 60 * 60 * 24 * 3)
+        }
+        "5D" => {
+            name_html = "5天";
+            (1000 * 60 * 60 * 24 * 5)
+        }
+        "1Z" => {
+            name_html = "1周";
+            (1000 * 60 * 60 * 24 * 7)
+        }
+        "2Z" => {
+            name_html = "2周";
+            (1000 * 60 * 60 * 24 * 7 * 2)
+        }
+        "4Z" => {
+            name_html = "1个月";
+            (1000 * 60 * 60 * 24 * 7 * 4)
+        }
+        _ => {
+            info!("错误时间");
+            return;
+        }
+    };
+    start_at = start_at / 1000;
+    let end_at = time_this.clone() / 1000;
+    let recall_start_at = start_at;
+
+    let mut exchange_list: BTreeMap<String, Vec<Value>> = BTreeMap::new();
+    let exchange_up = exchange.to_uppercase();
+    let exchange_result = match exchange_up.as_str() {
+        "GATE" => {
+            let mut start_at_ti = start_at;
+            loop {
+                let end_at_ti = start_at_ti + (20 * 60);
+                if start_at_ti > end_at {
+                    break;
+                }
+                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();
+                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();
+                info!("----时间:{:?}---{:?}",time_str_s,time_str_e);
+                //查询数据按照间隔20分钟
+                let url = "https://api.gateio.ws/api/v4/futures/usdt/trades";
+                let params = serde_json::json!({
+                    "contract": symbol,
+                    "from": start_at_ti,
+                    "to":  end_at_ti,
+                    "to":  end_at_ti,
+                    "limit": "1000"
+                });
+                let res_data = get(url, params.to_string(), None).await;
+                if res_data.code == "200" {
+                    let res_data_str = res_data.data;
+                    // info!("body:{:?}",res_data_str);
+                    let json_value: serde_json::Value = serde_json::from_str(&res_data_str).unwrap();
+                    for rows in json_value.as_array() {
+                        let rows_c = rows.clone();
+                        info!("数据数量:{:?}",rows_c.len());
+                        for row in rows_c {
+                            // {"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)}
+                            // info!("数据:{:?}",row.clone());
+                            let row_c = row.clone();
+                            let create_time = format!("{:?}", row["create_time"].as_f64().unwrap().to_i64().unwrap_or(0)).clone();
+                            let k = create_time.as_str();
+
+                            let mut def: Vec<Value> = vec![];
+                            if exchange_list.contains_key(k) {
+                                match exchange_list.get(k) {
+                                    None => {}
+                                    Some(r) => {
+                                        def = r.clone();
+                                    }
+                                }
+                            }
+                            def.push(row_c);
+                            exchange_list.insert(k.parse().unwrap(), def);
+                        }
+                    }
+                } else {
+                    info!("数据请求错误:");
+                    return;
+                }
+                start_at_ti = start_at_ti + (20 * 60);
+            }
+
+            // info!("数据:{:?}",exchange_list);
+            info!("----横向图--交易量:");
+            let mut exchange_list_sss: BTreeMap<String, Vec<Value>> = BTreeMap::new();
+            let mut turnover_data: BTreeMap<String, Vec<Value>> = BTreeMap::new();
+            let mut keys: Vec<String> = exchange_list.keys().cloned().collect();
+            keys.sort_by(|&(ref a), &(ref b)| a.len().cmp(&b.len()));
+            for k in keys.clone() {
+                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();
+                let data = exchange_list.get(k.clone().as_str()).unwrap();
+                let mut str_array = vec![""];
+                for d in data.clone() {
+                    str_array.push(" - ");
+                }
+                // info!("数据总数: {:?}: {:?}",time_str,str_array);
+
+
+                let mut def: Vec<Value> = vec![];
+                let size_k = format!("{:?}", data.len());
+                if exchange_list_sss.contains_key(&size_k) {
+                    match exchange_list_sss.get(&size_k) {
+                        None => {}
+                        Some(r) => {
+                            def = r.clone();
+                        }
+                    }
+                }
+                def.extend(data.clone());
+                exchange_list_sss.insert((str_array.len() - 1).to_string(), def);
+
+
+                let mut def2: Vec<Value> = vec![];
+                let size_k2 = format!("{:?}", time_str);
+                if turnover_data.contains_key(&size_k2) {
+                    match turnover_data.get(&size_k2) {
+                        None => {}
+                        Some(r) => {
+                            def2 = r.clone();
+                        }
+                    }
+                }
+                def2.extend(data.clone());
+                turnover_data.insert(String::from(&size_k2), def2);
+            }
+
+            //总结 交易量
+            info!("总结:");
+            let mut pancake_data_y: Vec<String> = vec![];   //{ value: 1048, name: 'Search Engine' },
+            let mut pancake_data_x: Vec<usize> = vec![];   //{ value: 1048, name: 'Search Engine' },
+            // let zzz = exchange_list_sss.keys();
+            let mut keys: Vec<String> = exchange_list_sss.keys().cloned().collect();
+            keys.sort_by(|&(ref a), &(ref b)| a.len().cmp(&b.len()));
+            for k in keys {
+                let data = exchange_list_sss.get(k.clone().as_str()).unwrap();
+
+                let mut initiative_ticker_info = vec![];
+                for d in data {
+                    let create_time = format!("{:?}", d["create_time"].as_f64().unwrap().to_i64().unwrap_or(0)).clone();
+                    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();
+
+                    initiative_ticker_info.push(time_str);
+                };
+                let mut seen = HashSet::new();
+                initiative_ticker_info.retain(|item| seen.insert(item.clone()));
+
+                let name = format!(" 交易量:{:?}", k.clone());
+                pancake_data_y.push(name);
+                pancake_data_x.push(initiative_ticker_info.len());
+                info!("交易量:{:?},一共有{:?}个时间点:{:?}",k.clone(),initiative_ticker_info.len(),0);
+            }
+
+
+            //柱形图
+            let mut turnover_data_y: Vec<f64> = vec![];
+            let mut turnover_data_x: Vec<String> = vec![];
+            let mut keys: Vec<String> = turnover_data.keys().cloned().collect();
+            keys.sort_by(|&(ref a), &(ref b)| a.len().cmp(&b.len()));
+            turnover_data_x = keys;
+            for k in turnover_data_x.clone() {
+                let rows = turnover_data.get(k.as_str()).unwrap();
+                let mut val_y = 0.0;
+                if rows.len() > 1 {
+                    let mut max_v = 0.0;
+                    let mut min_v = 0.0;
+                    for row in rows {
+                        let price = row["price"].as_str().unwrap().parse::<f64>().unwrap();
+                        // info!("price:{:?} " ,price);
+                        if max_v == 0.0{
+                            max_v =   price
+                        }
+                        if min_v == 0.0{
+                            min_v = price
+                        }
+
+                        max_v = if price > max_v { price } else { max_v };
+                        min_v = if price < min_v { price } else { min_v };
+                    }
+                    val_y = format!("{:.5}", (max_v - min_v) / max_v).to_string().parse::<f64>().unwrap();
+                }
+                turnover_data_y.push(val_y);
+            }
+
+            export_html(name_html, pancake_data_y, pancake_data_x, turnover_data_y, turnover_data_x)
+        }
+        _ => {
+            error!("交易所输入错误!");
+            panic!("交易所输入错误!")
+        }
+    };
+}
+
+//生成html
+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>) {
+    info!("正在生成网页,请稍后!");
+    let path = format!("./数据分析图.html");
+    // 创建 Handlebars 实例
+    let mut handlebars = Handlebars::new();
+
+
+    let json_data = json!({
+        "pancake_data_y":pancake_data_y,
+        "pancake_data_x":pancake_data_x,
+        "turnover_data_y":turnover_data_y,
+        "turnover_data_x":turnover_data_x,
+    });
+
+    info!("pancake_data:{:?}",json_data["pancake_data"].to_string());
+
+    let data = serde_json::json!({
+        "chart_title": format!("Ticker数据分析"),
+        "name_html": name_html.to_string().clone(),
+    });
+    // HTML 模板
+    let template = r#"
+        <!DOCTYPE html>
+        <html>
+        <head>
+            <meta charset="UTF-8">
+            <title>{{chart_title}}</title>
+            <script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.3/echarts.min.js"></script>
+            <script src="https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.10/dayjs.min.js"></script>
+
+            <style>
+                * {
+                    margin: 0;
+                    padding: 0;
+                }
+                #main {
+                    margin: 50px auto 0;
+                    width: calc(100vw - 100px);
+                    height: calc(100vh - 100px);
+
+                }
+                   #main2 {
+                    margin: 50px auto 0;
+                    width: calc(100vw - 100px);
+                    height: calc(100vh - 100px);
+
+                }
+            </style>
+        </head>
+        <body>
+           <div id="main"></div>
+           <div id="main2"></div>
+        </body>
+        <script>
+            var exchangeColor = {binance: '#F4BC0C', gate: '#0068FF', okx: '#171F30'};
+            var chartDom = document.getElementById('main');
+            var chartDom2 = document.getElementById('main2');
+            var myChart2 = echarts.init(chartDom2);
+            var myChart = echarts.init(chartDom);
+            var option,option2;
+
+            option = {
+              title: {
+                text: '{{name_html}}帧交易量分析图'
+              },
+              tooltip: {
+                trigger: 'axis',
+                axisPointer: {
+                  type: 'shadow'
+                }
+              },
+              legend: {},
+              grid: {
+                left: '3%',
+                right: '4%',
+                bottom: '3%',
+                containLabel: true
+              },
+              xAxis: {
+                type: 'value',
+                boundaryGap: [0, 0.01]
+              },
+              yAxis: {
+                type: 'category',
+                data: "#.to_owned() + json_data["pancake_data_y"].to_string().as_str() + "
+              },
+              series: [
+                {
+                  type: 'bar',
+                  data:  " + json_data["pancake_data_x"].to_string().as_str() + "
+                },
+              ]
+            };
+            option && myChart.setOption(option);
+
+
+            option2 = {
+                  title: {
+                    text:  ' Data',
+                    left: 10
+                  },
+                  toolbox: {
+                    feature: {
+                      dataZoom: {
+                        yAxisIndex: false
+                      },
+                      saveAsImage: {
+                        pixelRatio: 2
+                      }
+                    }
+                  },
+                  tooltip: {
+                    trigger: 'axis',
+                    axisPointer: {
+                      type: 'shadow'
+                    }
+                  },
+                  grid: {
+                    bottom: 90
+                  },
+                  dataZoom: [
+                    {
+                      type: 'inside'
+                    },
+                    {
+                      type: 'slider'
+                    }
+                  ],
+                  xAxis: {
+                    data:  " + json_data["turnover_data_x"].to_string().as_str() + ",
+                    silent: false,
+                    splitLine: {
+                      show: false
+                    },
+                    splitArea: {
+                      show: false
+                    }
+                  },
+                  yAxis: {
+                    splitArea: {
+                      show: false
+                    }
+                  },
+                  series: [
+                    {
+                      type: 'bar',
+                      data:  " + json_data["turnover_data_y"].to_string().as_str() + ",
+                      large: true
+                    }
+                  ]
+                };
+            option2 && myChart2.setOption(option2);
+
+            </script>
+        </html>
+    ";
+
+    // 编译模板
+    handlebars
+        .register_template_string("page", template)
+        .expect("编译模版失败!");
+
+    // 渲染模板
+    let output = handlebars
+        .render("page", &data)
+        .expect("渲染模版失败!");
+
+    // 将 HTML 写入文件
+    let mut file = File::create(&path).expect("创建文件失败!");
+    file.write_all(output.as_bytes()).expect("写入文件到本地失败!");
+    info!("Ticker信息网页生成成功!路径:{:?}\n\n", path);
+}

+ 36 - 9
src/main.rs

@@ -13,6 +13,7 @@ pub mod export_template;
 pub mod handle_ticker;
 pub mod export_balance;
 pub mod export_ticker;
+mod hl_pr_utile;
 
 struct ConfigList {
     ticker_info: TickerConfigInfo,
@@ -29,20 +30,46 @@ async fn main() {
         balance_info: balance_config,
     };
 
+    // if config_obj.ticker_info.is_export {
+    //     info!("----------正在导出Ticker信息----------");
+    //     let config_clone = config_obj.ticker_info.clone();
+    //     if http::proxy::ParsingDetail::http_enable_proxy(config_clone.proxy_address.clone()) {
+    //         info!("检测有代理配置,配置走代理");
+    //     }
+    //     export_ticker::export_ticker(config_clone).await;
+    // }
+    // if config_obj.balance_info.is_export {
+    //     info!("----------正在导出Balance信息----------");
+    //     let config_clone = config_obj.balance_info.clone();
+    //     if http::proxy::ParsingDetail::http_enable_proxy(config_clone.proxy_address.clone()) {
+    //         info!("检测有代理配置,配置走代理");
+    // }
+    //     export_balance::export_balance(config_clone).await;
+    // }
+
+
+    //根据帧 来查看数据,规则:根据当前币对选择 时间范围,的秒级 ticker 数量
     if config_obj.ticker_info.is_export {
         info!("----------正在导出Ticker信息----------");
         let config_clone = config_obj.ticker_info.clone();
         if http::proxy::ParsingDetail::http_enable_proxy(config_clone.proxy_address.clone()) {
             info!("检测有代理配置,配置走代理");
         }
-        export_ticker::export_ticker(config_clone).await;
-    }
-    if config_obj.balance_info.is_export {
-        info!("----------正在导出Balance信息----------");
-        let config_clone = config_obj.balance_info.clone();
-        if http::proxy::ParsingDetail::http_enable_proxy(config_clone.proxy_address.clone()) {
-            info!("检测有代理配置,配置走代理");
-        }
-        export_balance::export_balance(config_clone).await;
+
+        // "10M" => (1000 * 60 * 10),
+        // "20M" => (1000 * 60 * 20),
+        // "30M" => (1000 * 60 * 30),
+        // "1H" => (1000 * 60 * 60 * 1),
+        // "10H" => (1000 * 60 * 60 * 10),
+        // "1D" => (1000 * 60 * 60 * 24),
+        // "2D" => (1000 * 60 * 60 * 24 * 2),
+        // "3D" => (1000 * 60 * 60 * 24 * 3),
+        // "5D" => (1000 * 60 * 60 * 24 * 5),
+        // "1Z" => (1000 * 60 * 60 * 24 * 7),
+        // "2Z" => (1000 * 60 * 60 * 24 * 7 * 2),
+        // "4Z" => (1000 * 60 * 60 * 24 * 7 * 4),
+        // 提示: 1. 代码颗粒度为秒级,100毫秒与999毫秒都归纳于1秒之内,
+        //       2. 不同币对成交量可能存在差异,当前查询是1000条已经是最大值,但是有点币对交易量只有几百,但是有的可能好几千,这个需要酌情调整
+        hl_pr_utile::export_ticker("XRP_USDT","GATE","1H").await;
     }
 }