549 lines
22 KiB
Python
549 lines
22 KiB
Python
|
|
"""
|
||
|
|
Comprehensive Indicator Comparison Test Suite (Fixed Interface)
|
||
|
|
|
||
|
|
This module provides testing framework to compare original indicators from cycles module
|
||
|
|
with new implementations in IncrementalTrader module to ensure mathematical equivalence.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import pandas as pd
|
||
|
|
import numpy as np
|
||
|
|
import matplotlib.pyplot as plt
|
||
|
|
import matplotlib.dates as mdates
|
||
|
|
from datetime import datetime
|
||
|
|
import sys
|
||
|
|
import os
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
# Add project root to path
|
||
|
|
project_root = Path(__file__).parent.parent
|
||
|
|
sys.path.insert(0, str(project_root))
|
||
|
|
|
||
|
|
# Import original indicators
|
||
|
|
from cycles.IncStrategies.indicators import (
|
||
|
|
MovingAverageState as OriginalMA,
|
||
|
|
ExponentialMovingAverageState as OriginalEMA,
|
||
|
|
ATRState as OriginalATR,
|
||
|
|
SimpleATRState as OriginalSimpleATR,
|
||
|
|
SupertrendState as OriginalSupertrend,
|
||
|
|
RSIState as OriginalRSI,
|
||
|
|
SimpleRSIState as OriginalSimpleRSI,
|
||
|
|
BollingerBandsState as OriginalBB,
|
||
|
|
BollingerBandsOHLCState as OriginalBBOHLC
|
||
|
|
)
|
||
|
|
|
||
|
|
# Import new indicators
|
||
|
|
from IncrementalTrader.strategies.indicators import (
|
||
|
|
MovingAverageState as NewMA,
|
||
|
|
ExponentialMovingAverageState as NewEMA,
|
||
|
|
ATRState as NewATR,
|
||
|
|
SimpleATRState as NewSimpleATR,
|
||
|
|
SupertrendState as NewSupertrend,
|
||
|
|
RSIState as NewRSI,
|
||
|
|
SimpleRSIState as NewSimpleRSI,
|
||
|
|
BollingerBandsState as NewBB,
|
||
|
|
BollingerBandsOHLCState as NewBBOHLC
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class IndicatorComparisonTester:
|
||
|
|
"""Test framework for comparing original and new indicator implementations."""
|
||
|
|
|
||
|
|
def __init__(self, data_file: str = "data/btcusd_1-min_data.csv", sample_size: int = 5000):
|
||
|
|
"""
|
||
|
|
Initialize the tester with data.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
data_file: Path to the CSV data file
|
||
|
|
sample_size: Number of data points to use for testing (None for all data)
|
||
|
|
"""
|
||
|
|
self.data_file = data_file
|
||
|
|
self.sample_size = sample_size
|
||
|
|
self.data = None
|
||
|
|
self.results = {}
|
||
|
|
|
||
|
|
# Create results directory
|
||
|
|
self.results_dir = Path("test/results")
|
||
|
|
self.results_dir.mkdir(exist_ok=True)
|
||
|
|
|
||
|
|
def load_data(self):
|
||
|
|
"""Load and prepare the data for testing."""
|
||
|
|
print(f"Loading data from {self.data_file}...")
|
||
|
|
|
||
|
|
# Load data
|
||
|
|
df = pd.read_csv(self.data_file)
|
||
|
|
|
||
|
|
# Convert timestamp to datetime
|
||
|
|
df['datetime'] = pd.to_datetime(df['Timestamp'], unit='s')
|
||
|
|
|
||
|
|
# Take sample if specified
|
||
|
|
if self.sample_size and len(df) > self.sample_size:
|
||
|
|
# Take the most recent data
|
||
|
|
df = df.tail(self.sample_size).reset_index(drop=True)
|
||
|
|
|
||
|
|
self.data = df
|
||
|
|
print(f"Loaded {len(df)} data points from {df['datetime'].iloc[0]} to {df['datetime'].iloc[-1]}")
|
||
|
|
|
||
|
|
def compare_moving_averages(self, periods=[20, 50]):
|
||
|
|
"""Compare Moving Average implementations."""
|
||
|
|
print("\n=== Testing Moving Averages ===")
|
||
|
|
|
||
|
|
for period in periods:
|
||
|
|
print(f"Testing MA({period})...")
|
||
|
|
|
||
|
|
# Initialize indicators
|
||
|
|
original_ma = OriginalMA(period)
|
||
|
|
new_ma = NewMA(period)
|
||
|
|
|
||
|
|
original_values = []
|
||
|
|
new_values = []
|
||
|
|
|
||
|
|
# Process data
|
||
|
|
for _, row in self.data.iterrows():
|
||
|
|
price = row['Close']
|
||
|
|
|
||
|
|
original_ma.update(price)
|
||
|
|
new_ma.update(price)
|
||
|
|
|
||
|
|
original_values.append(original_ma.get_current_value() if original_ma.is_warmed_up() else np.nan)
|
||
|
|
new_values.append(new_ma.get_current_value() if new_ma.is_warmed_up() else np.nan)
|
||
|
|
|
||
|
|
# Store results
|
||
|
|
self.results[f'MA_{period}'] = {
|
||
|
|
'original': original_values,
|
||
|
|
'new': new_values,
|
||
|
|
'dates': self.data['datetime'].tolist()
|
||
|
|
}
|
||
|
|
|
||
|
|
# Calculate differences
|
||
|
|
diff = np.array(new_values) - np.array(original_values)
|
||
|
|
valid_diff = diff[~np.isnan(diff)]
|
||
|
|
|
||
|
|
print(f" Max difference: {np.max(np.abs(valid_diff)):.10f}")
|
||
|
|
print(f" Mean difference: {np.mean(np.abs(valid_diff)):.10f}")
|
||
|
|
print(f" Std difference: {np.std(valid_diff):.10f}")
|
||
|
|
|
||
|
|
def compare_exponential_moving_averages(self, periods=[20, 50]):
|
||
|
|
"""Compare Exponential Moving Average implementations."""
|
||
|
|
print("\n=== Testing Exponential Moving Averages ===")
|
||
|
|
|
||
|
|
for period in periods:
|
||
|
|
print(f"Testing EMA({period})...")
|
||
|
|
|
||
|
|
# Initialize indicators
|
||
|
|
original_ema = OriginalEMA(period)
|
||
|
|
new_ema = NewEMA(period)
|
||
|
|
|
||
|
|
original_values = []
|
||
|
|
new_values = []
|
||
|
|
|
||
|
|
# Process data
|
||
|
|
for _, row in self.data.iterrows():
|
||
|
|
price = row['Close']
|
||
|
|
|
||
|
|
original_ema.update(price)
|
||
|
|
new_ema.update(price)
|
||
|
|
|
||
|
|
original_values.append(original_ema.get_current_value() if original_ema.is_warmed_up() else np.nan)
|
||
|
|
new_values.append(new_ema.get_current_value() if new_ema.is_warmed_up() else np.nan)
|
||
|
|
|
||
|
|
# Store results
|
||
|
|
self.results[f'EMA_{period}'] = {
|
||
|
|
'original': original_values,
|
||
|
|
'new': new_values,
|
||
|
|
'dates': self.data['datetime'].tolist()
|
||
|
|
}
|
||
|
|
|
||
|
|
# Calculate differences
|
||
|
|
diff = np.array(new_values) - np.array(original_values)
|
||
|
|
valid_diff = diff[~np.isnan(diff)]
|
||
|
|
|
||
|
|
print(f" Max difference: {np.max(np.abs(valid_diff)):.10f}")
|
||
|
|
print(f" Mean difference: {np.mean(np.abs(valid_diff)):.10f}")
|
||
|
|
print(f" Std difference: {np.std(valid_diff):.10f}")
|
||
|
|
|
||
|
|
def compare_atr(self, periods=[14]):
|
||
|
|
"""Compare ATR implementations."""
|
||
|
|
print("\n=== Testing ATR ===")
|
||
|
|
|
||
|
|
for period in periods:
|
||
|
|
print(f"Testing ATR({period})...")
|
||
|
|
|
||
|
|
# Initialize indicators
|
||
|
|
original_atr = OriginalATR(period)
|
||
|
|
new_atr = NewATR(period)
|
||
|
|
|
||
|
|
original_values = []
|
||
|
|
new_values = []
|
||
|
|
|
||
|
|
# Process data
|
||
|
|
for _, row in self.data.iterrows():
|
||
|
|
high, low, close = row['High'], row['Low'], row['Close']
|
||
|
|
ohlc = {'open': close, 'high': high, 'low': low, 'close': close}
|
||
|
|
|
||
|
|
original_atr.update(ohlc)
|
||
|
|
new_atr.update(ohlc)
|
||
|
|
|
||
|
|
original_values.append(original_atr.get_current_value() if original_atr.is_warmed_up() else np.nan)
|
||
|
|
new_values.append(new_atr.get_current_value() if new_atr.is_warmed_up() else np.nan)
|
||
|
|
|
||
|
|
# Store results
|
||
|
|
self.results[f'ATR_{period}'] = {
|
||
|
|
'original': original_values,
|
||
|
|
'new': new_values,
|
||
|
|
'dates': self.data['datetime'].tolist()
|
||
|
|
}
|
||
|
|
|
||
|
|
# Calculate differences
|
||
|
|
diff = np.array(new_values) - np.array(original_values)
|
||
|
|
valid_diff = diff[~np.isnan(diff)]
|
||
|
|
|
||
|
|
print(f" Max difference: {np.max(np.abs(valid_diff)):.10f}")
|
||
|
|
print(f" Mean difference: {np.mean(np.abs(valid_diff)):.10f}")
|
||
|
|
print(f" Std difference: {np.std(valid_diff):.10f}")
|
||
|
|
|
||
|
|
def compare_simple_atr(self, periods=[14]):
|
||
|
|
"""Compare Simple ATR implementations."""
|
||
|
|
print("\n=== Testing Simple ATR ===")
|
||
|
|
|
||
|
|
for period in periods:
|
||
|
|
print(f"Testing SimpleATR({period})...")
|
||
|
|
|
||
|
|
# Initialize indicators
|
||
|
|
original_atr = OriginalSimpleATR(period)
|
||
|
|
new_atr = NewSimpleATR(period)
|
||
|
|
|
||
|
|
original_values = []
|
||
|
|
new_values = []
|
||
|
|
|
||
|
|
# Process data
|
||
|
|
for _, row in self.data.iterrows():
|
||
|
|
high, low, close = row['High'], row['Low'], row['Close']
|
||
|
|
ohlc = {'open': close, 'high': high, 'low': low, 'close': close}
|
||
|
|
|
||
|
|
original_atr.update(ohlc)
|
||
|
|
new_atr.update(ohlc)
|
||
|
|
|
||
|
|
original_values.append(original_atr.get_current_value() if original_atr.is_warmed_up() else np.nan)
|
||
|
|
new_values.append(new_atr.get_current_value() if new_atr.is_warmed_up() else np.nan)
|
||
|
|
|
||
|
|
# Store results
|
||
|
|
self.results[f'SimpleATR_{period}'] = {
|
||
|
|
'original': original_values,
|
||
|
|
'new': new_values,
|
||
|
|
'dates': self.data['datetime'].tolist()
|
||
|
|
}
|
||
|
|
|
||
|
|
# Calculate differences
|
||
|
|
diff = np.array(new_values) - np.array(original_values)
|
||
|
|
valid_diff = diff[~np.isnan(diff)]
|
||
|
|
|
||
|
|
print(f" Max difference: {np.max(np.abs(valid_diff)):.10f}")
|
||
|
|
print(f" Mean difference: {np.mean(np.abs(valid_diff)):.10f}")
|
||
|
|
print(f" Std difference: {np.std(valid_diff):.10f}")
|
||
|
|
|
||
|
|
def compare_supertrend(self, periods=[10], multipliers=[3.0]):
|
||
|
|
"""Compare Supertrend implementations."""
|
||
|
|
print("\n=== Testing Supertrend ===")
|
||
|
|
|
||
|
|
for period in periods:
|
||
|
|
for multiplier in multipliers:
|
||
|
|
print(f"Testing Supertrend({period}, {multiplier})...")
|
||
|
|
|
||
|
|
# Initialize indicators
|
||
|
|
original_st = OriginalSupertrend(period, multiplier)
|
||
|
|
new_st = NewSupertrend(period, multiplier)
|
||
|
|
|
||
|
|
original_values = []
|
||
|
|
new_values = []
|
||
|
|
original_trends = []
|
||
|
|
new_trends = []
|
||
|
|
|
||
|
|
# Process data
|
||
|
|
for _, row in self.data.iterrows():
|
||
|
|
high, low, close = row['High'], row['Low'], row['Close']
|
||
|
|
ohlc = {'open': close, 'high': high, 'low': low, 'close': close}
|
||
|
|
|
||
|
|
original_st.update(ohlc)
|
||
|
|
new_st.update(ohlc)
|
||
|
|
|
||
|
|
# Get current values
|
||
|
|
orig_result = original_st.get_current_value() if original_st.is_warmed_up() else None
|
||
|
|
new_result = new_st.get_current_value() if new_st.is_warmed_up() else None
|
||
|
|
|
||
|
|
if orig_result:
|
||
|
|
original_values.append(orig_result['supertrend'])
|
||
|
|
original_trends.append(orig_result['trend'])
|
||
|
|
else:
|
||
|
|
original_values.append(np.nan)
|
||
|
|
original_trends.append(0)
|
||
|
|
|
||
|
|
if new_result:
|
||
|
|
new_values.append(new_result['supertrend'])
|
||
|
|
new_trends.append(new_result['trend'])
|
||
|
|
else:
|
||
|
|
new_values.append(np.nan)
|
||
|
|
new_trends.append(0)
|
||
|
|
|
||
|
|
# Store results
|
||
|
|
key = f'Supertrend_{period}_{multiplier}'
|
||
|
|
self.results[key] = {
|
||
|
|
'original': original_values,
|
||
|
|
'new': new_values,
|
||
|
|
'original_trend': original_trends,
|
||
|
|
'new_trend': new_trends,
|
||
|
|
'dates': self.data['datetime'].tolist()
|
||
|
|
}
|
||
|
|
|
||
|
|
# Calculate differences
|
||
|
|
diff = np.array(new_values) - np.array(original_values)
|
||
|
|
valid_diff = diff[~np.isnan(diff)]
|
||
|
|
|
||
|
|
trend_diff = np.array(new_trends) - np.array(original_trends)
|
||
|
|
trend_matches = np.sum(trend_diff == 0) / len(trend_diff) * 100
|
||
|
|
|
||
|
|
print(f" Max difference: {np.max(np.abs(valid_diff)):.10f}")
|
||
|
|
print(f" Mean difference: {np.mean(np.abs(valid_diff)):.10f}")
|
||
|
|
print(f" Trend match: {trend_matches:.2f}%")
|
||
|
|
|
||
|
|
def compare_rsi(self, periods=[14]):
|
||
|
|
"""Compare RSI implementations."""
|
||
|
|
print("\n=== Testing RSI ===")
|
||
|
|
|
||
|
|
for period in periods:
|
||
|
|
print(f"Testing RSI({period})...")
|
||
|
|
|
||
|
|
# Initialize indicators
|
||
|
|
original_rsi = OriginalRSI(period)
|
||
|
|
new_rsi = NewRSI(period)
|
||
|
|
|
||
|
|
original_values = []
|
||
|
|
new_values = []
|
||
|
|
|
||
|
|
# Process data
|
||
|
|
for _, row in self.data.iterrows():
|
||
|
|
price = row['Close']
|
||
|
|
|
||
|
|
original_rsi.update(price)
|
||
|
|
new_rsi.update(price)
|
||
|
|
|
||
|
|
original_values.append(original_rsi.get_current_value() if original_rsi.is_warmed_up() else np.nan)
|
||
|
|
new_values.append(new_rsi.get_current_value() if new_rsi.is_warmed_up() else np.nan)
|
||
|
|
|
||
|
|
# Store results
|
||
|
|
self.results[f'RSI_{period}'] = {
|
||
|
|
'original': original_values,
|
||
|
|
'new': new_values,
|
||
|
|
'dates': self.data['datetime'].tolist()
|
||
|
|
}
|
||
|
|
|
||
|
|
# Calculate differences
|
||
|
|
diff = np.array(new_values) - np.array(original_values)
|
||
|
|
valid_diff = diff[~np.isnan(diff)]
|
||
|
|
|
||
|
|
print(f" Max difference: {np.max(np.abs(valid_diff)):.10f}")
|
||
|
|
print(f" Mean difference: {np.mean(np.abs(valid_diff)):.10f}")
|
||
|
|
print(f" Std difference: {np.std(valid_diff):.10f}")
|
||
|
|
|
||
|
|
def compare_simple_rsi(self, periods=[14]):
|
||
|
|
"""Compare Simple RSI implementations."""
|
||
|
|
print("\n=== Testing Simple RSI ===")
|
||
|
|
|
||
|
|
for period in periods:
|
||
|
|
print(f"Testing SimpleRSI({period})...")
|
||
|
|
|
||
|
|
# Initialize indicators
|
||
|
|
original_rsi = OriginalSimpleRSI(period)
|
||
|
|
new_rsi = NewSimpleRSI(period)
|
||
|
|
|
||
|
|
original_values = []
|
||
|
|
new_values = []
|
||
|
|
|
||
|
|
# Process data
|
||
|
|
for _, row in self.data.iterrows():
|
||
|
|
price = row['Close']
|
||
|
|
|
||
|
|
original_rsi.update(price)
|
||
|
|
new_rsi.update(price)
|
||
|
|
|
||
|
|
original_values.append(original_rsi.get_current_value() if original_rsi.is_warmed_up() else np.nan)
|
||
|
|
new_values.append(new_rsi.get_current_value() if new_rsi.is_warmed_up() else np.nan)
|
||
|
|
|
||
|
|
# Store results
|
||
|
|
self.results[f'SimpleRSI_{period}'] = {
|
||
|
|
'original': original_values,
|
||
|
|
'new': new_values,
|
||
|
|
'dates': self.data['datetime'].tolist()
|
||
|
|
}
|
||
|
|
|
||
|
|
# Calculate differences
|
||
|
|
diff = np.array(new_values) - np.array(original_values)
|
||
|
|
valid_diff = diff[~np.isnan(diff)]
|
||
|
|
|
||
|
|
print(f" Max difference: {np.max(np.abs(valid_diff)):.10f}")
|
||
|
|
print(f" Mean difference: {np.mean(np.abs(valid_diff)):.10f}")
|
||
|
|
print(f" Std difference: {np.std(valid_diff):.10f}")
|
||
|
|
|
||
|
|
def compare_bollinger_bands(self, periods=[20], std_devs=[2.0]):
|
||
|
|
"""Compare Bollinger Bands implementations."""
|
||
|
|
print("\n=== Testing Bollinger Bands ===")
|
||
|
|
|
||
|
|
for period in periods:
|
||
|
|
for std_dev in std_devs:
|
||
|
|
print(f"Testing BollingerBands({period}, {std_dev})...")
|
||
|
|
|
||
|
|
# Initialize indicators
|
||
|
|
original_bb = OriginalBB(period, std_dev)
|
||
|
|
new_bb = NewBB(period, std_dev)
|
||
|
|
|
||
|
|
original_upper = []
|
||
|
|
original_middle = []
|
||
|
|
original_lower = []
|
||
|
|
new_upper = []
|
||
|
|
new_middle = []
|
||
|
|
new_lower = []
|
||
|
|
|
||
|
|
# Process data
|
||
|
|
for _, row in self.data.iterrows():
|
||
|
|
price = row['Close']
|
||
|
|
|
||
|
|
original_bb.update(price)
|
||
|
|
new_bb.update(price)
|
||
|
|
|
||
|
|
# Get current values
|
||
|
|
orig_result = original_bb.get_current_value() if original_bb.is_warmed_up() else None
|
||
|
|
new_result = new_bb.get_current_value() if new_bb.is_warmed_up() else None
|
||
|
|
|
||
|
|
if orig_result:
|
||
|
|
original_upper.append(orig_result['upper_band'])
|
||
|
|
original_middle.append(orig_result['middle_band'])
|
||
|
|
original_lower.append(orig_result['lower_band'])
|
||
|
|
else:
|
||
|
|
original_upper.append(np.nan)
|
||
|
|
original_middle.append(np.nan)
|
||
|
|
original_lower.append(np.nan)
|
||
|
|
|
||
|
|
if new_result:
|
||
|
|
new_upper.append(new_result['upper_band'])
|
||
|
|
new_middle.append(new_result['middle_band'])
|
||
|
|
new_lower.append(new_result['lower_band'])
|
||
|
|
else:
|
||
|
|
new_upper.append(np.nan)
|
||
|
|
new_middle.append(np.nan)
|
||
|
|
new_lower.append(np.nan)
|
||
|
|
|
||
|
|
# Store results
|
||
|
|
key = f'BB_{period}_{std_dev}'
|
||
|
|
self.results[key] = {
|
||
|
|
'original_upper': original_upper,
|
||
|
|
'original_middle': original_middle,
|
||
|
|
'original_lower': original_lower,
|
||
|
|
'new_upper': new_upper,
|
||
|
|
'new_middle': new_middle,
|
||
|
|
'new_lower': new_lower,
|
||
|
|
'dates': self.data['datetime'].tolist()
|
||
|
|
}
|
||
|
|
|
||
|
|
# Calculate differences
|
||
|
|
for band in ['upper', 'middle', 'lower']:
|
||
|
|
orig = np.array(locals()[f'original_{band}'])
|
||
|
|
new = np.array(locals()[f'new_{band}'])
|
||
|
|
diff = new - orig
|
||
|
|
valid_diff = diff[~np.isnan(diff)]
|
||
|
|
|
||
|
|
print(f" {band.capitalize()} band - Max diff: {np.max(np.abs(valid_diff)):.10f}, "
|
||
|
|
f"Mean diff: {np.mean(np.abs(valid_diff)):.10f}")
|
||
|
|
|
||
|
|
def generate_summary_report(self):
|
||
|
|
"""Generate a summary report of all comparisons."""
|
||
|
|
print("\n=== Summary Report ===")
|
||
|
|
|
||
|
|
report_lines = []
|
||
|
|
report_lines.append("# Indicator Comparison Summary Report")
|
||
|
|
report_lines.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||
|
|
report_lines.append(f"Data file: {self.data_file}")
|
||
|
|
report_lines.append(f"Sample size: {len(self.data)} data points")
|
||
|
|
report_lines.append("")
|
||
|
|
|
||
|
|
for indicator_name, result in self.results.items():
|
||
|
|
report_lines.append(f"## {indicator_name}")
|
||
|
|
|
||
|
|
if 'original' in result and 'new' in result:
|
||
|
|
# Standard indicator
|
||
|
|
diff = np.array(result['new']) - np.array(result['original'])
|
||
|
|
valid_diff = diff[~np.isnan(diff)]
|
||
|
|
|
||
|
|
if len(valid_diff) > 0:
|
||
|
|
report_lines.append(f"- Max absolute difference: {np.max(np.abs(valid_diff)):.10f}")
|
||
|
|
report_lines.append(f"- Mean absolute difference: {np.mean(np.abs(valid_diff)):.10f}")
|
||
|
|
report_lines.append(f"- Standard deviation: {np.std(valid_diff):.10f}")
|
||
|
|
report_lines.append(f"- Valid data points: {len(valid_diff)}")
|
||
|
|
|
||
|
|
# Check if differences are negligible
|
||
|
|
if np.max(np.abs(valid_diff)) < 1e-10:
|
||
|
|
report_lines.append("- ✅ **PASSED**: Implementations are mathematically equivalent")
|
||
|
|
elif np.max(np.abs(valid_diff)) < 1e-6:
|
||
|
|
report_lines.append("- ⚠️ **WARNING**: Small differences detected (likely floating point precision)")
|
||
|
|
else:
|
||
|
|
report_lines.append("- ❌ **FAILED**: Significant differences detected")
|
||
|
|
else:
|
||
|
|
report_lines.append("- ❌ **ERROR**: No valid data points for comparison")
|
||
|
|
|
||
|
|
elif 'original_upper' in result:
|
||
|
|
# Bollinger Bands
|
||
|
|
all_passed = True
|
||
|
|
for band in ['upper', 'middle', 'lower']:
|
||
|
|
orig = np.array(result[f'original_{band}'])
|
||
|
|
new = np.array(result[f'new_{band}'])
|
||
|
|
diff = new - orig
|
||
|
|
valid_diff = diff[~np.isnan(diff)]
|
||
|
|
|
||
|
|
if len(valid_diff) > 0:
|
||
|
|
max_diff = np.max(np.abs(valid_diff))
|
||
|
|
report_lines.append(f"- {band.capitalize()} band max diff: {max_diff:.10f}")
|
||
|
|
if max_diff >= 1e-6:
|
||
|
|
all_passed = False
|
||
|
|
|
||
|
|
if all_passed:
|
||
|
|
report_lines.append("- ✅ **PASSED**: All bands are mathematically equivalent")
|
||
|
|
else:
|
||
|
|
report_lines.append("- ❌ **FAILED**: Significant differences in one or more bands")
|
||
|
|
|
||
|
|
report_lines.append("")
|
||
|
|
|
||
|
|
# Save report
|
||
|
|
report_path = self.results_dir / "comparison_summary.md"
|
||
|
|
with open(report_path, 'w', encoding='utf-8') as f:
|
||
|
|
f.write('\n'.join(report_lines))
|
||
|
|
|
||
|
|
print(f"Summary report saved to {report_path}")
|
||
|
|
|
||
|
|
# Print summary to console
|
||
|
|
print('\n'.join(report_lines))
|
||
|
|
|
||
|
|
def run_all_tests(self):
|
||
|
|
"""Run all indicator comparison tests."""
|
||
|
|
print("Starting comprehensive indicator comparison tests...")
|
||
|
|
|
||
|
|
# Load data
|
||
|
|
self.load_data()
|
||
|
|
|
||
|
|
# Run all comparisons
|
||
|
|
self.compare_moving_averages()
|
||
|
|
self.compare_exponential_moving_averages()
|
||
|
|
self.compare_atr()
|
||
|
|
self.compare_simple_atr()
|
||
|
|
self.compare_supertrend()
|
||
|
|
self.compare_rsi()
|
||
|
|
self.compare_simple_rsi()
|
||
|
|
self.compare_bollinger_bands()
|
||
|
|
|
||
|
|
# Generate reports
|
||
|
|
self.generate_summary_report()
|
||
|
|
|
||
|
|
print("\n✅ All tests completed! Check the test/results/ directory for detailed outputs.")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
# Run the comprehensive test suite
|
||
|
|
tester = IndicatorComparisonTester(sample_size=3000) # Use 3000 data points for faster testing
|
||
|
|
tester.run_all_tests()
|