Add scheduled process hooks to strategy engine
This commit is contained in:
@@ -220,31 +220,39 @@ where
|
||||
};
|
||||
let schedule_rules = self.strategy.schedule_rules();
|
||||
let mut process_events = Vec::new();
|
||||
push_phase_event(
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
&daily_context,
|
||||
&mut process_events,
|
||||
execution_date,
|
||||
ProcessEventKind::PreBeforeTrading,
|
||||
"before_trading:pre",
|
||||
);
|
||||
)?;
|
||||
self.strategy.before_trading(&daily_context)?;
|
||||
push_phase_event(
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
&daily_context,
|
||||
&mut process_events,
|
||||
execution_date,
|
||||
ProcessEventKind::BeforeTrading,
|
||||
"before_trading",
|
||||
);
|
||||
push_phase_event(
|
||||
)?;
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
&daily_context,
|
||||
&mut process_events,
|
||||
execution_date,
|
||||
ProcessEventKind::PostBeforeTrading,
|
||||
"before_trading:post",
|
||||
);
|
||||
push_phase_event(
|
||||
)?;
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
&daily_context,
|
||||
&mut process_events,
|
||||
execution_date,
|
||||
ProcessEventKind::PreOpenAuction,
|
||||
"open_auction:pre",
|
||||
);
|
||||
)?;
|
||||
let mut auction_decision = collect_scheduled_decisions(
|
||||
&mut self.strategy,
|
||||
&scheduler,
|
||||
@@ -252,34 +260,53 @@ where
|
||||
ScheduleStage::OpenAuction,
|
||||
&schedule_rules,
|
||||
&daily_context,
|
||||
&mut process_events,
|
||||
)?;
|
||||
auction_decision.merge_from(self.strategy.open_auction(&daily_context)?);
|
||||
push_phase_event(
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
&daily_context,
|
||||
&mut process_events,
|
||||
execution_date,
|
||||
ProcessEventKind::OpenAuction,
|
||||
"open_auction",
|
||||
);
|
||||
)?;
|
||||
let mut report = self.broker.execute(
|
||||
execution_date,
|
||||
&mut portfolio,
|
||||
&self.data,
|
||||
&auction_decision,
|
||||
)?;
|
||||
process_events.append(&mut report.process_events);
|
||||
push_phase_event(
|
||||
let post_auction_context = StrategyContext {
|
||||
execution_date,
|
||||
decision_date,
|
||||
decision_index,
|
||||
data: &self.data,
|
||||
portfolio: &portfolio,
|
||||
};
|
||||
publish_process_events(
|
||||
&mut self.strategy,
|
||||
&post_auction_context,
|
||||
&mut process_events,
|
||||
&mut report.process_events,
|
||||
)?;
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
&post_auction_context,
|
||||
&mut process_events,
|
||||
execution_date,
|
||||
ProcessEventKind::PostOpenAuction,
|
||||
"open_auction:post",
|
||||
);
|
||||
)?;
|
||||
|
||||
push_phase_event(
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
&post_auction_context,
|
||||
&mut process_events,
|
||||
execution_date,
|
||||
ProcessEventKind::PreOnDay,
|
||||
"on_day:pre",
|
||||
);
|
||||
)?;
|
||||
let mut decision = decision_slot
|
||||
.map(|(decision_idx, decision_date)| {
|
||||
self.strategy.on_day(&StrategyContext {
|
||||
@@ -305,18 +332,40 @@ where
|
||||
data: &self.data,
|
||||
portfolio: &portfolio,
|
||||
},
|
||||
&mut process_events,
|
||||
)?);
|
||||
push_phase_event(
|
||||
let on_day_context = StrategyContext {
|
||||
execution_date,
|
||||
decision_date,
|
||||
decision_index,
|
||||
data: &self.data,
|
||||
portfolio: &portfolio,
|
||||
};
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
&on_day_context,
|
||||
&mut process_events,
|
||||
execution_date,
|
||||
ProcessEventKind::OnDay,
|
||||
"on_day",
|
||||
);
|
||||
)?;
|
||||
|
||||
let mut intraday_report =
|
||||
self.broker
|
||||
.execute(execution_date, &mut portfolio, &self.data, &decision)?;
|
||||
process_events.append(&mut intraday_report.process_events);
|
||||
let post_intraday_context = StrategyContext {
|
||||
execution_date,
|
||||
decision_date,
|
||||
decision_index,
|
||||
data: &self.data,
|
||||
portfolio: &portfolio,
|
||||
};
|
||||
publish_process_events(
|
||||
&mut self.strategy,
|
||||
&post_intraday_context,
|
||||
&mut process_events,
|
||||
&mut intraday_report.process_events,
|
||||
)?;
|
||||
report.order_events.extend(intraday_report.order_events);
|
||||
report.fill_events.extend(intraday_report.fill_events);
|
||||
report
|
||||
@@ -324,12 +373,14 @@ where
|
||||
.extend(intraday_report.position_events);
|
||||
report.account_events.extend(intraday_report.account_events);
|
||||
report.diagnostics.extend(intraday_report.diagnostics);
|
||||
push_phase_event(
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
&post_intraday_context,
|
||||
&mut process_events,
|
||||
execution_date,
|
||||
ProcessEventKind::PostOnDay,
|
||||
"on_day:post",
|
||||
);
|
||||
)?;
|
||||
|
||||
portfolio.update_prices(execution_date, &self.data, PriceField::Close)?;
|
||||
|
||||
@@ -340,51 +391,68 @@ where
|
||||
data: &self.data,
|
||||
portfolio: &portfolio,
|
||||
};
|
||||
push_phase_event(
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
&post_trade_context,
|
||||
&mut process_events,
|
||||
execution_date,
|
||||
ProcessEventKind::PreAfterTrading,
|
||||
"after_trading:pre",
|
||||
);
|
||||
)?;
|
||||
self.strategy.after_trading(&post_trade_context)?;
|
||||
push_phase_event(
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
&post_trade_context,
|
||||
&mut process_events,
|
||||
execution_date,
|
||||
ProcessEventKind::AfterTrading,
|
||||
"after_trading",
|
||||
);
|
||||
)?;
|
||||
let mut close_report = self.broker.after_trading(execution_date);
|
||||
process_events.append(&mut close_report.process_events);
|
||||
publish_process_events(
|
||||
&mut self.strategy,
|
||||
&post_trade_context,
|
||||
&mut process_events,
|
||||
&mut close_report.process_events,
|
||||
)?;
|
||||
report.order_events.extend(close_report.order_events);
|
||||
report.fill_events.extend(close_report.fill_events);
|
||||
report.position_events.extend(close_report.position_events);
|
||||
report.account_events.extend(close_report.account_events);
|
||||
report.diagnostics.extend(close_report.diagnostics);
|
||||
push_phase_event(
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
&post_trade_context,
|
||||
&mut process_events,
|
||||
execution_date,
|
||||
ProcessEventKind::PostAfterTrading,
|
||||
"after_trading:post",
|
||||
);
|
||||
push_phase_event(
|
||||
)?;
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
&post_trade_context,
|
||||
&mut process_events,
|
||||
execution_date,
|
||||
ProcessEventKind::PreSettlement,
|
||||
"settlement:pre",
|
||||
);
|
||||
)?;
|
||||
self.strategy.on_settlement(&post_trade_context)?;
|
||||
push_phase_event(
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
&post_trade_context,
|
||||
&mut process_events,
|
||||
execution_date,
|
||||
ProcessEventKind::Settlement,
|
||||
"settlement",
|
||||
);
|
||||
push_phase_event(
|
||||
)?;
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
&post_trade_context,
|
||||
&mut process_events,
|
||||
execution_date,
|
||||
ProcessEventKind::PostSettlement,
|
||||
"settlement:post",
|
||||
);
|
||||
)?;
|
||||
let daily_fill_count = report.fill_events.len();
|
||||
let day_orders = report.order_events.clone();
|
||||
let day_fills = report.fill_events.clone();
|
||||
@@ -876,28 +944,70 @@ fn collect_scheduled_decisions<S: Strategy>(
|
||||
stage: ScheduleStage,
|
||||
rules: &[ScheduleRule],
|
||||
ctx: &StrategyContext<'_>,
|
||||
process_events: &mut Vec<ProcessEvent>,
|
||||
) -> Result<crate::strategy::StrategyDecision, BacktestError> {
|
||||
let mut combined = crate::strategy::StrategyDecision::default();
|
||||
for rule in scheduler.triggered_rules(execution_date, stage, rules) {
|
||||
publish_phase_event(
|
||||
strategy,
|
||||
ctx,
|
||||
process_events,
|
||||
execution_date,
|
||||
ProcessEventKind::PreScheduled,
|
||||
format!("scheduled:{}:{}:pre", rule.name, stage_label(stage)),
|
||||
)?;
|
||||
combined.merge_from(strategy.on_scheduled(ctx, rule)?);
|
||||
publish_phase_event(
|
||||
strategy,
|
||||
ctx,
|
||||
process_events,
|
||||
execution_date,
|
||||
ProcessEventKind::PostScheduled,
|
||||
format!("scheduled:{}:{}:post", rule.name, stage_label(stage)),
|
||||
)?;
|
||||
}
|
||||
Ok(combined)
|
||||
}
|
||||
|
||||
fn push_phase_event(
|
||||
fn publish_phase_event<S: Strategy>(
|
||||
strategy: &mut S,
|
||||
ctx: &StrategyContext<'_>,
|
||||
events: &mut Vec<ProcessEvent>,
|
||||
date: NaiveDate,
|
||||
kind: ProcessEventKind,
|
||||
detail: impl Into<String>,
|
||||
) {
|
||||
events.push(ProcessEvent {
|
||||
) -> Result<(), BacktestError> {
|
||||
let event = ProcessEvent {
|
||||
date,
|
||||
kind,
|
||||
order_id: None,
|
||||
symbol: None,
|
||||
side: None,
|
||||
detail: detail.into(),
|
||||
});
|
||||
};
|
||||
strategy.on_process_event(ctx, &event)?;
|
||||
events.push(event);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn publish_process_events<S: Strategy>(
|
||||
strategy: &mut S,
|
||||
ctx: &StrategyContext<'_>,
|
||||
target: &mut Vec<ProcessEvent>,
|
||||
incoming: &mut Vec<ProcessEvent>,
|
||||
) -> Result<(), BacktestError> {
|
||||
for event in incoming.drain(..) {
|
||||
strategy.on_process_event(ctx, &event)?;
|
||||
target.push(event);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stage_label(stage: ScheduleStage) -> &'static str {
|
||||
match stage {
|
||||
ScheduleStage::OpenAuction => "open_auction",
|
||||
ScheduleStage::OnDay => "on_day",
|
||||
}
|
||||
}
|
||||
|
||||
mod date_format {
|
||||
|
||||
@@ -99,6 +99,8 @@ pub enum ProcessEventKind {
|
||||
PreOpenAuction,
|
||||
OpenAuction,
|
||||
PostOpenAuction,
|
||||
PreScheduled,
|
||||
PostScheduled,
|
||||
PreOnDay,
|
||||
OnDay,
|
||||
PostOnDay,
|
||||
|
||||
@@ -9,13 +9,20 @@ use chrono::{Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime};
|
||||
use crate::cost::ChinaAShareCostModel;
|
||||
use crate::data::{DataSet, PriceField};
|
||||
use crate::engine::BacktestError;
|
||||
use crate::events::OrderSide;
|
||||
use crate::events::{OrderSide, ProcessEvent};
|
||||
use crate::portfolio::PortfolioState;
|
||||
use crate::scheduler::ScheduleRule;
|
||||
use crate::universe::{DynamicMarketCapBandSelector, SelectionContext, UniverseSelector};
|
||||
|
||||
pub trait Strategy {
|
||||
fn name(&self) -> &str;
|
||||
fn on_process_event(
|
||||
&mut self,
|
||||
_ctx: &StrategyContext<'_>,
|
||||
_event: &ProcessEvent,
|
||||
) -> Result<(), BacktestError> {
|
||||
Ok(())
|
||||
}
|
||||
fn schedule_rules(&self) -> Vec<ScheduleRule> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ impl Strategy for AuctionOrderStrategy {
|
||||
|
||||
struct ScheduledProbeStrategy {
|
||||
log: Rc<RefCell<Vec<String>>>,
|
||||
process_log: Rc<RefCell<Vec<String>>>,
|
||||
}
|
||||
|
||||
struct LimitCarryStrategy {
|
||||
@@ -129,6 +130,17 @@ impl Strategy for ScheduledProbeStrategy {
|
||||
"scheduled-probe"
|
||||
}
|
||||
|
||||
fn on_process_event(
|
||||
&mut self,
|
||||
_ctx: &StrategyContext<'_>,
|
||||
event: &fidc_core::ProcessEvent,
|
||||
) -> Result<(), fidc_core::BacktestError> {
|
||||
self.process_log
|
||||
.borrow_mut()
|
||||
.push(format!("{:?}:{}", event.kind, event.detail));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn schedule_rules(&self) -> Vec<ScheduleRule> {
|
||||
vec![
|
||||
ScheduleRule::daily("daily_auction", ScheduleStage::OpenAuction),
|
||||
@@ -826,7 +838,11 @@ fn engine_runs_scheduled_rules_for_daily_weekly_and_monthly_triggers() {
|
||||
.expect("dataset");
|
||||
|
||||
let log = Rc::new(RefCell::new(Vec::new()));
|
||||
let strategy = ScheduledProbeStrategy { log: log.clone() };
|
||||
let process_log = Rc::new(RefCell::new(Vec::new()));
|
||||
let strategy = ScheduledProbeStrategy {
|
||||
log: log.clone(),
|
||||
process_log: process_log.clone(),
|
||||
};
|
||||
let broker = BrokerSimulator::new_with_execution_price(
|
||||
ChinaAShareCostModel::default(),
|
||||
ChinaEquityRuleHooks::default(),
|
||||
@@ -859,4 +875,25 @@ fn engine_runs_scheduled_rules_for_daily_weekly_and_monthly_triggers() {
|
||||
"scheduled:first_trading_day_on_day:2025-02-03",
|
||||
]
|
||||
);
|
||||
let process_log = process_log.borrow();
|
||||
assert!(
|
||||
process_log
|
||||
.iter()
|
||||
.any(|item| { item == "PreScheduled:scheduled:daily_auction:open_auction:pre" })
|
||||
);
|
||||
assert!(
|
||||
process_log
|
||||
.iter()
|
||||
.any(|item| { item == "PostScheduled:scheduled:daily_auction:open_auction:post" })
|
||||
);
|
||||
assert!(
|
||||
process_log
|
||||
.iter()
|
||||
.any(|item| { item == "PreScheduled:scheduled:friday_on_day:on_day:pre" })
|
||||
);
|
||||
assert!(
|
||||
process_log
|
||||
.iter()
|
||||
.any(|item| { item == "PostScheduled:scheduled:first_trading_day_on_day:on_day:post" })
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user