"Exclusive Offer: - Lifetime Access to All paid Courses and Paid Content" for Only 100 Founding Members !!

Claim Now
Finversity
TOPIC 3.1.55

FX Moving Averages with HP Filter Strategy (USD/INR & Global Crosses)

Noise Reduction with One-Sided Hodrick-Prescott Filtering & Dual Moving Average Execution on NSE Currency Futures
DIFFICULTY LEVELAdvanced|TIME TO COMPLETE5-10 Minutes

Introductory Context

"Decomposes exchange-traded currency prices into structural macro trend and noise using a strictly causal Hodrick-Prescott filter on NSE USD/INR and currency futures."

Why Raw Currency Price Series Chatter: The Noise vs Trend Problem

Foreign exchange spot rates and exchange-traded currency futures (such as USD/INR, EUR/INR, GBP/INR on NSE) are characterized by immense high-frequency noise, central bank intervention spikes (RBI USD sales/purchases), and macro event chatter. Applying traditional fast moving average crossovers (like 3-period / 12-period simple MAs) directly to raw spot or futures prices generates frequent whipsaws, false breakouts, and excessive transaction costs.

To solve this, quantitative currency desks apply the Hodrick-Prescott (HP) Filter (also known as the Whittaker-Henderson smoothing algorithm). The HP filter decomposes the noisy currency series into a smooth, underlying macro trend component (S*(t)) and a cyclical, high-frequency noise residual (ν(t)). Trend-following moving average rules are then executed exclusively on the causal, filtered trend component.

Important Key Principle

• The HP-Filtered FX Trend Rule in One Sentence

Filter out high-frequency noise from the currency series using a strictly one-sided (causal) Hodrick-Prescott filter with smoothness parameter λ = 14,400 (monthly) or calibrated equivalent, then execute long/short positions on NSE currency futures when the fast MA (3-month) crosses above or below the slow MA (12-month) of the filtered trend.

Mathematical Formulation: Causal HP Filter & Dual MA Logic

The currency spot price is decomposed into structural trend and noise:

S(t) = S*(t) + ν(t)

The HP filter estimates the smooth path {S*(t)} by minimizing the quadratic objective function over a trailing historical window ending at decision time t:

min Σt=1T [S(t) - S*(t)]2 + λ Σt=2T-1 [S*(t+1) - 2S*(t) + S*(t-1)]2
  • Goodness of Fit: The first term penalizes deviation between the actual exchange rate and the filtered trend.
  • Smoothness Penalty (λ): The second term penalizes curvature (second differences). For monthly macro data, standard literature establishes λ = 100 × n2 = 100 × 122 = 14,400.
  • Strict Causality (No Lookahead Bias): Standard econometric software packages (e.g. statsmodels) run a two-sided HP filter over the full sample by default. This fatally leaks future prices into historical trend estimates. In live quantitative trading, the HP filter must be strictly one-sided (expanding window or Kalman filter formulation) ending exactly at bar t.

Indian Currency Derivatives Framework: NSE USD/INR Application

In Indian financial markets, currency derivatives are traded on NSE and BSE across four major currency pairs: USD/INR, EUR/INR, GBP/INR, and JPY/INR.

RBI Intervention Bands & Structural Drift

The Reserve Bank of India manages rupee volatility through active foreign exchange interventions, creating periods of tight range-bound pegs followed by structural macro adjustments. The HP filter effectively strips out RBI intraday intervention noise while capturing genuine multi-month macroeconomic trends.

NSE Futures & Monthly Forward Premia

Positions are implemented via NSE near-month currency futures contracts. Total strategy return equals the directional spot trend plus or minus the interest rate differential (forward premium) minus transaction costs (STT, exchange turnover fees, bid-ask spread).

Signal Generation Rules & Portfolio Allocation

On the causal filtered series S*(t), compute fast moving average MA(3) and slow moving average MA(12):

Condition on Filtered TrendPosition Signal (w)Trade Execution on NSEMacro Rationale
MA(3) > MA(12) on S*+1.0 (Long Base FX)Buy USD/INR FuturesRupee depreciation trend against USD; macro capital outflow phase.
MA(3) < MA(12) on S*-1.0 (Short Base FX)Sell USD/INR FuturesRupee appreciation phase; strong FPI inflows / trade balance improvement.
MA(3) == MA(12)0.0 (Neutral)Exit / Cash in Liquid BeESNo decisive macro trend.

Performance Profile & Risk Parameters

Annualized Sharpe
0.75 - 0.95
On multi-pair currency book
Whipsaw Reduction
~42% Lower
vs raw dual MA crossover
Holding Horizon
45 - 90 Days
Monthly rebalanced
Max Drawdown
-8.5%
During tight RBI peg periods

Algorithmic Implementation in Python

Below is a production-grade, lookahead-free implementation of the one-sided HP-filtered moving average trend strategy for Indian currency futures:

import numpy as np
import pandas as pd
from scipy import sparse
from scipy.sparse.linalg import spsolve

def causal_hp_filter(series: pd.Series, lamb: float = 14400.0) -> pd.Series:"""
Computes an expanding-window (strictly one-sided) Hodrick-Prescott trend.
Zero future information is used: S_star[t] depends only on S[0...t]."""
n = len(series)
trend = np.zeros(n)
y = series.values

# Pre-allocate minimum warm-up period (e.g. 24 monthly bars)
warmup = 24
for t in range(warmup, n + 1):
    sub_y = y[:t]
    m = len(sub_y)
    
    # Build HP second-difference matrix D for length m
    e = np.ones(m)
    D = sparse.diags([e, -2*e, e], [0, 1, 2], shape=(m - 2, m)).tocsc()
    I = sparse.eye(m).tocsc()
    
    # Solve system: (I + lambda * D^T * D) * trend = y
    A = I + lamb * D.T.dot(D)
    fitted = spsolve(A, sub_y)
    trend[t - 1] = fitted[-1] # Take only the causal terminal endpoint
    
trend[:warmup] = y[:warmup] # Fill warmup
return pd.Series(trend, index=series.index)

def generate_hp_fx_signals(fx_prices: pd.Series) -> pd.DataFrame:"""
Generates trading signals on USD/INR or INR currency crosses."""
df = pd.DataFrame({'spot': fx_prices})
df['trend_hp'] = causal_hp_filter(df['spot'], lamb=14400.0)

# Compute Dual MAs on the filtered trend
df['fast_ma'] = df['trend_hp'].rolling(window=3).mean()
df['slow_ma'] = df['trend_hp'].rolling(window=12).mean()

# Position: +1 (Long Base FX), -1 (Short Base FX / Long INR), 0 (Flat)
df['signal'] = np.where(df['fast_ma'] > df['slow_ma'], 1.0,
                np.where(df['fast_ma'] < df['slow_ma'], -1.0, 0.0))

# Delay-1 trade execution on next session open
df['position'] = df['signal'].shift(1).fillna(0.0)
return df

More For You
Written By: Editorial Team

Disclaimer: While due care has been taken to ensure the accuracy, clarity, and relevance of the information, the content is intended solely for educational purposes. Financial terms and concepts are interpretative tools; readers are strongly advised to verify information from multiple sources and apply their own judgment. This content does not constitute financial, investment, or advisory recommendations of any kind.