oakscriptjs 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/cjs/array/index.d.ts +1 -1
  2. package/dist/cjs/array/index.d.ts.map +1 -1
  3. package/dist/cjs/array/index.js +2 -1
  4. package/dist/cjs/array/index.js.map +2 -2
  5. package/dist/cjs/callsite/index.d.ts +27 -0
  6. package/dist/cjs/callsite/index.d.ts.map +1 -1
  7. package/dist/cjs/callsite/index.js +47 -0
  8. package/dist/cjs/callsite/index.js.map +2 -2
  9. package/dist/cjs/color/index.d.ts +1 -0
  10. package/dist/cjs/color/index.d.ts.map +1 -1
  11. package/dist/cjs/color/index.js +9 -30
  12. package/dist/cjs/color/index.js.map +2 -2
  13. package/dist/cjs/color/parse.d.ts +29 -0
  14. package/dist/cjs/color/parse.d.ts.map +1 -0
  15. package/dist/cjs/color/parse.js +57 -0
  16. package/dist/cjs/color/parse.js.map +7 -0
  17. package/dist/cjs/index.d.ts +1 -1
  18. package/dist/cjs/index.js +1 -1
  19. package/dist/cjs/index.js.map +1 -1
  20. package/dist/cjs/script/index.d.ts +4 -0
  21. package/dist/cjs/script/index.d.ts.map +1 -1
  22. package/dist/cjs/script/index.js +6 -1
  23. package/dist/cjs/script/index.js.map +2 -2
  24. package/dist/cjs/ta/index.d.ts +5 -0
  25. package/dist/cjs/ta/index.d.ts.map +1 -1
  26. package/dist/cjs/ta/index.js +19 -19
  27. package/dist/cjs/ta/index.js.map +2 -2
  28. package/dist/esm/array/index.d.ts +1 -1
  29. package/dist/esm/array/index.d.ts.map +1 -1
  30. package/dist/esm/array/index.js +3 -2
  31. package/dist/esm/array/index.js.map +1 -1
  32. package/dist/esm/callsite/index.d.ts +27 -0
  33. package/dist/esm/callsite/index.d.ts.map +1 -1
  34. package/dist/esm/callsite/index.js +79 -0
  35. package/dist/esm/callsite/index.js.map +1 -1
  36. package/dist/esm/color/index.d.ts +1 -0
  37. package/dist/esm/color/index.d.ts.map +1 -1
  38. package/dist/esm/color/index.js +13 -36
  39. package/dist/esm/color/index.js.map +1 -1
  40. package/dist/esm/color/parse.d.ts +29 -0
  41. package/dist/esm/color/parse.d.ts.map +1 -0
  42. package/dist/esm/color/parse.js +55 -0
  43. package/dist/esm/color/parse.js.map +1 -0
  44. package/dist/esm/index.d.ts +1 -1
  45. package/dist/esm/index.js +1 -1
  46. package/dist/esm/script/index.d.ts +4 -0
  47. package/dist/esm/script/index.d.ts.map +1 -1
  48. package/dist/esm/script/index.js +6 -1
  49. package/dist/esm/script/index.js.map +1 -1
  50. package/dist/esm/ta/index.d.ts +5 -0
  51. package/dist/esm/ta/index.d.ts.map +1 -1
  52. package/dist/esm/ta/index.js +34 -28
  53. package/dist/esm/ta/index.js.map +1 -1
  54. package/package.json +1 -1
  55. package/src/array/index.ts +3 -2
  56. package/src/callsite/index.ts +74 -0
  57. package/src/color/index.ts +14 -36
  58. package/src/color/parse.ts +56 -0
  59. package/src/index.ts +1 -1
  60. package/src/script/index.ts +6 -1
  61. package/src/ta/index.ts +32 -31
package/src/index.ts CHANGED
@@ -164,7 +164,7 @@ export type { LightweightSeriesDefinitions } from './runtime/adapters/Lightweigh
164
164
  export { SimpleInputAdapter } from './runtime/adapters/SimpleInputAdapter.js';
165
165
 
166
166
  // Version
167
- export const VERSION = '0.9.0';
167
+ export const VERSION = '0.9.2';
168
168
 
169
169
  /**
170
170
  * Library information
@@ -58,6 +58,7 @@ import type {
58
58
  import { BarData, Series } from '../runtime/series.js';
59
59
  import * as taSeries from '../ta-series.js';
60
60
  import * as colorCore from '../color/index.js';
61
+ import { inputColorDefault } from '../color/parse.js';
61
62
  import { fixnan as fixnanValues, isNA, nz } from '../utils/index.js';
62
63
  import * as lineCore from '../line/index.js';
63
64
  import * as labelCore from '../label/index.js';
@@ -404,8 +405,12 @@ export const input = {
404
405
  string(defval: string, title?: string, opts: StringInputOptions = {}): string {
405
406
  return registerInput<string>({ type: 'string', defval, title, options: opts.options, ...common(opts) }, 'input_string');
406
407
  },
408
+ /**
409
+ * Declares a colour input. As in PineScript, the default keeps an alpha of 2 decimals: an input default
410
+ * `color.new(c, 90)` has the alpha byte 26 (25 in the script). A value given by the host is not changed.
411
+ */
407
412
  color(defval: string, title?: string, opts: InputOptions = {}): string {
408
- return registerInput<string>({ type: 'color', defval, title, ...common(opts) }, 'input_color');
413
+ return registerInput<string>({ type: 'color', defval: inputColorDefault(defval), title, ...common(opts) }, 'input_color');
409
414
  },
