初始化回测核心引擎骨架

This commit is contained in:
zsb
2026-04-06 23:56:37 -07:00
commit 334864cbc5
25 changed files with 2878 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
use chrono::NaiveDate;
use crate::data::{CandidateEligibility, DailyMarketSnapshot};
use crate::portfolio::Position;
#[derive(Debug, Clone)]
pub struct RuleCheck {
pub allowed: bool,
pub reason: Option<String>,
}
impl RuleCheck {
pub fn allow() -> Self {
Self {
allowed: true,
reason: None,
}
}
pub fn reject(reason: impl Into<String>) -> Self {
Self {
allowed: false,
reason: Some(reason.into()),
}
}
}
pub trait EquityRuleHooks {
fn can_buy(
&self,
execution_date: NaiveDate,
snapshot: &DailyMarketSnapshot,
candidate: &CandidateEligibility,
) -> RuleCheck;
fn can_sell(
&self,
execution_date: NaiveDate,
snapshot: &DailyMarketSnapshot,
candidate: &CandidateEligibility,
position: &Position,
) -> RuleCheck;
}
#[derive(Debug, Clone, Default)]
pub struct ChinaEquityRuleHooks;
impl ChinaEquityRuleHooks {
fn at_upper_limit(snapshot: &DailyMarketSnapshot) -> bool {
snapshot.open >= snapshot.upper_limit - 1e-6
}
fn at_lower_limit(snapshot: &DailyMarketSnapshot) -> bool {
snapshot.open <= snapshot.lower_limit + 1e-6
}
}
impl EquityRuleHooks for ChinaEquityRuleHooks {
fn can_buy(
&self,
_execution_date: NaiveDate,
snapshot: &DailyMarketSnapshot,
candidate: &CandidateEligibility,
) -> RuleCheck {
if snapshot.paused || candidate.is_paused {
return RuleCheck::reject("paused");
}
if !candidate.allow_buy {
return RuleCheck::reject("buy disabled by eligibility flags");
}
if Self::at_upper_limit(snapshot) {
return RuleCheck::reject("open at or above upper limit");
}
RuleCheck::allow()
}
fn can_sell(
&self,
execution_date: NaiveDate,
snapshot: &DailyMarketSnapshot,
candidate: &CandidateEligibility,
position: &Position,
) -> RuleCheck {
if snapshot.paused || candidate.is_paused {
return RuleCheck::reject("paused");
}
if !candidate.allow_sell {
return RuleCheck::reject("sell disabled by eligibility flags");
}
if Self::at_lower_limit(snapshot) {
return RuleCheck::reject("open at or below lower limit");
}
if position.sellable_qty(execution_date) == 0 {
return RuleCheck::reject("t+1 sellable quantity is zero");
}
RuleCheck::allow()
}
}