Sfoglia il codice sorgente

可以实时成html

skyfffire 11 mesi fa
parent
commit
ccca106699
2 ha cambiato i file con 432 aggiunte e 37 eliminazioni
  1. 6 37
      strategy/src/avellaneda_stoikov.rs
  2. 426 0
      strategy/src/utils.rs

+ 6 - 37
strategy/src/avellaneda_stoikov.rs

@@ -1,20 +1,17 @@
 use std::cmp::{max, min};
 use std::collections::{BTreeMap, VecDeque};
-use std::path::Path;
 use std::sync::Arc;
 use chrono::Utc;
 use rust_decimal::prelude::*;
 use rust_decimal_macros::dec;
-use tokio::fs;
-use tokio::fs::File;
-use tokio::io::AsyncWriteExt;
 use tokio::sync::Mutex;
-use tracing::{error, info};
+use tracing::{info};
 use global::cci::CentralControlInfo;
 use global::fixed_time_range_deque::FixedTimeRangeDeque;
 use global::params::Params;
 use global::predictor_state::PredictorState;
 use standard::{Depth, Record, Ticker, Trade};
+use crate::utils;
 
 #[derive(Debug)]
 pub struct AvellanedaStoikov {
@@ -585,10 +582,10 @@ impl AvellanedaStoikov {
                 cci.predictor_state_vec.remove(0);
             }
         });
-
+        //
         let error_rate = self.error_rate;
         // 将数据存入本地json文件,要求不能在行情烈度比较大的时候执行这个逻辑,防止卡交易逻辑
