Update app.py
Browse files
app.py
CHANGED
|
@@ -16,6 +16,7 @@ from plotly.subplots import make_subplots
|
|
| 16 |
|
| 17 |
# 台股代號對應表 (移除台指期,因為它現在是獨立區塊)
|
| 18 |
TAIWAN_STOCKS = {
|
|
|
|
| 19 |
'台積電': '2330.TW',
|
| 20 |
'聯發科': '2454.TW',
|
| 21 |
'鴻海': '2317.TW',
|
|
@@ -25,20 +26,20 @@ TAIWAN_STOCKS = {
|
|
| 25 |
'國泰金': '2882.TW',
|
| 26 |
'台達電': '2308.TW',
|
| 27 |
'統一': '1216.TW',
|
| 28 |
-
'日月光': '
|
| 29 |
-
'長榮': '
|
| 30 |
'慧洋-KY': '2637.TW',
|
| 31 |
'上銀': '2049.TW',
|
| 32 |
'台泥': '1101.TW',
|
| 33 |
'譜瑞-KY': '4966.TWO',
|
| 34 |
'貿聯-KY': '3665.TW',
|
| 35 |
-
'南電': '8046.TW',
|
| 36 |
'騰雲': '6870.TWO',
|
| 37 |
'穩懋': '3105.TWO'
|
| 38 |
}
|
| 39 |
|
| 40 |
# 產業分類
|
| 41 |
INDUSTRY_MAPPING = {
|
|
|
|
| 42 |
'2330.TW': '半導體',
|
| 43 |
'2454.TW': '半導體',
|
| 44 |
'2317.TW': '電子組件',
|
|
@@ -48,14 +49,13 @@ INDUSTRY_MAPPING = {
|
|
| 48 |
'2882.TW': '金融',
|
| 49 |
'2308.TW': '電子',
|
| 50 |
'1216.TW': '食品',
|
| 51 |
-
'
|
| 52 |
-
'
|
| 53 |
'2637.TW': '散裝航運',
|
| 54 |
'2049.TW': '工具機',
|
| 55 |
'1101.TW': '營建',
|
| 56 |
'4966.TWO': '高速傳輸',
|
| 57 |
'3665.TW': '連接器',
|
| 58 |
-
'8046.TW': 'ABF載板',
|
| 59 |
'6870.TWO': '軟體整合',
|
| 60 |
'3105.TWO': 'PA功率'
|
| 61 |
}
|
|
@@ -293,7 +293,7 @@ app.layout = html.Div([
|
|
| 293 |
# 台指期獨立預測區塊 - 置於頂部
|
| 294 |
html.Div([
|
| 295 |
html.H2("🤖 AI深度學習預測 - 台指期指數", style={
|
| 296 |
-
'text-align': 'center',
|
| 297 |
'color': '#FFCC22',
|
| 298 |
'margin-bottom': '25px'
|
| 299 |
}),
|
|
@@ -383,7 +383,7 @@ app.layout = html.Div([
|
|
| 383 |
])
|
| 384 |
], style={'width': '100%', 'display': 'inline-block', 'vertical-align': 'top'}),
|
| 385 |
]),
|
| 386 |
-
|
| 387 |
# 技術指標選擇區域
|
| 388 |
html.Div([
|
| 389 |
html.H3("📊 進階技術指標分析", style={'margin-bottom': '20px'}),
|
|
@@ -494,7 +494,9 @@ app.layout = html.Div([
|
|
| 494 |
])
|
| 495 |
], style={'margin-top': '30px'}),
|
| 496 |
|
| 497 |
-
#
|
|
|
|
|
|
|
| 498 |
html.Div([
|
| 499 |
html.H3("📊 多檔股票比較分析", style={'margin-bottom': '20px'}),
|
| 500 |
html.Div([
|
|
@@ -503,9 +505,14 @@ app.layout = html.Div([
|
|
| 503 |
dcc.Dropdown(
|
| 504 |
id='comparison-stocks',
|
| 505 |
options=[{'label': name, 'value': symbol} for name, symbol in TAIWAN_STOCKS.items()],
|
| 506 |
-
value=['
|
| 507 |
multi=True,
|
| 508 |
-
style={'margin-bottom': '
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 509 |
)
|
| 510 |
], style={'width': '60%', 'display': 'inline-block'}),
|
| 511 |
|
|
@@ -521,7 +528,7 @@ app.layout = html.Div([
|
|
| 521 |
],
|
| 522 |
value='3mo'
|
| 523 |
)
|
| 524 |
-
], style={'width': '35%', 'display': 'inline-block', 'margin-left': '5%'})
|
| 525 |
]),
|
| 526 |
|
| 527 |
html.Div([
|
|
@@ -774,14 +781,14 @@ def update_price_chart(selected_stock, period, chart_type):
|
|
| 774 |
|
| 775 |
# 添加移動平均線到左側子圖
|
| 776 |
fig.add_trace(go.Scatter(
|
| 777 |
-
x=data.index, y=data['MA5'], mode='lines',
|
| 778 |
name='MA5', line=dict(color='orange')
|
| 779 |
), row=1, col=1)
|
| 780 |
fig.add_trace(go.Scatter(
|
| 781 |
-
x=data.index, y=data['MA20'], mode='lines',
|
| 782 |
name='MA20', line=dict(color='blue')
|
| 783 |
), row=1, col=1)
|
| 784 |
-
|
| 785 |
# --- 3. 在右側子圖 (col=2) 繪製成交量分佈圖 ---
|
| 786 |
# 計算 Volume Profile 數據
|
| 787 |
bin_edges, volume_per_bin, price_centers = calculate_volume_profile(data, num_bins=50)
|
|
@@ -806,7 +813,7 @@ def update_price_chart(selected_stock, period, chart_type):
|
|
| 806 |
title_text=f'{stock_name} 股價走勢與成交量分佈',
|
| 807 |
height=500,
|
| 808 |
showlegend=True,
|
| 809 |
-
|
| 810 |
# 左側子圖的座標軸設定
|
| 811 |
xaxis1=dict(
|
| 812 |
title='日期',
|
|
@@ -825,7 +832,7 @@ def update_price_chart(selected_stock, period, chart_type):
|
|
| 825 |
yaxis2=dict(
|
| 826 |
showticklabels=False # 因為共享Y軸,所以隱藏右側的Y軸標籤
|
| 827 |
),
|
| 828 |
-
|
| 829 |
bargap=0.05 # 長條圖間的間隙
|
| 830 |
)
|
| 831 |
|
|
@@ -854,7 +861,7 @@ def update_advanced_technical_chart(indicator, selected_stock, period):
|
|
| 854 |
fig.add_hline(y=70, line_dash="dash", line_color="green", annotation_text="超買線(70)")
|
| 855 |
fig.add_hline(y=30, line_dash="dash", line_color="red", annotation_text="超賣線(30)")
|
| 856 |
fig.add_hline(y=50, line_dash="dot", line_color="gray", annotation_text="中線(50)")
|
| 857 |
-
|
| 858 |
# 根據台股慣例修改顏色
|
| 859 |
fig.add_hrect(y0=70, y1=100, fillcolor="green", opacity=0.1)
|
| 860 |
fig.add_hrect(y0=0, y1=30, fillcolor="red", opacity=0.1)
|
|
@@ -876,9 +883,9 @@ def update_advanced_technical_chart(indicator, selected_stock, period):
|
|
| 876 |
|
| 877 |
# --- 上方子圖 (row=1):只繪製價格走勢 ---
|
| 878 |
fig.add_trace(go.Scatter(
|
| 879 |
-
x=data.index,
|
| 880 |
-
y=data['Close'],
|
| 881 |
-
mode='lines',
|
| 882 |
name='收盤價',
|
| 883 |
line=dict(color='black', width=1.5)
|
| 884 |
), row=1, col=1)
|
|
@@ -886,28 +893,28 @@ def update_advanced_technical_chart(indicator, selected_stock, period):
|
|
| 886 |
# --- 下方子圖 (row=2):繪製所有MACD相關指標 ---
|
| 887 |
# 1. MACD 快線 (DIF)
|
| 888 |
fig.add_trace(go.Scatter(
|
| 889 |
-
x=data.index,
|
| 890 |
-
y=data['MACD'],
|
| 891 |
-
mode='lines',
|
| 892 |
name='MACD (快線)',
|
| 893 |
line=dict(color='blue', width=2)
|
| 894 |
), row=2, col=1)
|
| 895 |
-
|
| 896 |
# 2. Signal 慢線 (MACD)
|
| 897 |
fig.add_trace(go.Scatter(
|
| 898 |
-
x=data.index,
|
| 899 |
-
y=data['MACD_Signal'],
|
| 900 |
-
mode='lines',
|
| 901 |
name='Signal (慢線)',
|
| 902 |
line=dict(color='red', width=2)
|
| 903 |
), row=2, col=1)
|
| 904 |
-
|
| 905 |
# 3. Histogram 柱狀圖
|
| 906 |
# 根據台股慣例修改顏色
|
| 907 |
colors = ['red' if x >= 0 else 'green' for x in data['MACD_Histogram']]
|
| 908 |
fig.add_trace(go.Bar(
|
| 909 |
-
x=data.index,
|
| 910 |
-
y=data['MACD_Histogram'],
|
| 911 |
name='MACD柱狀圖',
|
| 912 |
marker_color=colors
|
| 913 |
), row=2, col=1)
|
|
@@ -1039,7 +1046,7 @@ def update_volume_chart(selected_stock, period):
|
|
| 1039 |
|
| 1040 |
# 根據漲跌決定顏色 (台股慣例)
|
| 1041 |
colors = ['red' if data['Close'].iloc[i] > data['Open'].iloc[i] else 'green' for i in range(len(data))]
|
| 1042 |
-
|
| 1043 |
fig = go.Figure()
|
| 1044 |
fig.add_trace(go.Bar(
|
| 1045 |
x=data.index,
|
|
@@ -1383,7 +1390,10 @@ def update_pmi_chart(selected_stock):
|
|
| 1383 |
|
| 1384 |
return fig
|
| 1385 |
|
| 1386 |
-
|
|
|
|
|
|
|
|
|
|
| 1387 |
@app.callback(
|
| 1388 |
[dash.dependencies.Output('comparison-chart', 'figure'),
|
| 1389 |
dash.dependencies.Output('comparison-table', 'children')],
|
|
@@ -1391,6 +1401,16 @@ def update_pmi_chart(selected_stock):
|
|
| 1391 |
dash.dependencies.Input('comparison-period', 'value')]
|
| 1392 |
)
|
| 1393 |
def update_comparison_analysis(selected_stocks, period):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1394 |
if not selected_stocks:
|
| 1395 |
return {}, html.Div("請選擇要比較的股票")
|
| 1396 |
|
|
@@ -1403,7 +1423,8 @@ def update_comparison_analysis(selected_stocks, period):
|
|
| 1403 |
for stock in selected_stocks:
|
| 1404 |
data = get_stock_data(stock, period)
|
| 1405 |
if not data.empty:
|
| 1406 |
-
|
|
|
|
| 1407 |
|
| 1408 |
# 正規化價格(以期初為基準100)
|
| 1409 |
normalized_prices = (data['Close'] / data['Close'].iloc[0]) * 100
|
|
|
|
| 16 |
|
| 17 |
# 台股代號對應表 (移除台指期,因為它現在是獨立區塊)
|
| 18 |
TAIWAN_STOCKS = {
|
| 19 |
+
'元大台灣50': '0050.TW', # 新增
|
| 20 |
'台積電': '2330.TW',
|
| 21 |
'聯發科': '2454.TW',
|
| 22 |
'鴻海': '2317.TW',
|
|
|
|
| 26 |
'國泰金': '2882.TW',
|
| 27 |
'台達電': '2308.TW',
|
| 28 |
'統一': '1216.TW',
|
| 29 |
+
'日月光': '2311.TW',
|
| 30 |
+
'長榮': '2306.TW',
|
| 31 |
'慧洋-KY': '2637.TW',
|
| 32 |
'上銀': '2049.TW',
|
| 33 |
'台泥': '1101.TW',
|
| 34 |
'譜瑞-KY': '4966.TWO',
|
| 35 |
'貿聯-KY': '3665.TW',
|
|
|
|
| 36 |
'騰雲': '6870.TWO',
|
| 37 |
'穩懋': '3105.TWO'
|
| 38 |
}
|
| 39 |
|
| 40 |
# 產業分類
|
| 41 |
INDUSTRY_MAPPING = {
|
| 42 |
+
'0050.TW': 'ETF', # 新增
|
| 43 |
'2330.TW': '半導體',
|
| 44 |
'2454.TW': '半導體',
|
| 45 |
'2317.TW': '電子組件',
|
|
|
|
| 49 |
'2882.TW': '金融',
|
| 50 |
'2308.TW': '電子',
|
| 51 |
'1216.TW': '食品',
|
| 52 |
+
'2311.TW': '半導體',
|
| 53 |
+
'2306.TW': '航運',
|
| 54 |
'2637.TW': '散裝航運',
|
| 55 |
'2049.TW': '工具機',
|
| 56 |
'1101.TW': '營建',
|
| 57 |
'4966.TWO': '高速傳輸',
|
| 58 |
'3665.TW': '連接器',
|
|
|
|
| 59 |
'6870.TWO': '軟體整合',
|
| 60 |
'3105.TWO': 'PA功率'
|
| 61 |
}
|
|
|
|
| 293 |
# 台指期獨立預測區塊 - 置於頂部
|
| 294 |
html.Div([
|
| 295 |
html.H2("🤖 AI深度學習預測 - 台指期指數", style={
|
| 296 |
+
'text-align': 'center',
|
| 297 |
'color': '#FFCC22',
|
| 298 |
'margin-bottom': '25px'
|
| 299 |
}),
|
|
|
|
| 383 |
])
|
| 384 |
], style={'width': '100%', 'display': 'inline-block', 'vertical-align': 'top'}),
|
| 385 |
]),
|
| 386 |
+
|
| 387 |
# 技術指標選擇區域
|
| 388 |
html.Div([
|
| 389 |
html.H3("📊 進階技術指標分析", style={'margin-bottom': '20px'}),
|
|
|
|
| 494 |
])
|
| 495 |
], style={'margin-top': '30px'}),
|
| 496 |
|
| 497 |
+
# ==============================================================================
|
| 498 |
+
# ===== 修改後的多檔股票比較區域 =====
|
| 499 |
+
# ==============================================================================
|
| 500 |
html.Div([
|
| 501 |
html.H3("📊 多檔股票比較分析", style={'margin-bottom': '20px'}),
|
| 502 |
html.Div([
|
|
|
|
| 505 |
dcc.Dropdown(
|
| 506 |
id='comparison-stocks',
|
| 507 |
options=[{'label': name, 'value': symbol} for name, symbol in TAIWAN_STOCKS.items()],
|
| 508 |
+
value=['0050.TW', '2330.TW', '2454.TW'], # 修改:預設包含0050
|
| 509 |
multi=True,
|
| 510 |
+
style={'margin-bottom': '5px'} # 調整間距
|
| 511 |
+
),
|
| 512 |
+
# 新增:提示文字
|
| 513 |
+
html.Small(
|
| 514 |
+
'(元大台灣50 (0050.TW) 為固定比較基準,不可移除)',
|
| 515 |
+
style={'display': 'block', 'font-style': 'italic', 'color': 'gray'}
|
| 516 |
)
|
| 517 |
], style={'width': '60%', 'display': 'inline-block'}),
|
| 518 |
|
|
|
|
| 528 |
],
|
| 529 |
value='3mo'
|
| 530 |
)
|
| 531 |
+
], style={'width': '35%', 'display': 'inline-block', 'margin-left': '5%', 'vertical-align': 'top'})
|
| 532 |
]),
|
| 533 |
|
| 534 |
html.Div([
|
|
|
|
| 781 |
|
| 782 |
# 添加移動平均線到左側子圖
|
| 783 |
fig.add_trace(go.Scatter(
|
| 784 |
+
x=data.index, y=data['MA5'], mode='lines',
|
| 785 |
name='MA5', line=dict(color='orange')
|
| 786 |
), row=1, col=1)
|
| 787 |
fig.add_trace(go.Scatter(
|
| 788 |
+
x=data.index, y=data['MA20'], mode='lines',
|
| 789 |
name='MA20', line=dict(color='blue')
|
| 790 |
), row=1, col=1)
|
| 791 |
+
|
| 792 |
# --- 3. 在右側子圖 (col=2) 繪製成交量分佈圖 ---
|
| 793 |
# 計算 Volume Profile 數據
|
| 794 |
bin_edges, volume_per_bin, price_centers = calculate_volume_profile(data, num_bins=50)
|
|
|
|
| 813 |
title_text=f'{stock_name} 股價走勢與成交量分佈',
|
| 814 |
height=500,
|
| 815 |
showlegend=True,
|
| 816 |
+
|
| 817 |
# 左側子圖的座標軸設定
|
| 818 |
xaxis1=dict(
|
| 819 |
title='日期',
|
|
|
|
| 832 |
yaxis2=dict(
|
| 833 |
showticklabels=False # 因為共享Y軸,所以隱藏右側的Y軸標籤
|
| 834 |
),
|
| 835 |
+
|
| 836 |
bargap=0.05 # 長條圖間的間隙
|
| 837 |
)
|
| 838 |
|
|
|
|
| 861 |
fig.add_hline(y=70, line_dash="dash", line_color="green", annotation_text="超買線(70)")
|
| 862 |
fig.add_hline(y=30, line_dash="dash", line_color="red", annotation_text="超賣線(30)")
|
| 863 |
fig.add_hline(y=50, line_dash="dot", line_color="gray", annotation_text="中線(50)")
|
| 864 |
+
|
| 865 |
# 根據台股慣例修改顏色
|
| 866 |
fig.add_hrect(y0=70, y1=100, fillcolor="green", opacity=0.1)
|
| 867 |
fig.add_hrect(y0=0, y1=30, fillcolor="red", opacity=0.1)
|
|
|
|
| 883 |
|
| 884 |
# --- 上方子圖 (row=1):只繪製價格走勢 ---
|
| 885 |
fig.add_trace(go.Scatter(
|
| 886 |
+
x=data.index,
|
| 887 |
+
y=data['Close'],
|
| 888 |
+
mode='lines',
|
| 889 |
name='收盤價',
|
| 890 |
line=dict(color='black', width=1.5)
|
| 891 |
), row=1, col=1)
|
|
|
|
| 893 |
# --- 下方子圖 (row=2):繪製所有MACD相關指標 ---
|
| 894 |
# 1. MACD 快線 (DIF)
|
| 895 |
fig.add_trace(go.Scatter(
|
| 896 |
+
x=data.index,
|
| 897 |
+
y=data['MACD'],
|
| 898 |
+
mode='lines',
|
| 899 |
name='MACD (快線)',
|
| 900 |
line=dict(color='blue', width=2)
|
| 901 |
), row=2, col=1)
|
| 902 |
+
|
| 903 |
# 2. Signal 慢線 (MACD)
|
| 904 |
fig.add_trace(go.Scatter(
|
| 905 |
+
x=data.index,
|
| 906 |
+
y=data['MACD_Signal'],
|
| 907 |
+
mode='lines',
|
| 908 |
name='Signal (慢線)',
|
| 909 |
line=dict(color='red', width=2)
|
| 910 |
), row=2, col=1)
|
| 911 |
+
|
| 912 |
# 3. Histogram 柱狀圖
|
| 913 |
# 根據台股慣例修改顏色
|
| 914 |
colors = ['red' if x >= 0 else 'green' for x in data['MACD_Histogram']]
|
| 915 |
fig.add_trace(go.Bar(
|
| 916 |
+
x=data.index,
|
| 917 |
+
y=data['MACD_Histogram'],
|
| 918 |
name='MACD柱狀圖',
|
| 919 |
marker_color=colors
|
| 920 |
), row=2, col=1)
|
|
|
|
| 1046 |
|
| 1047 |
# 根據漲跌決定顏色 (台股慣例)
|
| 1048 |
colors = ['red' if data['Close'].iloc[i] > data['Open'].iloc[i] else 'green' for i in range(len(data))]
|
| 1049 |
+
|
| 1050 |
fig = go.Figure()
|
| 1051 |
fig.add_trace(go.Bar(
|
| 1052 |
x=data.index,
|
|
|
|
| 1390 |
|
| 1391 |
return fig
|
| 1392 |
|
| 1393 |
+
|
| 1394 |
+
# ==============================================================================
|
| 1395 |
+
# ===== 修改後的多檔股票比較回呼函式 =====
|
| 1396 |
+
# ==============================================================================
|
| 1397 |
@app.callback(
|
| 1398 |
[dash.dependencies.Output('comparison-chart', 'figure'),
|
| 1399 |
dash.dependencies.Output('comparison-table', 'children')],
|
|
|
|
| 1401 |
dash.dependencies.Input('comparison-period', 'value')]
|
| 1402 |
)
|
| 1403 |
def update_comparison_analysis(selected_stocks, period):
|
| 1404 |
+
# --- 新增:確保 0050.TW 始終存在 ---
|
| 1405 |
+
fixed_stock = '0050.TW'
|
| 1406 |
+
# 如果列表為空或 None,則只顯示 0050
|
| 1407 |
+
if not selected_stocks:
|
| 1408 |
+
selected_stocks = [fixed_stock]
|
| 1409 |
+
# 如果 0050 不在列表中,則將其插入到最前面
|
| 1410 |
+
elif fixed_stock not in selected_stocks:
|
| 1411 |
+
selected_stocks.insert(0, fixed_stock)
|
| 1412 |
+
# --- 修改結束 ---
|
| 1413 |
+
|
| 1414 |
if not selected_stocks:
|
| 1415 |
return {}, html.Div("請選擇要比較的股票")
|
| 1416 |
|
|
|
|
| 1423 |
for stock in selected_stocks:
|
| 1424 |
data = get_stock_data(stock, period)
|
| 1425 |
if not data.empty:
|
| 1426 |
+
# 安全地獲取股票名稱,如果找不到則使用代碼本身
|
| 1427 |
+
stock_name = next((name for name, symbol in TAIWAN_STOCKS.items() if symbol == stock), stock)
|
| 1428 |
|
| 1429 |
# 正規化價格(以期初為基準100)
|
| 1430 |
normalized_prices = (data['Close'] / data['Close'].iloc[0]) * 100
|