164 lines
4.4 KiB
Rust
164 lines
4.4 KiB
Rust
use std::collections::BTreeMap;
|
|
|
|
use chrono::NaiveDate;
|
|
|
|
use crate::events::OrderSide;
|
|
|
|
pub const STOCK_PIT_TAX_CHANGE_DATE: (i32, u32, u32) = (2023, 8, 28);
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct TradingCost {
|
|
pub commission: f64,
|
|
pub stamp_tax: f64,
|
|
}
|
|
|
|
impl TradingCost {
|
|
pub fn total(self) -> f64 {
|
|
self.commission + self.stamp_tax
|
|
}
|
|
}
|
|
|
|
pub trait CostModel {
|
|
fn calculate(&self, date: NaiveDate, side: OrderSide, gross_amount: f64) -> TradingCost;
|
|
|
|
fn calculate_with_order_state(
|
|
&self,
|
|
date: NaiveDate,
|
|
side: OrderSide,
|
|
gross_amount: f64,
|
|
_order_id: Option<u64>,
|
|
_commission_state: &mut BTreeMap<u64, f64>,
|
|
) -> TradingCost {
|
|
self.calculate(date, side, gross_amount)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct ChinaAShareCostModel {
|
|
pub commission_rate: f64,
|
|
pub stamp_tax_rate_before_change: f64,
|
|
pub stamp_tax_rate_after_change: f64,
|
|
pub minimum_commission: f64,
|
|
}
|
|
|
|
impl Default for ChinaAShareCostModel {
|
|
fn default() -> Self {
|
|
Self {
|
|
commission_rate: 0.0003,
|
|
stamp_tax_rate_before_change: 0.001,
|
|
stamp_tax_rate_after_change: 0.0005,
|
|
minimum_commission: 5.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ChinaAShareCostModel {
|
|
pub fn commission_for(&self, gross_amount: f64) -> f64 {
|
|
if gross_amount <= 0.0 {
|
|
return 0.0;
|
|
}
|
|
(gross_amount * self.commission_rate).max(self.minimum_commission)
|
|
}
|
|
|
|
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 {
|
|
self.stamp_tax_rate_before_change
|
|
} else {
|
|
self.stamp_tax_rate_after_change
|
|
}
|
|
}
|
|
|
|
pub fn stamp_tax_for(&self, date: NaiveDate, side: OrderSide, gross_amount: f64) -> f64 {
|
|
if gross_amount <= 0.0 || side == OrderSide::Buy {
|
|
return 0.0;
|
|
}
|
|
gross_amount * self.stamp_tax_rate_for(date)
|
|
}
|
|
|
|
pub fn commission_for_order_fill(
|
|
&self,
|
|
gross_amount: f64,
|
|
order_id: Option<u64>,
|
|
commission_state: &mut BTreeMap<u64, f64>,
|
|
) -> f64 {
|
|
if gross_amount <= 0.0 {
|
|
return 0.0;
|
|
}
|
|
|
|
let raw_commission = gross_amount * self.commission_rate;
|
|
let Some(order_id) = order_id else {
|
|
return raw_commission.max(self.minimum_commission);
|
|
};
|
|
|
|
let remaining_minimum = commission_state
|
|
.entry(order_id)
|
|
.or_insert(self.minimum_commission);
|
|
if raw_commission > *remaining_minimum {
|
|
let charged = if (*remaining_minimum - self.minimum_commission).abs() < 1e-12 {
|
|
raw_commission
|
|
} else {
|
|
raw_commission - *remaining_minimum
|
|
};
|
|
*remaining_minimum = 0.0;
|
|
charged
|
|
} else {
|
|
let charged = if (*remaining_minimum - self.minimum_commission).abs() < 1e-12 {
|
|
self.minimum_commission
|
|
} else {
|
|
0.0
|
|
};
|
|
*remaining_minimum -= raw_commission;
|
|
charged
|
|
}
|
|
}
|
|
}
|
|
|
|
impl CostModel for ChinaAShareCostModel {
|
|
fn calculate(&self, date: NaiveDate, side: OrderSide, gross_amount: f64) -> TradingCost {
|
|
if gross_amount <= 0.0 {
|
|
return TradingCost {
|
|
commission: 0.0,
|
|
stamp_tax: 0.0,
|
|
};
|
|
}
|
|
|
|
let commission = self.commission_for(gross_amount);
|
|
let stamp_tax = self.stamp_tax_for(date, side, gross_amount);
|
|
|
|
TradingCost {
|
|
commission,
|
|
stamp_tax,
|
|
}
|
|
}
|
|
|
|
fn calculate_with_order_state(
|
|
&self,
|
|
date: NaiveDate,
|
|
side: OrderSide,
|
|
gross_amount: f64,
|
|
order_id: Option<u64>,
|
|
commission_state: &mut BTreeMap<u64, f64>,
|
|
) -> TradingCost {
|
|
if gross_amount <= 0.0 {
|
|
return TradingCost {
|
|
commission: 0.0,
|
|
stamp_tax: 0.0,
|
|
};
|
|
}
|
|
|
|
let commission = self.commission_for_order_fill(gross_amount, order_id, commission_state);
|
|
let stamp_tax = self.stamp_tax_for(date, side, gross_amount);
|
|
|
|
TradingCost {
|
|
commission,
|
|
stamp_tax,
|
|
}
|
|
}
|
|
}
|