The Sharpe ratio is often displayed as one attractive number. That number is only interpretable when its return benchmark, volatility denominator, horizon, currency, and information cutoff are explicit. William F. Sharpe’s own explanation distinguishes expected differential return from predicted standard deviation and emphasizes time dependence (Sharpe, The Sharpe Ratio).
This tutorial builds one narrow, reproducible optimizer: fully invested, long-only risky assets, supplied same-horizon moments, and a finite risk-free return. Every example below is synthetic; none is a historical performance claim.
Before you start
This tutorial is for developers comfortable with weighted averages, vectors, and basic covariance. By the end you can derive a tangency portfolio, handle non-positive excess returns, and identify unsupported denominators. All percentages use one common horizon; .05 means 5%, and variance has squared-return units. No live market feed or numerical library is required to run the package examples.
The ratio is a declared contract
For weights w, expected asset returns mu, covariance Sigma, and a
risk-free return rf, define excess return e = mu − rf·1 and
The frozen feasible set is
1ᵀw = 1, wᵢ ≥ 0
There is no cash decision asset, shorting, leverage, fee, turnover, tax, or
downside-risk substitution in this variant. rf may be negative; it only has
to be finite and measured over the same horizon and in the same base currency
as mu and Sigma. Mixing a daily covariance with an annual risk-free return
creates a unit error before optimization begins.
What arrives before the optimizer
The core accepts assetIds, mu, Sigma, and rf. It does not infer a
history from a holdings snapshot. Before estimating moments, the upstream
record should distinguish:
- current valuation marks from acquisition cost;
- split-adjusted price and quantity, transformed consistently;
- local units, base currency, FX quote direction, rate date, and stale cutoff;
- cash dividends/receivables from raw-price and adjusted total-return returns;
- observation time, availability/knowledge time, revisions, and horizon.
A dividend can look like a loss in a holdings-only view
Investor.gov’s ex-dividend explanation is useful for separating entitlement and
payment timing (Investor.gov).
Consider this deliberately simple synthetic ledger: ten shares are marked at
$100 before a $2 cash dividend. On the ex-date the mark is $98, the stock
holding is $980, and the cash/receivable is $20:
| Item | Amount | Calculation |
|---|---|---|
| Before-event stock value | $1,000 | 10 × $100 |
| Ex-date stock mark | $980 | 10 × $98 |
| Dividend cash/receivable | $20 | 10 × $2 |
| Stock plus cash | $1,000 | $980 + $20 |
A holdings-only screen would show a $20 price decline while the simple
stock-plus-cash ledger has not changed. A price-return series, a cash ledger,
and a total-return/adjusted series are different inputs. Adding the $20 to an
already adjusted total-return series double counts income. The prior D02-F01-A03 Cash-Dividend Total-Return Adjustment
is relevant context for this accounting boundary;
this table is a new synthetic wealth reconciliation, not a copied historical
case or a claim about a particular investor account.
Why the solver enumerates supports
For a positive-excess support J, the homogeneous ratio has an interior
tangency direction from
Σ[J,J] x = e[J]
when the solution x is strictly positive. Normalizing x to sum to one gives
a long-only candidate. A boundary optimum is an interior optimum on a smaller
support, so the deterministic core enumerates every non-empty support through
12 assets and compares the directly recomputed ratio. The support enumeration
is a named implementation choice, not a claim that it is the only possible
method; a future SOCP/Dinkelbach implementation would need its own tests.
If every excess return is non-positive, the problem is not automatically infeasible. For a positive-semidefinite covariance,
√(wᵀΣw) ≤ Σᵢ wᵢ σᵢ, σᵢ = √Σᵢᵢ
and the non-positive numerator gives
S(w) ≤ Σᵢ wᵢ eᵢ / Σᵢ wᵢ σᵢ ≤ maxᵢ(eᵢ/σᵢ).
The pure asset attaining that endpoint bound is a global maximizer for this branch. A finite negative ratio is an answer; it is not a reason to return equal weights or to label the feasible simplex infeasible.
The independently calculated fixture
Use this small synthetic input:
assetIds = [A, B]
mu = [0.08, 0.11]
rf = 0.02
Sigma = [[0.01, 0.0],
[0.0, 0.0225]]
The excess vector is [0.06, 0.09]; therefore
Σ⁻¹e = [6,4] and normalization gives [0.6,0.4].
expected return = 0.6·0.08 + 0.4·0.11 = 0.092
excess return = 0.092 − 0.02 = 0.072
variance = 0.6²·0.01 + 0.4²·0.0225 = 0.0072
volatility = √0.0072 = 0.0848528137423857
Sharpe = 0.072 / 0.0848528137423857 = 0.848528137423857
Both pure endpoints have ratio .6 (.06/.10 and .09/.15), while the
interior support reaches .848528…. That comparison is true only for this
fixture; it is not an asset recommendation or a claim of outperformance.
The full-size ratio curve shows the same synthetic arithmetic. The purple point maximizes the computed ratio; the horizontal axis is portfolio weight, not elapsed time or a risk-free rate.
Run the calculation
From the topic directory, the Python implementation can be called directly:
from implementations.python.sharpe import maximum_sharpe
result = maximum_sharpe(
["A", "B"], [0.08, 0.11],
[[0.01, 0.0], [0.0, 0.0225]], 0.02,
)
print(result["status"], result["weights"], result["sharpe"])
# optimal [0.6, 0.4] 0.8485281374238569
The result includes the branch, covariance scale and eigen diagnostics, expected/excess return, variance/volatility, budget residual, solution class, and a named certificate. The TypeScript port and shared fixture provide a second-language parity check; neither status text nor a few stable iterations is treated as proof by itself.
Boundaries to exercise
| Scenario | Expected state | Lesson |
|---|---|---|
rf=.02, mu=[.01,.015] | optimal, endpoint branch, Sharpe −1/30 at B | all non-positive excess is finite, not infeasible |
rf=.02, mu=[.02,.02] | optimal, non_unique, Sharpe 0 | compare objective and feasibility, not a made-up allocation identity |
| all-ones covariance | unsupported_singular_covariance | singular is outside this core even though variance is 1 everywhere on this simplex |
| materially indefinite covariance | invalid_input | do not regularize or clip into a variance |
maxIterations=0 | numerical_issue | no global certificate was earned |
negative rf | valid if finite and same horizon | no unsupported positivity rule |
The core’s positive-definite boundary also prevents a denominator of zero. A future PSD variant must explicitly distinguish a positive-excess zero-risk direction, which makes the original ratio unbounded, from a merely singular but bounded case.
Related method and input lessons
The D01-F02-A04 Stale-Quote Detector
is the prior method to consult when a flat or stale observation may have
entered a moment estimate. In an independent synthetic four-row contrast,
clean aligned returns produce a two-asset GMV covariance and a nonzero risk
estimate; replacing one asset’s returns with imputed zeros creates a zero
variance direction and a spurious [1,0] GMV. That consequence is an
author-derived diagnostic, not proof that an unchanged quote is stale. Check
timestamps and missing-data evidence before treating the covariance as an
economic observation.
Reproduce, then challenge the answer
From this topic's root folder, run the dependency-free Python example and tests:
python -B examples/run.py
python -B -m unittest discover -s tests -p "test_*.py"
The earlier example and the packaged runnable example use the same canonical inputs.
For TypeScript, the runnable entry point imports the canonical core. The commands below use Node.js 22 and this repository’s TypeScript 7 compiler. Compile from the topic folder; if an older compiler rejects --ignoreConfig, omit that flag:
tsc --ignoreConfig --target ES2022 --module NodeNext --moduleResolution NodeNext --strict --skipLibCheck --outDir .topic-build examples/run.ts
node .topic-build/examples/run.js
The shared input fixture is byte-identical to the test fixture. The guided playground runs a bundled copy of the TypeScript core; preset names identify synthetic examples and deliberate failures.
Check your understanding
- For the canonical diagonal covariance, raise
rffrom 2% to 5%. - Set both expected returns equal to
rf. - Set covariance to zero.
Answers. The tangency direction becomes [3, 8/3], giving weights [9/17,8/17]. With zero excess returns every feasible portfolio has Sharpe zero. Zero covariance is outside this implementation's positive-definite contract and returns unsupported_singular_covariance; it must not be reported as an ordinary finite maximum.
The calculation path
Stop when validation fails. A successful numerical certificate verifies the declared model, not the quality of the market estimates supplied to it.
Summary
Maximum Sharpe is a ratio over a declared feasible set. The denominator,
risk-free horizon, covariance basis, and return history are part of the
algorithm. For the synthetic fixture, support enumeration returns
[.6,.4] and .848528…; the endpoint branch returns a finite negative ratio
when no asset clears rf; and singular or indefinite inputs stop with an
explicit status. A trustworthy result starts with reconciled moments and ends
with independently checked arithmetic.
Decision support: a ratio needs denominator governance
| Situation | Meaning | Required response |
|---|---|---|
Every expected return is below rf | The best feasible Sharpe can be negative | Return the least-negative certified ratio; do not call it infeasible. |
Every expected return equals rf | The numerator is zero everywhere | Report Sharpe zero and a non-unique solution class. |
| A zero-volatility direction has positive excess return | The ratio can be unbounded | Route to an explicitly defined singular-covariance policy. |
| The risk-free observation has a different horizon | Numerator mixes incomparable returns | Convert with a declared compounding convention first. |
| A high Sharpe appears after missing returns were filled with zero | Denominator may be artificially compressed | Revisit missingness and stale-quote evidence. |
| FX conversion is omitted for a multi-currency portfolio | Both numerator and covariance can be wrong | Rebuild moments in the portfolio base currency. |
Small glossary
| Term | Meaning here |
|---|---|
| Excess return | Expected portfolio return minus the same-horizon risk-free return. |
| Volatility | Square root of portfolio variance under the supplied covariance. |
| Sharpe ratio | Expected excess return divided by predicted volatility in this ex-ante topic. |
| Endpoint branch | Direct comparison of simplex vertices when no asset has positive excess return. |
| Support | Assets with materially positive portfolio weights. |
| Unbounded ratio | A positive numerator paired with a zero denominator; not a large finite optimum. |
Continue through Family 01
- Markowitz Mean-Variance uses a return floor rather than a ratio.
- Global Minimum Variance removes expected return and
rfentirely. - Mean-CVaR Optimization penalizes an explicit loss tail.
- Mean-Absolute-Deviation Optimization uses an LP over centered scenarios.
References
Technical source roles and observed access extents are listed in
REFERENCES.md. The article’s examples are synthetic or
author-derived; no historical market case is included.
Production handoff checklist
- Freeze the expected-return and covariance horizon together with the risk-free observation and compounding rule.
- Store excess return, volatility, ratio, covariance diagnostics, branch, and certificate—not weights alone.
- Exercise positive-, zero-, and negative-excess fixtures and the singular-denominator boundary.
- Revisit missingness and stale quotes whenever a denominator becomes unexpectedly small.
- Version changes to cash, borrowing, leverage, bounds, benchmark, or ex-post versus ex-ante interpretation.
ReferencesPrimary sources and evidence notesExpand the source trail, evidence role, and limitations behind the engineering choices.
Expand the source trail, evidence role, and limitations behind the engineering choices.
All Google and primary-source observations below were made or attempted on 2026-09-14 from Africa/Cairo. Google is a dated discovery audit, not evidence of ranking, quality, or performance.
Primary and official sources inspected
| Source | Role | Exact extent read and boundary |
|---|---|---|
| William F. Sharpe — The Sharpe Ratio | Primary author explanation of differential return, predicted standard deviation, time dependence, annualization, and benchmark caveats | Full 303-line HTML opened. Read lines 0–18, 19–50, 51–72, and 73–102. Formula images were not text-rendered; no claim relies on unread image content. |
| William F. Sharpe — Mutual Fund Performance | Original 1966 article provenance | Stanford PDF opened as 20 pages, but text extraction exposed 0 lines in this pass. Used for attribution only. |
| Sharpe author article index | Primary author bibliography context | Opened and used to identify the reprint; no formula copied from it. |
| Portfolio Optimization Book §7.2 | Technical corroboration for long-only fractional transformations | Full 52-line page opened and read, including bisection, Dinkelbach, and Schaible descriptions. Secondary exposition; implementation is independently tested. |
| Decision-Focused Learning for Mean-Variance Portfolio Optimization | Research context for moment estimation and decision sensitivity | Full 488-line HTML opened; lines 34–97 and 282–323 read. A preprint, not a performance guarantee. |
| Sharpe DOI metadata | Bibliographic attribution | DOI metadata verified; publisher full text not inspected. |
Google discovery audit
Search URL:
https://www.google.com/search?udm=14&q=maximum+Sharpe+ratio+portfolio+optimization&hl=en&gl=eg&num=10
The browser was Google Search Web results (udm=14), locale hl=en,
country gl=eg, Egypt location shown as Al Hay Al Asher/Nasr City. Ten organic
positions were observed; AI Mode, videos, News/Forums modules, and knowledge
panel text were excluded. The exact result-page order is time- and
locale-dependent.
| Rank | Destination observed | Extent / access result |
|---|---|---|
| 1 | Portfolio Optimization Book §7.2 | Full page, 52 lines read. |
| 2 | Medium applied comparison | 103 AX lines / 8,892 characters; title, outline, setup and visible introductory sections; full body not exposed. |
| 3 | Kaggle notebook | 5 AX lines / 522 characters; title only, notebook body unavailable. |
| 4 | ScienceDirect article | Human-verification/CAPTCHA page, 18 AX lines, 0 article lines; no challenge solved. |
| 5 | Picture Perfect Portfolios | 587 AX lines / 56,100 characters; lines 36–101 read; secondary only. |
| 6 | GitHub Sharpe-ratio-optimization | 224 AX lines / 12,883 characters; README lines 77–151 read; code not executed. |
| 7 | Ryan O’Connell Finance | 349 AX lines / 21,876 characters; lines 16–167 read; no tool submission. |
| 8 | Logical Invest Max Sharpe | Cloudflare interstitial, 16 AX lines / 999 characters; unavailable and not bypassed. |
| 9 | ResearchGate applied study | Device-check interstitial, 6 AX lines / 1,181 characters; unavailable and not bypassed. |
| 10 | Quantitative Finance Stack Exchange discussion | Direct open ended ERR_ABORTED; 0 readable article lines. |
No CAPTCHA, Cloudflare, device check, paywall, or access-control barrier was bypassed. Inaccessible destinations are retained as unavailable; no snippet is treated as primary proof.
Related data-quality sources
- SEC Investor Bulletin — Stock Splits — official split mechanics; used only for the upstream requirement that share count and per-share basis reconcile.
- Investor.gov — Ex-Dividend Dates — official entitlement/payment timing reference; no dividend is added to an adjusted total-return series in this core.
- D01-F02-A04 Stale-Quote Detector — prior method link for data-quality context; the D14 consequence fixture is synthetic and new.
The local claim ledger classifies source theory, synthetic calculations and implementation choices. The discovery table above is retained as a dated research record. This package does not include a historical market case.
Source roles
- Accessed: 2026-09-14, Google Web results and the linked pages from an Africa/Cairo session; each row above records its readable extent or blocker.
- Supports: Sharpe’s primary author explanation supports the benchmark, differential-return, volatility, and time-horizon conventions. Official Investor.gov and SEC pages support the dividend/split input-lineage boundary. The finite-support algorithm, fixtures, and arithmetic are implementation choices or author-derived synthetic calculations checked by tests.
- Limitations: inaccessible PDFs, publisher checks, and dynamic pages were not bypassed; no historical market data or performance result is established.
Independent recheck — 2026-09-16
Sharpe’s author-hosted 1994 article: inspected differential-return and horizon discussion.
Recheck scope: theory and attribution only. Fixtures and solver limits are author-derived calculations and implementation choices. No new historical-market or personal-experience evidence is asserted.
Full dependency-light reference implementations in both supported languages.
export interface MaximumSharpeResult {
status: string;
variant: string;
method: string;
branch: string | null;
assetIds: string[] | null;
mu: number[] | null;
riskFreeReturn: number | null;
weights: number[] | null;
expectedReturn: number | null;
excessReturn: number | null;
variance: number | null;
volatility: number | null;
sharpe: number | null;
budgetResidual: number | null;
lowerBoundResidual: number | null;
iterations: number;
maxIterations: number;
solutionClass: string;
diagnostics: Record<string, unknown>;
warnings: string[];
}
const DEFAULT_MAX_ITERATIONS = 10000;
const MAX_SUPPORT_ASSETS = 12;
const TOL_SYMMETRY = 1e-12;
const TOL_PSD = 1e-10;
const TOL_PD = 1e-10;
const TOL_EIGEN_RESIDUAL = 1e-12;
const TOL_OBJECTIVE = 1e-10;
function objectiveClose(left: number, right: number): boolean {
return Math.abs(left - right) <= TOL_OBJECTIVE * Math.max(Math.abs(left), Math.abs(right), 1e-300);
}
function finiteReal(value: unknown): value is number {
return typeof value === "number" && Number.isFinite(value);
}
function dot(left: number[], right: number[]): number {
return left.reduce((sum, value, index) => sum + value * right[index], 0);
}
function matVec(matrix: number[][], vector: number[]): number[] {
return matrix.map((row) => dot(row, vector));
}
function quadratic(matrix: number[][], vector: number[]): number {
return dot(vector, matVec(matrix, vector));
}
function maxAbs(matrix: number[][]): number {
return Math.max(0, ...matrix.flat().map((value) => Math.abs(value)));
}
function jacobiEigenvalues(matrix: number[][]): {
values: number[];
residual: number;
} {
const size = matrix.length;
if (size === 1)
return { values: [matrix[0][0]], residual: 0 };
const work = matrix.map((row) => row.slice());
const maxSweeps = Math.max(32, 8 * size * size);
for (let sweep = 0; sweep < maxSweeps; sweep += 1) {
let p = 0;
let q = 1;
let largest = 0;
for (let i = 0; i < size; i += 1) {
for (let j = i + 1; j < size; j += 1) {
if (Math.abs(work[i][j]) > largest) {
largest = Math.abs(work[i][j]);
p = i;
q = j;
}
}
}
if (largest <= 1e-15)
break;
const angle = 0.5 * Math.atan2(2 * work[p][q], work[q][q] - work[p][p]);
const cosine = Math.cos(angle);
const sine = Math.sin(angle);
for (let k = 0; k < size; k += 1) {
if (k === p || k === q)
continue;
const pk = work[p][k];
const qk = work[q][k];
work[p][k] = cosine * pk - sine * qk;
work[k][p] = work[p][k];
work[q][k] = sine * pk + cosine * qk;
work[k][q] = work[q][k];
}
const pp = work[p][p];
const qq = work[q][q];
const pq = work[p][q];
work[p][p] = cosine * cosine * pp - 2 * sine * cosine * pq + sine * sine * qq;
work[q][q] = sine * sine * pp + 2 * sine * cosine * pq + cosine * cosine * qq;
work[p][q] = 0;
work[q][p] = 0;
}
let residual = 0;
for (let i = 0; i < size; i += 1) {
for (let j = 0; j < size; j += 1) {
if (i !== j)
residual = Math.max(residual, Math.abs(work[i][j]));
}
}
return { values: work.map((row, index) => row[index]), residual };
}
function solveLinear(matrix: number[][], vector: number[]): number[] | null {
const size = vector.length;
const augmented = matrix.map((row, index) => [...row, vector[index]]);
for (let column = 0; column < size; column += 1) {
let pivot = column;
for (let row = column + 1; row < size; row += 1) {
if (Math.abs(augmented[row][column]) > Math.abs(augmented[pivot][column]))
pivot = row;
}
if (Math.abs(augmented[pivot][column]) <= 1e-14)
return null;
[augmented[column], augmented[pivot]] = [augmented[pivot], augmented[column]];
const divisor = augmented[column][column];
for (let j = column; j <= size; j += 1)
augmented[column][j] /= divisor;
for (let row = 0; row < size; row += 1) {
if (row === column)
continue;
const factor = augmented[row][column];
if (factor === 0)
continue;
for (let j = column; j <= size; j += 1)
augmented[row][j] -= factor * augmented[column][j];
}
}
return augmented.map((row) => row[size]);
}
function combinations(size: number, choose: number): number[][] {
const output: number[][] = [];
const current: number[] = [];
const visit = (start: number): void => {
if (current.length === choose) {
output.push(current.slice());
return;
}
for (let index = start; index <= size - (choose - current.length); index += 1) {
current.push(index);
visit(index + 1);
current.pop();
}
};
visit(0);
return output;
}
function failure(status: string, message: string, details: Partial<MaximumSharpeResult> = {}, diagnostics: Record<string, unknown> = {}): MaximumSharpeResult {
return {
status,
variant: "maximum-sharpe-long-only-pd",
method: "exhaustive-support-enumeration",
branch: null,
assetIds: details.assetIds ?? null,
mu: details.mu ?? null,
riskFreeReturn: details.riskFreeReturn ?? null,
weights: null,
expectedReturn: null,
excessReturn: null,
variance: null,
volatility: null,
sharpe: null,
budgetResidual: null,
lowerBoundResidual: null,
iterations: 0,
maxIterations: details.maxIterations ?? DEFAULT_MAX_ITERATIONS,
solutionClass: "unknown",
diagnostics: { message, ...diagnostics },
warnings: [],
};
}
function result(assetIds: string[], mu: number[], riskFreeReturn: number, weights: number[], covariance: number[][], covarianceScale: number, branch: string, iterations: number, maxIterations: number, solutionClass: string, diagnostics: Record<string, unknown>): MaximumSharpeResult {
const normalizedVariance = quadratic(covariance, weights);
const variance = normalizedVariance * covarianceScale;
if (!Number.isFinite(variance) || variance <= 0)
return failure("numerical_issue", "the selected covariance scale produced no finite positive denominator.", { assetIds, mu, riskFreeReturn }, { ...diagnostics, covarianceScale, variance });
const volatility = Math.sqrt(variance);
const expectedReturn = dot(mu, weights);
const excessReturn = mu.reduce((sum, value, index) => sum + (value - riskFreeReturn) * weights[index], 0);
if (!Number.isFinite(expectedReturn) || !Number.isFinite(excessReturn))
return failure("numerical_issue", "expected or excess return overflowed the finite arithmetic contract.", { assetIds, mu, riskFreeReturn }, { ...diagnostics, covarianceScale });
if (!Number.isFinite(excessReturn / volatility))
return failure("numerical_issue", "the Sharpe ratio is not finite at the selected denominator.", { assetIds, mu, riskFreeReturn }, { ...diagnostics, covarianceScale });
return {
status: "optimal",
variant: "maximum-sharpe-long-only-pd",
method: "exhaustive-support-enumeration",
branch,
assetIds,
mu,
riskFreeReturn,
weights,
expectedReturn,
excessReturn,
variance,
volatility,
sharpe: excessReturn / volatility,
budgetResidual: Math.abs(weights.reduce((sum, value) => sum + value, 0) - 1),
lowerBoundResidual: Math.max(0, -Math.min(...weights)),
iterations,
maxIterations,
solutionClass,
diagnostics: { covarianceScale, normalizedVariance, objectiveGap: 0, ...diagnostics },
warnings: [],
};
}
export function maximumSharpe(assetIdsInput: unknown, muInput: unknown, covarianceInput: unknown, riskFreeReturnInput: unknown, options: {
maxIterations?: unknown;
} = {}): MaximumSharpeResult {
if (!Array.isArray(assetIdsInput))
return failure("invalid_input", "assetIds must be an ordered array.");
const assetIds = assetIdsInput.slice();
if (!assetIds.length || assetIds.some((value) => typeof value !== "string" || value.trim() === ""))
return failure("invalid_input", "assetIds must contain non-empty strings.");
if (new Set(assetIds).size !== assetIds.length)
return failure("invalid_input", "assetIds must be unique.");
if (!Array.isArray(muInput) || muInput.length !== assetIds.length || muInput.some((value) => !finiteReal(value)))
return failure("invalid_input", "mu must be a finite vector aligned to assetIds.", { assetIds });
const mu = muInput.slice() as number[];
if (!finiteReal(riskFreeReturnInput))
return failure("invalid_input", "riskFreeReturn must be a finite same-horizon return.", { assetIds, mu });
const riskFreeReturn = riskFreeReturnInput;
const maxIterations = options.maxIterations ?? DEFAULT_MAX_ITERATIONS;
if (!Number.isInteger(maxIterations) || (maxIterations as number) < 0 || (maxIterations as number) > DEFAULT_MAX_ITERATIONS)
return failure("invalid_input", "maxIterations must be an integer from 0 through 10,000.", { assetIds, mu, riskFreeReturn });
const iterationBudget = maxIterations as number;
if (!Array.isArray(covarianceInput) || covarianceInput.length !== assetIds.length || covarianceInput.some((row) => !Array.isArray(row) || row.length !== assetIds.length))
return failure("invalid_input", "covariance must be a square matrix aligned to assetIds.", { assetIds, mu, riskFreeReturn });
if (covarianceInput.some((row) => (row as unknown[]).some((value) => !finiteReal(value))))
return failure("invalid_input", "covariance entries must be finite real numbers.", { assetIds, mu, riskFreeReturn });
const covariance = (covarianceInput as unknown[][]).map((row) => row.slice() as number[]);
const scale = maxAbs(covariance);
if (scale === 0)
return failure("unsupported_singular_covariance", "zero covariance has a zero-risk denominator.", { assetIds, mu, riskFreeReturn }, { covarianceScale: 0 });
const normalized = covariance.map((row) => row.map((value) => value / scale));
let symmetryResidual = 0;
for (let i = 0; i < assetIds.length; i += 1)
for (let j = 0; j < assetIds.length; j += 1)
symmetryResidual = Math.max(symmetryResidual, Math.abs(normalized[i][j] - normalized[j][i]));
if (symmetryResidual > TOL_SYMMETRY)
return failure("invalid_input", "covariance symmetry residual exceeds the normalized tolerance.", { assetIds, mu, riskFreeReturn }, { covarianceScale: scale, symmetryResidualS: symmetryResidual });
const symmetric = normalized.map((row, i) => row.map((value, j) => 0.5 * (value + normalized[j][i])));
const eigen = jacobiEigenvalues(symmetric);
const minimumEigenvalue = Math.min(...eigen.values);
const baseDiagnostics = { covarianceScale: scale, symmetryResidualS: symmetryResidual, minimumEigenvalueS: minimumEigenvalue, eigenResidualS: eigen.residual, tolPsd: TOL_PSD, tolPd: TOL_PD };
if (eigen.residual > TOL_EIGEN_RESIDUAL)
return failure("numerical_issue", "eigensolver residual exceeds the normalized tolerance.", { assetIds, mu, riskFreeReturn }, baseDiagnostics);
if (minimumEigenvalue < -TOL_PSD)
return failure("invalid_input", "covariance is materially indefinite.", { assetIds, mu, riskFreeReturn }, baseDiagnostics);
if (minimumEigenvalue <= TOL_PD)
return failure("unsupported_singular_covariance", "this ratio core requires positive-definite covariance.", { assetIds, mu, riskFreeReturn }, baseDiagnostics);
if (iterationBudget === 0)
return failure("numerical_issue", "maxIterations=0 cannot produce a ratio certificate.", { assetIds, mu, riskFreeReturn, maxIterations: iterationBudget }, baseDiagnostics);
const size = assetIds.length;
if (size > MAX_SUPPORT_ASSETS)
return failure("numerical_issue", "support enumeration is bounded at 12 assets in this deterministic core.", { assetIds, mu, riskFreeReturn }, { ...baseDiagnostics, maxSupportAssets: MAX_SUPPORT_ASSETS });
const excess = mu.map((value) => value - riskFreeReturn);
const diagonalVolatility = symmetric.map((row, index) => Math.sqrt(row[index]));
if (excess.every((value) => value <= 0)) {
const ratios = excess.map((value, index) => value / diagonalVolatility[index]);
const bestRatio = Math.max(...ratios);
const bestIndex = ratios.indexOf(bestRatio);
const ties = ratios.filter((value) => objectiveClose(value, bestRatio));
return result(assetIds, mu, riskFreeReturn, ratios.map((_value, index) => index === bestIndex ? 1 : 0), symmetric, scale, "nonpositive_excess_endpoint", 1, iterationBudget, ties.length > 1 ? "non_unique" : "unique", { ...baseDiagnostics, endpointRatios: ratios, certificate: "endpoint-excess-over-volatility-bound" });
}
let bestRatio = -Infinity;
let bestWeights: number[] | null = null;
let bestSupport: number[] = [];
let tiedSupports = 0;
let supportCount = 0;
const supportsTotal = (2 ** size) - 1;
let checkedSupports = 0;
for (let supportSize = 1; supportSize <= size; supportSize += 1) {
for (const support of combinations(size, supportSize)) {
if (checkedSupports >= iterationBudget)
break;
checkedSupports += 1;
const supportMatrix = support.map((i) => support.map((j) => symmetric[i][j]));
const direction = solveLinear(supportMatrix, support.map((i) => excess[i]));
if (direction === null || direction.some((value) => value <= 0))
continue;
const total = direction.reduce((sum, value) => sum + value, 0);
if (total <= 0)
continue;
const weights = Array.from({ length: size }, (_value, index) => { const position = support.indexOf(index); return position < 0 ? 0 : direction[position] / total; });
const variance = quadratic(symmetric, weights);
const numerator = dot(excess, weights);
if (variance <= 0 || numerator <= 0)
continue;
const ratio = numerator / Math.sqrt(variance);
supportCount += 1;
if (bestWeights === null || (ratio > bestRatio && !objectiveClose(ratio, bestRatio))) {
bestRatio = ratio;
bestWeights = weights;
bestSupport = support;
tiedSupports = 1;
}
else if (objectiveClose(ratio, bestRatio))
tiedSupports += 1;
}
if (checkedSupports >= iterationBudget)
break;
}
if (checkedSupports < supportsTotal)
return failure("numerical_issue", "maxIterations exhausted before all supports were checked.", { assetIds, mu, riskFreeReturn, iterations: checkedSupports, maxIterations: iterationBudget }, { ...baseDiagnostics, supportsChecked: checkedSupports, supportsRequired: supportsTotal, positiveExcessAssets: excess.filter((value) => value > 0).length });
if (bestWeights === null)
return failure("numerical_issue", "no positive-excess support produced a certified ratio.", { assetIds, mu, riskFreeReturn }, { ...baseDiagnostics, certificate: "not-certified", positiveExcessAssets: excess.filter((value) => value > 0).length });
return result(assetIds, mu, riskFreeReturn, bestWeights, symmetric, scale, "positive_excess_support", checkedSupports, iterationBudget, tiedSupports > 1 ? "non_unique" : "unique", { ...baseDiagnostics, certificate: "exhaustive-support-enumeration", support: bestSupport.map((index) => assetIds[index]), supportsChecked: checkedSupports, supportsRequired: supportsTotal, supportsCertified: supportCount, objectiveGap: 0 });
}
export const maximumSharpeRatio = maximumSharpe;
The embedded lab now expands to its full document height, keeping the article as the only scroll surface.
