chore: 更新 fidc-backtest-engine - 2026-05-22
This commit is contained in:
@@ -10,7 +10,9 @@ use crate::events::{
|
||||
AccountEvent, FillEvent, OrderEvent, OrderSide, OrderStatus, PositionEvent, ProcessEvent,
|
||||
ProcessEventKind,
|
||||
};
|
||||
use crate::instrument::Instrument;
|
||||
use crate::portfolio::PortfolioState;
|
||||
use crate::risk_control::ChinaAShareRiskControl;
|
||||
use crate::rules::{EquityRuleHooks, RuleCheck};
|
||||
use crate::strategy::{
|
||||
AlgoOrderStyle, OpenOrderView, OrderIntent, StrategyDecision, TargetPortfolioOrderPricing,
|
||||
@@ -1850,19 +1852,27 @@ where
|
||||
date: NaiveDate,
|
||||
snapshot: &crate::data::DailyMarketSnapshot,
|
||||
candidate: &crate::data::CandidateEligibility,
|
||||
instrument: Option<&Instrument>,
|
||||
) -> RuleCheck {
|
||||
let check_price = if self.aiquant_rqalpha_execution_rules {
|
||||
self.aiquant_limit_check_price(snapshot, OrderSide::Buy)
|
||||
} else {
|
||||
ChinaAShareRiskControl::buy_check_price(snapshot, self.execution_price_field)
|
||||
};
|
||||
if let Some(reason) = ChinaAShareRiskControl::buy_rejection_reason(
|
||||
date,
|
||||
candidate,
|
||||
snapshot,
|
||||
instrument,
|
||||
check_price,
|
||||
) {
|
||||
return RuleCheck::reject(reason);
|
||||
}
|
||||
if !self.aiquant_rqalpha_execution_rules {
|
||||
return self
|
||||
.rules
|
||||
.can_buy(date, snapshot, candidate, self.execution_price_field);
|
||||
}
|
||||
if snapshot.paused || candidate.is_paused {
|
||||
return RuleCheck::reject("paused");
|
||||
}
|
||||
let check_price = self.aiquant_limit_check_price(snapshot, OrderSide::Buy);
|
||||
if snapshot.is_at_upper_limit_price(check_price) {
|
||||
return RuleCheck::reject("open at or above upper limit");
|
||||
}
|
||||
RuleCheck::allow()
|
||||
}
|
||||
|
||||
@@ -1871,8 +1881,24 @@ where
|
||||
date: NaiveDate,
|
||||
snapshot: &crate::data::DailyMarketSnapshot,
|
||||
candidate: &crate::data::CandidateEligibility,
|
||||
instrument: Option<&Instrument>,
|
||||
position: &crate::portfolio::Position,
|
||||
) -> RuleCheck {
|
||||
let check_price = if self.aiquant_rqalpha_execution_rules {
|
||||
self.aiquant_limit_check_price(snapshot, OrderSide::Sell)
|
||||
} else {
|
||||
ChinaAShareRiskControl::sell_check_price(snapshot, self.execution_price_field)
|
||||
};
|
||||
if let Some(reason) = ChinaAShareRiskControl::sell_rejection_reason(
|
||||
date,
|
||||
candidate,
|
||||
snapshot,
|
||||
instrument,
|
||||
Some(position),
|
||||
check_price,
|
||||
) {
|
||||
return RuleCheck::reject(reason);
|
||||
}
|
||||
if !self.aiquant_rqalpha_execution_rules {
|
||||
return self.rules.can_sell(
|
||||
date,
|
||||
@@ -1882,16 +1908,6 @@ where
|
||||
self.execution_price_field,
|
||||
);
|
||||
}
|
||||
if snapshot.paused || candidate.is_paused {
|
||||
return RuleCheck::reject("paused");
|
||||
}
|
||||
let check_price = self.aiquant_limit_check_price(snapshot, OrderSide::Sell);
|
||||
if snapshot.is_at_lower_limit_price(check_price) {
|
||||
return RuleCheck::reject("open at or below lower limit");
|
||||
}
|
||||
if position.sellable_qty(date) == 0 {
|
||||
return RuleCheck::reject("t+1 sellable quantity is zero");
|
||||
}
|
||||
RuleCheck::allow()
|
||||
}
|
||||
|
||||
@@ -1917,7 +1933,8 @@ where
|
||||
let Ok(candidate) = data.require_candidate(date, symbol) else {
|
||||
return current_qty;
|
||||
};
|
||||
let rule = self.sell_rule_check(date, snapshot, candidate, position);
|
||||
let rule =
|
||||
self.sell_rule_check(date, snapshot, candidate, data.instrument(symbol), position);
|
||||
if !rule.allowed {
|
||||
return current_qty;
|
||||
}
|
||||
@@ -1955,7 +1972,7 @@ where
|
||||
let Ok(candidate) = data.require_candidate(date, symbol) else {
|
||||
return current_qty;
|
||||
};
|
||||
let rule = self.buy_rule_check(date, snapshot, candidate);
|
||||
let rule = self.buy_rule_check(date, snapshot, candidate, data.instrument(symbol));
|
||||
if !rule.allowed {
|
||||
return current_qty;
|
||||
}
|
||||
@@ -1999,7 +2016,8 @@ where
|
||||
let position = portfolio.position(symbol)?;
|
||||
let snapshot = data.require_market(date, symbol).ok()?;
|
||||
let candidate = data.require_candidate(date, symbol).ok()?;
|
||||
let rule = self.sell_rule_check(date, snapshot, candidate, position);
|
||||
let rule =
|
||||
self.sell_rule_check(date, snapshot, candidate, data.instrument(symbol), position);
|
||||
if !rule.allowed {
|
||||
return rule.reason;
|
||||
}
|
||||
@@ -2039,7 +2057,7 @@ where
|
||||
) -> Option<String> {
|
||||
let snapshot = data.require_market(date, symbol).ok()?;
|
||||
let candidate = data.require_candidate(date, symbol).ok()?;
|
||||
let rule = self.buy_rule_check(date, snapshot, candidate);
|
||||
let rule = self.buy_rule_check(date, snapshot, candidate, data.instrument(symbol));
|
||||
if !rule.allowed {
|
||||
return rule.reason;
|
||||
}
|
||||
@@ -2109,12 +2127,15 @@ where
|
||||
);
|
||||
}
|
||||
|
||||
let rule = self.sell_rule_check(date, snapshot, candidate, position);
|
||||
let rule =
|
||||
self.sell_rule_check(date, snapshot, candidate, data.instrument(symbol), position);
|
||||
if !rule.allowed {
|
||||
let rule_reason = rule.reason.as_deref().unwrap_or_default().to_string();
|
||||
let status = match rule.reason.as_deref() {
|
||||
Some("paused")
|
||||
| Some("sell disabled by eligibility flags")
|
||||
| Some("sell_disabled")
|
||||
| Some("lower_limit")
|
||||
| Some("open at or below lower limit") => OrderStatus::Canceled,
|
||||
_ => OrderStatus::Rejected,
|
||||
};
|
||||
@@ -3542,12 +3563,14 @@ where
|
||||
);
|
||||
}
|
||||
|
||||
let rule = self.buy_rule_check(date, snapshot, candidate);
|
||||
let rule = self.buy_rule_check(date, snapshot, candidate, data.instrument(symbol));
|
||||
if !rule.allowed {
|
||||
let rule_reason = rule.reason.as_deref().unwrap_or_default().to_string();
|
||||
let status = match rule.reason.as_deref() {
|
||||
Some("paused")
|
||||
| Some("buy disabled by eligibility flags")
|
||||
| Some("buy_disabled")
|
||||
| Some("upper_limit")
|
||||
| Some("open at or above upper limit") => OrderStatus::Canceled,
|
||||
_ => OrderStatus::Rejected,
|
||||
};
|
||||
@@ -4721,6 +4744,8 @@ fn zero_fill_status_for_reason(reason: &str) -> OrderStatus {
|
||||
| "tick volume limit"
|
||||
| "intraday quote liquidity exhausted"
|
||||
| "no execution quotes after start"
|
||||
| "upper_limit"
|
||||
| "lower_limit"
|
||||
| "open at or above upper limit"
|
||||
| "open at or below lower limit" => OrderStatus::Canceled,
|
||||
_ => OrderStatus::Rejected,
|
||||
@@ -4733,6 +4758,8 @@ fn final_partial_fill_status(partial_reason: Option<&str>) -> OrderStatus {
|
||||
if reason.contains("market liquidity or volume limit")
|
||||
|| reason.contains("intraday quote liquidity exhausted")
|
||||
|| reason.contains("no execution quotes after start")
|
||||
|| reason.contains("upper_limit")
|
||||
|| reason.contains("lower_limit")
|
||||
|| reason.contains("open at or above upper limit")
|
||||
|| reason.contains("open at or below lower limit") =>
|
||||
{
|
||||
@@ -4913,7 +4940,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn aiquant_rules_allow_buy_when_day_flags_block_but_last_price_is_tradable() {
|
||||
fn aiquant_rules_keep_structured_buy_risk_while_using_aiquant_limit_price() {
|
||||
let mut snapshot = limit_test_snapshot();
|
||||
snapshot.open = 11.0;
|
||||
snapshot.day_open = 11.0;
|
||||
@@ -4927,12 +4954,9 @@ mod tests {
|
||||
ChinaEquityRuleHooks,
|
||||
PriceField::Last,
|
||||
);
|
||||
let default_rule = default_broker.buy_rule_check(date, &snapshot, &candidate);
|
||||
let default_rule = default_broker.buy_rule_check(date, &snapshot, &candidate, None);
|
||||
assert!(!default_rule.allowed);
|
||||
assert_eq!(
|
||||
default_rule.reason.as_deref(),
|
||||
Some("buy disabled by eligibility flags")
|
||||
);
|
||||
assert_eq!(default_rule.reason.as_deref(), Some("trade_disabled"));
|
||||
|
||||
let aiquant_broker = BrokerSimulator::new_with_execution_price(
|
||||
ChinaAShareCostModel::default(),
|
||||
@@ -4940,7 +4964,13 @@ mod tests {
|
||||
PriceField::Last,
|
||||
)
|
||||
.with_aiquant_rqalpha_execution_rules(true);
|
||||
let aiquant_rule = aiquant_broker.buy_rule_check(date, &snapshot, &candidate);
|
||||
let aiquant_rule = aiquant_broker.buy_rule_check(date, &snapshot, &candidate, None);
|
||||
assert!(!aiquant_rule.allowed);
|
||||
assert_eq!(aiquant_rule.reason.as_deref(), Some("trade_disabled"));
|
||||
|
||||
let tradable_candidate = limit_test_candidate(true, true);
|
||||
let aiquant_rule =
|
||||
aiquant_broker.buy_rule_check(date, &snapshot, &tradable_candidate, None);
|
||||
assert!(aiquant_rule.allowed);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user