use std::collections::BTreeMap; use std::error::Error; use std::time::{SystemTime, UNIX_EPOCH}; use reqwest::header::{HeaderMap, HeaderValue}; use hex; use reqwest::Client; use rust_decimal::Decimal; use rust_decimal_macros::dec; use serde_json::Value; use crate::http_tool::RestTool; use crate::response_base::ResponseData; use sha2::{Digest, Sha256}; use tracing::{error}; #[derive(Clone)] pub struct CoinexSwapRest { label: String, base_url: String, client: Client, /*******参数*/ //登陆所需参数 login_param: BTreeMap, delays: Vec, max_delay: i64, avg_delay: Decimal, } impl CoinexSwapRest { /*******************************************************************************************************/ /*****************************************获取一个对象****************************************************/ /*******************************************************************************************************/ pub fn new(login_param: BTreeMap) -> CoinexSwapRest { return CoinexSwapRest::new_label("default-CoinexSwapRest".to_string(), login_param); } pub fn new_label(label: String, login_param: BTreeMap) -> CoinexSwapRest { let base_url: String = String::from("https://api.coinex.com"); /*****返回结构体*******/ CoinexSwapRest { label, base_url, client: Client::new(), login_param, delays: vec![], max_delay: 0, avg_delay: dec!(0.0), } } /*******************************************************************************************************/ /*****************************************rest请求函数********************************************************/ /*******************************************************************************************************/ //获取服务器当前时间 pub async fn get_server_time(&mut self) -> ResponseData { let data = self.request("GET".to_string(), "/v2".to_string(), "/time".to_string(), false, None, None, ).await; data } //查询个人交易费率 pub async fn wallet_fee(&mut self) -> ResponseData { let data = self.request("GET".to_string(), "/v2".to_string(), "/account/trade-fee-rate".to_string(), true, None, None, ).await; data } //查询合约账户 pub async fn get_account(&mut self) -> ResponseData { let data = self.request("GET".to_string(), "/v2".to_string(), "/assets/futures/balance".to_string(), true, None, None, ).await; data } //查询现货账户 pub async fn get_spot_account(&mut self) -> ResponseData { let data = self.request("GET".to_string(), "/v2".to_string(), "/assets/spot/balance".to_string(), true, None, None, ).await; data } //指定币对仓位列表 pub async fn get_position(&mut self, market: String) -> ResponseData { let params = serde_json::json!({ "market": market, "market_type": "FUTURES" }); let data = self.request("GET".to_string(), "/v2".to_string(), "/futures/pending-position".to_string(), true, Some(params.to_string()), None, ).await; data } //用户仓位列表 pub async fn get_user_position(&mut self) -> ResponseData { let params = serde_json::json!({ "market_type": "FUTURES" }); let data = self.request("GET".to_string(), "/v2".to_string(), "/futures/pending-position".to_string(), true, Some(params.to_string()), None, ).await; data } //获取所有合约交易行情统计 market 市场名列表,多个市场名之间使用英文","分隔,空字符串或不传表示查询全部市场,限制最多10个市场 pub async fn get_ticker(&mut self, market: String) -> ResponseData { let params = serde_json::json!({ "market": market }); let data = self.request("GET".to_string(), "/v2".to_string(), "/futures/ticker".to_string(), false, Some(params.to_string()), None, ).await; data } //查询所有的合约信息 pub async fn get_market_details(&mut self, market: String) -> ResponseData { let params = serde_json::json!({ "market": market }); let data = self.request("GET".to_string(), "/v2".to_string(), "/futures/market".to_string(), false, Some(params.to_string()), None, ).await; data } //查询单个订单详情 /spot/order-status?market=CETUSDT&order_id=13400 pub async fn get_order_details(&mut self, order_id: String, market: String) -> ResponseData { let params = serde_json::json!({ "market": market, "order_id": order_id }); let data = self.request("GET".to_string(), "/v2".to_string(), "/futures/order-status".to_string(), true, Some(params.to_string()), None, ).await; data } //查询未完成合约订单 /futures/pending-order?market=CETUSDT&market_type=FUTURES&side=buy&page=1&limit=10 pub async fn get_pending_order(&mut self, client_id: String) -> ResponseData { let params = serde_json::json!({ "market_type": "FUTURES", "client_id": client_id, "limit": 10 }); let data = self.request("GET".to_string(), "/v2".to_string(), "/futures/pending-order".to_string(), true, Some(params.to_string()), None, ).await; data } pub async fn get_pending_orders(&mut self) -> ResponseData { let params = serde_json::json!({ "market_type": "FUTURES", "limit": 100 }); let data = self.request("GET".to_string(), "/v2".to_string(), "/futures/pending-order".to_string(), true, Some(params.to_string()), None, ).await; data } pub async fn get_finished_orders(&mut self) -> ResponseData { let params = serde_json::json!({ "market_type": "FUTURES", "limit": 100 }); let data = self.request("GET".to_string(), "/v2".to_string(), "/futures/finished-order".to_string(), true, Some(params.to_string()), None, ).await; data } //下单 // coinex swap 平仓需考虑最小下单量 只能通过close_position和position_id来平仓 pub async fn order(&mut self, market: String, pos_side: String, side: String, size: Decimal, price: Decimal, client_id: String, ) -> ResponseData { // 默认为限价单 let mut type_y = "limit".to_string(); // 0为市价单, if price == Decimal::ZERO { type_y = "market".to_string(); } let data; match format!("{}_{}", pos_side, side).as_str() { "long_buy" => {//开多 data = self.swap_order(market, side, type_y, size, price, client_id, false).await; } "long_sell" => {//平多 data = self.close_position(market, type_y, price, client_id, false).await; } "short_buy" => {//平空 data = self.close_position(market, type_y, price, client_id, false).await; } "short_sell" => {//开空 data = self.swap_order(market, side, type_y, size, price, client_id, false).await; } _ => { // 处理未知请求类型 error!("下单失败,数量异常! size: {}", size); data = ResponseData::error(self.label.clone(), format!("下单失败, 下单参数: ", market, pos_side, side, size, price, client_id)); } }; data } // 平仓下单 pub async fn close_position(&mut self, market: String, type_y: String, price: Decimal, client_id: String, is_hide: bool) -> ResponseData { // 数量不传为全平 let param = serde_json::json!({ "market":market, "market_type": "FUTURES", "type": type_y, "price":price, "client_id":client_id, "is_hide": is_hide }); let data = self.request("POST".to_string(), "/v2".to_string(), "/futures/close-position".to_string(), true, None, Some(param.to_string()), ).await; data } //合约交易开仓下单 pub async fn swap_order(&mut self, market: String, side: String, type_y: String, amount: Decimal, price: Decimal, client_id: String, is_hide: bool) -> ResponseData { let param = serde_json::json!({ "market":market, "market_type": "FUTURES", "side": side, "type": type_y, "amount":amount, "price":price, "client_id":client_id, "is_hide": is_hide }); let data = self.request("POST".to_string(), "/v2".to_string(), "/futures/order".to_string(), true, None, Some(param.to_string()), ).await; data } //设置持仓模式 pub async fn setting_dual_mode(&mut self) -> ResponseData { ResponseData::error(self.label.clone(), "设置双向持仓失败, coinex没有设置双向持仓".to_string()) } //更新双仓模式下的杠杆 pub async fn setting_dual_leverage(&mut self, market: String, leverage: i32) -> ResponseData { let params = serde_json::json!({ "market": market, "market_type": "FUTURES", // cross: 全仓。全仓模式下,合约账户的全部可用余额都可用作当前全部仓位的共享保证金,系统会使用合约账户中的可用余额自动追加保证金,以避免仓位被强平 //isolated: 逐仓。逐仓模式下,仓位保证金不会共享,单个仓位的保证金仅用于当前仓位,系统不会自动追加保证金,需要手动追加。 "margin_mode": "cross", "leverage":leverage, }); let data = self.request("POST".to_string(), "/v2".to_string(), "/futures/adjust-position-leverage".to_string(), true, None, Some(params.to_string()), ).await; data } //撤销单个订单 pub async fn cancel_order(&mut self, market: String, order_id: &str, client_id: &str) -> ResponseData { if order_id != "" { // 如果真实订单id不为空,则用真实订单id取消订单 let id = order_id.parse::().unwrap(); let params = serde_json::json!({ "market": market, "market_type": "FUTURES", "order_id": id }); let data = self.request("POST".to_string(), "/v2".to_string(), "/futures/cancel-order".to_string(), true, None, Some(params.to_string()), ).await; data } else if client_id != "" { // 如果客户端id不为空,则用客户端id取消订单 let params = serde_json::json!({ "market": market, "market_type": "FUTURES", "client_id": client_id }); let mut data = self.request("POST".to_string(), "/v2".to_string(), "/futures/cancel-order-by-client-id".to_string(), true, None, Some(params.to_string()), ).await; // 非空的 if data.code == 200 && !data.data.is_null() { data.data = data.data.as_array().unwrap()[0]["data"].clone(); } data } else { // 否则返回错误 error!("取消订单失败失败,id异常"); ResponseData::error(self.label.clone(), format!("取消订单失败失败, orderId:{:?}, clientId: {:?} ", order_id, client_id)) } } // 撤销所有挂单 pub async fn cancel_order_all(&mut self, market: String) -> ResponseData { let params = serde_json::json!({ "market": market, "market_type": "FUTURES" }); let data = self.request("POST".to_string(), "/v2".to_string(), "/futures/cancel-all-order".to_string(), true, None, Some(params.to_string()), ).await; data } //查询个人成交记录 pub async fn my_trades(&mut self, market: String, limit: i64) -> ResponseData { let mut params = serde_json::json!({ "market": market, "market_type": "FUTURES", "limit": 1000 }); if limit > 0 { params["limit"] = serde_json::json!(limit); } let data = self.request("GET".to_string(), "/v2".to_string(), "/futures/user-deals".to_string(), true, Some(params.to_string()), None).await; data } //查询合约账户变更历史 pub async fn account_book(&mut self) -> ResponseData { error!("查询合约账户变更历史失败,无实现"); ResponseData::error(self.label.clone(), "查询合约账户变更历史失败,接口没实现".to_string()) } //查询子账号列表 pub async fn account_get(&mut self) -> ResponseData { let params = serde_json::json!({ "is_frozen":false, "page":1, "limit":100, }); let data = self.request("GET".to_string(), "/v2".to_string(), "/account/subs".to_string(), true, Some(params.to_string()), None).await; data } //根据子账号,生成子账号 APIKEY pub async fn account_subs_api(&mut self, params: Value) -> ResponseData { let data = self.request("POST".to_string(), "/v2".to_string(), "/account/subs/api".to_string(), true, None, Some(params.to_string())).await; data } //获取子账号 APIKEY 列表 pub async fn account_get_apikey(&mut self, params: Value) -> ResponseData { let data = self.request("GET".to_string(), "/v2".to_string(), "/account/subs/api".to_string(), true, Some(params.to_string()), None).await; data } //获取子账号 APIKEY 详情 pub async fn account_get_detail(&mut self, params: Value) -> ResponseData { let data = self.request("GET".to_string(), "/v2".to_string(), "/account/subs/api-detail".to_string(), true, Some(params.to_string()), None).await; data } //编辑子账号 APIKEY pub async fn account_get_update(&mut self, params: Value) -> ResponseData { let data = self.request("POST".to_string(), "/v2".to_string(), "/account/subs/edit-api".to_string(), true, None, Some(params.to_string())).await; data } //删除子账号 APIKEY pub async fn account_del_apikey(&mut self, params: Value) -> ResponseData { let data = self.request("POST".to_string(), "/v2".to_string(), "/account/subs/delete-api".to_string(), true, None, Some(params.to_string())).await; data } /*******************************************************************************************************/ /*****************************************工具函数********************************************************/ /*******************************************************************************************************/ pub fn get_delays(&self) -> Vec { self.delays.clone() } pub fn get_avg_delay(&self) -> Decimal { self.avg_delay.clone() } pub fn get_max_delay(&self) -> i64 { self.max_delay.clone() } // fn get_delay_info(&mut self) { // let last_100 = if self.delays.len() > 100 { // self.delays[self.delays.len() - 100..].to_vec() // } else { // self.delays.clone() // }; // // let max_value = last_100.iter().max().unwrap(); // if max_value.clone().to_owned() > self.max_delay { // self.max_delay = max_value.clone().to_owned(); // } // // let sum: i64 = last_100.iter().sum(); // let sum_v = Decimal::from_i64(sum).unwrap(); // let len_v = Decimal::from_u64(last_100.len() as u64).unwrap(); // self.avg_delay = (sum_v / len_v).round_dp(1); // self.delays = last_100.clone().into_iter().collect(); // } //调用请求 async fn request(&mut self, request_type: String, prefix_url: String, request_url: String, is_login: bool, params: Option, body: Option) -> ResponseData { // trace!("login_param:{:?}", self.login_param); //解析账号信息 let mut access_key = "".to_string(); let mut secret_key = "".to_string(); if self.login_param.contains_key("access_key") { access_key = self.login_param.get("access_key").unwrap().to_string(); } if self.login_param.contains_key("secret_key") { secret_key = self.login_param.get("secret_key").unwrap().to_string(); } let mut is_login_param = true; if access_key == "" || secret_key == "" { is_login_param = false } let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis().to_string(); // url let mut url_and_query = format!("{}{}", prefix_url.clone(), request_url.clone()); let mut headers = HeaderMap::new(); headers.insert("Content-Type", HeaderValue::from_static("application/json")); headers.insert( "X-COINEX-KEY", HeaderValue::from_str(&self.login_param.get("access_key").unwrap()).unwrap(), ); headers.insert( "X-COINEX-TIMESTAMP", HeaderValue::from_str(×tamp).unwrap(), ); if let Some(params) = params { let query = RestTool::parse_params_to_str(params); url_and_query = format!("{}?{}", url_and_query, query); } let body_s = if let Some(body) = body { body } else { "".to_string() }; //是否需要登陆-- 组装sing if is_login { if !is_login_param { let e = ResponseData::error(self.label.clone(), "登陆参数错误!".to_string()); return e; } else {//需要登陆-且登陆参数齐全 //组装sing let sing = Self::sign( &request_type, &url_and_query, &body_s, timestamp.clone(), &secret_key, ); // trace!("sing:{}", sing); //组装header headers.insert("X-COINEX-SIGN", HeaderValue::from_str(&sing.unwrap()).unwrap()); } } let start_time = chrono::Utc::now().timestamp_millis(); let response = self.http_toll( url_and_query, request_type, body_s.clone(), headers, ).await; let time_array = chrono::Utc::now().timestamp_millis() - start_time; self.delays.push(time_array); // self.get_delay_info(); response } fn sign( method: &String, path: &String, body: &String, timestamp: String, secret_key: &String, ) -> Result> { let prepared_str = format!( "{}{}{}{}{}", method, path, body, timestamp, secret_key ); let hash = Sha256::digest(prepared_str.as_bytes()); Ok(hex::encode(hash)) } async fn http_toll(&mut self, request_path: String, request_type: String, body: String, headers: HeaderMap) -> ResponseData { /****请求接口与 地址*/ let url = format!("{}{}", self.base_url.to_string(), request_path); let request_type = request_type.clone().to_uppercase(); let request_builder = match request_type.as_str() { "GET" => self.client.get(&url).headers(headers), "POST" => self.client.post(&url).body(body.clone()).headers(headers), "DELETE" => self.client.delete(&url).headers(headers), // "PUT" => self.client.put(url.clone()).json(¶ms), _ => { panic!("{}", format!("错误的请求类型:{}", request_type.clone())) } }; // 读取响应的内容 let res = request_builder.send().await; match res { Ok(response) => { let is_success = response.status().is_success(); // 先检查状态码 let text_result = response.text().await; match text_result { Ok(text) => { let data_json_str: Result = serde_json::from_str(text.as_str()); match data_json_str { Ok(data_json) => { return if is_success && data_json["code"].to_string() == "0" { self.on_success_data(data_json["data"].clone()) } else { self.on_error_data(&text, &url, &body) }; } Err(e) => { error!("{} 请求完成,解析响应内容JSON失败 {} {}", url, text.as_str(), e); self.on_error_data(&e.to_string(), &url, &body) } } } Err(e) => { error!("{} 请求完成,解析响应内容失败 {}", url, e); self.on_error_data(&e.to_string(), &url, &body) } } } Err(e) => { // 异常情况 error!("{} 请求失败,网络错误 {}", url, e); self.on_error_data(&e.to_string(), &url, &body) } } } pub fn on_success_data(&mut self, text: Value) -> ResponseData { ResponseData::new(self.label.clone(), 200, "success".to_string(), text) } pub fn on_error_data(&mut self, text: &String, base_url: &String, params: &String) -> ResponseData { let json_value = serde_json::from_str::(&text); match json_value { Ok(data) => { let message; if !data["message"].is_null() { message = format!("{}:{}", data["code"].to_string(), data["message"].as_str().unwrap()); } else { message = data["code"].to_string(); } let mut error = ResponseData::error(self.label.clone(), message); error.message = format!("请求地址:{}, 请求参数:{}, 报错内容:{}。", base_url, params, error.message); error } Err(e) => { error!("解析错误:{:?}", e); let error = ResponseData::error("".to_string(), format!("json 解析失败:{},相关参数:{}", e, text)); error } } } }