mirror of
https://github.com/mihakralj/QuanTAlib.git
synced 2026-08-17 01:58:06 +00:00
- Implement tests for HMA (Hull Moving Average) indicator to verify default settings, history depth calculations, and value computations during updates. - Create tests for KAMA (Kaufman Adaptive Moving Average) indicator, ensuring correct defaults, history depth, and value calculations. - Add tests for SMA (Simple Moving Average) indicator, checking default values, history depth, and value computations. - Develop tests for T3 (Tillson T3 Moving Average) indicator, validating defaults, history depth, and value calculations. - Implement tests for TEMA (Triple Exponential Moving Average) indicator, ensuring correct defaults and value computations. - Create tests for TRIMA (Triangular Moving Average) indicator, verifying defaults, history depth, and value calculations. - Add tests for WMA (Weighted Moving Average) indicator, checking default values, history depth, and value computations.
206 lines
10 KiB
Markdown
206 lines
10 KiB
Markdown
# EMA: Exponential Moving Average
|
||
|
||
## Overview and Purpose
|
||
|
||
The Exponential Moving Average (EMA) is a fundamental technical indicator that calculates the average price over a specific period while giving more weight to recent price data. Introduced in the 1950s, EMA has become one of the most widely used technical indicators in financial markets due to its balance of responsiveness and stability.
|
||
|
||
Unlike the Simple Moving Average (SMA) which assigns equal weight to all data points, the EMA emphasizes recent price action, allowing traders to identify trend changes earlier while still filtering out short-term market noise. Its mathematical elegance has made it a standard tool in signal processing beyond finance, including communications, control systems, and data analysis.
|
||
|
||
## Core Concepts
|
||
|
||
* **Weighted price action:** EMA gives greater importance to recent prices through exponential weighting, providing a more timely response to current market conditions
|
||
* **Smoothing mechanism:** Acts as a noise filter by reducing the impact of random price fluctuations while preserving meaningful trends
|
||
* **Universal application:** Functions effectively across all timeframes from intraday to monthly charts, with parameter adjustments
|
||
* **Foundation indicator:** Serves as the mathematical basis for numerous other technical indicators (MACD, PPO, etc.)
|
||
|
||
EMA achieves its enhanced responsiveness by applying a smoothing factor (α) that determines how quickly older data points lose influence. This approach creates a moving average that reacts faster to price changes than an SMA of the same length while maintaining enough stability to identify the underlying trend.
|
||
|
||
## Common Settings and Parameters
|
||
|
||
| Parameter | Default | Function | When to Adjust |
|
||
|-----------|---------|----------|---------------|
|
||
| Length | 20 | Controls responsiveness/smoothness | Shorter for faster signals in active markets, longer for stable trends in ranging markets |
|
||
| Source | Close | Data point used for calculation | Change to HL2 or HLC3 for more balanced price representation |
|
||
| Alpha | 2/(length+1) | Determines weighting decay | Direct alpha manipulation allows for precise tuning beyond standard length settings |
|
||
|
||
**Pro Tip:** Many professional traders use multiple EMAs simultaneously (e.g., 8, 21, 50) to identify potential support/resistance levels and trend strength based on their relative positioning.
|
||
|
||
## Calculation and Mathematical Foundation
|
||
|
||
**Simplified explanation:**
|
||
EMA works by calculating a weighted average where recent prices have more influence. The implementation uses an optimized form of the EMA calculation that is both computationally efficient and numerically stable.
|
||
|
||
**Technical formula:**
|
||
The optimized EMA formula used in the implementation is:
|
||
$$EMA_t = \alpha \cdot P_t + (1 - \alpha) \cdot EMA_{t-1}$$
|
||
|
||
Where:
|
||
|
||
* $\alpha = \frac{2}{N + 1}$ is the smoothing factor ($N$ is the period)
|
||
* $P_t$ is the current price value
|
||
* $EMA_{t-1}$ is the previous period's EMA value
|
||
|
||
This form is algebraically equivalent to the traditional EMA formula but offers better computational efficiency and numerical stability.
|
||
|
||
> 🔍 **Technical Note:** The implementation uses **Hunter's bias compensation** method, which provides mathematically correct EMA values from the very first data point. This technique, introduced by J.S. Hunter in 1986, corrects for the initialization bias that occurs when starting an EMA from zero rather than from an infinite history of data.
|
||
>
|
||
> The compensation works by tracking an error term $e$ that decays exponentially:
|
||
> $$e_t = e_{t-1} \cdot (1 - \alpha), \quad e_0 = 1$$
|
||
> $$Compensation = \frac{1}{1 - e_t}$$
|
||
> $$EMA_{corrected} = Compensation \cdot EMA_{raw}$$
|
||
>
|
||
> **Why it works:** The standard EMA formula implicitly assumes all historical values before the first observation were zero. This creates a downward bias in early values. The compensation factor $\frac{1}{1 - (1-\alpha)^n}$ exactly corrects for this missing history, making the first output equal to the first input and ensuring all subsequent values are consistent with what a properly-seeded infinite EMA would produce.
|
||
>
|
||
> The compensation automatically diminishes as more data is processed and becomes negligible ($e \le 10^{-10}$) after approximately $\frac{23}{\alpha}$ observations, at which point the implementation switches to the raw EMA for efficiency.
|
||
|
||
## C# Implementation
|
||
|
||
The library provides two implementations: a standard scalar version and a SIMD-optimized vector version for high-performance scenarios.
|
||
|
||
### Single EMA (`Ema`)
|
||
|
||
The `Ema` class calculates a single exponential moving average.
|
||
|
||
```csharp
|
||
using QuanTAlib;
|
||
|
||
// Initialize with period 10
|
||
var ema = new Ema(10);
|
||
|
||
// Or initialize with specific alpha
|
||
var emaAlpha = new Ema(0.5);
|
||
|
||
// Streaming update
|
||
TValue result = ema.Update(new TValue(time, price));
|
||
Console.WriteLine($"Current EMA: {result.Value}");
|
||
|
||
// Access current value property
|
||
Console.WriteLine($"Current Value: {ema.Value.Value}");
|
||
|
||
// Batch calculation (TSeries API)
|
||
TSeries source = ...;
|
||
TSeries results = Ema.Calculate(source, 10);
|
||
|
||
// High-performance Span API (zero allocation)
|
||
double[] prices = new double[10000];
|
||
double[] output = new double[10000];
|
||
Ema.Calculate(prices.AsSpan(), output.AsSpan(), period: 10);
|
||
// Or with direct alpha:
|
||
Ema.Calculate(prices.AsSpan(), output.AsSpan(), alpha: 0.1818);
|
||
```
|
||
|
||
### Zero-Allocation Span API
|
||
|
||
For performance-critical scenarios (backtesting, HFT), use the Span-based overload:
|
||
|
||
```csharp
|
||
// Allocate buffers once, reuse across calculations
|
||
double[] source = new double[200000];
|
||
double[] emaOutput = new double[200000];
|
||
|
||
// Zero heap allocation during calculation - by period
|
||
Ema.Calculate(source.AsSpan(), emaOutput.AsSpan(), period: 100);
|
||
|
||
// Or by alpha for direct control
|
||
Ema.Calculate(source.AsSpan(), emaOutput.AsSpan(), alpha: 0.02);
|
||
|
||
// Results are written directly to output buffer
|
||
Console.WriteLine($"Last EMA: {emaOutput[^1]}");
|
||
```
|
||
|
||
**Benefits:**
|
||
|
||
* **Zero allocation**: No GC pressure during calculation
|
||
* **Cache-friendly**: Sequential memory access patterns
|
||
* **Hunter's bias correction**: Same accuracy as TSeries API
|
||
* **Compatible** with `ArrayPool<T>` for buffer management
|
||
|
||
### Eventing and Reactive Support
|
||
|
||
This indicator implements the `ITValuePublisher` interface, enabling event-driven and reactive workflows.
|
||
|
||
* **Subscription:** Can be constructed with an `ITValuePublisher` (e.g., `TSeries`) to automatically update when the source emits a new value.
|
||
* **Publication:** Emits a `Pub` event with the new `TValue` whenever it is updated.
|
||
|
||
```csharp
|
||
using QuanTAlib;
|
||
|
||
// 1. Setup a source (publisher)
|
||
var source = new TSeries();
|
||
|
||
// 2. Create indicator subscribed to source
|
||
// It waits for events from 'source'
|
||
var ema = new Ema(source, period: 10);
|
||
|
||
// 3. Optional: Subscribe to indicator's output
|
||
ema.Pub += (item) => Console.WriteLine($"EMA Updated: {item.Value}");
|
||
|
||
// 4. Ingest data into source
|
||
// This triggers the chain: source -> ema -> Console.WriteLine
|
||
source.Add(new TValue(DateTime.Now, 100));
|
||
source.Add(new TValue(DateTime.Now, 105));
|
||
```
|
||
|
||
This pattern allows building complex, reactive processing pipelines without manual update loops.
|
||
|
||
### Handling Invalid Values (NaN/Infinity)
|
||
|
||
`Ema` uses **last-value substitution** for handling invalid inputs:
|
||
|
||
```csharp
|
||
var ema = new Ema(10);
|
||
|
||
// Valid values establish baseline
|
||
ema.Update(new TValue(time, 100));
|
||
ema.Update(new TValue(time, 110));
|
||
|
||
// NaN or Infinity inputs are replaced with last valid value (110)
|
||
var result = ema.Update(new TValue(time, double.NaN));
|
||
Console.WriteLine(double.IsFinite(result.Value)); // true
|
||
|
||
// Works identically for batch operations
|
||
var series = new TSeries();
|
||
series.Add(time, 100);
|
||
series.Add(time + 1, double.NaN); // Will use 100
|
||
series.Add(time + 2, 120);
|
||
var results = ema.Update(series); // All values are finite
|
||
```
|
||
|
||
**Behavior:**
|
||
|
||
* When `NaN`, `PositiveInfinity`, or `NegativeInfinity` is encountered, the last valid value is substituted
|
||
* This provides output continuity instead of propagating invalid values
|
||
* `Reset()` clears the last valid value, so the next valid input establishes a new baseline
|
||
|
||
### Performance Characteristics
|
||
|
||
* **O(1) Complexity:** The calculation time is constant regardless of the period length.
|
||
* **Zero Allocation:** The streaming `Update` method is designed to be allocation-free (excluding the return struct).
|
||
|
||
## Interpretation Details
|
||
|
||
The EMA's primary value comes from its ability to identify trend direction and potential reversal points:
|
||
|
||
* When price is above EMA, the short-term trend is generally bullish
|
||
* When price is below EMA, the short-term trend is generally bearish
|
||
* When a shorter-period EMA crosses above a longer-period EMA, it often signals the beginning of an uptrend
|
||
* When a shorter-period EMA crosses below a longer-period EMA, it often signals the beginning of a downtrend
|
||
* The slope of the EMA indicates trend strength and momentum
|
||
|
||
EMAs work particularly well in trending markets but may generate false signals during sideways or choppy conditions. For optimal results, traders typically use EMA crossovers or EMA-price crossovers as part of a broader system that includes volume and momentum confirmation.
|
||
|
||
## Limitations and Considerations
|
||
|
||
* **Market conditions:** Less effective in choppy, sideways markets where price constantly crosses the average
|
||
* **Lag factor:** While less significant than SMA, EMA still exhibits some lag, especially with longer lookback periods
|
||
* **False signals:** Can produce whipsaws during consolidation phases or range-bound conditions
|
||
* **Parameter sensitivity:** Small changes in length or alpha can significantly alter behavior
|
||
* **Complementary tools:** Should be used with momentum indicators (RSI, MACD) or volume indicators for confirmation
|
||
|
||
## References
|
||
|
||
1. Hunter, J.S. (1986). "The Exponentially Weighted Moving Average." *Journal of Quality Technology*, 18(4), 203-210.
|
||
2. Murphy, J.J. (1999). *Technical Analysis of the Financial Markets*. New York Institute of Finance.
|
||
3. Kaufman, P. (2013). *Trading Systems and Methods*, 5th Edition. Wiley Trading.
|
||
4. Ehlers, J. (2001). *Rocket Science for Traders*. John Wiley & Sons.
|