Browse Source

整理导出调参参考ticker

gepangpang 1 year ago
parent
commit
e599ce1354
7 changed files with 321 additions and 679 deletions
  1. 10 0
      config_analyze.toml.sample
  2. 173 0
      src/export_analyze.rs
  3. 2 1
      src/export_template/mod.rs
  4. 84 295
      src/export_template/template_analyze.rs
  5. 0 368
      src/hl_pr_utile.rs
  6. 12 13
      src/main.rs
  7. 40 2
      src/utils/utils.rs

+ 10 - 0
config_analyze.toml.sample

@@ -0,0 +1,10 @@
+# 是否执行导出
+is_export = true
+# 配置代理
+proxy_address = "127.0.0.1:7890"
+# 查询币对
+symbol = "kas_USDT"
+# 配置交易所 目前支持["gate"]
+exchanges = ["gate"]
+#时间范围 m:分钟, H:小时, D:天, W:周, M:月(分钟和月份区分大小写,其余不区分。例:10m)
+range_interval = "4H"

+ 173 - 0
src/export_analyze.rs

@@ -0,0 +1,173 @@
+use std::cmp::{max};
+use std::collections::{BTreeMap};
+use std::str::FromStr;
+use chrono::{Utc};
+use rust_decimal::Decimal;
+use rust_decimal_macros::dec;
+use tracing::{info};
+use crate::export_template;
+use crate::export_ticker::get_gate_ticker_info;
+
+#[derive(Debug)]
+pub struct Ticker {
+    pub create_time: Decimal,
+    pub price: Decimal,
+    pub amount: Decimal,
+}
+
+#[derive(Debug)]
+pub struct RangeInterval {
+    pub interval_time: i64,
+    pub interval_text: String,
+}
+
+pub async fn export_ticker(symbol: &str, exchange: &str, range_interval: &str) {
+    // 从此刻往前算时间
+    let timestamp_seconds_now = Utc::now().timestamp();
+    let range_interval = parse_range_interval(range_interval);
+
+    let start_at = timestamp_seconds_now - range_interval.interval_time;
+    let end_at = timestamp_seconds_now;
+
+    let amplitude_map;
+    match exchange.to_uppercase().as_str() {
+        "GATE" => {
+            amplitude_map = export_gate_ticker(start_at, end_at, symbol.to_uppercase()).await;
+        }
+        _ => {
+            panic!("交易所输入错误!")
+        }
+    };
+
+    export_template::template_analyze::export_html(&range_interval.interval_text, &symbol.to_uppercase(), amplitude_map.keys().cloned().collect(), amplitude_map.values().cloned().collect())
+}
+
+pub fn parse_range_interval (range_interval: &str) -> RangeInterval {
+    let interval_text;
+    let mut interval_time = 0;
+    let range_interval_len = range_interval.len();
+    let time = range_interval[0..range_interval_len - 1].parse::<i64>().unwrap();
+    let mut unit = range_interval[range_interval_len - 1..range_interval_len].to_string();
+    if unit != "m" { unit = unit.to_uppercase().clone();};
+    match unit.as_str() {
+        "m" => {
+            interval_text = format!("{} 分钟", time);
+            interval_time = 60 * time;
+        }
+        "H" => {
+            interval_text = format!("{} 小时", time);
+            interval_time = 60 * 60 * time;
+        }
+        "D" => {
+            interval_text = format!("{} 小时", time);
+            interval_time = 60 * 60 * 24 * time;
+        }
+        "W" => {
+            interval_text = format!("{} 周", time);
+            interval_time = 60 * 60 * 24 * 7 * time;
+        }
+        "M" => {
+            interval_text = format!("{} 月", time);
+            interval_time = 60 * 60 * 24 * 30 * time;
+        }
+        _ => panic!("时间单位错误")
+    }
+    RangeInterval { interval_time, interval_text: interval_text.to_string() }
+}
+
+pub async fn pull_gate_ticker(ticker_map: &mut BTreeMap<u64, Ticker>, start_at: i64, end_at: i64, symbol: String) {
+    let trades_list = get_gate_ticker_info(&symbol, &start_at.to_string(), &end_at.to_string()).await;
+    for trades in trades_list {
+        let key = trades.create_time.parse::<u64>().unwrap();
+        let ticker = Ticker {
+            create_time: Decimal::from_str(&trades.create_time).unwrap(),
+            price: Decimal::from_str(&trades.price).unwrap(),
+            amount: Decimal::from_str(&trades.size).unwrap(),
+        };
+        ticker_map.insert(key, ticker);
+    }
+}
+
+// 计算最近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, 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, 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);
+
+    return amplitude_map;
+}
+

