Spaces:
Sleeping
Sleeping
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,440 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import pandas as pd
|
3 |
+
import numpy as np
|
4 |
+
import plotly.graph_objects as go
|
5 |
+
import plotly.express as px
|
6 |
+
from datetime import datetime, timedelta
|
7 |
+
import json
|
8 |
+
|
9 |
+
# Custom CSS with Tailwind-like utilities
|
10 |
+
def load_css():
|
11 |
+
st.markdown("""
|
12 |
+
<style>
|
13 |
+
/* Tailwind-inspired utilities */
|
14 |
+
.dashboard-card {
|
15 |
+
background-color: white;
|
16 |
+
border-radius: 0.5rem;
|
17 |
+
padding: 1.5rem;
|
18 |
+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
|
19 |
+
margin-bottom: 1rem;
|
20 |
+
}
|
21 |
+
.metric-card {
|
22 |
+
background-color: #f8fafc;
|
23 |
+
border-radius: 0.375rem;
|
24 |
+
padding: 1rem;
|
25 |
+
margin: 0.5rem 0;
|
26 |
+
border: 1px solid #e2e8f0;
|
27 |
+
}
|
28 |
+
.metric-title {
|
29 |
+
color: #64748b;
|
30 |
+
font-size: 0.875rem;
|
31 |
+
font-weight: 500;
|
32 |
+
margin-bottom: 0.5rem;
|
33 |
+
}
|
34 |
+
.metric-value {
|
35 |
+
color: #1e293b;
|
36 |
+
font-size: 1.5rem;
|
37 |
+
font-weight: 600;
|
38 |
+
}
|
39 |
+
.risk-high {
|
40 |
+
color: #dc2626;
|
41 |
+
}
|
42 |
+
.risk-moderate {
|
43 |
+
color: #d97706;
|
44 |
+
}
|
45 |
+
.risk-low {
|
46 |
+
color: #059669;
|
47 |
+
}
|
48 |
+
/* Custom Streamlit modifications */
|
49 |
+
.stApp {
|
50 |
+
background-color: #f1f5f9;
|
51 |
+
}
|
52 |
+
.css-1d391kg {
|
53 |
+
padding: 1rem 1rem;
|
54 |
+
}
|
55 |
+
</style>
|
56 |
+
""", unsafe_allow_html=True)
|
57 |
+
|
58 |
+
class FinancialDashboard:
|
59 |
+
def __init__(self):
|
60 |
+
self.risk_analyzer = FinancialRiskAnalyzer()
|
61 |
+
load_css()
|
62 |
+
|
63 |
+
def run(self):
|
64 |
+
st.set_page_config(
|
65 |
+
page_title="Financial Risk Analysis Dashboard",
|
66 |
+
page_icon="📊",
|
67 |
+
layout="wide"
|
68 |
+
)
|
69 |
+
|
70 |
+
# Sidebar
|
71 |
+
self.create_sidebar()
|
72 |
+
|
73 |
+
# Main dashboard
|
74 |
+
st.title("📊 Financial Risk Analysis Dashboard")
|
75 |
+
|
76 |
+
# Load sample or uploaded data
|
77 |
+
financial_data = self.load_financial_data()
|
78 |
+
|
79 |
+
if financial_data:
|
80 |
+
# Generate risk report
|
81 |
+
risk_report = self.risk_analyzer.generate_risk_report(financial_data)
|
82 |
+
|
83 |
+
# Display dashboard components
|
84 |
+
self.display_risk_summary(risk_report)
|
85 |
+
self.display_detailed_metrics(risk_report)
|
86 |
+
self.display_risk_charts(financial_data, risk_report)
|
87 |
+
self.display_recommendations(risk_report)
|
88 |
+
|
89 |
+
def create_sidebar(self):
|
90 |
+
with st.sidebar:
|
91 |
+
st.title("Controls & Filters")
|
92 |
+
|
93 |
+
# Date range selector
|
94 |
+
st.subheader("Date Range")
|
95 |
+
start_date = st.date_input(
|
96 |
+
"Start Date",
|
97 |
+
datetime.now() - timedelta(days=30)
|
98 |
+
)
|
99 |
+
end_date = st.date_input(
|
100 |
+
"End Date",
|
101 |
+
datetime.now()
|
102 |
+
)
|
103 |
+
|
104 |
+
# Risk threshold adjustments
|
105 |
+
st.subheader("Risk Thresholds")
|
106 |
+
leverage_threshold = st.slider(
|
107 |
+
"Leverage Ratio Threshold",
|
108 |
+
min_value=10.0,
|
109 |
+
max_value=50.0,
|
110 |
+
value=30.0
|
111 |
+
)
|
112 |
+
npl_threshold = st.slider(
|
113 |
+
"NPL Ratio Threshold (%)",
|
114 |
+
min_value=1.0,
|
115 |
+
max_value=10.0,
|
116 |
+
value=5.0
|
117 |
+
) / 100
|
118 |
+
|
119 |
+
# Export options
|
120 |
+
st.subheader("Export Options")
|
121 |
+
if st.button("Export Report (PDF)"):
|
122 |
+
st.info("Generating PDF report...")
|
123 |
+
# Add PDF export functionality
|
124 |
+
|
125 |
+
if st.button("Export Data (Excel)"):
|
126 |
+
st.info("Generating Excel file...")
|
127 |
+
# Add Excel export functionality
|
128 |
+
|
129 |
+
def load_financial_data(self):
|
130 |
+
# File upload option
|
131 |
+
uploaded_file = st.file_uploader(
|
132 |
+
"Upload financial data (JSON/CSV)",
|
133 |
+
type=["json", "csv"]
|
134 |
+
)
|
135 |
+
|
136 |
+
if uploaded_file:
|
137 |
+
try:
|
138 |
+
if uploaded_file.type == "application/json":
|
139 |
+
return json.load(uploaded_file)
|
140 |
+
else:
|
141 |
+
df = pd.read_csv(uploaded_file)
|
142 |
+
return df.to_dict()
|
143 |
+
except Exception as e:
|
144 |
+
st.error(f"Error loading file: {str(e)}")
|
145 |
+
return None
|
146 |
+
|
147 |
+
# Use sample data if no file uploaded
|
148 |
+
return self.get_sample_data()
|
149 |
+
|
150 |
+
def display_risk_summary(self, risk_report):
|
151 |
+
st.subheader("Risk Summary")
|
152 |
+
|
153 |
+
# Create three columns for key metrics
|
154 |
+
col1, col2, col3 = st.columns(3)
|
155 |
+
|
156 |
+
with col1:
|
157 |
+
self.metric_card(
|
158 |
+
"Overall Risk Level",
|
159 |
+
risk_report['risk_level'],
|
160 |
+
self.get_risk_color(risk_report['risk_level'])
|
161 |
+
)
|
162 |
+
|
163 |
+
with col2:
|
164 |
+
self.metric_card(
|
165 |
+
"Risk Score",
|
166 |
+
f"{risk_report['risk_score']:.2f}",
|
167 |
+
self.get_risk_color(risk_report['risk_level'])
|
168 |
+
)
|
169 |
+
|
170 |
+
with col3:
|
171 |
+
self.metric_card(
|
172 |
+
"Total Alerts",
|
173 |
+
len(risk_report['risk_alerts']),
|
174 |
+
"risk-moderate" if len(risk_report['risk_alerts']) > 0 else "risk-low"
|
175 |
+
)
|
176 |
+
|
177 |
+
# Risk Alerts
|
178 |
+
if risk_report['risk_alerts']:
|
179 |
+
st.markdown("### ⚠️ Risk Alerts")
|
180 |
+
for alert in risk_report['risk_alerts']:
|
181 |
+
st.warning(alert)
|
182 |
+
|
183 |
+
def display_detailed_metrics(self, risk_report):
|
184 |
+
st.subheader("Detailed Metrics")
|
185 |
+
|
186 |
+
# Create tabs for different metric categories
|
187 |
+
tabs = st.tabs([
|
188 |
+
"Basic Ratios",
|
189 |
+
"Funding Risk",
|
190 |
+
"Asset Quality",
|
191 |
+
"Market Risk",
|
192 |
+
"Operational Risk"
|
193 |
+
])
|
194 |
+
|
195 |
+
# Basic Ratios Tab
|
196 |
+
with tabs[0]:
|
197 |
+
metrics = risk_report['detailed_metrics']['basic_ratios']
|
198 |
+
self.create_metrics_grid(metrics)
|
199 |
+
|
200 |
+
# Funding Risk Tab
|
201 |
+
with tabs[1]:
|
202 |
+
metrics = risk_report['detailed_metrics']['funding_risks']
|
203 |
+
self.create_metrics_grid(metrics)
|
204 |
+
|
205 |
+
# Asset Quality Tab
|
206 |
+
with tabs[2]:
|
207 |
+
metrics = risk_report['detailed_metrics']['asset_risks']
|
208 |
+
self.create_metrics_grid(metrics)
|
209 |
+
|
210 |
+
# Market Risk Tab
|
211 |
+
with tabs[3]:
|
212 |
+
metrics = risk_report['detailed_metrics']['market_risks']
|
213 |
+
self.create_metrics_grid(metrics)
|
214 |
+
|
215 |
+
# Operational Risk Tab
|
216 |
+
with tabs[4]:
|
217 |
+
metrics = risk_report['detailed_metrics']['operational_risks']
|
218 |
+
self.create_metrics_grid(metrics)
|
219 |
+
|
220 |
+
def display_risk_charts(self, financial_data, risk_report):
|
221 |
+
st.subheader("Risk Analysis Charts")
|
222 |
+
|
223 |
+
# Create two columns for charts
|
224 |
+
col1, col2 = st.columns(2)
|
225 |
+
|
226 |
+
with col1:
|
227 |
+
# Radar chart for key risk indicators
|
228 |
+
self.create_radar_chart(risk_report)
|
229 |
+
|
230 |
+
with col2:
|
231 |
+
# Time series chart for trending metrics
|
232 |
+
self.create_trend_chart(financial_data)
|
233 |
+
|
234 |
+
# Additional charts in new row
|
235 |
+
col3, col4 = st.columns(2)
|
236 |
+
|
237 |
+
with col3:
|
238 |
+
# Asset composition pie chart
|
239 |
+
self.create_asset_composition_chart(financial_data)
|
240 |
+
|
241 |
+
with col4:
|
242 |
+
# Funding structure chart
|
243 |
+
self.create_funding_structure_chart(financial_data)
|
244 |
+
|
245 |
+
def display_recommendations(self, risk_report):
|
246 |
+
st.subheader("Recommendations & Actions")
|
247 |
+
|
248 |
+
# Generate recommendations based on risk levels
|
249 |
+
recommendations = self.generate_recommendations(risk_report)
|
250 |
+
|
251 |
+
for category, rec_list in recommendations.items():
|
252 |
+
with st.expander(f"📋 {category}"):
|
253 |
+
for rec in rec_list:
|
254 |
+
st.markdown(f"- {rec}")
|
255 |
+
|
256 |
+
def metric_card(self, title, value, risk_class):
|
257 |
+
st.markdown(f"""
|
258 |
+
<div class="metric-card">
|
259 |
+
<div class="metric-title">{title}</div>
|
260 |
+
<div class="metric-value {risk_class}">{value}</div>
|
261 |
+
</div>
|
262 |
+
""", unsafe_allow_html=True)
|
263 |
+
|
264 |
+
def create_metrics_grid(self, metrics):
|
265 |
+
cols = st.columns(2)
|
266 |
+
for idx, (metric, value) in enumerate(metrics.items()):
|
267 |
+
with cols[idx % 2]:
|
268 |
+
self.metric_card(
|
269 |
+
self.format_metric_name(metric),
|
270 |
+
f"{value:.2%}" if isinstance(value, float) else value,
|
271 |
+
self.get_metric_risk_color(metric, value)
|
272 |
+
)
|
273 |
+
|
274 |
+
def create_radar_chart(self, risk_report):
|
275 |
+
# Extract key risk indicators
|
276 |
+
metrics = risk_report['detailed_metrics']['basic_ratios']
|
277 |
+
|
278 |
+
fig = go.Figure()
|
279 |
+
|
280 |
+
categories = list(metrics.keys())
|
281 |
+
values = list(metrics.values())
|
282 |
+
|
283 |
+
fig.add_trace(go.Scatterpolar(
|
284 |
+
r=values,
|
285 |
+
theta=categories,
|
286 |
+
fill='toself',
|
287 |
+
name='Current'
|
288 |
+
))
|
289 |
+
|
290 |
+
fig.update_layout(
|
291 |
+
polar=dict(
|
292 |
+
radialaxis=dict(
|
293 |
+
visible=True,
|
294 |
+
range=[0, 1]
|
295 |
+
)
|
296 |
+
),
|
297 |
+
showlegend=False
|
298 |
+
)
|
299 |
+
|
300 |
+
st.plotly_chart(fig, use_container_width=True)
|
301 |
+
|
302 |
+
def create_trend_chart(self, financial_data):
|
303 |
+
# Create sample trend data
|
304 |
+
dates = pd.date_range(end=datetime.now(), periods=30, freq='D')
|
305 |
+
trend_data = pd.DataFrame({
|
306 |
+
'Date': dates,
|
307 |
+
'Risk Score': np.random.uniform(2, 6, 30)
|
308 |
+
})
|
309 |
+
|
310 |
+
fig = px.line(
|
311 |
+
trend_data,
|
312 |
+
x='Date',
|
313 |
+
y='Risk Score',
|
314 |
+
title='Risk Score Trend'
|
315 |
+
)
|
316 |
+
|
317 |
+
st.plotly_chart(fig, use_container_width=True)
|
318 |
+
|
319 |
+
def create_asset_composition_chart(self, financial_data):
|
320 |
+
# Extract asset composition
|
321 |
+
assets = {
|
322 |
+
'Corporate Loans': financial_data.get('corporate_loans', 0),
|
323 |
+
'Retail Loans': financial_data.get('retail_loans', 0),
|
324 |
+
'Securities': financial_data.get('securities', 0),
|
325 |
+
'Interbank Assets': financial_data.get('interbank_assets', 0)
|
326 |
+
}
|
327 |
+
|
328 |
+
fig = px.pie(
|
329 |
+
values=list(assets.values()),
|
330 |
+
names=list(assets.keys()),
|
331 |
+
title='Asset Composition'
|
332 |
+
)
|
333 |
+
|
334 |
+
st.plotly_chart(fig, use_container_width=True)
|
335 |
+
|
336 |
+
def create_funding_structure_chart(self, financial_data):
|
337 |
+
# Extract funding structure
|
338 |
+
funding = {
|
339 |
+
'Retail Deposits': financial_data.get('retail_deposits', 0),
|
340 |
+
'Corporate Deposits': financial_data.get('corporate_deposits', 0),
|
341 |
+
'Wholesale Funding': financial_data.get('wholesale_funding', 0),
|
342 |
+
'Interbank Borrowing': financial_data.get('interbank_borrowing', 0)
|
343 |
+
}
|
344 |
+
|
345 |
+
fig = px.bar(
|
346 |
+
x=list(funding.keys()),
|
347 |
+
y=list(funding.values()),
|
348 |
+
title='Funding Structure'
|
349 |
+
)
|
350 |
+
|
351 |
+
st.plotly_chart(fig, use_container_width=True)
|
352 |
+
|
353 |
+
@staticmethod
|
354 |
+
def get_risk_color(risk_level):
|
355 |
+
colors = {
|
356 |
+
'CRITICAL': 'risk-high',
|
357 |
+
'HIGH': 'risk-high',
|
358 |
+
'MODERATE': 'risk-moderate',
|
359 |
+
'LOW': 'risk-low'
|
360 |
+
}
|
361 |
+
return colors.get(risk_level, 'risk-low')
|
362 |
+
|
363 |
+
@staticmethod
|
364 |
+
def get_metric_risk_color(metric, value):
|
365 |
+
# Add logic to determine color based on metric type and value
|
366 |
+
return 'risk-moderate'
|
367 |
+
|
368 |
+
@staticmethod
|
369 |
+
def format_metric_name(metric):
|
370 |
+
return metric.replace('_', ' ').title()
|
371 |
+
|
372 |
+
@staticmethod
|
373 |
+
def generate_recommendations(risk_report):
|
374 |
+
recommendations = {
|
375 |
+
'Immediate Actions': [
|
376 |
+
'Review and adjust leverage levels',
|
377 |
+
'Strengthen liquidity buffers',
|
378 |
+
'Enhance risk monitoring systems'
|
379 |
+
],
|
380 |
+
'Medium-term Improvements': [
|
381 |
+
'Develop comprehensive risk management framework',
|
382 |
+
'Implement stress testing scenarios',
|
383 |
+
'Review counterparty exposure limits'
|
384 |
+
],
|
385 |
+
'Long-term Strategy': [
|
386 |
+
'Diversify funding sources',
|
387 |
+
'Strengthen capital adequacy',
|
388 |
+
'Enhance risk reporting systems'
|
389 |
+
]
|
390 |
+
}
|
391 |
+
return recommendations
|
392 |
+
|
393 |
+
@staticmethod
|
394 |
+
def get_sample_data():
|
395 |
+
return {
|
396 |
+
'total_debt': 500000000,
|
397 |
+
'equity': 150000000,
|
398 |
+
'non_performing_loans': 25000000,
|
399 |
+
'total_loans': 400000000,
|
400 |
+
'loan_loss_provisions': 15000000,
|
401 |
+
'total_assets': 700000000,
|
402 |
+
'current_assets': 200000000,
|
403 |
+
'current_liabilities': 180000000,
|
404 |
+
'total_capital': 120000000,
|
405 |
+
'risk_weighted_assets': 500000000,
|
406 |
+
'short_term_funding': 300000000,
|
407 |
+
'total_funding': 600000000,
|
408 |
+
'wholesale_funding': 200000000,
|
409 |
+
'retail_deposits': 250000000,
|
410 |
+
'corporate_deposits': 150000000,
|
411 |
+
'interbank_borrowing': 100000000,
|
412 |
+
'long_term_funding': 200000000,
|
413 |
+
'level_3_assets': 50000000,
|
414 |
+
'derivative_notional': 400000000,
|
415 |
+
'contingent_liabilities': 80000000,
|
416 |
+
'undrawn_commitments': 120000000,
|
417 |
+
'var_99': 10000000,
|
418 |
+
'interest_rate_gap': 30000000,
|
419 |
+
'net_forex_position': 15000000,
|
420 |
+
'market_correlation': 0.6,
|
421 |
+
'process_risk_score': 0.04,
|
422 |
+
'system_risk_score': 0.03,
|
423 |
+
'compliance_risk_score': 0.02,
|
424 |
+
'fraud_risk_score': 0.03,
|
425 |
+
'collateral_coverage': 0.85,
|
426 |
+
'current_npl': 25000000,
|
427 |
+
'previous_npl': 20000000,
|
428 |
+
'corporate_loans': 200000000,
|
429 |
+
'retail_loans': 150000000,
|
430 |
+
'securities': 100000000,
|
431 |
+
'interbank_assets': 50000000
|
432 |
+
}
|
433 |
+
|
434 |
+
# Main app file (app.py)
|
435 |
+
def main():
|
436 |
+
dashboard = FinancialDashboard()
|
437 |
+
dashboard.run()
|
438 |
+
|
439 |
+
if __name__ == "__main__":
|
440 |
+
main()
|