Spaces:
Sleeping
Sleeping
Update src/streamlit_app.py
Browse files- 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 |
-
#
|
| 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 |
-
|
| 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. 执行对比 ---
|