Results

Retrieve optimization results, performance metrics, and analytics

GET
/jobs/{run_id}/result

Retrieve the full optimization result for a completed job. Only available when job status is "succeeded". You must own the run.

Authentication

Required. Bearer token (JWT) or API key. You must be the owner of the run.

Path Parameters

ParameterTypeDescription
run_idstringThe unique identifier returned when the optimization job was created

Response Structure

Top-level Fields
FieldTypeDescription
run_idstringUnique identifier for this optimization run
statusstringAlways "succeeded" for results endpoint
timingobjectContains startedAt, finishedAt (ISO 8601), and durationSeconds
Methods Array

Each element in the methodsarray represents one optimization method's results:

FieldTypeDescription
methodstringOptimization method identifier, e.g. "MVO", "HRP"
weightsobjectTicker to weight mapping, e.g. {"INFY": 0.35, "RELIANCE": 0.65}
metricsobjectAll performance metrics (see Performance Metrics Reference below)
yearly_returnsobjectYear to return mapping for the benchmark, e.g. {"2021": 0.24}
portfolio_yearly_returnsobjectYear to return mapping for this portfolio method
rolling_betasobjectRolling period to beta value, e.g. {"60": 0.92, "120": 0.87}
compute_started_atstringISO 8601 timestamp when this method began computing
compute_finished_atstringISO 8601 timestamp when this method finished
compute_duration_secondsnumberWall-clock time to compute this method in seconds
method_labelstring?Display label (set when method is degraded, e.g. "MVO (RF-clipped)")
base_methodstring?Original method name before degradation
is_degradedboolean?True if risk-free rate was clipped to fit feasible region
degradationobject|string?Structured metadata or string describing the RF-clipping (type, reason, rates, epsilon)
optimization_risk_free_ratenumber?Clipped risk-free rate used in the optimizer (differs from global RF)
Response Object

The top-level response object contains aggregated data and signed URLs:

FieldTypeDescription
resultsobjectPer-method data: weights, performance metrics, compute timing, risk_attribution, rolling_backtest, sharpe_inference, and degradation metadata
cumulative_returnsarrayArray of cumulative return values for the portfolio over time
datesarrayArray of date strings corresponding to cumulative_returns
benchmark_returnsarrayArray of benchmark cumulative return values
stock_yearly_returnsobjectPer-stock yearly returns keyed by ticker
chart_bundlestring|objectSigned URL (historical) or inline object (live) with pre-computed chart data: cumulative returns, rolling correlation, covariance, spectral decomposition, risk attribution, stationarity tests
risk_free_ratenumberThe risk-free rate used for performance metric computations
start_datestringStart date of the backtest period
end_datestringEnd date of the backtest period
metaobjectContains run_id

Example Response

