JiahengHe пре 2 година
родитељ
комит
96aa73bd58
3 измењених фајлова са 281 додато и 18 уклоњено
  1. 17 3
      strategy/src/model.rs
  2. 260 14
      strategy/src/quant.rs
  3. 4 1
      strategy/src/strategy.rs

+ 17 - 3
strategy/src/model.rs

@@ -1,7 +1,7 @@
 use std::collections::{HashMap};
 use rust_decimal::Decimal;
 use rust_decimal_macros::dec;
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct Position{
     // 做多仓位
     pub long_pos: Decimal,
@@ -69,15 +69,21 @@ pub struct OrderInfo{
     // 实际价格
     pub filled_price: Decimal,
     //
-    pub filled: i8,
+    pub filled: Decimal,
     // 订单号
     pub order_id: String,
     // 时间
     pub local_time: i64,
     // 时间
-    pub create_time: i64
+    pub create_time: i64,
+
+    pub status: String,
+
+    pub fee: Decimal
 }
 
+
+
 #[derive(Debug)]
 pub struct OrderCommand{
     // 取消订单指令,数据结构例子:
@@ -108,5 +114,13 @@ impl OrderCommand{
             limits_close: Default::default(),
         }
     }
+
+    pub fn is_not_empty(&self) -> bool{
+        if self.limits_close.is_empty() && self.limits.is_empty() && self.limits_open.is_empty()
+            && self.cancel.is_empty() && self.check.is_empty(){
+            return false;
+        }
+        return true;
+    }
 }
 

+ 260 - 14
strategy/src/quant.rs

@@ -1,4 +1,5 @@
 use std::collections::{BTreeMap, HashMap};
+use std::ops::Div;
 use std::str::FromStr;
 use std::thread;
 use std::time::Duration;
@@ -204,6 +205,217 @@ impl Quant {
         return quant_obj;
     }
 
