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
A short note from Dr. Ken Long on the craft of trading. One idea to think about and work on that day, drawn from decades of teaching and live trading. Free.
Your email:
Tue–Fri mornings. Unsubscribe anytime. No spam, no hype.