mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-12 15:48:05 +00:00
feat: Implement ADX Indicator with Quantower integration
This commit is contained in:
@@ -26,6 +26,17 @@ We do not store objects in lists. We store primitive arrays.
|
||||
* `TSeries`: The primary data structure for time series.
|
||||
* `ITValuePublisher`: The interface for reactive data flow.
|
||||
|
||||
### Design Principles
|
||||
|
||||
* **Source Material:** The algorithm and markdown documentation foundation should be sourced from [https://github.com/mihakralj/pinescript/blob/main/indicators/](PineScript).
|
||||
* **Zero Allocation:** The core calculation loop must not allocate memory on the heap. Use `stackalloc`, `Span<T>`, and pinned memory where possible.
|
||||
* **O(1) Complexity:** Streaming updates must be O(1) whenever mathematically possible. Use running sums/products or circular buffers to avoid re-iterating over history.
|
||||
* **Dual API:** Provide both a stateful object-oriented API (`Update`) and a stateless static vector API (`Calculate`).
|
||||
* **Bar Correction:** Support intra-bar updates via the `isNew` parameter. The indicator must be able to rollback the last update and apply a new value for the same timestamp.
|
||||
* **Robustness:** Handle `NaN` and `Infinity` gracefully using last-valid-value substitution. Never propagate invalid values.
|
||||
* **Reactive:** Implement `ITValuePublisher` to support event-driven architectures.
|
||||
* **Time Handling:** Always use `DateTime.UtcNow` instead of `DateTime.Now` to ensure consistent time handling across timezones.
|
||||
|
||||
### Performance Rules
|
||||
|
||||
1. **Zero Allocation**: The `Update` method MUST NOT allocate memory on the heap. Use `stackalloc` or pre-allocated buffers.
|
||||
@@ -44,13 +55,33 @@ Directory: `lib/[category]/[name]/` (e.g., `lib/trends/sma/`)
|
||||
|
||||
| File | Naming | Purpose |
|
||||
|------|--------|---------|
|
||||
| **Source** | `[Name].cs` | Main logic. `public sealed class`. |
|
||||
| **Source** | `[Name].cs` | Main implementation. `public sealed class`. |
|
||||
| **Tests** | `[Name].Tests.cs` | xUnit tests (correctness, edge cases). |
|
||||
| **Validation** | `[Name].Validation.Tests.cs` | Compare against TA-Lib, Skender, etc. |
|
||||
| **Docs** | `[Name].md` | User documentation with formulas. |
|
||||
| **Adapter** | `[Name].Quantower.cs` | Quantower platform integration. |
|
||||
| **Adapter Tests** | `[Name].Quantower.Tests.cs` | Tests for the adapter. |
|
||||
|
||||
### Class Definition
|
||||
|
||||
* **Namespace:** `QuanTAlib`
|
||||
* **Attributes:** `[SkipLocalsInit]` for performance.
|
||||
* **Modifiers:** `public sealed class`
|
||||
* **Interface:** Implements `ITValuePublisher`
|
||||
|
||||
### State Management
|
||||
|
||||
* **Scalar State:** Use a `private record struct State` to group all scalar state variables. This ensures value semantics, automatic `IEquatable` implementation, and cleaner rollback logic.
|
||||
* **State Variables:** Maintain `private State _state;` (current) and `private State _p_state;` (previous valid state).
|
||||
* **Buffers:** Use `RingBuffer` for sliding window data.
|
||||
* **Resync:** Implement a periodic full recalculation (e.g., every 1000 ticks) to prevent floating-point drift in running sums.
|
||||
|
||||
### Constructor
|
||||
|
||||
* Validate all parameters (throw `ArgumentException` for invalid values).
|
||||
* Initialize `Name` property (e.g., `$"Sma({period})"`);
|
||||
* Support chaining: `public [Name](ITValuePublisher source, ...)`
|
||||
|
||||
### The `Update` Method Contract
|
||||
|
||||
The `Update` method is the heart of the indicator.
|
||||
@@ -59,35 +90,94 @@ The `Update` method is the heart of the indicator.
|
||||
public TValue Update(TValue input, bool isNew = true)
|
||||
```
|
||||
|
||||
* **`isNew = true`**: A new bar has arrived. Save current state to history (or `_p_` variables), then calculate.
|
||||
* **`isNew = false`**: The current bar is updating (tick data). Restore state from history (or `_p_` variables), then recalculate.
|
||||
* **NaN Handling**: If input is `NaN` or `Infinity`, use the last valid value. Never propagate `NaN`.
|
||||
* **Attribute:** `[MethodImpl(MethodImplOptions.AggressiveInlining)]`
|
||||
* **Logic:**
|
||||
1. **State Rollback:**
|
||||
|
||||
### State Management
|
||||
```csharp
|
||||
if (isNew) {
|
||||
_p_state = _state;
|
||||
// ... update state (e.g. counters) ...
|
||||
} else {
|
||||
_state = _p_state;
|
||||
// ... update state ...
|
||||
}
|
||||
```
|
||||
|
||||
* **Scalar State:** Use a `private record struct State` to group all scalar state variables. This ensures value semantics, automatic `IEquatable` implementation, and cleaner rollback logic.
|
||||
* **State Variables:** Maintain `private State _state;` (current) and `private State _p_state;` (previous valid state).
|
||||
* **Buffers:** Use `RingBuffer` for sliding windows.
|
||||
* **Resync:** Periodically recalculate running sums to prevent floating-point drift.
|
||||
2. **Input Validation:** Check `double.IsFinite`. If not, use `_lastValidValue` (stored in `State`).
|
||||
3. **Calculation:** Perform the math.
|
||||
4. **Publish:** Update `Last` property, invoke `Pub` event, return `Last`.
|
||||
|
||||
### Dual API Requirement
|
||||
### Update Method (TSeries)
|
||||
|
||||
1. **Stateful (Streaming)**: `Update(TValue)` for live data.
|
||||
2. **Stateless (Vector)**: `static void Calculate(ReadOnlySpan<double> src, Span<double> dst)` for batch history.
|
||||
* **Signature:** `public TSeries Update(TSeries source)`
|
||||
* **Placement:** Must be adjacent to the `Update(TValue)` method.
|
||||
* **Logic:**
|
||||
1. Create output series.
|
||||
2. Call static `Calculate(Span)` for performance.
|
||||
3. Restore internal state by replaying the last `Period` bars (or full series if recursive).
|
||||
|
||||
### Static Calculate (TSeries)
|
||||
|
||||
* Create a new instance of the indicator.
|
||||
* Iterate through the source series.
|
||||
* Return the resulting `TSeries`.
|
||||
|
||||
### Static Calculate (Span) - **Critical for Performance**
|
||||
|
||||
* **Signature:** `public static void Calculate(ReadOnlySpan<double> source, Span<double> output, ...)`
|
||||
* **Attribute:** `[MethodImpl(MethodImplOptions.AggressiveInlining)]`
|
||||
* **Optimization:**
|
||||
* Check for SIMD support (`Avx2.IsSupported`).
|
||||
* Use `stackalloc` for small buffers (threshold ~256) and for internal state buffers in recursive algorithms where SIMD is not applicable.
|
||||
* Implement a scalar fallback path that handles `NaN` safely.
|
||||
* Implement a SIMD path for large, clean datasets (optional but recommended for simple averages).
|
||||
|
||||
## 4. Testing Protocol
|
||||
|
||||
### Unit Tests (`[Name].Tests.cs`)
|
||||
|
||||
* Use `GBM` (Geometric Brownian Motion) for data generation.
|
||||
* Test `isNew=true` vs `isNew=false` consistency.
|
||||
* Test `Reset()` and `IsHot` (warmup).
|
||||
* Test edge cases: `NaN` inputs, empty series, period=1.
|
||||
* **Framework:** xUnit
|
||||
* **Data Generation:** Use `GBM` (Geometric Brownian Motion) for generating realistic test data. Avoid using `System.Random` directly.
|
||||
* **Coverage:**
|
||||
* Constructor validation (invalid params).
|
||||
* Basic calculation correctness (compare against manual calc).
|
||||
* `isNew=true` vs `isNew=false` behavior (bar correction).
|
||||
* `Reset()` functionality.
|
||||
* `IsHot` property behavior.
|
||||
* `NaN` / `Infinity` handling (must not crash, must return finite values).
|
||||
* Consistency between Object API, Static TSeries API, and Static Span API.
|
||||
* Edge cases: Period=1, empty input, single input.
|
||||
|
||||
### Validation Tests (`[Name].Validation.Tests.cs`)
|
||||
|
||||
* **Mandatory**: You MUST validate against at least one external authority (TA-Lib, Skender, Tulip, OoplesFinance, Python libs).
|
||||
* **Tolerance**: Typically `1e-6` to `1e-9`.
|
||||
* **Data**: Use `ValidationTestData` class which wraps `GBM` (Geometric Brownian Motion) to generate realistic test data and provides pre-calculated Skender quotes.
|
||||
|
||||
#### External Library Usage Guide
|
||||
|
||||
* **Skender.Stock.Indicators:**
|
||||
* Use `_data.SkenderQuotes.Get[Indicator](...)`.
|
||||
* Compare using `ValidationHelper.VerifyData`.
|
||||
|
||||
* **TA-Lib (TALib.NETCore):**
|
||||
* Namespace: `using TALib;`
|
||||
* Method: `TALib.Functions.[Indicator]<double>(...)`.
|
||||
* Check `Assert.Equal(Core.RetCode.Success, retCode)`.
|
||||
* Use `ValidationHelper.VerifyData` with `outRange` and `lookback`.
|
||||
|
||||
* **Tulip (Tulip.NETCore):**
|
||||
* Namespace: `using Tulip;`
|
||||
* Method: `Tulip.Indicators.[indicator].Run(...)`.
|
||||
* Handle lookback/offset manually (Tulip output is shorter than input).
|
||||
* Use `ValidationHelper.VerifyData` with `lookback`.
|
||||
|
||||
* **OoplesFinance.StockIndicators:**
|
||||
* Namespace: `using OoplesFinance.StockIndicators;`
|
||||
* Convert data: `_data.SkenderQuotes.Select(q => new TickerData { ... }).ToList()`.
|
||||
* Use `new StockData(ooplesData).Calculate[Indicator](...)`.
|
||||
* Compare using `ValidationHelper.VerifyData`.
|
||||
|
||||
## 5. Documentation Standards
|
||||
|
||||
@@ -95,8 +185,20 @@ public TValue Update(TValue input, bool isNew = true)
|
||||
* **Content**: Title, Description, Parameters, Formula (LaTeX), C# Usage Examples.
|
||||
* **Index**: Add the new indicator to the category index (e.g., `lib/trends/_index.md`).
|
||||
* **Linting**: Ensure that markdownlint shows no issues for the file.
|
||||
* **MD030:** Ensure exactly one space after list markers.
|
||||
* **MD032:** Ensure lists are surrounded by blank lines.
|
||||
|
||||
## 6. Development Checklist
|
||||
## 6. Quantower Adapter
|
||||
|
||||
* **Implementation:** Create a wrapper class in `[Name].Quantower.cs` that adapts the QuanTAlib indicator for the Quantower platform.
|
||||
* **Tests:** Create unit tests in `[Name].Quantower.Tests.cs` to verify the adapter's functionality using mocks where necessary.
|
||||
|
||||
## 7. Code Review
|
||||
|
||||
* **Tool:** Run CodeRabbit on the changes.
|
||||
* **Requirement:** Address and fix **ALL** issues identified by the CodeRabbit review before considering the task complete.
|
||||
|
||||
## 8. Development Checklist
|
||||
|
||||
When creating a new indicator, you are **DONE** only when:
|
||||
|
||||
@@ -108,9 +210,10 @@ When creating a new indicator, you are **DONE** only when:
|
||||
* [ ] Unit tests pass (including edge cases).
|
||||
* [ ] Validation tests pass against external libs.
|
||||
* [ ] Documentation is complete and linked in `_index.md`.
|
||||
* [ ] Quantower adapter and tests are implemented.
|
||||
* [ ] CodeRabbit review issues are resolved.
|
||||
|
||||
## 7. Forbidden Actions
|
||||
## 9. Forbidden Actions
|
||||
|
||||
* **DO NOT** use LINQ in hot paths (`Update` or `Calculate`).
|
||||
* **DO NOT** use `new` inside `Update`.
|
||||
@@ -118,7 +221,7 @@ When creating a new indicator, you are **DONE** only when:
|
||||
* **DO NOT** remove `[SkipLocalsInit]` or `[MethodImpl]` attributes.
|
||||
* **DO NOT** ignore `NaN` inputs; handle them safely.
|
||||
|
||||
## 8. Context & Resources
|
||||
## 10. Context & Resources
|
||||
|
||||
* **Time**: Use `DateTime.UtcNow`.
|
||||
* **Math**: Use `System.Math` or `System.Numerics`.
|
||||
|
||||
Reference in New Issue
Block a user