Tax-Aware Portfolio Optimization
Install and import#
npm install fintech-algorithmsimport { optimizeTaxAwareTrade } from "fintech-algorithms/portfolio-construction/practical-constraints/tax-aware-portfolio-optimization";Signature#
optimizeTaxAwareTrade(holdingsInit, benchmark, expectedReturn, riskMatrix, prices, lots, tradeDate, options)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.
Parameters#
| Name | Type | Notes |
|---|---|---|
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; } | optional |
Worked example#
executed Captured by running this function on the input its own test provides. Real output of real code — but not asserted against a published figure.
Input#
[500, 500][600, 400][0.04, 0][
[0.0001, 0],
[0, 0.0001]
][1, 1][
[
{
"lotId": "A",
"shares": 500,
"saleScoreRate": 0.05,
"acquiredOn": "2025-01-02"
}
],
[
{
"lotId": "B",
"shares": 500,
"saleScoreRate": 0.01,
"acquiredOn": "2025-01-02"
}
]
]"2026-01-02"{
"gammaRisk": 1,
"gammaTax": 1,
"gammaTc": 0
}Call#
optimizeTaxAwareTrade(holdingsInit, benchmark, expectedReturn, riskMatrix, prices, lots, tradeDate, options)Returns#
object with 16 fields: status, tradeDate, trade, tradeTotal, holdings, direction, utility, terms, …
{
"status": "optimal",
"tradeDate": "2026-01-02",
"trade": [175, -175],
"tradeTotal": 0,
"holdings": [675, 325],
"direction": "buy-first-sell-second",
"utility": 4.125,
"terms": {
"expectedReturn": 7,
"riskPenalty": 1.125,
"executionCostPenalty": 0,
"taxScorePenalty": 1.75
},
"executionCost": 0,
"taxScore": 1.75,
"saleCapacities": [500, 500],
"lotSales": {
"asset": 1,
"price": 1,
"saleDollars": 175,
"totalCapacity": 500,
"fillSequence": ["B"],
"totalScore": 1.75,
"lots": [
{
"lotId": "B",
"acquiredOn": "2025-01-02",
"sharesAvailable": 500,
"basisPerShare": null,
"illustrativeRate": null,
"perDollarScore": 0.01,
"capacity": 500,
"saleDollars": 175,
"sharesSold": 175,
"remainingCapacity": 325,
"lotScore": 1.75
}
]
},
"branches": [
{
"branch": "buy-first-sell-second",
"interval": [0, 500],
"quadraticCoefficient": -0.0002,
"linearCoefficient": 0.07,
"constant": -2,
"stationaryX": 175,
"stationaryInInterval": true,
"x": 175,
"binding": "stationary",
"utility": 4.125000000000001
},
{
"branch": "sell-first-buy-second",
"interval": [-500, 0],
"quadraticCoefficient": -0.0002,
"linearCoefficient": 0.13,
"constant": -2,
"stationaryX": 325,
"stationaryInInterval": false,
"x": 0,
"binding": "upper-bound",
"utility": -2
}
],
"cash": {
"settlement": "objective-only-forecast",
"settlementDate": null,
"cashInit": 0,
"tradeTotal": 0,
"executionCost": 0,
"taxCash": 0,
"cashPost": 0,
"holdingsTotalPost": 1000,
"portfolioValuePost": 1000,
"taxChargedTo": "objective-score-only",
"note": "taxCash is subtracted once, inside cashPost; portfolioValuePost adds that net balance and never subtracts the same amoun…"
}
}Showing 14 of 16 fields.
Other exports#
This module also exports
reviewPolicyState, applySplitToLot, scoreFixedSale, settleTradeCash, canonicalExample. Every module additionally exports run as an alias of its
primary function, and a meta object carrying its catalog id, domain, family,
shape and article URL.
Diagrams#
How it works#
This page states the contract — how to call it correctly. The article explains the concept: why it works, and where it breaks.
References#
- Tax-Aware Portfolio Construction via Convex Optimization
- Investment Income and Expenses
- Shares and Capital Gains Tax
- Capital losses