docs: remove C# Implementation Considerations sections, clean up temp scripts, reorganize test files

- Remove 'C# Implementation Considerations' sections from 34 indicator .md files
- Delete 29 temp PowerShell scripts (_fix_mojibake.ps1, _hex_scan.ps1, etc.)
- Move test files into tests/ subdirectories for consistent project structure
- Add trader-focused bullet points to indicator documentation
This commit is contained in:
Miha Kralj
2026-03-12 12:34:16 -07:00
parent 8937b0c0fa
commit 060649192f
1149 changed files with 1780 additions and 3316 deletions
-48
View File
@@ -240,54 +240,6 @@ var dec = new Decycler(source, period: 60);
var (results, indicator) = Decycler.Calculate(series, period: 60);
```
## C# Implementation Considerations
### State Record Struct with Auto Layout
All IIR filter state is packed into a `record struct` with `LayoutKind.Auto` for compiler-optimized field ordering:
```csharp
[StructLayout(LayoutKind.Auto)]
private record struct State
{
public double Hp;
public double Hp1;
public double Src1;
public double Src2;
public bool IsInitialized;
}
```
Five fields, ~40 bytes. Minimal footprint per instance.
### FusedMultiplyAdd for IIR Recurrence
The HP recurrence uses nested `Math.FusedMultiplyAdd` calls to minimize rounding error and exploit hardware FMA instructions:
```csharp
double hp = Math.FusedMultiplyAdd(_a1, src - 2.0 * _state.Src1 + _state.Src2,
Math.FusedMultiplyAdd(_b1, _state.Hp, _c1 * _state.Hp1));
```
### Precomputed Coefficients
The trigonometric operations ($\cos$, $\sin$) execute once in the constructor. The hot path uses only the three precomputed coefficients `_a1`, `_b1`, `_c1`.
### Bar Correction via State Snapshots
The `_state` / `_p_state` pattern enables bar correction:
```csharp
if (isNew) { _p_state = _state; }
else { _state = _p_state; }
```
### Memory Layout
- **State struct**: ~40 bytes (4 doubles + 1 bool + padding)
- **Precomputed coefficients**: 24 bytes (3 doubles)
- **Total per instance**: ~120 bytes including base class overhead
## References
- Ehlers, J. F. (2015). "Decyclers." *Technical Analysis of Stocks & Commodities*, September 2015.
-100
View File
@@ -216,106 +216,6 @@ var dema = new Dema(source, period);
| **Tulip** | ✅ | Matches `dema` (tolerance: 1e-9) |
| **Ooples** | ✅ | Matches `2*EMA - EMA(EMA)` formula |
## C# Implementation Considerations
QuanTAlib's DEMA uses cascaded EMA instances with bias compensation and extensive FMA optimization. The implementation demonstrates several high-performance patterns:
### State Management
```csharp
[StructLayout(LayoutKind.Auto)]
private record struct EmaState(double Ema, double E, bool IsHot, bool IsCompensated)
{
public static EmaState New() => new() { Ema = 0, E = 1.0, IsHot = false, IsCompensated = false };
}
private EmaState _state1 = EmaState.New();
private EmaState _state2 = EmaState.New();
private EmaState _p_state1 = EmaState.New(); // Bar correction backup
private EmaState _p_state2 = EmaState.New(); // Bar correction backup
```
Each EMA stage has its own state with bias compensation tracking. Four state copies enable bar correction across both stages.
### Key Optimizations
| Technique | Implementation | Benefit |
| :--- | :--- | :--- |
| **Precomputed constants** | `_alpha = 2.0/(period+1)`, `_decay = 1-_alpha` | Eliminates division in hot path |
| **FMA in EMA update** | `FusedMultiplyAdd(ema, decay, alpha * input)` | Hardware-accelerated smoothing |
| **FMA in combiner** | `FusedMultiplyAdd(2.0, e1, -e2)` | Single instruction for DEMA formula |
| **Bias compensation** | Tracks convergence factor `E` | Accurate warmup values |
| **Auto-transition** | `IsCompensated` flag skips division | Steady-state optimization |
### FMA Usage
```csharp
// EMA smoothing step (IIR pattern)
state.Ema = Math.FusedMultiplyAdd(state.Ema, decay, alpha * input);
// Final DEMA combiner: 2*e1 - e2 → FMA(2.0, e1, -e2)
double result = Math.FusedMultiplyAdd(2.0, e1, -e2);
```
### Bias Compensation Logic
```csharp
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static double Compute(double input, double alpha, double decay, ref EmaState state)
{
state.Ema = Math.FusedMultiplyAdd(state.Ema, decay, alpha * input);
if (!state.IsCompensated)
{
state.E *= decay; // Bias factor decays each tick
if (!state.IsHot && state.E <= 0.05) // 95% coverage
state.IsHot = true;
if (state.E <= 1e-10) // Full convergence
{
state.IsCompensated = true;
return state.Ema;
}
return state.Ema / (1.0 - state.E); // Bias-corrected
}
return state.Ema; // No compensation needed
}
```
### Memory Layout
| Field | Type | Size | Purpose |
| :--- | :--- | :---: | :--- |
| `_alpha` | double | 8 bytes | EMA smoothing factor |
| `_decay` | double | 8 bytes | 1 - alpha (precomputed) |
| `_state1` | EmaState | 20 bytes | First EMA stage state |
| `_state2` | EmaState | 20 bytes | Second EMA stage state |
| `_p_state1` | EmaState | 20 bytes | Bar correction backup |
| `_p_state2` | EmaState | 20 bytes | Bar correction backup |
| `_lastValidValue` | double | 8 bytes | NaN substitution |
| `_p_lastValidValue` | double | 8 bytes | Bar correction backup |
| **Instance total** | | **~112 bytes** | No period-dependent allocations |
### Bar Correction Pattern
```csharp
if (isNew)
{
_p_state1 = _state1;
_p_state2 = _state2;
_p_lastValidValue = _lastValidValue;
}
else
{
_state1 = _p_state1;
_state2 = _p_state2;
_lastValidValue = _p_lastValidValue;
}
```
Both EMA states are rolled back atomically for consistent correction.
## Reference Calculation Table
| Period | Price Sequence | EMA₁ | EMA₂ | DEMA | Notes |
-83
View File
@@ -158,89 +158,6 @@ DSMA is not implemented in mainstream libraries (TA-Lib, Skender, Tulip, Ooples)
- **Bounds**: Output remains within [min, max] price range ±1% tolerance
- **Mathematical Consistency**: Streaming updates match batch calculations (ε < 1e-10)
## C# Implementation Considerations
### State Management
DSMA uses a comprehensive State record struct combining all filter stages:
```csharp
[StructLayout(LayoutKind.Auto)]
private record struct State
{
public double Filt; // current filtered value
public double Filt1; // filt[t-1]
public double Filt2; // filt[t-2]
public double Zeros1; // deviation[t-1]
public double SumSquared; // running sum for RMS
public double Result; // current DSMA value
public double LastPrice; // last valid price
public int Bars;
}
```
Bar correction requires coordinated rollback of both state and RingBuffer:
```csharp
if (isNew) { _p_state = _state; _filtSquaredBuffer.Snapshot(); }
else { _state = _p_state; _filtSquaredBuffer.Restore(); }
```
### RingBuffer for RMS
The RingBuffer maintains O(1) running sum updates for RMS calculation:
```csharp
double removed = _filtSquaredBuffer.Add(filtSq);
_state.SumSquared = Math.FusedMultiplyAdd(-1.0, removed, _state.SumSquared + filtSq);
```
The buffer's `Snapshot()`/`Restore()` methods enable atomic rollback on bar corrections.
### Precomputed Constants
Constructor calculates all filter coefficients once:
```csharp
double arg = SqrtTwo * Math.PI / (period * 0.5);
double a1 = Math.Exp(-arg);
_b1 = 2.0 * a1 * Math.Cos(arg);
_a1Sq = a1 * a1;
_c1Half = (1.0 - _b1 + _a1Sq) * 0.5;
_periodRecip = 1.0 / period;
_scaleAdjustment = scaleFactor * 5.0 / period;
```
### FMA Usage
FMA optimizes the Super Smoother IIR and adaptive EMA:
```csharp
// Super Smoother: filt = c1Half*(zeros+zeros1) + b1*filt1 - a1Sq*filt2
double filtPart2 = Math.FusedMultiplyAdd(_state.Filt1, _b1, -_a1Sq * _state.Filt2);
// Adaptive EMA: result = prevResult*decay + alpha*value
double result = Math.FusedMultiplyAdd(_state.Result, decay, alpha * value);
```
### Memory Layout
| Field | Type | Size | Purpose |
| :--- | :--- | :---: | :--- |
| `_b1` | double | 8B | Super Smoother coefficient |
| `_c1Half` | double | 8B | Halved c₁ coefficient |
| `_a1Sq` | double | 8B | a₁² coefficient |
| `_periodRecip` | double | 8B | 1/period |
| `_scaleAdjustment` | double | 8B | Combined scale factor |
| `_filtSquaredBuffer` | RingBuffer | ~8B+period×8B | Circular buffer for RMS |
| `_state` | State | ~64B | Current calculation state |
| `_p_state` | State | ~64B | Previous state for rollback |
| **Total (fixed)** | | **~176B + period×8B** | Per indicator instance |
### SIMD Limitations
The 2-pole IIR recursion and adaptive alpha dependency on running RMS preclude SIMD parallelization across bars. The `Calculate(Span)` method uses a scalar loop—parallelization should target multiple independent series rather than within-series vectorization.
## Common Pitfalls
1. **Warmup Period**: DSMA requires `Period` bars to fill the Super Smoother delay line and RMS buffer. The first `Period` outputs will be unstable. Use `IsHot` to detect when the indicator has sufficient history.
-97
View File
@@ -122,103 +122,6 @@ FRAMA is not implemented in the common TA libraries used by QuanTAlib. Validatio
| **Ooples** | N/A | Not implemented |
| **PineScript** | ✅ | Matches `lib/trends_IIR/frama/frama.pine` |
## C# Implementation Considerations
### State Management
FRAMA uses a compact State struct with dual RingBuffer tracking:
```csharp
[StructLayout(LayoutKind.Sequential)]
private struct State
{
public double Frama;
public double LastHigh;
public double LastLow;
public int Bars;
public bool HasValue;
}
```
Bar correction requires coordinated rollback of state and both ring buffers:
```csharp
if (isNew) { _p_state = _state; _highs.Snapshot(); _lows.Snapshot(); }
else { _state = _p_state; _highs.Restore(); _lows.Restore(); }
```
### Dual RingBuffer Architecture
FRAMA maintains separate High and Low buffers for fractal dimension calculation:
```csharp
private readonly RingBuffer _highs;
private readonly RingBuffer _lows;
```
The `GetMax` and `GetMin` helper methods scan these buffers for range calculations, supporting both recent-half and full-window lookups via `startOffset` parameter.
### Precomputed Constants
Constructor enforces even period and precalculates half-period:
```csharp
int pe = (period % 2 == 0) ? period : period + 1;
_periodEven = pe;
_half = pe / 2;
```
Alpha bounds are compile-time constants:
```csharp
private const double AlphaFloor = 0.01;
private const double AlphaCeil = 1.0;
private const double Log2 = 0.693147180559945309417232121458176568;
```
### FMA Usage
The final EMA update uses FusedMultiplyAdd:
```csharp
double result = Math.FusedMultiplyAdd(prev, 1.0 - alpha, alpha * price);
```
### TBar Input Support
FRAMA accepts TBar input for proper High/Low access, with TValue fallback:
```csharp
public TValue Update(TValue input, bool isNew = true)
{
return Update(new TBar(input.Time, input.Value, input.Value,
input.Value, input.Value, 0), isNew);
}
```
### Memory Layout
| Field | Type | Size | Purpose |
| :--- | :--- | :---: | :--- |
| `_periodEven` | int | 4B | Even-adjusted period |
| `_half` | int | 4B | Half period for ranges |
| `_highs` | RingBuffer | ~8B+period×8B | High values buffer |
| `_lows` | RingBuffer | ~8B+period×8B | Low values buffer |
| `_state` | State | ~32B | Current calculation state |
| `_p_state` | State | ~32B | Previous state for rollback |
| **Total** | | **~88B + 2×period×8B** | Per indicator instance |
### Range Scan Implementation
The `GetMax`/`GetMin` methods perform O(N) linear scans with modular indexing:
```csharp
int idx = start + offset + i;
if (idx >= capacity) idx -= capacity;
```
This approach is simple and cache-friendly for typical periods (10-50). Monotonic deque optimization would reduce to O(1) amortized but adds complexity.
## Common Pitfalls
1. **Period parity**: The algorithm requires even `N`. Odd values are rounded up.
-86
View File
@@ -21,7 +21,6 @@
## An EMA-domain analog of HMA with WMA-lag-matched alphas
HEMA is a Hull-style moving average built entirely from **exponential smoothers**. It preserves the classic HMA pipeline (fast minus slow, then smooth) but replaces WMA sub-filters with EMAs whose alphas are tuned to produce **identical lag** to the WMA stages they replace. At period $N$: HEMA($N$) and HMA($N$) have the same theoretical group delay, but HEMA has infinite memory and smoother transient behavior.
## Historical Context
@@ -218,91 +217,6 @@ HEMA is not commonly available in mainstream TA libraries. Validation uses a **r
- Cross-check via invariant tests: DC gain, step response monotonicity, no NaN propagation after first finite sample.
- Streaming vs batch vs span consistency verified in unit tests.
## C# Implementation Considerations
### State Management
HEMA uses a comprehensive State struct tracking three EMA stages and warmup:
```csharp
[StructLayout(LayoutKind.Sequential)]
private struct State
{
public double EmaSlowRaw;
public double EmaFastRaw;
public double EmaSmoothRaw;
public double DecaySlow;
public double DecayFast;
public double DecaySmooth;
public bool IsHot;
public bool Warmup;
}
```
Bar correction uses full state copy plus last-valid tracking:
```csharp
if (isNew) { _p_state = _state; _p_lastValidValue = _lastValidValue; }
else { _state = _p_state; _lastValidValue = _p_lastValidValue; }
```
### Precomputed Constants
Constructor calculates all alpha/beta pairs and the lag ratio once using integer floor sub-periods:
```csharp
int halfPeriod = period / 2; // integer floor, same as HMA
int sqrtPeriod = Math.Max((int)Math.Sqrt(period), 1); // integer floor, same as HMA
_alphaSlow = AlphaFromWmaLag(period);
_alphaFast = AlphaFromWmaLag(Math.Max(halfPeriod, 1));
_alphaSmooth = AlphaFromWmaLag(Math.Max(sqrtPeriod, 1));
_betaSlow = 1.0 - _alphaSlow;
_ratio = Math.Clamp(lagFast / lagSlow, 0.0, MaxRatio);
_invOneMinusRatio = 1.0 / Math.Max(1.0 - _ratio, MinDenominator);
```
### WMA-Lag-Matched Alpha Calculation
A single division replaces the old half-life exponential mapping:
```csharp
private static double AlphaFromWmaLag(int period)
{
// WMA-lag-matched alpha: EMA lag = (1-a)/a = (P-1)/3
// Solving: a = 3/(P+2)
return 3.0 / (Math.Max(period, 1) + 2.0);
}
```
### FMA Usage
All EMA updates use FusedMultiplyAdd for precision and performance:
```csharp
state.EmaSlowRaw = Math.FusedMultiplyAdd(state.EmaSlowRaw, _betaSlow, _alphaSlow * input);
state.EmaFastRaw = Math.FusedMultiplyAdd(state.EmaFastRaw, _betaFast, _alphaFast * input);
double deLag = Math.FusedMultiplyAdd(-_ratio, emaSlow, emaFast) * _invOneMinusRatio;
```
### Memory Layout
| Field | Type | Size | Purpose |
| :--- | :--- | :---: | :--- |
| `_alphaSlow` | double | 8B | Slow EMA alpha |
| `_alphaFast` | double | 8B | Fast EMA alpha |
| `_alphaSmooth` | double | 8B | Smooth stage alpha |
| `_betaSlow` | double | 8B | 1 - alphaSlow |
| `_betaFast` | double | 8B | 1 - alphaFast |
| `_betaSmooth` | double | 8B | 1 - alphaSmooth |
| `_ratio` | double | 8B | Lag ratio for de-lag |
| `_invOneMinusRatio` | double | 8B | Precomputed divisor |
| `_state` | State | ~56B | Current calculation state |
| `_p_state` | State | ~56B | Previous state for rollback |
| `_lastValidValue` | double | 8B | NaN substitution |
| `_p_lastValidValue` | double | 8B | Previous valid value |
| **Total** | | **~192B** | Per indicator instance |
## Common Pitfalls
1. **Period semantics are now WMA-lag-matched**
-98
View File
@@ -13,7 +13,6 @@
| **PineScript** | [htit.pine](htit.pine) |
| **Signature** | [htit_signature](htit_signature.md) |
- HTIT (Hilbert Transform Instantaneous Trend) is a trend-following indicator that doesn't rely on simple averaging.
- No configurable parameters; computation is stateless per bar.
- Output range: Tracks input.
@@ -179,100 +178,3 @@ The differences with Skender and Ooples arise from:
1. **Initialization**: How the first few bars are handled.
2. **Precision**: Hardcoded decimals vs exact fractions.
3. **Period Constraints**: How strictly the [6, 50] bounds are enforced during intermediate steps.
## C# Implementation Considerations
### State Management
HTIT uses a compact record struct for Hilbert Transform state tracking:
```csharp
[StructLayout(LayoutKind.Auto)]
private record struct State(
double I2, double Q2, double Re, double Im,
double Period, double SmoothPeriod,
double LastValidPrice, int Index
);
```
Bar correction uses simple state copy (no RingBuffer snapshot needed for state struct):
```csharp
if (isNew) { _p_state = _state; _state.Index++; }
else { _state = _p_state; }
```
### Multiple RingBuffers
HTIT maintains six separate circular buffers for the multi-stage pipeline:
```csharp
private readonly RingBuffer _priceBuffer; // 64 elements (for IT sum)
private readonly RingBuffer _smoothBuffer; // 8 elements
private readonly RingBuffer _detrenderBuffer; // 8 elements
private readonly RingBuffer _i1Buffer; // 8 elements
private readonly RingBuffer _q1Buffer; // 8 elements
private readonly RingBuffer _itBuffer; // 8 elements
```
The price buffer is larger (64) to support IT calculation over up to 50 bars.
### Precomputed Constants
High-precision rational constants avoid rounding accumulation:
```csharp
private const double c1 = 5.0 / 52.0; // ~0.09615385
private const double c2 = 15.0 / 26.0; // ~0.57692308
private const double adjSlope = 3.0 / 40.0; // 0.075
private const double adjIntercept = 27.0 / 50.0; // 0.54
private const double TwoPi = 2.0 * Math.PI;
```
### FMA Usage
Smoothing operations use FusedMultiplyAdd for precision:
```csharp
_state.I2 = Math.FusedMultiplyAdd(0.2, i2_val, 0.8 * _p_state.I2);
_state.Q2 = Math.FusedMultiplyAdd(0.2, q2_val, 0.8 * _p_state.Q2);
_state.Re = Math.FusedMultiplyAdd(0.2, re_val, 0.8 * _p_state.Re);
_state.Period = Math.FusedMultiplyAdd(0.2, period, 0.8 * prevPeriod);
```
### Stack-Allocated Calculate Method
The static `Calculate(Span)` method uses stackalloc for zero-allocation batch processing:
```csharp
Span<double> priceBuffer = stackalloc double[64];
Span<double> smoothBuffer = stackalloc double[8];
// ... etc
const int Mask63 = 63; // Power-of-2 masking for circular index
const int Mask7 = 7;
```
### Memory Layout
| Field | Type | Size | Purpose |
| :--- | :--- | :---: | :--- |
| `_priceBuffer` | RingBuffer | ~8B+512B | Price history (64×8B) |
| `_smoothBuffer` | RingBuffer | ~8B+64B | Smoothed prices (8×8B) |
| `_detrenderBuffer` | RingBuffer | ~8B+64B | Detrender output |
| `_i1Buffer` | RingBuffer | ~8B+64B | In-phase component |
| `_q1Buffer` | RingBuffer | ~8B+64B | Quadrature component |
| `_itBuffer` | RingBuffer | ~8B+64B | Instantaneous trend |
| `_state` | State | ~64B | Current Hilbert state |
| `_p_state` | State | ~64B | Previous state for rollback |
| **Total** | | **~960B** | Per indicator instance |
### Numerical Robustness
Uses `Math.Atan2` for proper quadrant handling in phase calculation, avoiding division-by-zero issues that plague `atan(y/x)` implementations.
### Common Pitfalls
1. **Warmup**: This indicator needs significant warmup (at least 12 bars, ideally 50+) for the feedback loops (period smoothing) to stabilize. Don't trust the first 50 bars.
2. **Lag**: While it adapts, the trendline still lags because it's essentially a dynamic SMA. The advantage is that the period is optimal for the current market condition, not that it has zero lag.
3. **Complexity**: Debugging this is a nightmare. Trust the math.
4. **Ranging Markets**: In a pure range, the "trend" should be flat. HTIT handles this well because the cycle cancellation works best when the cycle is clear.
-64
View File
@@ -177,70 +177,6 @@ QuanTAlib validates HWMA against its PineScript reference implementation.
| **Tulip** | ❌ | Not included. |
| **Ooples** | ❌ | Not included. |
### C# Implementation Considerations
The QuanTAlib HWMA implementation optimizes triple exponential smoothing through FMA operations and precomputed decay constants:
**Precomputed Decay Constants**
```csharp
_alpha = 2.0 / (period + 1.0);
_beta = 1.0 / period;
_gamma = 1.0 / period;
_decayAlpha = 1.0 - _alpha;
_decayBeta = 1.0 - _beta;
_decayGamma = 1.0 - _gamma;
```
All six constants computed once at construction. The decay values (1-α, 1-β, 1-γ) avoid repeated subtraction per tick.
**State Record Struct with Auto Layout**
```csharp
[StructLayout(LayoutKind.Auto)]
private record struct State(
double F, double V, double A,
double LastValidValue,
bool IsInitialized
);
private State _state;
private State _p_state;
```
Compiler optimizes field ordering. Three smoothing components plus validation tracking fit in ~41 bytes.
**FusedMultiplyAdd Throughout**
```csharp
double forecast = prevF + prevV + 0.5 * prevA;
double newF = Math.FusedMultiplyAdd(forecast, _decayAlpha, _alpha * val);
double newV = Math.FusedMultiplyAdd(prevV + prevA, _decayBeta, _beta * (newF - prevF));
double newA = Math.FusedMultiplyAdd(prevA, _decayGamma, _gamma * (newV - prevV));
```
All three component updates use FMA for `a*b+c` patterns, reducing rounding error and leveraging hardware acceleration.
**O(1) Memory Footprint**
Unlike FIR filters, HWMA requires no buffer—only state variables. No `ArrayPool`, no `stackalloc`, no circular buffer management.
**Dual Constructor API**
```csharp
public Hwma(int period = 10) // Derives α, β, γ from period
public Hwma(double alpha, double beta, double gamma) // Explicit smoothing factors
```
Period-based constructor for common use; explicit factors for fine-tuned control with validation.
**Memory Layout**
| Field | Type | Size | Notes |
|:------|:-----|-----:|:------|
| `_period` | int | 4B | Conceptual period (display) |
| `_alpha` | double | 8B | Level smoothing |
| `_beta` | double | 8B | Velocity smoothing |
| `_gamma` | double | 8B | Acceleration smoothing |
| `_decayAlpha` | double | 8B | 1 - α |
| `_decayBeta` | double | 8B | 1 - β |
| `_decayGamma` | double | 8B | 1 - γ |
| `_state` | State | ~41B | F, V, A, lastValid, init flag |
| `_p_state` | State | ~41B | Previous state for rollback |
| **Total** | | ~142B | Fixed size, no period scaling |
HWMA has constant memory regardless of period—approximately **142 bytes** per instance.
## Common Pitfalls
1. **Overshoot During Reversals**: HWMA's acceleration component can cause overshoot when trends reverse sharply. The filter "expects" the trend to continue and takes time to adapt. Consider lower β/γ values for less aggressive acceleration tracking.
-103
View File
@@ -267,109 +267,6 @@ JMA is proprietary. No open-source library implements it. Validation is performe
7. **Using isNew Incorrectly**: When processing live ticks within the same bar, use `Update(value, isNew: false)`. When a new bar opens, use `isNew: true` (default). Getting this wrong corrupts state and buffer snapshots.
## C# Implementation Considerations
### Dual RingBuffer Architecture
The implementation uses two `RingBuffer` instances:
- `_devBuffer` (10 samples): Tracks local deviation for short-term volatility SMA
- `_volBuffer` (128 samples): Maintains the volatility distribution for trimmed mean calculation
Both buffers support `Snapshot()` / `Restore()` for bar correction when `isNew=false`.
### State Record Struct with Auto Layout
All IIR filter state is packed into a `record struct` with `LayoutKind.Auto` for compiler-optimized field ordering:
```csharp
[StructLayout(LayoutKind.Auto)]
private record struct State
{
public double UpperBand;
public double LowerBand;
public double LastC0;
public double LastC8;
public double LastA8;
public double LastJma;
public double LastPrice;
public int Bars;
}
```
### Precomputed Logarithms for Exp Optimization
Instead of computing `Math.Pow(base, exponent)` on every bar, the implementation precomputes `log(base)` and uses `Math.Exp(log_base * exponent)`:
```csharp
_logLengthDivider = Math.Log(Math.Max(_lengthDivider, 1e-12));
_logSqrtDivider = Math.Log(Math.Max(sqrtDivider, 1e-12));
// Later: Math.Exp(_logLengthDivider * d) instead of Math.Pow(_lengthDivider, d)
```
This replaces expensive `Math.Pow` (~80 cycles) with `Math.Exp` (~50 cycles).
### FusedMultiplyAdd for IIR Calculations
All EMA and IIR filter operations use `Math.FusedMultiplyAdd` for hardware-optimized precision:
```csharp
double c0 = Math.FusedMultiplyAdd(_state.LastC0, alpha, decay * value);
double c8 = Math.FusedMultiplyAdd(_state.LastC8, _lengthDivider, lengthDecay * (value - c0));
double a8 = Math.FusedMultiplyAdd(_state.LastA8, alpha2, Math.FusedMultiplyAdd(_phaseParam, c8, c0 - prevJma) * coef);
```
### Stack-Allocated Sorting Buffer
The trimmed mean calculation uses `stackalloc` instead of heap allocation:
```csharp
Span<double> sorted = stackalloc double[count]; // max 1KB for 128 doubles
_volBuffer.CopyTo(sorted);
sorted.Sort();
```
This eliminates GC pressure during the per-bar sort operation.
### SIMD-Accelerated Summation
The trimmed mean summation uses `SumSIMD()` extension method for vectorized addition of the 65 central values:
```csharp
return sorted.Slice(start, len).SumSIMD() / len;
```
### Bar Correction via State + Buffer Snapshots
The `_state` / `_p_state` pattern combined with buffer snapshots enables bar correction:
```csharp
if (isNew)
{
_p_state = _state;
_devBuffer.Snapshot();
_volBuffer.Snapshot();
}
else
{
_state = _p_state;
_devBuffer.Restore();
_volBuffer.Restore();
}
```
### Aggressive Inlining
All hot-path methods are decorated with `[MethodImpl(MethodImplOptions.AggressiveInlining)]`:
- `Step()`, `HandleStateSnapshot()`, `UpdateBands()`, `CalculateIIRFilter()`
- `CalculateJurikExponent()`, `CalculateTrimmedMean()`
### Memory Layout Summary
- **Two RingBuffers**: 10 × 8 + 128 × 8 = 1,104 bytes
- **State struct**: ~72 bytes (8 doubles + 1 int)
- **Precomputed coefficients**: ~56 bytes (7 doubles)
- **Total per instance**: ~1,250 bytes typical
## References
- Jurik Research. (1998-2005). "JMA White Papers." *jurikres.com* (archived).
-106
View File
@@ -111,109 +111,3 @@ Validated against TA-Lib, Skender, Tulip, and Ooples.
| **Skender** | ✅ | Matches `GetKama` |
| **Tulip** | ✅ | Matches `kama` |
| **Ooples** | ✅ | Matches `CalculateKaufmanAdaptiveMovingAverage` |
## C# Implementation Considerations
### Buffer Strategy
KAMA uses a **RingBuffer** for the sliding price window:
```csharp
private readonly RingBuffer _buffer; // period + 1 values
```
The buffer stores `period + 1` values to calculate the net change (`Price[0] - Price[period]`) while maintaining incremental volatility updates. Buffer indexing uses `[^1]` for newest, `[0]` for oldest, enabling O(1) change calculation.
### State Management
State uses a record struct with `LayoutKind.Auto`:
```csharp
[StructLayout(LayoutKind.Auto)]
private record struct State(double Kama, double VolatilitySum, double NextDiffOut, double LastValidValue);
```
| Field | Size | Purpose |
| :--- | :---: | :--- |
| `Kama` | 8 bytes | Current KAMA value |
| `VolatilitySum` | 8 bytes | Running sum of |ΔP| |
| `NextDiffOut` | 8 bytes | Pre-staged diff for next removal |
| `LastValidValue` | 8 bytes | NaN substitution fallback |
| **Total** | **32 bytes** | Compact state for rollback |
The `NextDiffOut` field enables O(1) volatility updates by pre-calculating `|buffer[0] - buffer[1]|` — the value that will exit the window on the next bar.
### FMA Optimization
Two FMA operations replace traditional arithmetic in the hot path:
**Smoothing Constant calculation:**
```csharp
// sc = er * (fastAlpha - slowAlpha) + slowAlpha
double sc = Math.FusedMultiplyAdd(er, _fastAlpha - _slowAlpha, _slowAlpha);
sc *= sc; // SC squaring for noise suppression
```
**KAMA update:**
```csharp
// kama = prevKama + sc * (val - prevKama)
_state.Kama = Math.FusedMultiplyAdd(sc, val - prevKama, prevKama);
```
Both follow the EMA smoothing pattern `α·new + (1-α)·old` expressed as FMA.
### Precomputed Constants
Alpha values are computed once at construction:
```csharp
_fastAlpha = 2.0 / (fastPeriod + 1); // Typically 2/3 ≈ 0.667
_slowAlpha = 2.0 / (slowPeriod + 1); // Typically 2/31 ≈ 0.065
```
The difference `_fastAlpha - _slowAlpha` is computed at runtime (not stored) since it's used only once per bar.
### Static Calculate Path
The span-based method uses conditional allocation:
```csharp
Span<double> buffer = bufSize <= 256 ? stackalloc double[bufSize] : new double[bufSize];
```
For typical periods (≤255), this allocates on the stack. The circular buffer logic uses modular arithmetic:
```csharp
int prevIdx = (bufferIdx - 1 + bufSize) % bufSize;
int oldestIdx = (bufferIdx + 1) % bufSize;
bufferIdx = (bufferIdx + 1) % bufSize;
```
### Efficiency Ratio Bounds
The implementation guards against edge cases:
```csharp
double er = (volatility > 1e-10) ? change / volatility : 0.0;
if (er > 1.0) er = 1.0; // Cap floating-point drift
```
The epsilon guard (1e-10) prevents division by zero in flat markets, while the ER cap handles numerical precision issues where accumulated volatility might slightly undercount actual change.
### Memory Layout Summary
| Component | Size | Notes |
| :--- | :---: | :--- |
| RingBuffer | 8 + period×8 bytes | Header + price array |
| State | 32 bytes | 4 doubles |
| p_state | 32 bytes | Rollback copy |
| Constants | 16 bytes | Fast/slow alpha |
| **Per-instance** | **~168 bytes** | For period=10 |
### Common Pitfalls
1. **Flatlining**: In very choppy markets, KAMA can become almost horizontal. This is a feature, not a bug—it's telling you to stay out.
2. **Parameters**: The standard settings are (10, 2, 30). 10 is the ER period, 2 is the fast EMA, 30 is the slow EMA. Tweaking the ER period changes the sensitivity to noise.
3. **Trend Following**: KAMA is excellent for trailing stops because it flattens out when momentum stalls.
-83
View File
@@ -146,89 +146,6 @@ TEMA is inherently recursive due to cascaded EMAs. SIMD parallelization across b
| **Tulip** | ✅ | Matches `tema` exactly. |
| **Ooples** | ❌ | Diverges significantly due to initialization logic. |
## C# Implementation Considerations
### State Management
TEMA maintains six EmaState instances—three current, three previous—enabling atomic rollback on bar corrections:
```csharp
private record struct EmaState(double Ema, double E, bool IsHot, bool IsCompensated);
private EmaState _state1, _state2, _state3;
private EmaState _p_state1, _p_state2, _p_state3;
```
The `E` field tracks bias compensation factor for each EMA stage independently. Each state auto-transitions via `IsCompensated` flag when bias becomes negligible.
### Precomputed Constants
Constructor calculates smoothing constants once:
```csharp
_alpha = 2.0 / (period + 1);
_decay = 1 - _alpha;
```
These constants are reused across all three EMA stages, avoiding repeated division.
### FMA Usage
Each EMA update uses FusedMultiplyAdd for the standard EMA formula:
```csharp
double newEma = Math.FusedMultiplyAdd(state.Ema, _decay, _alpha * input);
```
The final TEMA combination `3*e1 - 3*e2 + e3` could use FMA but the coefficients (3, -3, 1) make chained FMA marginal; current implementation uses direct arithmetic.
### Bar Correction Pattern
TEMA's cascaded structure requires coordinated state rollback:
```csharp
if (isNew)
{
_p_state1 = _state1;
_p_state2 = _state2;
_p_state3 = _state3;
}
else
{
_state1 = _p_state1;
_state2 = _p_state2;
_state3 = _p_state3;
}
```
All three stages rollback atomically, ensuring consistent cascade state when `isNew=false`.
### Memory Layout
| Field | Type | Size | Purpose |
| :--- | :--- | :---: | :--- |
| `_alpha` | double | 8B | Smoothing constant |
| `_decay` | double | 8B | 1 - alpha |
| `_state1` | EmaState | 24B | First EMA state |
| `_state2` | EmaState | 24B | Second EMA state |
| `_state3` | EmaState | 24B | Third EMA state |
| `_p_state1` | EmaState | 24B | Previous state 1 |
| `_p_state2` | EmaState | 24B | Previous state 2 |
| `_p_state3` | EmaState | 24B | Previous state 3 |
| **Total** | | **160B** | Per indicator instance |
Each EmaState contains: Ema (8B), E (8B), IsHot (1B), IsCompensated (1B) + padding (~6B) = ~24B.
### Common Pitfalls
1. **Overshoot**: TEMA is so responsive it can overshoot price turns, creating a "whiplash" effect in volatile markets.
2. **Noise**: By reducing lag, TEMA sacrifices some noise suppression. It is "nervous" compared to an SMA.
3. **Identity Crisis**: Often confused with T3 (Tillson). T3 is a generalized version; TEMA is specifically T3 with $v=1$.
4. **Warmup period**: Requires 3× period bars before producing valid output; premature signals are unreliable.
5. **Parameter sensitivity**: Small period values (< 10) create excessive noise; large values (> 50) reduce responsiveness.
6. **False signals**: In choppy, sideways markets, frequent crossovers generate misleading signals.
7. **Computational cost**: 4× more expensive than simple EMA due to cascaded calculations.
## FAQ
**Q: How does TEMA differ from a triple-smoothed EMA?**

Some files were not shown because too many files have changed in this diff Show More