修正跌停卖出未成交仓位释放

This commit is contained in:
boris
2026-06-19 14:25:10 +08:00
parent 57aebe97ec
commit c3a5161db1
+37 -65
View File
@@ -1671,27 +1671,6 @@ impl PlatformExprStrategy {
.unwrap_or(true) .unwrap_or(true)
} }
fn projected_target_zero_would_fill(
&self,
ctx: &StrategyContext<'_>,
projected: &PortfolioState,
date: NaiveDate,
symbol: &str,
execution_state: &ProjectedExecutionState,
) -> bool {
let mut trial_projected = projected.clone();
let mut trial_execution_state = execution_state.clone();
self.project_target_zero(
ctx,
&mut trial_projected,
date,
symbol,
&mut trial_execution_state,
)
.is_some()
&& Self::projected_position_is_flat(&trial_projected, symbol)
}
fn projected_position_value_at_execution_price( fn projected_position_value_at_execution_price(
&self, &self,
ctx: &StrategyContext<'_>, ctx: &StrategyContext<'_>,
@@ -6693,15 +6672,11 @@ impl Strategy for PlatformExprStrategy {
let (stop_hit, profit_hit) = let (stop_hit, profit_hit) =
self.stop_take_action_for_position(ctx, execution_date, &day, position)?; self.stop_take_action_for_position(ctx, execution_date, &day, position)?;
if stop_hit { if stop_hit {
if self.projected_target_zero_would_fill(
ctx,
&projected,
execution_date,
&position.symbol,
&projected_execution_state,
) {
pending_full_close_symbols.insert(position.symbol.clone()); pending_full_close_symbols.insert(position.symbol.clone());
continue;
} }
if profit_hit {
pending_full_close_symbols.insert(position.symbol.clone());
continue; continue;
} }
let can_sell = self.can_sell_position(ctx, execution_date, &position.symbol); let can_sell = self.can_sell_position(ctx, execution_date, &position.symbol);
@@ -6725,17 +6700,6 @@ impl Strategy for PlatformExprStrategy {
continue; continue;
} }
} }
if profit_hit {
if self.projected_target_zero_would_fill(
ctx,
&projected,
execution_date,
&position.symbol,
&projected_execution_state,
) {
pending_full_close_symbols.insert(position.symbol.clone());
}
}
} }
} }
@@ -6834,9 +6798,6 @@ impl Strategy for PlatformExprStrategy {
target_value: 0.0, target_value: 0.0,
reason: "stop_loss_exit".to_string(), reason: "stop_loss_exit".to_string(),
}); });
if self.config.release_slot_on_exit_signal {
same_day_sold_symbols.insert(position.symbol.clone());
}
if can_sell { if can_sell {
if self if self
.project_target_zero( .project_target_zero(
@@ -6885,9 +6846,6 @@ impl Strategy for PlatformExprStrategy {
target_value: 0.0, target_value: 0.0,
reason: "take_profit_exit".to_string(), reason: "take_profit_exit".to_string(),
}); });
if self.config.release_slot_on_exit_signal {
same_day_sold_symbols.insert(position.symbol.clone());
}
if can_sell if can_sell
&& self && self
.project_target_zero( .project_target_zero(
@@ -14196,7 +14154,7 @@ mod tests {
} }
#[test] #[test]
fn platform_weak_market_keeps_positive_adjust_when_stop_loss_sell_blocked() { fn platform_weak_market_skips_positive_adjust_when_stop_loss_sell_blocked() {
let prev_date = d(2025, 2, 2); let prev_date = d(2025, 2, 2);
let date = d(2025, 2, 3); let date = d(2025, 2, 3);
let symbols = ["000001.SZ", "000002.SZ"]; let symbols = ["000001.SZ", "000002.SZ"];
@@ -14319,7 +14277,8 @@ mod tests {
let decision = strategy.on_day(&ctx).expect("platform decision"); let decision = strategy.on_day(&ctx).expect("platform decision");
assert!(decision.order_intents.iter().any(|intent| matches!( assert!(
!decision.order_intents.iter().any(|intent| matches!(
intent, intent,
OrderIntent::Shares { OrderIntent::Shares {
symbol, symbol,
@@ -14328,7 +14287,10 @@ mod tests {
} if symbol == "000001.SZ" } if symbol == "000001.SZ"
&& reason == "daily_position_target_adjust" && reason == "daily_position_target_adjust"
&& *quantity > 0 && *quantity > 0
))); )),
"{:?}",
decision.order_intents
);
assert!(decision.order_intents.iter().any(|intent| matches!( assert!(decision.order_intents.iter().any(|intent| matches!(
intent, intent,
OrderIntent::TargetValue { OrderIntent::TargetValue {
@@ -14340,7 +14302,7 @@ mod tests {
} }
#[test] #[test]
fn platform_weak_market_keeps_positive_adjust_when_intraday_stop_loss_sell_blocked() { fn platform_weak_market_skips_positive_adjust_when_intraday_stop_loss_sell_blocked() {
let prev_date = d(2025, 2, 2); let prev_date = d(2025, 2, 2);
let date = d(2025, 2, 3); let date = d(2025, 2, 3);
let symbols = ["000001.SZ", "000002.SZ"]; let symbols = ["000001.SZ", "000002.SZ"];
@@ -14482,7 +14444,8 @@ mod tests {
let decision = strategy.on_day(&ctx).expect("platform decision"); let decision = strategy.on_day(&ctx).expect("platform decision");
assert!(decision.order_intents.iter().any(|intent| matches!( assert!(
!decision.order_intents.iter().any(|intent| matches!(
intent, intent,
OrderIntent::Shares { OrderIntent::Shares {
symbol, symbol,
@@ -14491,7 +14454,10 @@ mod tests {
} if symbol == "000001.SZ" } if symbol == "000001.SZ"
&& reason == "daily_position_target_adjust" && reason == "daily_position_target_adjust"
&& *quantity > 0 && *quantity > 0
))); )),
"{:?}",
decision.order_intents
);
assert!(decision.order_intents.iter().any(|intent| matches!( assert!(decision.order_intents.iter().any(|intent| matches!(
intent, intent,
OrderIntent::TargetValue { OrderIntent::TargetValue {
@@ -15547,7 +15513,7 @@ mod tests {
} }
#[test] #[test]
fn platform_daily_top_up_can_release_unsellable_exit_signal_slot() { fn platform_daily_top_up_does_not_release_unsellable_exit_signal_slot() {
let prev_date = d(2026, 3, 31); let prev_date = d(2026, 3, 31);
let date = d(2026, 4, 1); let date = d(2026, 4, 1);
let symbols = ["000001.SZ", "000002.SZ", "000003.SZ"]; let symbols = ["000001.SZ", "000002.SZ", "000003.SZ"];
@@ -15687,14 +15653,10 @@ mod tests {
decision.order_intents decision.order_intents
); );
assert!( assert!(
decision.order_intents.iter().any(|intent| matches!( !decision
intent, .order_intents
OrderIntent::Value { .iter()
symbol, .any(|intent| matches!(intent, OrderIntent::Value { .. })),
reason,
..
} if symbol == "000003.SZ" && reason == "daily_top_up_buy"
)),
"{:?}", "{:?}",
decision.order_intents decision.order_intents
); );
@@ -16695,10 +16657,15 @@ mod tests {
"{:?}", "{:?}",
decision.diagnostics decision.diagnostics
); );
assert!(matches!( assert!(
matches!(
decision.order_intents.first(), decision.order_intents.first(),
Some(crate::strategy::OrderIntent::Value { symbol, .. }) if symbol == "300002.SZ" Some(crate::strategy::OrderIntent::Value { symbol, .. }) if symbol == "300002.SZ"
)); ),
"intents={:?} diagnostics={:?}",
decision.order_intents,
decision.diagnostics
);
} }
#[test] #[test]
@@ -16870,10 +16837,15 @@ mod tests {
"{:?}", "{:?}",
decision.diagnostics decision.diagnostics
); );
assert!(matches!( assert!(
matches!(
decision.order_intents.first(), decision.order_intents.first(),
Some(crate::strategy::OrderIntent::Value { symbol, .. }) if symbol == "300002.SZ" Some(crate::strategy::OrderIntent::Value { symbol, .. }) if symbol == "300001.SZ"
)); ),
"intents={:?} diagnostics={:?}",
decision.order_intents,
decision.diagnostics
);
} }
#[test] #[test]