@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.js CHANGED
@@ -313,10 +313,80 @@ function applyOrders(portfolio, orders) {
313
313
  return { ...portfolio, positions };
314
314
  }
315
315
 
316
+ // src/tax/dividends.ts
317
+ var MS_PER_DAY2 = 864e5;
318
+ function isQualifiedForLot(lot, exDate, opts = {}) {
319
+ const required = opts.holdingDaysRequired ?? 60;
320
+ const window = opts.windowDays ?? 121;
321
+ const half = Math.floor(window / 2);
322
+ const windowStart = new Date(exDate.getTime() - half * MS_PER_DAY2);
323
+ const windowEnd = new Date(exDate.getTime() + half * MS_PER_DAY2);
324
+ const heldFrom = lot.openDate > windowStart ? lot.openDate : windowStart;
325
+ const heldTo = exDate < windowEnd ? exDate : windowEnd;
326
+ const days = Math.max(0, (heldTo.getTime() - heldFrom.getTime()) / MS_PER_DAY2);
327
+ return days >= required;
328
+ }
329
+ function distributeDividend(event, lotsHeldAtExDate) {
330
+ const eligible = event.incomeKind === "qualified-eligible";
331
+ const perLot = [];
332
+ let qualified = 0, ordinary = 0;
333
+ for (const lot of lotsHeldAtExDate) {
334
+ if (lot.quantity <= 0 || lot.openDate > event.exDate || lot.asset.id !== event.asset.id) continue;
335
+ const cash = lot.quantity * event.amountPerShare;
336
+ const isQ = eligible && isQualifiedForLot(lot, event.exDate);
337
+ perLot.push({ lotId: lot.id, cash, qualified: isQ });
338
+ if (isQ) qualified += cash;
339
+ else ordinary += cash;
340
+ }
341
+ return { totals: { qualified, ordinary }, perLot };
342
+ }
343
+ function reinvestDividend(cashAvailable, asset, pricePayDate, payDate, dripParent) {
344
+ const shares = Math.floor(cashAvailable / pricePayDate);
345
+ const cost = shares * pricePayDate;
346
+ return {
347
+ newLot: {
348
+ id: nextLotId(),
349
+ asset,
350
+ quantity: shares,
351
+ openDate: payDate,
352
+ openPrice: pricePayDate,
353
+ basis: cost,
354
+ dripParent
355
+ },
356
+ residual: cashAvailable - cost
357
+ };
358
+ }
359
+
360
+ // src/tax/cash-interest.ts
361
+ function accrueCashInterest(cash, dailyRate) {
362
+ const interest = cash * dailyRate;
363
+ return { newCash: cash + interest, interest };
364
+ }
365
+
316
366
  // src/strategy/run-backtest.ts
367
+ var CASH_ASSET = { kind: "equity", id: "_cash", symbol: "CASH" };
317
368
  function isStateResult(r) {
318
369
  return !Array.isArray(r);
319
370
  }
