Browse Source

添加bybit成交记录导出

gepangpang 1 year ago
parent
commit
a40f64cd46

+ 109 - 43
derive/src/bybit_swap_export.rs

@@ -1,8 +1,6 @@
 use std::collections::{BTreeMap};
-use std::str::FromStr;
 use async_trait::async_trait;
 use chrono::{FixedOffset, NaiveDateTime, TimeZone};
-use rust_decimal::Decimal;
 use rust_decimal::prelude::ToPrimitive;
 use serde::{Deserialize, Serialize};
 use tracing::{warn};
@@ -22,6 +20,17 @@ impl BybitSwapExport {
     }
 }
 
+#[async_trait]
+impl ExportConnector for BybitSwapExport {
+    async fn export_trades(&mut self, prefix_name: &str, symbol: String, start_time: i64, end_time: i64, limit: i64) -> String {
+        let pnl_array = get_position_pnl(self.request.clone(), symbol.clone(), start_time.clone(), end_time.clone(), limit.clone()).await;
+        let trades_array = get_trades(self.request.clone(), symbol.clone(), start_time.clone(), end_time.clone(), limit.clone()).await;
+        let pnl_header_array = vec!["订单编号", "交易币对", "买卖方向", "入场价格", "出场价格", "交易类型", "成交数量", "交易盈亏", "创建时间", "更新时间"];
+        let trades_header_array = vec!["交易编号", "订单编号", "交易币对", "买卖方向", "成交价格", "成交数量", "交易类型", "成交价值", "交易费用", "交易费率", "交易时间"];
+        global::export_utils::export_excel_sheets(vec![pnl_header_array, trades_header_array], vec![pnl_array, trades_array], vec!["pnl", "trades"], prefix_name)
+    }
+}
+
 /// TradesSwap
 /// - `symbol`: String,
 /// - `order_id`: String,
@@ -84,52 +93,109 @@ struct TradesSwap {
     seq: i64,
 }
 
