|
|
@@ -1,55 +1,276 @@
|
|
|
+use std::str::FromStr;
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
use rust_decimal::Decimal;
|
|
|
+use rust_decimal::prelude::ToPrimitive;
|
|
|
use rust_decimal_macros::dec;
|
|
|
use tracing::error;
|
|
|
use crate::{Account, Market, Order, Position, PositionModeEnum, Ticker, utils};
|
|
|
|
|
|
-/// Binance交易所账户信息请求数据结构
|
|
|
-/// - 接口`"/fapi/v2/balance"`
|
|
|
-///
|
|
|
-/// struct SwapAccount
|
|
|
-/// - `account_alias`: String, 账户唯一识别码
|
|
|
-/// - `asset`: String, 资产
|
|
|
-/// - `balance`: Decimal, 总余额
|
|
|
-/// - `cross_wallet_balance`: Decimal, 全仓余额
|
|
|
-/// - `cross_un_pnl`: Decimal, 全仓持仓未实现盈亏
|
|
|
-/// - `available_balance`: Decimal, 下单可用余额
|
|
|
-/// - `max_withdraw_amount`: Decimal, 最大可转出余额
|
|
|
-/// - `margin_available`: bool, 是否可用作联合保证金
|
|
|
-/// - `update_time`: i64,时间戳
|
|
|
+/// Bitget交易所账户信息请求数据结构
|
|
|
+/// - 接口`"/api/v2/spot/account/assets"`
|
|
|
+///
|
|
|
+/// struct SpotAccount
|
|
|
+/// - `coin`: String, 币种名称
|
|
|
+/// - `available`: String, 可用资产
|
|
|
+/// - `frozen`: Decimal, 冻结资产数量
|
|
|
+/// - `locked`: Decimal, 锁仓资产数量
|
|
|
+/// - `limit_available`: Decimal, 受限可用
|
|
|
+/// - `u_time`: Decimal, 更新时间
|
|
|
#[derive(Debug, Deserialize, Serialize)]
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
struct SpotAccount {
|
|
|
coin: String,
|
|
|
- available: String,
|
|
|
+ available: Decimal,
|
|
|
frozen: Decimal,
|
|
|
locked: Decimal,
|
|
|
limit_available: Decimal,
|
|
|
u_time: String,
|
|
|
}
|
|
|
+
|
|
|
/// 处理合约本位货币账户信息
|
|
|
///
|
|
|
-/// pub(crate) fn handle_swap_account(data: String, symbol: String) -> Account;
|
|
|
+/// pub(crate) fn handle_spot_account(data: String, symbol: String) -> Account;
|
|
|
///
|
|
|
/// - `data`:String, 交易所返回账户信息,JsonString
|
|
|
/// - `symbol`:String, 交易币对
|
|
|
-pub(crate) fn handle_swap_account(data: String, symbol: String) -> Account {
|
|
|
- let symbol_array: Vec<&str> = symbol.split("_").collect();
|
|
|
- let balance_info_list: Vec<SwapAccount> = serde_json::from_str(&data).unwrap();
|
|
|
- let balance_info = balance_info_list.iter().find(|item| item.asset == symbol_array[1]);
|
|
|
- match balance_info {
|
|
|
+pub(crate) fn handle_spot_account(data: String) -> Vec<Account> {
|
|
|
+ let balance_info_list: Vec<SpotAccount> = serde_json::from_str(&data).unwrap();
|
|
|
+ balance_info_list.iter().map(|item| {
|
|
|
+ Account {
|
|
|
+ coin: item.coin.to_uppercase(),
|
|
|
+ balance: item.available + item.frozen,
|
|
|
+ available_balance: item.available,
|
|
|
+ frozen_balance: item.frozen,
|
|
|
+ stocks: dec!(0),
|
|
|
+ available_stocks: dec!(0),
|
|
|
+ frozen_stocks: dec!(0),
|
|
|
+ }
|
|
|
+ }).collect()
|
|
|
+}
|
|
|
+
|
|
|
+/// Bitget交易所行情信息请求数据结构
|
|
|
+/// - 接口`"/api/v2/spot/market/tickers"`
|
|
|
+///
|
|
|
+/// struct SpotTicker
|
|
|
+/// - `symbol`: String, 交易对名称
|
|
|
+/// - `high24h`: Decimal, 24小时最高价
|
|
|
+/// - `open`: Decimal, 24小时开盘价
|
|
|
+/// - `low24h`: Decimal, 最新成交价
|
|
|
+/// - `last_pr`: Decimal, 24小时最低价
|
|
|
+/// - `quote_volume`: Decimal, 计价币成交额
|
|
|
+/// - `base_volume`: Decimal, 基础币成交额
|
|
|
+/// - `usdt_volume`: Decimal, USDT成交额
|
|
|
+/// - `bid_pr`: Decimal, 买一价
|
|
|
+/// - `ask_pr`: Decimal, 卖一价
|
|
|
+/// - `bid_sz`: Decimal, 买一量
|
|
|
+/// - `ask_sz`: Decimal, 卖一量
|
|
|
+/// - `open_utc`: Decimal, 零时区 开盘价
|
|
|
+/// - `ts`: i64, 当前时间。Unix毫秒时间戳,例如1690196141868
|
|
|
+/// - `change_utc24h`: Decimal, UTC0时涨跌幅, 0.01表示1%
|
|
|
+/// - `change24h`: Decimal, UTC0时涨跌幅, 0.01表示1%
|
|
|
+#[derive(Debug, Deserialize, Serialize)]
|
|
|
+#[serde(rename_all = "camelCase")]
|
|
|
+struct SpotTicker {
|
|
|
+ symbol: String,
|
|
|
+ high24h: Decimal,
|
|
|
+ open: Decimal,
|
|
|
+ low24h: Decimal,
|
|
|
+ last_pr: Decimal,
|
|
|
+ quote_volume: Decimal,
|
|
|
+ base_volume: Decimal,
|
|
|
+ usdt_volume: Decimal,
|
|
|
+ bid_pr: Decimal,
|
|
|
+ ask_pr: Decimal,
|
|
|
+ bid_sz: Decimal,
|
|
|
+ ask_sz: Decimal,
|
|
|
+ open_utc: Decimal,
|
|
|
+ ts: String,
|
|
|
+ change_utc24h: Decimal,
|
|
|
+ change24h: Decimal,
|
|
|
+}
|
|
|
+
|
|
|
+/// 处理合约行情信息
|
|
|
+///
|
|
|
+/// pub(crate) fn handle_swap_ticker(data: String) -> Ticker;
|
|
|
+///
|
|
|
+/// - `data`:String, 交易所返回账户信息,JsonString
|
|
|
+pub(crate) fn handle_spot_ticker(data: String, symbol: String) -> Ticker {
|
|
|
+ let format_symbol = utils::format_symbol(symbol, "");
|
|
|
+ let ticker_info_list: Vec<SpotTicker> = serde_json::from_str(&data).unwrap();
|
|
|
+ let ticker_info = ticker_info_list.iter().find(|&item| item.symbol == format_symbol);
|
|
|
+ match ticker_info {
|
|
|
+ None => {
|
|
|
+ error!("Bitget:获取Ticker信息错误!\nhandle_spot_ticker:data={:?}", data);
|
|
|
+ panic!("Bitget:获取Ticker信息错误!\nhandle_spot_ticker:data={:?}", data)
|
|
|
+ }
|
|
|
+ Some(value) => {
|
|
|
+ Ticker {
|
|
|
+ time: value.ts.parse().unwrap(),
|
|
|
+ high: value.high24h,
|
|
|
+ low: value.low24h,
|
|
|
+ sell: value.ask_pr,
|
|
|
+ buy: value.bid_pr,
|
|
|
+ last: value.last_pr,
|
|
|
+ volume: dec!(-1),
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/// Bitget交易所行情信息请求数据结构
|
|
|
+/// - 接口`"/api/v2/spot/public/symbols"`
|
|
|
+///
|
|
|
+/// struct SpotMarket
|
|
|
+/// - `symbol`: String, 交易对名称
|
|
|
+/// - `base_coin`: String, 基础币
|
|
|
+/// - `quote_coin`: String, 计价货币
|
|
|
+/// - `min_trade_amount`: Decimal, 最小交易数量
|
|
|
+/// - `max_trade_amount`: Decimal, 最大交易数量
|
|
|
+/// - `taker_fee_rate`: Decimal, 默认吃单手续费率,可被个人交易手续费率覆盖
|
|
|
+/// - `maker_fee_rate`: Decimal, 默认挂单手续费率,可被个人交易手续费率覆盖
|
|
|
+/// - `price_precision`: Decimal, 价格精度
|
|
|
+/// - `quantity_precision`: Decimal, 数量精度
|
|
|
+/// - `quote_precision`: Decimal, 右币精度
|
|
|
+/// - `status`: String, 上架状态
|
|
|
+/// - `min_trade_usdt`: Decimal, 最小USDT交易额
|
|
|
+/// - `buy_limit_price_ratio`: Decimal, 买入与现价的价差百分比
|
|
|
+/// - `sell_limit_price_ratio`: Decimal, 卖出与现价的价差百分比
|
|
|
+#[derive(Debug, Deserialize, Serialize)]
|
|
|
+#[serde(rename_all = "camelCase")]
|
|
|
+struct SpotMarket {
|
|
|
+ symbol: String,
|
|
|
+ base_coin: String,
|
|
|
+ quote_coin: String,
|
|
|
+ min_trade_amount: Decimal,
|
|
|
+ max_trade_amount: Decimal,
|
|
|
+ taker_fee_rate: Decimal,
|
|
|
+ maker_fee_rate: Decimal,
|
|
|
+ price_precision: Decimal,
|
|
|
+ quantity_precision: Decimal,
|
|
|
+ quote_precision: Decimal,
|
|
|
+ status: String,
|
|
|
+ min_trade_u_s_d_t: Decimal,
|
|
|
+ buy_limit_price_ratio: Decimal,
|
|
|
+ sell_limit_price_ratio: Decimal,
|
|
|
+}
|
|
|
+
|
|
|
+/// 处理合约行情信息
|
|
|
+///
|
|
|
+/// pub(crate) fn handle_swap_market(data: String, symbol: String) -> Market;
|
|
|
+///
|
|
|
+/// - `data`:String, 交易所返回账户信息,JsonString
|
|
|
+/// - `symbol`:String, 交易币对
|
|
|
+pub(crate) fn handle_swap_market(data: String, symbol: String) -> Market {
|
|
|
+ let format_symbol = utils::format_symbol(symbol.clone(), "");
|
|
|
+ let market_info_list: Vec<SpotMarket> = serde_json::from_str(&data).unwrap();
|
|
|
+ let market_info = market_info_list.iter().find(|&item| item.symbol == format_symbol);
|
|
|
+ match market_info {
|
|
|
None => {
|
|
|
- panic!("格式化Binance账号信息错误,未找到该本位货币!\nhandle_account: balance_info={:?}", balance_info)
|
|
|
+ error!("Bitget:获取Market信息错误!\nhandle_spot_market:data={:?}", data);
|
|
|
+ panic!("Bitget:获取Market信息错误!\nhandle_spot_market:data={:?}", data)
|
|
|
}
|
|
|
Some(value) => {
|
|
|
- Account {
|
|
|
- balance: value.balance,
|
|
|
- available_balance: value.available_balance,
|
|
|
- frozen_balance: value.balance - value.available_balance,
|
|
|
- stocks: dec!(0),
|
|
|
- available_stocks: dec!(0),
|
|
|
- frozen_stocks: dec!(0),
|
|
|
+ let tick_size = if value.price_precision > dec!(0) {
|
|
|
+ Decimal::from_str(&format!("0.{}1", "0".repeat(usize::try_from(value.price_precision - dec!(1)).unwrap()))).unwrap()
|
|
|
+ } else {
|
|
|
+ Decimal::ONE
|
|
|
+ };
|
|
|
+ let amount_size = if value.quantity_precision > dec!(0) {
|
|
|
+ Decimal::from_str(&format!("0.{}1", "0".repeat(usize::try_from(value.quantity_precision - dec!(1)).unwrap()))).unwrap()
|
|
|
+ } else {
|
|
|
+ Decimal::ONE
|
|
|
+ };
|
|
|
+ Market {
|
|
|
+ symbol: symbol.clone(),
|
|
|
+ base_asset: value.base_coin.clone(),
|
|
|
+ quote_asset: value.quote_coin.clone(),
|
|
|
+ tick_size,
|
|
|
+ amount_size,
|
|
|
+ price_precision: value.price_precision,
|
|
|
+ amount_precision: value.quantity_precision,
|
|
|
+ min_qty: value.min_trade_amount,
|
|
|
+ max_qty: value.max_trade_amount,
|
|
|
+ min_notional: value.min_trade_amount,
|
|
|
+ max_notional: value.max_trade_amount,
|
|
|
+ ct_val: Decimal::ONE,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+}
|
|
|
+
|
|
|
+/// Bitget交易所行情信息请求数据结构
|
|
|
+/// - 接口`"/api/v2/spot/trade/orderInfo"`
|
|
|
+///
|
|
|
+/// struct SwapOrder
|
|
|
+/// `id`: i128, 合约订单 ID
|
|
|
+/// `create_time`: i64, 订单创建时间
|
|
|
+/// `finish_time`: Option<i64>, 订单结束时间,未结束订单无此字段返回
|
|
|
+/// `finish_as`: String, 结束方式(filled: 完全成交、cancelled: 用户撤销、liquidated: 强制平仓撤销、ioc: 未立即完全成交,因为tif设置为ioc、auto_deleveraged: 自动减仓撤销、reduce_only: 增持仓位撤销,因为设置reduce_only或平仓、position_closed: 因为仓位平掉了,所以挂单被撤掉、reduce_out: 只减仓被排除的不容易成交的挂单、stp: 订单发生自成交限制而被撤销)
|
|
|
+/// `status`: String, 订单状态(open: 等待处理、finished: 已结束的订单)
|
|
|
+/// `contract`: String, 合约标识
|
|
|
+/// `size`: Decimal, 必选。交易数量,正数为买入,负数为卖出。平仓委托则设置为0
|
|
|
+/// `iceberg`: Decimal, 冰山委托显示数量。0为完全不隐藏。注意,隐藏部分成交按照taker收取手续费
|
|
|
+/// `price`:Decimal, 委托价。价格为0并且tif为ioc,代表市价委托
|
|
|
+/// `close`: bool, 设置为 true 的时候执行平仓操作,并且size应设置为0
|
|
|
+/// `is_close`: bool, 是否为平仓委托。对应请求中的close
|
|
|
+/// `reduce_only`: bool, 设置为 true 的时候,为只减仓委托
|
|
|
+/// `is_reduce_only`: bool, 是否为只减仓委托。对应请求中的reduce_only
|
|
|
+/// `is_liq`: bool, 是否为强制平仓委托
|
|
|
+/// `tif`: String, Time in force 策略,市价单当前只支持 ioc 模式(gtc: GoodTillCancelled、ioc: ImmediateOrCancelled,立即成交或者取消,只吃单不挂单、poc: PendingOrCancelled,被动委托,只挂单不吃单、fok: FillOrKill, 完全成交,或者完全取消)
|
|
|
+/// `left`: Decimal, 未成交数量
|
|
|
+/// `fill_price`: Decimal, 成交价
|
|
|
+/// `text`: String, 订单自定义信息,用户可以用该字段设置自定义 ID,用户自定义字段必须满足以下条件:(1. 必须以 t- 开头、2. 不计算 t- ,长度不能超过 28 字节、3. 输入内容只能包含数字、字母、下划线(_)、中划线(-) 或者点(.))
|
|
|
+/// `tkfr`: Decimal, 吃单费率
|
|
|
+/// `mkfr`: Decimal, 做单费率
|
|
|
+/// `auto_size`: String,双仓模式下用于设置平仓的方向,close_long 平多头, close_short 平空头,需要同时设置 size 为 0
|
|
|
+/// `stp_id`: i64, 订单所属的STP用户组id,同一个STP用户组内用户之间的订单不允许发生自成交。
|
|
|
+/// `stp_act`: String, Self-Trading Prevention Action,用户可以用该字段设置自定义限制自成交策略。
|
|
|
+/// `amend_text`: String 用户修改订单时备注的信息
|
|
|
+#[derive(Debug, Deserialize, Serialize)]
|
|
|
+struct SwapOrder {
|
|
|
+ user_id: String,
|
|
|
+ symbol: String,
|
|
|
+ order_id: String,
|
|
|
+ client_oid: String,
|
|
|
+ price: Decimal,
|
|
|
+ size: Decimal,
|
|
|
+ order_type: String,
|
|
|
+ side: String,
|
|
|
+ status: String,
|
|
|
+ price_avg: Decimal,
|
|
|
+ base_volume: Decimal,
|
|
|
+ quote_volume: Decimal,
|
|
|
+ enter_point_source: String,
|
|
|
+ fee_detail: String,
|
|
|
+ order_source: String,
|
|
|
+ c_time: i64,
|
|
|
+ u_time: i64,
|
|
|
+}
|
|
|
+
|
|
|
+/// 处理合约订单信息
|
|
|
+///
|
|
|
+/// pub(crate) fn handle_swap_order(data: String, amount_size: Decimal) -> Order;
|
|
|
+///
|
|
|
+/// - `data`:String, 交易所返回账户信息,JsonString
|
|
|
+/// - `amount_size`:Decimal, 张数价值
|
|
|
+pub(crate) fn handle_swap_order(data: String, amount_size: Decimal) -> Order {
|
|
|
+ let order_info: SwapOrder = serde_json::from_str(&data).unwrap();
|
|
|
+ let custom_status = match order_info.status.as_str() {
|
|
|
+ "finished" => { "REMOVE".to_string() }
|
|
|
+ "open" => { "NEW".to_string() }
|
|
|
+ _ => {
|
|
|
+ error!("Gate:格式化订单状态错误!\nhandle_swap_order:status={:?}", order_info.status);
|
|
|
+ panic!("Gate:格式化订单状态错误!\nhandle_swap_order:status={:?}", order_info.status)
|
|
|
+ }
|
|
|
+ };
|
|
|
+ Order {
|
|
|
+ id: order_info.id.to_string(),
|
|
|
+ custom_id: order_info.text.replace("t-my-custom-id_", ""),
|
|
|
+ price: order_info.price,
|
|
|
+ amount: order_info.size * amount_size,
|
|
|
+ deal_amount: (order_info.size - order_info.left) * amount_size,
|
|
|
+ avg_price: order_info.fill_price,
|
|
|
+ status: custom_status,
|
|
|
+ order_type: "limit".to_string(),
|
|
|
+ }
|
|
|
}
|