修正next-open策略上下文日期
This commit is contained in:
@@ -4059,7 +4059,9 @@ mod date_format {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::BTreeMap;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::rc::Rc;
|
||||
|
||||
use chrono::NaiveDate;
|
||||
|
||||
@@ -4072,6 +4074,7 @@ mod tests {
|
||||
};
|
||||
use crate::events::{OrderSide, OrderStatus};
|
||||
use crate::instrument::Instrument;
|
||||
use crate::portfolio::PortfolioState;
|
||||
use crate::risk_control::{FidcRiskControlConfig, RiskCheckScope};
|
||||
use crate::rules::ChinaEquityRuleHooks;
|
||||
use crate::scheduler::{ScheduleRule, ScheduleStage};
|
||||
@@ -4147,6 +4150,66 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ScheduledDataDateProbeStrategy {
|
||||
rule: ScheduleRule,
|
||||
expected_decision_date: NaiveDate,
|
||||
observed: Rc<RefCell<Vec<String>>>,
|
||||
}
|
||||
|
||||
impl Strategy for ScheduledDataDateProbeStrategy {
|
||||
fn name(&self) -> &str {
|
||||
"scheduled_data_date_probe"
|
||||
}
|
||||
|
||||
fn schedule_rules(&self) -> Vec<ScheduleRule> {
|
||||
vec![self.rule.clone()]
|
||||
}
|
||||
|
||||
fn on_scheduled(
|
||||
&mut self,
|
||||
ctx: &StrategyContext<'_>,
|
||||
rule: &ScheduleRule,
|
||||
) -> Result<StrategyDecision, super::BacktestError> {
|
||||
assert_eq!(rule.name, self.rule.name);
|
||||
if ctx.decision_date != self.expected_decision_date {
|
||||
return Ok(StrategyDecision::default());
|
||||
}
|
||||
let current_close = ctx
|
||||
.current_snapshot(SYMBOL)
|
||||
.map(|snapshot| format!("{:.2}", snapshot.close))
|
||||
.unwrap_or_default();
|
||||
let history_close = ctx
|
||||
.history_bars(SYMBOL, 2, "1d", "close", true)
|
||||
.iter()
|
||||
.map(|value| format!("{value:.2}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
let suspended = ctx
|
||||
.is_suspended(SYMBOL, 1)
|
||||
.into_iter()
|
||||
.map(|value| if value { "1" } else { "0" })
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
self.observed.borrow_mut().push(format!(
|
||||
"signal={};execution={};active={};current={current_close};history={history_close};suspended={suspended}",
|
||||
ctx.signal_date(),
|
||||
ctx.execution_trade_date(),
|
||||
ctx.current_datetime()
|
||||
.map(|datetime| datetime.date().to_string())
|
||||
.unwrap_or_default()
|
||||
));
|
||||
Ok(StrategyDecision {
|
||||
order_intents: vec![OrderIntent::Shares {
|
||||
symbol: SYMBOL.to_string(),
|
||||
quantity: 100,
|
||||
reason: "data_date_probe_buy".to_string(),
|
||||
}],
|
||||
..StrategyDecision::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ScheduledRoundTripStrategy {
|
||||
rule: ScheduleRule,
|
||||
@@ -4679,6 +4742,80 @@ mod tests {
|
||||
assert_eq!(result.fills[0].price, 12.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_bar_open_strategy_context_data_helpers_use_decision_date() {
|
||||
let first = d(2025, 1, 2);
|
||||
let second = d(2025, 1, 3);
|
||||
let observed = Rc::new(RefCell::new(Vec::new()));
|
||||
let dataset = dataset_with(
|
||||
market_with_state(first, 10.0, 11.5, true, 11.5, 9.0),
|
||||
market_with_state(second, 12.0, 99.0, false, 200.0, 1.0),
|
||||
candidate_with_state(first, true, false),
|
||||
candidate(second),
|
||||
);
|
||||
let portfolio = PortfolioState::new(100_000.0);
|
||||
let subscriptions = BTreeSet::new();
|
||||
let manual_ctx = StrategyContext {
|
||||
execution_date: second,
|
||||
decision_date: first,
|
||||
decision_index: 0,
|
||||
data: &dataset,
|
||||
portfolio: &portfolio,
|
||||
futures_account: None,
|
||||
open_orders: &[],
|
||||
dynamic_universe: None,
|
||||
subscriptions: &subscriptions,
|
||||
process_events: &[],
|
||||
active_process_event: None,
|
||||
active_datetime: Some(second.and_hms_opt(9, 31, 0).unwrap()),
|
||||
order_events: &[],
|
||||
fills: &[],
|
||||
};
|
||||
assert_eq!(
|
||||
manual_ctx
|
||||
.current_snapshot(SYMBOL)
|
||||
.map(|snapshot| snapshot.close),
|
||||
Some(11.5)
|
||||
);
|
||||
assert_eq!(
|
||||
manual_ctx.history_bars(SYMBOL, 2, "1d", "close", true),
|
||||
vec![11.5]
|
||||
);
|
||||
assert_eq!(manual_ctx.is_suspended(SYMBOL, 1), vec![true]);
|
||||
let broker = scheduled_next_open_broker(FidcRiskControlConfig::default());
|
||||
let config = BacktestConfig {
|
||||
initial_cash: 100_000.0,
|
||||
benchmark_code: "000852.SH".to_string(),
|
||||
start_date: Some(first),
|
||||
end_date: Some(second),
|
||||
decision_lag_trading_days: 1,
|
||||
execution_price_field: PriceField::Open,
|
||||
};
|
||||
|
||||
let result = BacktestEngine::new(
|
||||
dataset.clone(),
|
||||
ScheduledDataDateProbeStrategy {
|
||||
rule: ScheduleRule::daily("daily_data_date_probe", ScheduleStage::OnDay),
|
||||
expected_decision_date: first,
|
||||
observed: observed.clone(),
|
||||
},
|
||||
broker,
|
||||
config,
|
||||
)
|
||||
.run()
|
||||
.expect("backtest run");
|
||||
|
||||
assert_eq!(
|
||||
observed.borrow().as_slice(),
|
||||
[
|
||||
"signal=2025-01-02;execution=2025-01-03;active=2025-01-02;current=11.50;history=11.50;suspended=1"
|
||||
]
|
||||
);
|
||||
assert_eq!(result.fills.len(), 1);
|
||||
assert_eq!(result.fills[0].date, second);
|
||||
assert_eq!(result.fills[0].price, 12.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn next_bar_open_execution_risk_ignores_decision_day_paused_state() {
|
||||
let first = d(2025, 1, 2);
|
||||
|
||||
Reference in New Issue
Block a user