| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619 |
- use std::collections::VecDeque;
- 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::public_params;
- // 生成订单的id,可以根据交易所名字来
- pub fn generate_client_id(exchange_name_some: Option<String>) -> String {
- // 交易所名字获取
- let mut exchange_name = exchange_name_some.unwrap_or("".to_string());
- // 0-1000的随机数
- let rand_num = rand::thread_rng().gen_range(1..1000);
- // 随机时间戳
- let in_ms = Utc::now().timestamp_micros();
- let time_str = in_ms.to_string();
- let time_slice = &time_str[10..];
- // 火币只能纯数字
- if exchange_name == "htx" {
- let mut rng = rand::thread_rng(); // 获取一个随机数生成器
- let random_number = rng.gen_range(1000..10000); // 生成一个四位随机数
- exchange_name = random_number.to_string();
- }
- let result = format!("{}{}{}{}", "1", time_slice, rand_num, exchange_name);
- return result;
- }
- // 大小限定:将num限定在lower_limit和upper_limit范围内
- pub fn clip(num: Decimal, lower_limit: Decimal, upper_limit: Decimal) -> Decimal {
- if num.ge(&upper_limit) {
- return upper_limit;
- }
- if num.le(&lower_limit) {
- return lower_limit;
- }
- num
- }
- // 步长修复数量
- pub fn fix_amount(amount: Decimal, step_size: Decimal) -> Decimal {
- // 思路:做除法后取整,然后使用这个值乘以步长
- amount.div(step_size).floor().mul(step_size)
- }
- // 根据最小下单价值获取下单数量
- // pub fn get_amount_by_min_amount_value(min_amount_value: Decimal,
- // order_price: Decimal,
- // step_size: Decimal) -> Decimal {
- // // ceil可以向上取整
- // let amount_unit = ((min_amount_value / order_price) / step_size).ceil();
- //
- // amount_unit * step_size
- // }
- // 步长修复价格
- pub fn fix_price(amount: Decimal, tick_size: Decimal) -> Decimal {
- // 思路:做除法后四舍五入,然后使用这个值乘以步长
- amount.div(tick_size).round().mul(tick_size)
- }
- // 每秒请求频率
- pub fn get_limit_requests_num_per_second(exchange: String) -> i64 {
- if exchange.eq("kucoin_spot") {
- return public_params::KUCOIN_SPOT_LIMIT * public_params::RATIO;
- } else if exchange.eq("kucoin_usdt_swap") {
- return public_params::KUCOIN_USDT_SWAP_LIMIT * public_params::RATIO;
- } else if exchange.eq("binance_usdt_swap") {
- return public_params::BINANCE_USDT_SWAP_LIMIT * public_params::RATIO;
- } else if exchange.eq("binance_spot") {
- return public_params::BINANCE_SPOT_LIMIT * public_params::RATIO;
- } else if exchange.eq("gate_spot") {
- return public_params::GATE_SPOT_LIMIT * public_params::RATIO;
- } else if exchange.eq("gate_usdt_swap") {
- return public_params::GATE_USDT_SWAP_LIMIT * public_params::RATIO;
- } else if exchange.eq("coinex_usdt_swap") {
- return public_params::COINEX_USDT_SWAP_LIMIT * public_params::RATIO;
- } else if exchange.eq("coinex_spot") {
- return public_params::COINEX_SPOT_LIMIT * public_params::RATIO;
- } else if exchange.eq("okex_usdt_swap") {
- return public_params::OKEX_USDT_SWAP_LIMIT * public_params::RATIO;
- } else if exchange.eq("bitget_spot") {
- return public_params::BITGET_USDT_SPOT_LIMIT * public_params::RATIO;
- } else if exchange.eq("bitget_usdt_swap") {
- return public_params::BITGET_USDT_SWAP_LIMIT * public_params::RATIO;
- } else if exchange.eq("bybit_usdt_swap"){
- return public_params::BYBIT_USDT_SWAP_LIMIT * public_params::RATIO;
- } else if exchange.eq("htx_usdt_swap") {
- return public_params::HTX_USDT_SWAP_LIMIT * public_params::RATIO;
- } else if exchange.eq("mexc_usdt_swap") {
- return public_params::MEXC_USDT_SWAP_LIMIT * public_params::RATIO;
- } else {
- error!("限频规则(ratio)未找到,请检查配置!");
- panic!("限频规则(ratio)未找到,请检查配置!");
- }
- }
- // 每秒下单请求频率
- pub fn get_limit_order_requests_num_per_second(exchange: String) -> i64 {
- if exchange.eq("kucoin_spot") {
- return public_params::KUCOIN_SPOT_LIMIT
- } else if exchange.eq("kucoin_usdt_swap") {
- return public_params::KUCOIN_USDT_SWAP_LIMIT
- } else if exchange.eq("binance_usdt_swap") {
- return public_params::BINANCE_USDT_SWAP_LIMIT
- } else if exchange.eq("binance_spot") {
- return public_params::BINANCE_SPOT_LIMIT
- } else if exchange.eq("gate_spot") {
- return public_params::GATE_SPOT_LIMIT
- } else if exchange.eq("gate_usdt_swap") {
- return public_params::GATE_USDT_SWAP_LIMIT
- } else if exchange.eq("coinex_usdt_swap") {
- return public_params::COINEX_USDT_SWAP_LIMIT
- } else if exchange.eq("coinex_spot") {
- return public_params::COINEX_SPOT_LIMIT
- } else if exchange.eq("okex_usdt_swap") {
- return public_params::OKEX_USDT_SWAP_LIMIT
- } else if exchange.eq("bitget_spot") {
- return public_params::BITGET_USDT_SPOT_LIMIT
- } else if exchange.eq("bitget_usdt_swap") {
- return public_params::BITGET_USDT_SWAP_LIMIT
- } else if exchange.eq("bybit_usdt_swap") {
- return public_params::BYBIT_USDT_SWAP_LIMIT
- } else if exchange.eq("htx_usdt_swap") {
- return public_params::HTX_USDT_SWAP_LIMIT
- } else if exchange.eq("mexc_usdt_swap") {
- return public_params::MEXC_USDT_SWAP_LIMIT
- } else {
- error!("限频规则(limit)未找到,请检查配置!");
- panic!("限频规则(limit)未找到,请检查配置!");
- }
- }
- 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 fn build_html_file(data_c: &Vec<VecDeque<Option<Decimal>>>) -> 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[0].map((item) => dayjs(item * 1).format("MM-DD HH:mm:ss:SSS"));
- let inventory = 0;
- let inventoryList = [];
- data[10].map((item, index) => {
- if (inventory != item) {
- inventory = item;
- let color = "";
- if(item > 0) color = "#F2495E"
- if(item < 0) color = "#13BF86"
- inventoryList.push({ name: "inventory",itemStyle:{color: color}, value: item, xAxis: index, yAxis: data[1][index] });
- }
- });
- 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", "fair_price"],
- top: "230px",
- },
- {
- data: ["mp", "ap", "bp"],
- top: "480px",
- },
- {
- data: ["sigma_square"],
- top: "630px",
- },
- {
- data: ["delay"],
- 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[1],
- markPoint: {
- symbolSize: 20,
- data: inventoryList,
- },
- },
- {
- name: "ask_price",
- type: "line",
- showSymbol: false,
- // data: data[2],
- data: [],
- },
- {
- name: "bid_price",
- type: "line",
- showSymbol: false,
- // data: data[3],
- data: [],
- },
- {
- name: "optimal_ask_price",
- type: "line",
- showSymbol: false,
- // data: data[8],
- data: [],
- lineStyle: {
- type: "dashed",
- },
- },
- {
- name: "optimal_bid_price",
- type: "line",
- showSymbol: false,
- // data: data[9],
- data: [],
- lineStyle: {
- type: "dashed",
- },
- },
- {
- name: "fair_price",
- type: "line",
- showSymbol: false,
- data: data[15],
- },
- {
- name: "mp",
- type: "line",
- xAxisIndex: 1,
- yAxisIndex: 1,
- showSymbol: false,
- data: data[5],
- },
- {
- name: "ap",
- type: "line",
- xAxisIndex: 1,
- yAxisIndex: 1,
- showSymbol: false,
- data: data[6],
- },
- {
- name: "bp",
- type: "line",
- xAxisIndex: 1,
- yAxisIndex: 1,
- showSymbol: false,
- data: data[7],
- },
- {
- name: "sigma_square",
- type: "line",
- xAxisIndex: 2,
- yAxisIndex: 2,
- showSymbol: false,
- data: data[11],
- },
- {
- name: "delay",
- type: "line",
- xAxisIndex: 3,
- yAxisIndex: 3,
- showSymbol: false,
- data: data[12],
- },
- {
- name: "kappa",
- type: "line",
- xAxisIndex: 4,
- yAxisIndex: 4,
- showSymbol: false,
- data: data[13],
- },
- ],
- };
- };
- let update_echarts_data = (data) => {
- const xData = data[0].map((item) => dayjs(item * 1).format("MM-DD HH:mm:ss:SSS"));
- let inventory = 0;
- let inventoryList = [];
- data[10].map((item, index) => {
- if (inventory != item) {
- inventory = item;
- let color = "";
- if(item > 0) color = "#F2495E"
- if(item < 0) color = "#13BF86"
- inventoryList.push({ name: "inventory",itemStyle:{color: color}, value: item, xAxis: index, yAxis: data[1][index] });
- }
- });
- return {
- xAxis: [{ data: xData }, { data: xData }, { data: xData }, { data: xData }, { data: xData }],
- series: [
- {
- data: data[1],
- markPoint: {
- data: inventoryList,
- },
- },
- // { data: data[2] },
- // { data: data[3] },
- // { data: data[8] },
- // { data: data[9] },
- { data: [] },
- { data: [] },
- { data: [] },
- { data: [] },
- { data: data[15] },
- { data: data[5] },
- { data: data[6] },
- { data: data[7] },
- { data: data[11] },
- { data: data[12] },
- { data: data[13] },
- ],
- };
- };
- let page = 1;
- let maxPage = initData[0].length % 100000 > 0 ? Math.ceil(initData[0].length / 100000) : initData[0].length / 100000 || 1;
- let showData = initData.map((item) => item.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.map((item) => item.slice((page - 1) * 100000, page * 100000 + 99999));
- 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;
- use rust_decimal_macros::dec;
- use crate::utils::{clip, fix_amount, fix_price, generate_client_id};
- #[test]
- fn clip_test() {
- let num = dec!(11);
- // let num2 = dec!(2);
- let lower_limit = dec!(3);
- let upper_limit = dec!(6);
- println!("mum: {}", clip(num, lower_limit, upper_limit));
- println!("mum2: {}", clip(num, lower_limit, upper_limit));
- }
- #[test]
- fn generate_client_id_test() {
- println!("{}", generate_client_id(None));
- println!("{}", generate_client_id(Some("binance".to_string())));
- }
- #[test]
- fn fix_amount_test() {
- println!("{}", fix_amount(dec!(0.9), dec!(0.04)));
- println!("{}", fix_amount(dec!(1.0), dec!(0.1)));
- println!("{}", fix_amount(dec!(0.9), dec!(0.05)));
- println!("{}", fix_amount(dec!(1), dec!(0.1)));
- println!("{}", fix_amount(dec!(0.01), dec!(0.05)));
- }
- #[test]
- fn fix_price_test() {
- println!("{}", fix_price(dec!(1), dec!(0.1)));
- println!("{}", fix_price(dec!(0.9), dec!(2.0)));
- println!("{}", fix_price(dec!(1.1), dec!(0.1)));
- println!("{}", fix_price(dec!(1.2), dec!(0.5)));
- println!("{}", fix_price(dec!(4999.99), dec!(0.5)));
- }
- #[test]
- fn utc_timestamp_test() {
- let now = Utc::now();
- println!("timestamp: {}", now.timestamp());
- println!("timestamp_millis: {}", now.timestamp_millis());
- println!("timestamp_micros: {}", now.timestamp_micros());
- println!("timestamp_nanos: {}", now.timestamp_nanos_opt().unwrap());
- }
- }
|