D14-F04-A05 / Released engineering topic

Tax-Aware Portfolio Optimization: Choose Trades and Lots Without Inventing Tax Rules

Scores which lots a fixed-size sale draws on, and separately solves the exact trade balancing return, tracking risk, cost and a declared illustrative lot score.

Tax-Aware Portfolio Optimization: Choose Trades and Lots Without Inventing Tax RulesD14 / D14-F04

Selling $100 of a stock does not identify which acquired shares leave the account. One share may have a $40 cost basis; another may have a $95 basis. The same sale proceeds can therefore produce very different gains before the applicable tax rules are considered.

My portfolio-data experience has taught me to question a suspicious price or quantity before trusting the resulting weights. Tax-aware allocation extends that input discipline into the holding's history: acquisition lots, adjusted basis, dates, and policy state. We will compare two lots, then solve a separate portfolio decision to see why choosing the cheapest lot does not answer whether the portfolio should trade.

You should already know a signed rebalance and the idea of a risk/return objective. We will calculate one lot choice, solve a separate whole-portfolio example, and audit what those synthetic numbers do not establish. This is education about model construction, not individualized tax or investment advice.

A holding is not a tax lot

At a valuation time, a holding says how many shares you own and what they are worth. A tax lot says how and when particular shares were acquired and what their adjusted basis is. A stock split can change share count and per-share basis while leaving the total economic holding unchanged; a current price decline does not by itself realize a loss. The trade must identify shares disposed of under the applicable policy. The backward split-adjustment lesson explains the price-series side of a split; its adjusted prices are not a substitute for a tax-lot basis ledger.

A small synthetic split example helps keep the units straight. Ten shares with $40 basis each have total basis $400. Under a simple two-for-one split with no other adjustment, the ledger becomes twenty shares with $20 basis each; total basis remains $400. Pairing the new quantity with the old per-share basis would falsely double it. An actual lot ledger must follow the applicable adjustment records and policy, rather than infer basis from a chart's adjusted closing price.

The Moehle–Kochenderfer–Boyd–Ang research paper describes a tax-aware portfolio construction problem that outputs both asset trades and the lots sold. Its full model is generally nonconvex; fixing each asset's buy/sell direction yields convex subproblems. That technical fact is a reason to report the solver variant and certificate, not to flatten all lots into one tax rate.

For a usable input record, keep these fields distinct:

FieldWhy it matters
Asset ID, account and owner scopeIdentifies holdings and which policy rules can interact
Trade date and data-as-of/knowledge timePrevents later transactions from leaking into a decision made today
Current price, currency and FX basisValues the proposed trade in one base currency
Lot shares, acquisition date and adjusted per-share basisDetermines potential gain/loss and holding-period classification
Policy jurisdiction, version/effective date and electionsSelects rules instead of inferring them from a ticker or reader location
Known/unknown replacement purchasesControls whether a loss treatment is established or still contingent

I would stop a real tax calculation when basis is missing, a corporate-action adjustment is suspect, or FX direction is unknown. “Assume zero basis” is not a safe missing-data policy. The split and quantity checks I have used in portfolio-data work motivate that gate; they do not establish anyone's tax return.

The small, explicit teaching score

First isolate a synthetic, one-currency, one-period rule. If lot j of asset i is marked at price p_i, has adjusted basis b_ij per share, and uses a declared illustrative coefficient ρ_ij, a sale of s_ij dollars has modeled score

Plain text
T_ij = ρ_ij (1 − b_ij/p_i)
lot score = T_ij s_ij
0 ≤ s_ij ≤ q_ij p_i,     Σ_j s_ij = dollars sold of asset i.

Here q_ij is the number of shares available in that lot. The ratio b/p converts a per-share gain to a fraction of sale proceeds. This score is an objective input, not an asserted tax bill. A negative score can stand for a possible loss benefit in this toy objective; it is not a cash refund, deductible-loss determination, or reason to credit the cash account. A purchase creates no immediate realized-sale score in this simple model.

With a fixed sale size and no cross-lot or cross-account coupling, taking available lots in ascending T_ij order minimizes this sum. That is a modeling result; whether those lots can be identified, and how gains/losses are actually treated, belongs to the supplied policy. For U.S. securities, IRS Publication 550 describes adequate identification, basis, and wash-sale rules. It does not make this toy least-score ranking a universal legal election.

One sale, two possible lots

On a fictional 2026-01-02 trade date, let asset A trade at $100 per share. Assume A1 was acquired before A2, so “oldest-first” means A1-first in this example. The coefficients below are chosen teaching inputs, not tax rates inferred from those acquisition times.

LotAvailable sharesBasis per shareIllustrative coefficientSale capacity
A1, older4$40.15$400
A2, newer2$95.35$200

For a $100 sale, one share is sold:

Lot soldCalculationToy score
A1.15 × (100−40)$9.00
A2.35 × (100−95)$1.75

The lower toy score comes from A2 despite its higher coefficient. A rigid “sell oldest” heuristic chooses A1 in this example. But the numerical comparison alone does not establish that a taxpayer may choose either lot: adequate-identification evidence and the applicable rules still matter.

Capacity matters as soon as the sale grows. For a $300 sale, the lower-score order sells both A2 shares for $200, then one A1 share for $100. Its score is 2×$1.75+$9=$12.50. Selling three older A1 shares instead scores 3×$9=$27. At $600, all six shares must be sold and both orders score $39.50. Lot ordering helps only while a choice remains; it cannot create additional low-basis or high-basis shares.

As a second fictional arithmetic probe, one share of B sells for $50 with adjusted basis $60 and illustrative ρ=.15. Its score is .15×(50−60)=−$1.50. Before any policy check, that is only a modeled benefit. A U.S. wash sale, for example, can affect loss deductibility if substantially identical securities are acquired within the relevant 30-day-before/after window under Publication 550. A decision made before the future side of that window closes cannot label the future state “clear.”

Lot choice is not the whole optimization

Choosing the least-score lot for a fixed sale does not tell us whether to sell the asset or how much to rebalance. One possible teaching utility is

Plain text
U(h,u) = aᵀu − γ_risk(h−h_b)ᵀV(h−h_b)
         − γ_tc C(u) − γ_tax L_policy(u),
h = h_init + u.

Here h and h_init are post/current dollar holdings; u is the signed dollar trade; h_b is a benchmark or target; a is an expected return contribution per trade dollar over one horizon; V is a risk matrix; C(u)=κᵀ|u| is a declared linear execution cost with per-dollar rates κ; and L_policy is the declared lot score. Scale V and the nonnegative γ coefficients so every utility term is comparable in dollars over that horizon (the toy V has inverse-dollar units). These are model parameters, not universal investor preferences.

