对齐ALV补仓订单返回语义
This commit is contained in:
@@ -357,6 +357,36 @@ struct ProjectedExecutionFill {
|
||||
next_cursor: NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
struct ProjectedOrderValueResult {
|
||||
submitted_quantity: u32,
|
||||
filled_quantity: u32,
|
||||
}
|
||||
|
||||
impl ProjectedOrderValueResult {
|
||||
fn not_submitted() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn submitted_without_fill(submitted_quantity: u32) -> Self {
|
||||
Self {
|
||||
submitted_quantity,
|
||||
filled_quantity: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn submitted_with_fill(submitted_quantity: u32, filled_quantity: u32) -> Self {
|
||||
Self {
|
||||
submitted_quantity,
|
||||
filled_quantity,
|
||||
}
|
||||
}
|
||||
|
||||
fn was_submitted(&self) -> bool {
|
||||
self.submitted_quantity > 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct DayExpressionState {
|
||||
date: NaiveDate,
|
||||
@@ -2037,9 +2067,9 @@ impl PlatformExprStrategy {
|
||||
return None;
|
||||
}
|
||||
if cash_delta > 0.0 {
|
||||
let filled =
|
||||
let result =
|
||||
self.project_order_value(ctx, projected, date, symbol, cash_delta, execution_state);
|
||||
return (filled > 0).then_some(filled);
|
||||
return (result.filled_quantity > 0).then_some(result.filled_quantity);
|
||||
}
|
||||
|
||||
if !Self::defer_projection_execution_risk(ctx, date)
|
||||
@@ -2235,23 +2265,23 @@ impl PlatformExprStrategy {
|
||||
symbol: &str,
|
||||
order_value: f64,
|
||||
execution_state: &mut ProjectedExecutionState,
|
||||
) -> u32 {
|
||||
) -> ProjectedOrderValueResult {
|
||||
if order_value <= 0.0 {
|
||||
return 0;
|
||||
return ProjectedOrderValueResult::not_submitted();
|
||||
}
|
||||
let round_lot = self.projected_round_lot(ctx, symbol);
|
||||
let minimum_order_quantity = self.projected_minimum_order_quantity(ctx, symbol);
|
||||
let order_step_size = self.projected_order_step_size(ctx, symbol);
|
||||
let market = match ctx.data.market(date, symbol) {
|
||||
Some(market) => market,
|
||||
None => return 0,
|
||||
None => return ProjectedOrderValueResult::not_submitted(),
|
||||
};
|
||||
let stock = match self.stock_state(ctx, date, symbol) {
|
||||
Ok(stock) => stock,
|
||||
Err(BacktestError::Data(crate::data::DataSetError::MissingSnapshot { .. })) => {
|
||||
return 0;
|
||||
return ProjectedOrderValueResult::not_submitted();
|
||||
}
|
||||
Err(_) => return 0,
|
||||
Err(_) => return ProjectedOrderValueResult::not_submitted(),
|
||||
};
|
||||
if !Self::defer_projection_execution_risk(ctx, date)
|
||||
&& self
|
||||
@@ -2260,7 +2290,7 @@ impl PlatformExprStrategy {
|
||||
.flatten()
|
||||
.is_some()
|
||||
{
|
||||
return 0;
|
||||
return ProjectedOrderValueResult::not_submitted();
|
||||
}
|
||||
let raw_sizing_price = if self.config.aiquant_transaction_cost {
|
||||
self.aiquant_scheduled_last_price(ctx, date, symbol)
|
||||
@@ -2274,7 +2304,7 @@ impl PlatformExprStrategy {
|
||||
raw_sizing_price
|
||||
};
|
||||
if !sizing_price.is_finite() || sizing_price <= 0.0 {
|
||||
return 0;
|
||||
return ProjectedOrderValueResult::not_submitted();
|
||||
}
|
||||
let snapshot_requested_qty = self.value_buy_quantity(
|
||||
projected.cash().min(order_value),
|
||||
@@ -2304,8 +2334,9 @@ impl PlatformExprStrategy {
|
||||
self.decrement_order_quantity(quantity, minimum_order_quantity, order_step_size);
|
||||
}
|
||||
if quantity == 0 {
|
||||
return 0;
|
||||
return ProjectedOrderValueResult::not_submitted();
|
||||
}
|
||||
let submitted_quantity = quantity;
|
||||
let defer_projection_execution_risk = Self::defer_projection_execution_risk(ctx, date);
|
||||
let fill = self
|
||||
.projected_select_execution_fill(
|
||||
@@ -2363,12 +2394,12 @@ impl PlatformExprStrategy {
|
||||
}
|
||||
});
|
||||
let Some(fill) = fill else {
|
||||
return 0;
|
||||
return ProjectedOrderValueResult::submitted_without_fill(submitted_quantity);
|
||||
};
|
||||
let gross_amount = fill.price * fill.quantity as f64;
|
||||
let cash_out = gross_amount + self.buy_commission(gross_amount);
|
||||
if cash_out > cash_limit + 1e-6 {
|
||||
return 0;
|
||||
return ProjectedOrderValueResult::submitted_without_fill(submitted_quantity);
|
||||
}
|
||||
projected.apply_cash_delta(-cash_out);
|
||||
projected
|
||||
@@ -2381,7 +2412,7 @@ impl PlatformExprStrategy {
|
||||
execution_state
|
||||
.execution_cursors
|
||||
.insert(symbol.to_string(), fill.next_cursor);
|
||||
fill.quantity
|
||||
ProjectedOrderValueResult::submitted_with_fill(submitted_quantity, fill.quantity)
|
||||
}
|
||||
|
||||
fn defer_projection_execution_risk(ctx: &StrategyContext<'_>, date: NaiveDate) -> bool {
|
||||
@@ -8600,7 +8631,7 @@ impl Strategy for PlatformExprStrategy {
|
||||
intraday_attempted_buys.insert(symbol.clone());
|
||||
attempted_any = true;
|
||||
let cash_before_buy = projected.cash();
|
||||
let filled_qty = self.project_order_value(
|
||||
let order_result = self.project_order_value(
|
||||
ctx,
|
||||
&mut projected,
|
||||
projection_date,
|
||||
@@ -8608,15 +8639,13 @@ impl Strategy for PlatformExprStrategy {
|
||||
buy_cash,
|
||||
&mut projected_execution_state,
|
||||
);
|
||||
if filled_qty > 0 {
|
||||
if order_result.was_submitted() {
|
||||
order_intents.push(OrderIntent::Value {
|
||||
symbol: symbol.clone(),
|
||||
value: buy_cash,
|
||||
reason: "daily_top_up_buy".to_string(),
|
||||
});
|
||||
self.remember_position_entry_date(symbol, signal_date);
|
||||
}
|
||||
if filled_qty > 0 {
|
||||
let spent = (cash_before_buy - projected.cash()).max(0.0);
|
||||
aiquant_available_cash = (aiquant_available_cash - spent).max(0.0);
|
||||
working_symbols.insert(symbol.clone());
|
||||
@@ -8628,7 +8657,7 @@ impl Strategy for PlatformExprStrategy {
|
||||
execution_date,
|
||||
symbol,
|
||||
buy_cash,
|
||||
filled_qty,
|
||||
order_result.filled_quantity,
|
||||
spent,
|
||||
aiquant_available_cash
|
||||
));
|
||||
@@ -8759,7 +8788,7 @@ impl Strategy for PlatformExprStrategy {
|
||||
continue;
|
||||
}
|
||||
let cash_before_buy = projected.cash();
|
||||
let filled_qty = self.project_order_value(
|
||||
let order_result = self.project_order_value(
|
||||
ctx,
|
||||
&mut projected,
|
||||
projection_date,
|
||||
@@ -8767,7 +8796,7 @@ impl Strategy for PlatformExprStrategy {
|
||||
buy_cash,
|
||||
&mut projected_execution_state,
|
||||
);
|
||||
if filled_qty > 0 {
|
||||
if order_result.was_submitted() {
|
||||
order_intents.push(OrderIntent::Value {
|
||||
symbol: symbol.clone(),
|
||||
value: buy_cash,
|
||||
@@ -8855,7 +8884,7 @@ impl Strategy for PlatformExprStrategy {
|
||||
continue;
|
||||
}
|
||||
let cash_before_buy = projected.cash();
|
||||
let filled_qty = self.project_order_value(
|
||||
let order_result = self.project_order_value(
|
||||
ctx,
|
||||
&mut projected,
|
||||
projection_date,
|
||||
@@ -8863,7 +8892,7 @@ impl Strategy for PlatformExprStrategy {
|
||||
buy_cash,
|
||||
&mut projected_execution_state,
|
||||
);
|
||||
if filled_qty > 0 {
|
||||
if order_result.was_submitted() {
|
||||
order_intents.push(OrderIntent::Value {
|
||||
symbol: symbol.clone(),
|
||||
value: buy_cash,
|
||||
@@ -10790,7 +10819,7 @@ mod tests {
|
||||
|
||||
let mut projected = portfolio.clone();
|
||||
let mut execution_state = super::ProjectedExecutionState::default();
|
||||
let filled = strategy.project_order_value(
|
||||
let result = strategy.project_order_value(
|
||||
&ctx,
|
||||
&mut projected,
|
||||
date,
|
||||
@@ -10799,7 +10828,7 @@ mod tests {
|
||||
&mut execution_state,
|
||||
);
|
||||
|
||||
assert_eq!(filled, 24_400);
|
||||
assert_eq!(result.filled_quantity, 24_400);
|
||||
let position = projected.position(symbol).expect("position");
|
||||
assert_eq!(position.quantity, 24_400);
|
||||
assert!((position.average_cost - 5.12022).abs() < 1e-9);
|
||||
@@ -10924,7 +10953,7 @@ mod tests {
|
||||
|
||||
let mut projected = portfolio.clone();
|
||||
let mut execution_state = super::ProjectedExecutionState::default();
|
||||
let filled = strategy.project_order_value(
|
||||
let result = strategy.project_order_value(
|
||||
&ctx,
|
||||
&mut projected,
|
||||
date,
|
||||
@@ -10933,7 +10962,7 @@ mod tests {
|
||||
&mut execution_state,
|
||||
);
|
||||
|
||||
assert_eq!(filled, 24_400);
|
||||
assert_eq!(result.filled_quantity, 24_400);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -19445,7 +19474,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn platform_daily_top_up_continues_after_unfilled_candidate() {
|
||||
fn platform_daily_top_up_counts_submitted_zero_fill_candidate_as_strategy_slot() {
|
||||
let prev_date = d(2025, 2, 2);
|
||||
let date = d(2025, 2, 3);
|
||||
let symbols = ["000001.SZ", "000002.SZ", "000003.SZ"];
|
||||
@@ -19590,7 +19619,7 @@ mod tests {
|
||||
symbol,
|
||||
reason,
|
||||
..
|
||||
} if symbol == "000002.SZ" && reason == "daily_top_up_buy"
|
||||
} if symbol == "000003.SZ" && reason == "daily_top_up_buy"
|
||||
)),
|
||||
"{:?}",
|
||||
decision.order_intents
|
||||
@@ -19598,7 +19627,7 @@ mod tests {
|
||||
assert!(
|
||||
!decision.order_intents.iter().any(|intent| matches!(
|
||||
intent,
|
||||
OrderIntent::Value { symbol, .. } if symbol == "000003.SZ"
|
||||
OrderIntent::Value { symbol, .. } if symbol == "000002.SZ"
|
||||
)),
|
||||
"{:?}",
|
||||
decision.order_intents
|
||||
@@ -23409,7 +23438,7 @@ mod tests {
|
||||
|
||||
assert!(ctx.data.execution_quotes_on(date, symbol).is_empty());
|
||||
assert!(!ctx.data.execution_quotes_on(date, other_symbol).is_empty());
|
||||
let filled = strategy.project_order_value(
|
||||
let result = strategy.project_order_value(
|
||||
&ctx,
|
||||
&mut projected,
|
||||
date,
|
||||
@@ -23418,7 +23447,7 @@ mod tests {
|
||||
&mut execution_state,
|
||||
);
|
||||
|
||||
assert_eq!(filled, 0);
|
||||
assert_eq!(result.filled_quantity, 0);
|
||||
assert_eq!(
|
||||
projected.position(symbol).map(|position| position.quantity),
|
||||
None
|
||||
@@ -23560,7 +23589,7 @@ mod tests {
|
||||
.execution_quotes_on(decision_date, other_symbol)
|
||||
.is_empty()
|
||||
);
|
||||
let filled = strategy.project_order_value(
|
||||
let result = strategy.project_order_value(
|
||||
&ctx,
|
||||
&mut projected,
|
||||
decision_date,
|
||||
@@ -23569,10 +23598,10 @@ mod tests {
|
||||
&mut execution_state,
|
||||
);
|
||||
|
||||
assert!(filled > 0);
|
||||
assert!(result.filled_quantity > 0);
|
||||
assert_eq!(
|
||||
projected.position(symbol).map(|position| position.quantity),
|
||||
Some(filled)
|
||||
Some(result.filled_quantity)
|
||||
);
|
||||
assert!(projected.cash() < portfolio.cash());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user