json
{
  "run_id": "opt_abc123def456",
  "status": "succeeded",
  "timing": {
    "startedAt": "2025-01-15T10:30:00.000Z",
    "finishedAt": "2025-01-15T10:30:47.000Z",
    "durationSeconds": 47.2
  },
  "methods": [
    {
      "method": "MVO",
      "weights": {
        "INFY": 0.2541,
        "RELIANCE": 0.3102,
        "TCS": 0.1893,
        "HDFCBANK": 0.2464
      },
      "metrics": {
        "expected_return": 0.1847,
        "cagr": 0.1623,
        "volatility": 0.2134,
        "max_drawdown": -0.2891,
        "sharpe": 0.8742,
        "sortino": 1.2156,
        "treynor_ratio": 0.1203,
        "omega_ratio": 1.4521,
        "calmar_ratio": 0.5614,
        "sterling_ratio": 0.4892,
        "v2_ratio": 1.0345,
        "information_ratio": 0.3241,
        "upside_potential_ratio": 1.8923,
        "modigliani_risk_adjusted_performance": 0.1456,
        "var_95": -0.0312,
        "cvar_95": -0.0487,
        "var_90": -0.0234,
        "cvar_90": -0.0356,
        "evar_95": -0.0423,
        "dar_95": -0.1845,
        "cdar_95": -0.2234,
        "portfolio_beta": 0.8734,
        "portfolio_alpha": 0.0523,
        "beta_pvalue": 0.0001,
        "r_squared": 0.7845,
        "blume_adjusted_beta": 0.9156,
        "welch_beta": 0.8612,
        "semi_beta": 0.9423,
        "vasicek_beta": 0.8891,
        "james_stein_beta": 0.8945,
        "skewness": -0.3421,
        "kurtosis": 4.2156,
        "entropy": 3.1245,
        "gini_mean_difference": 0.0187,
        "ulcer_index": 0.0923,
        "tracking_error": 0.0845,
        "upside_capture": 0.9234,
        "downside_capture": 0.7845,
        "effective_n": 3.42,
        "romad": 0.5614
      },
      "yearly_returns": {
        "2020": 0.1523,
        "2021": 0.2847,
        "2022": -0.0892,
        "2023": 0.1945,
        "2024": 0.1234
      },
      "portfolio_yearly_returns": {
        "2020": 0.1789,
        "2021": 0.3102,
        "2022": -0.0645,
        "2023": 0.2156,
        "2024": 0.1456
      },
      "rolling_betas": {
        "60": 0.9234,
        "120": 0.8745,
        "252": 0.8512
      },
      "compute_started_at": "2025-01-15T10:30:02.000Z",
      "compute_finished_at": "2025-01-15T10:30:15.000Z",
      "compute_duration_seconds": 13.4
    }
    // ... additional methods
  ],
  "response": {
    "results": {
      "MVO": {
        "returns_dist": "https://storage.googleapis.com/...signed-url...",
        "max_drawdown_plot": "https://storage.googleapis.com/...signed-url..."
      }
    },
    "cumulative_returns": [1.0, 1.002, 1.008, 1.003, ...],
    "dates": ["2020-01-02", "2020-01-03", "2020-01-06", ...],
    "benchmark_returns": [1.0, 1.001, 1.005, 0.998, ...],
    "stock_yearly_returns": {
      "INFY": { "2020": 0.85, "2021": 0.32, "2022": -0.12, ... },
      "RELIANCE": { "2020": 0.30, "2021": 0.18, "2022": 0.05, ... }
    },
    "covariance_heatmap": "https://storage.googleapis.com/...signed-url...",
    "risk_free_rate": 0.065,
    "start_date": "2020-01-01",
    "end_date": "2024-12-31",
    "meta": {
      "run_id": "opt_abc123def456",
      "timing": {
        "startedAt": "2025-01-15T10:30:00.000Z",
        "finishedAt": "2025-01-15T10:30:47.000Z",
        "durationSeconds": 47.2
      }
    }
  }
}

curl

bash
curl -X GET https://api.portfolioopt.in/jobs/opt_abc123def456/result \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Python

python
import requests

response = requests.get(
    "https://api.portfolioopt.in/jobs/opt_abc123def456/result",
    headers={"Authorization": "Bearer YOUR_JWT_TOKEN"}
)

result = response.json()

# Access MVO weights
mvo = next(m for m in result["methods"] if m["method"] == "MVO")
print("MVO Weights:", mvo["weights"])
print("Sharpe Ratio:", mvo["metrics"]["sharpe"])
print("Max Drawdown:", mvo["metrics"]["max_drawdown"])

# Access cumulative returns for charting
dates = result["response"]["dates"]
cum_returns = result["response"]["cumulative_returns"]

Performance Metrics Reference

Comprehensive reference of all metrics returned in the metrics object for each optimization method. Metrics are grouped by category.

Returns

KeyNameDescriptionTypical Range
expected_returnExpected ReturnAnnualized expected portfolio return based on historical mean0.05 - 0.30
cagrCAGRCompound Annual Growth Rate over the backtest period0.02 - 0.25

Risk

KeyNameDescriptionTypical Range
volatilityVolatilityAnnualized standard deviation of portfolio returns0.10 - 0.40
max_drawdownMaximum DrawdownLargest peak-to-trough decline during the backtest period-0.60 - 0.00
var_95VaR (95%)Value-at-Risk at 95% confidence level (daily)-0.05 - -0.01
cvar_95CVaR (95%)Conditional Value-at-Risk (Expected Shortfall) at 95%-0.08 - -0.01
var_90VaR (90%)Value-at-Risk at 90% confidence level (daily)-0.04 - -0.005
cvar_90CVaR (90%)Conditional Value-at-Risk at 90% confidence level-0.06 - -0.008
evar_95EVaR (95%)Entropic Value-at-Risk at 95% confidence level-0.07 - -0.01
dar_95DaR (95%)Drawdown-at-Risk at 95% confidence level-0.40 - -0.05
cdar_95CDaR (95%)Conditional Drawdown-at-Risk at 95% confidence level-0.50 - -0.08

Ratios

