|
|
@@ -10,26 +10,26 @@ use rust_decimal::Decimal;
|
|
|
use rust_decimal::prelude::{ToPrimitive};
|
|
|
use rust_decimal_macros::dec;
|
|
|
use tokio::spawn;
|
|
|
-use tokio::sync::mpsc::{Receiver, Sender};
|
|
|
-use tokio::sync::Mutex;
|
|
|
+use tokio::sync::mpsc::{Sender};
|
|
|
+use tokio::sync::{Mutex};
|
|
|
use tokio::task::JoinHandle;
|
|
|
use tokio::time::sleep;
|
|
|
use tracing::{debug, error, info, warn};
|
|
|
use global::params::Params;
|
|
|
use global::public_params::{ASK_PRICE_INDEX, BID_PRICE_INDEX, LENGTH};
|
|
|
+use global::trace_stack::TraceStack;
|
|
|
use standard::{Account, Market, Order, OrderCommand, Platform, Position, PositionModeEnum, SpecialTicker, Ticker};
|
|
|
use standard::exchange::{Exchange};
|
|
|
-use standard::exchange::ExchangeEnum::{BinanceSpot, BinanceSwap, GateSpot, GateSwap, KucoinSwap};
|
|
|
-
|
|
|
-use crate::model::{LocalPosition, OrderInfo, TraderMsg};
|
|
|
+use standard::exchange::ExchangeEnum::{BinanceSpot, BinanceSwap, BitgetSpot, GateSpot, GateSwap, KucoinSwap};
|
|
|
|
|
|
+use crate::model::{LocalPosition, OrderInfo, TokenParam, TraderMsg};
|
|
|
use crate::predictor::Predictor;
|
|
|
use crate::strategy::Strategy;
|
|
|
-use crate::utils::{clip};
|
|
|
+use crate::utils::clip;
|
|
|
|
|
|
|
|
|
pub struct Quant {
|
|
|
- pub params: Params,
|
|
|
+ pub params: Params,
|
|
|
// 启动时间
|
|
|
pub start_time: i64,
|
|
|
// 币对
|
|
|
@@ -38,8 +38,6 @@ pub struct Quant {
|
|
|
pub base: String,
|
|
|
// 报价货币
|
|
|
pub quote: String,
|
|
|
- // 现货底仓
|
|
|
- pub hold_coin: Decimal,
|
|
|
//
|
|
|
pub strategy: Strategy,
|
|
|
// 本地挂单表
|
|
|
@@ -66,7 +64,7 @@ pub struct Quant {
|
|
|
pub local_buy_value: Decimal,
|
|
|
pub local_sell_value: Decimal,
|
|
|
pub local_cancel_log: HashMap<String, i64>,
|
|
|
- pub interval: Decimal,
|
|
|
+ pub interval: u64,
|
|
|
pub exchange: String,
|
|
|
pub trade_msg: TraderMsg,
|
|
|
pub exit_msg: String,
|
|
|
@@ -102,6 +100,7 @@ pub struct Quant {
|
|
|
pub local_depths: HashMap<String, Vec<Decimal>>,
|
|
|
pub is_update: HashMap<String, bool>,
|
|
|
pub running: Arc<AtomicBool>,
|
|
|
+ pub hold_coin: Decimal,
|
|
|
}
|
|
|
|
|
|
impl Quant {
|
|
|
@@ -114,7 +113,8 @@ impl Quant {
|
|
|
symbol: symbol.clone(),
|
|
|
base: pairs[0].to_string(),
|
|
|
quote: pairs[1].to_string(),
|
|
|
- hold_coin: clip(params.hold_coin, dec!(0.0), dec!(10000.0)),
|
|
|
+ // 现货底仓
|
|
|
+ hold_coin: clip(params.hold_coin, Decimal::ZERO, Decimal::ONE_HUNDRED*Decimal::ONE_HUNDRED),
|
|
|
strategy: Strategy::new(¶ms, true),
|
|
|
local_orders: Default::default(),
|
|
|
local_orders_backup: Default::default(),
|
|
|
@@ -174,7 +174,6 @@ impl Quant {
|
|
|
base_asset: "".to_string(),
|
|
|
quote_asset: "".to_string(),
|
|
|
tick_size: Default::default(),
|
|
|
- amount_size: Default::default(),
|
|
|
price_precision: Default::default(),
|
|
|
amount_precision: Default::default(),
|
|
|
min_qty: Default::default(),
|
|
|
@@ -182,25 +181,30 @@ impl Quant {
|
|
|
min_notional: Default::default(),
|
|
|
max_notional: Default::default(),
|
|
|
ct_val: Default::default(),
|
|
|
+ amount_size: Default::default(),
|
|
|
},
|
|
|
platform_rest: match exchange.as_str() {
|
|
|
"kucoin_usdt_swap" => {
|
|
|
- Exchange::new(KucoinSwap, symbol, false, exchange_params, order_sender, error_sender).await
|
|
|
+ Exchange::new(KucoinSwap, symbol, params.colo != 0i8, exchange_params, order_sender, error_sender).await
|
|
|
},
|
|
|
"gate_usdt_swap" => {
|
|
|
- Exchange::new(GateSwap, symbol, false, exchange_params, order_sender, error_sender).await
|
|
|
+ Exchange::new(GateSwap, symbol, params.colo != 0i8, exchange_params, order_sender, error_sender).await
|
|
|
},
|
|
|
"gate_usdt_spot" => {
|
|
|
- Exchange::new(GateSpot, symbol, false, exchange_params, order_sender, error_sender).await
|
|
|
+ Exchange::new(GateSpot, symbol, params.colo != 0i8, exchange_params, order_sender, error_sender).await
|
|
|
},
|
|
|
"binance_usdt_swap" => {
|
|
|
- Exchange::new(BinanceSwap, symbol, false, exchange_params, order_sender, error_sender).await
|
|
|
+ Exchange::new(BinanceSwap, symbol, params.colo != 0i8, exchange_params, order_sender, error_sender).await
|
|
|
+ },
|
|
|
+ "binance_spot" => {
|
|
|
+ Exchange::new(BinanceSpot, symbol, params.colo != 0i8, exchange_params, order_sender, error_sender).await
|
|
|
},
|
|
|
- "binance_usdt_spot" => {
|
|
|
- Exchange::new(BinanceSpot, symbol, false, exchange_params, order_sender, error_sender).await
|
|
|
+ "bitget_spot" => {
|
|
|
+ Exchange::new(BitgetSpot, symbol, params.colo != 0i8, exchange_params, order_sender, error_sender).await
|
|
|
}
|
|
|
_ => {
|
|
|
- panic!("201未找到对应的交易所rest枚举!")
|
|
|
+ error!("203未找到对应的交易所rest枚举!");
|
|
|
+ panic!("203未找到对应的交易所rest枚举!");
|
|
|
}
|
|
|
},
|
|
|
max_buy_min_sell_cache: Default::default(),
|
|
|
@@ -263,55 +267,15 @@ impl Quant {
|
|
|
return quant_obj;
|
|
|
}
|
|
|
|
|
|
- pub async fn handle_signals(quant_arc: Arc<Mutex<Quant>>, mut rx: Receiver<Order>) {
|
|
|
- spawn(async move{
|
|
|
- loop {
|
|
|
- sleep(Duration::from_millis(1)).await;
|
|
|
- match rx.try_recv() {
|
|
|
- Ok(val)=>{
|
|
|
- // 只处理这两种订单回执
|
|
|
- if ["NEW", "REMOVE"].contains(&val.status.as_str()){
|
|
|
- let mut local_order_info = OrderInfo{
|
|
|
- symbol: "".to_string(),
|
|
|
- amount: Default::default(),
|
|
|
- side: "".to_string(),
|
|
|
- price: Default::default(),
|
|
|
- client_id: "".to_string(),
|
|
|
- filled_price: Default::default(),
|
|
|
- filled: Default::default(),
|
|
|
- order_id: "".to_string(),
|
|
|
- local_time: 0,
|
|
|
- create_time: 0,
|
|
|
- status: "".to_string(),
|
|
|
- fee: Decimal::ZERO,
|
|
|
- };
|
|
|
- if val.status== "NEW" {
|
|
|
- local_order_info.client_id = val.custom_id;
|
|
|
- local_order_info.order_id = val.id;
|
|
|
- } else if val.status == "REMOVE" {
|
|
|
- local_order_info.client_id = val.custom_id;
|
|
|
- }
|
|
|
- let mut bot = quant_arc.lock().await;
|
|
|
- // 写入本地订单缓存
|
|
|
- bot.update_local_order(local_order_info);
|
|
|
- }
|
|
|
- },
|
|
|
- Err(e) => {
|
|
|
- info!("订单回执消费失败!{}", e);
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
- pub fn update_order(&mut self, data: Vec<OrderInfo>){
|
|
|
+ pub fn update_order(&mut self, data: Vec<OrderInfo>, trace_stack: TraceStack){
|
|
|
for order in data {
|
|
|
- self.update_local_order(order);
|
|
|
+ self.update_local_order(order, trace_stack.clone());
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- pub fn update_local_order(&mut self, data: OrderInfo) {
|
|
|
+ pub fn update_local_order(&mut self, data: OrderInfo, mut trace_stack: TraceStack) {
|
|
|
if data.filled != Decimal::ZERO {
|
|
|
+ info!("\n\n");
|
|
|
info!("接收到订单信息①:{:?}", data);
|
|
|
}
|
|
|
/*
|
|
|
@@ -331,6 +295,16 @@ impl Quant {
|
|
|
*/
|
|
|
// 触发订单更新
|
|
|
self.trade_order_update_time = Utc::now().timestamp_millis();
|
|
|
+
|
|
|
+ // 更新跟踪
|
|
|
+ if self.local_orders.contains_key(&data.client_id) {
|
|
|
+ let mut order_info = self.local_orders.get(&data.client_id).unwrap().clone();
|
|
|
+
|
|
|
+ if data.trace_stack.after_network != 0 { order_info.trace_stack = data.trace_stack.clone() }
|
|
|
+
|
|
|
+ self.local_orders.insert(data.client_id.clone(), order_info);
|
|
|
+ }
|
|
|
+
|
|
|
// 新增订单推送 仅需要cid oid信息
|
|
|
if data.status == "NEW" {
|
|
|
// 更新oid信息 更新订单 loceltime信息(尤其是查单返回new的情况 必须更新 否则会误触发风控)
|
|
|
@@ -346,6 +320,12 @@ impl Quant {
|
|
|
self.local_cancel_log.remove(&data.client_id);
|
|
|
}
|
|
|
if self.local_orders.contains_key(&data.client_id) {
|
|
|
+ // 成交数量不为空,则打印耗时追踪
|
|
|
+ if data.filled > Decimal::ZERO {
|
|
|
+ let local_order = self.local_orders.get(&data.client_id).unwrap();
|
|
|
+ info!("订单耗时追踪:{:?}", local_order.trace_stack.to_string());
|
|
|
+ }
|
|
|
+
|
|
|
debug!("删除本地订单, client_id:{:?}", data);
|
|
|
self.local_orders.remove(&data.client_id);
|
|
|
} else {
|
|
|
@@ -483,9 +463,13 @@ impl Quant {
|
|
|
self.local_profit -= data.fee;
|
|
|
}
|
|
|
}
|
|
|
+ // info!("成交单耗时数据:{}", time_record.to_string());
|
|
|
info!("更新推算仓位 {:?}", self.local_position_by_orders);
|
|
|
// 本地计算利润
|
|
|
self._print_local_trades_summary();
|
|
|
+ // 打印各类信息
|
|
|
+ self.strategy.local_orders = self.local_orders.clone();
|
|
|
+ self.strategy._print_summary();
|
|
|
}
|
|
|
// 每次有订单变动就触发一次策略
|
|
|
if self.mode_signal == 0 && self.ready == 1 {
|
|
|
@@ -494,17 +478,22 @@ impl Quant {
|
|
|
// 触发策略挂单逻辑
|
|
|
// 更新策略时间
|
|
|
self.strategy.local_time = Utc::now().timestamp_millis();
|
|
|
+ trace_stack.on_before_strategy();
|
|
|
let order = self.strategy.on_time(&self.trade_msg);
|
|
|
+ trace_stack.on_after_strategy();
|
|
|
// 记录指令触发信息
|
|
|
if order.is_not_empty() {
|
|
|
// info!("触发onOrder");
|
|
|
self._update_local_orders(&order);
|
|
|
+ trace_stack.on_order();
|
|
|
//交易所处理订单信号
|
|
|
let mut platform_rest_fb = self.platform_rest.clone_box();
|
|
|
// info!("订单指令:{:?}", order);
|
|
|
+ let mut ts = trace_stack.clone();
|
|
|
+ ts.on_before_send_thread();
|
|
|
spawn(async move{
|
|
|
- // info!("update_local_order订单指令:{:?}", order);
|
|
|
- platform_rest_fb.command_order(order).await;
|
|
|
+ ts.on_before_send();
|
|
|
+ platform_rest_fb.command_order(order, ts.clone()).await;
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
@@ -569,7 +558,9 @@ impl Quant {
|
|
|
self.ready = 1
|
|
|
}
|
|
|
|
|
|
- pub fn _update_depth(&mut self, depth: Vec<Decimal>, name :String) {
|
|
|
+ pub fn _update_depth(&mut self, depth: Vec<Decimal>, name: String, trace_stack: &mut TraceStack) {
|
|
|
+ trace_stack.on_depth();
|
|
|
+
|
|
|
// 要从回调传入的深度信息中获取data.name
|
|
|
let market_update_interval_key = name.clone();
|
|
|
let market_update_time_key = name.clone();
|
|
|
@@ -623,18 +614,24 @@ impl Quant {
|
|
|
// 触发事件撤单逻辑
|
|
|
// 更新策略时间
|
|
|
self.strategy.local_time = Utc::now().timestamp_millis();
|
|
|
+
|
|
|
// 产生交易信号
|
|
|
+ trace_stack.on_before_strategy();
|
|
|
let orders = self.strategy.on_time(&self.trade_msg);
|
|
|
+ trace_stack.on_after_strategy();
|
|
|
if orders.is_not_empty() {
|
|
|
debug!("触发onTick");
|
|
|
self._update_local_orders(&orders);
|
|
|
-
|
|
|
//异步交易所处理订单信号
|
|
|
let mut platform_rest_fb = self.platform_rest.clone_box();
|
|
|
// info!("订单指令:{:?}", orders);
|
|
|
+ let mut ts = trace_stack.clone();
|
|
|
+ ts.on_order_command(orders.to_string());
|
|
|
+ ts.on_before_send_thread();
|
|
|
spawn(async move{
|
|
|
// info!("_update_depth订单指令:{:?}", orders);
|
|
|
- platform_rest_fb.command_order(orders).await;
|
|
|
+ ts.on_before_send();
|
|
|
+ platform_rest_fb.command_order(orders, ts.clone()).await;
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
@@ -656,10 +653,10 @@ impl Quant {
|
|
|
}
|
|
|
}
|
|
|
// 更新仓位信息
|
|
|
- info!("收到新的仓位推送, position: {:?}, local_position: {:?}", data, position);
|
|
|
if position != self.local_position {
|
|
|
- self.local_position = position;
|
|
|
+ info!("收到新的仓位推送, position: {:?}, local_position: {:?}", data, position);
|
|
|
info!("更新本地仓位:{:?}", self.local_position);
|
|
|
+ self.local_position = position;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -730,6 +727,7 @@ impl Quant {
|
|
|
create_time: self.strategy.local_time,
|
|
|
status: "".to_string(),
|
|
|
fee: Default::default(),
|
|
|
+ trace_stack: Default::default(),
|
|
|
};
|
|
|
// 本地挂单表
|
|
|
self.local_orders.insert(limits.get(j).unwrap()[3].clone(), order_info.clone());
|
|
|
@@ -806,14 +804,14 @@ impl Quant {
|
|
|
self.local_cash = data.balance * self.used_pct
|
|
|
}
|
|
|
|
|
|
- pub async fn update_equity_rest(&mut self) {
|
|
|
+ pub async fn update_equity_rest_swap(&mut self) {
|
|
|
match self.platform_rest.get_account().await {
|
|
|
Ok(val) => {
|
|
|
/*
|
|
|
- 更新保证金信息
|
|
|
- 合约一直更新
|
|
|
- 现货只有当出现异常时更新
|
|
|
- */
|
|
|
+ 更新保证金信息
|
|
|
+ 合约一直更新
|
|
|
+ 现货只有当出现异常时更新
|
|
|
+ */
|
|
|
if self.exchange.contains("spot") {
|
|
|
return;
|
|
|
}
|
|
|
@@ -825,6 +823,34 @@ impl Quant {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ pub async fn update_equity_rest_spot(&mut self) {
|
|
|
+ match self.platform_rest.get_spot_account().await {
|
|
|
+ Ok(mut val) => {
|
|
|
+ // 如果返回的数组里没有交易货币,则补充交易货币
|
|
|
+ if !val.iter().any(|a| a.coin.to_uppercase().eq(&self.base.to_uppercase())) {
|
|
|
+ let mut base_coin_account = Account::new();
|
|
|
+ base_coin_account.coin = self.base.to_uppercase();
|
|
|
+ val.push(base_coin_account);
|
|
|
+ }
|
|
|
+
|
|
|
+ for account in val {
|
|
|
+ // 交易货币
|
|
|
+ if self.base.to_uppercase() == account.coin {
|
|
|
+ self.local_coin = account.balance;
|
|
|
+ }
|
|
|
+ // 本位货币
|
|
|
+ if self.quote.to_uppercase() == account.coin {
|
|
|
+ self.local_cash = account.balance;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ },
|
|
|
+ Err(err) => {
|
|
|
+ error!("获取仓位信息异常: {}", err);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
pub async fn check_risk(&mut self) {
|
|
|
// 参数检查的风控
|
|
|
if self.strategy.start_cash == Decimal::ZERO {
|
|
|
@@ -976,63 +1002,218 @@ impl Quant {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ pub async fn buy_token(&mut self){
|
|
|
+ // 买入平台币
|
|
|
+ // 获取U数量,平台币数量
|
|
|
+ // 更新账户
|
|
|
+ let mut cash = Decimal::ZERO;
|
|
|
+ let mut token = Decimal::ZERO;
|
|
|
+ let token_param = get_exchange_token(&self.exchange);
|
|
|
+ if token_param.token=="***"{
|
|
|
+ error!("购买平台币失败,未找到交易所的平台币!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ match self.platform_rest.get_spot_account().await {
|
|
|
+ Ok(val) =>{
|
|
|
+ for account in val{
|
|
|
+ if account.coin == "USDT".to_string() {
|
|
|
+ cash += account.balance;
|
|
|
+ }
|
|
|
+ if token_param.token == account.coin {
|
|
|
+ token += account.balance;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },Err(err)=>{
|
|
|
+ error!("购买{}-获取账户失败 {}", token_param.token, err);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ info!("持u {} , 持有平台币 {}", cash, token);
|
|
|
+ match self.platform_rest.get_ticker_symbol(format!("{}_USDT", token_param.token)).await {
|
|
|
+ Ok(val)=>{
|
|
|
+ let mp = (val.buy + val.sell)/Decimal::TWO;
|
|
|
+ let token_value = token * mp;
|
|
|
+ if token_value < token_param.limit_value {
|
|
|
+ info!("{} 数量过少!", token_param.token);
|
|
|
+ if cash > Decimal::TWO*Decimal::ONE_HUNDRED{
|
|
|
+ info!("准备买入{}", token_param.token);
|
|
|
+ match self.platform_rest.take_order_symbol(token_param.token, Decimal::ONE, "t-888", "kd", mp * Decimal::from_str("1.001").unwrap(), Decimal::from_str("50").unwrap()/mp).await {
|
|
|
+ Ok(value) => {
|
|
|
+ info!("买入平台币下单成功: {:?}", value);
|
|
|
+ },
|
|
|
+ Err(error) => {
|
|
|
+ error!("买入平台币下单失败: {}", error)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ } else {
|
|
|
+ info!("现金不足 无法买入{}!", token_param.token);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ info!("{}数量充足!", token_param.token);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ Err(err)=>{
|
|
|
+ error!("购买平台币-获取平台币行情失败 {}", err);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
pub async fn check_position(&mut self){
|
|
|
info!("清空挂单!");
|
|
|
- match self.platform_rest.cancel_orders().await{
|
|
|
+ match self.platform_rest.cancel_orders_all().await {
|
|
|
Ok(val)=>{
|
|
|
- info!(?val);
|
|
|
+ info!("清空所有挂单,{:?}", val);
|
|
|
},
|
|
|
- Err(e)=>{
|
|
|
- error!("清空挂单异常: {}", e);
|
|
|
+ Err(err)=>{
|
|
|
+ error!("取消所有订单异常: {}",err);
|
|
|
+ match self.platform_rest.cancel_orders().await {
|
|
|
+ Ok(val) => {
|
|
|
+ info!("清空当前币对挂单,{:?}", val);
|
|
|
+ },
|
|
|
+ Err(exc) => {
|
|
|
+ error!("清空当前币对订单异常: {}",exc);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- };
|
|
|
+ }
|
|
|
+ if self.exchange.contains("spot") { // 现货
|
|
|
+ self.check_position_spot().await;
|
|
|
+ } else { // 合约
|
|
|
+ self.check_position_swap().await;
|
|
|
+ }
|
|
|
+ info!("遗留仓位检测完毕");
|
|
|
+ }
|
|
|
+
|
|
|
+ pub async fn check_position_spot(&mut self){
|
|
|
info!("检查遗漏仓位!");
|
|
|
- match self.platform_rest.get_positions().await {
|
|
|
- Ok(val)=>{
|
|
|
- for position in val {
|
|
|
- if !position.symbol.eq_ignore_ascii_case(self.symbol.as_str()){
|
|
|
+ match self.platform_rest.get_spot_account().await {
|
|
|
+ Ok(mut val) => {
|
|
|
+ // 如果返回的数组里没有交易货币,则补充交易货币
|
|
|
+ if !val.iter().any(|a| a.coin.to_uppercase().eq(&self.base.to_uppercase())) {
|
|
|
+ let mut base_coin_account = Account::new();
|
|
|
+ base_coin_account.coin = self.base.to_uppercase();
|
|
|
+ val.push(base_coin_account);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 仓位补货、卖货
|
|
|
+ for account in val {
|
|
|
+ let coin_name = account.coin.to_uppercase();
|
|
|
+ if check_coin(&self.exchange, &coin_name){
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ let symbol = format!("{}_USDT", coin_name);
|
|
|
+ let mut _hold_coin = Decimal::ZERO;
|
|
|
+ if coin_name.eq(self.base.as_str()){
|
|
|
+ _hold_coin = self.hold_coin;
|
|
|
+ }
|
|
|
+ let ap;
|
|
|
+ let bp;
|
|
|
+ let mp;
|
|
|
+ match self.platform_rest.get_ticker().await {
|
|
|
+ Ok(ticker)=>{
|
|
|
+ ap = ticker.sell;
|
|
|
+ bp = ticker.buy;
|
|
|
+ mp = (ap + bp)/Decimal::TWO;
|
|
|
+ },
|
|
|
+ Err(_e)=>{
|
|
|
+ error!("清仓中- 获取 {} 币对行情错误,该币对无法调整仓位。", symbol);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ let coin_value = account.balance * mp;
|
|
|
+ let diff = (_hold_coin - coin_value)* Decimal::from_str("0.99").unwrap();
|
|
|
+ let side;
|
|
|
+ let price= Decimal::ZERO;
|
|
|
+ let amount;
|
|
|
+ if diff > Decimal::from(20) {
|
|
|
+ side = "kd";
|
|
|
+ // price = mp*1.001;
|
|
|
+ amount = diff/mp;
|
|
|
+ } else if diff < Decimal::from(-10) {
|
|
|
+ side = "kk";
|
|
|
+ // price = mp*0.999;
|
|
|
+ amount = -diff/mp;
|
|
|
+ } else {
|
|
|
continue;
|
|
|
}
|
|
|
+ info!("{}, 需要调整现货仓位 {} usdt", symbol, diff);
|
|
|
+ // 价格0,市价单
|
|
|
+ match self.platform_rest.take_order_symbol(symbol.clone(), Decimal::ONE, "t-123", side, price, amount).await{
|
|
|
+ Ok(v)=>{
|
|
|
+ info!("side: {}, {} 下单,{:?}", side, symbol, v);
|
|
|
+ // 执行完当前币对 结束循环
|
|
|
+ continue;
|
|
|
+ },Err(ex)=>{
|
|
|
+ error!("side: {}, {} {}", side, symbol, ex);
|
|
|
+ // 执行完当前币对 结束循环
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ Err(err) => {
|
|
|
+ error!("获取仓位信息异常: {}", err);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
+ pub async fn check_position_swap(&mut self){
|
|
|
+ info!("检查遗漏仓位!");
|
|
|
+ match self.platform_rest.get_positions().await {
|
|
|
+ Ok(val)=>{
|
|
|
+ for position in val {
|
|
|
if position.amount.eq(&Decimal::ZERO) {
|
|
|
continue;
|
|
|
}
|
|
|
-
|
|
|
- match self.platform_rest.get_ticker().await {
|
|
|
+ match self.platform_rest.get_ticker_symbol(position.symbol.clone()).await {
|
|
|
Ok(ticker)=>{
|
|
|
let ap = ticker.sell;
|
|
|
let bp = ticker.buy;
|
|
|
let mp = ( ap + bp ) / Decimal::TWO;
|
|
|
let price;
|
|
|
let side;
|
|
|
+ let market_info;
|
|
|
+ // 获取market
|
|
|
+ match self.platform_rest.get_market_symbol(position.symbol.clone()).await {
|
|
|
+ Ok(market) => {
|
|
|
+ market_info = market;
|
|
|
+ },
|
|
|
+ Err(err) => {
|
|
|
+ error!("获取当前market异常: {}", err);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
info!(?position);
|
|
|
match position.position_mode {
|
|
|
PositionModeEnum::Long => {
|
|
|
// pd
|
|
|
- price = (mp*dec!(0.999)/self.market.tick_size).floor()*self.market.tick_size;
|
|
|
+ price = (mp*dec!(0.999)/market_info.tick_size).floor()*market_info.tick_size;
|
|
|
side = "pd";
|
|
|
},
|
|
|
PositionModeEnum::Short => {
|
|
|
// pk
|
|
|
- price = (mp*dec!(1.001)/self.market.tick_size).floor()*self.market.tick_size;
|
|
|
+ price = (mp*dec!(1.001)/market_info.tick_size).floor()*market_info.tick_size;
|
|
|
side = "pk";
|
|
|
}
|
|
|
_ => {
|
|
|
info!("仓位匹配失败,不做操作!");
|
|
|
// 执行完当前币对 结束循环
|
|
|
- break;
|
|
|
+ continue;
|
|
|
}
|
|
|
}
|
|
|
- match self.platform_rest.take_order("t-123", side, price, position.amount.abs()).await {
|
|
|
+ // 发起清仓订单
|
|
|
+ match self.platform_rest.take_order_symbol(position.symbol.clone(), Decimal::ONE,"t-123", side, price, position.amount.abs()).await {
|
|
|
Ok(order)=>{
|
|
|
- info!("清仓下单,{:?}", order);
|
|
|
+ info!("{} 清仓下单,{:?}", position.symbol, order);
|
|
|
// 执行完当前币对 结束循环
|
|
|
- break;
|
|
|
+ continue;
|
|
|
},
|
|
|
Err(error)=>{
|
|
|
- error!("清仓下单异常:{}", error);
|
|
|
+ error!("{} 清仓下单异常:{}", position.symbol, error);
|
|
|
// 执行完当前币对 结束循环
|
|
|
- break;
|
|
|
+ continue;
|
|
|
}
|
|
|
};
|
|
|
},
|
|
|
@@ -1053,27 +1234,24 @@ impl Quant {
|
|
|
/*
|
|
|
* 停机函数
|
|
|
* mode_signal 不能小于80
|
|
|
- * 前6秒用于maker平仓
|
|
|
- * 后2秒用于撤maker平仓单
|
|
|
- * 休眠2秒再执行check_position 避免卡单导致漏仓位
|
|
|
+ * 前3秒用于maker平仓
|
|
|
+ * 后1秒用于撤maker平仓单
|
|
|
+ * 休眠1秒再执行check_position 避免卡单导致漏仓位
|
|
|
*/
|
|
|
- info!("进入停机流程...");
|
|
|
- self.mode_signal = 80;
|
|
|
- sleep(Duration::from_secs(10)).await;
|
|
|
-
|
|
|
- info!("开始退出操作");
|
|
|
- info!("为避免api失效导致遗漏仓位 建议人工复查");
|
|
|
- self.check_position().await;
|
|
|
- // 开启停机信号
|
|
|
-
|
|
|
- sleep(Duration::from_secs(3)).await;
|
|
|
- info!("双重检查遗漏仓位");
|
|
|
- self.check_position().await;
|
|
|
- info!("停机退出 停机原因: {}", self.exit_msg);
|
|
|
- // 发送交易状态 await self._post_params()
|
|
|
- // TODO: 向中控发送信号
|
|
|
+ info!("止损后进入停机流程...");
|
|
|
self.running.store(false, Ordering::Relaxed);
|
|
|
- info!("退出进程!");
|
|
|
+ self.mode_signal = 80;
|
|
|
+ // info!("开始退出操作");
|
|
|
+ // info!("为避免api失效导致遗漏仓位 建议人工复查");
|
|
|
+ // self.check_position().await;
|
|
|
+ // // 开启停机信号
|
|
|
+ // // sleep(Duration::from_secs(1)).await;
|
|
|
+ // info!("双重检查遗漏仓位");
|
|
|
+ // self.check_position().await;
|
|
|
+ // info!("停机退出 停机原因: {}", self.exit_msg);
|
|
|
+ // // 发送交易状态 await self._post_params()
|
|
|
+ // // TODO: 向中控发送信号
|
|
|
+ // info!("退出进程!");
|
|
|
}
|
|
|
|
|
|
pub async fn exit(&mut self, delay: i8){
|
|
|
@@ -1104,12 +1282,16 @@ impl Quant {
|
|
|
let ticker = self.platform_rest.get_ticker().await.expect("获取价格信息异常!");
|
|
|
let mp = (ticker.buy + ticker.sell) / Decimal::TWO;
|
|
|
// 获取账户信息
|
|
|
- self.update_equity_rest().await;
|
|
|
+ if self.exchange.contains("spot") {
|
|
|
+ self.update_equity_rest_spot().await;
|
|
|
+ } else {
|
|
|
+ self.update_equity_rest_swap().await;
|
|
|
+ }
|
|
|
// 初始资金
|
|
|
let start_cash = self.local_cash.clone();
|
|
|
let start_coin = self.local_coin.clone();
|
|
|
if start_cash.is_zero() && start_coin.is_zero() {
|
|
|
- self.exit_msg = format!("{}{}{}{}", "初始为零 cash: ", start_cash, " coin: ", start_coin);
|
|
|
+ self.exit_msg = format!("{}{}{}{}", "初始余额为零 cash: ", start_cash, " coin: ", start_coin);
|
|
|
// 停止程序
|
|
|
self.stop().await;
|
|
|
return false;
|
|
|
@@ -1177,6 +1359,11 @@ impl Quant {
|
|
|
self.local_cash = start_cash;
|
|
|
self.local_coin = start_coin;
|
|
|
|
|
|
+ // 买入平台币
|
|
|
+ if self.exchange.contains("spot") { // 现货
|
|
|
+ self.buy_token().await;
|
|
|
+ }
|
|
|
+
|
|
|
// 清空挂单和仓位
|
|
|
self.check_position().await;
|
|
|
/*
|
|
|
@@ -1222,9 +1409,10 @@ pub fn run_strategy(quant_arc: Arc<Mutex<Quant>>) -> JoinHandle<()>{
|
|
|
let orders = quant.strategy.on_exit(&trade_msg);
|
|
|
if orders.is_not_empty() {
|
|
|
info!("触发onExit");
|
|
|
+ info!(?orders);
|
|
|
quant._update_local_orders(&orders);
|
|
|
spawn(async move {
|
|
|
- platform_rest_fb.command_order(orders).await;
|
|
|
+ platform_rest_fb.command_order(orders, Default::default()).await;
|
|
|
});
|
|
|
}
|
|
|
} else {
|
|
|
@@ -1232,9 +1420,11 @@ pub fn run_strategy(quant_arc: Arc<Mutex<Quant>>) -> JoinHandle<()>{
|
|
|
let orders = quant.strategy.on_sleep(&trade_msg);
|
|
|
// 记录指令触发信息
|
|
|
if orders.is_not_empty() {
|
|
|
+ info!("触发onSleep");
|
|
|
+ info!(?orders);
|
|
|
quant._update_local_orders(&orders);
|
|
|
spawn(async move {
|
|
|
- platform_rest_fb.command_order(orders).await;
|
|
|
+ platform_rest_fb.command_order(orders, Default::default()).await;
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
@@ -1243,9 +1433,9 @@ pub fn run_strategy(quant_arc: Arc<Mutex<Quant>>) -> JoinHandle<()>{
|
|
|
quant.check_ready();
|
|
|
}
|
|
|
// 计算耗时并进行休眠
|
|
|
- let pass_time = Utc::now().timestamp_millis() - start_time;
|
|
|
- if pass_time < quant.interval.to_i64().unwrap() {
|
|
|
- delay = quant.interval.to_u64().unwrap() - pass_time.to_u64().unwrap();
|
|
|
+ let pass_time = (Utc::now().timestamp_millis() - start_time).to_u64().unwrap();
|
|
|
+ if pass_time < quant.interval {
|
|
|
+ delay = quant.interval - pass_time;
|
|
|
}
|
|
|
}
|
|
|
sleep(Duration::from_millis(delay)).await;
|
|
|
@@ -1280,8 +1470,6 @@ pub fn on_timer(quant_arc: Arc<Mutex<Quant>>) -> JoinHandle<()> {
|
|
|
quant.strategy.trade_vol_24h_w = trade_vol_24h / dec!(10000);
|
|
|
quant.strategy.trade_vol_24h_w.rescale(2);
|
|
|
|
|
|
- // 打印各类信息
|
|
|
- quant.strategy._print_summary();
|
|
|
// TODO quant没有rest
|
|
|
// info!("Rest报单平均延迟{}ms", quant.rest.avg_delay);
|
|
|
// info!("Rest报单最高延迟{}ms", quant.rest.max_delay);
|
|
|
@@ -1293,58 +1481,33 @@ pub fn on_timer(quant_arc: Arc<Mutex<Quant>>) -> JoinHandle<()> {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
-
|
|
|
-
|
|
|
-#[cfg(test)]
|
|
|
-mod tests {
|
|
|
- use rust_decimal::Decimal;
|
|
|
- use rust_decimal_macros::dec;
|
|
|
- use tracing::info;
|
|
|
-
|
|
|
- #[tokio::test]
|
|
|
- async fn test_new_exchange() {
|
|
|
- // let (order_tx, order_rx):(Sender<Order>, Receiver<Order>) = channel(1024);
|
|
|
- // let (err_tx, err_rx):(Sender<Error>, Receiver<Error>) = channel(1024);
|
|
|
- //
|
|
|
- // let _params = Params::new("config.toml").unwrap();
|
|
|
- //
|
|
|
- // let mut params_exc: BTreeMap<String, String> = BTreeMap::new();
|
|
|
- // let access_key = "";
|
|
|
- // let secret_key = "";
|
|
|
- //
|
|
|
- // let mut quant: Quant = Quant::new(_params, params_exc,order_tx, err_tx).await;
|
|
|
- // let is_ok = quant.before_trade().await;
|
|
|
- //
|
|
|
- // info!("结果: {}", is_ok)
|
|
|
-
|
|
|
+// 是不是不用调整仓位的币
|
|
|
+pub fn check_coin(exchanges :&String, coin_name: &String) -> bool{
|
|
|
+ let mut result = false;
|
|
|
+ match exchanges.as_str() {
|
|
|
+ "bitget_spot" => {
|
|
|
+ result = ["BGB", "USDT"].contains(&coin_name.as_str());
|
|
|
+ }
|
|
|
+ _ => {}
|
|
|
}
|
|
|
+ result
|
|
|
+}
|
|
|
|
|
|
- #[tokio::test]
|
|
|
- async fn test_time(){
|
|
|
- global::log_utils::init_log_with_trace();
|
|
|
-
|
|
|
- let start_cash:Decimal = dec!(1.11);
|
|
|
- let start_coin:Decimal = dec!(0.12);
|
|
|
- let lever_rate:Decimal = dec!(10);
|
|
|
- let grid:Decimal = dec!(1);
|
|
|
- let mp:Decimal = dec!(235.562);
|
|
|
- let step_size:Decimal = dec!(0.02);
|
|
|
-
|
|
|
- let mut long_one_hand_value: Decimal = start_cash * lever_rate / grid;
|
|
|
- let mut short_one_hand_value: Decimal = Decimal::ZERO;
|
|
|
- let long_one_hand_amount: Decimal =(long_one_hand_value/mp/step_size).floor()*step_size;
|
|
|
- let mut short_one_hand_amount: Decimal = Decimal::ZERO;
|
|
|
-
|
|
|
- // if self.exchange.contains("spot"){
|
|
|
- short_one_hand_value = start_coin * mp * lever_rate / grid;
|
|
|
- short_one_hand_amount = (short_one_hand_value/mp/step_size).floor()*step_size;
|
|
|
- // } else {
|
|
|
- // short_one_hand_value = start_cash * lever_rate / grid;
|
|
|
- // short_one_hand_amount = (short_one_hand_value/mp/step_size).trunc()*step_size;
|
|
|
- // }
|
|
|
-
|
|
|
- info!("long_one_hand_value:{:?}, short_one_hand_value: {:?}", long_one_hand_value, short_one_hand_value);
|
|
|
- info!("long_one_hand_amount:{:?}, short_one_hand_amount: {:?}", long_one_hand_amount, short_one_hand_amount);
|
|
|
- info!("{:?},{:?},{:?},{:?},{:?}", short_one_hand_value, mp, step_size, (short_one_hand_value/mp/step_size), ((short_one_hand_value/mp)/step_size).floor())
|
|
|
+//获取平台币
|
|
|
+pub fn get_exchange_token(exchanges :&String) -> TokenParam{
|
|
|
+ return match exchanges.as_str() {
|
|
|
+ "bitget_spot" => {
|
|
|
+ TokenParam{
|
|
|
+ token: "BGB".to_string(),
|
|
|
+ // 30u
|
|
|
+ limit_value: Decimal::TEN*(Decimal::ONE + Decimal::TWO)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ _ => {
|
|
|
+ TokenParam{
|
|
|
+ token: "***".to_string(),
|
|
|
+ limit_value: Decimal::ZERO
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|