For a separate synthetic exact oracle, let h_init=[500,500], h_b=[600,400], a=[.04,0], V=diag(.0001,.0001), γ_risk=γ_tax=1, and γ_tc=0. Each asset has 500 available one-dollar shares in a single toy lot; sale-score rates are T_A=.05 and T_B=.01. Treat the tax score as objective-only, with no immediate tax cash settlement. Buy and sale dollars offset under Σu=0, so u=[x,−x] for −500≤x≤500. This keeps the external cash balance unchanged while reallocating the holdings.

To see where the scalar objective comes from, buying x dollars of A moves the holdings to [500+x,500−x]. Deviations from the target are [x−100,−(x−100)]. The two diagonal risk terms therefore sum to .0002(x−100)². Expected trade return is .04x; selling B incurs score .01x when x≥0.

If x≥0, the trade buys A and sells B. The utility becomes U=.03x−.0002(x−100)². Differentiate: .03−.0004(x−100)=0, giving x=175. Final holdings are [675,325] and U=4.125. On the x≤0 branch, the model sells A and buys B when the trade is strictly negative; U=.09x−.0002(x−100)² increases through that branch and reaches its maximum at the shared no-trade boundary x=0, where U=−2. Thus [675,325] is the exact global optimum of this restricted two-asset toy problem. It is not a claim about a live taxpayer or future after-tax return.

A fixed-sale lot comparison is separate from a whole-portfolio trade decision

The left and right panels are deliberately different synthetic fixtures. A $1.75 lot score does not imply the $175 portfolio trade. Open the full-size tax-decision visual.

Cash and policy are separate ledgers

A tax score in the objective is not automatically tax paid on trade day. If the model only forecasts liability, set immediate tax_cash=0. If a documented amount really settles with the trade, state the settlement timing and reconcile cash_post = cash_init − Σu − execution_cost − tax_cash. The same tax amount should not be taken once from cash and then again from terminal wealth. A negative modeled loss score should not silently increase cash.

Real rules also differ across jurisdictions. The U.K.'s current HMRC share helpsheet discusses share matching and pooling; Canada's CRA guidance describes a superficial-loss rule with additional conditions. These are reasons to require a jurisdiction and effective policy date, not alternative rules silently implemented here. A richer engine must account for loss netting, carryovers, account/household scope, corporate actions, and later trades.

The diagram is a decision boundary, not a tax engine: a completed policy review still needs the account's actual rules and trade dates.

Try the lot-choice and policy-state lab. Increase the synthetic A sale beyond A2's $200 capacity, then compare the lower-score order with oldest-first. Switch to the loss case and notice that a −$1.50 objective score never becomes cash or a certified deduction. The lab also shows the separate exact $175 whole-portfolio oracle; it does not run a real tax-policy engine.

The distinction I would keep on a review sheet

QuestionData neededResult that can be stated
Which lot has the lowest toy score for a fixed sale?Valid shares, mark, adjusted basis, and declared score coefficientsConditional ranking under that model
Whether and how much should this portfolio trade?Current holdings, objective, risk, costs, lot capacities, and cash ruleSolver result with status and objective
What is actually reportable or deductible?Jurisdiction, taxpayer/account scope, elections, dates, replacements, and current rulesOnly the applicable policy process can decide

I would keep those three answers in separate output fields. A useful optimizer can report a conditional model result while clearly stating which policy evidence is still missing.

Try the decision before reading the answer

Use the lot-choice lab for a $300 A sale. Prediction: how much comes from A2 before its capacity runs out? Check: $200 from A2 and $100 from A1, giving $12.50, compared with $27 oldest-first.

Switch to the loss scenario. Its −$1.50 score remains the same when you mark a replacement purchase as known; the policy state changes. That is deliberate: a known purchase prompts review, and an unknown future window stays unverified. Neither makes a cash refund appear. Finally, switch to the separate whole-portfolio case and identify the output's units: $175 is a trade amount; 4.125 is its utility score.

Before calling a result usable

  1. Validate dated holdings, lot quantities, adjusted basis, acquisition dates, price and currency conversion; reject sales larger than available lots.
  2. Identify the jurisdiction, account scope, tax-policy version, election/identification evidence, and what is known as of the decision time.
  3. Keep predicted tax score separate from actual settlement cash and from the execution-cost ledger in Transaction-Cost-Aware Optimization.
  4. Record whether the solver found a global optimum, a direction-fixed solution, or only a relaxation/incumbent; publish its bound and gap where relevant.
  5. Mark a future loss-replacement window as unverified until the required information exists.

The historical-example decision is deferred: a named case would need publishable, dated lot records, corporate-action lineage, account/policy scope, and the law effective at the trade date. The two examples here are synthetic and independently calculable. The canonical contract and source record separate those teaching choices from the primary research and jurisdictional references.

ReferencesPrimary sources and evidence notes

Expand the source trail, evidence role, and limitations behind the engineering choices.

MOEHLE2021 — Tax-Aware Portfolio Construction via Convex Optimization

  • Authors: Nicholas Moehle, Mykel J. Kochenderfer, Stephen Boyd, and Andrew Ang.
  • Type/date: Original research, Journal of Optimization Theory and Applications 189, 364–383 (2021), DOI 10.1007/s10957-021-01823-0.
  • URL: Stanford paper page and author PDF.
  • Accessed: 2026-09-21.
  • Jurisdiction: Illustrative U.S.-like tax model, not a tax code.
  • Supports: Lot-level sale quantities, trade-list output, generally nonconvex tax-aware model, direction-fixed convexity, and approximate relaxation.
  • Limit: The article's numerical fixtures and simplified objective-only tax score are author-derived teaching choices; empirical performance is not claimed.

IRS-P550-2025 — Investment Income and Expenses

  • Organization: U.S. Internal Revenue Service.
  • Type/version: Official Publication 550, 2025 edition as displayed at access.
  • URL: Publication 550.
  • Accessed: 2026-09-21.
  • Jurisdiction: U.S. federal income tax, subject to taxpayer facts and later changes.
  • Supports: Basis/adequate identification and wash-sale provisions, including a window spanning 30 days before and after a loss sale.
  • Limit: Does not turn the toy least-score rule into a permitted lot election or establish a future trade's status.

HMRC-HS284-2026 — Shares and Capital Gains Tax

  • Organization: HM Revenue & Customs.
  • Type/version: Official HS284 collection, 2025–26 edition listed on the page, updated 6 April 2026.
  • URL: HS284 collection and current helpsheet.
  • Accessed: 2026-09-21.
  • Jurisdiction: United Kingdom.
  • Supports: A jurisdictional contrast involving share matching and Section 104 pooling.
  • Limit: This package does not compute U.K. gains or tax.