KeyNameDescriptionTypical Range
sharpeSharpe RatioRisk-adjusted return relative to the risk-free rate-1.0 - 3.0
sortinoSortino RatioDownside risk-adjusted return using semi-deviation-1.0 - 5.0
treynor_ratioTreynor RatioExcess return per unit of systematic (beta) risk-0.10 - 0.30
omega_ratioOmega RatioProbability-weighted ratio of gains to losses0.5 - 3.0
calmar_ratioCalmar RatioAnnualized return divided by maximum drawdown-1.0 - 5.0
sterling_ratioSterling RatioAnnualized return divided by average drawdown-1.0 - 4.0
v2_ratioV2 RatioRisk-adjusted performance using second-order lower partial moments-2.0 - 5.0
information_ratioInformation RatioActive return divided by tracking error vs. benchmark-2.0 - 2.0
upside_potential_ratioUpside Potential RatioRatio of upside potential to downside risk0.5 - 4.0
modigliani_risk_adjusted_performanceM² (Modigliani)Risk-adjusted performance normalized to benchmark volatility0.0 - 0.30

Beta & Regression

KeyNameDescriptionTypical Range
portfolio_betaPortfolio BetaSensitivity of portfolio returns to the benchmark0.3 - 1.5
portfolio_alphaPortfolio AlphaExcess return not explained by benchmark exposure (annualized)-0.10 - 0.15
beta_pvalueBeta p-valueStatistical significance of the beta estimate0.0 - 1.0
r_squaredR-squaredProportion of portfolio variance explained by the benchmark0.0 - 1.0
blume_adjusted_betaBlume Adjusted BetaBeta adjusted toward 1.0 using Blume's mean-reversion formula0.5 - 1.3
welch_betaWelch BetaBeta estimated using Welch's robust regression method0.3 - 1.5
semi_betaSemi BetaBeta calculated using only downside return observations0.3 - 2.0
vasicek_betaVasicek BetaBayesian-shrinkage beta using Vasicek's adjustment0.4 - 1.4
james_stein_betaJames-Stein BetaShrinkage estimator that reduces extreme beta estimates0.5 - 1.3

Distribution

KeyNameDescriptionTypical Range
skewnessSkewnessAsymmetry of the return distribution (negative = left tail)-2.0 - 1.0
kurtosisKurtosisTail heaviness of the return distribution (excess kurtosis)0.0 - 10.0
entropyEntropyInformation-theoretic measure of return distribution uncertainty0.0 - 5.0

Other

KeyNameDescriptionTypical Range
gini_mean_differenceGini Mean DifferenceAverage absolute difference between all pairs of returns0.005 - 0.05
ulcer_indexUlcer IndexRoot mean squared drawdown measuring sustained losses0.01 - 0.30
tracking_errorTracking ErrorStandard deviation of the portfolio's excess returns vs. benchmark0.02 - 0.25
upside_captureUpside CapturePercentage of benchmark gains captured in up markets0.5 - 1.5
downside_captureDownside CapturePercentage of benchmark losses captured in down markets0.3 - 1.3
effective_nEffective NEffective number of independent bets in the portfolio1.0 - N
romadRoMaDReturn over Maximum Drawdown-1.0 - 5.0

Analytics Endpoints

Dashboard summaries, run comparisons, and cached statistics for the analytics layer.

GET
/analytics/dashboard

Retrieve a comprehensive dashboard summary including method performance, benchmark stats, daily run counts, recent runs, saved comparisons, and auto-generated insights.

Authentication

Required. JWT only (not available via API key). Returns data scoped to the authenticated user.

Example Response

json
{
  "summary": {
    "totalRuns": 42,
    "succeededRuns": 38,
    "failedRuns": 3,
    "pendingRuns": 1,
    "avgDurationSeconds": 34.7,
    "totalStocksAnalyzed": 156
  },
  "methodPerformance": {
    "MVO": { "avgSharpe": 0.92, "avgReturn": 0.18, "runCount": 15 },
    "HRP": { "avgSharpe": 0.85, "avgReturn": 0.16, "runCount": 12 },
    ...
  },
  "benchmarkStats": {
    "cagr": 0.1245,
    "volatility": 0.1834,
    "sharpe": 0.6723
  },
  "dailyRunCounts": [
    { "date": "2025-01-10", "count": 3 },
    { "date": "2025-01-11", "count": 5 },
    ...
  ],
  "recentRuns": [
    {
      "runId": "opt_abc123",
      "status": "succeeded",
      "createdAt": "2025-01-15T10:30:00Z",
      "stocks": ["INFY", "RELIANCE", "TCS"],
      "methods": ["MVO", "HRP"]
    },
    ...
  ],
  "savedComparisons": [
    {
      "comparisonId": "cmp_xyz789",
      "name": "Q4 Strategy Comparison",
      "runCount": 3,
      "createdAt": "2025-01-12T08:00:00Z"
    }
  ],
  "insights": [
    "HRP has outperformed MVO by 2.3% CAGR over the last 10 runs",
    "Your average Sharpe ratio improved 12% this month"
  ]
}

