OGT Owl Group Trading by Dr. Ken Long
Home About Learn The Loop Code Courses Essays Store Partners FAQ
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) };
}