-#[async_trait]
-impl ExportConnector for BybitSwapExport {
-    async fn export_trades(&mut self, prefix_name: &str, symbol: String, start_time: i64, end_time: i64, limit: i64) -> String {
-        let symbol_format = utils::format_symbol(symbol.clone(), "");
-        let limit_params = if limit > 1000 {
-            warn!("查询条数最大为1000条,已修改为1000条!");
-            1000
-        } else { limit };
-        let mut cursor = "".to_string();
-        let mut data_array = vec![];
+async fn get_trades(mut request: BybitSwapRest, symbol: String, start_time: i64, end_time: i64, limit: i64) -> Vec<Vec<String>> {
+    let symbol_format = utils::format_symbol(symbol.clone(), "");
+    let limit_params = if limit > 100 {
+        warn!("查询条数最大为1000条,已修改为100条!");
+        100
+    } else { limit };
+    let mut cursor = "".to_string();
+    let mut data_array = vec![];
 
-        loop {
-            let res_data = self.request.get_user_trades(symbol_format.clone(), start_time, end_time, limit_params, cursor.clone()).await;
-            if res_data.code == 200 {
-                let data_clone = res_data.data.clone();
-                let trades_info: Vec<TradesSwap> = serde_json::from_str(&*data_clone["list"].to_string()).unwrap();
-                cursor = data_clone["nextPageCursor"].as_str().unwrap_or("").to_string();
-                for value in trades_info.iter() {
-                    let time = FixedOffset::east_opt(8 * 3600).unwrap().from_utc_datetime(&NaiveDateTime::from_timestamp_millis(value.exec_time.parse::<i64>().unwrap()).unwrap()).format("%Y-%m-%d %H:%M:%S%.3f").to_string();
-                    let order_type = value.order_type.clone();
-                    let income = (Decimal::from_str(&value.exec_price).unwrap() - Decimal::from_str(&value.order_price).unwrap()) * Decimal::from_str(&value.exec_qty).unwrap();
+    loop {
+        let res_data = request.get_user_trades(symbol_format.clone(), start_time, end_time, limit_params, cursor.clone()).await;
+        if res_data.code == 200 {
+            let data_clone = res_data.data.clone();
+            let trades_info: Vec<TradesSwap> = serde_json::from_str(&*data_clone["list"].to_string()).unwrap();
+            cursor = data_clone["nextPageCursor"].as_str().unwrap_or("").to_string();
+            for value in trades_info.iter() {
+                let time = FixedOffset::east_opt(8 * 3600).unwrap().from_utc_datetime(&NaiveDateTime::from_timestamp_millis(value.exec_time.parse::<i64>().unwrap()).unwrap()).format("%Y-%m-%d %H:%M:%S%.3f").to_string();
+                let order_type = value.order_type.clone();
 
-                    data_array.push(vec![
-                        value.exec_id.clone(),
-                        value.order_id.clone(),
-                        value.symbol.clone(),
-                        value.side.clone(),
-                        value.exec_price.clone(),
-                        value.exec_qty.clone(),
-                        order_type.to_string(),
-                        value.exec_value.clone(),
-                        value.exec_fee.clone(),
-                        value.fee_rate.clone(),
-                        income.to_string(),
-                        time,
-                    ]);
-                }
+                data_array.push(vec![
+                    value.exec_id.clone(),
+                    value.order_id.clone(),
+                    value.symbol.clone(),
+                    value.side.clone(),
+                    value.exec_price.clone(),
+                    value.exec_qty.clone(),
+                    order_type.to_string(),
+                    value.exec_value.clone(),
+                    value.exec_fee.clone(),
+                    value.fee_rate.clone(),
+                    time,
+                ]);
+            }
 
-                if trades_info.len().to_i64().unwrap() < limit_params {
-                    break;
-                }
+            if trades_info.len().to_i64().unwrap() < limit_params {
+                break;
             }
         }
+    }
+    data_array.reverse();
+    data_array
+}
 
-        let header_array = vec!["交易编号", "订单编号", "交易币对", "买卖方向", "成交价格", "成交数量", "交易类型", "成交价值", "交易费用", "交易费率", "交易盈亏", "交易时间"];
-        data_array.reverse();
-        global::export_utils::export_excel(header_array, data_array, prefix_name)
+#[derive(Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+struct PositionPnlSwap {
+    symbol: String,
+    order_id: String,
+    side: String,
+    qty: String,
+    order_price: String,
+    order_type: String,
+    exec_type: String,
+    closed_size: String,
+    cum_entry_value: String,
+    avg_entry_price: String,
+    cum_exit_value: String,
+    avg_exit_price: String,
+    closed_pnl: String,
+    fill_count: String,
+    leverage: String,
+    created_time: String,
+    updated_time: String,
+}
+async fn get_position_pnl(mut request: BybitSwapRest, symbol: String, start_time: i64, end_time: i64, limit: i64) -> Vec<Vec<String>> {
+    let symbol_format = utils::format_symbol(symbol.clone(), "");
+    let limit_params = if limit > 100 {
+        warn!("查询条数最大为1000条,已修改为100条!");
+        100
+    } else { limit };
+    let mut cursor = "".to_string();
+    let mut data_array = vec![];
+
+    loop {
+        let res_data = request.get_position_closed_pnl(symbol_format.clone(), start_time, end_time, limit_params, cursor.clone()).await;
+        if res_data.code == 200 {
+            let data_clone = res_data.data.clone();
+            let trades_info: Vec<PositionPnlSwap> = serde_json::from_str(&*data_clone["list"].to_string()).unwrap();
+            cursor = data_clone["nextPageCursor"].as_str().unwrap_or("").to_string();
+            for value in trades_info.iter() {
+                let create_time = FixedOffset::east_opt(8 * 3600).unwrap().from_utc_datetime(&NaiveDateTime::from_timestamp_millis(value.created_time.parse::<i64>().unwrap()).unwrap()).format("%Y-%m-%d %H:%M:%S%.3f").to_string();
+                let update_time = FixedOffset::east_opt(8 * 3600).unwrap().from_utc_datetime(&NaiveDateTime::from_timestamp_millis(value.updated_time.parse::<i64>().unwrap()).unwrap()).format("%Y-%m-%d %H:%M:%S%.3f").to_string();
+
+                data_array.push(vec![
+                    value.order_id.clone(),
+                    value.symbol.clone(),
+                    value.side.clone(),
+                    value.avg_entry_price.clone(),
+                    value.avg_exit_price.clone(),
+                    value.order_type.clone(),
+                    value.closed_size.clone(),
+                    value.closed_pnl.clone(),
+                    create_time,
+                    update_time,
+                ]);
+            }
+
+            if trades_info.len().to_i64().unwrap() < limit_params {
+                break;
+            }
+        }
     }
+
+    data_array.reverse();
+    data_array
 }

+ 2 - 2
derive/tests/bybit_swap_export_test.rs

@@ -5,7 +5,7 @@ use derive::export_excel::ExportEnum;
 use crate::export_excel_test::test_new_export;
 
 
-const SYMBOL: &str = "ORDI_USDT";
+const SYMBOL: &str = "1000PEPE_USDT";
 
 // 测试获取Exchange实体
 #[tokio::test]
@@ -14,6 +14,6 @@ async fn test_get_self_exchange() {
     global::log_utils::init_log_with_trace();
 
     let mut export = test_new_export(ExportEnum::BybitSwap).await;
-    let export_trades = export.export_trades("bybit_swap", SYMBOL.to_string(), 1726109857000, 1726132850000, 100).await;
+    let export_trades = export.export_trades("bybit_swap", SYMBOL.to_string(), 1726131600000, 1726192800000, 100).await;
     trace!(?export_trades);
 }

+ 19 - 0
exchanges/src/bybit_swap_rest.rs

@@ -277,6 +277,25 @@ impl BybitSwapRest {
         ).await;
         data
     }
+    //账户成交历史
+    pub async fn get_position_closed_pnl(&mut self, symbol: String, start_time: i64, end_time: i64, limit: i64, cursor: String) -> ResponseData {
+        let mut params = serde_json::json!({
+            "category": "linear",
+            "limit": 100
+         });
+        if symbol != "" { params["symbol"] = serde_json::json!(symbol); }
+        if start_time > 0 { params["startTime"] = serde_json::json!(start_time); }
+        if end_time > 0 { params["endTime"] = serde_json::json!(end_time); }
+        if limit > 0 { params["limit"] = serde_json::json!(limit); }
+        if cursor != "" { params["cursor"] = serde_json::json!(cursor); }
+        let data = self.request("GET".to_string(),
+                                "/v5".to_string(),
+                                "/position/closed-pnl".to_string(),
+                                true,
+                                params.to_string(),
+        ).await;
+        data
+    }
     /*******************************************************************************************************/
     /*****************************************工具函数********************************************************/
     /*******************************************************************************************************/

+ 38 - 0
global/src/export_utils.rs

@@ -35,4 +35,42 @@ pub fn export_excel(header_array: Vec<&str>, data: Vec<Vec<String>>, prefix_name
     }).expect("写入excel错误!");
     wb.close().expect("关闭excel错误!");
     file_name
+}
+
+pub fn export_excel_sheets(header_array: Vec<Vec<&str>>, data: Vec<Vec<Vec<String>>>, sheet_name: Vec<&str>, prefix_name: &str) -> String {
+    //本地存储路径
+    let save_path = "C:/Users/Public/Documents/";
+    let _ = std::fs::create_dir(format!("{}rust_export/", save_path));
+    // 当前时间
+    let date = Local::now().format("%Y-%m-%d").to_string();
+    let uid = Uuid::new_v4().to_string()[0..8].to_string();
+    let name = if prefix_name == "" { "".to_string() } else { format!("{}_", prefix_name) };
+    let file_name = format!("{}rust_export/{}{}_{}.xlsx", save_path, name, date, uid);
+    let mut wb = Workbook::create(&file_name);
+    if header_array.len() != data.len() { return "表头和数据数量有误,请检查后再试!".to_string(); }
+    for (index, headers) in header_array.iter().enumerate() {
+        let mut sheet = wb.create_sheet(sheet_name[index]);
+        // 设置行宽
+        for _ in 0..headers.len() {
+            sheet.add_column(Column { width: 20.0 });
+        }
+        wb.write_sheet(&mut sheet, |sheet_writer| {
+            let sw = sheet_writer;
+            let mut header_row = Row::new();
+            for value in headers.clone() {
+                header_row.add_cell(value);
+            }
+            sw.append_row(header_row).expect(format!("创建excel标题错误!header:{:?}", header_array).as_str());
+            for cell in data[index].clone() {
+                let mut data_row = Row::new();
+                for value in cell.clone() {
+                    data_row.add_cell(value);
+                }
+                sw.append_row(data_row).expect(format!("添加excel数据错误!cell:{:?}", cell).as_str());
+            }
+            sw.append_row(Default::default())
+        }).expect("写入excel错误!");
+    }
+    wb.close().expect("关闭excel错误!");
+    file_name
 }