@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 +196 -2
- package/dist/index.js +201 -39
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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({
|
|
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
|
|
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 *
|
|
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() -
|
|
1314
|
-
if (dow === 0) return new Date(d.getTime() +
|
|
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() +
|
|
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() -
|
|
1357
|
-
if (dow === 0) return new Date(d.getTime() +
|
|
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)) *
|
|
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 *
|
|
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
|
|
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() +
|
|
1536
|
-
while (!this.isOpen(d)) d = new Date(d.getTime() +
|
|
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() -
|
|
1542
|
-
while (!this.isOpen(d)) d = new Date(d.getTime() -
|
|
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() +
|
|
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
|
|
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 *
|
|
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 *
|
|
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() +
|
|
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() +
|
|
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() +
|
|
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() +
|
|
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() +
|
|
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() +
|
|
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() +
|
|
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() +
|
|
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() +
|
|
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() +
|
|
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
|
|
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 *
|
|
2542
|
-
if (dow === SUN2) return new Date(d.getTime() +
|
|
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() -
|
|
2548
|
-
if (dow === SUN2) return new Date(d.getTime() - 2 *
|
|
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
|
|
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() +
|
|
2903
|
+
return new Date(midnightUtc(t).getTime() + MS_PER_DAY7);
|
|
2750
2904
|
}
|
|
2751
2905
|
previous(t) {
|
|
2752
|
-
return new Date(midnightUtc(t).getTime() -
|
|
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() +
|
|
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() +
|
|
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,
|