+    pub fn update_order(&mut self, data: OrderInfo){
+        /*
+         更新订单
+            首先直接复写本地订单
+            1、如果是开仓单
+                如果新增: 增加本地订单
+                如果取消: 删除本地订单 查看是否完全成交 如果是部分成交 则按已成交量发送平仓订单 修改本地仓位
+                如果成交: 删除本地订单 发送平仓订单 修改本地仓位
+            2、如果是平仓单
+                如果新增: 增加本地订单
+                如果取消: 删除本地订单 查看是否完全成交 如果是部分成交 则按未成交量发送平仓订单 修改本地仓位
+                如果成交: 删除本地订单 修改本地仓位
+            NEW 可以从 ws / rest 来
+            REMOVE 主要从 ws 来 必须包含 filled 和 filled_price 用于本地仓位推算 定期rest查过旧订单
+            为了防止下单失败依然有订单成交 本地需要做一个缓存
+        */
+        // 触发订单更新
+        self.trade_order_update_time = Utc::now().timestamp_millis();
+        // 新增订单推送 仅需要cid oid信息
+        if data.status == "NEW"{
+            // 更新oid信息 更新订单 loceltime信息(尤其是查单返回new的情况 必须更新 否则会误触发风控)
+            if self.local_orders.contains_key(&data.client_id){
+                let mut order_info = self.local_orders.get(&data.client_id).unwrap().clone();
+                order_info.order_id = data.order_id;
+                order_info.local_time = Utc::now().timestamp_millis();
+                self.local_orders.insert(data.client_id.clone(), order_info);
+            }
+        } else if data.status == "REMOVE"{
+            // 如果在撤单记录中 说明此订单结束生命周期 可以移除记录
+            if self.local_cancel_log.contains_key(&data.client_id){
+                self.local_cancel_log.remove(&data.client_id);
+            }
+            // 在cid缓存队列中 说明是本策略的订单
+            if self.local_orders_backup.contains_key(&data.client_id){
+                // 不在已处理cid缓存队列中 说明还没参与过仓位计算 则执行订单计算
+                if self.handled_orders_cid.contains(&data.client_id){
+                    println!("订单已经参与过仓位计算 拒绝重复进行计算, 订单号:{}", data.client_id);
+                } else {
+                    // 添加进已处理队列
+                    self.handled_orders_cid.push(data.client_id.clone());
+                    // 提取成交信息 方向 价格 量
+                    let filled = data.filled;
+                    let side = self.local_orders_backup.get(&data.client_id).unwrap().side.clone();
+                    let mut filled_price = Decimal::ZERO;
+                    if data.filled_price > filled_price {
+                        filled_price = data.filled_price;
+                    } else {
+                        filled_price = self.local_orders_backup.get(&data.client_id).unwrap().price.clone();
+                    }
+                    // 只有开仓成交才触发onPosition
+                    // 如果漏推送 rest补充的订单查询信息过来 可能会导致 kd kk 推送出现计算分母为0的情况
+                    if filled > Decimal::ZERO{
+                        if self.exchange.contains("spot"){ // 如果是现货交易 还需要修改equity
+                            // 现货必须考虑fee 买入fee单位为币 卖出fee单位为u
+                            let fee = data.fee;
+                            if side == "kd"{ // buy  开多
+                                self.local_buy_amount += filled-fee;
+                                self.local_buy_value += (filled - fee) * filled_price;
+                                let new_long_pos = self.local_position_by_orders.long_pos.clone();
+                                if new_long_pos == Decimal::ZERO {
+                                    self.local_position_by_orders.long_avg = Decimal::ZERO;
+                                    self.local_position_by_orders.long_pos = Decimal::ZERO;
+                                } else {
+                                    self.local_position_by_orders.long_avg = (self.local_position_by_orders.long_pos * self.local_position_by_orders.long_avg +
+                                        filled * filled_price) / new_long_pos;
+                                    self.local_position_by_orders.long_pos = new_long_pos;
+                                }
+                                self.local_cash -= filled * filled_price;
+                                self.local_coin = filled - fee;
+                            } else if side == "pd"{ // sell 平多
+                                self.local_sell_amount += filled;
+                                self.local_sell_value += filled * filled_price;
+                                self.local_profit += filled * (filled_price - self.local_position_by_orders.long_avg);
+                                let new_long_pos = self.local_position_by_orders.long_pos - filled;
+                                if new_long_pos == Decimal::ZERO{
+                                    self.local_position_by_orders.long_avg = Decimal::ZERO;
+                                    self.local_position_by_orders.long_pos = Decimal::ZERO;
+                                } else {
+                                    self.local_position_by_orders.long_pos = new_long_pos;
+                                }
+                                self.local_cash += filled * filled_price - fee;
+                                self.local_coin -= filled;
+                            } else if side == "pk"{ // buy 平空
+                                self.local_buy_amount += filled - fee;
+                                self.local_buy_value += (filled - fee) * filled_price;
+                                self.local_profit += filled * (self.local_position_by_orders.short_avg - filled_price);
+                                let new_short_pos = self.local_position_by_orders.short_pos - filled;
+                                if new_short_pos == Decimal::ZERO{
+                                    self.local_position_by_orders.short_avg = Decimal::ZERO;
+                                    self.local_position_by_orders.short_pos = Decimal::ZERO;
+                                } else {
+                                    self.local_position_by_orders.short_pos = new_short_pos;
+                                }
+                                self.local_cash -= filled * filled_price;
+                                self.local_coin += filled - fee;
+                            } else if side == "kk"{ // sell 开空
+                                self.local_sell_amount += filled;
+                                self.local_sell_value += filled * filled_price;
+                                let new_short_pos = self.local_position_by_orders.short_pos - filled;
+                                if new_short_pos == Decimal::ZERO{
+                                    self.local_position_by_orders.short_avg = Decimal::ZERO;
+                                    self.local_position_by_orders.short_pos = Decimal::ZERO;
+                                } else {
+                                    self.local_position_by_orders.short_avg = (self.local_position_by_orders.short_pos * self.local_position_by_orders.short_avg
+                                        + filled * filled_price) / new_short_pos;
+                                    self.local_position_by_orders.short_pos = new_short_pos;
+                                }
+                                self.local_cash += filled * filled_price - fee;
+                                self.local_coin -= filled;
+                            } else {
+                                println!("错误的仓位方向{}", side);
+                            }
+                        } else { // 合约订单流仓位计算
+                            if side == "kd" { // buy 开多
+                                self.local_buy_amount += filled;
+                                self.local_buy_value += filled * filled_price;
+                                let new_long_pos = self.local_position_by_orders.long_pos + filled;
+                                if new_long_pos == Decimal::ZERO {
+                                    self.local_position_by_orders.long_avg = Decimal::ZERO;
+                                    self.local_position_by_orders.long_pos = Decimal::ZERO;
+                                } else {
+                                    self.local_position_by_orders.long_avg = (self.local_position_by_orders.long_pos *
+                                        self.local_position_by_orders.long_avg + filled * filled_price) / new_long_pos;
+                                    self.local_position_by_orders.long_pos = self.local_position_by_orders.long_pos + filled;
+                                }
+                            } else if side == "kk" { // sell 开空
+                                self.local_sell_amount += filled;
+                                self.local_sell_value += filled * filled_price;
+                                let new_short_pos = self.local_position_by_orders.short_pos + filled;
+                                if new_short_pos == Decimal::ZERO {
+                                    self.local_position_by_orders.short_avg = Decimal::ZERO;
+                                    self.local_position_by_orders.short_pos = Decimal::ZERO;
+                                } else {
+                                    self.local_position_by_orders.short_avg = (self.local_position_by_orders.short_pos * self.local_position_by_orders.short_avg + filled * filled_price) / new_short_pos;
+                                    self.local_position_by_orders.short_pos = self.local_position_by_orders.short_pos + filled;
+                                }
+                            } else if side == "pd" { // sell 平多
+                                self.local_sell_amount += filled;
+                                self.local_sell_value += filled * filled_price;
+                                self.local_profit += filled * (filled_price - self.local_position_by_orders.long_avg);
+                                self.local_position_by_orders.long_pos = self.local_position_by_orders.long_pos - filled;
+                                if self.local_position_by_orders.long_pos == Decimal::ZERO {
+                                    self.local_position_by_orders.long_avg = Decimal::ZERO;
+                                }
+                            } else if side == "pk" { // buy 平空
+                                self.local_buy_amount += filled;
+                                self.local_buy_value += filled * filled_price;
+                                self.local_profit += filled * (self.local_position_by_orders.short_avg - filled_price);
+                                self.local_position_by_orders.short_pos = self.local_position_by_orders.short_pos - filled;
+                                if self.local_position_by_orders.short_pos == Decimal::ZERO {
+                                    self.local_position_by_orders.short_avg = Decimal::ZERO;
+                                }
+                            } else {
+                                println!("错误的仓位方向{}", side);
+                            }
+                            // 统计合约交易手续费 正fee为扣手续费 负fee为返佣
+                            if data.fee > Decimal::ZERO {
+                                self.local_profit -= data.fee;
+                            }
+                        }
+                        println!("更新推算仓位 {:?}", self.local_position_by_orders);
+                        // 本地计算利润
+                        self._print_local_trades_summary();
+                    }
+                    // 每次有订单变动就触发一次策略
+                    if self.mode_signal == 0 && self.ready == 1{
+                        // 更新交易数据
+                        self.update_trade_msg();
+                        // 触发策略挂单逻辑
+                        // 更新策略时间
+                        self.strategy.local_time = Utc::now().timestamp_millis();
+                        let order = self.strategy.on_time(&self.trade_msg);
+                        // 记录指令触发信息
+                        if order.is_not_empty() {
+                            print!("触发onOrder");
+                            self._update_local_orders(&order);
+                            //TODO: 交易所处理订单信号 self.loop.create_task(self.rest.handle_signals(orders))
+                            println!("订单指令:{:?}", order);
+                        }
+                    }
+                }
+            } else {
+                println!("订单不属于本策略 拒绝进行仓位计算: {}", data.client_id);
+            }
+            if self.local_orders.contains_key(&data.client_id){
+                println!("删除本地订单, client_id:{}", data.client_id);
+                self.local_orders.remove(&data.client_id);
+            } else {
+                println!("该订单不在本地挂单表中, client_id:{}", data.client_id);
+            }
+        } else {
+            println!("未知的订单事件类型:{:?}", data);
+        }
+    }
+
+    pub fn _print_local_trades_summary(&mut self){
+        // 计算本地累计利润
+        let local_buy_amount = self.local_buy_amount.round_dp(5);
+        let local_buy_value = self.local_buy_value.round_dp(5);
+        let local_sell_amount = self.local_sell_amount.round_dp(5);
+        let local_sell_value = self.local_sell_value.round_dp(5);
+
+        if self.strategy.mp > Decimal::ZERO{
+            let unrealized = (local_buy_amount - local_sell_amount) * self.strategy.mp;
+            let realized = local_sell_value - local_buy_value;
+            let local_profit = (unrealized+realized).round_dp(5);
+            self.strategy.local_profit = local_profit;
+            println!("买量 {},卖量 {},买额{},卖额{}", local_buy_amount, local_sell_amount, local_buy_value, local_sell_value);
+        }
+    }
+
     // 检测初始数据是否齐全
     pub fn check_ready(&mut self){
         // 检查 ticker 行情
@@ -307,6 +519,14 @@ impl Quant {
         }
     }
 
