初始化回测核心引擎骨架
This commit is contained in:
100
crates/fidc-core/src/rules.rs
Normal file
100
crates/fidc-core/src/rules.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user