mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-07-29 02:07:42 +00:00
285 lines
8.9 KiB
Markdown
285 lines
8.9 KiB
Markdown
|
|
# Usage Guides
|
|||
|
|
|
|||
|
|
> "Make it work, make it right, make it fast." Kent Beck (QuanTAlib skips straight to fast, assuming you already made it right)
|
|||
|
|
|
|||
|
|
QuanTAlib provides four operating modes. Each mode optimizes for different constraints. Choosing the wrong mode costs either performance or developer sanity. Sometimes both.
|
|||
|
|
|
|||
|
|
## Choosing Your Mode
|
|||
|
|
|
|||
|
|
| Mode | Best For | Allocations | Throughput | Complexity |
|
|||
|
|
| :--- | :------- | :---------: | :--------: | :--------: |
|
|||
|
|
| Span | Backtesting, batch processing | Zero | Highest | Low |
|
|||
|
|
| Streaming | Live trading, tick-by-tick | Minimal | High | Medium |
|
|||
|
|
| Batch (TSeries) | Exploration, notebooks | Some | Medium | Low |
|
|||
|
|
| Event-Driven | Reactive systems | Varies | Medium | High |
|
|||
|
|
|
|||
|
|
**Rule of thumb:** Start with Span mode for research. Move to Streaming for production. Use Event-Driven only when the architecture demands it.
|
|||
|
|
|
|||
|
|
## Mode 1: Span (High Performance)
|
|||
|
|
|
|||
|
|
The fastest path. Operates directly on memory spans. Zero heap allocations. Maximum cache efficiency.
|
|||
|
|
|
|||
|
|
**When to use:**
|
|||
|
|
|
|||
|
|
- Processing historical data in bulk
|
|||
|
|
- Backtesting where every microsecond matters
|
|||
|
|
- SIMD-optimized batch calculations
|
|||
|
|
- Memory-constrained environments
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
using QuanTAlib;
|
|||
|
|
|
|||
|
|
// Prepare buffers (allocate once, reuse many times)
|
|||
|
|
double[] prices = GetPriceHistory(); // Your data source
|
|||
|
|
double[] smaResults = new double[prices.Length];
|
|||
|
|
double[] emaResults = new double[prices.Length];
|
|||
|
|
|
|||
|
|
// Calculate (zero allocations inside these calls)
|
|||
|
|
Sma.Calculate(prices.AsSpan(), smaResults.AsSpan(), period: 14);
|
|||
|
|
Ema.Calculate(prices.AsSpan(), emaResults.AsSpan(), period: 14);
|
|||
|
|
|
|||
|
|
// Access results
|
|||
|
|
Console.WriteLine($"Last SMA: {smaResults[^1]:F4}");
|
|||
|
|
Console.WriteLine($"Last EMA: {emaResults[^1]:F4}");
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Performance note:** Span mode processes 500,000 bars in approximately 300 ¼s (0.6 ns/bar) for simple indicators like SMA. Complex indicators like JMA take longer but still measure in microseconds.
|
|||
|
|
|
|||
|
|
**Gotcha:** The destination span must be at least as long as the source span. Passing mismatched lengths throws `ArgumentException`.
|
|||
|
|
|
|||
|
|
## Mode 2: Streaming (Real-Time)
|
|||
|
|
|
|||
|
|
Updates one value at a time. Maintains internal state between calls. Handles bar corrections via the `isNew` parameter.
|
|||
|
|
|
|||
|
|
**When to use:**
|
|||
|
|
|
|||
|
|
- Live trading with real-time feeds
|
|||
|
|
- Tick-by-tick processing
|
|||
|
|
- Systems that update mid-bar
|
|||
|
|
- Memory-sensitive applications (no full history needed)
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
using QuanTAlib;
|
|||
|
|
|
|||
|
|
// Initialize once
|
|||
|
|
var sma = new Sma(period: 14);
|
|||
|
|
var ema = new Ema(period: 14);
|
|||
|
|
|
|||
|
|
// Update loop (called from your data feed)
|
|||
|
|
void OnTick(DateTime timestamp, double price)
|
|||
|
|
{
|
|||
|
|
// Update returns TValue struct: { Time, Value, IsHot }
|
|||
|
|
TValue smaResult = sma.Update(new TValue(timestamp, price));
|
|||
|
|
TValue emaResult = ema.Update(new TValue(timestamp, price));
|
|||
|
|
|
|||
|
|
// IsHot indicates warmup period is complete
|
|||
|
|
if (smaResult.IsHot && emaResult.IsHot)
|
|||
|
|
{
|
|||
|
|
double spread = emaResult.Value - smaResult.Value;
|
|||
|
|
ProcessSignal(spread);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Bar Correction Pattern
|
|||
|
|
|
|||
|
|
Real-time feeds often send multiple updates for the same bar (as price changes within the bar). The `isNew` parameter handles this:
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
// New bar arrives (isNew = true, the default)
|
|||
|
|
indicator.Update(new TValue(barTime, openPrice), isNew: true);
|
|||
|
|
|
|||
|
|
// Price updates within same bar (isNew = false)
|
|||
|
|
indicator.Update(new TValue(barTime, currentPrice), isNew: false);
|
|||
|
|
indicator.Update(new TValue(barTime, currentPrice), isNew: false);
|
|||
|
|
// ... more updates as price changes ...
|
|||
|
|
|
|||
|
|
// Next bar arrives (isNew = true again)
|
|||
|
|
indicator.Update(new TValue(nextBarTime, newOpenPrice), isNew: true);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**What happens internally:** When `isNew = false`, the indicator rolls back its state to before the previous update, then applies the new value. This ensures mid-bar corrections produce the same final result as if only the last value had been seen.
|
|||
|
|
|
|||
|
|
**Gotcha:** Forgetting to set `isNew = false` for corrections causes the indicator to treat each correction as a new bar. The output will be wrong and debugging will be painful.
|
|||
|
|
|
|||
|
|
## Mode 3: Batch (TSeries)
|
|||
|
|
|
|||
|
|
Wraps calculations in `TSeries` objects that manage timestamps and alignment. More convenient than Span mode, slightly less performant.
|
|||
|
|
|
|||
|
|
**When to use:**
|
|||
|
|
|
|||
|
|
- Jupyter-style exploration
|
|||
|
|
- Prototyping trading strategies
|
|||
|
|
- Situations where convenience beats raw speed
|
|||
|
|
- Multi-indicator analysis with automatic alignment
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
using QuanTAlib;
|
|||
|
|
|
|||
|
|
// Create time series
|
|||
|
|
var prices = new TSeries();
|
|||
|
|
prices.Add(DateTime.UtcNow, 100.0);
|
|||
|
|
prices.Add(DateTime.UtcNow.AddMinutes(1), 101.5);
|
|||
|
|
prices.Add(DateTime.UtcNow.AddMinutes(2), 99.8);
|
|||
|
|
// ... add historical data ...
|
|||
|
|
|
|||
|
|
// Calculate (returns new TSeries aligned with input)
|
|||
|
|
var sma = new Sma(prices, period: 14);
|
|||
|
|
var ema = new Ema(prices, period: 14);
|
|||
|
|
|
|||
|
|
// Access individual values
|
|||
|
|
Console.WriteLine($"Latest SMA: {sma.Last.Value:F4}");
|
|||
|
|
Console.WriteLine($"SMA at index 5: {sma[5].Value:F4}");
|
|||
|
|
|
|||
|
|
// Access as span for downstream processing
|
|||
|
|
ReadOnlySpan<double> values = sma.Values;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Gotcha:** Creating a new indicator with a TSeries computes all historical values immediately. For large datasets, this blocks until complete. For streaming scenarios, use Mode 2 instead.
|
|||
|
|
|
|||
|
|
## Mode 4: Event-Driven (Reactive)
|
|||
|
|
|
|||
|
|
Indicators can subscribe to other indicators or data sources. Changes propagate automatically through the dependency graph.
|
|||
|
|
|
|||
|
|
**When to use:**
|
|||
|
|
|
|||
|
|
- Complex indicator chains
|
|||
|
|
- Reactive architectures
|
|||
|
|
- Systems where manual update ordering is error-prone
|
|||
|
|
- UI binding scenarios
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
using QuanTAlib;
|
|||
|
|
|
|||
|
|
// Build the dependency graph
|
|||
|
|
var source = new TSeries();
|
|||
|
|
var smaFast = new Sma(source, period: 10);
|
|||
|
|
var smaSlow = new Sma(source, period: 20);
|
|||
|
|
var crossover = new Crossover(smaFast, smaSlow);
|
|||
|
|
|
|||
|
|
// Subscribe to events
|
|||
|
|
crossover.Pub += (sender, args) =>
|
|||
|
|
{
|
|||
|
|
if (args.Tick.Value > 0)
|
|||
|
|
Console.WriteLine("Bullish crossover detected");
|
|||
|
|
else if (args.Tick.Value < 0)
|
|||
|
|
Console.WriteLine("Bearish crossover detected");
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// Feed data (propagates automatically: source ’ SMAs ’ crossover ’ event)
|
|||
|
|
source.Add(DateTime.UtcNow, 105.0);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**Gotcha:** Event subscriptions create strong references. Forgetting to unsubscribe causes memory leaks. In long-running applications, use weak event patterns or explicitly call `Dispose()` on indicators when done.
|
|||
|
|
|
|||
|
|
## Common Patterns
|
|||
|
|
|
|||
|
|
### Warmup Handling
|
|||
|
|
|
|||
|
|
Every indicator has a warmup period before output becomes meaningful. Check `IsHot` before using values:
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
var rsi = new Rsi(period: 14);
|
|||
|
|
|
|||
|
|
// Feed some data...
|
|||
|
|
foreach (var price in prices)
|
|||
|
|
{
|
|||
|
|
var result = rsi.Update(new TValue(DateTime.UtcNow, price));
|
|||
|
|
|
|||
|
|
if (!result.IsHot)
|
|||
|
|
continue; // Skip warmup period
|
|||
|
|
|
|||
|
|
// Safe to use result.Value
|
|||
|
|
if (result.Value > 70)
|
|||
|
|
Console.WriteLine("Overbought");
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**WarmupPeriod property:** Each indicator exposes `WarmupPeriod` indicating how many bars until `IsHot` becomes true.
|
|||
|
|
|
|||
|
|
### Indicator Chaining
|
|||
|
|
|
|||
|
|
Feed the output of one indicator into another:
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
var ema = new Ema(period: 12);
|
|||
|
|
var rsiOfEma = new Rsi(period: 14);
|
|||
|
|
|
|||
|
|
void OnTick(double price)
|
|||
|
|
{
|
|||
|
|
// EMA smooths price
|
|||
|
|
var smoothed = ema.Update(new TValue(DateTime.UtcNow, price));
|
|||
|
|
|
|||
|
|
// RSI of the smoothed values (reduces noise)
|
|||
|
|
var rsiResult = rsiOfEma.Update(smoothed);
|
|||
|
|
|
|||
|
|
if (rsiResult.IsHot)
|
|||
|
|
ProcessSignal(rsiResult.Value);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Reset and Reuse
|
|||
|
|
|
|||
|
|
Indicators can be reset and reused without reallocation:
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
var sma = new Sma(period: 14);
|
|||
|
|
|
|||
|
|
// Process first symbol
|
|||
|
|
foreach (var tick in symbol1Ticks)
|
|||
|
|
sma.Update(tick);
|
|||
|
|
var symbol1Result = sma.Last.Value;
|
|||
|
|
|
|||
|
|
// Reset for next symbol (clears state, keeps parameters)
|
|||
|
|
sma.Reset();
|
|||
|
|
|
|||
|
|
// Process second symbol
|
|||
|
|
foreach (var tick in symbol2Ticks)
|
|||
|
|
sma.Update(tick);
|
|||
|
|
var symbol2Result = sma.Last.Value;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Multi-Indicator Analysis
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
// Using Span mode for maximum performance
|
|||
|
|
double[] prices = GetPrices();
|
|||
|
|
int length = prices.Length;
|
|||
|
|
|
|||
|
|
double[] sma = new double[length];
|
|||
|
|
double[] ema = new double[length];
|
|||
|
|
double[] rsi = new double[length];
|
|||
|
|
double[] atr = new double[length];
|
|||
|
|
|
|||
|
|
Sma.Calculate(prices, sma, 20);
|
|||
|
|
Ema.Calculate(prices, ema, 20);
|
|||
|
|
Rsi.Calculate(prices, rsi, 14);
|
|||
|
|
Atr.Calculate(highs, lows, closes, atr, 14);
|
|||
|
|
|
|||
|
|
// Analyze correlations, generate signals, etc.
|
|||
|
|
for (int i = Math.Max(20, 14); i < length; i++)
|
|||
|
|
{
|
|||
|
|
bool bullish = ema[i] > sma[i] && rsi[i] < 70;
|
|||
|
|
bool lowVolatility = atr[i] < atr[i - 1];
|
|||
|
|
// ... strategy logic ...
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Performance Comparison
|
|||
|
|
|
|||
|
|
Measured on Intel i7-12700K, .NET 8.0, 500,000 bars:
|
|||
|
|
|
|||
|
|
| Mode | SMA(14) Time | Allocations |
|
|||
|
|
| :--- | -----------: | ----------: |
|
|||
|
|
| Span | 298 ¼s | 0 bytes |
|
|||
|
|
| Streaming | 12.4 ms | 0 bytes |
|
|||
|
|
| Batch (TSeries) | 15.1 ms | 8 MB |
|
|||
|
|
| Event-Driven | 18.7 ms | 12 MB |
|
|||
|
|
|
|||
|
|
Span mode runs 40x faster than streaming for batch operations. Streaming mode remains allocation-free but incurs per-call overhead. Choose based on the actual requirements, not assumptions.
|
|||
|
|
|
|||
|
|
## References
|
|||
|
|
|
|||
|
|
- [API Reference](api.md): Complete method signatures and parameters
|
|||
|
|
- [Architecture](architecture.md): Internal design and memory model
|
|||
|
|
- [Benchmarks](benchmarks.md): Detailed performance measurements
|