Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -5,13 +5,10 @@ import plotly.express as px
|
|
| 5 |
import os
|
| 6 |
|
| 7 |
# Global API key and default ETF symbols
|
| 8 |
-
|
| 9 |
API_KEY = os.getenv("FMP_API_KEY")
|
| 10 |
|
| 11 |
ETF_ASSET_SYMBOL = "SPY" # Used for asset composition
|
| 12 |
-
|
| 13 |
-
# For sector and country composition, you may adjust this as needed.
|
| 14 |
-
ETF_SYMBOL = "QDVE.DE"
|
| 15 |
|
| 16 |
def format_number(n):
|
| 17 |
"""
|
|
@@ -33,15 +30,19 @@ def format_number(n):
|
|
| 33 |
@st.cache_data(show_spinner=False)
|
| 34 |
def get_etf_asset_composition(etf_symbol: str) -> pd.DataFrame:
|
| 35 |
url = f"https://financialmodelingprep.com/api/v3/etf-holder/{etf_symbol}?apikey={API_KEY}"
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
return pd.DataFrame()
|
| 41 |
-
df = pd.DataFrame(data)
|
| 42 |
-
df['marketValue'] = pd.to_numeric(df['marketValue'], errors='coerce')
|
| 43 |
-
df_sorted = df.sort_values(by="marketValue", ascending=False)
|
| 44 |
-
return df_sorted
|
| 45 |
|
| 46 |
def plot_etf_asset_composition(df: pd.DataFrame, etf_symbol: str):
|
| 47 |
if df.empty:
|
|
@@ -76,15 +77,18 @@ def plot_etf_asset_composition(df: pd.DataFrame, etf_symbol: str):
|
|
| 76 |
@st.cache_data(show_spinner=False)
|
| 77 |
def get_etf_sector_composition(etf_symbol: str) -> pd.DataFrame:
|
| 78 |
url = f"https://financialmodelingprep.com/api/v3/etf-sector-weightings/{etf_symbol}?apikey={API_KEY}"
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
return pd.DataFrame()
|
| 84 |
-
df = pd.DataFrame(data)
|
| 85 |
-
df['weightPercentage'] = df['weightPercentage'].str.rstrip('%').astype(float)
|
| 86 |
-
df_sorted = df.sort_values(by="weightPercentage", ascending=False)
|
| 87 |
-
return df_sorted
|
| 88 |
|
| 89 |
def plot_etf_sector_composition(df: pd.DataFrame, etf_symbol: str):
|
| 90 |
if df.empty:
|
|
@@ -110,15 +114,18 @@ def plot_etf_sector_composition(df: pd.DataFrame, etf_symbol: str):
|
|
| 110 |
@st.cache_data(show_spinner=False)
|
| 111 |
def get_etf_country_composition(etf_symbol: str) -> pd.DataFrame:
|
| 112 |
url = f"https://financialmodelingprep.com/api/v3/etf-country-weightings/{etf_symbol}?apikey={API_KEY}"
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
return pd.DataFrame()
|
| 118 |
-
df = pd.DataFrame(data)
|
| 119 |
-
df['weightPercentage'] = df['weightPercentage'].str.rstrip('%').astype(float)
|
| 120 |
-
df_sorted = df.sort_values(by="weightPercentage", ascending=False)
|
| 121 |
-
return df_sorted
|
| 122 |
|
| 123 |
def plot_etf_country_composition(df: pd.DataFrame, etf_symbol: str):
|
| 124 |
if df.empty:
|
|
@@ -145,13 +152,13 @@ def main():
|
|
| 145 |
st.set_page_config(page_title="ETF Asset Composition Dashboard", layout="wide")
|
| 146 |
st.title("ETF Asset Composition Dashboard")
|
| 147 |
st.write(
|
| 148 |
-
"
|
| 149 |
"Use the side menu to enter the ETF ticker symbol and then click the 'Run ETF Composition' button. "
|
| 150 |
-
"The
|
| 151 |
"Each section includes a chart and a data table. Hover over the charts for more details."
|
| 152 |
)
|
| 153 |
|
| 154 |
-
# Initialize run button
|
| 155 |
if "run_etf" not in st.session_state:
|
| 156 |
st.session_state.run_etf = False
|
| 157 |
|
|
@@ -166,14 +173,13 @@ def main():
|
|
| 166 |
st.session_state.run_etf = True
|
| 167 |
st.session_state.etf_ticker = etf_ticker
|
| 168 |
|
| 169 |
-
# Main content
|
| 170 |
if st.session_state.get("run_etf", False):
|
| 171 |
# Section 1: ETF Asset Composition
|
| 172 |
st.header("1. ETF Asset Composition")
|
| 173 |
st.write(
|
| 174 |
-
"
|
| 175 |
-
"The bar chart
|
| 176 |
-
"The data table below shows the raw holdings data."
|
| 177 |
)
|
| 178 |
asset_df = get_etf_asset_composition(st.session_state.etf_ticker)
|
| 179 |
asset_fig = plot_etf_asset_composition(asset_df, st.session_state.etf_ticker)
|
|
@@ -185,9 +191,8 @@ def main():
|
|
| 185 |
# Section 2: Sector Composition
|
| 186 |
st.header("2. Sector Composition")
|
| 187 |
st.write(
|
| 188 |
-
"
|
| 189 |
-
"The bar chart visualizes
|
| 190 |
-
"The data table below contains the raw sector weighting data."
|
| 191 |
)
|
| 192 |
sector_df = get_etf_sector_composition(st.session_state.etf_ticker)
|
| 193 |
sector_fig = plot_etf_sector_composition(sector_df, st.session_state.etf_ticker)
|
|
@@ -199,8 +204,8 @@ def main():
|
|
| 199 |
# Section 3: Country Composition
|
| 200 |
st.header("3. Country Composition")
|
| 201 |
st.write(
|
| 202 |
-
"
|
| 203 |
-
"The bar chart displays the weight percentage by country
|
| 204 |
)
|
| 205 |
country_df = get_etf_country_composition(st.session_state.etf_ticker)
|
| 206 |
country_fig = plot_etf_country_composition(country_df, st.session_state.etf_ticker)
|
|
@@ -214,7 +219,6 @@ def main():
|
|
| 214 |
if __name__ == "__main__":
|
| 215 |
main()
|
| 216 |
|
| 217 |
-
|
| 218 |
hide_streamlit_style = """
|
| 219 |
<style>
|
| 220 |
#MainMenu {visibility: hidden;}
|
|
|
|
| 5 |
import os
|
| 6 |
|
| 7 |
# Global API key and default ETF symbols
|
|
|
|
| 8 |
API_KEY = os.getenv("FMP_API_KEY")
|
| 9 |
|
| 10 |
ETF_ASSET_SYMBOL = "SPY" # Used for asset composition
|
| 11 |
+
ETF_SYMBOL = "QDVE.DE" # For sector and country composition, you may adjust this as needed.
|
|
|
|
|
|
|
| 12 |
|
| 13 |
def format_number(n):
|
| 14 |
"""
|
|
|
|
| 30 |
@st.cache_data(show_spinner=False)
|
| 31 |
def get_etf_asset_composition(etf_symbol: str) -> pd.DataFrame:
|
| 32 |
url = f"https://financialmodelingprep.com/api/v3/etf-holder/{etf_symbol}?apikey={API_KEY}"
|
| 33 |
+
try:
|
| 34 |
+
response = requests.get(url)
|
| 35 |
+
response.raise_for_status()
|
| 36 |
+
data = response.json()
|
| 37 |
+
if not data:
|
| 38 |
+
return pd.DataFrame()
|
| 39 |
+
df = pd.DataFrame(data)
|
| 40 |
+
df['marketValue'] = pd.to_numeric(df['marketValue'], errors='coerce')
|
| 41 |
+
df_sorted = df.sort_values(by="marketValue", ascending=False)
|
| 42 |
+
return df_sorted
|
| 43 |
+
except:
|
| 44 |
+
# Generic failover, no mention of the source
|
| 45 |
return pd.DataFrame()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
def plot_etf_asset_composition(df: pd.DataFrame, etf_symbol: str):
|
| 48 |
if df.empty:
|
|
|
|
| 77 |
@st.cache_data(show_spinner=False)
|
| 78 |
def get_etf_sector_composition(etf_symbol: str) -> pd.DataFrame:
|
| 79 |
url = f"https://financialmodelingprep.com/api/v3/etf-sector-weightings/{etf_symbol}?apikey={API_KEY}"
|
| 80 |
+
try:
|
| 81 |
+
response = requests.get(url)
|
| 82 |
+
response.raise_for_status()
|
| 83 |
+
data = response.json()
|
| 84 |
+
if not data:
|
| 85 |
+
return pd.DataFrame()
|
| 86 |
+
df = pd.DataFrame(data)
|
| 87 |
+
df['weightPercentage'] = df['weightPercentage'].str.rstrip('%').astype(float)
|
| 88 |
+
df_sorted = df.sort_values(by="weightPercentage", ascending=False)
|
| 89 |
+
return df_sorted
|
| 90 |
+
except:
|
| 91 |
return pd.DataFrame()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
|
| 93 |
def plot_etf_sector_composition(df: pd.DataFrame, etf_symbol: str):
|
| 94 |
if df.empty:
|
|
|
|
| 114 |
@st.cache_data(show_spinner=False)
|
| 115 |
def get_etf_country_composition(etf_symbol: str) -> pd.DataFrame:
|
| 116 |
url = f"https://financialmodelingprep.com/api/v3/etf-country-weightings/{etf_symbol}?apikey={API_KEY}"
|
| 117 |
+
try:
|
| 118 |
+
response = requests.get(url)
|
| 119 |
+
response.raise_for_status()
|
| 120 |
+
data = response.json()
|
| 121 |
+
if not data:
|
| 122 |
+
return pd.DataFrame()
|
| 123 |
+
df = pd.DataFrame(data)
|
| 124 |
+
df['weightPercentage'] = df['weightPercentage'].str.rstrip('%').astype(float)
|
| 125 |
+
df_sorted = df.sort_values(by="weightPercentage", ascending=False)
|
| 126 |
+
return df_sorted
|
| 127 |
+
except:
|
| 128 |
return pd.DataFrame()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
def plot_etf_country_composition(df: pd.DataFrame, etf_symbol: str):
|
| 131 |
if df.empty:
|
|
|
|
| 152 |
st.set_page_config(page_title="ETF Asset Composition Dashboard", layout="wide")
|
| 153 |
st.title("ETF Asset Composition Dashboard")
|
| 154 |
st.write(
|
| 155 |
+
"Analyze the composition of an ETF. "
|
| 156 |
"Use the side menu to enter the ETF ticker symbol and then click the 'Run ETF Composition' button. "
|
| 157 |
+
"The analysis is divided into three sections: Asset Composition, Sector Composition, and Country Composition. "
|
| 158 |
"Each section includes a chart and a data table. Hover over the charts for more details."
|
| 159 |
)
|
| 160 |
|
| 161 |
+
# Initialize run button state
|
| 162 |
if "run_etf" not in st.session_state:
|
| 163 |
st.session_state.run_etf = False
|
| 164 |
|
|
|
|
| 173 |
st.session_state.run_etf = True
|
| 174 |
st.session_state.etf_ticker = etf_ticker
|
| 175 |
|
| 176 |
+
# Main content
|
| 177 |
if st.session_state.get("run_etf", False):
|
| 178 |
# Section 1: ETF Asset Composition
|
| 179 |
st.header("1. ETF Asset Composition")
|
| 180 |
st.write(
|
| 181 |
+
"Shows the top holdings by market value. "
|
| 182 |
+
"The bar chart includes each asset's market value, shares held, and weight in the ETF."
|
|
|
|
| 183 |
)
|
| 184 |
asset_df = get_etf_asset_composition(st.session_state.etf_ticker)
|
| 185 |
asset_fig = plot_etf_asset_composition(asset_df, st.session_state.etf_ticker)
|
|
|
|
| 191 |
# Section 2: Sector Composition
|
| 192 |
st.header("2. Sector Composition")
|
| 193 |
st.write(
|
| 194 |
+
"Displays the ETF's sector weighting. "
|
| 195 |
+
"The bar chart visualizes each sector's percentage in the portfolio."
|
|
|
|
| 196 |
)
|
| 197 |
sector_df = get_etf_sector_composition(st.session_state.etf_ticker)
|
| 198 |
sector_fig = plot_etf_sector_composition(sector_df, st.session_state.etf_ticker)
|
|
|
|
| 204 |
# Section 3: Country Composition
|
| 205 |
st.header("3. Country Composition")
|
| 206 |
st.write(
|
| 207 |
+
"Shows the geographic distribution of the ETF's holdings by country. "
|
| 208 |
+
"The bar chart displays the weight percentage by country."
|
| 209 |
)
|
| 210 |
country_df = get_etf_country_composition(st.session_state.etf_ticker)
|
| 211 |
country_fig = plot_etf_country_composition(country_df, st.session_state.etf_ticker)
|
|
|
|
| 219 |
if __name__ == "__main__":
|
| 220 |
main()
|
| 221 |
|
|
|
|
| 222 |
hide_streamlit_style = """
|
| 223 |
<style>
|
| 224 |
#MainMenu {visibility: hidden;}
|