实现类型化订单有效期合同

This commit is contained in:
boris
2026-08-26 19:48:21 +08:00
parent 0793473210
commit 88f5a1a0ae
10 changed files with 1025 additions and 73 deletions
+174
View File
@@ -1014,6 +1014,35 @@ pub enum AlgoOrderStyle {
Twap,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OrderTimeInForce {
Day,
Ioc,
Fok,
Gtc,
}
impl OrderTimeInForce {
pub fn parse(value: &str) -> Option<Self> {
match value.trim().to_ascii_lowercase().as_str() {
"day" => Some(Self::Day),
"ioc" | "immediate_or_cancel" | "immediate-or-cancel" => Some(Self::Ioc),
"fok" | "fill_or_kill" | "fill-or-kill" => Some(Self::Fok),
"gtc" | "good_til_canceled" | "good-til-canceled" => Some(Self::Gtc),
_ => None,
}
}
pub fn as_str(self) -> &'static str {
match self {
Self::Day => "day",
Self::Ioc => "ioc",
Self::Fok => "fok",
Self::Gtc => "gtc",
}
}
}
#[derive(Debug, Clone)]
pub enum TargetPortfolioOrderPricing {
LimitPrices(BTreeMap<String, f64>),
@@ -1026,6 +1055,10 @@ pub enum TargetPortfolioOrderPricing {
#[derive(Debug, Clone)]
pub enum OrderIntent {
WithTimeInForce {
intent: Box<OrderIntent>,
time_in_force: OrderTimeInForce,
},
Shares {
symbol: String,
quantity: i32,
@@ -1174,6 +1207,100 @@ pub enum OrderIntent {
},
}
impl OrderIntent {
pub fn with_time_in_force(self, time_in_force: OrderTimeInForce) -> Self {
match self {
Self::WithTimeInForce { intent, .. } => Self::WithTimeInForce {
intent,
time_in_force,
},
intent => Self::WithTimeInForce {
intent: Box::new(intent),
time_in_force,
},
}
}
pub fn time_in_force(&self) -> Option<OrderTimeInForce> {
match self {
Self::WithTimeInForce { time_in_force, .. } => Some(*time_in_force),
_ => None,
}
}
pub fn into_time_in_force_parts(self) -> (Self, Option<OrderTimeInForce>) {
match self {
Self::WithTimeInForce {
intent,
time_in_force,
} => (*intent, Some(time_in_force)),
intent => (intent, None),
}
}
pub fn apply_time_in_force(self, time_in_force: Option<OrderTimeInForce>) -> Self {
match time_in_force {
Some(time_in_force) => self.with_time_in_force(time_in_force),
None => self,
}
}
pub fn unwrapped(&self) -> &Self {
match self {
Self::WithTimeInForce { intent, .. } => intent.unwrapped(),
_ => self,
}
}
pub fn supports_time_in_force(&self, time_in_force: OrderTimeInForce) -> bool {
let intent = self.unwrapped();
if matches!(
intent,
Self::CancelOrder { .. }
| Self::CancelSymbol { .. }
| Self::CancelAll { .. }
| Self::UpdateUniverse { .. }
| Self::Subscribe { .. }
| Self::Unsubscribe { .. }
| Self::DepositWithdraw { .. }
| Self::FinanceRepay { .. }
| Self::SetManagementFeeRate { .. }
| Self::Futures { .. }
) {
return false;
}
let is_algo = matches!(
intent,
Self::AlgoValue { .. } | Self::AlgoPercent { .. } | Self::TimedTargetValue { .. }
) || matches!(
intent,
Self::TargetPortfolioSmart {
order_prices: Some(TargetPortfolioOrderPricing::AlgoOrder { .. }),
..
}
);
let is_limit = matches!(
intent,
Self::LimitShares { .. }
| Self::LimitLots { .. }
| Self::LimitTargetShares { .. }
| Self::LimitTargetValue { .. }
| Self::LimitValue { .. }
| Self::LimitPercent { .. }
| Self::LimitTargetPercent { .. }
| Self::TargetPortfolioSmart {
order_prices: Some(TargetPortfolioOrderPricing::LimitPrices(_)),
..
}
);
match time_in_force {
OrderTimeInForce::Day | OrderTimeInForce::Ioc => true,
OrderTimeInForce::Fok => !is_algo,
OrderTimeInForce::Gtc => is_limit,
}
}
}
#[derive(Debug, Clone)]
pub struct CnSmallCapRotationConfig {
pub strategy_name: String,
@@ -2909,6 +3036,53 @@ mod tests {
use super::*;
use crate::{BenchmarkSnapshot, CandidateEligibility, DailyFactorSnapshot};
#[test]
fn order_time_in_force_parsing_and_order_type_contract_are_explicit() {
assert_eq!(OrderTimeInForce::parse("DAY"), Some(OrderTimeInForce::Day));
assert_eq!(
OrderTimeInForce::parse("immediate_or_cancel"),
Some(OrderTimeInForce::Ioc)
);
assert_eq!(
OrderTimeInForce::parse("fill-or-kill"),
Some(OrderTimeInForce::Fok)
);
assert_eq!(
OrderTimeInForce::parse("good_til_canceled"),
Some(OrderTimeInForce::Gtc)
);
assert_eq!(OrderTimeInForce::parse("unknown"), None);
let market = OrderIntent::Shares {
symbol: "000001.SZ".to_string(),
quantity: 100,
reason: "market".to_string(),
};
assert!(market.supports_time_in_force(OrderTimeInForce::Day));
assert!(market.supports_time_in_force(OrderTimeInForce::Ioc));
assert!(market.supports_time_in_force(OrderTimeInForce::Fok));
assert!(!market.supports_time_in_force(OrderTimeInForce::Gtc));
let limit = OrderIntent::LimitShares {
symbol: "000001.SZ".to_string(),
quantity: 100,
limit_price: 10.0,
reason: "limit".to_string(),
};
assert!(limit.supports_time_in_force(OrderTimeInForce::Gtc));
let algo = OrderIntent::AlgoValue {
symbol: "000001.SZ".to_string(),
value: 10_000.0,
style: AlgoOrderStyle::Vwap,
start_time: None,
end_time: None,
reason: "algo".to_string(),
};
assert!(!algo.supports_time_in_force(OrderTimeInForce::Fok));
assert!(!algo.supports_time_in_force(OrderTimeInForce::Gtc));
}
#[test]
fn omni_microcap_projection_uses_configured_trading_cost() {
let mut cfg = OmniMicroCapConfig::omni_microcap();