@livefolio/sdk 0.4.4 → 0.5.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -176,6 +176,54 @@ type Position = {
176
176
  /** Optional key-value labels for strategy attribution or downstream reporting. */
177
177
  tags?: Record<string, unknown>;
178
178
  };
179
+ /**
180
+ * Tax classification of a {@link RealizedEvent}. Drives which rate bucket the
181
+ * income falls into during year-level tax aggregation.
182
+ */
183
+ type IncomeKind = 'capital-gain' | 'qualified-dividend' | 'ordinary-dividend' | 'interest';
184
+ /**
185
+ * A single tax lot — one acquisition of `quantity` shares of `asset` at a
186
+ * point in time. The cost-basis source of truth for long holdings. Partial
187
+ * sales reduce `quantity` and pro-rate `basis`.
188
+ */
189
+ type Lot = {
190
+ /** Opaque id assigned by {@link nextLotId} on creation. */
191
+ id: string;
192
+ asset: Asset;
193
+ /** Shares remaining in this lot after any partial sales. */
194
+ quantity: number;
195
+ /** Date the lot was opened (a DRIP lot's clock starts at its pay date). */
196
+ openDate: Date;
197
+ /** Per-share open price, excluding fees. */
198
+ openPrice: number;
199
+ /** Total cost basis for `quantity` shares incl. entry fees; pro-rated on partial sale and bumped by wash-sale §1091. */
200
+ basis: number;
201
+ /** Running total of disallowed wash-sale losses absorbed into `basis`. */
202
+ washSaleAdjustment?: number;
203
+ /** Set when this lot was created via dividend reinvestment; references the lot whose dividend funded it. */
204
+ dripParent?: string;
205
+ };
206
+ /**
207
+ * An append-only record of realized taxable activity: a capital gain/loss from
208
+ * closing (part of) a lot, or dividend/interest income. `quantity` is `0` for
209
+ * income events (dividends, interest), which carry `basis: 0` and `gain = proceeds`.
210
+ */
211
+ type RealizedEvent = {
212
+ asset: Asset;
213
+ /** The lot this event closed against. For income events, a reference token (e.g. the paying lot, or `'cash'` for interest). */
214
+ lotId: string;
215
+ /** Shares closed; `0` for dividend and interest income events. */
216
+ quantity: number;
217
+ openDate: Date;
218
+ closeDate: Date;
219
+ proceeds: number;
220
+ basis: number;
221
+ termType: 'short' | 'long';
222
+ gain: number;
223
+ incomeKind: IncomeKind;
224
+ /** When `> 0`, this much of a (negative) capital gain was disallowed by the wash-sale rule and rolled into a replacement lot's basis. */
225
+ washSaleDisallowed?: number;
226
+ };
179
227
  /**
180
228
  * A point-in-time snapshot of the full portfolio state.
181
229
  *
@@ -199,6 +247,17 @@ type Portfolio = {
199
247
  cash: number;
200
248
  /** All currently open positions. Immutable array — replaced (not mutated) by apply functions. */
201
249
  positions: ReadonlyArray<Position>;
250
+ /**
251
+ * Long-side tax ledger — the cost-basis source of truth for lot accounting.
252
+ * Maintained in parallel with `positions` by `applyFills`; defaults to `[]`
253
+ * when absent. Short positions and `adjust` orders do not participate.
254
+ */
255
+ lots?: ReadonlyArray<Lot>;
256
+ /**
257
+ * Append-only log of realized capital gains and dividend/interest income
258
+ * accumulated during a run. Defaults to `[]` when absent.
259
+ */
260
+ realized?: ReadonlyArray<RealizedEvent>;
202
261
  /** Logical timestamp of the most recent fill (or the portfolio's start date). */
203
262
  t: Date;
204
263
  };
@@ -380,6 +439,13 @@ type Fill = {
380
439
  price: number;
381
440
  /** Total transaction fees in the portfolio's base currency. */
382
441
  fees: number;
442
+ /**
443
+ * Optional id of the specific {@link Lot} this fill draws from on a sell.
444
+ * Set by {@link BacktestExecutor} when a `lotMethod` is configured so
445
+ * {@link applyFills} consumes the chosen lot rather than defaulting to FIFO.
446
+ * Absent on buys and on default-FIFO sells.
447
+ */
448
+ lotId?: string;
383
449
  };
384
450
 
385
451
  /**
@@ -3220,34 +3286,34 @@ declare function isRebalanceDay(t: Date, freq: RebalanceFrequency, calendar: Cal
3220
3286
  */
3221
3287
  declare function fromSpec(spec: TacticalSpec, opts: FromSpecOptions): Strategy<TacticalFeatures, RuleTreeState>;
3222
3288
 
