AlanRex commited on
Commit
d93552a
·
verified ·
1 Parent(s): abe1dbc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +115 -1
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'),