Skip to content

Commit 0c716b3

Browse files
authored
New tab stock ethan (#69)
* Add statsmodels to our environment, closes #68 * Add a new tab named stock including beta, regression and trend plot, closes #68
1 parent b837103 commit 0c716b3

File tree

5 files changed

+167
-2
lines changed

5 files changed

+167
-2
lines changed

environment.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ dependencies:
1414
- jupyterlab=4.2.6
1515
- pip=24.3
1616
- pyecharts=2.0.8
17+
- statsmodels=0.14.4
1718
- pip:
1819
- dash_bootstrap_components==1.7.1
1920
- dash-ag-grid

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ dash_bootstrap_components==1.7.1
88
gunicorn==23.0.0
99
requests==2.32.3
1010
dash-ag-grid==31.3.0
11-
11+
statsmodels==0.14.4

src/callbacks.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,42 @@ def update_intraday_bar(selected_sectors):
7777
style={"border": "0", "width": "100%", "height": "350px"}
7878
)
7979

80+
# Callback for updating regression graph based on stock and time range selection
81+
@app.callback(
82+
Output('regression-graph-container', 'children'),
83+
[Input('stock-dropdown', 'value'),
84+
Input('date-picker-range', 'start_date'),
85+
Input('date-picker-range', 'end_date')]
86+
)
87+
def update_regression_graph(selected_stock, start_date, end_date):
88+
"""Updates the regession and beta value based on selected stock and date range"""
89+
# 调用 render_regression_graph 函数,生成图表
90+
regression_fig_html = render_regression_graph(selected_stock, start_date, end_date)
91+
92+
# 将生成的图表 HTML 结果嵌入 Iframe 中
93+
return html.Iframe(
94+
srcDoc=regression_fig_html, style={"border": "0", "width": "100%", "height": "600px"}
95+
)
96+
97+
# Callback for updating trend graph based on stock and time range selection
98+
@app.callback(
99+
Output('price-trend-graph-container', 'children'),
100+
[Input('stock-dropdown', 'value'),
101+
Input('date-picker-range', 'start_date'),
102+
Input('date-picker-range', 'end_date')]
103+
)
104+
def update_trend_graph(selected_stock, start_date, end_date):
105+
"""Updates the trend graph based on selected stock and date range"""
106+
# 调用 render_trend_graph 函数,生成图表
107+
price_trend_fig_html = render_trend_graph(selected_stock, start_date, end_date)
108+
109+
# 将生成的图表 HTML 结果嵌入 Iframe 中
110+
return html.Iframe(
111+
srcDoc=price_trend_fig_html, style={"border": "0", "width": "100%", "height": "600px"}
112+
)
113+
114+
115+
80116

81117
# Callback: Update data every n seconds and store it in dcc.Store (requires adding dcc.Store(id="data-store") in the layout)
82118
@app.callback(

src/layout.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from dash import dcc, html
33
from dash_ag_grid import AgGrid
44
from src.plotting import *
5+
from src.qqqm_data import getQQQMHolding
56

67
# Modify the update frequency selection dropdown: Change the options to '3 seconds', '10 seconds', and 'No update',
78
# with the default value set to 'No update'
@@ -136,6 +137,27 @@
136137
]
137138
)
138139

140+
# Components for stock analysis
141+
regression_graph = html.Div(
142+
id="regression-graph-container", children=[
143+
html.Iframe(
144+
srcDoc=render_regression_graph(selected_stock='AAPL', start_date='2024-01-01', end_date='2025-01-01'),
145+
style={"border": "0", "width": "100%", "height": "350px"}
146+
)
147+
]
148+
)
149+
150+
price_trend_graph = html.Div(
151+
id="price-trend-graph-container", children=[
152+
html.Iframe(
153+
srcDoc=render_trend_graph(selected_stock='AAPL', start_date='2024-01-01', end_date='2025-01-01'),
154+
style={"border": "0", "width": "100%", "height": "350px"}
155+
)
156+
]
157+
)
158+
159+
160+
139161

140162
# Filter Form (with ticker and name input)
141163
filter_form = dbc.Row(
@@ -234,6 +256,13 @@
234256
n_intervals=0
235257
)
236258