-        if now - self.prev_save_time < dec!(600000) || error_rate > dec!(0.15) {
+        if now - self.prev_save_time < dec!(60000) || error_rate > dec!(0.15) {
             return;
         }
         // 存放逻辑
@@ -600,8 +597,8 @@ impl AvellanedaStoikov {
         let data_c = cci.predictor_state_vec.clone();
 
         tokio::spawn(async move {
-            let temp_json_str = serde_json::to_string(&data_c).unwrap();
-            AvellanedaStoikov::write_to_file(&temp_json_str, "./db/db.json".to_string()).await;
+            let temp_html_str = utils::build_html_file(&data_c).await;
+            utils::write_to_file(&temp_html_str, "./db/db.html".to_string()).await;
         });
 
         self.prev_save_time = Decimal::from(Utc::now().timestamp_millis());
@@ -612,32 +609,4 @@ impl AvellanedaStoikov {
     pub fn get_ref_price(&mut self, _ref_ticker_map: &BTreeMap<String, Ticker>) -> Vec<Vec<Decimal>> {
         vec![]
     }
-
-    pub async fn write_to_file(json_data: &String, file_path: String) {
-        // 尝试创建文件路径
-        if let Err(e) = fs::create_dir_all(
-            // 获取文件目录路径
-            Path::new(&file_path)
-                .parent() // 获取父目录(即文件路径除去文件名后的部分)
-                .unwrap_or_else(|| Path::new("")), // 如果没有父目录,使用当前目录
-        )
-            .await
-        {
-            // 如果创建路径失败,打印错误日志
-            error!("创建目录错误: {:?}", e);
-            return; // 结束任务
-        }
-
-        // 异步地执行文件写入操作
-        if let Err(e) = async {
-            let mut file = File::create(&file_path).await?;
-            file.write_all(json_data.as_bytes()).await?;
-            Result::<(), std::io::Error>::Ok(())
-        }
-            .await
-        {
-            // 如果发生错误,只打印错误日志
-            error!("json db写入错误: {:?}", e);
-        }
-    }
 }

+ 426 - 0
strategy/src/utils.rs

@@ -1,8 +1,13 @@
 use std::ops::{Div, Mul};
+use std::path::Path;
 use chrono::Utc;
 use rand::Rng;
 use rust_decimal::Decimal;
+use tokio::fs;
+use tokio::fs::File;
+use tokio::io::AsyncWriteExt;
 use tracing::{error};
+use global::predictor_state::PredictorState;
 use global::public_params;
 
 // 生成订单的id,可以根据交易所名字来
@@ -127,6 +132,427 @@ pub fn get_limit_order_requests_num_per_second(exchange: String) -> i64 {
     }
 }
 
+pub async fn write_to_file(json_data: &String, file_path: String) {
+    // 尝试创建文件路径
+    if let Err(e) = fs::create_dir_all(
+        // 获取文件目录路径
+        Path::new(&file_path)
+            .parent() // 获取父目录(即文件路径除去文件名后的部分)
+            .unwrap_or_else(|| Path::new("")), // 如果没有父目录,使用当前目录
+    )
+        .await
+    {
+        // 如果创建路径失败,打印错误日志
+        error!("创建目录错误: {:?}", e);
+        return; // 结束任务
+    }
+
+    // 异步地执行文件写入操作
+    if let Err(e) = async {
+        let mut file = File::create(&file_path).await?;
+        file.write_all(json_data.as_bytes()).await?;
+        Result::<(), std::io::Error>::Ok(())
+    }
+        .await
+    {
+        // 如果发生错误,只打印错误日志
+        error!("json db写入错误: {:?}", e);
+    }
+}
+
+pub async fn build_html_file(data_c: &Vec<PredictorState>) -> String {
+    let temp_json_str = serde_json::to_string(&data_c).unwrap();
+
+    let str1 = r##"
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Document</title>
+    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
+    <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;
+      }
+      #echarts {
+        margin: 50px auto 0;
+        width: calc(100vw - 100px);
+        height: 1030px;
+      }
+      .page-group-wp {
+        padding: 20px 40px;
+        display: flex;
+        justify-content: space-between;
+      }
+      .page-button {
+        cursor: pointer;
+        border: 1px solid #16b777;
+        color: #16b777;
+        padding: 2px 10px;
+        user-select: none;
+      }
+      .disabled {
+        border: 1px solid #ccc;
+        color: #ccc;
+        cursor: not-allowed;
+      }
+    </style>
+  </head>
+  <body>
+    <div class="page-group-wp">
+      <div class="page-button upper-btn">上一页</div>
+      <div class="show-page">第1/1页</div>
+      <div class="page-button next-btn">下一页</div>
+    </div>
+    <div id="echarts"></div>
+  </body>
+  <script>
+    let initData = "##;
+
+    let str2 = r##";
+    let init_echarts_data = (data) => {
+      const xData = data.map((item) => dayjs(item.update_time * 1).format("MM-DD HH:mm:ss:SSS"));
+      let inventory = 0;
+      let inventoryList = [];
+
+      data.map((item, index) => {
+        if (inventory != item.inventory) {
+          inventory = item.inventory;
+          let color = "";
+          if(item.inventory > 0) color = "#F2495E"
+          if(item.inventory < 0) color = "#13BF86"
+          inventoryList.push({ name: "inventory",itemStyle:{color: color}, value: item.inventory, xAxis: index, yAxis: item.mid_price });
+        }
+      });
+      return {
+        dataZoom: [
+          {
+            type: "inside",
+            xAxisIndex: [0, 1, 2, 3, 4],
+            start: 0,
+            end: 100,
+          },
+          {
+            xAxisIndex: [0, 1, 2, 3, 4],
+            start: 0,
+            end: 100,
+          },
+        ],
+        tooltip: {
+          trigger: "axis",
+          formatter: (value) => {
+            return `时间:${value[0].name}<br />${value.map((item) => `${item.marker}${item.seriesName}:${item.value}`).join("<br/>")}`;
+          },
+          axisPointer: {
+            animation: false,
+          },
+        },
+        toolbox: {
+          feature: {
+            dataZoom: {},
+            brush: {
+              type: ["rect", "clear"],
+            },
+          },
+        },
+        legend: [
+          {
+            data: ["mid_price", "ask_price", "bid_price", "optimal_ask_price", "optimal_bid_price", "ref_price"],
+            top: "230px",
+          },
+          {
+            data: ["spread", "spread_max", "spread_min"],
+            top: "480px",
+          },
+          {
+            data: ["sigma_square"],
+            top: "630px",
+          },
+          {
+            data: ["gamma"],
+            top: "780px",
+          },
+          {
+            data: ["kappa"],
+            top: "930px",
+          },
+        ],
+        grid: [
+          {
+            top: "50px",
+            left: "60px",
+            right: "60px",
+            height: "150px", // 主图高度
+          },
+          {
+            top: "300px",
+            left: "60px",
+            right: "60px",
+            height: "150px",
+          },
+          {
+            top: "550px",
+            left: "60px",
+            right: "60px",
+            height: "50px",
+          },
+          {
+            top: "700px",
+            left: "60px",
+            right: "60px",
+            height: "50px",
+          },
+          {
+            top: "850px",
+            left: "60px",
+            right: "60px",
+            height: "50px",
+          },
+        ],
+        xAxis: [
+          {
+            type: "category",
+            data: xData,
+          },
+          {
+            gridIndex: 1, // 第1个网格的 x 轴
+            type: "category",
+            data: xData,
+          },
+          {
+            gridIndex: 2, // 第2个网格的 x 轴
+            type: "category",
+            data: xData,
+          },
+          {
+            gridIndex: 3, // 第3个网格的 x 轴
+            type: "category",
+            data: xData,
+          },
+          {
+            gridIndex: 4, // 第4个网格的 x 轴
+            type: "category",
+            data: xData,
+          },
+        ],
+        yAxis: [
+          {
+            type: "value",
+            min: "dataMin",
+          },
+          {
+            gridIndex: 1, // 第1个网格的 y 轴
+            type: "value",
+            min: "dataMin",
+          },
+          {
+            gridIndex: 2, // 第2个网格的 y 轴
+            type: "value",
+            min: "dataMin",
+          },
+          {
+            gridIndex: 3, // 第3个网格的 y 轴
+            type: "value",
+            min: "dataMin",
+          },
+          {
+            gridIndex: 4, // 第4个网格的 y 轴
+            type: "value",
+            min: "dataMin",
+          },
+        ],
+        series: [
+          {
+            name: "mid_price",
+            type: "line",
+            showSymbol: false,
+            data: data.map((item) => item.mid_price),
+            markPoint: {
+              symbolSize: 30,
+              data: inventoryList,
+            },
+          },
+          {
+            name: "ask_price",
+            type: "line",
+            showSymbol: false,
+            data: data.map((item) => item.ask_price),
+          },
+          {
+            name: "bid_price",
+            type: "line",
+            showSymbol: false,
+            data: data.map((item) => item.bid_price),
+          },
+          {
+            name: "optimal_ask_price",
+            type: "line",
+            showSymbol: false,
+            data: data.map((item) => item.optimal_ask_price),
+            lineStyle: {
+              type: "dashed",
+            },
+          },
+          {
+            name: "optimal_bid_price",
+            type: "line",
+            showSymbol: false,
+            data: data.map((item) => item.optimal_bid_price),
+            lineStyle: {
+              type: "dashed",
+            },
+          },
+          {
+            name: "ref_price",
+            type: "line",
+            showSymbol: false,
+            data: data.map((item) => item.ref_price),
+          },
+
+          {
+            name: "spread",
+            type: "line",
+            xAxisIndex: 1,
+            yAxisIndex: 1,
+            showSymbol: false,
+            data: data.map((item) => item.spread),
+          },
+          {
+            name: "spread_max",
+            type: "line",
+            xAxisIndex: 1,
+            yAxisIndex: 1,
+            showSymbol: false,
+            data: data.map((item) => item.spread_max),
+          },
+          {
+            name: "spread_min",
+            type: "line",
+            xAxisIndex: 1,
+            yAxisIndex: 1,
+            showSymbol: false,
+            data: data.map((item) => item.spread_min),
+          },
+
+          {
+            name: "sigma_square",
+            type: "line",
+            xAxisIndex: 2,
+            yAxisIndex: 2,
+            showSymbol: false,
+            data: data.map((item) => item.sigma_square),
+          },
+
+          {
+            name: "gamma",
+            type: "line",
+            xAxisIndex: 3,
+            yAxisIndex: 3,
+            showSymbol: false,
+            data: data.map((item) => item.gamma),
+          },
+
+          {
+            name: "kappa",
+            type: "line",
+            xAxisIndex: 4,
+            yAxisIndex: 4,
+            showSymbol: false,
+            data: data.map((item) => item.kappa),
+          },
+        ],
+      };
+    };
+
+    let update_echarts_data = (data) => {
+      const xData = data.map((item) => dayjs(item.update_time * 1).format("MM-DD HH:mm:ss:SSS"));
+      let inventory = 0;
+      let inventoryList = [];
+
+      data.map((item, index) => {
+        if (inventory != item.inventory) {
+          inventory = item.inventory;
+          let color = "";
+          if(item.inventory > 0) color = "#F2495E"
+          if(item.inventory < 0) color = "#13BF86"
+          inventoryList.push({ name: "inventory",itemStyle:{color: color}, value: item.inventory, xAxis: index, yAxis: item.mid_price });
+        }
+      });
+      return {
+        xAxis: [{ data: xData }, { data: xData }, { data: xData }, { data: xData }, { data: xData }],
+        series: [
+          {
+            data: data.map((item) => item.mid_price),
+            markPoint: {
+              data: inventoryList,
+            },
+          },
+          { data: data.map((item) => item.ask_price) },
+          { data: data.map((item) => item.bid_price) },
+          { data: data.map((item) => item.optimal_ask_price) },
+          { data: data.map((item) => item.optimal_bid_price) },
+          { data: data.map((item) => item.ref_price) },
+          { data: data.map((item) => item.inventory) },
+          { data: data.map((item) => item.spread) },
+          { data: data.map((item) => item.spread_max) },
+          { data: data.map((item) => item.spread_min) },
+          { data: data.map((item) => item.sigma_square) },
+          { data: data.map((item) => item.gamma) },
+          { data: data.map((item) => item.kappa) },
+        ],
+      };
+    };
+
+    let page = 1;
+    let maxPage = initData.length % 50000 > 0 ? Math.ceil(initData.length / 50000) : initData.length / 50000 || 1;
+    let showData = initData.slice((page - 1) * 100000, page * 100000 - 1);
+    let chartDom = document.getElementById("echarts");
+    let myChart = echarts.init(chartDom);
+    let initOption = init_echarts_data(showData);
+    initOption && myChart.setOption(initOption);
+
+    let updata_page = () => {
+      $(".show-page").eq(0).html(`第${page}/${maxPage}页`);
+      $(".next-btn").eq(0).removeClass("disabled");
+      $(".upper-btn").eq(0).removeClass("disabled");
+      if (page >= maxPage) $(".next-btn").eq(0).addClass("disabled");
+      if (page <= 1) $(".upper-btn").eq(0).addClass("disabled");
+    };
+    updata_page();
+    let update_echarts = () => {
+      showData = initData.slice((page - 1) * 50000, page * 50000 + 49999);
+      let option = update_echarts_data(showData);
+      myChart.setOption(option, false, true);
+    };
+
+    $(".next-btn")
+      .eq(0)
+      .click(() => {
+        if (page < maxPage) {
+          page += 1;
+          update_echarts();
+          updata_page();
+        }
+      });
+    $(".upper-btn")
+      .eq(0)
+      .click(() => {
+        if (page > 1) {
+          page -= 1;
+          update_echarts();
+          updata_page();
+        }
+      });
+  </script>
+</html>"##;
+
+    format!("{}{}{}", str1, temp_json_str, str2)
+}
+
 #[cfg(test)]
 mod tests {
     use chrono::Utc;