Indicator Code · Owl Group Trading
The River, Flood Plain & Red Line
Owl Group Trading's 30-period Bollinger zones around the Mean: River (+/-1 SD), Flood Plain (+/-2 SD), Red Line (+/-3 SD).
Concept: what RIVER-FLOOD-PLAIN means →
Bollinger bands matter in Owl Group Trading as named zones around the Mean (Z0 = the 30-period SMA). The River is +/-1 SD (top = Z1), the Flood Plain is +/-2 SD (Z2), and the Red Line is +/-3 SD (Z3).
Price drifting through these zones is how a trader reads stretch and reversion: inside the River is normal, into the Flood Plain is stretched, and at the Red Line is statistically extreme.
Verified. Python and JavaScript implementations agree to
2.84e-14 on a 60-bar reference price series (Python vs JavaScript across the River/Flood Plain/Red Line bands (+/-1, +/-2, +/-3 SD over 30 periods) - float64 round-off only).Pythonpermalink →
import numpy as np
def _sma(values, n):
v = np.asarray(values, float)
out = np.full(len(v), np.nan)
for i in range(n - 1, len(v)):
out[i] = v[i - n + 1:i + 1].mean()
return out
def _rolling_std(values, n): # population std (ddof=0)
v = np.asarray(values, float)
out = np.full(len(v), np.nan)
for i in range(n - 1, len(v)):
out[i] = v[i - n + 1:i + 1].std()
return out
def river_flood_plain(closes, n=30):
"""The River, Flood Plain and Red Line - 30-period Bollinger zones around
the Mean (Z0 = the 30-period SMA). River = +/-1 SD (Z1), Flood Plain =
+/-2 SD (Z2), Red Line = +/-3 SD (Z3). Returns the Mean plus each (upper,
lower) band pair."""
mean = _sma(closes, n)
sd = _rolling_std(closes, n)
band = lambda k: (mean + k * sd, mean - k * sd)
return {"mean": mean,
"river": band(1), # Z1 / Z-1
"flood_plain": band(2), # Z2 / Z-2
"red_line": band(3)} # Z3 / Z-3
JavaScriptpermalink →
// The River, Flood Plain and Red Line - 30-period Bollinger zones around the
// Mean (Z0 = the 30-period SMA). River = +/-1 SD (Z1), Flood Plain = +/-2 SD
// (Z2), Red Line = +/-3 SD (Z3).
function sma(values, n = 30) {
const len = values.length, out = new Array(len).fill(null);
let sum = 0, count = 0;
for (let i = 0; i < len; i++) {
const v = values[i];
if (v != null && !Number.isNaN(v)) { sum += v; count++; }
if (i >= n) { const old = values[i - n]; if (old != null && !Number.isNaN(old)) { sum -= old; count--; } }
if (i >= n - 1 && count === n) out[i] = sum / n;
}
return out;
}
function rollingStd(values, n) { // population std
const len = values.length, out = new Array(len).fill(null);
for (let i = n - 1; i < len; i++) {
let s = 0, c = 0;
for (let j = i - n + 1; j <= i; j++) { const v = values[j]; if (v != null && !Number.isNaN(v)) { s += v; c++; } }
if (!c) continue;
const m = s / c; let sq = 0;
for (let j = i - n + 1; j <= i; j++) { const v = values[j]; if (v != null && !Number.isNaN(v)) sq += (v - m) * (v - m); }
out[i] = Math.sqrt(sq / c);
}
return out;
}
export function riverFloodPlain(closes, n = 30) {
const mean = sma(closes, n), sd = rollingStd(closes, n);
const band = (k) => ({
upper: mean.map((m, i) => (m != null && sd[i] != null) ? m + k * sd[i] : null),
lower: mean.map((m, i) => (m != null && sd[i] != null) ? m - k * sd[i] : null),
});
return { mean, river: band(1), floodPlain: band(2), redLine: band(3) };
}
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.