mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-26 06:18:05 +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:
@@ -116,91 +116,3 @@ For 512 bars:
|
||||
| **Skender** | ✅ | Matches `GetSma` exactly. |
|
||||
| **Tulip** | ✅ | Matches `sma` exactly. |
|
||||
| **Ooples** | ✅ | Matches `CalculateSimpleMovingAverage`. |
|
||||
|
||||
## C# Implementation Considerations
|
||||
|
||||
### RingBuffer for O(1) Running Sum
|
||||
|
||||
The implementation maintains a `RingBuffer` of the most recent $N$ values alongside a running `Sum`. On each update, the oldest value is subtracted and the newest added—eliminating the need to iterate over the entire window:
|
||||
|
||||
```csharp
|
||||
Sum = Math.FusedMultiplyAdd(-_buffer[^1], 1, Sum + p);
|
||||
_buffer.Add(p, isNew);
|
||||
```
|
||||
|
||||
Using `FusedMultiplyAdd` for the combined subtraction/addition improves numerical stability compared to separate operations.
|
||||
|
||||
### State Record Struct
|
||||
|
||||
Minimal state is captured in a `record struct` for efficient bar correction:
|
||||
|
||||
```csharp
|
||||
private record struct State(double Sum, double LastValidValue, int TickCount);
|
||||
```
|
||||
|
||||
When `isNew=false`, the implementation restores `_p_state` to revert any partial calculation—enabling accurate bar correction when the same timestamp updates multiple times.
|
||||
|
||||
### Periodic Resync for Drift Correction
|
||||
|
||||
Floating-point drift accumulates over millions of additions/subtractions. The implementation resyncs every 1000 ticks:
|
||||
|
||||
```csharp
|
||||
if (_state.TickCount >= ResyncPeriod)
|
||||
{
|
||||
_state = _state with { Sum = _buffer.Span.Sum(), TickCount = 0 };
|
||||
}
|
||||
```
|
||||
|
||||
This bounds cumulative error to within `1e-9` of true mean regardless of stream length.
|
||||
|
||||
### Multi-Architecture SIMD Implementation
|
||||
|
||||
The static `Calculate` method dispatches to architecture-specific implementations:
|
||||
|
||||
```csharp
|
||||
if (Avx512F.IsSupported) CalculateAvx512Core(source, output, period);
|
||||
else if (Avx2.IsSupported) CalculateAvx2Core(source, output, period);
|
||||
else if (AdvSimd.Arm64.IsSupported) CalculateNeonCore(source, output, period);
|
||||
else CalculateScalarCore(source, output, period);
|
||||
```
|
||||
|
||||
- **AVX-512**: Processes 8 doubles simultaneously with 512-bit vectors
|
||||
- **AVX2**: Processes 4 doubles with 256-bit vectors
|
||||
- **NEON (ARM64)**: Processes 2 doubles with 128-bit vectors
|
||||
- **Scalar fallback**: Portable loop for unsupported architectures
|
||||
|
||||
### Prefix-Sum Vectorization
|
||||
|
||||
For batch processing, the SIMD paths use a prefix-sum technique that enables parallel computation of running sums. The initial window sum is computed with vectorized horizontal addition, then subsequent values use the optimized running-sum pattern.
|
||||
|
||||
### ArrayPool for Memory Efficiency
|
||||
|
||||
Large period buffers are rented from `ArrayPool<double>` rather than allocated, reducing GC pressure during batch operations. Combined with `stackalloc` for small intermediate buffers, this achieves zero-allocation in hot paths.
|
||||
|
||||
### NaN Handling with Last-Valid Substitution
|
||||
|
||||
Non-finite inputs are replaced with the last valid value stored in state:
|
||||
|
||||
```csharp
|
||||
p = double.IsFinite(p) ? p : _state.LastValidValue;
|
||||
```
|
||||
|
||||
This prevents NaN propagation through the running sum without requiring expensive validation on every buffer access.
|
||||
|
||||
### Memory Layout
|
||||
|
||||
| Component | Size | Purpose |
|
||||
| :--- | :--- | :--- |
|
||||
| `_buffer` (RingBuffer) | 32 + 8×period bytes | Sliding window history |
|
||||
| `_state` | ~24 bytes | Sum, LastValidValue, TickCount |
|
||||
| `_p_state` | ~24 bytes | Previous state for rollback |
|
||||
| Scalars | ~16 bytes | Period, reciprocal |
|
||||
| **Total** | **~96 + 8N bytes** | Per-instance footprint |
|
||||
|
||||
For SMA(200), total memory is approximately 1.7 KB per instance.
|
||||
|
||||
### Common Pitfalls
|
||||
|
||||
1. **Lag**: SMA has the most lag of all moving averages (Lag $\approx N/2$).
|
||||
2. **Drop-off Effect**: An old, large outlier dropping out of the window causes the SMA to jump, even if the current price is flat. This "Barker effect" is why EMAs are often preferred.
|
||||
3. **NaN Handling**: A single `NaN` in the history window corrupts the entire SMA. QuanTAlib handles this by substituting the last valid value.
|
||||
|
||||
Reference in New Issue
Block a user