import numpy as np import matplotlib.pyplot as plt import warnings warnings.filterwarnings('ignore') # Import our utility functions from .backshift import backshift from .smartMovingStd import smartMovingStd from .calculateReturns import calculateReturns from .calculateMaxDD import calculateMaxDD from .data_loader import load_futures_data def main(): """ Python implementation of the gapFutures_FSTX.m gap trading strategy. This strategy trades gaps in futures based on volatility thresholds. """ print("Gap Futures Trading Strategy (FSTX)") print("=" * 40) # Strategy parameters entry_zscore = 0.1 print(f"Strategy Parameters:") print(f"Entry Z-score threshold: {entry_zscore}") try: # Try to load real futures data (using any available futures data) print("Loading futures data...") # Try different futures symbols for symbol in ['CL', 'TU', 'VX', 'HG']: try: data = load_futures_data(symbol) cl = data['cl'][:, 0] # Use first contract # For gap analysis, we need OHLC data # Try to get from the data loader or create approximations if 'op' in data: op = data['op'][:, 0] else: # Approximate opening prices with small gaps op = cl * (1 + np.random.normal(0, 0.005, len(cl))) if 'hi' in data: hi = data['hi'][:, 0] else: # Approximate high prices hi = np.maximum(op, cl) * (1 + np.abs(np.random.normal(0, 0.01, len(cl)))) if 'lo' in data: lo = data['lo'][:, 0] else: # Approximate low prices lo = np.minimum(op, cl) * (1 - np.abs(np.random.normal(0, 0.01, len(cl)))) print(f"Loaded {symbol} futures data for {len(cl)} days") print(f"Price range: {np.min(cl):.2f} to {np.max(cl):.2f}") break except (FileNotFoundError, KeyError): continue else: raise FileNotFoundError("No futures data available") 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 = 1000 # Generate synthetic OHLC data for FSTX base_price = 100 returns = np.random.normal(0, 0.02, n_days) # Create price series cl = base_price * np.cumprod(1 + returns) # Create OHLC data with realistic relationships gap_factor = np.random.normal(1, 0.01, n_days) # Opening gaps op = cl * gap_factor # High and low based on intraday volatility intraday_vol = np.abs(np.random.normal(0, 0.01, n_days)) hi = np.maximum(op, cl) * (1 + intraday_vol) lo = np.minimum(op, cl) * (1 - intraday_vol) print(f"Generated synthetic data for {n_days} days") print(f"Price range: {np.min(cl):.2f} to {np.max(cl):.2f}") # Calculate 90-day moving standard deviation of close-to-close returns c2c_returns = calculateReturns(cl, 1) stdret_c2c_90d = backshift(1, smartMovingStd(c2c_returns, 90)) # Generate trading signals based on gaps # Long signal: opening price is significantly above previous high longs = op >= backshift(1, hi) * (1 + entry_zscore * stdret_c2c_90d) # Short signal: opening price is significantly below previous low shorts = op <= backshift(1, lo) * (1 - entry_zscore * stdret_c2c_90d) # Initialize positions positions = np.zeros_like(cl) positions[longs] = 1 # Long position positions[shorts] = -1 # Short position # Calculate returns (gap fade strategy - profit from gap closure) # Return is from open to close, expecting gaps to fade with np.errstate(divide='ignore', invalid='ignore'): ret = positions * (op - cl) / op ret = np.nan_to_num(ret, nan=0) # Remove any infinite or NaN values ret = ret[np.isfinite(ret)] # Calculate performance metrics if len(ret) > 0: apr = np.prod(1 + ret) ** (252 / len(ret)) - 1 sharpe = np.mean(ret) * np.sqrt(252) / np.std(ret) if np.std(ret) > 0 else 0 print(f"\nFSTX Performance:") print(f"APR: {apr:10.4f}") print(f"Sharpe: {sharpe:4.2f}") # Calculate cumulative returns cumret = np.cumprod(1 + ret) - 1 # Plot results plt.figure(figsize=(12, 6)) plt.plot(cumret) plt.title('Gap Futures Strategy (FSTX) - Cumulative Returns') plt.xlabel('Days') plt.ylabel('Cumulative Return') plt.grid(True) plt.show() # Calculate maximum drawdown maxDD, maxDDD = calculateMaxDD(cumret) print(f"Max Drawdown: {maxDD:.6f}") print(f"Max Drawdown Duration: {int(maxDDD)} days") # Trading statistics num_trades = np.sum(positions != 0) num_long_trades = np.sum(positions > 0) num_short_trades = np.sum(positions < 0) print(f"\nTrading Statistics:") print(f"Total trades: {num_trades}") print(f"Long trades: {num_long_trades}") print(f"Short trades: {num_short_trades}") if num_trades > 0: win_rate = np.sum(ret > 0) / len(ret[ret != 0]) if len(ret[ret != 0]) > 0 else 0 print(f"Win rate: {win_rate:.2%}") return { 'returns': ret, 'cumulative_returns': cumret, 'apr': apr, 'sharpe': sharpe, 'max_drawdown': maxDD, 'num_trades': num_trades } else: print("No valid returns calculated") return None if __name__ == "__main__": main()