import numpy as np import matplotlib.pyplot as plt from scipy.stats import pearsonr import warnings warnings.filterwarnings('ignore') # Import our utility functions from .backshift import backshift from .fwdshift import fwdshift from .smartmean import smartmean from .smartstd import smartstd from .calculateMaxDD import calculateMaxDD from .data_loader import load_futures_data def main(): """ Python implementation of the TU_mom.m momentum trading strategy. """ print("TU Momentum Trading Strategy") print("=" * 40) try: # Try to load real Treasury futures data print("Loading Treasury futures data...") data = load_futures_data('TU', '20120813') tday = data['tday'] cl = data['cl'][:, 0] # Use first contract for simplicity print(f"Loaded {len(tday)} days of Treasury futures data") except (FileNotFoundError, KeyError) as e: print(f"Could not load real data ({e}), using synthetic data for demonstration...") # Synthetic data for demonstration np.random.seed(42) n_days = 2000 tday = np.arange(20090102, 20090102 + n_days) cl = 100 * np.cumprod(1 + np.random.normal(0, 0.01, n_days)) # Correlation tests print("\nCorrelation Analysis:") print("Lookback\tHolddays\tCorrelation\tp-value") print("-" * 50) for lookback in [1, 5, 10, 25, 60, 120, 250]: for holddays in [1, 5, 10, 25, 60, 120, 250]: # Calculate lagged returns ret_lag = (cl - backshift(lookback, cl)) / backshift(lookback, cl) ret_fut = (fwdshift(holddays, cl) - cl) / cl # Remove bad dates (NaN values) bad_dates = np.isnan(ret_lag) | np.isnan(ret_fut) ret_lag_clean = ret_lag[~bad_dates] ret_fut_clean = ret_fut[~bad_dates] if len(ret_lag_clean) == 0: continue # Create independent set if lookback >= holddays: indep_set = np.arange(0, len(ret_lag_clean), holddays) else: indep_set = np.arange(0, len(ret_lag_clean), lookback) ret_lag_indep = ret_lag_clean[indep_set] ret_fut_indep = ret_fut_clean[indep_set] # Calculate correlation if len(ret_lag_indep) > 1: cc, pval = pearsonr(ret_lag_indep, ret_fut_indep) print(f"{lookback:3d}\t\t{holddays:3d}\t\t{cc:7.4f}\t\t{pval:6.4f}") # Trading strategy implementation lookback = 250 holddays = 25 print(f"\nImplementing Trading Strategy:") print(f"Lookback: {lookback} days, Hold: {holddays} days") # Generate trading signals longs = cl > backshift(lookback, cl) shorts = cl < backshift(lookback, cl) # Initialize positions pos = np.zeros(len(cl)) # Build position over holding period for h in range(holddays): long_lag = backshift(h, longs.astype(float)) long_lag = np.nan_to_num(long_lag, nan=0).astype(bool) short_lag = backshift(h, shorts.astype(float)) short_lag = np.nan_to_num(short_lag, nan=0).astype(bool) pos[long_lag] += 1 pos[short_lag] -= 1 # Calculate returns ret = (backshift(1, pos) * (cl - backshift(1, cl)) / backshift(1, cl)) / holddays ret = np.nan_to_num(ret, nan=0) # Find start index (equivalent to finding date 20090102) idx = 250 # Start after sufficient data for lookback # Calculate cumulative returns cumret = np.cumprod(1 + ret[idx:]) - 1 # Plot results plt.figure(figsize=(12, 6)) plt.plot(cumret) plt.title('TU Momentum Strategy - Cumulative Returns') plt.xlabel('Days') plt.ylabel('Cumulative Return') plt.grid(True) plt.show() # Performance metrics strategy_returns = ret[idx:] avg_ann_ret = 252 * smartmean(strategy_returns) ann_volatility = np.sqrt(252) * smartstd(strategy_returns) sharpe_ratio = avg_ann_ret / ann_volatility if ann_volatility != 0 else 0 apr = np.prod(1 + strategy_returns) ** (252 / len(strategy_returns)) - 1 maxDD, maxDDD = calculateMaxDD(cumret) kelly_f = np.mean(strategy_returns) / np.var(strategy_returns) if np.var(strategy_returns) != 0 else 0 print(f"\nPerformance Metrics:") print(f"Average Annual Return: {avg_ann_ret:7.4f}") print(f"Annual Volatility: {ann_volatility:7.4f}") print(f"Sharpe Ratio: {sharpe_ratio:4.2f}") print(f"APR: {apr:10.4f}") print(f"Max Drawdown: {maxDD:.6f}") print(f"Max Drawdown Duration: {int(maxDDD)} days") print(f"Kelly f: {kelly_f:.6f}") if __name__ == "__main__": main()