curl

bash
curl -X GET https://api.portfolioopt.in/analytics/dashboard \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Python

python
import requests

response = requests.get(
    "https://api.portfolioopt.in/analytics/dashboard",
    headers={"Authorization": "Bearer YOUR_JWT_TOKEN"}
)

dashboard = response.json()
print("Total Runs:", dashboard["summary"]["totalRuns"])
print("Insights:", dashboard["insights"])
POST
/analytics/comparisons

Create a saved comparison to group multiple optimization runs for side-by-side analysis.

Authentication

Required. Bearer token (JWT) or API key.

Request Body

json
{
  "name": "Q4 2024 Strategy Comparison",
  "description": "Comparing MVO vs HRP across different stock sets",
  "runIds": ["opt_abc123", "opt_def456", "opt_ghi789"]
}

Response

json
{
  "comparisonId": "cmp_xyz789",
  "name": "Q4 2024 Strategy Comparison",
  "description": "Comparing MVO vs HRP across different stock sets",
  "runIds": ["opt_abc123", "opt_def456", "opt_ghi789"],
  "isPinned": false,
  "createdAt": "2025-01-15T12:00:00Z",
  "updatedAt": "2025-01-15T12:00:00Z"
}

curl

bash
curl -X POST https://api.portfolioopt.in/analytics/comparisons \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Q4 2024 Strategy Comparison",
    "description": "Comparing MVO vs HRP across different stock sets",
    "runIds": ["opt_abc123", "opt_def456", "opt_ghi789"]
  }'

Python

python
import requests

response = requests.post(
    "https://api.portfolioopt.in/analytics/comparisons",
    headers={"Authorization": "Bearer YOUR_JWT_TOKEN"},
    json={
        "name": "Q4 2024 Strategy Comparison",
        "description": "Comparing MVO vs HRP across different stock sets",
        "runIds": ["opt_abc123", "opt_def456", "opt_ghi789"]
    }
)

comparison = response.json()
print("Comparison ID:", comparison["comparisonId"])
GET
/analytics/comparisons/{comparison_id}

Retrieve the details of a saved comparison including all associated run data.

Authentication

Required. Bearer token (JWT) or API key.

Response

json
{
  "comparisonId": "cmp_xyz789",
  "name": "Q4 2024 Strategy Comparison",
  "description": "Comparing MVO vs HRP across different stock sets",
  "isPinned": false,
  "createdAt": "2025-01-15T12:00:00Z",
  "updatedAt": "2025-01-15T12:00:00Z",
  "runs": [
    {
      "runId": "opt_abc123",
      "status": "succeeded",
      "stocks": ["INFY", "RELIANCE", "TCS"],
      "methods": ["MVO", "HRP"],
      "createdAt": "2025-01-14T09:00:00Z",
      "metrics": { ... }
    },
    {
      "runId": "opt_def456",
      "status": "succeeded",
      "stocks": ["HDFCBANK", "ICICIBANK", "SBIN"],
      "methods": ["MVO", "MinCVaR"],
      "createdAt": "2025-01-14T11:30:00Z",
      "metrics": { ... }
    }
  ]
}

curl

bash
curl -X GET https://api.portfolioopt.in/analytics/comparisons/cmp_xyz789 \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Python

python
import requests

response = requests.get(
    "https://api.portfolioopt.in/analytics/comparisons/cmp_xyz789",
    headers={"Authorization": "Bearer YOUR_JWT_TOKEN"}
)

comparison = response.json()
for run in comparison["runs"]:
    print(f"Run {run['runId']}: {run['status']}, stocks={run['stocks']}")
PATCH
/analytics/comparisons/{comparison_id}

Update a saved comparison. All fields are optional -- only include the fields you want to change.

Authentication

Required. Bearer token (JWT) or API key.

Request Body

FieldTypeDescription
namestring?New name for the comparison
descriptionstring?New description
isPinnedboolean?Pin or unpin the comparison on the dashboard
addRunIdsstring[]?Run IDs to add to this comparison
removeRunIdsstring[]?Run IDs to remove from this comparison
json
{
  "name": "Updated Comparison Name",
  "isPinned": true,
  "addRunIds": ["opt_newrun001"],
  "removeRunIds": ["opt_ghi789"]
}

