@livefolio/sdk 0.5.0-rc.4 → 0.5.0-rc.5

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
@@ -1651,6 +1651,26 @@ type CashEvent = {
1651
1651
  */
1652
1652
  reason?: 'deposit' | 'withdrawal' | 'interest' | 'dividend';
1653
1653
  };
1654
+ /** How dividends are handled during a backtest. `reinvest: true` enables DRIP (dividend reinvestment). */
1655
+ type DividendsConfig = {
1656
+ reinvest: boolean;
1657
+ };
1658
+ /**
1659
+ * How idle cash earns interest during a backtest.
1660
+ * - `none` — cash earns nothing (default).
1661
+ * - `flat` — a constant annual percentage yield; daily rate is `apy / 365`.
1662
+ * - `tbill` — track a macro yield series (default `DGS3MO`) minus `spread`, as `(yield/100 - spread) / 365`.
1663
+ */
1664
+ type CashYieldConfig = {
1665
+ kind: 'none';
1666
+ } | {
1667
+ kind: 'flat';
1668
+ apy: number;
1669
+ } | {
1670
+ kind: 'tbill';
1671
+ spread: number;
1672
+ assetId?: AssetId;
1673
+ };
1654
1674
  /**
1655
1675
  * All inputs required to run a historical backtest.
1656
1676
  *
@@ -1715,6 +1735,32 @@ type RunBacktestOptions<F extends Features = Features, S = unknown> = {
1715
1735
  * fund withdrawals is deferred to a later release, so this behavior may change.
1716
1736
  */
1717
1737
  cashEvents?: ReadonlyArray<CashEvent>;
1738
+ /**
1739
+ * Optional dividend handling. When `dataFeed.dividends` exists, the universe's
1740
+ * day-0 dividends are pre-fetched and dividends are applied to held lots on the
1741
+ * first session on/after their `exDate`: cash mode credits cash, DRIP mode
1742
+ * (`reinvest: true`) reinvests into a new lot at the unadjusted pay-date close.
1743
+ *
1744
+ * @remarks Default = no dividends applied unless `dataFeed.dividends` exists;
1745
+ * `reinvest` defaults to `false` (cash mode).
1746
+ * @remarks Static-universe assumption: dividends are queried once for the day-0
1747
+ * universe (`strategy.universe(sessions[0], initialPortfolio)`). Assets that
1748
+ * enter the universe on later sessions — e.g. a dynamic-universe strategy that
1749
+ * opens a new position mid-run — will NOT have their dividends applied. This is
1750
+ * a non-issue for `fromSpec` strategies, whose universe is statically declared.
1751
+ */
1752
+ dividends?: DividendsConfig;
1753
+ /**
1754
+ * Optional idle-cash interest accrual. Each session — after dividends, before
1755
+ * the strategy runs — interest is accrued on positive cash at a daily rate
1756
+ * resolved from this config (`flat` → `apy/365`; `tbill` → macro yield series
1757
+ * `(yield/100 - spread)/365`, clamped ≥ 0). The interest is credited to cash,
1758
+ * recorded as an `interest` `RealizedEvent` on the synthetic cash asset, and
1759
+ * reported via `BacktestSnapshot.interestIncome`.
1760
+ *
1761
+ * @remarks Default = `{ kind: 'none' }` (no interest → today's behavior).
1762
+ */
1763
+ cashYield?: CashYieldConfig;
1718
1764
  };
1719
1765
  /**
1720
1766
  * A point-in-time snapshot of the simulation at the end of a single trading session.
@@ -1735,6 +1781,13 @@ type BacktestSnapshot = {
1735
1781
  * Net cash delta applied this session via `cashEvents`. Omitted when zero.
1736
1782
  */
1737
1783
  cashFlow?: number;
1784
+ /** Dividend income recognized this session, split by qualified status. Omitted when zero. */
1785
+ dividendIncome?: {
1786
+ qualified: number;
1787
+ ordinary: number;
1788
+ };
1789
+ /** Interest income accrued on cash this session. Omitted when zero. */
1790
+ interestIncome?: number;
1738
1791
  };
