AlanRex commited on
Commit
762f409
·
1 Parent(s): 1297344

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +89 -56
app.py CHANGED
@@ -374,7 +374,7 @@ app.layout = html.Div([
374
 
375
  # 主要圖表區域 - 移除RSI圖表
376
  html.Div([
377
- # 左側:股價走勢圖與成交量分佈圖合併
378
  html.Div([
379
  html.Div([
380
  dcc.Graph(id='price-chart')
@@ -385,8 +385,19 @@ app.layout = html.Div([
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
  # 技術指標選擇區域
391
  html.Div([
392
  html.H3("📊 進階技術指標分析", style={'margin-bottom': '20px'}),
@@ -730,14 +741,14 @@ def update_stock_info(selected_stock):
730
  })
731
  ])
732
 
733
- # 更新股價圖表 (新版:結合成交量分佈圖)
734
  @app.callback(
735
  dash.dependencies.Output('price-chart', 'figure'),
736
  [dash.dependencies.Input('stock-dropdown', 'value'),
737
  dash.dependencies.Input('period-dropdown', 'value'),
738
  dash.dependencies.Input('chart-type', 'value')]
739
  )
740
- def update_combined_chart(selected_stock, period, chart_type):
741
  data = get_stock_data(selected_stock, period)
742
  if data.empty:
743
  return {}
@@ -745,70 +756,29 @@ def update_combined_chart(selected_stock, period, chart_type):
745
  data = calculate_technical_indicators(data)
746
  stock_name = [name for name, symbol in TAIWAN_STOCKS.items() if symbol == selected_stock][0]
747
 
748
- # 創建主圖,帶有雙 Y 軸
749
- fig = make_subplots(specs=[[{"secondary_y": True}]])
750
-
751
- # 1. 價格走勢圖 (主 Y 軸)
752
  if chart_type == 'candlestick':
753
- fig.add_trace(go.Candlestick(
754
  x=data.index,
755
  open=data['Open'],
756
  high=data['High'],
757
  low=data['Low'],
758
  close=data['Close'],
759
  name=stock_name
760
- ), secondary_y=False)
761
  else:
762
- fig.add_trace(go.Scatter(x=data.index, y=data['Close'], mode='lines', name=stock_name), secondary_y=False)
763
 
764
  # 添加移動平均線
765
- fig.add_trace(go.Scatter(x=data.index, y=data['MA5'], mode='lines', name='MA5', line=dict(color='orange')), secondary_y=False)
766
- fig.add_trace(go.Scatter(x=data.index, y=data['MA20'], mode='lines', name='MA20', line=dict(color='blue')), secondary_y=False)
767
-
768
- # 2. 成交量分佈圖 (輔助 Y 軸)
769
- bin_edges, volume_per_bin, price_centers = calculate_volume_profile(data, num_bins=50)
770
-
771
- if bin_edges is not None:
772
- fig.add_trace(go.Bar(
773
- y=price_centers,
774
- x=volume_per_bin,
775
- name='成交量分佈',
776
- orientation='h', # 水平長條圖
777
- marker_color='rgba(173, 216, 230, 0.4)', # 半透明顏色
778
- hoverinfo='y+x'
779
- ), secondary_y=True)
780
-
781
- # 獲取最高成交量的價格區間 (Point of Control, POC)
782
- if len(volume_per_bin) > 0:
783
- poc_volume = np.max(volume_per_bin)
784
- poc_index = np.argmax(volume_per_bin)
785
- poc_price = price_centers[poc_index]
786
-
787
- # 在 POC 價格線上添加一條線
788
- fig.add_hline(y=poc_price, line_dash="dash", line_color="red",
789
- annotation_text=f"POC: ${poc_price:.2f}",
790
- annotation_position="top right")
791
-
792
- # 更新布局
793
  fig.update_layout(
794
- title=f'{stock_name} 股價與成交量分佈',
795
  xaxis_title='日期',
796
  yaxis_title='價格 (TWD)',
797
- height=500,
798
- yaxis2=dict(
799
- title="成交量分佈",
800
- overlaying="y", # 疊加在主 Y 軸上
801
- side="right",
802
- range=[data['Low'].min(), data['High'].max()], # 確保 Y 軸範圍與價格圖一致
803
- showgrid=False
804
- ),
805
- legend=dict(x=0, y=1, traceorder="normal"),
806
- hovermode="x unified"
807
  )
808
-
809
- # 將成交量分佈圖的 y 軸反轉,讓價格從高到低
810
- fig.update_yaxes(autorange='reversed', secondary_y=True)
811
-
812
  return fig
813
 
814
  # 更新RSI圖表(保持兼容性)
@@ -1373,8 +1343,71 @@ def update_pmi_chart(selected_stock):
1373
 
1374
  return fig
1375
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1376
  @app.callback(
1377
- dash.dependencies.Output('comparison-chart', 'figure'),
 
1378
  [dash.dependencies.Input('comparison-stocks', 'value'),
1379
  dash.dependencies.Input('comparison-period', 'value')]
1380
  )
 
374
 
375
  # 主要圖表區域 - 移除RSI圖表
376
  html.Div([
377
+ # 左側:股價走勢圖
378
  html.Div([
379
  html.Div([
380
  dcc.Graph(id='price-chart')
 
385
  html.Div([
386
  html.Div(id='analysis-panel')
387
  ], style={'width': '33%', 'display': 'inline-block', 'margin-left': '2%', 'vertical-align': 'top'})
388
+ ]),
389
+ # 新增:成交量分佈圖 (Volume Profile)
390
+ html.Div([
391
+ html.H3("📊 成交量分佈圖 (Volume Profile)"),
392
+ dcc.Graph(id='volume-profile-chart')
393
+ ], style={
394
+ 'margin-top': '30px',
395
+ 'padding': '20px',
396
+ 'background': 'white',
397
+ 'border-radius': '10px',
398
+ 'box-shadow': '0 2px 10px rgba(0,0,0,0.1)'
399
+ }),
400
+
401
  # 技術指標選擇區域
402
  html.Div([
403
  html.H3("📊 進階技術指標分析", style={'margin-bottom': '20px'}),
 
741
  })
742
  ])
743
 
744
+ # 更新股價圖表
745
  @app.callback(
746
  dash.dependencies.Output('price-chart', 'figure'),
747
  [dash.dependencies.Input('stock-dropdown', 'value'),
748
  dash.dependencies.Input('period-dropdown', 'value'),
749
  dash.dependencies.Input('chart-type', 'value')]
750
  )
751
+ def update_price_chart(selected_stock, period, chart_type):
752
  data = get_stock_data(selected_stock, period)
753
  if data.empty:
754
  return {}
 
756
  data = calculate_technical_indicators(data)
757
  stock_name = [name for name, symbol in TAIWAN_STOCKS.items() if symbol == selected_stock][0]
758
 
 
 
 
 
759
  if chart_type == 'candlestick':
760
+ fig = go.Figure(data=go.Candlestick(
761
  x=data.index,
762
  open=data['Open'],
763
  high=data['High'],
764
  low=data['Low'],
765
  close=data['Close'],
766
  name=stock_name
767
+ ))
768
  else:
769
+ fig = px.line(data, y='Close', title=f'{stock_name} 股價走勢')
770
 
771
  # 添加移動平均線
772
+ fig.add_trace(go.Scatter(x=data.index, y=data['MA5'], mode='lines', name='MA5', line=dict(color='orange')))
773
+ fig.add_trace(go.Scatter(x=data.index, y=data['MA20'], mode='lines', name='MA20', line=dict(color='blue')))
774
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
775
  fig.update_layout(
776
+ title=f'{stock_name} 股價走勢',
777
  xaxis_title='日期',
778
  yaxis_title='價格 (TWD)',
779
+ height=400
 
 
 
 
 
 
 
 
 
780
  )
781
+
 
 
 
782
  return fig
783
 
784
  # 更新RSI圖表(保持兼容性)
 
1343
 
1344
  return fig
1345
 
1346
+
1347
+ @app.callback(
1348
+ dash.dependencies.Output('volume-profile-chart', 'figure'),
1349
+ [dash.dependencies.Input('stock-dropdown', 'value'),
1350
+ dash.dependencies.Input('period-dropdown', 'value')]
1351
+ )
1352
+ def update_volume_profile_chart(selected_stock, period):
1353
+ data = get_stock_data(selected_stock, period)
1354
+ if data.empty:
1355
+ return {}
1356
+
1357
+ stock_name = [name for name, symbol in TAIWAN_STOCKS.items() if symbol == selected_stock][0]
1358
+
1359
+ # 計算 Volume Profile
1360
+ bin_edges, volume_per_bin, price_centers = calculate_volume_profile(data, num_bins=50) # 您可以調整 num_bins
1361
+
1362
+ if bin_edges is None or volume_per_bin is None:
1363
+ return {}
1364
+
1365
+ # 創建 Volume Profile 圖 (通常是水平長條圖)
1366
+ # 我們將其繪製為一個水平的長條圖,成交量在 X 軸,價格在 Y 軸
1367
+ fig = go.Figure(go.Bar(
1368
+ orientation='h', # 設定為水平長條圖
1369
+ y=price_centers,
1370
+ x=volume_per_bin,
1371
+ name='Volume Profile',
1372
+ marker=dict(
1373
+ color='rgba(173, 216, 230, 0.6)', # 淡藍色
1374
+ line=dict(color='rgba(30, 144, 255, 0.8)', width=1) # 邊框線
1375
+ ),
1376
+ # 顯示具體的成交量數字
1377
+ text=[f'{vol:.0f}' for vol in volume_per_bin],
1378
+ textposition='outside', # 將文字顯示在長條圖外面
1379
+ hoverinfo='y+text' # hover 時顯示 Y 軸 (價格) 和 text (成交量)
1380
+ ))
1381
+
1382
+ # 獲取最高成交量的價格區間 (Point of Control, POC)
1383
+ if len(volume_per_bin) > 0:
1384
+ poc_volume = np.max(volume_per_bin)
1385
+ poc_index = np.argmax(volume_per_bin)
1386
+ poc_price = price_centers[poc_index]
1387
+
1388
+ # 在 POC 價格線上添加一條垂直線
1389
+ fig.add_vline(x=poc_volume, line_dash="dash", line_color="red",
1390
+ annotation_text=f"POC: ${poc_price:.2f} ({poc_volume:.0f})",
1391
+ annotation_position="top right")
1392
+
1393
+ # 更新圖表佈局
1394
+ fig.update_layout(
1395
+ title=f'{stock_name} 成交量分佈圖 (Volume Profile)',
1396
+ xaxis_title='成交量',
1397
+ yaxis_title='價格 (TWD)',
1398
+ height=450,
1399
+ yaxis=dict(autorange='reversed'), # 讓價格從高到低排列
1400
+ bargap=0, # 讓長條圖緊密排列
1401
+ plot_bgcolor='rgba(0,0,0,0)', # 透明背景
1402
+ hoverlabel=dict(bgcolor="white", font_size=12, font_family="Rockwell")
1403
+ )
1404
+
1405
+ return fig
1406
+
1407
+ # 新增:多檔股票比較
1408
  @app.callback(
1409
+ [dash.dependencies.Output('comparison-chart', 'figure'),
1410
+ dash.dependencies.Output('comparison-table', 'children')],
1411
  [dash.dependencies.Input('comparison-stocks', 'value'),
1412
  dash.dependencies.Input('comparison-period', 'value')]
1413
  )