Tighten market order residual fill semantics
This commit is contained in:
@@ -716,7 +716,6 @@ where
|
|||||||
let mut filled_qty = 0_u32;
|
let mut filled_qty = 0_u32;
|
||||||
let mut gross_amount = 0.0_f64;
|
let mut gross_amount = 0.0_f64;
|
||||||
let mut last_timestamp = None;
|
let mut last_timestamp = None;
|
||||||
let mut last_quote_price = None;
|
|
||||||
|
|
||||||
for quote in quotes {
|
for quote in quotes {
|
||||||
if start_cursor.is_some_and(|cursor| quote.timestamp < cursor) {
|
if start_cursor.is_some_and(|cursor| quote.timestamp < cursor) {
|
||||||
@@ -728,10 +727,6 @@ where
|
|||||||
} else {
|
} else {
|
||||||
quote.buy_price()
|
quote.buy_price()
|
||||||
};
|
};
|
||||||
if fallback_quote_price.is_some() {
|
|
||||||
last_quote_price = fallback_quote_price;
|
|
||||||
last_timestamp = Some(quote.timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
if quote.volume_delta == 0 {
|
if quote.volume_delta == 0 {
|
||||||
continue;
|
continue;
|
||||||
@@ -784,35 +779,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if filled_qty < requested_qty {
|
|
||||||
let remaining_qty = requested_qty.saturating_sub(filled_qty);
|
|
||||||
let mut residual_qty = self.round_buy_quantity(remaining_qty, lot);
|
|
||||||
if residual_qty > 0 {
|
|
||||||
if let Some(residual_price) = last_quote_price {
|
|
||||||
if let Some(cash) = cash_limit {
|
|
||||||
while residual_qty > 0 {
|
|
||||||
let candidate_gross =
|
|
||||||
gross_amount + residual_price * residual_qty as f64;
|
|
||||||
if gross_limit.is_some_and(|limit| candidate_gross > limit + 1e-6) {
|
|
||||||
residual_qty = residual_qty.saturating_sub(lot);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let candidate_cost =
|
|
||||||
self.cost_model.calculate(OrderSide::Buy, candidate_gross);
|
|
||||||
if candidate_gross + candidate_cost.total() <= cash + 1e-6 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
residual_qty = residual_qty.saturating_sub(lot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if residual_qty > 0 {
|
|
||||||
gross_amount += residual_price * residual_qty as f64;
|
|
||||||
filled_qty += residual_qty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if filled_qty == 0 {
|
if filled_qty == 0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -1177,22 +1143,12 @@ where
|
|||||||
let mut filled_qty = 0_u32;
|
let mut filled_qty = 0_u32;
|
||||||
let mut gross_amount = 0.0_f64;
|
let mut gross_amount = 0.0_f64;
|
||||||
let mut last_timestamp = None;
|
let mut last_timestamp = None;
|
||||||
let mut last_quote_price = None;
|
|
||||||
|
|
||||||
for quote in quotes {
|
for quote in quotes {
|
||||||
if start_cursor.is_some_and(|cursor| quote.timestamp < cursor) {
|
if start_cursor.is_some_and(|cursor| quote.timestamp < cursor) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let fallback_quote_price = match side {
|
|
||||||
OrderSide::Buy => quote.buy_price(),
|
|
||||||
OrderSide::Sell => quote.sell_price(),
|
|
||||||
};
|
|
||||||
if fallback_quote_price.is_some() {
|
|
||||||
last_quote_price = fallback_quote_price;
|
|
||||||
last_timestamp = Some(quote.timestamp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Approximate JoinQuant market-order fills with the evolving L1 book after
|
// Approximate JoinQuant market-order fills with the evolving L1 book after
|
||||||
// the decision time instead of trade VWAP. This keeps quantities/prices
|
// the decision time instead of trade VWAP. This keeps quantities/prices
|
||||||
// closer to the observed 10:18 execution logs.
|
// closer to the observed 10:18 execution logs.
|
||||||
@@ -1256,37 +1212,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if filled_qty < requested_qty {
|
|
||||||
let remaining_qty = requested_qty.saturating_sub(filled_qty);
|
|
||||||
let mut residual_qty = self.round_buy_quantity(remaining_qty, lot);
|
|
||||||
if residual_qty > 0 {
|
|
||||||
if let Some(residual_price) = last_quote_price {
|
|
||||||
if let Some(cash) = cash_limit {
|
|
||||||
while residual_qty > 0 {
|
|
||||||
let candidate_gross =
|
|
||||||
gross_amount + residual_price * residual_qty as f64;
|
|
||||||
if gross_limit.is_some_and(|limit| candidate_gross > limit + 1e-6) {
|
|
||||||
residual_qty = residual_qty.saturating_sub(lot);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if candidate_gross <= cash + 1e-6 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
residual_qty = residual_qty.saturating_sub(lot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if residual_qty > 0 {
|
|
||||||
let execution_price = match side {
|
|
||||||
OrderSide::Buy => residual_price,
|
|
||||||
OrderSide::Sell => residual_price,
|
|
||||||
};
|
|
||||||
gross_amount += execution_price * residual_qty as f64;
|
|
||||||
filled_qty += residual_qty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if filled_qty == 0 {
|
if filled_qty == 0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -820,7 +820,6 @@ impl JqMicroCapStrategy {
|
|||||||
let mut filled_qty = 0_u32;
|
let mut filled_qty = 0_u32;
|
||||||
let mut gross_amount = 0.0_f64;
|
let mut gross_amount = 0.0_f64;
|
||||||
let mut last_timestamp = None;
|
let mut last_timestamp = None;
|
||||||
let mut last_quote_price = None;
|
|
||||||
|
|
||||||
for quote in quotes {
|
for quote in quotes {
|
||||||
if start_cursor.is_some_and(|cursor| quote.timestamp < cursor) {
|
if start_cursor.is_some_and(|cursor| quote.timestamp < cursor) {
|
||||||
@@ -838,7 +837,6 @@ impl JqMicroCapStrategy {
|
|||||||
OrderSide::Sell => quote.sell_price(),
|
OrderSide::Sell => quote.sell_price(),
|
||||||
};
|
};
|
||||||
if fallback_quote_price.is_some() {
|
if fallback_quote_price.is_some() {
|
||||||
last_quote_price = fallback_quote_price;
|
|
||||||
last_timestamp = Some(quote.timestamp);
|
last_timestamp = Some(quote.timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -893,33 +891,6 @@ impl JqMicroCapStrategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if filled_qty < requested_qty {
|
|
||||||
let remaining_qty = requested_qty.saturating_sub(filled_qty);
|
|
||||||
let mut residual_qty = self.round_lot_quantity(remaining_qty, lot);
|
|
||||||
if residual_qty > 0 {
|
|
||||||
if let Some(residual_price) = last_quote_price {
|
|
||||||
if let Some(cash) = cash_limit {
|
|
||||||
while residual_qty > 0 {
|
|
||||||
let candidate_gross =
|
|
||||||
gross_amount + residual_price * residual_qty as f64;
|
|
||||||
if gross_limit.is_some_and(|limit| candidate_gross > limit + 1e-6) {
|
|
||||||
residual_qty = residual_qty.saturating_sub(lot);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if candidate_gross <= cash + 1e-6 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
residual_qty = residual_qty.saturating_sub(lot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if residual_qty > 0 {
|
|
||||||
gross_amount += residual_price * residual_qty as f64;
|
|
||||||
filled_qty += residual_qty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if filled_qty == 0 {
|
if filled_qty == 0 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user