259+
nasdaq100_tickers = getQQQMHolding()
260+
# 生成下拉菜单选项
261+
stock_dropdown_options = [
262+
{'label': nasdaq100_tickers.iloc[i]['Name'], 'value': nasdaq100_tickers.iloc[i]['Ticker']}
263+
for i in range(len(nasdaq100_tickers))
264+
]
265+
237266
tabs = dbc.Tabs([
238267
dbc.Tab(
239268
[
@@ -248,6 +277,39 @@
248277
],
249278
label="Overview"
250279
),
280+
281+
dbc.Tab(
282+
[
283+
dbc.Row([
284+
dbc.Col(
285+
dcc.Dropdown(
286+
id='stock-dropdown',
287+
options=stock_dropdown_options,
288+
value='AAPL', # 默认值
289+
placeholder="Select a stock", # 提示文本
290+
),
291+
width=6,
292+
),
293+
dbc.Col(
294+
dcc.DatePickerRange(
295+
id='date-picker-range',
296+
start_date=pd.to_datetime('2024-01-01'),
297+
end_date=pd.to_datetime('2025-02-05'),
298+
display_format='YYYY-MM-DD', # 日期格式
299+
min_date_allowed=pd.to_datetime('2010-01-01'),
300+
max_date_allowed=pd.to_datetime('2025-12-31'),
301+
),
302+
width=6,
303+
),
304+
]),
305+
dbc.Row([
306+
dbc.Col(regression_graph),
307+
dbc.Col(price_trend_graph)
308+
], class_name="g-0", style={"marginTop": "20px"}),
309+
],
310+
label="Stock"
311+
),
312+
251313
dbc.Tab(
252314
[
253315
search_download_row,

src/plotting.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import pandas as pd
2+
import numpy as np
23

34
from pyecharts.charts import Pie
45
import plotly.graph_objects as go
56
import plotly.express as px
67
from pyecharts import options as opts
78
from src.qqqm_data import getQQQMHolding
9+
from src.xueqiu_data import getUSStockHistoryByDate
810

911
def render_pie_chart(selected_sectors=["All"]):
1012
"""
@@ -353,4 +355,68 @@ def render_intraday_contribution_5(selected_sectors=["All"]):
353355
showlegend=False
354356
)
355357

356-
return fig.to_html(full_html=False)
358+
return fig.to_html(full_html=False)
359+
360+
def calculate_beta(stock_returns, index_returns):
361+
"""Calculate the beta value"""
362+
covariance = np.cov(stock_returns, index_returns)[0, 1]
363+
variance = np.var(index_returns)
364+
beta = covariance / variance
365+
return beta
366+
367+
368+
def fetch_stock_data(selected_stock, start_date, end_date):
369+
370+
stock_data = getUSStockHistoryByDate(selected_stock, start_date, end_date)
371+
stock_data = stock_data[['Timestamp_str', 'percent', 'close']] # Keep only the desired columns
372+
stock_data = stock_data.rename(columns={'Timestamp_str': 'Date', 'percent': 'return', 'close': 'price'}) # Rename columns
373+
stock_data['Date'] = pd.to_datetime(stock_data['Date']) # Parse dates
374+
stock_data.set_index('Date', inplace=True)
375+
stock_returns = stock_data['return'].dropna() * 100
376+
377+
nasdaq_data = getUSStockHistoryByDate('QQQ', start_date, end_date)
378+
nasdaq_data = nasdaq_data[['Timestamp_str', 'percent', 'close']] # Keep only the desired columns
379+
nasdaq_data = nasdaq_data.rename(columns={'Timestamp_str': 'Date', 'percent': 'return', 'close': 'price'}) # Rename columns
380+
nasdaq_data['Date'] = pd.to_datetime(nasdaq_data['Date']) # Parse date
381+
nasdaq_data.set_index('Date', inplace=True)
382+
nasdaq_returns = nasdaq_data['return'].dropna() * 100
383+
384+
return stock_data, nasdaq_data, stock_returns, nasdaq_returns
385+
386+
def render_regression_graph(selected_stock, start_date, end_date):
387+
388+
stock_data, nasdaq_data, stock_returns, nasdaq_returns = fetch_stock_data(selected_stock, start_date, end_date)
389+
390+
beta = calculate_beta(stock_returns, nasdaq_returns)
391+
392+
regression_fig = px.scatter(
393+
x=nasdaq_returns,
394+
y=stock_returns,
395+
trendline="ols",
396+
labels={'x': 'NASDAQ 100 Returns', 'y': f'{selected_stock} Returns'},
397+
title=f"Regression Analysis: Beta = {beta:.2f}"
398+
)
399+
400+
return regression_fig.to_html(full_html=False)
401+
402+
def render_trend_graph(selected_stock, start_date, end_date):
403+
404+
stock_data, nasdaq_data, stock_returns, nasdaq_returns = fetch_stock_data(selected_stock, start_date, end_date)
405+
stock_price_normalized = stock_data['price'].dropna() / stock_data['price'].iloc[0]
406+
nasdaq_price_normalized = nasdaq_data['price'].dropna() / nasdaq_data['price'].iloc[0]
407+
combined_data = pd.DataFrame({
408+
'Date': stock_price_normalized.index, # 日期索引
409+
'Stock Price': stock_price_normalized, # 归一化后的股票价格
410+
'NASDAQ Price': nasdaq_price_normalized # 归一化后的指数价格
411+
})
412+
413+
# 绘制价格走势图
414+
price_trend_fig = px.line(
415+
combined_data, # 数据
416+
x='Date', # X 轴:日期
417+
y=['Stock Price', 'NASDAQ Price'], # Y 轴:股票和指数价格
418+
labels={'value': 'Normalized Price', 'variable': 'Legend'}, # 标签
419+
title=f'{selected_stock} and NASDAQ 100 Price Trend (Normalized)' # 标题
420+
)
421+
422+
return price_trend_fig.to_html(full_html=False)

0 commit comments

Comments
 (0)