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
-139
View File
@@ -116,145 +116,6 @@ BLMA is validated against a reference implementation using the standard Blackman
| **QuanTAlib** | ✅ | Matches theoretical formula. |
| **PineScript** | ✅ | Matches PineScript reference logic. |
### C# Implementation Considerations
The QuanTAlib BLMA implementation emphasizes precomputation and zero-allocation streaming:
#### Precomputed Weights Array
Blackman window weights are calculated once in the constructor and reused for every update:
```csharp
public Blma(int period)
{
_weights = new double[period];
_weightSum = CalculateWeights(period, _weights);
}
private static double CalculateWeights(int n, Span<double> weights)
{
const double a0 = 0.42;
const double a1 = 0.5;
const double a2 = 0.08;
double invNMinus1 = 1.0 / (n - 1);
for (int i = 0; i < n; i++)
{
double ratio = i * invNMinus1;
double w = a0 - (a1 * Math.Cos(2.0 * Math.PI * ratio))
+ (a2 * Math.Cos(4.0 * Math.PI * ratio));
weights[i] = w;
totalWeight += w;
}
return totalWeight;
}
```
#### RingBuffer with DotProduct Extension
The weighted sum uses an optimized dot product that handles circular buffer wraparound:
```csharp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double CalculateWeightedSum(RingBuffer buffer, ReadOnlySpan<double> weights)
{
int start = buffer.StartIndex;
int count = buffer.Count;
int capacity = buffer.Capacity;
if (start + count <= capacity)
{
// Contiguous case - single dot product
return buffer.InternalBuffer.Slice(start, count).DotProduct(weights);
}
// Wraparound case - two dot products
int firstPartLength = capacity - start;
int secondPartLength = count - firstPartLength;
double sum1 = buffer.InternalBuffer.Slice(start, firstPartLength).DotProduct(weights[..firstPartLength]);
double sum2 = buffer.InternalBuffer.Slice(0, secondPartLength).DotProduct(weights[firstPartLength..]);
return sum1 + sum2;
}
```
#### Dynamic Warmup Weights
During warmup (fewer than `period` bars), weights are calculated dynamically using stackalloc:
```csharp
if (_buffer.Count < _period)
{
int count = _buffer.Count;
Span<double> currentWeights = stackalloc double[count];
double currentWeightSum = CalculateWeights(count, currentWeights);
result = ComputeWeightedAverage(currentWeightSum, weightedSum, _buffer.Average());
}
```
#### Stackalloc Strategy for Batch Processing
The static `Calculate` method uses stackalloc for small periods (≤256) to avoid heap allocation:
```csharp
Span<double> weights = period <= 256 ? stackalloc double[period] : new double[period];
double weightSum = CalculateWeights(period, weights);
// Buffer for warmup weights to avoid stackalloc in loop
Span<double> warmupWeightsBuffer = period <= 256 ? stackalloc double[period] : new double[period];
```
#### NaN Handling with Last-Valid-Value Substitution
Invalid values are substituted with the last valid value to maintain calculation continuity:
```csharp
double val = input.Value;
if (!double.IsFinite(val))
{
return Last; // Return last result without changing state
}
```
In batch mode:
```csharp
double lastValid = double.NaN;
for (int i = 0; i < source.Length; i++)
{
double val = source[i];
if (!double.IsFinite(val))
val = double.IsNaN(lastValid) ? 0 : lastValid;
else
lastValid = val;
// ...
}
```
#### AggressiveInlining on Hot Paths
Critical methods are marked for inlining:
```csharp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static double ComputeWeightedAverage(double weightSum, double weightedSum, double fallbackAverage)
{
return Math.Abs(weightSum) < double.Epsilon ? fallbackAverage : weightedSum / weightSum;
}
```
#### Memory Layout
| Field | Type | Size | Purpose |
| :--- | :--- | :---: | :--- |
| `_period` | `int` | 4 | Window size |
| `_buffer` | `RingBuffer` | 8 (ref) | Circular price storage |
| `_weights` | `double[]` | 8 (ref) | Precomputed Blackman weights |
| `_weightSum` | `double` | 8 | Sum of weights (precomputed) |
| **Total** | | **~28 bytes** | Per instance (excluding buffer/array internals) |
**Weight array storage:** `period × 8` bytes (e.g., 160 bytes for period=20)
### Common Pitfalls
* **Lag**: BLMA has more lag than EMA or WMA because it suppresses the most recent data. It is a smoothing filter, not a leading indicator.