修正回测执行时tick取价
This commit is contained in:
@@ -291,6 +291,10 @@ impl<C, R> BrokerSimulator<C, R> {
|
|||||||
self.execution_price_field
|
self.execution_price_field
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn intraday_execution_start_time(&self) -> Option<NaiveTime> {
|
||||||
|
self.intraday_execution_start_time
|
||||||
|
}
|
||||||
|
|
||||||
pub fn open_order_views(&self) -> Vec<OpenOrderView> {
|
pub fn open_order_views(&self) -> Vec<OpenOrderView> {
|
||||||
self.open_orders
|
self.open_orders
|
||||||
.borrow()
|
.borrow()
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use thiserror::Error;
|
|||||||
|
|
||||||
use crate::broker::{BrokerExecutionReport, BrokerSimulator, MatchingType};
|
use crate::broker::{BrokerExecutionReport, BrokerSimulator, MatchingType};
|
||||||
use crate::cost::CostModel;
|
use crate::cost::CostModel;
|
||||||
use crate::data::{BenchmarkSnapshot, DataSet, DataSetError, PriceField};
|
use crate::data::{BenchmarkSnapshot, DataSet, DataSetError, IntradayExecutionQuote, PriceField};
|
||||||
use crate::event_bus::{BacktestProcessMod, BacktestProcessModLoader, ProcessEventBus};
|
use crate::event_bus::{BacktestProcessMod, BacktestProcessModLoader, ProcessEventBus};
|
||||||
use crate::events::{
|
use crate::events::{
|
||||||
AccountEvent, FillEvent, OrderEvent, OrderSide, OrderStatus, PositionEvent, ProcessEvent,
|
AccountEvent, FillEvent, OrderEvent, OrderSide, OrderStatus, PositionEvent, ProcessEvent,
|
||||||
@@ -20,7 +20,10 @@ use crate::metrics::{BacktestMetrics, compute_backtest_metrics};
|
|||||||
use crate::portfolio::{CashReceivable, HoldingSummary, PortfolioState};
|
use crate::portfolio::{CashReceivable, HoldingSummary, PortfolioState};
|
||||||
use crate::rules::EquityRuleHooks;
|
use crate::rules::EquityRuleHooks;
|
||||||
use crate::scheduler::{ScheduleRule, ScheduleStage, Scheduler, default_stage_time};
|
use crate::scheduler::{ScheduleRule, ScheduleStage, Scheduler, default_stage_time};
|
||||||
use crate::strategy::{Strategy, StrategyContext};
|
use crate::strategy::{
|
||||||
|
OpenOrderView, OrderIntent, Strategy, StrategyContext, StrategyDecision,
|
||||||
|
TargetPortfolioOrderPricing,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum BacktestError {
|
pub enum BacktestError {
|
||||||
@@ -95,6 +98,18 @@ pub struct BacktestResult {
|
|||||||
pub metrics: BacktestMetrics,
|
pub metrics: BacktestMetrics,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ExecutionQuoteRequest {
|
||||||
|
pub date: NaiveDate,
|
||||||
|
pub start_time: Option<chrono::NaiveTime>,
|
||||||
|
pub end_time: Option<chrono::NaiveTime>,
|
||||||
|
pub symbols: BTreeSet<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExecutionQuoteLoader = Box<
|
||||||
|
dyn FnMut(ExecutionQuoteRequest) -> Result<Vec<IntradayExecutionQuote>, BacktestError> + Send,
|
||||||
|
>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct AnalyzerTradeRow {
|
pub struct AnalyzerTradeRow {
|
||||||
#[serde(with = "date_format")]
|
#[serde(with = "date_format")]
|
||||||
@@ -325,6 +340,7 @@ pub struct BacktestEngine<S, C, R> {
|
|||||||
futures_settlement_price_mode: String,
|
futures_settlement_price_mode: String,
|
||||||
futures_cost_model: FuturesTransactionCostModel,
|
futures_cost_model: FuturesTransactionCostModel,
|
||||||
futures_validation_config: FuturesValidationConfig,
|
futures_validation_config: FuturesValidationConfig,
|
||||||
|
execution_quote_loader: Option<ExecutionQuoteLoader>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S, C, R> BacktestEngine<S, C, R> {
|
impl<S, C, R> BacktestEngine<S, C, R> {
|
||||||
@@ -352,9 +368,24 @@ impl<S, C, R> BacktestEngine<S, C, R> {
|
|||||||
futures_settlement_price_mode: "close".to_string(),
|
futures_settlement_price_mode: "close".to_string(),
|
||||||
futures_cost_model: FuturesTransactionCostModel::default(),
|
futures_cost_model: FuturesTransactionCostModel::default(),
|
||||||
futures_validation_config: FuturesValidationConfig::default(),
|
futures_validation_config: FuturesValidationConfig::default(),
|
||||||
|
execution_quote_loader: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn into_data(self) -> DataSet {
|
||||||
|
self.data
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_execution_quote_loader<F>(mut self, loader: F) -> Self
|
||||||
|
where
|
||||||
|
F: FnMut(ExecutionQuoteRequest) -> Result<Vec<IntradayExecutionQuote>, BacktestError>
|
||||||
|
+ Send
|
||||||
|
+ 'static,
|
||||||
|
{
|
||||||
|
self.execution_quote_loader = Some(Box::new(loader));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_dividend_reinvestment(mut self, enabled: bool) -> Self {
|
pub fn with_dividend_reinvestment(mut self, enabled: bool) -> Self {
|
||||||
self.dividend_reinvestment = enabled;
|
self.dividend_reinvestment = enabled;
|
||||||
self
|
self
|
||||||
@@ -474,6 +505,48 @@ where
|
|||||||
C: CostModel,
|
C: CostModel,
|
||||||
R: EquityRuleHooks,
|
R: EquityRuleHooks,
|
||||||
{
|
{
|
||||||
|
fn ensure_execution_quotes_for_decision(
|
||||||
|
&mut self,
|
||||||
|
execution_date: NaiveDate,
|
||||||
|
portfolio: &PortfolioState,
|
||||||
|
open_orders: &[OpenOrderView],
|
||||||
|
decision: &StrategyDecision,
|
||||||
|
start_time: Option<chrono::NaiveTime>,
|
||||||
|
end_time: Option<chrono::NaiveTime>,
|
||||||
|
) -> Result<(), BacktestError> {
|
||||||
|
if self.execution_quote_loader.is_none() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if self.broker.execution_price_field() != PriceField::Last
|
||||||
|
&& !decision_has_algo_execution(decision)
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let start_time = start_time.or_else(|| self.broker.intraday_execution_start_time());
|
||||||
|
let mut symbols = execution_quote_symbols_for_decision(decision, portfolio, open_orders);
|
||||||
|
symbols.retain(|symbol| {
|
||||||
|
!has_execution_quote_in_window(&self.data, execution_date, symbol, start_time, end_time)
|
||||||
|
});
|
||||||
|
if symbols.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = ExecutionQuoteRequest {
|
||||||
|
date: execution_date,
|
||||||
|
start_time,
|
||||||
|
end_time,
|
||||||
|
symbols,
|
||||||
|
};
|
||||||
|
let quotes = self
|
||||||
|
.execution_quote_loader
|
||||||
|
.as_mut()
|
||||||
|
.expect("checked execution quote loader")
|
||||||
|
.as_mut()(request)?;
|
||||||
|
self.data.add_execution_quotes(quotes);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn apply_strategy_directives(
|
fn apply_strategy_directives(
|
||||||
&mut self,
|
&mut self,
|
||||||
execution_date: NaiveDate,
|
execution_date: NaiveDate,
|
||||||
@@ -1735,6 +1808,15 @@ where
|
|||||||
&mut auction_decision,
|
&mut auction_decision,
|
||||||
&mut directive_report,
|
&mut directive_report,
|
||||||
)?;
|
)?;
|
||||||
|
let pre_auction_execution_orders = self.open_order_views();
|
||||||
|
self.ensure_execution_quotes_for_decision(
|
||||||
|
execution_date,
|
||||||
|
&portfolio,
|
||||||
|
&pre_auction_execution_orders,
|
||||||
|
&auction_decision,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
let mut report = self.broker.execute(
|
let mut report = self.broker.execute(
|
||||||
execution_date,
|
execution_date,
|
||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
@@ -1939,6 +2021,15 @@ where
|
|||||||
&mut directive_report,
|
&mut directive_report,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let pre_intraday_execution_orders = self.open_order_views();
|
||||||
|
self.ensure_execution_quotes_for_decision(
|
||||||
|
execution_date,
|
||||||
|
&portfolio,
|
||||||
|
&pre_intraday_execution_orders,
|
||||||
|
&decision,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
let mut intraday_report =
|
let mut intraday_report =
|
||||||
self.broker
|
self.broker
|
||||||
.execute(execution_date, &mut portfolio, &self.data, &decision)?;
|
.execute(execution_date, &mut portfolio, &self.data, &decision)?;
|
||||||
@@ -2096,6 +2187,15 @@ where
|
|||||||
&mut tick_decision,
|
&mut tick_decision,
|
||||||
&mut directive_report,
|
&mut directive_report,
|
||||||
)?;
|
)?;
|
||||||
|
let pre_tick_execution_orders = self.open_order_views();
|
||||||
|
self.ensure_execution_quotes_for_decision(
|
||||||
|
execution_date,
|
||||||
|
&portfolio,
|
||||||
|
&pre_tick_execution_orders,
|
||||||
|
&tick_decision,
|
||||||
|
Some(tick_time),
|
||||||
|
Some(tick_time),
|
||||||
|
)?;
|
||||||
let mut tick_report = self.broker.execute_between(
|
let mut tick_report = self.broker.execute_between(
|
||||||
execution_date,
|
execution_date,
|
||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
@@ -3088,6 +3188,94 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn has_execution_quote_in_window(
|
||||||
|
data: &DataSet,
|
||||||
|
date: NaiveDate,
|
||||||
|
symbol: &str,
|
||||||
|
start_time: Option<chrono::NaiveTime>,
|
||||||
|
end_time: Option<chrono::NaiveTime>,
|
||||||
|
) -> bool {
|
||||||
|
let start_cursor = start_time.map(|time| date.and_time(time));
|
||||||
|
let end_cursor = end_time.map(|time| date.and_time(time));
|
||||||
|
data.execution_quotes_on(date, symbol).iter().any(|quote| {
|
||||||
|
!start_cursor.is_some_and(|cursor| quote.timestamp < cursor)
|
||||||
|
&& !end_cursor.is_some_and(|cursor| quote.timestamp > cursor)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decision_has_algo_execution(decision: &StrategyDecision) -> bool {
|
||||||
|
decision.order_intents.iter().any(|intent| {
|
||||||
|
matches!(
|
||||||
|
intent,
|
||||||
|
OrderIntent::AlgoValue { .. }
|
||||||
|
| OrderIntent::AlgoPercent { .. }
|
||||||
|
| OrderIntent::TargetPortfolioSmart {
|
||||||
|
order_prices: Some(TargetPortfolioOrderPricing::AlgoOrder { .. }),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execution_quote_symbols_for_decision(
|
||||||
|
decision: &StrategyDecision,
|
||||||
|
portfolio: &PortfolioState,
|
||||||
|
open_orders: &[OpenOrderView],
|
||||||
|
) -> BTreeSet<String> {
|
||||||
|
let mut symbols = BTreeSet::new();
|
||||||
|
symbols.extend(open_orders.iter().map(|order| order.symbol.clone()));
|
||||||
|
if decision.rebalance
|
||||||
|
|| !decision.target_weights.is_empty()
|
||||||
|
|| !decision.exit_symbols.is_empty()
|
||||||
|
{
|
||||||
|
symbols.extend(portfolio.positions().keys().cloned());
|
||||||
|
symbols.extend(decision.target_weights.keys().cloned());
|
||||||
|
symbols.extend(decision.exit_symbols.iter().cloned());
|
||||||
|
}
|
||||||
|
|
||||||
|
for intent in &decision.order_intents {
|
||||||
|
match intent {
|
||||||
|
OrderIntent::Shares { symbol, .. }
|
||||||
|
| OrderIntent::LimitShares { symbol, .. }
|
||||||
|
| OrderIntent::Lots { symbol, .. }
|
||||||
|
| OrderIntent::LimitLots { symbol, .. }
|
||||||
|
| OrderIntent::TargetShares { symbol, .. }
|
||||||
|
| OrderIntent::LimitTargetShares { symbol, .. }
|
||||||
|
| OrderIntent::TargetValue { symbol, .. }
|
||||||
|
| OrderIntent::LimitTargetValue { symbol, .. }
|
||||||
|
| OrderIntent::Value { symbol, .. }
|
||||||
|
| OrderIntent::LimitValue { symbol, .. }
|
||||||
|
| OrderIntent::Percent { symbol, .. }
|
||||||
|
| OrderIntent::LimitPercent { symbol, .. }
|
||||||
|
| OrderIntent::TargetPercent { symbol, .. }
|
||||||
|
| OrderIntent::LimitTargetPercent { symbol, .. }
|
||||||
|
| OrderIntent::AlgoValue { symbol, .. }
|
||||||
|
| OrderIntent::AlgoPercent { symbol, .. }
|
||||||
|
| OrderIntent::CancelSymbol { symbol, .. } => {
|
||||||
|
symbols.insert(symbol.clone());
|
||||||
|
}
|
||||||
|
OrderIntent::TargetPortfolioSmart { target_weights, .. } => {
|
||||||
|
symbols.extend(portfolio.positions().keys().cloned());
|
||||||
|
symbols.extend(target_weights.keys().cloned());
|
||||||
|
}
|
||||||
|
OrderIntent::CancelAll { .. } => {
|
||||||
|
symbols.extend(open_orders.iter().map(|order| order.symbol.clone()));
|
||||||
|
}
|
||||||
|
OrderIntent::UpdateUniverse { .. }
|
||||||
|
| OrderIntent::Subscribe { .. }
|
||||||
|
| OrderIntent::Unsubscribe { .. }
|
||||||
|
| OrderIntent::DepositWithdraw { .. }
|
||||||
|
| OrderIntent::FinanceRepay { .. }
|
||||||
|
| OrderIntent::SetManagementFeeRate { .. }
|
||||||
|
| OrderIntent::CancelOrder { .. }
|
||||||
|
| OrderIntent::Futures { .. } => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
symbols.retain(|symbol| !symbol.trim().is_empty());
|
||||||
|
symbols
|
||||||
|
}
|
||||||
|
|
||||||
fn collect_scheduled_decisions<S: Strategy>(
|
fn collect_scheduled_decisions<S: Strategy>(
|
||||||
strategy: &mut S,
|
strategy: &mut S,
|
||||||
scheduler: &Scheduler<'_>,
|
scheduler: &Scheduler<'_>,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ pub use data::{
|
|||||||
pub use engine::{
|
pub use engine::{
|
||||||
AnalyzerMonthlyReturnRow, AnalyzerPositionRow, AnalyzerReport, AnalyzerRiskSummary,
|
AnalyzerMonthlyReturnRow, AnalyzerPositionRow, AnalyzerReport, AnalyzerRiskSummary,
|
||||||
AnalyzerTradeRow, BacktestConfig, BacktestDayProgress, BacktestEngine, BacktestError,
|
AnalyzerTradeRow, BacktestConfig, BacktestDayProgress, BacktestEngine, BacktestError,
|
||||||
BacktestResult, DailyEquityPoint, FuturesValidationConfig,
|
BacktestResult, DailyEquityPoint, ExecutionQuoteRequest, FuturesValidationConfig,
|
||||||
};
|
};
|
||||||
pub use event_bus::{BacktestProcessMod, BacktestProcessModLoader, ProcessEventBus};
|
pub use event_bus::{BacktestProcessMod, BacktestProcessModLoader, ProcessEventBus};
|
||||||
pub use events::{
|
pub use events::{
|
||||||
@@ -51,7 +51,7 @@ pub use metrics::{BacktestMetrics, compute_backtest_metrics};
|
|||||||
pub use platform_expr_strategy::{
|
pub use platform_expr_strategy::{
|
||||||
PlatformAccountActionKind, PlatformExplicitActionStage, PlatformExplicitCancelKind,
|
PlatformAccountActionKind, PlatformExplicitActionStage, PlatformExplicitCancelKind,
|
||||||
PlatformExplicitOrderKind, PlatformExprStrategy, PlatformExprStrategyConfig,
|
PlatformExplicitOrderKind, PlatformExprStrategy, PlatformExprStrategyConfig,
|
||||||
PlatformRebalanceSchedule, PlatformScheduleFrequency, PlatformSelectionPrefetchPlan,
|
PlatformRebalanceSchedule, PlatformScheduleFrequency, PlatformSelectionQuotePlan,
|
||||||
PlatformTradeAction, PlatformUniverseActionKind,
|
PlatformTradeAction, PlatformUniverseActionKind,
|
||||||
};
|
};
|
||||||
pub use platform_runtime_schema::{
|
pub use platform_runtime_schema::{
|
||||||
|
|||||||
@@ -473,7 +473,7 @@ pub struct PlatformExprStrategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct PlatformSelectionPrefetchPlan {
|
pub struct PlatformSelectionQuotePlan {
|
||||||
pub execution_date: NaiveDate,
|
pub execution_date: NaiveDate,
|
||||||
pub decision_date: NaiveDate,
|
pub decision_date: NaiveDate,
|
||||||
pub selection_date: NaiveDate,
|
pub selection_date: NaiveDate,
|
||||||
@@ -1761,6 +1761,10 @@ impl PlatformExprStrategy {
|
|||||||
include_process_event_counts: bool,
|
include_process_event_counts: bool,
|
||||||
) -> Scope<'static> {
|
) -> Scope<'static> {
|
||||||
let mut scope = Scope::new();
|
let mut scope = Scope::new();
|
||||||
|
let trade_date = day.date.format("%Y-%m-%d").to_string();
|
||||||
|
scope.push("trade_date", trade_date.clone());
|
||||||
|
scope.push("current_date", trade_date.clone());
|
||||||
|
scope.push("date", trade_date);
|
||||||
scope.push("signal_open", day.signal_open);
|
scope.push("signal_open", day.signal_open);
|
||||||
scope.push("signal_close", day.signal_close);
|
scope.push("signal_close", day.signal_close);
|
||||||
scope.push("benchmark_open", day.benchmark_open);
|
scope.push("benchmark_open", day.benchmark_open);
|
||||||
@@ -5130,7 +5134,7 @@ impl PlatformExprStrategy {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_prefetch_symbols(
|
fn select_quote_plan_symbols(
|
||||||
&self,
|
&self,
|
||||||
ctx: &StrategyContext<'_>,
|
ctx: &StrategyContext<'_>,
|
||||||
date: NaiveDate,
|
date: NaiveDate,
|
||||||
@@ -5151,7 +5155,7 @@ impl PlatformExprStrategy {
|
|||||||
if !field_value.is_finite() {
|
if !field_value.is_finite() {
|
||||||
if diagnostics.len() < 12 {
|
if diagnostics.len() < 12 {
|
||||||
diagnostics.push(format!(
|
diagnostics.push(format!(
|
||||||
"{} prefetch rejected by missing selection field",
|
"{} quote_plan rejected by missing selection field",
|
||||||
candidate.symbol
|
candidate.symbol
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -5164,7 +5168,7 @@ impl PlatformExprStrategy {
|
|||||||
if !rank_value.is_finite() {
|
if !rank_value.is_finite() {
|
||||||
if diagnostics.len() < 12 {
|
if diagnostics.len() < 12 {
|
||||||
diagnostics.push(format!(
|
diagnostics.push(format!(
|
||||||
"{} prefetch rejected by missing rank field",
|
"{} quote_plan rejected by missing rank field",
|
||||||
candidate.symbol
|
candidate.symbol
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -5202,13 +5206,13 @@ impl PlatformExprStrategy {
|
|||||||
let stock = self.stock_state_with_factor_date(ctx, date, factor_date, symbol)?;
|
let stock = self.stock_state_with_factor_date(ctx, date, factor_date, symbol)?;
|
||||||
if let Some(reason) = self.buy_rejection_reason(ctx, date, symbol, &stock)? {
|
if let Some(reason) = self.buy_rejection_reason(ctx, date, symbol, &stock)? {
|
||||||
if diagnostics.len() < 12 {
|
if diagnostics.len() < 12 {
|
||||||
diagnostics.push(format!("{symbol} prefetch rejected by {reason}"));
|
diagnostics.push(format!("{symbol} quote_plan rejected by {reason}"));
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if apply_stock_filter && !self.stock_passes_expr(ctx, day, &stock)? {
|
if apply_stock_filter && !self.stock_passes_expr(ctx, day, &stock)? {
|
||||||
if diagnostics.len() < 12 {
|
if diagnostics.len() < 12 {
|
||||||
diagnostics.push(format!("{symbol} prefetch rejected by stock_expr"));
|
diagnostics.push(format!("{symbol} quote_plan rejected by stock_expr"));
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -5223,13 +5227,13 @@ impl PlatformExprStrategy {
|
|||||||
Ok((processed_symbols, selected_symbols, diagnostics))
|
Ok((processed_symbols, selected_symbols, diagnostics))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selection_prefetch_plan(
|
pub fn selection_quote_plan(
|
||||||
&self,
|
&self,
|
||||||
ctx: &StrategyContext<'_>,
|
ctx: &StrategyContext<'_>,
|
||||||
_scope_limit: usize,
|
_scope_limit: usize,
|
||||||
) -> Result<PlatformSelectionPrefetchPlan, BacktestError> {
|
) -> Result<PlatformSelectionQuotePlan, BacktestError> {
|
||||||
if !self.config.rotation_enabled || self.config.in_skip_window(ctx.execution_date) {
|
if !self.config.rotation_enabled || self.config.in_skip_window(ctx.execution_date) {
|
||||||
return Ok(PlatformSelectionPrefetchPlan {
|
return Ok(PlatformSelectionQuotePlan {
|
||||||
execution_date: ctx.execution_date,
|
execution_date: ctx.execution_date,
|
||||||
decision_date: ctx.decision_date,
|
decision_date: ctx.decision_date,
|
||||||
selection_date: ctx.execution_date,
|
selection_date: ctx.execution_date,
|
||||||
@@ -5252,7 +5256,7 @@ impl PlatformExprStrategy {
|
|||||||
let selection_limit = self
|
let selection_limit = self
|
||||||
.selection_limit(ctx, &day)?
|
.selection_limit(ctx, &day)?
|
||||||
.min(self.config.max_positions.max(1));
|
.min(self.config.max_positions.max(1));
|
||||||
let (candidate_symbols, order_symbols, diagnostics) = self.select_prefetch_symbols(
|
let (candidate_symbols, order_symbols, diagnostics) = self.select_quote_plan_symbols(
|
||||||
ctx,
|
ctx,
|
||||||
selection_date,
|
selection_date,
|
||||||
factor_date,
|
factor_date,
|
||||||
@@ -5261,7 +5265,7 @@ impl PlatformExprStrategy {
|
|||||||
band_high,
|
band_high,
|
||||||
selection_limit,
|
selection_limit,
|
||||||
)?;
|
)?;
|
||||||
Ok(PlatformSelectionPrefetchPlan {
|
Ok(PlatformSelectionQuotePlan {
|
||||||
execution_date: ctx.execution_date,
|
execution_date: ctx.execution_date,
|
||||||
decision_date: ctx.decision_date,
|
decision_date: ctx.decision_date,
|
||||||
selection_date,
|
selection_date,
|
||||||
@@ -8669,6 +8673,7 @@ mod tests {
|
|||||||
cfg.rotation_enabled = false;
|
cfg.rotation_enabled = false;
|
||||||
cfg.benchmark_short_ma_days = 1;
|
cfg.benchmark_short_ma_days = 1;
|
||||||
cfg.benchmark_long_ma_days = 1;
|
cfg.benchmark_long_ma_days = 1;
|
||||||
|
cfg.prelude = "let blackout = trade_date >= \"2025-01-06\";".to_string();
|
||||||
cfg.explicit_actions = vec![PlatformTradeAction::Order {
|
cfg.explicit_actions = vec![PlatformTradeAction::Order {
|
||||||
kind: PlatformExplicitOrderKind::Value,
|
kind: PlatformExplicitOrderKind::Value,
|
||||||
symbol: "000001.SZ".to_string(),
|
symbol: "000001.SZ".to_string(),
|
||||||
@@ -8688,7 +8693,9 @@ mod tests {
|
|||||||
" && stddev(\"close\", 2) > 0.49",
|
" && stddev(\"close\", 2) > 0.49",
|
||||||
" && rolling_zscore(\"close\", 2) > 0.9",
|
" && rolling_zscore(\"close\", 2) > 0.9",
|
||||||
" && pct_change(\"close\", 1) > 0.09",
|
" && pct_change(\"close\", 1) > 0.09",
|
||||||
" && factor_value(\"mixed_factor\") == 7.0"
|
" && factor_value(\"mixed_factor\") == 7.0",
|
||||||
|
" && trade_date == \"2025-01-06\"",
|
||||||
|
" && blackout"
|
||||||
)
|
)
|
||||||
.to_string(),
|
.to_string(),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user