CRA-SUPERFICIAL — Capital losses

  • Organization: Canada Revenue Agency.
  • Type/version: Official online guidance, current at access.
  • URL: CRA capital-loss guidance.
  • Accessed: 2026-09-21.
  • Jurisdiction: Canada federal income tax.
  • Supports: A different loss-disallowance condition involving a 30-day before/after acquisition window, affiliation, and continued ownership.
  • Limit: This package does not implement Canada's rule.
tax-aware-portfolio.ts
/**
 * Tax-aware lot scoring and a restricted exact trade oracle.
 *
 * Two decisions live here and they are kept apart on purpose, because they
 * answer different questions.  `scoreFixedSale` ranks the acquisition lots
 * that can satisfy a sale whose dollar size is already fixed.
 * `optimizeTaxAwareTrade` decides whether and how much the portfolio should
 * trade at all.  The cheapest lot never implies a trade, and a worthwhile
 * trade never implies a lot, so neither routine calls the other's answer its
 * own.
 *
 * Everything tax-shaped in this package is a *declared illustrative
 * coefficient*.  The per-dollar lot score `T = rho * (1 - b / p)` weights a
 * sale by a number the caller supplies; it is an objective input, not an
 * asserted tax outcome.  A negative score stands for a possible loss benefit
 * under the toy objective only: it is never a refund, never a deduction, and
 * never credited to cash.  No jurisdiction's rules are implemented, so the
 * policy evidence a caller supplies is carried through and reported verbatim,
 * and whatever is missing is named rather than guessed.
 */

export const ABSOLUTE_TOLERANCE = 1e-9;
export const RELATIVE_TOLERANCE = 2e-8;
export const CAPACITY_TOLERANCE = 1e-9;
export const PSD_RELATIVE_TOLERANCE = 1e-10;

/** The exact oracle's declared scope.  See `optimizeTaxAwareTrade`. */
export const ORACLE_ASSETS = 2;
export const ORACLE_LOTS_PER_ASSET = 1;

export const FILL_ORDERS = ["lowest-score", "oldest-first"] as const;
export const SETTLEMENT_MODES = ["objective-only-forecast", "settles-at-trade"] as const;

export type FillOrder = (typeof FILL_ORDERS)[number];
export type SettlementMode = (typeof SETTLEMENT_MODES)[number];

const LOT_FIELDS = ["lotId", "shares", "basisPerShare", "illustrativeRate", "saleScoreRate", "acquiredOn"];
const POLICY_FIELDS = [
  "jurisdiction",
  "policyVersion",
  "policyEffectiveDate",
  "identificationEvidence",
  "replacementPurchaseKnown",
];

/**
 * This package models; it never concludes.  Even a complete evidence record
 * only makes the model result quotable alongside the evidence.
 */
export const POLICY_CONCLUSION = "conditional-model-result-only";

const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;

export class TaxAwareValidationError extends Error {
  readonly code: string;
  constructor(code: string, message: string) {
    super(message);
    this.name = "TaxAwareValidationError";
    this.code = code;
  }
}

export class TaxAwareNumericalError extends Error {
  readonly code: string;
  constructor(code: string, message: string) {
    super(message);
    this.name = "TaxAwareNumericalError";
    this.code = code;
  }
}

export interface PolicyState {
  jurisdiction: string | null;
  policyVersion: string | null;
  policyEffectiveDate: string | null;
  identificationEvidence: string | null;
  replacementPurchaseKnown: boolean | null;
  missing: string[];
  evidenceComplete: boolean;
  lossReplacementWindow: "known_replacement_review_required" | "future_window_unverified";
  conclusion: string;
}

export interface LotFill {
  lotId: string;
  acquiredOn: string;
  sharesAvailable: number;
  basisPerShare: number | null;
  illustrativeRate: number | null;
  perDollarScore: number;
  capacity: number;
  saleDollars: number;
  sharesSold: number;
  remainingCapacity: number;
  lotScore: number;
}

export interface FixedSaleResult {
  assetId: string;
  tradeDate: string;
  price: number;
  fillOrder: FillOrder;
  saleDollars: number;
  sharesSold: number;
  totalCapacity: number;
  remainingCapacity: number;
  totalScore: number;
  fillSequence: string[];
  lots: LotFill[];
  settlementCash: number;
  scoreIsObjectiveOnly: true;
  policy: PolicyState;
  certificate: {
    kind: "separable-lot-score-fill";
    fillOrder: FillOrder;
    minimizesDeclaredScore: boolean;
    scope: string;
    note: string;
  };
}

export interface CashLedger {
  settlement: SettlementMode;
  settlementDate: string | null;
  cashInit: number;
  tradeTotal: number;
  executionCost: number;
  taxCash: number;
  cashPost: number;
  holdingsTotalPost: number | null;
  portfolioValuePost: number | null;
  taxChargedTo: "cash-settlement" | "objective-score-only";
  note: string;
}

export interface BranchRecord {
  branch: "buy-first-sell-second" | "sell-first-buy-second";
  interval: [number, number];
  quadraticCoefficient: number;
  linearCoefficient: number;
  constant: number;
  stationaryX: number | null;
  stationaryInInterval: boolean;
  x: number;
  binding: "stationary" | "lower-bound" | "upper-bound";
  utility: number;
}

export interface TradeResult {
  status: "optimal";
  tradeDate: string;
  trade: number[];
  tradeTotal: number;
  holdings: number[];
  direction: "buy-first-sell-second" | "sell-first-buy-second" | "no-trade";
  utility: number;
  terms: {
    expectedReturn: number;
    riskPenalty: number;
    executionCostPenalty: number;
    taxScorePenalty: number;
  };
  executionCost: number;
  taxScore: number;
  saleCapacities: number[];
  lotSales: {
    asset: number;
    price: number;
    saleDollars: number;
    totalCapacity: number;
    fillSequence: string[];
    totalScore: number;
    lots: LotFill[];
  } | null;
  branches: BranchRecord[];
  cash: CashLedger;
  policy: PolicyState;
  certificate: {
    kind: "direction-fixed-branch-enumeration";
    scope: string;
    branchesChecked: number;
    complete: boolean;
    globalOptimality: string;
    taxModel: string;
  };
}

export interface SplitCheck {
  splitRatio: number;
  sharesBefore: number;
  basisPerShareBefore: number;
  totalBasisBefore: number;
  sharesAfter: number;
  basisPerShareAfter: number;
  totalBasisAfter: number;
  totalBasisPreserved: boolean;
  stalePairingTotalBasis: number;
  stalePairingOverstatement: number;
}

