@livefolio/sdk 0.4.0 → 0.4.2
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 +208 -3
- package/dist/index.js +161 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -577,6 +577,11 @@ type PriceMap = ReadonlyMap<AssetId, number>;
|
|
|
577
577
|
* value and existing share counts.
|
|
578
578
|
* @param prices - Current prices for all assets that appear in `targets` or are
|
|
579
579
|
* currently held. Throws `Error` if a target asset is missing from this map.
|
|
580
|
+
* @param assets - Optional canonical {@link Asset} metadata keyed by id. When
|
|
581
|
+
* `reconcile` needs to emit an order for an asset that is not yet held in the
|
|
582
|
+
* portfolio, it consults this map for the proper `symbol`/`kind`. If the
|
|
583
|
+
* asset is missing here too, the order falls back to a synthesized
|
|
584
|
+
* `{ kind: 'equity', id, symbol: id }` — lossless but display-unfriendly.
|
|
580
585
|
* @returns A readonly array of `RebalanceOrder` objects. The array may be empty
|
|
581
586
|
* if the portfolio is already at the target allocation. Order IDs are
|
|
582
587
|
* deterministic within a single call (`rebal_<assetId>_<counter>`).
|
|
@@ -603,7 +608,7 @@ type PriceMap = ReadonlyMap<AssetId, number>;
|
|
|
603
608
|
* // ]
|
|
604
609
|
* ```
|
|
605
610
|
*/
|
|
606
|
-
declare function reconcile(targets: TargetWeights, portfolio: Portfolio, prices: PriceMap): ReadonlyArray<RebalanceOrder>;
|
|
611
|
+
declare function reconcile(targets: TargetWeights, portfolio: Portfolio, prices: PriceMap, assets?: ReadonlyMap<AssetId, Asset>): ReadonlyArray<RebalanceOrder>;
|
|
607
612
|
|
|
608
613
|
/**
|
|
609
614
|
* A flat record of fundamental data points for an asset at a point in time.
|
|
@@ -1884,6 +1889,100 @@ type RunLiveOptions<F extends Features = Features, S = unknown> = {
|
|
|
1884
1889
|
*/
|
|
1885
1890
|
declare function runLive<F extends Features = Features, S = unknown>(opts: RunLiveOptions<F, S>): AsyncIterable<LiveEvent<F, S>>;
|
|
1886
1891
|
|
|
1892
|
+
/**
|
|
1893
|
+
* A point-in-time quote for an asset. The `t` field is the vendor-stamped
|
|
1894
|
+
* quote time — callers should treat it as the staleness upper bound, not
|
|
1895
|
+
* "now". `price` is the last trade price, or the mid when the vendor only
|
|
1896
|
+
* exposes bid/ask. `bid` and `ask` surface Level 1 data when available.
|
|
1897
|
+
*
|
|
1898
|
+
* @example
|
|
1899
|
+
* ```ts
|
|
1900
|
+
* import type { Quote } from '@livefolio/sdk';
|
|
1901
|
+
*
|
|
1902
|
+
* const q: Quote = {
|
|
1903
|
+
* asset: { kind: 'equity', id: 'AAPL', symbol: 'AAPL' },
|
|
1904
|
+
* t: new Date('2024-06-03T13:30:00Z'),
|
|
1905
|
+
* price: 195.12,
|
|
1906
|
+
* bid: 195.11,
|
|
1907
|
+
* ask: 195.13,
|
|
1908
|
+
* currency: 'USD',
|
|
1909
|
+
* };
|
|
1910
|
+
* ```
|
|
1911
|
+
*/
|
|
1912
|
+
type Quote = {
|
|
1913
|
+
asset: Asset;
|
|
1914
|
+
/** Vendor-stamped quote time. */
|
|
1915
|
+
t: Date;
|
|
1916
|
+
/** Last trade price, or mid if the vendor only exposes bid/ask. */
|
|
1917
|
+
price: number;
|
|
1918
|
+
/** Best bid, when the vendor exposes Level 1 data. */
|
|
1919
|
+
bid?: number;
|
|
1920
|
+
/** Best ask, when the vendor exposes Level 1 data. */
|
|
1921
|
+
ask?: number;
|
|
1922
|
+
/** Quote currency, when the vendor reports it. */
|
|
1923
|
+
currency?: string;
|
|
1924
|
+
};
|
|
1925
|
+
/**
|
|
1926
|
+
* One-shot current-price source. Sibling interface to {@link DataFeed} and
|
|
1927
|
+
* {@link StreamingDataFeed} — they are NOT a union and there is no
|
|
1928
|
+
* composition helper. Historical adapters implement `DataFeed.bars()`;
|
|
1929
|
+
* streaming adapters implement `StreamingDataFeed.subscribe()`; quote
|
|
1930
|
+
* adapters implement `QuoteFeed.quote()`. A vendor that offers all three
|
|
1931
|
+
* implements all three interfaces on one class.
|
|
1932
|
+
*
|
|
1933
|
+
* Implementations MUST guarantee:
|
|
1934
|
+
* - `quote` returns a freshly fetched {@link Quote} each call. Implementations
|
|
1935
|
+
* MAY cache for a short TTL to coalesce bursts; cache behavior MUST be
|
|
1936
|
+
* documented on the adapter.
|
|
1937
|
+
* - The returned `Quote.t` is the vendor's stamp, not the local clock.
|
|
1938
|
+
* - `quote` rejects with a typed error if the asset is unsupported or the
|
|
1939
|
+
* vendor is unreachable. It MUST NOT silently return a stale or fabricated
|
|
1940
|
+
* price.
|
|
1941
|
+
*
|
|
1942
|
+
* `quoteBatch` is optional. Vendors whose endpoints accept a symbol list
|
|
1943
|
+
* SHOULD implement it to avoid N-round-trip storms. Callers feature-detect:
|
|
1944
|
+
*
|
|
1945
|
+
* ```ts
|
|
1946
|
+
* const quotes = feed.quoteBatch
|
|
1947
|
+
* ? await feed.quoteBatch(assets)
|
|
1948
|
+
* : await Promise.all(assets.map((a) => feed.quote(a)));
|
|
1949
|
+
* ```
|
|
1950
|
+
*
|
|
1951
|
+
* When `quoteBatch` is implemented, the returned array MUST preserve request
|
|
1952
|
+
* order — `quotes[i]` corresponds to `assets[i]`.
|
|
1953
|
+
*
|
|
1954
|
+
* @example
|
|
1955
|
+
* ```ts
|
|
1956
|
+
* import type { QuoteFeed } from '@livefolio/sdk';
|
|
1957
|
+
*
|
|
1958
|
+
* const feed: QuoteFeed = {
|
|
1959
|
+
* async quote(asset) {
|
|
1960
|
+
* return { asset, t: new Date(), price: 195.12 };
|
|
1961
|
+
* },
|
|
1962
|
+
* };
|
|
1963
|
+
* ```
|
|
1964
|
+
*/
|
|
1965
|
+
interface QuoteFeed {
|
|
1966
|
+
/**
|
|
1967
|
+
* Returns a freshly fetched quote for `asset`.
|
|
1968
|
+
*
|
|
1969
|
+
* @param asset - The instrument to quote.
|
|
1970
|
+
* @returns A {@link Quote} carrying the vendor-stamped time and price.
|
|
1971
|
+
*/
|
|
1972
|
+
quote(asset: Asset): Promise<Quote>;
|
|
1973
|
+
/**
|
|
1974
|
+
* Returns quotes for `assets` in a single vendor round-trip. Optional —
|
|
1975
|
+
* adapters whose vendor does not expose a batch endpoint may omit this.
|
|
1976
|
+
*
|
|
1977
|
+
* Returned array MUST preserve request order: `result[i]` corresponds to
|
|
1978
|
+
* `assets[i]`.
|
|
1979
|
+
*
|
|
1980
|
+
* @param assets - The instruments to quote.
|
|
1981
|
+
* @returns An array of {@link Quote} objects in request order.
|
|
1982
|
+
*/
|
|
1983
|
+
quoteBatch?(assets: ReadonlyArray<Asset>): Promise<ReadonlyArray<Quote>>;
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1887
1986
|
/**
|
|
1888
1987
|
* In-process, Map-backed implementation of {@link FeatureCache}. Caches
|
|
1889
1988
|
* computed indicator series in memory for the lifetime of the instance.
|
|
@@ -2095,6 +2194,49 @@ declare class RoutingStreamingDataFeed implements StreamingDataFeed {
|
|
|
2095
2194
|
private merged;
|
|
2096
2195
|
}
|
|
2097
2196
|
|
|
2197
|
+
/**
|
|
2198
|
+
* Error thrown by {@link RoutingQuoteFeed} when an asset cannot be routed.
|
|
2199
|
+
*/
|
|
2200
|
+
declare class RoutingQuoteFeedError extends Error {
|
|
2201
|
+
constructor(message: string);
|
|
2202
|
+
}
|
|
2203
|
+
/** Function form of the routing rule. Returns the feed for `asset`, or `undefined` when no feed handles it. */
|
|
2204
|
+
type RoutingQuoteFeedRouteFn = (asset: Asset) => QuoteFeed | undefined;
|
|
2205
|
+
/** Map form of the routing rule. Keys are `Asset['kind']` discriminants. */
|
|
2206
|
+
type RoutingQuoteFeedRouteMap = Readonly<Partial<Record<Asset['kind'], QuoteFeed>>>;
|
|
2207
|
+
/**
|
|
2208
|
+
* A {@link QuoteFeed} that delegates each call to one of several underlying
|
|
2209
|
+
* feeds based on the asset. Use this to compose vendors — e.g. Alpaca for
|
|
2210
|
+
* equity quotes and a polling adapter for macro series — behind a single
|
|
2211
|
+
* `QuoteFeed` instance.
|
|
2212
|
+
*
|
|
2213
|
+
* Routing rules:
|
|
2214
|
+
* - **Map form:** `new RoutingQuoteFeed({ equity: alpaca, macro: fredPolling })`.
|
|
2215
|
+
* Keys are `asset.kind` discriminants. The 90% case.
|
|
2216
|
+
* - **Function form:** `new RoutingQuoteFeed((a) => a.kind === 'macro' ? fred : alpaca)`.
|
|
2217
|
+
* Use when routing depends on more than `kind` (e.g. allowlists).
|
|
2218
|
+
*
|
|
2219
|
+
* The router always implements `quoteBatch` — even if some inner feeds lack
|
|
2220
|
+
* it, the router falls back to per-asset `quote()` calls within that group,
|
|
2221
|
+
* preserving request order across the full result.
|
|
2222
|
+
*
|
|
2223
|
+
* @example
|
|
2224
|
+
* ```ts
|
|
2225
|
+
* import { RoutingQuoteFeed } from '@livefolio/sdk';
|
|
2226
|
+
*
|
|
2227
|
+
* const feed = new RoutingQuoteFeed({ equity: alpacaQuotes, macro: fredQuotes });
|
|
2228
|
+
* const quotes = await feed.quoteBatch([aaplAsset, dgs10Asset, msftAsset]);
|
|
2229
|
+
* // quotes[0] is for AAPL, quotes[1] for DGS10, quotes[2] for MSFT — request order preserved.
|
|
2230
|
+
* ```
|
|
2231
|
+
*/
|
|
2232
|
+
declare class RoutingQuoteFeed implements QuoteFeed {
|
|
2233
|
+
private readonly route;
|
|
2234
|
+
constructor(routes: RoutingQuoteFeedRouteMap | RoutingQuoteFeedRouteFn);
|
|
2235
|
+
quote(asset: Asset): Promise<Quote>;
|
|
2236
|
+
quoteBatch(assets: ReadonlyArray<Asset>): Promise<ReadonlyArray<Quote>>;
|
|
2237
|
+
private resolve;
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2098
2240
|
type PollingSchedule = {
|
|
2099
2241
|
kind: 'interval';
|
|
2100
2242
|
intervalMs: number;
|
|
@@ -2915,6 +3057,67 @@ declare function evaluateFeatureSpecs(specs: ReadonlyArray<TacticalFeatureSpec>,
|
|
|
2915
3057
|
* ```
|
|
2916
3058
|
*/
|
|
2917
3059
|
declare function withSynthetics(dataFeed: DataFeed, synthetics: ReadonlyArray<SyntheticAsset>): DataFeed;
|
|
3060
|
+
/**
|
|
3061
|
+
* Options for {@link withStreamingSynthetics}.
|
|
3062
|
+
*/
|
|
3063
|
+
interface WithStreamingSyntheticsOptions {
|
|
3064
|
+
/**
|
|
3065
|
+
* Last known close per asset id, used to seed `prevUnderlyingClose` and
|
|
3066
|
+
* `prevSynthClose` so the first live tick of a synthetic continues smoothly
|
|
3067
|
+
* from the end of its historical series.
|
|
3068
|
+
*
|
|
3069
|
+
* Build it from a {@link BacktestResult}:
|
|
3070
|
+
* ```ts
|
|
3071
|
+
* const seedLastCloses = new Map<AssetId, number>();
|
|
3072
|
+
* for (const [id, bars] of history.bars) {
|
|
3073
|
+
* const last = bars.at(-1)?.close;
|
|
3074
|
+
* if (last !== undefined) seedLastCloses.set(id, last);
|
|
3075
|
+
* }
|
|
3076
|
+
* ```
|
|
3077
|
+
*
|
|
3078
|
+
* Without seeding, the first synthesized tick lands on the underlying's
|
|
3079
|
+
* price and produces a visible jump in live preview.
|
|
3080
|
+
*/
|
|
3081
|
+
seedLastCloses: ReadonlyMap<AssetId, number>;
|
|
3082
|
+
}
|
|
3083
|
+
/**
|
|
3084
|
+
* Streaming-feed counterpart to {@link withSynthetics}. Wraps a
|
|
3085
|
+
* {@link StreamingDataFeed} so that subscriptions for synthetic asset ids
|
|
3086
|
+
* resolve to upstream subscriptions on the underlying, with each underlying
|
|
3087
|
+
* tick re-emitted as a synthesized tick on the synthetic's id using the same
|
|
3088
|
+
* `(1 + leverage × r) × (1 − expense/252)` formula as the historical wrapper.
|
|
3089
|
+
*
|
|
3090
|
+
* Behavior:
|
|
3091
|
+
* - Non-synthetic ids in the `assets` argument pass through to the inner feed
|
|
3092
|
+
* unchanged.
|
|
3093
|
+
* - Underlyings that aren't directly in `assets` but are needed by a synthetic
|
|
3094
|
+
* are subscribed silently — only the synthesized ticks are yielded back to
|
|
3095
|
+
* the caller for those.
|
|
3096
|
+
* - Underlyings that the caller _did_ ask for in `assets` are yielded both as
|
|
3097
|
+
* the raw underlying tick and as the synthesized tick(s).
|
|
3098
|
+
*
|
|
3099
|
+
* Throws at construction time if `synthetics` contains duplicate `id` values.
|
|
3100
|
+
*
|
|
3101
|
+
* @example
|
|
3102
|
+
* ```ts
|
|
3103
|
+
* import { withStreamingSynthetics, runLive } from '@livefolio/sdk';
|
|
3104
|
+
*
|
|
3105
|
+
* const seedLastCloses = new Map<AssetId, number>();
|
|
3106
|
+
* for (const [id, bars] of history.bars) {
|
|
3107
|
+
* const last = bars.at(-1)?.close;
|
|
3108
|
+
* if (last !== undefined) seedLastCloses.set(id, last);
|
|
3109
|
+
* }
|
|
3110
|
+
*
|
|
3111
|
+
* const liveFeed = withStreamingSynthetics(rawStreamingFeed, spec.synthetics ?? [], {
|
|
3112
|
+
* seedLastCloses,
|
|
3113
|
+
* });
|
|
3114
|
+
*
|
|
3115
|
+
* for await (const event of runLive({ strategy, history, dataFeed: liveFeed, executor, calendar })) {
|
|
3116
|
+
* // …
|
|
3117
|
+
* }
|
|
3118
|
+
* ```
|
|
3119
|
+
*/
|
|
3120
|
+
declare function withStreamingSynthetics(inner: StreamingDataFeed, synthetics: ReadonlyArray<SyntheticAsset>, opts: WithStreamingSyntheticsOptions): StreamingDataFeed;
|
|
2918
3121
|
|
|
2919
3122
|
/** Test-only: reset the once-per-process deprecation gate. */
|
|
2920
3123
|
declare function _resetTacticalDeprecationWarningForTesting(): void;
|
|
@@ -3018,15 +3221,17 @@ type index$1_TacticalFeatureSpec = TacticalFeatureSpec;
|
|
|
3018
3221
|
type index$1_TacticalFeatures = TacticalFeatures;
|
|
3019
3222
|
type index$1_TacticalSpec = TacticalSpec;
|
|
3020
3223
|
type index$1_Tolerance = Tolerance;
|
|
3224
|
+
type index$1_WithStreamingSyntheticsOptions = WithStreamingSyntheticsOptions;
|
|
3021
3225
|
declare const index$1__resetTacticalDeprecationWarningForTesting: typeof _resetTacticalDeprecationWarningForTesting;
|
|
3022
3226
|
declare const index$1_evaluateFeatureSpecs: typeof evaluateFeatureSpecs;
|
|
3023
3227
|
declare const index$1_evaluateRuleTree: typeof evaluateRuleTree;
|
|
3024
3228
|
declare const index$1_fromSpec: typeof fromSpec;
|
|
3025
3229
|
declare const index$1_isRebalanceDay: typeof isRebalanceDay;
|
|
3026
3230
|
declare const index$1_periodKey: typeof periodKey;
|
|
3231
|
+
declare const index$1_withStreamingSynthetics: typeof withStreamingSynthetics;
|
|
3027
3232
|
declare const index$1_withSynthetics: typeof withSynthetics;
|
|
3028
3233
|
declare namespace index$1 {
|
|
3029
|
-
export { type index$1_AllocateNode as AllocateNode, type index$1_AssetRef as AssetRef, type index$1_Comparison as Comparison, type index$1_ComparisonOp as ComparisonOp, type index$1_FeatureRef as FeatureRef, type index$1_FromSpecOptions as FromSpecOptions, type index$1_IfNode as IfNode, type index$1_RebalanceConfig as RebalanceConfig, type index$1_RebalanceFrequency as RebalanceFrequency, type index$1_RuleNode as RuleNode, type index$1_RuleTreeState as RuleTreeState, type index$1_SyntheticAsset as SyntheticAsset, type index$1_TacticalFeatureKind as TacticalFeatureKind, type index$1_TacticalFeatureSpec as TacticalFeatureSpec, type index$1_TacticalFeatures as TacticalFeatures, type index$1_TacticalSpec as TacticalSpec, type index$1_Tolerance as Tolerance, index$1__resetTacticalDeprecationWarningForTesting as _resetTacticalDeprecationWarningForTesting, index$1_evaluateFeatureSpecs as evaluateFeatureSpecs, index$1_evaluateRuleTree as evaluateRuleTree, index$1_fromSpec as fromSpec, index$1_isRebalanceDay as isRebalanceDay, index$1_periodKey as periodKey, index$1_withSynthetics as withSynthetics };
|
|
3234
|
+
export { type index$1_AllocateNode as AllocateNode, type index$1_AssetRef as AssetRef, type index$1_Comparison as Comparison, type index$1_ComparisonOp as ComparisonOp, type index$1_FeatureRef as FeatureRef, type index$1_FromSpecOptions as FromSpecOptions, type index$1_IfNode as IfNode, type index$1_RebalanceConfig as RebalanceConfig, type index$1_RebalanceFrequency as RebalanceFrequency, type index$1_RuleNode as RuleNode, type index$1_RuleTreeState as RuleTreeState, type index$1_SyntheticAsset as SyntheticAsset, type index$1_TacticalFeatureKind as TacticalFeatureKind, type index$1_TacticalFeatureSpec as TacticalFeatureSpec, type index$1_TacticalFeatures as TacticalFeatures, type index$1_TacticalSpec as TacticalSpec, type index$1_Tolerance as Tolerance, type index$1_WithStreamingSyntheticsOptions as WithStreamingSyntheticsOptions, index$1__resetTacticalDeprecationWarningForTesting as _resetTacticalDeprecationWarningForTesting, index$1_evaluateFeatureSpecs as evaluateFeatureSpecs, index$1_evaluateRuleTree as evaluateRuleTree, index$1_fromSpec as fromSpec, index$1_isRebalanceDay as isRebalanceDay, index$1_periodKey as periodKey, index$1_withStreamingSynthetics as withStreamingSynthetics, index$1_withSynthetics as withSynthetics };
|
|
3030
3235
|
}
|
|
3031
3236
|
|
|
3032
3237
|
/**
|
|
@@ -3393,4 +3598,4 @@ declare function applyFills(portfolio: Portfolio, fills: ReadonlyArray<Fill>, or
|
|
|
3393
3598
|
*/
|
|
3394
3599
|
declare function applyOrders(portfolio: Portfolio, orders: ReadonlyArray<Order>): Portfolio;
|
|
3395
3600
|
|
|
3396
|
-
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, LSEExchangeCalendar, type LiveEvent, type MacroAsset, MemoryFeatureCache, NYSEExchangeCalendar, type NextOpenFn, type OpenOrder, type Order, type PollingSchedule, type PollingStreamOptions, type Portfolio, type Position, type PositionId, type PriceMap, type RebalanceConfig, type RebalanceFrequency, type RebalanceOrder, type ReturnMode, RoutingDataFeed, RoutingDataFeedError, type RoutingDataFeedRouteFn, type RoutingDataFeedRouteMap, 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 TimeOfDay, type Tolerance, applyFills, applyOrders, barsToSeries, collectBars, defineFeature, drawdown, ema, evaluateFeatureSpecs, evaluateRuleTree, index as features, fromSpec, getCalendar, getFeatureCompute, isRebalanceDay, paramsHash, periodKey, pollingStreamFromHistorical, reconcile, returnSeries, rsi, runBacktest, runLive, seriesAt, sma, index$1 as tactical, volatility, withSynthetics };
|
|
3601
|
+
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, LSEExchangeCalendar, type LiveEvent, type MacroAsset, MemoryFeatureCache, NYSEExchangeCalendar, type NextOpenFn, type OpenOrder, type Order, type PollingSchedule, type PollingStreamOptions, type Portfolio, type Position, type PositionId, type PriceMap, type Quote, type QuoteFeed, 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 TimeOfDay, type Tolerance, type WithStreamingSyntheticsOptions, applyFills, applyOrders, barsToSeries, collectBars, defineFeature, drawdown, ema, evaluateFeatureSpecs, evaluateRuleTree, index as features, fromSpec, getCalendar, getFeatureCompute, isRebalanceDay, paramsHash, periodKey, pollingStreamFromHistorical, reconcile, returnSeries, rsi, runBacktest, runLive, seriesAt, sma, index$1 as tactical, volatility, withStreamingSynthetics, withSynthetics };
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// src/strategy/reconcile.ts
|
|
8
|
-
function reconcile(targets, portfolio, prices) {
|
|
8
|
+
function reconcile(targets, portfolio, prices, assets) {
|
|
9
9
|
const longByAsset = /* @__PURE__ */ new Map();
|
|
10
10
|
for (const p of portfolio.positions) {
|
|
11
11
|
if (p.side !== "long") continue;
|
|
@@ -33,10 +33,10 @@ function reconcile(targets, portfolio, prices) {
|
|
|
33
33
|
const delta = targetShares - currentShares;
|
|
34
34
|
seen.add(assetId);
|
|
35
35
|
if (delta !== 0) {
|
|
36
|
-
const asset = held?.asset ?? {
|
|
36
|
+
const asset = held?.asset ?? assets?.get(assetId) ?? {
|
|
37
37
|
kind: "equity",
|
|
38
38
|
id: assetId,
|
|
39
|
-
symbol: assetId
|
|
39
|
+
symbol: assetId
|
|
40
40
|
};
|
|
41
41
|
orders.push({ id: nextId(assetId), kind: "rebalance", asset, delta });
|
|
42
42
|
}
|
|
@@ -925,6 +925,58 @@ async function* mergeIterators(iters) {
|
|
|
925
925
|
}
|
|
926
926
|
}
|
|
927
927
|
|
|
928
|
+
// src/reference/routing-quote-feed.ts
|
|
929
|
+
var RoutingQuoteFeedError = class extends Error {
|
|
930
|
+
constructor(message) {
|
|
931
|
+
super(message);
|
|
932
|
+
this.name = "RoutingQuoteFeedError";
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
var RoutingQuoteFeed = class {
|
|
936
|
+
route;
|
|
937
|
+
constructor(routes) {
|
|
938
|
+
if (typeof routes === "function") {
|
|
939
|
+
this.route = routes;
|
|
940
|
+
} else {
|
|
941
|
+
this.route = (asset) => routes[asset.kind];
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
async quote(asset) {
|
|
945
|
+
return this.resolve(asset).quote(asset);
|
|
946
|
+
}
|
|
947
|
+
async quoteBatch(assets) {
|
|
948
|
+
if (assets.length === 0) return [];
|
|
949
|
+
const groups = /* @__PURE__ */ new Map();
|
|
950
|
+
for (let i = 0; i < assets.length; i++) {
|
|
951
|
+
const asset = assets[i];
|
|
952
|
+
const feed = this.resolve(asset);
|
|
953
|
+
const bucket = groups.get(feed) ?? [];
|
|
954
|
+
bucket.push({ asset, index: i });
|
|
955
|
+
groups.set(feed, bucket);
|
|
956
|
+
}
|
|
957
|
+
const output = new Array(assets.length);
|
|
958
|
+
await Promise.all(
|
|
959
|
+
[...groups.entries()].map(async ([feed, bucket]) => {
|
|
960
|
+
const bucketAssets = bucket.map((b) => b.asset);
|
|
961
|
+
const results = typeof feed.quoteBatch === "function" ? await feed.quoteBatch(bucketAssets) : await Promise.all(bucketAssets.map((a) => feed.quote(a)));
|
|
962
|
+
for (let i = 0; i < bucket.length; i++) {
|
|
963
|
+
output[bucket[i].index] = results[i];
|
|
964
|
+
}
|
|
965
|
+
})
|
|
966
|
+
);
|
|
967
|
+
return output;
|
|
968
|
+
}
|
|
969
|
+
resolve(asset) {
|
|
970
|
+
const feed = this.route(asset);
|
|
971
|
+
if (feed === void 0) {
|
|
972
|
+
throw new RoutingQuoteFeedError(
|
|
973
|
+
`RoutingQuoteFeed: no feed registered for asset.kind="${asset.kind}" (id="${asset.id}")`
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
return feed;
|
|
977
|
+
}
|
|
978
|
+
};
|
|
979
|
+
|
|
928
980
|
// src/reference/polling-stream-from-historical.ts
|
|
929
981
|
function pollingStreamFromHistorical(opts) {
|
|
930
982
|
const now = opts.now ?? (() => /* @__PURE__ */ new Date());
|
|
@@ -2490,6 +2542,7 @@ __export(tactical_exports, {
|
|
|
2490
2542
|
fromSpec: () => fromSpec,
|
|
2491
2543
|
isRebalanceDay: () => isRebalanceDay,
|
|
2492
2544
|
periodKey: () => periodKey,
|
|
2545
|
+
withStreamingSynthetics: () => withStreamingSynthetics,
|
|
2493
2546
|
withSynthetics: () => withSynthetics
|
|
2494
2547
|
});
|
|
2495
2548
|
|
|
@@ -2642,19 +2695,27 @@ async function evaluateFeatureSpecs(specs, runtime, t) {
|
|
|
2642
2695
|
|
|
2643
2696
|
// src/tactical/synthetics.ts
|
|
2644
2697
|
var TRADING_DAYS_PER_YEAR = 252;
|
|
2645
|
-
|
|
2698
|
+
function nextSynthClose(opts) {
|
|
2699
|
+
const { prevSynthClose, prevUnderlyingClose, underlyingClose, leverage, expense } = opts;
|
|
2700
|
+
if (prevSynthClose === void 0 || prevUnderlyingClose === void 0) {
|
|
2701
|
+
return underlyingClose;
|
|
2702
|
+
}
|
|
2646
2703
|
const drag = (expense ?? 0) / TRADING_DAYS_PER_YEAR;
|
|
2704
|
+
const safe = Number.isFinite(prevUnderlyingClose) && prevUnderlyingClose !== 0;
|
|
2705
|
+
const r = safe ? (underlyingClose - prevUnderlyingClose) / prevUnderlyingClose : 0;
|
|
2706
|
+
return prevSynthClose * (1 + leverage * r) * (1 - drag);
|
|
2707
|
+
}
|
|
2708
|
+
async function* synthesize(underlyingBars, leverage, expense) {
|
|
2647
2709
|
let prevUnderlyingClose;
|
|
2648
2710
|
let prevSynthClose;
|
|
2649
2711
|
for await (const u of underlyingBars) {
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
}
|
|
2712
|
+
const close = nextSynthClose({
|
|
2713
|
+
prevSynthClose,
|
|
2714
|
+
prevUnderlyingClose,
|
|
2715
|
+
underlyingClose: u.close,
|
|
2716
|
+
leverage,
|
|
2717
|
+
expense
|
|
2718
|
+
});
|
|
2658
2719
|
yield {
|
|
2659
2720
|
t: u.t,
|
|
2660
2721
|
open: close,
|
|
@@ -2691,6 +2752,83 @@ function withSynthetics(dataFeed, synthetics) {
|
|
|
2691
2752
|
}
|
|
2692
2753
|
return wrapped;
|
|
2693
2754
|
}
|
|
2755
|
+
function withStreamingSynthetics(inner, synthetics, opts) {
|
|
2756
|
+
const synthById = /* @__PURE__ */ new Map();
|
|
2757
|
+
for (const s of synthetics) {
|
|
2758
|
+
if (synthById.has(s.id)) {
|
|
2759
|
+
throw new Error(`withStreamingSynthetics: duplicate synthetic asset id "${s.id}"`);
|
|
2760
|
+
}
|
|
2761
|
+
synthById.set(s.id, s);
|
|
2762
|
+
}
|
|
2763
|
+
return {
|
|
2764
|
+
async *subscribe(assets) {
|
|
2765
|
+
const passthroughIds = /* @__PURE__ */ new Set();
|
|
2766
|
+
const requestedSynths = [];
|
|
2767
|
+
const upstream = [];
|
|
2768
|
+
const upstreamSeen = /* @__PURE__ */ new Set();
|
|
2769
|
+
for (const a of assets) {
|
|
2770
|
+
const synth = synthById.get(a.id);
|
|
2771
|
+
if (synth) {
|
|
2772
|
+
requestedSynths.push(synth);
|
|
2773
|
+
if (!upstreamSeen.has(synth.underlying.id)) {
|
|
2774
|
+
upstreamSeen.add(synth.underlying.id);
|
|
2775
|
+
upstream.push(resolveAssetRef(synth.underlying));
|
|
2776
|
+
}
|
|
2777
|
+
} else {
|
|
2778
|
+
passthroughIds.add(a.id);
|
|
2779
|
+
if (!upstreamSeen.has(a.id)) {
|
|
2780
|
+
upstreamSeen.add(a.id);
|
|
2781
|
+
upstream.push(a);
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
const synthsByUnderlying = /* @__PURE__ */ new Map();
|
|
2786
|
+
for (const s of requestedSynths) {
|
|
2787
|
+
const st = {
|
|
2788
|
+
synth: s,
|
|
2789
|
+
asset: resolveAssetRef({ id: s.id, symbol: s.symbol }),
|
|
2790
|
+
prevUnderlyingClose: opts.seedLastCloses.get(s.underlying.id),
|
|
2791
|
+
prevSynthClose: opts.seedLastCloses.get(s.id)
|
|
2792
|
+
};
|
|
2793
|
+
const list = synthsByUnderlying.get(s.underlying.id) ?? [];
|
|
2794
|
+
list.push(st);
|
|
2795
|
+
synthsByUnderlying.set(s.underlying.id, list);
|
|
2796
|
+
}
|
|
2797
|
+
for await (const tick of inner.subscribe(upstream)) {
|
|
2798
|
+
if (passthroughIds.has(tick.asset.id)) {
|
|
2799
|
+
yield tick;
|
|
2800
|
+
}
|
|
2801
|
+
const states = synthsByUnderlying.get(tick.asset.id);
|
|
2802
|
+
if (!states) continue;
|
|
2803
|
+
const underlyingClose = tick.bar.close;
|
|
2804
|
+
for (const st of states) {
|
|
2805
|
+
const synthClose = nextSynthClose({
|
|
2806
|
+
prevSynthClose: st.prevSynthClose,
|
|
2807
|
+
prevUnderlyingClose: st.prevUnderlyingClose,
|
|
2808
|
+
underlyingClose,
|
|
2809
|
+
leverage: st.synth.leverage,
|
|
2810
|
+
expense: st.synth.expense
|
|
2811
|
+
});
|
|
2812
|
+
yield {
|
|
2813
|
+
asset: st.asset,
|
|
2814
|
+
bar: {
|
|
2815
|
+
t: tick.bar.t,
|
|
2816
|
+
open: synthClose,
|
|
2817
|
+
high: synthClose,
|
|
2818
|
+
low: synthClose,
|
|
2819
|
+
close: synthClose,
|
|
2820
|
+
volume: tick.bar.volume
|
|
2821
|
+
}
|
|
2822
|
+
};
|
|
2823
|
+
st.prevSynthClose = synthClose;
|
|
2824
|
+
}
|
|
2825
|
+
for (const st of states) {
|
|
2826
|
+
st.prevUnderlyingClose = underlyingClose;
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
};
|
|
2831
|
+
}
|
|
2694
2832
|
|
|
2695
2833
|
// src/tactical/from-spec.ts
|
|
2696
2834
|
var _warnedV0 = false;
|
|
@@ -2752,6 +2890,13 @@ function fromSpec(spec, opts) {
|
|
|
2752
2890
|
}
|
|
2753
2891
|
validateSynthetics(spec);
|
|
2754
2892
|
const universe = spec.universe.map(resolveAssetRef);
|
|
2893
|
+
const assetsById = /* @__PURE__ */ new Map();
|
|
2894
|
+
for (const a of universe) assetsById.set(a.id, a);
|
|
2895
|
+
for (const s of spec.synthetics ?? []) {
|
|
2896
|
+
if (!assetsById.has(s.id)) {
|
|
2897
|
+
assetsById.set(s.id, { kind: "equity", id: s.id, symbol: s.symbol });
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2755
2900
|
const { runtime, calendar } = opts;
|
|
2756
2901
|
const cadence = spec.rebalance?.frequency ?? "Daily";
|
|
2757
2902
|
return {
|
|
@@ -2796,7 +2941,7 @@ function fromSpec(spec, opts) {
|
|
|
2796
2941
|
}
|
|
2797
2942
|
}
|
|
2798
2943
|
return {
|
|
2799
|
-
orders: reconcile(evaluated.weights, portfolio, features.prices),
|
|
2944
|
+
orders: reconcile(evaluated.weights, portfolio, features.prices, assetsById),
|
|
2800
2945
|
state: evaluated.state
|
|
2801
2946
|
};
|
|
2802
2947
|
}
|
|
@@ -2830,6 +2975,8 @@ export {
|
|
|
2830
2975
|
NYSEExchangeCalendar,
|
|
2831
2976
|
RoutingDataFeed,
|
|
2832
2977
|
RoutingDataFeedError,
|
|
2978
|
+
RoutingQuoteFeed,
|
|
2979
|
+
RoutingQuoteFeedError,
|
|
2833
2980
|
RoutingStreamingDataFeed,
|
|
2834
2981
|
RoutingStreamingDataFeedError,
|
|
2835
2982
|
applyFills,
|
|
@@ -2858,6 +3005,7 @@ export {
|
|
|
2858
3005
|
sma,
|
|
2859
3006
|
tactical_exports as tactical,
|
|
2860
3007
|
volatility,
|
|
3008
|
+
withStreamingSynthetics,
|
|
2861
3009
|
withSynthetics
|
|
2862
3010
|
};
|
|
2863
3011
|
//# sourceMappingURL=index.js.map
|