实现FIDC配置化风控与交易成本
This commit is contained in:
@@ -12,7 +12,7 @@ use crate::events::{
|
||||
};
|
||||
use crate::instrument::Instrument;
|
||||
use crate::portfolio::PortfolioState;
|
||||
use crate::risk_control::ChinaAShareRiskControl;
|
||||
use crate::risk_control::{ChinaAShareRiskControl, FidcRiskControlConfig};
|
||||
use crate::rules::{EquityRuleHooks, RuleCheck};
|
||||
use crate::strategy::{
|
||||
AlgoOrderStyle, OpenOrderView, OrderIntent, StrategyDecision, TargetPortfolioOrderPricing,
|
||||
@@ -171,6 +171,8 @@ pub struct BrokerSimulator<C, R> {
|
||||
strict_value_budget: bool,
|
||||
aiquant_execution_rules: bool,
|
||||
same_day_buy_close_mark_at_fill: bool,
|
||||
risk_config: FidcRiskControlConfig,
|
||||
same_day_sold_symbols: RefCell<BTreeMap<NaiveDate, BTreeSet<String>>>,
|
||||
intraday_execution_start_time: Option<NaiveTime>,
|
||||
runtime_intraday_start_time: Cell<Option<NaiveTime>>,
|
||||
runtime_intraday_end_time: Cell<Option<NaiveTime>>,
|
||||
@@ -194,6 +196,8 @@ impl<C, R> BrokerSimulator<C, R> {
|
||||
strict_value_budget: false,
|
||||
aiquant_execution_rules: false,
|
||||
same_day_buy_close_mark_at_fill: false,
|
||||
risk_config: FidcRiskControlConfig::default(),
|
||||
same_day_sold_symbols: RefCell::new(BTreeMap::new()),
|
||||
intraday_execution_start_time: None,
|
||||
runtime_intraday_start_time: Cell::new(None),
|
||||
runtime_intraday_end_time: Cell::new(None),
|
||||
@@ -221,6 +225,8 @@ impl<C, R> BrokerSimulator<C, R> {
|
||||
strict_value_budget: false,
|
||||
aiquant_execution_rules: false,
|
||||
same_day_buy_close_mark_at_fill: false,
|
||||
risk_config: FidcRiskControlConfig::default(),
|
||||
same_day_sold_symbols: RefCell::new(BTreeMap::new()),
|
||||
intraday_execution_start_time: None,
|
||||
runtime_intraday_start_time: Cell::new(None),
|
||||
runtime_intraday_end_time: Cell::new(None),
|
||||
@@ -259,6 +265,14 @@ impl<C, R> BrokerSimulator<C, R> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_risk_config(mut self, config: FidcRiskControlConfig) -> Self {
|
||||
self.volume_limit = config.trading_constraints.volume_limit_enabled;
|
||||
self.volume_percent = config.trading_constraints.volume_percent;
|
||||
self.liquidity_limit = config.trading_constraints.liquidity_limit_enabled;
|
||||
self.risk_config = config;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn same_day_buy_close_mark_at_fill(&self) -> bool {
|
||||
self.same_day_buy_close_mark_at_fill
|
||||
}
|
||||
@@ -1381,6 +1395,34 @@ where
|
||||
.retain(|existing| existing.order_id != order_id);
|
||||
}
|
||||
|
||||
fn mark_same_day_sold(&self, date: NaiveDate, symbol: &str) {
|
||||
self.same_day_sold_symbols
|
||||
.borrow_mut()
|
||||
.entry(date)
|
||||
.or_default()
|
||||
.insert(symbol.to_string());
|
||||
}
|
||||
|
||||
fn same_day_rebuy_rejection_reason(
|
||||
&self,
|
||||
date: NaiveDate,
|
||||
symbol: &str,
|
||||
) -> Option<&'static str> {
|
||||
if self
|
||||
.risk_config
|
||||
.static_rules
|
||||
.forbid_same_day_rebuy_after_sell
|
||||
&& self
|
||||
.same_day_sold_symbols
|
||||
.borrow()
|
||||
.get(&date)
|
||||
.is_some_and(|symbols| symbols.contains(symbol))
|
||||
{
|
||||
return Some("same_day_rebuy_forbidden");
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn extend_report(into: &mut BrokerExecutionReport, mut other: BrokerExecutionReport) {
|
||||
into.order_events.append(&mut other.order_events);
|
||||
into.fill_events.append(&mut other.fill_events);
|
||||
@@ -2181,16 +2223,17 @@ where
|
||||
} else {
|
||||
ChinaAShareRiskControl::buy_check_price(snapshot, self.execution_price_field)
|
||||
};
|
||||
if let Some(reason) = ChinaAShareRiskControl::buy_rejection_reason(
|
||||
if let Some(reason) = ChinaAShareRiskControl::buy_rejection_reason_with_config(
|
||||
date,
|
||||
candidate,
|
||||
snapshot,
|
||||
instrument,
|
||||
check_price,
|
||||
&self.risk_config,
|
||||
) {
|
||||
return RuleCheck::reject(reason);
|
||||
}
|
||||
if !self.aiquant_execution_rules {
|
||||
if !self.aiquant_execution_rules && !self.rules.duplicates_standard_china_risk() {
|
||||
return self
|
||||
.rules
|
||||
.can_buy(date, snapshot, candidate, self.execution_price_field);
|
||||
@@ -2220,16 +2263,20 @@ where
|
||||
} else {
|
||||
ChinaAShareRiskControl::buy_check_price(snapshot, self.execution_price_field)
|
||||
};
|
||||
if let Some(reason) = ChinaAShareRiskControl::buy_rejection_reason(
|
||||
if let Some(reason) = self.same_day_rebuy_rejection_reason(date, symbol) {
|
||||
return RuleCheck::reject(reason);
|
||||
}
|
||||
if let Some(reason) = ChinaAShareRiskControl::buy_rejection_reason_with_config(
|
||||
date,
|
||||
candidate,
|
||||
snapshot,
|
||||
instrument,
|
||||
check_price,
|
||||
&self.risk_config,
|
||||
) {
|
||||
return RuleCheck::reject(reason);
|
||||
}
|
||||
if !self.aiquant_execution_rules {
|
||||
if !self.aiquant_execution_rules && !self.rules.duplicates_standard_china_risk() {
|
||||
return self
|
||||
.rules
|
||||
.can_buy(date, snapshot, candidate, self.execution_price_field);
|
||||
@@ -2263,17 +2310,18 @@ where
|
||||
} else {
|
||||
ChinaAShareRiskControl::sell_check_price(snapshot, self.execution_price_field)
|
||||
};
|
||||
if let Some(reason) = ChinaAShareRiskControl::sell_rejection_reason(
|
||||
if let Some(reason) = ChinaAShareRiskControl::sell_rejection_reason_with_config(
|
||||
date,
|
||||
candidate,
|
||||
snapshot,
|
||||
instrument,
|
||||
Some(position),
|
||||
check_price,
|
||||
&self.risk_config,
|
||||
) {
|
||||
return RuleCheck::reject(reason);
|
||||
}
|
||||
if !self.aiquant_execution_rules {
|
||||
if !self.aiquant_execution_rules && !self.rules.duplicates_standard_china_risk() {
|
||||
return self.rules.can_sell(
|
||||
date,
|
||||
snapshot,
|
||||
@@ -2929,6 +2977,7 @@ where
|
||||
net_cash_flow: net_cash,
|
||||
reason: reason.to_string(),
|
||||
});
|
||||
self.mark_same_day_sold(date, symbol);
|
||||
Self::emit_order_process_event(
|
||||
report,
|
||||
date,
|
||||
@@ -5065,10 +5114,16 @@ where
|
||||
return None;
|
||||
}
|
||||
match side {
|
||||
OrderSide::Buy if snapshot.is_at_upper_limit_price(execution_price) => {
|
||||
OrderSide::Buy
|
||||
if self.risk_config.static_rules.reject_upper_limit_buy
|
||||
&& snapshot.is_at_upper_limit_price(execution_price) =>
|
||||
{
|
||||
Some("open at or above upper limit")
|
||||
}
|
||||
OrderSide::Sell if snapshot.is_at_lower_limit_price(execution_price) => {
|
||||
OrderSide::Sell
|
||||
if self.risk_config.static_rules.reject_lower_limit_sell
|
||||
&& snapshot.is_at_lower_limit_price(execution_price) =>
|
||||
{
|
||||
Some("open at or below lower limit")
|
||||
}
|
||||
_ => None,
|
||||
@@ -5991,6 +6046,7 @@ mod tests {
|
||||
stamp_tax_rate_before_change: 0.0005,
|
||||
stamp_tax_rate_after_change: 0.0005,
|
||||
minimum_commission: 5.0,
|
||||
..ChinaAShareCostModel::default()
|
||||
},
|
||||
ChinaEquityRuleHooks,
|
||||
PriceField::Last,
|
||||
|
||||
Reference in New Issue
Block a user