Spaces:
Sleeping
Sleeping
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +39 -38
src/streamlit_app.py
CHANGED
|
@@ -1473,7 +1473,8 @@ st.markdown("""
|
|
| 1473 |
""", unsafe_allow_html=True)
|
| 1474 |
|
| 1475 |
# ------ 功能函數 ------ #
|
| 1476 |
-
|
|
|
|
| 1477 |
參數:
|
| 1478 |
stock_ids (list): 股票代碼列表
|
| 1479 |
period (str): 時間週期
|
|
@@ -1481,9 +1482,9 @@ st.markdown("""
|
|
| 1481 |
|
| 1482 |
返回:
|
| 1483 |
dict: 股票數據字典
|
| 1484 |
-
|
| 1485 |
stock_data = {}
|
| 1486 |
-
|
| 1487 |
with st.spinner("正在獲取股票數據..."):
|
| 1488 |
progress_bar = st.progress(0)
|
| 1489 |
for i, stock_id in enumerate(stock_ids):
|
|
@@ -1517,7 +1518,7 @@ st.markdown("""
|
|
| 1517 |
time.sleep(0.5) # 為了視覺效果添加的短暫延遲
|
| 1518 |
|
| 1519 |
progress_bar.empty()
|
| 1520 |
-
|
| 1521 |
return stock_data
|
| 1522 |
|
| 1523 |
rs = gain / loss
|
|
@@ -1526,24 +1527,24 @@ st.markdown("""
|
|
| 1526 |
|
| 1527 |
# 累積回報率
|
| 1528 |
cumulative_return = (data.iloc[-1] / data.iloc[0] - 1) * 100
|
| 1529 |
-
|
| 1530 |
# 年化回報率 (假設252個交易日)
|
| 1531 |
days = len(data)
|
| 1532 |
annualized_return = ((1 + cumulative_return / 100) ** (252 / days) - 1) * 100
|
| 1533 |
-
|
| 1534 |
# 波動率 (年化標準差)
|
| 1535 |
volatility = daily_returns.std() * np.sqrt(252) * 100
|
| 1536 |
-
|
| 1537 |
# 夏普比率 (假設無風險利率為2%)
|
| 1538 |
risk_free_rate = 0.02
|
| 1539 |
sharpe_ratio = (annualized_return / 100 - risk_free_rate) / (volatility / 100)
|
| 1540 |
-
|
| 1541 |
# 最大回撤
|
| 1542 |
cum_returns = (1 + daily_returns).cumprod()
|
| 1543 |
running_max = cum_returns.cummax()
|
| 1544 |
drawdown = (cum_returns / running_max - 1) * 100
|
| 1545 |
max_drawdown = drawdown.min()
|
| 1546 |
-
|
| 1547 |
return {
|
| 1548 |
'cumulative_return': cumulative_return,
|
| 1549 |
'annualized_return': annualized_return,
|
|
@@ -1554,7 +1555,7 @@ st.markdown("""
|
|
| 1554 |
|
| 1555 |
# 創建列來顯示指標
|
| 1556 |
cols = st.columns(len(stock_data))
|
| 1557 |
-
|
| 1558 |
for i, (stock_id, data) in enumerate(stock_data.items()):
|
| 1559 |
hist = data['history']
|
| 1560 |
if hist.empty:
|
|
@@ -1606,13 +1607,13 @@ st.markdown("""
|
|
| 1606 |
vertical_spacing=0.1,
|
| 1607 |
subplot_titles=("股價走勢比較", "正規化股價比較 (基準=100)"),
|
| 1608 |
row_heights=[0.6, 0.4])
|
| 1609 |
-
|
| 1610 |
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd']
|
| 1611 |
color_idx = 0
|
| 1612 |
-
|
| 1613 |
# 用於正規化的數據
|
| 1614 |
normalized_data = {}
|
| 1615 |
-
|
| 1616 |
# 添加每個股票的數據到圖表
|
| 1617 |
for stock_id, data in stock_data.items():
|
| 1618 |
hist = data['history']
|
|
@@ -1654,7 +1655,7 @@ st.markdown("""
|
|
| 1654 |
),
|
| 1655 |
row=2, col=1
|
| 1656 |
)
|
| 1657 |
-
|
| 1658 |
# 更新布局
|
| 1659 |
fig.update_layout(
|
| 1660 |
height=700,
|
|
@@ -1670,24 +1671,24 @@ st.markdown("""
|
|
| 1670 |
hovermode="x unified",
|
| 1671 |
plot_bgcolor='rgba(240,242,246,0.8)',
|
| 1672 |
)
|
| 1673 |
-
|
| 1674 |
# 更新Y軸標題
|
| 1675 |
fig.update_yaxes(title_text="價格 (TWD)", row=1, col=1, gridcolor='rgba(220,220,220,0.5)')
|
| 1676 |
fig.update_yaxes(title_text="正規化價格 (基準=100)", row=2, col=1, gridcolor='rgba(220,220,220,0.5)')
|
| 1677 |
fig.update_xaxes(gridcolor='rgba(220,220,220,0.5)')
|
| 1678 |
-
|
| 1679 |
# 顯示圖表
|
| 1680 |
st.plotly_chart(fig, use_container_width=True)
|
| 1681 |
|
| 1682 |
# 創建圖表
|
| 1683 |
fig = go.Figure()
|
| 1684 |
-
|
| 1685 |
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd']
|
| 1686 |
color_idx = 0
|
| 1687 |
-
|
| 1688 |
# 添加每個股票的成交量到圖表
|
| 1689 |
has_volume_data = False
|
| 1690 |
-
|
| 1691 |
for stock_id, data in stock_data.items():
|
| 1692 |
hist = data['history']
|
| 1693 |
# 確保數據不為空且有成交量數據
|
|
@@ -1713,11 +1714,11 @@ st.markdown("""
|
|
| 1713 |
opacity=0.7
|
| 1714 |
)
|
| 1715 |
)
|
| 1716 |
-
|
| 1717 |
if not has_volume_data:
|
| 1718 |
st.warning("沒有可用的成交量數據")
|
| 1719 |
return
|
| 1720 |
-
|
| 1721 |
# 更新布局
|
| 1722 |
fig.update_layout(
|
| 1723 |
title="股票成交量比較",
|
|
@@ -1736,10 +1737,10 @@ st.markdown("""
|
|
| 1736 |
margin=dict(l=40, r=40, t=60, b=40),
|
| 1737 |
plot_bgcolor='rgba(240,242,246,0.8)',
|
| 1738 |
)
|
| 1739 |
-
|
| 1740 |
fig.update_yaxes(title_text="成交量", gridcolor='rgba(220,220,220,0.5)')
|
| 1741 |
fig.update_xaxes(gridcolor='rgba(220,220,220,0.5)')
|
| 1742 |
-
|
| 1743 |
# 顯示圖表
|
| 1744 |
st.plotly_chart(fig, use_container_width=True)
|
| 1745 |
|
|
@@ -1747,7 +1748,7 @@ st.markdown("""
|
|
| 1747 |
if hist.empty:
|
| 1748 |
st.warning("所選股票沒有足夠的歷史數據")
|
| 1749 |
return
|
| 1750 |
-
|
| 1751 |
# RSI 指標
|
| 1752 |
if indicator_type == "RSI":
|
| 1753 |
rsi = calculate_rsi(hist['Close'])
|
|
@@ -1809,7 +1810,7 @@ st.markdown("""
|
|
| 1809 |
fig.update_xaxes(gridcolor='rgba(220,220,220,0.5)')
|
| 1810 |
|
| 1811 |
st.plotly_chart(fig, use_container_width=True)
|
| 1812 |
-
|
| 1813 |
# MACD 指標
|
| 1814 |
elif indicator_type == "MACD":
|
| 1815 |
macd, signal, histogram = calculate_macd(hist['Close'])
|
|
@@ -1888,7 +1889,7 @@ st.markdown("""
|
|
| 1888 |
fig.update_xaxes(gridcolor='rgba(220,220,220,0.5)')
|
| 1889 |
|
| 1890 |
st.plotly_chart(fig, use_container_width=True)
|
| 1891 |
-
|
| 1892 |
# 布林帶
|
| 1893 |
elif indicator_type == "布林帶":
|
| 1894 |
ma, upper, lower = calculate_bollinger_bands(hist['Close'])
|
|
@@ -1972,7 +1973,7 @@ st.markdown("""
|
|
| 1972 |
fig.update_xaxes(gridcolor='rgba(220,220,220,0.5)')
|
| 1973 |
|
| 1974 |
st.plotly_chart(fig, use_container_width=True)
|
| 1975 |
-
|
| 1976 |
# 移動��均線
|
| 1977 |
elif indicator_type == "移動平均線":
|
| 1978 |
ma20, ma50, ma100, ma200 = calculate_moving_averages(hist['Close'])
|
|
@@ -2059,17 +2060,17 @@ st.markdown("""
|
|
| 2059 |
if not hist.empty:
|
| 2060 |
metrics = calculate_performance_metrics(hist['Close'])
|
| 2061 |
performance_metrics[stock_id] = metrics
|
| 2062 |
-
|
| 2063 |
if not performance_metrics:
|
| 2064 |
st.warning("沒有足夠的數據來計算績效指標")
|
| 2065 |
return
|
| 2066 |
-
|
| 2067 |
# 創建圖表數據
|
| 2068 |
metrics_df = pd.DataFrame(performance_metrics).T
|
| 2069 |
-
|
| 2070 |
# 創建多圖表比較
|
| 2071 |
cols = st.columns(2)
|
| 2072 |
-
|
| 2073 |
# 1. 累積回報率比較
|
| 2074 |
with cols[0]:
|
| 2075 |
fig_return = px.bar(
|
|
@@ -2098,7 +2099,7 @@ st.markdown("""
|
|
| 2098 |
)
|
| 2099 |
|
| 2100 |
st.plotly_chart(fig_return, use_container_width=True)
|
| 2101 |
-
|
| 2102 |
# 2. 波動率比較
|
| 2103 |
with cols[1]:
|
| 2104 |
fig_vol = px.bar(
|
|
@@ -2127,9 +2128,9 @@ st.markdown("""
|
|
| 2127 |
)
|
| 2128 |
|
| 2129 |
st.plotly_chart(fig_vol, use_container_width=True)
|
| 2130 |
-
|
| 2131 |
cols = st.columns(2)
|
| 2132 |
-
|
| 2133 |
# 3. 夏普比率比較
|
| 2134 |
with cols[0]:
|
| 2135 |
sharpe_colors = ['#d62728' if s < 0 else '#2ca02c' for s in metrics_df['sharpe_ratio']]
|
|
@@ -2159,7 +2160,7 @@ st.markdown("""
|
|
| 2159 |
)
|
| 2160 |
|
| 2161 |
st.plotly_chart(fig_sharpe, use_container_width=True)
|
| 2162 |
-
|
| 2163 |
# 4. 最大回撤比較
|
| 2164 |
with cols[1]:
|
| 2165 |
fig_drawdown = px.bar(
|
|
@@ -2188,10 +2189,10 @@ st.markdown("""
|
|
| 2188 |
)
|
| 2189 |
|
| 2190 |
st.plotly_chart(fig_drawdown, use_container_width=True)
|
| 2191 |
-
|
| 2192 |
# 5. 績效指標比較表格
|
| 2193 |
st.markdown("### 績效指標詳細比較")
|
| 2194 |
-
|
| 2195 |
# 準備顯示的數據
|
| 2196 |
display_df = pd.DataFrame({
|
| 2197 |
'累積報酬率 (%)': metrics_df['cumulative_return'].round(2),
|
|
@@ -2200,7 +2201,7 @@ st.markdown("""
|
|
| 2200 |
'夏普比率': metrics_df['sharpe_ratio'].round(2),
|
| 2201 |
'最大回撤 (%)': metrics_df['max_drawdown'].round(2)
|
| 2202 |
})
|
| 2203 |
-
|
| 2204 |
# 表格高亮設定
|
| 2205 |
st.dataframe(
|
| 2206 |
display_df,
|
|
|
|
| 1473 |
""", unsafe_allow_html=True)
|
| 1474 |
|
| 1475 |
# ------ 功能函數 ------ #
|
| 1476 |
+
def get_stock_data(stock_ids, period="1y", interval="1d"):
|
| 1477 |
+
'''
|
| 1478 |
參數:
|
| 1479 |
stock_ids (list): 股票代碼列表
|
| 1480 |
period (str): 時間週期
|
|
|
|
| 1482 |
|
| 1483 |
返回:
|
| 1484 |
dict: 股票數據字典
|
| 1485 |
+
'''
|
| 1486 |
stock_data = {}
|
| 1487 |
+
|
| 1488 |
with st.spinner("正在獲取股票數據..."):
|
| 1489 |
progress_bar = st.progress(0)
|
| 1490 |
for i, stock_id in enumerate(stock_ids):
|
|
|
|
| 1518 |
time.sleep(0.5) # 為了視覺效果添加的短暫延遲
|
| 1519 |
|
| 1520 |
progress_bar.empty()
|
| 1521 |
+
|
| 1522 |
return stock_data
|
| 1523 |
|
| 1524 |
rs = gain / loss
|
|
|
|
| 1527 |
|
| 1528 |
# 累積回報率
|
| 1529 |
cumulative_return = (data.iloc[-1] / data.iloc[0] - 1) * 100
|
| 1530 |
+
|
| 1531 |
# 年化回報率 (假設252個交易日)
|
| 1532 |
days = len(data)
|
| 1533 |
annualized_return = ((1 + cumulative_return / 100) ** (252 / days) - 1) * 100
|
| 1534 |
+
|
| 1535 |
# 波動率 (年化標準差)
|
| 1536 |
volatility = daily_returns.std() * np.sqrt(252) * 100
|
| 1537 |
+
|
| 1538 |
# 夏普比率 (假設無風險利率為2%)
|
| 1539 |
risk_free_rate = 0.02
|
| 1540 |
sharpe_ratio = (annualized_return / 100 - risk_free_rate) / (volatility / 100)
|
| 1541 |
+
|
| 1542 |
# 最大回撤
|
| 1543 |
cum_returns = (1 + daily_returns).cumprod()
|
| 1544 |
running_max = cum_returns.cummax()
|
| 1545 |
drawdown = (cum_returns / running_max - 1) * 100
|
| 1546 |
max_drawdown = drawdown.min()
|
| 1547 |
+
|
| 1548 |
return {
|
| 1549 |
'cumulative_return': cumulative_return,
|
| 1550 |
'annualized_return': annualized_return,
|
|
|
|
| 1555 |
|
| 1556 |
# 創建列來顯示指標
|
| 1557 |
cols = st.columns(len(stock_data))
|
| 1558 |
+
|
| 1559 |
for i, (stock_id, data) in enumerate(stock_data.items()):
|
| 1560 |
hist = data['history']
|
| 1561 |
if hist.empty:
|
|
|
|
| 1607 |
vertical_spacing=0.1,
|
| 1608 |
subplot_titles=("股價走勢比較", "正規化股價比較 (基準=100)"),
|
| 1609 |
row_heights=[0.6, 0.4])
|
| 1610 |
+
|
| 1611 |
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd']
|
| 1612 |
color_idx = 0
|
| 1613 |
+
|
| 1614 |
# 用於正規化的數據
|
| 1615 |
normalized_data = {}
|
| 1616 |
+
|
| 1617 |
# 添加每個股票的數據到圖表
|
| 1618 |
for stock_id, data in stock_data.items():
|
| 1619 |
hist = data['history']
|
|
|
|
| 1655 |
),
|
| 1656 |
row=2, col=1
|
| 1657 |
)
|
| 1658 |
+
|
| 1659 |
# 更新布局
|
| 1660 |
fig.update_layout(
|
| 1661 |
height=700,
|
|
|
|
| 1671 |
hovermode="x unified",
|
| 1672 |
plot_bgcolor='rgba(240,242,246,0.8)',
|
| 1673 |
)
|
| 1674 |
+
|
| 1675 |
# 更新Y軸標題
|
| 1676 |
fig.update_yaxes(title_text="價格 (TWD)", row=1, col=1, gridcolor='rgba(220,220,220,0.5)')
|
| 1677 |
fig.update_yaxes(title_text="正規化價格 (基準=100)", row=2, col=1, gridcolor='rgba(220,220,220,0.5)')
|
| 1678 |
fig.update_xaxes(gridcolor='rgba(220,220,220,0.5)')
|
| 1679 |
+
|
| 1680 |
# 顯示圖表
|
| 1681 |
st.plotly_chart(fig, use_container_width=True)
|
| 1682 |
|
| 1683 |
# 創建圖表
|
| 1684 |
fig = go.Figure()
|
| 1685 |
+
|
| 1686 |
colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd']
|
| 1687 |
color_idx = 0
|
| 1688 |
+
|
| 1689 |
# 添加每個股票的成交量到圖表
|
| 1690 |
has_volume_data = False
|
| 1691 |
+
|
| 1692 |
for stock_id, data in stock_data.items():
|
| 1693 |
hist = data['history']
|
| 1694 |
# 確保數據不為空且有成交量數據
|
|
|
|
| 1714 |
opacity=0.7
|
| 1715 |
)
|
| 1716 |
)
|
| 1717 |
+
|
| 1718 |
if not has_volume_data:
|
| 1719 |
st.warning("沒有可用的成交量數據")
|
| 1720 |
return
|
| 1721 |
+
|
| 1722 |
# 更新布局
|
| 1723 |
fig.update_layout(
|
| 1724 |
title="股票成交量比較",
|
|
|
|
| 1737 |
margin=dict(l=40, r=40, t=60, b=40),
|
| 1738 |
plot_bgcolor='rgba(240,242,246,0.8)',
|
| 1739 |
)
|
| 1740 |
+
|
| 1741 |
fig.update_yaxes(title_text="成交量", gridcolor='rgba(220,220,220,0.5)')
|
| 1742 |
fig.update_xaxes(gridcolor='rgba(220,220,220,0.5)')
|
| 1743 |
+
|
| 1744 |
# 顯示圖表
|
| 1745 |
st.plotly_chart(fig, use_container_width=True)
|
| 1746 |
|
|
|
|
| 1748 |
if hist.empty:
|
| 1749 |
st.warning("所選股票沒有足夠的歷史數據")
|
| 1750 |
return
|
| 1751 |
+
|
| 1752 |
# RSI 指標
|
| 1753 |
if indicator_type == "RSI":
|
| 1754 |
rsi = calculate_rsi(hist['Close'])
|
|
|
|
| 1810 |
fig.update_xaxes(gridcolor='rgba(220,220,220,0.5)')
|
| 1811 |
|
| 1812 |
st.plotly_chart(fig, use_container_width=True)
|
| 1813 |
+
|
| 1814 |
# MACD 指標
|
| 1815 |
elif indicator_type == "MACD":
|
| 1816 |
macd, signal, histogram = calculate_macd(hist['Close'])
|
|
|
|
| 1889 |
fig.update_xaxes(gridcolor='rgba(220,220,220,0.5)')
|
| 1890 |
|
| 1891 |
st.plotly_chart(fig, use_container_width=True)
|
| 1892 |
+
|
| 1893 |
# 布林帶
|
| 1894 |
elif indicator_type == "布林帶":
|
| 1895 |
ma, upper, lower = calculate_bollinger_bands(hist['Close'])
|
|
|
|
| 1973 |
fig.update_xaxes(gridcolor='rgba(220,220,220,0.5)')
|
| 1974 |
|
| 1975 |
st.plotly_chart(fig, use_container_width=True)
|
| 1976 |
+
|
| 1977 |
# 移動��均線
|
| 1978 |
elif indicator_type == "移動平均線":
|
| 1979 |
ma20, ma50, ma100, ma200 = calculate_moving_averages(hist['Close'])
|
|
|
|
| 2060 |
if not hist.empty:
|
| 2061 |
metrics = calculate_performance_metrics(hist['Close'])
|
| 2062 |
performance_metrics[stock_id] = metrics
|
| 2063 |
+
|
| 2064 |
if not performance_metrics:
|
| 2065 |
st.warning("沒有足夠的數據來計算績效指標")
|
| 2066 |
return
|
| 2067 |
+
|
| 2068 |
# 創建圖表數據
|
| 2069 |
metrics_df = pd.DataFrame(performance_metrics).T
|
| 2070 |
+
|
| 2071 |
# 創建多圖表比較
|
| 2072 |
cols = st.columns(2)
|
| 2073 |
+
|
| 2074 |
# 1. 累積回報率比較
|
| 2075 |
with cols[0]:
|
| 2076 |
fig_return = px.bar(
|
|
|
|
| 2099 |
)
|
| 2100 |
|
| 2101 |
st.plotly_chart(fig_return, use_container_width=True)
|
| 2102 |
+
|
| 2103 |
# 2. 波動率比較
|
| 2104 |
with cols[1]:
|
| 2105 |
fig_vol = px.bar(
|
|
|
|
| 2128 |
)
|
| 2129 |
|
| 2130 |
st.plotly_chart(fig_vol, use_container_width=True)
|
| 2131 |
+
|
| 2132 |
cols = st.columns(2)
|
| 2133 |
+
|
| 2134 |
# 3. 夏普比率比較
|
| 2135 |
with cols[0]:
|
| 2136 |
sharpe_colors = ['#d62728' if s < 0 else '#2ca02c' for s in metrics_df['sharpe_ratio']]
|
|
|
|
| 2160 |
)
|
| 2161 |
|
| 2162 |
st.plotly_chart(fig_sharpe, use_container_width=True)
|
| 2163 |
+
|
| 2164 |
# 4. 最大回撤比較
|
| 2165 |
with cols[1]:
|
| 2166 |
fig_drawdown = px.bar(
|
|
|
|
| 2189 |
)
|
| 2190 |
|
| 2191 |
st.plotly_chart(fig_drawdown, use_container_width=True)
|
| 2192 |
+
|
| 2193 |
# 5. 績效指標比較表格
|
| 2194 |
st.markdown("### 績效指標詳細比較")
|
| 2195 |
+
|
| 2196 |
# 準備顯示的數據
|
| 2197 |
display_df = pd.DataFrame({
|
| 2198 |
'累積報酬率 (%)': metrics_df['cumulative_return'].round(2),
|
|
|
|
| 2201 |
'夏普比率': metrics_df['sharpe_ratio'].round(2),
|
| 2202 |
'最大回撤 (%)': metrics_df['max_drawdown'].round(2)
|
| 2203 |
})
|
| 2204 |
+
|
| 2205 |
# 表格高亮設定
|
| 2206 |
st.dataframe(
|
| 2207 |
display_df,
|