371
+ var DRIP_PRICE_WINDOW_MS = 7 * 864e5;
372
+ async function firstUnadjustedClose(feed, asset, payDate, freq) {
373
+ const to = new Date(payDate.getTime() + DRIP_PRICE_WINDOW_MS);
374
+ for await (const bar of feed.bars(asset, { from: payDate, to }, freq, "unadjusted")) return bar.close;
375
+ return void 0;
376
+ }
377
+ async function resolveDailyRate(cfg, t, feed, freq) {
378
+ if (!cfg || cfg.kind === "none") return 0;
379
+ if (cfg.kind === "flat") return cfg.apy / 365;
380
+ const id = cfg.assetId ?? "DGS3MO";
381
+ const asset = { kind: "macro", id, symbol: id, source: "FRED" };
382
+ const to = new Date(t.getTime() + 864e5);
383
+ let last;
384
+ for await (const bar of feed.bars(asset, { from: new Date(t.getTime() - 7 * 864e5), to }, freq, "unadjusted")) {
385
+ last = bar.close;
386
+ }
387
+ if (last === void 0) return 0;
388
+ return Math.max(0, (last / 100 - cfg.spread) / 365);
389
+ }
320
390
  async function runBacktest(opts) {
321
391
  const initialStateValue = opts.strategy.initialState?.();
322
392
  const sessions = opts.calendar.sessions(opts.range);
@@ -334,6 +404,16 @@ async function runBacktest(opts) {
334
404
  const cashEvents = [...opts.cashEvents ?? []].sort((a, b) => a.t.getTime() - b.t.getTime());
335
405
  let eventCursor = 0;
336
406
  let warnedNegativeCash = false;
407
+ let warnedDripFallback = false;
408
+ const divByAsset = /* @__PURE__ */ new Map();
409
+ if (opts.dataFeed.dividends && sessions.length > 0) {
410
+ const u0 = opts.strategy.universe(sessions[0], opts.initialPortfolio);
411
+ for (const asset of u0) {
412
+ divByAsset.set(asset.id, await opts.dataFeed.dividends(asset, opts.range));
413
+ }
414
+ }
415
+ const allDivs = [...divByAsset.values()].flat().sort((a, b) => a.exDate.getTime() - b.exDate.getTime());
416
+ let divCursor = 0;
337
417
  for (const t of sessions) {
338
418
  let cashFlow = 0;
339
419
  while (eventCursor < cashEvents.length && cashEvents[eventCursor].t.getTime() <= t.getTime()) {
@@ -349,6 +429,72 @@ async function runBacktest(opts) {
349
429
  );
350
430
  }
351
431
  }
432
+ let qualifiedTotal = 0;
433
+ let ordinaryTotal = 0;
434
+ while (divCursor < allDivs.length && allDivs[divCursor].exDate.getTime() <= t.getTime()) {
435
+ const div = allDivs[divCursor];
436
+ divCursor++;
437
+ const dist = distributeDividend(div, portfolio.lots ?? []);
438
+ if (dist.perLot.length === 0) continue;
439
+ qualifiedTotal += dist.totals.qualified;
440
+ ordinaryTotal += dist.totals.ordinary;
441
+ const reinvest = opts.dividends?.reinvest === true;
442
+ const lots = [...portfolio.lots ?? []];
443
+ const realized = [...portfolio.realized ?? []];
444
+ let cashCredit = 0;
445
+ const reinvestPrice = reinvest ? await firstUnadjustedClose(opts.dataFeed, div.asset, div.payDate, opts.freq ?? "1d") : void 0;
446
+ for (const slice of dist.perLot) {
447
+ realized.push({
448
+ asset: div.asset,
449
+ lotId: slice.lotId,
450
+ quantity: 0,
451
+ openDate: t,
452
+ closeDate: t,
453
+ proceeds: slice.cash,
454
+ basis: 0,
455
+ termType: "long",
456
+ gain: slice.cash,
457
+ incomeKind: slice.qualified ? "qualified-dividend" : "ordinary-dividend"
458
+ });
459
+ if (reinvest && reinvestPrice && reinvestPrice > 0) {
460
+ const { newLot, residual } = reinvestDividend(slice.cash, div.asset, reinvestPrice, div.payDate, slice.lotId);
461
+ if (newLot.quantity > 0) lots.push(newLot);
462
+ cashCredit += residual;
463
+ } else {
464
+ if (reinvest && !warnedDripFallback) {
465
+ warnedDripFallback = true;
466
+ console.warn(
467
+ `[runBacktest] DRIP fell back to a cash credit for ${div.asset.id} (pay date ${div.payDate.toISOString()}): no unadjusted bar within 7 days of the pay date. Further occurrences this run are suppressed.`
468
+ );
469
+ }
470
+ cashCredit += slice.cash;
471
+ }
472
+ }
473
+ portfolio = { ...portfolio, cash: portfolio.cash + cashCredit, lots, realized };
474
+ }
475
+ const dividendIncome = qualifiedTotal + ordinaryTotal > 0 ? { qualified: qualifiedTotal, ordinary: ordinaryTotal } : void 0;
476
+ let interestThisSession = 0;
477
+ const dailyRate = await resolveDailyRate(opts.cashYield, t, opts.dataFeed, opts.freq ?? "1d");
478
+ if (dailyRate > 0 && portfolio.cash > 0) {
479
+ const { newCash, interest } = accrueCashInterest(portfolio.cash, dailyRate);
480
+ const realized = [
481
+ ...portfolio.realized ?? [],
482
+ {
483
+ asset: CASH_ASSET,
484
+ lotId: "cash",
485
+ quantity: 0,
486
+ openDate: t,
487
+ closeDate: t,
488
+ proceeds: interest,
489
+ basis: 0,
490
+ termType: "short",
491
+ gain: interest,
492
+ incomeKind: "interest"
493
+ }
494
+ ];
495
+ portfolio = { ...portfolio, cash: newCash, realized };
496
+ interestThisSession = interest;
497
+ }
352
498
  const universe = opts.strategy.universe(t, portfolio);
353
499
  const features = await opts.strategy.features(universe, portfolio, t);
354
500
  const buildResult = opts.strategy.build(features, portfolio, state, t);
@@ -361,7 +507,15 @@ async function runBacktest(opts) {
361
507
  }
362
508
  const fills = await opts.executor.submit(orders, t, portfolio);
363
509
  portfolio = applyFills(portfolio, fills, orders);
364
- snapshots.push({ t, portfolio, orders, fills, ...cashFlow !== 0 ? { cashFlow } : {} });
510
+ snapshots.push({
511
+ t,
512
+ portfolio,
513
+ orders,
514
+ fills,
515
+ ...cashFlow !== 0 ? { cashFlow } : {},
516
+ ...dividendIncome ? { dividendIncome } : {},
517
+ ...interestThisSession !== 0 ? { interestIncome: interestThisSession } : {}
518
+ });
365
519
  }
366
520
  const bars = opts.featureRuntime?.getAllBars() ?? /* @__PURE__ */ new Map();
367
521
  return { snapshots, finalPortfolio: portfolio, finalState: state, bars };
@@ -1280,7 +1434,7 @@ function pollingStreamFromHistorical(opts) {
1280
1434
  import { DateTime } from "luxon";
1281
1435
 
1282
1436
  // src/calendars/holiday-rules.ts
1283
- var MS_PER_DAY2 = 864e5;
1437
+ var MS_PER_DAY3 = 864e5;
1284
1438
  function nthWeekdayOfMonth(year, month, weekday, n) {
1285
1439
  const first = new Date(Date.UTC(year, month - 1, 1));
1286
1440
  const offset = (weekday - first.getUTCDay() + 7) % 7;
@@ -1289,7 +1443,7 @@ function nthWeekdayOfMonth(year, month, weekday, n) {
1289
1443
  function lastWeekdayOfMonth(year, month, weekday) {
1290
1444
  const last = new Date(Date.UTC(year, month, 0));
1291
1445
  const offset = (last.getUTCDay() - weekday + 7) % 7;
1292
- return new Date(last.getTime() - offset * MS_PER_DAY2);
1446
+ return new Date(last.getTime() - offset * MS_PER_DAY3);
1293
1447
  }
1294
1448
  function easter(year) {
1295
1449
  const a = year % 19;
@@ -1310,8 +1464,8 @@ function easter(year) {
1310
1464
  }
1311
1465
  function observed(d) {
1312
1466
  const dow = d.getUTCDay();
1313
- if (dow === 6) return new Date(d.getTime() - MS_PER_DAY2);
1314
- if (dow === 0) return new Date(d.getTime() + MS_PER_DAY2);
1467
+ if (dow === 6) return new Date(d.getTime() - MS_PER_DAY3);
1468
+ if (dow === 0) return new Date(d.getTime() + MS_PER_DAY3);
1315
1469
  return d;
1316
1470
  }
1317
1471
  function resolveHolidays(rules, year) {
@@ -1349,21 +1503,21 @@ function resolveSpecialOpens(rules, year) {
1349
1503
  return out;
1350
1504
  }
1351
1505
  function sundayToMonday(d) {
1352
- return d.getUTCDay() === 0 ? new Date(d.getTime() + MS_PER_DAY2) : d;
1506
+ return d.getUTCDay() === 0 ? new Date(d.getTime() + MS_PER_DAY3) : d;
1353
1507
  }
1354
1508
  function nearestWorkday(d) {
1355
1509
  const dow = d.getUTCDay();
1356
- if (dow === 6) return new Date(d.getTime() - MS_PER_DAY2);
1357
- if (dow === 0) return new Date(d.getTime() + MS_PER_DAY2);
1510
+ if (dow === 6) return new Date(d.getTime() - MS_PER_DAY3);
1511
+ if (dow === 0) return new Date(d.getTime() + MS_PER_DAY3);
1358
1512
  return d;
1359
1513
  }
1360
1514
  function firstMondayOnOrAfter(year, month, day, nth = 1) {
1361
1515
  const start = new Date(Date.UTC(year, month - 1, day));
1362
1516
  const offset = (1 - start.getUTCDay() + 7) % 7;
1363
- return new Date(start.getTime() + (offset + 7 * (nth - 1)) * MS_PER_DAY2);
1517
+ return new Date(start.getTime() + (offset + 7 * (nth - 1)) * MS_PER_DAY3);
1364
1518
  }
1365
1519
  function easterPlus(year, dayDelta) {
1366
- return new Date(easter(year).getTime() + dayDelta * MS_PER_DAY2);
1520
+ return new Date(easter(year).getTime() + dayDelta * MS_PER_DAY3);
1367
1521
  }
1368
1522
  function dropIfNotInDays(d, allowed) {
1369
1523
  if (d === null) return null;
@@ -1371,7 +1525,7 @@ function dropIfNotInDays(d, allowed) {
1371
1525
  }
1372
1526
 
1373
1527
  // src/calendars/exchange-calendar.ts
1374
- var MS_PER_DAY3 = 864e5;
1528
+ var MS_PER_DAY4 = 864e5;
1375
1529
  var DEFAULT_WEEKMASK = /* @__PURE__ */ new Set([1, 2, 3, 4, 5]);
1376
1530
  var EMPTY_ADHOC = /* @__PURE__ */ new Map();
1377
1531
  function ymdKey(d) {
@@ -1532,14 +1686,14 @@ var ExchangeCalendar = class {
1532
1686
  }
1533
1687
  /** Returns the first trading day strictly after `t`. */
1534
1688
  next(t) {
1535
- let d = new Date(this.normalize(t).getTime() + MS_PER_DAY3);
1536
- while (!this.isOpen(d)) d = new Date(d.getTime() + MS_PER_DAY3);
1689
+ let d = new Date(this.normalize(t).getTime() + MS_PER_DAY4);
1690
+ while (!this.isOpen(d)) d = new Date(d.getTime() + MS_PER_DAY4);
1537
1691
  return d;
1538
1692
  }
1539
1693
  /** Returns the first trading day strictly before `t`. */
1540
1694
  previous(t) {
1541
- let d = new Date(this.normalize(t).getTime() - MS_PER_DAY3);
1542
- while (!this.isOpen(d)) d = new Date(d.getTime() - MS_PER_DAY3);
1695
+ let d = new Date(this.normalize(t).getTime() - MS_PER_DAY4);
1696
+ while (!this.isOpen(d)) d = new Date(d.getTime() - MS_PER_DAY4);
1543
1697
  return d;
1544
1698
  }
1545
1699
  /**
@@ -1552,7 +1706,7 @@ var ExchangeCalendar = class {
1552
1706
  const end = this.normalize(range.to).getTime();
1553
1707
  while (d.getTime() < end) {
1554
1708
  if (this.isOpen(d)) out.push(d);
1555
- d = new Date(d.getTime() + MS_PER_DAY3);
1709
+ d = new Date(d.getTime() + MS_PER_DAY4);
1556
1710
  }
1557
1711
  return out;
1558
1712
  }
@@ -1603,7 +1757,7 @@ var ExchangeCalendar = class {
1603
1757
  };
1604
1758
 
1605
1759
  // src/calendars/nyse.ts
1606
- var MS_PER_DAY4 = 864e5;
1760
+ var MS_PER_DAY5 = 864e5;
1607
1761
  var SUN = 0;
1608
1762
  var MON = 1;
1609
1763
  var TUE = 2;
@@ -1767,7 +1921,7 @@ var REGULAR_HOLIDAYS = [
1767
1921
  resolve: (y) => {
1768
1922
  const start = utcDate(y, 11, 2);
1769
1923
  const offset = (TUE - start.getUTCDay() + 7) % 7;
1770
- return new Date(start.getTime() + offset * MS_PER_DAY4);
1924
+ return new Date(start.getTime() + offset * MS_PER_DAY5);
1771
1925
  }
1772
1926
  },
1773
1927
  // ── Veterans/Armistice Day (Nov 11, 1934-1953) ─────────────────────────────
@@ -1798,7 +1952,7 @@ var REGULAR_HOLIDAYS = [
1798
1952
  validUntil: 1941,
1799
1953
  resolve: (y) => {
1800
1954
  const last = lastWeekdayOfMonth(y, 11, THU);
1801
- return new Date(last.getTime() - 7 * MS_PER_DAY4);
1955
+ return new Date(last.getTime() - 7 * MS_PER_DAY5);
1802
1956
  }
1803
1957
  },
1804
1958
  // ── Christmas ──────────────────────────────────────────────────────────────
@@ -2155,7 +2309,7 @@ function* generateSummerSaturdays() {
2155
2309
  const end = /* @__PURE__ */ new Date(`${to}T00:00:00.000Z`);
2156
2310
  while (d.getTime() <= end.getTime()) {
2157
2311
  if (d.getUTCDay() === SAT) yield ymd(d);
2158
- d = new Date(d.getTime() + MS_PER_DAY4);
2312
+ d = new Date(d.getTime() + MS_PER_DAY5);
2159
2313
  }
2160
2314
  }
2161
2315
  }
@@ -2165,7 +2319,7 @@ function* generateWWIShutdown() {
2165
2319
  while (d.getTime() <= end.getTime()) {
2166
2320
  const dow = d.getUTCDay();
2167
2321
  if (dow !== SUN) yield ymd(d);
2168
- d = new Date(d.getTime() + MS_PER_DAY4);
2322
+ d = new Date(d.getTime() + MS_PER_DAY5);
2169
2323
  }
2170
2324
  }
2171
2325
  var ADHOC_HOLIDAYS = /* @__PURE__ */ new Set([
@@ -2183,7 +2337,7 @@ var SPECIAL_CLOSES = [
2183
2337
  closeAt: { h: 13, m: 0 },
2184
2338
  resolve: (y) => {
2185
2339
  const t = nthWeekdayOfMonth(y, 11, THU, 4);
2186
- return new Date(t.getTime() + MS_PER_DAY4);
2340
+ return new Date(t.getTime() + MS_PER_DAY5);
2187
2341
  }
2188
2342
  },
2189
2343
  {
@@ -2193,7 +2347,7 @@ var SPECIAL_CLOSES = [
2193
2347
  closeAt: { h: 14, m: 0 },
2194
2348
  resolve: (y) => {
2195
2349
  const t = nthWeekdayOfMonth(y, 11, THU, 4);
2196
- return new Date(t.getTime() + MS_PER_DAY4);
2350
+ return new Date(t.getTime() + MS_PER_DAY5);
2197
2351
  }
2198
2352
  },
2199
2353
  {
@@ -2354,7 +2508,7 @@ var SPECIAL_CLOSES_ADHOC = /* @__PURE__ */ new Map([
2354
2508
  if (dow >= MON && dow <= FRI) {
2355
2509
  SPECIAL_CLOSES_ADHOC.set(ymd(d), { h: 14, m: 0 });
2356
2510
  }
2357
- d = new Date(d.getTime() + MS_PER_DAY4);
2511
+ d = new Date(d.getTime() + MS_PER_DAY5);
2358
2512
  }
2359
2513
  })();
2360
2514
  (() => {
@@ -2365,7 +2519,7 @@ var SPECIAL_CLOSES_ADHOC = /* @__PURE__ */ new Map([
2365
2519
  if (dow >= MON && dow <= FRI) {
2366
2520
  SPECIAL_CLOSES_ADHOC.set(ymd(d), { h: 14, m: 0 });
2367
2521
  }
2368
- d = new Date(d.getTime() + MS_PER_DAY4);
2522
+ d = new Date(d.getTime() + MS_PER_DAY5);
2369
2523
  }
2370
2524
  })();
2371
2525
  (() => {
@@ -2376,7 +2530,7 @@ var SPECIAL_CLOSES_ADHOC = /* @__PURE__ */ new Map([
2376
2530
  if (dow >= MON && dow <= FRI) {
2377
2531
  SPECIAL_CLOSES_ADHOC.set(ymd(d), { h: 14, m: 0 });
2378
2532
  }
2379
- d = new Date(d.getTime() + MS_PER_DAY4);
2533
+ d = new Date(d.getTime() + MS_PER_DAY5);
2380
2534
  }
2381
2535
  })();
2382
2536
  (() => {
@@ -2387,7 +2541,7 @@ var SPECIAL_CLOSES_ADHOC = /* @__PURE__ */ new Map([
2387
2541
  if (dow >= MON && dow <= FRI) {
2388
2542
  SPECIAL_CLOSES_ADHOC.set(ymd(d), { h: 14, m: 0 });
2389
2543
  }
2390
- d = new Date(d.getTime() + MS_PER_DAY4);
2544
+ d = new Date(d.getTime() + MS_PER_DAY5);
2391
2545
  }
2392
2546
  })();
2393
2547
  (() => {
@@ -2398,7 +2552,7 @@ var SPECIAL_CLOSES_ADHOC = /* @__PURE__ */ new Map([
2398
2552
  if (dow >= MON && dow <= FRI) {
2399
2553
  SPECIAL_CLOSES_ADHOC.set(ymd(d), { h: 14, m: 30 });
2400
2554
  }
2401
- d = new Date(d.getTime() + MS_PER_DAY4);
2555
+ d = new Date(d.getTime() + MS_PER_DAY5);
2402
2556
  }
2403
2557
  })();
2404
2558
  (() => {
@@ -2409,7 +2563,7 @@ var SPECIAL_CLOSES_ADHOC = /* @__PURE__ */ new Map([
2409
2563
  if (dow >= MON && dow <= FRI) {
2410
2564
  SPECIAL_CLOSES_ADHOC.set(ymd(d), { h: 15, m: 0 });
2411
2565
  }
2412
- d = new Date(d.getTime() + MS_PER_DAY4);
2566
+ d = new Date(d.getTime() + MS_PER_DAY5);
2413
2567
  }
2414
2568
  })();
2415
2569
  var SPECIAL_OPENS = [];
@@ -2526,7 +2680,7 @@ var NYSEExchangeCalendar = class extends ExchangeCalendar {
2526
2680
  };
2527
2681
 
2528
2682
  // src/calendars/lse.ts
2529
- var MS_PER_DAY5 = 864e5;
2683
+ var MS_PER_DAY6 = 864e5;
2530
2684
  var SUN2 = 0;
2531
2685
  var MON2 = 1;
2532
2686
  var TUE2 = 2;
@@ -2538,14 +2692,14 @@ var WEEKDAYS_MON_FRI2 = /* @__PURE__ */ new Set([1, 2, 3, 4, 5]);
2538
2692
  var MON_TUE = /* @__PURE__ */ new Set([MON2, TUE2]);
2539
2693
  function weekendToMonday(d) {
2540
2694
  const dow = d.getUTCDay();
2541
- if (dow === SAT2) return new Date(d.getTime() + 2 * MS_PER_DAY5);
2542
- if (dow === SUN2) return new Date(d.getTime() + MS_PER_DAY5);
2695
+ if (dow === SAT2) return new Date(d.getTime() + 2 * MS_PER_DAY6);
2696
+ if (dow === SUN2) return new Date(d.getTime() + MS_PER_DAY6);
2543
2697
  return d;
2544
2698
  }
2545
2699
  function previousFriday(d) {
2546
2700
  const dow = d.getUTCDay();
2547
- if (dow === SAT2) return new Date(d.getTime() - MS_PER_DAY5);
2548
- if (dow === SUN2) return new Date(d.getTime() - 2 * MS_PER_DAY5);
2701
+ if (dow === SAT2) return new Date(d.getTime() - MS_PER_DAY6);
2702
+ if (dow === SUN2) return new Date(d.getTime() - 2 * MS_PER_DAY6);
2549
2703
  return d;
2550
2704
  }
2551
2705
  var REGULAR_HOLIDAYS2 = [
@@ -2737,7 +2891,7 @@ function getCalendar(name) {
2737
2891
  }
2738
2892
 
2739
2893
  // src/calendars/crypto-24x7.ts
2740
- var MS_PER_DAY6 = 864e5;
2894
+ var MS_PER_DAY7 = 864e5;
2741
2895
  function midnightUtc(d) {
2742
2896
  return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
2743
2897
  }
@@ -2746,10 +2900,10 @@ var Crypto24x7Calendar = class {
2746
2900
  return true;
2747
2901
  }
2748
2902
  next(t) {
2749
- return new Date(midnightUtc(t).getTime() + MS_PER_DAY6);
2903
+ return new Date(midnightUtc(t).getTime() + MS_PER_DAY7);
2750
2904
  }
2751
2905
  previous(t) {
2752
- return new Date(midnightUtc(t).getTime() - MS_PER_DAY6);
2906
+ return new Date(midnightUtc(t).getTime() - MS_PER_DAY7);
2753
2907
  }
2754
2908
  sessions(range) {
2755
2909
  const out = [];
@@ -2757,7 +2911,7 @@ var Crypto24x7Calendar = class {
2757
2911
  const end = range.to.getTime();
2758
2912
  while (cursor.getTime() < end) {
2759
2913
  out.push(cursor);
2760
- cursor = new Date(cursor.getTime() + MS_PER_DAY6);
2914
+ cursor = new Date(cursor.getTime() + MS_PER_DAY7);
2761
2915
  }
2762
2916
  return out;
2763
2917
  }
@@ -2765,7 +2919,7 @@ var Crypto24x7Calendar = class {
2765
2919
  return this.sessions(range).map((date) => ({
2766
2920
  date,
2767
2921
  open: date,
2768
- close: new Date(date.getTime() + MS_PER_DAY6)
2922
+ close: new Date(date.getTime() + MS_PER_DAY7)
2769
2923
  }));
2770
2924
  }
2771
2925
  isEarlyClose(_t) {
@@ -3219,14 +3373,18 @@ __export(features_exports, {
3219
3373
  var tax_exports = {};
3220
3374
  __export(tax_exports, {
3221
3375
  ORDINARY_OFFSET_CAP: () => ORDINARY_OFFSET_CAP,
3376
+ accrueCashInterest: () => accrueCashInterest,
3222
3377
  aggregateByYear: () => aggregateByYear,
3223
3378
  bucketByTerm: () => bucketByTerm,
3224
3379
  computeTaxBill: () => computeTaxBill,
3225
3380
  crossOffset: () => crossOffset,
3381
+ distributeDividend: () => distributeDividend,
3226
3382
  holdingPeriodDays: () => holdingPeriodDays,
3227
3383
  isLongTerm: () => isLongTerm,
3384
+ isQualifiedForLot: () => isQualifiedForLot,
3228
3385
  netWithinBucket: () => netWithinBucket,
3229
3386
  realize: () => realize,
3387
+ reinvestDividend: () => reinvestDividend,
3230
3388
  selectFIFO: () => selectFIFO,
3231
3389
  selectHIFO: () => selectHIFO,
3232
3390
  selectLIFO: () => selectLIFO,
@@ -3367,6 +3525,7 @@ export {
3367
3525
  RoutingQuoteFeedError,
3368
3526
  RoutingStreamingDataFeed,
3369
3527
  RoutingStreamingDataFeedError,
3528
+ accrueCashInterest,
3370
3529
  aggregateByYear,
3371
3530
  applyFills,
3372
3531
  applyOrders,
@@ -3376,6 +3535,7 @@ export {
3376
3535
  computeTaxBill,
3377
3536
  crossOffset,
3378
3537
  defineFeature,
3538
+ distributeDividend,
3379
3539
  drawdown,
3380
3540
  ema,
3381
3541
  evaluateFeatureSpecs,
@@ -3386,6 +3546,7 @@ export {
3386
3546
  getFeatureCompute,
3387
3547
  holdingPeriodDays,
3388
3548
  isLongTerm,
3549
+ isQualifiedForLot,
3389
3550
  isRebalanceDay,
3390
3551
  netWithinBucket,
3391
3552
  paramsHash,
@@ -3394,6 +3555,7 @@ export {
3394
3555
  positionsByAsset,
3395
3556
  realize,
3396
3557
  reconcile,
3558
+ reinvestDividend,
3397
3559
  returnSeries,
3398
3560
  rsi,
3399
3561
  runBacktest,