@livefolio/sdk 0.5.0-rc.2 → 0.5.0-rc.3
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 +81 -1
- package/dist/index.js +44 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1599,6 +1599,27 @@ declare class FeatureRuntime {
|
|
|
1599
1599
|
compute(spec: FeatureSpec, asset: Asset): Promise<Series>;
|
|
1600
1600
|
}
|
|
1601
1601
|
|
|
1602
|
+
/**
|
|
1603
|
+
* A scheduled cash injection or withdrawal. Applied at the start of the
|
|
1604
|
+
* matching session — BEFORE `universe`/`features`/`build` — by `runBacktest`
|
|
1605
|
+
* (see Task 10 wiring). Events with `t <= sessionT` that have not yet been
|
|
1606
|
+
* consumed are applied (and summed) on that session.
|
|
1607
|
+
*/
|
|
1608
|
+
type CashEvent = {
|
|
1609
|
+
t: Date;
|
|
1610
|
+
/** Positive = deposit, negative = withdrawal. */
|
|
1611
|
+
delta: number;
|
|
1612
|
+
/**
|
|
1613
|
+
* Optional attribution tag for downstream metrics. `'deposit'`/`'withdrawal'`
|
|
1614
|
+
* are the natural tags for user-scheduled flows (this surface's main use case).
|
|
1615
|
+
* `'interest'`/`'dividend'` are accepted for callers who want to tag a
|
|
1616
|
+
* manually-scheduled flow as income, but the SDK's automatic per-session
|
|
1617
|
+
* interest/dividend hooks do NOT emit `CashEvent`s — they credit cash directly
|
|
1618
|
+
* and report via `BacktestSnapshot.interestIncome`/`dividendIncome`. User code
|
|
1619
|
+
* typically only sets `'deposit'`/`'withdrawal'`.
|
|
1620
|
+
*/
|
|
1621
|
+
reason?: 'deposit' | 'withdrawal' | 'interest' | 'dividend';
|
|
1622
|
+
};
|
|
1602
1623
|
/**
|
|
1603
1624
|
* All inputs required to run a historical backtest.
|
|
1604
1625
|
*
|
|
@@ -1653,6 +1674,16 @@ type RunBacktestOptions<F extends Features = Features, S = unknown> = {
|
|
|
1653
1674
|
* runtime seed its buffer from the historical bars without refetching).
|
|
1654
1675
|
*/
|
|
1655
1676
|
featureRuntime?: FeatureRuntime;
|
|
1677
|
+
/**
|
|
1678
|
+
* Optional scheduled deposits/withdrawals applied per-session before the
|
|
1679
|
+
* strategy runs. Matched by `t <= sessionT`; multiple due events are summed.
|
|
1680
|
+
* Defaults to none (today's behavior). See `BacktestSnapshot.cashFlow`.
|
|
1681
|
+
*
|
|
1682
|
+
* @remarks A withdrawal that exceeds available cash is allowed to drive cash
|
|
1683
|
+
* negative (logged via `console.warn`); automatic force-selling of holdings to
|
|
1684
|
+
* fund withdrawals is deferred to a later release, so this behavior may change.
|
|
1685
|
+
*/
|
|
1686
|
+
cashEvents?: ReadonlyArray<CashEvent>;
|
|
1656
1687
|
};
|
|
1657
1688
|
/**
|
|
1658
1689
|
* A point-in-time snapshot of the simulation at the end of a single trading session.
|
|
@@ -1669,6 +1700,10 @@ type BacktestSnapshot = {
|
|
|
1669
1700
|
orders: ReadonlyArray<Order>;
|
|
1670
1701
|
/** Fills returned by the executor for the orders above. */
|
|
1671
1702
|
fills: ReadonlyArray<Fill>;
|
|
1703
|
+
/**
|
|
1704
|
+
* Net cash delta applied this session via `cashEvents`. Omitted when zero.
|
|
1705
|
+
*/
|
|
1706
|
+
cashFlow?: number;
|
|
1672
1707
|
};
|
|
1673
1708
|
/**
|
|
1674
1709
|
* The return value of `runBacktest`, containing the full simulation history
|
|
@@ -1820,6 +1855,18 @@ interface StreamingDataFeed {
|
|
|
1820
1855
|
subscribe(assets: ReadonlyArray<Asset>): AsyncIterable<StreamingBar>;
|
|
1821
1856
|
}
|
|
1822
1857
|
|
|
1858
|
+
/**
|
|
1859
|
+
* Mutable queue for scheduling {@link CashEvent}s into a running `runLive`
|
|
1860
|
+
* stream. Construct one, pass it via {@link RunLiveOptions.cashEventQueue}, and
|
|
1861
|
+
* `push` events from outside the generator; they are drained at each session
|
|
1862
|
+
* close. A non-breaking alternative to a returned scheduling handle.
|
|
1863
|
+
*/
|
|
1864
|
+
declare class CashEventQueue {
|
|
1865
|
+
private pending;
|
|
1866
|
+
push(e: CashEvent): void;
|
|
1867
|
+
/** Remove and return events with `t <= now`, leaving later events queued. */
|
|
1868
|
+
drainDue(now: Date): CashEvent[];
|
|
1869
|
+
}
|
|
1823
1870
|
/**
|
|
1824
1871
|
* Unified event stream from {@link runLive}. Discriminated union of two variants:
|
|
1825
1872
|
*
|
|
@@ -1861,6 +1908,25 @@ type LiveEvent<F extends Features = Features, _S = unknown> = {
|
|
|
1861
1908
|
* `portfolio` + `prices`.
|
|
1862
1909
|
*/
|
|
1863
1910
|
previewPortfolio: Portfolio;
|
|
1911
|
+
} | {
|
|
1912
|
+
/**
|
|
1913
|
+
* A net cash injection/withdrawal applied to the committed portfolio at a
|
|
1914
|
+
* session close. Emitted BEFORE the `snapshot` for that session, so the
|
|
1915
|
+
* subsequent snapshot's `portfolio.cash` already reflects this delta.
|
|
1916
|
+
* Only emitted when the summed delta of due events is non-zero.
|
|
1917
|
+
*/
|
|
1918
|
+
type: 'cash';
|
|
1919
|
+
/** Session-close timestamp at which the delta was applied. */
|
|
1920
|
+
t: Date;
|
|
1921
|
+
/** Net cash delta applied (sum of all due events). Positive = deposit. */
|
|
1922
|
+
delta: number;
|
|
1923
|
+
/**
|
|
1924
|
+
* Attribution tag of the FIRST contributing event. When multiple cash
|
|
1925
|
+
* events are due at the same session close they are summed into one delta
|
|
1926
|
+
* and the other reasons are dropped — use `cashEventQueue` and drain
|
|
1927
|
+
* manually if you need per-event attribution.
|
|
1928
|
+
*/
|
|
1929
|
+
reason?: CashEvent['reason'];
|
|
1864
1930
|
} | (BacktestSnapshot & {
|
|
1865
1931
|
type: 'snapshot';
|
|
1866
1932
|
});
|
|
@@ -1891,6 +1957,20 @@ type RunLiveOptions<F extends Features = Features, S = unknown> = {
|
|
|
1891
1957
|
* seeded from `history.bars`.
|
|
1892
1958
|
*/
|
|
1893
1959
|
streamingRuntime?: FeatureRuntime;
|
|
1960
|
+
/**
|
|
1961
|
+
* Cash injections/withdrawals known up front. Each is applied to the
|
|
1962
|
+
* committed portfolio at the first session close whose date is `>= e.t`,
|
|
1963
|
+
* summed with any other due events for that session. For dynamic scheduling
|
|
1964
|
+
* after the stream has started, use {@link RunLiveOptions.cashEventQueue}.
|
|
1965
|
+
*/
|
|
1966
|
+
cashEvents?: ReadonlyArray<CashEvent>;
|
|
1967
|
+
/**
|
|
1968
|
+
* Optional mutable queue for scheduling cash events into the running stream
|
|
1969
|
+
* from outside the generator. Drained (alongside `cashEvents`) at each
|
|
1970
|
+
* session close. Construct a {@link CashEventQueue}, pass it here, and `push`
|
|
1971
|
+
* events as they arrive.
|
|
1972
|
+
*/
|
|
1973
|
+
cashEventQueue?: CashEventQueue;
|
|
1894
1974
|
};
|
|
1895
1975
|
/**
|
|
1896
1976
|
* Drives a {@link Strategy} against a streaming market-data source and yields
|
|
@@ -3977,4 +4057,4 @@ declare function applyOrders(portfolio: Portfolio, orders: ReadonlyArray<Order>)
|
|
|
3977
4057
|
*/
|
|
3978
4058
|
declare function positionsByAsset(portfolio: Portfolio): Position[];
|
|
3979
4059
|
|
|
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 };
|
|
4060
|
+
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 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 };
|
package/dist/index.js
CHANGED
|
@@ -331,7 +331,24 @@ async function runBacktest(opts) {
|
|
|
331
331
|
let portfolio = opts.initialPortfolio;
|
|
332
332
|
let state = initialStateValue;
|
|
333
333
|
const snapshots = [];
|
|
334
|
+
const cashEvents = [...opts.cashEvents ?? []].sort((a, b) => a.t.getTime() - b.t.getTime());
|
|
335
|
+
let eventCursor = 0;
|
|
336
|
+
let warnedNegativeCash = false;
|
|
334
337
|
for (const t of sessions) {
|
|
338
|
+
let cashFlow = 0;
|
|
339
|
+
while (eventCursor < cashEvents.length && cashEvents[eventCursor].t.getTime() <= t.getTime()) {
|
|
340
|
+
cashFlow += cashEvents[eventCursor].delta;
|
|
341
|
+
eventCursor++;
|
|
342
|
+
}
|
|
343
|
+
if (cashFlow !== 0) {
|
|
344
|
+
portfolio = { ...portfolio, cash: portfolio.cash + cashFlow };
|
|
345
|
+
if (portfolio.cash < 0 && !warnedNegativeCash) {
|
|
346
|
+
warnedNegativeCash = true;
|
|
347
|
+
console.warn(
|
|
348
|
+
`[runBacktest] cash went negative at ${t.toISOString()}: ${portfolio.cash}. Withdrawals exceed available cash (force-sell is deferred); further occurrences this run are suppressed.`
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
335
352
|
const universe = opts.strategy.universe(t, portfolio);
|
|
336
353
|
const features = await opts.strategy.features(universe, portfolio, t);
|
|
337
354
|
const buildResult = opts.strategy.build(features, portfolio, state, t);
|
|
@@ -344,7 +361,7 @@ async function runBacktest(opts) {
|
|
|
344
361
|
}
|
|
345
362
|
const fills = await opts.executor.submit(orders, t, portfolio);
|
|
346
363
|
portfolio = applyFills(portfolio, fills, orders);
|
|
347
|
-
snapshots.push({ t, portfolio, orders, fills });
|
|
364
|
+
snapshots.push({ t, portfolio, orders, fills, ...cashFlow !== 0 ? { cashFlow } : {} });
|
|
348
365
|
}
|
|
349
366
|
const bars = opts.featureRuntime?.getAllBars() ?? /* @__PURE__ */ new Map();
|
|
350
367
|
return { snapshots, finalPortfolio: portfolio, finalState: state, bars };
|
|
@@ -757,6 +774,18 @@ var MemoryFeatureCache = class {
|
|
|
757
774
|
};
|
|
758
775
|
|
|
759
776
|
// src/strategy/run-live.ts
|
|
777
|
+
var CashEventQueue = class {
|
|
778
|
+
pending = [];
|
|
779
|
+
push(e) {
|
|
780
|
+
this.pending.push(e);
|
|
781
|
+
}
|
|
782
|
+
/** Remove and return events with `t <= now`, leaving later events queued. */
|
|
783
|
+
drainDue(now) {
|
|
784
|
+
const due = this.pending.filter((e) => e.t.getTime() <= now.getTime());
|
|
785
|
+
if (due.length > 0) this.pending = this.pending.filter((e) => e.t.getTime() > now.getTime());
|
|
786
|
+
return due;
|
|
787
|
+
}
|
|
788
|
+
};
|
|
760
789
|
function snapshotState(state) {
|
|
761
790
|
if (state === void 0) return void 0;
|
|
762
791
|
return structuredClone(state);
|
|
@@ -775,6 +804,8 @@ async function* runLive(opts) {
|
|
|
775
804
|
});
|
|
776
805
|
let portfolio = history.finalPortfolio;
|
|
777
806
|
let state = history.finalState;
|
|
807
|
+
const seedQueue = new CashEventQueue();
|
|
808
|
+
for (const e of opts.cashEvents ?? []) seedQueue.push(e);
|
|
778
809
|
const anchorTime = history.snapshots.length > 0 ? history.snapshots[history.snapshots.length - 1].t : /* @__PURE__ */ new Date(0);
|
|
779
810
|
const universe = strategy.universe(anchorTime, portfolio);
|
|
780
811
|
let currentSession = history.snapshots.length > 0 ? calendar.next(history.snapshots[history.snapshots.length - 1].t) : null;
|
|
@@ -826,6 +857,17 @@ async function* runLive(opts) {
|
|
|
826
857
|
}
|
|
827
858
|
const fills = await executor.submit(orders, currentSession, portfolio);
|
|
828
859
|
portfolio = applyFills(portfolio, fills, orders);
|
|
860
|
+
const dueCash = [...seedQueue.drainDue(currentSession), ...opts.cashEventQueue?.drainDue(currentSession) ?? []];
|
|
861
|
+
const cashDelta = dueCash.reduce((s, e) => s + e.delta, 0);
|
|
862
|
+
if (cashDelta !== 0) {
|
|
863
|
+
portfolio = { ...portfolio, cash: portfolio.cash + cashDelta };
|
|
864
|
+
yield {
|
|
865
|
+
type: "cash",
|
|
866
|
+
t: currentSession,
|
|
867
|
+
delta: cashDelta,
|
|
868
|
+
reason: dueCash[0]?.reason
|
|
869
|
+
};
|
|
870
|
+
}
|
|
829
871
|
yield {
|
|
830
872
|
type: "snapshot",
|
|
831
873
|
t: currentSession,
|
|
@@ -3258,6 +3300,7 @@ function positionsByAsset(portfolio) {
|
|
|
3258
3300
|
}
|
|
3259
3301
|
export {
|
|
3260
3302
|
BacktestExecutor,
|
|
3303
|
+
CashEventQueue,
|
|
3261
3304
|
Crypto24x7Calendar,
|
|
3262
3305
|
ExchangeCalendar,
|
|
3263
3306
|
FeatureRuntime,
|