修正满仓后既有目标调仓中断
This commit is contained in:
@@ -10606,7 +10606,9 @@ impl Strategy for PlatformExprStrategy {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if rebalance_working_symbols.len() >= selection_limit {
|
if rebalance_working_symbols.len() >= selection_limit {
|
||||||
break;
|
// New symbols cannot consume another slot, but later ranked symbols may
|
||||||
|
// already be held and still require a target-value adjustment.
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if ((pre_rebalance_symbols.contains(symbol)
|
if ((pre_rebalance_symbols.contains(symbol)
|
||||||
|| projected.positions().contains_key(symbol))
|
|| projected.positions().contains_key(symbol))
|
||||||
@@ -11072,7 +11074,7 @@ mod tests {
|
|||||||
let previous_date = d(2025, 1, 2);
|
let previous_date = d(2025, 1, 2);
|
||||||
let execution_date = d(2025, 1, 3);
|
let execution_date = d(2025, 1, 3);
|
||||||
let unresolved_symbol = "000999.SZ";
|
let unresolved_symbol = "000999.SZ";
|
||||||
let active_symbols = ["000001.SZ", "000002.SZ"];
|
let active_symbols = ["000001.SZ", "000002.SZ", "000003.SZ"];
|
||||||
let data = DataSet::from_components(
|
let data = DataSet::from_components(
|
||||||
std::iter::once(Instrument {
|
std::iter::once(Instrument {
|
||||||
symbol: unresolved_symbol.to_string(),
|
symbol: unresolved_symbol.to_string(),
|
||||||
@@ -11164,6 +11166,9 @@ mod tests {
|
|||||||
portfolio
|
portfolio
|
||||||
.position_mut(unresolved_symbol)
|
.position_mut(unresolved_symbol)
|
||||||
.buy(previous_date, 1_000, 10.0);
|
.buy(previous_date, 1_000, 10.0);
|
||||||
|
portfolio
|
||||||
|
.position_mut("000003.SZ")
|
||||||
|
.buy(previous_date, 100, 10.0);
|
||||||
let subscriptions = BTreeSet::new();
|
let subscriptions = BTreeSet::new();
|
||||||
let before_delisting_ctx = StrategyContext {
|
let before_delisting_ctx = StrategyContext {
|
||||||
execution_date: previous_date,
|
execution_date: previous_date,
|
||||||
@@ -11205,21 +11210,28 @@ mod tests {
|
|||||||
let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
|
let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
|
||||||
cfg.signal_symbol = "000001.SZ".to_string();
|
cfg.signal_symbol = "000001.SZ".to_string();
|
||||||
cfg.refresh_rate = 1;
|
cfg.refresh_rate = 1;
|
||||||
cfg.max_positions = 2;
|
cfg.max_positions = 3;
|
||||||
cfg.benchmark_short_ma_days = 1;
|
cfg.benchmark_short_ma_days = 1;
|
||||||
cfg.benchmark_long_ma_days = 1;
|
cfg.benchmark_long_ma_days = 1;
|
||||||
cfg.market_cap_lower_expr = "0".to_string();
|
cfg.market_cap_lower_expr = "0".to_string();
|
||||||
cfg.market_cap_upper_expr = "100".to_string();
|
cfg.market_cap_upper_expr = "100".to_string();
|
||||||
cfg.selection_limit_expr = "2".to_string();
|
cfg.selection_limit_expr = "3".to_string();
|
||||||
cfg.stock_filter_expr = "close > 0".to_string();
|
cfg.stock_filter_expr = "close > 0".to_string();
|
||||||
cfg.stop_loss_expr.clear();
|
cfg.stop_loss_expr.clear();
|
||||||
cfg.take_profit_expr.clear();
|
cfg.take_profit_expr.clear();
|
||||||
cfg.aiquant_transaction_cost = true;
|
cfg.aiquant_transaction_cost = true;
|
||||||
cfg.daily_replacement_limit = 2;
|
cfg.daily_replacement_limit = 3;
|
||||||
cfg.selection_buffer_multiple = 2.0;
|
cfg.selection_buffer_multiple = 2.0;
|
||||||
cfg.rebalance_existing_positions = false;
|
cfg.rebalance_existing_positions = false;
|
||||||
let mut strategy = PlatformExprStrategy::new(cfg);
|
let mut strategy = PlatformExprStrategy::new(cfg);
|
||||||
strategy.rebalance_day_counter = 1;
|
strategy.rebalance_day_counter = 1;
|
||||||
|
strategy.last_trading_ratio = Some(0.1);
|
||||||
|
strategy.last_target_selection = Some(
|
||||||
|
active_symbols
|
||||||
|
.iter()
|
||||||
|
.map(|symbol| (*symbol).to_string())
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
|
||||||
let decision = strategy.on_day(&ctx).expect("platform decision");
|
let decision = strategy.on_day(&ctx).expect("platform decision");
|
||||||
|
|
||||||
@@ -11233,7 +11245,9 @@ mod tests {
|
|||||||
decision
|
decision
|
||||||
.diagnostics
|
.diagnostics
|
||||||
.iter()
|
.iter()
|
||||||
.any(|note| note == "selected_symbols=000001.SZ,000002.SZ")
|
.any(|note| note == "selected_symbols=000001.SZ,000002.SZ,000003.SZ"),
|
||||||
|
"{:?}",
|
||||||
|
decision.diagnostics
|
||||||
);
|
);
|
||||||
assert!(!decision.order_intents.iter().any(|intent| {
|
assert!(!decision.order_intents.iter().any(|intent| {
|
||||||
matches!(
|
matches!(
|
||||||
@@ -11265,6 +11279,18 @@ mod tests {
|
|||||||
&& reason == "periodic_rebalance_buy"
|
&& reason == "periodic_rebalance_buy"
|
||||||
)
|
)
|
||||||
}));
|
}));
|
||||||
|
assert!(decision.order_intents.iter().any(|intent| {
|
||||||
|
matches!(
|
||||||
|
intent,
|
||||||
|
OrderIntent::TargetValue {
|
||||||
|
symbol,
|
||||||
|
target_value,
|
||||||
|
reason,
|
||||||
|
} if symbol == "000003.SZ"
|
||||||
|
&& *target_value > 0.0
|
||||||
|
&& reason == "periodic_rebalance_target_adjust"
|
||||||
|
)
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user