|
|
@@ -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())
|
|
|
}
|
|
|
}
|