|
|
@@ -0,0 +1,641 @@
|
|
|
+use std::collections::{BTreeMap};
|
|
|
+use exchanges::htx_swap_rest::HtxSwapRest;
|
|
|
+use std::io::{Error, ErrorKind};
|
|
|
+use tokio::sync::mpsc::Sender;
|
|
|
+use std::str::FromStr;
|
|
|
+use async_trait::async_trait;
|
|
|
+use futures::stream::FuturesUnordered;
|
|
|
+use futures::TryStreamExt;
|
|
|
+use rust_decimal::Decimal;
|
|
|
+use rust_decimal::prelude::{FromPrimitive};
|
|
|
+use serde_json::json;
|
|
|
+use serde_json::Value::Null;
|
|
|
+use tokio::spawn;
|
|
|
+use tokio::time::Instant;
|
|
|
+use tracing::{error, info};
|
|
|
+use global::trace_stack::TraceStack;
|
|
|
+use crate::exchange::ExchangeEnum;
|
|
|
+use crate::{Account, Market, Order, OrderCommand, Platform, Position, PositionModeEnum, Ticker, utils};
|
|
|
+
|
|
|
+#[allow(dead_code)]
|
|
|
+#[derive(Clone)]
|
|
|
+pub struct HtxSwap {
|
|
|
+ exchange: ExchangeEnum,
|
|
|
+ symbol: String,
|
|
|
+ is_colo: bool,
|
|
|
+ params: BTreeMap<String, String>,
|
|
|
+ request: HtxSwapRest,
|
|
|
+ market: Market,
|
|
|
+ order_sender: Sender<Order>,
|
|
|
+ error_sender: Sender<Error>,
|
|
|
+}
|
|
|
+
|
|
|
+impl HtxSwap {
|
|
|
+ pub async fn new(symbol: String, is_colo: bool, params: BTreeMap<String, String>, order_sender: Sender<Order>, error_sender: Sender<Error>) -> HtxSwap {
|
|
|
+ let market = Market::new();
|
|
|
+ let mut htx_swap = HtxSwap {
|
|
|
+ exchange: ExchangeEnum::HtxSwap,
|
|
|
+ symbol: symbol.to_uppercase(),
|
|
|
+ is_colo,
|
|
|
+ params: params.clone(),
|
|
|
+ request: HtxSwapRest::new(is_colo, params.clone()),
|
|
|
+ market,
|
|
|
+ order_sender,
|
|
|
+ error_sender,
|
|
|
+ };
|
|
|
+ htx_swap.market = HtxSwap::get_market(&mut htx_swap).await.unwrap();
|
|
|
+ // 修改持仓模式
|
|
|
+ let mode_result = htx_swap.set_dual_mode("", true).await;
|
|
|
+ match mode_result {
|
|
|
+ Ok(ok) => {
|
|
|
+ info!("HtxSwap:设置持仓模式成功!{:?}", ok);
|
|
|
+ }
|
|
|
+ Err(error) => {
|
|
|
+ error!("HtxSwap:设置持仓模式失败!{:?}", error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 设置持仓杠杆
|
|
|
+ let lever_rate_result = htx_swap.set_dual_leverage("10").await;
|
|
|
+ match lever_rate_result {
|
|
|
+ Ok(ok) => {
|
|
|
+ info!("HtxSwap:设置持仓杠杆成功!{:?}", ok);
|
|
|
+ }
|
|
|
+ Err(error) => {
|
|
|
+ error!("HtxSwap:设置持仓杠杆失败!{:?}", error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return htx_swap;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[async_trait]
|
|
|
+impl Platform for HtxSwap {
|
|
|
+ fn clone_box(&self) -> Box<dyn Platform + Send + Sync> { Box::new(self.clone()) }
|
|
|
+
|
|
|
+ fn get_self_exchange(&self) -> ExchangeEnum { ExchangeEnum::HtxSwap }
|
|
|
+
|
|
|
+ fn get_self_symbol(&self) -> String { self.symbol.clone() }
|
|
|
+
|
|
|
+ fn get_self_is_colo(&self) -> bool { self.is_colo }
|
|
|
+
|
|
|
+ fn get_self_params(&self) -> BTreeMap<String, String> { self.params.clone() }
|
|
|
+
|
|
|
+ fn get_self_market(&self) -> Market { self.market.clone() }
|
|
|
+
|
|
|
+ fn get_request_delays(&self) -> Vec<i64> {
|
|
|
+ // self.request.get_delays()
|
|
|
+ vec![]
|
|
|
+ }
|
|
|
+
|
|
|
+ fn get_request_avg_delay(&self) -> Decimal {
|
|
|
+ // self.request.get_avg_delay()
|
|
|
+ Decimal::ZERO
|
|
|
+ }
|
|
|
+
|
|
|
+ fn get_request_max_delay(&self) -> i64 { 0 }
|
|
|
+
|
|
|
+ async fn get_server_time(&mut self) -> Result<String, Error> {
|
|
|
+ let response = self.request.get_server_time().await;
|
|
|
+ if response.code != 200 {
|
|
|
+ return Err(Error::new(ErrorKind::NotFound, format!("htx_swap 获取服务器时间异常{:?}", response).to_string()));
|
|
|
+ }
|
|
|
+
|
|
|
+ let res_data_json = response.data;
|
|
|
+ let result = res_data_json["ts"].to_string();
|
|
|
+ Ok(result)
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn get_account(&mut self) -> Result<Account, Error> {
|
|
|
+ let params = json!({
|
|
|
+ "margin_account": "USDT"
|
|
|
+ });
|
|
|
+ let response = self.request.get_account(params).await;
|
|
|
+ if response.code != 200 {
|
|
|
+ return Err(Error::new(ErrorKind::NotFound, format!("htx_swap 获取账户信息异常{:?}", response).to_string()));
|
|
|
+ }
|
|
|
+
|
|
|
+ let res_data_json = response.data;
|
|
|
+ let mut account = Account::new();
|
|
|
+ for data in res_data_json.as_array().unwrap() {
|
|
|
+ let margin_position = Decimal::from_f64(data["margin_position"].as_f64().unwrap()).unwrap();
|
|
|
+
|
|
|
+ let balance = Decimal::from_f64(data["margin_static"].as_f64().unwrap()).unwrap();
|
|
|
+ let frozen_balance = Decimal::from_f64(data["margin_frozen"].as_f64().unwrap()).unwrap();
|
|
|
+ let available_balance = balance - margin_position - frozen_balance;
|
|
|
+ // 格式化account信息
|
|
|
+ account = Account {
|
|
|
+ coin: data["margin_asset"].as_str().unwrap().to_string(),
|
|
|
+ balance,
|
|
|
+ available_balance,
|
|
|
+ frozen_balance,
|
|
|
+ stocks: Default::default(),
|
|
|
+ available_stocks: Default::default(),
|
|
|
+ frozen_stocks: Default::default(),
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return Ok(account);
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn get_spot_account(&mut self) -> Result<Vec<Account>, Error> {
|
|
|
+ Err(Error::new(ErrorKind::NotFound, "htx_swap get_spot_account:该交易所方法未实现".to_string()))
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn get_position(&mut self) -> Result<Vec<Position>, Error> {
|
|
|
+ let symbol_format = utils::format_symbol(self.symbol.clone(), "-");
|
|
|
+ let ct_val = self.market.ct_val;
|
|
|
+ let params = json!({
|
|
|
+ "contract_type": "swap",
|
|
|
+ "contract_code": symbol_format
|
|
|
+ });
|
|
|
+ let response = self.request.get_user_position(params).await;
|
|
|
+ if response.code != 200 {
|
|
|
+ return Err(Error::new(ErrorKind::NotFound, format!("htx_swap 获取仓位异常{:?}", response).to_string()));
|
|
|
+ }
|
|
|
+
|
|
|
+ let res_data_json = response.data;
|
|
|
+ let positions_info = res_data_json.as_array().unwrap();
|
|
|
+ let result = positions_info.iter().map(|item| { format_position_item(item, &ct_val) }).collect();
|
|
|
+ Ok(result)
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn get_positions(&mut self) -> Result<Vec<Position>, Error> {
|
|
|
+ let params = json!({
|
|
|
+ "contract_type": "swap"
|
|
|
+ });
|
|
|
+ let response = self.request.get_user_position(params).await;
|
|
|
+
|
|
|
+ if response.code != 200 {
|
|
|
+ return Err(Error::new(ErrorKind::NotFound, format!("htx_swap 获取仓位异常{:?}", response).to_string()));
|
|
|
+ }
|
|
|
+
|
|
|
+ let res_data_json = response.data;
|
|
|
+ let positions_info = res_data_json.as_array().unwrap();
|
|
|
+ let result = positions_info.iter().map(|item| { format_position_item(item, &Decimal::ONE) }).collect();
|
|
|
+ Ok(result)
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn get_ticker(&mut self) -> Result<Ticker, Error> {
|
|
|
+ return self.get_ticker_symbol(self.symbol.clone()).await;
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn get_ticker_symbol(&mut self, symbol: String) -> Result<Ticker, Error> {
|
|
|
+ let symbol_format = utils::format_symbol(symbol.clone(), "-");
|
|
|
+ let params = json!({
|
|
|
+ "contract_code": symbol_format,
|
|
|
+ });
|
|
|
+ let response = self.request.get_ticker(params).await;
|
|
|
+ if response.code != 200 {
|
|
|
+ return Err(Error::new(ErrorKind::NotFound, format!("htx_swap 获取行情信息异常{:?}", response).to_string()));
|
|
|
+ }
|
|
|
+
|
|
|
+ let res_data_json = response.data;
|
|
|
+ let ticker_info = res_data_json["tick"].clone();
|
|
|
+ let time = ticker_info["ts"].as_i64().unwrap();
|
|
|
+ let result = Ticker {
|
|
|
+ time,
|
|
|
+ high: Decimal::from_str(ticker_info["high"].as_str().unwrap()).unwrap(),
|
|
|
+ low: Decimal::from_str(ticker_info["low"].as_str().unwrap()).unwrap(),
|
|
|
+ sell: Decimal::from_f64(ticker_info["bid"][0].as_f64().unwrap()).unwrap(),
|
|
|
+ buy: Decimal::from_f64(ticker_info["ask"][0].as_f64().unwrap()).unwrap(),
|
|
|
+ last: Decimal::from_str(ticker_info["close"].as_str().unwrap()).unwrap(),
|
|
|
+ volume: Decimal::from_str(ticker_info["amount"].as_str().unwrap()).unwrap(),
|
|
|
+ };
|
|
|
+ Ok(result)
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn get_market(&mut self) -> Result<Market, Error> {
|
|
|
+ self.get_market_symbol(self.symbol.clone()).await
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn get_market_symbol(&mut self, symbol: String) -> Result<Market, Error> {
|
|
|
+ let symbol_format = utils::format_symbol(symbol.clone(), "-");
|
|
|
+ let symbol_array: Vec<&str> = self.symbol.split("_").collect();
|
|
|
+
|
|
|
+ let params = json!({
|
|
|
+ "pair": symbol_format,
|
|
|
+ "contract_type":"swap"
|
|
|
+ });
|
|
|
+ let response = self.request.get_market(params).await;
|
|
|
+ if response.code != 200 {
|
|
|
+ return Err(Error::new(ErrorKind::NotFound, format!("htx_swap 获取市场信息异常{:?}", response).to_string()));
|
|
|
+ }
|
|
|
+ let res_data_json = response.data.as_array().unwrap();
|
|
|
+ let market_info = res_data_json[0].clone();
|
|
|
+
|
|
|
+ if !market_info["pair"].as_str().unwrap().to_string().eq(&symbol_format) {
|
|
|
+ return Err(Error::new(ErrorKind::NotFound, format!("符号未找到:symbol={}, response={:?}", symbol_format, response))).unwrap();
|
|
|
+ }
|
|
|
+
|
|
|
+ let base_asset = market_info["symbol"].as_str().unwrap().to_string();
|
|
|
+ let quote_asset = symbol_array[1].to_string();
|
|
|
+
|
|
|
+ let tick_size = Decimal::from_f64(market_info["price_tick"].as_f64().unwrap()).unwrap();
|
|
|
+ let price_precision = Decimal::from_u32(tick_size.scale()).unwrap();
|
|
|
+ let amount_size = Decimal::from_f64(market_info["contract_size"].as_f64().unwrap()).unwrap();
|
|
|
+ let amount_precision = Decimal::ZERO;
|
|
|
+ let min_qty = Decimal::NEGATIVE_ONE;
|
|
|
+ let max_qty = Decimal::NEGATIVE_ONE;
|
|
|
+ let ct_val = Decimal::from_f64(market_info["contract_size"].as_f64().unwrap()).unwrap();
|
|
|
+
|
|
|
+ let result = Market {
|
|
|
+ symbol: format!("{}_{}", base_asset, quote_asset),
|
|
|
+ base_asset,
|
|
|
+ quote_asset,
|
|
|
+ tick_size,
|
|
|
+ amount_size,
|
|
|
+ price_precision,
|
|
|
+ amount_precision,
|
|
|
+ min_qty,
|
|
|
+ max_qty,
|
|
|
+ min_notional: min_qty,
|
|
|
+ max_notional: max_qty,
|
|
|
+ ct_val,
|
|
|
+ };
|
|
|
+ Ok(result)
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn get_order_detail(&mut self, order_id: &str, custom_id: &str) -> Result<Order, Error> {
|
|
|
+ let symbol_format = utils::format_symbol(self.symbol.clone(), "-");
|
|
|
+ let ct_val = self.market.ct_val;
|
|
|
+
|
|
|
+ let mut params = json!({
|
|
|
+ "pair": symbol_format
|
|
|
+ });
|
|
|
+ if order_id.eq("") { params["client_order_id"] = json!(custom_id) } else { params["order_id"] = json!(order_id) };
|
|
|
+ let response = self.request.get_order_details(params).await;
|
|
|
+ if response.code != 200 || response.data == Null {
|
|
|
+ return Err(Error::new(ErrorKind::NotFound, format!("htx_swap 获取订单详情异常{:?}", response).to_string()));
|
|
|
+ }
|
|
|
+
|
|
|
+ let res_data_json = response.data;
|
|
|
+ let orders = res_data_json.as_array().unwrap();
|
|
|
+ let result = format_order_item(orders[0].clone(), ct_val);
|
|
|
+ Ok(result)
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn get_orders_list(&mut self, _status: &str) -> Result<Vec<Order>, Error> {
|
|
|
+ Err(Error::new(ErrorKind::NotFound, "htx_swap get_orders_list:该交易所方法未实现".to_string()))
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn take_order(&mut self, custom_id: &str, origin_side: &str, price: Decimal, amount: Decimal) -> Result<Order, Error> {
|
|
|
+ let ct_val = self.market.ct_val;
|
|
|
+
|
|
|
+ return self.take_order_symbol(self.symbol.clone(), ct_val, custom_id, origin_side, price, amount).await;
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn take_order_symbol(&mut self, symbol: String, ct_val: Decimal, custom_id: &str, origin_side: &str, price: Decimal, amount: Decimal) -> Result<Order, Error> {
|
|
|
+ let symbol_format = utils::format_symbol(symbol, "-");
|
|
|
+ let final_size = (amount / ct_val).floor();
|
|
|
+ let mut params = json!({
|
|
|
+ "pair": symbol_format,
|
|
|
+ "client_order_id": custom_id,
|
|
|
+ "contract_type": "swap",
|
|
|
+ "volume": final_size.to_string(),
|
|
|
+ "lever_rate": 10
|
|
|
+ });
|
|
|
+ if price.eq(&Decimal::ZERO) {
|
|
|
+ params["order_price_type"] = json!("market");
|
|
|
+ } else {
|
|
|
+ params["price"] = json!(price.to_string());
|
|
|
+ params["order_price_type"] = json!("limit");
|
|
|
+ };
|
|
|
+ match origin_side {
|
|
|
+ "kd" => {
|
|
|
+ params["direction"] = json!("buy");
|
|
|
+ params["offset"] = json!("open");
|
|
|
+ }
|
|
|
+ "pd" => {
|
|
|
+ params["direction"] = json!("sell");
|
|
|
+ params["offset"] = json!("close");
|
|
|
+ }
|
|
|
+ "kk" => {
|
|
|
+ params["direction"] = json!("sell");
|
|
|
+ params["offset"] = json!("open");
|
|
|
+ }
|
|
|
+ "pk" => {
|
|
|
+ params["direction"] = json!("buy");
|
|
|
+ params["offset"] = json!("close");
|
|
|
+ }
|
|
|
+ _ => { panic!("htx_usdt_swap 下单参数错误"); }
|
|
|
+ };
|
|
|
+ let response = self.request.swap_order(params).await;
|
|
|
+ if response.code != 200 {
|
|
|
+ return Err(Error::new(ErrorKind::NotFound, format!("htx_swap 下单异常{:?}", response).to_string()));
|
|
|
+ }
|
|
|
+
|
|
|
+ let res_data_json = response.data;
|
|
|
+ let result = Order {
|
|
|
+ id: res_data_json["order_id"].to_string(),
|
|
|
+ custom_id: res_data_json["client_order_id"].to_string(),
|
|
|
+ price: Decimal::ZERO,
|
|
|
+ amount: Decimal::ZERO,
|
|
|
+ deal_amount: Decimal::ZERO,
|
|
|
+ avg_price: Decimal::ZERO,
|
|
|
+ status: "NEW".to_string(),
|
|
|
+ order_type: "".to_string(),
|
|
|
+ trace_stack: TraceStack::new(0, Instant::now()).on_special("339 htx_swap".to_string()),
|
|
|
+ };
|
|
|
+ return Ok(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn cancel_order(&mut self, order_id: &str, custom_id: &str) -> Result<Order, Error> {
|
|
|
+ let symbol_format = utils::format_symbol(self.symbol.clone(), "-");
|
|
|
+ let mut params = json!({
|
|
|
+ "pair": symbol_format,
|
|
|
+ "contract_type": "swap"
|
|
|
+ });
|
|
|
+ if order_id.eq("") { params["client_order_id"] = json!(custom_id) } else { params["order_id"] = json!(order_id) };
|
|
|
+ let response = self.request.cancel_order(params).await;
|
|
|
+
|
|
|
+ // 取消失败,进行报错
|
|
|
+ if response.code != 200 || response.data["errors"].as_array().unwrap_or(&vec![]).len() > 0 {
|
|
|
+ return Err(Error::new(ErrorKind::NotFound, format!("htx_swap 取消订单异常{:?}", response).to_string()));
|
|
|
+ }
|
|
|
+ let res_data_json = response.data;
|
|
|
+ let mut id = order_id.to_string();
|
|
|
+ let mut custom_id = custom_id.to_string();
|
|
|
+ if order_id.eq("") {
|
|
|
+ custom_id = res_data_json["successes"].as_str().unwrap().to_string()
|
|
|
+ } else {
|
|
|
+ id = res_data_json["successes"].as_str().unwrap().to_string()
|
|
|
+ };
|
|
|
+ let result = Order {
|
|
|
+ id,
|
|
|
+ custom_id,
|
|
|
+ price: Decimal::ZERO,
|
|
|
+ amount: Decimal::ZERO,
|
|
|
+ deal_amount: Decimal::ZERO,
|
|
|
+ avg_price: Decimal::ZERO,
|
|
|
+ status: "REMOVE".to_string(),
|
|
|
+ order_type: "".to_string(),
|
|
|
+ trace_stack: TraceStack::new(0, Instant::now()).on_special("374 htx_swap".to_string()),
|
|
|
+ };
|
|
|
+ Ok(result)
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn cancel_orders(&mut self) -> Result<Vec<Order>, Error> {
|
|
|
+ Err(Error::new(ErrorKind::NotFound, "htx_swap cancel_orders:该交易所方法未实现".to_string()))
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn cancel_orders_all(&mut self) -> Result<Vec<Order>, Error> {
|
|
|
+ let symbol_format = utils::format_symbol(self.symbol.clone(), "-");
|
|
|
+ let params = json!({
|
|
|
+ "pair": symbol_format,
|
|
|
+ "contract_type": "swap"
|
|
|
+ });
|
|
|
+ let response = self.request.cancel_price_order(params).await;
|
|
|
+ if response.code != 200 {
|
|
|
+ if response.data.to_string().contains("No cancellable orders") { return Ok(vec![]); }
|
|
|
+ return Err(Error::new(ErrorKind::NotFound, format!("htx_swap 撤销所有订单异常{:?}", response).to_string()));
|
|
|
+ }
|
|
|
+
|
|
|
+ Ok(vec![])
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn take_stop_loss_order(&mut self, _stop_price: Decimal, _price: Decimal, _side: &str) -> Result<serde_json::Value, Error> {
|
|
|
+ Err(Error::new(ErrorKind::NotFound, "htx_swap take_stop_loss_order:该交易所方法未实现".to_string()))
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn cancel_stop_loss_order(&mut self, _order_id: &str) -> Result<serde_json::Value, Error> {
|
|
|
+ Err(Error::new(ErrorKind::NotFound, "htx_swap cancel_stop_loss_order:该交易所方法未实现".to_string()))
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn set_dual_mode(&mut self, _coin: &str, is_dual_mode: bool) -> Result<String, Error> {
|
|
|
+ let pos_mode = if is_dual_mode { "dual_side" } else { "single_side" };
|
|
|
+ let params = json!({
|
|
|
+ "margin_account": "USDT",
|
|
|
+ "position_mode": pos_mode,
|
|
|
+ });
|
|
|
+ let response = self.request.setting_dual_mode(params).await;
|
|
|
+ if response.code != 200 {
|
|
|
+ return Err(Error::new(ErrorKind::Other, format!("设置持仓模式失败:{:?}", response).to_string()));
|
|
|
+ }
|
|
|
+
|
|
|
+ return Ok(response.data.to_string());
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn set_dual_leverage(&mut self, leverage: &str) -> Result<String, Error> {
|
|
|
+ let symbol_format = utils::format_symbol(self.symbol.clone(), "-");
|
|
|
+ let params = json!({
|
|
|
+ "pair": symbol_format,
|
|
|
+ "contract_type": "swap",
|
|
|
+ "lever_rate": leverage
|
|
|
+ });
|
|
|
+ let response = self.request.setting_dual_leverage(params).await;
|
|
|
+
|
|
|
+ if response.code != 200 {
|
|
|
+ return Err(Error::new(ErrorKind::Other, format!("设置杠杆失败:{:?}", response).to_string()));
|
|
|
+ }
|
|
|
+
|
|
|
+ return Ok(response.data.to_string());
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn set_auto_deposit_status(&mut self, _status: bool) -> Result<String, Error> {
|
|
|
+ Err(Error::new(ErrorKind::NotFound, "htx_swap set_auto_deposit_status:该交易所方法未实现".to_string()))
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn wallet_transfers(&mut self, _coin: &str, _from: &str, _to: &str, _amount: Decimal) -> Result<String, Error> {
|
|
|
+ Err(Error::new(ErrorKind::NotFound, "htx_swap wallet_transfers:该交易所方法未实现".to_string()))
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn command_order(&mut self, order_command: &mut OrderCommand, trace_stack: &TraceStack) {
|
|
|
+ let mut handles = vec![];
|
|
|
+
|
|
|
+ // 下单指令
|
|
|
+ for item in order_command.limits_open.keys() {
|
|
|
+ let mut ts = trace_stack.clone();
|
|
|
+
|
|
|
+ let amount = Decimal::from_str(&*order_command.limits_open[item].get(0).unwrap().clone()).unwrap();
|
|
|
+ let side = order_command.limits_open[item].get(1).unwrap().clone();
|
|
|
+ let price = Decimal::from_str(&*order_command.limits_open[item].get(2).unwrap().clone()).unwrap();
|
|
|
+ let cid = order_command.limits_open[item].get(3).unwrap().clone();
|
|
|
+
|
|
|
+ // order_name: [数量,方向,价格,c_id]
|
|
|
+ let mut self_clone = self.clone();
|
|
|
+ let handle = spawn(async move {
|
|
|
+ // TraceStack::show_delay(&ts.ins);
|
|
|
+ ts.on_before_send();
|
|
|
+ let result = self_clone.take_order(cid.as_str(), side.as_str(), price, amount).await;
|
|
|
+ ts.on_after_send();
|
|
|
+ // info!("下单 custom_id {}", cid);
|
|
|
+ match result {
|
|
|
+ Ok(mut result) => {
|
|
|
+ result.trace_stack = ts;
|
|
|
+ // info!("下单完成 order_id {}", result.id);
|
|
|
+ self_clone.order_sender.send(result).await.unwrap();
|
|
|
+ }
|
|
|
+ Err(error) => {
|
|
|
+ info!(?error);
|
|
|
+ let mut err_order = Order::new();
|
|
|
+ err_order.custom_id = cid.clone();
|
|
|
+ err_order.status = "REMOVE".to_string();
|
|
|
+ ts.source = "htx_swap 474".to_string();
|
|
|
+ err_order.trace_stack = ts;
|
|
|
+
|
|
|
+ self_clone.order_sender.send(err_order).await.unwrap();
|
|
|
+ self_clone.error_sender.send(error).await.unwrap();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ handles.push(handle)
|
|
|
+ }
|
|
|
+ let futures = FuturesUnordered::from_iter(handles);
|
|
|
+ // 等待所有任务完成
|
|
|
+ let _: Result<Vec<_>, _> = futures.try_collect().await;
|
|
|
+
|
|
|
+ // 撤销订单
|
|
|
+ let mut cancel_handlers = vec![];
|
|
|
+ for item in order_command.cancel.keys() {
|
|
|
+ let order_id = order_command.cancel[item].get(1).unwrap().clone();
|
|
|
+ let custom_id = order_command.cancel[item].get(0).unwrap().clone();
|
|
|
+ // info!("取消订单 order_id {}, custom_id {}", order_id, custom_id);
|
|
|
+ let mut self_clone = self.clone();
|
|
|
+ let handle = spawn(async move {
|
|
|
+ let result = self_clone.cancel_order(&order_id, &custom_id).await;
|
|
|
+ match result {
|
|
|
+ Ok(_) => {
|
|
|
+ // result_sd.send(result).await.unwrap();
|
|
|
+ }
|
|
|
+ Err(error) => {
|
|
|
+ // 已经取消的订单不去撤单了
|
|
|
+ if !error.to_string().contains("Your order has been canceled") {
|
|
|
+ // 取消失败去查订单。
|
|
|
+ let query_rst = self_clone.get_order_detail(&order_id, &custom_id).await;
|
|
|
+ match query_rst {
|
|
|
+ Ok(order) => {
|
|
|
+ self_clone.order_sender.send(order).await.unwrap();
|
|
|
+ }
|
|
|
+ Err(err) => {
|
|
|
+ error!("撤单失败,而且查单也失败了,htx_swap,oid={}, cid={}, err={:?}。", order_id.clone(), custom_id.clone(), err);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ error!("撤单失败,异常:{}", error);
|
|
|
+ self_clone.error_sender.send(error).await.unwrap();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ cancel_handlers.push(handle)
|
|
|
+ }
|
|
|
+ let futures = FuturesUnordered::from_iter(cancel_handlers);
|
|
|
+ // 等待所有任务完成
|
|
|
+ let _: Result<Vec<_>, _> = futures.try_collect().await;
|
|
|
+
|
|
|
+ // 检查订单指令
|
|
|
+ let mut check_handlers = vec![];
|
|
|
+ for item in order_command.check.keys() {
|
|
|
+ let order_id = order_command.check[item].get(1).unwrap().clone();
|
|
|
+ let custom_id = order_command.check[item].get(0).unwrap().clone();
|
|
|
+
|
|
|
+ let mut self_clone = self.clone();
|
|
|
+ let handle = spawn(async move {
|
|
|
+ let result = self_clone.get_order_detail(order_id.as_str(), custom_id.as_str()).await;
|
|
|
+ match result {
|
|
|
+ Ok(result) => {
|
|
|
+ self_clone.order_sender.send(result).await.unwrap();
|
|
|
+ }
|
|
|
+ Err(error) => {
|
|
|
+ self_clone.error_sender.send(error).await.unwrap();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ check_handlers.push(handle)
|
|
|
+ }
|
|
|
+
|
|
|
+ let futures = FuturesUnordered::from_iter(check_handlers);
|
|
|
+ // 等待所有任务完成
|
|
|
+ let _: Result<Vec<_>, _> = futures.try_collect().await;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// pub fn format_account_info(balance_data: Value) -> Account {
|
|
|
+// let balance_coin = balance_data["coin"].as_str().unwrap().to_string().to_uppercase();
|
|
|
+// let available_balance = Decimal::from_str(balance_data["available"].as_str().unwrap()).unwrap();
|
|
|
+// let frozen_balance = Decimal::from_str(balance_data["frozen"].as_str().unwrap()).unwrap();
|
|
|
+// let balance = available_balance + frozen_balance;
|
|
|
+//
|
|
|
+// Account {
|
|
|
+// coin: balance_coin,
|
|
|
+// balance,
|
|
|
+// available_balance,
|
|
|
+// frozen_balance,
|
|
|
+// stocks: Decimal::ZERO,
|
|
|
+// available_stocks: Decimal::ZERO,
|
|
|
+// frozen_stocks: Decimal::ZERO,
|
|
|
+// }
|
|
|
+// }
|
|
|
+
|
|
|
+pub fn format_order_item(order: serde_json::Value, ct_val: Decimal) -> Order {
|
|
|
+ let id = order["order_id"].to_string();
|
|
|
+ let custom_id = order["client_order_id"].to_string();
|
|
|
+ let price = Decimal::from_f64(order["price"].as_f64().unwrap()).unwrap();
|
|
|
+ let amount = Decimal::from_f64(order["volume"].as_f64().unwrap()).unwrap() * ct_val;
|
|
|
+ let deal_amount = Decimal::from_f64(order["trade_volume"].as_f64().unwrap()).unwrap() * ct_val;
|
|
|
+ let avg_price = Decimal::from_f64(order["trade_avg_price"].as_f64().unwrap_or(0.0)).unwrap();
|
|
|
+
|
|
|
+ let status = order["status"].to_string();
|
|
|
+
|
|
|
+ let custom_status = if ["5", "6", "7"].contains(&&**&status) {
|
|
|
+ "REMOVE".to_string()
|
|
|
+ } else if ["1", "2", "3", "4"].contains(&&**&status) {
|
|
|
+ "NEW".to_string()
|
|
|
+ } else {
|
|
|
+ "NULL".to_string()
|
|
|
+ };
|
|
|
+ Order {
|
|
|
+ id,
|
|
|
+ custom_id,
|
|
|
+ price,
|
|
|
+ amount,
|
|
|
+ deal_amount,
|
|
|
+ avg_price,
|
|
|
+ status: custom_status,
|
|
|
+ order_type: order["order_price_type"].as_str().unwrap().to_string(),
|
|
|
+ trace_stack: TraceStack::new(0, Instant::now()).on_special("630 htx_swap".to_string()),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+pub fn format_position_item(position: &serde_json::Value, ct_val: &Decimal) -> Position {
|
|
|
+ let symbol = position["pair"].as_str().unwrap().to_string();
|
|
|
+ let margin_level = Decimal::from_str(&position["lever_rate"].to_string()).unwrap();
|
|
|
+ let amount = Decimal::from_f64(position["volume"].as_f64().unwrap()).unwrap() * ct_val;
|
|
|
+
|
|
|
+ let frozen_amount = Decimal::from_f64(position["frozen"].as_f64().unwrap()).unwrap();
|
|
|
+ let price = Decimal::from_f64(position["cost_hold"].as_f64().unwrap()).unwrap();
|
|
|
+ let profit = Decimal::from_f64(position["profit_unreal"].as_f64().unwrap()).unwrap();
|
|
|
+ let position_mode = match position["position_mode"].as_str().unwrap() {
|
|
|
+ "dual_side" => {
|
|
|
+ match position["direction"].as_str().unwrap() {
|
|
|
+ "sell" => {
|
|
|
+ PositionModeEnum::Short
|
|
|
+ }
|
|
|
+ "buy" => {
|
|
|
+ PositionModeEnum::Long
|
|
|
+ }
|
|
|
+ _ => {
|
|
|
+ panic!("htx_usdt_swap: 未知的持仓模式与持仓方向: {}, {}",
|
|
|
+ position["direction"].as_str().unwrap(), position["direction"].as_str().unwrap())
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ "single_side" => {
|
|
|
+ PositionModeEnum::Both
|
|
|
+ }
|
|
|
+ _ => {
|
|
|
+ panic!("htx_usdt_swap: 未知的持仓模式: {}", position["position_mode"].as_str().unwrap())
|
|
|
+ }
|
|
|
+ };
|
|
|
+ let margin = Decimal::from_f64(position["position_margin"].as_f64().unwrap()).unwrap();
|
|
|
+
|
|
|
+ Position {
|
|
|
+ symbol,
|
|
|
+ margin_level,
|
|
|
+ amount,
|
|
|
+ frozen_amount,
|
|
|
+ price,
|
|
|
+ profit,
|
|
|
+ position_mode,
|
|
|
+ margin,
|
|
|
+ }
|
|
|
+}
|