Refine jq microcap execution alignment
This commit is contained in:
@@ -594,12 +594,24 @@ where
|
||||
let price = self.sizing_price(snapshot);
|
||||
let snapshot_requested_qty =
|
||||
self.round_buy_quantity(((value.abs()) / price).floor() as u32, round_lot);
|
||||
self.process_buy(
|
||||
let requested_qty = self.maybe_expand_periodic_value_buy_quantity(
|
||||
date,
|
||||
portfolio,
|
||||
data,
|
||||
symbol,
|
||||
snapshot_requested_qty,
|
||||
round_lot,
|
||||
value.abs(),
|
||||
reason,
|
||||
execution_cursors,
|
||||
*global_execution_cursor,
|
||||
);
|
||||
self.process_buy(
|
||||
date,
|
||||
portfolio,
|
||||
data,
|
||||
symbol,
|
||||
requested_qty,
|
||||
reason,
|
||||
intraday_turnover,
|
||||
execution_cursors,
|
||||
@@ -654,83 +666,50 @@ where
|
||||
)
|
||||
.ok()?;
|
||||
let max_requested_qty = market_limited_qty;
|
||||
let start_cursor = execution_cursors
|
||||
.get(symbol)
|
||||
.copied()
|
||||
.into_iter()
|
||||
.chain(global_execution_cursor)
|
||||
.chain(
|
||||
self.intraday_execution_start_time
|
||||
.map(|start_time| date.and_time(start_time)),
|
||||
)
|
||||
.max();
|
||||
let start_cursor = self
|
||||
.intraday_execution_start_time
|
||||
.map(|start_time| date.and_time(start_time));
|
||||
let quotes = data.execution_quotes_on(date, symbol);
|
||||
let estimated = self.select_buy_sizing_fill(
|
||||
if let Some(estimated) = self.select_buy_sizing_fill(
|
||||
quotes,
|
||||
start_cursor,
|
||||
max_requested_qty,
|
||||
round_lot,
|
||||
Some(portfolio.cash()),
|
||||
Some(value_budget),
|
||||
)?;
|
||||
Some(estimated.quantity)
|
||||
) {
|
||||
return Some(estimated.quantity);
|
||||
}
|
||||
|
||||
let execution_price = self.snapshot_execution_price(snapshot, OrderSide::Buy);
|
||||
let fallback_qty = self.affordable_buy_quantity(
|
||||
portfolio.cash(),
|
||||
Some(value_budget),
|
||||
execution_price,
|
||||
max_requested_qty,
|
||||
round_lot,
|
||||
);
|
||||
if fallback_qty > 0 {
|
||||
Some(fallback_qty)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_expand_periodic_value_buy_quantity(
|
||||
&self,
|
||||
date: NaiveDate,
|
||||
portfolio: &PortfolioState,
|
||||
data: &DataSet,
|
||||
symbol: &str,
|
||||
_date: NaiveDate,
|
||||
_portfolio: &PortfolioState,
|
||||
_data: &DataSet,
|
||||
_symbol: &str,
|
||||
requested_qty: u32,
|
||||
round_lot: u32,
|
||||
value_budget: f64,
|
||||
reason: &str,
|
||||
execution_cursors: &BTreeMap<String, NaiveDateTime>,
|
||||
global_execution_cursor: Option<NaiveDateTime>,
|
||||
_round_lot: u32,
|
||||
_value_budget: f64,
|
||||
_reason: &str,
|
||||
_execution_cursors: &BTreeMap<String, NaiveDateTime>,
|
||||
_global_execution_cursor: Option<NaiveDateTime>,
|
||||
) -> u32 {
|
||||
const PERIODIC_BUY_OVERSHOOT_TOLERANCE: f64 = 400.0;
|
||||
|
||||
if requested_qty == 0 || reason != "periodic_rebalance_buy" {
|
||||
return requested_qty;
|
||||
}
|
||||
|
||||
let candidate_qty = requested_qty.saturating_add(round_lot.max(1));
|
||||
let start_cursor = execution_cursors
|
||||
.get(symbol)
|
||||
.copied()
|
||||
.into_iter()
|
||||
.chain(global_execution_cursor)
|
||||
.chain(
|
||||
self.intraday_execution_start_time
|
||||
.map(|start_time| date.and_time(start_time)),
|
||||
)
|
||||
.max();
|
||||
let quotes = data.execution_quotes_on(date, symbol);
|
||||
let Some(fill) = self.select_execution_fill(
|
||||
quotes,
|
||||
OrderSide::Buy,
|
||||
start_cursor,
|
||||
candidate_qty,
|
||||
round_lot,
|
||||
Some(portfolio.cash()),
|
||||
None,
|
||||
) else {
|
||||
return requested_qty;
|
||||
};
|
||||
if fill.quantity < candidate_qty {
|
||||
return requested_qty;
|
||||
}
|
||||
let candidate_gross = fill.price * fill.quantity as f64;
|
||||
let candidate_cost = self.cost_model.calculate(OrderSide::Buy, candidate_gross);
|
||||
let candidate_cash_out = candidate_gross + candidate_cost.total();
|
||||
if candidate_cash_out <= value_budget + PERIODIC_BUY_OVERSHOOT_TOLERANCE
|
||||
&& candidate_cash_out <= portfolio.cash() + 1e-6
|
||||
{
|
||||
candidate_qty
|
||||
} else {
|
||||
requested_qty
|
||||
}
|
||||
requested_qty
|
||||
}
|
||||
|
||||
fn select_buy_sizing_fill(
|
||||
@@ -925,7 +904,7 @@ where
|
||||
execution_cursors,
|
||||
None,
|
||||
Some(portfolio.cash()),
|
||||
None,
|
||||
value_budget.map(|budget| budget + 400.0),
|
||||
);
|
||||
let (filled_qty, execution_price) = if let Some(fill) = fill {
|
||||
execution_cursors.insert(symbol.to_string(), fill.next_cursor);
|
||||
@@ -937,7 +916,7 @@ where
|
||||
let execution_price = self.snapshot_execution_price(snapshot, OrderSide::Buy);
|
||||
let filled_qty = self.affordable_buy_quantity(
|
||||
portfolio.cash(),
|
||||
value_budget,
|
||||
value_budget.map(|budget| budget + 400.0),
|
||||
execution_price,
|
||||
constrained_qty,
|
||||
self.round_lot(data, symbol),
|
||||
@@ -1148,6 +1127,23 @@ where
|
||||
return None;
|
||||
}
|
||||
|
||||
let start_cursor = self
|
||||
.intraday_execution_start_time
|
||||
.map(|start_time| date.and_time(start_time));
|
||||
let quotes = data.execution_quotes_on(date, symbol);
|
||||
|
||||
if let Some(fill) = self.select_execution_fill(
|
||||
quotes,
|
||||
side,
|
||||
start_cursor,
|
||||
requested_qty,
|
||||
round_lot,
|
||||
cash_limit,
|
||||
gross_limit,
|
||||
) {
|
||||
return Some(fill);
|
||||
}
|
||||
|
||||
if self.intraday_execution_start_time.is_some() {
|
||||
let execution_price = self.snapshot_execution_price(snapshot, side);
|
||||
let quantity = match side {
|
||||
@@ -1174,26 +1170,7 @@ where
|
||||
});
|
||||
}
|
||||
|
||||
let start_cursor = execution_cursors
|
||||
.get(symbol)
|
||||
.copied()
|
||||
.into_iter()
|
||||
.chain(global_execution_cursor)
|
||||
.chain(
|
||||
self.intraday_execution_start_time
|
||||
.map(|start_time| date.and_time(start_time)),
|
||||
)
|
||||
.max();
|
||||
let quotes = data.execution_quotes_on(date, symbol);
|
||||
self.select_execution_fill(
|
||||
quotes,
|
||||
side,
|
||||
start_cursor,
|
||||
requested_qty,
|
||||
round_lot,
|
||||
cash_limit,
|
||||
gross_limit,
|
||||
)
|
||||
None
|
||||
}
|
||||
|
||||
fn select_execution_fill(
|
||||
@@ -1339,13 +1316,8 @@ where
|
||||
}
|
||||
|
||||
fn uses_serial_execution_cursor(&self, reason: &str) -> bool {
|
||||
matches!(
|
||||
reason,
|
||||
"stop_loss_exit"
|
||||
| "take_profit_exit"
|
||||
| "replacement_after_stop_loss_exit"
|
||||
| "replacement_after_take_profit_exit"
|
||||
)
|
||||
let _ = reason;
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user