|
|
@@ -0,0 +1,381 @@
|
|
|
+use std::collections::{BTreeMap};
|
|
|
+use std::io::{Error, ErrorKind};
|
|
|
+use std::str::FromStr;
|
|
|
+use tokio::sync::mpsc::Sender;
|
|
|
+use async_trait::async_trait;
|
|
|
+use rust_decimal::Decimal;
|
|
|
+use rust_decimal::prelude::ToPrimitive;
|
|
|
+use rust_decimal_macros::dec;
|
|
|
+use serde_json::json;
|
|
|
+use tracing::error;
|
|
|
+use exchanges::kucoin_swap_rest::KucoinSwapRest;
|
|
|
+use global::trace_stack::TraceStack;
|
|
|
+use crate::exchange::ExchangeEnum;
|
|
|
+use crate::{Account, bitget_spot_handle, Market, Order, OrderCommand, Platform, Position, Ticker, utils};
|
|
|
+
|
|
|
+#[allow(dead_code)]
|
|
|
+#[derive(Clone)]
|
|
|
+pub struct BitgetSpot {
|
|
|
+ exchange: ExchangeEnum,
|
|
|
+ symbol: String,
|
|
|
+ is_colo: bool,
|
|
|
+ params: BTreeMap<String, String>,
|
|
|
+ request: KucoinSwapRest,
|
|
|
+ market: Market,
|
|
|
+ order_sender: Sender<Order>,
|
|
|
+ error_sender: Sender<Error>,
|
|
|
+}
|
|
|
+
|
|
|
+impl BitgetSpot {
|
|
|
+ pub async fn new(symbol: String, is_colo: bool, params: BTreeMap<String, String>, order_sender: Sender<Order>, error_sender: Sender<Error>) -> BitgetSpot {
|
|
|
+ let market = Market::new();
|
|
|
+ let mut kucoin_swap = BitgetSpot {
|
|
|
+ exchange: ExchangeEnum::KucoinSwap,
|
|
|
+ symbol: symbol.to_uppercase(),
|
|
|
+ is_colo,
|
|
|
+ params: params.clone(),
|
|
|
+ request: KucoinSwapRest::new(is_colo, params.clone()),
|
|
|
+ market,
|
|
|
+ order_sender,
|
|
|
+ error_sender,
|
|
|
+ };
|
|
|
+ kucoin_swap.market = BitgetSpot::get_market(&mut kucoin_swap).await.unwrap_or(kucoin_swap.market);
|
|
|
+ return kucoin_swap;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[async_trait]
|
|
|
+impl Platform for BitgetSpot {
|
|
|
+ fn clone_box(&self) -> Box<dyn Platform + Send + Sync> { Box::new(self.clone()) }
|
|
|
+
|
|
|
+ fn get_self_exchange(&self) -> ExchangeEnum { ExchangeEnum::BitgetSpot }
|
|
|
+
|
|
|
+ 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() }
|
|
|
+
|
|
|
+ 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_str = &res_data.data;
|
|
|
+ let res_data_json: serde_json::Value = serde_json::from_str(res_data_str).unwrap();
|
|
|
+ let result = res_data_json["serverTime"].as_str().unwrap().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 res_data = self.request.get_account(symbol_array[1].to_string().to_uppercase()).await;
|
|
|
+ if res_data.code == "200" {
|
|
|
+ let res_data_str = &res_data.data;
|
|
|
+ let res_data_json: Vec<serde_json::Value> = serde_json::from_str(res_data_str).unwrap();
|
|
|
+ let account_info = res_data_json[0].clone();
|
|
|
+
|
|
|
+ let available_balance = Decimal::from_str(account_info["available"].as_str().unwrap()).unwrap();
|
|
|
+ let frozen_balance = Decimal::from_str(account_info["frozen"].as_str().unwrap()).unwrap();
|
|
|
+ let balance = available_balance + frozen_balance;
|
|
|
+
|
|
|
+ let result = Account {
|
|
|
+ balance,
|
|
|
+ available_balance,
|
|
|
+ frozen_balance,
|
|
|
+ stocks: dec!(0),
|
|
|
+ available_stocks: dec!(0),
|
|
|
+ frozen_stocks: dec!(0),
|
|
|
+ };
|
|
|
+ Ok(result)
|
|
|
+ } else {
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn get_position(&mut self) -> Result<Vec<Position>, Error> { todo!() }
|
|
|
+
|
|
|
+ async fn get_positions(&mut self) -> Result<Vec<Position>, Error> { todo!() }
|
|
|
+
|
|
|
+ async fn get_ticker(&mut self) -> Result<Ticker, Error> {
|
|
|
+ let symbol_format = utils::format_symbol(self.symbol.clone(), "");
|
|
|
+ let res_data = self.request.get_ticker(symbol_format).await;
|
|
|
+ if res_data.code == "200" {
|
|
|
+ let res_data_str = &res_data.data;
|
|
|
+ let res_data_json: serde_json::Value = serde_json::from_str(res_data_str).unwrap();
|
|
|
+ let time = (Decimal::from_str(&*res_data_json["ts"].as_str().unwrap()).unwrap() / dec!(1000)).floor().to_i64().unwrap();
|
|
|
+ let result = Ticker {
|
|
|
+ time,
|
|
|
+ high: Decimal::from_str(res_data_json["high24h"].as_str().unwrap()).unwrap(),
|
|
|
+ low: Decimal::from_str(res_data_json["low24h"].as_str().unwrap()).unwrap(),
|
|
|
+ sell: Decimal::from_str(res_data_json["askPr"].as_str().unwrap()).unwrap(),
|
|
|
+ buy: Decimal::from_str(res_data_json["bidPr"].as_str().unwrap()).unwrap(),
|
|
|
+ last: Decimal::from_str(res_data_json["lastPr"].as_str().unwrap()).unwrap(),
|
|
|
+ volume: Decimal::from_str(res_data_json["quoteVolume"].as_str().unwrap()).unwrap(),
|
|
|
+ };
|
|
|
+ Ok(result)
|
|
|
+ } else {
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn get_ticker_symbol(&mut self, symbol: String) -> Result<Ticker, Error> {
|
|
|
+ todo!()
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn get_market(&mut self) -> Result<Market, Error> {
|
|
|
+ let symbol_format = utils::format_symbol(self.symbol.clone(), "");
|
|
|
+ let res_data = self.request.get_market_details().await;
|
|
|
+ if res_data.code == "200" {
|
|
|
+ let res_data_str = &res_data.data;
|
|
|
+ let res_data_json: Vec<serde_json::Value> = serde_json::from_str(res_data_str).unwrap();
|
|
|
+ let market_info = res_data_json.iter().find(|item| item["symbol"].as_str().unwrap() == symbol_format);
|
|
|
+ match market_info {
|
|
|
+ None => {
|
|
|
+ error!("Gate:获取Market信息错误!\nget_market:market_info={:?}", market_info);
|
|
|
+ panic!("Gate:获取Market信息错误!\nget_market:market_info={:?}", market_info)
|
|
|
+ }
|
|
|
+ Some(value) => {
|
|
|
+ let base_asset = value["baseCoin"].as_str().unwrap().to_string();
|
|
|
+ let quote_asset = value["quoteCoin"].as_str().unwrap().to_string();
|
|
|
+ let price_precision = Decimal::from_str(value["pricePrecision"].as_str().unwrap()).unwrap();
|
|
|
+ let amount_precision = Decimal::from_str(value["quantityPrecision"].as_str().unwrap()).unwrap();
|
|
|
+ let tick_size = Decimal::from_str(&format!("0.{}", "0".repeat(usize::try_from(price_precision).unwrap()))).unwrap();
|
|
|
+ let amount_size = Decimal::from_str(&format!("0.{}", "0".repeat(usize::try_from(amount_precision).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: Decimal::from_str(&value["minTradeAmount"].as_str().unwrap()).unwrap(),
|
|
|
+ max_qty: Decimal::from_str(&value["maxTradeAmount"].as_str().unwrap()).unwrap(),
|
|
|
+ min_notional: Default::default(),
|
|
|
+ max_notional: Default::default(),
|
|
|
+ ct_val: Default::default(),
|
|
|
+ };
|
|
|
+ Ok(result)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn get_market_symbol(&mut self, symbol: String) -> Result<Market, Error> {
|
|
|
+ todo!()
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn get_order_detail(&mut self, order_id: &str, custom_id: &str) -> Result<Order, Error> {
|
|
|
+ let amount_size = self.market.amount_size;
|
|
|
+ let id = if order_id.eq("") { custom_id.to_string() } else { order_id.to_string() };
|
|
|
+ let res_data = self.request.get_orders(id, "".to_string()).await;
|
|
|
+ if res_data.code == "200" {
|
|
|
+ let res_data_str = &res_data.data;
|
|
|
+ let res_data_json: Vec<serde_json::Value> = serde_json::from_str(res_data_str).unwrap();
|
|
|
+ let order_info = res_data_json[0].clone();
|
|
|
+ let result = bitget_spot_handle::format_order_item(order_info, amount_size);
|
|
|
+ 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_format = utils::format_symbol(self.symbol.clone(), "");
|
|
|
+ let amount_size = self.market.amount_size;
|
|
|
+ let res_data = self.request.get_orders(status.to_string(), symbol_format.to_string()).await;
|
|
|
+ if res_data.code == "200" {
|
|
|
+ let res_data_str = &res_data.data;
|
|
|
+ let res_data_json: Vec<serde_json::Value> = serde_json::from_str(res_data_str).unwrap();
|
|
|
+ let order_info: Vec<_> = res_data_json.iter().filter(|item| item["symbol"].as_str().unwrap_or("") == symbol_format.to_lowercase()).collect();
|
|
|
+ let result = order_info.iter().map(|&item| bitget_spot_handle::format_order_item(item.clone(), amount_size)).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_format = utils::format_symbol(self.symbol.clone(), "");
|
|
|
+ let mut params = json!({
|
|
|
+ "symbol": symbol_format.to_string(),
|
|
|
+ "clientOid": custom_id,
|
|
|
+ "orderType": "limit",
|
|
|
+ "price": price.to_string(),
|
|
|
+ "force": "gtc"
|
|
|
+ });
|
|
|
+ let size = amount;
|
|
|
+ match origin_side {
|
|
|
+ "kd" => {
|
|
|
+ params["side"] = json!("buy");
|
|
|
+ params["size"] = json!(size);
|
|
|
+ }
|
|
|
+ "pd" => {
|
|
|
+ params["side"] = json!(true);
|
|
|
+ params["size"] = json!(size);
|
|
|
+ }
|
|
|
+ "kk" => {
|
|
|
+ params["side"] = json!(false);
|
|
|
+ params["size"] = json!(size);
|
|
|
+ }
|
|
|
+ "pk" => {
|
|
|
+ params["side"] = json!(true);
|
|
|
+ params["size"] = json!(size);
|
|
|
+ }
|
|
|
+ _ => { error!("下单参数错误"); }
|
|
|
+ };
|
|
|
+ let res_data = self.request.swap_order(params).await;
|
|
|
+ if res_data.code == "200" {
|
|
|
+ let res_data_str = &res_data.data;
|
|
|
+ let res_data_json: serde_json::Value = serde_json::from_str(res_data_str).unwrap();
|
|
|
+ let result = Order {
|
|
|
+ id: res_data_json["orderId"].as_str().unwrap().to_string(),
|
|
|
+ custom_id: res_data_json["clientOid"].as_str().unwrap().to_string(),
|
|
|
+ price: dec!(0),
|
|
|
+ amount: dec!(0),
|
|
|
+ deal_amount: dec!(0),
|
|
|
+ avg_price: dec!(0),
|
|
|
+ status: "NEW".to_string(),
|
|
|
+ order_type: "".to_string(),
|
|
|
+ trace_stack: TraceStack::default().on_special("245 bitget_spot".to_string()),
|
|
|
+ };
|
|
|
+ Ok(result)
|
|
|
+ } else {
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn take_order_symbol(&mut self, symbol: String, custom_id: &str, origin_side: &str, price: Decimal, amount: Decimal) -> Result<Order, Error> {
|
|
|
+ let symbol_format = utils::format_symbol(symbol.clone(), "");
|
|
|
+ let mut params = json!({
|
|
|
+ "symbol": symbol_format.to_string(),
|
|
|
+ "clientOid": custom_id,
|
|
|
+ "orderType": "limit",
|
|
|
+ "price": price.to_string(),
|
|
|
+ "force": "gtc"
|
|
|
+ });
|
|
|
+ let size = amount;
|
|
|
+ match origin_side {
|
|
|
+ "kd" => {
|
|
|
+ params["side"] = json!("buy");
|
|
|
+ params["size"] = json!(size);
|
|
|
+ }
|
|
|
+ "pd" => {
|
|
|
+ params["side"] = json!(true);
|
|
|
+ params["size"] = json!(size);
|
|
|
+ }
|
|
|
+ "kk" => {
|
|
|
+ params["side"] = json!(false);
|
|
|
+ params["size"] = json!(size);
|
|
|
+ }
|
|
|
+ "pk" => {
|
|
|
+ params["side"] = json!(true);
|
|
|
+ params["size"] = json!(size);
|
|
|
+ }
|
|
|
+ _ => { error!("下单参数错误"); }
|
|
|
+ };
|
|
|
+ let res_data = self.request.swap_order(params).await;
|
|
|
+ if res_data.code == "200" {
|
|
|
+ let res_data_str = &res_data.data;
|
|
|
+ let res_data_json: serde_json::Value = serde_json::from_str(res_data_str).unwrap();
|
|
|
+ let result = Order {
|
|
|
+ id: res_data_json["orderId"].as_str().unwrap().to_string(),
|
|
|
+ custom_id: res_data_json["clientOid"].as_str().unwrap().to_string(),
|
|
|
+ price: dec!(0),
|
|
|
+ amount: dec!(0),
|
|
|
+ deal_amount: dec!(0),
|
|
|
+ avg_price: dec!(0),
|
|
|
+ status: "NEW".to_string(),
|
|
|
+ order_type: "".to_string(),
|
|
|
+ trace_stack: TraceStack::default().on_special("295 bitget_spot".to_string()),
|
|
|
+ };
|
|
|
+ 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_format = utils::format_symbol(self.symbol.clone(), "");
|
|
|
+ let id = if order_id.eq("") { custom_id.to_string() } else { order_id.to_string() };
|
|
|
+ let res_data = self.request.cancel_order(order_id.to_string(), custom_id.to_string()).await;
|
|
|
+ if res_data.code == "200" {
|
|
|
+ let res_data_str = &res_data.data;
|
|
|
+ let res_data_json: serde_json::Value = serde_json::from_str(res_data_str).unwrap();
|
|
|
+ let result = Order {
|
|
|
+ id: res_data_json["orderId"].as_str().unwrap().to_string(),
|
|
|
+ custom_id: res_data_json["clientOid"].as_str().unwrap().to_string(),
|
|
|
+ price: dec!(0),
|
|
|
+ amount: dec!(0),
|
|
|
+ deal_amount: dec!(0),
|
|
|
+ avg_price: dec!(0),
|
|
|
+ status: "REMOVE".to_string(),
|
|
|
+ order_type: "".to_string(),
|
|
|
+ trace_stack: TraceStack::default().on_special("319 bitget_spot".to_string()),
|
|
|
+ };
|
|
|
+ Ok(result)
|
|
|
+ } else {
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn cancel_orders(&mut self) -> Result<Vec<Order>, Error> {
|
|
|
+ let symbol_format = utils::format_symbol(self.symbol.clone(), "");
|
|
|
+ let res_data = self.request.cancel_orders(symbol_format.clone()).await;
|
|
|
+ if res_data.code == "200" {
|
|
|
+ let res_data_str = &res_data.data;
|
|
|
+ let res_data_json: serde_json::Value = serde_json::from_str(res_data_str).unwrap();
|
|
|
+ let order_info_list: Vec<serde_json::Value> = res_data_json["successList"].as_array().unwrap().clone();
|
|
|
+ let result = order_info_list.iter().map(|item|
|
|
|
+ Order {
|
|
|
+ id: item["orderId"].as_str().unwrap().to_string(),
|
|
|
+ custom_id: item["clientOid"].as_str().unwrap().to_string(),
|
|
|
+ price: dec!(0),
|
|
|
+ amount: dec!(0),
|
|
|
+ deal_amount: dec!(0),
|
|
|
+ avg_price: dec!(0),
|
|
|
+ status: "REMOVE".to_string(),
|
|
|
+ order_type: "".to_string(),
|
|
|
+ trace_stack: TraceStack::default().on_special("344 bitget_spot".to_string()),
|
|
|
+ }
|
|
|
+ ).collect();
|
|
|
+ Ok(result)
|
|
|
+ } else {
|
|
|
+ Err(Error::new(ErrorKind::Other, res_data.to_string()))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn set_dual_mode(&mut self, _coin: &str, _is_dual_mode: bool) -> Result<String, Error> {
|
|
|
+ todo!()
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn set_dual_leverage(&mut self, _leverage: &str) -> Result<String, Error> {
|
|
|
+ todo!()
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn set_auto_deposit_status(&mut self, _status: bool) -> Result<String, Error> {
|
|
|
+ todo!()
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn wallet_transfers(&mut self, _coin: &str, _from: &str, _to: &str, _amount: Decimal) -> Result<String, Error> {
|
|
|
+ todo!()
|
|
|
+ }
|
|
|
+
|
|
|
+ async fn command_order(&mut self, _order_command: OrderCommand, _trace_stack: TraceStack) {
|
|
|
+ todo!()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|