Optimization return
optimization_return is the weighted asset CAGR used by the solver's target-return constraint. Its basis is weighted_asset_cagr.
Public API · v1
Send a ticker set, receive an efficient frontier and machine-readable metric provenance.
One stateless request
The public endpoint accepts JSON directly from agents, scripts, and services. It requires no API key,
CSRF token, cookie, or prior session setup. All returns are decimal values, so 0.12 means 12%.
/api/v1/optimizeapplication/json01
Provide two or more unique symbols. The options object is optional.
curl --request POST "https://crafter.dlauinger.com/api/v1/optimize" \
--header "Content-Type: application/json" \
--data '{
"tickers": ["AAPL", "MSFT", "BND"],
"options": {
"risk_metric": "standard_deviation",
"num_years": 10,
"num_portfolios": 10
}
}'
GET /api/v1 for endpoint discovery or fetch the versioned OpenAPI 3.1 contract.
02
Unknown fields are rejected at both the root and options levels.
| Field | Type | Required | Rules |
|---|---|---|---|
tickers |
string[] | Yes | 2–20 unique symbols. Symbols are trimmed, validated, and normalized to uppercase; each is at most 15 characters. |
options |
object | No | May contain only the three fields below. |
options.risk_metric |
string | No | standard_deviation (default) or downside_deviation. |
options.num_years |
integer | No | 5–20 years; default 10. |
options.num_portfolios |
integer | No | 2–100 sampled portfolios; default 10. |
{
"tickers": ["AAPL", "MSFT"]
}
03
A successful response echoes the normalized request and places optimizer output in data.
data.discrete_resultsdata.efficient_frontierdata.best_sampled_ratio_portfolionull.data.tickers and data.missing_tickersweights array follows the exact order in data.tickers.data.*_basis{
"api_version": "v1",
"request": {
"tickers": ["AAPL", "MSFT", "BND"],
"options": {
"risk_metric": "standard_deviation",
"num_years": 10,
"num_portfolios": 10
}
},
"data": {
"tickers": ["AAPL", "MSFT", "BND"],
"start_date": "2016-08-29",
"end_date": "2026-08-28",
"discrete_results": [
{
"portfolio_cagr": 0.124,
"optimization_return": 0.119,
"portfolio_risk": 0.168,
"sharpe_ratio": 0.71,
"sharpe_ratio_status": "defined",
"weights": [0.42, 0.38, 0.20],
"feasible": true
}
],
"best_sampled_ratio_portfolio": {
"weights": [0.42, 0.38, 0.20],
"sharpe_ratio": 0.71
},
"ratio_selection_status": "defined",
"optimization_return_basis": "weighted_asset_cagr",
"portfolio_cagr_basis": "compounded_daily_returns",
"optimization_risk_basis": "annualized_covariance",
"reported_risk_basis": "annualized_standard_deviation"
}
}
The example is deliberately abbreviated; production responses also include ticker statistics, a correlation matrix, date-range detail, the full frontier, ratio bases, and the risk-free rate.
04
Do not substitute similarly named fields. Some metrics differ by design because their series and optimization goals differ.
optimization_return is the weighted asset CAGR used by the solver's target-return constraint. Its basis is weighted_asset_cagr.
portfolio_cagr compounds the combined weighted daily portfolio series. Its basis is compounded_daily_returns, so it can differ from optimization return.
The standard frontier minimizes annualized_covariance; reported volatility uses annualized_standard_deviation.
The downside frontier minimizes centered_zero_clipped_covariance. Reported downside deviation is conditional loss-period RMS severity. These are intentionally different.
Annualized arithmetic return minus annual risk-free rate, divided by annualized volatility.
Annualized mean daily excess over the daily-equivalent risk-free target, divided by all-period lower-partial-moment shortfall deviation.
null when its denominator is zero or otherwise undefined. Check its companion *_status field instead of coercing null to zero.
05
Every public API error uses one stable envelope.
{
"api_version": "v1",
"error": {
"code": "validation_error",
"message": "tickers must contain at least 2 unique ticker symbols"
}
}
| HTTP | error.code |
|---|---|
| 400 | validation_error |
| 404 | not_found |
| 405 | method_not_allowed |
| 413 | payload_too_large |
| 422 | optimization_failed |
| 429 | rate_limit_exceeded |
| 500 | internal_error |
The optimizer accepts JSON bodies up to 16 KiB and is limited to 6 public requests per 60 seconds per IP by default.
On HTTP 429, wait for the number of seconds in the Retry-After response header before retrying.
06
Keep orchestration small and let the response describe its own math.
Normalize and deduplicate candidate symbols before sending 2–20 tickers.
Choose the risk metric from the user's stated goal; do not infer that downside and volatility outputs are interchangeable.
Require a 2xx response, then verify data.missing_tickers and data.ratio_selection_status.
Pair each chosen value with its *_basis and *_status fields when explaining results.
Back off on 429 using Retry-After; do not retry validation errors without changing the request.
Portfolio Crafter uses historical data. The API output is not investment advice, and past performance does not predict future results.