1739
1792
  /**
1740
1793
  * The return value of `runBacktest`, containing the full simulation history
@@ -3994,25 +4047,166 @@ declare function computeTaxBill(income: TaxableIncome, rates: TaxRates): {
3994
4047
  };
3995
4048
  };
3996
4049
 
4050
+ /**
4051
+ * Options for the 60-of-121-day qualified-dividend holding test.
4052
+ *
4053
+ * Both fields default to values for common stock dividends: the investor must
4054
+ * have held the lot throughout the 60-day window ending at the ex-dividend date, within
4055
+ * the 121-day window that begins 60 days before the ex-date.
4056
+ */
4057
+ type QualificationOpts = {
4058
+ /** Minimum days the lot must be held within the window. Defaults to `60`. */
4059
+ holdingDaysRequired?: number;
4060
+ /** Width of the holding window in calendar days. Defaults to `121`. */
4061
+ windowDays?: number;
4062
+ };
4063
+ /**
4064
+ * Determines whether a single {@link Lot} satisfies the IRS 60-of-121-day
4065
+ * qualified-dividend holding requirement as of a given `exDate`.
4066
+ *
4067
+ * The 121-day window is centred on `exDate` with `half = floor(121 / 2) = 60`,
4068
+ * so it spans the 60 days before `exDate`, the ex-date itself, and the 60 days
4069
+ * after. Only days the lot was held **on or before** `exDate` count — `heldTo`
4070
+ * is capped at `exDate` so future hold time never qualifies a dividend. This
4071
+ * keeps the test lookahead-free and conservative: it never over-grants
4072
+ * qualified status. Because `heldTo` is capped at `exDate`, the most a lot can
4073
+ * accumulate is the 60-day run-up to the ex-date, so the default threshold of
4074
+ * `60` models "held throughout the 60-day window ending at ex-date"
4075
+ * (`60 >= 60`). Returns `true` when the days held within the window are
4076
+ * `>= holdingDaysRequired`.
4077
+ *
4078
+ * @param lot - The tax lot to test.
4079
+ * @param exDate - The dividend ex-dividend date.
4080
+ * @param opts - Optional overrides for `holdingDaysRequired` (default `60` —
4081
+ * "held throughout the 60-day window ending at ex-date") and `windowDays`
4082
+ * (default `121`).
4083
+ * @returns `true` when the lot satisfies the holding-period test.
4084
+ */
4085
+ declare function isQualifiedForLot(lot: Lot, exDate: Date, opts?: QualificationOpts): boolean;
4086
+ /**
4087
+ * The result of {@link distributeDividend}: per-lot cash amounts with
4088
+ * qualified/ordinary classification, plus rolled-up totals.
4089
+ */
4090
+ type DividendDistribution = {
4091
+ /** Rolled-up qualified and ordinary dividend totals across all participating lots. */
4092
+ totals: {
4093
+ qualified: number;
4094
+ ordinary: number;
4095
+ };
4096
+ /** One entry per participating lot: the cash received and whether it is qualified. */
4097
+ perLot: {
4098
+ lotId: string;
4099
+ cash: number;
4100
+ qualified: boolean;
4101
+ }[];
4102
+ };
4103
+ /**
4104
+ * Distributes a {@link DividendEvent} across a set of lots held at the
4105
+ * ex-dividend date, classifying each lot's income as qualified or ordinary.
4106
+ *
4107
+ * Only lots that pass all three eligibility tests participate:
4108
+ * - `quantity > 0`
4109
+ * - `openDate <= exDate` (lot was open before or on the ex-date)
4110
+ * - `asset.id === event.asset.id` (same asset)
4111
+ *
4112
+ * For `incomeKind: 'ordinary'` or `'interest'` events every participating lot
4113
+ * is classified as ordinary regardless of how long it was held. For
4114
+ * `'qualified-eligible'` events each lot is tested individually via
4115
+ * {@link isQualifiedForLot} using the default 60-of-121-day rule.
4116
+ *
4117
+ * @param event - The dividend event to distribute.
4118
+ * @param lotsHeldAtExDate - All lots in the portfolio at the ex-dividend date
4119
+ * (the function filters to eligible ones internally).
4120
+ * @returns A {@link DividendDistribution} with per-lot breakdown and rolled-up totals.
4121
+ */
4122
+ declare function distributeDividend(event: DividendEvent, lotsHeldAtExDate: readonly Lot[]): DividendDistribution;
4123
+ /**
4124
+ * Converts dividend cash into a new whole-share DRIP tax lot at the pay-date price.
4125
+ *
4126
+ * Computes `shares = floor(cashAvailable / pricePayDate)` and opens a fresh
4127
+ * {@link Lot} with `openDate = payDate`, `openPrice = pricePayDate`,
4128
+ * `basis = shares * pricePayDate`, and `dripParent` set to the originating lot
4129
+ * id. Any fractional-share remainder is returned as `residual`.
4130
+ *
4131
+ * When `cashAvailable` is less than one share (`shares === 0`), `newLot.quantity`
4132
+ * will be `0` and `residual` will equal `cashAvailable`. The caller is
4133
+ * responsible for skipping zero-quantity lots.
4134
+ *
4135
+ * @param cashAvailable - Total dividend cash to reinvest.
4136
+ * @param asset - The asset being purchased.
4137
+ * @param pricePayDate - Share price on the dividend pay date.
4138
+ * @param payDate - The dividend pay date; becomes `openDate` on the new lot.
4139
+ * @param dripParent - Id of the originating lot that generated the dividend cash.
4140
+ * @returns An object with `newLot` (the freshly created {@link Lot}) and
4141
+ * `residual` (uninvested cash remainder).
4142
+ */
4143
+ declare function reinvestDividend(cashAvailable: number, asset: Asset, pricePayDate: number, payDate: Date, dripParent: string): {
4144
+ newLot: Lot;
4145
+ residual: number;
4146
+ };
4147
+
4148
+ /**
4149
+ * Result of a single {@link accrueCashInterest} call: the updated cash balance
4150
+ * and the interest earned in that session.
4151
+ */
4152
+ type CashInterestResult = {
4153
+ /** Cash balance after interest has been credited: `cash + interest`. */
4154
+ newCash: number;
4155
+ /** Interest earned in this session: `cash * dailyRate`. */
4156
+ interest: number;
4157
+ };
4158
+ /**
4159
+ * Pure per-session interest accrual using the actual/365 day-count convention.
4160
+ *
4161
+ * Computes simple (non-compounding within a single call) interest for one
4162
+ * trading session. To model compounding over multiple sessions, call this
4163
+ * function repeatedly, passing the returned `newCash` as `cash` in each
4164
+ * subsequent call.
4165
+ *
4166
+ * `dailyRate` should be `APY / 365` for an actual/365 day-count (e.g.
4167
+ * `0.05 / 365` for a 5% APY). A daily rate of `0` returns the original cash
4168
+ * balance with zero interest.
4169
+ *
4170
+ * @param cash - The current cash balance before interest is applied.
4171
+ * @param dailyRate - The per-session interest rate expressed as a decimal
4172
+ * fraction (e.g. `0.05 / 365` for 5% APY on an actual/365 basis).
4173
+ * @returns A {@link CashInterestResult} with `newCash` (updated balance) and
4174
+ * `interest` (amount earned this session).
4175
+ *
4176
+ * @example
4177
+ * ```ts
4178
+ * // Single session at 5% APY
4179
+ * const { newCash, interest } = accrueCashInterest(10_000, 0.05 / 365);
4180
+ * ```
4181
+ */
4182
+ declare function accrueCashInterest(cash: number, dailyRate: number): CashInterestResult;
4183
+
4184
+ type index_CashInterestResult = CashInterestResult;
4185
+ type index_DividendDistribution = DividendDistribution;
3997
4186
  type index_LotSlice = LotSlice;
