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.
• 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:
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:
- 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.
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.
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 Trend | Position Signal (w) | Trade Execution on NSE | Macro Rationale |
|---|---|---|---|
| MA(3) > MA(12) on S* | +1.0 (Long Base FX) | Buy USD/INR Futures | Rupee depreciation trend against USD; macro capital outflow phase. |
| MA(3) < MA(12) on S* | -1.0 (Short Base FX) | Sell USD/INR Futures | Rupee appreciation phase; strong FPI inflows / trade balance improvement. |
| MA(3) == MA(12) | 0.0 (Neutral) | Exit / Cash in Liquid BeES | No decisive macro trend. |
Performance Profile & Risk Parameters
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