ohlcv-indicators 3.5.78 → 3.5.80
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/AGENTS.md +113 -0
- package/README.md +456 -0
- package/dist/ohlcv-indicators.min.js +2 -1
- package/dist/ohlcv-indicators.min.js.LICENSE.txt +22 -0
- package/index.js +112 -84
- package/package.json +2 -5
- package/src/core-functions/initializeColumns.js +40 -0
- package/src/core-functions/mainLoop.js +35 -30
- package/src/core-functions/pushToMain.js +8 -27
- package/src/core-indicators/README.md +60 -0
- package/src/core-indicators/composites.js +136 -0
- package/src/core-indicators/index.js +5 -0
- package/src/core-indicators/indicator.js +81 -0
- package/src/core-indicators/movingAverages.js +88 -0
- package/src/core-indicators/rsi.js +41 -0
- package/src/core-indicators/volatility.js +107 -0
- package/src/moving-averages/bollingerBands.js +39 -31
- package/src/moving-averages/donchianChannel.js +62 -36
- package/src/moving-averages/heikenAshi.js +33 -52
- package/src/moving-averages/macd.js +10 -26
- package/src/moving-averages/movingAverages.js +19 -25
- package/src/moving-averages/relativeVolume.js +30 -24
- package/src/oscillators/mfi.js +56 -0
- package/src/oscillators/rsi.js +7 -26
- package/src/oscillators/stochastic.js +6 -19
- package/src/oscillators/volumeDelta.js +36 -28
- package/src/oscillators/volumeOscillator.js +24 -32
- package/src/studies/candleFeatures.js +40 -28
- package/src/studies/dateTime.js +54 -56
- package/src/studies/findCrosses.js +18 -16
- package/src/studies/lag.js +32 -20
- package/src/utilities/assignTypes.js +1 -1
- package/src/utilities/config.js +31 -0
- package/src/utilities/numberUtilities.js +5 -3
- package/src/utilities/precisionMultiplier.js +11 -6
- package/src/utilities/validators.js +40 -1
- package/src/utilities/verticalToHorizontal.js +26 -16
- package/src/volatility/adx.js +7 -20
- package/src/volatility/atr.js +10 -23
- package/test/test.js +1230 -0
- package/webpack.config.js +0 -11
- package/src/moving-averages/vidya.js +0 -181
- package/src/studies/mapCols.js +0 -46
package/AGENTS.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# OHLCV Indicators Agent Guide
|
|
2
|
+
|
|
3
|
+
## Project
|
|
4
|
+
|
|
5
|
+
`ohlcv-indicators` is an ESM JavaScript library for computing stateful indicators over OHLCV rows. The public API is chainable and row-oriented, while the runtime stores data column-wise in typed arrays for backtesting and feature generation.
|
|
6
|
+
|
|
7
|
+
Input rows normally contain `open`, `high`, `low`, `close`, and `volume`; `date` is optional. Most studies require the full OHLC set even though the constructor currently enforces only `close`.
|
|
8
|
+
|
|
9
|
+
## Core design
|
|
10
|
+
|
|
11
|
+
1. `index.js` owns the `OHLCV_INDICATORS` class. The constructor accepts `{ input, ticker?, chunkProcess?, config? }`. Public indicator methods validate arguments and register immutable `{ key, order?, params }` declarations through `_registerIndicator()`; they do not calculate results immediately.
|
|
12
|
+
2. `compute()`, `getData()`, or `getLastValues()` starts the one-shot calculation. Do not allow indicators to be registered after computation.
|
|
13
|
+
3. `compute()` copies declarations into `main.executionParams` and sorts only that runtime queue. `src/core-functions/mainLoop.js` creates input columns in `main.verticalOhlcv`, walks rows in chronological order, writes the raw row and `mid_price`, and invokes every queued handler from `mainFunctions`.
|
|
14
|
+
4. Indicator handlers initialize state and output columns when `index === 0`, keep rolling state in `main.instances`, update once per row, and write through `main.pushToMain()`.
|
|
15
|
+
5. Output columns start as `NaN` during warm-up or after an unexpected value. `areKeyValuesValid()` determines the last invalid row recorded in `main.invalidValueIndex`; `getData({ skipNull: true })` starts after that row. Cache validation column references only after all row-zero handlers and generated lags finish.
|
|
16
|
+
6. `verticalToHorizontal()` converts the column store back to row objects. Temporary columns in `verticalOhlcvTempCols` are not returned.
|
|
17
|
+
|
|
18
|
+
### Indicator contract
|
|
19
|
+
|
|
20
|
+
- Add or change the public method in `index.js`, then import and register its runtime handler in `mainFunctions` in `src/core-functions/mainLoop.js`.
|
|
21
|
+
- At `index === 0`, validate required source columns, create a unique instance key, and declare outputs through `initializeColumns(main, columns, { lag })`. Each declaration has a `key` and optional `type`, `fill`, `enabled`, `priceBased`, and `includeInLag`; numeric outputs default to `Float64Array` filled with `NaN`.
|
|
22
|
+
- Preserve `NaN` for warm-up and invalid output. Never substitute zero for unavailable numeric data.
|
|
23
|
+
- Update each stateful instance exactly once per row. Reject duplicate configurations when they would share an instance or output key.
|
|
24
|
+
- Let `initializeColumns` append generated lag jobs only to `main.executionParams` after allocation, using only enabled outputs. Never call the public `main.lag()` during computation. Set `includeInLag: false` for explicit exclusions. Numeric lag columns use `Float64Array` to preserve missing values as `NaN`; object/one-hot lag columns use `Array` with `null`.
|
|
25
|
+
- Add an output to `main.priceBased` only when it remains in scaled price units. Ratios, percentages, return logs, counters, dates, volume, and one-hot vectors are not price-based.
|
|
26
|
+
- `retLogs` outputs use natural log ratios through `mathLog(a, b)` where applicable. Logged values are dimensionless.
|
|
27
|
+
- Keep output names deterministic. When multiple configurations are supported, include enough parameters and target information to prevent collisions.
|
|
28
|
+
- `src/core-indicators/` supplies numeric EMA, SMA, WSMA, RSI, MACD, Bollinger Bands, stochastic, ATR, and ADX implementations without an external indicator dependency. Use cheap `isStable` checks before `getResult()`; do not use exceptions for routine warm-up. Core tests use known results and fixed behavior traces, not a third-party test oracle.
|
|
29
|
+
- Preserve reference arithmetic order, warm-up, zero/NaN behavior, and update/replacement sequencing in core classes. SMA/Bollinger use bounded chronological rings rather than rolling sums that change rounding. RSI history and MACD counters must stay bounded; WSMA releases its seed window. Runtime state must never enter exported config.
|
|
30
|
+
- Cross detection retains only state needed for the next update, not arrays of historical cross indexes. Preserve output counters and clipping behavior.
|
|
31
|
+
- Allocation optimizations must preserve handler evaluation/write order, optional-target all-or-nothing validation, and independent one-hot vectors. Never share mutable result vectors between unrelated rows.
|
|
32
|
+
|
|
33
|
+
## Configuration export and replay guards
|
|
34
|
+
|
|
35
|
+
- `config` contains `schemaVersion` (currently `1`), `precision`, `useFullNames`, `inputParams`, `dateFormat`, `skipNull`, and `timeZone`.
|
|
36
|
+
- Omitting `config.inputParams` (or setting it to `null`) creates a chainable instance. Supplying an array, including `[]`, imports independent copies and computes immediately.
|
|
37
|
+
- `exportConfig()` returns a new JSON-safe snapshot. Export before and after `compute()`, `getData()`, or `getLastValues()` must produce equivalent configuration. Never include generated lag jobs, indicator instances, buffers, or cached column references in that snapshot.
|
|
38
|
+
- `this.inputParams` is persistent configuration, not a work queue. Keep declarations deeply copied and frozen. Register public methods through `_registerIndicator()`; never sort, push into, or edit existing declarations directly. Runtime caching and parameter mutation belong only in `executionParams` or `instances`.
|
|
39
|
+
- Sorting and generated jobs must affect only `executionParams`. Import must never mutate the caller's config, even when the same saved object is replayed repeatedly. Exports must not share nested arrays or objects with the instance or the imported config.
|
|
40
|
+
- Every public indicator method must reject registration during or after computation. Preserve the `isComputing` reentrancy guard and the `isComputed` guard, including for `stochastic()`.
|
|
41
|
+
- All indicator parameters must be JSON-safe. Reject functions, undefined, non-finite numbers, circular references, and non-JSON objects instead of silently allowing JSON to omit or replace them with null. Reject unsupported config versions and unknown indicator methods.
|
|
42
|
+
- `dateFormat` and `skipNull` in config are output defaults. Per-call getter overrides must not modify exported settings. `timeZone` is stored explicitly (defaults to the resolved runtime timezone) and controls `dateTime` calendar features through a per-instance formatter.
|
|
43
|
+
- Cross-runtime replay requires identical inputs and library versions. Use unambiguous input dates (valid Date objects, supported timestamps, or ISO strings with `Z`/offsets). Timezone-less date strings and local `dateFormat: 'string'/'toString'` output still depend on runtime date parsing/formatting; prefer `iso`, `milliseconds`, or `seconds` for portable output.
|
|
44
|
+
- Verify JSON export/import both before and after computation, repeated reuse of one frozen saved config, nested-copy isolation, generated and explicit lags, constructor/output defaults, and calendar features across runtime timezones.
|
|
45
|
+
|
|
46
|
+
## Indicator files
|
|
47
|
+
|
|
48
|
+
### Moving averages and channels
|
|
49
|
+
|
|
50
|
+
- `src/moving-averages/movingAverages.js` — shared runtime for public `ema()` and `sma()`, optionally returning `ln(currentTarget / currentAverage)`. Raw/log variants have independent keys/state; only the selected output and its lags are allocated. Logged outputs are not price-based. Missing/nonpositive operands or non-finite/nonpositive ratios remain `NaN`; raw arithmetic and warm-up are unchanged.
|
|
51
|
+
- `src/moving-averages/bollingerBands.js` — Bollinger upper, middle, and lower bands, or only dimensionless logarithmic width and position with `retLogs: true`.
|
|
52
|
+
- `src/moving-averages/donchianChannel.js` — rolling Donchian upper, basis, and lower channels with optional offset, or only logarithmic width and position. Offset shifts bounds, never the current close used for position.
|
|
53
|
+
- `src/moving-averages/heikenAshi.js` — optional pre/post-smoothed Heiken-Ashi structure returns and trend counter.
|
|
54
|
+
- `src/moving-averages/macd.js` — MACD difference, signal/DEA, and histogram for a selectable target.
|
|
55
|
+
- `src/moving-averages/relativeVolume.js` — current volume divided by the previous completed volume-SMA window, optionally its natural log. Zero-volume rows still skip state updates. Raw/log variants have independent keys/state and selected-output lags; neither is price-based. Old configs omitting `retLogs` retain raw behavior, as do EMA/SMA configs.
|
|
56
|
+
|
|
57
|
+
#### Channel log-feature guards
|
|
58
|
+
|
|
59
|
+
- Bollinger/Donchian `retLogs` defaults to `false`; omitted options in old configs preserve raw outputs, arithmetic, and warm-up. Raw/log variants at identical periods have independent runtime state and may coexist. Preserve selected mode in config export/replay.
|
|
60
|
+
- Log mode emits only `ret_log_bollinger_bands_width/position` or `ret_log_donchian_channel_width/position`. Append existing parameter suffixes when `useFullNames` is enabled or total registrations of the same method exceed one, counting both raw and logged modes. Generate lags only for the selected outputs; logged columns and lags must never be price-based.
|
|
61
|
+
- For finite positive ordered bounds, use `logRange = ln(upper / lower)`, `width = logRange / 2`, and `position = 2 * ln(currentClose / lower) / logRange - 1`. Require finite representable ratios/results. Position is geometrically centered and unclipped, not a temporal return. Do not allocate an absolute log-center column.
|
|
62
|
+
- Invalid/nonpositive bounds (possible for Bollinger even with valid prices) leave both outputs `NaN`; never clamp or silently switch transforms. Valid equal bounds produce width `0` and position `NaN`. Width depends only on bounds and must not be invalidated solely by an invalid current close. Invalid position operands/results stay `NaN`. Retain normal `skipNull` semantics, including an empty result if the final position is invalid.
|
|
63
|
+
|
|
64
|
+
### Oscillators and volume
|
|
65
|
+
|
|
66
|
+
- `src/oscillators/rsi.js` — RSI plus an SMA of RSI, optionally expressed as log ratios to 50.
|
|
67
|
+
- `src/oscillators/mfi.js` — money flow index from HLC3 and volume, with a bounded signed-flow window, optional log ratio to 50, and generated lags. The first row seeds typical price; a period of N needs N + 1 rows. Unlike the three zero-skipping volume indicators, MFI counts zero-volume rows as zero flow. Equal-price rows contribute zero; a window with no directional flow stays `NaN`. Raw/log variants have independent runtime state and neither output is price-based.
|
|
68
|
+
- `src/oscillators/stochastic.js` — stochastic K and D, optionally expressed as log ratios to 50.
|
|
69
|
+
- `src/oscillators/volumeDelta.js` — signed per-bar volume and consecutive buy/sell direction counter.
|
|
70
|
+
- `src/oscillators/volumeOscillator.js` — percentage difference and optional log ratio between fast and slow volume EMAs.
|
|
71
|
+
|
|
72
|
+
### Volatility
|
|
73
|
+
|
|
74
|
+
- `src/volatility/atr.js` — ATR or its log ratio to close; only raw ATR and its lags are price-based, never the dimensionless logged variant.
|
|
75
|
+
- `src/volatility/adx.js` — ADX or its log ratio to the neutral reference value.
|
|
76
|
+
|
|
77
|
+
### Studies and feature utilities
|
|
78
|
+
|
|
79
|
+
- `src/studies/candleFeatures.js` — candle change, gap, body, wick, range, mid-price, and selected price-target returns.
|
|
80
|
+
- `src/studies/dateTime.js` — numeric or one-hot calendar features.
|
|
81
|
+
- `src/studies/findCrosses.js` — signed intervals since a cross, optional clipping, and optional one-hot encoding.
|
|
82
|
+
- `src/studies/lag.js` — lag columns for existing vertical columns.
|
|
83
|
+
|
|
84
|
+
## Minimum required helpers
|
|
85
|
+
|
|
86
|
+
- `src/core-functions/mainLoop.js` — handler registry and chronological execution engine.
|
|
87
|
+
- `src/core-indicators/index.js` — compatible `Faster*` class exports; `src/core-indicators/README.md` records storage choices and reference-compatibility constraints.
|
|
88
|
+
- `src/core-functions/initializeColumns.js` — output allocation using `main.len`, explicit type/fill overrides, price metadata, collision checks, and lag registration from the same declarations.
|
|
89
|
+
- `src/core-functions/pushToMain.js` — column writes and per-row validity checks.
|
|
90
|
+
- `src/utilities/assignTypes.js` — input type inference, typed-array allocation, and array-type lookup used by lagging.
|
|
91
|
+
- `src/utilities/validators.js` — public API and queued-parameter validation.
|
|
92
|
+
- `src/utilities/config.js` — strict configuration copying/freezing, schema version, and JSON-safe parameter export.
|
|
93
|
+
- `src/utilities/verticalToHorizontal.js` — output materialization, invalid-prefix skipping, temporary-column filtering, and precision restoration.
|
|
94
|
+
- `src/utilities/numberUtilities.js` — input/output numeric formatters, numeric classification, and volume-number predicates.
|
|
95
|
+
- `src/utilities/precisionMultiplier.js` — fixed-point conversion for exact price-string processing when `precision: true`.
|
|
96
|
+
- `src/utilities/math.js` — finite-number checks and safe natural log ratios.
|
|
97
|
+
- `src/utilities/dateUtilities.js` — date input detection/conversion and requested output formatting.
|
|
98
|
+
- `src/machine-learning/ml-utilities.js` — one-hot vector creation used by date and cross studies.
|
|
99
|
+
|
|
100
|
+
## Precision and storage rules
|
|
101
|
+
|
|
102
|
+
- With `precision: false`, price inputs use normal floating-point values.
|
|
103
|
+
- With `precision: true`, OHLC price strings are converted to fixed-point numbers using one shared multiplier. Price-based indicator outputs stay in those units internally and are restored only during output conversion.
|
|
104
|
+
- Cache numeric multiplier decimal metadata once per computation/output conversion, outside exported configuration. Preserve string parsing, truncation, signed-zero, and error behavior; do not replace conversion formulas merely to avoid allocations.
|
|
105
|
+
- Volume is currently stored in an `Int32Array`; do not assume that integer typed arrays preserve `NaN`.
|
|
106
|
+
- `Array` columns are used for objects such as dates and one-hot vectors; numeric indicator outputs normally use `Float64Array`.
|
|
107
|
+
|
|
108
|
+
## Changes and verification
|
|
109
|
+
|
|
110
|
+
- Preserve unrelated working-tree changes; indicator files are frequently edited together.
|
|
111
|
+
- Do not edit `dist/ohlcv-indicators.min.js` manually. Run `npm run build` after source changes that should ship in the browser bundle.
|
|
112
|
+
- Use deterministic local OHLCV arrays to test warm-up boundaries, zero/invalid inputs, lagged columns, multiple configurations, `retLogs`, `precision: true`, and both `getData()` modes.
|
|
113
|
+
- Run `git diff --check`, `npm test`, and `npm run build`. `test/test.js` runs deterministic local regression checks by default; `npm test -- --live` runs the optional integration demo against its configured market-data service.
|
package/README.md
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
# OHLCV Indicators
|
|
2
|
+
|
|
3
|
+
Compute technical indicators and features from an array of market candles. Configure the calculations with a chain of methods, then get the results as ordinary JavaScript objects.
|
|
4
|
+
|
|
5
|
+
**OHLCV** means **open, high, low, close, and volume**. Each input row represents one candle. The library is useful for preparing backtest data, building feature datasets, and calculating indicators for trading applications; it does not fetch market data or execute trades.
|
|
6
|
+
|
|
7
|
+
This README describes the API in this repository. Keep the same library version when reproducing stored configurations.
|
|
8
|
+
|
|
9
|
+
## Contents
|
|
10
|
+
|
|
11
|
+
- [Install and quick start](#install-and-quick-start)
|
|
12
|
+
- [Input requirements](#input-requirements)
|
|
13
|
+
- [How calculation and output work](#how-calculation-and-output-work)
|
|
14
|
+
- [Constructor options](#constructor-options)
|
|
15
|
+
- [Indicator reference](#indicator-reference)
|
|
16
|
+
- [Advanced: combine indicators and lagged features](#advanced-combine-indicators-and-lagged-features)
|
|
17
|
+
- [Save and reuse a configuration](#save-and-reuse-a-configuration)
|
|
18
|
+
- [Precision mode](#precision-mode)
|
|
19
|
+
- [Current limitations and troubleshooting](#current-limitations-and-troubleshooting)
|
|
20
|
+
- [Notes for developers and LLMs](#notes-for-developers-and-llms)
|
|
21
|
+
|
|
22
|
+
## Install and quick start
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npm install ohlcv-indicators
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Use ES modules: save your script as `.mjs`, or set `"type": "module"` in your application's `package.json`.
|
|
29
|
+
|
|
30
|
+
### Calculate a simple moving average
|
|
31
|
+
|
|
32
|
+
A moving average smooths prices over a window. `sma(3)` averages the latest three closing prices.
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
import OHLCV_INDICATORS from 'ohlcv-indicators';
|
|
36
|
+
|
|
37
|
+
const input = [
|
|
38
|
+
{ date: '2024-01-01T00:00:00Z', open: 100, high: 103, low: 99, close: 102, volume: 1000 },
|
|
39
|
+
{ date: '2024-01-02T00:00:00Z', open: 102, high: 105, low: 101, close: 104, volume: 1200 },
|
|
40
|
+
{ date: '2024-01-03T00:00:00Z', open: 104, high: 105, low: 100, close: 102, volume: 900 },
|
|
41
|
+
{ date: '2024-01-04T00:00:00Z', open: 102, high: 107, low: 101, close: 106, volume: 1500 },
|
|
42
|
+
{ date: '2024-01-05T00:00:00Z', open: 106, high: 109, low: 105, close: 108, volume: 1300 },
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const study = new OHLCV_INDICATORS({
|
|
46
|
+
input,
|
|
47
|
+
config: { timeZone: 'UTC', dateFormat: 'iso' },
|
|
48
|
+
}).sma(3);
|
|
49
|
+
|
|
50
|
+
const rows = study.getData();
|
|
51
|
+
console.table(rows);
|
|
52
|
+
|
|
53
|
+
const latest = study.getLastValues();
|
|
54
|
+
console.log(latest.close); // 108
|
|
55
|
+
console.log(latest.sma_3); // 105.33333333333333
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`rows` contains the last three candles: the first two do not yet have enough history for the average. Each result includes the original columns, `mid_price` (`(open + close) / 2`), and `sma_3`.
|
|
59
|
+
|
|
60
|
+
To keep the warm-up rows too:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
// Reuses the already computed study; it does not calculate the indicators again.
|
|
64
|
+
const allRows = study.getData({ skipNull: false });
|
|
65
|
+
console.log(allRows.length); // 5
|
|
66
|
+
console.log(allRows[0].sma_3); // NaN: not enough history yet
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Later examples reuse `input` and the import above unless they define their own data.
|
|
70
|
+
|
|
71
|
+
### Browser bundle
|
|
72
|
+
|
|
73
|
+
Serve the repository's `dist/ohlcv-indicators.min.js` with your application. The current bundle exposes the class through `OHLCV_INDICATORS.default`:
|
|
74
|
+
|
|
75
|
+
```html
|
|
76
|
+
<script src="./dist/ohlcv-indicators.min.js"></script>
|
|
77
|
+
<script>
|
|
78
|
+
const Indicators = OHLCV_INDICATORS.default;
|
|
79
|
+
const candles = [
|
|
80
|
+
{ open: 10, high: 12, low: 9, close: 11, volume: 100 },
|
|
81
|
+
{ open: 11, high: 13, low: 10, close: 12, volume: 120 },
|
|
82
|
+
{ open: 12, high: 14, low: 11, close: 13, volume: 110 },
|
|
83
|
+
];
|
|
84
|
+
console.log(new Indicators({ input: candles }).sma(2).getLastValues());
|
|
85
|
+
</script>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Use a modern JavaScript runtime with typed arrays and `Intl.DateTimeFormat` support. Browser bundling does not imply that every host environment has been tested.
|
|
89
|
+
|
|
90
|
+
## Input requirements
|
|
91
|
+
|
|
92
|
+
Provide a nonempty array ordered **oldest to newest**, with consistent columns and value types. The library does not sort candles, fill missing timestamps, or resample their intervals.
|
|
93
|
+
|
|
94
|
+
| Field | Recommended input |
|
|
95
|
+
| --- | --- |
|
|
96
|
+
| `open`, `high`, `low`, `close` | Finite numbers greater than zero. Whole-number prices are valid too. |
|
|
97
|
+
| `volume` | Integer greater than or equal to zero; currently limited to `0`–`2147483647` by `Int32Array` storage. |
|
|
98
|
+
| `date` | Optional. Prefer a valid `Date` or an ISO string with `Z` or an explicit timezone offset. Required by `dateTime()`. |
|
|
99
|
+
| Extra columns | Supported; columns and storage types are inferred from the first row. |
|
|
100
|
+
|
|
101
|
+
Use the full OHLCV shape shown above. The constructor checks for `close`, but the calculation loop also uses `open` for `mid_price`; different indicators require other columns.
|
|
102
|
+
|
|
103
|
+
Clean numeric strings such as `'102.50'` are also accepted. Use a consistent numeric representation for all OHLC fields. With `precision: true`, OHLC values **must** be numeric strings; see [precision mode](#precision-mode).
|
|
104
|
+
|
|
105
|
+
Required indicator inputs are checked at row zero. Subsequent rows are not comprehensively validated for performance, so validate your dataset before passing it in. In particular, integer storage can truncate fractional volume or wrap oversized values before a handler sees them. Do not rely on the library to reject every malformed row.
|
|
106
|
+
|
|
107
|
+
## How calculation and output work
|
|
108
|
+
|
|
109
|
+
1. Construct an instance with input candles.
|
|
110
|
+
2. Chain all indicators and features you want.
|
|
111
|
+
3. Call `compute()`, `getData()`, or `getLastValues()` to calculate once.
|
|
112
|
+
|
|
113
|
+
| Method | Result |
|
|
114
|
+
| --- | --- |
|
|
115
|
+
| `compute()` | Calculates and returns the instance, without building output row objects. |
|
|
116
|
+
| `getData({ skipNull?, dateFormat? })` | Calculates if needed and returns an array of rows. |
|
|
117
|
+
| `getLastValues({ dateFormat? })` | Calculates if needed and returns one final row, even if it contains unavailable values. |
|
|
118
|
+
| `exportConfig()` | Returns a fresh JSON-safe configuration without triggering calculation. |
|
|
119
|
+
|
|
120
|
+
Register everything before calculation. You cannot append indicators or new candles to a computed instance. Create another instance to change the setup or process another dataset. `getLastValues()` still computes the full input history; it is not a streaming update API.
|
|
121
|
+
|
|
122
|
+
### Warm-up and missing values
|
|
123
|
+
|
|
124
|
+
Numeric outputs generally start as `NaN` until sufficient history exists. Combined indicators may need more rows than their period alone suggests. For example, RSI also produces an SMA of RSI, which needs additional history.
|
|
125
|
+
|
|
126
|
+
`skipNull: true` is the default. It returns only the suffix **after the last row containing any `NaN`, `null`, or `undefined` column value**. It does not just remove individual bad rows. A late missing value can therefore discard many earlier valid rows; an invalid final row can make the result `[]`. This check is not a general finite-number check: `Infinity` is not filtered by it.
|
|
127
|
+
|
|
128
|
+
Use `getData({ skipNull: false })` when debugging or when you need one output row for every input row. Missing values remain missing; they are not replaced with zero.
|
|
129
|
+
|
|
130
|
+
`relativeVolume()`, `volumeDelta()`, and `volumeOscillator()` deliberately skip zero-volume candles without updating their indicator state. Their output on those candles stays `NaN`, and their warm-up can take more calendar rows.
|
|
131
|
+
|
|
132
|
+
## Constructor options
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
const configured = new OHLCV_INDICATORS({
|
|
136
|
+
input,
|
|
137
|
+
ticker: 'EXAMPLE',
|
|
138
|
+
chunkProcess: 2000,
|
|
139
|
+
config: {
|
|
140
|
+
schemaVersion: 1,
|
|
141
|
+
precision: false,
|
|
142
|
+
useFullNames: true,
|
|
143
|
+
inputParams: null,
|
|
144
|
+
dateFormat: 'iso',
|
|
145
|
+
skipNull: false,
|
|
146
|
+
timeZone: 'UTC',
|
|
147
|
+
},
|
|
148
|
+
}).ema(3);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Only `input` is required. Put calculation and output settings inside `config`, not at the constructor's top level.
|
|
152
|
+
|
|
153
|
+
| Option | Default | Meaning |
|
|
154
|
+
| --- | --- | --- |
|
|
155
|
+
| `ticker` | `null` | Optional label used in some validation messages; does not load data. |
|
|
156
|
+
| `chunkProcess` | `2000` | Loop chunk size, integer `100`–`50000`. Processing remains synchronous and retains full output columns. |
|
|
157
|
+
| `config.schemaVersion` | `1` | Configuration format version, not the package version. |
|
|
158
|
+
| `config.precision` | `false` | Enables fixed-point input conversion from price strings. |
|
|
159
|
+
| `config.useFullNames` | `false` | Includes parameter suffixes in indicators with optional compact names. |
|
|
160
|
+
| `config.inputParams` | `null` | Saved indicator declarations. An array, even `[]`, triggers calculation during construction. |
|
|
161
|
+
| `config.dateFormat` | `'milliseconds'` | Default date representation in returned rows. |
|
|
162
|
+
| `config.skipNull` | `true` | Default invalid-prefix handling for `getData()`. |
|
|
163
|
+
| `config.timeZone` | Runtime timezone | Explicit timezone for `dateTime()` calendar features; saved in exports. |
|
|
164
|
+
|
|
165
|
+
Supported output date formats: `milliseconds`, `seconds`, `iso` / `toISOString`, `object` (a `Date`), `string` / `toString`, and `toUTCString`. Getter overrides do not change exported defaults. Prefer `iso`, `milliseconds`, or `seconds` for portable output; local string formatting depends on the runtime.
|
|
166
|
+
|
|
167
|
+
## Indicator reference
|
|
168
|
+
|
|
169
|
+
All methods below return the instance for chaining. Periods and lookbacks are measured in rows, not minutes or days. Periods must be positive integers within the input length; allow extra history for warm-up. Avoid repeating configurations that produce the same output names.
|
|
170
|
+
|
|
171
|
+
Defaults are shown below. `target` means the numeric source column; `lag: n` creates output copies from each of the previous `1` through `n` rows. `retLogs` selects natural-log-based features, not application logging; see each indicator's formula below.
|
|
172
|
+
|
|
173
|
+
| Method | Options and behavior |
|
|
174
|
+
| --- | --- |
|
|
175
|
+
| `ema(size = 5, options = {})` | Exponential moving average, or `ln(target / EMA)` when logged. `{ target: 'close', lag: 0, retLogs: false }`. |
|
|
176
|
+
| `sma(size = 5, options = {})` | Simple moving average, or `ln(target / SMA)` when logged. `{ target: 'close', lag: 0, retLogs: false }`. |
|
|
177
|
+
| `bollingerBands(size = 20, stdDev = 2, options = {})` | Upper, middle, and lower close-price bands, or logarithmic width and position. `{ lag: 0, retLogs: false }`. |
|
|
178
|
+
| `donchianChannels(size = 20, offset = 0, options = {})` | Rolling high/low channel and midpoint, or logarithmic width and position. `{ lag: 0, retLogs: false }`; positive offset shifts the channel window into the past, not the close used for position. |
|
|
179
|
+
| `macd(fast = 12, slow = 26, signal = 9, options = {})` | Difference, signal (`dea`), and histogram. `{ target: 'close', lag: 0 }`. |
|
|
180
|
+
| `rsi(size = 14, options = {})` | RSI and a same-period SMA of RSI. `{ target: 'close', lag: 0, retLogs: false }`. |
|
|
181
|
+
| `mfi(size = 14, options = {})` | Money flow index using high, low, close, and volume. `{ lag: 0, retLogs: false }`; logged output uses `mathLog(MFI, 50)`. |
|
|
182
|
+
| `stochastic(kPeriod = 14, kSlowingPeriod = 3, dPeriod = 3, options = {})` | K and D outputs. `{ lag: 0, retLogs }`; explicitly supply `retLogs: false` or `true` in this version. |
|
|
183
|
+
| `atr(size = 14, options = {})` | Average true range. `{ lag: 0, retLogs: false }`. |
|
|
184
|
+
| `adx(size = 14, options = {})` | Average directional index. `{ lag: 0, retLogs: false }`. |
|
|
185
|
+
| `heikenAshi(smoothLength = null, afterSmoothLength = null, options = {})` | Heiken-Ashi body/wick/range returns and direction counter, not raw HA OHLC columns. `{ lag: 0, retLogs: false }`; use either two `null`s or two positive smoothing periods. |
|
|
186
|
+
| `relativeVolume(size = 10, options = {})` | Current volume / previous completed volume-SMA window, or its natural log, excluding the current sample from the denominator. `{ lag: 0, retLogs: false }`. |
|
|
187
|
+
| `volumeDelta(options = {})` | Candle-direction-based signed volume, delta high/low/close, and direction counter; not trade-by-trade bid/ask delta. `{ lag: 0 }`. |
|
|
188
|
+
| `volumeOscillator(fast = 5, slow = 10, options = {})` | One output: percentage difference, or `ln(fastVolumeEMA / slowVolumeEMA)` when logged. `{ lag: 0, retLogs: false }`. |
|
|
189
|
+
| `candleFeatures(options = {})` | Change, gap, body, wick, and range returns. `{ lag: 0, colKeys: [], retLogs: false }`. Extra `colKeys` compare current close to each selected price column, not that column's previous value. |
|
|
190
|
+
| `dateTime(options = {})` | Calendar features from `date`. `{ lag: 0, oneHot: false }`. |
|
|
191
|
+
| `crossPairs(pairs = [], options = {})` | Signed direction/run counters for `{ fast, slow }` pairs. `{ limit: null, oneHot: false }`; `slow` can be a column name or numeric constant. Call once with all pairs. |
|
|
192
|
+
| `lag(colKeys = ['close'], lookback = 1)` | Copies existing columns into `<column>_lag_1`, ..., `<column>_lag_<lookback>`. |
|
|
193
|
+
|
|
194
|
+
### Output names and units
|
|
195
|
+
|
|
196
|
+
Examples of names: `ema_5`, `sma_5_open`, `rsi_14`, `rsi_sma_14`, `mfi_14`, `atr_14`, `relative_volume_10`, and `volume_oscillator_5_10`.
|
|
197
|
+
|
|
198
|
+
A single Bollinger configuration normally uses `bollinger_bands_upper`, `bollinger_bands_middle`, and `bollinger_bands_lower`. With `useFullNames: true`, `.bollingerBands(20, 2)` adds `_20_2`. MACD and Donchian also expand their names when multiple configurations are registered. Use full names for persistent downstream schemas, and inspect `Object.keys(instance.getLastValues())` rather than guessing names.
|
|
199
|
+
|
|
200
|
+
For `retLogs: true`, RSI, MFI, and stochastic use `ln(value / 50)`, ADX uses `ln(value / 20)`, and ATR uses `ln(ATR / close)`. Their logged columns replace the corresponding raw outputs.
|
|
201
|
+
|
|
202
|
+
EMA and SMA use `ln(currentTarget / currentAverage)`; relative volume uses `ln(currentVolume / previousCompletedSMA)`. These are log-relative features, not changes from the previous row. Their names are `ret_log_ema_<size>`, `ret_log_sma_<size>`, and `ret_log_relative_volume_<size>`; non-close moving-average targets append `_<target>`, for example `ret_log_sma_3_open`. A value of zero means the source equals its reference. Raw mode remains the default, including replay of older configs that omit `retLogs`.
|
|
203
|
+
|
|
204
|
+
Each of these three methods emits only the selected raw or logged column, with generated lags only for that column. Register both modes to obtain both outputs; raw/log instances have independent state. Logs and their lags are dimensionless numbers even with `precision: true`. Logged ATR and its lags are also dimensionless and are no longer incorrectly rescaled as prices in precision mode; replaying older configs with this combination now produces the corrected output.
|
|
205
|
+
|
|
206
|
+
```js
|
|
207
|
+
const logFeatures = new OHLCV_INDICATORS({ input })
|
|
208
|
+
.ema(3, { retLogs: true, lag: 1 })
|
|
209
|
+
.sma(3, { target: 'open', retLogs: true })
|
|
210
|
+
.relativeVolume(3, { retLogs: true });
|
|
211
|
+
|
|
212
|
+
const savedLogConfig = JSON.parse(JSON.stringify(logFeatures.exportConfig()));
|
|
213
|
+
const replayedLogFeatures = new OHLCV_INDICATORS({ input, config: savedLogConfig });
|
|
214
|
+
console.log(replayedLogFeatures.getLastValues().ret_log_ema_3);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
The EMA/SMA/relative-volume log modes leave unavailable or invalid ratios as `NaN`: operands must be positive and the computed ratio finite and greater than zero. They do not apply epsilon substitution to zero targets. Their underlying averages still update exactly as in raw mode; relative volume still skips zero-volume rows without updating its state. MACD and volume delta have no `retLogs` option.
|
|
218
|
+
|
|
219
|
+
`volumeOscillator()` also emits only the selected output:
|
|
220
|
+
|
|
221
|
+
- `retLogs: false` creates `volume_oscillator_<fast>_<slow>` containing `100 * (fastEMA - slowEMA) / slowEMA`.
|
|
222
|
+
- `retLogs: true` creates `ret_log_volume_oscillator_<fast>_<slow>` containing `mathLog(fastEMA, slowEMA)`; it does **not** create the percentage column.
|
|
223
|
+
|
|
224
|
+
Its `lag` option generates lags only for that selected column. To obtain both forms, register both modes before computation, for example `.volumeOscillator(5, 10, { lag: 2 }).volumeOscillator(5, 10, { retLogs: true, lag: 2 })`. Each mode has independent EMA state; both outputs and their lags are dimensionless.
|
|
225
|
+
|
|
226
|
+
Older saved configs using `retLogs: true` may expect the percentage column that was previously emitted too. If an explicit lag or downstream consumer still needs that percentage column, add a raw-mode registration before the consumer. Change a reference to the logged column only when you intend to consume log ratios instead of percentages. Current configs preserve the selected mode on export/replay.
|
|
227
|
+
|
|
228
|
+
Non-log candle and Heiken-Ashi returns are fractions, not percentages: `0.01` means 1%. Unavailable indicator values can remain `NaN`. The shared `mathLog()` helper substitutes `0.001` for a zero numerator or denominator and throws for incompatible signs or a non-finite result; its zero handling is not literal `ln(0)`. Candle and Heiken-Ashi features have their own log calculations.
|
|
229
|
+
|
|
230
|
+
### Bollinger and Donchian: logarithmic channel features
|
|
231
|
+
|
|
232
|
+
With `{ retLogs: true }`, these methods replace their three raw price columns with two dimensionless features. Given upper bound `U`, lower bound `L`, and the current candle's close `C`:
|
|
233
|
+
|
|
234
|
+
```text
|
|
235
|
+
logRange = ln(U / L)
|
|
236
|
+
width = logRange / 2
|
|
237
|
+
position = 2 * ln(C / L) / logRange - 1
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
`position` is `-1` at the lower bound, `+1` at the upper bound, and `0` at their geometric midpoint, `sqrt(U * L)`. Breakouts remain below `-1` or above `+1`; values are not clipped. These describe the current channel, not returns from the previous candle. Donchian's `offset` shifts only the bounds: position always compares the **current close** to that historical channel.
|
|
241
|
+
|
|
242
|
+
The compact output names are `ret_log_bollinger_bands_width`, `ret_log_bollinger_bands_position`, `ret_log_donchian_channel_width`, and `ret_log_donchian_channel_position`. With `useFullNames: true` or more than one registration of the same method (counting both modes), they append `_<size>_<stdDev>` for Bollinger or `_<size>_<offset>` for Donchian. Prefer full names for a stable downstream schema.
|
|
243
|
+
|
|
244
|
+
This example reuses the five quick-start candles and includes saved-config replay:
|
|
245
|
+
|
|
246
|
+
```js
|
|
247
|
+
const channelFeatures = new OHLCV_INDICATORS({
|
|
248
|
+
input,
|
|
249
|
+
config: { useFullNames: true },
|
|
250
|
+
})
|
|
251
|
+
.bollingerBands(3, 2, { retLogs: true, lag: 1 })
|
|
252
|
+
.donchianChannels(3, 1, { retLogs: true, lag: 1 });
|
|
253
|
+
|
|
254
|
+
const savedChannelConfig = JSON.parse(JSON.stringify(channelFeatures.exportConfig()));
|
|
255
|
+
const replayedChannels = new OHLCV_INDICATORS({ input, config: savedChannelConfig });
|
|
256
|
+
const lastChannel = replayedChannels.getLastValues();
|
|
257
|
+
console.log(lastChannel.ret_log_bollinger_bands_width_3_2);
|
|
258
|
+
console.log(lastChannel.ret_log_donchian_channel_position_3_1);
|
|
259
|
+
console.log(lastChannel.ret_log_donchian_channel_position_3_1_lag_1);
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Both bounds must be finite, positive, ordered (`U >= L`), and have a finite representable ratio. Invalid bounds leave both features `NaN`. In particular, Bollinger's lower band can be zero or negative despite valid positive input prices; it is not clamped and there is no fallback transform. Valid equal bounds produce width `0`, but position remains `NaN` because it is undefined. Width depends only on the bounds; an invalid close or close/bound ratio leaves position `NaN` without discarding an otherwise valid width.
|
|
263
|
+
|
|
264
|
+
Warm-up and raw calculations are unchanged. The default remains `{ retLogs: false }`, including replay of older configs that omit the option. To obtain raw bands as well, register the same method again in raw mode before computation; raw/log variants maintain independent state. Each registration generates lags only for its selected outputs. Logged columns and their lags are not price-based and remain dimensionless in precision mode. No absolute log-center column is allocated: two numeric outputs instead of three save approximately `8 * input.length * (1 + lag)` bytes per registration.
|
|
265
|
+
|
|
266
|
+
A flat or otherwise invalid final channel can make default `getData()` return `[]`, because `skipNull` removes the prefix through the last invalid row. Use `getData({ skipNull: false })` to inspect these rows and their `NaN` positions.
|
|
267
|
+
|
|
268
|
+
### Money flow index: raw, logged, and lagged
|
|
269
|
+
|
|
270
|
+
MFI weights typical price `(high + low + close) / 3` by volume, then compares positive and negative flow over the selected period. Rising typical prices contribute positive flow, falling prices negative flow, and unchanged prices zero. The result is `100 - 100 / (1 + positiveFlow / negativeFlow)`. See the [MFI calculation reference](https://www.tradingview.com/support/solutions/43000502348-money-flow-mfi/).
|
|
271
|
+
|
|
272
|
+
This example reuses the five quick-start candles, so it uses a short period:
|
|
273
|
+
|
|
274
|
+
```js
|
|
275
|
+
const moneyFlow = new OHLCV_INDICATORS({ input })
|
|
276
|
+
.mfi(3, { lag: 1 })
|
|
277
|
+
.mfi(3, { lag: 1, retLogs: true });
|
|
278
|
+
|
|
279
|
+
const lastMoneyFlow = moneyFlow.getLastValues();
|
|
280
|
+
console.log(lastMoneyFlow.mfi_3); // Raw index, between 0 and 100
|
|
281
|
+
console.log(lastMoneyFlow.ret_log_mfi_3); // 50 maps to 0; below 50 is negative
|
|
282
|
+
console.log(lastMoneyFlow.mfi_3_lag_1); // Previous candle's MFI
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
For ordinary use, call `.mfi()` for period 14, or `.mfi(14, { lag: 2, retLogs: true })` for logged output and two lags. Logged output is named `ret_log_mfi_14`; raw and logged variants can coexist at the same period without sharing state. Both are dimensionless, including their lags, even with `precision: true`.
|
|
286
|
+
|
|
287
|
+
MFI needs one starting candle plus `size` flow observations: period 14 first becomes available at index 14 (the fifteenth row). Zero-volume candles contribute zero **and count toward this window**; unlike `relativeVolume`, `volumeDelta`, and `volumeOscillator`, MFI does not skip them. A positive-only window returns 100, a negative-only window returns 0, and a window with no directional flow returns `NaN`. Logged zero uses the existing `mathLog` epsilon behavior. Invalid flow/comparisons leave output `NaN` until they leave the window. Storage is bounded by the period; summation is chronological to avoid rolling-subtraction drift.
|
|
288
|
+
|
|
289
|
+
## Advanced: combine indicators and lagged features
|
|
290
|
+
|
|
291
|
+
This runnable example generates deterministic demonstration candles. Replace them with your own chronological dataset in a real application.
|
|
292
|
+
|
|
293
|
+
```js
|
|
294
|
+
const history = Array.from({ length: 160 }, (_, i) => {
|
|
295
|
+
const open = 100 + i * 0.1 + Math.sin(i / 5);
|
|
296
|
+
const close = open + Math.cos(i / 3) * 0.8;
|
|
297
|
+
return {
|
|
298
|
+
date: new Date(Date.UTC(2024, 0, 1) + i * 3600000).toISOString(),
|
|
299
|
+
open,
|
|
300
|
+
high: Math.max(open, close) + 1,
|
|
301
|
+
low: Math.min(open, close) - 1,
|
|
302
|
+
close,
|
|
303
|
+
volume: 1000 + (i % 20) * 25,
|
|
304
|
+
};
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const features = new OHLCV_INDICATORS({
|
|
308
|
+
input: history,
|
|
309
|
+
config: { useFullNames: true, timeZone: 'UTC', dateFormat: 'iso' },
|
|
310
|
+
})
|
|
311
|
+
.ema(5, { lag: 2 })
|
|
312
|
+
.ema(20)
|
|
313
|
+
.rsi(14)
|
|
314
|
+
.mfi(14, { lag: 2, retLogs: true })
|
|
315
|
+
.macd(12, 26, 9)
|
|
316
|
+
.bollingerBands(20, 2)
|
|
317
|
+
.donchianChannels(20, 1)
|
|
318
|
+
.stochastic(14, 3, 3, { retLogs: false })
|
|
319
|
+
.atr(14)
|
|
320
|
+
.adx(14)
|
|
321
|
+
.relativeVolume(10)
|
|
322
|
+
.volumeOscillator(5, 10, { retLogs: true })
|
|
323
|
+
.candleFeatures({ retLogs: true })
|
|
324
|
+
.dateTime({ oneHot: true })
|
|
325
|
+
.crossPairs([{ fast: 'close', slow: 'open' }], { limit: 5, oneHot: true })
|
|
326
|
+
.lag(['close', 'volume'], 2);
|
|
327
|
+
|
|
328
|
+
const featureRows = features.getData();
|
|
329
|
+
const lastFeature = features.getLastValues();
|
|
330
|
+
console.log(featureRows.length);
|
|
331
|
+
console.log(lastFeature.ema_5_lag_1); // EMA value from the previous row
|
|
332
|
+
console.log(lastFeature.macd_histogram_12_26_9);
|
|
333
|
+
console.log(lastFeature.one_hot_hour); // Uint8Array of length 24
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Register producers before consumers: for example, register `.ema(5)` before asking `.lag(['ema_5'], 1)` to read its output. Do not create the same lag twice using both an indicator's `lag` option and an explicit `.lag()` call.
|
|
337
|
+
|
|
338
|
+
A lag always refers to an earlier row, not an earlier nonzero-volume row. Features on the current candle can depend on its final high, low, close, and volume; account for candle completion when using them in a backtest.
|
|
339
|
+
|
|
340
|
+
Numeric `dateTime()` weekdays use Monday = 1 through Sunday = 7. One-hot weekdays instead use Sunday at index 0 through Saturday at index 6. One-hot values are `Uint8Array` vectors; convert with `Array.from(vector)` if another tool needs plain arrays. One-hot calendar output omits the year.
|
|
341
|
+
|
|
342
|
+
## Save and reuse a configuration
|
|
343
|
+
|
|
344
|
+
`exportConfig()` saves **the recipe**, not the candles or computed values. It includes indicator declarations, precision/naming settings, date output defaults, and timezone. It excludes `input`, `ticker`, `chunkProcess`, buffers, and indicator state.
|
|
345
|
+
|
|
346
|
+
The following Node.js example writes the recipe to a file, loads it, and reproduces the result. It reuses the quick-start `input`.
|
|
347
|
+
|
|
348
|
+
```js
|
|
349
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
350
|
+
import assert from 'node:assert/strict';
|
|
351
|
+
|
|
352
|
+
const original = new OHLCV_INDICATORS({
|
|
353
|
+
input,
|
|
354
|
+
config: { timeZone: 'UTC', dateFormat: 'iso', useFullNames: true },
|
|
355
|
+
})
|
|
356
|
+
.sma(3, { lag: 1 })
|
|
357
|
+
.volumeOscillator(2, 3, { retLogs: true });
|
|
358
|
+
|
|
359
|
+
// Export before computing. Exporting afterward gives the same recipe.
|
|
360
|
+
const storedConfig = original.exportConfig();
|
|
361
|
+
await writeFile('./indicator-config.json', JSON.stringify(storedConfig, null, 2));
|
|
362
|
+
|
|
363
|
+
const expectedRows = original.getData();
|
|
364
|
+
assert.deepStrictEqual(original.exportConfig(), storedConfig);
|
|
365
|
+
|
|
366
|
+
// Later, or in another process using the same library version:
|
|
367
|
+
const loadedConfig = JSON.parse(await readFile('./indicator-config.json', 'utf8'));
|
|
368
|
+
const restored = new OHLCV_INDICATORS({ input, config: loadedConfig });
|
|
369
|
+
assert.deepStrictEqual(restored.getData(), expectedRows);
|
|
370
|
+
|
|
371
|
+
// Reuse the exact same saved object again; the constructor does not mutate it.
|
|
372
|
+
const restoredAgain = new OHLCV_INDICATORS({ input, config: loadedConfig });
|
|
373
|
+
assert.deepStrictEqual(restoredAgain.getData(), expectedRows);
|
|
374
|
+
|
|
375
|
+
// Apply the same recipe to a different dataset. Results depend on that data.
|
|
376
|
+
const onOtherHistory = new OHLCV_INDICATORS({ input: history, config: loadedConfig });
|
|
377
|
+
console.log(onOtherHistory.getLastValues());
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
The last two lines use `history` from the advanced example. In a browser, store the same JSON text in your chosen storage instead of using `node:fs/promises`.
|
|
381
|
+
|
|
382
|
+
Important replay rules:
|
|
383
|
+
|
|
384
|
+
- Passing `config.inputParams` as an array computes immediately, even if it is empty. Do **not** chain more indicators onto a restored instance.
|
|
385
|
+
- Preserve the whole exported config; saving only `inputParams` loses settings that affect the result.
|
|
386
|
+
- For identical results, reuse identical ordered input, the same library version, and unambiguous dates. The config schema version alone does not pin library behavior.
|
|
387
|
+
- Exported objects are independent copies. Runtime-generated lag jobs are not added to the saved declarations; replay regenerates them from their options.
|
|
388
|
+
- Configuration values must be JSON-safe. Functions, `undefined`, `NaN`, `Infinity`, circular references, and non-plain objects are not supported as configuration.
|
|
389
|
+
- `mapCols()` and `registerMapCallback()` have been removed. Older configurations containing `mapCols` are rejected as unknown indicators; they cannot be replayed unchanged in this version.
|
|
390
|
+
|
|
391
|
+
JSON safety applies to exported **configuration**, not arbitrary result rows. Serializing results converts `NaN` to `null`, and typed-array vectors are not serialized as ordinary arrays automatically.
|
|
392
|
+
|
|
393
|
+
## Precision mode
|
|
394
|
+
|
|
395
|
+
Use numeric strings for all OHLC prices when enabling precision:
|
|
396
|
+
|
|
397
|
+
```js
|
|
398
|
+
const stringPrices = input.map(row => ({
|
|
399
|
+
...row,
|
|
400
|
+
open: row.open.toFixed(4),
|
|
401
|
+
high: row.high.toFixed(4),
|
|
402
|
+
low: row.low.toFixed(4),
|
|
403
|
+
close: row.close.toFixed(4),
|
|
404
|
+
}));
|
|
405
|
+
|
|
406
|
+
const fixedPoint = new OHLCV_INDICATORS({
|
|
407
|
+
input: stringPrices,
|
|
408
|
+
config: { precision: true, timeZone: 'UTC', dateFormat: 'iso' },
|
|
409
|
+
}).sma(3);
|
|
410
|
+
|
|
411
|
+
console.log(fixedPoint.getLastValues().sma_3); // 105.3333
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
Prices are scaled internally using a shared multiplier derived from the first row, then price-based outputs are restored to ordinary numbers. This is **not arbitrary-precision decimal arithmetic**: calculations still use JavaScript numbers. Extra decimal places beyond the chosen scale are truncated, including fractional scaled indicator results during output conversion. Keep scaled prices within JavaScript's safe-integer range and use a consistent decimal scale throughout your dataset.
|
|
415
|
+
|
|
416
|
+
## Current limitations and troubleshooting
|
|
417
|
+
|
|
418
|
+
- **“Already computed” error:** register all methods before calling any getter or `compute()`. Imported configs already computed during construction.
|
|
419
|
+
- **Empty output:** inspect `getData({ skipNull: false })`. You may need more history, or a late missing/zero-volume row may have moved the `skipNull` cutoff.
|
|
420
|
+
- **Stochastic options error:** explicitly call, for example, `.stochastic(14, 3, 3, { retLogs: false })`; the current implementation does not supply that boolean's default.
|
|
421
|
+
- **A derived target fails at row zero:** source validation requires positive finite values at the first row for non-volume targets. This restricts using warming-up outputs (such as an EMA) or signed/zero-valued extra input columns as inputs to another indicator or `crossPairs()`. Registering the producer first does not bypass that validation.
|
|
422
|
+
- **Date errors:** use `Date` objects or timezone-qualified ISO strings consistently. Numeric millisecond input currently has a formatter-name mismatch; wrap it with `new Date(timestampMs)` first. Millisecond *output* is supported. Local/timezone-less strings can parse differently across runtimes.
|
|
423
|
+
- **Unexpected volume:** storage is signed 32-bit integer. Normalize data externally; larger counts and fractional volumes are not preserved.
|
|
424
|
+
- **MACD on a non-price target:** its outputs are always marked price-based. With `precision: true`, targets such as volume or a custom ratio are therefore rescaled incorrectly; use `precision: false` for those targets.
|
|
425
|
+
- **Comparing raw and logged variants:** use separate instances for ADX, stochastic, or Heiken-Ashi at identical periods/smoothing settings. Their raw/log variants can share internal state despite having different output names.
|
|
426
|
+
- **Cross one-hot output:** provide a finite integer `limit` of at least 2 (within the input length) when `oneHot: true` so the vector has a meaningful fixed size.
|
|
427
|
+
|
|
428
|
+
These are descriptions of the current implementation, not promises that invalid inputs will always throw. No data cleaning or strategy decisions are performed for you.
|
|
429
|
+
|
|
430
|
+
## Notes for developers and LLMs
|
|
431
|
+
|
|
432
|
+
Use this section as a compact implementation contract, alongside [AGENTS.md](./AGENTS.md).
|
|
433
|
+
|
|
434
|
+
- Public entry point: default ESM export `OHLCV_INDICATORS` from `index.js`. Constructor shape: `{ input, ticker?, chunkProcess?, config? }`. There are no public `scaler()` or `vidya()` methods.
|
|
435
|
+
- Registration is separate from execution. `inputParams` contains immutable declarations; `executionParams` is the mutable runtime queue. Never serialize runtime instances, buffers, or generated jobs. All configuration parameters must be JSON-safe.
|
|
436
|
+
- Handlers execute chronologically, allocate columns on `index === 0`, and keep rolling state in `main.instances`. The runtime is column-oriented even though input and returned data are row-oriented.
|
|
437
|
+
- `initializeColumns()` owns output allocation, naming collision checks, price metadata, and generated lags. Numeric outputs normally use `Float64Array` with `NaN`; object/one-hot columns use arrays. Volume input uses `Int32Array`.
|
|
438
|
+
- `src/core-indicators/` contains the local numeric indicator classes used at runtime, with no external indicator dependency. Self-contained tests cover known results and fixed behavior traces. Preserve warm-up, arithmetic order, and intentional zero/NaN behavior when changing these classes.
|
|
439
|
+
- Full output storage grows with both row count and column count. Each `Float64Array` column uses approximately `8 * input.length` bytes, excluding other overhead. Lags add full columns; `getData()` allocates row objects on each call. Request only needed features, or use `getLastValues()` when you only need the final row.
|
|
440
|
+
- `chunkProcess` does not turn the API into asynchronous, streaming, or bounded-memory processing. Create a new instance for each independent run and retain the input separately if you need replay.
|
|
441
|
+
- Read the implementation for exact output naming and formulas; do not assume every third-party indicator convention or parameter combination is supported.
|
|
442
|
+
|
|
443
|
+
### Development checks
|
|
444
|
+
|
|
445
|
+
From a checkout with dependencies installed:
|
|
446
|
+
|
|
447
|
+
```sh
|
|
448
|
+
npm test
|
|
449
|
+
npm test -- --benchmark --rows 20000 --repeats 9
|
|
450
|
+
npm run build
|
|
451
|
+
git diff --check
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
`test/test.js` runs deterministic local regressions by default, including indicator behavior, configuration replay, known-value core checks, and fixed behavior traces. The tests do not require a third-party indicator oracle. `npm test -- --live` is an optional integration demo requiring its configured market-data service. Build the browser bundle with `npm run build`; do not edit the minified file manually.
|
|
455
|
+
|
|
456
|
+
The package declares the ISC license. Adapted core indicator code retains its upstream MIT notice; preserve that notice when redistributing it and the generated bundle's accompanying license file.
|