interface LotRecord {
  lotId: string;
  acquiredOn: string;
  acquiredOrdinal: number;
  declaredIndex: number;
  sharesAvailable: number;
  basisPerShare: number | null;
  illustrativeRate: number | null;
  perDollarScore: number;
  capacity: number;
}

function invalid(code: string, message: string): never {
  throw new TaxAwareValidationError(code, message);
}

function isPlainRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

function asReal(value: unknown, name: string): number {
  if (typeof value !== "number" || !Number.isFinite(value)) {
    invalid("invalid-input", `${name} must be a finite real number.`);
  }
  return value;
}

function vector(value: unknown, name: string): number[] {
  if (!Array.isArray(value) || value.length === 0) {
    invalid("invalid-input", `${name} must be a non-empty numeric array.`);
  }
  return value.map((entry, index) => asReal(entry, `${name}[${index}]`));
}

function matrix(value: unknown, name: string, size: number): number[][] {
  if (!Array.isArray(value) || value.length !== size) {
    invalid("invalid-input", `${name} must have ${size} rows.`);
  }
  return value.map((row, rowIndex) => {
    if (!Array.isArray(row) || row.length !== size) {
      invalid("invalid-input", `${name} must be ${size} by ${size}.`);
    }
    return row.map((entry, column) => asReal(entry, `${name}[${rowIndex}][${column}]`));
  });
}

function stableSum(values: readonly number[]): number {
  let total = 0;
  let compensation = 0;
  for (const value of values) {
    const candidate = total + value;
    if (Math.abs(total) >= Math.abs(value)) compensation += (total - candidate) + value;
    else compensation += (value - candidate) + total;
    total = candidate;
  }
  return total + compensation;
}

function dot(left: readonly number[], right: readonly number[]): number {
  return stableSum(left.map((value, index) => value * right[index]));
}

function quadraticForm(
  matrixValue: readonly (readonly number[])[], left: readonly number[], right: readonly number[],
): number {
  return stableSum(left.map((value, row) => value * dot(matrixValue[row], right)));
}

function text(value: unknown, name: string, code = "invalid-input"): string {
  if (typeof value !== "string" || value.trim() === "") {
    invalid(code, `${name} must be a non-empty string.`);
  }
  return value;
}

/** Return the verbatim date and a day ordinal for ordering comparisons. */
function isoDate(value: unknown, name: string): [string, number] {
  if (typeof value !== "string" || !ISO_DATE.test(value)) {
    invalid("invalid-date", `${name} must be an ISO calendar date written YYYY-MM-DD.`);
  }
  const [year, month, day] = value.split("-").map(Number);
  const stamp = new Date(0);
  stamp.setUTCFullYear(year, month - 1, day);
  stamp.setUTCHours(0, 0, 0, 0);
  if (stamp.getUTCFullYear() !== year || stamp.getUTCMonth() !== month - 1 || stamp.getUTCDate() !== day) {
    invalid("invalid-date", `${name} is not a real calendar date.`);
  }
  return [value, Math.round(stamp.getTime() / 86400000)];
}

/**
 * Closed-form smallest eigenvalue of a symmetric 2x2 matrix.  The oracle's
 * declared scope is two assets, so the 2x2 characteristic root is exact and
 * needs no iterative sweep.
 */
function minimumSymmetricEigenvalue2x2(matrixValue: readonly (readonly number[])[]): number {
  const halfTrace = 0.5 * (matrixValue[0][0] + matrixValue[1][1]);
  const halfGap = 0.5 * (matrixValue[0][0] - matrixValue[1][1]);
  return halfTrace - Math.hypot(halfGap, matrixValue[0][1]);
}

/**
 * Normalize the supplied policy evidence without inferring any of it.
 *
 * Jurisdiction, policy version and effective date, identification evidence,
 * and whether a replacement purchase is known are all caller-supplied facts.
 * Anything absent is reported in `missing` rather than defaulted, because a
 * ticker, a price, or the reader's location cannot select a tax rule.
 */
export function reviewPolicyState(policy?: unknown): PolicyState {
  if (policy !== undefined && policy !== null && !isPlainRecord(policy)) {
    invalid("invalid-policy", "policy must be an object of declared evidence fields.");
  }
  const supplied: Record<string, unknown> = isPlainRecord(policy) ? policy : {};
  const unknown = Object.keys(supplied).filter((key) => !POLICY_FIELDS.includes(key)).sort();
  if (unknown.length) {
    invalid(
      "unknown-policy-field",
      `unrecognized policy fields ${JSON.stringify(unknown)}; declared fields are ${JSON.stringify([...POLICY_FIELDS].sort())}.`,
    );
  }

  const jurisdiction = supplied.jurisdiction === undefined || supplied.jurisdiction === null
    ? null : text(supplied.jurisdiction, "policy.jurisdiction", "invalid-policy");
  const version = supplied.policyVersion === undefined || supplied.policyVersion === null
    ? null : text(supplied.policyVersion, "policy.policyVersion", "invalid-policy");
  const effectiveDate = supplied.policyEffectiveDate === undefined || supplied.policyEffectiveDate === null
    ? null : isoDate(supplied.policyEffectiveDate, "policy.policyEffectiveDate")[0];
  const evidence = supplied.identificationEvidence === undefined || supplied.identificationEvidence === null
    ? null : text(supplied.identificationEvidence, "policy.identificationEvidence", "invalid-policy");
  const replacementValue = supplied.replacementPurchaseKnown;
  if (replacementValue !== undefined && replacementValue !== null && typeof replacementValue !== "boolean") {
    invalid(
      "invalid-policy",
      "policy.replacementPurchaseKnown must be true, false, or omitted when it is not known.",
    );
  }
  // Narrow on the boolean itself rather than on the absence of one. The guard
  // above has already refused every other type, but it does so inside a helper,
  // so the compiler still carries `unknown` past it.
  const replacement = typeof replacementValue === "boolean" ? replacementValue : null;

  const missing = ([
    ["jurisdiction", jurisdiction],
    ["policyVersion", version],
    ["policyEffectiveDate", effectiveDate],
    ["identificationEvidence", evidence],
    ["replacementPurchaseKnown", replacement],
  ] as const).filter(([, value]) => value === null).map(([field]) => field as string);
  // Declaring that no replacement purchase is known today does not close the
  // future side of a replacement window, so the state stays unverified either
  // way.  A known purchase only raises the question; it never answers it.
  const window = replacement === true ? "known_replacement_review_required" : "future_window_unverified";
  return {
    jurisdiction, policyVersion: version, policyEffectiveDate: effectiveDate,
    identificationEvidence: evidence, replacementPurchaseKnown: replacement,
    missing, evidenceComplete: missing.length === 0,
    lossReplacementWindow: window, conclusion: POLICY_CONCLUSION,
  };
}

