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