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
-116
View File
@@ -172,122 +172,6 @@ QuanTAlib validates against reference implementations that respect the Gaussian
| **TA-Lib** | ❌ | Not included in standard C distribution. |
| **Tulip** | ❌ | Not included. |
## C# Implementation Considerations
### Precomputed Gaussian Weights
Weights are computed once in the constructor and stored in a `double[]` array:
```csharp
_weights = new double[period];
ComputeWeights(_weights, period, offset, sigma, out _invWeightSum);
```
The inverse of the weight sum is precomputed for multiplication instead of division in the hot path.
### State Record Struct with Auto Layout
Minimal state for bar correction:
```csharp
[StructLayout(LayoutKind.Auto)]
private record struct State(double LastValidValue, bool IsInitialized);
```
The `LayoutKind.Auto` lets the JIT optimize field placement for cache efficiency.
### SIMD-Optimized Dot Product
The weighted sum calculation delegates to a SIMD-optimized `DotProduct` extension method:
```csharp
double sum1 = internalBuf.Slice(head, part1Len).DotProduct(_weights.AsSpan(0, part1Len));
double sum2 = internalBuf[..head].DotProduct(_weights.AsSpan(part1Len));
return (sum1 + sum2) * _invWeightSum;
```
The dot product leverages AVX2/AVX-512/NEON intrinsics internally, achieving up to 8× speedup.
### Circular Buffer Handling
The RingBuffer's internal array is accessed directly to split the dot product across the wrap boundary:
```csharp
ReadOnlySpan<double> internalBuf = _buffer.InternalBuffer;
int head = _buffer.StartIndex;
int part1Len = _period - head;
// Part 1: head..end with weights[0..part1Len]
// Part 2: 0..head with weights[part1Len..period]
```
This avoids copying the buffer into a contiguous array.
### Stackalloc/ArrayPool Allocation Strategy
The static `Calculate` method uses stackalloc for small periods and ArrayPool for large:
```csharp
double[]? weightsArray = period > 256 ? ArrayPool<double>.Shared.Rent(period) : null;
Span<double> weights = period <= 256
? stackalloc double[period]
: weightsArray!.AsSpan(0, period);
```
The 256-element threshold balances stack safety with allocation overhead.
### NaN Handling with Initialization Tracking
Non-finite inputs are replaced with the last valid value, with explicit tracking for uninitialized state:
```csharp
private double GetValidValue(double input)
{
if (double.IsFinite(input))
return input;
return _state.IsInitialized ? _state.LastValidValue : double.NaN;
}
```
This prevents NaN propagation while correctly handling series that start with invalid values.
### Incremental Weight Sum for Warmup
During the warmup period, the weight sum is computed incrementally:
```csharp
if (count < period)
{
count++;
currentWeightSum += weights[period - count];
}
```
This avoids recalculating the partial sum on each bar during convergence.
### Separate Internal Update Method
The `Update` method has a private overload with a `publish` parameter:
```csharp
private TValue Update(TValue input, bool isNew, bool publish)
```
This allows state restoration after batch processing without firing events.
### Memory Layout
| Component | Size | Purpose |
| :--- | :--- | :--- |
| `_weights` | 8×period bytes | Precomputed Gaussian weights |
| `_buffer` (RingBuffer) | 32 + 8×period bytes | Sliding window history |
| `_state` | ~16 bytes | LastValidValue, IsInitialized |
| `_p_state` | ~16 bytes | Previous state for rollback |
| Scalars | ~40 bytes | Period, offset, sigma, invWeightSum |
| **Total** | **~104 + 16N bytes** | Per-instance footprint |
For ALMA(50), total memory is approximately 900 bytes per instance.
## Common Pitfalls
1. **Offset Abuse**: Setting offset to `0.99` creates a filter that barely filters. It tracks price so closely you might as well use `Price[0]`. Setting it to `0.5` makes it a centered moving average (great for smoothing, terrible for trading due to repainting if used as such, but ALMA does not repaint). The magic is in the `0.85` region.