3223
- type index$1_AllocateNode = AllocateNode;
3224
- type index$1_AssetRef = AssetRef;
3225
- type index$1_Comparison = Comparison;
3226
- type index$1_ComparisonOp = ComparisonOp;
3227
- type index$1_FeatureRef = FeatureRef;
3228
- type index$1_FromSpecOptions = FromSpecOptions;
3229
- type index$1_IfNode = IfNode;
3230
- type index$1_RebalanceConfig = RebalanceConfig;
3231
- type index$1_RebalanceFrequency = RebalanceFrequency;
3232
- type index$1_RuleNode = RuleNode;
3233
- type index$1_RuleTreeState = RuleTreeState;
3234
- type index$1_SyntheticAsset = SyntheticAsset;
3235
- type index$1_TacticalFeatureKind = TacticalFeatureKind;
3236
- type index$1_TacticalFeatureSpec = TacticalFeatureSpec;
3237
- type index$1_TacticalFeatures = TacticalFeatures;
3238
- type index$1_TacticalSpec = TacticalSpec;
3239
- type index$1_Tolerance = Tolerance;
3240
- type index$1_WithStreamingSyntheticsOptions = WithStreamingSyntheticsOptions;
3241
- declare const index$1__resetTacticalDeprecationWarningForTesting: typeof _resetTacticalDeprecationWarningForTesting;
3242
- declare const index$1_evaluateFeatureSpecs: typeof evaluateFeatureSpecs;
3243
- declare const index$1_evaluateRuleTree: typeof evaluateRuleTree;
3244
- declare const index$1_fromSpec: typeof fromSpec;
3245
- declare const index$1_isRebalanceDay: typeof isRebalanceDay;
3246
- declare const index$1_periodKey: typeof periodKey;
3247
- declare const index$1_withStreamingSynthetics: typeof withStreamingSynthetics;
3248
- declare const index$1_withSynthetics: typeof withSynthetics;
3249
- declare namespace index$1 {
3250
- export { type index$1_AllocateNode as AllocateNode, type index$1_AssetRef as AssetRef, type index$1_Comparison as Comparison, type index$1_ComparisonOp as ComparisonOp, type index$1_FeatureRef as FeatureRef, type index$1_FromSpecOptions as FromSpecOptions, type index$1_IfNode as IfNode, type index$1_RebalanceConfig as RebalanceConfig, type index$1_RebalanceFrequency as RebalanceFrequency, type index$1_RuleNode as RuleNode, type index$1_RuleTreeState as RuleTreeState, type index$1_SyntheticAsset as SyntheticAsset, type index$1_TacticalFeatureKind as TacticalFeatureKind, type index$1_TacticalFeatureSpec as TacticalFeatureSpec, type index$1_TacticalFeatures as TacticalFeatures, type index$1_TacticalSpec as TacticalSpec, type index$1_Tolerance as Tolerance, type index$1_WithStreamingSyntheticsOptions as WithStreamingSyntheticsOptions, index$1__resetTacticalDeprecationWarningForTesting as _resetTacticalDeprecationWarningForTesting, index$1_evaluateFeatureSpecs as evaluateFeatureSpecs, index$1_evaluateRuleTree as evaluateRuleTree, index$1_fromSpec as fromSpec, index$1_isRebalanceDay as isRebalanceDay, index$1_periodKey as periodKey, index$1_withStreamingSynthetics as withStreamingSynthetics, index$1_withSynthetics as withSynthetics };
3289
+ type index$2_AllocateNode = AllocateNode;
3290
+ type index$2_AssetRef = AssetRef;
3291
+ type index$2_Comparison = Comparison;
3292
+ type index$2_ComparisonOp = ComparisonOp;
3293
+ type index$2_FeatureRef = FeatureRef;
3294
+ type index$2_FromSpecOptions = FromSpecOptions;
3295
+ type index$2_IfNode = IfNode;
3296
+ type index$2_RebalanceConfig = RebalanceConfig;
3297
+ type index$2_RebalanceFrequency = RebalanceFrequency;
3298
+ type index$2_RuleNode = RuleNode;
3299
+ type index$2_RuleTreeState = RuleTreeState;
3300
+ type index$2_SyntheticAsset = SyntheticAsset;
3301
+ type index$2_TacticalFeatureKind = TacticalFeatureKind;
3302
+ type index$2_TacticalFeatureSpec = TacticalFeatureSpec;
3303
+ type index$2_TacticalFeatures = TacticalFeatures;
3304
+ type index$2_TacticalSpec = TacticalSpec;
3305
+ type index$2_Tolerance = Tolerance;
3306
+ type index$2_WithStreamingSyntheticsOptions = WithStreamingSyntheticsOptions;
3307
+ declare const index$2__resetTacticalDeprecationWarningForTesting: typeof _resetTacticalDeprecationWarningForTesting;
3308
+ declare const index$2_evaluateFeatureSpecs: typeof evaluateFeatureSpecs;
3309
+ declare const index$2_evaluateRuleTree: typeof evaluateRuleTree;
3310
+ declare const index$2_fromSpec: typeof fromSpec;
3311
+ declare const index$2_isRebalanceDay: typeof isRebalanceDay;
3312
+ declare const index$2_periodKey: typeof periodKey;
3313
+ declare const index$2_withStreamingSynthetics: typeof withStreamingSynthetics;
3314
+ declare const index$2_withSynthetics: typeof withSynthetics;
3315
+ declare namespace index$2 {
3316
+ export { type index$2_AllocateNode as AllocateNode, type index$2_AssetRef as AssetRef, type index$2_Comparison as Comparison, type index$2_ComparisonOp as ComparisonOp, type index$2_FeatureRef as FeatureRef, type index$2_FromSpecOptions as FromSpecOptions, type index$2_IfNode as IfNode, type index$2_RebalanceConfig as RebalanceConfig, type index$2_RebalanceFrequency as RebalanceFrequency, type index$2_RuleNode as RuleNode, type index$2_RuleTreeState as RuleTreeState, type index$2_SyntheticAsset as SyntheticAsset, type index$2_TacticalFeatureKind as TacticalFeatureKind, type index$2_TacticalFeatureSpec as TacticalFeatureSpec, type index$2_TacticalFeatures as TacticalFeatures, type index$2_TacticalSpec as TacticalSpec, type index$2_Tolerance as Tolerance, type index$2_WithStreamingSyntheticsOptions as WithStreamingSyntheticsOptions, index$2__resetTacticalDeprecationWarningForTesting as _resetTacticalDeprecationWarningForTesting, index$2_evaluateFeatureSpecs as evaluateFeatureSpecs, index$2_evaluateRuleTree as evaluateRuleTree, index$2_fromSpec as fromSpec, index$2_isRebalanceDay as isRebalanceDay, index$2_periodKey as periodKey, index$2_withStreamingSynthetics as withStreamingSynthetics, index$2_withSynthetics as withSynthetics };
3251
3317
  }
3252
3318
 
3253
3319
  /**
@@ -3509,28 +3575,293 @@ declare function volatility(series: Series, period: number): Series;
3509
3575
  */
3510
3576
  declare function drawdown(series: Series, period: number): Series;
3511
3577
 
3512
- type index_BarField = BarField;
3513
- type index_ComputeFn = ComputeFn;
3514
- type index_FeatureKind = FeatureKind;
3515
- type index_FeatureRuntime = FeatureRuntime;
3516
- declare const index_FeatureRuntime: typeof FeatureRuntime;
3517
- type index_FeatureRuntimeOptions = FeatureRuntimeOptions;
3518
- type index_FeatureSpec = FeatureSpec;
3519
- type index_ReturnMode = ReturnMode;
3520
- declare const index_barsToSeries: typeof barsToSeries;
3521
- declare const index_collectBars: typeof collectBars;
3522
- declare const index_defineFeature: typeof defineFeature;
3523
- declare const index_drawdown: typeof drawdown;
3524
- declare const index_ema: typeof ema;
3525
- declare const index_getFeatureCompute: typeof getFeatureCompute;
3526
- declare const index_paramsHash: typeof paramsHash;
3527
- declare const index_returnSeries: typeof returnSeries;
3528
- declare const index_rsi: typeof rsi;
3529
- declare const index_seriesAt: typeof seriesAt;
3530
- declare const index_sma: typeof sma;
3531
- declare const index_volatility: typeof volatility;
3578
+ type index$1_BarField = BarField;
3579
+ type index$1_ComputeFn = ComputeFn;
3580
+ type index$1_FeatureKind = FeatureKind;
3581
+ type index$1_FeatureRuntime = FeatureRuntime;
3582
+ declare const index$1_FeatureRuntime: typeof FeatureRuntime;
3583
+ type index$1_FeatureRuntimeOptions = FeatureRuntimeOptions;
3584
+ type index$1_FeatureSpec = FeatureSpec;
3585
+ type index$1_ReturnMode = ReturnMode;
3586
+ declare const index$1_barsToSeries: typeof barsToSeries;
3587
+ declare const index$1_collectBars: typeof collectBars;
3588
+ declare const index$1_defineFeature: typeof defineFeature;
3589
+ declare const index$1_drawdown: typeof drawdown;
3590
+ declare const index$1_ema: typeof ema;
3591
+ declare const index$1_getFeatureCompute: typeof getFeatureCompute;
3592
+ declare const index$1_paramsHash: typeof paramsHash;
3593
+ declare const index$1_returnSeries: typeof returnSeries;
3594
+ declare const index$1_rsi: typeof rsi;
3595
+ declare const index$1_seriesAt: typeof seriesAt;
3596
+ declare const index$1_sma: typeof sma;
3597
+ declare const index$1_volatility: typeof volatility;
3598
+ declare namespace index$1 {
3599
+ export { type index$1_BarField as BarField, type index$1_ComputeFn as ComputeFn, type index$1_FeatureKind as FeatureKind, index$1_FeatureRuntime as FeatureRuntime, type index$1_FeatureRuntimeOptions as FeatureRuntimeOptions, type index$1_FeatureSpec as FeatureSpec, type index$1_ReturnMode as ReturnMode, index$1_barsToSeries as barsToSeries, index$1_collectBars as collectBars, index$1_defineFeature as defineFeature, index$1_drawdown as drawdown, index$1_ema as ema, index$1_getFeatureCompute as getFeatureCompute, index$1_paramsHash as paramsHash, index$1_returnSeries as returnSeries, index$1_rsi as rsi, index$1_seriesAt as seriesAt, index$1_sma as sma, index$1_volatility as volatility };
3600
+ }
3601
+
3602
+ /**
3603
+ * Calendar days between a lot's open date and `asOf` (float; may be fractional
3604
+ * or negative if `asOf` precedes `openDate`). Callers may `Math.floor` it.
3605
+ */
3606
+ declare function holdingPeriodDays(lot: Lot, asOf: Date): number;
3607
+ /** IRS §1222 rule: a holding period of strictly more than 365 calendar days is long-term. */
3608
+ declare function isLongTerm(days: number): boolean;
3609
+ /** Result of {@link realize}: the realized event plus what's left of the lot (`null` on a full sale). */
3610
+ type RealizeResult = {
3611
+ event: RealizedEvent;
3612
+ remainingLot: Lot | null;
3613
+ };
3614
+ /**
3615
+ * Realizes `qty` shares of `lot` at `salePrice` as of `asOf`, producing one
3616
+ * {@link RealizedEvent} and the remaining lot (or `null` on a full sale).
3617
+ *
3618
+ * Pure gain/loss primitive for estimation and lot ranking (e.g. selectMinTax,
3619
+ * TLH preview). It does **not** model transaction fees: pass a `salePrice` that
3620
+ * is already net of any per-share fee if you need fee-adjusted proceeds. The
3621
+ * stateful, fee-aware realization path used during execution lives in
3622
+ * `applyFills` (`consumeLots`), which pro-rates `fill.fees` across slices.
3623
+ *
3624
+ * Basis is pro-rated as `lot.basis / lot.quantity * qty` (so any
3625
+ * `washSaleAdjustment` already folded into `lot.basis` is carried through).
3626
+ *
3627
+ * @param lot - The lot to realize against.
3628
+ * @param qty - Shares to realize. Must be `> 0` and `<= lot.quantity`.
3629
+ * @param salePrice - Per-share sale price (net of fees if fee-adjustment is desired).
3630
+ * @param asOf - Sale date; drives short/long classification via the 365-day rule.
3631
+ * @returns `{ event, remainingLot }`; `remainingLot` is `null` when the whole lot is sold.
3632
+ * @throws {RangeError} if `qty <= 0` or `qty > lot.quantity`.
3633
+ */
3634
+ declare function realize(lot: Lot, qty: number, salePrice: number, asOf: Date): RealizeResult;
3635
+
3636
+ /** A slice of a tax lot consumed to fulfill part of a sell order. */
3637
+ type LotSlice = {
3638
+ lotId: string;
3639
+ quantity: number;
3640
+ };
3641
+ /** Short-term and long-term capital-gains tax rates (as decimals, e.g. 0.37). */
3642
+ type TaxRates = {
3643
+ shortTerm: number;
3644
+ longTerm: number;
3645
+ };
3646
+ /**
3647
+ * First-In-First-Out lot selection.
3648
+ *
3649
+ * Selects lots in ascending `openDate` order, consuming oldest lots first.
3650
+ */
3651
+ declare function selectFIFO(lots: readonly Lot[], qty: number): LotSlice[];
3652
+ /**
3653
+ * Last-In-First-Out lot selection.
3654
+ *
3655
+ * Selects lots in descending `openDate` order, consuming newest lots first.
3656
+ */
3657
+ declare function selectLIFO(lots: readonly Lot[], qty: number): LotSlice[];
3658
+ /**
3659
+ * Highest-In-First-Out lot selection.
3660
+ *
3661
+ * Selects lots in descending per-share basis (`basis / quantity`) order,
3662
+ * realizing the highest-cost lots first to minimize gains.
3663
+ */
3664
+ declare function selectHIFO(lots: readonly Lot[], qty: number): LotSlice[];
3665
+ /**
3666
+ * Tax-minimizing lot selection.
3667
+ *
3668
+ * Ranks lots by a 4-tier comparator designed to realize the least tax:
3669
+ * 1. Long-term losses (tier 0) — offsets LT gains at the lower LT rate
3670
+ * 2. Short-term losses (tier 1) — offsets ST gains at the higher ST rate
3671
+ * 3. Long-term gains (tier 2) — taxed at the lower LT rate
3672
+ * 4. Short-term gains (tier 3) — taxed at the higher ST rate
3673
+ *
3674
+ * Within a tier, lots are sorted by ascending gain-per-share so that the
3675
+ * largest losses (or smallest gains) are consumed first.
3676
+ *
3677
+ * Uses `isLongTerm(holdingPeriodDays(lot, ctx.asOf))` for term classification
3678
+ * and `ctx.price - lot.basis / lot.quantity` for gain-per-share.
3679
+ *
3680
+ * `ctx.rates` is accepted for forward-compatibility and signature stability
3681
+ * (the executor's min-tax `lotMethod` passes it through); the current 4-tier
3682
+ * ranking does not read the rate magnitudes.
3683
+ *
3684
+ * NOTE: this is a conservative, IRS-aligned *ordering* heuristic, not a literal
3685
+ * rate-weighted per-share tax minimum. The two can diverge: a $100/share LT gain
3686
+ * (tier 2, $20 tax at a 20% LT rate) is selected before a $50/share ST gain
3687
+ * (tier 3, $18.50 tax at a 37% ST rate), even though the ST lot realizes less
3688
+ * tax. The tier order favors the lower-rate bucket and sidesteps marginal-bracket
3689
+ * complexity; it does not compute `gain × rate` per lot.
3690
+ */
3691
+ declare function selectMinTax(lots: readonly Lot[], qty: number, ctx: {
3692
+ price: number;
3693
+ asOf: Date;
3694
+ rates: TaxRates;
3695
+ }): LotSlice[];
3696
+
3697
+ /**
3698
+ * Maximum capital-loss deduction against ordinary income per IRS §1211(b).
3699
+ * In a given tax year, net capital losses above `ORDINARY_OFFSET_CAP` are
3700
+ * carried forward to future years rather than deducted immediately.
3701
+ */
3702
+ declare const ORDINARY_OFFSET_CAP = 3000;
3703
+ /**
3704
+ * Year-level summary of taxable income across all categories.
3705
+ *
3706
+ * - `shortTermGains` / `longTermGains`: sum of positive `gain` values from
3707
+ * capital-gain events in each term bucket.
3708
+ * - `shortTermLosses` / `longTermLosses`: sum of |negative `gain`| values
3709
+ * (stored as **positive magnitudes**) from capital-gain events.
3710
+ * - `qualifiedDividends`: qualified dividend income (taxed at LT rate).
3711
+ * - `ordinaryDividends`: non-qualified dividend income (taxed at ST rate).
3712
+ * - `interestIncome`: interest income (taxed at ST rate).
3713
+ *
3714
+ * Capital losses **never** offset `qualifiedDividends` or `ordinaryDividends`.
3715
+ * The cross-offset logic in {@link crossOffset} operates only on the capital-
3716
+ * gain buckets; dividend/interest income is added post-offset at full value.
3717
+ */
3718
+ type TaxableIncome = {
3719
+ shortTermGains: number;
3720
+ /** Positive magnitude of short-term capital losses. */
3721
+ shortTermLosses: number;
3722
+ longTermGains: number;
3723
+ /** Positive magnitude of long-term capital losses. */
3724
+ longTermLosses: number;
3725
+ qualifiedDividends: number;
3726
+ ordinaryDividends: number;
3727
+ interestIncome: number;
3728
+ };
3729
+ /**
3730
+ * Partitions `events` into short-term and long-term capital-gain buckets.
3731
+ *
3732
+ * Events with `incomeKind !== 'capital-gain'` (dividends, interest) are
3733
+ * excluded entirely — they are not subject to the capital-gain offset logic.
3734
+ *
3735
+ * @param events - Flat array of {@link RealizedEvent}s for a single year or
3736
+ * for the full history (caller selects the relevant slice).
3737
+ * @returns `{ short, long }` — two arrays of capital-gain events by term.
3738
+ */
3739
+ declare function bucketByTerm(events: readonly RealizedEvent[]): {
3740
+ short: RealizedEvent[];
3741
+ long: RealizedEvent[];
3742
+ };
3743
+ /**
3744
+ * Nets gains and losses within a single bucket (all-short or all-long).
3745
+ *
3746
+ * Events with `gain >= 0` contribute to `gains`; events with `gain < 0`
3747
+ * contribute to `losses` as a **positive magnitude**.
3748
+ *
3749
+ * @param events - Capital-gain events for one term bucket.
3750
+ * @returns `{ gains, losses, net }` where `net = gains - losses`.
3751
+ */
3752
+ declare function netWithinBucket(events: readonly RealizedEvent[]): {
3753
+ gains: number;
3754
+ losses: number;
3755
+ net: number;
3756
+ };
3757
+ /**
3758
+ * Applies IRS capital-gain cross-offset rules between short-term and long-term nets.
3759
+ *
3760
+ * Rules (in order of precedence):
3761
+ * 1. **Both non-negative**: no offset; return each net unchanged.
3762
+ * 2. **Both negative**: combine losses; apply up to `ORDINARY_OFFSET_CAP` ($3,000)
3763
+ * against ordinary income; remainder becomes `carryForward`.
3764
+ * 3. **Opposite signs, combined ≥ 0**: the loss bucket fully absorbs into the gain
3765
+ * bucket; the residual stays in the gain bucket's term (`taxableShort` if
3766
+ * `netShort > 0`, else `taxableLong`). No ordinary offset or carry-forward.
3767
+ * 4. **Opposite signs, combined < 0**: net loss after cross-offset;
3768
+ * up to `ORDINARY_OFFSET_CAP` deducted against ordinary income; remainder
3769
+ * becomes `carryForward`.
3770
+ *
3771
+ * **Important:** `ordinaryOffset` and `carryForward` apply only to capital
3772
+ * losses. Dividend and interest income is never offset by capital losses.
3773
+ *
3774
+ * @param netShort - Net short-term capital gain (negative = loss).
3775
+ * @param netLong - Net long-term capital gain (negative = loss).
3776
+ * @returns `{ taxableShort, taxableLong, ordinaryOffset, carryForward }`.
3777
+ */
3778
+ declare function crossOffset(netShort: number, netLong: number): {
3779
+ taxableShort: number;
3780
+ taxableLong: number;
3781
+ ordinaryOffset: number;
3782
+ carryForward: number;
3783
+ };
3784
+ /**
3785
+ * Aggregates {@link RealizedEvent}s by UTC calendar year into a
3786
+ * {@link TaxableIncome} map.
3787
+ *
3788
+ * Keyed by `closeDate.getUTCFullYear()`. Each event is routed:
3789
+ * - `capital-gain`: bucketed into `shortTermGains`/`shortTermLosses` (ST) or
3790
+ * `longTermGains`/`longTermLosses` (LT) by `termType` and sign of `gain`.
3791
+ * Losses are stored as **positive magnitudes**.
3792
+ * - `qualified-dividend`: adds `proceeds` to `qualifiedDividends`.
3793
+ * - `ordinary-dividend`: adds `proceeds` to `ordinaryDividends`.
3794
+ * - `interest`: adds `proceeds` to `interestIncome`.
3795
+ *
3796
+ * Year boundaries use **UTC**. Backtests are unambiguous (daily bars are
3797
+ * UTC-midnight-anchored). Live-mode callers should normalize `closeDate` to the
3798
+ * tax jurisdiction's local time before passing events here if they need
3799
+ * calendar-year precision around year-end — e.g. a US fill on Dec 31 evening
3800
+ * Eastern has a UTC `closeDate` of Jan 1, which belongs to the *prior* US tax
3801
+ * year but would otherwise be counted in the next year.
3802
+ *
3803
+ * @param events - Full sequence of realized events (multiple years OK).
3804
+ * @returns `Map<year, TaxableIncome>` with one entry per UTC calendar year.
3805
+ */
3806
+ declare function aggregateByYear(events: readonly RealizedEvent[]): Map<number, TaxableIncome>;
3807
+ /**
3808
+ * Computes the tax bill for a single year's {@link TaxableIncome}.
3809
+ *
3810
+ * Steps:
3811
+ * 1. Net each capital-gain bucket: `netShort = shortTermGains - shortTermLosses`,
3812
+ * `netLong = longTermGains - longTermLosses`.
3813
+ * 2. Apply {@link crossOffset} to get `taxableShort`, `taxableLong`,
3814
+ * `ordinaryOffset`, and `carryForward`.
3815
+ * 3. `ordinaryPortion = (taxableShort + ordinaryDividends + interestIncome) * shortTerm`.
3816
+ * 4. `ltPortion = (taxableLong + qualifiedDividends) * longTerm`.
3817
+ * 5. `total = ordinaryPortion + ltPortion`.
3818
+ *
3819
+ * **Critical invariant:** capital losses do **not** offset `qualifiedDividends`.
3820
+ * Qualified dividends are added to the LT pool *after* cross-offset, at their
3821
+ * full value, so a LT capital loss that wipes out `taxableLong` to zero still
3822
+ * leaves the qualified dividend income fully taxable at the LT rate.
3823
+ *
3824
+ * `carryForward` is surfaced as a return field for the caller to track across
3825
+ * years; this function does **not** consume carry-forward from prior years
3826
+ * (cross-year carry is V3 work).
3827
+ *
3828
+ * Note: the `ordinaryOffset` produced by {@link crossOffset} (the up-to-$3,000
3829
+ * §1211(b) capital-loss deduction against ordinary income) is intentionally not
3830
+ * surfaced here. This function models only the tax on capital income; that
3831
+ * deduction applies against wage/business income outside this module's scope.
3832
+ *
3833
+ * @param income - Year-level taxable income, as produced by {@link aggregateByYear}.
3834
+ * @param rates - `{ shortTerm, longTerm }` tax rates as decimals (e.g. `0.37`).
3835
+ * @returns `{ total, breakdown: { ordinaryPortion, ltPortion, carryForward } }`.
3836
+ */
3837
+ declare function computeTaxBill(income: TaxableIncome, rates: TaxRates): {
3838
+ total: number;
3839
+ breakdown: {
3840
+ ordinaryPortion: number;
3841
+ ltPortion: number;
3842
+ carryForward: number;
3843
+ };
3844
+ };
3845
+
3846
+ type index_LotSlice = LotSlice;
3847
+ declare const index_ORDINARY_OFFSET_CAP: typeof ORDINARY_OFFSET_CAP;
3848
+ type index_RealizeResult = RealizeResult;
3849
+ type index_TaxRates = TaxRates;
3850
+ type index_TaxableIncome = TaxableIncome;
3851
+ declare const index_aggregateByYear: typeof aggregateByYear;
3852
+ declare const index_bucketByTerm: typeof bucketByTerm;
3853
+ declare const index_computeTaxBill: typeof computeTaxBill;
3854
+ declare const index_crossOffset: typeof crossOffset;
3855
+ declare const index_holdingPeriodDays: typeof holdingPeriodDays;
3856
+ declare const index_isLongTerm: typeof isLongTerm;
3857
+ declare const index_netWithinBucket: typeof netWithinBucket;
3858
+ declare const index_realize: typeof realize;
3859
+ declare const index_selectFIFO: typeof selectFIFO;
3860
+ declare const index_selectHIFO: typeof selectHIFO;
3861
+ declare const index_selectLIFO: typeof selectLIFO;
3862
+ declare const index_selectMinTax: typeof selectMinTax;
3532
3863
  declare namespace index {
3533
- export { type index_BarField as BarField, type index_ComputeFn as ComputeFn, type index_FeatureKind as FeatureKind, index_FeatureRuntime as FeatureRuntime, type index_FeatureRuntimeOptions as FeatureRuntimeOptions, type index_FeatureSpec as FeatureSpec, type index_ReturnMode as ReturnMode, index_barsToSeries as barsToSeries, index_collectBars as collectBars, index_defineFeature as defineFeature, index_drawdown as drawdown, index_ema as ema, index_getFeatureCompute as getFeatureCompute, index_paramsHash as paramsHash, index_returnSeries as returnSeries, index_rsi as rsi, index_seriesAt as seriesAt, index_sma as sma, index_volatility as volatility };
3864
+ export { type index_LotSlice as LotSlice, index_ORDINARY_OFFSET_CAP as ORDINARY_OFFSET_CAP, type index_RealizeResult as RealizeResult, type index_TaxRates as TaxRates, type index_TaxableIncome as TaxableIncome, index_aggregateByYear as aggregateByYear, index_bucketByTerm as bucketByTerm, index_computeTaxBill as computeTaxBill, index_crossOffset as crossOffset, index_holdingPeriodDays as holdingPeriodDays, index_isLongTerm as isLongTerm, index_netWithinBucket as netWithinBucket, index_realize as realize, index_selectFIFO as selectFIFO, index_selectHIFO as selectHIFO, index_selectLIFO as selectLIFO, index_selectMinTax as selectMinTax };
3534
3865
  }
3535
3866
 
3536
3867
  /**
@@ -3551,6 +3882,14 @@ declare namespace index {
3551
3882
  *
3552
3883
  * The returned `portfolio.t` is updated to the maximum fill timestamp.
3553
3884
  *
3885
+ * In parallel with `positions`/`cash`, a long-side tax ledger is maintained:
3886
+ * buys (`open` long, `rebalance` delta>0) append a {@link Lot}; sells
3887
+ * (`close` of a long, `rebalance` delta<0) consume lots — by `fill.lotId` if
3888
+ * present, else FIFO oldest-first — pro-rating basis and appending one
3889
+ * {@link RealizedEvent} per consumed slice. Short positions and `adjust`
3890
+ * orders do not participate. The `positions`/`cash` outputs are unaffected by
3891
+ * this ledger.
3892
+ *
3554
3893
  * @param portfolio - The current portfolio state before this batch.
3555
3894
  * @param fills - Execution confirmations returned by {@link Executor.submit}.
3556
3895
  * Each fill's `orderRef` MUST match an `id` in `orders`.
@@ -3588,6 +3927,11 @@ declare function applyFills(portfolio: Portfolio, fills: ReadonlyArray<Fill>, or
3588
3927
  * - `cash` is left unchanged (no price is available at projection time).
3589
3928
  * - Newly opened positions have `basis: 0` and `entry.price: 0` as
3590
3929
  * provisional values. A price-aware projection is planned for a later phase.
3930
+ * - The long-side tax ledger (`lots` / `realized`) is **not** advanced — it is
3931
+ * carried through unchanged, so it will be stale relative to the projected
3932
+ * `positions`. Use {@link applyFills} to settle the ledger after confirmed
3933
+ * execution; do not read `lots` from an `applyOrders` result expecting it to
3934
+ * reflect the projected positions.
3591
3935
  *
3592
3936
  * Use {@link applyFills} (not this function) to settle the portfolio after
3593
3937
  * confirmed execution.
@@ -3617,4 +3961,20 @@ declare function applyFills(portfolio: Portfolio, fills: ReadonlyArray<Fill>, or
3617
3961
  */
3618
3962
  declare function applyOrders(portfolio: Portfolio, orders: ReadonlyArray<Order>): Portfolio;
3619
3963
 
3620
- export { type AdhocTimeOverrides, type AdjustOrder, type AllocateNode, type Asset, type AssetId, type AssetRef, BacktestExecutor, type BacktestExecutorOptions, type BacktestResult, type BacktestSnapshot, type Bar, type BarField, type Calendar, type CloseOrder, type Comparison, type ComparisonOp, type ComputeFn, Crypto24x7Calendar, type DataEvent, type DataFeed, type DateRange, type EquityAsset, type EventKind, ExchangeCalendar, type ExchangeName, type Executor, type FeatureCache, type FeatureKey, type FeatureKind, type FeatureRef, FeatureRuntime, type FeatureRuntimeOptions, type FeatureScope, type FeatureSpec, type Features, type Fill, type Frequency, type FromSpecOptions, type Fundamentals, type HolidayRule, type IfNode, LSEExchangeCalendar, type LiveEvent, type MacroAsset, MemoryFeatureCache, NYSEExchangeCalendar, type NextOpenFn, type OpenOrder, type Order, type PollingSchedule, type PollingStreamOptions, type Portfolio, type Position, type PositionId, type PriceMap, type Quote, type QuoteFeed, type RebalanceConfig, type RebalanceFrequency, type RebalanceOrder, type ReturnMode, RoutingDataFeed, RoutingDataFeedError, type RoutingDataFeedRouteFn, type RoutingDataFeedRouteMap, RoutingQuoteFeed, RoutingQuoteFeedError, type RoutingQuoteFeedRouteFn, type RoutingQuoteFeedRouteMap, RoutingStreamingDataFeed, RoutingStreamingDataFeedError, type RoutingStreamingDataFeedRouteFn, type RoutingStreamingDataFeedRouteMap, type RuleNode, type RuleTreeState, type RunBacktestOptions, type RunLiveOptions, type Series, type Session, type SpecialClose, type SpecialOpen, type Strategy, type StreamingBar, type StreamingDataFeed, type SyntheticAsset, type TacticalFeatureKind, type TacticalFeatureSpec, type TacticalFeatures, type TacticalSpec, type TargetWeights, type TimeOfDay, type Tolerance, type WithStreamingSyntheticsOptions, applyFills, applyOrders, barsToSeries, collectBars, defineFeature, drawdown, ema, evaluateFeatureSpecs, evaluateRuleTree, index as features, fromSpec, getCalendar, getFeatureCompute, isRebalanceDay, paramsHash, periodKey, pollingStreamFromHistorical, reconcile, returnSeries, rsi, runBacktest, runLive, seriesAt, sma, index$1 as tactical, volatility, withStreamingSynthetics, withSynthetics };
3964
+ /**
3965
+ * Aggregates a portfolio's tax {@link Lot}s into a per-asset {@link Position}
3966
+ * view (`side: 'long'`). The lot-level analogue of `portfolio.positions`,
3967
+ * offered for consumers that want a single position per asset derived from the
3968
+ * cost-basis ledger. `reconcile` continues to read `portfolio.positions`.
3969
+ *
3970
+ * The returned ids are synthetic view keys (`lot_view_<assetId>`) — they are
3971
+ * NOT stable `PositionId`s and must not be passed to `CloseOrder.positionId`
3972
+ * or compared against `portfolio.positions[*].id`.
3973
+ *
3974
+ * @param portfolio - Source portfolio; reads `portfolio.lots` (treated as `[]` when absent).
3975
+ * @returns One {@link Position} per distinct asset id, summing quantity and basis,
3976
+ * with `entry` taken from the earliest lot. Empty when there are no lots.
3977
+ */
3978
+ declare function positionsByAsset(portfolio: Portfolio): Position[];
3979
+
3980
+ export { type AdhocTimeOverrides, type AdjustOrder, type AllocateNode, type Asset, type AssetId, type AssetRef, BacktestExecutor, type BacktestExecutorOptions, type BacktestResult, type BacktestSnapshot, type Bar, type BarField, type Calendar, type CloseOrder, type Comparison, type ComparisonOp, type ComputeFn, Crypto24x7Calendar, type DataEvent, type DataFeed, type DateRange, type EquityAsset, type EventKind, ExchangeCalendar, type ExchangeName, type Executor, type FeatureCache, type FeatureKey, type FeatureKind, type FeatureRef, FeatureRuntime, type FeatureRuntimeOptions, type FeatureScope, type FeatureSpec, type Features, type Fill, type Frequency, type FromSpecOptions, type Fundamentals, type HolidayRule, type IfNode, type IncomeKind, LSEExchangeCalendar, type LiveEvent, type Lot, type LotSlice, type MacroAsset, MemoryFeatureCache, NYSEExchangeCalendar, type NextOpenFn, ORDINARY_OFFSET_CAP, type OpenOrder, type Order, type PollingSchedule, type PollingStreamOptions, type Portfolio, type Position, type PositionId, type PriceMap, type Quote, type QuoteFeed, type RealizeResult, type RealizedEvent, type RebalanceConfig, type RebalanceFrequency, type RebalanceOrder, type ReturnMode, RoutingDataFeed, RoutingDataFeedError, type RoutingDataFeedRouteFn, type RoutingDataFeedRouteMap, RoutingQuoteFeed, RoutingQuoteFeedError, type RoutingQuoteFeedRouteFn, type RoutingQuoteFeedRouteMap, RoutingStreamingDataFeed, RoutingStreamingDataFeedError, type RoutingStreamingDataFeedRouteFn, type RoutingStreamingDataFeedRouteMap, type RuleNode, type RuleTreeState, type RunBacktestOptions, type RunLiveOptions, type Series, type Session, type SpecialClose, type SpecialOpen, type Strategy, type StreamingBar, type StreamingDataFeed, type SyntheticAsset, type TacticalFeatureKind, type TacticalFeatureSpec, type TacticalFeatures, type TacticalSpec, type TargetWeights, type TaxRates, type TaxableIncome, type TimeOfDay, type Tolerance, type WithStreamingSyntheticsOptions, aggregateByYear, applyFills, applyOrders, barsToSeries, bucketByTerm, collectBars, computeTaxBill, crossOffset, defineFeature, drawdown, ema, evaluateFeatureSpecs, evaluateRuleTree, index$1 as features, fromSpec, getCalendar, getFeatureCompute, holdingPeriodDays, isLongTerm, isRebalanceDay, netWithinBucket, paramsHash, periodKey, pollingStreamFromHistorical, positionsByAsset, realize, reconcile, returnSeries, rsi, runBacktest, runLive, selectFIFO, selectHIFO, selectLIFO, selectMinTax, seriesAt, sma, index$2 as tactical, index as tax, volatility, withStreamingSynthetics, withSynthetics };