@livefolio/sdk 0.3.1 → 0.3.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 CHANGED
@@ -114,9 +114,81 @@ declare class IndicatorHandle {
114
114
  private _sync;
115
115
  private _upsertSeries;
116
116
  private _querySeriesFromDb;
117
+ /**
118
+ * Apply leverage compounding to a raw bar series, anchored to a stored
119
+ * leveraged value. Used by both `_sync` and `computeAt` so they stay
120
+ * consistent.
121
+ *
122
+ * `anchorDate` is the date of the last *already-stored* leveraged bar
123
+ * (i.e., the bar just before `rawBars[0]`). The stored leveraged value
124
+ * at that date becomes `leveraged[0]`; raw returns are then compounded
125
+ * forward for each subsequent bar.
126
+ *
127
+ * If no stored anchor exists (first-ever sync), falls back to rawBars[0]
128
+ * as the starting raw value — identical to `_sync`'s behaviour.
129
+ */
130
+ private _applyLeverage;
131
+ /**
132
+ * Compute the indicator's value at `date` without persisting anything, with
133
+ * optional live-quote `overrides` keyed by raw market symbol (the same symbol
134
+ * space `MarketProvider.fetchBars` uses — ticker symbols for Price/SMA/etc.,
135
+ * `^VIX` / `^VIX3M` for macro, FRED series IDs like `DGS3MO` for Treasury).
136
+ *
137
+ * Bars for the underlying symbol are resolved storage-first when the market
138
+ * hasn't yet produced bars for `date` (trading day still open), and storage
139
+ * is the fallback whenever the remote fetch fails — see `_resolveRawBars`.
140
+ *
141
+ * For Threshold: returns the threshold constant. For calendar types: computed
142
+ * from `tradingDays.getRange()`. For all others: `_resolveRawBars` → leverage
143
+ * compounding (if any) → lookback-specific computation. Returns null if the
144
+ * value cannot be computed.
145
+ */
146
+ computeAt(date: string, overrides?: Record<string, number>): Promise<number | null>;
147
+ /**
148
+ * Raw (unleveraged) bars for `symbol` up through `date`, with the live quote
149
+ * from `overrides[symbol]` (if any) spliced in at `date`.
150
+ *
151
+ * Decision policy:
152
+ * - `date` > `tradingDays.getLatestClosed()`: market has nothing for that
153
+ * day yet — skip the remote fetch entirely and read from storage.
154
+ * - otherwise: try `this._market.fetchBars(symbol, from)`. On failure, fall
155
+ * back to storage — upstream HTTP providers (Yahoo / FRED) are flaky.
156
+ *
157
+ * After the base is resolved, `overrides[symbol]` is spliced at `date`
158
+ * (replaces the existing bar, or is appended in-order). When no override is
159
+ * present but `date` isn't in the base bars, the last known value is carried
160
+ * forward to `date` — this preserves the fallbackMissingQuotes behaviour the
161
+ * old overlay exposed so leverage compounding / computations always have a
162
+ * point at `date` to land on.
163
+ */
164
+ private _resolveRawBars;
165
+ /**
166
+ * Resolve raw (unleveraged) bars for a market symbol from storage. Maps:
167
+ * - `^VIX` → the VIX indicator's stored series
168
+ * - `^VIX3M` → the VIX3M indicator's stored series
169
+ * - `DGS*` → the matching Treasury-tenor indicator's stored series
170
+ * - anything else → the `Price` indicator for that ticker symbol with
171
+ * `leverage = 1` (the raw contract that `MarketProvider.fetchBars` has).
172
+ *
173
+ * Returns `[]` when the resolved indicator has no stored bars yet.
174
+ */
175
+ private _readStoredBars;
117
176
  series(range?: DateRange): Promise<DailyBar[]>;
118
177
  private _syntheticThresholdSeries;
119
178
  value(date?: string): Promise<number | null>;
179
+ /**
180
+ * Read-only preview of the indicator series with an in-memory bar at `date`
181
+ * computed via `computeAt` with the supplied live-quote `overrides`. Does
182
+ * NOT write to `indicators_series`. Safe to call before market close.
183
+ *
184
+ * @param date - Target trading day whose value is computed in-memory.
185
+ * Must be in `tradingDays.getRange()`.
186
+ * @param overrides - Raw (unleveraged) quotes keyed by market symbol.
187
+ * Symbols omitted fall back to the last known value (see `_resolveRawBars`).
188
+ * @param range - Optional filter applied to the returned bars.
189
+ * @returns Stored historical bars plus (or with) today's in-memory value.
190
+ */
191
+ previewSeries(date: string, overrides: Record<string, number>, range?: DateRange): Promise<DailyBar[]>;
120
192
  }
