Commit
Β·
196cdf6
1
Parent(s):
13cc81b
Add stunning row styling and arrow indicators to Investment Performance table
Browse files- Added transparent green/red row backgrounds based on profit/loss
- Replaced +/- symbols with up/down arrow indicators (πΊ/π»)
- Added rounded corners and modern styling to table
- Converted from Dataframe to HTML component for full styling control
- Each row now has visual color coding: green for profits, red for losses
- Enhanced visual impact with gradient headers and hover effects
π€ Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
app.py
CHANGED
|
@@ -432,21 +432,77 @@ def refresh_investment_performance_table():
|
|
| 432 |
pl_dollars = sold_value - investment
|
| 433 |
pl_percent = (pl_dollars / investment * 100) if investment > 0 else 0
|
| 434 |
|
| 435 |
-
#
|
| 436 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 437 |
|
| 438 |
invested_data.append({
|
| 439 |
-
'Symbol':
|
| 440 |
'Status': status,
|
| 441 |
'IPO Price': f"${ipo_price:.2f}" if ipo_price > 0 else 'N/A',
|
| 442 |
'Buy Price': f"${avg_buy_price:.2f}",
|
| 443 |
'Sell Price': f"${avg_sell_price:.2f}" if avg_sell_price > 0 else 'N/A',
|
| 444 |
'Investment': f"${investment:.2f}",
|
| 445 |
-
'P&L ($)':
|
| 446 |
-
'P&L (%)':
|
|
|
|
| 447 |
})
|
| 448 |
|
| 449 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 450 |
|
| 451 |
def refresh_vm_stats():
|
| 452 |
"""Refresh VM statistics"""
|
|
@@ -782,6 +838,63 @@ custom_css = """
|
|
| 782 |
.status-wrong { color: #8b949e !important; }
|
| 783 |
.status-unknown { color: #ff0080 !important; }
|
| 784 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 785 |
.profit-positive { color: #00d647 !important; font-weight: 600 !important; }
|
| 786 |
.profit-negative { color: #ff0080 !important; font-weight: 600 !important; }
|
| 787 |
.profit-neutral { color: #8b949e !important; }
|
|
@@ -934,9 +1047,9 @@ def create_dashboard():
|
|
| 934 |
gr.Markdown("## π― IPO Investment Performance")
|
| 935 |
gr.Markdown("### Track profit/loss on your IPO investments with real-time market data")
|
| 936 |
|
| 937 |
-
investment_performance_table = gr.
|
| 938 |
-
label="IPO Investment P&L Analysis",
|
| 939 |
-
|
| 940 |
)
|
| 941 |
refresh_investment_btn = gr.Button("π Refresh Investment Performance", variant="primary", size="lg")
|
| 942 |
|
|
@@ -1070,7 +1183,7 @@ def create_dashboard():
|
|
| 1070 |
|
| 1071 |
# Investment Performance tab
|
| 1072 |
refresh_investment_btn.click(
|
| 1073 |
-
fn=
|
| 1074 |
outputs=[investment_performance_table]
|
| 1075 |
)
|
| 1076 |
|
|
|
|
| 432 |
pl_dollars = sold_value - investment
|
| 433 |
pl_percent = (pl_dollars / investment * 100) if investment > 0 else 0
|
| 434 |
|
| 435 |
+
# Format P&L with arrows and colors
|
| 436 |
+
if pl_dollars > 0:
|
| 437 |
+
pl_arrow = "πΊ"
|
| 438 |
+
pl_color = "#00d647"
|
| 439 |
+
row_bg = "rgba(0, 214, 71, 0.1)"
|
| 440 |
+
elif pl_dollars < 0:
|
| 441 |
+
pl_arrow = "π»"
|
| 442 |
+
pl_color = "#ff0080"
|
| 443 |
+
row_bg = "rgba(255, 0, 128, 0.1)"
|
| 444 |
+
else:
|
| 445 |
+
pl_arrow = ""
|
| 446 |
+
pl_color = "#8b949e"
|
| 447 |
+
row_bg = "rgba(139, 148, 158, 0.05)"
|
| 448 |
+
|
| 449 |
+
# Format P&L values with styled arrows
|
| 450 |
+
pl_dollar_str = f"<span style='color: {pl_color}; font-weight: 600;'>{pl_arrow} ${abs(pl_dollars):.2f}</span>"
|
| 451 |
+
pl_percent_str = f"<span style='color: {pl_color}; font-weight: 600;'>{pl_arrow} {abs(pl_percent):.2f}%</span>"
|
| 452 |
|
| 453 |
invested_data.append({
|
| 454 |
+
'Symbol': symbol,
|
| 455 |
'Status': status,
|
| 456 |
'IPO Price': f"${ipo_price:.2f}" if ipo_price > 0 else 'N/A',
|
| 457 |
'Buy Price': f"${avg_buy_price:.2f}",
|
| 458 |
'Sell Price': f"${avg_sell_price:.2f}" if avg_sell_price > 0 else 'N/A',
|
| 459 |
'Investment': f"${investment:.2f}",
|
| 460 |
+
'P&L ($)': pl_dollar_str,
|
| 461 |
+
'P&L (%)': pl_percent_str,
|
| 462 |
+
'_row_bg': row_bg # Store background color for styling
|
| 463 |
})
|
| 464 |
|
| 465 |
+
df = pd.DataFrame(invested_data)
|
| 466 |
+
return df
|
| 467 |
+
|
| 468 |
+
def refresh_investment_performance_html():
|
| 469 |
+
"""Return styled HTML table for investment performance"""
|
| 470 |
+
df = refresh_investment_performance_table()
|
| 471 |
+
|
| 472 |
+
if df.empty:
|
| 473 |
+
return "<div style='text-align: center; padding: 2rem; color: #666;'>No trading data available</div>"
|
| 474 |
+
|
| 475 |
+
# Build HTML table
|
| 476 |
+
html = '<table class="investment-table">'
|
| 477 |
+
|
| 478 |
+
# Header
|
| 479 |
+
html += '<thead><tr>'
|
| 480 |
+
for col in df.columns:
|
| 481 |
+
if not col.startswith('_'): # Skip internal columns
|
| 482 |
+
html += f'<th>{col}</th>'
|
| 483 |
+
html += '</tr></thead>'
|
| 484 |
+
|
| 485 |
+
# Body
|
| 486 |
+
html += '<tbody>'
|
| 487 |
+
for _, row in df.iterrows():
|
| 488 |
+
# Determine row class based on P&L
|
| 489 |
+
row_class = ""
|
| 490 |
+
pl_str = str(row.get('P&L ($)', ''))
|
| 491 |
+
if 'πΊ' in pl_str:
|
| 492 |
+
row_class = "profit-row"
|
| 493 |
+
elif 'π»' in pl_str:
|
| 494 |
+
row_class = "loss-row"
|
| 495 |
+
else:
|
| 496 |
+
row_class = "neutral-row"
|
| 497 |
+
|
| 498 |
+
html += f'<tr class="{row_class}">'
|
| 499 |
+
for col in df.columns:
|
| 500 |
+
if not col.startswith('_'): # Skip internal columns
|
| 501 |
+
html += f'<td>{row[col]}</td>'
|
| 502 |
+
html += '</tr>'
|
| 503 |
+
|
| 504 |
+
html += '</tbody></table>'
|
| 505 |
+
return html
|
| 506 |
|
| 507 |
def refresh_vm_stats():
|
| 508 |
"""Refresh VM statistics"""
|
|
|
|
| 838 |
.status-wrong { color: #8b949e !important; }
|
| 839 |
.status-unknown { color: #ff0080 !important; }
|
| 840 |
|
| 841 |
+
/* Investment Performance Table Styling */
|
| 842 |
+
.investment-table {
|
| 843 |
+
width: 100%;
|
| 844 |
+
border-collapse: separate;
|
| 845 |
+
border-spacing: 0;
|
| 846 |
+
background: white;
|
| 847 |
+
border-radius: 16px;
|
| 848 |
+
overflow: hidden;
|
| 849 |
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
| 850 |
+
}
|
| 851 |
+
|
| 852 |
+
.investment-table th {
|
| 853 |
+
background: linear-gradient(135deg, #0070f3 0%, #0051a5 100%);
|
| 854 |
+
color: white;
|
| 855 |
+
padding: 1rem;
|
| 856 |
+
font-weight: 600;
|
| 857 |
+
text-align: left;
|
| 858 |
+
border: none;
|
| 859 |
+
}
|
| 860 |
+
|
| 861 |
+
.investment-table th:first-child {
|
| 862 |
+
border-top-left-radius: 16px;
|
| 863 |
+
}
|
| 864 |
+
|
| 865 |
+
.investment-table th:last-child {
|
| 866 |
+
border-top-right-radius: 16px;
|
| 867 |
+
}
|
| 868 |
+
|
| 869 |
+
.investment-table td {
|
| 870 |
+
padding: 1rem;
|
| 871 |
+
border-bottom: 1px solid #f5f5f5;
|
| 872 |
+
font-weight: 500;
|
| 873 |
+
}
|
| 874 |
+
|
| 875 |
+
.profit-row {
|
| 876 |
+
background: rgba(0, 214, 71, 0.1) !important;
|
| 877 |
+
border-left: 4px solid #00d647;
|
| 878 |
+
}
|
| 879 |
+
|
| 880 |
+
.loss-row {
|
| 881 |
+
background: rgba(255, 0, 128, 0.1) !important;
|
| 882 |
+
border-left: 4px solid #ff0080;
|
| 883 |
+
}
|
| 884 |
+
|
| 885 |
+
.neutral-row {
|
| 886 |
+
background: rgba(139, 148, 158, 0.05) !important;
|
| 887 |
+
border-left: 4px solid #8b949e;
|
| 888 |
+
}
|
| 889 |
+
|
| 890 |
+
.investment-table tr:last-child td:first-child {
|
| 891 |
+
border-bottom-left-radius: 16px;
|
| 892 |
+
}
|
| 893 |
+
|
| 894 |
+
.investment-table tr:last-child td:last-child {
|
| 895 |
+
border-bottom-right-radius: 16px;
|
| 896 |
+
}
|
| 897 |
+
|
| 898 |
.profit-positive { color: #00d647 !important; font-weight: 600 !important; }
|
| 899 |
.profit-negative { color: #ff0080 !important; font-weight: 600 !important; }
|
| 900 |
.profit-neutral { color: #8b949e !important; }
|
|
|
|
| 1047 |
gr.Markdown("## π― IPO Investment Performance")
|
| 1048 |
gr.Markdown("### Track profit/loss on your IPO investments with real-time market data")
|
| 1049 |
|
| 1050 |
+
investment_performance_table = gr.HTML(
|
| 1051 |
+
label="IPO Investment P&L Analysis",
|
| 1052 |
+
value="<div style='text-align: center; padding: 2rem; color: #666;'>Click Refresh to load investment performance data</div>"
|
| 1053 |
)
|
| 1054 |
refresh_investment_btn = gr.Button("π Refresh Investment Performance", variant="primary", size="lg")
|
| 1055 |
|
|
|
|
| 1183 |
|
| 1184 |
# Investment Performance tab
|
| 1185 |
refresh_investment_btn.click(
|
| 1186 |
+
fn=refresh_investment_performance_html,
|
| 1187 |
outputs=[investment_performance_table]
|
| 1188 |
)
|
| 1189 |
|