openalgo-script 0.6.0 → 0.7.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.
- package/CHANGELOG.md +105 -0
- package/README.md +12 -12
- package/dist/core/engine/channels.js +2 -2
- package/dist/core/engine/channels.js.map +1 -1
- package/dist/core/engine/engine.js +2 -3
- package/dist/core/engine/engine.js.map +1 -1
- package/dist/core/engine/library/maths.js +2 -2
- package/dist/core/engine/library/maths.js.map +1 -1
- package/dist/core/engine/machine.js +5 -5
- package/dist/core/engine/machine.js.map +1 -1
- package/dist/core/engine/memory.js +2 -2
- package/dist/core/engine/memory.js.map +1 -1
- package/dist/core/engine/registers.js +2 -2
- package/dist/core/engine/registers.js.map +1 -1
- package/dist/core/engine/values/index.d.ts +1 -1
- package/dist/core/engine/values/index.d.ts.map +1 -1
- package/dist/core/engine/values/index.js +1 -1
- package/dist/core/engine/values/index.js.map +1 -1
- package/dist/core/engine/values/value.d.ts +2 -0
- package/dist/core/engine/values/value.d.ts.map +1 -1
- package/dist/core/engine/values/value.js +4 -0
- package/dist/core/engine/values/value.js.map +1 -1
- package/dist/core/stdlib/averages/exponential.d.ts.map +1 -1
- package/dist/core/stdlib/averages/exponential.js +3 -6
- package/dist/core/stdlib/averages/exponential.js.map +1 -1
- package/dist/core/stdlib/averages/gaussian.d.ts +15 -0
- package/dist/core/stdlib/averages/gaussian.d.ts.map +1 -0
- package/dist/core/stdlib/averages/gaussian.js +55 -0
- package/dist/core/stdlib/averages/gaussian.js.map +1 -0
- package/dist/core/stdlib/averages/shaped.d.ts +2 -6
- package/dist/core/stdlib/averages/shaped.d.ts.map +1 -1
- package/dist/core/stdlib/averages/shaped.js +8 -22
- package/dist/core/stdlib/averages/shaped.js.map +1 -1
- package/dist/core/stdlib/maths/arithmetic.d.ts.map +1 -1
- package/dist/core/stdlib/maths/arithmetic.js +2 -1
- package/dist/core/stdlib/maths/arithmetic.js.map +1 -1
- package/dist/core/stdlib/maths/elementary.d.ts +3 -20
- package/dist/core/stdlib/maths/elementary.d.ts.map +1 -1
- package/dist/core/stdlib/maths/elementary.js +17 -13
- package/dist/core/stdlib/maths/elementary.js.map +1 -1
- package/dist/core/stdlib/maths/hypot.d.ts +8 -0
- package/dist/core/stdlib/maths/hypot.d.ts.map +1 -0
- package/dist/core/stdlib/maths/hypot.js +68 -0
- package/dist/core/stdlib/maths/hypot.js.map +1 -0
- package/dist/core/stdlib/maths/power.d.ts +3 -0
- package/dist/core/stdlib/maths/power.d.ts.map +1 -0
- package/dist/core/stdlib/maths/power.js +83 -0
- package/dist/core/stdlib/maths/power.js.map +1 -0
- package/dist/core/stdlib/maths/transcendental.d.ts +15 -0
- package/dist/core/stdlib/maths/transcendental.d.ts.map +1 -0
- package/dist/core/stdlib/maths/transcendental.js +185 -0
- package/dist/core/stdlib/maths/transcendental.js.map +1 -0
- package/dist/core/stdlib/maths/trigonometric.d.ts +4 -0
- package/dist/core/stdlib/maths/trigonometric.d.ts.map +1 -0
- package/dist/core/stdlib/maths/trigonometric.js +196 -0
- package/dist/core/stdlib/maths/trigonometric.js.map +1 -0
- package/dist/core/stdlib/series/conditions.d.ts +2 -4
- package/dist/core/stdlib/series/conditions.d.ts.map +1 -1
- package/dist/core/stdlib/series/conditions.js +9 -13
- package/dist/core/stdlib/series/conditions.js.map +1 -1
- package/dist/core/stdlib/series/statistics.d.ts.map +1 -1
- package/dist/core/stdlib/series/statistics.js +2 -0
- package/dist/core/stdlib/series/statistics.js.map +1 -1
- package/dist/core/stdlib/values/history.d.ts +26 -0
- package/dist/core/stdlib/values/history.d.ts.map +1 -0
- package/dist/core/stdlib/values/history.js +52 -0
- package/dist/core/stdlib/values/history.js.map +1 -0
- package/dist/core/stdlib/values/index.d.ts +1 -0
- package/dist/core/stdlib/values/index.d.ts.map +1 -1
- package/dist/core/stdlib/values/index.js +1 -0
- package/dist/core/stdlib/values/index.js.map +1 -1
- package/dist/core/stdlib/values/lookback.d.ts +4 -1
- package/dist/core/stdlib/values/lookback.d.ts.map +1 -1
- package/dist/core/stdlib/values/lookback.js +48 -45
- package/dist/core/stdlib/values/lookback.js.map +1 -1
- package/dist/core/stdlib/values/region.d.ts +6 -5
- package/dist/core/stdlib/values/region.d.ts.map +1 -1
- package/dist/core/stdlib/values/region.js +3 -3
- package/dist/core/stdlib/values/region.js.map +1 -1
- package/dist/core/stdlib/volatility/measures.d.ts.map +1 -1
- package/dist/core/stdlib/volatility/measures.js +6 -4
- package/dist/core/stdlib/volatility/measures.js.map +1 -1
- package/dist/core/version/version.generated.d.ts +1 -1
- package/dist/core/version/version.generated.js +1 -1
- package/package.json +9 -1
- package/src/core/engine/channels.ts +2 -2
- package/src/core/engine/engine.ts +3 -3
- package/src/core/engine/library/maths.ts +2 -2
- package/src/core/engine/machine.ts +5 -5
- package/src/core/engine/memory.ts +2 -2
- package/src/core/engine/registers.ts +2 -2
- package/src/core/engine/values/index.ts +1 -0
- package/src/core/engine/values/value.ts +5 -0
- package/src/core/stdlib/averages/exponential.ts +3 -4
- package/src/core/stdlib/averages/gaussian.ts +52 -0
- package/src/core/stdlib/averages/shaped.ts +8 -21
- package/src/core/stdlib/maths/arithmetic.ts +2 -1
- package/src/core/stdlib/maths/elementary.ts +20 -33
- package/src/core/stdlib/maths/hypot.ts +63 -0
- package/src/core/stdlib/maths/power.ts +57 -0
- package/src/core/stdlib/maths/transcendental.ts +160 -0
- package/src/core/stdlib/maths/trigonometric.ts +160 -0
- package/src/core/stdlib/series/conditions.ts +9 -12
- package/src/core/stdlib/series/statistics.ts +1 -0
- package/src/core/stdlib/values/history.ts +70 -0
- package/src/core/stdlib/values/index.ts +1 -0
- package/src/core/stdlib/values/lookback.ts +51 -47
- package/src/core/stdlib/values/region.ts +7 -5
- package/src/core/stdlib/volatility/measures.ts +6 -4
- package/src/core/version/version.generated.ts +1 -1
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/** Directed integer enclosures for the real-result recipe in section 20.10.4. */
|
|
2
|
+
import { bitLength, ceil, floor, parts, rounded, type Interval } from './transcendental.js';
|
|
3
|
+
import { integerSqrt } from './hypot.js';
|
|
4
|
+
|
|
5
|
+
export type TrigonometricKind = 'sin' | 'cos' | 'tan' | 'asin' | 'acos' | 'atan' | 'atan2';
|
|
6
|
+
let cachedPi: { precision: number; value: Interval } = { precision: 0, value: [0n, 0n] };
|
|
7
|
+
const negate = (a: Interval): Interval => [-a[1], -a[0]];
|
|
8
|
+
const add = (a: Interval, b: Interval): Interval => [a[0] + b[0], a[1] + b[1]];
|
|
9
|
+
const subtract = (a: Interval, b: Interval): Interval => [a[0] - b[1], a[1] - b[0]];
|
|
10
|
+
const absolute = (n: bigint): bigint => n < 0n ? -n : n;
|
|
11
|
+
|
|
12
|
+
function ratio(x: number): readonly [bigint, bigint] {
|
|
13
|
+
const [mantissa, exponent] = parts(x);
|
|
14
|
+
return exponent >= 0 ? [mantissa << BigInt(exponent), 1n] : [mantissa, 1n << BigInt(-exponent)];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function coarsen(a: Interval, shift: number): Interval {
|
|
18
|
+
const divisor = 1n << BigInt(shift);
|
|
19
|
+
return [floor(a[0], divisor), ceil(a[1], divisor)];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function quotient(a: Interval, b: Interval, precision: number): Interval | null {
|
|
23
|
+
if (b[0] <= 0n && b[1] >= 0n) return null;
|
|
24
|
+
const scale = 1n << BigInt(precision);
|
|
25
|
+
let low: bigint | undefined, high: bigint | undefined;
|
|
26
|
+
for (const numerator of a) for (const denominator of b) {
|
|
27
|
+
const n = denominator < 0n ? -numerator : numerator, d = absolute(denominator);
|
|
28
|
+
const lower = floor(n * scale, d), upper = ceil(n * scale, d);
|
|
29
|
+
if (low === undefined || lower < low) low = lower;
|
|
30
|
+
if (high === undefined || upper > high) high = upper;
|
|
31
|
+
}
|
|
32
|
+
return [low!, high!];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Alternating odd powers, with the first omitted term on its signed side. */
|
|
36
|
+
function smallAtan(n: bigint, d: bigint, precision: number): Interval {
|
|
37
|
+
if (n === 0n) return [0n, 0n];
|
|
38
|
+
if (n < 0n) return negate(smallAtan(-n, d, precision));
|
|
39
|
+
const scale = 1n << BigInt(precision), zl = n * scale / d, zh = ceil(n * scale, d);
|
|
40
|
+
const squaredLow = zl * zl / scale, squaredHigh = ceil(zh * zh, scale);
|
|
41
|
+
let powerLow = zl, powerHigh = zh, low = 0n, high = 0n, odd = 1n, positive = true;
|
|
42
|
+
for (;;) {
|
|
43
|
+
const lower = powerLow / odd, upper = ceil(powerHigh, odd);
|
|
44
|
+
if (positive) { low += lower; high += upper; } else { low -= upper; high -= lower; }
|
|
45
|
+
powerLow = powerLow * squaredLow / scale;
|
|
46
|
+
powerHigh = ceil(powerHigh * squaredHigh, scale);
|
|
47
|
+
odd += 2n; positive = !positive;
|
|
48
|
+
if (powerHigh <= 1n) {
|
|
49
|
+
const tail = ceil(powerHigh, odd);
|
|
50
|
+
return positive ? [low, high + tail] : [low - tail, high];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function pi(precision: number): Interval {
|
|
56
|
+
if (cachedPi.precision >= precision) return coarsen(cachedPi.value, cachedPi.precision - precision);
|
|
57
|
+
const a = smallAtan(1n, 5n, precision), b = smallAtan(1n, 239n, precision);
|
|
58
|
+
const value: Interval = [16n * a[0] - 4n * b[1], 16n * a[1] - 4n * b[0]];
|
|
59
|
+
cachedPi = { precision, value };
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function positiveAtan(n: bigint, d: bigint, precision: number): Interval {
|
|
64
|
+
if (n === 0n) return [0n, 0n];
|
|
65
|
+
if (d === 0n) return coarsen(pi(precision), 1);
|
|
66
|
+
if (n > d) return subtract(coarsen(pi(precision), 1), positiveAtan(d, n, precision));
|
|
67
|
+
if (2n * n > d) return add(coarsen(pi(precision), 2), smallAtan(n - d, n + d, precision));
|
|
68
|
+
return smallAtan(n, d, precision);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function pointSeries(z: bigint, precision: number, cosine: boolean): Interval {
|
|
72
|
+
if (z < 0n) {
|
|
73
|
+
const out = pointSeries(-z, precision, cosine);
|
|
74
|
+
return cosine ? out : negate(out);
|
|
75
|
+
}
|
|
76
|
+
const scale = 1n << BigInt(precision);
|
|
77
|
+
if (z === 0n) return cosine ? [scale, scale] : [0n, 0n];
|
|
78
|
+
const squaredLow = z * z / scale, squaredHigh = ceil(z * z, scale);
|
|
79
|
+
let low = cosine ? scale : z, high = low, termLow = low, termHigh = low;
|
|
80
|
+
let degree = cosine ? 0n : 1n, positive = false;
|
|
81
|
+
for (;;) {
|
|
82
|
+
const divisor = scale * (degree + 1n) * (degree + 2n);
|
|
83
|
+
termLow = termLow * squaredLow / divisor;
|
|
84
|
+
termHigh = ceil(termHigh * squaredHigh, divisor);
|
|
85
|
+
degree += 2n;
|
|
86
|
+
if (termHigh <= 1n) return positive ? [low, high + termHigh] : [low - termHigh, high];
|
|
87
|
+
if (positive) { low += termLow; high += termHigh; } else { low -= termHigh; high -= termLow; }
|
|
88
|
+
positive = !positive;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function reducedSinCos(x: number, precision: number): readonly [Interval, Interval] | null {
|
|
93
|
+
const [n, d] = ratio(x);
|
|
94
|
+
const reductionPrecision = precision + Math.max(0, bitLength(n) - bitLength(d)) + 32;
|
|
95
|
+
const scale = 1n << BigInt(reductionPrecision), [pl, ph] = pi(reductionPrecision);
|
|
96
|
+
const ql = (4n * n * scale + d * ph) / (2n * d * ph);
|
|
97
|
+
const qh = (4n * n * scale + d * pl) / (2n * d * pl);
|
|
98
|
+
if (ql !== qh) return null;
|
|
99
|
+
const residual: Interval = [n * scale / d - ceil(ql * ph, 2n), ceil(n * scale, d) - ql * pl / 2n];
|
|
100
|
+
const [lower, upper] = coarsen(residual, reductionPrecision - precision);
|
|
101
|
+
const unit = 1n << BigInt(precision);
|
|
102
|
+
if (lower < -unit || upper > unit) return null;
|
|
103
|
+
let sine: Interval = [pointSeries(lower, precision, false)[0], pointSeries(upper, precision, false)[1]];
|
|
104
|
+
const a = absolute(lower), b = absolute(upper), far = a > b ? a : b;
|
|
105
|
+
const near = lower <= 0n && upper >= 0n ? 0n : a < b ? a : b;
|
|
106
|
+
let cosine: Interval = [pointSeries(far, precision, true)[0], pointSeries(near, precision, true)[1]];
|
|
107
|
+
switch (Number(ql % 4n)) {
|
|
108
|
+
case 1: [sine, cosine] = [cosine, negate(sine)]; break;
|
|
109
|
+
case 2: [sine, cosine] = [negate(sine), negate(cosine)]; break;
|
|
110
|
+
case 3: [sine, cosine] = [negate(cosine), sine]; break;
|
|
111
|
+
}
|
|
112
|
+
return [x < 0 ? negate(sine) : sine, cosine];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function inverse(kind: TrigonometricKind, x: number, y: number, precision: number): Interval {
|
|
116
|
+
if (kind === 'atan') {
|
|
117
|
+
const [n, d] = ratio(x), out = positiveAtan(n, d, precision);
|
|
118
|
+
return x < 0 ? negate(out) : out;
|
|
119
|
+
}
|
|
120
|
+
if (kind === 'atan2') {
|
|
121
|
+
if (x === 0) return y > 0 ? coarsen(pi(precision), 1) : negate(coarsen(pi(precision), 1));
|
|
122
|
+
const [yn, yd] = ratio(y), [xn, xd] = ratio(x);
|
|
123
|
+
let out = positiveAtan(yn * xd, xn * yd, precision);
|
|
124
|
+
if (x < 0) out = subtract(pi(precision), out);
|
|
125
|
+
return y < 0 ? negate(out) : out;
|
|
126
|
+
}
|
|
127
|
+
const [n, d] = ratio(x), scale = 1n << BigInt(precision);
|
|
128
|
+
const radicand = (d * d - n * n) * scale * scale, root = integerSqrt(radicand / (d * d));
|
|
129
|
+
const upper = root + (root * root * d * d === radicand ? 0n : 1n);
|
|
130
|
+
if (kind === 'asin') {
|
|
131
|
+
const out: Interval = [positiveAtan(n * scale, d * upper, precision)[0], positiveAtan(n * scale, d * root, precision)[1]];
|
|
132
|
+
return x < 0 ? negate(out) : out;
|
|
133
|
+
}
|
|
134
|
+
const out: Interval = [positiveAtan(root * d, n * scale, precision)[0], positiveAtan(upper * d, n * scale, precision)[1]];
|
|
135
|
+
return x < 0 ? subtract(pi(precision), out) : out;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** The ordinary argument order is preserved, including atan2(y,x). */
|
|
139
|
+
export function trigonometric(kind: TrigonometricKind, first: number, second?: number, start = 160): number | null {
|
|
140
|
+
if (!Number.isFinite(first) || kind === 'atan2' && !Number.isFinite(second)) return null;
|
|
141
|
+
const x = kind === 'atan2' ? second! : first, y = kind === 'atan2' ? first : 0;
|
|
142
|
+
if ((kind === 'asin' || kind === 'acos') && Math.abs(x) > 1) return null;
|
|
143
|
+
if (kind === 'atan2' && y === 0 && x >= 0) return 0;
|
|
144
|
+
if (x === 0 && kind !== 'atan2') {
|
|
145
|
+
if (kind === 'cos') return 1;
|
|
146
|
+
if (kind !== 'acos') return 0;
|
|
147
|
+
}
|
|
148
|
+
if (kind === 'acos' && x === 1) return 0;
|
|
149
|
+
for (let precision = Math.max(64, start);; precision += 80) {
|
|
150
|
+
let interval: Interval | null;
|
|
151
|
+
if (kind === 'sin' || kind === 'cos' || kind === 'tan') {
|
|
152
|
+
const both = reducedSinCos(x, precision);
|
|
153
|
+
interval = both === null ? null : kind === 'sin' ? both[0] : kind === 'cos' ? both[1] : quotient(both[0], both[1], precision);
|
|
154
|
+
} else interval = inverse(kind, x, y, precision);
|
|
155
|
+
if (interval !== null) {
|
|
156
|
+
const left = rounded(interval[0], -precision), right = rounded(interval[1], -precision);
|
|
157
|
+
if (left === right) return left;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* happened on this bar", which is the one answer a reader would act on.
|
|
7
7
|
*/
|
|
8
8
|
import type { Flag, Flags, Series, StateRecord, Tail, Value } from '../values/index.js';
|
|
9
|
-
import { NONE, fold,
|
|
9
|
+
import { ContributionHistory, NONE, fold, slot, tailOf } from '../values/index.js';
|
|
10
10
|
|
|
11
11
|
/** `barsSince(cond)`: bars since the condition last held, 0 on the bar itself. */
|
|
12
12
|
export function barsSinceStep(state: StateRecord, key: string, cond: Flag): Value {
|
|
@@ -37,10 +37,8 @@ export interface Occasion {
|
|
|
37
37
|
* `valueWhen(cond, src, occurrence)`: `src` as it stood the last time the
|
|
38
38
|
* condition held, or the one before that.
|
|
39
39
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* queue grew with the dataset would not be copyable in the sense
|
|
43
|
-
* `compiled-program.md` 2.11 requires.
|
|
40
|
+
* A later occurrence can name any earlier true event. Immutable history keeps
|
|
41
|
+
* those values, including absence, while checkpoints share the sealed prefix.
|
|
44
42
|
*/
|
|
45
43
|
export function valueWhenStep(
|
|
46
44
|
state: StateRecord,
|
|
@@ -49,15 +47,14 @@ export function valueWhenStep(
|
|
|
49
47
|
occurrence: number,
|
|
50
48
|
): Value {
|
|
51
49
|
if (!Number.isInteger(occurrence) || occurrence < 0) return NONE;
|
|
52
|
-
const
|
|
53
|
-
|
|
50
|
+
const held = state[key];
|
|
51
|
+
let hits = held instanceof ContributionHistory ? held : new ContributionHistory();
|
|
54
52
|
if (input.cond === true) {
|
|
55
|
-
hits.
|
|
56
|
-
|
|
53
|
+
hits = hits.append(input.src);
|
|
54
|
+
state[key] = hits;
|
|
57
55
|
}
|
|
58
|
-
if (hits.
|
|
59
|
-
|
|
60
|
-
return typeof first === 'number' ? first : NONE;
|
|
56
|
+
if (hits.count <= occurrence) return NONE;
|
|
57
|
+
return hits.view(occurrence + 1).at(occurrence);
|
|
61
58
|
}
|
|
62
59
|
|
|
63
60
|
/** `valueWhen(cond, src, occurrence)` as a tail. */
|
|
@@ -157,6 +157,7 @@ function jointStep(
|
|
|
157
157
|
const b = ordered((back) => right.at(back), len);
|
|
158
158
|
const m = moments(a, b, len);
|
|
159
159
|
if (want === 'covariance') return result(m.covariance);
|
|
160
|
+
if (!Number.isFinite(m.varianceA) || !Number.isFinite(m.varianceB)) return NONE;
|
|
160
161
|
const scale = Math.sqrt(m.varianceA) * Math.sqrt(m.varianceB);
|
|
161
162
|
if (scale === 0) return NONE;
|
|
162
163
|
return result(m.covariance / scale);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/** Immutable contribution chunks, shared by state checkpoints and independent forks. */
|
|
2
|
+
import type { Value } from './value.js';
|
|
3
|
+
|
|
4
|
+
const WIDTH = 32;
|
|
5
|
+
|
|
6
|
+
interface Chunk {
|
|
7
|
+
readonly values: readonly Value[];
|
|
8
|
+
readonly prior: Chunk | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Appending copies at most one short tail, never the contributed prefix. */
|
|
12
|
+
export class ContributionHistory {
|
|
13
|
+
readonly head: Chunk | null;
|
|
14
|
+
readonly tail: readonly Value[];
|
|
15
|
+
readonly count: number;
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
head: Chunk | null = null,
|
|
19
|
+
tail: readonly Value[] = Object.freeze([]),
|
|
20
|
+
count = 0,
|
|
21
|
+
) {
|
|
22
|
+
this.head = head;
|
|
23
|
+
this.tail = tail;
|
|
24
|
+
this.count = count;
|
|
25
|
+
Object.freeze(this);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
append(value: Value): ContributionHistory {
|
|
29
|
+
const full = this.tail.length === WIDTH;
|
|
30
|
+
const head = full ? Object.freeze({ values: this.tail, prior: this.head }) : this.head;
|
|
31
|
+
const tail = Object.freeze(full ? [value] : [...this.tail, value]);
|
|
32
|
+
return new ContributionHistory(head, tail, this.count + 1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Index chunk references once; each subsequent newest-first read is constant work. */
|
|
36
|
+
view(length: number): ContributionView {
|
|
37
|
+
const size = Math.min(length, this.count);
|
|
38
|
+
const chunks: (readonly Value[])[] = [];
|
|
39
|
+
let remaining = size - this.tail.length;
|
|
40
|
+
let head = this.head;
|
|
41
|
+
while (remaining > 0 && head !== null) {
|
|
42
|
+
chunks.push(head.values);
|
|
43
|
+
head = head.prior;
|
|
44
|
+
remaining -= WIDTH;
|
|
45
|
+
}
|
|
46
|
+
return new ContributionView(this.tail, chunks, size);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** A transient index into the current window. It is not retained in state. */
|
|
51
|
+
export class ContributionView {
|
|
52
|
+
private readonly tail: readonly Value[];
|
|
53
|
+
private readonly chunks: readonly (readonly Value[])[];
|
|
54
|
+
readonly size: number;
|
|
55
|
+
|
|
56
|
+
constructor(
|
|
57
|
+
tail: readonly Value[], chunks: readonly (readonly Value[])[], size: number,
|
|
58
|
+
) {
|
|
59
|
+
this.tail = tail;
|
|
60
|
+
this.chunks = chunks;
|
|
61
|
+
this.size = size;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
at(back: number): Value {
|
|
65
|
+
if (back < 0 || back >= this.size || !Number.isInteger(back)) return null;
|
|
66
|
+
if (back < this.tail.length) return this.tail[this.tail.length - 1 - back]!;
|
|
67
|
+
const before = back - this.tail.length;
|
|
68
|
+
return this.chunks[Math.floor(before / WIDTH)]![WIDTH - 1 - before % WIDTH]!;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -3,6 +3,7 @@ export { NONE, at, isLength, isPresent, result } from './value.js';
|
|
|
3
3
|
|
|
4
4
|
export type { StateField, StateRecord, StateSlot } from './region.js';
|
|
5
5
|
export { copyState, flag, held, newState, queue, slot } from './region.js';
|
|
6
|
+
export { ContributionHistory, ContributionView } from './history.js';
|
|
6
7
|
|
|
7
8
|
export type { Tail } from './tail.js';
|
|
8
9
|
export { fold, tailOf } from './tail.js';
|
|
@@ -30,8 +30,9 @@
|
|
|
30
30
|
* the length the script asked for rather than by how much history is loaded,
|
|
31
31
|
* and that is the property a live chart actually needs.
|
|
32
32
|
*/
|
|
33
|
-
import type {
|
|
34
|
-
import { newState } from './region.js';
|
|
33
|
+
import type { StateRecord } from './region.js';
|
|
34
|
+
import { newState, slot } from './region.js';
|
|
35
|
+
import { ContributionHistory } from './history.js';
|
|
35
36
|
import type { Value } from './value.js';
|
|
36
37
|
import { NONE, isLength, isPresent, result } from './value.js';
|
|
37
38
|
|
|
@@ -39,7 +40,7 @@ import { NONE, isLength, isPresent, result } from './value.js';
|
|
|
39
40
|
export interface Lookback {
|
|
40
41
|
/** Advance by one bar. */
|
|
41
42
|
push(value: Value): void;
|
|
42
|
-
/** True
|
|
43
|
+
/** True after the largest observed length's contributions, present or not. */
|
|
43
44
|
filled(): boolean;
|
|
44
45
|
/** True when the lookback is filled and every bar in it has a value. */
|
|
45
46
|
complete(): boolean;
|
|
@@ -64,48 +65,28 @@ export interface Lookback {
|
|
|
64
65
|
*/
|
|
65
66
|
export function ring(record: StateRecord, key: string, len: number | null): Lookback {
|
|
66
67
|
const size = len !== null && isLength(len) ? len : 0;
|
|
67
|
-
const bars = `${key}:
|
|
68
|
-
const
|
|
69
|
-
const seenKey = `${key}:s`;
|
|
68
|
+
const bars = `${key}:h`;
|
|
69
|
+
const maximumKey = `${key}:m`;
|
|
70
70
|
const held = record[bars];
|
|
71
|
-
let
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
record[
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const value = record[name];
|
|
83
|
-
return typeof value === 'number' ? value : 0;
|
|
84
|
-
};
|
|
85
|
-
const index = (back: number): number => {
|
|
86
|
-
const from = count(nextKey) - 1 - back;
|
|
87
|
-
return ((from % size) + size) % size;
|
|
88
|
-
};
|
|
71
|
+
let history = held instanceof ContributionHistory ? held : new ContributionHistory();
|
|
72
|
+
const before = record[maximumKey];
|
|
73
|
+
const maximum = Math.max(typeof before === 'number' ? before : 0, size);
|
|
74
|
+
record[maximumKey] = maximum;
|
|
75
|
+
let view = history.view(size);
|
|
76
|
+
return lookback(size, (value) => {
|
|
77
|
+
history = history.append(value);
|
|
78
|
+
record[bars] = history;
|
|
79
|
+
view = history.view(size);
|
|
80
|
+
}, () => size > 0 && history.count >= maximum, (back) => view.at(back));
|
|
81
|
+
}
|
|
89
82
|
|
|
83
|
+
/** The arithmetic is shared so fixed-length accumulation keeps its exact order. */
|
|
84
|
+
function lookback(
|
|
85
|
+
size: number, push: (value: Value) => void, filled: () => boolean,
|
|
86
|
+
at: (back: number) => Value,
|
|
87
|
+
): Lookback {
|
|
90
88
|
const lookback: Lookback = {
|
|
91
|
-
push
|
|
92
|
-
if (size === 0) return;
|
|
93
|
-
items[count(nextKey)] = value;
|
|
94
|
-
const next = count(nextKey) + 1;
|
|
95
|
-
record[nextKey] = next === size ? 0 : next;
|
|
96
|
-
if (count(seenKey) < size) record[seenKey] = count(seenKey) + 1;
|
|
97
|
-
},
|
|
98
|
-
|
|
99
|
-
filled(): boolean {
|
|
100
|
-
return size > 0 && count(seenKey) === size;
|
|
101
|
-
},
|
|
102
|
-
|
|
103
|
-
at(back: number): Value {
|
|
104
|
-
if (size === 0 || back < 0 || back >= size) return NONE;
|
|
105
|
-
const value = items[index(back)];
|
|
106
|
-
return typeof value === 'number' ? value : NONE;
|
|
107
|
-
},
|
|
108
|
-
|
|
89
|
+
push, filled, at,
|
|
109
90
|
presentCount(): number {
|
|
110
91
|
let counted = 0;
|
|
111
92
|
for (let back = size - 1; back >= 0; back -= 1) {
|
|
@@ -170,6 +151,9 @@ export type Recurrence = (previous: number, value: number) => number;
|
|
|
170
151
|
* next present bar continues from where the last one left off. The alternatives
|
|
171
152
|
* are worse: consuming absence as zero would drag the average toward nothing,
|
|
172
153
|
* and re-seeding would let one missing bar restart a two hundred bar average.
|
|
154
|
+
* Missing lengths also freeze it. Readiness tracks all contributions and the
|
|
155
|
+
* largest observed length, independently from the running value. A valid step
|
|
156
|
+
* still advances that value while a later, larger length hides its output.
|
|
173
157
|
*/
|
|
174
158
|
export function smoothed(
|
|
175
159
|
record: StateRecord,
|
|
@@ -178,21 +162,41 @@ export function smoothed(
|
|
|
178
162
|
value: Value,
|
|
179
163
|
step: Recurrence,
|
|
180
164
|
): Value {
|
|
181
|
-
const
|
|
165
|
+
const countKey = `${key}:n`;
|
|
166
|
+
const maximumKey = `${key}:m`;
|
|
167
|
+
const historyKey = `${key}~:h`;
|
|
182
168
|
const runningKey = `${key}:r`;
|
|
183
169
|
const startedKey = `${key}:o`;
|
|
170
|
+
const valid = len !== null && isLength(len);
|
|
171
|
+
const count = slot(record, countKey, 0) + 1;
|
|
172
|
+
const maximum = Math.max(slot(record, maximumKey, 0), valid ? len : 0);
|
|
173
|
+
record[countKey] = count;
|
|
174
|
+
record[maximumKey] = maximum;
|
|
184
175
|
if (record[startedKey] !== true) {
|
|
185
|
-
|
|
186
|
-
const
|
|
176
|
+
const held = record[historyKey];
|
|
177
|
+
const before = held instanceof ContributionHistory ? held : new ContributionHistory();
|
|
178
|
+
const history = before.append(value);
|
|
179
|
+
record[historyKey] = history;
|
|
180
|
+
if (!valid || count < maximum) return NONE;
|
|
181
|
+
const view = history.view(len);
|
|
182
|
+
let total = 0;
|
|
183
|
+
for (let back = len - 1; back >= 0; back -= 1) {
|
|
184
|
+
const next = view.at(back);
|
|
185
|
+
if (!isPresent(next)) return NONE;
|
|
186
|
+
total += next;
|
|
187
|
+
}
|
|
188
|
+
const mean = result(total / len);
|
|
187
189
|
if (!isPresent(mean)) return NONE;
|
|
188
190
|
record[startedKey] = true;
|
|
189
191
|
record[runningKey] = mean;
|
|
192
|
+
// Checkpoints retain their immutable version; this live state needs only scalars.
|
|
193
|
+
delete record[historyKey];
|
|
190
194
|
return result(mean);
|
|
191
195
|
}
|
|
192
|
-
if (!isPresent(value)) return NONE;
|
|
196
|
+
if (!valid || !isPresent(value)) return NONE;
|
|
193
197
|
const previous = record[runningKey];
|
|
194
198
|
if (typeof previous !== 'number') return NONE;
|
|
195
199
|
const next = step(previous, value);
|
|
196
200
|
record[runningKey] = next;
|
|
197
|
-
return result(next);
|
|
201
|
+
return count >= maximum ? result(next) : NONE;
|
|
198
202
|
}
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* is what 6.2 refuses in as many words: an engine does not re-seed an average
|
|
12
12
|
* from history, it puts back the record it copied.
|
|
13
13
|
*
|
|
14
|
-
* So a region is a flat record of
|
|
15
|
-
*
|
|
14
|
+
* So a region is a flat record of scalars, bounded mutable queues and immutable
|
|
15
|
+
* contribution history, and `copyState` is the mechanical copy. Every function in this
|
|
16
16
|
* library is written against one, and the tail form in `tail.ts` is the same
|
|
17
17
|
* step over a region nobody else can reach. That is what lets the one
|
|
18
18
|
* implementation serve both a library call folded over a series and an engine
|
|
@@ -23,11 +23,13 @@
|
|
|
23
23
|
* of them.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
import type { ContributionHistory } from './history.js';
|
|
27
|
+
|
|
28
|
+
/** One scalar field of a state region. */
|
|
27
29
|
export type StateField = number | boolean | string | null;
|
|
28
30
|
|
|
29
31
|
/** What a single key holds. */
|
|
30
|
-
export type StateSlot = StateField | StateField[];
|
|
32
|
+
export type StateSlot = StateField | StateField[] | ContributionHistory;
|
|
31
33
|
|
|
32
34
|
/** A state region: a flat record, copyable without knowing what owns it. */
|
|
33
35
|
export type StateRecord = Record<string, StateSlot | undefined>;
|
|
@@ -37,7 +39,7 @@ export function newState(): StateRecord {
|
|
|
37
39
|
return {};
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
/**
|
|
42
|
+
/** Mutable queues are cloned; immutable history versions are safely shared. */
|
|
41
43
|
export function copyState(record: StateRecord): StateRecord {
|
|
42
44
|
const out: StateRecord = {};
|
|
43
45
|
for (const key of Object.keys(record)) {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import type { Bar, Series, StateRecord, Tail, Value } from '../values/index.js';
|
|
10
10
|
import { NONE, fold, isPresent, result, tailOf } from '../values/index.js';
|
|
11
11
|
import { extremeStep, historyStep, sumStep } from '../series/index.js';
|
|
12
|
+
import { log, log10 } from '../maths/index.js';
|
|
12
13
|
|
|
13
14
|
import type { Gap } from './range.js';
|
|
14
15
|
import { gapOf, trueRangeOf } from './range.js';
|
|
@@ -41,9 +42,10 @@ export function chopStep(
|
|
|
41
42
|
if (len === null) return NONE;
|
|
42
43
|
if (!isPresent(distance) || !isPresent(upper) || !isPresent(lower)) return NONE;
|
|
43
44
|
const span = upper - lower;
|
|
44
|
-
const scale =
|
|
45
|
-
if (!(span > 0) || !(distance > 0) || scale === 0) return NONE;
|
|
46
|
-
|
|
45
|
+
const scale = log10(len);
|
|
46
|
+
if (!(span > 0) || !(distance > 0) || !isPresent(scale) || scale === 0) return NONE;
|
|
47
|
+
const travelled = log10(distance / span);
|
|
48
|
+
return isPresent(travelled) ? result((100 * travelled) / scale) : NONE;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
/** `chop(len)` as a tail. */
|
|
@@ -78,7 +80,7 @@ export function hvStep(
|
|
|
78
80
|
const previous = historyStep(state, `${key}h`, value, 1);
|
|
79
81
|
let logReturn: Value = NONE;
|
|
80
82
|
if (isPresent(value) && isPresent(previous) && value > 0 && previous > 0) {
|
|
81
|
-
logReturn =
|
|
83
|
+
logReturn = log(value / previous);
|
|
82
84
|
}
|
|
83
85
|
const deviation = stdevStep(state, `${key}q`, logReturn, len, false);
|
|
84
86
|
if (!isPresent(deviation) || periodsPerYear === null || !(periodsPerYear > 0)) return NONE;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// spec/compiled-program.md, so neither can drift from the thing it names.
|
|
5
5
|
|
|
6
6
|
/** This build's package version. The release refuses to publish unless the tag, the manifest and this agree. */
|
|
7
|
-
export const VERSION = "0.
|
|
7
|
+
export const VERSION = "0.7.0";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* The compiled program format this compiler emits.
|