JavaScript · Indicator Code
PSAR on RL10 in JavaScript
A Wilder Parabolic SAR trailing stop walked over the smoothed RL10 regression line instead of raw price, so the stop reacts to trend rather than to single-bar noise.
Concept: what PSAR-ON-RL10 means →
Verified. Python and JavaScript implementations agree to
4.26e-14 on a 60-bar reference price series (RL10 then PSAR) (canonical Python psar_walk_series vs JS calcPSAROnSeries on the RL10 series).JavaScript
// RL10: linear-regression endpoint at the last bar of each n-bar window.
// Null for the first n-1 bars (insufficient lookback). Canonical:
// edge-canvas/static/indicators.js::calcRL.
function calcRL(closes, n = 10) {
const len = closes.length;
const result = new Array(len).fill(null);
if (n <= 0 || len < n) return result;
const sumX = (n * (n - 1)) / 2;
const sumX2 = (n * (n - 1) * (2 * n - 1)) / 6;
const denom = n * sumX2 - sumX * sumX;
if (denom === 0) return result;
for (let end = n; end <= len; end++) {
const start = end - n;
let sumY = 0, sumXY = 0, hasNaN = false;
for (let j = 0; j < n; j++) {
const v = closes[start + j];
if (v == null || Number.isNaN(v)) { hasNaN = true; break; }
sumY += v; sumXY += j * v;
}
if (hasNaN) continue;
const slope = (n * sumXY - sumX * sumY) / denom;
const intercept = (sumY - slope * sumX) / n;
result[end - 1] = slope * (n - 1) + intercept;
}
return result;
}
// Wilder Parabolic SAR over an arbitrary 1-D series (here the RL10 line).
// Null entries are skipped; walk starts at the first two consecutive
// non-null values. Canonical: edge-canvas/static/indicators.js::calcPSAROnSeries.
function calcPSAROnSeries(series, afInit = 0.02, afMax = 0.12) {
const len = series.length;
const result = new Array(len).fill(null);
let start = -1;
for (let i = 0; i < len - 1; i++) {
if (series[i] != null && series[i + 1] != null) { start = i; break; }
}
if (start < 0) return result;
let trend = series[start + 1] >= series[start] ? 1 : -1;
let ep = series[start + 1]; // extreme point
let af = afInit; // acceleration factor
result[start] = series[start];
result[start + 1] = series[start];
for (let i = start + 2; i < len; i++) {
if (series[i] == null) continue;
const prev = result[i - 1];
let p;
if (trend === 1) {
p = prev + af * (ep - prev);
// SAR may not penetrate the prior one/two bars.
if (i >= 2 && series[i - 1] != null) p = Math.min(p, series[i - 1]);
if (i >= 3 && series[i - 2] != null) p = Math.min(p, series[i - 2]);
if (series[i] < p) { // flip to short
trend = -1; p = ep; ep = series[i]; af = afInit;
} else if (series[i] > ep) { // new high -> step accel
ep = series[i]; af = Math.min(af + afInit, afMax);
}
} else {
p = prev + af * (ep - prev);
if (i >= 2 && series[i - 1] != null) p = Math.max(p, series[i - 1]);
if (i >= 3 && series[i - 2] != null) p = Math.max(p, series[i - 2]);
if (series[i] > p) { // flip to long
trend = 1; p = ep; ep = series[i]; af = afInit;
} else if (series[i] < ep) { // new low -> step accel
ep = series[i]; af = Math.min(af + afInit, afMax);
}
}
result[i] = p;
}
return result;
}
// Parabolic SAR computed over the RL10 line of `closes`.
// 1. Smooth price into the RL10 regression-endpoint series.
// 2. Walk a Wilder PSAR (af=0.02, max=0.12) over that smoothed series.
export function psar_on_rl10(closes, afInit = 0.02, afMax = 0.12) {
const rl = calcRL(closes, 10);
return calcPSAROnSeries(rl, afInit, afMax);
}
Improve Your Craft Every Morning
Daily commentary from Dr. Ken Long — what he's seeing in markets, how he's framing trades, and what's worth practicing today. Free.
Your email:
Tue–Fri mornings. Unsubscribe anytime. No spam, no hype.