|
|
@@ -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);
|
|
|
}
|