mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-23 21:18:04 +00:00
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:
@@ -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.
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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**
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user