oakscriptjs 0.9.1 → 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.
package/src/ta/index.ts CHANGED
@@ -573,6 +573,7 @@ export function atr(length: simple_int, high?: Source, low?: Source, close?: Sou
573
573
  * - Direction: 1 = downtrend (red), -1 = uptrend (green)
574
574
  * - Uses hl2 (average of high and low) as the source
575
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
576
577
  *
577
578
  * @example
578
579
  * ```typescript
@@ -612,41 +613,35 @@ export function supertrend(
612
613
  // Calculate ATR
613
614
  const atrValues = atr(atrPeriod, high, low, close);
614
615
 
615
- // Track previous values across iterations
616
- let prevLowerBand = NaN;
617
- 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;
618
625
  let prevSuperTrend = NaN;
619
626
 
620
627
  for (let i = 0; i < source.length; i++) {
621
628
  const atrValue = atrValues[i]! * factor;
622
-
623
- // Skip calculation if ATR is not available yet
624
- if (isNaN(atrValue)) {
625
- supertrendValues.push(NaN);
626
- directions.push(1);
627
- continue;
628
- }
629
-
630
- // Calculate initial bands
631
629
  let upperBand = source[i]! + atrValue;
632
630
  let lowerBand = source[i]! - atrValue;
631
+ const prevLowerBand = nz(lastLowerBand);
632
+ const prevUpperBand = nz(lastUpperBand);
633
633
 
634
- // Determine which price to use for comparison
634
+ // Price compared with the bands: close, or low / high with wicks
635
635
  const highPrice = wicks ? high[i]! : close[i]!;
636
636
  const lowPrice = wicks ? low[i]! : close[i]!;
637
- const prevLowPrice = i > 0 ? (wicks ? low[i - 1]! : close[i - 1]!) : 0;
638
- 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;
639
639
 
640
- // Update bands conditionally (trailing behavior) - only if previous bands are valid
641
- if (i > 0 && !isNaN(prevLowerBand) && !isNaN(prevUpperBand)) {
642
- lowerBand = (lowerBand > prevLowerBand || prevLowPrice < prevLowerBand) ? lowerBand : prevLowerBand;
643
- upperBand = (upperBand < prevUpperBand || prevHighPrice > prevUpperBand) ? upperBand : prevUpperBand;
644
- }
640
+ lowerBand = lowerBand > prevLowerBand || prevLowPrice < prevLowerBand ? lowerBand : prevLowerBand;
641
+ upperBand = upperBand < prevUpperBand || prevHighPrice > prevUpperBand ? upperBand : prevUpperBand;
645
642
 
646
- // Determine trend direction
647
643
  let currentDirection: int;
648
- if (isNaN(prevSuperTrend)) {
649
- // Initial direction when we don't have previous supertrend
644
+ if (i === 0 || isNaN(atrValues[i - 1]!)) {
650
645
  currentDirection = 1;
651
646
  } else if (prevSuperTrend === prevUpperBand) {
652
647
  // Was in downtrend (following upper band)
@@ -656,15 +651,12 @@ export function supertrend(
656
651
  currentDirection = lowPrice < lowerBand ? 1 : -1;
657
652
  }
658
653
 
659
- // Calculate supertrend value based on direction
660
654
  const superTrendValue = currentDirection === -1 ? lowerBand : upperBand;
661
-
662
655
  supertrendValues.push(superTrendValue);
663
656
  directions.push(currentDirection);
664
657
 
665
- // Update previous values for next iteration
666
- prevLowerBand = lowerBand;
667
- prevUpperBand = upperBand;
658
+ lastLowerBand = lowerBand;
659
+ lastUpperBand = upperBand;
668
660
  prevSuperTrend = superTrendValue;
669
661
  }
670
662
 
@@ -2086,6 +2078,7 @@ export function tsi(source: Source, shortLength: simple_int, longLength: simple_
2086
2078
  * - CMO > +50: overbought conditions
2087
2079
  * - CMO < -50: oversold conditions
2088
2080
  * - Unlike RSI, CMO uses sum of gains/losses instead of averages
2081
+ * - na on a flat window (every change 0), as in PineScript
2089
2082
  * - More volatile than RSI
2090
2083
  *
2091
2084
  * @example
@@ -2116,9 +2109,10 @@ export function cmo(source: Source, length: simple_int): series_float {
2116
2109
  }
2117
2110
  }
2118
2111
 
2112
+ // PineScript: 100 * (sm1 - sm2) / (sm1 + sm2), so a flat window (0 / 0) is na
2119
2113
  const totalMovement = sumGains + sumLosses;
2120
2114
  if (totalMovement === 0) {
2121
- result.push(0);
2115
+ result.push(NaN);
2122
2116
  } else {
2123
2117
  const cmoValue = ((sumGains - sumLosses) / totalMovement) * 100;
2124
2118
  result.push(cmoValue);