You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于权重偏离阈值的动态投资组合再平衡回溯测试问询

Solution for Threshold-Based Dynamic Rebalancing Backtest

Great question—dynamic rebalancing based on weight thresholds is trickier than fixed-period rebalancing because the portfolio state depends on whether we rebalanced in prior periods, which simple lag() can't handle cleanly. Let's walk through a solution using iterative state tracking that meets all your requirements: custom target weights, multiple stocks/periods, and automatic rebalancing when weights drift beyond your threshold.

Step 1: Setup Base Data & Parameters

First, let's start with your existing data and define our rebalancing threshold (adjust this to your needs):

library(dplyr)
library(purrr)
library(tidyr)
library(ggplot2)

set.seed(3)
n <- 6
rets <- tibble(period = rep(1:n, 3),
               stock = c(rep("A", n), rep("B", n), rep("C", n)),
               ret = c(rnorm(n, 0, 0.3), rnorm(n, 0, 0.2), rnorm(n, 0, 0.1)))
target_weights <- tibble(stock = c("A", "B", "C"), target_weight = 1/3)

# Define rebalancing threshold (e.g., 10% deviation from target)
rebalance_threshold <- 0.1

Step 2: Iterative State Tracking with accumulate()

The core idea is to track the portfolio's state (current values per stock, whether we just rebalanced) across each period. We'll use purrr::accumulate() to iterate through each period, updating the state based on returns and our rebalancing rule.

First, Reshape Returns to Wide Format

This makes it easier to handle all stock returns for a single period at once:

rets_wide <- rets %>% 
  pivot_wider(names_from = stock, values_from = ret) %>% 
  arrange(period)

Define Initial Portfolio State

Our starting point is a fully rebalanced portfolio with each stock at its target weight (assuming an initial investment of 1 unit):

target_weights_vec <- target_weights %>% deframe() # Convert to named vector: A=1/3, B=1/3, C=1/3

initial_state <- list(
  period = 0,
  portfolio_values = target_weights_vec,
  rebalanced = TRUE # Mark initial state as rebalanced
)

Define Period Processing Function

This function takes the previous portfolio state and current period returns, then calculates whether rebalancing is needed:

process_period <- function(prev_state, current_rets) {
  current_period <- current_rets$period
  
  # Calculate portfolio values if we DON'T rebalance
  new_values_no_rebal <- prev_state$portfolio_values * (1 + current_rets[c("A", "B", "C")])
  total_value <- sum(new_values_no_rebal)
  current_weights_no_rebal <- new_values_no_rebal / total_value
  
  # Check if any weight deviates beyond the threshold
  deviations <- abs(current_weights_no_rebal - target_weights_vec)
  needs_rebalance <- any(deviations > rebalance_threshold)
  
  # Apply rebalancing if needed
  if (needs_rebalance) {
    # Reset values to match target weights (total value remains the same)
    final_values <- total_value * target_weights_vec
    final_weights <- target_weights_vec
    rebalanced_flag <- TRUE
  } else {
    final_values <- new_values_no_rebal
    final_weights <- current_weights_no_rebal
    rebalanced_flag <- FALSE
  }
  
  # Return updated state
  list(
    period = current_period,
    portfolio_values = final_values,
    actual_weights = final_weights,
    rebalanced = rebalanced_flag,
    deviations = deviations
  )
}

Run the Backtest

Now we'll iterate through each period to build our portfolio history:

# Convert wide returns to a list of individual period rows
period_list <- split(rets_wide, rets_wide$period)

# Run the iterative backtest
portfolio_history <- accumulate(period_list, process_period, .init = initial_state)

# Clean up results into a tidy data frame
results <- portfolio_history[-1] %>% # Remove initial state
  map_dfr(function(state) {
    tibble(
      period = state$period,
      stock = names(state$actual_weights),
      actual_weight = state$actual_weights,
      target_weight = target_weights_vec[stock],
      deviation = state$deviations[stock],
      rebalanced = state$rebalanced
    )
  })

Step 3: Inspect & Visualize Results

Let's check the output to see when rebalancing happened and how weights evolved:

# View key results
print(results %>% select(period, stock, actual_weight, deviation, rebalanced))

# Visualize weights over time
results %>% 
  ggplot(aes(x = period, y = actual_weight, color = stock)) +
  geom_line(linewidth = 1) +
  geom_hline(aes(yintercept = target_weight), linetype = "dashed", color = "gray50") +
  # Mark rebalance periods with white-filled points
  geom_point(data = subset(results, rebalanced), size = 3, shape = 21, fill = "white") +
  labs(
    title = "Threshold-Based Dynamic Rebalancing",
    x = "Period",
    y = "Portfolio Weight",
    color = "Stock",
    caption = "Dashed lines = target weights; white points = rebalance events"
  ) +
  theme_minimal()

Key Notes & Extensions

  • State Tracking: This approach works because we explicitly carry forward the portfolio's value state between periods, which lag() can't do since it only looks at prior rows without accounting for rebalancing resets.
  • Customization: Adjust rebalance_threshold to tighten or loosen your rebalancing rule, or modify target_weights to use non-equal weights.
  • Add-Ons: You can extend the process_period function to include transaction costs, minimum rebalance amounts, or other rules (e.g., only rebalance if multiple stocks are out of threshold).

内容的提问来源于stack exchange,提问作者Martin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 15:47:48