Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -236,6 +236,47 @@ def get_pmi_data():
|
|
| 236 |
print(f"無法獲取 PMI 資料: {str(e)}")
|
| 237 |
return pd.DataFrame()
|
| 238 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
# 建立 Dash 應用程式
|
| 240 |
app = dash.Dash(__name__, suppress_callback_exceptions=True)
|
| 241 |
|
|
@@ -344,7 +385,19 @@ app.layout = html.Div([
|
|
| 344 |
html.Div([
|
| 345 |
html.Div(id='analysis-panel')
|
| 346 |
], style={'width': '33%', 'display': 'inline-block', 'margin-left': '2%', 'vertical-align': 'top'})
|
| 347 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 348 |
|
| 349 |
# 技術指標選擇區域
|
| 350 |
html.Div([
|
|
@@ -1291,6 +1344,67 @@ def update_pmi_chart(selected_stock):
|
|
| 1291 |
|
| 1292 |
return fig
|
| 1293 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1294 |
# 新增:多檔股票比較
|
| 1295 |
@app.callback(
|
| 1296 |
[dash.dependencies.Output('comparison-chart', 'figure'),
|
|
|
|
| 236 |
print(f"無法獲取 PMI 資料: {str(e)}")
|
| 237 |
return pd.DataFrame()
|
| 238 |
|
| 239 |
+
def calculate_volume_profile(df, num_bins=50):
|
| 240 |
+
"""
|
| 241 |
+
計算成交量分佈圖 (Volume Profile) 的數據。
|
| 242 |
+
|
| 243 |
+
Args:
|
| 244 |
+
df (pd.DataFrame): 包含 'High', 'Low', 'Volume' 欄位的 DataFrame。
|
| 245 |
+
num_bins (int): 分割價格區間的數量。
|
| 246 |
+
|
| 247 |
+
Returns:
|
| 248 |
+
tuple: 包含 (bin_edges, volume_per_bin, price_centers) 的 tuple。
|
| 249 |
+
bin_edges: 每個區間的邊界。
|
| 250 |
+
volume_per_bin: 每個區間對應的成交量。
|
| 251 |
+
price_centers: 每個區間的中心價格。
|
| 252 |
+
"""
|
| 253 |
+
if df.empty or 'High' not in df.columns or 'Low' not in df.columns or 'Volume' not in df.columns:
|
| 254 |
+
return None, None, None
|
| 255 |
+
|
| 256 |
+
# 建立一個包含所有高低點的陣列,用於確定價格範圍
|
| 257 |
+
all_prices = np.concatenate([df['High'].values, df['Low'].values])
|
| 258 |
+
min_price = all_prices.min()
|
| 259 |
+
max_price = all_prices.max()
|
| 260 |
+
|
| 261 |
+
price_for_volume = (df['High'] + df['Low'] + df['Close']) / 3
|
| 262 |
+
df_vol_profile = df.copy()
|
| 263 |
+
df_vol_profile['Price_Indicator'] = price_for_volume
|
| 264 |
+
df_vol_profile['Volume'] = df_vol_profile['Volume'] # 確保 Volume 欄位存在
|
| 265 |
+
|
| 266 |
+
# 創建直方圖來計算成交量分佈
|
| 267 |
+
# `density=False` 確保我們得到的是實際的成交量總和,而不是密度
|
| 268 |
+
# `bins=num_bins` 設定價格區間的數量
|
| 269 |
+
# `range` 設定價格的最小值和最大值
|
| 270 |
+
hist, bin_edges = np.histogram(df_vol_profile['Price_Indicator'], bins=num_bins, range=(min_price, max_price), weights=df_vol_profile['Volume'])
|
| 271 |
+
|
| 272 |
+
# 計算每個區間的中心價格
|
| 273 |
+
price_centers = (bin_edges[:-1] + bin_edges[1:]) / 2
|
| 274 |
+
|
| 275 |
+
return bin_edges, hist, price_centers
|
| 276 |
+
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
|
| 280 |
# 建立 Dash 應用程式
|
| 281 |
app = dash.Dash(__name__, suppress_callback_exceptions=True)
|
| 282 |
|
|
|
|
| 385 |
html.Div([
|
| 386 |
html.Div(id='analysis-panel')
|
| 387 |
], style={'width': '33%', 'display': 'inline-block', 'margin-left': '2%', 'vertical-align': 'top'})
|
| 388 |
+
]),
|
| 389 |
+
|
| 390 |
+
# 新增:成交量分佈圖 (Volume Profile)
|
| 391 |
+
html.Div([
|
| 392 |
+
html.H3("📊 成交量分佈圖 (Volume Profile)"),
|
| 393 |
+
dcc.Graph(id='volume-profile-chart')
|
| 394 |
+
], style={
|
| 395 |
+
'margin-top': '30px',
|
| 396 |
+
'padding': '20px',
|
| 397 |
+
'background': 'white',
|
| 398 |
+
'border-radius': '10px',
|
| 399 |
+
'box-shadow': '0 2px 10px rgba(0,0,0,0.1)'
|
| 400 |
+
}),
|
| 401 |
|
| 402 |
# 技術指標選擇區域
|
| 403 |
html.Div([
|
|
|
|
| 1344 |
|
| 1345 |
return fig
|
| 1346 |
|
| 1347 |
+
|
| 1348 |
+
@app.callback(
|
| 1349 |
+
dash.dependencies.Output('volume-profile-chart', 'figure'),
|
| 1350 |
+
[dash.dependencies.Input('stock-dropdown', 'value'),
|
| 1351 |
+
dash.dependencies.Input('period-dropdown', 'value')]
|
| 1352 |
+
)
|
| 1353 |
+
def update_volume_profile_chart(selected_stock, period):
|
| 1354 |
+
data = get_stock_data(selected_stock, period)
|
| 1355 |
+
if data.empty:
|
| 1356 |
+
return {}
|
| 1357 |
+
|
| 1358 |
+
stock_name = [name for name, symbol in TAIWAN_STOCKS.items() if symbol == selected_stock][0]
|
| 1359 |
+
|
| 1360 |
+
# 計算 Volume Profile
|
| 1361 |
+
bin_edges, volume_per_bin, price_centers = calculate_volume_profile(data, num_bins=50) # 您可以調整 num_bins
|
| 1362 |
+
|
| 1363 |
+
if bin_edges is None or volume_per_bin is None:
|
| 1364 |
+
return {}
|
| 1365 |
+
|
| 1366 |
+
# 創建 Volume Profile 圖 (通常是水平長條圖)
|
| 1367 |
+
# 我們將其繪製為一個水平的長條圖,成交量在 X 軸,價格在 Y 軸
|
| 1368 |
+
fig = go.Figure(go.Bar(
|
| 1369 |
+
orientation='h', # 設定為水平長條圖
|
| 1370 |
+
y=price_centers,
|
| 1371 |
+
x=volume_per_bin,
|
| 1372 |
+
name='Volume Profile',
|
| 1373 |
+
marker=dict(
|
| 1374 |
+
color='rgba(173, 216, 230, 0.6)', # 淡藍色
|
| 1375 |
+
line=dict(color='rgba(30, 144, 255, 0.8)', width=1) # 邊框線
|
| 1376 |
+
),
|
| 1377 |
+
# 顯示具體的成交量數字
|
| 1378 |
+
text=[f'{vol:.0f}' for vol in volume_per_bin],
|
| 1379 |
+
textposition='outside', # 將文字顯示在長條圖外面
|
| 1380 |
+
hoverinfo='y+text' # hover 時顯示 Y 軸 (價格) 和 text (成交量)
|
| 1381 |
+
))
|
| 1382 |
+
|
| 1383 |
+
# 獲取最高成交量的價格區間 (Point of Control, POC)
|
| 1384 |
+
if len(volume_per_bin) > 0:
|
| 1385 |
+
poc_volume = np.max(volume_per_bin)
|
| 1386 |
+
poc_index = np.argmax(volume_per_bin)
|
| 1387 |
+
poc_price = price_centers[poc_index]
|
| 1388 |
+
|
| 1389 |
+
# 在 POC 價格線上添加一條垂直線
|
| 1390 |
+
fig.add_vline(x=poc_volume, line_dash="dash", line_color="red",
|
| 1391 |
+
annotation_text=f"POC: ${poc_price:.2f} ({poc_volume:.0f})",
|
| 1392 |
+
annotation_position="top right")
|
| 1393 |
+
|
| 1394 |
+
# 更新圖表佈局
|
| 1395 |
+
fig.update_layout(
|
| 1396 |
+
title=f'{stock_name} 成交量分佈圖 (Volume Profile)',
|
| 1397 |
+
xaxis_title='成交量',
|
| 1398 |
+
yaxis_title='價格 (TWD)',
|
| 1399 |
+
height=450,
|
| 1400 |
+
yaxis=dict(autorange='reversed'), # 讓價格從高到低排列
|
| 1401 |
+
bargap=0, # 讓長條圖緊密排列
|
| 1402 |
+
plot_bgcolor='rgba(0,0,0,0)', # 透明背景
|
| 1403 |
+
hoverlabel=dict(bgcolor="white", font_size=12, font_family="Rockwell")
|
| 1404 |
+
)
|
| 1405 |
+
|
| 1406 |
+
return fig
|
| 1407 |
+
|
| 1408 |
# 新增:多檔股票比較
|
| 1409 |
@app.callback(
|
| 1410 |
[dash.dependencies.Output('comparison-chart', 'figure'),
|