3998
4187
  declare const index_ORDINARY_OFFSET_CAP: typeof ORDINARY_OFFSET_CAP;
4188
+ type index_QualificationOpts = QualificationOpts;
3999
4189
  type index_RealizeResult = RealizeResult;
4000
4190
  type index_TaxRates = TaxRates;
4001
4191
  type index_TaxableIncome = TaxableIncome;
4192
+ declare const index_accrueCashInterest: typeof accrueCashInterest;
4002
4193
  declare const index_aggregateByYear: typeof aggregateByYear;
4003
4194
  declare const index_bucketByTerm: typeof bucketByTerm;
4004
4195
  declare const index_computeTaxBill: typeof computeTaxBill;
4005
4196
  declare const index_crossOffset: typeof crossOffset;
4197
+ declare const index_distributeDividend: typeof distributeDividend;
4006
4198
  declare const index_holdingPeriodDays: typeof holdingPeriodDays;
4007
4199
  declare const index_isLongTerm: typeof isLongTerm;
4200
+ declare const index_isQualifiedForLot: typeof isQualifiedForLot;
4008
4201
  declare const index_netWithinBucket: typeof netWithinBucket;
4009
4202
  declare const index_realize: typeof realize;
4203
+ declare const index_reinvestDividend: typeof reinvestDividend;
4010
4204
  declare const index_selectFIFO: typeof selectFIFO;