410
415
  /** Declares a source input and returns it as a Series. */
411
416
  source(defval: string = 'close', title?: string, opts: InputOptions = {}): Series {
package/src/ta/index.ts CHANGED
@@ -127,6 +127,8 @@ export function ema(source: Source, length: simple_int): series_float {
127
127
  * - RSI values below 30 typically indicate oversold conditions
128
128
  * - Uses `ta.rma()` (Relative Moving Average) for smoothing, matching PineScript v6
129
129
  * - Formula: RSI = 100 - (100 / (1 + RS)), where RS = Average Gain / Average Loss
130
+ * - As in PineScript (`down == 0 ? 100 : up == 0 ? 0 : ...`): 100 when the average loss is 0, else 0 when the
131
+ * average gain is 0, both with the 1e-10 tolerance of the comparison operators
130
132
  * - Average Gain and Average Loss are calculated using RMA (alpha = 1 / length)
131
133
  * - `na` values in the source series are ignored
132
134
  *
@@ -158,8 +160,10 @@ export function rsi(source: Source, length: simple_int): series_float {
158
160
  result.push(NaN); // First value is NaN
159
161
 
160
162
  for (let i = 0; i < avgGains.length; i++) {
161
- if (avgLosses[i]! === 0) {
163
+ if (eq(avgLosses[i]!, 0)) {
162
164
  result.push(100);
165
+ } else if (eq(avgGains[i]!, 0)) {
166
+ result.push(0);
163
167
  } else {
164
168
  const rs = avgGains[i]! / avgLosses[i]!;
165
169
  result.push(100 - (100 / (1 + rs)));
@@ -569,6 +573,7 @@ export function atr(length: simple_int, high?: Source, low?: Source, close?: Sou
569
573
  * - Direction: 1 = downtrend (red), -1 = uptrend (green)
570
574
  * - Uses hl2 (average of high and low) as the source
571
575
  * - SuperTrend helps identify the current market trend and potential reversal points
576
+ * - As in PineScript (`nz` of the previous bands): 0 on bar 0, na on the next bars while the ATR is na
572
577
  *
573
578
  * @example
574
579
  * ```typescript
@@ -608,41 +613,35 @@ export function supertrend(
608
613
  // Calculate ATR
609
614
  const atrValues = atr(atrPeriod, high, low, close);
610
615
 
611
- // Track previous values across iterations
612
- let prevLowerBand = NaN;
613
- let prevUpperBand = NaN;
616
+ // PineScript reference implementation (bar by bar):
617
+ // prevLowerBand = nz(lowerBand[1]), prevUpperBand = nz(upperBand[1])
618
+ // lowerBand := lowerBand > prevLowerBand or close[1] < prevLowerBand ? lowerBand : prevLowerBand
619
+ // upperBand := upperBand < prevUpperBand or close[1] > prevUpperBand ? upperBand : prevUpperBand
620
+ // direction = na(atr[1]) ? 1 : prevSuperTrend == prevUpperBand ? (close > upperBand ? -1 : 1) : (close < lowerBand ? 1 : -1)
621
+ // A comparison with na is false: on bar 0 (close[1] na) both bands take nz(na) = 0, so the supertrend is 0.
622
+ const nz = (v: number) => (isNaN(v) ? 0 : v);
623
+ let lastLowerBand = NaN;
624
+ let lastUpperBand = NaN;
614
625
  let prevSuperTrend = NaN;
615
626
 
616
627
  for (let i = 0; i < source.length; i++) {
617
628
  const atrValue = atrValues[i]! * factor;
618
-
619
- // Skip calculation if ATR is not available yet
620
- if (isNaN(atrValue)) {
621
- supertrendValues.push(NaN);
622
- directions.push(1);
623
- continue;
624
- }
625
-
626
- // Calculate initial bands
627
629
  let upperBand = source[i]! + atrValue;
628
630
  let lowerBand = source[i]! - atrValue;
631
+ const prevLowerBand = nz(lastLowerBand);
632
+ const prevUpperBand = nz(lastUpperBand);
629
633
 
630
- // Determine which price to use for comparison
634
+ // Price compared with the bands: close, or low / high with wicks
631
635
  const highPrice = wicks ? high[i]! : close[i]!;
632
636
  const lowPrice = wicks ? low[i]! : close[i]!;
633
- const prevLowPrice = i > 0 ? (wicks ? low[i - 1]! : close[i - 1]!) : 0;
634
- const prevHighPrice = i > 0 ? (wicks ? high[i - 1]! : close[i - 1]!) : 0;
637
+ const prevLowPrice = i > 0 ? (wicks ? low[i - 1]! : close[i - 1]!) : NaN;
638
+ const prevHighPrice = i > 0 ? (wicks ? high[i - 1]! : close[i - 1]!) : NaN;
635
639
 
636
- // Update bands conditionally (trailing behavior) - only if previous bands are valid
637
- if (i > 0 && !isNaN(prevLowerBand) && !isNaN(prevUpperBand)) {
638
- lowerBand = (lowerBand > prevLowerBand || prevLowPrice < prevLowerBand) ? lowerBand : prevLowerBand;
639
- upperBand = (upperBand < prevUpperBand || prevHighPrice > prevUpperBand) ? upperBand : prevUpperBand;
640
- }
640
+ lowerBand = lowerBand > prevLowerBand || prevLowPrice < prevLowerBand ? lowerBand : prevLowerBand;
641
+ upperBand = upperBand < prevUpperBand || prevHighPrice > prevUpperBand ? upperBand : prevUpperBand;
641
642
 
642
- // Determine trend direction
643
643
  let currentDirection: int;
644
- if (isNaN(prevSuperTrend)) {
645
- // Initial direction when we don't have previous supertrend
644
+ if (i === 0 || isNaN(atrValues[i - 1]!)) {
646
645
  currentDirection = 1;
647
646
  } else if (prevSuperTrend === prevUpperBand) {
648
647
  // Was in downtrend (following upper band)
@@ -652,15 +651,12 @@ export function supertrend(
652
651
  currentDirection = lowPrice < lowerBand ? 1 : -1;
653
652
  }
654
653
 
655
- // Calculate supertrend value based on direction
656
654
  const superTrendValue = currentDirection === -1 ? lowerBand : upperBand;
657
-
658
655
  supertrendValues.push(superTrendValue);
659
656
  directions.push(currentDirection);
660
657
 
661
- // Update previous values for next iteration
662
- prevLowerBand = lowerBand;
663
- prevUpperBand = upperBand;
658
+ lastLowerBand = lowerBand;
659
+ lastUpperBand = upperBand;
664
660
  prevSuperTrend = superTrendValue;
665
661
  }
666
662
 
@@ -1476,6 +1472,7 @@ export function percentrank(source: Source, length: simple_int): series_float {
1476
1472
  * - Typical Price = (High + Low + Close) / 3
1477
1473
  * - Mean Deviation = Average of absolute differences from mean
1478
1474
  * - Scaled by 0.015 to provide more readable numbers
1475
+ * - As in PineScript: when the mean deviation is 0 (1e-10 tolerance, a flat window), the CCI of the previous bar
1479
1476
  * - Values above +100 indicate overbought conditions
1480
1477
  * - Values below -100 indicate oversold conditions
1481
1478
  * - \`na\` values in the source series are ignored
@@ -1493,8 +1490,10 @@ export function cci(source: Source, length: simple_int): series_float {
1493
1490
  const devValues = dev(source, length);
1494
1491
 
1495
1492
  for (let i = 0; i < source.length; i++) {
1496
- if (isNaN(smaValues[i]!) || isNaN(devValues[i]!) || devValues[i]! === 0) {
1493
+ if (isNaN(smaValues[i]!) || isNaN(devValues[i]!)) {
1497
1494
  result.push(NaN);
1495
+ } else if (eq(devValues[i]!, 0)) {
1496
+ result.push(i > 0 ? result[i - 1]! : NaN);
1498
1497
  } else {
1499
1498
  const cci = (source[i]! - smaValues[i]!) / (0.015 * devValues[i]!);
1500
1499
  result.push(cci);
@@ -2079,6 +2078,7 @@ export function tsi(source: Source, shortLength: simple_int, longLength: simple_
2079
2078
  * - CMO > +50: overbought conditions
2080
2079
  * - CMO < -50: oversold conditions
2081
2080
  * - Unlike RSI, CMO uses sum of gains/losses instead of averages
2081
+ * - na on a flat window (every change 0), as in PineScript
2082
2082
  * - More volatile than RSI
2083
2083
  *
2084
2084
  * @example
@@ -2109,9 +2109,10 @@ export function cmo(source: Source, length: simple_int): series_float {
2109
2109
  }
2110
2110
  }
2111
2111
 
2112
+ // PineScript: 100 * (sm1 - sm2) / (sm1 + sm2), so a flat window (0 / 0) is na
2112
2113
  const totalMovement = sumGains + sumLosses;
2113
2114
  if (totalMovement === 0) {
2114
- result.push(0);
2115
+ result.push(NaN);
2115
2116
  } else {
2116
2117
  const cmoValue = ((sumGains - sumLosses) / totalMovement) * 100;
2117
2118
  result.push(cmoValue);