Expose process event context to strategy runtime

This commit is contained in:
boris
2026-04-23 05:12:07 -07:00
parent 4d43d1b176
commit 2857f72d84
8 changed files with 742 additions and 92 deletions

View File

@@ -121,6 +121,10 @@ struct ScheduledProbeStrategy {
process_log: Rc<RefCell<Vec<String>>>,
}
struct ProcessContextProbeStrategy {
snapshots: Rc<RefCell<Vec<String>>>,
}
struct LimitCarryStrategy {
issued: bool,
}
@@ -197,6 +201,33 @@ impl Strategy for LimitCarryStrategy {
}
}
impl Strategy for ProcessContextProbeStrategy {
fn name(&self) -> &str {
"process-context-probe"
}
fn on_process_event(
&mut self,
ctx: &StrategyContext<'_>,
_event: &fidc_core::ProcessEvent,
) -> Result<(), fidc_core::BacktestError> {
self.snapshots.borrow_mut().push(format!(
"{}:{}:{}",
ctx.current_process_event_kind(),
ctx.latest_process_event_kind(),
ctx.process_event_count()
));
Ok(())
}
fn on_day(
&mut self,
_ctx: &StrategyContext<'_>,
) -> Result<StrategyDecision, fidc_core::BacktestError> {
Ok(StrategyDecision::default())
}
}
#[test]
fn engine_runs_strategy_hooks_in_daily_order() {
let date1 = d(2025, 1, 2);
@@ -1133,3 +1164,104 @@ fn engine_dispatches_process_events_to_external_bus_listeners() {
.any(|item| { item == "PostScheduled:scheduled:first_trading_day_on_day:on_day:post" })
);
}
#[test]
fn engine_exposes_current_process_context_to_strategies() {
let date = d(2025, 1, 2);
let data = DataSet::from_components(
vec![Instrument {
symbol: "000001.SZ".to_string(),
name: "Anchor".to_string(),
board: "SZ".to_string(),
round_lot: 100,
listed_at: Some(d(2020, 1, 1)),
delisted_at: None,
status: "active".to_string(),
}],
vec![DailyMarketSnapshot {
date,
symbol: "000001.SZ".to_string(),
timestamp: Some("2025-01-02 10:18:00".to_string()),
day_open: 10.0,
open: 10.0,
high: 10.1,
low: 9.9,
close: 10.0,
last_price: 10.0,
bid1: 10.0,
ask1: 10.0,
prev_close: 9.9,
volume: 100_000,
tick_volume: 100_000,
bid1_volume: 100_000,
ask1_volume: 100_000,
trading_phase: Some("continuous".to_string()),
paused: false,
upper_limit: 11.0,
lower_limit: 9.0,
price_tick: 0.01,
}],
vec![DailyFactorSnapshot {
date,
symbol: "000001.SZ".to_string(),
market_cap_bn: 20.0,
free_float_cap_bn: 18.0,
pe_ttm: 10.0,
turnover_ratio: Some(1.0),
effective_turnover_ratio: Some(1.0),
extra_factors: BTreeMap::new(),
}],
vec![CandidateEligibility {
date,
symbol: "000001.SZ".to_string(),
is_st: false,
is_new_listing: false,
is_paused: false,
allow_buy: true,
allow_sell: true,
is_kcb: false,
is_one_yuan: false,
}],
vec![BenchmarkSnapshot {
date,
benchmark: "000300.SH".to_string(),
open: 100.0,
close: 100.0,
prev_close: 99.0,
volume: 1_000_000,
}],
)
.expect("dataset");
let snapshots = Rc::new(RefCell::new(Vec::new()));
let strategy = ProcessContextProbeStrategy {
snapshots: snapshots.clone(),
};
let broker = BrokerSimulator::new_with_execution_price(
ChinaAShareCostModel::default(),
ChinaEquityRuleHooks::default(),
PriceField::Last,
);
let mut engine = BacktestEngine::new(
data,
strategy,
broker,
BacktestConfig {
initial_cash: 100_000.0,
benchmark_code: "000300.SH".to_string(),
start_date: Some(date),
end_date: Some(date),
decision_lag_trading_days: 0,
execution_price_field: PriceField::Last,
},
);
engine.run().expect("backtest run");
let snapshots = snapshots.borrow();
assert_eq!(
snapshots.first().map(String::as_str),
Some("pre_before_trading:pre_before_trading:1")
);
assert!(snapshots.iter().any(|item| item == "on_day:on_day:8"));
}

View File

@@ -26,6 +26,8 @@ fn strategy_emits_target_weights_and_diagnostics() {
data: &data,
portfolio: &portfolio,
open_orders: &[],
process_events: &[],
active_process_event: None,
})
.expect("decision");
@@ -64,6 +66,8 @@ fn jq_strategy_emits_same_day_decision() {
data: &data,
portfolio: &portfolio,
open_orders: &[],
process_events: &[],
active_process_event: None,
})
.expect("jq decision");