/**
 * Re-express one lot after a simple share split, preserving total basis.
 *
 * A split changes the share count and the per-share basis together.  The
 * check that matters is that the product does not move: pairing the new
 * quantity with the pre-split per-share basis is the classic way to double a
 * cost basis that never changed.
 */
export function applySplitToLot(shares: unknown, basisPerShare: unknown, splitRatio: unknown): SplitCheck {
  const quantity = asReal(shares, "shares");
  const basis = asReal(basisPerShare, "basisPerShare");
  const ratio = asReal(splitRatio, "splitRatio");
  if (quantity < 0) invalid("invalid-lot-shares", "shares must be nonnegative.");
  if (basis < 0) invalid("invalid-basis", "basisPerShare must be nonnegative.");
  if (ratio <= 0) invalid("invalid-split-ratio", "splitRatio must be positive; it is new shares per old share.");

  const sharesAfter = quantity * ratio;
  const basisAfter = basis / ratio;
  const totalBefore = quantity * basis;
  const totalAfter = sharesAfter * basisAfter;
  const stale = sharesAfter * basis;
  const scale = Math.max(1, Math.abs(totalBefore));
  return {
    splitRatio: ratio,
    sharesBefore: quantity, basisPerShareBefore: basis, totalBasisBefore: totalBefore,
    sharesAfter, basisPerShareAfter: basisAfter, totalBasisAfter: totalAfter,
    totalBasisPreserved: Math.abs(totalAfter - totalBefore) <= RELATIVE_TOLERANCE * scale,
    // What the ledger would say if the new quantity kept the old per-share
    // basis.  It is the error this check exists to catch, never a basis.
    stalePairingTotalBasis: stale,
    stalePairingOverstatement: stale - totalBefore,
  };
}

function normalizeLot(entry: unknown, index: number, price: number, tradeOrdinal: number, label: string): LotRecord {
  const name = `${label}[${index}]`;
  if (!isPlainRecord(entry)) invalid("invalid-lot", `${name} must be an object describing one acquisition lot.`);
  const unknown = Object.keys(entry).filter((key) => !LOT_FIELDS.includes(key)).sort();
  if (unknown.length) {
    invalid(
      "unknown-lot-field",
      `${name} has unrecognized fields ${JSON.stringify(unknown)}; the coefficient field is 'illustrativeRate' `
      + "because it is a declared teaching weight, not a tax rate this package can look up.",
    );
  }
  const lotId = text(entry.lotId, `${name}.lotId`, "invalid-lot");
  const shares = asReal(entry.shares, `${name}.shares`);
  if (shares < 0) invalid("invalid-lot-shares", `${name}.shares must be nonnegative.`);
  const [acquiredOn, acquiredOrdinal] = isoDate(entry.acquiredOn, `${name}.acquiredOn`);
  if (acquiredOrdinal > tradeOrdinal) {
    invalid(
      "lot-acquired-after-trade-date",
      `${name} was acquired after the trade date; a later acquisition cannot inform today's decision.`,
    );
  }

  const declared = entry.saleScoreRate;
  const rawBasis = entry.basisPerShare;
  const rawRate = entry.illustrativeRate;
  let basisPerShare: number | null = null;
  let illustrativeRate: number | null = null;
  let perDollarScore: number;
  if (declared !== undefined && declared !== null) {
    if ((rawBasis !== undefined && rawBasis !== null) || (rawRate !== undefined && rawRate !== null)) {
      invalid(
        "ambiguous-lot-score",
        `${name} declares saleScoreRate as well as basis/coefficient inputs; supply one form only.`,
      );
    }
    perDollarScore = asReal(declared, `${name}.saleScoreRate`);
  } else {
    if (rawBasis === undefined || rawBasis === null) {
      invalid(
        "missing-basis",
        `${name}.basisPerShare is missing; assuming zero basis is not a safe missing-data policy.`,
      );
    }
    if (rawRate === undefined || rawRate === null) {
      invalid(
        "missing-coefficient",
        `${name}.illustrativeRate is missing; the coefficient is declared by the caller and is never inferred `
        + "from a ticker, an acquisition date, or a reader's location.",
      );
    }
    basisPerShare = asReal(rawBasis, `${name}.basisPerShare`);
    illustrativeRate = asReal(rawRate, `${name}.illustrativeRate`);
    if (basisPerShare < 0) invalid("invalid-basis", `${name}.basisPerShare must be nonnegative.`);
    if (illustrativeRate < 0) invalid("invalid-coefficient", `${name}.illustrativeRate must be nonnegative.`);
    perDollarScore = illustrativeRate * (1 - basisPerShare / price);
  }
  return {
    lotId, acquiredOn, acquiredOrdinal, declaredIndex: index, sharesAvailable: shares,
    basisPerShare, illustrativeRate, perDollarScore, capacity: shares * price,
  };
}

function normalizeLots(lots: unknown, price: number, tradeOrdinal: number, label: string): LotRecord[] {
  if (!Array.isArray(lots)) invalid("invalid-input", `${label} must be an ordered array of lot records.`);
  if (lots.length === 0) invalid("invalid-input", `${label} must contain at least one lot.`);
  const records = lots.map((entry, index) => normalizeLot(entry, index, price, tradeOrdinal, label));
  const seen = new Set<string>();
  for (const record of records) {
    if (seen.has(record.lotId)) invalid("duplicate-lot-id", `${label} repeats lot id "${record.lotId}".`);
    seen.add(record.lotId);
  }
  return records;
}

function fillRank(record: LotRecord, fillOrder: FillOrder): [number, number, number] {
  // Ascending per-dollar score minimizes the declared separable score for a
  // sale whose size is already fixed.  Age and declared order only break
  // ties, so the ranking stays reproducible.
  return fillOrder === "lowest-score"
    ? [record.perDollarScore, record.acquiredOrdinal, record.declaredIndex]
    : [record.acquiredOrdinal, record.declaredIndex, record.declaredIndex];
}

