| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 |
- 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: {
- 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
- },
- toolbox: {
- feature: {
- dataZoom: {
- yAxisIndex: false
- },
- saveAsImage: {
- pixelRatio: 2
- }
- }
- },
- tooltip: {
- axisPointer: {
- type: 'cross',
- lineStyle: {
- type: 'dashed',
- width: 1
- }
- }
- },
- grid: {
- bottom: 90
- },
- dataZoom: [
- {
- type: 'inside'
- },
- {
- type: 'slider'
- }
- ],
- 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
- }
- ]
- };
- 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);
- }
|