对齐模型轮动目标调仓语义

This commit is contained in:
boris
2026-07-12 00:31:00 +08:00
parent 942ba84ca5
commit 30a4071ee0
2 changed files with 97 additions and 0 deletions
@@ -229,6 +229,8 @@ pub struct PlatformExprStrategyConfig {
pub signal_rebalance_dates: BTreeSet<NaiveDate>,
pub rotation_enabled: bool,
pub daily_top_up_enabled: bool,
pub daily_position_target_adjust_enabled: bool,
pub rebalance_existing_positions: bool,
pub retry_empty_rebalance: bool,
pub calendar_rebalance_interval: bool,
pub max_holding_days: Option<i64>,
@@ -299,6 +301,8 @@ fn band_low(index_close) {
signal_rebalance_dates: BTreeSet::new(),
rotation_enabled: true,
daily_top_up_enabled: false,
daily_position_target_adjust_enabled: true,
rebalance_existing_positions: false,
retry_empty_rebalance: false,
calendar_rebalance_interval: false,
max_holding_days: None,
@@ -8881,6 +8885,7 @@ impl Strategy for PlatformExprStrategy {
if self.config.aiquant_transaction_cost
&& self.config.rotation_enabled
&& self.config.daily_position_target_adjust_enabled
&& trading_ratio > 0.0
&& trading_ratio < 1.0
&& selection_limit > 0
@@ -9370,7 +9375,44 @@ impl Strategy for PlatformExprStrategy {
&& !same_day_sold_symbols.contains(symbol)
&& !pending_full_close_symbols.contains(symbol);
if projected.positions().contains_key(symbol) && !released_exit_position {
if self.config.aiquant_transaction_cost
&& !self.config.rebalance_existing_positions
{
continue;
}
if self.config.aiquant_transaction_cost {
let target_value = target_budget / selection_limit as f64 * stock_scale;
let before_qty = projected
.position(symbol)
.map(|position| position.quantity)
.unwrap_or(0);
let mut trial_projected = projected.clone();
let mut trial_execution_state = projected_execution_state.clone();
self.project_target_value(
ctx,
&mut trial_projected,
projection_date,
symbol,
target_value,
&mut trial_execution_state,
);
let after_qty = trial_projected
.position(symbol)
.map(|position| position.quantity)
.unwrap_or(0);
if after_qty != before_qty {
projected = trial_projected;
projected_execution_state = trial_execution_state;
order_intents.push(OrderIntent::TargetValue {
symbol: symbol.clone(),
target_value,
reason: "periodic_rebalance_target_adjust".to_string(),
});
if after_qty > before_qty {
intraday_attempted_buys.insert(symbol.clone());
}
aiquant_available_cash = projected.cash().max(0.0);
}
continue;
}
if same_day_sold_symbols.contains(symbol)
@@ -25690,6 +25732,47 @@ mod tests {
&& reason == "periodic_rebalance_buy"
&& (*value - 499_000.0).abs() < 1e-6
));
let mut target_value_cfg = PlatformExprStrategyConfig::microcap_rotation();
target_value_cfg.signal_symbol = symbol.to_string();
target_value_cfg.refresh_rate = 20;
target_value_cfg.max_positions = 1;
target_value_cfg.benchmark_short_ma_days = 1;
target_value_cfg.benchmark_long_ma_days = 1;
target_value_cfg.market_cap_lower_expr = "0".to_string();
target_value_cfg.market_cap_upper_expr = "100".to_string();
target_value_cfg.selection_limit_expr = "1".to_string();
target_value_cfg.stock_filter_expr = "close > 0".to_string();
target_value_cfg.take_profit_expr.clear();
target_value_cfg.stop_loss_expr.clear();
target_value_cfg.aiquant_transaction_cost = true;
target_value_cfg.rebalance_existing_positions = true;
target_value_cfg.intraday_execution_time =
Some(NaiveTime::from_hms_opt(10, 18, 0).unwrap());
let mut target_value_strategy = PlatformExprStrategy::new(target_value_cfg);
target_value_strategy.rebalance_day_counter = 20;
let target_value_decision = target_value_strategy
.on_day(&ctx)
.expect("target-value platform decision");
assert!(
target_value_decision
.order_intents
.iter()
.any(|intent| matches!(
intent,
OrderIntent::TargetValue {
symbol: intent_symbol,
target_value,
reason,
} if intent_symbol == symbol
&& reason == "periodic_rebalance_target_adjust"
&& (*target_value - 500_000.0).abs() < 1e-6
)),
"{:?}",
target_value_decision.order_intents
);
}
#[test]
@@ -691,6 +691,10 @@ pub struct StrategyExpressionTradingConfig {
#[serde(default)]
pub daily_top_up: Option<bool>,
#[serde(default)]
pub daily_position_target_adjust: Option<bool>,
#[serde(default)]
pub rebalance_existing_positions: Option<bool>,
#[serde(default)]
pub retry_empty_rebalance: Option<bool>,
#[serde(default)]
pub weak_market_shrink_overweight_threshold: Option<f64>,
@@ -1628,6 +1632,12 @@ pub fn platform_expr_config_from_spec(
if let Some(enabled) = trading.daily_top_up {
cfg.daily_top_up_enabled = enabled;
}
if let Some(enabled) = trading.daily_position_target_adjust {
cfg.daily_position_target_adjust_enabled = enabled;
}
if let Some(enabled) = trading.rebalance_existing_positions {
cfg.rebalance_existing_positions = enabled;
}
if let Some(enabled) = trading.retry_empty_rebalance {
cfg.retry_empty_rebalance = enabled;
}
@@ -2945,6 +2955,8 @@ mod tests {
"runtimeExpressions": {
"trading": {
"dailyTopUp": false,
"dailyPositionTargetAdjust": false,
"rebalanceExistingPositions": true,
"retryEmptyRebalance": false
}
}
@@ -2953,6 +2965,8 @@ mod tests {
let cfg = platform_expr_config_from_value("", "", &explicit_off).expect("config");
assert!(!cfg.daily_top_up_enabled);
assert!(!cfg.daily_position_target_adjust_enabled);
assert!(cfg.rebalance_existing_positions);
assert!(!cfg.retry_empty_rebalance);
}