Harvest losses 38
Tax-managed equity above all, then any taxable account in a down market. Very common, and the most mishandled: the rule reaches across accounts, across spouses, and into retirement plans where the loss is destroyed rather than deferred.
| Case | What it pins | Suite | Defined in |
|---|---|---|---|
portfolio-lots | What is held and what it carries. Proposes nothing: this is the tool for 'where do we stand', not 'what should we do'. | runs live | use_cases.py |
portfolio-harvest | A proposal, the losses it realises, and the lots it held back — one for wash-sale risk, one for unknown basis. | runs live | use_cases.py |
portfolio-planned-purchase | Screening a planned buy before it happens turns a disallowed loss into a decision the advisor still gets to make. | runs live | use_cases.py |
holding-period-anniversary-is-short-term | The period begins the day after acquisition, so a sale on the one-year anniversary is short-term. Bought 2025-09-30, sold 2026-09-30. | rule | investment_rules.yaml |
holding-period-one-day-later-is-long-term | One day past the anniversary crosses into long-term. | rule | investment_rules.yaml |
wash-sale-window-includes-day-thirty | A purchase exactly 30 days after the sale is inside the window; the loss on the matched shares is disallowed. | rule | investment_rules.yaml |
wash-sale-window-excludes-day-thirty-one | One day outside the 61-day window and the loss stands in full. | rule | investment_rules.yaml |
wash-sale-partial-quantity-allocates-pro-rata | 40 replacement shares against a 100-share loss disallow 40%. | rule | investment_rules.yaml |
replacement-in-a-roth-permanently-disallows | Rev. Rul. 2008-5: a replacement bought in an IRA or Roth kills the loss outright, with no basis adjustment available anywhere. | rule | investment_rules.yaml |
replacement-in-a-taxable-account-adjusts-basis | The same facts in a taxable account defer the loss instead. | rule | investment_rules.yaml |
different-ticker-is-not-matched-and-not-cleared | A different identifier is never matched, and the screen says the equivalence question was not decided rather than implying safety. | rule | investment_rules.yaml |
dividend-reinvestment-is-a-replacement-purchase | A reinvested dividend is an acquisition and can wash a loss. | rule | investment_rules.yaml |
select-prefers-losses-before-gains | With a tight gross budget, only losses can be taken. | conformance | selection.yaml |
select-takes-the-only-eligible-lot | When three lots are unusable the fourth is the only possible trade. | conformance | selection.yaml |
select-extra-trades-past-the-target-are-losses | Harvesting may continue past the cash target, because taking a loss is itself the objective. What it may not do is realize a gain nobody needed: any trade beyond the target must consume no budget. | conformance | selection.yaml |
select-no-gain-realized-when-losses-suffice | Cash available from losses should not be raised by realizing a gain. | conformance | selection.yaml |
select-all-lots-when-target-needs-them | A target equal to the whole portfolio requires every eligible lot. | conformance | selection.yaml |
select-restricted-lot-forces-the-alternative | Restricting the larger loss forces the smaller one to be used. | conformance | selection.yaml |
select-washed-lot-forces-the-alternative | A washed lot is withheld, so the other loss lot carries the target. | conformance | selection.yaml |
select-nothing-to-do-is-a-valid-answer | A zero target with gains only means no trade is the right answer. | conformance | selection.yaml |
select-harvest-without-a-cash-target | With no cash objective the losses are still worth harvesting. | conformance | selection.yaml |
select-gain-lot-only-when-required | A target beyond the losses requires reaching into the gains. | conformance | selection.yaml |
wash-existing-replacement-blocks-harvest | A repurchase already inside the window makes that loss lot unharvestable. | conformance | wash_sale.yaml |
wash-other-loss-lot-remains-available | Only the washed lot is withheld; the clean one is still sold. | conformance | wash_sale.yaml |
wash-reason-is-reported | A withheld harvest is reported, not silently omitted. | conformance | wash_sale.yaml |
wash-roth-replacement-blocks-harvest | A replacement in a Roth would permanently disallow the loss, so it is withheld. | conformance | wash_sale.yaml |
wash-planned-purchase-blocks-harvest | A buy a manager intends but has not placed is invisible to history and must still block. | conformance | wash_sale.yaml |
wash-planned-purchase-names-the-conflict | A cross-unit conflict is labelled as such, not as ordinary wash-sale risk. | conformance | wash_sale.yaml |
wash-planned-purchase-outside-window | A planned buy beyond 30 days after the sale does not block the harvest. | conformance | wash_sale.yaml |
wash-planned-purchase-of-another-security | A planned buy of a different identifier is not a replacement. | conformance | wash_sale.yaml |
wash-does-not-block-gain-lots | Wash-sale rules concern losses; a gain lot is unaffected by a repurchase. | conformance | wash_sale.yaml |
wash-blocked-lot-never-appears-in-trades | Whatever the cash pressure, a washed lot must not be sold. | conformance | wash_sale.yaml |
wash-withholding-can-make-a-target-infeasible | Withholding a washed lot may put the target out of reach, which is reported honestly. | conformance | wash_sale.yaml |
wash-clean-portfolio-harvests-freely | With no replacement anywhere, both loss lots are available. | conformance | wash_sale.yaml |
wash-multiple-planned-purchases | Several planned buys each block their own security. | conformance | wash_sale.yaml |
wash-partial-planned-quantity-still-blocks | A planned buy smaller than the lot still matches part of it and withholds the harvest. | conformance | wash_sale.yaml |
wash-traditional-ira-replacement-blocks-harvest | Rev. Rul. 2008-5 applies to a traditional IRA exactly as to a Roth: the loss is permanently disallowed with no basis adjustment, so the lot is never harvested. | conformance | wash_sale.yaml |
wash-qualified-plan-replacement-blocks-harvest | The same permanent disallowance through an employer plan. All three retirement registrations are treated alike by the rule pack, and now all three are exercised. | conformance | wash_sale.yaml |
Raise cash 23
Every segment. The objective is proceeds by a date, with the gain budget as the constraint rather than the goal.
| Case | What it pins | Suite | Defined in |
|---|---|---|---|
portfolio-infeasible | The shortfall is reported. The constraint is never quietly relaxed to manufacture a result that looks like success. | runs live | use_cases.py |
cash-met-exactly-from-losses | Losses alone can satisfy a target without touching the budget. | conformance | cash_target.yaml |
cash-requires-a-gain | When losses are insufficient, reaching the target means realizing a gain. | conformance | cash_target.yaml |
cash-unreachable-is-infeasible | A target beyond the whole portfolio is infeasible, not partially served as success. | conformance | cash_target.yaml |
cash-unreachable-within-budget | Cash exists in the portfolio but only behind gains the budget forbids. | conformance | cash_target.yaml |
cash-shortfall-is-reported | An infeasible run says how far short it fell. | conformance | cash_target.yaml |
cash-infeasibility-gives-a-reason | The diagnostics must say why, not merely that it failed. | conformance | cash_target.yaml |
cash-zero-needs-no-trade | A zero target must not provoke a gain nobody asked for. | conformance | cash_target.yaml |
cash-small-target-from-one-lot | A small target should not require liquidating the whole portfolio. | conformance | cash_target.yaml |
cash-whole-portfolio-exactly | A target equal to the entire portfolio value is reachable. | conformance | cash_target.yaml |
cash-one-cent-beyond-the-portfolio | A cent more than everything is infeasible. | conformance | cash_target.yaml |
cash-never-relaxes-the-budget-to-succeed | An adapter may not breach a hard budget to report success. | conformance | cash_target.yaml |
cash-withdrawal-account-named | A named withdrawal account does not change conservation or the budget. | conformance | cash_target.yaml |
infeasible-reports-status | An unreachable target yields the infeasible status, not a partial success. | conformance | infeasibility.yaml |
infeasible-still-conserves | Even when failing, the partial trade list must remain valid. | conformance | infeasibility.yaml |
infeasible-does-not-breach-the-budget | Falling short is preferable to exceeding a hard budget. | conformance | infeasibility.yaml |
infeasible-does-not-breach-restrictions | A restriction is not relaxed to close a shortfall. | conformance | infeasibility.yaml |
infeasible-reports-the-shortfall | The gap between target and achievable cash is quantified. | conformance | infeasibility.yaml |
infeasible-explains-itself | A reason accompanies the status so a person can act on it. | conformance | infeasibility.yaml |
infeasible-empty-portfolio | No holdings and a positive target is infeasible, not an exception. | conformance | infeasibility.yaml |
infeasible-everything-restricted | A fully restricted portfolio cannot raise a cent. | conformance | infeasibility.yaml |
infeasible-zero-budget-blocks-the-only-cash | When all cash sits behind gains a zero budget makes the target unreachable. | conformance | infeasibility.yaml |
infeasible-feasible-when-target-lowered | The same portfolio succeeds once the target is within reach, proving the refusal was about the constraint. | conformance | infeasibility.yaml |
Cap realized gains 29
UHNW and HNW accounts under a signed gain budget. Twelve of these are the optimality gate: the difference between a plan that works and a plan that is provably best.
| Case | What it pins | Suite | Defined in |
|---|---|---|---|
portfolio-missing-netting | 'Keep gains under $15,000' reads two ways and they give different answers, so the schema requires the caller to say which. | runs live | use_cases.py |
budget-gross-blocks-the-gain-lot | Under gross netting a 5,000 gain cannot fit a 1,000 budget, however much loss is harvested. | conformance | gain_budget.yaml |
budget-net-allows-offset | Under net netting the harvested loss creates room for the gain. | conformance | gain_budget.yaml |
budget-zero-gross-permits-losses-only | A zero gross budget still allows loss harvesting, which consumes none of it. | conformance | gain_budget.yaml |
budget-zero-net-permits-offsetting | A zero net budget permits a gain exactly offset by a loss. | conformance | gain_budget.yaml |
budget-external-realized-reduces-room | Gains already realized elsewhere consume the same allowance. | conformance | gain_budget.yaml |
budget-external-exhausts-it | When prior gains have used the whole budget, no further gain fits. | conformance | gain_budget.yaml |
budget-exactly-met-is-within-it | A gain equal to the budget is within it; the limit is not strict. | conformance | gain_budget.yaml |
budget-one-cent-short | A budget a cent below the gain must exclude that lot. | conformance | gain_budget.yaml |
budget-small-gain-fits | The smaller gain lot fits a budget the larger one cannot. | conformance | gain_budget.yaml |
budget-generous-allows-everything | A budget larger than every gain imposes no restriction. | conformance | gain_budget.yaml |
budget-losses-do-not-consume-gross | Harvesting losses under gross netting leaves the budget untouched. | conformance | gain_budget.yaml |
budget-net-can-go-negative | A net figure below zero is a harvested loss, not a breach. | conformance | gain_budget.yaml |
budget-soft-limit-is-still-reported | A budget marked soft may be exceeded, but the figures must still recompute. | conformance | gain_budget.yaml |
budget-with-no-cash-target | A budget alone still binds when there is no cash objective. | conformance | gain_budget.yaml |
budget-efficiency-matters | Two lots raise cash at very different budget cost per dollar. | conformance | gain_budget.yaml |
budget-period-is-recorded | A budget carries the period it applies to, which the run must respect. | conformance | gain_budget.yaml |
opt-spends-budget-on-the-better-lot | A 5,000 gross budget buys 15,000 of cash from the large gain lot or 5,000 from the small one. Greedy ordering takes the small lot first and then cannot afford the large one, missing the target entirely. | conformance | optimality.yaml |
opt-chooses-cheaper-cash-per-budget-dollar | lot_cheap yields 15,000 of cash for 1,000 of gain; lot_dear yields 5,000 for 4,000. A 1,000 budget should buy the cheap lot rather than fail. | conformance | optimality.yaml |
opt-no-dominated-trade | Where one lot is better on both cash raised and budget consumed, the dominated lot must never be preferred. | conformance | optimality.yaml |
opt-maximises-cash-within-the-budget | With a 1,000 gross budget the best reachable cash is both loss lots plus the small gain lot: 14,200 + 5,000 = 19,200. | conformance | optimality.yaml |
opt-uses-net-offsetting-to-reach-further | Under net netting the 6,800 of harvested losses offset the gains, so the whole portfolio is reachable within a zero net budget. | conformance | optimality.yaml |
opt-declines-an-unnecessary-gain | Where losses alone reach the target, an optimizer realizes no gain at all even though the budget would have allowed one. | conformance | optimality.yaml |
opt-minimum-trades-for-the-target | A target reachable from a single lot should not be met by liquidating two, where transaction count forms part of the objective. | conformance | optimality.yaml |
opt-respects-restrictions-while-optimising | Optimality never licenses breaking a restriction in order to find a better answer; the restricted lot stays untouched. | conformance | optimality.yaml |
opt-reports-optimal-status | An engine that proves optimality reports it, and must set the optimality_claimed diagnostic to match the status it gives. | conformance | optimality.yaml |
opt-reports-a-gap-when-it-stops-early | An optimizer halted by a time limit reports a bound and a gap rather than claiming an optimum it never proved. | conformance | optimality.yaml |
opt-infeasible-is-proven-not-assumed | An optimizer declaring infeasibility should identify the conflicting constraints rather than merely failing to find an answer. | conformance | optimality.yaml |
opt-household-joint-beats-sequential | A joint household optimizer serves both accounts from one budget with no ordering at all, which is the entire reason to build one. | conformance | optimality.yaml |
Rebalance 10
Tax-managed equity and any model-driven book. Exact per overweight security, and it never sells a security below its target.
| Case | What it pins | Suite | Defined in |
|---|---|---|---|
portfolio-rebalance | Closes drift within a gain budget, never selling a security below its target. With chunky lots it under-trades rather than overshooting, and reports the residual. | runs live | use_cases.py |
portfolio-rebalance-needs-targets | Refused rather than inferred. Deriving a target from the current holdings would make the portfolio its own benchmark. | runs live | use_cases.py |
rebal-closes-drift-toward-target | AAA is far above its 50% target and CCC is absent. Selling the affordable AAA lot and buying the shortfall moves the portfolio toward target. | conformance | rebalance.yaml |
rebal-never-sells-past-target | Even with an unlimited budget, no security is sold below its target weight. Overshooting would close drift on one side and open it on the other. | conformance | rebalance.yaml |
rebal-respects-the-gain-budget | A tight gross budget limits which overweight lots can be sold, so drift closes only partly. The budget is never breached to reach the target. | conformance | rebalance.yaml |
rebal-zero-budget-still-trades-what-costs-nothing | A zero gross budget blocks gain lots but not loss or break-even lots, which consume no gross capacity. | conformance | rebalance.yaml |
rebal-tolerance-suppresses-small-trades | A wide tolerance band leaves every drift inside it, so nothing is traded and the portfolio is left alone. | conformance | rebalance.yaml |
rebal-conserves-positions | No lot may be sold in a quantity it does not hold, and no lot may appear twice, whatever the objective. | conformance | rebalance.yaml |
rebal-trims-a-combination-that-would-overshoot | Total value is 18,200, so a 50% AAA target leaves AAA 2,900 overweight. Either 6,000 lot alone already exceeds that, so the engine sells neither rather than overshoot. This is the only portfolio shape that exercises the trim, and without it a deliberately broken trim still passed the suite. | conformance | rebalance.yaml |
rebal-sells-one-of-two-when-only-one-fits | A 30% AAA target leaves AAA 6,540 overweight. One 6,000 lot fits inside that and a second would overshoot by more than 5,000, so exactly one is sold. This is the case the trim has to get right. | conformance | rebalance.yaml |
Coordinate a household 14
UHNW families, UMAs with several managers, spouses at different custodians. Budgets belong to taxpayers and are never pooled.
| Case | What it pins | Suite | Defined in |
|---|---|---|---|
household-coordinated | Budget is reserved per tax unit as each account is served, so no two runs spend the same allowance. | runs live | use_cases.py |
household-uma-wash | Same account, same taxpayer, two managers. Neither did anything wrong and the loss would still be disallowed, so it is caught first. | runs live | use_cases.py |
household-gap | Sequential allocation depends on the order units are served, so the run reports an LP bound on what any allocation could have raised. | runs live | use_cases.py |
household-joint-optimal | No ordering at all. Where the search exhausts, the run proves no other selection raises more cash within the budget — which the sequential coordinator can never say. | runs live | use_cases.py |
household-joint-infeasible-is-proven | Because each tax unit was searched exhaustively, the shortfall is a proof rather than a failure to find a plan. | runs live | use_cases.py |
household-capabilities | Two engines on one server: the joint optimizer proves optimality where its search exhausts, the coordinator never does. The server states the difference rather than letting a coordinated result read as an optimal one. | runs live | use_cases.py |
scope-household-refused-by-account-engine | An account-scoped engine must refuse household scope rather than looping over accounts. | conformance | scope.yaml |
scope-unconfigured-adapter-refuses | An adapter with no pinned engine build refuses rather than guessing. | conformance | scope.yaml |
scope-coordinated-household-is-served | The coordinator accepts the scope it declares and produces a candidate. | conformance | scope.yaml |
scope-coordination-never-claims-optimality | A coordinated result must not describe itself as optimal. | conformance | scope.yaml |
scope-coordination-declares-order-dependence | Order dependence is stated, because it changes the answer. | conformance | scope.yaml |
scope-coordination-budget-by-tax-unit | Budgets are accounted per taxpayer, never pooled across a household. | conformance | scope.yaml |
scope-coordination-respects-the-budget | Coordination does not loosen the shared budget it is coordinating. | conformance | scope.yaml |
scope-coordination-conserves-positions | The combined trade list across units must still be valid as a whole. | conformance | scope.yaml |
Municipal bonds 26
UHNW and HNW in high-tax states, and anyone comparing a muni to a Treasury. The standard formula uses the federal rate alone and understates the equivalent yield by over a point.
| Case | What it pins | Suite | Defined in |
|---|---|---|---|
muni-naive-understates | 7.01% against the 5.56% the federal-only formula gives. State tax and the 3.8% surtax are both avoided, and both are routinely left out. | runs live | use_cases.py |
muni-out-of-state | Federally exempt, state taxable. 5.91% instead of 7.01% — a different proposition that looks identical on a screen. | runs live | use_cases.py |
muni-private-activity-in-amt | The interest is a preference item, so nothing is avoided and the advantage is zero. The answer reverses rather than shades. | runs live | use_cases.py |
muni-2009-window | Bonds issued in 2009 and 2010 are excepted from the preference, so the advantage survives AMT. A narrow window with a total effect. | runs live | use_cases.py |
muni-discount-is-ordinary | A $50 discount against a $25 threshold: accrued market discount is ordinary income on disposition. The interest stays exempt; the discount does not. | runs live | use_cases.py |
muni-discount-de-minimis | $20 against the same $25 threshold is treated as zero, so the gain on redemption is capital. The margin is what a buyer can act on. | runs live | use_cases.py |
muni-discount-short-maturity | The threshold is a quarter point per year, so two years allows only $5. Maturity drives this more than price does. | runs live | use_cases.py |
muni-premium-no-loss | Amortization is mandatory with no deduction, and basis falls to the redemption price. The expectation of a loss at maturity is the thing to correct before they buy. | runs live | use_cases.py |
muni-rejects-percentage-rate | Refused rather than producing a yield nobody could sanity-check. | runs live | use_cases.py |
muni-capabilities | Whether a bond is a private activity bond, what any state charges, and credit or call risk. Munis are also outside every portfolio engine. | runs live | use_cases.py |
muni-naive-formula-understates-for-a-high-earner | A 3.5% in-state muni for a 37% federal, 9.3% state, surtax-paying holder. The rate avoided is 0.37 + 0.038 + 0.093 = 0.501, so the equivalent yield is 0.035/0.499 = 7.014%. Federal alone gives 0.035/0.63 = 5.556%, which understates by nearly a point and a half. | rule | municipal.yaml |
muni-out-of-state-bond-avoids-less | The same bond bought out of state is federally exempt and state taxable, so only 0.37 + 0.038 = 0.408 is avoided and the equivalent yield falls to 0.035/0.592 = 5.912%. | rule | municipal.yaml |
muni-surtax-is-part-of-what-is-avoided | Without the surtax the same in-state bond avoids 0.463 and yields an equivalent 6.518%. The 3.8 points are worth half a point of yield. | rule | municipal.yaml |
muni-private-activity-in-amt-has-no-advantage | A private activity bond held by a taxpayer in AMT: the interest is a preference item under section 57(a)(5), so nothing is avoided and the equivalent yield is the stated 3.5%. | rule | municipal.yaml |
muni-private-activity-outside-amt-keeps-the-advantage | The same bond for a taxpayer not in AMT is exempt as normal. The result carries a warning rather than a different number. | rule | municipal.yaml |
muni-discount-above-the-threshold-is-ordinary-income | 1,000 redemption, 10 complete years: the threshold is 1000 x 0.0025 x 10 = 25. A 50 discount is above it, so accrued market discount is ordinary income on disposition, not capital gain. | rule | municipal.yaml |
muni-small-discount-stays-capital | The same bond bought at 980: a 20 discount against a 25 threshold is de minimis, treated as zero, and the gain on redemption is capital. | rule | municipal.yaml |
muni-a-short-bond-tolerates-far-less-discount | Two complete years gives a threshold of only 1000 x 0.0025 x 2 = 5, so the same 20 discount that was de minimis over ten years is ordinary income over two. Maturity drives this more than price does. | rule | municipal.yaml |
muni-a-discount-exactly-at-the-threshold-is-not-de-minimis | The statute says "less than", so a 25 discount against a 25 threshold fails the test. The boundary matters because it is where advice changes. | rule | municipal.yaml |
muni-bought-at-par-has-no-discount | No discount at all, so no character question arises and nothing accrues. | rule | municipal.yaml |
muni-premium-buys-no-deduction | 1,050 for a bond redeeming at 1,000 over ten years: 50 of premium, 5 a year, and section 171(a)(2) allows no deduction for any of it. | rule | municipal.yaml |
muni-premium-leaves-no-loss-at-maturity | Basis falls by the amortization each year, so at maturity it equals the 1,000 redemption price and there is no loss to claim. The holder who expected one has already had the benefit. | rule | municipal.yaml |
muni-no-premium-when-bought-at-a-discount | Bought below par, so there is no premium to amortize. The discount has its own treatment and is not computed by this calculation. | rule | municipal.yaml |
muni-exempt-interest-still-reaches-social-security | The pack records that exempt interest is added back under section 86(b)(2)(B) when deciding how much Social Security is taxable. No engine computes that, so the result carries it as a note rather than a figure. | rule | municipal.yaml |
muni-private-activity-issued-in-2009-escapes-the-preference | Section 57(a)(5)(C)(vi) excepts private activity bonds issued in 2009 and 2010. A 2009 bond held by a taxpayer in AMT keeps its exemption, so the full 50.1% is still avoided where an otherwise identical 2008 bond would avoid nothing. | rule | municipal.yaml |
muni-private-activity-issued-in-2008-does-not | One year earlier and the preference applies, so the same taxpayer in AMT gets no advantage at all. The window is narrow and the difference total. | rule | municipal.yaml |
Know what a gain costs 31
Every segment, and where a wrong answer is most likely to be believed, because the output looks like a return even though it is not one.
| Case | What it pins | Suite | Defined in |
|---|---|---|---|
federal-estimate | Ordinary tax band by band, then the gain stacked on top of taxable income so it lands in the right preferential band. | runs live | use_cases.py |
federal-gain-splits | The gain is split across bands rather than taxed wholly at the rate its top dollar reaches. This is the error the legacy code made. | runs live | use_cases.py |
federal-niit-threshold | At $260,000 MAGI only $10,000 is over the threshold, so the 3.8% applies to that, not to the whole $45,000 of investment income. | runs live | use_cases.py |
federal-amt-bites | Tentative minimum tax against regular tax, with the exemption phasing out at 50 cents per dollar over the threshold. | runs live | use_cases.py |
federal-amt-keeps-rates | A gain inside AMTI keeps its preferential rate. Taxing it at 26/28% would overstate the liability substantially. | runs live | use_cases.py |
federal-loss-limit | The annual limit and what carries forward, rather than deducting the whole loss against ordinary income. | runs live | use_cases.py |
federal-rejects-float | Money is decimal. A value that would silently lose a fraction of a cent is rejected at the boundary rather than rounded quietly. | runs live | use_cases.py |
federal-capabilities | The boundary stated by the server itself: no return, no credits, no state tax, and AMT and section 199A never folded together. | runs live | use_cases.py |
amt-below-phaseout-full-exemption | AMTI 600,000 MFJ is below the 1,000,000 threshold, so the full 140,200 exemption survives. Base 459,800: 26% x 244,500 = 63,570 plus 28% x 215,300 = 60,284, giving 123,854. | rule | amt.yaml |
amt-not-owed-when-regular-tax-is-higher | AMT is the excess over regular tax, so a higher regular tax means none. | rule | amt.yaml |
amt-exemption-phases-out-at-fifty-percent | AMTI 1,200,000 is 200,000 over the MFJ threshold. At 50 cents on the dollar that removes 100,000 of the 140,200 exemption, leaving 40,200. | rule | amt.yaml |
amt-exemption-fully-eliminated | The loss is capped at the exemption; it cannot go negative. | rule | amt.yaml |
amt-single-thresholds-are-half | A single filer starts losing the exemption at 500,000. | rule | amt.yaml |
amt-preserves-preferential-rates-on-gains | Capital gain keeps its preferential rate inside AMT. Base 459,800 less the 400,000 gain leaves 59,800 of ordinary base at 26% = 15,548. The gain stacked on 59,800 puts 39,100 in the 0% band and 360,900 at 15% = 54,135. Tentative minimum tax 69,683. Taxing the whole base at 26/28% would have given 123,854 — an overstatement of more than 54,000. | rule | amt.yaml |
amt-low-income-owes-nothing | AMTI below the exemption leaves no base at all. | rule | amt.yaml |
ordinary-mfj-three-bands | MFJ taxable income of 152,800 spans three bands. 10% x 24,800 = 2,480; 12% x 76,000 = 9,120; 22% x 52,000 = 11,440. | rule | federal_income.yaml |
ordinary-single-top-band | A single filer above 640,600 reaches the 37% band. | rule | federal_income.yaml |
ordinary-hoh-differs-from-single | HOH bands are wider at the bottom than single. 10% x 17,700 = 1,770 plus 12% x 42,300 = 5,076, giving 6,846. | rule | federal_income.yaml |
ordinary-mfs-top-band-is-half-of-mfj | MFS reaches 37% at 384,350, half the MFJ figure. | rule | federal_income.yaml |
standard-deduction-mfj | 185,000 less the 32,200 MFJ standard deduction leaves 152,800. | rule | federal_income.yaml |
standard-deduction-overridden-by-itemizing | A supplied itemized figure replaces the standard deduction. | rule | federal_income.yaml |
capital-gain-straddles-zero-and-fifteen | The classic error: taxing the whole gain at the rate its top dollar reaches. Ordinary income of 90,000 leaves 8,900 of the MFJ 0% band; the remaining 91,100 is taxed at 15% for 13,665, not 15,000. | rule | federal_income.yaml |
capital-gain-entirely-in-zero-band | A modest gain with no other income is untaxed. | rule | federal_income.yaml |
capital-gain-reaches-twenty-percent | 13,700 of room below 613,700 at 15%, then 86,300 at 20%. | rule | federal_income.yaml |
niit-excess-binds | MAGI 260,000 is 10,000 over the MFJ threshold; NII is larger. | rule | federal_income.yaml |
niit-nii-binds | Well over the threshold, so the smaller NII is the base. | rule | federal_income.yaml |
niit-below-threshold | No surtax below the threshold, however large the NII. | rule | federal_income.yaml |
niit-mfs-threshold-is-125000 | The MFS threshold is half the MFJ figure and is not indexed. | rule | federal_income.yaml |
capital-loss-limited-with-carryforward | 3,000 deductible now, the remaining 9,000 carries forward. | rule | federal_income.yaml |
capital-loss-mfs-limit-halved | MFS is limited to 1,500, half the amount available to other statuses. | rule | federal_income.yaml |
capital-loss-smaller-than-the-limit | A loss under the cap is fully deductible with nothing carried. | rule | federal_income.yaml |
Business owner and entity 25
Founders, practice owners, and anyone with pass-through income — at UHNW that is most clients.
| Case | What it pins | Suite | Defined in |
|---|---|---|---|
federal-qbi-phase-in | Above the threshold the wage limit applies in proportion to how far into the phase-in range the taxpayer is, not all at once. | runs live | use_cases.py |
federal-qbi-sstb | The same figures as a consultancy rather than a manufacturer: the applicable percentage falls to zero and the deduction goes with it. | runs live | use_cases.py |
biz-179-phaseout | The dollar limit falls one-for-one with spending above the threshold, so deferring a purchase across the year end can restore it. | runs live | use_cases.py |
biz-179-eliminated | Past a point there is no section 179 deduction at all, which is the opposite of what buying more usually does. | runs live | use_cases.py |
biz-179-income-limited | Capped at business income, with the excess carried forward rather than lost. Two different limits that are easily confused. | runs live | use_cases.py |
biz-entity-close-call | $368,000 against $370,000 — close enough that payroll or state tax, neither of which is included, would decide it. | runs live | use_cases.py |
biz-entity-retained | Only the corporate layer is paid this year. The second layer is deferred, not avoided, and the comparison flips. | runs live | use_cases.py |
biz-rejects-rate-over-one | Refused rather than taxing the income at 3,700%. The fraction is range-checked at the boundary. | runs live | use_cases.py |
section-179-under-the-threshold-is-unreduced | 3,000,000 of spending is below the 4,090,000 threshold, so the full 2,560,000 dollar limit survives and the deduction is capped by it. | rule | business_compliance_salt.yaml |
section-179-phases-out-dollar-for-dollar | 4,500,000 of spending is 410,000 over the threshold, so the limit falls by the same 410,000 to 2,150,000. The reduction is one-for-one, not a percentage. | rule | business_compliance_salt.yaml |
section-179-disappears-entirely-with-enough-spending | Spending of 6,700,000 is 2,610,000 over the threshold, more than the 2,560,000 limit itself, so no section 179 deduction is available at all. | rule | business_compliance_salt.yaml |
section-179-business-income-limit-carries-forward | 500,000 elected against 300,000 of business income: the deduction is capped at income and the remaining 200,000 carries forward rather than being lost. | rule | business_compliance_salt.yaml |
entity-double-layer-against-a-top-rate-owner | 1,000,000 at 21% is 210,000 of corporate tax, leaving 790,000. Distributed in full at 20% adds 158,000, for 368,000 against 370,000 passed through at 37%. The corporation is ahead by 2,000, which is a narrower margin than people expect. | rule | business_compliance_salt.yaml |
entity-retaining-earnings-defers-the-second-layer | The same income with nothing distributed costs only the 210,000 corporate layer this year, well under the 370,000 pass-through figure. The second layer is deferred, not avoided. | rule | business_compliance_salt.yaml |
entity-passthrough-deduction-reverses-the-comparison | A 200,000 section 199A deduction drops the pass-through base to 800,000, taxed at 37% for 296,000, against the same 368,000 corporate total. | rule | business_compliance_salt.yaml |
qbi-below-threshold-no-limits | Below the threshold neither the wage limit nor the service-business restriction applies. 20% of 200,000 is 40,000. | rule | qbi.yaml |
qbi-sstb-unrestricted-below-threshold | A service business below the threshold is treated like any other. | rule | qbi.yaml |
qbi-wage-limit-above-the-range | Fully phased in, the wage limit binds: 50% of 50,000 wages is 25,000, below the 40,000 tentative deduction. | rule | qbi.yaml |
qbi-ubia-alternative-beats-wages-alone | 25% of wages plus 2.5% of property can exceed half the wages where a business is capital heavy: 5,000 + 50,000 = 55,000 beats 10,000. | rule | qbi.yaml |
qbi-wage-limit-phases-in-not-switches-on | Halfway through the 150,000 MFJ range only half the excess over the limit is removed. Tentative 40,000, limit 25,000, ratio 0.5, so the reduction is 7,500 and the deduction is 32,500. | rule | qbi.yaml |
qbi-sstb-fully-phased-out | A specified service business above the range gets nothing on that income, and the new minimum deduction is not applied because no QBI is taken into account. That interaction is unsettled and is flagged for review. | rule | qbi.yaml |
qbi-sstb-partial-phase-out | At 51% through the range the applicable percentage is 49%. QBI, wages and property are all reduced, then the wage limit phases in on top. | rule | qbi.yaml |
qbi-capped-by-taxable-income-less-gains | The overall cap is 20% of taxable income less net capital gain. With 150,000 of the 200,000 taxable income being gain, the cap is 10,000. | rule | qbi.yaml |
qbi-minimum-deduction-for-small-active-business | Above the range with no wages the limit would be zero, but at least 1,000 of active QBI secures the 400 minimum. | rule | qbi.yaml |
qbi-single-threshold-is-half-of-mfj | A single filer is fully phased in by 276,750. | rule | qbi.yaml |
Transfer wealth 18
UHNW almost exclusively, plus any trustee filing a 1041. Lifetime gifts are added back to the estate, not subtracted from it.
| Case | What it pins | Suite | Defined in |
|---|---|---|---|
estate-within-exclusion | No federal estate tax at $12m against a $15m exclusion. A return may still be due, and several states tax far lower estates. | runs live | use_cases.py |
estate-gifts-added-back | $2m of prior gifts does not simply shrink the estate: it is added to it before the rate schedule, so it pushes the estate into higher brackets. | runs live | use_cases.py |
estate-portability | Doubles the shelter to $30m. Taken as given: portability needs a timely return on the first death, which this server cannot check. | runs live | use_cases.py |
estate-gift-splitting | $19,000 times three recipients times two donors shelters $114,000. Splitting needs both spouses to consent on a return. | runs live | use_cases.py |
estate-noncitizen-spouse | The unlimited marital deduction does not apply, so a separate and much larger annual figure governs instead of the ordinary $19,000. | runs live | use_cases.py |
estate-splitting-refused-for-spouse-gift | The non-citizen spouse figure applies to one donor and one spouse. Multiplying it would invent an exclusion that does not exist. | runs live | use_cases.py |
estate-fiduciary-compression | The compression that drives most distribution planning: an individual reaches 37% in the hundreds of thousands, a trust at $16,000. | runs live | use_cases.py |
estate-within-the-exclusion-owes-nothing | A 12,000,000 estate sits under the 15,000,000 exclusion, so the credit covers the whole tentative tax. | rule | estate_gift.yaml |
estate-lifetime-gifts-are-added-back-not-subtracted | Taxable estate 19,500,000 plus 2,000,000 of lifetime gifts is a 21,500,000 base. Tentative 345,800 + 40% x 20,500,000 = 8,545,800. Credit on the 15,000,000 exclusion is 345,800 + 40% x 14,000,000 = 5,945,800. Tax is the difference, 2,600,000. | rule | estate_gift.yaml |
estate-portability-doubles-the-shelter | A deceased spouse's unused exclusion of 15,000,000 raises the available exclusion to 30,000,000, which covers a 28,000,000 estate entirely. | rule | estate_gift.yaml |
estate-lowest-bracket-is-reached-only-without-the-credit | With no exclusion consumed by gifts, a 15,010,000 estate has 10,000 above the exclusion. Tentative on 15,010,000 is 345,800 + 40% x 14,010,000 = 5,949,800; credit is 5,945,800; tax is 4,000, which is the 40% top rate applied to the 10,000 of excess. | rule | estate_gift.yaml |
gift-single-donor-single-recipient | One donor to one recipient: 19,000 of a 25,000 gift is covered by the annual exclusion and 6,000 is a taxable gift. | rule | estate_gift.yaml |
gift-splitting-doubles-the-exclusion | Two donors, one recipient, so 38,000 of a 50,000 gift is covered. | rule | estate_gift.yaml |
gift-to-three-children-from-two-parents | 19,000 x 3 recipients x 2 donors is 114,000, covering a 100,000 gift. | rule | estate_gift.yaml |
gift-to-noncitizen-spouse-uses-its-own-figure | The unlimited marital deduction does not apply, so a separate and much larger annual figure of 194,000 governs. | rule | estate_gift.yaml |
fiduciary-reaches-the-top-rate-at-sixteen-thousand | 20,000 of ordinary income: 10% x 3,300 = 330, 24% x 8,400 = 2,016, 35% x 4,300 = 1,505, 37% x 4,000 = 1,480. Total 5,331. | rule | estate_gift.yaml |
fiduciary-gain-stacks-on-ordinary-income | With 20,000 of ordinary income the stack is already past the 16,250 fifteen-percent ceiling, so the whole 10,000 gain is taxed at 20%. | rule | estate_gift.yaml |
fiduciary-small-gain-falls-in-the-zero-band | With 1,000 of ordinary income a 2,000 gain stacks to 3,000, still inside the 3,300 zero-rate ceiling, so no tax on the gain at all. | rule | estate_gift.yaml |
Fix a filing problem 16
Every segment, and the group most often needed by a prospect rather than a client: someone arrives already late.
| Case | What it pins | Suite | Defined in |
|---|---|---|---|
comp-file-vs-pay | Failure to file runs at ten times the failure-to-pay rate, with the section 6651(c) offset applied so the combined rate is 5%, not 5.5%. | runs live | use_cases.py |
comp-filed-not-paid | $200 instead of $2,000. This is why 'file even if you cannot pay' is the standard advice, and the numbers make the case. | runs live | use_cases.py |
comp-minimum-penalty | The percentage addition would be $360, so the statutory floor applies instead. Small balances are not proportionally cheap. | runs live | use_cases.py |
comp-partnership-penalty | $8,320 for eight partners and four months. The penalty is per owner per month and does not depend on tax owed. | runs live | use_cases.py |
comp-safe-harbour-jump | 110% of last year's known tax beats 90% of this year's guess. The prior-year route is what protects a year like this. | runs live | use_cases.py |
comp-safe-harbour-fall | The current-year route governs when income drops. Taking the prior-year route out of habit ties up cash for a year. | runs live | use_cases.py |
comp-rejects-negative-days | Refused rather than producing a negative penalty. | runs live | use_cases.py |
penalty-late-filing-is-ten-times-late-payment | 95 days is four months. Failure-to-file runs at 5% less the 0.5% failure-to-pay rate, so 4.5% x 4 x 10,000 = 1,800, plus 0.5% x 4 x 10,000 = 200 for failure to pay. Total 2,000. | rule | business_compliance_salt.yaml |
penalty-filing-on-time-without-paying-costs-far-less | The same 10,000 unpaid for the same four months, but filed on time, costs 200 rather than 2,000. This is why filing without paying is the advice. | rule | business_compliance_salt.yaml |
penalty-one-day-late-is-a-whole-month | Any part of a month counts as a whole month, so a single day late already carries 4.5% + 0.5% of the unpaid tax. | rule | business_compliance_salt.yaml |
penalty-minimum-applies-after-sixty-days | With only 2,000 unpaid and 95 days late, the computed failure-to-file addition is 4.5% x 4 x 2,000 = 360, below the 535 floor, so the floor applies instead. | rule | business_compliance_salt.yaml |
penalty-partnership-does-not-depend-on-tax-owed | Eight partners, four months late: 260 x 8 x 4 = 8,320, owed even by a partnership with no taxable income. | rule | business_compliance_salt.yaml |
penalty-entity-is-capped-at-twelve-months | Twenty months late is charged as twelve: 260 x 3 x 12 = 9,360. | rule | business_compliance_salt.yaml |
safe-harbour-prior-year-route-protects-a-jump-in-income | Prior AGI of 400,000 is over the 150,000 threshold, so the prior-year route is 110% x 80,000 = 88,000. The current-year route is 90% x 200,000 = 180,000. The smaller governs, so 88,000 protects the year. | rule | business_compliance_salt.yaml |
safe-harbour-lower-income-uses-one-hundred-percent | Prior AGI of 120,000 is under the threshold, so the prior-year route is 100% of last year's 20,000 rather than 110%. | rule | business_compliance_salt.yaml |
safe-harbour-current-year-route-wins-when-income-falls | Income fell, so 90% of this year's 20,000 is 18,000, below 110% of last year's 90,000. Paying to last year's figure would massively overpay. | rule | business_compliance_salt.yaml |
Expat and cross-border 14
Clients working abroad, and anyone receiving money from family overseas.
| Case | What it pins | Suite | Defined in |
|---|---|---|---|
intl-feie-with-housing | The housing amount is the excess over a 16% base, capped at 30%, not a deduction for everything spent on rent. | runs live | use_cases.py |
intl-feie-prorated | 183 days prorates both the exclusion and the housing figures. A day count that is off by one changes the answer. | runs live | use_cases.py |
intl-feie-housing-below-base | The base is a floor, not a starting allowance. Spending under it produces no housing amount at all. | runs live | use_cases.py |
intl-foreign-gift-individual | Reportable, and not income. The threshold for individuals is the flat statutory $100,000, which is not indexed. | runs live | use_cases.py |
intl-foreign-gift-entity | A far lower, inflation-adjusted threshold applies, so $25,000 is reportable where it would not be from an individual. | runs live | use_cases.py |
intl-rejects-bad-day-count | 400 days in a year is not a typo the server will absorb. | runs live | use_cases.py |
feie-full-year-exclusion-caps-the-income | 180,000 of foreign earned income against the 132,900 exclusion for a full qualifying year leaves 47,100 before any housing amount. | rule | international.yaml |
feie-is-prorated-for-a-partial-qualifying-period | 183 qualifying days is 183/365 of the year, so the exclusion is 132,900 x 183/365 = 66,632.05 rather than the full amount. | rule | international.yaml |
feie-housing-is-the-excess-over-a-base-and-is-capped | Base is 16% of 132,900 = 21,264 and the limit is 30% = 39,870. With 45,000 of expenses the limit binds, so the housing amount is 39,870 - 21,264 = 18,606. Total excluded is 132,900 + 18,606 = 151,506. | rule | international.yaml |
feie-housing-below-the-base-gives-nothing | Expenses of 15,000 are below the 21,264 base, so there is no housing amount at all. The base is a floor, not a starting allowance. | rule | international.yaml |
feie-cannot-exclude-more-than-was-earned | Income of 50,000 is below the exclusion, so only 50,000 is excluded and nothing remains for a housing amount to attach to. | rule | international.yaml |
foreign-gift-from-an-individual-uses-the-statutory-figure | Gifts from foreign individuals and estates use the flat 100,000 statutory threshold, which section 6039F does not index, so 150,000 is reportable. | rule | international.yaml |
foreign-gift-from-a-corporation-uses-the-indexed-figure | Gifts from foreign corporations and partnerships use the much lower inflation-adjusted threshold of 20,573, so 25,000 is reportable where the same amount from an individual would not be. | rule | international.yaml |
foreign-gift-below-the-threshold-is-not-reportable | 90,000 from a foreign individual is under the 100,000 threshold. It is still not income either way; the question here is disclosure only. | rule | international.yaml |
What is deductible 9
HNW and UHNW itemizers in high-tax states. The phase-down is a slope, not a cliff.
| Case | What it pins | Suite | Defined in |
|---|---|---|---|
salt-full-cap | $40,400 of a $60,000 bill is deductible; the rest is simply lost and does not carry forward. | runs live | use_cases.py |
salt-phasing-down | A slope, not a cliff: 30 cents per dollar of MAGI above the threshold. Income in this band costs more than its own marginal rate. | runs live | use_cases.py |
salt-at-the-floor | The cap stops falling at $10,000 however high income goes, so the penalty on extra income ends there too. | runs live | use_cases.py |
salt-separate-filer | Cap, threshold and floor are all halved, which is easy to miss when comparing a joint and separate return. | runs live | use_cases.py |
salt-no-state-is-covered | No state pack is bound, so no state's own tax is computed. The answer names what a state pack would have to contain. | runs live | use_cases.py |
salt-below-the-threshold-gets-the-full-cap | MAGI of 400,000 is under the 505,000 threshold, so the full 40,400 cap applies and 60,000 of tax paid is limited to it. | rule | business_compliance_salt.yaml |
salt-phases-down-thirty-cents-on-the-dollar | MAGI of 550,000 is 45,000 over the threshold. At 30 cents that removes 13,500, leaving an effective cap of 26,900. It is a slope, not a cliff. | rule | business_compliance_salt.yaml |
salt-stops-falling-at-the-floor | MAGI of 700,000 is far past the 606,333 point where the cap reaches the 10,000 floor, and it does not fall below it however high income goes. | rule | business_compliance_salt.yaml |
salt-separate-filers-get-half-of-everything | Married filing separately halves the cap to 20,200, the threshold to 252,500 and the floor to 5,000. | rule | business_compliance_salt.yaml |
Trust the data 12
Every segment, worst at UHNW. Unknown basis blocks rather than defaulting to zero, which is a question instead of a confident wrong answer.
| Case | What it pins | Suite | Defined in |
|---|---|---|---|
portfolio-intake | Run first on a new export. Lots with unknown basis are named here rather than silently treated as zero-basis later. | runs live | use_cases.py |
missing-basis-blocks-rather-than-defaulting-to-zero | A lot with unknown basis produces a blocked disposition, not a gain computed from a zero that was never in the record. | rule | investment_rules.yaml |
data-missing-basis-is-never-sold | A lot with unknown basis cannot be priced for tax and must not be traded. | conformance | data_quality.yaml |
data-missing-basis-is-reported | The exclusion is visible in diagnostics rather than silent. | conformance | data_quality.yaml |
data-missing-acquisition-date-is-never-sold | Without an acquisition date the holding period is undeterminable. | conformance | data_quality.yaml |
data-missing-price-is-never-sold | A lot with no supplied price cannot have proceeds computed. | conformance | data_quality.yaml |
data-missing-price-is-reported | A missing price is a data gap worth naming. | conformance | data_quality.yaml |
data-usable-lot-still-trades | Bad neighbours do not disqualify the one sound lot. | conformance | data_quality.yaml |
data-all-unusable-is-infeasible | When only unusable lots could meet the target, the run is infeasible. | conformance | data_quality.yaml |
data-unusable-lots-do-not-count-toward-cash | Excluded lots contribute nothing to the proceeds figure. | conformance | data_quality.yaml |
data-no-price-for-any-lot | With no prices at all nothing can be valued, so nothing is proposed. | conformance | data_quality.yaml |
data-blocked-lots-never-reach-the-trade-list | The invariant battery rejects any trade against an unusable lot. | conformance | data_quality.yaml |
Entitlement, scope and replay 6
Every deployment. An account the caller may not read is refused in the same words as one that does not exist.
| Case | What it pins | Suite | Defined in |
|---|---|---|---|
portfolio-discover | The first call of a session. Account metadata comes from the document, so it is named here; entitlements still decide the answer. | runs live | use_cases.py |
portfolio-denied | The document describes it, so it exists. It is still refused, and with the same wording as an account that does not exist at all. | runs live | use_cases.py |
portfolio-idempotent | Repeating a request must not produce a second analysis with a new evidence package. The key is the caller's guarantee. | runs live | use_cases.py |
portfolio-household-refused | Running an account engine over a household is refused rather than approximated, and the refusal names what is available instead. | runs live | use_cases.py |
portfolio-wrong-year | No reviewed pack covers 2019, so the run is blocked rather than computed from this year's rules under last decade's law. | runs live | use_cases.py |
portfolio-no-execution | Not disabled, not permissioned off — absent. A caller asking for one is refused at the boundary. | runs live | use_cases.py |
Conservation, restrictions and determinism 30
Not client scenarios: the invariant battery every proposal is recomputed against, whatever engine produced it.
| Case | What it pins | Suite | Defined in |
|---|---|---|---|
cons-no-lot-sold-twice | A lot may appear at most once in the trade list. | conformance | conservation.yaml |
cons-only-known-lots | Every lot sold must exist in the frozen snapshot. | conformance | conservation.yaml |
cons-quantities-positive | A proposed quantity of zero or less is not a trade. | conformance | conservation.yaml |
cons-empty-portfolio-yields-no-trades | Nothing to sell means an empty trade list, not an error. | conformance | conservation.yaml |
cons-no-cash-target-still-conserves | With no cash target the adapter may harvest, but not oversell. | conformance | conservation.yaml |
cons-proceeds-match-price-times-quantity | Recomputed proceeds must equal quantity times the supplied price. | conformance | conservation.yaml |
cons-losses-only-portfolio | A portfolio of losses raises cash without consuming any budget. | conformance | conservation.yaml |
cons-gains-only-portfolio | A portfolio of gains can still be sold when the budget allows. | conformance | conservation.yaml |
cons-zero-cash-target | A zero cash target is satisfied immediately and needs no gain. | conformance | conservation.yaml |
cons-large-target-still-conserves | An unreachable target must not tempt an adapter into overselling. | conformance | conservation.yaml |
cons-no-budget-declared | With no gain budget at all the run is unconstrained but still valid. | conformance | conservation.yaml |
cons-single-lot-portfolio | A one-lot portfolio is a degenerate but legal problem. | conformance | conservation.yaml |
determinism-mixed-portfolio | Two independent solves of one problem must produce identical trades. | conformance | determinism.yaml |
determinism-tight-budget | A binding budget must not introduce run-to-run variation. | conformance | determinism.yaml |
determinism-infeasible-run | Even a failing run must fail the same way each time. | conformance | determinism.yaml |
determinism-with-restrictions | Restrictions must not make tie-breaking order dependent. | conformance | determinism.yaml |
determinism-with-wash-screening | Wash-sale probing must not vary between runs. | conformance | determinism.yaml |
determinism-equal-value-lots | Lots of similar appeal need a declared tie-break, not an arbitrary one. | conformance | determinism.yaml |
restrict-security-is-never-sold | A do-not-sell security stays untouched even when it is the obvious source of cash. | conformance | restrictions.yaml |
restrict-two-securities | Several restricted securities are all excluded. | conformance | restrictions.yaml |
restrict-account-blocks-everything-in-it | A restricted account contributes nothing at all. | conformance | restrictions.yaml |
restrict-min-holding-blocks-recent-lot | A lot held fewer days than policy allows cannot be sold. | conformance | restrictions.yaml |
restrict-min-holding-allows-seasoned-lot | A long-held lot passes the same minimum-holding policy. | conformance | restrictions.yaml |
restrict-min-holding-zero-blocks-nothing | A zero-day minimum is no restriction at all. | conformance | restrictions.yaml |
restrict-everything-is-infeasible | Restricting every security leaves nothing to sell. | conformance | restrictions.yaml |
restrict-plus-budget-interact | A restriction on the loss lot removes the offset the budget relied on. | conformance | restrictions.yaml |
restrict-unrelated-security-has-no-effect | Restricting a security the portfolio does not hold changes nothing. | conformance | restrictions.yaml |
restrict-loss-lot-still-allows-other-losses | Restricting one loss lot leaves the other available. | conformance | restrictions.yaml |
restrict-no-restrictions-declared | An empty restriction list is not a restriction. | conformance | restrictions.yaml |
restrict-account-and-security-together | Account and security restrictions apply together, not in preference. | conformance | restrictions.yaml |