实现FIDC配置化风控与交易成本

This commit is contained in:
boris
2026-07-02 07:16:47 +08:00
parent 754fc91376
commit 7db0e8da1d
17 changed files with 2689 additions and 249 deletions
+57 -9
View File
@@ -3,6 +3,7 @@ use std::collections::BTreeMap;
use chrono::NaiveDate;
use crate::events::OrderSide;
use crate::risk_control::TradingConstraintConfig;
pub const STOCK_PIT_TAX_CHANGE_DATE: (i32, u32, u32) = (2023, 8, 28);
@@ -38,6 +39,7 @@ pub struct ChinaAShareCostModel {
pub commission_rate: f64,
pub stamp_tax_rate_before_change: f64,
pub stamp_tax_rate_after_change: f64,
pub stamp_tax_change_date: NaiveDate,
pub minimum_commission: f64,
}
@@ -47,6 +49,7 @@ impl Default for ChinaAShareCostModel {
commission_rate: 0.0008,
stamp_tax_rate_before_change: 0.001,
stamp_tax_rate_after_change: 0.0005,
stamp_tax_change_date: default_stamp_tax_change_date(),
minimum_commission: 5.0,
}
}
@@ -55,13 +58,23 @@ impl Default for ChinaAShareCostModel {
impl ChinaAShareCostModel {
pub fn aiquant_default() -> Self {
Self {
commission_rate: 0.0008,
commission_rate: 0.0003,
stamp_tax_rate_before_change: 0.0005,
stamp_tax_rate_after_change: 0.0005,
..Self::default()
}
}
pub fn from_trading_constraints(config: TradingConstraintConfig) -> Self {
Self {
commission_rate: config.commission_rate,
stamp_tax_rate_before_change: config.stamp_tax_rate_before_change,
stamp_tax_rate_after_change: config.stamp_tax_rate_after_change,
stamp_tax_change_date: config.stamp_tax_change_date,
minimum_commission: config.minimum_commission,
}
}
pub fn commission_for(&self, gross_amount: f64) -> f64 {
if gross_amount <= 0.0 {
return 0.0;
@@ -70,13 +83,7 @@ impl ChinaAShareCostModel {
}
pub fn stamp_tax_rate_for(&self, date: NaiveDate) -> f64 {
let change_date = NaiveDate::from_ymd_opt(
STOCK_PIT_TAX_CHANGE_DATE.0,
STOCK_PIT_TAX_CHANGE_DATE.1,
STOCK_PIT_TAX_CHANGE_DATE.2,
)
.expect("valid pit tax change date");
if date < change_date {
if date < self.stamp_tax_change_date {
self.stamp_tax_rate_before_change
} else {
self.stamp_tax_rate_after_change
@@ -128,6 +135,15 @@ impl ChinaAShareCostModel {
}
}
fn default_stamp_tax_change_date() -> NaiveDate {
NaiveDate::from_ymd_opt(
STOCK_PIT_TAX_CHANGE_DATE.0,
STOCK_PIT_TAX_CHANGE_DATE.1,
STOCK_PIT_TAX_CHANGE_DATE.2,
)
.expect("valid pit tax change date")
}
impl CostModel for ChinaAShareCostModel {
fn calculate(&self, date: NaiveDate, side: OrderSide, gross_amount: f64) -> TradingCost {
if gross_amount <= 0.0 {
@@ -180,9 +196,41 @@ mod tests {
let model = ChinaAShareCostModel::aiquant_default();
let date = NaiveDate::from_ymd_opt(2025, 11, 11).expect("valid date");
assert!((model.commission_for(248_059.812) - 198.4478496).abs() < 1e-9);
assert!((model.commission_for(248_059.812) - 74.4179436).abs() < 1e-9);
assert!(
(model.stamp_tax_for(date, OrderSide::Sell, 245_747.007) - 122.8735035).abs() < 1e-9
);
}
#[test]
fn cost_model_can_use_configurable_stamp_tax_change_date() {
let config = TradingConstraintConfig {
commission_rate: 0.0003,
minimum_commission: 5.0,
stamp_tax_rate_before_change: 0.002,
stamp_tax_rate_after_change: 0.001,
stamp_tax_change_date: NaiveDate::from_ymd_opt(2025, 1, 10).expect("valid date"),
..TradingConstraintConfig::default()
};
let model = ChinaAShareCostModel::from_trading_constraints(config);
assert!(
(model.stamp_tax_for(
NaiveDate::from_ymd_opt(2025, 1, 9).expect("valid date"),
OrderSide::Sell,
10_000.0
) - 20.0)
.abs()
< 1e-9
);
assert!(
(model.stamp_tax_for(
NaiveDate::from_ymd_opt(2025, 1, 10).expect("valid date"),
OrderSide::Sell,
10_000.0
) - 10.0)
.abs()
< 1e-9
);
}
}