121
193
 
122
194
  interface StorageProvider {
@@ -188,6 +260,7 @@ interface StorageProvider {
188
260
  getSeries(strategyId: number, range?: DateRange): Promise<StrategySeriesEntry[]>;
189
261
  writeSeries(strategyId: number, entries: StrategySeriesEntry[]): Promise<void>;
190
262
  getLatestSeriesDate(strategyId: number): Promise<string | null>;
263
+ getLatestAllocationId(strategyId: number): Promise<number | null>;
191
264
  resolveReference(linkId: string): Promise<StrategyReferenceData>;
192
265
  };
193
266
  tradingDays: {
@@ -208,13 +281,12 @@ declare class SignalHandle {
208
281
  readonly comparison: Comparison;
209
282
  readonly tolerance: number;
210
283
  private _storage;
211
- private _market;
212
284
  private _resolvedId;
213
285
  private _resolving;
214
286
  private _cachedSeries;
215
287
  private _cachedAsOf;
216
288
  private _syncing;
217
- constructor(storage: StorageProvider, market: MarketProvider, identity: SignalIdentity);
289
+ constructor(storage: StorageProvider, _market: MarketProvider, identity: SignalIdentity);
218
290
  get id(): number;
219
291
  resolve(): Promise<{
220
292
  id: number;
@@ -228,8 +300,31 @@ declare class SignalHandle {
228
300
  private _sync;
229
301
  private _upsertSeries;
230
302
  private _querySeriesFromDb;
303
+ /**
304
+ * Compute the signal's boolean value at `date` without persisting anything,
305
+ * with optional live-quote `overrides` that are routed through each
306
+ * indicator's `computeAt`. Returns null if either indicator cannot produce
307
+ * a value at `date`.
308
+ *
309
+ * @param prevBool - The signal's boolean value at the bar immediately
310
+ * preceding `date`, used for hysteresis when `tolerance > 0`. If not
311
+ * provided, falls back to `storage.signals.getLastValue` (suitable for
312
+ * standalone callers). On the preview path `_evaluate` passes this from
313
+ * the in-memory `dateMap` so we never read stale storage.
314
+ */
315
+ computeAt(date: string, overrides?: Record<string, number>, prevBool?: boolean | null): Promise<boolean | null>;
231
316
  series(range?: DateRange): Promise<DailyBar[]>;
232
317
  value(date?: string): Promise<number | null>;
318
+ /**
319
+ * Read-only preview of the signal series with an in-memory bar at `date`
320
+ * computed via `computeAt` with the supplied live-quote `overrides`. Does
321
+ * NOT write to `signals_series`.
322
+ *
323
+ * @param date - Target trading day whose boolean is computed in-memory.
324
+ * @param overrides - Raw (unleveraged) quotes keyed by market symbol.
325
+ * @param range - Optional filter applied to the returned bars.
326
+ */
327
+ previewSeries(date: string, overrides: Record<string, number>, range?: DateRange): Promise<DailyBar[]>;
233
328
  }
234
329
 
235
330
  declare class AllocationHandle {
@@ -280,6 +375,45 @@ interface FinalState {
280
375
  closePrices: Record<string, number>;
281
376
  leveragedPrices: Record<string, number>;
282
377
  }
378
+ /** Per-signal slice of a live strategy snapshot. */
379
+ interface LiveSignalState {
380
+ indicator1: {
381
+ value: number | null;
382
+ date: string | null;
383
+ };
384
+ indicator2: {
385
+ value: number | null;
386
+ date: string | null;
387
+ };
388
+ isTrue: boolean;
389
+ }
390
+ /** Per-rule collection of live signal states, in the same order as the rule's `when` list. */
391
+ interface LiveRuleState {
392
+ signals: LiveSignalState[];
393
+ }
394
+ /** Full live strategy view for a single evaluation date — no portfolio info. */
395
+ interface StrategyLiveState {
396
+ allocation: AllocationHandle | null;
397
+ activeRuleIndex: number;
398
+ rules: LiveRuleState[];
399
+ }
400
+ /**
401
+ * Combined live state returned by `SimulationHandle.pushAndPreview`: both the
402
+ * portfolio snapshot from a `push` and the strategy evaluation at the target
403
+ * date under the accumulated live-quote overrides.
404
+ */
405
+ interface LivePreviewState extends StrategyLiveState {
406
+ snapshot: PortfolioSnapshot;
407
+ }
408
+ /**
409
+ * Callback shape that `SimulationHandle.pushAndPreview` delegates to. Exists
410
+ * purely to break the circular import between `SimulationHandle` (in this
411
+ * file) and `StrategyHandle` (which creates simulations) — a strategy passes
412
+ * a bound `(date, overrides) => previewLiveState(...)` into the handle.
413
+ */
414
+ interface LiveEvaluator {
415
+ previewLiveState(date: string, overrides: Record<string, number>): Promise<StrategyLiveState>;
416
+ }
283
417
  declare class SimulationHandle {
284
418
  readonly series: DailyBar[];
285
419
  readonly trades: Trade[];
@@ -290,8 +424,30 @@ declare class SimulationHandle {
290
424
  private _lastLeveragedPrices;
291
425
  private _currentLeveragedPrices;
292
426
  private _lastDate;
293
- constructor(series: DailyBar[], trades: Trade[], startingPortfolio: PortfolioHandle, finalState?: FinalState);
427
+ private _pushedQuotes;
428
+ private _liveEvaluator;
429
+ constructor(series: DailyBar[], trades: Trade[], startingPortfolio: PortfolioHandle, finalState?: FinalState, liveEvaluator?: LiveEvaluator);
294
430
  push(...prices: [TickerHandle, number][]): PortfolioSnapshot;
431
+ /**
432
+ * One-call live update. Feeds portfolio-relevant ticker prices into `push`
433
+ * (derived from `quotes` via the running portfolio's holdings), accumulates
434
+ * every symbol in `quotes` into an internal override map so macro symbols
435
+ * (e.g. `^VIX`) persist across ticks, then delegates to the simulation's
436
+ * strategy for rule / signal / indicator evaluation at `date`.
437
+ *
438
+ * Without a live evaluator attached, returns just the portfolio snapshot
439
+ * with allocation/rules/signals empty.
440
+ *
441
+ * @param quotes Symbol → raw live price. Portfolio tickers flow through
442
+ * `push` for leveraged-equity math; non-portfolio symbols are still
443
+ * layered into the overlay so indicators can see them.
444
+ * @param options.date Target trading day to evaluate against. Defaults to
445
+ * the current UTC ISO date; callers with non-UTC semantics or after-hours
446
+ * rollover should supply their own.
447
+ */
448
+ pushAndPreview(quotes: Record<string, number>, options?: {
449
+ date?: string;
450
+ }): Promise<LivePreviewState>;
295
451
  }
296
452
 
297
453
  interface StrategyRule {
@@ -339,10 +495,56 @@ declare class StrategyHandle {
339
495
  private _getLatestStrategySeriesDate;
340
496
  private _ensureFresh;
341
497
  private _sync;
498
+ /**
499
+ * Pure evaluate — runs the same pipeline as _sync but returns the computed
500
+ * evaluation instead of persisting. Used by both _sync (post-close write
501
+ * path) and the public preview methods (pre-close read-only path).
502
+ *
503
+ * When `overrides` is `undefined` we take the write path — syncing signals
504
+ * through storage as normal. When `overrides` is provided (even an empty
505
+ * map) we take the read-only preview path: historical signal bars come
506
+ * straight from storage, today's bar is computed in-memory via
507
+ * `signal.computeAt(date, overrides, prevBool)`, and nothing is written.
508
+ */
509
+ private _evaluate;
342
510
  private _querySeriesFromDb;
343
511
  series(range?: DateRange): Promise<StrategyBar[]>;
344
512
  value(date?: string): Promise<AllocationHandle | null>;
345
513
  simulate(options: SimulateOptions): Promise<SimulationHandle>;
514
+ /**
515
+ * Preview the allocation this strategy would produce for `date` if today
516
+ * closed at the provided raw quote prices. Does NOT write to strategies_series,
517
+ * signals_series, or indicators_series. Safe to call before market close.
518
+ *
519
+ * @param date - The trading day to preview (must be in tradingDays.getRange()).
520
+ * @param overrides - Raw (unleveraged) live prices keyed by market symbol.
521
+ * Symbols absent from this map fall back to the last stored value
522
+ * (see `IndicatorHandle._resolveRawBars`).
523
+ * @returns The AllocationHandle for `date`, or null if the strategy has no
524
+ * evaluable entry for that date.
525
+ */
526
+ previewAllocation(date: string, overrides: Record<string, number>): Promise<AllocationHandle | null>;
527
+ /**
528
+ * Read-only preview of the strategy's allocation series including `date`.
529
+ * Returns stored historical allocations plus an in-memory bar at `date`
530
+ * computed via the same overrides-based preview path as `previewAllocation`.
531
+ *
532
+ * @param date - Target trading day to splice in-memory.
533
+ * @param overrides - Raw (unleveraged) quotes keyed by market symbol.
534
+ * @param range - Optional filter applied to the returned bars.
535
+ */
536
+ previewSeries(date: string, overrides: Record<string, number>, range?: DateRange): Promise<StrategyBar[]>;
537
+ /**
538
+ * Full live strategy view at `date` under live-quote `overrides`: the active
539
+ * allocation, the index of the rule that fired (or fallback), and per-rule
540
+ * per-signal indicator values + truth. Computed entirely through the
541
+ * overrides preview path — no writes to any `*_series` tables.
542
+ *
543
+ * Threshold indicators have their date suppressed (`null`) since their
544
+ * synthetic series runs over every trading day in storage including future
545
+ * dates and would report a far-future date for the last bar.
546
+ */
547
+ previewLiveState(date: string, overrides: Record<string, number>): Promise<StrategyLiveState>;
346
548
  private _fetchPricesForTickers;
347
549
  private _fetchRawClosePrices;
348
550
  }
@@ -394,4 +596,4 @@ interface PriceStream {
394
596
  close(): void;
395
597
  }
396
598
 
397
- export { AllocationHandle, type Comparison, type DailyBar, type DateRange, IndicatorHandle, type IndicatorIdentity, type IndicatorType, type LivefolioClient, type LivefolioClientOptions, type MarketProvider, PortfolioHandle, type PortfolioSnapshot, type PriceStream, SignalHandle, type SignalIdentity, type SimulateOptions, SimulationHandle, type StorageProvider, type StrategyBar, type StrategyDefinition, StrategyHandle, type StrategyOptions, type StrategyReferenceData, type StrategyRule, type StrategyRuleDefinition, type StrategySeriesEntry, type StreamStatus, TickerHandle, type Trade, type TradingFreq, type Unit, createClient };
599
+ export { AllocationHandle, type Comparison, type DailyBar, type DateRange, IndicatorHandle, type IndicatorIdentity, type IndicatorType, type LiveEvaluator, type LivePreviewState, type LiveRuleState, type LiveSignalState, type LivefolioClient, type LivefolioClientOptions, type MarketProvider, PortfolioHandle, type PortfolioSnapshot, type PriceStream, SignalHandle, type SignalIdentity, type SimulateOptions, SimulationHandle, type StorageProvider, type StrategyBar, type StrategyDefinition, StrategyHandle, type StrategyLiveState, type StrategyOptions, type StrategyReferenceData, type StrategyRule, type StrategyRuleDefinition, type StrategySeriesEntry, type StreamStatus, TickerHandle, type Trade, type TradingFreq, type Unit, createClient };