Browse Source

新版本测试完毕,更准确了

skyffire 1 year ago
parent
commit
e2284cd85a
2 changed files with 200 additions and 243 deletions
  1. 189 220
      src/hl_pr_utile.rs
  2. 11 23
      src/main.rs

+ 189 - 220
src/hl_pr_utile.rs

@@ -1,12 +1,24 @@
+use std::cmp::{max};
 use std::collections::{BTreeMap};
 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::{FromPrimitive, ToPrimitive};
+use rust_decimal_macros::dec;
 use serde_json::{json, Value};
 use tracing::{info};
 use crate::http::request::get;
 
+#[derive(Debug)]
+pub struct Ticker {
+    pub create_time: Decimal,
+    pub price: Decimal,
+    pub amount: Decimal,
+}
+
 pub fn parse_str_to_seconds(name_html: &mut String, time_str: &str) -> i64 {
     match time_str {
         "5M" => {
@@ -17,6 +29,10 @@ pub fn parse_str_to_seconds(name_html: &mut String, time_str: &str) -> i64 {
             *name_html = "10分钟".to_string();
             60 * 10
         }
+        "15M" => {
+            *name_html = "15分钟".to_string();
+            60 * 15
+        }
         "20M" => {
             *name_html = "20分钟".to_string();
             60 * 20
@@ -79,7 +95,7 @@ pub fn parse_str_to_seconds(name_html: &mut String, time_str: &str) -> i64 {
     }
 }
 
-pub async fn pull_gate_ticker(ticker_map: &mut BTreeMap<u64, Value>, start_at: i64, end_at: i64, time_interval: i64, symbol: String) {
+pub async fn pull_gate_ticker(ticker_map: &mut BTreeMap<u64, Ticker>, start_at: i64, end_at: i64, time_interval: i64, symbol: String) {
     let mut from_time = start_at;
     loop {
         let to_time = from_time + time_interval;
@@ -101,11 +117,21 @@ pub async fn pull_gate_ticker(ticker_map: &mut BTreeMap<u64, Value>, start_at: i
             let json_value: Value = serde_json::from_str(&res_data_str).unwrap();
             if let Some(tickers) = json_value.as_array() {
                 info!("数据数量:{:?}", tickers.len());
-                for ticker in tickers {
-                    let key = ticker["id"].as_u64().unwrap();
+                // 倒序插入,可以保证使用最新的ticker进行计算,最新的ticker可以代表行情的一个最新情况
+                for ticker_json in tickers.iter().rev() {
+                    let key_f64 = ticker_json["create_time_ms"].as_f64().unwrap() * 1000.0;
+                    let key = key_f64.to_u64().unwrap();
+
+                    let price_str = ticker_json["price"].as_str().unwrap();
+                    let amount_i64 = ticker_json["size"].as_i64().unwrap();
+                    let ticker = Ticker {
+                        create_time: Decimal::from_u64(key).unwrap(),
+                        price: Decimal::from_str(price_str).unwrap(),
+                        amount: Decimal::from_i64(amount_i64).unwrap(),
+                    };
 
                     // key是id,value是json
-                    ticker_map.insert(key, ticker.clone());
+                    ticker_map.insert(key, ticker);
                 }
             }
 
@@ -120,13 +146,88 @@ pub async fn pull_gate_ticker(ticker_map: &mut BTreeMap<u64, Value>, start_at: i
     }
 }
 
-pub async fn export_gate_ticker(start_at: i64, end_at: i64, time_interval: i64, symbol: String) {
-    let mut ticker_map: BTreeMap<u64, Value> = BTreeMap::new();
+// 计算最近range毫秒的的波动
+pub fn calc_gate_ticker_amplitude(ticker_map: BTreeMap<u64, Ticker>) -> BTreeMap<Decimal, Decimal> {
+    let limit_range = dec!(100);
+    let mut amplitude_map: BTreeMap<Decimal, Decimal> = BTreeMap::new();
+
+    // 每一个元素都遍历一遍
+    info!("精确幅度计算执行中");
+    let keys: Vec<u64> = ticker_map.keys().cloned().collect();
+    for (index, create_time) in keys.iter().enumerate() {
+        let ticker = ticker_map.get(&create_time).unwrap();
+
+        // 该元素向前遍历range毫秒
+        let mut prev_index = if index == 0 {
+            0
+        } else {
+            index
+        };
+
+        // 寻找区间最大值、最小值
+        let mut max_price = dec!(-1);
+        let mut min_price = dec!(1e28);
+
+        loop {
+            // 第0个就不搞
+            if prev_index == 0 {
+                break;
+            }
+
+            let prev_ticker = ticker_map.get(&keys[prev_index]).unwrap();
+            // 判断该ticker是否是range ms以外
+            if ticker.create_time - prev_ticker.create_time > limit_range {
+                break;
+            }
+
+            // 判断最大值、最小值
+            if prev_ticker.price > max_price {
+                max_price = prev_ticker.price;
+            }
+            if prev_ticker.price < min_price {
+                min_price = prev_ticker.price;
+            }
+
+            prev_index -= 1;
+        }
+
+        // 逻辑计算层
+        // 取离当前点最远的点进行测量
+        let last_price = ticker.price;
+
+        // 不是初始值,以及不是0波动
+        if index != 0 && (max_price != last_price || min_price != last_price) {
+            let mut up_rate = (last_price - min_price) / min_price;
+            let mut dn_rate = (max_price - last_price) / max_price;
+
+            up_rate.rescale(4);
+            dn_rate.rescale(4);
+
+            // 去除小数位之后,可以忽略一些太小的波动,减少图表生成压力
+            if up_rate > Decimal::ZERO || dn_rate > Decimal::ZERO {
+                // info!("up={}, dn={}, ra={}", up_rate, dn_rate, max(up_rate, dn_rate));
+                amplitude_map.insert(ticker.create_time, max(up_rate, dn_rate));
+            }
+        }
+    }
+
+    return amplitude_map;
+}
+
+pub async fn export_gate_ticker(start_at: i64, end_at: i64, time_interval: i64, symbol: String) -> BTreeMap<Decimal, Decimal> {
+    let mut ticker_map: BTreeMap<u64, Ticker> = BTreeMap::new();
 
     // 获取ticker数据
     pull_gate_ticker(&mut ticker_map, start_at, end_at, time_interval, symbol).await;
+    let len = ticker_map.len();
+    info!("网络层执行完毕,总数据量条数:{}。", len);
+
+    // 逻辑层执行
+    let amplitude_map = calc_gate_ticker_amplitude(ticker_map);
+    let amplitude_map_len = amplitude_map.len();
+    info!("逻辑层执行完毕,剩余有效波动条数:{}。", amplitude_map_len);
 
-    info!(?ticker_map);
+    return amplitude_map;
 }
 
 pub async fn export_ticker(symbol: &str, exchange: &str, time_range_str: &str, time_interval: i64) {
@@ -139,144 +240,35 @@ pub async fn export_ticker(symbol: &str, exchange: &str, time_range_str: &str, t
     let start_at = timestamp_seconds_now - time_distance_seconds;
     let end_at = timestamp_seconds_now;
 
+    let amplitude_map;
     match exchange.to_uppercase().as_str() {
         "GATE" => {
-            export_gate_ticker(start_at, end_at, time_interval, symbol.to_uppercase()).await;
-
-            // 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![""];
-            //     let mut ti_str = vec![""];
-            //     for d in data.clone() {
-            //         str_array.push(" - ");
-            //         // ti_str.push(d.clone());
-            //         // info!("数据: {:?} ",d);
-            //     }
-            //
-            //     // 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() * 1000 + 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)
+            amplitude_map = export_gate_ticker(start_at, end_at, time_interval, symbol.to_uppercase()).await;
         }
         _ => {
             panic!("交易所输入错误!")
         }
     };
+
+    export_html(name_html.as_str(),
+                amplitude_map.keys().cloned().collect(),
+                amplitude_map.values().cloned().collect())
 }
 
 #[allow(dead_code)]
 //生成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");
+pub fn export_html(name_html: &str,
+                   x_values: Vec<Decimal>,
+                   y_values: Vec<Decimal>) {
+    info!("正在生成网页,请稍候……");
+    let path = "./新版无敌大数据ticker.html".to_string();
     // 创建 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(),
+        "chart_title": format!("{}新版无敌大数据Ticker", name_html),
+        "x_values": x_values,
+        "y_values": y_values,
     });
     // HTML 模板
     let template = r#"
@@ -297,129 +289,106 @@ pub fn export_html(name_html: &str, pancake_data_y: Vec<String>, pancake_data_x:
                     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);
-
+                .info_wp{
+                    padding: 0 40px;
                 }
             </style>
         </head>
         <body>
-           <div id="main"></div>
-           <div id="main2"></div>
+            <div id="main"></div>
+            <div class="info_wp">{{statistic_result}}</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;
+            var option;
 
             option = {
-              title: {
-                text: '{{name_html}}帧交易量分析图'
-              },
-              tooltip: {
-                axisPointer: {
-                          type: 'cross',
-                          lineStyle: {
-                              type: 'dashed',
-                              width: 1
-                          }
-                      }
-              },
-              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
+                    text: '{{chart_title}}'
                   },
-                  toolbox: {
+                  tooltip: {
+                    trigger: 'axis',
+                    axisPointer: {
+                      type: 'cross',
+                      lineStyle: {
+                          type: 'dashed',
+                          width: 1
+                      }
+                    },
+                    formatter: function (value) {
+                      let time = dayjs(value[0].name *1).format('YYYY-MM-DD HH:mm:ss.SSS');
+                      let price = value[0].value;
+                      return `时间:${time}<br/>价格:${price}`
+                    },
+                  },
+                   toolbox: {
                     feature: {
-                      dataZoom: {
-                        yAxisIndex: false
-                      },
-                      saveAsImage: {
-                        pixelRatio: 2
+                      dataZoom: {},
+                      brush: {
+                        type: ['rect', 'clear']
                       }
                     }
                   },
-                  tooltip: {
-                   axisPointer: {
-                          type: 'cross',
-                          lineStyle: {
-                              type: 'dashed',
-                              width: 1
-                          }
-                      }
+                  legend: {
+                       data: {{x_values}}
+                  },
+                  xAxis: {
+                    type: 'category',
+                    boundaryGap: false,
+                    data: {{x_values}},
+                    show : false
                   },
-                  grid: {
-                    bottom: 90
+                  yAxis: {
+                    type: 'value',
+                    boundaryGap: [0, '100%'],
+                    formatter: function (value) {
+                      return dayjs(value[0].name *1).format('YYYY-MM-DD HH:mm:ss.SSS');
+                    },
                   },
                   dataZoom: [
                     {
-                      type: 'inside'
+                      type: 'inside',
+                      start: 0,
+                      end: 10
                     },
                     {
-                      type: 'slider'
+                      start: 0,
+                      end: 10
                     }
                   ],
-                  xAxis: {
-                    data:  " + json_data["turnover_data_x"].to_string().as_str() + ",
-                    silent: false,
-                    splitLine: {
-                      show: true
-                    },
-                    splitArea: {
-                      show: true
-                    }
-                  },
-                  yAxis: {
-                    splitArea: {
-                      show: false
-                    }
-                  },
                   series: [
                     {
-                      type: 'bar',
-                      data:  " + json_data["turnover_data_y"].to_string().as_str() + ",
-                      large: true
+                      name: '价格',
+                      type: 'line',
+                      symbol: 'none',
+                      sampling: 'lttb',
+                      itemStyle: {
+                        color: 'rgb(255, 70, 131)'
+                      },
+                      areaStyle: {
+                        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+                          {
+                            offset: 0,
+                            color: 'rgb(255, 158, 68)'
+                          },
+                          {
+                            offset: 1,
+                            color: 'rgb(255, 70, 131)'
+                          }
+                        ])
+                      },
+                      data: {{y_values}}
                     }
                   ]
                 };
-            option2 && myChart2.setOption(option2);
 
+            option && myChart.setOption(option);
             </script>
         </html>
-    ";
+    "#;
 
     // 编译模板
     handlebars
@@ -434,5 +403,5 @@ pub fn export_html(name_html: &str, pancake_data_y: Vec<String>, pancake_data_x:
     // 将 HTML 写入文件
     let mut file = File::create(&path).expect("创建文件失败!");
     file.write_all(output.as_bytes()).expect("写入文件到本地失败!");
-    info!("Ticker信息网页生成成功!路径:{:?}\n\n", path);
+    info!("Balance信息网页生成成功!路径:{:?}\n\n", path);
 }

+ 11 - 23
src/main.rs

@@ -46,28 +46,16 @@ async fn main() {
         export_balance::export_balance(config_clone).await;
     }
 
-    if true {
-        info!("----------正在导出Ticker信息----------");
-        let config_clone = config_obj.ticker_info.clone();
-        if http::proxy::ParsingDetail::http_enable_proxy(config_clone.proxy_address.clone()) {
-            info!("检测有代理配置,配置走代理");
-        }
-
-        // "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条已经是最大值,但是有点币对交易量只有几百,但是有的可能好几千,这个需要酌情调整
-        //time_interval -数据请求的时间间隔 毫秒级
-        hl_pr_utile::export_ticker("ORDI_USDT", "GATE", "10M", 1 * 30).await;
+    info!("----------正在导出Ticker信息----------");
+    let config_clone = config_obj.ticker_info.clone();
+    if http::proxy::ParsingDetail::http_enable_proxy(config_clone.proxy_address.clone()) {
+        info!("检测有代理配置,配置走代理");
     }
+
+    // 提示: 1. 代码颗粒度为秒级,100毫秒与999毫秒都归纳于1秒之内,
+    //       2. 不同币对成交量可能存在差异,当前查询是1000条已经是最大值,但是有点币对交易量只有几百,但是有的可能好几千,这个需要酌情调整
+    // time_interval -数据请求的时间间隔 毫秒级
+    //
+    // 时间范围可以选择:[5M, 10M, 15M, 20M, 30M, 1H, 2H, 3H, 4H, 12H, 1D, 2D, 3D, 5D, 1Z, 2Z, 4Z]
+    hl_pr_utile::export_ticker("BAKE_USDT", "GATE", "30M", 1 * 30).await;
 }