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