FinancialWaveletAnalyzer.java
package com.morphiqlabs.financial;
import com.morphiqlabs.wavelet.modwt.MODWTTransform;
import com.morphiqlabs.wavelet.modwt.MODWTResult;
import com.morphiqlabs.wavelet.api.Haar;
import com.morphiqlabs.wavelet.api.BoundaryMode;
/**
* Financial analysis using wavelet transforms.
* Provides methods for calculating risk-adjusted returns and other financial metrics
* using wavelet-based signal processing techniques.
*
* <p><strong>⚠️ WARNING - Aggressive Denoising:</strong></p>
* <p>The wavelet-based Sharpe ratio methods use a simple but aggressive denoising
* approach that completely removes all high-frequency components (detail coefficients).
* This can remove important market signals along with noise. Consider using
* {@link com.morphiqlabs.wavelet.denoising.WaveletDenoiser} for more sophisticated
* denoising with configurable thresholds.</p>
*
* <p><strong>Note:</strong></p>
* <ul>
* <li>This class now uses MODWT (Maximal Overlap Discrete Wavelet Transform) which works with any signal length</li>
* <li>MODWT is shift-invariant and provides better time-frequency localization for financial analysis</li>
* <li>The transform produces same-length coefficients, preserving temporal alignment</li>
* </ul>
*
* <p><strong>Usage Example:</strong></p>
* <pre>{@code
* // MODWT works with any signal length - no padding needed
* double[] returns = calculateReturns(prices);
*
* FinancialWaveletAnalyzer analyzer = new FinancialWaveletAnalyzer();
* double waveletSharpe = analyzer.calculateWaveletSharpeRatio(returns);
* }</pre>
*/
public class FinancialWaveletAnalyzer {
private final FinancialConfig config;
private final MODWTTransform transform;
/**
* Creates a financial analyzer with specified configuration and default Haar wavelet.
*
* @param config the financial configuration containing risk-free rate and other parameters
* @throws IllegalArgumentException if config is null
*/
public FinancialWaveletAnalyzer(FinancialConfig config) {
if (config == null) {
throw new IllegalArgumentException("Financial configuration cannot be null");
}
this.config = config;
this.transform = new MODWTTransform(new Haar(), BoundaryMode.PERIODIC);
}
/**
* Creates a financial analyzer with specified configuration and wavelet transform.
*
* @param config the financial configuration
* @param transform the wavelet transform to use for signal processing
* @throws IllegalArgumentException if config or transform is null
*/
public FinancialWaveletAnalyzer(FinancialConfig config, MODWTTransform transform) {
if (config == null) {
throw new IllegalArgumentException("Financial configuration cannot be null");
}
if (transform == null) {
throw new IllegalArgumentException("Wavelet transform cannot be null");
}
this.config = config;
this.transform = transform;
}
/**
* Calculates the Sharpe ratio for the given returns using the configured risk-free rate.
* The Sharpe ratio is calculated as (mean_return - risk_free_rate) / std_deviation.
*
* @param returns the array of returns (as decimals, e.g., 0.05 for 5% return)
* @return the Sharpe ratio
* @throws IllegalArgumentException if returns is null, empty, or contains insufficient data
*/
public double calculateSharpeRatio(double[] returns) {
return calculateSharpeRatio(returns, config.getRiskFreeRate());
}
/**
* Calculates the Sharpe ratio for the given returns using a specified risk-free rate.
* The Sharpe ratio is calculated as (mean_return - risk_free_rate) / std_deviation.
*
* @param returns the array of returns (as decimals, e.g., 0.05 for 5% return)
* @param riskFreeRate the risk-free rate to use (as annual decimal)
* @return the Sharpe ratio
* @throws IllegalArgumentException if returns is null, empty, or contains insufficient data
*/
public double calculateSharpeRatio(double[] returns, double riskFreeRate) {
if (returns == null) {
throw new IllegalArgumentException("Returns array cannot be null");
}
if (returns.length == 0) {
throw new IllegalArgumentException("Returns array cannot be empty");
}
if (returns.length == 1) {
throw new IllegalArgumentException(
"Cannot calculate Sharpe ratio with a single return value. " +
"At least 2 returns are required to calculate standard deviation (risk).");
}
// Calculate mean return
double mean = calculateMean(returns);
// Calculate standard deviation
double stdDev = calculateStandardDeviation(returns, mean);
// Handle zero standard deviation case
if (stdDev == 0.0) {
if (mean == riskFreeRate) {
return 0.0; // No excess return, no risk
}
return mean > riskFreeRate ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY;
}
// Calculate Sharpe ratio
return (mean - riskFreeRate) / stdDev;
}
/**
* Calculates wavelet-denoised Sharpe ratio by first applying wavelet transform
* to filter out noise from the returns series.
*
* <p><strong>WARNING - Aggressive Denoising:</strong> This method uses a simple but
* aggressive denoising approach that zeros ALL detail coefficients, keeping only the
* approximation (low-frequency) components. While this effectively removes noise, it
* may also remove important market signals including volatility spikes, rapid price
* movements, and short-term trading patterns.</p>
*
* <p><strong>Suitable for:</strong> Long-term investment analysis where smoothing is desired.
* <br><strong>Not suitable for:</strong> Risk management (may hide volatility), high-frequency
* trading, or any application requiring preservation of short-term market dynamics.</p>
*
* <p><strong>Signal Length:</strong> MODWT works with signals of any length,
* not just powers of two. This makes it more flexible than traditional DWT
* for financial time series analysis.</p>
*
* <p>For more sophisticated denoising with configurable thresholds, consider using
* the {@link com.morphiqlabs.wavelet.denoising.WaveletDenoiser} class instead.</p>
*
* @param returns the array of returns (any length)
* @return the Sharpe ratio calculated from denoised returns
* @throws IllegalArgumentException if returns is null or empty
*/
public double calculateWaveletSharpeRatio(double[] returns) {
return calculateWaveletSharpeRatio(returns, config.getRiskFreeRate());
}
/**
* Calculates wavelet-denoised Sharpe ratio using specified risk-free rate.
*
* <p><strong>WARNING:</strong> Uses aggressive denoising that zeros all detail coefficients.
* See {@link #calculateWaveletSharpeRatio(double[])} for important limitations and warnings.</p>
*
* @param returns the array of returns (any length)
* @param riskFreeRate the risk-free rate to use
* @return the Sharpe ratio calculated from denoised returns
* @throws IllegalArgumentException if returns is null or empty
*/
public double calculateWaveletSharpeRatio(double[] returns, double riskFreeRate) {
if (returns == null) {
throw new IllegalArgumentException("Returns array cannot be null");
}
if (returns.length == 0) {
throw new IllegalArgumentException("Returns array cannot be empty");
}
// MODWT works with any signal length - no power-of-two restriction
// Apply wavelet transform for denoising
MODWTResult result = transform.forward(returns);
// WARNING: Aggressive denoising approach - zeros ALL detail coefficients
// This removes all high-frequency components, which may include:
// - Market microstructure noise (good to remove)
// - Short-term volatility patterns (possibly important)
// - Rapid market movements and jumps (definitely important)
// - Intraday trading signals (critical for HFT)
//
// TRADE-OFFS:
// - Pro: Produces very smooth, stable Sharpe ratios
// - Pro: Eliminates measurement noise and microstructure effects
// - Con: May remove legitimate market signals and volatility
// - Con: Can underestimate true risk by smoothing out volatility spikes
// - Con: Inappropriate for high-frequency or short-term trading strategies
//
// RECOMMENDATIONS:
// - For long-term investment analysis: May be appropriate
// - For risk management: Use with caution - may hide important risks
// - For trading signals: Not recommended - consider soft thresholding instead
// - Alternative: Use WaveletDenoiser class with configurable thresholds
double[] approxCoeffs = result.approximationCoeffs();
double[] detailCoeffs = result.detailCoeffs();
// Create zero-filled detail coefficients array of the same length
// The MODWTResultImpl constructor requires matching array lengths
double[] zeroDetails = new double[detailCoeffs.length];
// Create a new MODWTResult with original approximation and zeroed details
// Note: approxCoeffs and detailCoeffs have the same length by design from the forward transform
MODWTResult denoisedResult = MODWTResult.create(approxCoeffs, zeroDetails);
// Perform inverse transform to get denoised signal
double[] denoisedReturns = transform.inverse(denoisedResult);
return calculateSharpeRatio(denoisedReturns, riskFreeRate);
}
/**
* Returns the current financial configuration.
*
* @return the financial configuration
*/
public FinancialConfig getConfig() {
return config;
}
/**
* Returns the wavelet transform being used.
*
* @return the wavelet transform
*/
public MODWTTransform getTransform() {
return transform;
}
// Private helper methods
private double calculateMean(double[] values) {
double sum = 0.0;
for (double value : values) {
sum += value;
}
return sum / values.length;
}
private double calculateStandardDeviation(double[] values, double mean) {
// Requires at least 2 values for sample standard deviation
if (values.length < 2) {
throw new IllegalArgumentException(
"Standard deviation calculation requires at least 2 values, but got " + values.length);
}
double sumSquaredDiffs = 0.0;
for (double value : values) {
double diff = value - mean;
sumSquaredDiffs += diff * diff;
}
return Math.sqrt(sumSquaredDiffs / (values.length - 1)); // Sample standard deviation
}
}