4011
4205
  declare const index_selectHIFO: typeof selectHIFO;
4012
4206
  declare const index_selectLIFO: typeof selectLIFO;
4013
4207
  declare const index_selectMinTax: typeof selectMinTax;
4014
4208
  declare namespace index {
4015
- 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 };
4209
+ export { type index_CashInterestResult as CashInterestResult, type index_DividendDistribution as DividendDistribution, type index_LotSlice as LotSlice, index_ORDINARY_OFFSET_CAP as ORDINARY_OFFSET_CAP, type index_QualificationOpts as QualificationOpts, type index_RealizeResult as RealizeResult, type index_TaxRates as TaxRates, type index_TaxableIncome as TaxableIncome, index_accrueCashInterest as accrueCashInterest, index_aggregateByYear as aggregateByYear, index_bucketByTerm as bucketByTerm, index_computeTaxBill as computeTaxBill, index_crossOffset as crossOffset, index_distributeDividend as distributeDividend, index_holdingPeriodDays as holdingPeriodDays, index_isLongTerm as isLongTerm, index_isQualifiedForLot as isQualifiedForLot, index_netWithinBucket as netWithinBucket, index_realize as realize, index_reinvestDividend as reinvestDividend, index_selectFIFO as selectFIFO, index_selectHIFO as selectHIFO, index_selectLIFO as selectLIFO, index_selectMinTax as selectMinTax };
4016
4210
  }
4017
4211
 
4018
4212
  /**
@@ -4128,4 +4322,4 @@ declare function applyOrders(portfolio: Portfolio, orders: ReadonlyArray<Order>)
4128
4322
  */
4129
4323
  declare function positionsByAsset(portfolio: Portfolio): Position[];
4130
4324
 
4131
- 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 CashEvent, CashEventQueue, type CloseOrder, type Comparison, type ComparisonOp, type ComputeFn, Crypto24x7Calendar, type DataEvent, type DataFeed, type DateRange, type DividendEvent, 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 };
4325
+ 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 CashEvent, CashEventQueue, type CashInterestResult, type CashYieldConfig, type CloseOrder, type Comparison, type ComparisonOp, type ComputeFn, Crypto24x7Calendar, type DataEvent, type DataFeed, type DateRange, type DividendDistribution, type DividendEvent, type DividendsConfig, 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 QualificationOpts, 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, accrueCashInterest, aggregateByYear, applyFills, applyOrders, barsToSeries, bucketByTerm, collectBars, computeTaxBill, crossOffset, defineFeature, distributeDividend, drawdown, ema, evaluateFeatureSpecs, evaluateRuleTree, index$1 as features, fromSpec, getCalendar, getFeatureCompute, holdingPeriodDays, isLongTerm, isQualifiedForLot, isRebalanceDay, netWithinBucket, paramsHash, periodKey, pollingStreamFromHistorical, positionsByAsset, realize, reconcile, reinvestDividend, returnSeries, rsi, runBacktest, runLive, selectFIFO, selectHIFO, selectLIFO, selectMinTax, seriesAt, sma, index$2 as tactical, index as tax, volatility, withStreamingSynthetics, withSynthetics };