|
|
@@ -1,771 +1,771 @@
|
|
|
-// use std::collections::{BTreeMap};
|
|
|
-// use std::io::{Error, ErrorKind};
|
|
|
-// use std::str::FromStr;
|
|
|
-// use tokio::sync::mpsc::Sender;
|
|
|
-// use async_trait::async_trait;
|
|
|
-// use futures::stream::FuturesUnordered;
|
|
|
-// use futures::TryStreamExt;
|
|
|
-// use rust_decimal::Decimal;
|
|
|
-// use rust_decimal::prelude::{FromPrimitive, ToPrimitive};
|
|
|
-// use serde_json::{Value};
|
|
|
-// use tokio::spawn;
|
|
|
-// use tokio::time::Instant;
|
|
|
-// use tracing::{error, info, trace};
|
|
|
-// use exchanges::coinex_swap_rest::CoinexSwapRest;
|
|
|
-// use crate::{Platform, ExchangeEnum, Account, Position, Ticker, Market, Order, OrderCommand, PositionModeEnum, utils};
|
|
|
-// use global::trace_stack::TraceStack;
|
|
|
-// use crate::utils::get_tick_size;
|
|
|
-//
|
|
|
-// #[allow(dead_code)]
|
|
|
-// #[derive(Clone)]
|
|
|
-// pub struct CoinexSwap {
|
|
|
-// exchange: ExchangeEnum,
|
|
|
-// symbol: String,
|
|
|
-// is_colo: bool,
|
|
|
-// params: BTreeMap<String, String>,
|
|
|
-// request: CoinexSwapRest,
|
|
|
-// market: Market,
|
|
|
-// order_sender: Sender<Order>,
|
|
|
-// error_sender: Sender<Error>,
|
|
|
-// }
|
|
|
-//
|
|
|
-// impl CoinexSwap {
|
|
|
-// pub async fn new(symbol: String, is_colo: bool, params: BTreeMap<String, String>, order_sender: Sender<Order>, error_sender: Sender<Error>) -> CoinexSwap {
|
|
|
-// let market = Market::new();
|
|
|
-// let mut coinex_swap = CoinexSwap {
|
|
|
-// exchange: ExchangeEnum::CoinexSwap,
|
|
|
-// symbol: symbol.to_uppercase(),
|
|
|
-// is_colo,
|
|
|
-// params: params.clone(),
|
|
|
-// request: CoinexSwapRest::new(params.clone()),
|
|
|
-// market,
|
|
|
-// order_sender,
|
|
|
-// error_sender,
|
|
|
-// };
|
|
|
-//
|
|
|
-// // 修改持仓模式
|
|
|
-// let symbol_array: Vec<&str> = symbol.split("_").collect();
|
|
|
-// let mode_result = coinex_swap.set_dual_mode(symbol_array[1], true).await;
|
|
|
-// match mode_result {
|
|
|
-// Ok(_) => {
|
|
|
-// trace!("Coinex:设置持仓模式成功!")
|
|
|
-// }
|
|
|
-// Err(error) => {
|
|
|
-// error!("Coinex:设置持仓模式失败!mode_result={}", error)
|
|
|
-// }
|
|
|
-// }
|
|
|
-// // 获取市场信息
|
|
|
-// coinex_swap.market = CoinexSwap::get_market(&mut coinex_swap).await.unwrap_or(coinex_swap.market);
|
|
|
-// // 设置持仓杠杆
|
|
|
-// let lever_rate_result = coinex_swap.set_dual_leverage("10").await;
|
|
|
-// match lever_rate_result {
|
|
|
-// Ok(ok) => {
|
|
|
-// info!("Coinex:设置持仓杠杆成功!{:?}", ok);
|
|
|
-// }
|
|
|
-// Err(error) => {
|
|
|
-// error!("Coinex:设置持仓杠杆失败!{:?}", error)
|
|
|
-// }
|
|
|
-// }
|
|
|
-// return coinex_swap;
|
|
|
-// }
|
|
|
-// }
|
|
|
-//
|
|
|
-// #[async_trait]
|
|
|
-// impl Platform for CoinexSwap {
|
|
|
-// // 克隆方法
|
|
|
-// fn clone_box(&self) -> Box<dyn Platform + Send + Sync> { Box::new(self.clone()) }
|
|
|
-// // 获取交易所模式
|
|
|
-// fn get_self_exchange(&self) -> ExchangeEnum {
|
|
|
-// ExchangeEnum::CoinexSwap
|
|
|
-// }
|
|
|
-// // 获取交易对
|
|
|
-// fn get_self_symbol(&self) -> String { self.symbol.clone() }
|
|
|
-// // 获取是否使用高速通道
|
|
|
-// fn get_self_is_colo(&self) -> bool {
|
|
|
-// self.is_colo
|
|
|
-// }
|
|
|
-// // 获取params信息
|
|
|
-// fn get_self_params(&self) -> BTreeMap<String, String> {
|
|
|
-// self.params.clone()
|
|
|
-// }
|
|
|
-// // 获取market信息
|
|
|
-// fn get_self_market(&self) -> Market { self.market.clone() }
|
|
|
-// // 获取请求时间
|
|
|
-// fn get_request_delays(&self) -> Vec<i64> { self.request.get_delays() }
|
|
|
-// // 获取请求平均时间
|
|
|
-// fn get_request_avg_delay(&self) -> Decimal { self.request.get_avg_delay() }
|
|
|
-// // 获取请求最大时间
|
|
|
-// fn get_request_max_delay(&self) -> i64 { self.request.get_max_delay() }
|
|
|
-//
|
|
|
-// // 获取服务器时间
|
|
|
-// async fn get_server_time(&mut self) -> Result<String, Error> {
|
|
|
-// let res_data = self.request.get_server_time().await;
|
|
|
-// if res_data.code == 200 {
|
|
|
-// let res_data_json: Value = res_data.data;
|
|
|
-// let result = res_data_json["timestamp"].to_string();
|
|
|
-// Ok(result)
|
|
|
-// } else {
|
|
|
-// Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
-// }
|
|
|
-// }
|
|
|
-// // 获取账号信息
|
|
|
-// async fn get_account(&mut self) -> Result<Account, Error> {
|
|
|
-// let symbol_array: Vec<&str> = self.symbol.split("_").collect();
|
|
|
-// let coin = symbol_array[1].to_string().to_uppercase();
|
|
|
-// let res_data = self.request.get_account().await;
|
|
|
-// if res_data.code == 200 {
|
|
|
-// if res_data.data.is_array() {
|
|
|
-// let res_data_array = res_data.data.as_array().unwrap();
|
|
|
-// for res_data_json in res_data_array.iter() {
|
|
|
-// if res_data_json["ccy"].as_str().unwrap() == coin {
|
|
|
-// let frozen_balance= Decimal::from_str(res_data_json["frozen"].as_str().unwrap()).unwrap();
|
|
|
-// let available_balance = Decimal::from_str(res_data_json["available"].as_str().unwrap()).unwrap();
|
|
|
-// let balance = frozen_balance + available_balance;
|
|
|
-// let result = Account {
|
|
|
-// coin: symbol_array[1].to_string(),
|
|
|
-// balance,
|
|
|
-// available_balance,
|
|
|
-// frozen_balance,
|
|
|
-// stocks: Decimal::ZERO,
|
|
|
-// available_stocks: Decimal::ZERO,
|
|
|
-// frozen_stocks: Decimal::ZERO,
|
|
|
-// };
|
|
|
-// return Ok(result);
|
|
|
-// }
|
|
|
-// }
|
|
|
-// }
|
|
|
-// }
|
|
|
-// Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
-// }
|
|
|
-//
|
|
|
-// async fn get_spot_account(&mut self) -> Result<Vec<Account>, Error> {
|
|
|
-// Err(Error::new(ErrorKind::NotFound, "coinex_swap:该方法暂未实现".to_string()))
|
|
|
-// }
|
|
|
-//
|
|
|
-// // 获取持仓信息
|
|
|
-// async fn get_position(&mut self) -> Result<Vec<Position>, Error> {
|
|
|
-// let symbol: String = self.symbol.replace("_", "").to_uppercase();
|
|
|
-// let ct_val = self.market.ct_val;
|
|
|
-// let res_data = self.request.get_position(symbol).await;
|
|
|
-// if res_data.code == 200 {
|
|
|
-// let res_data_json = res_data.data.as_array().unwrap();
|
|
|
-// let result = res_data_json.iter().map(|item| { format_position_item(item, ct_val) }).collect();
|
|
|
-// Ok(result)
|
|
|
-// } else {
|
|
|
-// Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
-// }
|
|
|
-// }
|
|
|
-//
|
|
|
-// // 获取所有持仓
|
|
|
-// async fn get_positions(&mut self) -> Result<Vec<Position>, Error> {
|
|
|
-// let res_data = self.request.get_user_position().await;
|
|
|
-// if res_data.code == 200 {
|
|
|
-// info!("{}", res_data.data.to_string());
|
|
|
-// let res_data_json = res_data.data.as_array().unwrap();
|
|
|
-// let result = res_data_json.iter().map(|item| { format_position_item(item, Decimal::ONE) }).collect();
|
|
|
-// Ok(result)
|
|
|
-// } else {
|
|
|
-// Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
-// }
|
|
|
-// }
|
|
|
-// // 获取市场行情
|
|
|
-// async fn get_ticker(&mut self) -> Result<Ticker, Error> {
|
|
|
-// let symbol: String = self.symbol.replace("_", "");
|
|
|
-// let res_data = self.request.get_ticker(symbol.clone()).await;
|
|
|
-// if res_data.code == 200 {
|
|
|
-// let res_data_json = res_data.data.as_array().unwrap();
|
|
|
-// let ticker_info = res_data_json.iter().find(|item| item["market"].as_str().unwrap() == symbol);
|
|
|
-// match ticker_info {
|
|
|
-// None => {
|
|
|
-// error!("coinex_swap:获取Ticker信息错误!\nget_ticker:res_data={:?}", res_data);
|
|
|
-// Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
-// }
|
|
|
-// Some(value) => {
|
|
|
-// let result = Ticker {
|
|
|
-// time: chrono::Utc::now().timestamp_millis(),
|
|
|
-// high: Decimal::from_str(value["high"].as_str().unwrap()).unwrap(),
|
|
|
-// low: Decimal::from_str(value["low"].as_str().unwrap()).unwrap(),
|
|
|
-// sell: Decimal::from_str(value["last"].as_str().unwrap()).unwrap(),
|
|
|
-// buy: Decimal::from_str(value["last"].as_str().unwrap()).unwrap(),
|
|
|
-// last: Decimal::from_str(value["last"].as_str().unwrap()).unwrap(),
|
|
|
-// volume: Decimal::from_str(value["volume"].as_str().unwrap()).unwrap(),
|
|
|
-// };
|
|
|
-// Ok(result)
|
|
|
-// }
|
|
|
-// }
|
|
|
-// } else {
|
|
|
-// Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
-// }
|
|
|
-// }
|
|
|
-//
|
|
|
-// async fn get_ticker_symbol(&mut self, symbol_param: String) -> Result<Ticker, Error> {
|
|
|
-// let symbol: String = symbol_param.replace("_", "").to_uppercase();
|
|
|
-// let res_data = self.request.get_ticker(symbol.clone()).await;
|
|
|
-// if res_data.code == 200 {
|
|
|
-// let res_data_json = res_data.data.as_array().unwrap();
|
|
|
-// let ticker_info = res_data_json.iter().find(|item| item["contract"].as_str().unwrap() == symbol);
|
|
|
-// match ticker_info {
|
|
|
-// None => {
|
|
|
-// error!("coinex_swap:获取Ticker信息错误!\nget_ticker:res_data={:?}", res_data);
|
|
|
-// Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
-// }
|
|
|
-// Some(value) => {
|
|
|
-// let result = Ticker {
|
|
|
-// time: chrono::Utc::now().timestamp_millis(),
|
|
|
-// high: Decimal::from_str(value["high"].as_str().unwrap()).unwrap(),
|
|
|
-// low: Decimal::from_str(value["low"].as_str().unwrap()).unwrap(),
|
|
|
-// sell: Decimal::from_str(value["last"].as_str().unwrap()).unwrap(),
|
|
|
-// buy: Decimal::from_str(value["last"].as_str().unwrap()).unwrap(),
|
|
|
-// last: Decimal::from_str(value["last"].as_str().unwrap()).unwrap(),
|
|
|
-// volume: Decimal::from_str(value["volume"].as_str().unwrap()).unwrap(),
|
|
|
-// };
|
|
|
-// Ok(result)
|
|
|
-// }
|
|
|
-// }
|
|
|
-// } else {
|
|
|
-// Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
-// }
|
|
|
-// }
|
|
|
-//
|
|
|
-// async fn get_market(&mut self) -> Result<Market, Error> {
|
|
|
-// let symbol_array: Vec<&str> = self.symbol.split("_").collect();
|
|
|
-// let symbol = format!("{}{}", symbol_array[0], symbol_array[1]);
|
|
|
-// let res_data = self.request.get_market_details(symbol.clone()).await;
|
|
|
-// if res_data.code == 200 {
|
|
|
-// let res_data_json = res_data.data.as_array().unwrap();
|
|
|
-// let market_info = res_data_json.iter().find(|item| item["market"].as_str().unwrap() == symbol);
|
|
|
-// match market_info {
|
|
|
-// None => {
|
|
|
-// error!("coinex_swap:获取Market信息错误!\nget_market:res_data={:?}", res_data);
|
|
|
-// Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
-// }
|
|
|
-// Some(value) => {
|
|
|
-// // 报价精度字符串
|
|
|
-// let price_precision_i64 = value["quote_ccy_precision"].as_i64().unwrap();
|
|
|
-// // 价格最小变动数值
|
|
|
-// let tick_size = get_tick_size(price_precision_i64.to_u32().unwrap());
|
|
|
-// // 报价精度
|
|
|
-// let price_precision = Decimal::from_i64(price_precision_i64).unwrap();
|
|
|
-// // 最小数量
|
|
|
-// let min_qty = Decimal::from_str(value["min_amount"].as_str().unwrap()).unwrap();
|
|
|
-// // 数量没有最大值
|
|
|
-// let max_qty = Decimal::MAX;
|
|
|
-// // 没有张数
|
|
|
-// let ct_val = Decimal::ONE;
|
|
|
-//
|
|
|
-// let amount_size = min_qty * ct_val;
|
|
|
-// let amount_precision = Decimal::from_u32(amount_size.scale()).unwrap();
|
|
|
-// let min_notional = min_qty * ct_val;
|
|
|
-// let max_notional = max_qty * ct_val;
|
|
|
-//
|
|
|
-// let result = Market {
|
|
|
-// symbol: self.symbol.clone(),
|
|
|
-// base_asset: symbol_array[0].to_string(),
|
|
|
-// quote_asset: symbol_array[1].to_string(),
|
|
|
-// tick_size,
|
|
|
-// amount_size,
|
|
|
-// price_precision,
|
|
|
-// amount_precision,
|
|
|
-// min_qty,
|
|
|
-// max_qty,
|
|
|
-// min_notional,
|
|
|
-// max_notional,
|
|
|
-// ct_val,
|
|
|
-// };
|
|
|
-// Ok(result)
|
|
|
-// }
|
|
|
-// }
|
|
|
-// } else {
|
|
|
-// Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
-// }
|
|
|
-// }
|
|
|
-//
|
|
|
-// async fn get_market_symbol(&mut self, symbol: String) -> Result<Market, Error> {
|
|
|
-// let symbol_upper = symbol.to_uppercase();
|
|
|
-// let symbol_array: Vec<&str> = symbol_upper.split("_").collect();
|
|
|
-// let symbol = format!("{}{}", symbol_array[0], symbol_array[1]);
|
|
|
-// let res_data = self.request.get_market_details(symbol.clone()).await;
|
|
|
-// if res_data.code == 200 {
|
|
|
-// let res_data_json = res_data.data.as_array().unwrap();
|
|
|
-// let market_info = res_data_json.iter().find(|item| item["name"].as_str().unwrap() == symbol.clone());
|
|
|
-// match market_info {
|
|
|
-// None => {
|
|
|
-// error!("coinex_swap:获取Market信息错误!\nget_market:res_data={:?}", res_data);
|
|
|
-// Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
-// }
|
|
|
-// Some(value) => {
|
|
|
-// let tick_size = Decimal::from_str(value["quote_ccy_precision"].as_str().unwrap()).unwrap();
|
|
|
-// let min_qty = Decimal::from_str(&value["min_amount"].to_string()).unwrap();
|
|
|
-// // 数量没有最大值
|
|
|
-// let max_qty = Decimal::MAX;
|
|
|
-// // 没有张数
|
|
|
-// let ct_val = Decimal::ONE;
|
|
|
-//
|
|
|
-// let amount_size = min_qty * ct_val;
|
|
|
-// let price_precision = Decimal::from_u32(tick_size.scale()).unwrap();
|
|
|
-// let amount_precision = Decimal::from_u32(amount_size.scale()).unwrap();
|
|
|
-// let min_notional = min_qty * ct_val;
|
|
|
-// let max_notional = max_qty * ct_val;
|
|
|
-//
|
|
|
-// let result = Market {
|
|
|
-// symbol: self.symbol.clone(),
|
|
|
-// base_asset: symbol_array[0].to_string(),
|
|
|
-// quote_asset: symbol_array[1].to_string(),
|
|
|
-// tick_size,
|
|
|
-// amount_size,
|
|
|
-// price_precision,
|
|
|
-// amount_precision,
|
|
|
-// min_qty,
|
|
|
-// max_qty,
|
|
|
-// min_notional,
|
|
|
-// max_notional,
|
|
|
-// ct_val,
|
|
|
-// };
|
|
|
-// Ok(result)
|
|
|
-// }
|
|
|
-// }
|
|
|
-// } else {
|
|
|
-// Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
-// }
|
|
|
-// }
|
|
|
-//
|
|
|
-// // 获取订单详情
|
|
|
-// async fn get_order_detail(&mut self, order_id: &str, custom_id: &str) -> Result<Order, Error> {
|
|
|
-// let symbol = self.symbol.replace("_", "").to_uppercase();
|
|
|
-// let ct_val = self.market.ct_val;
|
|
|
-// let res_data;
|
|
|
-// let status ;
|
|
|
-// if order_id != "" {
|
|
|
-// status = "";
|
|
|
-// res_data = self.request.get_order_details(order_id.to_string(), symbol).await;
|
|
|
-// } else if custom_id != "" {
|
|
|
-// // 通过客户端id查询 只有未完成的订单才能查询出来
|
|
|
-// res_data = self.request.get_pending_order(custom_id.to_string()).await;
|
|
|
-// status = "open";
|
|
|
-// } else {
|
|
|
-// return Err(Error::new(ErrorKind::Other, format!("订单id和客户端id都为空,查询失败!order_id :{}, custom_id: {}", order_id, custom_id)));
|
|
|
-// }
|
|
|
-//
|
|
|
-// if res_data.code == 200 {
|
|
|
-// // info!("order_detail {}", res_data.data);
|
|
|
-// if res_data.data.is_array() {
|
|
|
-// let res_data_json = res_data.data.as_array().unwrap();
|
|
|
-// if res_data_json.len() == 0 { // 已取消或已成交
|
|
|
-// return Ok(Order{
|
|
|
-// id: order_id.to_string(),
|
|
|
-// custom_id: custom_id.to_string(),
|
|
|
-// price: Default::default(),
|
|
|
-// amount: Default::default(),
|
|
|
-// deal_amount: Default::default(),
|
|
|
-// avg_price: Default::default(),
|
|
|
-// status: "NULL".to_string(),
|
|
|
-// order_type: "".to_string(),
|
|
|
-// trace_stack: TraceStack::new(0, Instant::now()).on_special("358 coinex_swap".to_string()),
|
|
|
-// })
|
|
|
-// } else { // 待成交
|
|
|
-// let mut result = format_order_item(res_data_json[0].clone(), ct_val, status);
|
|
|
-// result.custom_id = custom_id.to_string();
|
|
|
-// result.id = order_id.to_string();
|
|
|
-// Ok(result)
|
|
|
-// }
|
|
|
-// } else {
|
|
|
-// let mut result = format_order_item(res_data.data, ct_val, status);
|
|
|
-// result.custom_id = custom_id.to_string();
|
|
|
-// result.id = order_id.to_string();
|
|
|
-// Ok(result)
|
|
|
-// }
|
|
|
-// } else {
|
|
|
-// Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
-// }
|
|
|
-// }
|
|
|
-//
|
|
|
-// // 获取未完成订单列表
|
|
|
-// async fn get_orders_list(&mut self, status: &str) -> Result<Vec<Order>, Error> {
|
|
|
-// let symbol = self.symbol.replace("_", "").to_uppercase();
|
|
|
-// let ct_val = self.market.ct_val;
|
|
|
-// let status_order;
|
|
|
-// let res_data;
|
|
|
-// if status == "pending" {
|
|
|
-// res_data = self.request.get_pending_orders().await;
|
|
|
-// status_order = "open";
|
|
|
-// } else if status == "finish" {
|
|
|
-// res_data = self.request.get_finished_orders().await;
|
|
|
-// status_order = "filled";
|
|
|
-// }else{
|
|
|
-// return Err(Error::new(ErrorKind::Other, status));
|
|
|
-// }
|
|
|
-// if res_data.code == 200 {
|
|
|
-// let res_data_json = res_data.data.as_array().unwrap();
|
|
|
-// if res_data_json.len() ==0 {
|
|
|
-// return Ok(vec![])
|
|
|
-// }
|
|
|
-// let order_info: Vec<_> = res_data_json.iter().filter(|item| item["market"].as_str().unwrap_or("") == symbol).collect();
|
|
|
-// let result = order_info.iter().map(|&item| format_order_item(item.clone(), ct_val, status_order)).collect();
|
|
|
-// Ok(result)
|
|
|
-// } else {
|
|
|
-// Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
-// }
|
|
|
-// }
|
|
|
-//
|
|
|
-// // 下单接口
|
|
|
-// async fn take_order(&mut self, custom_id: &str, origin_side: &str, price: Decimal, amount: Decimal) -> Result<Order, Error> {
|
|
|
-// let symbol = self.symbol.replace("_", "").to_uppercase();
|
|
|
-// let ct_val = self.market.ct_val;
|
|
|
-// let order_side;
|
|
|
-// let position_side;
|
|
|
-// let size = utils::truncate_decimal(amount, self.market.amount_precision.to_u32().unwrap());
|
|
|
-// if size <= Decimal::ZERO {
|
|
|
-// error!("下单数量异常 amount {} amount_precision {} size {}", amount, self.market.amount_precision.to_u32().unwrap(), size);
|
|
|
-// return Err(Error::new(ErrorKind::Other, format!("下单数量错误 amount:{}", amount)));
|
|
|
-// }
|
|
|
-// match origin_side {
|
|
|
-// "kd" => {
|
|
|
-// position_side = "long";
|
|
|
-// order_side = "buy";
|
|
|
-// }
|
|
|
-// "pd" => {
|
|
|
-// position_side = "long";
|
|
|
-// order_side = "sell";
|
|
|
-// }
|
|
|
-// "kk" => {
|
|
|
-// position_side = "short";
|
|
|
-// order_side = "sell";
|
|
|
-// }
|
|
|
-// "pk" => {
|
|
|
-// position_side = "short";
|
|
|
-// order_side = "buy";
|
|
|
-// }
|
|
|
-// _ => {
|
|
|
-// error!("下单参数错误");
|
|
|
-// position_side = "error";
|
|
|
-// order_side = "error";
|
|
|
-// }
|
|
|
-// };
|
|
|
-// let res_data = self.request.order(symbol, position_side.to_string(), order_side.to_string(), size, price, custom_id.to_string()).await;
|
|
|
-// if res_data.code == 200 {
|
|
|
-// let res_data_json: Value = res_data.data;
|
|
|
-// // info!("take_order {}", res_data_json);
|
|
|
-// let result = format_order_item(res_data_json, ct_val, "open");
|
|
|
-// Ok(result)
|
|
|
-// } else {
|
|
|
-// // error!("take_order error {}", res_data.data);
|
|
|
-// Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
-// }
|
|
|
-// }
|
|
|
-//
|
|
|
-// async fn take_order_symbol(&mut self, symbol_y: String, ct_val: Decimal, custom_id: &str, origin_side: &str, price: Decimal, amount: Decimal) -> Result<Order, Error> {
|
|
|
-// let symbol = symbol_y.replace("_", "").to_uppercase();
|
|
|
-// let order_side;
|
|
|
-// let position_side;
|
|
|
-// let size = (amount / ct_val).floor();
|
|
|
-// match origin_side {
|
|
|
-// "kd" => {
|
|
|
-// position_side = "long";
|
|
|
-// order_side = "buy";
|
|
|
-// }
|
|
|
-// "pd" => {
|
|
|
-// position_side = "long";
|
|
|
-// order_side = "sell";
|
|
|
-// }
|
|
|
-// "kk" => {
|
|
|
-// position_side = "short";
|
|
|
-// order_side = "sell";
|
|
|
-// }
|
|
|
-// "pk" => {
|
|
|
-// position_side = "short";
|
|
|
-// order_side = "buy";
|
|
|
-// }
|
|
|
-// _ => {
|
|
|
-// error!("下单参数错误");
|
|
|
-// position_side = "error";
|
|
|
-// order_side = "error";
|
|
|
-// }
|
|
|
-// };
|
|
|
-// let res_data = self.request.order(symbol, position_side.to_string(), order_side.to_string(), size, price, custom_id.to_string()).await;
|
|
|
-// if res_data.code == 200 {
|
|
|
-// let res_data_json: Value = res_data.data;
|
|
|
-// let result = format_order_item(res_data_json, ct_val, "open");
|
|
|
-// Ok(result)
|
|
|
-// } else {
|
|
|
-// Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
-// }
|
|
|
-// }
|
|
|
-//
|
|
|
-// // 撤销订单
|
|
|
-// async fn cancel_order(&mut self, order_id: &str, custom_id: &str) -> Result<Order, Error> {
|
|
|
-// let symbol = self.symbol.replace("_", "").to_uppercase();
|
|
|
-//
|
|
|
-// let ct_val = self.market.ct_val;
|
|
|
-// let res_data = self.request.cancel_order(symbol, order_id, custom_id).await;
|
|
|
-// if res_data.code == 200 {
|
|
|
-// let res_data_json: Value = res_data.data;
|
|
|
-// let mut result;
|
|
|
-// if res_data_json.is_null(){
|
|
|
-// result = Order{
|
|
|
-// id: custom_id.to_string(),
|
|
|
-// custom_id: order_id.to_string(),
|
|
|
-// price: Default::default(),
|
|
|
-// amount: Default::default(),
|
|
|
-// deal_amount: Default::default(),
|
|
|
-// avg_price: Default::default(),
|
|
|
-// status: "NULL".to_string(),
|
|
|
-// order_type: "".to_string(),
|
|
|
-// trace_stack: TraceStack::new(0, Instant::now()).on_special("513 coinex_swap".to_string())
|
|
|
-// };
|
|
|
-// } else {
|
|
|
-// result = format_order_item(res_data_json, ct_val, "canceled");
|
|
|
-// result.custom_id = custom_id.to_string();
|
|
|
-// result.id = order_id.to_string();
|
|
|
-// }
|
|
|
-// Ok(result)
|
|
|
-// } else {
|
|
|
-// let message = format!("撤单HTTP请求失败 order_id: {}, custom_id: {}, res_data: {:?}", order_id, custom_id, res_data);
|
|
|
-// Err(Error::new(ErrorKind::Other, message))
|
|
|
-// }
|
|
|
-// }
|
|
|
-// // 批量撤销订单
|
|
|
-// async fn cancel_orders(&mut self) -> Result<Vec<Order>, Error> {
|
|
|
-// let symbol = self.symbol.replace("_", "").to_uppercase();
|
|
|
-// let res_data = self.request.cancel_order_all(symbol).await;
|
|
|
-// if res_data.code == 200 {
|
|
|
-// info!("{}", res_data.data.to_string());
|
|
|
-// let result = vec![];
|
|
|
-// Ok(result)
|
|
|
-// } else {
|
|
|
-// Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
-// }
|
|
|
-// }
|
|
|
-//
|
|
|
-// async fn cancel_orders_all(&mut self) -> Result<Vec<Order>, Error> {
|
|
|
-// let orders_res_data = self.request.get_pending_orders().await;
|
|
|
-// if orders_res_data.code == 200 {
|
|
|
-// let result = vec![];
|
|
|
-// let orders_res_data_json = orders_res_data.data.as_array().unwrap();
|
|
|
-// for order in orders_res_data_json {
|
|
|
-// let cancel_res_data = self.request.cancel_order_all( order["market"].as_str().unwrap().to_string()).await;
|
|
|
-// if cancel_res_data.code != 200 {
|
|
|
-// return Err(Error::new(ErrorKind::Other, cancel_res_data.to_string()));
|
|
|
-// }
|
|
|
-// }
|
|
|
-// Ok(result)
|
|
|
-// } else {
|
|
|
-// Err(Error::new(ErrorKind::Other, orders_res_data.to_string()))
|
|
|
-// }
|
|
|
-// }
|
|
|
-//
|
|
|
-// async fn take_stop_loss_order(&mut self, _stop_price: Decimal, _price: Decimal, _side: &str) -> Result<Value, Error> {
|
|
|
-// Err(Error::new(ErrorKind::NotFound, "coin_ex:该交易所暂未实现自动订单下单".to_string()))
|
|
|
-// }
|
|
|
-//
|
|
|
-// async fn cancel_stop_loss_order(&mut self, _order_id: &str) -> Result<Value, Error> {
|
|
|
-// Err(Error::new(ErrorKind::NotFound, "coin_ex:该交易所暂未实现取消自动订单".to_string()))
|
|
|
-// }
|
|
|
-//
|
|
|
-// // 设置持仓模式
|
|
|
-// async fn set_dual_mode(&mut self, _coin: &str, _is_dual_mode: bool) -> Result<String, Error> {
|
|
|
-// Err(Error::new(ErrorKind::NotFound, "coin_ex:该交易所只允许单向持仓,无法设置持仓模式".to_string()))
|
|
|
-// }
|
|
|
-//
|
|
|
-// // 更新杠杆
|
|
|
-// async fn set_dual_leverage(&mut self, leverage: &str) -> Result<String, Error> {
|
|
|
-// let leverage_int = leverage.parse::<i32>().unwrap();
|
|
|
-// let symbol = self.symbol.replace("_", "").to_uppercase();
|
|
|
-// let res_data = self.request.setting_dual_leverage(symbol, leverage_int).await;
|
|
|
-// if res_data.code == 200 {
|
|
|
-// let res_data_str = &res_data.data;
|
|
|
-// let result = res_data_str.clone();
|
|
|
-// Ok(result.to_string())
|
|
|
-// } else {
|
|
|
-// Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
-// }
|
|
|
-// }
|
|
|
-//
|
|
|
-// async fn set_auto_deposit_status(&mut self, _status: bool) -> Result<String, Error> { Err(Error::new(ErrorKind::NotFound, "Coinex:该交易所方法未实现".to_string())) }
|
|
|
-//
|
|
|
-// async fn wallet_transfers(&mut self, _coin: &str, _from: &str, _to: &str, _amount: Decimal) -> Result<String, Error> {
|
|
|
-// Err(Error::new(ErrorKind::NotFound, "Coinex 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 {
|
|
|
-// // info!("数量 {},方向 {},价格 {},c_id {}", amount, side, price, cid);
|
|
|
-// ts.on_before_send();
|
|
|
-// let result = self_clone.take_order(&cid, &side, price, amount).await;
|
|
|
-// ts.on_after_send();
|
|
|
-//
|
|
|
-// match result {
|
|
|
-// Ok(mut result) => {
|
|
|
-// result.trace_stack = ts;
|
|
|
-// // info!("数量 {},方向 {},价格 {},c_id {}", amount, side, price, cid);
|
|
|
-// self_clone.order_sender.send(result).await.unwrap();
|
|
|
-// }
|
|
|
-// Err(error) => {
|
|
|
-// let mut err_order = Order::new();
|
|
|
-// err_order.custom_id = cid.clone();
|
|
|
-// err_order.status = "REMOVE".to_string();
|
|
|
-// // info!("coinex下单error 数量: {},方向: {},价格: {},c_id: {}, err: {}", amount, side, price, cid, error);
|
|
|
-// self_clone.order_sender.send(err_order).await.unwrap();
|
|
|
-// self_clone.error_sender.send(error).await.unwrap();
|
|
|
-// // 触发限频
|
|
|
-// // if error_info.to_string().contains("213:Please don't try too frequently") {
|
|
|
-// // Err(Error::new(ErrorKind::Other, "触发限频, 请调整下单频率"))
|
|
|
-// // }
|
|
|
-// }
|
|
|
-// }
|
|
|
-// });
|
|
|
-// 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();
|
|
|
-//
|
|
|
-// 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(order) => {
|
|
|
-// if order.status == "REMOVE" {
|
|
|
-// // 由于有时撤单成功ws不推送,所以加入rest
|
|
|
-// self_clone.order_sender.send(order).await.unwrap();
|
|
|
-// }
|
|
|
-// }
|
|
|
-// Err(error) => {
|
|
|
-// // info!("撤单失败:{:?}", error.to_string());
|
|
|
-// // 取消失败去查订单。
|
|
|
-// let query_rst = self_clone.get_order_detail(&order_id, &custom_id).await;
|
|
|
-// match query_rst {
|
|
|
-// Ok(order) => {
|
|
|
-// // info!("查单 订单详情:{:?}", order);
|
|
|
-// self_clone.order_sender.send(order).await.unwrap();
|
|
|
-// }
|
|
|
-// Err(_err) => {
|
|
|
-// // warn!("撤单失败,而且查单也失败了,Coinex_io_swap,oid={}, cid={} err={:?}", order_id.clone(), custom_id.clone(), err);
|
|
|
-// // panic!("撤单失败,而且查单也失败了,Coinex_io_swap,oid={}, cid={}。", order_id.clone(), custom_id.clone());
|
|
|
-// }
|
|
|
-// }
|
|
|
-// 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, &custom_id).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_position_item(position: &Value, ct_val: Decimal) -> Position {
|
|
|
-// let position_mode = match position["side"].as_str().unwrap_or("") {
|
|
|
-// "long" => PositionModeEnum::Long,
|
|
|
-// "short" => PositionModeEnum::Short,
|
|
|
-// _ => {
|
|
|
-// error!("coinex_swap:格式化持仓模式错误!\nformat_position_item:position={:?}", position);
|
|
|
-// panic!("coinex_swap:格式化持仓模式错误!\nformat_position_item:position={:?}", position)
|
|
|
-// }
|
|
|
-// };
|
|
|
-// let size = Decimal::from_str(&position["open_interest"].as_str().unwrap()).unwrap();
|
|
|
-// let amount = size * ct_val;
|
|
|
-// Position {
|
|
|
-// symbol: position["market"].as_str().unwrap_or("").parse().unwrap(),
|
|
|
-// margin_level: Decimal::from_str(position["leverage"].as_str().unwrap()).unwrap(),
|
|
|
-// amount,
|
|
|
-// frozen_amount: Decimal::ZERO,
|
|
|
-// price: Decimal::from_str(position["avg_entry_price"].as_str().unwrap()).unwrap(),
|
|
|
-// profit: Decimal::from_str(position["unrealized_pnl"].as_str().unwrap()).unwrap(),
|
|
|
-// position_mode,
|
|
|
-// margin: Decimal::from_str(position["ath_margin_size"].as_str().unwrap()).unwrap(),
|
|
|
-// }
|
|
|
-// }
|
|
|
-//
|
|
|
-// pub fn format_order_item(order: Value, ct_val: Decimal, status: &str) -> Order {
|
|
|
-// // info!("order_format {}", order);
|
|
|
-// let size;
|
|
|
-// match order["amount"].as_str() {
|
|
|
-// Some(val) => {
|
|
|
-// size = Decimal::from_str(val).unwrap()
|
|
|
-// },
|
|
|
-// None => {
|
|
|
-// error!("coinex_swap:格式化订单大小错误!\nformat_order_item:order={:?} status={}", order, status);
|
|
|
-// panic!("coinex_swap:格式化订单大小失败,退出程序!");
|
|
|
-// }
|
|
|
-// }
|
|
|
-// // info!("order {}", order);
|
|
|
-// // 通过客户端id查询订单 只能查出未完成订单(没有状态字段)
|
|
|
-// let status = order["status"].as_str().unwrap_or(status);
|
|
|
-// let text = order["client_id"].as_str().unwrap_or("");
|
|
|
-//
|
|
|
-// let deal_amount = Decimal::from_str(&order["filled_amount"].as_str().unwrap()).unwrap();
|
|
|
-// let filled_value = Decimal::from_str(&order["filled_value"].as_str().unwrap()).unwrap();
|
|
|
-//
|
|
|
-// let amount = size * ct_val;
|
|
|
-// let mut avg_price = Decimal::ZERO;
|
|
|
-// if deal_amount != Decimal::ZERO{
|
|
|
-// avg_price = filled_value/deal_amount;
|
|
|
-// }
|
|
|
-// let custom_status = if status == "filled" || status == "canceled" {
|
|
|
-// "REMOVE".to_string()
|
|
|
-// } else if status == "open" || status == "part_filled" || status == "part_canceled" {
|
|
|
-// "NEW".to_string()
|
|
|
-// } else {
|
|
|
-// error!("coinex_swap:格式化订单状态错误!\nformat_order_item:order={:?}", order);
|
|
|
-// "NULL".to_string()
|
|
|
-// };
|
|
|
-// let rst_order = Order {
|
|
|
-// id: order["order_id"].to_string(),
|
|
|
-// custom_id: text.to_string(),
|
|
|
-// price: Decimal::from_str(order["price"].as_str().unwrap()).unwrap(),
|
|
|
-// amount,
|
|
|
-// deal_amount,
|
|
|
-// avg_price,
|
|
|
-// status: custom_status,
|
|
|
-// order_type: "limit".to_string(),
|
|
|
-// trace_stack: TraceStack::new(0, Instant::now()).on_special("765 trace_stack".to_string()),
|
|
|
-// };
|
|
|
-// return rst_order;
|
|
|
-// }
|
|
|
+use std::collections::{BTreeMap};
|
|
|
+use std::io::{Error, ErrorKind};
|
|
|
+use std::str::FromStr;
|
|
|
+use tokio::sync::mpsc::Sender;
|
|
|
+use async_trait::async_trait;
|
|
|
+use futures::stream::FuturesUnordered;
|
|
|
+use futures::TryStreamExt;
|
|
|
+use rust_decimal::Decimal;
|
|
|
+use rust_decimal::prelude::{FromPrimitive, ToPrimitive};
|
|
|
+use serde_json::{Value};
|
|
|
+use tokio::spawn;
|
|
|
+use tokio::time::Instant;
|
|
|
+use tracing::{error, info, trace};
|
|
|
+use exchanges::coinex_swap_rest::CoinexSwapRest;
|
|
|
+use crate::{Platform, ExchangeEnum, Account, Position, Ticker, Market, Order, OrderCommand, PositionModeEnum, utils};
|
|
|
+use global::trace_stack::TraceStack;
|
|
|
+use crate::utils::get_tick_size;
|
|
|
+
|
|
|
+#[allow(dead_code)]
|
|
|
+#[derive(Clone)]
|
|
|
+pub struct CoinexSwap {
|
|
|
+ exchange: ExchangeEnum,
|
|
|
+ symbol: String,
|
|
|
+ is_colo: bool,
|
|
|
+ params: BTreeMap<String, String>,
|
|
|
+ request: CoinexSwapRest,
|
|
|
+ market: Market,
|
|
|
+ order_sender: Sender<Order>,
|
|
|
+ error_sender: Sender<Error>,
|
|
|
+}
|
|
|
+
|
|
|
+impl CoinexSwap {
|
|
|
+ pub async fn new(symbol: String, is_colo: bool, params: BTreeMap<String, String>, order_sender: Sender<Order>, error_sender: Sender<Error>) -> CoinexSwap {
|
|
|
+ let market = Market::new();
|
|
|
+ let mut coinex_swap = CoinexSwap {
|
|
|
+ exchange: ExchangeEnum::CoinexSwap,
|
|
|
+ symbol: symbol.to_uppercase(),
|
|
|
+ is_colo,
|
|
|
+ params: params.clone(),
|
|
|
+ request: CoinexSwapRest::new(params.clone()),
|
|
|
+ market,
|
|
|
+ order_sender,
|
|
|
+ error_sender,
|
|
|
+ };
|
|
|
+
|
|
|
+ // 修改持仓模式
|
|
|
+ let symbol_array: Vec<&str> = symbol.split("_").collect();
|
|
|
+ let mode_result = coinex_swap.set_dual_mode(symbol_array[1], true).await;
|
|
|
+ match mode_result {
|
|
|
+ Ok(_) => {
|
|
|
+ trace!("Coinex:设置持仓模式成功!")
|
|
|
+ }
|
|
|
+ Err(error) => {
|
|
|
+ error!("Coinex:设置持仓模式失败!mode_result={}", error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 获取市场信息
|
|
|
+ coinex_swap.market = CoinexSwap::get_market(&mut coinex_swap).await.unwrap_or(coinex_swap.market);
|
|
|
+ // 设置持仓杠杆
|
|
|
+ let lever_rate_result = coinex_swap.set_dual_leverage("10").await;
|
|
|
+ match lever_rate_result {
|
|
|
+ Ok(ok) => {
|
|
|
+ info!("Coinex:设置持仓杠杆成功!{:?}", ok);
|
|
|
+ }
|
|
|
+ Err(error) => {
|
|
|
+ error!("Coinex:设置持仓杠杆失败!{:?}", error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return coinex_swap;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[async_trait]
|
|
|
+impl Platform for CoinexSwap {
|
|
|
+ // 克隆方法
|
|
|
+ fn clone_box(&self) -> Box<dyn Platform + Send + Sync> { Box::new(self.clone()) }
|
|
|
+ // 获取交易所模式
|
|
|
+ fn get_self_exchange(&self) -> ExchangeEnum {
|
|
|
+ ExchangeEnum::CoinexSwap
|
|
|
+ }
|
|
|
+ // 获取交易对
|
|
|
+ fn get_self_symbol(&self) -> String { self.symbol.clone() }
|
|
|
+ // 获取是否使用高速通道
|
|
|
+ fn get_self_is_colo(&self) -> bool {
|
|
|
+ self.is_colo
|
|
|
+ }
|
|
|
+ // 获取params信息
|
|
|
+ fn get_self_params(&self) -> BTreeMap<String, String> {
|
|
|
+ self.params.clone()
|
|
|
+ }
|
|
|
+ // 获取market信息
|
|
|
+ fn get_self_market(&self) -> Market { self.market.clone() }
|
|
|
+ // 获取请求时间
|
|
|
+ fn get_request_delays(&self) -> Vec<i64> { self.request.get_delays() }
|
|
|
+ // 获取请求平均时间
|
|
|
+ fn get_request_avg_delay(&self) -> Decimal { self.request.get_avg_delay() }
|
|
|
+ // 获取请求最大时间
|
|
|
+ fn get_request_max_delay(&self) -> i64 { self.request.get_max_delay() }
|
|
|
+
|
|
|
+ // 获取服务器时间
|
|
|
+ async fn get_server_time(&mut self) -> Result<String, Error> {
|
|
|
+ let res_data = self.request.get_server_time().await;
|
|
|
+ if res_data.code == 200 {
|
|
|
+ let res_data_json: Value = res_data.data;
|
|
|
+ let result = res_data_json["timestamp"].to_string();
|
|
|
+ Ok(result)
|
|
|
+ } else {
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 获取账号信息
|
|
|
+ async fn get_account(&mut self) -> Result<Account, Error> {
|
|
|
+ let symbol_array: Vec<&str> = self.symbol.split("_").collect();
|
|
|
+ let coin = symbol_array[1].to_string().to_uppercase();
|
|
|
+ let res_data = self.request.get_account().await;
|
|
|
+ if res_data.code == 200 {
|
|
|
+ if res_data.data.is_array() {
|
|
|
+ let res_data_array = res_data.data.as_array().unwrap();
|
|
|
+ for res_data_json in res_data_array.iter() {
|
|
|
+ if res_data_json["ccy"].as_str().unwrap() == coin {
|
|
|
+ let frozen_balance= Decimal::from_str(res_data_json["frozen"].as_str().unwrap()).unwrap();
|
|
|
+ let available_balance = Decimal::from_str(res_data_json["available"].as_str().unwrap()).unwrap();
|
|
|
+ let balance = frozen_balance + available_balance;
|
|
|
+ let result = Account {
|
|
|
+ coin: symbol_array[1].to_string(),
|
|
|
+ balance,
|
|
|
+ available_balance,
|
|
|
+ frozen_balance,
|
|
|
+ stocks: Decimal::ZERO,
|
|
|
+ available_stocks: Decimal::ZERO,
|
|
|
+ frozen_stocks: Decimal::ZERO,
|
|
|
+ };
|
|
|
+ return Ok(result);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn get_spot_account(&mut self) -> Result<Vec<Account>, Error> {
|
|
|
+ Err(Error::new(ErrorKind::NotFound, "coinex_swap:该方法暂未实现".to_string()))
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取持仓信息
|
|
|
+ async fn get_position(&mut self) -> Result<Vec<Position>, Error> {
|
|
|
+ let symbol: String = self.symbol.replace("_", "").to_uppercase();
|
|
|
+ let ct_val = self.market.multiplier;
|
|
|
+ let res_data = self.request.get_position(symbol).await;
|
|
|
+ if res_data.code == 200 {
|
|
|
+ let res_data_json = res_data.data.as_array().unwrap();
|
|
|
+ let result = res_data_json.iter().map(|item| { format_position_item(item, ct_val) }).collect();
|
|
|
+ Ok(result)
|
|
|
+ } else {
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取所有持仓
|
|
|
+ async fn get_positions(&mut self) -> Result<Vec<Position>, Error> {
|
|
|
+ let res_data = self.request.get_user_position().await;
|
|
|
+ if res_data.code == 200 {
|
|
|
+ info!("{}", res_data.data.to_string());
|
|
|
+ let res_data_json = res_data.data.as_array().unwrap();
|
|
|
+ let result = res_data_json.iter().map(|item| { format_position_item(item, Decimal::ONE) }).collect();
|
|
|
+ Ok(result)
|
|
|
+ } else {
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 获取市场行情
|
|
|
+ async fn get_ticker(&mut self) -> Result<Ticker, Error> {
|
|
|
+ let symbol: String = self.symbol.replace("_", "");
|
|
|
+ let res_data = self.request.get_ticker(symbol.clone()).await;
|
|
|
+ if res_data.code == 200 {
|
|
|
+ let res_data_json = res_data.data.as_array().unwrap();
|
|
|
+ let ticker_info = res_data_json.iter().find(|item| item["market"].as_str().unwrap() == symbol);
|
|
|
+ match ticker_info {
|
|
|
+ None => {
|
|
|
+ error!("coinex_swap:获取Ticker信息错误!\nget_ticker:res_data={:?}", res_data);
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ Some(value) => {
|
|
|
+ let result = Ticker {
|
|
|
+ time: chrono::Utc::now().timestamp_millis(),
|
|
|
+ high: Decimal::from_str(value["high"].as_str().unwrap()).unwrap(),
|
|
|
+ low: Decimal::from_str(value["low"].as_str().unwrap()).unwrap(),
|
|
|
+ sell: Decimal::from_str(value["last"].as_str().unwrap()).unwrap(),
|
|
|
+ buy: Decimal::from_str(value["last"].as_str().unwrap()).unwrap(),
|
|
|
+ last: Decimal::from_str(value["last"].as_str().unwrap()).unwrap(),
|
|
|
+ volume: Decimal::from_str(value["volume"].as_str().unwrap()).unwrap(),
|
|
|
+ };
|
|
|
+ Ok(result)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn get_ticker_symbol(&mut self, symbol_param: String) -> Result<Ticker, Error> {
|
|
|
+ let symbol: String = symbol_param.replace("_", "").to_uppercase();
|
|
|
+ let res_data = self.request.get_ticker(symbol.clone()).await;
|
|
|
+ if res_data.code == 200 {
|
|
|
+ let res_data_json = res_data.data.as_array().unwrap();
|
|
|
+ let ticker_info = res_data_json.iter().find(|item| item["contract"].as_str().unwrap() == symbol);
|
|
|
+ match ticker_info {
|
|
|
+ None => {
|
|
|
+ error!("coinex_swap:获取Ticker信息错误!\nget_ticker:res_data={:?}", res_data);
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ Some(value) => {
|
|
|
+ let result = Ticker {
|
|
|
+ time: chrono::Utc::now().timestamp_millis(),
|
|
|
+ high: Decimal::from_str(value["high"].as_str().unwrap()).unwrap(),
|
|
|
+ low: Decimal::from_str(value["low"].as_str().unwrap()).unwrap(),
|
|
|
+ sell: Decimal::from_str(value["last"].as_str().unwrap()).unwrap(),
|
|
|
+ buy: Decimal::from_str(value["last"].as_str().unwrap()).unwrap(),
|
|
|
+ last: Decimal::from_str(value["last"].as_str().unwrap()).unwrap(),
|
|
|
+ volume: Decimal::from_str(value["volume"].as_str().unwrap()).unwrap(),
|
|
|
+ };
|
|
|
+ Ok(result)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn get_market(&mut self) -> Result<Market, Error> {
|
|
|
+ let symbol_array: Vec<&str> = self.symbol.split("_").collect();
|
|
|
+ let symbol = format!("{}{}", symbol_array[0], symbol_array[1]);
|
|
|
+ let res_data = self.request.get_market_details(symbol.clone()).await;
|
|
|
+ if res_data.code == 200 {
|
|
|
+ let res_data_json = res_data.data.as_array().unwrap();
|
|
|
+ let market_info = res_data_json.iter().find(|item| item["market"].as_str().unwrap() == symbol);
|
|
|
+ match market_info {
|
|
|
+ None => {
|
|
|
+ error!("coinex_swap:获取Market信息错误!\nget_market:res_data={:?}", res_data);
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ Some(value) => {
|
|
|
+ // 报价精度字符串
|
|
|
+ let price_precision_i64 = value["quote_ccy_precision"].as_i64().unwrap();
|
|
|
+ // 价格最小变动数值
|
|
|
+ let tick_size = get_tick_size(price_precision_i64.to_u32().unwrap());
|
|
|
+ // 报价精度
|
|
|
+ let price_precision = Decimal::from_i64(price_precision_i64).unwrap();
|
|
|
+ // 最小数量
|
|
|
+ let min_qty = Decimal::from_str(value["min_amount"].as_str().unwrap()).unwrap();
|
|
|
+ // 数量没有最大值
|
|
|
+ let max_qty = Decimal::MAX;
|
|
|
+ // 没有张数
|
|
|
+ let multiplier = Decimal::ONE;
|
|
|
+
|
|
|
+ let amount_size = min_qty * multiplier;
|
|
|
+ let amount_precision = Decimal::from_u32(amount_size.scale()).unwrap();
|
|
|
+ let min_notional = min_qty * multiplier;
|
|
|
+ let max_notional = max_qty * multiplier;
|
|
|
+
|
|
|
+ let result = Market {
|
|
|
+ symbol: self.symbol.clone(),
|
|
|
+ base_asset: symbol_array[0].to_string(),
|
|
|
+ quote_asset: symbol_array[1].to_string(),
|
|
|
+ tick_size,
|
|
|
+ amount_size,
|
|
|
+ price_precision,
|
|
|
+ amount_precision,
|
|
|
+ min_qty,
|
|
|
+ max_qty,
|
|
|
+ min_notional,
|
|
|
+ max_notional,
|
|
|
+ multiplier,
|
|
|
+ };
|
|
|
+ Ok(result)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn get_market_symbol(&mut self, symbol: String) -> Result<Market, Error> {
|
|
|
+ let symbol_upper = symbol.to_uppercase();
|
|
|
+ let symbol_array: Vec<&str> = symbol_upper.split("_").collect();
|
|
|
+ let symbol = format!("{}{}", symbol_array[0], symbol_array[1]);
|
|
|
+ let res_data = self.request.get_market_details(symbol.clone()).await;
|
|
|
+ if res_data.code == 200 {
|
|
|
+ let res_data_json = res_data.data.as_array().unwrap();
|
|
|
+ let market_info = res_data_json.iter().find(|item| item["name"].as_str().unwrap() == symbol.clone());
|
|
|
+ match market_info {
|
|
|
+ None => {
|
|
|
+ error!("coinex_swap:获取Market信息错误!\nget_market:res_data={:?}", res_data);
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ Some(value) => {
|
|
|
+ let tick_size = Decimal::from_str(value["quote_ccy_precision"].as_str().unwrap()).unwrap();
|
|
|
+ let min_qty = Decimal::from_str(&value["min_amount"].to_string()).unwrap();
|
|
|
+ // 数量没有最大值
|
|
|
+ let max_qty = Decimal::MAX;
|
|
|
+ // 没有张数
|
|
|
+ let multiplier = Decimal::ONE;
|
|
|
+
|
|
|
+ let amount_size = min_qty * multiplier;
|
|
|
+ let price_precision = Decimal::from_u32(tick_size.scale()).unwrap();
|
|
|
+ let amount_precision = Decimal::from_u32(amount_size.scale()).unwrap();
|
|
|
+ let min_notional = min_qty * multiplier;
|
|
|
+ let max_notional = max_qty * multiplier;
|
|
|
+
|
|
|
+ let result = Market {
|
|
|
+ symbol: self.symbol.clone(),
|
|
|
+ base_asset: symbol_array[0].to_string(),
|
|
|
+ quote_asset: symbol_array[1].to_string(),
|
|
|
+ tick_size,
|
|
|
+ amount_size,
|
|
|
+ price_precision,
|
|
|
+ amount_precision,
|
|
|
+ min_qty,
|
|
|
+ max_qty,
|
|
|
+ min_notional,
|
|
|
+ max_notional,
|
|
|
+ multiplier,
|
|
|
+ };
|
|
|
+ Ok(result)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取订单详情
|
|
|
+ async fn get_order_detail(&mut self, order_id: &str, custom_id: &str) -> Result<Order, Error> {
|
|
|
+ let symbol = self.symbol.replace("_", "").to_uppercase();
|
|
|
+ let multiplier = self.market.multiplier;
|
|
|
+ let res_data;
|
|
|
+ let status ;
|
|
|
+ if order_id != "" {
|
|
|
+ status = "";
|
|
|
+ res_data = self.request.get_order_details(order_id.to_string(), symbol).await;
|
|
|
+ } else if custom_id != "" {
|
|
|
+ // 通过客户端id查询 只有未完成的订单才能查询出来
|
|
|
+ res_data = self.request.get_pending_order(custom_id.to_string()).await;
|
|
|
+ status = "open";
|
|
|
+ } else {
|
|
|
+ return Err(Error::new(ErrorKind::Other, format!("订单id和客户端id都为空,查询失败!order_id :{}, custom_id: {}", order_id, custom_id)));
|
|
|
+ }
|
|
|
+
|
|
|
+ if res_data.code == 200 {
|
|
|
+ // info!("order_detail {}", res_data.data);
|
|
|
+ if res_data.data.is_array() {
|
|
|
+ let res_data_json = res_data.data.as_array().unwrap();
|
|
|
+ if res_data_json.len() == 0 { // 已取消或已成交
|
|
|
+ return Ok(Order{
|
|
|
+ id: order_id.to_string(),
|
|
|
+ custom_id: custom_id.to_string(),
|
|
|
+ price: Default::default(),
|
|
|
+ amount: Default::default(),
|
|
|
+ deal_amount: Default::default(),
|
|
|
+ avg_price: Default::default(),
|
|
|
+ status: "NULL".to_string(),
|
|
|
+ order_type: "".to_string(),
|
|
|
+ trace_stack: TraceStack::new(0, Instant::now()).on_special("358 coinex_swap".to_string()),
|
|
|
+ })
|
|
|
+ } else { // 待成交
|
|
|
+ let mut result = format_order_item(res_data_json[0].clone(), multiplier, status);
|
|
|
+ result.custom_id = custom_id.to_string();
|
|
|
+ result.id = order_id.to_string();
|
|
|
+ Ok(result)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ let mut result = format_order_item(res_data.data, multiplier, status);
|
|
|
+ result.custom_id = custom_id.to_string();
|
|
|
+ result.id = order_id.to_string();
|
|
|
+ Ok(result)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取未完成订单列表
|
|
|
+ async fn get_orders_list(&mut self, status: &str) -> Result<Vec<Order>, Error> {
|
|
|
+ let symbol = self.symbol.replace("_", "").to_uppercase();
|
|
|
+ let multiplier = self.market.multiplier;
|
|
|
+ let status_order;
|
|
|
+ let res_data;
|
|
|
+ if status == "pending" {
|
|
|
+ res_data = self.request.get_pending_orders().await;
|
|
|
+ status_order = "open";
|
|
|
+ } else if status == "finish" {
|
|
|
+ res_data = self.request.get_finished_orders().await;
|
|
|
+ status_order = "filled";
|
|
|
+ }else{
|
|
|
+ return Err(Error::new(ErrorKind::Other, status));
|
|
|
+ }
|
|
|
+ if res_data.code == 200 {
|
|
|
+ let res_data_json = res_data.data.as_array().unwrap();
|
|
|
+ if res_data_json.len() ==0 {
|
|
|
+ return Ok(vec![])
|
|
|
+ }
|
|
|
+ let order_info: Vec<_> = res_data_json.iter().filter(|item| item["market"].as_str().unwrap_or("") == symbol).collect();
|
|
|
+ let result = order_info.iter().map(|&item| format_order_item(item.clone(), multiplier, status_order)).collect();
|
|
|
+ Ok(result)
|
|
|
+ } else {
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 下单接口
|
|
|
+ async fn take_order(&mut self, custom_id: &str, origin_side: &str, price: Decimal, amount: Decimal) -> Result<Order, Error> {
|
|
|
+ let symbol = self.symbol.replace("_", "").to_uppercase();
|
|
|
+ let multiplier = self.market.multiplier;
|
|
|
+ let order_side;
|
|
|
+ let position_side;
|
|
|
+ let size = utils::truncate_decimal(amount, self.market.amount_precision.to_u32().unwrap());
|
|
|
+ if size <= Decimal::ZERO {
|
|
|
+ error!("下单数量异常 amount {} amount_precision {} size {}", amount, self.market.amount_precision.to_u32().unwrap(), size);
|
|
|
+ return Err(Error::new(ErrorKind::Other, format!("下单数量错误 amount:{}", amount)));
|
|
|
+ }
|
|
|
+ match origin_side {
|
|
|
+ "kd" => {
|
|
|
+ position_side = "long";
|
|
|
+ order_side = "buy";
|
|
|
+ }
|
|
|
+ "pd" => {
|
|
|
+ position_side = "long";
|
|
|
+ order_side = "sell";
|
|
|
+ }
|
|
|
+ "kk" => {
|
|
|
+ position_side = "short";
|
|
|
+ order_side = "sell";
|
|
|
+ }
|
|
|
+ "pk" => {
|
|
|
+ position_side = "short";
|
|
|
+ order_side = "buy";
|
|
|
+ }
|
|
|
+ _ => {
|
|
|
+ error!("下单参数错误");
|
|
|
+ position_side = "error";
|
|
|
+ order_side = "error";
|
|
|
+ }
|
|
|
+ };
|
|
|
+ let res_data = self.request.order(symbol, position_side.to_string(), order_side.to_string(), size, price, custom_id.to_string()).await;
|
|
|
+ if res_data.code == 200 {
|
|
|
+ let res_data_json: Value = res_data.data;
|
|
|
+ // info!("take_order {}", res_data_json);
|
|
|
+ let result = format_order_item(res_data_json, multiplier, "open");
|
|
|
+ Ok(result)
|
|
|
+ } else {
|
|
|
+ // error!("take_order error {}", res_data.data);
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn take_order_symbol(&mut self, symbol_y: String, ct_val: Decimal, custom_id: &str, origin_side: &str, price: Decimal, amount: Decimal) -> Result<Order, Error> {
|
|
|
+ let symbol = symbol_y.replace("_", "").to_uppercase();
|
|
|
+ let order_side;
|
|
|
+ let position_side;
|
|
|
+ let size = (amount / ct_val).floor();
|
|
|
+ match origin_side {
|
|
|
+ "kd" => {
|
|
|
+ position_side = "long";
|
|
|
+ order_side = "buy";
|
|
|
+ }
|
|
|
+ "pd" => {
|
|
|
+ position_side = "long";
|
|
|
+ order_side = "sell";
|
|
|
+ }
|
|
|
+ "kk" => {
|
|
|
+ position_side = "short";
|
|
|
+ order_side = "sell";
|
|
|
+ }
|
|
|
+ "pk" => {
|
|
|
+ position_side = "short";
|
|
|
+ order_side = "buy";
|
|
|
+ }
|
|
|
+ _ => {
|
|
|
+ error!("下单参数错误");
|
|
|
+ position_side = "error";
|
|
|
+ order_side = "error";
|
|
|
+ }
|
|
|
+ };
|
|
|
+ let res_data = self.request.order(symbol, position_side.to_string(), order_side.to_string(), size, price, custom_id.to_string()).await;
|
|
|
+ if res_data.code == 200 {
|
|
|
+ let res_data_json: Value = res_data.data;
|
|
|
+ let result = format_order_item(res_data_json, ct_val, "open");
|
|
|
+ Ok(result)
|
|
|
+ } else {
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 撤销订单
|
|
|
+ async fn cancel_order(&mut self, order_id: &str, custom_id: &str) -> Result<Order, Error> {
|
|
|
+ let symbol = self.symbol.replace("_", "").to_uppercase();
|
|
|
+
|
|
|
+ let multiplier = self.market.multiplier;
|
|
|
+ let res_data = self.request.cancel_order(symbol, order_id, custom_id).await;
|
|
|
+ if res_data.code == 200 {
|
|
|
+ let res_data_json: Value = res_data.data;
|
|
|
+ let mut result;
|
|
|
+ if res_data_json.is_null(){
|
|
|
+ result = Order{
|
|
|
+ id: custom_id.to_string(),
|
|
|
+ custom_id: order_id.to_string(),
|
|
|
+ price: Default::default(),
|
|
|
+ amount: Default::default(),
|
|
|
+ deal_amount: Default::default(),
|
|
|
+ avg_price: Default::default(),
|
|
|
+ status: "NULL".to_string(),
|
|
|
+ order_type: "".to_string(),
|
|
|
+ trace_stack: TraceStack::new(0, Instant::now()).on_special("513 coinex_swap".to_string())
|
|
|
+ };
|
|
|
+ } else {
|
|
|
+ result = format_order_item(res_data_json, multiplier, "canceled");
|
|
|
+ result.custom_id = custom_id.to_string();
|
|
|
+ result.id = order_id.to_string();
|
|
|
+ }
|
|
|
+ Ok(result)
|
|
|
+ } else {
|
|
|
+ let message = format!("撤单HTTP请求失败 order_id: {}, custom_id: {}, res_data: {:?}", order_id, custom_id, res_data);
|
|
|
+ Err(Error::new(ErrorKind::Other, message))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 批量撤销订单
|
|
|
+ async fn cancel_orders(&mut self) -> Result<Vec<Order>, Error> {
|
|
|
+ let symbol = self.symbol.replace("_", "").to_uppercase();
|
|
|
+ let res_data = self.request.cancel_order_all(symbol).await;
|
|
|
+ if res_data.code == 200 {
|
|
|
+ info!("{}", res_data.data.to_string());
|
|
|
+ let result = vec![];
|
|
|
+ Ok(result)
|
|
|
+ } else {
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn cancel_orders_all(&mut self) -> Result<Vec<Order>, Error> {
|
|
|
+ let orders_res_data = self.request.get_pending_orders().await;
|
|
|
+ if orders_res_data.code == 200 {
|
|
|
+ let result = vec![];
|
|
|
+ let orders_res_data_json = orders_res_data.data.as_array().unwrap();
|
|
|
+ for order in orders_res_data_json {
|
|
|
+ let cancel_res_data = self.request.cancel_order_all( order["market"].as_str().unwrap().to_string()).await;
|
|
|
+ if cancel_res_data.code != 200 {
|
|
|
+ return Err(Error::new(ErrorKind::Other, cancel_res_data.to_string()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Ok(result)
|
|
|
+ } else {
|
|
|
+ Err(Error::new(ErrorKind::Other, orders_res_data.to_string()))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn take_stop_loss_order(&mut self, _stop_price: Decimal, _price: Decimal, _side: &str) -> Result<Value, Error> {
|
|
|
+ Err(Error::new(ErrorKind::NotFound, "coin_ex:该交易所暂未实现自动订单下单".to_string()))
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn cancel_stop_loss_order(&mut self, _order_id: &str) -> Result<Value, Error> {
|
|
|
+ Err(Error::new(ErrorKind::NotFound, "coin_ex:该交易所暂未实现取消自动订单".to_string()))
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置持仓模式
|
|
|
+ async fn set_dual_mode(&mut self, _coin: &str, _is_dual_mode: bool) -> Result<String, Error> {
|
|
|
+ Err(Error::new(ErrorKind::NotFound, "coin_ex:该交易所只允许单向持仓,无法设置持仓模式".to_string()))
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新杠杆
|
|
|
+ async fn set_dual_leverage(&mut self, leverage: &str) -> Result<String, Error> {
|
|
|
+ let leverage_int = leverage.parse::<i32>().unwrap();
|
|
|
+ let symbol = self.symbol.replace("_", "").to_uppercase();
|
|
|
+ let res_data = self.request.setting_dual_leverage(symbol, leverage_int).await;
|
|
|
+ if res_data.code == 200 {
|
|
|
+ let res_data_str = &res_data.data;
|
|
|
+ let result = res_data_str.clone();
|
|
|
+ Ok(result.to_string())
|
|
|
+ } else {
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn set_auto_deposit_status(&mut self, _status: bool) -> Result<String, Error> { Err(Error::new(ErrorKind::NotFound, "Coinex:该交易所方法未实现".to_string())) }
|
|
|
+
|
|
|
+ async fn wallet_transfers(&mut self, _coin: &str, _from: &str, _to: &str, _amount: Decimal) -> Result<String, Error> {
|
|
|
+ Err(Error::new(ErrorKind::NotFound, "Coinex 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 {
|
|
|
+ // info!("数量 {},方向 {},价格 {},c_id {}", amount, side, price, cid);
|
|
|
+ ts.on_before_send();
|
|
|
+ let result = self_clone.take_order(&cid, &side, price, amount).await;
|
|
|
+ ts.on_after_send();
|
|
|
+
|
|
|
+ match result {
|
|
|
+ Ok(mut result) => {
|
|
|
+ result.trace_stack = ts;
|
|
|
+ // info!("数量 {},方向 {},价格 {},c_id {}", amount, side, price, cid);
|
|
|
+ self_clone.order_sender.send(result).await.unwrap();
|
|
|
+ }
|
|
|
+ Err(error) => {
|
|
|
+ let mut err_order = Order::new();
|
|
|
+ err_order.custom_id = cid.clone();
|
|
|
+ err_order.status = "REMOVE".to_string();
|
|
|
+ // info!("coinex下单error 数量: {},方向: {},价格: {},c_id: {}, err: {}", amount, side, price, cid, error);
|
|
|
+ self_clone.order_sender.send(err_order).await.unwrap();
|
|
|
+ self_clone.error_sender.send(error).await.unwrap();
|
|
|
+ // 触发限频
|
|
|
+ // if error_info.to_string().contains("213:Please don't try too frequently") {
|
|
|
+ // Err(Error::new(ErrorKind::Other, "触发限频, 请调整下单频率"))
|
|
|
+ // }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ 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();
|
|
|
+
|
|
|
+ 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(order) => {
|
|
|
+ if order.status == "REMOVE" {
|
|
|
+ // 由于有时撤单成功ws不推送,所以加入rest
|
|
|
+ self_clone.order_sender.send(order).await.unwrap();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Err(error) => {
|
|
|
+ // info!("撤单失败:{:?}", error.to_string());
|
|
|
+ // 取消失败去查订单。
|
|
|
+ let query_rst = self_clone.get_order_detail(&order_id, &custom_id).await;
|
|
|
+ match query_rst {
|
|
|
+ Ok(order) => {
|
|
|
+ // info!("查单 订单详情:{:?}", order);
|
|
|
+ self_clone.order_sender.send(order).await.unwrap();
|
|
|
+ }
|
|
|
+ Err(_err) => {
|
|
|
+ // warn!("撤单失败,而且查单也失败了,Coinex_io_swap,oid={}, cid={} err={:?}", order_id.clone(), custom_id.clone(), err);
|
|
|
+ // panic!("撤单失败,而且查单也失败了,Coinex_io_swap,oid={}, cid={}。", order_id.clone(), custom_id.clone());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ 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, &custom_id).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_position_item(position: &Value, ct_val: Decimal) -> Position {
|
|
|
+ let position_mode = match position["side"].as_str().unwrap_or("") {
|
|
|
+ "long" => PositionModeEnum::Long,
|
|
|
+ "short" => PositionModeEnum::Short,
|
|
|
+ _ => {
|
|
|
+ error!("coinex_swap:格式化持仓模式错误!\nformat_position_item:position={:?}", position);
|
|
|
+ panic!("coinex_swap:格式化持仓模式错误!\nformat_position_item:position={:?}", position)
|
|
|
+ }
|
|
|
+ };
|
|
|
+ let size = Decimal::from_str(&position["open_interest"].as_str().unwrap()).unwrap();
|
|
|
+ let amount = size * ct_val;
|
|
|
+ Position {
|
|
|
+ symbol: position["market"].as_str().unwrap_or("").parse().unwrap(),
|
|
|
+ margin_level: Decimal::from_str(position["leverage"].as_str().unwrap()).unwrap(),
|
|
|
+ amount,
|
|
|
+ frozen_amount: Decimal::ZERO,
|
|
|
+ price: Decimal::from_str(position["avg_entry_price"].as_str().unwrap()).unwrap(),
|
|
|
+ profit: Decimal::from_str(position["unrealized_pnl"].as_str().unwrap()).unwrap(),
|
|
|
+ position_mode,
|
|
|
+ margin: Decimal::from_str(position["ath_margin_size"].as_str().unwrap()).unwrap(),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+pub fn format_order_item(order: Value, ct_val: Decimal, status: &str) -> Order {
|
|
|
+ // info!("order_format {}", order);
|
|
|
+ let size;
|
|
|
+ match order["amount"].as_str() {
|
|
|
+ Some(val) => {
|
|
|
+ size = Decimal::from_str(val).unwrap()
|
|
|
+ },
|
|
|
+ None => {
|
|
|
+ error!("coinex_swap:格式化订单大小错误!\nformat_order_item:order={:?} status={}", order, status);
|
|
|
+ panic!("coinex_swap:格式化订单大小失败,退出程序!");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // info!("order {}", order);
|
|
|
+ // 通过客户端id查询订单 只能查出未完成订单(没有状态字段)
|
|
|
+ let status = order["status"].as_str().unwrap_or(status);
|
|
|
+ let text = order["client_id"].as_str().unwrap_or("");
|
|
|
+
|
|
|
+ let deal_amount = Decimal::from_str(&order["filled_amount"].as_str().unwrap()).unwrap();
|
|
|
+ let filled_value = Decimal::from_str(&order["filled_value"].as_str().unwrap()).unwrap();
|
|
|
+
|
|
|
+ let amount = size * ct_val;
|
|
|
+ let mut avg_price = Decimal::ZERO;
|
|
|
+ if deal_amount != Decimal::ZERO{
|
|
|
+ avg_price = filled_value/deal_amount;
|
|
|
+ }
|
|
|
+ let custom_status = if status == "filled" || status == "canceled" {
|
|
|
+ "REMOVE".to_string()
|
|
|
+ } else if status == "open" || status == "part_filled" || status == "part_canceled" {
|
|
|
+ "NEW".to_string()
|
|
|
+ } else {
|
|
|
+ error!("coinex_swap:格式化订单状态错误!\nformat_order_item:order={:?}", order);
|
|
|
+ "NULL".to_string()
|
|
|
+ };
|
|
|
+ let rst_order = Order {
|
|
|
+ id: order["order_id"].to_string(),
|
|
|
+ custom_id: text.to_string(),
|
|
|
+ price: Decimal::from_str(order["price"].as_str().unwrap()).unwrap(),
|
|
|
+ amount,
|
|
|
+ deal_amount,
|
|
|
+ avg_price,
|
|
|
+ status: custom_status,
|
|
|
+ order_type: "limit".to_string(),
|
|
|
+ trace_stack: TraceStack::new(0, Instant::now()).on_special("765 trace_stack".to_string()),
|
|
|
+ };
|
|
|
+ return rst_order;
|
|
|
+}
|