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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user