mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-13 16:18:05 +00:00
Merge branch 'dev'
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Aberration (ABERR)", "ABERR", overlay=true)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Acceleration Bands (ACCBANDS)", "ACCBANDS", overlay=true)
|
||||
|
||||
@@ -4,17 +4,17 @@
|
||||
| ---------------- | -------------------------------- |
|
||||
| **Category** | Channel |
|
||||
| **Inputs** | OHLCV bar (TBar) |
|
||||
| **Parameters** | None |
|
||||
| **Outputs** | Single series (Apchannel) |
|
||||
| **Parameters** | `alpha` (default 0.2) |
|
||||
| **Outputs** | Multiple series (Upper, Lower) |
|
||||
| **Output range** | Tracks input |
|
||||
| **Warmup** | 1 bar |
|
||||
| **Warmup** | `⌈3/alpha⌉` bars (default 15) |
|
||||
|
||||
### TL;DR
|
||||
|
||||
- APCHANNEL applies exponential smoothing independently to price highs and lows, creating a dynamic envelope that "remembers" significant extremes wh...
|
||||
- No configurable parameters; computation is stateless per bar.
|
||||
- Parameterized by `alpha` (default 0.2).
|
||||
- Output range: Tracks input.
|
||||
- Requires 1 bar of warmup before first valid output (IsHot = true).
|
||||
- Requires `⌈3/alpha⌉` bars (default 15) of warmup before first valid output (IsHot = true).
|
||||
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
|
||||
|
||||
APCHANNEL applies exponential smoothing independently to price highs and lows, creating a dynamic envelope that "remembers" significant extremes while gradually fading their influence over time. Unlike rigid Donchian channels that drop price extremes abruptly when they exit the lookback window (the "cliff effect"), APCHANNEL decays them smoothly through leaky integration. The result is a channel with continuously sloping boundaries that responds to volatility without the discontinuous jumps that plague fixed-window approaches. The algorithm is $O(1)$ per bar with only two state variables and no buffers.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Adaptive Price Channel (APCHANNEL)", "APCHANNEL", overlay=true)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Adaptive Price Zone", "APZ", overlay=true)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("ATR Bands (ATRBANDS)", "ATRBANDS", overlay=true)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Bollinger Bands (BBANDS)", "BBANDS", overlay=true)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Donchian Channels (DCHANNEL)", "DCHANNEL", overlay=true)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Decay Min-Max Channel (DECAYCHANNEL)", "DECAYCHANNEL", overlay=true)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Fractal Chaos Bands (FCB)", "FCB", overlay=true)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Jurik Adaptive Envelope Bands", "JBANDS", overlay=true)
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
| **Parameters** | `period`, `phase` (default 0) |
|
||||
| **Outputs** | Multiple series (Upper, Lower) |
|
||||
| **Output range** | Tracks input |
|
||||
| **Warmup** | 1 bar |
|
||||
| **Warmup** | `⌈20 + 80 × period^0.36⌉` bars |
|
||||
|
||||
### TL;DR
|
||||
|
||||
- JBANDS expose the internal adaptive envelope mechanism of the Jurik Moving Average (JMA), producing asymmetric bands that snap instantly to new pri...
|
||||
- Parameterized by `period`, `phase` (default 0).
|
||||
- Output range: Tracks input.
|
||||
- Requires 1 bar of warmup before first valid output (IsHot = true).
|
||||
- Requires `⌈20 + 80 × period^0.36⌉` bars of warmup before first valid output (IsHot = true).
|
||||
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
|
||||
|
||||
JBANDS expose the internal adaptive envelope mechanism of the Jurik Moving Average (JMA), producing asymmetric bands that snap instantly to new price extremes and decay exponentially during consolidation. Unlike standard volatility bands (Bollinger, Keltner) which maintain symmetric width around a center line, JBANDS feature "snap-and-decay" hysteresis: expansion is instantaneous (plasticity), contraction is gradual (elasticity). The decay rate is dynamically modulated by a two-stage volatility estimator — a 10-bar SMA feeding a 128-bar trimmed mean — making the bands tight during quiet markets and expansive during trends. The center line is the full JMA: a 2-pole IIR filter with phase control and adaptive alpha.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Keltner Channel (KCHANNEL)", "KCHANNEL", overlay=true)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("MA Envelope (MAE)", "MAE", overlay=true)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Min-Max Channel (MMCHANNEL)", "MMCHANNEL", overlay=true)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Price Channel (PCHANNEL)", "PCHANNEL", overlay=true)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@@ -206,7 +207,7 @@ public sealed class Regchannel : ITValuePublisher
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
sumY += values[i];
|
||||
sumXY += i * values[i];
|
||||
sumXY = Math.FusedMultiplyAdd((double)i, values[i], sumXY);
|
||||
}
|
||||
|
||||
double n = count;
|
||||
@@ -325,7 +326,11 @@ public sealed class Regchannel : ITValuePublisher
|
||||
|
||||
/// <summary>
|
||||
/// Batch calculation using spans.
|
||||
/// O(period) per bar: sums recomputed from circular buffer each bar for numerical
|
||||
/// consistency with the streaming path, plus closed-form residual variance:
|
||||
/// sumResiduals² = sumY² − intercept·sumY − slope·sumXY, eliminating a second O(period) pass.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
public static void Batch(
|
||||
ReadOnlySpan<double> source,
|
||||
Span<double> middle,
|
||||
@@ -360,121 +365,124 @@ public sealed class Regchannel : ITValuePublisher
|
||||
double sumX2Full = (period - 1.0) * period * (2.0 * period - 1.0) / 6.0;
|
||||
double denomFull = period * sumX2Full - sumXFull * sumXFull;
|
||||
|
||||
// Track last valid value for NaN substitution
|
||||
double lastValid = double.NaN;
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
// Circular buffer of NaN-sanitised values for O(1) sliding-window recurrences.
|
||||
const int StackAllocThreshold = 256;
|
||||
double[]? rentedWindow = null;
|
||||
scoped Span<double> window;
|
||||
if (period <= StackAllocThreshold)
|
||||
{
|
||||
// Get valid value with last-valid substitution
|
||||
double currentValue = source[i];
|
||||
if (double.IsFinite(currentValue))
|
||||
{
|
||||
lastValid = currentValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentValue = lastValid;
|
||||
}
|
||||
window = stackalloc double[period];
|
||||
}
|
||||
else
|
||||
{
|
||||
rentedWindow = ArrayPool<double>.Shared.Rent(period);
|
||||
window = rentedWindow.AsSpan(0, period);
|
||||
}
|
||||
|
||||
// If still NaN (no valid value seen yet), output NaN
|
||||
if (!double.IsFinite(currentValue))
|
||||
try
|
||||
{
|
||||
window.Clear();
|
||||
|
||||
double lastValid = double.NaN;
|
||||
int head = 0, count = 0;
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
middle[i] = double.NaN;
|
||||
upper[i] = double.NaN;
|
||||
lower[i] = double.NaN;
|
||||
continue;
|
||||
}
|
||||
|
||||
int count = Math.Min(i + 1, period);
|
||||
int start = i - count + 1;
|
||||
|
||||
if (count <= 1)
|
||||
{
|
||||
middle[i] = currentValue;
|
||||
upper[i] = currentValue;
|
||||
lower[i] = currentValue;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate sums for linear regression with NaN handling
|
||||
double sumY = 0;
|
||||
double sumXY = 0;
|
||||
double lastValidInWindow = double.NaN;
|
||||
|
||||
for (int j = 0; j < count; j++)
|
||||
{
|
||||
double rawY = source[start + j];
|
||||
double y;
|
||||
if (double.IsFinite(rawY))
|
||||
// NaN substitution
|
||||
double y = source[i];
|
||||
if (double.IsFinite(y))
|
||||
{
|
||||
lastValidInWindow = rawY;
|
||||
y = rawY;
|
||||
lastValid = y;
|
||||
}
|
||||
else
|
||||
{
|
||||
y = double.IsFinite(lastValidInWindow) ? lastValidInWindow : 0.0;
|
||||
y = lastValid;
|
||||
}
|
||||
sumY += y;
|
||||
sumXY += j * y;
|
||||
}
|
||||
|
||||
double n = count;
|
||||
double sx, denom;
|
||||
|
||||
if (count < period)
|
||||
{
|
||||
sx = 0.5 * n * (n - 1);
|
||||
double sx2 = (n - 1.0) * n * (2.0 * n - 1.0) / 6.0;
|
||||
denom = n * sx2 - sx * sx;
|
||||
}
|
||||
else
|
||||
{
|
||||
sx = sumXFull;
|
||||
denom = denomFull;
|
||||
}
|
||||
|
||||
double slope, intercept, regression;
|
||||
|
||||
if (Math.Abs(denom) < 1e-10)
|
||||
{
|
||||
slope = 0;
|
||||
intercept = sumY / n;
|
||||
regression = intercept;
|
||||
}
|
||||
else
|
||||
{
|
||||
slope = (n * sumXY - sx * sumY) / denom;
|
||||
intercept = (sumY - slope * sx) / n;
|
||||
regression = Math.FusedMultiplyAdd(slope, count - 1, intercept);
|
||||
}
|
||||
|
||||
// Calculate standard deviation of residuals with NaN handling
|
||||
double sumResiduals2 = 0;
|
||||
lastValidInWindow = double.NaN;
|
||||
for (int j = 0; j < count; j++)
|
||||
{
|
||||
double rawY = source[start + j];
|
||||
double y;
|
||||
if (double.IsFinite(rawY))
|
||||
// No valid value seen yet
|
||||
if (!double.IsFinite(y))
|
||||
{
|
||||
lastValidInWindow = rawY;
|
||||
y = rawY;
|
||||
middle[i] = double.NaN;
|
||||
upper[i] = double.NaN;
|
||||
lower[i] = double.NaN;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Store in circular buffer
|
||||
window[head] = y;
|
||||
head = (head + 1) % period;
|
||||
if (count < period)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count <= 1)
|
||||
{
|
||||
middle[i] = y;
|
||||
upper[i] = y;
|
||||
lower[i] = y;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Recompute sums fresh from circular buffer (oldest-to-newest).
|
||||
// This matches the streaming Update() path numerically.
|
||||
double sumY = 0, sumXY = 0, sumY2 = 0;
|
||||
int oldestIdx = (head - count + period) % period;
|
||||
for (int k = 0; k < count; k++)
|
||||
{
|
||||
double wk = window[(oldestIdx + k) % period];
|
||||
sumY += wk;
|
||||
sumXY = Math.FusedMultiplyAdd((double)k, wk, sumXY);
|
||||
sumY2 = Math.FusedMultiplyAdd(wk, wk, sumY2);
|
||||
}
|
||||
|
||||
double n = count;
|
||||
double sx, denom;
|
||||
|
||||
if (count < period)
|
||||
{
|
||||
sx = 0.5 * n * (n - 1);
|
||||
double sx2 = (n - 1.0) * n * (2.0 * n - 1.0) / 6.0;
|
||||
denom = n * sx2 - sx * sx;
|
||||
}
|
||||
else
|
||||
{
|
||||
y = double.IsFinite(lastValidInWindow) ? lastValidInWindow : 0.0;
|
||||
sx = sumXFull;
|
||||
denom = denomFull;
|
||||
}
|
||||
double predicted = Math.FusedMultiplyAdd(slope, j, intercept);
|
||||
double residual = y - predicted;
|
||||
sumResiduals2 = Math.FusedMultiplyAdd(residual, residual, sumResiduals2);
|
||||
|
||||
double slope, intercept, regression;
|
||||
|
||||
if (Math.Abs(denom) < 1e-10)
|
||||
{
|
||||
slope = 0;
|
||||
intercept = sumY / n;
|
||||
regression = intercept;
|
||||
}
|
||||
else
|
||||
{
|
||||
slope = (n * sumXY - sx * sumY) / denom;
|
||||
intercept = (sumY - slope * sx) / n;
|
||||
regression = Math.FusedMultiplyAdd(slope, count - 1, intercept);
|
||||
}
|
||||
|
||||
// Closed-form residual variance (normal-equation identity):
|
||||
// sumResiduals² = sumY² − intercept·sumY − slope·sumXY
|
||||
double sumResiduals2 = Math.Max(0.0, sumY2 - intercept * sumY - slope * sumXY);
|
||||
double stdDev = Math.Sqrt(sumResiduals2 / n);
|
||||
double band = multiplier * stdDev;
|
||||
|
||||
middle[i] = regression;
|
||||
upper[i] = regression + band;
|
||||
lower[i] = regression - band;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (rentedWindow != null)
|
||||
{
|
||||
ArrayPool<double>.Shared.Return(rentedWindow);
|
||||
}
|
||||
|
||||
double stdDev = Math.Sqrt(sumResiduals2 / n);
|
||||
double band = multiplier * stdDev;
|
||||
|
||||
middle[i] = regression;
|
||||
upper[i] = regression + band;
|
||||
lower[i] = regression - band;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Regression Channels (REGCHANNEL)", "REGCHANNEL", overlay=true)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@@ -325,7 +326,11 @@ public sealed class Sdchannel : ITValuePublisher
|
||||
|
||||
/// <summary>
|
||||
/// Batch calculation using spans.
|
||||
/// O(period) per bar: sums and residuals recomputed from circular buffer each bar for numerical
|
||||
/// consistency with the streaming path. Uses the same plain arithmetic as Update() for sumXY
|
||||
/// and the same FMA residual loop to ensure streaming/batch agreement within 1e-9.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
public static void Batch(
|
||||
ReadOnlySpan<double> source,
|
||||
Span<double> middle,
|
||||
@@ -360,75 +365,130 @@ public sealed class Sdchannel : ITValuePublisher
|
||||
double sumX2Full = (period - 1.0) * period * (2.0 * period - 1.0) / 6.0;
|
||||
double denomFull = period * sumX2Full - sumXFull * sumXFull;
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
// Circular buffer of NaN-sanitised values for O(1) sliding-window recurrences.
|
||||
const int StackAllocThreshold = 256;
|
||||
double[]? rentedWindow = null;
|
||||
scoped Span<double> window;
|
||||
if (period <= StackAllocThreshold)
|
||||
{
|
||||
int count = Math.Min(i + 1, period);
|
||||
int start = i - count + 1;
|
||||
window = stackalloc double[period];
|
||||
}
|
||||
else
|
||||
{
|
||||
rentedWindow = ArrayPool<double>.Shared.Rent(period);
|
||||
window = rentedWindow.AsSpan(0, period);
|
||||
}
|
||||
|
||||
if (count <= 1)
|
||||
try
|
||||
{
|
||||
window.Clear();
|
||||
|
||||
double lastValid = double.NaN;
|
||||
int head = 0, count = 0;
|
||||
|
||||
for (int i = 0; i < len; i++)
|
||||
{
|
||||
middle[i] = source[i];
|
||||
upper[i] = source[i];
|
||||
lower[i] = source[i];
|
||||
continue;
|
||||
// NaN substitution matching GetValid() in streaming Update()
|
||||
double y = source[i];
|
||||
if (double.IsFinite(y))
|
||||
{
|
||||
lastValid = y;
|
||||
}
|
||||
else
|
||||
{
|
||||
y = lastValid;
|
||||
}
|
||||
|
||||
// No valid value seen yet
|
||||
if (!double.IsFinite(y))
|
||||
{
|
||||
middle[i] = double.NaN;
|
||||
upper[i] = double.NaN;
|
||||
lower[i] = double.NaN;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Store in circular buffer
|
||||
window[head] = y;
|
||||
head = (head + 1) % period;
|
||||
if (count < period)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count <= 1)
|
||||
{
|
||||
middle[i] = y;
|
||||
upper[i] = y;
|
||||
lower[i] = y;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Recompute sums fresh from circular buffer (oldest-to-newest).
|
||||
// Plain arithmetic for sumXY matches streaming Update() path numerically.
|
||||
double sumY = 0, sumXY = 0;
|
||||
int oldestIdx = (head - count + period) % period;
|
||||
for (int k = 0; k < count; k++)
|
||||
{
|
||||
double wk = window[(oldestIdx + k) % period];
|
||||
sumY += wk;
|
||||
sumXY += (double)k * wk;
|
||||
}
|
||||
|
||||
double n = count;
|
||||
double sx, denom;
|
||||
|
||||
if (count < period)
|
||||
{
|
||||
sx = 0.5 * n * (n - 1);
|
||||
double sx2 = (n - 1.0) * n * (2.0 * n - 1.0) / 6.0;
|
||||
denom = n * sx2 - sx * sx;
|
||||
}
|
||||
else
|
||||
{
|
||||
sx = sumXFull;
|
||||
denom = denomFull;
|
||||
}
|
||||
|
||||
double slope, intercept, regression;
|
||||
|
||||
if (Math.Abs(denom) < 1e-10)
|
||||
{
|
||||
slope = 0;
|
||||
intercept = sumY / n;
|
||||
regression = intercept;
|
||||
}
|
||||
else
|
||||
{
|
||||
slope = (n * sumXY - sx * sumY) / denom;
|
||||
intercept = (sumY - slope * sx) / n;
|
||||
regression = Math.FusedMultiplyAdd(slope, count - 1, intercept);
|
||||
}
|
||||
|
||||
// Explicit residual loop matching streaming Update() path numerically.
|
||||
double sumResiduals2 = 0;
|
||||
for (int k = 0; k < count; k++)
|
||||
{
|
||||
double wk = window[(oldestIdx + k) % period];
|
||||
double predicted = Math.FusedMultiplyAdd(slope, k, intercept);
|
||||
double residual = wk - predicted;
|
||||
sumResiduals2 = Math.FusedMultiplyAdd(residual, residual, sumResiduals2);
|
||||
}
|
||||
|
||||
double stdDev = Math.Sqrt(sumResiduals2 / n);
|
||||
double band = multiplier * stdDev;
|
||||
|
||||
middle[i] = regression;
|
||||
upper[i] = regression + band;
|
||||
lower[i] = regression - band;
|
||||
}
|
||||
|
||||
// Calculate sums for linear regression
|
||||
double sumY = 0;
|
||||
double sumXY = 0;
|
||||
|
||||
for (int j = 0; j < count; j++)
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (rentedWindow != null)
|
||||
{
|
||||
double y = source[start + j];
|
||||
sumY += y;
|
||||
sumXY += j * y;
|
||||
ArrayPool<double>.Shared.Return(rentedWindow);
|
||||
}
|
||||
|
||||
double n = count;
|
||||
double sx, denom;
|
||||
|
||||
if (count < period)
|
||||
{
|
||||
sx = 0.5 * n * (n - 1);
|
||||
double sx2 = (n - 1.0) * n * (2.0 * n - 1.0) / 6.0;
|
||||
denom = n * sx2 - sx * sx;
|
||||
}
|
||||
else
|
||||
{
|
||||
sx = sumXFull;
|
||||
denom = denomFull;
|
||||
}
|
||||
|
||||
double slope, intercept, regression;
|
||||
|
||||
if (Math.Abs(denom) < 1e-10)
|
||||
{
|
||||
slope = 0;
|
||||
intercept = sumY / n;
|
||||
regression = intercept;
|
||||
}
|
||||
else
|
||||
{
|
||||
slope = (n * sumXY - sx * sumY) / denom;
|
||||
intercept = (sumY - slope * sx) / n;
|
||||
regression = Math.FusedMultiplyAdd(slope, count - 1, intercept);
|
||||
}
|
||||
|
||||
// Calculate standard deviation of residuals
|
||||
double sumResiduals2 = 0;
|
||||
for (int j = 0; j < count; j++)
|
||||
{
|
||||
double predicted = Math.FusedMultiplyAdd(slope, j, intercept);
|
||||
double residual = source[start + j] - predicted;
|
||||
sumResiduals2 = Math.FusedMultiplyAdd(residual, residual, sumResiduals2);
|
||||
}
|
||||
|
||||
double stdDev = Math.Sqrt(sumResiduals2 / n);
|
||||
double band = multiplier * stdDev;
|
||||
|
||||
middle[i] = regression;
|
||||
upper[i] = regression + band;
|
||||
lower[i] = regression - band;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -464,7 +524,9 @@ public sealed class Sdchannel : ITValuePublisher
|
||||
|
||||
public static ((TSeries Middle, TSeries Upper, TSeries Lower) Results, Sdchannel Indicator) Calculate(TSeries source, int period = 20, double multiplier = 2.0)
|
||||
{
|
||||
var indicator = new Sdchannel(source, period, multiplier);
|
||||
// Use parameterless constructor to avoid double-processing: new Sdchannel(source, ...) calls Prime(source),
|
||||
// then Update(source) would call Prime again.
|
||||
var indicator = new Sdchannel(period, multiplier);
|
||||
var results = indicator.Update(source);
|
||||
return (results, indicator);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Standard Deviation Channel (SDCHANNEL)", "SDCHANNEL", overlay=true)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Stoller Average Range Channel (STARCHANNEL)", "STARCHANNEL", overlay=true)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("Super Trend Bands (STBANDS)", "STBANDS", overlay=true)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
// Ultimate Bands logic based on work by John F. Ehlers (c) 2024
|
||||
|
||||
@@ -7,14 +7,14 @@
|
||||
| **Parameters** | `strPeriod` (default DefaultStrPeriod), `centerPeriod` (default DefaultCenterPeriod), `multiplier` (default DefaultMultiplier) |
|
||||
| **Outputs** | Multiple series (Upper, Middle, Lower, STR) |
|
||||
| **Output range** | Tracks input |
|
||||
| **Warmup** | 1 bar |
|
||||
| **Warmup** | `Math.Max(strPeriod, centerPeriod)` bars |
|
||||
|
||||
### TL;DR
|
||||
|
||||
- Ehlers Ultimate Channel applies the Ultrasmooth Filter (USF) twice: once to the close price for the centerline and once to True Range for band widt...
|
||||
- Parameterized by `strperiod` (default defaultstrperiod), `centerperiod` (default defaultcenterperiod), `multiplier` (default defaultmultiplier).
|
||||
- Output range: Tracks input.
|
||||
- Requires 1 bar of warmup before first valid output (IsHot = true).
|
||||
- Requires `Math.Max(strPeriod, centerPeriod)` bars of warmup before first valid output (IsHot = true).
|
||||
- Validated against TA-Lib, Skender, and Tulip reference implementations where available.
|
||||
|
||||
Ehlers Ultimate Channel applies the Ultrasmooth Filter (USF) twice: once to the close price for the centerline and once to True Range for band width, creating a channel where both the trend estimate and the volatility measure share the same low-lag, zero-overshoot filter characteristics. Unlike UBANDS which uses RMS of price residuals, UCHANNEL uses Smoothed True Range (STR) for band width, making it responsive to gap-inclusive volatility. Separate period parameters allow independent tuning of centerline smoothness and band-width responsiveness.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
// Ultimate Channel logic based on work by John F. Ehlers (c) 2024
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("VWAP Bands (VWAPBANDS)", "VWAPBANDS", overlay=true)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// The MIT License (MIT)
|
||||
// Licensed under the Apache License, Version 2.0
|
||||
// © mihakralj
|
||||
//@version=6
|
||||
indicator("VWAP with Standard Deviation Bands", "VWAPSD", overlay=true)
|
||||
|
||||
Reference in New Issue
Block a user