Compare commits
5 Commits
0542a44afe
...
5fa3d3bf54
| Author | SHA1 | Date | |
|---|---|---|---|
| 5fa3d3bf54 | |||
| 7bc832f3c6 | |||
| 46c647d676 | |||
| 24b961ba61 | |||
| d2cf021194 |
@@ -178,121 +178,6 @@ impl PlatformPortfolioDrawdownController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ordered_weight_bps_from_scales(
|
|
||||||
scales: &[(String, f64)],
|
|
||||||
) -> Result<Vec<(String, u32)>, BacktestError> {
|
|
||||||
if scales.is_empty() {
|
|
||||||
return Ok(Vec::new());
|
|
||||||
}
|
|
||||||
if scales
|
|
||||||
.iter()
|
|
||||||
.any(|(_, scale)| !scale.is_finite() || *scale < 0.0)
|
|
||||||
{
|
|
||||||
return Err(BacktestError::Execution(
|
|
||||||
"target portfolio buy scale must be finite and non-negative".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let scale_total = scales.iter().map(|(_, scale)| *scale).sum::<f64>();
|
|
||||||
if !scale_total.is_finite() || scale_total <= 0.0 {
|
|
||||||
return Err(BacktestError::Execution(
|
|
||||||
"target portfolio buy scale total must be positive".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
let mut weights = scales
|
|
||||||
.iter()
|
|
||||||
.map(|(symbol, scale)| {
|
|
||||||
(
|
|
||||||
symbol.clone(),
|
|
||||||
((*scale / scale_total * 10_000.0) + 1e-9).floor() as u32,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let assigned = weights.iter().map(|(_, weight)| *weight).sum::<u32>();
|
|
||||||
let remainder = 10_000_u32.saturating_sub(assigned);
|
|
||||||
let weight_count = weights.len();
|
|
||||||
for index in 0..remainder as usize {
|
|
||||||
weights[index % weight_count].1 += 1;
|
|
||||||
}
|
|
||||||
Ok(weights)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn replenish_target_weight_bps(
|
|
||||||
original_weights: &[(String, u32)],
|
|
||||||
candidate_symbols: &[String],
|
|
||||||
excluded_symbols: &BTreeSet<String>,
|
|
||||||
target_count: usize,
|
|
||||||
) -> Vec<(String, u32)> {
|
|
||||||
let original_by_symbol = original_weights.iter().cloned().collect::<BTreeMap<_, _>>();
|
|
||||||
let mut active = original_weights
|
|
||||||
.iter()
|
|
||||||
.filter(|(symbol, weight)| *weight > 0 && !excluded_symbols.contains(symbol))
|
|
||||||
.map(|(symbol, _)| symbol.clone())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let mut seen = active.iter().cloned().collect::<BTreeSet<_>>();
|
|
||||||
let mut promoted = Vec::new();
|
|
||||||
for symbol in candidate_symbols {
|
|
||||||
if active.len() >= target_count {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if excluded_symbols.contains(symbol) || !seen.insert(symbol.clone()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
active.push(symbol.clone());
|
|
||||||
promoted.push(symbol.clone());
|
|
||||||
}
|
|
||||||
if active.is_empty() {
|
|
||||||
return Vec::new();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut result = active
|
|
||||||
.iter()
|
|
||||||
.map(|symbol| {
|
|
||||||
(
|
|
||||||
symbol.clone(),
|
|
||||||
*original_by_symbol.get(symbol).unwrap_or(&0),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let assigned = result.iter().map(|(_, weight)| *weight).sum::<u32>();
|
|
||||||
let missing = 10_000_u32.saturating_sub(assigned);
|
|
||||||
if missing == 0 {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
let recipients = if promoted.is_empty() {
|
|
||||||
active
|
|
||||||
} else {
|
|
||||||
promoted
|
|
||||||
};
|
|
||||||
let base_total = recipients
|
|
||||||
.iter()
|
|
||||||
.map(|symbol| *original_by_symbol.get(symbol).unwrap_or(&0))
|
|
||||||
.sum::<u32>();
|
|
||||||
let mut allocated = recipients
|
|
||||||
.iter()
|
|
||||||
.map(|symbol| {
|
|
||||||
if base_total > 0 {
|
|
||||||
(u64::from(missing) * u64::from(*original_by_symbol.get(symbol).unwrap_or(&0))
|
|
||||||
/ u64::from(base_total)) as u32
|
|
||||||
} else {
|
|
||||||
missing / recipients.len() as u32
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let allocated_total = allocated.iter().sum::<u32>();
|
|
||||||
let recipient_count = allocated.len();
|
|
||||||
for index in 0..missing.saturating_sub(allocated_total) as usize {
|
|
||||||
allocated[index % recipient_count] += 1;
|
|
||||||
}
|
|
||||||
let additions = recipients
|
|
||||||
.into_iter()
|
|
||||||
.zip(allocated)
|
|
||||||
.collect::<BTreeMap<_, _>>();
|
|
||||||
for (symbol, weight) in &mut result {
|
|
||||||
*weight += additions.get(symbol).copied().unwrap_or(0);
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
enum SelectionRiskDeferral {
|
enum SelectionRiskDeferral {
|
||||||
None,
|
None,
|
||||||
@@ -1225,7 +1110,6 @@ pub struct PlatformExprStrategy {
|
|||||||
rebalance_day_counter: usize,
|
rebalance_day_counter: usize,
|
||||||
last_rebalance_date: Option<NaiveDate>,
|
last_rebalance_date: Option<NaiveDate>,
|
||||||
last_target_selection: Option<BTreeSet<String>>,
|
last_target_selection: Option<BTreeSet<String>>,
|
||||||
last_target_order: Option<Vec<String>>,
|
|
||||||
last_trading_ratio: Option<f64>,
|
last_trading_ratio: Option<f64>,
|
||||||
portfolio_drawdown_controller: Option<PlatformPortfolioDrawdownController>,
|
portfolio_drawdown_controller: Option<PlatformPortfolioDrawdownController>,
|
||||||
pending_highlimit_holdings: BTreeSet<String>,
|
pending_highlimit_holdings: BTreeSet<String>,
|
||||||
@@ -1560,7 +1444,6 @@ impl PlatformExprStrategy {
|
|||||||
rebalance_day_counter: 0,
|
rebalance_day_counter: 0,
|
||||||
last_rebalance_date: None,
|
last_rebalance_date: None,
|
||||||
last_target_selection: None,
|
last_target_selection: None,
|
||||||
last_target_order: None,
|
|
||||||
last_trading_ratio: None,
|
last_trading_ratio: None,
|
||||||
portfolio_drawdown_controller,
|
portfolio_drawdown_controller,
|
||||||
pending_highlimit_holdings: BTreeSet::new(),
|
pending_highlimit_holdings: BTreeSet::new(),
|
||||||
@@ -12533,53 +12416,15 @@ impl Strategy for PlatformExprStrategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let stop_take_exit_signal_symbols = current_stop_take_exit_symbols.clone();
|
let stop_take_exit_signal_symbols = current_stop_take_exit_symbols.clone();
|
||||||
let target_portfolio_weights =
|
|
||||||
if self.config.target_portfolio_daily_enabled && selection_limit > 0 {
|
|
||||||
let mut scales = Vec::new();
|
|
||||||
let original_target_symbols = self
|
|
||||||
.last_target_order
|
|
||||||
.as_ref()
|
|
||||||
.filter(|symbols| !symbols.is_empty())
|
|
||||||
.cloned()
|
|
||||||
.or_else(|| {
|
|
||||||
self.last_target_selection
|
|
||||||
.as_ref()
|
|
||||||
.filter(|symbols| !symbols.is_empty())
|
|
||||||
.map(|symbols| symbols.iter().cloned().collect())
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| stock_list.iter().take(selection_limit).cloned().collect());
|
|
||||||
for symbol in original_target_symbols.iter().take(selection_limit) {
|
|
||||||
let decision_stock = self.stock_state_with_factor_date(
|
|
||||||
ctx,
|
|
||||||
decision_date,
|
|
||||||
selection_factor_date,
|
|
||||||
symbol,
|
|
||||||
)?;
|
|
||||||
scales.push((symbol.clone(), self.buy_scale(ctx, &day, &decision_stock)?));
|
|
||||||
}
|
|
||||||
let original_weights = ordered_weight_bps_from_scales(&scales)?;
|
|
||||||
let mut excluded_target_symbols = exit_symbols.clone();
|
|
||||||
excluded_target_symbols.extend(stop_take_exit_signal_symbols.iter().cloned());
|
|
||||||
replenish_target_weight_bps(
|
|
||||||
&original_weights,
|
|
||||||
&stock_list,
|
|
||||||
&excluded_target_symbols,
|
|
||||||
selection_limit,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
};
|
|
||||||
let target_portfolio_weight_bps = target_portfolio_weights
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.collect::<BTreeMap<_, _>>();
|
|
||||||
|
|
||||||
if self.config.rotation_enabled
|
if self.config.rotation_enabled
|
||||||
&& self.config.daily_position_target_adjust_enabled
|
&& self.config.daily_position_target_adjust_enabled
|
||||||
&& trading_ratio > 0.0
|
&& trading_ratio > 0.0
|
||||||
&& (self.config.target_portfolio_daily_enabled || trading_ratio < 1.0)
|
&& (self.config.target_portfolio_daily_enabled || trading_ratio < 1.0)
|
||||||
&& selection_limit > 0
|
&& selection_limit > 0
|
||||||
&& !(self.config.target_portfolio_daily_enabled && daily_top_up_active)
|
&& !(persistent_model_lifecycle
|
||||||
|
&& self.config.target_portfolio_daily_enabled
|
||||||
|
&& daily_top_up_active)
|
||||||
&& (!ctx.portfolio.positions().is_empty()
|
&& (!ctx.portfolio.positions().is_empty()
|
||||||
|| (persistent_model_lifecycle && !self.position_entry_dates.is_empty()))
|
|| (persistent_model_lifecycle && !self.position_entry_dates.is_empty()))
|
||||||
{
|
{
|
||||||
@@ -12612,24 +12457,9 @@ impl Strategy for PlatformExprStrategy {
|
|||||||
&position.symbol,
|
&position.symbol,
|
||||||
)?;
|
)?;
|
||||||
let stock_scale = self.buy_scale(ctx, &day, &decision_stock)?;
|
let stock_scale = self.buy_scale(ctx, &day, &decision_stock)?;
|
||||||
let target_value = if self.config.target_portfolio_daily_enabled {
|
let target_value = strategy_visible_total_value * trading_ratio
|
||||||
target_portfolio_weight_bps
|
/ selection_limit as f64
|
||||||
.get(&position.symbol)
|
* stock_scale;
|
||||||
.map(|weight_bps| {
|
|
||||||
strategy_visible_total_value
|
|
||||||
* trading_ratio
|
|
||||||
* f64::from(*weight_bps)
|
|
||||||
/ 10_000.0
|
|
||||||
})
|
|
||||||
.unwrap_or(
|
|
||||||
strategy_visible_total_value * trading_ratio
|
|
||||||
/ selection_limit as f64
|
|
||||||
* stock_scale,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
strategy_visible_total_value * trading_ratio / selection_limit as f64
|
|
||||||
* stock_scale
|
|
||||||
};
|
|
||||||
if !target_value.is_finite() || target_value <= 0.0 {
|
if !target_value.is_finite() || target_value <= 0.0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -12728,24 +12558,9 @@ impl Strategy for PlatformExprStrategy {
|
|||||||
&symbol,
|
&symbol,
|
||||||
)?;
|
)?;
|
||||||
let stock_scale = self.buy_scale(ctx, &day, &decision_stock)?;
|
let stock_scale = self.buy_scale(ctx, &day, &decision_stock)?;
|
||||||
let target_value = if self.config.target_portfolio_daily_enabled {
|
let target_value = strategy_visible_total_value * trading_ratio
|
||||||
target_portfolio_weight_bps
|
/ selection_limit as f64
|
||||||
.get(&symbol)
|
* stock_scale;
|
||||||
.map(|weight_bps| {
|
|
||||||
strategy_visible_total_value
|
|
||||||
* trading_ratio
|
|
||||||
* f64::from(*weight_bps)
|
|
||||||
/ 10_000.0
|
|
||||||
})
|
|
||||||
.unwrap_or(
|
|
||||||
strategy_visible_total_value * trading_ratio
|
|
||||||
/ selection_limit as f64
|
|
||||||
* stock_scale,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
strategy_visible_total_value * trading_ratio / selection_limit as f64
|
|
||||||
* stock_scale
|
|
||||||
};
|
|
||||||
if !target_value.is_finite() || target_value <= 0.0 {
|
if !target_value.is_finite() || target_value <= 0.0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -13147,11 +12962,46 @@ impl Strategy for PlatformExprStrategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if daily_top_up_active && self.config.target_portfolio_daily_enabled {
|
if daily_top_up_active
|
||||||
for (symbol, weight_bps) in &target_portfolio_weights {
|
&& self.config.target_portfolio_daily_enabled
|
||||||
let target_value =
|
&& persistent_model_lifecycle
|
||||||
strategy_visible_total_value * trading_ratio * f64::from(*weight_bps)
|
{
|
||||||
/ 10_000.0;
|
let mut target_symbols = self
|
||||||
|
.position_entry_dates
|
||||||
|
.keys()
|
||||||
|
.filter(|symbol| !exit_symbols.contains(*symbol))
|
||||||
|
.filter(|symbol| !factor_position_action_symbols.contains(*symbol))
|
||||||
|
.cloned()
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
for symbol in &stock_list {
|
||||||
|
if target_symbols.len() >= selection_limit {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if target_symbols.contains(symbol) || exit_symbols.contains(symbol) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ctx
|
||||||
|
.data
|
||||||
|
.market_latest_back_adjusted_close(signal_date, symbol)
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
self.remember_position_entry_date(symbol, signal_date);
|
||||||
|
target_symbols.insert(symbol.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
for symbol in target_symbols {
|
||||||
|
let decision_stock = self.stock_state_with_factor_date(
|
||||||
|
ctx,
|
||||||
|
decision_date,
|
||||||
|
selection_factor_date,
|
||||||
|
&symbol,
|
||||||
|
)?;
|
||||||
|
let stock_scale = self.buy_scale(ctx, &day, &decision_stock)?;
|
||||||
|
let target_value = strategy_visible_total_value * trading_ratio
|
||||||
|
/ selection_limit as f64
|
||||||
|
* stock_scale;
|
||||||
if !target_value.is_finite() || target_value <= 0.0 {
|
if !target_value.is_finite() || target_value <= 0.0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -13179,7 +13029,6 @@ impl Strategy for PlatformExprStrategy {
|
|||||||
&mut projected_execution_state,
|
&mut projected_execution_state,
|
||||||
);
|
);
|
||||||
intraday_attempted_buys.insert(symbol.clone());
|
intraday_attempted_buys.insert(symbol.clone());
|
||||||
self.remember_position_entry_date(symbol, signal_date);
|
|
||||||
}
|
}
|
||||||
let after_qty = projected
|
let after_qty = projected
|
||||||
.position(&symbol)
|
.position(&symbol)
|
||||||
@@ -13194,9 +13043,40 @@ impl Strategy for PlatformExprStrategy {
|
|||||||
deferred_daily_target_values.insert(symbol.clone(), target_value);
|
deferred_daily_target_values.insert(symbol.clone(), target_value);
|
||||||
}
|
}
|
||||||
if after_qty > before_qty {
|
if after_qty > before_qty {
|
||||||
same_bar_buy_symbols.insert(symbol.clone());
|
same_bar_buy_symbols.insert(symbol);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if daily_top_up_active && self.config.target_portfolio_daily_enabled {
|
||||||
|
self.try_daily_top_up_at_position(
|
||||||
|
ctx,
|
||||||
|
&day,
|
||||||
|
&stock_list,
|
||||||
|
decision_date,
|
||||||
|
execution_date,
|
||||||
|
projection_date,
|
||||||
|
selection_factor_date,
|
||||||
|
signal_date,
|
||||||
|
daily_top_up_target_budget,
|
||||||
|
selection_limit,
|
||||||
|
defer_execution_risk,
|
||||||
|
None,
|
||||||
|
&mut projected,
|
||||||
|
&mut projected_execution_state,
|
||||||
|
&mut order_intents,
|
||||||
|
&mut available_cash,
|
||||||
|
&mut slot_working_symbols,
|
||||||
|
&mut same_bar_buy_symbols,
|
||||||
|
&pending_full_close_symbols,
|
||||||
|
&slot_blocking_symbols,
|
||||||
|
&same_day_sold_symbols,
|
||||||
|
&exit_symbols,
|
||||||
|
&delayed_sold_symbols,
|
||||||
|
&mut intraday_attempted_buys,
|
||||||
|
&mut daily_top_up_pending_buy_value,
|
||||||
|
&deferred_daily_target_values,
|
||||||
|
debug_daily_top_up,
|
||||||
|
&mut daily_top_up_debug_notes,
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if periodic_rebalance {
|
if periodic_rebalance {
|
||||||
@@ -13286,10 +13166,7 @@ impl Strategy for PlatformExprStrategy {
|
|||||||
if !rebalance_existing_positions {
|
if !rebalance_existing_positions {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let target_value = target_portfolio_weight_bps
|
let target_value = target_budget / selection_limit as f64 * stock_scale;
|
||||||
.get(symbol)
|
|
||||||
.map(|weight_bps| target_budget * f64::from(*weight_bps) / 10_000.0)
|
|
||||||
.unwrap_or(target_budget / selection_limit as f64 * stock_scale);
|
|
||||||
let before_qty = projected
|
let before_qty = projected
|
||||||
.position(symbol)
|
.position(symbol)
|
||||||
.map(|position| position.quantity)
|
.map(|position| position.quantity)
|
||||||
@@ -13341,10 +13218,7 @@ impl Strategy for PlatformExprStrategy {
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let target_value = target_portfolio_weight_bps
|
let target_value = fixed_buy_cash * stock_scale;
|
||||||
.get(symbol)
|
|
||||||
.map(|weight_bps| target_budget * f64::from(*weight_bps) / 10_000.0)
|
|
||||||
.unwrap_or(fixed_buy_cash * stock_scale);
|
|
||||||
if target_value <= 0.0 {
|
if target_value <= 0.0 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -13397,13 +13271,8 @@ impl Strategy for PlatformExprStrategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if self.config.rotation_enabled && periodic_rebalance {
|
if self.config.rotation_enabled && periodic_rebalance {
|
||||||
let target_order = stock_list
|
self.last_target_selection =
|
||||||
.iter()
|
Some(stock_list.iter().take(selection_limit).cloned().collect());
|
||||||
.take(selection_limit)
|
|
||||||
.cloned()
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
self.last_target_selection = Some(target_order.iter().cloned().collect());
|
|
||||||
self.last_target_order = Some(target_order);
|
|
||||||
}
|
}
|
||||||
if self.config.rotation_enabled && trading_ratio.is_finite() {
|
if self.config.rotation_enabled && trading_ratio.is_finite() {
|
||||||
self.last_trading_ratio = Some(trading_ratio);
|
self.last_trading_ratio = Some(trading_ratio);
|
||||||
@@ -13575,8 +13444,7 @@ mod tests {
|
|||||||
PlatformScheduleFrequency, PlatformStopTakeReferencePriceMode, PlatformTradeAction,
|
PlatformScheduleFrequency, PlatformStopTakeReferencePriceMode, PlatformTradeAction,
|
||||||
PlatformUniverseActionKind, RuntimeHelperResolution, SelectionRiskDeferral,
|
PlatformUniverseActionKind, RuntimeHelperResolution, SelectionRiskDeferral,
|
||||||
StockFilterQuoteUsage, StockRollingField, StockSnapshotFieldRequirements,
|
StockFilterQuoteUsage, StockRollingField, StockSnapshotFieldRequirements,
|
||||||
framework_stock_rolling_factor_requirement, ordered_weight_bps_from_scales,
|
framework_stock_rolling_factor_requirement, scheduled_position_exposure,
|
||||||
replenish_target_weight_bps, scheduled_position_exposure,
|
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
AlgoOrderStyle, BenchmarkSnapshot, CandidateEligibility, CorporateAction,
|
AlgoOrderStyle, BenchmarkSnapshot, CandidateEligibility, CorporateAction,
|
||||||
@@ -13592,39 +13460,6 @@ mod tests {
|
|||||||
NaiveDate::from_ymd_opt(year, month, day).expect("valid date")
|
NaiveDate::from_ymd_opt(year, month, day).expect("valid date")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn target_weight_replenishment_matches_integer_bps_pool_contract() {
|
|
||||||
let scales = (0..24)
|
|
||||||
.map(|index| {
|
|
||||||
(
|
|
||||||
format!("S{index:02}"),
|
|
||||||
if index < 16 { 1.0008 } else { 0.9984 },
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let original = ordered_weight_bps_from_scales(&scales).expect("weights");
|
|
||||||
let excluded = BTreeSet::from(["S01".to_string()]);
|
|
||||||
let candidates = scales
|
|
||||||
.iter()
|
|
||||||
.map(|(symbol, _)| symbol.clone())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let replenished = replenish_target_weight_bps(&original, &candidates, &excluded, 24);
|
|
||||||
let by_symbol = replenished.iter().cloned().collect::<BTreeMap<_, _>>();
|
|
||||||
|
|
||||||
assert_eq!(replenished.len(), 23);
|
|
||||||
assert_eq!(
|
|
||||||
replenished.iter().map(|(_, weight)| *weight).sum::<u32>(),
|
|
||||||
10_000
|
|
||||||
);
|
|
||||||
assert_eq!(by_symbol.get("S00"), Some(&436));
|
|
||||||
assert_eq!(by_symbol.get("S02"), Some(&436));
|
|
||||||
assert_eq!(by_symbol.get("S03"), Some(&436));
|
|
||||||
assert_eq!(by_symbol.get("S04"), Some(&435));
|
|
||||||
assert_eq!(by_symbol.get("S16"), Some(&434));
|
|
||||||
assert!(!by_symbol.contains_key("S01"));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn single_symbol_platform_data(dates: &[NaiveDate], symbol: &str) -> DataSet {
|
fn single_symbol_platform_data(dates: &[NaiveDate], symbol: &str) -> DataSet {
|
||||||
DataSet::from_components(
|
DataSet::from_components(
|
||||||
vec![Instrument {
|
vec![Instrument {
|
||||||
@@ -27722,14 +27557,6 @@ mod tests {
|
|||||||
let mut strategy = PlatformExprStrategy::new(cfg);
|
let mut strategy = PlatformExprStrategy::new(cfg);
|
||||||
strategy.rebalance_day_counter = 2;
|
strategy.rebalance_day_counter = 2;
|
||||||
strategy.last_rebalance_date = Some(prev_date);
|
strategy.last_rebalance_date = Some(prev_date);
|
||||||
let target_order = vec![
|
|
||||||
buy_first.to_string(),
|
|
||||||
buy_second.to_string(),
|
|
||||||
keep_first.to_string(),
|
|
||||||
keep_second.to_string(),
|
|
||||||
];
|
|
||||||
strategy.last_target_selection = Some(target_order.iter().cloned().collect());
|
|
||||||
strategy.last_target_order = Some(target_order);
|
|
||||||
strategy
|
strategy
|
||||||
.position_entry_dates
|
.position_entry_dates
|
||||||
.insert(take_profit.to_string(), prev_date);
|
.insert(take_profit.to_string(), prev_date);
|
||||||
|
|||||||
Reference in New Issue
Block a user