function fillLots(
  records: readonly LotRecord[], saleDollars: number, fillOrder: FillOrder, price: number,
): { fills: LotFill[]; sequence: string[]; totalCapacity: number; totalScore: number } {
  const totalCapacity = stableSum(records.map((record) => record.capacity));
  if (saleDollars > totalCapacity + CAPACITY_TOLERANCE * Math.max(1, totalCapacity)) {
    invalid(
      "sale-exceeds-capacity",
      `a ${saleDollars} dollar sale exceeds the ${totalCapacity} dollars of validated lot capacity; `
      + "lot ordering cannot create additional shares.",
    );
  }
  const filled = records.map(() => 0);
  const sequence: string[] = [];
  let remaining = saleDollars;
  const ordered = [...records].sort((left, right) => {
    const a = fillRank(left, fillOrder);
    const b = fillRank(right, fillOrder);
    return (a[0] - b[0]) || (a[1] - b[1]) || (a[2] - b[2]);
  });
  for (const record of ordered) {
    if (remaining <= 0) break;
    const take = Math.min(remaining, record.capacity);
    if (take <= 0) continue;
    filled[record.declaredIndex] = take;
    remaining -= take;
    sequence.push(record.lotId);
  }
  if (remaining > CAPACITY_TOLERANCE * Math.max(1, totalCapacity)) {
    throw new TaxAwareNumericalError(
      "fill-residual", "the requested sale could not be filled from the validated lot capacity.",
    );
  }
  const fills: LotFill[] = records.map((record) => {
    const sale = filled[record.declaredIndex];
    return {
      lotId: record.lotId,
      acquiredOn: record.acquiredOn,
      sharesAvailable: record.sharesAvailable,
      basisPerShare: record.basisPerShare,
      illustrativeRate: record.illustrativeRate,
      perDollarScore: record.perDollarScore,
      capacity: record.capacity,
      saleDollars: sale,
      sharesSold: sale / price,
      remainingCapacity: record.capacity - sale,
      // T_ij * s_ij for this lot: a modeled score, not a tax bill.
      lotScore: record.perDollarScore * sale,
    };
  });
  return { fills, sequence, totalCapacity, totalScore: stableSum(fills.map((fill) => fill.lotScore)) };
}

/**
 * Score a sale whose dollar size is already fixed against its lots.
 *
 * Each lot contributes `T_ij * s_ij` with `T_ij = rho_ij (1 - b_ij/p_i)` and
 * `0 <= s_ij <= q_ij p_i`, and the fills sum to the requested sale.
 * `fillOrder` chooses between the ascending-score fill, which minimizes the
 * declared score for this fixed sale size, and the oldest-first fill, which
 * is here only so the two can be compared.
 *
 * The answer is a ranking under the supplied coefficients.  It does not
 * establish that the taxpayer may identify those shares, and the returned
 * score is never cash: `settlementCash` is zero in both directions.
 */
export function scoreFixedSale(
  assetId: unknown, price: unknown, lots: unknown, saleDollars: unknown, tradeDate: unknown,
  options: { fillOrder?: FillOrder; policy?: unknown } = {},
): FixedSaleResult {
  const asset = text(assetId, "assetId");
  const mark = asReal(price, "price");
  if (mark <= 0) invalid("invalid-price", "price must be positive; the score divides the basis by the mark.");
  const fillOrder = options.fillOrder ?? "lowest-score";
  if (!FILL_ORDERS.includes(fillOrder)) {
    invalid("unknown-fill-order", `fillOrder must be one of ${JSON.stringify(FILL_ORDERS)}.`);
  }
  const [tradeDay, tradeOrdinal] = isoDate(tradeDate, "tradeDate");
  const records = normalizeLots(lots, mark, tradeOrdinal, "lots");
  const sale = asReal(saleDollars, "saleDollars");
  if (sale < 0) invalid("invalid-sale-amount", "saleDollars must be nonnegative.");

  const { fills, sequence, totalCapacity, totalScore } = fillLots(records, sale, fillOrder, mark);
  return {
    assetId: asset,
    tradeDate: tradeDay,
    price: mark,
    fillOrder,
    saleDollars: sale,
    sharesSold: stableSum(fills.map((fill) => fill.sharesSold)),
    totalCapacity,
    remainingCapacity: stableSum(fills.map((fill) => fill.remainingCapacity)),
    totalScore,
    fillSequence: sequence,
    lots: fills,
    // A score is an objective input.  Negative or positive, it moves no money
    // here; only a declared settlement does, via settleTradeCash.
    settlementCash: 0,
    scoreIsObjectiveOnly: true,
    policy: reviewPolicyState(options.policy),
    certificate: {
      kind: "separable-lot-score-fill",
      fillOrder,
      minimizesDeclaredScore: fillOrder === "lowest-score",
      scope: "one asset, one currency, one period, no cross-lot or cross-account coupling",
      note:
        "ascending per-dollar score minimizes the declared separable score for a fixed sale size; "
        + "that is a modeling result under the supplied coefficients, not a statutory lot-ordering rule",
    },
  };
}

/**
 * Reconcile the cash ledger, charging any declared tax amount once.
 *
 * A score in the objective is not money leaving the account on trade day.
 * Under `objective-only-forecast` the immediate `taxCash` is zero by
 * construction.  Only a caller-declared settlement moves cash, through
 * `cashPost = cashInit - sum(trade) - executionCost - taxCash`, and
 * `portfolioValuePost` then adds that already-net balance instead of
 * subtracting the same amount a second time.
 */
export function settleTradeCash(
  cashInit: unknown, trade: unknown, executionCost: unknown,
  options: {
    taxCash?: unknown; settlement?: SettlementMode; settlementDate?: unknown; holdingsPost?: unknown;
  } = {},
): CashLedger {
  const settlement = options.settlement ?? "objective-only-forecast";
  if (!SETTLEMENT_MODES.includes(settlement)) {
    invalid("unknown-settlement-mode", `settlement must be one of ${JSON.stringify(SETTLEMENT_MODES)}.`);
  }
  const opening = asReal(cashInit, "cashInit");
  const signed = vector(trade, "trade");
  const cost = asReal(executionCost, "executionCost");
  if (cost < 0) invalid("negative-execution-cost", "executionCost must be nonnegative.");
  const settled = asReal(options.taxCash ?? 0, "taxCash");
  let settlementDay: string | null = null;
  if (settlement === "objective-only-forecast") {
    if (settled !== 0) {
      invalid(
        "tax-cash-without-settlement-policy",
        "taxCash must be zero while the model only forecasts; declare settlement='settles-at-trade' with its "
        + "timing before any amount moves.",
      );
    }
  } else {
    if (settled < 0) {
      invalid(
        "negative-tax-cash",
        "taxCash must be nonnegative; a negative modeled loss score is not a refund and must not increase cash.",
      );
    }
    if (options.settlementDate === undefined || options.settlementDate === null) {
      invalid(
        "settlement-date-required",
        "settlement='settles-at-trade' requires the date on which the declared amount settles.",
      );
    }
    settlementDay = isoDate(options.settlementDate, "settlementDate")[0];
  }

  const tradeTotal = stableSum(signed);
  const cashPost = stableSum([opening, -tradeTotal, -cost, -settled]);
  const holdingsTotalPost = options.holdingsPost === undefined || options.holdingsPost === null
    ? null : stableSum(vector(options.holdingsPost, "holdingsPost"));
  return {
    settlement,
    settlementDate: settlementDay,
    cashInit: opening,
    tradeTotal,
    executionCost: cost,
    taxCash: settled,
    cashPost,
    holdingsTotalPost,
    portfolioValuePost: holdingsTotalPost === null ? null : holdingsTotalPost + cashPost,
    taxChargedTo: settlement === "settles-at-trade" ? "cash-settlement" : "objective-score-only",
    note:
      "taxCash is subtracted once, inside cashPost; portfolioValuePost adds that net balance and never "
      + "subtracts the same amount again from terminal wealth",
  };
}

