D14-F01-A03 / Released engineering topic

Maximum Sharpe Ratio: Declare the Benchmark and Denominator

A production-minded guide to Maximum Sharpe Ratio.

Maximum Sharpe Ratio: Declare the Benchmark and DenominatorD14 / D14-F01

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

S(w)=eTwwTΣw,e=μrf1.S(w)=\frac{e^{\mathsf T}w}{\sqrt{w^{\mathsf T}\Sigma w}}, \qquad e=\mu-r_f\mathbf{1}.

The frozen feasible set is

Plain text
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:

ItemAmountCalculation
Before-event stock value$1,00010 × $100
Ex-date stock mark$98010 × $98
Dividend cash/receivable$2010 × $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

Plain text
Σ[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,

Plain text
√(wᵀΣw) ≤ Σᵢ wᵢ σᵢ,   σᵢ = √Σᵢᵢ

and the non-positive numerator gives

Plain text
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:

Plain text
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].

Plain text
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.

Synthetic Sharpe curve with its maximum at 60% A and 40% B

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:

Python
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

ScenarioExpected stateLesson
rf=.02, mu=[.01,.015]optimal, endpoint branch, Sharpe −1/30 at Ball non-positive excess is finite, not infeasible
rf=.02, mu=[.02,.02]optimal, non_unique, Sharpe 0compare objective and feasibility, not a made-up allocation identity
all-ones covarianceunsupported_singular_covariancesingular is outside this core even though variance is 1 everywhere on this simplex
materially indefinite covarianceinvalid_inputdo not regularize or clip into a variance
maxIterations=0numerical_issueno global certificate was earned
negative rfvalid if finite and same horizonno 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:

Shell
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:

Shell
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

  1. For the canonical diagonal covariance, raise rf from 2% to 5%.
  2. Set both expected returns equal to rf.
  3. 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

SituationMeaningRequired response
Every expected return is below rfThe best feasible Sharpe can be negativeReturn the least-negative certified ratio; do not call it infeasible.
Every expected return equals rfThe numerator is zero everywhereReport Sharpe zero and a non-unique solution class.
A zero-volatility direction has positive excess returnThe ratio can be unboundedRoute to an explicitly defined singular-covariance policy.
The risk-free observation has a different horizonNumerator mixes incomparable returnsConvert with a declared compounding convention first.
A high Sharpe appears after missing returns were filled with zeroDenominator may be artificially compressedRevisit missingness and stale-quote evidence.
FX conversion is omitted for a multi-currency portfolioBoth numerator and covariance can be wrongRebuild moments in the portfolio base currency.

Small glossary

TermMeaning here
Excess returnExpected portfolio return minus the same-horizon risk-free return.
VolatilitySquare root of portfolio variance under the supplied covariance.
Sharpe ratioExpected excess return divided by predicted volatility in this ex-ante topic.
Endpoint branchDirect comparison of simplex vertices when no asset has positive excess return.
SupportAssets with materially positive portfolio weights.
Unbounded ratioA positive numerator paired with a zero denominator; not a large finite optimum.

Continue through Family 01

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 notes

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

SourceRoleExact extent read and boundary
William F. Sharpe — The Sharpe RatioPrimary author explanation of differential return, predicted standard deviation, time dependence, annualization, and benchmark caveatsFull 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 PerformanceOriginal 1966 article provenanceStanford PDF opened as 20 pages, but text extraction exposed 0 lines in this pass. Used for attribution only.
Sharpe author article indexPrimary author bibliography contextOpened and used to identify the reprint; no formula copied from it.
Portfolio Optimization Book §7.2Technical corroboration for long-only fractional transformationsFull 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 OptimizationResearch context for moment estimation and decision sensitivityFull 488-line HTML opened; lines 34–97 and 282–323 read. A preprint, not a performance guarantee.
Sharpe DOI metadataBibliographic attributionDOI 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.

RankDestination observedExtent / access result
1Portfolio Optimization Book §7.2Full page, 52 lines read.
2Medium applied comparison103 AX lines / 8,892 characters; title, outline, setup and visible introductory sections; full body not exposed.
3Kaggle notebook5 AX lines / 522 characters; title only, notebook body unavailable.
4ScienceDirect articleHuman-verification/CAPTCHA page, 18 AX lines, 0 article lines; no challenge solved.
5Picture Perfect Portfolios587 AX lines / 56,100 characters; lines 36–101 read; secondary only.
6GitHub Sharpe-ratio-optimization224 AX lines / 12,883 characters; README lines 77–151 read; code not executed.
7Ryan O’Connell Finance349 AX lines / 21,876 characters; lines 16–167 read; no tool submission.
8Logical Invest Max SharpeCloudflare interstitial, 16 AX lines / 999 characters; unavailable and not bypassed.
9ResearchGate applied studyDevice-check interstitial, 6 AX lines / 1,181 characters; unavailable and not bypassed.
10Quantitative Finance Stack Exchange discussionDirect 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

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.

sharpe.ts
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;
Full-height labplaygroundOpen full screen
Written by

Fintech engineer building market-data and financial systems, and the author of every article, glossary record, and reference implementation on The Fintech Builder.