支持按持仓成交均价止盈止损
This commit is contained in:
@@ -575,6 +575,7 @@ pub enum PlatformExplicitActionStage {
|
|||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum PlatformStopTakeReferencePriceMode {
|
pub enum PlatformStopTakeReferencePriceMode {
|
||||||
PositionCostBasis,
|
PositionCostBasis,
|
||||||
|
PositionAverageEntryPrice,
|
||||||
SignalDayPostAdjustedClose,
|
SignalDayPostAdjustedClose,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -11674,6 +11675,7 @@ impl PlatformExprStrategy {
|
|||||||
entry_avg_price
|
entry_avg_price
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
PlatformStopTakeReferencePriceMode::PositionAverageEntryPrice => entry_avg_price,
|
||||||
PlatformStopTakeReferencePriceMode::SignalDayPostAdjustedClose => {
|
PlatformStopTakeReferencePriceMode::SignalDayPostAdjustedClose => {
|
||||||
let entry_date = self.position_entry_dates.get(symbol).copied().ok_or_else(|| {
|
let entry_date = self.position_entry_dates.get(symbol).copied().ok_or_else(|| {
|
||||||
BacktestError::Execution(format!(
|
BacktestError::Execution(format!(
|
||||||
@@ -19112,6 +19114,60 @@ mod tests {
|
|||||||
.expect("quoted stop check"),
|
.expect("quoted stop check"),
|
||||||
(true, false),
|
(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]
|
#[test]
|
||||||
|
|||||||
@@ -1259,6 +1259,9 @@ fn parse_stop_take_reference_price_mode(
|
|||||||
"position_cost_basis" | "position_cost" | "execution_cost_basis" => {
|
"position_cost_basis" | "position_cost" | "execution_cost_basis" => {
|
||||||
Ok(PlatformStopTakeReferencePriceMode::PositionCostBasis)
|
Ok(PlatformStopTakeReferencePriceMode::PositionCostBasis)
|
||||||
}
|
}
|
||||||
|
"position_average_entry_price" | "position_avg_price" | "average_entry_price" => {
|
||||||
|
Ok(PlatformStopTakeReferencePriceMode::PositionAverageEntryPrice)
|
||||||
|
}
|
||||||
"signal_day_post_adjusted_close"
|
"signal_day_post_adjusted_close"
|
||||||
| "signal_post_adjusted_close"
|
| "signal_post_adjusted_close"
|
||||||
| "model_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]
|
#[test]
|
||||||
fn rejects_unknown_stop_take_reference_price_mode() {
|
fn rejects_unknown_stop_take_reference_price_mode() {
|
||||||
let spec = serde_json::json!({
|
let spec = serde_json::json!({
|
||||||
|
|||||||
Reference in New Issue
Block a user