支持按持仓成交均价止盈止损

This commit is contained in:
boris
2026-09-07 05:36:23 +08:00
parent e00777ebc2
commit 929b105173
2 changed files with 77 additions and 0 deletions
@@ -575,6 +575,7 @@ pub enum PlatformExplicitActionStage {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PlatformStopTakeReferencePriceMode {
PositionCostBasis,
PositionAverageEntryPrice,
SignalDayPostAdjustedClose,
}
@@ -11674,6 +11675,7 @@ impl PlatformExprStrategy {
entry_avg_price
}
}
PlatformStopTakeReferencePriceMode::PositionAverageEntryPrice => entry_avg_price,
PlatformStopTakeReferencePriceMode::SignalDayPostAdjustedClose => {
let entry_date = self.position_entry_dates.get(symbol).copied().ok_or_else(|| {
BacktestError::Execution(format!(
@@ -19112,6 +19114,60 @@ mod tests {
.expect("quoted stop check"),
(true, false),
);
let mut borderline_portfolio = PortfolioState::new(1_000_000.0);
let borderline_position = borderline_portfolio.position_mut(symbol);
borderline_position.buy(prev_date, 9_300, 25.495);
borderline_position.average_cost = 25.51;
let borderline_context = StrategyContext {
execution_date: date,
decision_date: date,
decision_index: 40,
data: &with_quote,
portfolio: &borderline_portfolio,
futures_account: None,
open_orders: &[],
dynamic_universe: None,
subscriptions: &subscriptions,
process_events: &[],
active_process_event: None,
active_datetime: None,
order_events: &[],
fills: &[],
};
let mut average_price_cfg = PlatformExprStrategyConfig::microcap_rotation();
average_price_cfg.rotation_enabled = false;
average_price_cfg.matching_type = MatchingType::CurrentBarClose;
average_price_cfg.intraday_execution_time =
Some(NaiveTime::from_hms_opt(9, 30, 0).expect("time"));
average_price_cfg.signal_symbol = symbol.to_string();
average_price_cfg.stop_loss_expr = "holding_return <= -0.08".to_string();
average_price_cfg.take_profit_expr.clear();
average_price_cfg.stop_take_reference_price_mode =
PlatformStopTakeReferencePriceMode::PositionAverageEntryPrice;
let average_price_strategy = PlatformExprStrategy::new(average_price_cfg.clone());
let borderline_day = average_price_strategy
.day_state(&borderline_context, date)
.expect("borderline day state");
assert_eq!(
average_price_strategy
.stop_take_action(&borderline_context, date, date, &borderline_day, symbol,)
.expect("average entry price stop check"),
(false, false),
);
average_price_cfg.stop_take_reference_price_mode =
PlatformStopTakeReferencePriceMode::PositionCostBasis;
let cost_basis_strategy = PlatformExprStrategy::new(average_price_cfg);
let borderline_day = cost_basis_strategy
.day_state(&borderline_context, date)
.expect("cost basis day state");
assert_eq!(
cost_basis_strategy
.stop_take_action(&borderline_context, date, date, &borderline_day, symbol,)
.expect("cost basis stop check"),
(true, false),
);
}
#[test]
@@ -1259,6 +1259,9 @@ fn parse_stop_take_reference_price_mode(
"position_cost_basis" | "position_cost" | "execution_cost_basis" => {
Ok(PlatformStopTakeReferencePriceMode::PositionCostBasis)
}
"position_average_entry_price" | "position_avg_price" | "average_entry_price" => {
Ok(PlatformStopTakeReferencePriceMode::PositionAverageEntryPrice)
}
"signal_day_post_adjusted_close"
| "signal_post_adjusted_close"
| "model_signal_post_adjusted_close" => {
@@ -4497,6 +4500,24 @@ mod tests {
);
}
#[test]
fn parses_position_average_entry_stop_take_reference_price_mode() {
let spec = serde_json::json!({
"runtimeExpressions": {
"risk": {
"stopTakeReferencePriceMode": "position_average_entry_price"
}
}
});
let cfg = platform_expr_config_from_value("", "", &spec).expect("config");
assert_eq!(
cfg.stop_take_reference_price_mode,
PlatformStopTakeReferencePriceMode::PositionAverageEntryPrice
);
}
#[test]
fn rejects_unknown_stop_take_reference_price_mode() {
let spec = serde_json::json!({