+ 2 - 1
src/export_template/mod.rs

@@ -1,2 +1,3 @@
 pub mod template_ticker;
-pub mod template_balance;
+pub mod template_balance;
+pub mod template_analyze;

+ 84 - 295
src/export_template/template_analyze.rs

@@ -1,177 +1,21 @@
 use std::fs::File;
 use std::io::Write;
-use std::str::FromStr;
-use chrono::NaiveDateTime;
 use handlebars::Handlebars;
 use rust_decimal::Decimal;
-use rust_decimal::prelude::{FromPrimitive, ToPrimitive};
-use uuid::Uuid;
-use rust_decimal_macros::dec;
-use serde::{Deserialize, Serialize};
 use tracing::info;
-use crate::struct_standard::Trades;
-use crate::utils::utils::TickerConfigInfo;
 
-#[derive(Debug, Clone, Deserialize, Serialize)]
-pub struct ExportExchangeTickerInfo {
-    pub name: String,
-    pub ticker_info: Vec<Trades>,
-    pub recall_ticker_info: Vec<Trades>,
-    pub max_price: String,
-}
-
-#[derive(Debug, Clone, Deserialize, Serialize)]
-pub struct SeriesInfo {
-    pub name: String,
-    pub classify: String,
-    pub data: Vec<Trades>,
-}
-
-impl SeriesInfo {
-    fn new() -> SeriesInfo {
-        SeriesInfo {
-            name: "".to_string(),
-            classify: "".to_string(),
-            data: vec![],
-        }
-    }
-}
-
-pub fn export_html(export_info: Vec<ExportExchangeTickerInfo>, start_at: &str, end_at: &str, config: TickerConfigInfo, robot_info: Vec<Trades>) {
-    info!("正在生成网页,请稍后!");
-    let export_path = if config.export_path == "" { "./" } else { config.export_path.as_str() };
-    let export_name = if config.export_name == "" { "export_ticker" } else { config.export_name.as_str() };
-    let path = format!("{}/{}.html", export_path, export_name).replace("//", "/");
+#[allow(dead_code)]
+//生成html
+pub fn export_html(title: &str, symbol: &str, x_values: Vec<Decimal>, y_values: Vec<Decimal>) {
+    info!("正在生成网页,请稍候……");
+    let path = format!("./{}新版无敌大数据ticker.html", symbol);
     // 创建 Handlebars 实例
     let mut handlebars = Handlebars::new();
 
-    let mut initiative_info = SeriesInfo::new();
-    if config.recall_time != 0 {
-        let mut max_price = Decimal::ZERO;
-        for item in export_info.clone() {
-            let exchange_max_price = Decimal::from_str(&item.max_price).unwrap();
-            max_price = if max_price < exchange_max_price { exchange_max_price } else { max_price };
-        }
-
-        let mut old_ticker_info: Vec<Trades> = export_info.iter().flat_map(|exchange| exchange.ticker_info.clone()).collect();
-        old_ticker_info.sort_by(|a, b| a.create_time.cmp(&b.create_time));
-
-        let mut old_recall_ticker_info: Vec<Trades> = export_info.iter().flat_map(|exchange| exchange.recall_ticker_info.clone()).collect();
-        old_recall_ticker_info.sort_by(|a, b| a.create_time.cmp(&b.create_time));
-
-        let mut ticker_info = vec![];
-        let mut last_bool = "";
-        for trades in old_ticker_info {
-            // 计算交易量差
-            let mut volume = Decimal::ZERO;
-            let mut short_volume = Decimal::ZERO;
-            let mut long_volume = Decimal::ZERO;
-            let mut sum_volume = Decimal::ZERO;
-            let recall_ticker_info: Vec<Trades> = old_recall_ticker_info.iter().filter(|recall_trades| {
-                let recall_create_time = Decimal::from_str(&recall_trades.create_time).unwrap();
-                let create_time = Decimal::from_str(&trades.create_time).unwrap();
-                let recall_time = Decimal::from_i64(config.recall_time).unwrap();
-                recall_create_time <= create_time && create_time - recall_create_time <= recall_time
-            }).cloned().collect();
-            for recall_trades in recall_ticker_info.clone() {
-                let size = Decimal::from_str(&recall_trades.size).unwrap();
-                volume += size;
-                sum_volume += size.abs();
-                if size > dec!(0) { long_volume += size } else { short_volume += size.abs() }
-            };
-            let long_volume_bool = long_volume / sum_volume >= config.long_volume_rate;
-            let short_volume_bool = short_volume / sum_volume >= config.short_volume_rate;
-
-            if (long_volume_bool && last_bool != "initiative_long") || (short_volume_bool && last_bool != "initiative_short") || (!long_volume_bool && !short_volume_bool && last_bool != "initiative_none") {
-                // 新增订单流主动性数据
-                let max_price = max_price * dec!(1.005);
-                let mut side = "";
-                if long_volume_bool {
-                    last_bool = "initiative_long";
-                    side = "LONG";
-                }
-                if short_volume_bool {
-                    last_bool = "initiative_short";
-                    side = "SHORT";
-                }
-                if !long_volume_bool && !short_volume_bool {
-                    last_bool = "initiative_none";
-                    side = "NONE";
-                }
-                ticker_info.push(Trades {
-                    id: Uuid::new_v4().to_string()[0..8].to_string(),
-                    data_type: last_bool.to_string(),
-                    symbol: trades.symbol,
-                    create_time: trades.create_time,
-                    size: volume.to_string(),
-                    price: max_price.to_string(),
-                    side: side.to_string(),
-                    is_effect: true,
-                });
-            }
-        }
-        // 对订单流主动性数据(recall)去重
-        let mut ticker_set = std::collections::HashSet::new();
-        let mut initiative_ticker_info = vec![];
-        for trades in ticker_info {
-            if ticker_set.insert((trades.data_type.clone(), trades.create_time.clone())) {
-                initiative_ticker_info.push(trades.clone());
-            }
-        };
-        initiative_info = SeriesInfo {
-            name: "主动性".to_string(),
-            classify: "initiative".to_string(),
-            data: initiative_ticker_info.clone(),
-        };
-    }
-
-    let start_time_d = Decimal::from_str(start_at).unwrap() * dec!(1000) + dec!(8) * dec!(3600000);
-    let end_time_d = Decimal::from_str(end_at).unwrap() * dec!(1000) + dec!(8) * dec!(3600000);
-    let start_time = NaiveDateTime::from_timestamp_millis(start_time_d.to_i64().unwrap()).unwrap().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
-    let end_time = NaiveDateTime::from_timestamp_millis(end_time_d.to_i64().unwrap()).unwrap().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
-
-    let mut name_list: Vec<String> = vec![];
-    let mut legend_list: Vec<String> = vec![];
-    let mut series_info: Vec<SeriesInfo> = vec![];
-
-    for item in export_info.clone() {
-        name_list.push(format!("'{}'", item.name));
-        series_info.push(SeriesInfo {
-            name: item.name.clone(),
-            classify: "ticker".to_string(),
-            data: item.ticker_info.clone(),
-        })
-    }
-    if config.recall_time != 0 {
-        series_info.push(initiative_info);
-    }
-
-    if config.robot_name != "" {
-        let ref_robot_info: Vec<Trades> = robot_info.iter().filter(|trades| trades.data_type == "robot_ref_info").cloned().collect();
-        series_info.push(SeriesInfo {
-            name: "预定价格".to_string(),
-            classify: "robot_ref".to_string(),
-            data: ref_robot_info.clone(),
-        });
-        let reg_robot_info: Vec<Trades> = robot_info.iter().filter(|trades| trades.data_type == "robot_reg_info").cloned().collect();
-        series_info.push(SeriesInfo {
-            name: "挂单价格".to_string(),
-            classify: "robot_reg".to_string(),
-            data: reg_robot_info.clone(),
-        });
-    }
-
-    for item in series_info.clone() {
-        legend_list.push(format!("'{}'", item.name));
-    }
-
     let data = serde_json::json!({
-        "chart_title": format!("{} Ticker数据", name_list.join("、").replace("'","")),
-        "legend_data": format!("[{}]", legend_list.join(", ")),
-        "series_info": series_info.clone(),
-        "symbol": config.symbol.to_uppercase(),
-        "start_at": start_time,
-        "end_at": end_time,
+        "chart_title": format!("{}的{}新版无敌大数据Ticker", title, symbol),
+        "x_values": x_values,
+        "y_values": y_values,
     });
     // HTML 模板
     let template = r#"
@@ -193,10 +37,14 @@ pub fn export_html(export_info: Vec<ExportExchangeTickerInfo>, start_at: &str, e
                     width: calc(100vw - 100px);
                     height: calc(100vh - 100px);
                 }
+                .info_wp{
+                    padding: 0 40px;
+                }
             </style>
         </head>
         <body>
             <div id="main"></div>
+            <div class="info_wp">{{statistic_result}}</div>
         </body>
         <script>
             var exchangeColor = {binance: '#F4BC0C', gate: '#0068FF', okx: '#171F30'};
@@ -205,143 +53,84 @@ pub fn export_html(export_info: Vec<ExportExchangeTickerInfo>, start_at: &str, e
             var option;
 
             option = {
-              title: {
-                text: '{{{chart_title}}}',
-                subtext: '币对:{{symbol}}  时间:{{start_at}}  ~  {{end_at}}'
-              },
-              grid: {
-                left: '3%',
-                right: '7%',
-                bottom: '7%',
-                containLabel: true
-              },
-              tooltip: {
-                // trigger: 'axis',
-                showDelay: 0,
-                formatter: function (params) {
-                  if(params.value.length <= 0) return "";
-                  var time = dayjs(params.value[0]).format('YYYY-MM-DD HH:mm:ss.SSS');
-                  switch (params.value[4]){
-                    case "ticker": return `${params.seriesName}:<br/>时间: ${time}<br/>价格: ${params.value[1]}<br/>数量: ${params.value[2]}<br/>买卖方向: ${params.value[3]}`;
-                    case "initiative": return `${params.seriesName}:<br/>时间: ${time}<br/>数量: ${params.value[2]}<br/>主动方向: ${params.value[3]}`;
-                    case "robot_ref": return `${params.seriesName}:<br/>时间: ${time}<br/>价格: ${params.value[1]}<br/>数量: ${params.value[2]}`;
-                    case "robot_reg": return `${params.seriesName}:<br/>时间: ${time}<br/>价格: ${params.value[1]}<br/>数量: ${params.value[2]}<br/>仓位方向: ${params.value[3]}`;
-                  }
-                },
-                axisPointer: {
-                  show: true,
-                  type: 'cross',
-                  lineStyle: {
-                    type: 'dashed',
-                    width: 1
-                  }
-                }
-              },
-              toolbox: {
-                feature: {
-                  dataZoom: {},
-                  brush: {
-                    type: ['rect', 'clear']
-                  }
-                }
-              },
-              brush: {},
-              legend: {
-                data: {{{legend_data}}},
-                left: 'center',
-                bottom: 10
-              },
-              xAxis: [
-                {
-                  type: 'value',
-                  scale: true,
-                  axisLabel: {
+                  title: {
+                    text: '{{chart_title}}'
+                  },
+                  tooltip: {
+                    trigger: 'axis',
+                    axisPointer: {
+                      type: 'cross',
+                      lineStyle: {
+                          type: 'dashed',
+                          width: 1
+                      }
+                    },
                     formatter: function (value) {
-                      var time = dayjs(value).format('YYYY-MM-DD HH:mm:ss');
-                      return time;
+                      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: {},
+                      brush: {
+                        type: ['rect', 'clear']
+                      }
                     }
                   },
-                  splitLine: {
-                    show: false
-                  }
-                }
-              ],
-              yAxis: [
-                {
-                  type: 'value',
-                  scale: true,
-                  axisLabel: {
-                    formatter: '{value}'
-                  }
-                }
-              ],
-              series: [
-                {{#each series_info as | series |}}
-                {
-                  name: '{{series.name}}',
-                  type: 'scatter',
-                  {{#if (eq series.classify 'ticker')}}symbol: 'triangle',{{/if}}
-                  {{#if (eq series.classify 'initiative')}}symbol: 'circle',{{/if}}
-                  {{#if (eq series.classify 'robot_ref')}}symbol: 'rect',{{/if}}
-                  {{#if (eq series.classify 'robot_reg')}}symbol: 'rect',{{/if}}
-
-                  {{#if (eq series.classify 'ticker')}}color: exchangeColor['{{series.name}}'.toLocaleLowerCase()],{{/if}}
-                  {{#if (eq series.classify 'initiative')}}color: '#D2D2D2',{{/if}}
-                  {{#if (eq series.classify 'robot_reg')}}
-                  color: {
-                    type: 'linear',
-                    x: 0,
-                    y: 0,
-                    x2: 1,
-                    y2: 0,
-                    colorStops: [{offset: 0, color: 'green'},{offset: 0.5, color: 'green'},{offset: 0.5, color: 'red'}, {offset: 1, color: 'red'}],
-                    global: false
+                  legend: {
+                       data: {{x_values}}
                   },
-                  {{/if}}
-                  emphasis: {
-                    focus: 'series'
+                  xAxis: {
+                    type: 'category',
+                    boundaryGap: false,
+                    data: {{x_values}},
+                    show : false
                   },
-                  data:[
-                    {{#each data as |value|}}
-                      {
-                        value: [{{value.create_time}}, {{value.price}}, {{value.size}}, '{{value.side}}', '{{series.classify}}', '{{value.data_type}}'],
-                        symbolRotate: {{value.size}} * 1 > 0 && '{{series.classify}}' == 'ticker' ? '0' : '180',
-                        itemStyle: {
-                          {{#if (eq value.data_type 'robot_reg_info')}}color: {{value.size}} * 1 > 0 ? 'green' : 'red',{{/if}}
-                          {{#if (eq value.data_type 'initiative_long')}}borderColor: 'green',{{/if}}
-                          {{#if (eq value.data_type 'initiative_short')}}borderColor: 'red',{{/if}}
-                          {{#if (eq value.data_type 'initiative_none')}}borderColor: 'black',{{/if}}
-                          {{#if (eq series.classify 'ticker')}}borderColor: {{value.size}} * 1 > 0 ? 'green' : 'red',{{/if}}
-                          borderWidth: 1,
-                        }
-                      },
-                    {{/each}}
-                  ],
-                  markArea: {
-                    silent: true,
-                    itemStyle: {
-                      color: 'transparent',
-                      borderWidth: 1,
-                      borderType: 'dashed'
+                  yAxis: {
+                    type: 'value',
+                    boundaryGap: [0, '100%'],
+                    formatter: function (value) {
+                      return dayjs(value[0].name *1).format('YYYY-MM-DD HH:mm:ss.SSS');
                     },
-                    data: [
-                      [
-                        {
-                          name: '{{name}}数据',
-                          xAxis: 'min',
-                          yAxis: 'min'
-                        },
-                        {
-                          xAxis: 'max',
-                          yAxis: 'max'
-                        }
-                      ]
-                    ]
                   },
-                },
-                {{/each}}
-              ]
-            };
+                  dataZoom: [
+                    {
+                      type: 'inside',
+                      start: 0,
+                      end: 10
+                    },
+                    {
+                      start: 0,
+                      end: 10
+                    }
+                  ],
+                  series: [
+                    {
+                      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}}
+                    }
+                  ]
+                };
 
             option && myChart.setOption(option);
             </script>
@@ -361,5 +150,5 @@ pub fn export_html(export_info: Vec<ExportExchangeTickerInfo>, start_at: &str, e
     // 将 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);
 }

+ 0 - 368
src/hl_pr_utile.rs

@@ -1,368 +0,0 @@
-use std::cmp::{max};
-use std::collections::{BTreeMap};
-use std::fs::File;
-use std::io::Write;
-use std::str::FromStr;
-use chrono::{Utc};
-use handlebars::Handlebars;
-use rust_decimal::Decimal;
-use rust_decimal_macros::dec;
-use tracing::{info};
-use crate::export_ticker::get_gate_ticker_info;
-
-#[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" => {
-            *name_html = "5分钟".to_string();
-            60 * 5
-        }
-        "10M" => {
-            *name_html = "10分钟".to_string();
-            60 * 10
-        }
-        "15M" => {
-            *name_html = "15分钟".to_string();
-            60 * 15
-        }
-        "20M" => {
-            *name_html = "20分钟".to_string();
-            60 * 20
-        }
-        "30M" => {
-            *name_html = "30分钟".to_string();
-            60 * 30
-        }
-        "1H" => {
-            *name_html = "1小时".to_string();
-            60 * 60 * 1
-        }
-        "2H" => {
-            *name_html = "2小时".to_string();
-            60 * 60 * 2
-        }
-        "3H" => {
-            *name_html = "3小时".to_string();
-            60 * 60 * 3
-        }
-        "4H" => {
-            *name_html = "4小时".to_string();
-            60 * 60 * 4
-        }
-        "12H" => {
-            *name_html = "12小时".to_string();
-            60 * 60 * 12
-        }
-        "1D" => {
-            *name_html = "1天".to_string();
-            60 * 60 * 24
-        }
-        "2D" => {
-            *name_html = "1天".to_string();
-            60 * 60 * 24 * 2
-        }
-        "3D" => {
-            *name_html = "3天".to_string();
-            60 * 60 * 24 * 3
-        }
-        "5D" => {
-            *name_html = "5天".to_string();
-            60 * 60 * 24 * 5
-        }
-        "1Z" => {
-            *name_html = "1周".to_string();
-            60 * 60 * 24 * 7
-        }
-        "2Z" => {
-            *name_html = "2周".to_string();
-            60 * 60 * 24 * 7 * 2
-        }
-        "4Z" => {
-            *name_html = "1个月".to_string();
-            60 * 60 * 24 * 7 * 4
-        }
-        _ => {
-            panic!("错误时间");
-        }
-    }
-}
-
-pub async fn pull_gate_ticker(ticker_map: &mut BTreeMap<u64, Ticker>, start_at: i64, end_at: i64, symbol: String) {
-    let trades_list = get_gate_ticker_info(&symbol, &start_at.to_string(), &end_at.to_string()).await;
-    for trades in trades_list {
-        let key = trades.create_time.parse::<u64>().unwrap();
-        let ticker = Ticker {
-            create_time: Decimal::from_str(&trades.create_time).unwrap(),
-            price: Decimal::from_str(&trades.price).unwrap(),
-            amount:Decimal::from_str(&trades.size).unwrap(),
-        };
-        ticker_map.insert(key, ticker);
-    }
-}
-
-// 计算最近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, 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, 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);
-
-    return amplitude_map;
-}
-
-pub async fn export_ticker(symbol: &str, exchange: &str, time_range_str: &str) {
-    // 只是显示在网页上,记录选择的时间周期
-    let mut name_html = Default::default();
-    // 从此刻往前算时间
-    let timestamp_seconds_now = Utc::now().timestamp();
-    let time_distance_seconds = parse_str_to_seconds(&mut name_html, time_range_str);
-
-    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" => {
-            amplitude_map = export_gate_ticker(start_at, end_at, symbol.to_uppercase()).await;
-        }
-        _ => {
-            panic!("交易所输入错误!")
-        }
-    };
-
-    export_html(name_html.as_str(),
-                symbol,
-                amplitude_map.keys().cloned().collect(),
-                amplitude_map.values().cloned().collect())
-}
-
-#[allow(dead_code)]
-//生成html
-pub fn export_html(name_html: &str,
-                   symbol: &str,
-                   x_values: Vec<Decimal>,
-                   y_values: Vec<Decimal>) {
-    info!("正在生成网页,请稍候……");
-    let path = format!("./{}新版无敌大数据ticker.html", symbol);
-    // 创建 Handlebars 实例
-    let mut handlebars = Handlebars::new();
-
-    let data = serde_json::json!({
-        "chart_title": format!("{}的{}新版无敌大数据Ticker", name_html, symbol),
-        "x_values": x_values,
-        "y_values": y_values,
-    });
-    // 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);
-                }
-                .info_wp{
-                    padding: 0 40px;
-                }
-            </style>
-        </head>
-        <body>
-            <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 myChart = echarts.init(chartDom);
-            var option;
-
-            option = {
-                  title: {
-                    text: '{{chart_title}}'
-                  },
-                  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: {},
-                      brush: {
-                        type: ['rect', 'clear']
-                      }
-                    }
-                  },
-                  legend: {
-                       data: {{x_values}}
-                  },
-                  xAxis: {
-                    type: 'category',
-                    boundaryGap: false,
-                    data: {{x_values}},
-                    show : false
-                  },
-                  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',
-                      start: 0,
-                      end: 10
-                    },
-                    {
-                      start: 0,
-                      end: 10
-                    }
-                  ],
-                  series: [
-                    {
-                      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}}
-                    }
-                  ]
-                };
-
-            option && myChart.setOption(option);
-            </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!("Balance信息网页生成成功!路径:{:?}\n\n", path);
-}

+ 12 - 13
src/main.rs

@@ -1,6 +1,6 @@
 use tracing::info;
 use crate::utils::logs;
-use crate::utils::utils::{BalanceConfigInfo, TickerConfigInfo};
+use crate::utils::utils::{AnalyzeConfigInfo, BalanceConfigInfo, TickerConfigInfo};
 
 pub mod swap_binance;
 pub mod swap_gate;
@@ -12,11 +12,12 @@ pub mod struct_standard;
 pub mod export_template;
 pub mod export_balance;
 pub mod export_ticker;
-mod hl_pr_utile;
+pub mod export_analyze;
 
 struct ConfigList {
     ticker_info: TickerConfigInfo,
     balance_info: BalanceConfigInfo,
+    analyze_info: AnalyzeConfigInfo
 }
 
 #[tokio::main]
@@ -24,9 +25,11 @@ async fn main() {
     logs::init_log_with_info();
     let ticker_config = utils::utils::get_ticker_config_info("./config_ticker.toml");
     let balance_config = utils::utils::get_balance_config_info("./config_balance.toml");
+    let analyze_config = utils::utils::get_analyze_config_info("./config_analyze.toml");
     let config_obj = ConfigList {
         ticker_info: ticker_config,
         balance_info: balance_config,
+        analyze_info: analyze_config
     };
 
     if config_obj.ticker_info.is_export {
@@ -46,16 +49,12 @@ async fn main() {
         export_balance::export_balance(config_clone).await;
     }
 
-    info!("----------正在导出Ticker信息----------");
-    let config_clone = config_obj.ticker_info.clone();
-    if http::proxy::ParsingDetail::http_enable_proxy(config_clone.proxy_address.clone()) {
-        info!("检测有代理配置,配置走代理");
+    if config_obj.analyze_info.is_export {
+        info!("----------正在导出Balance信息----------");
+        let config_clone = config_obj.analyze_info.clone();
+        if http::proxy::ParsingDetail::http_enable_proxy(config_clone.proxy_address.clone()) {
+            info!("检测有代理配置,配置走代理");
+        }
+        export_analyze::export_ticker(&config_clone.symbol, &config_clone.exchanges[0], &config_clone.range_interval).await;
     }
-
-    // 提示: 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("SOL_USDT", "GATE", "4H", 1 * 30).await;
 }

+ 40 - 2
src/utils/utils.rs

@@ -14,7 +14,7 @@ pub fn format_symbol(symbol: &str, pat: &str) -> String {
 
 #[derive(Debug, Clone, Deserialize)]
 pub struct TickerConfigInfo {
-    pub is_export:bool,
+    pub is_export: bool,
     pub proxy_address: String,
     pub exchanges: Vec<String>,
     pub symbol: String,
@@ -66,7 +66,7 @@ pub fn get_ticker_config_info(file_path: &str) -> TickerConfigInfo {
 
 #[derive(Debug, Clone, Deserialize)]
 pub struct BalanceConfigInfo {
-    pub is_export:bool,
+    pub is_export: bool,
     pub proxy_address: String,
     pub account_list: Vec<Vec<String>>,
     pub exchanges: Vec<String>,
@@ -108,6 +108,44 @@ pub fn get_balance_config_info(file_path: &str) -> BalanceConfigInfo {
     result
 }
 
+#[derive(Debug, Clone, Deserialize)]
+pub struct AnalyzeConfigInfo {
+    pub is_export: bool,
+    pub proxy_address: String,
+    pub symbol: String,
+    pub exchanges: Vec<String>,
+    pub range_interval: String,
+}
+
+impl AnalyzeConfigInfo {
+    fn new() -> AnalyzeConfigInfo {
+        AnalyzeConfigInfo {
+            is_export: false,
+            proxy_address: "".to_string(),
+            symbol: "".to_string(),
+            exchanges: vec![],
+            range_interval: "".to_string(),
+        }
+    }
+}
+
+// 获取文件内容
+pub fn get_analyze_config_info(file_path: &str) -> AnalyzeConfigInfo {
+    let file = File::open(file_path);
+    let mut contents = String::new();
+    let result = match file {
+        Ok(mut value) => {
+            value.read_to_string(&mut contents).unwrap_or_default();
+            from_str(&contents).unwrap_or(AnalyzeConfigInfo::new())
+        }
+        Err(_) => {
+            error!("没有获取到配置文件!");
+            AnalyzeConfigInfo::new()
+        }
+    };
+    result
+}
+
 //map 参数转换为 get请求?之后的参数
 //map数据转 get请求参数
 pub fn parse_params_to_str(parameters: String) -> String {