Portfolio-Optimizer / src /email_report.py
shashankheg's picture
Initial portfolio optimizer pipeline
3a79665
Raw
History Blame Contribute Delete
7.07 kB
import os
from datetime import datetime
import re
from dotenv import load_dotenv
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail
load_dotenv()
def build_html_report (
portfolio: dict,
metrics: dict,
forecasts: dict,
sentiment: dict,
explanation: str
)->str :
"""Build a rich HTML email report."""
stock_rows = ""
for stock, weight in sorted(portfolio.items(), key=lambda x:x[1] , reverse=True):
forecast = forecasts.get(stock, {})
ret = forecast.get("return", 0)
vol = forecast.get("volatility", 0)
sig = sentiment.get(stock, {}).get("signal", "neutral")
color = "#00b894" if ret > 0 else "#d63031"
sig_icon = "🟢" if sig == "bullish" else \
"🔴" if sig == "bearish" else "⚪"
stock_rows += f"""
<tr>
<td style="padding:10px;font-weight:600">{stock}</td>
<td style="padding:10px;text-align:center">{weight:.1%}</td>
<td style="padding:10px;text-align:center;color:{color}">
{ret :.2%}
</td>
<td style="padding:10px;text-align:center">±{vol:.2%}</td>
<td style="padding:10px;text-align:center">{sig_icon} {sig}</td>
</tr>"""
return f"""
<html>
<body style="font-family:Arial,sans-serif;max-width:700px;
margin:0 auto;padding:20px;background:#f8fafc">
<div style="background:linear-gradient(135deg,#0D9488,#14B8A6);
padding:30px;border-radius:12px;text-align:center;color:white">
<h1 style="margin:0">📈 Daily Portfolio Report</h1>
<p style="margin:8px 0 0;opacity:0.9">
{datetime.now().strftime("%A, %B %d, %Y")}
</p>
</div>
<div style="display:grid;grid-template-columns:repeat(3,1fr);
gap:12px;margin:20px 0">
<div style="background:white;border:1px solid #e5e7eb;
border-radius:8px;padding:16px;text-align:center">
<p style="margin:0;color:#6b7280;font-size:12px">Expected Return</p>
<p style="margin:4px 0 0;font-size:22px;font-weight:700;color:#0D9488">
{metrics.get('expected_return', 0):+.2%}
</p>
</div>
<div style="background:white;border:1px solid #e5e7eb;
border-radius:8px;padding:16px;text-align:center">
<p style="margin:0;color:#6b7280;font-size:12px">Sharpe Ratio</p>
<p style="margin:4px 0 0;font-size:22px;font-weight:700;color:#0D9488">
{metrics.get('sharpe_ratio', 0):.2f}
</p>
</div>
<div style="background:white;border:1px solid #e5e7eb;
border-radius:8px;padding:16px;text-align:center">
<p style="margin:0;color:#6b7280;font-size:12px">Volatility</p>
<p style="margin:4px 0 0;font-size:22px;font-weight:700;color:#e17055">
{metrics.get('volatility', 0):.2%}
</p>
</div>
</div>
<h2 style="color:#134e4a">📊 Portfolio Allocation</h2>
<table style="width:100%;border-collapse:collapse;
background:white;border-radius:8px;overflow:hidden">
<thead>
<tr style="background:#0D9488;color:white">
<th style="padding:12px;text-align:left">Stock</th>
<th style="padding:12px">Weight</th>
<th style="padding:12px">Forecast</th>
<th style="padding:12px">Volatility</th>
<th style="padding:12px">Sentiment</th>
</tr>
</thead>
<tbody>{stock_rows}</tbody>
</table>
<h2 style="color:#134e4a">🤖 AI Portfolio Analysis</h2>
<div style="background:white;border-left:4px solid #0D9488;
padding:16px;border-radius:0 8px 8px 0">
<p style="margin:0;line-height:1.8;color:#374151">
{explanation}
</p>
</div>
<div style="text-align:center;margin-top:30px;
padding-top:20px;border-top:1px solid #e5e7eb">
<p style="color:#9ca3af;font-size:12px">
⚠️ This report is for informational purposes only.
Not financial advice.<br>
Generated by Portfolio Optimizer ·
LangChain · Groq · GitHub Actions
</p>
</div>
</body>
</html>
"""
def send_daily_report( results :dict)->bool :
"""
Send the dailyportfolio report via email
Args:
results:Pipeline results dict containing portfolio, mertics,etc
Returns :
True if sent successfully , False otherwise
"""
print("\n Sending daily portfolio report email ...")
html_content = build_html_report(
portfolio = results.get("portfolio", {}),
metrics = results.get("metrics", {}),
forecasts = results.get("forecasts", {}),
sentiment = results.get("sentiment", {}),
explanation = results.get("explanation", "No explanation available.")
)
sender_email = os.getenv("SENDER_EMAIL")
recipient_email = os.getenv("RECIPIENT_EMAIL")
api_key = os.getenv("SENDGRID_API_KEY")
if not all([sender_email, recipient_email, api_key]):
print(" ⚠️ Missing email configuration in .env — skipping email")
return False
message = Mail(
from_email = sender_email,
to_emails = recipient_email,
subject = f"📈 Daily Portfolio Report — "
f"{datetime.now().strftime('%B %d, %Y')}",
html_content = html_content
)
try:
sg = SendGridAPIClient(api_key)
response = sg.send(message)
print(f" ✅ Email sent successfully! Status: {response.status_code}")
return True
except Exception as e:
if hasattr(e, 'body'):
print(f" ❌ Email failed: {e.body}")
return False
if __name__ == "__main__":
# Test with mock data
mock_results = {
"portfolio": {"AAPL": 0.285, "MSFT": 0.321, "GOOGL": 0.154},
"metrics": {
"expected_return": 0.184,
"volatility": 0.092,
"sharpe_ratio": 1.82
},
"forecasts": {
"AAPL": {"return": 0.082, "volatility": 0.021},
"MSFT": {"return": 0.051, "volatility": 0.018},
"GOOGL": {"return": -0.023, "volatility": 0.032},
},
"sentiment": {
"AAPL": {"signal": "bullish"},
"MSFT": {"signal": "bullish"},
"GOOGL": {"signal": "bearish"},
},
"explanation": "This is a test explanation of the portfolio strategy."
}
send_daily_report(mock_results)