# Transaction-Cost-Aware Optimization

This package freezes one auditable, one-rebalance reference contract for
linear variable transaction costs. Holdings are dollar values in one declared
base currency. The universe contains an explicit non-negative cash coordinate;
the other coordinates are risky holdings. The reference solver is general in
the risky dimension up to 16 coordinates for its global gap certificate.

## Contract

Let `x⁰ ∈ Rⁿ_+` be current dollar holdings, `W₀ = 1ᵀx⁰ > 0`, and
`t = x − x⁰` be the signed trade. Let `c⁺` and `c⁻` be nonnegative dollar
cost rates per dollar bought and sold during one rebalance. The direct cost is

```text
C(t) = Σᵢ c⁺ᵢ max(tᵢ, 0) + c⁻ᵢ max(−tᵢ, 0).
```

The self-financing equation is exact:

```text
1ᵀx + C(t) = W₀,       x ≥ 0.
```

The objective, in dollars of risk-adjusted expected gain over the investment period after this single rebalance, is

```text
μᵀx − λ xᵀΣx − C(t),
```

where `μ` is expected return per dollar over that investment horizon, `Σ` is a
positive-semidefinite return covariance matrix for that horizon, and `λ ≥ 0`
has the reciprocal units needed to combine the dollar return and dollar
quadratic risk terms. The cash coordinate has zero expected return, zero
covariance, and zero variable cost in this reference scope. A cost rate without
the `per-rebalance` horizon is rejected; annual rates must be converted before
calling the package.

Putting the cost in the budget and subtracting it in the expected-gain
objective is intentional, not an accidental duplicate: the first prevents a
fee from creating money, while the second accounts for the fee in the return
being optimized. If a caller instead uses a terminal-wealth expression whose
cash balance already includes the fee, that expression must not subtract the
same fee a second time.

The implementation eliminates cash as a residual. With `z=x/W₀` on risky
coordinates and `z⁰=x⁰/W₀`, it optimizes

```text
maximize  μᵀz − λW₀ zᵀΣz − C(z−z⁰)
subject to z ≥ 0 and 1ᵀz + C(z−z⁰) ≤ 1,
cash = W₀[1 − 1ᵀz − C(z−z⁰)] ≥ 0.
```

The reported full holdings reconstruct the cash residual and are checked
against the original dollar equation. This is a tractable reference scope,
not a universal broker-fee, tax, spread, impact, or settlement model.

## Why the cost ledger is explicit

The official [MOSEK transaction-cost chapter](https://docs.mosek.com/portfolio-cookbook/transaction.html)
defines variable costs from positive and negative trade parts and makes the
self-financing equation explicit. Its cost terms must be aggregated over the
investment period before comparison with returns and risk. Its market-impact
section also warns that a loose epigraph can absorb money unless tightness is
forced or audited. This package therefore recomputes `C(t)` from the signed
trade and reports both direct and auxiliary representations. Positive and
negative parts that recombine to a trade are not assumed minimal when a caller
supplies them.

The [Boyd convex portfolio reference](https://stanford.edu/~boyd/papers/cvx_portfolio.html)
places expected return, risk, and transaction/holding costs in a convex
portfolio-optimization framework. Neither source supplies a user’s broker
schedule or tax law; those remain explicit inputs.

## Synthetic fixtures

The fixtures are controlled numerics, not observed prices or an account story.
The simple cash-ledger check uses

```text
x⁰ = [600, 400, 100] USD
t  = [100, −100, −0.30] USD
c⁺ = c⁻ = [0.001, 0.002, 0]
C(t) = 0.001×100 + 0.002×100 = 0.30 USD
x  = [700, 300, 99.70] USD
```

Thus `Σx = 1099.70`, `Σx + C = 1100`, and cash remains nonnegative. The
optimizer fixture uses the same current holdings, `μ=[.08,.04,0]`,
`Σ=diag(.04,.01,0)`, and `λ=.0005`. Its independently solved optimum is

```text
x = [957.3853839647959, 141.7407120753901, 0] USD
C = 0.8739039598140157 USD
objective = 62.9543676244987 USD
```

In the observed buy-asset-1/sell-asset-2 region, the dollar budget reduces to
`1.001x₁ + 0.998x₂ = 1099.8`; the KKT linear system gives the fixture above.
The zero-cost counterpart is `[1020,80,0]`, which is a parity check rather
than evidence that costs are immaterial.

## Solver and status contract

The Python and TypeScript implementations use the same deterministic
projected-gradient ascent on normalized risky holdings. Projection is onto
the nonnegative piecewise-linear cash-ledger domain by a bisection multiplier;
the code never clips and silently renormalizes holdings. The certificate is a
composite Frank–Wolfe gap: it linearizes the smooth quadratic part while
retaining the exact piecewise cost in the oracle. This matters at an ordinary
no-trade kink, where a midpoint cost subgradient could otherwise report a
false gap. A returned `optimal` status requires feasibility and a normalized gap no larger
than the declared tolerance. A `max-iterations` result is not an optimality
claim. The input gate rejects malformed/non-finite values, negative holdings,
non-PSD or nonsymmetric covariance, nonzero cash return/risk/cost, unsupported
cost horizons, and rates outside the reduced-ledger domain. Numerical and
certificate failures are structured errors with a code and message.

Result fields include `weights`, signed `trade`, direct positive/negative
parts, `direct_cost`, `auxiliary_cost`, `budget_residual`, `cash_balance`,
objective, iteration count, horizon, and the normalized/global gap status.

## Runnable usage

From this package directory:

```powershell
python -B -m unittest discover -s tests -p "test_*.py" -v
npx tsc --ignoreConfig --noEmit --target ES2022 --module NodeNext --moduleResolution NodeNext --strict --types node `
  implementations/typescript/transaction-cost-aware.ts `
  tests/transaction-cost-aware.test.ts
npx tsx tests/transaction-cost-aware.test.ts
```

The JSON adapter accepts the same fields (`mu`, `covariance`, `lambda_risk`,
`x0`, `buy_cost`, `sell_cost`, optional `cash_index`, `cost_horizon`, and
`max_iterations`) on stdin. It emits JSON with `status:error` and a structured
`code` for rejected inputs; it never serializes numeric infinity.

## Scope exclusions and nearby topics

- Fixed per-trade fees and buy-in thresholds require binary/semi-continuous
  variables and are excluded from this convex reference.
- Nonlinear market impact requires a calibrated volume/volatility horizon and
  a tight impact epigraph; it is not silently represented by a linear rate.
- Taxes and tax-lot identity are not a generic cost coefficient; see the
  separate tax-aware topic. Tax cash, dividend entitlement/receipt chronology,
  adjusted total-return prices, FX, and split repair belong in upstream data
  contracts and must not be double counted here.
- Multi-period execution, borrowing, shorting, and settlement timing require a
  separate declared model.

Historical-example decision: deferred. No dated market case is included: a
real fee schedule and settlement history would not be independently checkable
from the supplied inputs. The fixture is
synthetic and intentionally exposes the complete cash arithmetic.

## Related handoffs

Turnover constraints (D14-F04-A01) can limit the same signed rebalance but do
not imply a fee schedule. Long/short gross-net constraints (D14-F04-A04) must
include cost terms consistently if both are active. Tax-aware optimization
(D14-F04-A05) needs lot basis, current marks, units, withholding, and tax cash
states; it must not inherit this linear-cost model as a tax calculator.
