Products
GG网络技术分享 2025-03-18 16:13 0
使用 Python 构建股票分析仪表板的综合指南。
在过去的几周里,我使用 Python 的 Dash 库设计了一个股票价格分析面板。 我的主要布局灵感来自 Trading View 的网站。 面板需要显示一个烛台图表,其中包含一组要使用的指标以及用户选择的股票的一些其他额外细节。 所有这些信息都将由众所周知的 pandas.data_reader.DataReader 对象检索。
我们即将构建的视觉展示了一些公司在 2015 年 1 月 1 日至 2021 年 12 月 31 日期间的表现。
该项目的代码可以分为四个主要部分,按顺序排列:
1. 数据收集与清理
2.数据导入和默认图表
3. 应用布局
4. 交互性
加载所需的数据
首先,需要获取股票名称,以便 DataReader 可以在 Internet 上收集它们的数据。 为了完成这项工作,我发现了一个有趣的网页,其中显示了一个表格,其中包含公司的 BOVESPA 标识符以及它们所属的经济部门。
此阶段的代码包含项目“数据收集和清理”部分的一部分。
# These are the libaries to be used.
import dash
from dash import html, dcc, dash_table, callback_context
import plotly.graph_objects as go
import dash_trich_components as dtc
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc
import plotly.express as px
import json
import pandas as pd
from pandas_ta import bbands
import pandas_datareader as web
import numpy as np
import datetime
from scipy.stats import pearsonr
# 1. Data Collection & Cleaning
stocks = pd.read_html(
'https://blog.toroinvestimentos.com.br/empresas-listadas-b3-bovespa', decimal=',')
stocks_sectores = stocks[1]
stocks_sectores.columns = stocks_sectores.iloc[0]
stocks_sectores.drop(index=0, inplace=True)
stocks_sectores_dict = stocks_sectores[[
'Ticker', 'Setor']].to_dict(orient='list')
# Implementing the economic sector names as the dictionay key and their stocks as values.
d = {}
for stock, sector in zip(stocks_sectores_dict['Ticker'], stocks_sectores_dict['Setor']):
if sector not in d.keys():
d[sector] = [stock]
else:
d[sector].append(stock)
# Correcting a tiny issue with the resulting dictionary: sometimes, two of its keys
# were concerning the very same economic sector! Let's merge them into a single one.
d['Bens Industriais'].extend(d['Bens industriais'])
d.pop('Bens industriais')
d['Bens Industriais']
d['Energia Elétrica'].extend(d['Energia elétrica'])
d.pop('Energia elétrica')
d['Energia Elétrica']
d['Aluguel de Veículos'].extend(d['Locação de veículos'])
d.pop('Locação de veículos')
d['Construção civil'].extend(d['Construção'])
d.pop('Construção')
d['Shopping Centers'].extend(d['Exploração de imóveis'])
d.pop('Exploração de imóveis')
# Now, we'll translate the economic sectors names to English for better understanding.
translate = {'Alimentos': 'Food & Beverages', 'Varejo': 'Retail',
'Companhia aérea': 'Aerospace', 'Seguros': 'Insurace',
'Shopping Centers': 'Shopping Malls', 'Financeiro': 'Finance',
'Mineração': 'Mining', 'Químicos': 'Chemical', 'Educação': 'Education',
'Energia Elétrica': 'Electrical Energy', 'Viagens e lazer': 'Traveling',
'Construção civil': 'Real Estate', 'Saúde': 'Health',
'Siderurgia e Metalurgia': 'Steel & Metallurgy',
'Madeira e papel': 'Wood & Paper', 'Aluguel de Veículos': 'Vehicle Rental',
'Petróleo e Gás': 'Oil & Gas', 'Saneamento': 'Sanitation',
'Telecomunicações': 'Telecommunication', 'Tecnologia': 'Technology',
'Comércio': 'Commerce', 'Bens Industriais': 'Industrial Goods'}
for key, value in translate.items():
d[value] = d.pop(key)
# Unfortunately, some of the stocks listed in the dictionary cannot be accessed with yahoo's API.
# Thus, we'll need to exclude them.
for sector in list(d.keys()):
for stock in d[sector]:
# Trying to read the stock's data and removing it if it's not possible.
try:
web.DataReader(f'{stock}.SA', 'yahoo', '01-01-2015', '31-12-2021')
except:
d[sector].remove(stock)
# After the removing process is completed, some economic sectors may not have
# any stocks at all, so they won't be of use for the project.
if d[sector] == []:
d.pop(sector)
# Converting it into a json file
with open("sector_stocks.json", "w") as outfile:
json.dump(d, outfile)
此操作的最终输出是一个名为“sector_stocks.json”的 JSON 文件,其键和值是经济部门名称及其各自的股票。
设置默认图表
正如您在视觉效果的 GIF 中所看到的,仪表板总共包含三个图表。 主要的是显示 OHLC 价格信息的烛台图。 另外两个显示了对公司过去 52 周业绩的一些见解。 折线图代表股票的每周平均价格,速度计显示当前价格与该期间达到的最小值和最大值之间的差距。
Dash 要求我们设置这些视觉效果的默认版本。 加载仪表板后,它们将立即显示。 考虑到这一点,我决定这些图表将按标准涵盖饮料行业巨头 AMBEV (ABEV3) 的股票数据。 请注意,以下代码构成了项目的“数据导入和默认图表”部分。
# 2. Data Importing & Default Charts
# The standard stock displayed when the dashboard is initialized will be ABEV3.
ambev = web.DataReader('ABEV3.SA', 'yahoo',
start='01-01-2015', end='31-12-2021')
# 'ambev_variation' is a complementary information stored inside the card that
# shows the stock's current price (to be built futurely). It presents, as its
# variable name already suggests, the stock's price variation when compared to
# its previous value.
ambev_variation = 1 - (ambev['Close'].iloc[-1] / ambev['Close'].iloc[-2])
# 'fig' exposes the candlestick chart with the prices of the stock since 2015.
fig = go.Figure()
# Observe that we are promtly filling the charts with AMBEV's data.
fig.add_trace(go.Candlestick(x=ambev.index,
open=ambev['Open'],
close=ambev['Close'],
high=ambev['High'],
low=ambev['Low'],
name='Stock Price'))
fig.update_layout(
paper_bgcolor='black',
font_color='grey',
height=500,
width=1000,
margin=dict(l=10, r=10, b=5, t=5),
autosize=False,
showlegend=False
)
# Setting the graph to display the 2021 prices in a first moment.
# Nonetheless,the user can also manually ajust the zoom size either by selecting a
# section of the chart or using one of the time span buttons available.
# These two variables are going to be of use for the time span buttons.
min_date = '2021-01-01'
max_date = '2021-12-31'
fig.update_xaxes(range=[min_date, max_date])
fig.update_yaxes(tickprefix='R#39;)
# The output from this small resample operation feeds the weekly average price chart.
ambev_mean_52 = ambev.resample('W')[
'Close'].mean().iloc[-52:]
fig2 = go.Figure()
fig2.add_trace(go.Scatter(x=ambev_mean_52.index, y=ambev_mean_52.values))
fig2.update_layout(
title={'text': 'Weekly Average Price', 'y': 0.9},
font={'size': 8},
plot_bgcolor='black',
paper_bgcolor='black',
font_color='grey',
height=220,
width=310,
margin=dict(l=10, r=10, b=5, t=5),
autosize=False,
showlegend=False
)
fig2.update_xaxes(showticklabels=False, showgrid=False)
fig2.update_yaxes(range=[ambev_mean_52.min()-1, ambev_mean_52.max()+1.5],
showticklabels=False, gridcolor='darkgrey', showgrid=False)
# Making a speedometer chart which indicates the stock' minimum and maximum closing prices
# reached during the last 52 weeks along its current price.
df_52_weeks_min = ambev.resample('W')['Close'].min()[-52:].min()
df_52_weeks_max = ambev.resample('W')['Close'].max()[-52:].max()
current_price = ambev.iloc[-1]['Close']
fig3 = go.Figure()
fig3.add_trace(go.Indicator(mode='gauge+number', value=current_price,
domain={'x': [0, 1], 'y': [0, 1]},
gauge={
'axis': {'range': [df_52_weeks_min, df_52_weeks_max]},
'bar': {'color': '#606bf3'}}))
fig3.update_layout(
title={'text': 'Min-Max Prices', 'y': 0.9},
font={'size': 8},
paper_bgcolor='black',
font_color='grey',
height=220,
width=280,
margin=dict(l=35, r=0, b=5, t=5),
autosize=False,
showlegend=False
)
完成此任务后,我们终于可以开始创建仪表板了!
配置布局
仪表板的大部分组件都包含在两个 Bootstrap 列中。左侧部分占可用宽度的 75%,而右侧部分占据剩余空间。
然而,有一个元素不会放在这些分段中,即页面顶部的轮播。它显示了一些 BOVESPA 最相关股票的价格变化。
库存变化轮播
制作这个组件给我们带来了项目的技术限制之一。由于轮播需要显示一组股票的价格变化,它必须向 Yahoo Finance 的 API 发出多个请求。这会损害系统快速显示仪表板的速度。
考虑到这一点,我计划了一种方法,可以在不影响应用程序效率的情况下为我们提供我们想要的东西。
在创建“sector_stocks.json”文档时,还会生成第二个 JSON 文件。它将包含股票名称作为键,它们的价格变化作为值。由于仪表板的时间范围固定到 2021 年 12 月 1 日,因此不会给项目带来任何问题。
# 1. Data Collection & Cleaning (Continuance)
# A function that is going to measure the stocks' price variation.
def variation(name):
df = web.DataReader(f'{name}.SA', 'yahoo',
start='29-12-2021', end='30-12-2021')
return 1-(df['Close'].iloc[-1] / df['Close'].iloc[-2])
# Listing the companies to be shown in the Carousel.
carousel_stocks = ['ITUB4', 'BBDC4', 'VALE3', 'PETR4', 'PETR3',
'ABEV3', 'BBAS3', 'B3SA3', 'ITSA4', 'CRFB3', 'CIEL3',
'EMBR3', 'JBSS3', 'MGLU3', 'PCAR3', 'SANB11', 'SULA11']
# This dictionary will later be converted into a json file.
carousel_prices = {}
for stock in carousel_stocks:
# Applying the 'variation' function for each of the list elements.
carousel_prices[stock] = variation(stock)
# Turning 'carousel_prices' into a json file.
with open('carousel_prices.json', 'w') as outfile:
json.dump(carousel_prices, outfile)
完成最后一个操作后,我们完成了脚本的第一部分。 是时候最终构建应用程序的布局了。
# 2. Data Importing & Default Charts (Ending)
# Loading the dashboard's 'sector_stock' and 'carousel_prices' json files.
sector_stocks = json.load(open('sector_stocks.json', 'r'))
carousel_prices = json.load(open('carousel_prices.json', 'r'))
# 3. Application's Layout
app = dash.Dash(external_stylesheets=[dbc.themes.CYBORG])
# Beginning the layout. The whole dashboard is contained inside a Div and a Bootstrap
# Row.
app.layout = html.Div([
dbc.Row([
dtc.Carousel([
html.Div([
# This span shows the name of the stock.
html.Span(stock, style={
'margin-right': '10px'}),
# This other one shows its variation.
html.Span('{}{:.2%}'.format('+' if carousel_prices[stock] > 0 else '',
carousel_prices[stock]),
style={'color': 'green' if carousel_prices[stock] > 0 else 'red'})
]) for stock in sorted(carousel_prices.keys())
], id='main-carousel', autoplay=True, slides_to_show=4),
])
])
HTML Spans 显示股票名称及其价格变化。
仪表板的左侧部分
如前所述,应用程序的大部分内容都放在两个 Bootstrap 列中。 左边一个占可用宽度的 75%,另一个占据其余空间。 查看本文的 GIF 时,我们看到仪表板左侧的第一个功能是两个下拉菜单。
下拉菜单
这两个组件的目的是使用户能够选择将向其公开数据的公司。
首先,需要告知所需企业经营所在的经济部门(第一个下拉菜单)。 选择此选项后,第二个下拉列表会列出属于该行业的公司。 所需要做的就是单击想要的股票名称,仪表板将显示其数据。
但是由于我们没有设置所有的可视化组件,所以现在我们只处理两个下拉菜单之间的内部交互。
# 3. Application's Layout (Continuance)
# Place the following code under the top carousel structure.
# The column below will occupy 75% of the width available in the dashboard.
dbc.Col([
# This row holds the dropdowns responsible for the selection of the stock
# which informations are going to be displayed.
dbc.Row([
# Both dropdowns are placed inside two Bootstrap columns with equal length.
dbc.Col([
# A small title guiding the user on how to use the component.
html.Label('Select the desired sector',
style={'margin-left': '40px'}),
# The economic sectors dropdown. It mentions all the ones that are
# available in the 'sector_stocks' dictionary.
dcc.Dropdown(options=[{'label': sector, 'value': sector}
for sector in sorted(list(sector_stocks.keys()))],
# The dashboard's default stock is ABEV3, which pertains
# to the 'Food & Beverages' sector. So make sure
# these informations are automatically displayed
# when the dashboard is loaded.
value='Food & Beverages',
# It is essential to create an identification for the
# components which will be used in interactivity operations.
id='sectors-dropdown',
style={'margin-left': '20px', 'width': '400px'},
searchable=False)
], width=6),
# The column holding the stock names dropdown.
dbc.Col([
# Nothing new here. Just using the same commands as above.
html.Label('Select the stock to be displayed'),
dcc.Dropdown(
id='stocks-dropdown',
value='ABEV3',
style={'margin-right': '20px'},
searchable=False
)
], width=6)
]),
# The left major column closing bracket
], width=9),
# These are the dashboard's major dbc.Row and html.Div closing brackets.
])
])
# 4. Interactivity
# Allowing the stock dropdown to display the companies that are pertained to the
# economic sector chosen.
@ app.callback(
# Observe how important the components id are to reference from where the function's
# input comes from and in which element its output is used.
@ app.callback(
Output('stocks-dropdown', 'options'),
Input('sectors-dropdown', 'value')
)
def modify_stocks_dropdown(sector):
# The 'stocks-dropdown' dropdown values are the stocks that are part of the
# selected economic domain.
stocks = sector_stocks[sector]
return stocks
上面的代码足以生成两个元素并激活它们的内部关系。
烛台图、时间跨度按钮和指标
本节将处理三个组件,因为将有一个方法来管理它们之间的关系。
烛台图是最相关的特征。 它显示了 pandas_datareader 的 DataReader 对象检索到的 OHLC 价格。 与交易视图一样,它的 x 轴长度可以使用时间跨度按钮进行编辑。 技术分析指标被列为清单元素; 当有人选择他们喜欢的那个时,必须在视觉上绘制其各自的线条
在这部分脚本中产生了多种交互。 我们必须耐心地创造它们。
# 3. Application's Layout (Continuance)
# Insert the following portion of the code under the Dropdowns' dbc.Row.
# All the three components are placed inside this dbc.Row.
dbc.Row([
# Firstly, the candlestick chart is invoked. It is contained in a dcc.Loading
# object, which presents a loading animation while the data is retrieved.
dcc.Loading(
[dcc.Graph(id='price-chart', figure=fig)],
id='loading-price-chart', type='dot', color='#1F51FF'),
# Next, this row will store the time span buttons as well
# as the indicators checklist
dbc.Row([
# The buttons occupy 1/3 of the available width.
dbc.Col([
# This Div contains the time span buttons for adjusting
# of the x-axis' length.
html.Div([
html.Button('1W', id='1W-button',
n_clicks=0, className='btn-secondary'),
html.Button('1M', id='1M-button',
n_clicks=0, className='btn-secondary'),
html.Button('3M', id='3M-button',
n_clicks=0, className='btn-secondary'),
html.Button('6M', id='6M-button',
n_clicks=0, className='btn-secondary'),
html.Button('1Y', id='1Y-button',
n_clicks=0, className='btn-secondary'),
html.Button('3Y', id='3Y-button',
n_clicks=0, className='btn-secondary'),
], style={'padding': '15px', 'margin-left': '35px'})
], width=4),
# The indicators have the remaining two thirds of the space.
dbc.Col([
dcc.Checklist(
['Rolling Mean',
'Exponential Rolling Mean',
'Bollinger Bands'],
inputStyle={'margin-left': '15px',
'margin-right': '5px'},
id='complements-checklist',
style={'margin-top': '20px'})
], width=8)
]),
]),
# The left major column's closing bracket.
], width=9),
# The dashboard's major dbc.Row and html.Div closing brackets.
])
])
# 4. Interactivity (Continuance)
# Place this function below the dropdowns' interactivity method. It manages the
# relationship among the three elements just created.
@ app.callback(
Output('price-chart', 'figure'),
Input('stocks-dropdown', 'value'),
Input('complements-checklist', 'value'),
Input('1W-button', 'n_clicks'),
Input('1M-button', 'n_clicks'),
Input('3M-button', 'n_clicks'),
Input('6M-button', 'n_clicks'),
Input('1Y-button', 'n_clicks'),
Input('3Y-button', 'n_clicks'),
)
def change_price_chart(stock, checklist_values, button_1w, button_1m, button_3m,
button_6m, button_1y, button_3y):
# Retrieving the stock's data.
df = web.DataReader(f'{stock}.SA', 'yahoo',
start='01-01-2015', end='31-12-2021')
# Applying some indicators to its closing prices. Below we are measuring
# Bollinger Bands.
df_bbands = bbands(df['Close'], length=20, std=2)
# Measuring the Rolling Mean and Exponential Rolling means
df['Rolling Mean'] = df['Close'].rolling(window=9).mean()
df['Exponential Rolling Mean'] = df['Close'].ewm(
span=9, adjust=False).mean()
# Each metric will have its own color in the chart.
colors = {'Rolling Mean': '#6fa8dc',
'Exponential Rolling Mean': '#03396c', 'Bollinger Bands Low': 'darkorange',
'Bollinger Bands AVG': 'brown',
'Bollinger Bands High': 'darkorange'}
fig = go.Figure()
fig.add_trace(go.Candlestick(x=df.index, open=df['Open'], close=df['Close'],
high=df['High'], low=df['Low'], name='Stock Price'))
# If the user has selected any of the indicators in the checklist, we'll represent it in the chart.
if checklist_values != None:
for metric in checklist_values:
# Adding the Bollinger Bands' typical three lines.
if metric == 'Bollinger Bands':
fig.add_trace(go.Scatter(
x=df.index, y=df_bbands.iloc[:, 0],
mode='lines', name=metric,
line={'color': colors['Bollinger Bands Low'], 'width': 1}))
fig.add_trace(go.Scatter(
x=df.index, y=df_bbands.iloc[:, 1],
mode='lines', name=metric,
line={'color': colors['Bollinger Bands AVG'], 'width': 1}))
fig.add_trace(go.Scatter(
x=df.index, y=df_bbands.iloc[:, 2],
mode='lines', name=metric,
line={'color': colors['Bollinger Bands High'], 'width': 1}))
# Plotting any of the other metrics remained, if they are chosen.
else:
fig.add_trace(go.Scatter(
x=df.index, y=df[metric], mode='lines', name=metric,
line={'color': colors[metric], 'width': 1}))
fig.update_layout(
paper_bgcolor='black',
font_color='grey',
height=500,
width=1000,
margin=dict(l=10, r=10, b=5, t=5),
autosize=False,
showlegend=False
)
# Defining the chart's x-axis length according to the button clicked.
# To do this, we'll alter the 'min_date' and 'max_date' global variables that were
# defined in the beginning of the script.
global min_date, max_date
changed_id = [p['prop_id'] for p in callback_context.triggered][0]
if '1W-button' in changed_id:
min_date = df.iloc[-1].name - datetime.timedelta(7)
max_date = df.iloc[-1].name
elif '1M-button' in changed_id:
min_date = df.iloc[-1].name - datetime.timedelta(30)
max_date = df.iloc[-1].name
elif '3M-button' in changed_id:
min_date = df.iloc[-1].name - datetime.timedelta(90)
max_date = df.iloc[-1].name
elif '6M-button' in changed_id:
min_date = df.iloc[-1].name - datetime.timedelta(180)
max_date = df.iloc[-1].name
elif '1Y-button' in changed_id:
min_date = df.iloc[-1].name - datetime.timedelta(365)
max_date = df.iloc[-1].name
elif '3Y-button' in changed_id:
min_date = df.iloc[-1].name - datetime.timedelta(1095)
max_date = df.iloc[-1].name
else:
min_date = min_date
max_date = max_date
fig.update_xaxes(range=[min_date, max_date])
fig.update_yaxes(tickprefix='R#39;)
return fig
# Updating the x-axis range.
fig.update_xaxes(range=[min_date, max_date])
fig.update_yaxes(tickprefix='R#39;)
return fig
完成这部分代码后,我们已经结束了面板的左侧。
仪表板的右侧部分
这整个区域暴露了一些关于股票及其各自经济部门的补充信息。 我们将要编写的代码比我们刚刚编写的代码要简单得多。
经济部门价格表
右上角的表格显示了在下拉列表中选择的属于经济领域的企业的当前价格。 这样一来,个人就能够想象公司在与竞争对手的竞争中表现如何。
# 3. Application's Layout (Continuance)
# Beginning the Dashboard's right section. It should be under the panel's left-side code.
# This other column occupies 25% of the dashboard's width.
dbc.Col([
dbc.Row([
# The DataTable stores the prices from the companies that pertain
# to the same economic sector as the one chosen in the dropdowns.
dcc.Loading([
dash_table.DataTable(
id='stocks-table', style_cell={'font_size': '12px',
'textAlign': 'center'},
style_header={'backgroundColor': 'black',
'padding-right': '62px', 'border': 'none'},
style_data={'height': '12px', 'backgroundColor': 'black',
'border': 'none'}, style_table={
'height': '90px', 'overflowY': 'auto'})
], id='loading-table', type='circle', color='#1F51FF')
], style={'margin-top': '28px'}),
# The right major column closing bracket.
], width=3)
# The dashboard's major dbc.Row and html.Div closing brackets.
])
])
# 4. Interactivity (Continuance)
# The following function goes after the candlestick interactivity method.
@ app.callback(
Output('stocks-table', 'data'),
Output('stocks-table', 'columns'),
Input('sectors-dropdown', 'value')
)
# Updating the panel's DataTable
def update_stocks_table(sector):
global sector_stocks
# This DataFrame will be the base for the table.
df = pd.DataFrame({'Stock': [stock for stock in sector_stocks[sector]],
'Close': [np.nan for i in range(len(sector_stocks[sector]))]},
index=[stock for stock in sector_stocks[sector]])
# Each one of the stock names and their respective prices are going to be stored
# in the 'df' DataFrame.
for stock in sector_stocks[sector]:
stock_value = web.DataReader(
f'{stock}.SA', 'yahoo', start='30-12-2021', end='31-12-2021')['Close'].iloc[-1]
df.loc[stock, 'Close'] = f'R$ {stock_value :.2f}'
# Finally, the DataFrame cell values are stored in a dictionary and its column
# names in a list of dictionaries.
return df.to_dict('records'), [{'name': i, 'id': i} for i in df.columns]
update_stocks_table 可能看起来令人困惑,但它基本上需要返回两件事:填充字典中表格单元格的信息和字典列表中其列的名称。 有关 Dash 的 DataTable 工作原理的更多见解,请查看该库的文档。
当前价格卡
毫无疑问,这是最重要的家庭经纪人功能之一,此卡旨在显示所选公司的名称、当前价值以及与前一天相比的价格变化。
# 3. Application's Layout (Continuance)
# Place this section beneath the DataTable code
# This Div will hold a card displaying the selected stock's current price.
html.Div([
dbc.Card([
# The card below presents the selected stock's current price.
dbc.CardBody([
# Recall that the name shown as default needs to be 'ABEV3',
# since it is the panel's standard stock.
html.H1('ABEV3', id='stock-name',
style={'font-size': '13px', 'text-align': 'center'}),
dbc.Row([
dbc.Col([
# Placing the current price.
html.P('R$ {:.2f}'.format(ambev['Close'].iloc[-1]),
id='stock-price', style={
'font-size': '40px', 'margin-left': '5px'})
], width=8),
dbc.Col([
# This another paragraph shows the price variation.
html.P(
'{}{:.2%}'.format(
'+' if ambev_variation > 0 else '-', ambev_variation),
id='stock-variation',
style={'font-size': '14px',
'margin-top': '25px',
'color': 'green' if ambev_variation > 0 else 'red'})
], width=4)
])
])
], id='stock-data', style={'height': '105px'}, color='black'),
])
# The right major column closing bracket.
], width=3)
# The dashboard's major dbc.Row and html.Div closing brackets.
])
])
# 4. Interactivity (Continuance)
# The following function will edit the values being displayed in the price card
# and it should follow the method that handles the DataTable.
@app.callback(
Output('stock-name', 'children'),
Output('stock-price', 'children'),
Output('stock-variation', 'children'),
Output('stock-variation', 'style'),
Input('stocks-dropdown', 'value')
)
def update_stock_data_card(stock):
# Retrieving data from the Yahoo Finance API.
df = web.DataReader(f'{stock}.SA', 'yahoo',
start='2021-12-29', end='2021-12-31')['Close']
# Getting the chosen stock's current price and variation in comparison to
# its previous value.
stock_current_price = df.iloc[-1]
stock_variation = 1 - (df.iloc[-1] / df.iloc[-2])
# Note that as in the Carousel, the varitation value will be painted in red or
# green depending if it is a negative or positive number.
return stock, 'R$ {:.2f}'.format(stock_current_price),
'{}{:.2%}'.format('+' if stock_variation > 0 else'-', stock_variation), \\
{'font-size': '14px', 'margin-top': '25px',
'color': 'green' if stock_variation > 0 else 'red'}
52 周数据轮播
这个轮播在面板右侧启动了 52 周信息区域。 该组件将包含一个显示公司每周平均价格的折线图和一个速度计,它基本上显示了当前值与该期间达到的最低和最高水平之间的距离。
与创建的任何其他事物一样,此结构需要与股票下拉列表中选择的值进行通信,以显示正确的信息。 但与这个项目中构建的其他不同,这个有两种交互方法,一种用于每个图表。
# 3. Application's Layout (Continuance)
# This part of the script goes right under the price card construct.
# In the section below, some informations about the stock's performance in the last
# 52 weeks are going to be exposed.
html.Hr(),
html.H1(
'52-Week Data', style={'font-size': '25px', 'text-align': 'center',
'color': 'grey', 'margin-top': '5px',
'margin-bottom': '0px'}),
# Creating a Carousel showing the stock's weekly average price and a
# Speedoemeter displaying how far its current price is from
# the minimum and maximum values achieved.
dtc.Carousel([
# By standard, the charts 'fig2' and 'fig3' made in the beginning
# of the script are displayed.
dcc.Graph(id='52-avg-week-price', figure=fig2),
dcc.Graph(id='52-week-min-max', figure=fig3)
], slides_to_show=1, autoplay=True, speed=2000,
style={'height': '220px', 'width': '310px', 'margin-bottom': '0px'}),
# The right major column closing bracket.
], width=3)
# The dashboard's major dbc.Row and html.Div closing brackets.
])
])
# 4. Interactivity (Continuance)
# The function here is placed under the one dealing with the price card and
# is responsible for updating the weekly average price chart.
@app.callback(
Output('52-avg-week-price', 'figure'),
Input('stocks-dropdown', 'value')
)
def update_average_weekly_price(stock):
# Receiving the stock's prices and measuring its average weekly price
# in the last 52 weeks.
df = web.DataReader(f'{stock}.SA', 'yahoo',
start='2021-01-01', end='2021-12-31')['Close']
df_avg_52 = df.resample('W').mean().iloc[-52:]
# Plotting the data in a line chart.
fig2 = go.Figure()
fig2.add_trace(go.Scatter(x=df_avg_52.index, y=df_avg_52.values))
fig2.update_layout(
title={'text': 'Weekly Average Price', 'y': 0.9},
font={'size': 8},
plot_bgcolor='black',
paper_bgcolor='black',
font_color='grey',
height=220,
width=310,
margin=dict(l=10, r=10, b=5, t=5),
autosize=False,
showlegend=False
)
fig2.update_xaxes(tickformat='%m-%y', showticklabels=False,
gridcolor='darkgrey', showgrid=False)
fig2.update_yaxes(range=[df_avg_52.min()-1, df_avg_52.max()+1.5],
showticklabels=False, gridcolor='darkgrey', showgrid=False)
return fig2
# This function will update the speedometer chart.
@app.callback(
Output('52-week-min-max', 'figure'),
Input('stocks-dropdown', 'value')
)
def update_min_max(stock):
# The same logic as 'update_average_weekly_price', but instead we are getting
# the minimum and maximum prices reached in the last 52 weeks and comparing
# them with the stock's current price.
df = web.DataReader(f'{stock}.SA', 'yahoo',
start='2021-01-01', end='2021-12-31')['Close']
df_avg_52 = df.resample('W').mean().iloc[-52:]
df_52_weeks_min = df_avg_52.resample('W').min()[-52:].min()
df_52_weeks_max = df_avg_52.resample('W').max()[-52:].max()
current_price = df.iloc[-1]
fig3 = go.Figure()
fig3.add_trace(go.Indicator(mode='gauge+number', value=current_price,
domain={'x': [0, 1], 'y': [0, 1]},
gauge={
'axis': {'range': [df_52_weeks_min, df_52_weeks_max]},
'bar': {'color': '#606bf3'}}))
fig3.update_layout(
title={'text': 'Min-Max Prices', 'y': 0.9},
font={'size': 8},
paper_bgcolor='black',
font_color='grey',
height=220,
width=280,
margin=dict(l=35, r=0, b=5, t=5),
autosize=False,
showlegend=False
)
return fig3
我们即将结束仪表板的结构。 我们需要插入的只是股票与 BOVESPA 指数 (IBOVESPA) 及其经济领域平均价格的相关性。
股票的相关性
如前所述,这些最后的数字旨在提供与 IBOVESPA 及其经济部门同行相比企业绩效的一些观点。 要使用的统计量是 Pearson 的 r。
# 3. Application's Layout (Ending!)
# This row is beneath the carousel just made and places the stock's correlations with
# the BOVESPA index (IBOVESPA) and its sector's daily average price.
dbc.Row([
# A small title for the section.
html.H2('Correlations', style={
'font-size': '12px', 'color': 'grey', 'text-align': 'center'}),
dbc.Col([
# IBOVESPA correlation.
html.Div([
html.H1('IBOVESPA',
style={'font-size': '10px'}),
html.P(id='ibovespa-correlation',
style={'font-size': '20px', 'margin-top': '5px'})
], style={'text-align': 'center'})
], width=6),
dbc.Col([
# Sector correlation.
html.Div([
html.H1('Sector',
style={'font-size': '10px'}),
html.P(id='sector-correlation',
style={'font-size': '20px', 'margin-top': '5px'})
], style={'text-align': 'center'})
], width=6)
], style={'margin-top': '2px'})
], style={'backgroundColor': 'black', 'margin-top': '20px', 'padding': '5px'})
# The right major column closing bracket.
], width=3)
# The dashboard's major dbc.Row and html.Div closing brackets.
])
])
# 4. Interactivity (Ending!)
# This function will measure the correlation coefficient between the stock's closing
# values and the BOVESPA index.
@ app.callback(
Output('ibovespa-correlation', 'children'),
Input('stocks-dropdown', 'value')
)
def ibovespa_correlation(stock):
start = datetime.datetime(2021, 12, 31).date() - \\
datetime.timedelta(days=7 * 52)
end = datetime.datetime(2021, 12, 31).date()
# Retrieving the IBOVESPA values in the last 52 weeks.
ibovespa = web.DataReader('^BVSP', 'yahoo', start=start, end=end)['Close']
# Now, doing the same with the chosen stock's prices.
stock_close = web.DataReader(
f'{stock}.SA', 'yahoo', start=start, end=end)['Close']
# Returning the correlation coefficient.
return f'{pearsonr(ibovespa, stock_close)[0] :.2%}'
# Now, this other function will measure the same stat, but now between the stock's value
# and the daily average closing price from its respective sector in the last 52 weeks.
@ app.callback(
Output('sector-correlation', 'children'),
Input('sectors-dropdown', 'value'),
Input('stocks-dropdown', 'value')
)
def sector_correlation(sector, stock):
start = datetime.datetime(2021, 12, 31).date() - \\
datetime.timedelta(days=7 * 52)
end = datetime.datetime(2021, 12, 31).date()
# Retrieving the daily closing prices from the selected stocks in the prior 52 weeks.
stock_close = web.DataReader(
f'{stock}.SA', 'yahoo', start=start, end=end)['Close']
# Creating a DataFrame that will store the prices in the past 52 weeks
# from all the stocks that pertain to the economic domain selected.
sector_df = pd.DataFrame()
# Retrieving the price for each of the stocks included in 'sector_stocks'
global sector_stocks
stocks_from_sector = [stock_ for stock_ in sector_stocks[sector]]
for stock_ in stocks_from_sector:
sector_df[stock_] = web.DataReader(
f'{stock_}.SA', 'yahoo', start=start, end=end)['Close']
# With all the prices obtained, let's measure the sector's daily average value.
sector_daily_average = sector_df.mean(axis=1)
# Now, returning the correlation coefficient.
return f'{pearsonr(sector_daily_average, stock_close)[0] :.2%}'
# Finally, running the Dash app.
if __name__ == '__main__':
app.run_server(debug=True)就是这样! 编写最后一段脚本将确保您拥有完整的股票数据分析仪表板!
结论
我真诚地希望您喜欢和我一起构建这个面板!
Bootstrap4 Carousel轮播非常简单好用,默认左右滚动效果,但通常的需求是三种情况:左右滚动(水平滚动)、上下滚动(垂直滚动)、淡入淡出。
通过增加少量的CSS,就可以为Bootstrap4 Carousel轮播增加上下滚动(垂直滚动)、淡入淡出效果。
新增的CSS如下:
.carousel-fade .carousel-inner .carousel-item {-webkit-transform: translateX(0);
transform: translateX(0);
transition-property: opacity;
}
.carousel-fade .carousel-inner .carousel-item,
.carousel-fade .carousel-inner .active.carousel-item-left,
.carousel-fade .carousel-inner .active.carousel-item-right {
opacity: 0;
}
.carousel-fade .carousel-inner .active,
.carousel-fade .carousel-inner .carousel-item-next.carousel-item-left,
.carousel-fade .carousel-inner .carousel-item-prev.carousel-item-right {
opacity: 1;
}
.carousel-vertical .carousel-inner .carousel-item-next.carousel-item-left,
.carousel-vertical .carousel-inner .carousel-item-prev.carousel-item-right {
-webkit-transform: translateY(0);
transform: translateY(0);
}
.carousel-vertical .carousel-inner .active.carousel-item-left,
.carousel-vertical .carousel-inner .carousel-item-prev {
-webkit-transform: translateY(-100 {
5cc1b29162d549a8071384de182cc9fc6e6a0fd85e7907f22fd9e18cff4269c3
}
);
transform: translateY(-100 {
5cc1b29162d549a8071384de182cc9fc6e6a0fd85e7907f22fd9e18cff4269c3
}
);
}
.carousel-vertical .carousel-inner .active.carousel-item-right,
.carousel-vertical .carousel-inner .carousel-item-next {
-webkit-transform: translateY(100 {
5cc1b29162d549a8071384de182cc9fc6e6a0fd85e7907f22fd9e18cff4269c3
}
);
transform: translateY(100 {
5cc1b29162d549a8071384de182cc9fc6e6a0fd85e7907f22fd9e18cff4269c3
}
);
}
Bootstrap Carousel轮播上下滚动HTML如下:
<div id=\"slides\" data-ride=\"carousel\"><ul>
<li data-target=\"#slides\" data-slide-to=\"0\"></li>
<li data-target=\"#slides\" data-slide-to=\"1\"></li>
<li data-target=\"#slides\" data-slide-to=\"2\"></li>
<li data-target=\"#slides\" data-slide-to=\"3\"></li>
</ul>
<div>
<div><a href=\"#\" rel=\"external nofollow\" rel=\"external nofollow\" rel=\"external nofollow\" rel=\"external nofollow\" ><img src=\"\" alt=\"幻灯片\" /></a></div>
<div><a href=\"#\" rel=\"external nofollow\" rel=\"external nofollow\" rel=\"external nofollow\" rel=\"external nofollow\" ><img src=\"\" alt=\"幻灯片\" /></a></div>
<div><a href=\"#\" rel=\"external nofollow\" rel=\"external nofollow\" rel=\"external nofollow\" rel=\"external nofollow\" ><img src=\"\" alt=\"幻灯片\" /></a></div>
<div><a href=\"#\" rel=\"external nofollow\" rel=\"external nofollow\" rel=\"external nofollow\" rel=\"external nofollow\" ><img src=\"\" alt=\"幻灯片\" /></a></div>
</div><a href=\"#slides\" rel=\"external nofollow\" rel=\"external nofollow\" data-slide=\"prev\"><span></span></a><a href=\"#slides\" rel=\"external nofollow\" rel=\"external nofollow\"
data-slide=\"next\"><span></span></a>
</div>
淡入淡出的HTML与上下滚动的基本相同,只需将Classcarousel-vertical换为carousel-fade即可。而水平滚动只需要删除Classcarousel-vertical即可。
Demand feedback