curl

bash
curl -X PATCH https://api.portfolioopt.in/analytics/comparisons/cmp_xyz789 \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Updated Comparison Name",
    "isPinned": true,
    "addRunIds": ["opt_newrun001"]
  }'

Python

python
import requests

response = requests.patch(
    "https://api.portfolioopt.in/analytics/comparisons/cmp_xyz789",
    headers={"Authorization": "Bearer YOUR_JWT_TOKEN"},
    json={
        "name": "Updated Comparison Name",
        "isPinned": True,
        "addRunIds": ["opt_newrun001"]
    }
)

print("Updated:", response.json())
DELETE
/analytics/comparisons/{comparison_id}

Permanently delete a saved comparison. This does not delete the underlying runs.

Authentication

Required. Bearer token (JWT) or API key.

Response

json
{
  "message": "Comparison deleted successfully"
}

curl

bash
curl -X DELETE https://api.portfolioopt.in/analytics/comparisons/cmp_xyz789 \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Python

python
import requests

response = requests.delete(
    "https://api.portfolioopt.in/analytics/comparisons/cmp_xyz789",
    headers={"Authorization": "Bearer YOUR_JWT_TOKEN"}
)

print(response.json()["message"])
POST
/analytics/refresh-stats

Force a refresh of cached analytics statistics. Use this after completing new runs if dashboard data appears stale.

Authentication

Required. Bearer token (JWT) or API key.

Response

json
{
  "message": "Statistics refreshed successfully",
  "refreshedAt": "2025-01-15T12:30:00Z"
}

curl

bash
curl -X POST https://api.portfolioopt.in/analytics/refresh-stats \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Python

python
import requests

response = requests.post(
    "https://api.portfolioopt.in/analytics/refresh-stats",
    headers={"Authorization": "Bearer YOUR_JWT_TOKEN"}
)

print("Refreshed at:", response.json()["refreshedAt"])

Tags

Organize and label your optimization runs with custom tags for filtering and grouping.

POST
/runs/{run_id}/tags

Add a tag to an optimization run. Tags have a name and optional color for visual identification.

Authentication

Required. Bearer token (JWT) or API key.

Request Body

json
{
  "tagName": "production",
  "tagColor": "#22c55e"
}

Response

json
{
  "message": "Tag added successfully",
  "tag": {
    "tagName": "production",
    "tagColor": "#22c55e",
    "addedAt": "2025-01-15T14:00:00Z"
  }
}

curl

bash
curl -X POST https://api.portfolioopt.in/runs/opt_abc123def456/tags \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"tagName": "production", "tagColor": "#22c55e"}'

Python

python
import requests

response = requests.post(
    "https://api.portfolioopt.in/runs/opt_abc123def456/tags",
    headers={"Authorization": "Bearer YOUR_JWT_TOKEN"},
    json={"tagName": "production", "tagColor": "#22c55e"}
)

print(response.json()["tag"])
DELETE
/runs/{run_id}/tags/{tag_name}

Remove a tag from an optimization run by tag name.

Authentication

Required. Bearer token (JWT) or API key.

Response

json
{
  "message": "Tag removed successfully"
}

curl

bash
curl -X DELETE https://api.portfolioopt.in/runs/opt_abc123def456/tags/production \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Python

python
import requests

response = requests.delete(
    "https://api.portfolioopt.in/runs/opt_abc123def456/tags/production",
    headers={"Authorization": "Bearer YOUR_JWT_TOKEN"}
)

print(response.json()["message"])
GET
/analytics/tags

List all unique tags across your optimization runs with usage counts.

Authentication

Required. Bearer token (JWT) or API key.

Response

json
{
  "tags": [
    { "tagName": "production", "tagColor": "#22c55e", "runCount": 8 },
    { "tagName": "experimental", "tagColor": "#f59e0b", "runCount": 5 },
    { "tagName": "backtest-q4", "tagColor": "#3b82f6", "runCount": 3 }
  ]
}

curl

bash
curl -X GET https://api.portfolioopt.in/analytics/tags \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Python

python
import requests

response = requests.get(
    "https://api.portfolioopt.in/analytics/tags",
    headers={"Authorization": "Bearer YOUR_JWT_TOKEN"}
)

for tag in response.json()["tags"]:
    print(f"{tag['tagName']} ({tag['runCount']} runs)")