@backtest-kit/graph 13.6.0 β†’ 14.1.0

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.
Files changed (2) hide show
  1. package/README.md +148 -200
  2. package/package.json +5 -5
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  <img src="https://github.com/tripolskypetr/backtest-kit/raw/refs/heads/master/assets/assignation.svg" height="45px" align="right">
2
2
 
3
- # πŸ“Š @backtest-kit/graph
3
+ # πŸ•ΈοΈ @backtest-kit/graph
4
4
 
5
- > Compose backtest-kit computations as a typed directed acyclic graph. Define source nodes that fetch market data and output nodes that compute derived values β€” then resolve the whole graph in topological order.
5
+ > Compose [backtest-kit](https://www.npmjs.com/package/backtest-kit) computations as a typed directed acyclic graph. Declare **source nodes** that fetch market data and **output nodes** that derive values from them β€” then resolve the whole graph in topological order, in parallel, fully type-inferred.
6
6
 
7
7
  ![screenshot](https://raw.githubusercontent.com/tripolskypetr/backtest-kit/HEAD/assets/screenshots/screenshot16.png)
8
8
 
@@ -10,13 +10,28 @@
10
10
  [![npm](https://img.shields.io/npm/v/@backtest-kit/graph.svg?style=flat-square)](https://npmjs.org/package/@backtest-kit/graph)
11
11
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue)]()
12
12
 
13
- πŸ“š **[Backtest Kit Docs](https://backtest-kit.github.io/documents/article_07_ai_news_trading_signals.html)** | 🌟 **[GitHub](https://github.com/tripolskypetr/backtest-kit)**
13
+ πŸ“š **[Docs](https://backtest-kit.github.io/documents/article_07_ai_news_trading_signals.html)** Β· 🌟 **[Reference implementation](https://github.com/tripolskypetr/backtest-kit/tree/master/example)** Β· πŸ™ **[GitHub](https://github.com/tripolskypetr/backtest-kit)**
14
14
 
15
- > **New to backtest-kit?** The fastest way to get a real, production-ready setup is to clone the [reference implementation](https://github.com/tripolskypetr/backtest-kit/tree/master/example) β€” a fully working news-sentiment AI trading system with LLM forecasting, multi-timeframe data, and a documented February 2026 backtest. Start there instead of from scratch.
15
+ ```bash
16
+ npm install @backtest-kit/graph backtest-kit
17
+ ```
18
+
19
+ ---
20
+
21
+ ## What it's for
22
+
23
+ A trading signal is rarely one number β€” it's a small computation: pull 4h trend, pull 15m entry, combine them, maybe gate on RSI. Written inline, that becomes a tangle of `await`s with hand-managed ordering. This package lets you declare it as a **graph of typed nodes**: leaves (`sourceNode`) fetch data, branches (`outputNode`) compute from their children, and `resolve()` walks the tree bottom-up β€” resolving every node's dependencies **in parallel** (`Promise.all`) before computing the node itself. Swapping a timeframe or adding a filter node needs no change to the strategy wiring, and TypeScript infers the value type through the entire graph.
16
24
 
17
- ## πŸ”₯ Multi-timeframe Pine Script strategy
25
+ It plugs straight into a `getSignal` and runs inside backtest-kit's execution context, so a `sourceNode`'s `fetch` automatically receives `(symbol, when, currentPrice, exchangeName)` β€” the same look-ahead-safe "now" the rest of the engine sees.
18
26
 
19
- The graph below replicates a two-timeframe strategy: a 4h Pine Script acts as a trend filter, a 15m Pine Script generates the entry signal. `outputNode` combines them and returns `null` when the trend disagrees.
27
+ ---
28
+
29
+ ## Multi-timeframe Pine strategy (the canonical example)
30
+
31
+ A two-timeframe strategy: a **4h Pine Script** acts as a trend filter, a **15m Pine Script** generates the entry. The output node combines them and returns `null` whenever the trend disagrees with the entry β€” so a long signal is suppressed in a downtrend and vice-versa.
32
+
33
+ <details>
34
+ <summary>The Code</summary>
20
35
 
21
36
  ```typescript
22
37
  import { extract, run, toSignalDto, File } from '@backtest-kit/pinets';
@@ -25,281 +40,214 @@ import { randomString } from 'functools-kit';
25
40
  import { sourceNode, outputNode, resolve } from '@backtest-kit/graph';
26
41
 
27
42
  // SourceNode β€” 4h trend filter, cached per candle interval
28
- const higherTimeframe = sourceNode(
29
- Cache.fn(
30
- async (symbol) => {
31
- const plots = await run(File.fromPath('timeframe_4h.pine'), {
32
- symbol,
33
- timeframe: '4h',
34
- limit: 100,
35
- });
36
- return extract(plots, {
37
- allowLong: 'AllowLong',
38
- allowShort: 'AllowShort',
39
- noTrades: 'NoTrades',
40
- });
41
- },
42
- { interval: '4h', key: ([symbol]) => symbol },
43
- ),
44
- );
43
+ const higherTimeframe = sourceNode(Cache.fn(async (symbol) => {
44
+ const plots = await run(File.fromPath('timeframe_4h.pine'), { symbol, timeframe: '4h', limit: 100 });
45
+ return extract(plots, { allowLong: 'AllowLong', allowShort: 'AllowShort', noTrades: 'NoTrades' });
46
+ }, { interval: '4h', key: ([symbol]) => symbol }));
45
47
 
46
48
  // SourceNode β€” 15m entry signal, cached per candle interval
47
- const lowerTimeframe = sourceNode(
48
- Cache.fn(
49
- async (symbol) => {
50
- const plots = await run(File.fromPath('timeframe_15m.pine'), {
51
- symbol,
52
- timeframe: '15m',
53
- limit: 100,
54
- });
55
- return extract(plots, {
56
- position: 'Signal',
57
- priceOpen: 'Close',
58
- priceTakeProfit: 'TakeProfit',
59
- priceStopLoss: 'StopLoss',
60
- minuteEstimatedTime: 'EstimatedTime',
61
- });
62
- },
63
- { interval: '15m', key: ([symbol]) => symbol },
64
- ),
65
- );
66
-
67
- // OutputNode β€” applies MTF filter, returns ISignalDto or null
68
- const mtfSignal = outputNode(
69
- async ([higher, lower]) => {
70
- if (higher.noTrades) return null;
71
- if (lower.position === 0) return null;
72
- if (higher.allowShort && lower.position === 1) return null;
73
- if (higher.allowLong && lower.position === -1) return null;
74
-
75
- return toSignalDto(randomString(), lower, null);
76
- },
77
- higherTimeframe,
78
- lowerTimeframe,
79
- );
49
+ const lowerTimeframe = sourceNode(Cache.fn(async (symbol) => {
50
+ const plots = await run(File.fromPath('timeframe_15m.pine'), { symbol, timeframe: '15m', limit: 100 });
51
+ return extract(plots, { position: 'Signal', priceOpen: 'Close', priceTakeProfit: 'TakeProfit', priceStopLoss: 'StopLoss', minuteEstimatedTime: 'EstimatedTime' });
52
+ }, { interval: '15m', key: ([symbol]) => symbol }));
53
+
54
+ // OutputNode β€” applies the MTF filter, returns ISignalDto or null
55
+ const mtfSignal = outputNode(async ([higher, lower]) => {
56
+ if (higher.noTrades) return null;
57
+ if (lower.position === 0) return null;
58
+ if (higher.allowShort && lower.position === 1) return null; // long blocked in downtrend
59
+ if (higher.allowLong && lower.position === -1) return null; // short blocked in uptrend
60
+ return toSignalDto(randomString(), lower, null);
61
+ }, higherTimeframe, lowerTimeframe);
80
62
 
81
63
  addStrategySchema({
82
- strategyName: 'mtf_graph_strategy',
83
- interval: '5m',
84
- getSignal: (symbol) => resolve(mtfSignal),
85
- actions: ['partial_profit_action', 'breakeven_action'],
64
+ strategyName: 'mtf_graph_strategy', interval: '5m',
65
+ getSignal: (symbol) => resolve(mtfSignal),
66
+ actions: ['partial_profit_action', 'breakeven_action'],
86
67
  });
87
68
  ```
88
69
 
89
- The graph resolves both Pine Script nodes **in parallel** via `Promise.all`, then passes their typed results to `compute`. Replacing either timeframe script or adding a third filter node requires no changes to the strategy wiring.
70
+ Both Pine nodes resolve **in parallel**; their typed results flow into `compute`. Replacing either script β€” or adding a third filter node β€” requires no change to the strategy registration.
90
71
 
72
+ </details>
91
73
 
92
- ## πŸš€ Installation
74
+ ---
93
75
 
94
- ```bash
95
- npm install @backtest-kit/graph backtest-kit
96
- ```
76
+ ## The model
97
77
 
98
- ## ✨ Features
78
+ Two layers, by design: a **low-level runtime interface** (`INode`) that serializes to a DB and reconstructs without builders, and a **high-level authoring API** (`TypedNode` + `sourceNode`/`outputNode`) that gives full type inference. You write with the builders; the runtime and storage use the interface.
99
79
 
100
- - πŸ“Š **DAG execution**: Nodes are resolved bottom-up in topological order with `Promise.all` parallelism
101
- - πŸ”’ **Type-safe values**: TypeScript infers the return type of every node through the graph via generics
102
- - 🧱 **Two APIs**: Low-level `INode` for runtime/storage, high-level `TypedNode` + builders for authoring
103
- - πŸ’Ύ **DB-ready serialization**: `serialize` / `deserialize` convert the graph to a flat `IFlatNode[]` list with `id` / `nodeIds`
104
- - πŸ”Œ **Context-aware fetch**: `SourceNode.fetch` receives `(symbol, when, currentPrice, exchangeName)` from the execution context automatically
80
+ - πŸ“Š **DAG execution** β€” nodes resolve bottom-up in topological order with `Promise.all` parallelism.
81
+ - πŸ”’ **Type-safe values** β€” TypeScript infers each node's return type through the graph via generics (`OutputNode<[SourceNode<number>, SourceNode<string>], …>`).
82
+ - πŸ’Ύ **DB-ready** β€” `serialize`/`deserialize` convert the graph to a flat `IFlatNode[]` list with `id`/`nodeIds`.
83
+ - πŸ”Œ **Context-aware fetch** β€” `SourceNode.fetch` receives `(symbol, when, currentPrice, exchangeName)` from the execution context automatically.
105
84
 
106
- ## πŸ“– Usage
85
+ ---
107
86
 
108
- ### Quick Start β€” builder API
87
+ ## Authoring API
109
88
 
110
- Use `sourceNode` and `outputNode` to define a typed computation graph. TypeScript infers the type of `values` in `compute` from the `nodes` passed to `outputNode`:
89
+ <details>
90
+ <summary>Builder API β€” sourceNode / outputNode / resolve</summary>
91
+
92
+ `outputNode` infers the type of `values` in `compute` from the nodes you pass:
111
93
 
112
94
  ```typescript
113
95
  import { sourceNode, outputNode, resolve } from '@backtest-kit/graph';
114
96
 
115
- // SourceNode<number> β€” fetch receives symbol, when, currentPrice, exchangeName from context
116
97
  const closePrice = sourceNode(async (symbol, when, currentPrice, exchangeName) => {
117
- const candles = await getCandles(symbol, '1h', 1, exchangeName);
118
- return candles[0].close; // number
98
+ const candles = await getCandles(symbol, '1h', 1, exchangeName);
99
+ return candles[0].close; // SourceNode<number>
119
100
  });
120
-
121
- // SourceNode<number>
122
101
  const volume = sourceNode(async (symbol, when, currentPrice, exchangeName) => {
123
- const candles = await getCandles(symbol, '1h', 1, exchangeName);
124
- return candles[0].volume; // number
102
+ const candles = await getCandles(symbol, '1h', 1, exchangeName);
103
+ return candles[0].volume; // SourceNode<number>
125
104
  });
126
105
 
127
- // OutputNode<[SourceNode<number>, SourceNode<number>], number>
128
- // price and vol are automatically number
129
- const vwap = outputNode(
130
- ([price, vol]) => price * vol,
131
- closePrice,
132
- volume,
133
- );
134
-
135
- // Resolve inside a backtest-kit strategy
136
- const result = await resolve(vwap); // Promise<number>
106
+ const vwap = outputNode(([price, vol]) => price * vol, closePrice, volume); // price, vol: number
107
+ const result = await resolve(vwap); // Promise<number>, inside a strategy
137
108
  ```
138
109
 
139
- ### Inline anonymous composition
110
+ </details>
140
111
 
141
- The entire graph can be defined as a single object literal.
112
+ <details>
113
+ <summary>Mixed types β€” heterogeneous inference by position</summary>
142
114
 
143
115
  ```typescript
144
- import { NodeType } from '@backtest-kit/graph';
145
- import { TypedNode, resolve } from '@backtest-kit/graph';
146
-
147
- const signal: TypedNode = {
148
- type: NodeType.OutputNode,
149
- nodes: [
150
- {
151
- type: NodeType.SourceNode,
152
- fetch: async (symbol, when, currentPrice, exchangeName) => {
153
- const plots = await run(File.fromPath('timeframe_4h.pine'), { symbol, timeframe: '4h', limit: 100 });
154
- return extract(plots, { allowLong: 'AllowLong', allowShort: 'AllowShort', noTrades: 'NoTrades' });
155
- },
156
- },
157
- {
158
- type: NodeType.SourceNode,
159
- fetch: async (symbol, when, currentPrice, exchangeName) => {
160
- const plots = await run(File.fromPath('timeframe_15m.pine'), { symbol, timeframe: '15m', limit: 100 });
161
- return extract(plots, { position: 'Signal', priceOpen: 'Close', priceTakeProfit: 'TakeProfit', priceStopLoss: 'StopLoss' });
162
- },
163
- },
164
- ],
165
- compute: ([higher, lower]) => {
166
- if (higher.noTrades || lower.position === 0) return null;
167
- if (higher.allowShort && lower.position === 1) return null;
168
- if (higher.allowLong && lower.position === -1) return null;
169
- return lower.position;
170
- },
171
- };
116
+ const price = sourceNode(async (symbol) => 42); // SourceNode<number>
117
+ const name = sourceNode(async (symbol) => 'BTCUSDT'); // SourceNode<string>
118
+ const flag = sourceNode(async (symbol) => true); // SourceNode<boolean>
172
119
 
173
- const result = await resolve(signal);
120
+ const result = outputNode(
121
+ ([p, n, f]) => `${n}: ${p} (active: ${f})`, // p: number, n: string, f: boolean
122
+ price, name, flag,
123
+ ); // OutputNode<[SourceNode<number>, SourceNode<string>, SourceNode<boolean>], string>
174
124
  ```
175
125
 
176
- ### Mixed types
126
+ </details>
177
127
 
178
- TypeScript correctly infers heterogeneous types by position in `nodes`:
128
+ <details>
129
+ <summary>Inline anonymous composition β€” a single object literal</summary>
179
130
 
180
131
  ```typescript
181
- const price = sourceNode(async (symbol) => 42); // SourceNode<number>
182
- const name = sourceNode(async (symbol) => 'BTCUSDT'); // SourceNode<string>
183
- const flag = sourceNode(async (symbol) => true); // SourceNode<boolean>
132
+ import { NodeType, TypedNode, resolve } from '@backtest-kit/graph';
184
133
 
185
- const result = outputNode(
186
- ([p, n, f]) => `${n}: ${p} (active: ${f})`, // p: number, n: string, f: boolean
187
- price,
188
- name,
189
- flag,
190
- );
191
- // OutputNode<[SourceNode<number>, SourceNode<string>, SourceNode<boolean>], string>
134
+ const signal: TypedNode = {
135
+ type: NodeType.OutputNode,
136
+ nodes: [
137
+ { type: NodeType.SourceNode, fetch: async (symbol) => extract(await run(File.fromPath('timeframe_4h.pine'), { symbol, timeframe: '4h', limit: 100 }), { allowLong: 'AllowLong', allowShort: 'AllowShort', noTrades: 'NoTrades' }) },
138
+ { type: NodeType.SourceNode, fetch: async (symbol) => extract(await run(File.fromPath('timeframe_15m.pine'), { symbol, timeframe: '15m', limit: 100 }), { position: 'Signal', priceOpen: 'Close', priceTakeProfit: 'TakeProfit', priceStopLoss: 'StopLoss' }) },
139
+ ],
140
+ compute: ([higher, lower]) => {
141
+ if (higher.noTrades || lower.position === 0) return null;
142
+ if (higher.allowShort && lower.position === 1) return null;
143
+ if (higher.allowLong && lower.position === -1) return null;
144
+ return lower.position;
145
+ },
146
+ };
147
+ const result = await resolve(signal);
192
148
  ```
193
149
 
194
- ### Using inside a backtest-kit strategy
150
+ </details>
151
+
152
+ <details>
153
+ <summary>Inside a backtest-kit strategy</summary>
195
154
 
196
155
  ```typescript
197
156
  import { addStrategy } from 'backtest-kit';
198
157
  import { sourceNode, outputNode, resolve } from '@backtest-kit/graph';
199
158
 
200
- const rsi = sourceNode(async (symbol, when, currentPrice, exchangeName) => {
201
- // ... compute RSI
202
- return 55.2;
203
- });
204
-
205
- const signal = outputNode(
206
- ([rsiValue]) => rsiValue < 30 ? 1 : rsiValue > 70 ? -1 : 0,
207
- rsi,
208
- );
159
+ const rsi = sourceNode(async (symbol, when, currentPrice, exchangeName) => 55.2 /* compute RSI */);
160
+ const signal = outputNode(([rsiValue]) => rsiValue < 30 ? 1 : rsiValue > 70 ? -1 : 0, rsi);
209
161
 
210
162
  addStrategy({
211
- strategyName: 'graph-rsi',
212
- interval: '1h',
213
- riskName: 'demo',
214
- getSignal: async (symbol) => {
215
- const direction = await resolve(signal); // 1 | -1 | 0
216
- return direction === 1
217
- ? { position: 'long', ... }
218
- : null;
219
- },
163
+ strategyName: 'graph-rsi', interval: '1h', riskName: 'demo',
164
+ getSignal: async (symbol) => {
165
+ const direction = await resolve(signal); // 1 | -1 | 0
166
+ return direction === 1 ? { position: 'long', /* … */ } : null;
167
+ },
220
168
  });
221
169
  ```
222
170
 
223
- ### Low-level INode
171
+ </details>
172
+
173
+ ---
224
174
 
225
- For manual graph construction without builders (e.g. after deserialization or in a DI container):
175
+ ## Runtime interface & storage
176
+
177
+ <details>
178
+ <summary>Low-level INode β€” manual construction (post-deserialize / DI)</summary>
226
179
 
227
180
  ```typescript
228
181
  import { INode, Value } from '@backtest-kit/graph';
229
182
  import NodeType from '@backtest-kit/graph/enum/NodeType';
230
183
 
231
- const priceNode: INode = {
232
- type: NodeType.SourceNode,
233
- description: 'Close price',
234
- fetch: async (symbol, when, currentPrice, exchangeName) => 42,
235
- };
236
-
237
- const outputNode: INode = {
238
- type: NodeType.OutputNode,
239
- description: 'Doubled price',
240
- nodes: [priceNode],
241
- compute: ([price]) => (price as number) * 2,
242
- };
184
+ const priceNode: INode = { type: NodeType.SourceNode, description: 'Close price', fetch: async (symbol, when, currentPrice, exchangeName) => 42 };
185
+ const doubled: INode = { type: NodeType.OutputNode, description: 'Doubled price', nodes: [priceNode], compute: ([price]) => (price as number) * 2 };
243
186
  ```
244
187
 
245
- > `INode` has no generic parameters β€” `values` in `compute` is typed as `Value[]`. Use `TypedNode` and builders for full IntelliSense.
188
+ `INode` has no generic parameters β€” `values` in `compute` is typed as `Value[]` (`string | number | boolean | null`). Use `TypedNode` + builders for full IntelliSense.
189
+
190
+ </details>
246
191
 
247
- ### DB serialization
192
+ <details>
193
+ <summary>DB serialization β€” serialize / deserialize</summary>
248
194
 
249
- `serialize` flattens the graph into an `IFlatNode[]` array, replacing object references in `nodes` with `nodeIds`. `deserialize` reconstructs the tree:
195
+ `serialize` flattens the graph into `IFlatNode[]`, replacing object references in `nodes` with `nodeIds`; `deserialize` rebuilds the tree:
250
196
 
251
197
  ```typescript
252
198
  import { serialize, deserialize, IFlatNode } from '@backtest-kit/graph';
253
199
 
254
- // Graph β†’ flat array for DB
255
200
  const flat: IFlatNode[] = serialize([vwap]);
256
- // [
257
- // { id: 'abc', type: 'source_node', nodeIds: [] }, // closePrice
258
- // { id: 'def', type: 'source_node', nodeIds: [] }, // volume
259
- // { id: 'ghi', type: 'output_node', nodeIds: ['abc', 'def'] }, // vwap
260
- // ]
261
-
262
- // Save to DB
201
+ // [ { id:'abc', type:'source_node', nodeIds:[] }, // closePrice
202
+ // { id:'def', type:'source_node', nodeIds:[] }, // volume
203
+ // { id:'ghi', type:'output_node', nodeIds:['abc','def'] } ] // vwap
263
204
  await db.collection('nodes').insertMany(flat);
264
205
 
265
- // Load from DB and reconstruct the graph
266
- const stored: IFlatNode[] = await db.collection('nodes').find().toArray();
267
- const roots: INode[] = deserialize(stored); // nodes[] is wired up from nodeIds
206
+ const stored = await db.collection('nodes').find().toArray();
207
+ const roots: INode[] = deserialize(stored); // nodes[] re-wired from nodeIds
268
208
  ```
269
209
 
270
- > `fetch` and `compute` are not stored in the DB β€” they must be restored on the application side after `deserialize`.
210
+ `fetch` and `compute` are **not** stored β€” restore them on the application side after `deserialize`.
271
211
 
272
- ### deepFlat β€” traversal utility
212
+ </details>
273
213
 
274
- `deepFlat` returns all nodes in topological order (dependencies before parents), deduplicated by reference:
214
+ <details>
215
+ <summary>deepFlat β€” topological traversal</summary>
216
+
217
+ Returns all nodes in topological order (dependencies before parents), deduplicated by reference:
275
218
 
276
219
  ```typescript
277
220
  import { deepFlat } from '@backtest-kit/graph';
278
-
279
- const all = deepFlat([vwap]);
280
- // [closePrice, volume, vwap] β€” dependencies first
281
-
221
+ const all = deepFlat([vwap]); // [closePrice, volume, vwap]
282
222
  all.forEach(node => console.log(node.description));
283
223
  ```
284
224
 
285
- ## πŸ“‹ API Reference
225
+ </details>
226
+
227
+ ---
228
+
229
+ ## API reference
286
230
 
287
231
  | Export | Description |
288
232
  |--------|-------------|
289
- | **`sourceNode(fetch)`** | Builder β€” creates a typed source node |
290
- | **`outputNode(compute, ...nodes)`** | Builder β€” creates a typed output node, infers `values` types from `nodes` |
291
- | **`resolve(node)`** | Recursively resolves a node graph within backtest-kit execution context |
292
- | **`serialize(roots)`** | Flattens a node tree into `IFlatNode[]` for DB storage |
293
- | **`deserialize(flat)`** | Reconstructs a node tree from `IFlatNode[]`, returns root nodes |
294
- | **`deepFlat(nodes)`** | Utility β€” returns all nodes in topological order (dependencies first) |
295
- | **`INode`** | Base runtime interface (untyped, used internally and for serialization) |
296
- | **`TypedNode`** | Discriminated union for authoring with full IntelliSense |
297
- | **`IFlatNode`** | Serialized node shape for DB storage |
298
- | **`Value`** | `string \| number \| boolean \| null` |
233
+ | `sourceNode(fetch)` | Builder β€” typed source (leaf) node; `fetch(symbol, when, currentPrice, exchangeName)` |
234
+ | `outputNode(compute, ...nodes)` | Builder β€” typed output node; infers `values` types from `nodes` |
235
+ | `resolve(node)` | Recursively resolves a graph within backtest-kit execution context |
236
+ | `serialize(roots)` | Flattens a node tree into `IFlatNode[]` for DB storage |
237
+ | `deserialize(flat)` | Reconstructs a node tree from `IFlatNode[]`, returns root nodes |
238
+ | `deepFlat(nodes)` | Returns all nodes in topological order (dependencies first) |
239
+ | `INode` | Base runtime interface (untyped; used internally and for serialization) |
240
+ | `TypedNode` | Discriminated union for authoring with full IntelliSense |
241
+ | `IFlatNode` | Serialized node shape for DB storage (`id`, `type`, `nodeIds`) |
242
+ | `NodeType` | Enum β€” `SourceNode` (`source_node`) / `OutputNode` (`output_node`) |
243
+ | `Value` | `string \| number \| boolean \| null` |
244
+ | `ExchangeName` | Exchange-name type alias passed to `fetch` |
245
+
246
+ > **Complete source map.** `enum/NodeType.ts` Β· `helpers/{node,resolve,serialize,deepFlat}.ts` Β· `interfaces/{Node,TypedNode,FlatNode}.interface.ts` Β· `model/ExchangeName.model.ts` Β· `index.ts` β€” every export above is one of these files; nothing in `src/` is undocumented.
299
247
 
300
248
  ## 🀝 Contribute
301
249
 
302
- Fork/PR on [GitHub](https://github.com/tripolskypetr/backtest-kit).
250
+ Fork / PR on [GitHub](https://github.com/tripolskypetr/backtest-kit).
303
251
 
304
252
  ## πŸ“œ License
305
253
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backtest-kit/graph",
3
- "version": "13.6.0",
3
+ "version": "14.1.0",
4
4
  "description": "Compose backtest-kit computations as a typed directed acyclic graph. Define source nodes that fetch market data and output nodes that compute derived values β€” then resolve the whole graph in topological order.",
5
5
  "author": {
6
6
  "name": "Petr Tripolsky",
@@ -67,17 +67,17 @@
67
67
  "ts-morph": "27.0.2",
68
68
  "tslib": "2.7.0",
69
69
  "typedoc": "0.27.9",
70
- "backtest-kit": "13.6.0",
71
- "worker-testbed": "2.0.0"
70
+ "backtest-kit": "14.1.0",
71
+ "worker-testbed": "2.1.0"
72
72
  },
73
73
  "peerDependencies": {
74
- "backtest-kit": "^13.6.0",
74
+ "backtest-kit": "^14.1.0",
75
75
  "typescript": "^5.0.0"
76
76
  },
77
77
  "dependencies": {
78
78
  "di-kit": "^1.1.1",
79
79
  "di-scoped": "^1.0.21",
80
- "functools-kit": "^2.3.0",
80
+ "functools-kit": "^3.0.0",
81
81
  "get-moment-stamp": "^2.0.0"
82
82
  },
83
83
  "publishConfig": {