diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index e9345c0b..acf70bec 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -1,74 +1,91 @@
# QuanTAlib AI Coding Agent Instructions
## Project Overview
-QuanTAlib is a high-performance C# library for quantitative technical analysis, targeting .NET 8.0 with real-time streaming data processing. The library provides 50+ technical indicators optimized for sub-millisecond calculations using circular buffers, SIMD operations, and event-driven architecture.
+QuanTAlib is a high-performance C# library for quantitative technical analysis targeting .NET 8.0. Provides 50+ technical indicators optimized for sub-millisecond real-time streaming calculations using circular buffers, SIMD operations, and event-driven architecture. Used in production live trading environments.
## Critical Architecture Patterns
### Core Data Flow
-All indicators inherit from `AbstractBase` (in `lib/core/abstractBase.cs`) which implements `ITValue`:
+All indicators inherit from `AbstractBase` (`lib/core/abstractBase.cs`) implementing `ITValue`:
```csharp
// Standard indicator lifecycle:
-Input → Calc() → ManageState(isNew) → Calculation() → Process() → Pub event
+TValue/TBar Input → Calc() → ManageState(isNew) → Calculation() → Process() → Pub event
```
-**Key insight**: The `isNew` parameter distinguishes between new bars and updates to the last bar. Indicators must support both modes - this is tested extensively in `Tests/test_updates_*.cs`.
+**Critical concept**: The `isNew` parameter differentiates:
+- `isNew=true`: New bar/candle arrives → increment `_index`, backup all state variables
+- `isNew=false`: Update to current bar → restore backed-up state, recalculate with new value
+
+This dual-mode processing is **essential** for real-time trading where the current bar updates continuously before the next bar starts. Every indicator must handle both modes correctly - validated extensively in `Tests/test_updates_*.cs`.
### Circular Buffer Pattern
-`CircularBuffer` (in `lib/core/circularbuffer.cs`) is the foundation for memory-efficient fixed-capacity storage:
-- Never grows beyond initial capacity
-- O(1) add/access operations
-- SIMD-optimized aggregations (Sum, Min, Max, Average)
-- **Critical**: Always use `Add(item, isNew)` - the `isNew` flag controls whether to append or update
+`CircularBuffer` (`lib/core/circularbuffer.cs`) provides memory-efficient fixed-capacity storage:
+- Never grows beyond initial capacity (fixed memory footprint regardless of data volume)
+- O(1) add/access operations with wraparound
+- SIMD-optimized aggregations (Sum, Min, Max, Average) using `System.Numerics.Vector`
+- **Critical**: Always use `Add(item, isNew)` - the `isNew` flag controls append vs update behavior
### State Management in Indicators
-Every indicator must implement:
+Every indicator **must** implement this pattern to support bar updates:
```csharp
protected override void ManageState(bool isNew)
{
if (isNew) {
_index++;
- _p_prevValue = _prevValue; // Backup state
+ _p_prevValue = _prevValue; // Backup state
+ _p_lastEma = _lastEma; // Backup all stateful variables
} else {
- _prevValue = _p_prevValue; // Restore state
+ _prevValue = _p_prevValue; // Restore state
+ _lastEma = _p_lastEma; // Restore all stateful variables
}
}
```
-This allows bar updates without corrupting historical calculations.
+**Pattern**: Use `_p_` prefix for backup variables (e.g., `_p_lastEma`, `_p_isInit`, `_p_e`). When `isNew=false`, restore ALL stateful variables before recalculating. See `lib/averages/Ema.cs` for reference implementation.
## Development Workflow
### MCP-Orchestrated Process
-**Research Gate**: Before implementing non-trivial indicators, use Context7 to retrieve authoritative formulas/references. Embed citation tags in PR descriptions.
+**Research Gate**: Before implementing non-trivial indicators, use Context7 MCP to retrieve authoritative formulas/references. Embed citation tags in PR descriptions.
-**Decomposition**: Use Sequential-Thinking for complex multi-stage work (SIMD refactors, multi-timeframe logic).
+**Decomposition**: Use Sequential-Thinking MCP for complex multi-stage work (SIMD refactors, multi-timeframe logic, performance optimization epics).
-**Task Tracking**: Taskmaster holds the canonical task graph. Feature branches follow pattern: `feature/{taskId}-{slug}`.
+**Task Tracking**: Taskmaster MCP holds the canonical task graph. Feature branches follow pattern: `feature/{taskId}-{slug}`. Tasks include: feature, performance, documentation with status transitions (not-started → in-progress → done).
**Quality Gates**:
-1. Formula citation required for non-trivial indicators (Context7 tag)
+1. Formula citation required for non-trivial indicators (Context7 tag in PR description)
2. Benchmark data required for performance-related changes
-3. Taskmaster task IDs must be referenced in PRs
-4. Update `memory-bank/progress.md` after merge when threshold met
+3. Taskmaster task IDs must be referenced in PR body with closing keywords
+4. Update `memory-bank/progress.md` after merge when threshold met (≥5 feature tasks or perf epic completes)
### Build & Test Commands
```powershell
-# Build solution
+# Build solution (or use VS Code Task: "build")
dotnet build QuanTAlib.sln
-# Run all tests
-dotnet test --no-build
+# Run all tests (or use VS Code Task: "test")
+dotnet test --no-build --verbosity:normal
# Run with coverage
-dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=lcov
+dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=lcov /p:CoverletOutput=./lcov.info --no-build
-# Build using tasks.json
-# Use Run Task: "build" or "test"
+# Clean build artifacts
+dotnet clean QuanTAlib.sln
```
+**VS Code Tasks**: Use Run Task menu for `build`, `test`, `test with coverage`, `clean` - configured in `.vscode/tasks.json`.
+
### Adding a New Indicator
-1. **Research**: Get formula/specification (Context7 if needed)
-2. **Location**: Place in appropriate `lib/` subdirectory (averages, oscillators, momentum, volatility, volume, statistics)
+
+1. **Research**: Get formula/specification. For non-trivial indicators, use Context7 to retrieve authoritative references.
+
+2. **Location**: Place in appropriate `lib/` subdirectory:
+ - `averages/` - Moving averages (SMA, EMA, JMA, etc.)
+ - `oscillators/` - RSI, Stochastic, CCI, etc.
+ - `momentum/` - MACD, ADX, ROC, etc.
+ - `volatility/` - ATR, Bollinger Bands, volatility measures
+ - `volume/` - Volume-based indicators
+ - `statistics/` - Statistical measures, correlations
+
3. **Template structure**:
```csharp
using System.Runtime.CompilerServices;
@@ -77,16 +94,27 @@ namespace QuanTAlib;
[SkipLocalsInit]
public sealed class MyIndicator : AbstractBase
{
+ private readonly int _period;
private CircularBuffer _buffer;
- private double _prevValue, _p_prevValue; // State + backup
+ private double _prevValue, _p_prevValue; // State + backup with _p_ prefix
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public MyIndicator(int period)
{
ArgumentOutOfRangeException.ThrowIfLessThan(period, 1);
+ _period = period;
_buffer = new(period);
- WarmupPeriod = period; // Set when indicator stabilizes
+ WarmupPeriod = period; // Set when indicator stabilizes (95% accuracy)
Name = $"MyIndicator({period})";
+ Init();
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public override void Init()
+ {
+ base.Init();
+ _prevValue = 0;
+ _buffer = new(_period);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -105,118 +133,226 @@ public sealed class MyIndicator : AbstractBase
{
ManageState(Input.IsNew);
_buffer.Add(Input.Value, Input.IsNew);
+
// Implement calculation logic
+ double result = _buffer.Average(); // Example using SIMD-optimized operation
+ _prevValue = result;
+
+ IsHot = _index >= WarmupPeriod; // Mark when indicator reaches accuracy threshold
return result;
}
}
```
-4. **Testing**: Create update test in `Tests/test_updates_*.cs`:
+4. **Testing**: Create update test in appropriate `Tests/test_updates_*.cs` file:
```csharp
[Fact]
public void MyIndicator_Update()
{
var indicator = new MyIndicator(period: 14);
- TestTValueUpdate(indicator, indicator.Calc);
+ double initialValue = indicator.Calc(new TValue(DateTime.Now, 100.0, IsNew: true));
+
+ // Apply 100 random updates with isNew=false
+ for (int i = 0; i < 100; i++)
+ {
+ indicator.Calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false));
+ }
+
+ // Final value with same input should equal initial value
+ double finalValue = indicator.Calc(new TValue(DateTime.Now, 100.0, IsNew: false));
+ Assert.Equal(initialValue, finalValue, precision: 8);
}
```
+5. **Validation**: Compare against reference implementations (TALib, Trady, Skender) in appropriate test file.
+
### Quantower Integration
For platform indicators in `quantower/`, create wrapper classes inheriting from Quantower's `Indicator`:
+```csharp
+public class MyIndicator : Indicator, IWatchlistIndicator
+{
+ [InputParameter("Period", sortIndex: 1, 1, 2000, 1, 0)]
+ public int Period { get; set; } = 14;
+
+ private QuanTAlib.MyIndicator? ma;
+ protected LineSeries? Series;
+
+ protected override void OnInit()
+ {
+ ma = new QuanTAlib.MyIndicator(period: Period);
+ base.OnInit();
+ }
+
+ protected override void OnUpdate(UpdateArgs args)
+ {
+ TValue input = this.GetInputValue(args, Source);
+ TValue result = ma!.Calc(input);
+ Series!.SetValue(result.Value);
+ }
+}
+```
- Use private `lib/` indicator instances
-- Map `OnUpdate()` to indicator's `Calc()` method
-- Extract output fields (e.g., `ma`, `jmaUp`, `jmaLo`) from indicator state
+- Map `OnUpdate()` to indicator's `Calc()` method
+- Extract output from indicator state/properties
+- Apply `IndicatorExtensions` for styling and painting
## Code Style Requirements
### Performance First
-- Use `[MethodImpl(MethodImplOptions.AggressiveInlining)]` for hot paths
-- Use `[MethodImpl(MethodImplOptions.AggressiveOptimization)]` for calculation methods
-- Apply `[SkipLocalsInit]` to indicator classes
-- Prefer SIMD operations in `CircularBuffer` for aggregations
-- Minimize allocations in `Calculation()` methods
+- Use `[MethodImpl(MethodImplOptions.AggressiveInlining)]` for all public methods and hot paths
+- Use `[MethodImpl(MethodImplOptions.AggressiveOptimization)]` for `Calculation()` methods
+- Apply `[SkipLocalsInit]` to indicator classes to skip zero-initialization
+- Prefer SIMD operations in `CircularBuffer` for aggregations (Sum, Min, Max, Average)
+- Minimize allocations in `Calculation()` methods - reuse buffers and avoid LINQ
+- Use `sealed` classes when possible for devirtualization
### C# Conventions
-- **No inline comments** within methods - code should be self-documenting
-- Use XML doc comments for public APIs only
-- PascalCase for public members, _camelCase for private fields
+- **No inline comments** within methods - code should be self-documenting through clear naming
+- Use XML doc comments for public classes/methods only - include purpose, formula description, and source citations
+- PascalCase for public members, `_camelCase` for private fields
+- `_p_` prefix for backup state variables used in `ManageState()`
- Compact code - minimal whitespace between logical blocks
-- Latest C# features: `ArgumentOutOfRangeException.ThrowIfLessThan`, pattern matching, etc.
+- Latest C# features: `ArgumentOutOfRangeException.ThrowIfLessThan`, pattern matching, collection expressions, etc.
+- No namespace imports in individual files - `Directory.Build.props` enables implicit usings
-### Project Settings
+### Project Settings (Directory.Build.props)
- `LangVersion: preview` - use cutting-edge C# features
- `AllowUnsafeBlocks: true` - SIMD and unsafe operations permitted
-- `Nullable: enable` - strict nullability checking
+- `Nullable: enable` - strict nullability checking enforced
- Target: `net8.0`
+- `DisableImplicitNamespaceImports: true` - explicit namespace control
+- Release optimizations: AOT, ReadyToRun, TieredCompilation, trimming enabled
## Key Files & Directories
### Core Library Structure
```
lib/
-├── core/ # AbstractBase, CircularBuffer, TSeries, TBar, TValue
-├── averages/ # Moving averages (SMA, EMA, DEMA, TEMA, JMA, etc.)
-├── oscillators/ # RSI, Stochastic, Williams %R, CCI, Fisher
-├── momentum/ # MACD, ADX, ROC, Vortex
-├── volatility/ # ATR, Bollinger Bands, volatility measures
-├── volume/ # Volume-based indicators
-└── statistics/ # Statistical measures, correlations
+├── core/ # AbstractBase, CircularBuffer, TSeries, TBar, TValue, ITValue
+├── averages/ # Moving averages: SMA, EMA, DEMA, TEMA, JMA, KAMA, etc. (25+ indicators)
+├── oscillators/ # RSI, Stochastic, Williams %R, CCI, Fisher, CTI, etc.
+├── momentum/ # MACD, ADX, DMI, ROC, TRIX, Vortex, PMO, etc.
+├── volatility/ # ATR, Bollinger Bands, Keltner Channels, volatility measures
+├── volume/ # Volume-based indicators (OBV, MFI, etc.)
+├── statistics/ # Statistical measures, correlations
+└── errors/ # Error metrics: MAE, MSE, RMSE, MAPE, R-squared, etc.
```
### Critical Reference Files
-- `lib/core/abstractBase.cs` - Base class for all indicators
-- `lib/core/circularbuffer.cs` - Memory-efficient storage with SIMD
-- `Directory.Build.props` - Solution-wide MSBuild properties
-- `memory-bank/systemPatterns.md` - Architecture patterns
-- `memory-bank/activeContext.md` - Current work focus and MCP policies
-- `memory-bank/progress.md` - Completed features and roadmap
+- `lib/core/abstractBase.cs` - Base class for all indicators with lifecycle management
+- `lib/core/circularbuffer.cs` - Memory-efficient storage with SIMD operations
+- `lib/core/TValue.cs` - Immutable record struct for time-value pairs with IsNew/IsHot flags
+- `lib/core/TBar.cs` - OHLCV bar data structure
+- `Directory.Build.props` - Solution-wide MSBuild properties and optimizations
+- `memory-bank/systemPatterns.md` - Architecture patterns and design decisions
+- `memory-bank/activeContext.md` - Current work focus, MCP policies, and operational rules
+- `memory-bank/progress.md` - Completed features, roadmap, and version history
### Testing Reference
-- `Tests/test_updates_*.cs` - Update behavior validation (IsNew handling)
+- `Tests/test_updates_*.cs` - Update behavior validation (IsNew handling) - **CRITICAL TESTS**
- `Tests/test_quantower.cs` - Quantower integration validation
-- `Tests/test_talib.cs`, `test_Trady.cs` - Cross-validation against reference libraries
+- `Tests/test_talib.cs` - Cross-validation against TA-Lib reference library
+- `Tests/test_Trady.cs` - Cross-validation against Trady reference library
+- `Tests/test_skender.stock.cs` - Cross-validation against Skender.Stock.Indicators
## Common Patterns
### Multi-Stage Smoothing
-Many indicators (DEMA, TEMA, MACD) use cascaded smoothing:
+Many indicators (DEMA, TEMA, MACD) use cascaded smoothing with child indicator instances:
```csharp
private readonly Ema _ema1;
private readonly Ema _ema2;
-_ema1.Calc(Input.Value, Input.IsNew);
-_ema2.Calc(_ema1.Value, Input.IsNew);
+public MyIndicator(int period)
+{
+ _ema1 = new Ema(period);
+ _ema2 = new Ema(period);
+}
+
+protected override double Calculation()
+{
+ _ema1.Calc(Input.Value, Input.IsNew);
+ _ema2.Calc(_ema1.Value, Input.IsNew); // Feed output of first into second
+ return _ema2.Value;
+}
```
-### Bar-Based vs Value-Based
-- **Value-based**: Accept `TValue`, process single values (most indicators)
+### Bar-Based vs Value-Based Indicators
+- **Value-based**: Accept `TValue`, process single values (most indicators like SMA, EMA, RSI)
- **Bar-based**: Accept `TBar` (OHLCV), process bar data (ATR, Stochastic, volume indicators)
Override appropriate `Calc()` method:
```csharp
-public override TValue Calc(TBar barInput) { /* ... */ }
+// For bar-based indicators
+public override TValue Calc(TBar barInput)
+{
+ BarInput = barInput;
+ return Process(barInput.Close, barInput.Time, barInput.IsNew);
+}
```
### WarmupPeriod Calculation
-Set `WarmupPeriod` to indicate when the indicator reaches 95% accuracy:
+Set `WarmupPeriod` to indicate when the indicator reaches 95% accuracy (used for IsHot flag):
```csharp
-WarmupPeriod = (int)Math.Ceiling(Math.Log(0.05) / Math.Log(1 - alpha));
+// For exponential smoothing with constant alpha/k
+WarmupPeriod = (int)Math.Ceiling(Math.Log(0.05) / Math.Log(1 - k));
+
+// For simple period-based indicators
+WarmupPeriod = period;
+
+// For multi-stage indicators
+WarmupPeriod = stage1.WarmupPeriod + stage2.WarmupPeriod;
+```
+
+### Event-Driven Updates
+Indicators support pub-sub pattern through `Pub` event:
+```csharp
+// Publishing side (automatic in AbstractBase.Process())
+Pub?.Invoke(this, new ValueEventArgs(value));
+
+// Subscribing side
+var ema = new Ema(20);
+ema.Pub += (sender, args) => Console.WriteLine($"New EMA value: {args.Tick.Value}");
+
+// Or subscribe one indicator to another
+var sma = new Sma(10);
+var ema = new Ema(sma, period: 20); // EMA automatically subscribes to SMA's Pub event
```
## Validation Strategy
-1. **Update tests**: Verify `isNew=false` behavior converges to `isNew=true` with same final value
-2. **Reference comparison**: Validate against TALib, Trady, or Skender implementations
-3. **Edge cases**: Test with insufficient data (< period), NaN/Infinity, extreme values
-4. **Performance**: Benchmark calculation time - target < 0.5ms per update
+
+1. **Update tests** (CRITICAL): Verify `isNew=false` behavior converges to `isNew=true` with same final value after 100 random updates. This validates state management correctness. See `Tests/test_updates_*.cs`.
+
+2. **Reference comparison**: Validate against TALib, Trady, or Skender implementations. Expect high precision match (typically 8+ decimal places).
+
+3. **Edge cases**: Test with:
+ - Insufficient data (count < period)
+ - NaN and Infinity inputs (should propagate last valid value)
+ - Extreme values (very large/small numbers)
+ - Zero and negative values where applicable
+
+4. **Performance**: Benchmark calculation time - target < 0.5ms per update. Use `BenchmarkDotNet` for precise measurements.
## Documentation Requirements
-- XML docs on public classes/methods describing purpose, formula, and sources
-- Mathematical formulas in doc comments with source citations
-- No internal comments - let code structure communicate intent
-- Update `memory-bank/progress.md` after significant feature completion
+
+- XML doc comments on public classes/methods describing:
+ - Purpose and use case
+ - Formula/algorithm description
+ - Source citations (URLs to papers, documentation, books)
+ - Parameter constraints and validation
+- Mathematical formulas in doc comments with proper notation
+- No internal code comments - let code structure communicate intent through clear naming
+- Update `memory-bank/progress.md` after significant feature completion (threshold: ≥5 feature tasks merged)
## GitVersion & Releases
-- Semantic versioning via GitVersion.yml
-- Version properties auto-injected: `$(GitVersion_MajorMinorPatch)`
-- Commit messages influence version bumps (conventional commits)
-- Build creates NuGet package with embedded version metadata
+
+- Semantic versioning via `GitVersion.yml`
+- Version properties auto-injected: `$(GitVersion_MajorMinorPatch)`, `$(GitVersion_AssemblySemVer)`
+- Commit messages influence version bumps using conventional commits:
+ - `+semver: major` or `+semver: breaking` → major bump
+ - `+semver: minor` or `+semver: feature` → minor bump
+ - `+semver: patch` or `+semver: fix` → patch bump
+ - `+semver: none` or `+semver: skip` → no bump
+- `main` branch: ContinuousDeployment mode, patch increment
+- `dev` branch: ContinuousDelivery mode, pre-release weight 30000
+- Build creates NuGet package with embedded version metadata and source link
diff --git a/.github/copilot-instructions.md.backup b/.github/copilot-instructions.md.backup
new file mode 100644
index 00000000..8b320b78
--- /dev/null
+++ b/.github/copilot-instructions.md.backup
@@ -0,0 +1,222 @@
+# QuanTAlib AI Coding Agent Instructions
+
+## Project Overview
+QuanTAlib is a high-performance C# library for quantitative technical analysis, targeting .NET 8.0 with real-time streaming data processing. The library provides 50+ technical indicators optimized for sub-millisecond calculations using circular buffers, SIMD operations, and event-driven architecture.
+
+## Critical Architecture Patterns
+
+### Core Data Flow
+All indicators inherit from `AbstractBase` (in `lib/core/abstractBase.cs`) which implements `ITValue`:
+```csharp
+// Standard indicator lifecycle:
+Input → Calc() → ManageState(isNew) → Calculation() → Process() → Pub event
+```
+
+**Key insight**: The `isNew` parameter distinguishes between new bars and updates to the last bar. Indicators must support both modes - this is tested extensively in `Tests/test_updates_*.cs`.
+
+### Circular Buffer Pattern
+`CircularBuffer` (in `lib/core/circularbuffer.cs`) is the foundation for memory-efficient fixed-capacity storage:
+- Never grows beyond initial capacity
+- O(1) add/access operations
+- SIMD-optimized aggregations (Sum, Min, Max, Average)
+- **Critical**: Always use `Add(item, isNew)` - the `isNew` flag controls whether to append or update
+
+### State Management in Indicators
+Every indicator must implement:
+```csharp
+protected override void ManageState(bool isNew)
+{
+ if (isNew) {
+ _index++;
+ _p_prevValue = _prevValue; // Backup state
+ } else {
+ _prevValue = _p_prevValue; // Restore state
+ }
+}
+```
+This allows bar updates without corrupting historical calculations.
+
+## Development Workflow
+
+### MCP-Orchestrated Process
+**Research Gate**: Before implementing non-trivial indicators, use Context7 to retrieve authoritative formulas/references. Embed citation tags in PR descriptions.
+
+**Decomposition**: Use Sequential-Thinking for complex multi-stage work (SIMD refactors, multi-timeframe logic).
+
+**Task Tracking**: Taskmaster holds the canonical task graph. Feature branches follow pattern: `feature/{taskId}-{slug}`.
+
+**Quality Gates**:
+1. Formula citation required for non-trivial indicators (Context7 tag)
+2. Benchmark data required for performance-related changes
+3. Taskmaster task IDs must be referenced in PRs
+4. Update `memory-bank/progress.md` after merge when threshold met
+
+### Build & Test Commands
+```powershell
+# Build solution
+dotnet build QuanTAlib.sln
+
+# Run all tests
+dotnet test --no-build
+
+# Run with coverage
+dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=lcov
+
+# Build using tasks.json
+# Use Run Task: "build" or "test"
+```
+
+### Adding a New Indicator
+1. **Research**: Get formula/specification (Context7 if needed)
+2. **Location**: Place in appropriate `lib/` subdirectory (averages, oscillators, momentum, volatility, volume, statistics)
+3. **Template structure**:
+```csharp
+using System.Runtime.CompilerServices;
+namespace QuanTAlib;
+
+[SkipLocalsInit]
+public sealed class MyIndicator : AbstractBase
+{
+ private CircularBuffer _buffer;
+ private double _prevValue, _p_prevValue; // State + backup
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public MyIndicator(int period)
+ {
+ ArgumentOutOfRangeException.ThrowIfLessThan(period, 1);
+ _buffer = new(period);
+ WarmupPeriod = period; // Set when indicator stabilizes
+ Name = $"MyIndicator({period})";
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void ManageState(bool isNew)
+ {
+ if (isNew) {
+ _index++;
+ _p_prevValue = _prevValue;
+ } else {
+ _prevValue = _p_prevValue;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
+ protected override double Calculation()
+ {
+ ManageState(Input.IsNew);
+ _buffer.Add(Input.Value, Input.IsNew);
+ // Implement calculation logic
+ return result;
+ }
+}
+```
+
+4. **Testing**: Create update test in `Tests/test_updates_*.cs`:
+```csharp
+[Fact]
+public void MyIndicator_Update()
+{
+ var indicator = new MyIndicator(period: 14);
+ TestTValueUpdate(indicator, indicator.Calc);
+}
+```
+
+### Quantower Integration
+For platform indicators in `quantower/`, create wrapper classes inheriting from Quantower's `Indicator`:
+- Use private `lib/` indicator instances
+- Map `OnUpdate()` to indicator's `Calc()` method
+- Extract output fields (e.g., `ma`, `jmaUp`, `jmaLo`) from indicator state
+
+## Code Style Requirements
+
+### Performance First
+- Use `[MethodImpl(MethodImplOptions.AggressiveInlining)]` for hot paths
+- Use `[MethodImpl(MethodImplOptions.AggressiveOptimization)]` for calculation methods
+- Apply `[SkipLocalsInit]` to indicator classes
+- Prefer SIMD operations in `CircularBuffer` for aggregations
+- Minimize allocations in `Calculation()` methods
+
+### C# Conventions
+- **No inline comments** within methods - code should be self-documenting
+- Use XML doc comments for public APIs only
+- PascalCase for public members, _camelCase for private fields
+- Compact code - minimal whitespace between logical blocks
+- Latest C# features: `ArgumentOutOfRangeException.ThrowIfLessThan`, pattern matching, etc.
+
+### Project Settings
+- `LangVersion: preview` - use cutting-edge C# features
+- `AllowUnsafeBlocks: true` - SIMD and unsafe operations permitted
+- `Nullable: enable` - strict nullability checking
+- Target: `net8.0`
+
+## Key Files & Directories
+
+### Core Library Structure
+```
+lib/
+├── core/ # AbstractBase, CircularBuffer, TSeries, TBar, TValue
+├── averages/ # Moving averages (SMA, EMA, DEMA, TEMA, JMA, etc.)
+├── oscillators/ # RSI, Stochastic, Williams %R, CCI, Fisher
+├── momentum/ # MACD, ADX, ROC, Vortex
+├── volatility/ # ATR, Bollinger Bands, volatility measures
+├── volume/ # Volume-based indicators
+└── statistics/ # Statistical measures, correlations
+```
+
+### Critical Reference Files
+- `lib/core/abstractBase.cs` - Base class for all indicators
+- `lib/core/circularbuffer.cs` - Memory-efficient storage with SIMD
+- `Directory.Build.props` - Solution-wide MSBuild properties
+- `memory-bank/systemPatterns.md` - Architecture patterns
+- `memory-bank/activeContext.md` - Current work focus and MCP policies
+- `memory-bank/progress.md` - Completed features and roadmap
+
+### Testing Reference
+- `Tests/test_updates_*.cs` - Update behavior validation (IsNew handling)
+- `Tests/test_quantower.cs` - Quantower integration validation
+- `Tests/test_talib.cs`, `test_Trady.cs` - Cross-validation against reference libraries
+
+## Common Patterns
+
+### Multi-Stage Smoothing
+Many indicators (DEMA, TEMA, MACD) use cascaded smoothing:
+```csharp
+private readonly Ema _ema1;
+private readonly Ema _ema2;
+
+_ema1.Calc(Input.Value, Input.IsNew);
+_ema2.Calc(_ema1.Value, Input.IsNew);
+```
+
+### Bar-Based vs Value-Based
+- **Value-based**: Accept `TValue`, process single values (most indicators)
+- **Bar-based**: Accept `TBar` (OHLCV), process bar data (ATR, Stochastic, volume indicators)
+
+Override appropriate `Calc()` method:
+```csharp
+public override TValue Calc(TBar barInput) { /* ... */ }
+```
+
+### WarmupPeriod Calculation
+Set `WarmupPeriod` to indicate when the indicator reaches 95% accuracy:
+```csharp
+WarmupPeriod = (int)Math.Ceiling(Math.Log(0.05) / Math.Log(1 - alpha));
+```
+
+## Validation Strategy
+1. **Update tests**: Verify `isNew=false` behavior converges to `isNew=true` with same final value
+2. **Reference comparison**: Validate against TALib, Trady, or Skender implementations
+3. **Edge cases**: Test with insufficient data (< period), NaN/Infinity, extreme values
+4. **Performance**: Benchmark calculation time - target < 0.5ms per update
+
+## Documentation Requirements
+- XML docs on public classes/methods describing purpose, formula, and sources
+- Mathematical formulas in doc comments with source citations
+- No internal comments - let code structure communicate intent
+- Update `memory-bank/progress.md` after significant feature completion
+
+## GitVersion & Releases
+- Semantic versioning via GitVersion.yml
+- Version properties auto-injected: `$(GitVersion_MajorMinorPatch)`
+- Commit messages influence version bumps (conventional commits)
+- Build creates NuGet package with embedded version metadata
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index 9bf69387..d23a4a35 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -1,5 +1,7 @@
{
"recommendations": [
+ "ms-dotnettools.csdevkit",
+ "ms-dotnettools.csharp",
"bierner.markdown-mermaid"
]
-}
\ No newline at end of file
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 970bdfa4..d656a7dc 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,4 +1,121 @@
{
+ // ???????????????????????????????????????????????????????????????????
+ // GitHub Copilot Settings for QuanTAlib Workspace
+ // Optimized for high-performance financial library development
+ // ???????????????????????????????????????????????????????????????????
+
+ // ?????????????????????????????????????????????????????????????????
+ // Copilot Core Settings
+ // ?????????????????????????????????????????????????????????????????
+
+ // Enable Copilot completions (suggestions appear automatically)
+ "github.copilot.editor.enableAutoCompletions": true,
+
+ // Enable Copilot for all file types
+ "github.copilot.enable": {
+ "*": true,
+ "plaintext": false,
+ "markdown": true,
+ "scminput": false
+ },
+
+ // Show inline suggestions
+ "editor.inlineSuggest.enabled": true,
+
+ // Always show the inline suggestion toolbar
+ "editor.inlineSuggest.showToolbar": "always",
+
+ // ?????????????????????????????????????????????????????????????????
+ // Copilot Chat Settings (Manual Review Required)
+ // ?????????????????????????????????????????????????????????????????
+
+ // DO NOT auto-apply chat edits - require manual review for quality control
+ "chat.editing.autoApply": "off",
+
+ // Confirm before removing edit requests
+ "chat.editing.confirmEditRequestRemoval": true,
+
+ // Show chat panel on the side
+ "chat.editor.wordWrap": "on",
+
+ // ?????????????????????????????????????????????????????????????????
+ // Editor Settings for Productivity
+ // ?????????????????????????????????????????????????????????????????
+
+ // Enable quick suggestions in all contexts
+ "editor.quickSuggestions": {
+ "other": true,
+ "comments": true,
+ "strings": true
+ },
+
+ // Show suggestions on trigger characters
+ "editor.suggestOnTriggerCharacters": true,
+
+ // Accept suggestion on commit character (like dot, parenthesis)
+ "editor.acceptSuggestionOnCommitCharacter": true,
+
+ // Faster suggestion appearance
+ "editor.quickSuggestionsDelay": 0,
+
+ // Show snippet suggestions with other suggestions
+ "editor.snippetSuggestions": "inline",
+
+ // Tab key behavior
+ "editor.tabCompletion": "on",
+
+ // ?????????????????????????????????????????????????????????????????
+ // C# Specific Settings
+ // ?????????????????????????????????????????????????????????????????
+
+ "[csharp]": {
+ "editor.formatOnSave": true,
+ "editor.formatOnPaste": true,
+ "editor.codeActionsOnSave": {
+ "source.organizeImports": "explicit"
+ },
+ "editor.quickSuggestions": {
+ "other": true,
+ "comments": true,
+ "strings": true
+ }
+ },
+
+ // ?????????????????????????????????????????????????????????????????
+ // Performance & Quality Control
+ // ?????????????????????????????????????????????????????????????????
+
+ // Save automatically (helps with Copilot context)
+ "files.autoSave": "afterDelay",
+ "files.autoSaveDelay": 1000,
+
+ // Show whitespace (important for performance-critical code)
+ "editor.renderWhitespace": "boundary",
+
+ // Show inline parameter hints
+ "editor.inlayHints.enabled": "on",
+
+ // Highlight matching brackets
+ "editor.bracketPairColorization.enabled": true,
+ "editor.guides.bracketPairs": true,
+
+ // ?????????????????????????????????????????????????????????????????
+ // Git Integration
+ // ?????????????????????????????????????????????????????????????????
+
+ // Auto-fetch git changes
+ "git.autofetch": true,
+
+ // Confirm before synchronizing
+ "git.confirmSync": false,
+
+ // Show inline blame
+ "git.decorations.enabled": true,
+
+ // ?????????????????????????????????????????????????????????????????
+ // Terminal Settings (Preserved from original)
+ // ?????????????????????????????????????????????????????????????????
+
"terminal.integrated.defaultProfile.windows": "PowerShell",
"terminal.integrated.profiles.windows": {
"PowerShell": {
@@ -6,5 +123,68 @@
"icon": "terminal-powershell"
}
},
- "terminal.integrated.shellIntegration.enabled": true
+ "terminal.integrated.shellIntegration.enabled": true,
+ "terminal.integrated.suggest.enabled": true,
+
+ // ?????????????????????????????????????????????????????????????????
+ // File Exclusions (Reduce Noise)
+ // ?????????????????????????????????????????????????????????????????
+
+ "files.exclude": {
+ "**/bin": true,
+ "**/obj": true,
+ "**/.vs": true,
+ "**/node_modules": true,
+ "**/.git": false
+ },
+
+ "search.exclude": {
+ "**/bin": true,
+ "**/obj": true,
+ "**/node_modules": true,
+ "**/.vs": true,
+ "**/coverage": true
+ },
+
+ // ?????????????????????????????????????????????????????????????????
+ // .NET Specific Settings
+ // ?????????????????????????????????????????????????????????????????
+
+ "omnisharp.enableEditorConfigSupport": true,
+ "omnisharp.enableRoslynAnalyzers": true,
+ "dotnet.backgroundAnalysis.enabled": true,
+
+ // ?????????????????????????????????????????????????????????????????
+ // Testing Integration
+ // ?????????????????????????????????????????????????????????????????
+
+ "dotnet.defaultSolution": "QuanTAlib.sln",
+ "dotnet.testController.enabled": true,
+ "dotnet.testExplorer.enabled": true,
+ "dotnet-test-explorer.autoExpandTree": true,
+ "dotnet-test-explorer.autoWatch": false,
+ "dotnet-test-explorer.runAfterBuild": false,
+ "dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true,
+ "dotnet.server.useOmnisharp": false,
+
+ "testing.automaticallyOpenPeekView": "never",
+ "testing.openTesting": "neverOpen",
+ "testing.automaticallyOpenTestResults": "neverOpen"
+
+ // ???????????????????????????????????????????????????????????????????
+ // Keyboard Shortcuts Reference
+ // ???????????????????????????????????????????????????????????????????
+ // Tab - Accept inline suggestion
+ // Ctrl+? - Accept next word
+ // Ctrl+Enter - Accept line
+ // Esc - Dismiss suggestion
+ // Alt+] - Next suggestion
+ // Alt+[ - Previous suggestion
+ // Ctrl+I - Open Copilot Chat
+ //
+ // Quality Control Reminders:
+ // ? Review all Copilot suggestions for optimization patterns
+ // ? Run tests after accepting: dotnet test
+ // ? Check performance impact with benchmarks
+ // ? Validate against reference implementations
}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 33d40edd..aa1fd30c 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -1,62 +1,54 @@
{
- "version": "2.0.0",
- "tasks": [
- {
- "label": "build",
- "command": "dotnet",
- "type": "process",
- "args": [
- "build",
- "${workspaceFolder}/QuanTAlib.sln",
- "/property:GenerateFullPaths=true",
- "/consoleloggerparameters:NoSummary"
- ],
- "problemMatcher": "$msCompile",
- "group": {
- "kind": "build",
- "isDefault": true
- }
- },
- {
- "label": "test",
- "command": "dotnet",
- "type": "process",
- "args": [
- "test",
- "${workspaceFolder}/QuanTAlib.sln",
- "--no-build",
- "--verbosity:normal"
- ],
- "problemMatcher": "$msCompile",
- "group": {
- "kind": "test",
- "isDefault": true
- },
- "dependsOn": ["build"]
- },
- {
- "label": "test with coverage",
- "command": "dotnet",
- "type": "process",
- "args": [
- "test",
- "${workspaceFolder}/QuanTAlib.sln",
- "/p:CollectCoverage=true",
- "/p:CoverletOutputFormat=lcov",
- "/p:CoverletOutput=./lcov.info",
- "--no-build"
- ],
- "problemMatcher": "$msCompile"
- },
- {
- "label": "clean",
- "command": "dotnet",
- "type": "process",
- "args": [
- "clean",
- "${workspaceFolder}/QuanTAlib.sln"
- ],
- "problemMatcher": "$msCompile"
- }
- ]
-}
\ No newline at end of file
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "test-net10",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "test",
+ "${workspaceFolder}/tests/QuanTAlib.Tests/QuanTAlib.Tests.csproj",
+ "--framework",
+ "net10.0"
+ ],
+ "problemMatcher": "$msCompile",
+ "group": {
+ "kind": "test",
+ "isDefault": true
+ },
+ "presentation": {
+ "reveal": "always",
+ "panel": "new"
+ }
+ },
+ {
+ "label": "test-all-frameworks",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "test",
+ "${workspaceFolder}/tests/QuanTAlib.Tests/QuanTAlib.Tests.csproj"
+ ],
+ "problemMatcher": "$msCompile",
+ "group": "test",
+ "presentation": {
+ "reveal": "always",
+ "panel": "new"
+ }
+ },
+ {
+ "label": "build",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "build",
+ "${workspaceFolder}/QuanTAlib.sln"
+ ],
+ "problemMatcher": "$msCompile",
+ "group": {
+ "kind": "build",
+ "isDefault": true
+ }
+ }
+ ]
+}
diff --git a/QuanTAlib.sln b/QuanTAlib.sln
index 63971a37..4ae60645 100644
--- a/QuanTAlib.sln
+++ b/QuanTAlib.sln
@@ -1,85 +1,91 @@
+
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
-VisualStudioVersion = 17.5.2.0
+VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{9CF47860-2CEA-F379-09D8-9AEF27965D12}"
-EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "quantalib", "lib\quantalib.csproj", "{F455234B-2A3C-140A-17C3-683D7820A733}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "quantower", "quantower", "{6CF592EE-4302-E72F-3CB4-AB1D314DD5A8}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "_Averages", "quantower\Averages\_Averages.csproj", "{F6651413-2F44-2F7B-EBE6-A300E8655AFD}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CoreTypes", "examples\CoreTypes\CoreTypes.csproj", "{8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "_Experiments", "quantower\Experiments\_Experiments.csproj", "{87051F5D-8006-0241-4339-A1B2D29EA094}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{3A8DF596-E814-FECC-DD4B-D8EF8AAC1A0D}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "_Momentum", "quantower\Momentum\_Momentum.csproj", "{2D6628C9-C059-15E9-F3A0-C50F1BCCADA0}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "_Oscillators", "quantower\Oscillators\_Oscillators.csproj", "{A95DA667-23DF-4067-A173-E9C7FC430D09}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuanTAlib.Tests", "tests\QuanTAlib.Tests\QuanTAlib.Tests.csproj", "{43CA2584-D4AD-4082-AFF4-68B3D1239221}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "_Statistics", "quantower\Statistics\_Statistics.csproj", "{556D8C92-E3DD-F64A-53B1-D741A96888F2}"
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "feeds", "feeds", "{2B942E44-74DA-CD21-D337-7A5E9D347C1B}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "_Volatility", "quantower\Volatility\_Volatility.csproj", "{4FAD1FB1-4696-ABF4-50D9-162F81114A20}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "_Volume", "quantower\Volume\_Volume.csproj", "{03C2D1D7-AB94-445B-2127-285A367DC6A6}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GbmExample", "examples\feeds\GbmExample.csproj", "{B27145AF-B255-4D6E-827B-3512952FB29C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {908D03EE-717E-7E8C-7EAA-0DF14BA8C45E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {908D03EE-717E-7E8C-7EAA-0DF14BA8C45E}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {908D03EE-717E-7E8C-7EAA-0DF14BA8C45E}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {908D03EE-717E-7E8C-7EAA-0DF14BA8C45E}.Release|Any CPU.Build.0 = Release|Any CPU
- {9CF47860-2CEA-F379-09D8-9AEF27965D12}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {9CF47860-2CEA-F379-09D8-9AEF27965D12}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {9CF47860-2CEA-F379-09D8-9AEF27965D12}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {9CF47860-2CEA-F379-09D8-9AEF27965D12}.Release|Any CPU.Build.0 = Release|Any CPU
{F455234B-2A3C-140A-17C3-683D7820A733}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F455234B-2A3C-140A-17C3-683D7820A733}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F455234B-2A3C-140A-17C3-683D7820A733}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F455234B-2A3C-140A-17C3-683D7820A733}.Debug|x64.Build.0 = Debug|Any CPU
+ {F455234B-2A3C-140A-17C3-683D7820A733}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F455234B-2A3C-140A-17C3-683D7820A733}.Debug|x86.Build.0 = Debug|Any CPU
{F455234B-2A3C-140A-17C3-683D7820A733}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F455234B-2A3C-140A-17C3-683D7820A733}.Release|Any CPU.Build.0 = Release|Any CPU
- {F6651413-2F44-2F7B-EBE6-A300E8655AFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {F6651413-2F44-2F7B-EBE6-A300E8655AFD}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {F6651413-2F44-2F7B-EBE6-A300E8655AFD}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {F6651413-2F44-2F7B-EBE6-A300E8655AFD}.Release|Any CPU.Build.0 = Release|Any CPU
- {87051F5D-8006-0241-4339-A1B2D29EA094}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {87051F5D-8006-0241-4339-A1B2D29EA094}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {87051F5D-8006-0241-4339-A1B2D29EA094}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {87051F5D-8006-0241-4339-A1B2D29EA094}.Release|Any CPU.Build.0 = Release|Any CPU
- {2D6628C9-C059-15E9-F3A0-C50F1BCCADA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2D6628C9-C059-15E9-F3A0-C50F1BCCADA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2D6628C9-C059-15E9-F3A0-C50F1BCCADA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2D6628C9-C059-15E9-F3A0-C50F1BCCADA0}.Release|Any CPU.Build.0 = Release|Any CPU
- {A95DA667-23DF-4067-A173-E9C7FC430D09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A95DA667-23DF-4067-A173-E9C7FC430D09}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A95DA667-23DF-4067-A173-E9C7FC430D09}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A95DA667-23DF-4067-A173-E9C7FC430D09}.Release|Any CPU.Build.0 = Release|Any CPU
- {556D8C92-E3DD-F64A-53B1-D741A96888F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {556D8C92-E3DD-F64A-53B1-D741A96888F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {556D8C92-E3DD-F64A-53B1-D741A96888F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {556D8C92-E3DD-F64A-53B1-D741A96888F2}.Release|Any CPU.Build.0 = Release|Any CPU
- {4FAD1FB1-4696-ABF4-50D9-162F81114A20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {4FAD1FB1-4696-ABF4-50D9-162F81114A20}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {4FAD1FB1-4696-ABF4-50D9-162F81114A20}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {4FAD1FB1-4696-ABF4-50D9-162F81114A20}.Release|Any CPU.Build.0 = Release|Any CPU
- {03C2D1D7-AB94-445B-2127-285A367DC6A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {03C2D1D7-AB94-445B-2127-285A367DC6A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {03C2D1D7-AB94-445B-2127-285A367DC6A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {03C2D1D7-AB94-445B-2127-285A367DC6A6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F455234B-2A3C-140A-17C3-683D7820A733}.Release|x64.ActiveCfg = Release|Any CPU
+ {F455234B-2A3C-140A-17C3-683D7820A733}.Release|x64.Build.0 = Release|Any CPU
+ {F455234B-2A3C-140A-17C3-683D7820A733}.Release|x86.ActiveCfg = Release|Any CPU
+ {F455234B-2A3C-140A-17C3-683D7820A733}.Release|x86.Build.0 = Release|Any CPU
+ {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Debug|x64.Build.0 = Debug|Any CPU
+ {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Debug|x86.Build.0 = Debug|Any CPU
+ {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Release|x64.ActiveCfg = Release|Any CPU
+ {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Release|x64.Build.0 = Release|Any CPU
+ {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Release|x86.ActiveCfg = Release|Any CPU
+ {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782}.Release|x86.Build.0 = Release|Any CPU
+ {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Debug|x64.Build.0 = Debug|Any CPU
+ {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Debug|x86.Build.0 = Debug|Any CPU
+ {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Release|Any CPU.Build.0 = Release|Any CPU
+ {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Release|x64.ActiveCfg = Release|Any CPU
+ {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Release|x64.Build.0 = Release|Any CPU
+ {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Release|x86.ActiveCfg = Release|Any CPU
+ {43CA2584-D4AD-4082-AFF4-68B3D1239221}.Release|x86.Build.0 = Release|Any CPU
+ {B27145AF-B255-4D6E-827B-3512952FB29C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B27145AF-B255-4D6E-827B-3512952FB29C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B27145AF-B255-4D6E-827B-3512952FB29C}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {B27145AF-B255-4D6E-827B-3512952FB29C}.Debug|x64.Build.0 = Debug|Any CPU
+ {B27145AF-B255-4D6E-827B-3512952FB29C}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {B27145AF-B255-4D6E-827B-3512952FB29C}.Debug|x86.Build.0 = Debug|Any CPU
+ {B27145AF-B255-4D6E-827B-3512952FB29C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B27145AF-B255-4D6E-827B-3512952FB29C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B27145AF-B255-4D6E-827B-3512952FB29C}.Release|x64.ActiveCfg = Release|Any CPU
+ {B27145AF-B255-4D6E-827B-3512952FB29C}.Release|x64.Build.0 = Release|Any CPU
+ {B27145AF-B255-4D6E-827B-3512952FB29C}.Release|x86.ActiveCfg = Release|Any CPU
+ {B27145AF-B255-4D6E-827B-3512952FB29C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
- {F6651413-2F44-2F7B-EBE6-A300E8655AFD} = {6CF592EE-4302-E72F-3CB4-AB1D314DD5A8}
- {87051F5D-8006-0241-4339-A1B2D29EA094} = {6CF592EE-4302-E72F-3CB4-AB1D314DD5A8}
- {2D6628C9-C059-15E9-F3A0-C50F1BCCADA0} = {6CF592EE-4302-E72F-3CB4-AB1D314DD5A8}
- {A95DA667-23DF-4067-A173-E9C7FC430D09} = {6CF592EE-4302-E72F-3CB4-AB1D314DD5A8}
- {556D8C92-E3DD-F64A-53B1-D741A96888F2} = {6CF592EE-4302-E72F-3CB4-AB1D314DD5A8}
- {4FAD1FB1-4696-ABF4-50D9-162F81114A20} = {6CF592EE-4302-E72F-3CB4-AB1D314DD5A8}
- {03C2D1D7-AB94-445B-2127-285A367DC6A6} = {6CF592EE-4302-E72F-3CB4-AB1D314DD5A8}
+ {8AB1BE0C-06AE-4EE2-B45A-4F8CE6381782} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}
+ {43CA2584-D4AD-4082-AFF4-68B3D1239221} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
+ {2B942E44-74DA-CD21-D337-7A5E9D347C1B} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}
+ {B27145AF-B255-4D6E-827B-3512952FB29C} = {2B942E44-74DA-CD21-D337-7A5E9D347C1B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {E6DB434C-508E-4231-B8A6-5EDD7FF87E22}
diff --git a/Tests/Tests.csproj b/Tests/Tests.csproj
deleted file mode 100644
index 9b508306..00000000
--- a/Tests/Tests.csproj
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
- QuanTAlib.Tests
- QuanTAlib.Tests
- false
-
-
-
- runtime; build; native; contentfiles; analyzers; buildtransitive
- all
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
- all
- runtime; build; native; contentfiles; analyzers
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ..\.github\TradingPlatform.BusinessLayer.dll
-
-
- TradingPlatform.BusinessLayer.xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Tests/UpdateTestBase.cs b/Tests/UpdateTestBase.cs
deleted file mode 100644
index 7a245277..00000000
--- a/Tests/UpdateTestBase.cs
+++ /dev/null
@@ -1,91 +0,0 @@
-using Xunit;
-using System.Security.Cryptography;
-
-namespace QuanTAlib.Tests;
-
-public abstract class UpdateTestBase
-{
- protected readonly RandomNumberGenerator rng = RandomNumberGenerator.Create();
- protected const int RandomUpdates = 100;
- protected const double ReferenceValue = 100.0;
- protected const int precision = 8;
-
- protected double GetRandomDouble()
- {
- byte[] bytes = new byte[8];
- rng.GetBytes(bytes);
- return ((double)BitConverter.ToUInt64(bytes, 0) / ulong.MaxValue * 200) - 100; // Range: -100 to 100
- }
-
- protected TBar GetRandomBar(bool IsNew)
- {
- double open = GetRandomDouble();
- double high = open + Math.Abs(GetRandomDouble());
- double low = open - Math.Abs(GetRandomDouble());
- double close = low + ((high - low) * GetRandomDouble());
- return new TBar(DateTime.Now, open, high, low, close, 1000, IsNew);
- }
-
- protected void TestTValueUpdate(T indicator, Func calc) where T : class
- {
- var initialValue = calc(new TValue(DateTime.Now, ReferenceValue, IsNew: true));
-
- for (int i = 0; i < RandomUpdates; i++)
- {
- calc(new TValue(DateTime.Now, GetRandomDouble(), IsNew: false));
- }
- var finalValue = calc(new TValue(DateTime.Now, ReferenceValue, IsNew: false));
-
- Assert.Equal(initialValue.Value, finalValue.Value, precision);
- }
-
- protected void TestTBarUpdate(T indicator, Func calc) where T : class
- {
- TBar r = GetRandomBar(true);
- var initialValue = calc(r);
-
- for (int i = 0; i < RandomUpdates; i++)
- {
- calc(GetRandomBar(IsNew: false));
- }
- var finalValue = calc(new TBar(r.Time, r.Open, r.High, r.Low, r.Close, r.Volume, IsNew: false));
-
- Assert.Equal(initialValue.Value, finalValue.Value, precision);
- }
-
- protected void TestDualTValueUpdate(T indicator, Func calc) where T : class
- {
- var initialValue = calc(
- new TValue(DateTime.Now, ReferenceValue, IsNew: true),
- new TValue(DateTime.Now, ReferenceValue, IsNew: true));
-
- for (int i = 0; i < RandomUpdates; i++)
- {
- calc(
- new TValue(DateTime.Now, GetRandomDouble(), IsNew: false),
- new TValue(DateTime.Now, GetRandomDouble(), IsNew: false));
- }
- var finalValue = calc(
- new TValue(DateTime.Now, ReferenceValue, IsNew: false),
- new TValue(DateTime.Now, ReferenceValue, IsNew: false));
-
- Assert.Equal(initialValue.Value, finalValue.Value, precision);
- }
-
- protected void TestDualTBarUpdate(T indicator, Func calc) where T : class
- {
- TBar bar1 = GetRandomBar(true);
- TBar bar2 = GetRandomBar(true);
- var initialValue = calc(bar1, bar2);
-
- for (int i = 0; i < RandomUpdates; i++)
- {
- calc(GetRandomBar(false), GetRandomBar(false));
- }
- var finalValue = calc(
- new TBar(bar1.Time, bar1.Open, bar1.High, bar1.Low, bar1.Close, bar1.Volume, false),
- new TBar(bar2.Time, bar2.Open, bar2.High, bar2.Low, bar2.Close, bar2.Volume, false));
-
- Assert.Equal(initialValue.Value, finalValue.Value, precision);
- }
-}
diff --git a/Tests/test_Trady.cs b/Tests/test_Trady.cs
deleted file mode 100644
index 10de3783..00000000
--- a/Tests/test_Trady.cs
+++ /dev/null
@@ -1,108 +0,0 @@
-using Xunit;
-using Trady.Analysis.Indicator;
-using Trady.Core;
-using Trady.Core.Infrastructure;
-using System.Diagnostics.CodeAnalysis;
-using System.Security.Cryptography;
-
-#pragma warning disable S1944, S2053, S2222, S2259, S2583, S2589, S3329, S3655, S3900, S3949, S3966, S4158, S4347, S5773, S6781
-
-namespace QuanTAlib;
-
-public class TradyTests
-{
- private readonly TBarSeries bars;
- private readonly GbmFeed feed;
- private readonly RandomNumberGenerator rng;
- private readonly double range;
- private readonly int iterations;
- private readonly int skip;
- private readonly IEnumerable Candles;
-
- public TradyTests()
- {
- rng = RandomNumberGenerator.Create();
- feed = new(sigma: 0.5, mu: 0.0);
- bars = new(feed);
- range = 1e-9;
- feed.Add(10000);
- iterations = 3;
- skip = 500;
- Candles = bars.Select(bar => new Candle(
- bar.Time,
- (decimal)bar.Open,
- (decimal)bar.High,
- (decimal)bar.Low,
- (decimal)bar.Close,
- (decimal)bar.Volume
- )).ToList();
- }
-
- private int GetRandomNumber(int minValue, int maxValue)
- {
- byte[] randomBytes = new byte[4];
- rng.GetBytes(randomBytes);
- int randomInt = BitConverter.ToInt32(randomBytes, 0);
- return Math.Abs(randomInt % (maxValue - minValue)) + minValue;
- }
-
- [Fact]
- public void SMA()
- {
- for (int run = 0; run < iterations; run++)
- {
- int period = GetRandomNumber(5, 55);
- Sma ma = new(period);
- TSeries QL = new();
- foreach (TBar item in feed)
- { QL.Add(ma.Calc(new TValue(item.Time, item.Close))); }
-
- var Trady = new SimpleMovingAverage(Candles, period)
- .Compute()
- .Select(result => new
- {
- Date = result.DateTime,
- Value = result.Tick.HasValue ? (double)result.Tick.Value : double.NaN
- })
- .ToList();
-
- Assert.Equal(QL.Length, Trady.Count);
- for (int i = QL.Length - 1; i > skip; i--)
- {
- double QL_item = QL[i].Value;
- double Tr_item = Trady[i].Value;
- Assert.InRange(Tr_item - QL_item, -range, range);
- }
- }
- }
-
- [Fact]
- public void EMA()
- {
- for (int run = 0; run < iterations; run++)
- {
- int period = GetRandomNumber(5, 55);
- Ema ma = new(period);
- TSeries QL = new();
- foreach (TBar item in feed)
- { QL.Add(ma.Calc(new TValue(item.Time, item.Close))); }
-
- var Trady = new ExponentialMovingAverage(Candles, period)
- .Compute()
- .Select(result => new
- {
- Date = result.DateTime,
- Value = result.Tick.HasValue ? (double)result.Tick.Value : double.NaN
- })
- .ToList();
-
- Assert.Equal(QL.Length, Trady.Count);
- for (int i = QL.Length - 1; i > skip * 2; i--)
- {
- double QL_item = QL[i].Value;
- double Tr_item = Trady[i].Value;
- Assert.InRange(Tr_item - QL_item, -range, range);
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Tests/test_Tulip.cs b/Tests/test_Tulip.cs
deleted file mode 100644
index 8b495acc..00000000
--- a/Tests/test_Tulip.cs
+++ /dev/null
@@ -1,88 +0,0 @@
-using Xunit;
-using Tulip;
-using System.Diagnostics.CodeAnalysis;
-using System.Security.Cryptography;
-
-#pragma warning disable S1944, S2053, S2222, S2259, S2583, S2589, S3329, S3655, S3900, S3949, S3966, S4158, S4347, S5773, S6781
-
-namespace QuanTAlib;
-
-public class TulipTests
-{
- private readonly GbmFeed feed;
- private readonly RandomNumberGenerator rng;
- private readonly double range;
- private readonly int iterations;
- private readonly double[] data;
- private readonly double[] outdata;
- private readonly int skip;
-
- public TulipTests()
- {
- rng = RandomNumberGenerator.Create();
- feed = new(sigma: 0.5, mu: 0.0);
- range = 1e-9;
- feed.Add(10000);
- iterations = 3;
- skip = 500;
- data = feed.Close.v.ToArray();
- outdata = new double[data.Count()];
- }
-
- private int GetRandomNumber(int minValue, int maxValue)
- {
- byte[] randomBytes = new byte[4];
- rng.GetBytes(randomBytes);
- int randomInt = BitConverter.ToInt32(randomBytes, 0);
- return Math.Abs(randomInt % (maxValue - minValue)) + minValue;
- }
-
- [Fact]
- public void SMA()
- {
- for (int run = 0; run < iterations; run++)
- {
- int period = GetRandomNumber(5, 55);
- Sma ma = new(period);
- TSeries QL = new();
- foreach (TBar item in feed)
- { QL.Add(ma.Calc(new TValue(item.Time, item.Close))); }
-
- double[][] arrin = [data];
- double[][] arrout = [outdata];
- Tulip.Indicators.sma.Run(inputs: arrin, options: [period], outputs: arrout);
- Assert.Equal(QL.Length, arrout[0].Length);
- for (int i = QL.Length - 1; i > skip; i--)
- {
- double QL_item = QL[i].Value;
- double TU = i < period - 1 ? double.NaN : arrout[0][i - period + 1];
- Assert.InRange(TU - QL_item, -range, range);
- }
- }
- }
-
- [Fact]
- public void EMA()
- {
- for (int run = 0; run < iterations; run++)
- {
- int period = GetRandomNumber(5, 35);
- Ema ma = new(period, useSma: false);
- TSeries QL = new();
- foreach (TBar item in feed)
- { QL.Add(ma.Calc(new TValue(item.Time, item.Close))); }
-
- double[][] arrin = [data];
- double[][] arrout = [outdata];
- Tulip.Indicators.ema.Run(inputs: arrin, options: [period], outputs: arrout);
-
- Assert.Equal(QL.Length, arrout[0].Length);
- for (int i = QL.Length - 1; i > skip * 2; i--) //Initial Tulip Ema value is (wrongly) set to the first input value - therefore large skip
- {
- double QL_item = QL[i].Value;
- double TU = arrout[0][i];
- Assert.True(Math.Abs(TU - QL_item) <= range, $"Assertion failed at index {i} for period {period}: TU = {TU}, QL_item = {QL_item}, delta = {TU - QL_item}");
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Tests/test_core.cs b/Tests/test_core.cs
deleted file mode 100644
index 49d67d4b..00000000
--- a/Tests/test_core.cs
+++ /dev/null
@@ -1,251 +0,0 @@
-using Xunit;
-
-namespace QuanTAlib.Tests;
-
-public class CoreTests
-{
- #region CircularBuffer Tests
-
- [Fact]
- public void CircularBuffer_BasicOperations()
- {
- var buffer = new CircularBuffer(5);
-
- // Test initial state
- Assert.Equal(5, buffer.Capacity);
- Assert.Equal(0, buffer.Count);
-
- // Test adding items
- buffer.Add(1.0);
- buffer.Add(2.0);
- Assert.Equal(2, buffer.Count);
- Assert.Equal(1.0, buffer[0]);
- Assert.Equal(2.0, buffer[^1]);
-
- // Test overflow behavior
- buffer.Add(3.0);
- buffer.Add(4.0);
- buffer.Add(5.0);
- buffer.Add(6.0); // Should remove oldest item (1.0)
- Assert.Equal(5, buffer.Count);
- Assert.Equal(2.0, buffer[0]);
- Assert.Equal(6.0, buffer[^1]);
- }
-
- [Fact]
- public void CircularBuffer_UpdateBehavior()
- {
- var buffer = new CircularBuffer(3);
-
- // Add new values
- buffer.Add(1.0, isNew: true);
- buffer.Add(2.0, isNew: true);
- Assert.Equal(2, buffer.Count);
-
- // Update last value
- buffer.Add(2.5, isNew: false);
- Assert.Equal(2, buffer.Count);
- Assert.Equal(2.5, buffer[^1]);
- }
-
- [Fact]
- public void CircularBuffer_MinMaxSumAverage()
- {
- var buffer = new CircularBuffer(5);
-
- buffer.Add(1.0);
- buffer.Add(2.0);
- buffer.Add(3.0);
- buffer.Add(4.0);
- buffer.Add(5.0);
-
- Assert.Equal(1.0, buffer.Min());
- Assert.Equal(5.0, buffer.Max());
- Assert.Equal(15.0, buffer.Sum());
- Assert.Equal(3.0, buffer.Average());
- }
-
- [Fact]
- public void CircularBuffer_Enumeration()
- {
- var buffer = new CircularBuffer(3);
-
- buffer.Add(1.0);
- buffer.Add(2.0);
- buffer.Add(3.0);
-
- var list = buffer.ToList();
- Assert.Equal(3, list.Count);
- Assert.Equal(1.0, list[0]);
- Assert.Equal(3.0, list[2]);
- }
-
- #endregion
-
- #region TBar Tests
-
- [Fact]
- public void TBar_Construction()
- {
- // Default constructor
- var bar1 = new TBar();
- Assert.Equal(0, bar1.Open);
- Assert.True(bar1.IsNew);
-
- // Value constructor
- var bar2 = new TBar(10.0);
- Assert.Equal(10.0, bar2.Open);
- Assert.Equal(10.0, bar2.High);
- Assert.Equal(10.0, bar2.Low);
- Assert.Equal(10.0, bar2.Close);
-
- // Full constructor
- var time = DateTime.Now;
- var bar3 = new TBar(time, 10.0, 12.0, 9.0, 11.0, 1000.0, false);
- Assert.Equal(time, bar3.Time);
- Assert.Equal(10.0, bar3.Open);
- Assert.Equal(12.0, bar3.High);
- Assert.Equal(9.0, bar3.Low);
- Assert.Equal(11.0, bar3.Close);
- Assert.Equal(1000.0, bar3.Volume);
- Assert.False(bar3.IsNew);
- }
-
- [Fact]
- public void TBar_DerivedValues()
- {
- var bar = new TBar(DateTime.Now, 10.0, 20.0, 5.0, 15.0, 1000.0);
-
- Assert.Equal(12.5, bar.HL2); // (20 + 5) / 2
- Assert.Equal(12.5, bar.OC2); // (10 + 15) / 2
- Assert.Equal(11.67, bar.OHL3, 2); // (10 + 20 + 5) / 3
- Assert.Equal(13.33, bar.HLC3, 2); // (20 + 5 + 15) / 3
- Assert.Equal(12.5, bar.OHLC4); // (10 + 20 + 5 + 15) / 4
- Assert.Equal(13.75, bar.HLCC4); // (20 + 5 + 15 + 15) / 4
- }
-
- [Fact]
- public void TBarSeries_Operations()
- {
- var series = new TBarSeries();
- var time = DateTime.Now;
- var bar1 = new TBar(time, 10.0, 12.0, 9.0, 11.0, 1000.0);
- var bar2 = new TBar(time.AddMinutes(1), 11.0, 13.0, 10.0, 12.0, 1100.0);
-
- // Test adding bars
- series.Add(bar1);
- series.Add(bar2);
- Assert.Equal(2, series.Count);
-
- // Test updating last bar
- var bar2Update = new TBar(bar2.Time, 11.0, 13.5, 9.5, 12.5, 1200.0, false);
- series.Add(bar2Update);
- Assert.Equal(2, series.Count);
- Assert.Equal(12.5, series.Last.Close);
-
- // Test derived series
- Assert.Equal(11.0, series.Open.Last.Value);
- Assert.Equal(13.5, series.High.Last.Value);
- Assert.Equal(9.5, series.Low.Last.Value);
- Assert.Equal(12.5, series.Close.Last.Value);
- Assert.Equal(1200.0, series.Volume.Last.Value);
- }
-
- #endregion
-
- #region TValue Tests
-
- [Fact]
- public void TValue_Construction()
- {
- // Default constructor
- var value1 = new TValue();
- Assert.Equal(0, value1.Value);
- Assert.True(value1.IsNew);
- Assert.True(value1.IsHot);
-
- // Value constructor
- var value2 = new TValue(10.0);
- Assert.Equal(10.0, value2.Value);
-
- // Full constructor
- var time = DateTime.Now;
- var value3 = new TValue(time, 10.0, false, false);
- Assert.Equal(time, value3.Time);
- Assert.Equal(10.0, value3.Value);
- Assert.False(value3.IsNew);
- Assert.False(value3.IsHot);
- }
-
- [Fact]
- public void TValue_Conversions()
- {
- var value = new TValue(10.0);
-
- // Test implicit conversions
- double d = value;
- Assert.Equal(10.0, d);
-
- DateTime time = value;
- Assert.Equal(value.Time, time);
-
- // Test implicit conversion from double
- TValue newValue = 20.0;
- Assert.Equal(20.0, newValue.Value);
- }
-
- [Fact]
- public void TSeries_Operations()
- {
- var series = new TSeries();
- var time = DateTime.Now;
-
- // Test adding values
- series.Add(time, 10.0);
- series.Add(time.AddMinutes(1), 20.0);
- Assert.Equal(2, series.Count);
-
- // Test updating last value
- series.Add(new TValue(time.AddMinutes(1), 25.0, false));
- Assert.Equal(2, series.Count);
- Assert.Equal(25.0, series.Last.Value);
-
- // Test adding range of values
- var values = new[] { 30.0, 40.0, 50.0 };
- foreach (var value in values)
- {
- series.Add(time.AddMinutes(series.Count + 1), value);
- }
- Assert.Equal(5, series.Count);
-
- // Test conversions
- var doubleList = (List)series;
- Assert.Equal(5, doubleList.Count);
- Assert.Equal(50.0, doubleList[^1]);
-
- var doubleArray = (double[])series;
- Assert.Equal(5, doubleArray.Length);
- Assert.Equal(50.0, doubleArray[^1]);
- }
-
- [Fact]
- public void TSeries_EventHandling()
- {
- var series = new TSeries();
- var receivedValues = new List();
- var time = DateTime.Now;
-
- series.Pub += (object sender, in ValueEventArgs args) => receivedValues.Add(args.Tick.Value);
-
- series.Add(time, 10.0);
- series.Add(time.AddMinutes(1), 20.0);
- series.Add(time.AddMinutes(2), 30.0);
-
- Assert.Equal(3, receivedValues.Count);
- Assert.Equal(10.0, receivedValues[0]);
- Assert.Equal(20.0, receivedValues[1]);
- Assert.Equal(30.0, receivedValues[2]);
- }
-
- #endregion
-}
diff --git a/Tests/test_eventing.cs b/Tests/test_eventing.cs
deleted file mode 100644
index 1d9c6b7c..00000000
--- a/Tests/test_eventing.cs
+++ /dev/null
@@ -1,160 +0,0 @@
-using Xunit;
-using System.Security.Cryptography;
-using System.Reflection;
-
-namespace QuanTAlib.Tests;
-
-public class EventingTests
-{
- private const int TestDataPoints = 200;
- private const int DefaultPeriod = 10;
- private const double Tolerance = 1e-9;
-
- private static readonly (string Name, object[] DirectParams, object[] EventParams)[] ValueIndicators =
- {
- ("Afirma", new object[] { DefaultPeriod, DefaultPeriod, Afirma.WindowType.BlackmanHarris }, new object[] { new TSeries(), DefaultPeriod, DefaultPeriod, Afirma.WindowType.BlackmanHarris }),
- ("Alma", new object[] { DefaultPeriod, 0.85, 6.0 }, new object[] { new TSeries(), DefaultPeriod, 0.85, 6.0 }),
- ("Beta", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
- ("Convolution", new object[] { new double[] {1,2,3,2,1} }, new object[] { new TSeries(), new double[] {1,2,3,2,1} }),
- ("Corr", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
- ("Covar", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
- ("Curvature", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
- ("Dema", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
- ("Dsma", new object[] { DefaultPeriod, 0.9 }, new object[] { new TSeries(), DefaultPeriod, 0.9 }),
- ("Dwma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
- ("Ema", new object[] { DefaultPeriod, true }, new object[] { new TSeries(), DefaultPeriod, true }),
- ("Entropy", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
- ("Epma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
- ("Fisher", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
- ("Frama", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
- ("Fwma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
- ("Gma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
- ("Granger", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
- ("Hma", new object[] { DefaultPeriod }, new object[] { new TSeries(), DefaultPeriod }),
- ("Htit", Array.Empty