Portfolio — equal-weight vs max-Sharpe optimisation
See reference.md for ticker formats.
Prerequisites
GET http://localhost:8000/health
Must return {"status": "ok"}. If not: tell user to run python backend/run.py.
Step 1 — Parse tickers
- Extract all ticker symbols from the message. Uppercase, strip whitespace.
- Minimum 2, maximum 10. Duplicates are rejected by the API — deduplicate silently and note it.
- If fewer than 2 tickers are provided, ask for more before proceeding.
Step 2 — Submit portfolio job
POST http://localhost:8000/api/v1/portfolio
Content-Type: application/json
{"tickers": ["T1", "T2", "..."], "query": "Optimise allocation for maximum risk-adjusted return."}
Response: {"job_id": "<uuid>", "status": "pending", "message": "..."}
Tell user:
Portfolio job submitted for {n} tickers. Fetching 1-year price history and optimising… typically 15–30 s.
Step 3 — Poll status
GET http://localhost:8000/api/v1/status/{job_id}
Every 5 s until completed or failed.
Step 4 — Fetch result
GET http://localhost:8000/api/v1/result/{job_id}
Error case
If the result contains a top-level error key (e.g. "At least two tickers with overlapping price history are required"), explain the issue in plain language and stop:
Portfolio optimisation requires at least two tickers with overlapping trading history over the past year. Check that all tickers are valid and have sufficient history.
Field map
| Field | Path | Notes |
|---|---|---|
| Tickers used | tickers | May differ from input if some had no data |
| Price rows | price_rows | Number of overlapping trading days |
| Equal weights | equal_weight.weights | {ticker: decimal} — multiply by 100 for % |
| Equal return | equal_weight.annual_return_pct | Already in % |
| Equal volatility | equal_weight.annual_volatility_pct | Already in % |
| Equal Sharpe | equal_weight.sharpe_ratio | Annualised, risk-free = 5% |
| Equal VaR | equal_weight.var_95_daily_pct | Already in %, daily |
| Optimal weights | optimal.weights | {ticker: decimal} — multiply by 100 for % |
| Optimal return | optimal.annual_return_pct | Already in % |
| Optimal volatility | optimal.annual_volatility_pct | Already in % |
| Optimal Sharpe | optimal.sharpe_ratio | Annualised, risk-free = 5% |
| Optimal VaR | optimal.var_95_daily_pct | Already in %, daily |
| Optimizer succeeded | optimal.optimizer_success | bool |
| Optimizer message | optimal.optimizer_message | Only relevant if success = false |
| Correlation matrix | correlation_matrix | Nested dict {ticker: {ticker: corr}} |
| Portfolio VaR | portfolio_var_95_daily_pct | Optimal portfolio daily VaR, already in % |
Step 5 — Interpret and present
Weights
Convert decimal weights to percentages (weight * 100, 1 decimal place).
If any weight is 0.0% in the optimal portfolio, note that the optimiser excluded that asset (insufficient return or high correlation with others).
Sharpe ratio
- < 0: portfolio returns less than the risk-free rate (5%); reconsider composition.
- 0–1: poor risk-adjusted return.
- 1–2: good.
-
2: excellent.
Compare equal-weight Sharpe vs optimal Sharpe. If optimal Sharpe is higher, the concentrated weights are justified. If similar (difference < 0.1), the equal-weight portfolio is nearly as efficient and simpler to rebalance.
Correlation matrix
Interpret the pairwise correlations:
-
0.80: highly correlated — these two assets move almost together; holding both adds little diversification.
- 0.50–0.80: moderate correlation — some co-movement, partial diversification benefit.
- < 0.30: low correlation — good diversification pair.
- Near 0 or negative: strong diversifier.
Identify the highest-correlated pair and the lowest-correlated pair and call them out in the narrative.
VaR
Daily VaR at 95% confidence: on the worst 1-in-20 trading days, the portfolio has historically lost approximately {portfolio_var_95_daily_pct}%. Annualise a rough estimate only if helpful: multiply daily VaR by √252 ≈ 15.9 to approximate annual VaR (note this is a rough approximation).
Optimizer failure
If optimal.optimizer_success = false, say:
The optimiser did not converge ({optimal.optimizer_message}). Optimal weights fell back to equal weights — treat the "optimal" column as equal weight.
Indian stocks in the portfolio
If any ticker ends in .NS or .BO, note that INR-denominated assets introduce FX risk for USD-based investors. The correlation matrix reflects local-currency returns and does not capture INR/USD volatility.
Step 6 — Recommendation
After presenting the data, give a 2–3 sentence recommendation:
- State whether to use equal-weight or optimal weights and why (Sharpe comparison).
- Name the top 1–2 holdings in the optimal portfolio and what drives their weight.
- Note any diversification concern (highly correlated pairs) or FX consideration.
Output
# Portfolio Analysis — {comma-separated tickers}
Price history: {price_rows} overlapping trading days (≈ {price_rows/252:.1f} years)
---
## Allocation Comparison
| Ticker | Equal weight | Optimal weight |
|----------|-------------|----------------|
| {ticker} | {ew*100:.1f}% | {ow*100:.1f}% |
| … | … | … |
---
## Portfolio Metrics
| Metric | Equal weight | Optimal (max Sharpe) |
|--------------------------|-------------|----------------------|
| Expected annual return | {eq_ret}% | {opt_ret}% |
| Annual volatility | {eq_vol}% | {opt_vol}% |
| Sharpe ratio | {eq_sh} | {opt_sh} |
| VaR (95%, 1-day) | {eq_var}% | {opt_var}% |
{If optimizer_success = false:
> ⚠ Optimiser did not converge ({optimizer_message}) — optimal weights equal equal weights.}
---
## Correlation Matrix
| Ticker | {t1} | {t2} | … |
|--------|------|------|---|
| {t1} | 1.00 | … | … |
| … | … | … | … |
**Most correlated pair:** {t_a} / {t_b} at {corr:.2f} — limited diversification benefit.
**Least correlated pair:** {t_c} / {t_d} at {corr:.2f} — strongest diversification benefit.
---
## Recommendation
{2–3 sentences: equal vs optimal choice, key holdings rationale, any diversification or FX caveat.}
{If any .NS/.BO tickers:
> ⚠ Indian stocks ({list}) are INR-denominated. The correlation matrix reflects local-currency returns and does not capture INR/USD exchange rate risk for USD-based investors.}
Error recovery
| Situation | Action |
|---|---|
Top-level error in result | Explain the issue; ask user to verify tickers and try again |
A ticker dropped from tickers list | Note it had insufficient or non-overlapping price history |
optimizer_success = false | Explain fallback to equal weights; still show the metrics |
Single .NS/.BO ticker | Note FX risk; show it in the correlation matrix as usual |