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
-88
View File
@@ -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.