10 KiB
SMA: Simple Moving Average
Overview and Purpose
The Simple Moving Average (SMA) is one of the most fundamental and widely used technical indicators in financial analysis. It calculates the arithmetic mean of a selected range of prices over a specified number of periods. Developed in the early days of technical analysis, the SMA provides traders with a straightforward method to identify trends by smoothing price data and filtering out short-term fluctuations.
Unlike the Exponential Moving Average (EMA) which gives more weight to recent data, the SMA treats all data points in the window equally. This equal weighting makes the SMA particularly intuitive to understand, as it simply represents the average price over the specified time period. Due to its simplicity and effectiveness, it remains a cornerstone indicator that forms the basis for numerous other technical analysis tools.
Core Concepts
- Equal weighting: SMA gives equal importance to each price point in the calculation period, unlike weighted averages that emphasize certain data points
- Noise reduction: Smooths price fluctuations to help identify the underlying trend direction
- Timeframe flexibility: Effective across all timeframes, with shorter periods for short-term analysis and longer periods for identifying major trends
- Foundation indicator: Serves as the mathematical basis for Bollinger Bands, moving average envelopes, and other derived indicators
The core principle of SMA is its unbiased approach to price data. By treating all prices within the lookback period with equal importance, SMA creates a balanced view of recent market activity. This equal weighting makes the SMA particularly intuitive to understand, as it simply represents the average price over the specified time period.
Common Settings and Parameters
| Parameter | Default | Function | When to Adjust |
|---|---|---|---|
| Period | 20 | Controls the lookback period | Increase for smoother signals in volatile markets, decrease for responsiveness |
| Source | Close | Price data used for calculation | Consider using HLC3 for a more balanced price representation |
Pro Tip: For trend following strategies, consider using two SMAs with different periods (e.g., 50 and 200) – crossovers between these can identify significant trend changes while filtering out minor fluctuations. This "golden cross" (50 crossing above 200) and "death cross" (50 crossing below 200) are among the most watched signals in technical analysis.
Calculation and Mathematical Foundation
Simplified explanation: SMA adds up the prices for a specific number of periods and divides by that number. For example, a 10-period SMA adds the last 10 closing prices and divides by 10 to find the average.
Technical formula: The standard calculation:
SMA = \frac{P_1 + P_2 + ... + P_n}{n} = \frac{1}{n}\sum_{i=1}^{n}P_i
An optimized recursive calculation used in the implementation:
SMA_t = SMA_{t-1} + \frac{P_t - P_{t-n}}{n}
Where:
P_1, P_2, ..., P_nare price values in the lookback windownis the period lengthP_{t-n}is the oldest price leaving the window
🔍 Technical Note: The SMA has a precisely defined lag of
(n-1)/2periods, meaning a 21-period SMA lags behind price by 10 bars. This consistent, deterministic lag makes its behavior predictable across all market conditions. The implementation uses a running sum approach for O(1) update complexity regardless of period length.
C# Implementation
The library provides two implementations: a standard scalar version and a high-performance Span-based static version.
Single SMA (Sma)
The Sma class calculates a single simple moving average with O(1) update complexity.
using QuanTAlib;
// Initialize with period 10
var sma = new Sma(10);
// Streaming update
TValue result = sma.Update(new TValue(time, price));
Console.WriteLine($"Current SMA: {result.Value}");
// Access properties
Console.WriteLine($"Name: {sma.Name}"); // "Sma(10)"
Console.WriteLine($"IsHot: {sma.IsHot}"); // true when buffer is full
// Batch calculation (TSeries API)
TSeries source = ...;
TSeries results = Sma.Calculate(source, 10);
// High-performance Span API (zero allocation)
double[] prices = new double[10000];
double[] output = new double[10000];
Sma.Calculate(prices.AsSpan(), output.AsSpan(), period: 10);
Zero-Allocation Span API
For performance-critical scenarios (backtesting, HFT), use the Span-based overload:
// Allocate buffers once, reuse across calculations
double[] source = new double[200000];
double[] smaOutput = new double[200000];
// Zero heap allocation during calculation
Sma.Calculate(source.AsSpan(), smaOutput.AsSpan(), period: 100);
// Results are written directly to output buffer
Console.WriteLine($"Last SMA: {smaOutput[^1]}");
Benefits:
- Zero allocation: No GC pressure during calculation
- Cache-friendly: Sequential memory access patterns
- 2-3x faster than TSeries API for large datasets
- Compatible with
ArrayPool<T>for buffer management
Bar Correction (isNew Parameter)
Sma supports intra-bar updates for real-time trading systems:
var sma = new Sma(10);
// Process historical bars
for (int i = 0; i < historicalBars.Count; i++)
{
sma.Update(historicalBars[i], isNew: true);
}
// Real-time: receive initial tick for new bar
sma.Update(new TValue(time, 100.5), isNew: true);
// Real-time: price updates within same bar
sma.Update(new TValue(time, 101.0), isNew: false); // O(1) correction
sma.Update(new TValue(time, 100.8), isNew: false); // O(1) correction
// Bar closes, next bar starts
sma.Update(new TValue(time + 1, 101.2), isNew: true);
Implementation detail: Bar correction is O(1) using scalar state save/restore, not buffer copying.
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
Pubevent with the newTValuewhenever it is updated.
using QuanTAlib;
// 1. Setup a source (publisher)
var source = new TSeries();
// 2. Create indicator subscribed to source
// It waits for events from 'source'
var sma = new Sma(source, period: 10);
// 3. Optional: Subscribe to indicator's output
sma.Pub += (item) => Console.WriteLine($"SMA Updated: {item.Value}");
// 4. Ingest data into source
// This triggers the chain: source -> sma -> 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)
Sma uses last-value substitution for handling invalid inputs:
var sma = new Sma(10);
// Valid values establish baseline
sma.Update(new TValue(time, 100));
sma.Update(new TValue(time, 110));
// NaN or Infinity inputs are replaced with last valid value (110)
var result = sma.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 = sma.Update(series); // All values are finite
Behavior:
- When
NaN,PositiveInfinity, orNegativeInfinityis 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
| Operation | Complexity | Notes |
|---|---|---|
| Update (isNew=true) | O(1) | Running sum: sum = sum - oldest + newest |
| Update (isNew=false) | O(1) | Scalar state restore + recalculate |
| Batch processing | O(n) | Where n is series length |
| Memory (single) | O(period) | One RingBuffer for values |
| Memory (state) | O(1) | 6 doubles for bar correction |
The implementation uses:
- Running sum for O(1) average calculation
- Scalar state save/restore for O(1) bar correction
- Pinned memory in RingBuffer for cache-friendly access
- CollectionsMarshal.SetCount for zero-allocation batch processing
Interpretation Details
SMA can be used in various trading strategies:
- Trend identification: The direction of SMA indicates the prevailing trend
- Signal generation: Crossovers between price and SMA generate basic trade signals
- Support/resistance levels: SMA can act as dynamic support during uptrends and resistance during downtrends
- Multiple timeframe analysis: Using SMAs with different periods can confirm trends across different timeframes
- Moving average crossovers: When a shorter-period SMA crosses above a longer-period SMA, it signals a potential uptrend (and vice versa)
SMA vs EMA Comparison
| Aspect | SMA | EMA |
|---|---|---|
| Weighting | Equal for all values | Recent values weighted more |
| Lag | Higher: (n-1)/2 bars |
Lower due to recent weighting |
| Sensitivity | Slower to react | Faster reaction to changes |
| Noise | Better noise filtering | More responsive but noisier |
| Sudden changes | Abrupt when oldest value exits | Smooth exponential decay |
| Best use | Long-term trends, support/resistance | Short-term signals, momentum |
Limitations and Considerations
- Market conditions: Less effective in choppy, sideways markets where price oscillates around the average
- Lag factor: Significant lag in responding to rapid price changes means SMA will always be late to signal reversals
- Equal weighting: Treats recent and older prices equally, which may not reflect current market dynamics
- Sudden changes: When a price point leaves the calculation window, it can cause abrupt changes in the SMA
- Complementary tools: Best used with momentum oscillators, volume indicators, or other trend confirmation tools
References
- Edwards, R.D. and Magee, J. (2007). Technical Analysis of Stock Trends. CRC Press.
- Murphy, J.J. (1999). Technical Analysis of the Financial Markets. New York Institute of Finance.
- Kaufman, P. (2013). Trading Systems and Methods, 5th Edition. Wiley Trading.