jscmp4 commited on
Commit
6c35bd4
·
verified ·
1 Parent(s): bbe58c1

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +3 -12
src/streamlit_app.py CHANGED
@@ -13,8 +13,8 @@ st.title("🧩 永久组合 (Permanent Portfolio) 阈值策略回测")
13
  @st.cache_data
14
  def get_data():
15
  tickers = ['SPY', 'TLT', 'GLD', 'SHV']
16
- # 下载数据,使用 Adj Close 以包含分红
17
- data = yf.download(tickers, start="2007-01-01")['Adj Close']
18
  data = data.dropna() # 确保所有ETF都有数据的日期才开始
19
  return data
20
 
@@ -57,28 +57,21 @@ def run_backtest(data, threshold=0.05, check_freq='Daily'):
57
  if check_freq == 'Daily':
58
  is_rebalance_day[:] = True
59
  elif check_freq == 'Monthly':
60
- is_rebalance_day = data.reset_index().groupby(data.index.to_period('M'))['Date'].idxmax().isin(data.index)
61
- # 上面这行稍复杂,简化处理:取重采样后的索引
62
  resampled_dates = data.resample('ME').last().index
63
  # 找到最接近的交易日
64
  search_indexer = data.index.searchsorted(resampled_dates)
65
  search_indexer = search_indexer[search_indexer < len(data)]
66
  valid_dates = data.index[search_indexer]
67
  is_rebalance_day = pd.Series(data.index.isin(valid_dates), index=data.index)
68
- # (Quarterly, Annually 逻辑类似,此处为了demo简化,重点放在阈值逻辑)
69
 
70
  # --- 每日循环 ---
71
- # 为了性能,这里不使用iterrows,而是简单循环
72
- # 注意:这里模拟的是 "Close 价格触发,Close 价格成交" (简化模型)
73
-
74
  dates = data.index
75
  prices_values = data.values # Numpy array 更快
76
 
77
  # 映射 column index
78
  asset_idx = {asset: i for i, asset in enumerate(data.columns)}
79
 
80
- last_rebalance_val = initial_cash
81
-
82
  for i in range(len(dates)):
83
  current_date = dates[i]
84
  current_prices = prices_values[i]
@@ -94,7 +87,6 @@ def run_backtest(data, threshold=0.05, check_freq='Daily'):
94
  portfolio_history.append({'Date': current_date, 'Total Value': total_value})
95
 
96
  # 2. 检查是否需要检测
97
- # 如果不是检测日,直接跳过
98
  if check_freq != 'Daily' and not is_rebalance_day[i]:
99
  continue
100
 
@@ -138,7 +130,6 @@ selected_thresholds = st.sidebar.multiselect(
138
  format_func=lambda x: f"±{int(x*100)}% ({25-int(x*100)}%-{25+int(x*100)}%)"
139
  )
140
 
141
- # 频率选择 (简化版,强制 Daily Check 以配合阈值策略)
142
  st.sidebar.info("注意:阈值策略通常配合 '每日检测' 使用,以捕捉瞬间波动。")
143
 
144
  # --- 5. 执行对比 ---
 
13
  @st.cache_data
14
  def get_data():
15
  tickers = ['SPY', 'TLT', 'GLD', 'SHV']
16
+ # 【修复点】:添加 auto_adjust=False 以保留 'Adj Close'
17
+ data = yf.download(tickers, start="2007-01-01", auto_adjust=False)['Adj Close']
18
  data = data.dropna() # 确保所有ETF都有数据的日期才开始
19
  return data
20
 
 
57
  if check_freq == 'Daily':
58
  is_rebalance_day[:] = True
59
  elif check_freq == 'Monthly':
60
+ # Pandas 2.x 建议使用 'ME' (Month End) 代替 'M'
 
61
  resampled_dates = data.resample('ME').last().index
62
  # 找到最接近的交易日
63
  search_indexer = data.index.searchsorted(resampled_dates)
64
  search_indexer = search_indexer[search_indexer < len(data)]
65
  valid_dates = data.index[search_indexer]
66
  is_rebalance_day = pd.Series(data.index.isin(valid_dates), index=data.index)
 
67
 
68
  # --- 每日循环 ---
 
 
 
69
  dates = data.index
70
  prices_values = data.values # Numpy array 更快
71
 
72
  # 映射 column index
73
  asset_idx = {asset: i for i, asset in enumerate(data.columns)}
74
 
 
 
75
  for i in range(len(dates)):
76
  current_date = dates[i]
77
  current_prices = prices_values[i]
 
87
  portfolio_history.append({'Date': current_date, 'Total Value': total_value})
88
 
89
  # 2. 检查是否需要检测
 
90
  if check_freq != 'Daily' and not is_rebalance_day[i]:
91
  continue
92
 
 
130
  format_func=lambda x: f"±{int(x*100)}% ({25-int(x*100)}%-{25+int(x*100)}%)"
131
  )
132
 
 
133
  st.sidebar.info("注意:阈值策略通常配合 '每日检测' 使用,以捕捉瞬间波动。")
134
 
135
  # --- 5. 执行对比 ---