+    pub fn update_position(&mut self, data: Position){
+        // 更新仓位信息
+        if data != self.local_position {
+            self.local_position = data;
+            println!("更新本地仓位:{:?}", self.local_position);
+        }
+    }
+
     pub fn _update_ticker(&mut self, data: SpecialDepth){
         // update ticker infomation
         // 要从回调传入的深度信息中获取data.name  TODO: 这里暂时只支持一个参考交易所,支持多个须在入参中添加name字段及值
@@ -358,26 +578,28 @@ impl Quant {
 
     // 本地记录所有报单信息
     pub fn _update_local_orders(&mut self, orders: &OrderCommand){
-        if !orders.limits.is_empty() {
-            for j in orders.limits.keys() {
+        if !orders.limits_open.is_empty() {
+            for j in orders.limits_open.keys() {
                 let order_info = OrderInfo{
                     symbol: self.symbol.clone(),
-                    amount: Decimal::from_str(orders.limits.get(j).unwrap()[0].as_str()).unwrap(),
-                    side: orders.limits.get(j).unwrap()[1].clone(),
-                    price: Decimal::from_str(orders.limits.get(j).unwrap()[2].as_str()).unwrap(),
-                    client_id: orders.limits.get(j).unwrap()[3].clone(),
+                    amount: Decimal::from_str(orders.limits_open.get(j).unwrap()[0].as_str()).unwrap(),
+                    side: orders.limits_open.get(j).unwrap()[1].clone(),
+                    price: Decimal::from_str(orders.limits_open.get(j).unwrap()[2].as_str()).unwrap(),
+                    client_id: orders.limits_open.get(j).unwrap()[3].clone(),
                     filled_price: Default::default(),
-                    filled: 0,
+                    filled: Decimal::ZERO,
                     order_id: "".to_string(),
                     local_time: self.strategy.local_time,
                     create_time: self.strategy.local_time,
+                    status: "".to_string(),
+                    fee: Default::default(),
                 };
                 // 本地挂单表
-                self.local_orders.insert(orders.limits.get(j).unwrap()[3].clone(), order_info.clone());
+                self.local_orders.insert(orders.limits_open.get(j).unwrap()[3].clone(), order_info.clone());
                 // 本地缓存表
-                self.local_orders_backup.insert(orders.limits.get(j).unwrap()[3].clone(), order_info);
+                self.local_orders_backup.insert(orders.limits_open.get(j).unwrap()[3].clone(), order_info);
                 // 本地缓存cid表
-                self.local_orders_backup_cid.push(orders.limits.get(j).unwrap()[3].clone());
+                self.local_orders_backup_cid.push(orders.limits_open.get(j).unwrap()[3].clone());
             }
         }
         if !orders.cancel.is_empty() {
@@ -484,15 +706,15 @@ impl Quant {
         // 计算下单数量
         let mut long_one_hand_value: Decimal = start_cash * self.params.lever_rate / grid;
         let mut short_one_hand_value: Decimal = Decimal::ZERO;
-        let long_one_hand_amount: Decimal =(long_one_hand_value/mp/self.strategy.step_size).trunc()*self.strategy.step_size;
+        let long_one_hand_amount: Decimal =(long_one_hand_value/mp/&self.strategy.step_size).floor()*self.strategy.step_size;
         let mut short_one_hand_amount: Decimal = Decimal::ZERO;
 
         if self.exchange.contains("spot"){
             short_one_hand_value = start_coin * mp * self.params.lever_rate / grid;
-            short_one_hand_amount = (short_one_hand_value/mp/self.strategy.step_size).trunc()*self.strategy.step_size;
+            short_one_hand_amount = (short_one_hand_value/mp/self.strategy.step_size).floor()*self.strategy.step_size;
         } else {
             short_one_hand_value = start_cash * self.params.lever_rate / grid;
-            short_one_hand_amount = (short_one_hand_value/mp/self.strategy.step_size).trunc()*self.strategy.step_size;
+            short_one_hand_amount = (short_one_hand_value/mp/self.strategy.step_size).floor()*self.strategy.step_size;
         }
         println!("最低单手交易下单量为 buy: {}, sell: {}", long_one_hand_amount, short_one_hand_amount);
         let hand_min_limit = Decimal::new(20, 0);
@@ -535,6 +757,8 @@ fn order_not_empty(orders: &OrderCommand) -> i8{
 #[cfg(test)]
 mod tests {
     use chrono::Utc;
+    use rust_decimal::Decimal;
+    use rust_decimal_macros::dec;
     use crate::params::Params;
     use crate::quant::Quant;
 
@@ -547,6 +771,28 @@ mod tests {
 
     #[tokio::test]
     async fn test_time(){
-        println!("毫秒:{}", Utc::now().timestamp_millis())
+        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;
+        // }
+
+        println!("long_one_hand_value:{:?}, short_one_hand_value: {:?}", long_one_hand_value, short_one_hand_value);
+        println!("long_one_hand_amount:{:?}, short_one_hand_amount: {:?}", long_one_hand_amount, short_one_hand_amount);
+        println!("{:?},{:?},{:?},{:?},{:?}", short_one_hand_value, mp, step_size, (short_one_hand_value/mp/step_size), ((short_one_hand_value/mp)/step_size).floor())
     }
 }

+ 4 - 1
strategy/src/strategy.rs

@@ -1026,6 +1026,7 @@ impl Strategy {
 
 #[cfg(test)]
 mod tests {
+    use rust_decimal::Decimal;
     use rust_decimal_macros::dec;
     use tracing::{info, trace};
     use crate::model::{OrderInfo, TraderMsg};
@@ -1047,10 +1048,12 @@ mod tests {
             price: dec!(10),
             client_id: "0001".to_string(),
             filled_price: Default::default(),
-            filled: 0,
+            filled: Decimal::ZERO,
             order_id: "".to_string(),
             local_time: 0,
             create_time: 0,
+            status: "".to_string(),
+            fee: Default::default(),
         });
         trader_msg.cash = dec!(1000);
         trader_msg.coin = dec!(0);