/**
 * Maximize `curvature*x^2 + slope*x + constant` on `[lower, upper]`.
 *
 * `curvature` is `-gammaRisk * dᵀVd` and is never positive for a
 * positive-semidefinite risk matrix, so the branch is concave and the
 * stationary point plus the two endpoints exhaust the candidates.
 */
function maximizeBranch(
  label: BranchRecord["branch"], curvature: number, slope: number, constant: number,
  lower: number, upper: number,
): BranchRecord {
  const value = (x: number) => curvature * x * x + slope * x + constant;
  const stationary = curvature < 0 ? slope / (-2 * curvature) : null;
  const feasible = stationary !== null && stationary >= lower && stationary <= upper;
  const candidates = feasible ? [lower, upper, stationary as number] : [lower, upper];
  let bestX = candidates[0];
  let bestValue = value(bestX);
  for (const candidate of candidates.slice(1)) {
    const candidateValue = value(candidate);
    const tolerance = RELATIVE_TOLERANCE * Math.max(Math.abs(candidateValue), Math.abs(bestValue));
    if (candidateValue > bestValue + tolerance) {
      bestX = candidate; bestValue = candidateValue;
    } else if (candidateValue >= bestValue - tolerance && Math.abs(candidate) < Math.abs(bestX)) {
      // A tie inside the comparison tolerance resolves to the smaller trade,
      // so a flat branch reports no trade rather than a boundary.
      bestX = candidate; bestValue = candidateValue;
    }
  }
  const binding = feasible && bestX === stationary ? "stationary" : bestX === lower ? "lower-bound" : "upper-bound";
  return {
    branch: label,
    interval: [lower, upper],
    quadraticCoefficient: curvature,
    linearCoefficient: slope,
    constant,
    stationaryX: stationary,
    stationaryInInterval: feasible,
    x: bestX,
    binding,
    utility: bestValue,
  };
}

/**
 * Maximize the teaching utility exactly, inside a narrow declared scope.
 *
 * The utility is `U(h,u) = aᵀu - gammaRisk (h-h_b)ᵀV(h-h_b) - gammaTc C(u)
 * - gammaTax L_policy(u)` with `h = h_init + u` and `C(u) = kappaᵀ|u|`.
 *
 * Declared scope: exactly `ORACLE_ASSETS` assets, exactly
 * `ORACLE_LOTS_PER_ASSET` lot per asset, and a cash-neutral trade, so
 * `sum(u) = 0` and `u = [x, -x]` with `x` bounded by the sale capacity on
 * each side.  Inside that scope the problem collapses to one free variable,
 * each sign branch fixes the buy/sell direction and is a concave quadratic in
 * `x`, and both branches are maximized in closed form: the result is the
 * exact global optimum of this restricted toy problem and nothing wider.  A
 * second lot per asset would make the score piecewise linear and make the
 * answer depend on the fill order, which is a different problem than the one
 * the article solves, so it is rejected rather than approximated.
 */
