Moving Average with Variable Period (MAVP)¶
The Moving Average with Variable Period (MAVP) is a unique adaptive moving average that allows the smoothing period to change dynamically bar-by-bar based on an input array. Unlike traditional moving averages with fixed periods, MAVP adjusts its responsiveness based on external data such as volatility measurements, cycle periods, or custom adaptive logic.
Formula¶
MAVP calculates a moving average where the period varies for each data point:
For each bar i:
Period[i] = periods[i] (from input array)
MAVP[i] = MA(prices, Period[i]) (using specified MA type)
The moving average type can be any standard MA: SMA, EMA, WMA, DEMA, TEMA, TRIMA, KAMA, MAMA, or T3.
Parameters¶
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
prices |
Array |
Yes | - | Array of price values |
periods |
Array |
Yes | - | Array of period values (one per price point) |
ma_type |
Integer | No | 0 | MA type: 0=SMA, 1=EMA, 2=WMA, 3=DEMA, 4=TEMA, 5=TRIMA, 6=KAMA, 7=MAMA, 8=T3 |
Returns¶
Returns an array of MAVP values. The first several values will be nil based on the maximum period value in the periods array.
Usage¶
require 'sqa/tai'
prices = [44.34, 44.09, 44.15, 43.61, 44.33, 44.83,
45.10, 45.42, 45.84, 46.08, 46.03, 46.41,
46.22, 45.64, 46.21, 46.25, 46.08, 46.46,
46.82, 47.00, 47.32, 47.20, 47.57, 47.80,
48.00, 48.15, 48.30, 48.40, 48.55, 48.70]
# Create a variable period array
# For example: shorter periods in high volatility, longer in low volatility
periods = Array.new(prices.size, 10) # Default to 10
periods[-5..-1] = [15, 18, 20, 18, 15] # Increase period in recent bars
# Calculate MAVP using SMA (default)
mavp_sma = SQA::TAI.mavp(prices, periods)
# Calculate MAVP using EMA
mavp_ema = SQA::TAI.mavp(prices, periods, ma_type: 1)
puts "Current MAVP (SMA): #{mavp_sma.last.round(2)}"
puts "Current MAVP (EMA): #{mavp_ema.last.round(2)}"
Interpretation¶
MAVP's adaptive period makes it highly flexible:
- Variable Responsiveness: Period adjusts based on input array
- Adapts to Conditions: Can use volatility, cycle data, or custom logic
- Short Periods: More responsive, tracks price closely
- Long Periods: Smoother, filters out noise
- Period Transitions: Gradual changes create smooth adaptive behavior
Example: Volatility-Adaptive MAVP¶
# Use ATR to create volatility-adaptive periods
high, low, close = load_ohlc_data('AAPL')
# Calculate ATR for volatility
atr = SQA::TAI.atr(high, low, close, period: 14)
# Create adaptive periods based on volatility
# High ATR -> shorter period (more responsive)
# Low ATR -> longer period (smoother)
avg_atr = atr.compact.sum / atr.compact.size
periods = atr.map do |atr_val|
next 20 if atr_val.nil?
# Scale period inversely with volatility
# ATR above average -> period 10-15
# ATR below average -> period 20-30
if atr_val > avg_atr * 1.5
10 # High volatility -> fast MA
elsif atr_val > avg_atr
15
elsif atr_val > avg_atr * 0.5
20
else
30 # Low volatility -> slow MA
end
end
# Calculate volatility-adaptive MAVP
mavp = SQA::TAI.mavp(close, periods, ma_type: 1) # Using EMA
puts "Current Price: #{close.last.round(2)}"
puts "Current ATR: #{atr.last.round(2)}"
puts "Current Period: #{periods.last}"
puts "MAVP Value: #{mavp.last.round(2)}"
puts "Signal: #{close.last > mavp.last ? 'BULLISH' : 'BEARISH'}"
Example: Cycle-Based MAVP¶
# Use Hilbert Transform Dominant Cycle Period for adaptive periods
prices = load_historical_prices('SPY')
# Get the dominant cycle period
cycle_periods = SQA::TAI.ht_dcperiod(prices)
# Use cycle period directly for MAVP
# This makes the MA adapt to the market's natural cycle
mavp_cycle = SQA::TAI.mavp(prices, cycle_periods, ma_type: 1)
# Also calculate a fixed EMA for comparison
ema_20 = SQA::TAI.ema(prices, period: 20)
puts "Market Analysis:"
puts "Current Price: #{prices.last.round(2)}"
puts "Dominant Cycle: #{cycle_periods.last.round(1)} periods"
puts "Cycle-based MAVP: #{mavp_cycle.last.round(2)}"
puts "Fixed EMA(20): #{ema_20.last.round(2)}"
# Compare responsiveness
if cycle_periods.last < 20
puts "Market in SHORT cycle - MAVP more responsive than EMA"
elsif cycle_periods.last > 20
puts "Market in LONG cycle - MAVP smoother than EMA"
else
puts "Cycle matches EMA period"
end
Example: Custom Adaptive Logic¶
prices = load_historical_prices('TSLA')
# Create custom adaptive periods based on price momentum
periods = []
base_period = 20
prices.each_with_index do |price, i|
if i < 10
periods << base_period
next
end
# Calculate 10-bar momentum
momentum = ((price - prices[i-10]) / prices[i-10] * 100).abs
# Adjust period based on momentum
# High momentum -> shorter period (follow trends)
# Low momentum -> longer period (filter noise)
if momentum > 5.0
periods << 10 # Strong move -> fast MA
elsif momentum > 2.0
periods << 15
elsif momentum > 1.0
periods << 20
else
periods << 30 # Slow market -> slow MA
end
end
mavp = SQA::TAI.mavp(prices, periods, ma_type: 1)
puts "Current Momentum: #{(((prices.last - prices[-11]) / prices[-11]) * 100).round(2)}%"
puts "Adaptive Period: #{periods.last}"
puts "MAVP: #{mavp.last.round(2)}"
Example: Multi-Condition Adaptive Periods¶
high, low, close = load_ohlc_data('NVDA')
# Combine multiple factors for period selection
atr = SQA::TAI.atr(high, low, close, period: 14)
adx = SQA::TAI.adx(high, low, close, period: 14)
periods = []
close.each_with_index do |price, i|
if i < 14 || atr[i].nil? || adx[i].nil?
periods << 20
next
end
# Factor 1: Volatility (ATR)
avg_atr = atr.compact[0..i].sum / atr.compact[0..i].size
high_vol = atr[i] > avg_atr * 1.2
# Factor 2: Trend Strength (ADX)
strong_trend = adx[i] > 25
# Factor 3: Price momentum
momentum = i >= 10 ? ((price - close[i-10]) / close[i-10] * 100).abs : 0
high_momentum = momentum > 3.0
# Combine conditions
if strong_trend && high_momentum
periods << 10 # Strong trending market -> fast MA
elsif strong_trend && !high_vol
periods << 15 # Trending but calm -> medium-fast MA
elsif high_vol && !strong_trend
periods << 30 # Volatile but no trend -> slow MA
else
periods << 20 # Default
end
end
mavp = SQA::TAI.mavp(close, periods, ma_type: 1)
puts <<~OUTPUT
Market State Analysis:
ATR: #{atr.last.round(2)} (Volatility)
ADX: #{adx.last.round(2)} (Trend Strength)
Adaptive Period: #{periods.last}
MAVP: #{mavp.last.round(2)}
Price: #{close.last.round(2)}
Position: #{close.last > mavp.last ? 'Above MAVP (Bullish)' : 'Below MAVP (Bearish)'}
OUTPUT
Example: Stepped Period Zones¶
prices = load_historical_prices('MSFT')
# Create stepped zones based on price action
periods = []
lookback = 20
prices.each_with_index do |price, i|
if i < lookback
periods << 20
next
end
# Check where price is relative to recent range
recent_high = prices[(i-lookback)..i].max
recent_low = prices[(i-lookback)..i].min
range_position = (price - recent_low) / (recent_high - recent_low)
# Period zones based on range position
case range_position
when 0.0..0.2
periods << 30 # Bottom zone -> slow MA (support area)
when 0.2..0.4
periods << 20 # Lower zone
when 0.4..0.6
periods << 15 # Mid zone -> responsive
when 0.6..0.8
periods << 20 # Upper zone
else
periods << 30 # Top zone -> slow MA (resistance area)
end
end
mavp = SQA::TAI.mavp(prices, periods, ma_type: 0) # SMA
# Detect crossovers
if prices[-2] <= mavp[-2] && prices.last > mavp.last
puts "BULLISH CROSSOVER detected"
puts "Period at crossover: #{periods.last}"
end
Example: MAVP Trading System¶
high, low, close = load_ohlc_data('QQQ')
# Build adaptive periods from volatility
atr = SQA::TAI.atr(high, low, close, period: 14)
avg_atr = atr.compact.sum / atr.compact.size
periods = atr.map do |val|
next 20 if val.nil?
val > avg_atr ? 12 : 25 # Binary: fast in volatility, slow in calm
end
mavp = SQA::TAI.mavp(close, periods, ma_type: 1)
# Generate signals
signals = []
close.each_with_index do |price, i|
next if mavp[i].nil? || i < 1
prev_price = close[i-1]
prev_mavp = mavp[i-1]
curr_mavp = mavp[i]
# Bullish crossover
if prev_price <= prev_mavp && price > curr_mavp
signals << {
index: i,
type: 'BUY',
price: price,
mavp: curr_mavp,
period: periods[i]
}
# Bearish crossover
elsif prev_price >= prev_mavp && price < curr_mavp
signals << {
index: i,
type: 'SELL',
price: price,
mavp: curr_mavp,
period: periods[i]
}
end
end
# Display recent signals
puts "Recent Trading Signals:"
signals.last(5).each do |sig|
puts "#{sig[:type]} at $#{sig[:price].round(2)} (MAVP: #{sig[:mavp].round(2)}, Period: #{sig[:period]})"
end
# Current status
puts "\nCurrent Status:"
puts "Price: $#{close.last.round(2)}"
puts "MAVP: $#{mavp.last.round(2)}"
puts "Period: #{periods.last}"
puts "Trend: #{close.last > mavp.last ? 'BULLISH' : 'BEARISH'}"
MA Type Options¶
MAVP supports all standard moving average types:
| MA Type | Value | Description |
|---|---|---|
| SMA | 0 | Simple Moving Average (default) |
| EMA | 1 | Exponential Moving Average |
| WMA | 2 | Weighted Moving Average |
| DEMA | 3 | Double Exponential MA |
| TEMA | 4 | Triple Exponential MA |
| TRIMA | 5 | Triangular Moving Average |
| KAMA | 6 | Kaufman Adaptive MA |
| MAMA | 7 | MESA Adaptive MA |
| T3 | 8 | Tillson T3 |
Note: Array elements should be ordered from oldest to newest (chronological order)
Common Period Sources¶
1. Volatility-Based¶
# ATR for volatility
atr = SQA::TAI.atr(high, low, close, period: 14)
periods = atr.map { |v| v.nil? ? 20 : (v > avg_atr ? 10 : 30) }
2. Cycle-Based¶
3. Momentum-Based¶
# ROC for momentum
roc = SQA::TAI.roc(prices, period: 10)
periods = roc.map { |v| v.nil? ? 20 : (v.abs > 5 ? 10 : 30) }
4. Trend Strength¶
# ADX for trend strength
adx = SQA::TAI.adx(high, low, close, period: 14)
periods = adx.map { |v| v.nil? ? 20 : (v > 25 ? 12 : 28) }
Best Practices¶
- Smooth Period Changes: Avoid dramatic period jumps; use gradual transitions
- Set Bounds: Keep periods within reasonable range (e.g., 5-50)
- Test Logic: Backtest your adaptive period logic before live trading
- Combine Factors: Use multiple indicators for period determination
- Use Appropriate MA Type: EMA for trends, SMA for support/resistance
Advantages¶
- Maximum Flexibility: Period adapts to any custom logic
- Responsive to Conditions: Adjusts automatically to market changes
- Multi-Factor: Can combine multiple indicators for period selection
- Reduces Whipsaws: Slower periods in choppy markets
- Catches Trends: Faster periods in trending markets
Limitations¶
- Complexity: Requires good understanding of period logic
- Optimization Risk: Easy to over-optimize period selection
- Calculation Overhead: More complex than fixed-period MAs
- Look-Ahead Bias: Period array must be generated properly
- Requires External Data: Needs additional indicators for period selection
Use Cases¶
- Volatility-Adaptive Systems: Adjust to market volatility automatically
- Cycle-Based Trading: Follow market's natural cycle rhythms
- Multi-Timeframe Analysis: Adapt period to timeframe characteristics
- Regime Detection: Different periods for trending vs ranging markets
- Custom Strategies: Implement proprietary adaptive logic
Related Indicators¶
- ATR - Use for volatility-based periods
- HT_DCPERIOD - Dominant cycle period for cycle-based MAVP
- ADX - Trend strength for period selection
- KAMA - Another adaptive moving average
- MAMA - MESA Adaptive Moving Average
- EMA - Standard exponential moving average