feat: add decision-scoped buy denials to broker submission
This commit is contained in:
@@ -380,6 +380,7 @@ pub struct BrokerSimulator<C, R> {
|
|||||||
runtime_intraday_start_time: Cell<Option<NaiveTime>>,
|
runtime_intraday_start_time: Cell<Option<NaiveTime>>,
|
||||||
runtime_intraday_end_time: Cell<Option<NaiveTime>>,
|
runtime_intraday_end_time: Cell<Option<NaiveTime>>,
|
||||||
runtime_decision_date: Cell<Option<NaiveDate>>,
|
runtime_decision_date: Cell<Option<NaiveDate>>,
|
||||||
|
runtime_buy_denials: RefCell<BTreeMap<String, String>>,
|
||||||
runtime_order_created_date: Cell<Option<NaiveDate>>,
|
runtime_order_created_date: Cell<Option<NaiveDate>>,
|
||||||
runtime_decision_total_equity: Cell<Option<f64>>,
|
runtime_decision_total_equity: Cell<Option<f64>>,
|
||||||
runtime_target_position_limit: Cell<Option<usize>>,
|
runtime_target_position_limit: Cell<Option<usize>>,
|
||||||
@@ -412,6 +413,7 @@ impl<C, R> BrokerSimulator<C, R> {
|
|||||||
runtime_intraday_start_time: Cell::new(None),
|
runtime_intraday_start_time: Cell::new(None),
|
||||||
runtime_intraday_end_time: Cell::new(None),
|
runtime_intraday_end_time: Cell::new(None),
|
||||||
runtime_decision_date: Cell::new(None),
|
runtime_decision_date: Cell::new(None),
|
||||||
|
runtime_buy_denials: RefCell::new(BTreeMap::new()),
|
||||||
runtime_order_created_date: Cell::new(None),
|
runtime_order_created_date: Cell::new(None),
|
||||||
runtime_decision_total_equity: Cell::new(None),
|
runtime_decision_total_equity: Cell::new(None),
|
||||||
runtime_target_position_limit: Cell::new(None),
|
runtime_target_position_limit: Cell::new(None),
|
||||||
@@ -448,6 +450,7 @@ impl<C, R> BrokerSimulator<C, R> {
|
|||||||
runtime_intraday_start_time: Cell::new(None),
|
runtime_intraday_start_time: Cell::new(None),
|
||||||
runtime_intraday_end_time: Cell::new(None),
|
runtime_intraday_end_time: Cell::new(None),
|
||||||
runtime_decision_date: Cell::new(None),
|
runtime_decision_date: Cell::new(None),
|
||||||
|
runtime_buy_denials: RefCell::new(BTreeMap::new()),
|
||||||
runtime_order_created_date: Cell::new(None),
|
runtime_order_created_date: Cell::new(None),
|
||||||
runtime_decision_total_equity: Cell::new(None),
|
runtime_decision_total_equity: Cell::new(None),
|
||||||
runtime_target_position_limit: Cell::new(None),
|
runtime_target_position_limit: Cell::new(None),
|
||||||
@@ -1385,6 +1388,7 @@ where
|
|||||||
decision: &StrategyDecision,
|
decision: &StrategyDecision,
|
||||||
) -> Result<BrokerExecutionReport, BacktestError> {
|
) -> Result<BrokerExecutionReport, BacktestError> {
|
||||||
let previous_decision_date = self.runtime_decision_date.get();
|
let previous_decision_date = self.runtime_decision_date.get();
|
||||||
|
let previous_buy_denials = self.runtime_buy_denials.replace(decision.buy_denials.clone());
|
||||||
let previous_order_created_date = self.runtime_order_created_date.get();
|
let previous_order_created_date = self.runtime_order_created_date.get();
|
||||||
let previous_decision_total_equity = self.runtime_decision_total_equity.get();
|
let previous_decision_total_equity = self.runtime_decision_total_equity.get();
|
||||||
self.runtime_decision_date.set(Some(decision_date));
|
self.runtime_decision_date.set(Some(decision_date));
|
||||||
@@ -1393,6 +1397,7 @@ where
|
|||||||
self.runtime_decision_total_equity
|
self.runtime_decision_total_equity
|
||||||
.set(decision_total_equity.filter(|equity| equity.is_finite() && *equity >= 0.0));
|
.set(decision_total_equity.filter(|equity| equity.is_finite() && *equity >= 0.0));
|
||||||
let result = self.execute_with_runtime_dates(date, portfolio, data, decision);
|
let result = self.execute_with_runtime_dates(date, portfolio, data, decision);
|
||||||
|
self.runtime_buy_denials.replace(previous_buy_denials);
|
||||||
self.runtime_decision_date.set(previous_decision_date);
|
self.runtime_decision_date.set(previous_decision_date);
|
||||||
self.runtime_order_created_date
|
self.runtime_order_created_date
|
||||||
.set(previous_order_created_date);
|
.set(previous_order_created_date);
|
||||||
@@ -4189,6 +4194,9 @@ where
|
|||||||
if !rule.allowed {
|
if !rule.allowed {
|
||||||
return rule.reason;
|
return rule.reason;
|
||||||
}
|
}
|
||||||
|
if let Some(reason) = self.runtime_buy_denials.borrow().get(symbol) {
|
||||||
|
return Some(reason.clone());
|
||||||
|
}
|
||||||
match self.market_fillable_quantity(
|
match self.market_fillable_quantity(
|
||||||
snapshot,
|
snapshot,
|
||||||
OrderSide::Buy,
|
OrderSide::Buy,
|
||||||
@@ -6140,6 +6148,12 @@ where
|
|||||||
data.instrument(symbol),
|
data.instrument(symbol),
|
||||||
algo_request,
|
algo_request,
|
||||||
);
|
);
|
||||||
|
let rule = if rule.allowed && emit_creation_events {
|
||||||
|
self.runtime_buy_denials.borrow().get(symbol)
|
||||||
|
.map_or(rule, |reason| RuleCheck::reject(reason.clone()))
|
||||||
|
} else {
|
||||||
|
rule
|
||||||
|
};
|
||||||
if !rule.allowed {
|
if !rule.allowed {
|
||||||
let rule_reason = rule.reason.as_deref().unwrap_or_default().to_string();
|
let rule_reason = rule.reason.as_deref().unwrap_or_default().to_string();
|
||||||
let status = match rule.reason.as_deref() {
|
let status = match rule.reason.as_deref() {
|
||||||
@@ -8204,6 +8218,7 @@ mod tests {
|
|||||||
|
|
||||||
fn next_open_buy_decision() -> StrategyDecision {
|
fn next_open_buy_decision() -> StrategyDecision {
|
||||||
StrategyDecision {
|
StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Shares {
|
order_intents: vec![OrderIntent::Shares {
|
||||||
symbol: "000001.SZ".to_string(),
|
symbol: "000001.SZ".to_string(),
|
||||||
quantity: 100,
|
quantity: 100,
|
||||||
@@ -8213,8 +8228,58 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decision_buy_denial_blocks_topup_but_allows_sell_and_does_not_leak() {
|
||||||
|
let first = chrono::NaiveDate::from_ymd_opt(2025, 1, 2).unwrap();
|
||||||
|
let second = chrono::NaiveDate::from_ymd_opt(2025, 1, 3).unwrap();
|
||||||
|
let data = DataSet::from_components(
|
||||||
|
vec![limit_test_instrument()],
|
||||||
|
vec![dated_limit_test_snapshot(first), dated_limit_test_snapshot(second)],
|
||||||
|
Vec::new(),
|
||||||
|
vec![dated_limit_test_candidate(first, false, false, true, true),
|
||||||
|
dated_limit_test_candidate(second, false, false, true, true)],
|
||||||
|
vec![dated_limit_test_benchmark(first), dated_limit_test_benchmark(second)],
|
||||||
|
).unwrap();
|
||||||
|
let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks)
|
||||||
|
.with_matching_type(MatchingType::CurrentBarClose);
|
||||||
|
let mut portfolio = PortfolioState::new(100_000.0);
|
||||||
|
broker.execute(first, &mut portfolio, &data, &next_open_buy_decision()).unwrap();
|
||||||
|
assert_eq!(portfolio.position("000001.SZ").unwrap().quantity, 100);
|
||||||
|
let mut blocked = StrategyDecision::default();
|
||||||
|
blocked.buy_denials.insert("000001.SZ".to_string(), "strategy_buy_condition_false".to_string());
|
||||||
|
blocked.order_intents.push(OrderIntent::TargetValue {
|
||||||
|
symbol: "000001.SZ".to_string(), target_value: 3_000.0, reason: "topup".to_string(),
|
||||||
|
});
|
||||||
|
let report = broker.execute(second, &mut portfolio, &data, &blocked).unwrap();
|
||||||
|
assert!(report.fills.is_empty());
|
||||||
|
assert_eq!(portfolio.position("000001.SZ").unwrap().quantity, 100);
|
||||||
|
assert!(broker.runtime_buy_denials.borrow().is_empty());
|
||||||
|
blocked.order_intents = next_open_sell_decision().order_intents;
|
||||||
|
let report = broker.execute(second, &mut portfolio, &data, &blocked).unwrap();
|
||||||
|
assert_eq!(report.fills.len(), 1);
|
||||||
|
assert_eq!(report.fills[0].side, OrderSide::Sell);
|
||||||
|
assert!(broker.runtime_buy_denials.borrow().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn decision_buy_denial_does_not_rewrite_existing_pending_order() {
|
||||||
|
let date = limit_test_snapshot().date;
|
||||||
|
let data = DataSet::from_components(vec![limit_test_instrument()], vec![limit_test_snapshot()],
|
||||||
|
Vec::new(), vec![limit_test_candidate(true, true)], vec![limit_test_benchmark()]).unwrap();
|
||||||
|
let broker = BrokerSimulator::new(ChinaAShareCostModel::default(), ChinaEquityRuleHooks)
|
||||||
|
.with_matching_type(MatchingType::CurrentBarClose);
|
||||||
|
broker.upsert_open_order(test_open_order(99));
|
||||||
|
let mut decision = StrategyDecision::default();
|
||||||
|
decision.buy_denials.insert("000001.SZ".to_string(), "strategy_buy_condition_false".to_string());
|
||||||
|
let mut portfolio = PortfolioState::new(100_000.0);
|
||||||
|
let report = broker.execute(date, &mut portfolio, &data, &decision).unwrap();
|
||||||
|
assert!(!report.fills.is_empty());
|
||||||
|
assert!(broker.runtime_buy_denials.borrow().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
fn next_open_sell_decision() -> StrategyDecision {
|
fn next_open_sell_decision() -> StrategyDecision {
|
||||||
StrategyDecision {
|
StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Shares {
|
order_intents: vec![OrderIntent::Shares {
|
||||||
symbol: "000001.SZ".to_string(),
|
symbol: "000001.SZ".to_string(),
|
||||||
quantity: -100,
|
quantity: -100,
|
||||||
@@ -10192,6 +10257,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.expect("valid dataset");
|
.expect("valid dataset");
|
||||||
let decision = StrategyDecision {
|
let decision = StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![
|
order_intents: vec![
|
||||||
OrderIntent::TargetValue {
|
OrderIntent::TargetValue {
|
||||||
symbol: "000002.SZ".to_string(),
|
symbol: "000002.SZ".to_string(),
|
||||||
@@ -10304,6 +10370,7 @@ mod tests {
|
|||||||
.position_mut("000002.SZ")
|
.position_mut("000002.SZ")
|
||||||
.buy(prev_date, 1_000, 10.0);
|
.buy(prev_date, 1_000, 10.0);
|
||||||
let decision = StrategyDecision {
|
let decision = StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![
|
order_intents: vec![
|
||||||
OrderIntent::TargetValue {
|
OrderIntent::TargetValue {
|
||||||
symbol: "000001.SZ".to_string(),
|
symbol: "000001.SZ".to_string(),
|
||||||
@@ -10371,6 +10438,7 @@ mod tests {
|
|||||||
.position_mut("000002.SZ")
|
.position_mut("000002.SZ")
|
||||||
.buy(prev_date, 1_000, 10.0);
|
.buy(prev_date, 1_000, 10.0);
|
||||||
let decision = StrategyDecision {
|
let decision = StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![
|
order_intents: vec![
|
||||||
OrderIntent::TargetValue {
|
OrderIntent::TargetValue {
|
||||||
symbol: "000003.SZ".to_string(),
|
symbol: "000003.SZ".to_string(),
|
||||||
@@ -10431,6 +10499,7 @@ mod tests {
|
|||||||
.position_mut("000001.SZ")
|
.position_mut("000001.SZ")
|
||||||
.buy(prev_date, 1_000, 10.0);
|
.buy(prev_date, 1_000, 10.0);
|
||||||
let decision = StrategyDecision {
|
let decision = StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::TargetValue {
|
order_intents: vec![OrderIntent::TargetValue {
|
||||||
symbol: "000003.SZ".to_string(),
|
symbol: "000003.SZ".to_string(),
|
||||||
target_value: 9_000.0,
|
target_value: 9_000.0,
|
||||||
@@ -10595,6 +10664,7 @@ mod tests {
|
|||||||
.with_inactive_limit(false);
|
.with_inactive_limit(false);
|
||||||
let mut portfolio = PortfolioState::new(20_000.0);
|
let mut portfolio = PortfolioState::new(20_000.0);
|
||||||
let decision = StrategyDecision {
|
let decision = StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::TargetValue {
|
order_intents: vec![OrderIntent::TargetValue {
|
||||||
symbol: "000001.SZ".to_string(),
|
symbol: "000001.SZ".to_string(),
|
||||||
target_value: 10_000.0,
|
target_value: 10_000.0,
|
||||||
@@ -10636,6 +10706,7 @@ mod tests {
|
|||||||
.with_inactive_limit(false);
|
.with_inactive_limit(false);
|
||||||
let mut portfolio = PortfolioState::new(20_000.0);
|
let mut portfolio = PortfolioState::new(20_000.0);
|
||||||
let decision = StrategyDecision {
|
let decision = StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::TargetValue {
|
order_intents: vec![OrderIntent::TargetValue {
|
||||||
symbol: "000001.SZ".to_string(),
|
symbol: "000001.SZ".to_string(),
|
||||||
target_value: 10_000.0,
|
target_value: 10_000.0,
|
||||||
@@ -10673,6 +10744,7 @@ mod tests {
|
|||||||
.with_inactive_limit(false);
|
.with_inactive_limit(false);
|
||||||
let mut portfolio = PortfolioState::new(20_000.0);
|
let mut portfolio = PortfolioState::new(20_000.0);
|
||||||
let decision = StrategyDecision {
|
let decision = StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::TargetPortfolioSmart {
|
order_intents: vec![OrderIntent::TargetPortfolioSmart {
|
||||||
target_weights: BTreeMap::from([("000001.SZ".to_string(), 0.5)]),
|
target_weights: BTreeMap::from([("000001.SZ".to_string(), 0.5)]),
|
||||||
order_prices: None,
|
order_prices: None,
|
||||||
|
|||||||
@@ -4856,6 +4856,7 @@ mod tests {
|
|||||||
) -> Result<StrategyDecision, super::BacktestError> {
|
) -> Result<StrategyDecision, super::BacktestError> {
|
||||||
if ctx.decision_date == self.decision_date && ctx.portfolio.position(SYMBOL).is_none() {
|
if ctx.decision_date == self.decision_date && ctx.portfolio.position(SYMBOL).is_none() {
|
||||||
return Ok(StrategyDecision {
|
return Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Shares {
|
order_intents: vec![OrderIntent::Shares {
|
||||||
symbol: SYMBOL.to_string(),
|
symbol: SYMBOL.to_string(),
|
||||||
quantity: 100,
|
quantity: 100,
|
||||||
@@ -4896,6 +4897,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
if ctx.portfolio.position(SYMBOL).is_none() {
|
if ctx.portfolio.position(SYMBOL).is_none() {
|
||||||
return Ok(StrategyDecision {
|
return Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Shares {
|
order_intents: vec![OrderIntent::Shares {
|
||||||
symbol: SYMBOL.to_string(),
|
symbol: SYMBOL.to_string(),
|
||||||
quantity: 100,
|
quantity: 100,
|
||||||
@@ -4955,6 +4957,7 @@ mod tests {
|
|||||||
rule: &ScheduleRule,
|
rule: &ScheduleRule,
|
||||||
) -> Result<StrategyDecision, super::BacktestError> {
|
) -> Result<StrategyDecision, super::BacktestError> {
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Shares {
|
order_intents: vec![OrderIntent::Shares {
|
||||||
symbol: SYMBOL.to_string(),
|
symbol: SYMBOL.to_string(),
|
||||||
quantity: 100,
|
quantity: 100,
|
||||||
@@ -4992,6 +4995,7 @@ mod tests {
|
|||||||
return Ok(StrategyDecision::default());
|
return Ok(StrategyDecision::default());
|
||||||
}
|
}
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Shares {
|
order_intents: vec![OrderIntent::Shares {
|
||||||
symbol: SYMBOL.to_string(),
|
symbol: SYMBOL.to_string(),
|
||||||
quantity: 100,
|
quantity: 100,
|
||||||
@@ -5028,6 +5032,7 @@ mod tests {
|
|||||||
return Ok(StrategyDecision::default());
|
return Ok(StrategyDecision::default());
|
||||||
}
|
}
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::TargetPortfolioSmart {
|
order_intents: vec![OrderIntent::TargetPortfolioSmart {
|
||||||
target_weights: self.target_weights.clone(),
|
target_weights: self.target_weights.clone(),
|
||||||
order_prices: None,
|
order_prices: None,
|
||||||
@@ -5070,6 +5075,7 @@ mod tests {
|
|||||||
Vec::new()
|
Vec::new()
|
||||||
};
|
};
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents,
|
order_intents,
|
||||||
..StrategyDecision::default()
|
..StrategyDecision::default()
|
||||||
})
|
})
|
||||||
@@ -5106,6 +5112,7 @@ mod tests {
|
|||||||
return Ok(StrategyDecision::default());
|
return Ok(StrategyDecision::default());
|
||||||
};
|
};
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Shares {
|
order_intents: vec![OrderIntent::Shares {
|
||||||
symbol,
|
symbol,
|
||||||
quantity: 100,
|
quantity: 100,
|
||||||
@@ -5166,6 +5173,7 @@ mod tests {
|
|||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
));
|
));
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Shares {
|
order_intents: vec![OrderIntent::Shares {
|
||||||
symbol: SYMBOL.to_string(),
|
symbol: SYMBOL.to_string(),
|
||||||
quantity: 100,
|
quantity: 100,
|
||||||
@@ -5206,6 +5214,7 @@ mod tests {
|
|||||||
&& ctx.portfolio.position(SYMBOL).is_none()
|
&& ctx.portfolio.position(SYMBOL).is_none()
|
||||||
{
|
{
|
||||||
return Ok(StrategyDecision {
|
return Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Shares {
|
order_intents: vec![OrderIntent::Shares {
|
||||||
symbol: SYMBOL.to_string(),
|
symbol: SYMBOL.to_string(),
|
||||||
quantity: 100,
|
quantity: 100,
|
||||||
@@ -5217,6 +5226,7 @@ mod tests {
|
|||||||
if ctx.decision_date == self.sell_decision_date {
|
if ctx.decision_date == self.sell_decision_date {
|
||||||
if let Some(position) = ctx.portfolio.position(SYMBOL) {
|
if let Some(position) = ctx.portfolio.position(SYMBOL) {
|
||||||
return Ok(StrategyDecision {
|
return Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Shares {
|
order_intents: vec![OrderIntent::Shares {
|
||||||
symbol: SYMBOL.to_string(),
|
symbol: SYMBOL.to_string(),
|
||||||
quantity: -(position.quantity as i32),
|
quantity: -(position.quantity as i32),
|
||||||
@@ -5256,6 +5266,7 @@ mod tests {
|
|||||||
&& ctx.portfolio.position(SYMBOL).is_none()
|
&& ctx.portfolio.position(SYMBOL).is_none()
|
||||||
{
|
{
|
||||||
return Ok(StrategyDecision {
|
return Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Shares {
|
order_intents: vec![OrderIntent::Shares {
|
||||||
symbol: SYMBOL.to_string(),
|
symbol: SYMBOL.to_string(),
|
||||||
quantity: 100,
|
quantity: 100,
|
||||||
@@ -5267,6 +5278,7 @@ mod tests {
|
|||||||
if ctx.decision_date == self.rebuy_decision_date {
|
if ctx.decision_date == self.rebuy_decision_date {
|
||||||
if let Some(position) = ctx.portfolio.position(SYMBOL) {
|
if let Some(position) = ctx.portfolio.position(SYMBOL) {
|
||||||
return Ok(StrategyDecision {
|
return Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![
|
order_intents: vec![
|
||||||
OrderIntent::Shares {
|
OrderIntent::Shares {
|
||||||
symbol: SYMBOL.to_string(),
|
symbol: SYMBOL.to_string(),
|
||||||
|
|||||||
@@ -9913,6 +9913,7 @@ impl PlatformExprStrategy {
|
|||||||
)];
|
)];
|
||||||
diagnostics.extend(action_diagnostics);
|
diagnostics.extend(action_diagnostics);
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -12578,6 +12579,7 @@ impl Strategy for PlatformExprStrategy {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
return Ok(StrategyDecision {
|
return Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols,
|
exit_symbols,
|
||||||
@@ -13854,6 +13856,7 @@ impl Strategy for PlatformExprStrategy {
|
|||||||
];
|
];
|
||||||
|
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols,
|
exit_symbols,
|
||||||
|
|||||||
@@ -977,6 +977,7 @@ fn safe_ratio(numerator: f64, denominator: f64) -> f64 {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct StrategyDecision {
|
pub struct StrategyDecision {
|
||||||
|
pub buy_denials: BTreeMap<String, String>,
|
||||||
pub rebalance: bool,
|
pub rebalance: bool,
|
||||||
pub target_weights: BTreeMap<String, f64>,
|
pub target_weights: BTreeMap<String, f64>,
|
||||||
pub exit_symbols: BTreeSet<String>,
|
pub exit_symbols: BTreeSet<String>,
|
||||||
@@ -988,6 +989,7 @@ pub struct StrategyDecision {
|
|||||||
|
|
||||||
impl StrategyDecision {
|
impl StrategyDecision {
|
||||||
pub fn merge_from(&mut self, mut other: StrategyDecision) {
|
pub fn merge_from(&mut self, mut other: StrategyDecision) {
|
||||||
|
self.buy_denials.append(&mut other.buy_denials);
|
||||||
self.rebalance |= other.rebalance;
|
self.rebalance |= other.rebalance;
|
||||||
self.target_weights.append(&mut other.target_weights);
|
self.target_weights.append(&mut other.target_weights);
|
||||||
self.exit_symbols.append(&mut other.exit_symbols);
|
self.exit_symbols.append(&mut other.exit_symbols);
|
||||||
@@ -998,7 +1000,8 @@ impl StrategyDecision {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
!self.rebalance
|
self.buy_denials.is_empty()
|
||||||
|
&& !self.rebalance
|
||||||
&& self.target_weights.is_empty()
|
&& self.target_weights.is_empty()
|
||||||
&& self.exit_symbols.is_empty()
|
&& self.exit_symbols.is_empty()
|
||||||
&& self.order_intents.is_empty()
|
&& self.order_intents.is_empty()
|
||||||
@@ -1569,6 +1572,7 @@ impl Strategy for CnSmallCapRotationStrategy {
|
|||||||
if self.config.in_skip_window(ctx.decision_date) {
|
if self.config.in_skip_window(ctx.decision_date) {
|
||||||
self.last_gross_exposure = Some(0.0);
|
self.last_gross_exposure = Some(0.0);
|
||||||
return Ok(StrategyDecision {
|
return Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: true,
|
rebalance: true,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: ctx.portfolio.positions().keys().cloned().collect(),
|
exit_symbols: ctx.portfolio.positions().keys().cloned().collect(),
|
||||||
@@ -1590,6 +1594,7 @@ impl Strategy for CnSmallCapRotationStrategy {
|
|||||||
if message.contains("signal series insufficient") =>
|
if message.contains("signal series insufficient") =>
|
||||||
{
|
{
|
||||||
return Ok(StrategyDecision {
|
return Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -1765,6 +1770,7 @@ impl Strategy for CnSmallCapRotationStrategy {
|
|||||||
self.last_gross_exposure = Some(gross_exposure);
|
self.last_gross_exposure = Some(gross_exposure);
|
||||||
|
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance,
|
rebalance,
|
||||||
target_weights,
|
target_weights,
|
||||||
exit_symbols,
|
exit_symbols,
|
||||||
@@ -2773,6 +2779,7 @@ impl Strategy for OmniMicroCapStrategy {
|
|||||||
let lagged_execution = ctx.is_lagged_execution();
|
let lagged_execution = ctx.is_lagged_execution();
|
||||||
if self.config.in_skip_window(signal_date) {
|
if self.config.in_skip_window(signal_date) {
|
||||||
return Ok(StrategyDecision {
|
return Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: ctx.portfolio.positions().keys().cloned().collect(),
|
exit_symbols: ctx.portfolio.positions().keys().cloned().collect(),
|
||||||
@@ -2803,6 +2810,7 @@ impl Strategy for OmniMicroCapStrategy {
|
|||||||
if message.contains("insufficient benchmark") =>
|
if message.contains("insufficient benchmark") =>
|
||||||
{
|
{
|
||||||
return Ok(StrategyDecision {
|
return Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -3013,6 +3021,7 @@ impl Strategy for OmniMicroCapStrategy {
|
|||||||
];
|
];
|
||||||
|
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols,
|
exit_symbols,
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ impl Strategy for BuyAndHoldStrategy {
|
|||||||
ctx: &StrategyContext<'_>,
|
ctx: &StrategyContext<'_>,
|
||||||
) -> Result<StrategyDecision, fidc_core::BacktestError> {
|
) -> Result<StrategyDecision, fidc_core::BacktestError> {
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ impl Strategy for DecisionQuoteReader {
|
|||||||
self.day_count += 1;
|
self.day_count += 1;
|
||||||
if self.day_count == 1 {
|
if self.day_count == 1 {
|
||||||
return Ok(StrategyDecision {
|
return Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Value {
|
order_intents: vec![OrderIntent::Value {
|
||||||
symbol: "000001.SZ".to_string(),
|
symbol: "000001.SZ".to_string(),
|
||||||
value: 5_000.0,
|
value: 5_000.0,
|
||||||
@@ -626,6 +627,7 @@ impl Strategy for MultiTimeDecisionQuoteReader {
|
|||||||
self.day_count += 1;
|
self.day_count += 1;
|
||||||
if self.day_count == 1 {
|
if self.day_count == 1 {
|
||||||
return Ok(StrategyDecision {
|
return Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Value {
|
order_intents: vec![OrderIntent::Value {
|
||||||
symbol: "000001.SZ".to_string(),
|
symbol: "000001.SZ".to_string(),
|
||||||
value: 5_000.0,
|
value: 5_000.0,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ impl Strategy for BuyThenHoldStrategy {
|
|||||||
) -> Result<StrategyDecision, fidc_core::BacktestError> {
|
) -> Result<StrategyDecision, fidc_core::BacktestError> {
|
||||||
if ctx.decision_date == d(2025, 1, 2) && ctx.portfolio.position("000001.SZ").is_none() {
|
if ctx.decision_date == d(2025, 1, 2) && ctx.portfolio.position("000001.SZ").is_none() {
|
||||||
return Ok(StrategyDecision {
|
return Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
|
|||||||
@@ -295,6 +295,7 @@ impl Strategy for HookProbeStrategy {
|
|||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.push(format!("on_day:{}", ctx.execution_date));
|
.push(format!("on_day:{}", ctx.execution_date));
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -334,6 +335,7 @@ impl Strategy for AuctionOrderStrategy {
|
|||||||
_ctx: &StrategyContext<'_>,
|
_ctx: &StrategyContext<'_>,
|
||||||
) -> Result<StrategyDecision, fidc_core::BacktestError> {
|
) -> Result<StrategyDecision, fidc_core::BacktestError> {
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -377,6 +379,7 @@ impl Strategy for FuturesOrderStrategy {
|
|||||||
return Ok(StrategyDecision::default());
|
return Ok(StrategyDecision::default());
|
||||||
}
|
}
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -413,6 +416,7 @@ impl Strategy for FuturesLimitOrderStrategy {
|
|||||||
return Ok(StrategyDecision::default());
|
return Ok(StrategyDecision::default());
|
||||||
}
|
}
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Futures {
|
order_intents: vec![OrderIntent::Futures {
|
||||||
intent: FuturesOrderIntent::limit_open(
|
intent: FuturesOrderIntent::limit_open(
|
||||||
"IF2501",
|
"IF2501",
|
||||||
@@ -444,6 +448,7 @@ impl Strategy for FuturesInvalidTickLimitStrategy {
|
|||||||
return Ok(StrategyDecision::default());
|
return Ok(StrategyDecision::default());
|
||||||
}
|
}
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Futures {
|
order_intents: vec![OrderIntent::Futures {
|
||||||
intent: FuturesOrderIntent::limit_open(
|
intent: FuturesOrderIntent::limit_open(
|
||||||
"IF2501",
|
"IF2501",
|
||||||
@@ -475,6 +480,7 @@ impl Strategy for FuturesClosedPhaseOrderStrategy {
|
|||||||
return Ok(StrategyDecision::default());
|
return Ok(StrategyDecision::default());
|
||||||
}
|
}
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Futures {
|
order_intents: vec![OrderIntent::Futures {
|
||||||
intent: FuturesOrderIntent::open(
|
intent: FuturesOrderIntent::open(
|
||||||
"IF2501",
|
"IF2501",
|
||||||
@@ -506,6 +512,7 @@ impl Strategy for FuturesAboveUpperLimitStrategy {
|
|||||||
return Ok(StrategyDecision::default());
|
return Ok(StrategyDecision::default());
|
||||||
}
|
}
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Futures {
|
order_intents: vec![OrderIntent::Futures {
|
||||||
intent: FuturesOrderIntent::limit_open(
|
intent: FuturesOrderIntent::limit_open(
|
||||||
"IF2501",
|
"IF2501",
|
||||||
@@ -537,6 +544,7 @@ impl Strategy for FuturesDepthLimitOrderStrategy {
|
|||||||
return Ok(StrategyDecision::default());
|
return Ok(StrategyDecision::default());
|
||||||
}
|
}
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Futures {
|
order_intents: vec![OrderIntent::Futures {
|
||||||
intent: FuturesOrderIntent::limit_open(
|
intent: FuturesOrderIntent::limit_open(
|
||||||
"IF2501",
|
"IF2501",
|
||||||
@@ -720,6 +728,7 @@ impl Strategy for LimitCarryStrategy {
|
|||||||
}
|
}
|
||||||
self.issued = true;
|
self.issued = true;
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -803,6 +812,7 @@ impl Strategy for UniverseDirectiveStrategy {
|
|||||||
_ => Vec::new(),
|
_ => Vec::new(),
|
||||||
};
|
};
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -844,6 +854,7 @@ impl Strategy for MinuteProbeStrategy {
|
|||||||
_ctx: &StrategyContext<'_>,
|
_ctx: &StrategyContext<'_>,
|
||||||
) -> Result<StrategyDecision, fidc_core::BacktestError> {
|
) -> Result<StrategyDecision, fidc_core::BacktestError> {
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -885,6 +896,7 @@ impl Strategy for MinuteProbeStrategy {
|
|||||||
}
|
}
|
||||||
self.ordered = true;
|
self.ordered = true;
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -987,6 +999,7 @@ impl Strategy for OrderInspectionStrategy {
|
|||||||
_ctx: &StrategyContext<'_>,
|
_ctx: &StrategyContext<'_>,
|
||||||
) -> Result<StrategyDecision, fidc_core::BacktestError> {
|
) -> Result<StrategyDecision, fidc_core::BacktestError> {
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -1030,6 +1043,7 @@ impl Strategy for AccountFlowStrategy {
|
|||||||
return Ok(StrategyDecision::default());
|
return Ok(StrategyDecision::default());
|
||||||
}
|
}
|
||||||
Ok(StrategyDecision {
|
Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -4134,6 +4148,7 @@ impl Strategy for BuyMissingRowThenHoldStrategy {
|
|||||||
) -> Result<StrategyDecision, fidc_core::BacktestError> {
|
) -> Result<StrategyDecision, fidc_core::BacktestError> {
|
||||||
if ctx.execution_date == d(2025, 5, 26) {
|
if ctx.execution_date == d(2025, 5, 26) {
|
||||||
return Ok(StrategyDecision {
|
return Ok(StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
|
|||||||
@@ -209,6 +209,7 @@ fn execute_single_value_order(
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
data,
|
data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -399,6 +400,7 @@ fn broker_executes_explicit_order_value_buy() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -557,6 +559,7 @@ fn broker_delayed_limit_open_sell_uses_minute_price() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -687,6 +690,7 @@ fn broker_executes_order_shares_and_order_lots() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -806,6 +810,7 @@ fn broker_executes_target_shares_like_order_to() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -981,6 +986,7 @@ fn broker_executes_target_portfolio_smart_with_custom_prices() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -1139,6 +1145,7 @@ fn broker_executes_target_portfolio_smart_with_algo_order_style() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -1254,6 +1261,7 @@ fn broker_executes_order_percent_and_target_percent() {
|
|||||||
&mut percent_portfolio,
|
&mut percent_portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -1278,6 +1286,7 @@ fn broker_executes_order_percent_and_target_percent() {
|
|||||||
&mut target_percent_portfolio,
|
&mut target_percent_portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -1380,6 +1389,7 @@ fn broker_uses_day_open_price_for_open_auction_matching() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -1487,6 +1497,7 @@ fn broker_open_auction_uses_auction_volume_without_quote_liquidity() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -1590,6 +1601,7 @@ fn broker_cancels_buy_when_open_hits_upper_limit() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -1707,6 +1719,7 @@ fn broker_applies_price_ratio_slippage_on_snapshot_fills() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -1812,6 +1825,7 @@ fn broker_applies_dynamic_slippage_on_snapshot_fills() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -1933,6 +1947,7 @@ fn broker_applies_tick_size_slippage_on_intraday_last_fills() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -2039,6 +2054,7 @@ fn broker_rejects_intraday_last_order_without_execution_quotes() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -2163,6 +2179,7 @@ fn broker_executes_intraday_last_on_start_quote_with_trade_delta() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -2282,6 +2299,7 @@ fn broker_cancels_market_order_remainder_when_intraday_quote_liquidity_exhausted
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -2398,6 +2416,7 @@ fn broker_cancels_market_buy_when_minute_has_no_volume() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -2534,6 +2553,7 @@ fn broker_splits_intraday_quote_fills_and_tracks_commission_by_order() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -2707,6 +2727,7 @@ fn broker_aggregates_intraday_quote_fills_into_vwap_leg() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -2888,6 +2909,7 @@ fn broker_executes_algo_vwap_value_with_time_window() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -3036,6 +3058,7 @@ fn broker_executes_algo_twap_percent_across_window_quotes() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -3172,6 +3195,7 @@ fn broker_uses_best_own_price_for_intraday_matching() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -3290,6 +3314,7 @@ fn broker_uses_best_counterparty_price_for_intraday_matching() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -3461,6 +3486,7 @@ fn rebalance_optimizer_skips_unfunded_buy_when_existing_position_cannot_sell() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: true,
|
rebalance: true,
|
||||||
target_weights: BTreeMap::from([("000002.SZ".to_string(), 1.0)]),
|
target_weights: BTreeMap::from([("000002.SZ".to_string(), 1.0)]),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -3657,6 +3683,7 @@ fn rebalance_uses_day_open_for_open_auction_valuation() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: true,
|
rebalance: true,
|
||||||
target_weights: BTreeMap::from([
|
target_weights: BTreeMap::from([
|
||||||
("000001.SZ".to_string(), 0.5),
|
("000001.SZ".to_string(), 0.5),
|
||||||
@@ -3841,6 +3868,7 @@ fn rebalance_optimizer_prioritizes_higher_target_weight_when_cash_is_tight() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: true,
|
rebalance: true,
|
||||||
target_weights: BTreeMap::from([
|
target_weights: BTreeMap::from([
|
||||||
("000001.SZ".to_string(), 0.2),
|
("000001.SZ".to_string(), 0.2),
|
||||||
@@ -4025,6 +4053,7 @@ fn rebalance_optimizer_does_not_scale_targets_above_requested_weight() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: true,
|
rebalance: true,
|
||||||
target_weights: BTreeMap::from([
|
target_weights: BTreeMap::from([
|
||||||
("000001.SZ".to_string(), 0.48),
|
("000001.SZ".to_string(), 0.48),
|
||||||
@@ -4139,6 +4168,7 @@ fn broker_uses_board_specific_min_quantity_and_step_size_for_buy_sizing() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -4244,6 +4274,7 @@ fn broker_allows_bjse_quantities_above_minimum_without_round_lot_step() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -4350,6 +4381,7 @@ fn broker_allows_full_odd_lot_sell_when_liquidating_position() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -4483,6 +4515,7 @@ fn same_day_sell_then_rebuy_is_rejected_by_default() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -4627,6 +4660,7 @@ fn same_day_sell_then_rebuy_can_be_allowed_by_policy() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -4680,6 +4714,7 @@ fn broker_configured_policy_can_allow_upper_limit_buy() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -4726,6 +4761,7 @@ fn broker_configured_policy_can_allow_lower_limit_sell() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -4901,6 +4937,7 @@ fn broker_expires_day_limit_buy_at_market_close() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -4941,6 +4978,7 @@ fn broker_expires_day_limit_buy_at_market_close() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -4976,6 +5014,7 @@ fn broker_ioc_limit_order_fills_available_quantity_and_cancels_remainder() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![
|
order_intents: vec![
|
||||||
OrderIntent::LimitShares {
|
OrderIntent::LimitShares {
|
||||||
symbol: "000002.SZ".to_string(),
|
symbol: "000002.SZ".to_string(),
|
||||||
@@ -5014,6 +5053,7 @@ fn broker_persists_daily_volume_consumption_across_execute_calls() {
|
|||||||
.with_liquidity_limit(false);
|
.with_liquidity_limit(false);
|
||||||
let mut portfolio = PortfolioState::new(1_000_000.0);
|
let mut portfolio = PortfolioState::new(1_000_000.0);
|
||||||
let decision = || StrategyDecision {
|
let decision = || StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Shares {
|
order_intents: vec![OrderIntent::Shares {
|
||||||
symbol: "000002.SZ".to_string(),
|
symbol: "000002.SZ".to_string(),
|
||||||
quantity: 100,
|
quantity: 100,
|
||||||
@@ -5061,6 +5101,7 @@ fn broker_persists_quote_depth_until_fresh_level_data_arrives() {
|
|||||||
.with_liquidity_limit(true);
|
.with_liquidity_limit(true);
|
||||||
let mut portfolio = PortfolioState::new(1_000_000.0);
|
let mut portfolio = PortfolioState::new(1_000_000.0);
|
||||||
let decision = |quantity| StrategyDecision {
|
let decision = |quantity| StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Shares {
|
order_intents: vec![OrderIntent::Shares {
|
||||||
symbol: symbol.to_string(),
|
symbol: symbol.to_string(),
|
||||||
quantity,
|
quantity,
|
||||||
@@ -5078,6 +5119,7 @@ fn broker_persists_quote_depth_until_fresh_level_data_arrives() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![
|
order_intents: vec![
|
||||||
OrderIntent::Shares {
|
OrderIntent::Shares {
|
||||||
symbol: symbol.to_string(),
|
symbol: symbol.to_string(),
|
||||||
@@ -5182,6 +5224,7 @@ fn broker_persists_quote_volume_participation_until_next_quote() {
|
|||||||
.with_liquidity_limit(false);
|
.with_liquidity_limit(false);
|
||||||
let mut portfolio = PortfolioState::new(1_000_000.0);
|
let mut portfolio = PortfolioState::new(1_000_000.0);
|
||||||
let decision = |quantity| StrategyDecision {
|
let decision = |quantity| StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::Shares {
|
order_intents: vec![OrderIntent::Shares {
|
||||||
symbol: symbol.to_string(),
|
symbol: symbol.to_string(),
|
||||||
quantity,
|
quantity,
|
||||||
@@ -5264,6 +5307,7 @@ fn broker_day_market_order_cancels_remainder_without_creating_invalid_open_order
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![
|
order_intents: vec![
|
||||||
OrderIntent::Shares {
|
OrderIntent::Shares {
|
||||||
symbol: "000002.SZ".to_string(),
|
symbol: "000002.SZ".to_string(),
|
||||||
@@ -5305,6 +5349,7 @@ fn broker_fok_order_is_atomic_when_liquidity_is_insufficient() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![
|
order_intents: vec![
|
||||||
OrderIntent::Shares {
|
OrderIntent::Shares {
|
||||||
symbol: "000002.SZ".to_string(),
|
symbol: "000002.SZ".to_string(),
|
||||||
@@ -5351,6 +5396,7 @@ fn broker_fok_order_fills_when_full_quantity_is_available() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![
|
order_intents: vec![
|
||||||
OrderIntent::Shares {
|
OrderIntent::Shares {
|
||||||
symbol: "000002.SZ".to_string(),
|
symbol: "000002.SZ".to_string(),
|
||||||
@@ -5388,6 +5434,7 @@ fn broker_gtc_limit_order_survives_close_and_fills_next_day() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![
|
order_intents: vec![
|
||||||
OrderIntent::LimitShares {
|
OrderIntent::LimitShares {
|
||||||
symbol: "000002.SZ".to_string(),
|
symbol: "000002.SZ".to_string(),
|
||||||
@@ -5439,6 +5486,7 @@ fn broker_gtc_partial_fills_preserve_cumulative_order_and_commission_state() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![
|
order_intents: vec![
|
||||||
OrderIntent::LimitShares {
|
OrderIntent::LimitShares {
|
||||||
symbol: "000002.SZ".to_string(),
|
symbol: "000002.SZ".to_string(),
|
||||||
@@ -5503,6 +5551,7 @@ fn broker_modifies_gtc_limit_order_without_changing_order_identity() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![
|
order_intents: vec![
|
||||||
OrderIntent::LimitShares {
|
OrderIntent::LimitShares {
|
||||||
symbol: "000002.SZ".to_string(),
|
symbol: "000002.SZ".to_string(),
|
||||||
@@ -5524,6 +5573,7 @@ fn broker_modifies_gtc_limit_order_without_changing_order_identity() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::ModifyOrder {
|
order_intents: vec![OrderIntent::ModifyOrder {
|
||||||
order_id,
|
order_id,
|
||||||
new_total_quantity: Some(400),
|
new_total_quantity: Some(400),
|
||||||
@@ -5591,6 +5641,7 @@ fn broker_modifies_partially_filled_gtc_total_and_preserves_commission_state() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![
|
order_intents: vec![
|
||||||
OrderIntent::LimitShares {
|
OrderIntent::LimitShares {
|
||||||
symbol: "000002.SZ".to_string(),
|
symbol: "000002.SZ".to_string(),
|
||||||
@@ -5614,6 +5665,7 @@ fn broker_modifies_partially_filled_gtc_total_and_preserves_commission_state() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::ModifyOrder {
|
order_intents: vec![OrderIntent::ModifyOrder {
|
||||||
order_id,
|
order_id,
|
||||||
new_total_quantity: Some(200),
|
new_total_quantity: Some(200),
|
||||||
@@ -5668,6 +5720,7 @@ fn broker_rejected_modify_has_zero_side_effects() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![
|
order_intents: vec![
|
||||||
OrderIntent::LimitShares {
|
OrderIntent::LimitShares {
|
||||||
symbol: "000002.SZ".to_string(),
|
symbol: "000002.SZ".to_string(),
|
||||||
@@ -5691,6 +5744,7 @@ fn broker_rejected_modify_has_zero_side_effects() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::ModifyOrder {
|
order_intents: vec![OrderIntent::ModifyOrder {
|
||||||
order_id,
|
order_id,
|
||||||
new_total_quantity: Some(100),
|
new_total_quantity: Some(100),
|
||||||
@@ -5728,6 +5782,7 @@ fn broker_accepted_modify_resets_queue_priority_but_reduction_preserves_it() {
|
|||||||
);
|
);
|
||||||
let mut portfolio = PortfolioState::new(1_000_000.0);
|
let mut portfolio = PortfolioState::new(1_000_000.0);
|
||||||
let create = |reason: &str| StrategyDecision {
|
let create = |reason: &str| StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![
|
order_intents: vec![
|
||||||
OrderIntent::LimitShares {
|
OrderIntent::LimitShares {
|
||||||
symbol: "000002.SZ".to_string(),
|
symbol: "000002.SZ".to_string(),
|
||||||
@@ -5758,6 +5813,7 @@ fn broker_accepted_modify_resets_queue_priority_but_reduction_preserves_it() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::ModifyOrder {
|
order_intents: vec![OrderIntent::ModifyOrder {
|
||||||
order_id: initial_ids[0],
|
order_id: initial_ids[0],
|
||||||
new_total_quantity: Some(200),
|
new_total_quantity: Some(200),
|
||||||
@@ -5787,6 +5843,7 @@ fn broker_accepted_modify_resets_queue_priority_but_reduction_preserves_it() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![OrderIntent::ModifyOrder {
|
order_intents: vec![OrderIntent::ModifyOrder {
|
||||||
order_id: initial_ids[0],
|
order_id: initial_ids[0],
|
||||||
new_total_quantity: None,
|
new_total_quantity: None,
|
||||||
@@ -5828,6 +5885,7 @@ fn broker_rejects_gtc_for_market_order() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
order_intents: vec![
|
order_intents: vec![
|
||||||
OrderIntent::Shares {
|
OrderIntent::Shares {
|
||||||
symbol: "000002.SZ".to_string(),
|
symbol: "000002.SZ".to_string(),
|
||||||
@@ -5867,6 +5925,7 @@ fn broker_uses_limit_price_slippage_for_limit_orders() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -5905,6 +5964,7 @@ fn broker_rejects_limit_buy_when_final_execution_price_reaches_upper_limit() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -5949,6 +6009,7 @@ fn broker_executes_limit_value_and_limit_percent_intents() {
|
|||||||
&mut value_portfolio,
|
&mut value_portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -5974,6 +6035,7 @@ fn broker_executes_limit_value_and_limit_percent_intents() {
|
|||||||
&mut percent_portfolio,
|
&mut percent_portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -6010,6 +6072,7 @@ fn broker_cancels_open_order_by_order_id() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -6033,6 +6096,7 @@ fn broker_cancels_open_order_by_order_id() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -6080,6 +6144,7 @@ fn broker_emits_cancellation_reject_for_unknown_order() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
@@ -6188,6 +6253,7 @@ fn broker_reserves_sellable_quantity_for_open_limit_sells() {
|
|||||||
&mut portfolio,
|
&mut portfolio,
|
||||||
&data,
|
&data,
|
||||||
&StrategyDecision {
|
&StrategyDecision {
|
||||||
|
buy_denials: Default::default(),
|
||||||
rebalance: false,
|
rebalance: false,
|
||||||
target_weights: BTreeMap::new(),
|
target_weights: BTreeMap::new(),
|
||||||
exit_symbols: BTreeSet::new(),
|
exit_symbols: BTreeSet::new(),
|
||||||
|
|||||||
Reference in New Issue
Block a user