export function optimizeTaxAwareTrade(
  holdingsInit: unknown, benchmark: unknown, expectedReturn: unknown, riskMatrix: unknown,
  prices: unknown, lots: unknown, tradeDate: unknown,
  options: {
    gammaRisk?: unknown; gammaTax?: unknown; gammaTc?: unknown; costRates?: unknown;
    cashInit?: unknown; taxCash?: unknown; settlement?: SettlementMode; settlementDate?: unknown;
    policy?: unknown;
  } = {},
): TradeResult {
  const holdings = vector(holdingsInit, "holdingsInit");
  const size = holdings.length;
  if (size !== ORACLE_ASSETS) {
    invalid(
      "scope-limit",
      `the exact oracle is declared for exactly ${ORACLE_ASSETS} assets under a cash-neutral trade; a wider `
      + "problem needs a certified convex solver per fixed direction.",
    );
  }
  const target = vector(benchmark, "benchmark");
  const mean = vector(expectedReturn, "expectedReturn");
  const risk = matrix(riskMatrix, "riskMatrix", size);
  const mark = vector(prices, "prices");
  if (target.length !== size || mean.length !== size || mark.length !== size) {
    invalid("invalid-input", "holdingsInit, benchmark, expectedReturn, and prices must align.");
  }
  if (holdings.some((value) => value < 0)) invalid("invalid-input", "holdingsInit must be nonnegative dollar holdings.");
  if (mark.some((value) => value <= 0)) invalid("invalid-price", "every price must be positive.");
  const riskWeight = asReal(options.gammaRisk ?? 1, "gammaRisk");
  const taxWeight = asReal(options.gammaTax ?? 1, "gammaTax");
  const costWeight = asReal(options.gammaTc ?? 0, "gammaTc");
  for (const [name, value] of [["gammaRisk", riskWeight], ["gammaTax", taxWeight], ["gammaTc", costWeight]] as const) {
    if (value < 0) invalid("invalid-coefficient", `${name} must be nonnegative.`);
  }
  const kappa = options.costRates === undefined || options.costRates === null
    ? holdings.map(() => 0) : vector(options.costRates, "costRates");
  if (kappa.length !== size) invalid("invalid-input", "costRates must align with holdingsInit.");
  if (kappa.some((value) => value < 0)) {
    invalid("invalid-coefficient", "costRates must be nonnegative per-dollar execution rates.");
  }

  const scale = Math.max(...risk.flat().map((value) => Math.abs(value)));
  if (scale > 0) {
    if (Math.abs(risk[0][1] - risk[1][0]) > 1e-12 * scale) {
      invalid("indefinite-risk-matrix", "riskMatrix must be symmetric at the matrix scale.");
    }
    if (minimumSymmetricEigenvalue2x2(risk) < -PSD_RELATIVE_TOLERANCE * scale) {
      invalid("indefinite-risk-matrix", "riskMatrix must be positive semidefinite.");
    }
  }

  const [tradeDay, tradeOrdinal] = isoDate(tradeDate, "tradeDate");
  if (!Array.isArray(lots) || lots.length !== size) invalid("invalid-input", "lots must hold one lot list per asset.");
  const perAsset: LotRecord[][] = [];
  for (let index = 0; index < size; index += 1) {
    const records = normalizeLots(lots[index], mark[index], tradeOrdinal, `lots[${index}]`);
    if (records.length !== ORACLE_LOTS_PER_ASSET) {
      invalid(
        "scope-limit",
        `the exact oracle is declared for ${ORACLE_LOTS_PER_ASSET} lot per asset; lots[${index}] has `
        + `${records.length}, which would make the score piecewise linear and fill-order dependent.`,
      );
    }
    const capacity = stableSum(records.map((record) => record.capacity));
    if (capacity > holdings[index] + CAPACITY_TOLERANCE * Math.max(1, holdings[index])) {
      invalid(
        "lot-capacity-exceeds-holding",
        `lots[${index}] can sell ${capacity} dollars but the validated holding is ${holdings[index]}.`,
      );
    }
    perAsset.push(records);
  }

  const capacities = perAsset.map((records) => stableSum(records.map((record) => record.capacity)));
  const saleRates = perAsset.map((records) => records[0].perDollarScore);

  // u = [x, -x], so the whole objective is one quadratic in x per sign branch.
  const gap = holdings.map((value, index) => value - target[index]);
  const direction = [1, -1];
  const curvature = -riskWeight * quadraticForm(risk, direction, direction);
  const cross = quadraticForm(risk, gap, direction);
  const constant = -riskWeight * quadraticForm(risk, gap, gap);
  const returnSlope = mean[0] - mean[1];
  const riskSlope = -2 * riskWeight * cross;
  const costSlope = costWeight * (kappa[0] + kappa[1]);

  const branches: BranchRecord[] = [
    maximizeBranch(
      "buy-first-sell-second", curvature,
      returnSlope + riskSlope - taxWeight * saleRates[1] - costSlope, constant, 0, capacities[1],
    ),
    maximizeBranch(
      "sell-first-buy-second", curvature,
      returnSlope + riskSlope + taxWeight * saleRates[0] + costSlope, constant, -capacities[0], 0,
    ),
  ];
  let best = branches[0];
  const tolerance = RELATIVE_TOLERANCE * Math.max(Math.abs(branches[0].utility), Math.abs(branches[1].utility));
  if (branches[1].utility > best.utility + tolerance) best = branches[1];
  else if (branches[1].utility >= best.utility - tolerance && Math.abs(branches[1].x) < Math.abs(best.x)) best = branches[1];

  const x = best.x;
  const trade = [x, -x];
  const post = holdings.map((value, index) => value + trade[index]);
  const deviation = post.map((value, index) => value - target[index]);
  const executionCost = stableSum(kappa.map((rate, index) => rate * Math.abs(trade[index])));
  const sold = x > 0 ? 1 : x < 0 ? 0 : null;
  const saleDollars = Math.abs(x);
  const taxScore = sold === null ? 0 : saleRates[sold] * saleDollars;

  const expectedTerm = dot(mean, trade);
  const riskTerm = riskWeight * quadraticForm(risk, deviation, deviation);
  const costTerm = costWeight * executionCost;
  const taxTerm = taxWeight * taxScore;
  const utility = stableSum([expectedTerm, -riskTerm, -costTerm, -taxTerm]);
  if (Math.abs(utility - best.utility) > ABSOLUTE_TOLERANCE * Math.max(1, Math.abs(utility))) {
    throw new TaxAwareNumericalError(
      "objective-mismatch", "the branch polynomial and the direct utility disagree beyond tolerance.",
    );
  }

  let lotSales: TradeResult["lotSales"] = null;
  if (sold !== null) {
    const filled = fillLots(perAsset[sold], saleDollars, "lowest-score", mark[sold]);
    lotSales = {
      asset: sold, price: mark[sold], saleDollars, totalCapacity: filled.totalCapacity,
      fillSequence: filled.sequence, totalScore: filled.totalScore, lots: filled.fills,
    };
  }

  const cash = settleTradeCash(options.cashInit ?? 0, trade, executionCost, {
    taxCash: options.taxCash ?? 0,
    settlement: options.settlement ?? "objective-only-forecast",
    settlementDate: options.settlementDate,
    holdingsPost: post,
  });
  if (![...post, utility, executionCost, taxScore].every(Number.isFinite)) {
    throw new TaxAwareNumericalError("nonfinite-result", "the oracle returned a non-finite diagnostic.");
  }
  return {
    status: "optimal",
    tradeDate: tradeDay,
    trade,
    tradeTotal: stableSum(trade),
    holdings: post,
    direction: sold === null ? "no-trade" : best.branch,
    utility,
    terms: {
      expectedReturn: expectedTerm, riskPenalty: riskTerm,
      executionCostPenalty: costTerm, taxScorePenalty: taxTerm,
    },
    executionCost,
    // L_policy(u) in objective dollars under the declared coefficients.  It
    // is not a tax bill and it is not the cash in the ledger below.
    taxScore,
    saleCapacities: capacities,
    lotSales,
    branches,
    cash,
    policy: reviewPolicyState(options.policy),
    certificate: {
      kind: "direction-fixed-branch-enumeration",
      scope:
        `${ORACLE_ASSETS} assets, ${ORACLE_LOTS_PER_ASSET} declared lot each, cash-neutral sum(u)=0 so u=[x,-x]`,
      branchesChecked: branches.length,
      complete: true,
      globalOptimality:
        "each sign branch fixes the buy/sell direction, is concave in the single free variable x, and was "
        + "maximized in closed form over its closed interval; the better branch is the exact global optimum of "
        + "this restricted toy problem",
      taxModel: "declared illustrative lot score; no jurisdiction rule is applied",
    },
  };
}

/** The article's whole-portfolio oracle inputs. */
export function canonicalExample() {
  return {
    holdingsInit: [500, 500],
    benchmark: [600, 400],
    expectedReturn: [0.04, 0],
    riskMatrix: [[0.0001, 0], [0, 0.0001]],
    prices: [1, 1],
    lots: [
      [{ lotId: "A", shares: 500, saleScoreRate: 0.05, acquiredOn: "2025-01-02" }],
      [{ lotId: "B", shares: 500, saleScoreRate: 0.01, acquiredOn: "2025-01-02" }],
    ],
    tradeDate: "2026-01-02",
    options: { gammaRisk: 1, gammaTax: 1, gammaTc: 0 },
  };
}
Interactive 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.