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
-145
View File
@@ -191,151 +191,6 @@ Self-consistency validation ensures:
* NaN handling substitutes last valid value
* Reset produces identical results on replay
### C# Implementation Considerations
The QuanTAlib BWMA implementation optimizes for streaming throughput with precomputed weights and zero-allocation hot paths:
#### Precomputed Weights with Inverse Sum
Weights and the inverse of their sum are calculated once in the constructor, replacing division with multiplication:
```csharp
public Bwma(int period, int order = 0)
{
_weights = new double[period];
ComputeWeights(_weights, period, order, out _invWeightSum);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ComputeWeights(Span<double> weights, int period, int order, out double invWeightSum)
{
double sum = 0;
double scale = period > 1 ? 2.0 / (period - 1) : 0.0;
double power = order * 0.5 + 0.5;
for (int i = 0; i < period; i++)
{
double x = period > 1 ? i * scale - 1.0 : 0.0;
double arg = 1.0 - x * x;
double w = arg > 0.0 ? Math.Pow(arg, power) : 0.0;
weights[i] = w;
sum += w;
}
invWeightSum = sum > 0 ? 1.0 / sum : 0.0; // Precompute inverse
}
```
#### State Record Struct with Auto Layout
State uses `LayoutKind.Auto` for compiler-optimized field arrangement:
```csharp
[StructLayout(LayoutKind.Auto)]
private record struct State
{
public double LastValidValue;
public bool IsInitialized;
}
private State _state;
private State _p_state; // Previous state for bar correction
```
#### FusedMultiplyAdd in Warmup Path
The warmup calculation uses FMA for coordinate mapping and argument computation:
```csharp
for (int i = 0; i < p; i++)
{
double x = Math.FusedMultiplyAdd(i, scale, -1.0); // x = i * scale - 1.0
double arg = Math.FusedMultiplyAdd(-x, x, 1.0); // arg = 1.0 - x * x
// ...
sum = Math.FusedMultiplyAdd(window[i], w, sum); // sum += window[i] * w
}
```
#### Optimized Circular Buffer DotProduct
The hot path handles ring buffer wraparound with two slice dot products:
```csharp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private double CalculateWeightedSum(double fallbackValue)
{
if (_invWeightSum == 0.0) return fallbackValue;
ReadOnlySpan<double> internalBuf = _buffer.InternalBuffer;
int head = _buffer.StartIndex;
int part1Len = _period - head;
double sum1 = internalBuf.Slice(head, part1Len).DotProduct(_weights.AsSpan(0, part1Len));
double sum2 = internalBuf[..head].DotProduct(_weights.AsSpan(part1Len));
return (sum1 + sum2) * _invWeightSum; // Multiply by precomputed inverse
}
```
#### ArrayPool for Large Periods in Batch Mode
The static `Calculate` method uses ArrayPool for periods >256 to avoid large stack allocations:
```csharp
double[]? weightsArray = period > 256 ? ArrayPool<double>.Shared.Rent(period) : null;
Span<double> weights = period <= 256
? stackalloc double[period]
: weightsArray!.AsSpan(0, period);
double[]? ringArray = period > 256 ? ArrayPool<double>.Shared.Rent(period) : null;
Span<double> ring = period <= 256
? stackalloc double[period]
: ringArray!.AsSpan(0, period);
try
{
// Processing loop...
}
finally
{
if (weightsArray != null) ArrayPool<double>.Shared.Return(weightsArray);
if (ringArray != null) ArrayPool<double>.Shared.Return(ringArray);
}
```
#### PineScript-Exact Order Handling
The implementation matches PineScript behavior with special cases for orders 0 and 1:
```csharp
if (order == 0)
{
w = arg; // (1 - x²)^1.0 - parabolic
}
else if (order == 1)
{
w = arg * Math.Sqrt(arg); // (1 - x²)^1.5 - avoids Math.Pow overhead
}
else
{
w = Math.Pow(arg, power); // (1 - x²)^power
}
```
#### Memory Layout
| Field | Type | Size | Purpose |
| :--- | :--- | :---: | :--- |
| `_period` | `int` | 4 | Window length |
| `_order` | `int` | 4 | Bessel order parameter |
| `_power` | `double` | 8 | Precomputed exponent |
| `_weights` | `double[]` | 8 (ref) | Precomputed weights |
| `_invWeightSum` | `double` | 8 | Inverse of weight sum |
| `_buffer` | `RingBuffer` | 8 (ref) | Circular price storage |
| `_state` | `State` | 16 | Current state (LastValidValue, IsInitialized) |
| `_p_state` | `State` | 16 | Previous state for rollback |
| **Total** | | **~72 bytes** | Per instance (excluding buffer/array internals) |
**Weight array storage:** `period × 8` bytes (e.g., 160 bytes for period=20)
## Common Pitfalls
1. **Order Selection Paralysis**: Start with order 0 (parabolic). It's the most balanced choice. Higher orders provide sharper filtering but may over-smooth trend transitions.