Overall Statistics |
Total Orders 1 Average Win 0% Average Loss 0% Compounding Annual Return 47.241% Drawdown 28.100% Expectancy 0 Start Equity 100000 End Equity 147344.98 Net Profit 47.345% Sharpe Ratio 1.227 Sortino Ratio 1.168 Probabilistic Sharpe Ratio 53.062% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha -0.003 Beta 0.98 Annual Standard Deviation 0.291 Annual Variance 0.085 Information Ratio -0.569 Tracking Error 0.018 Treynor Ratio 0.365 Total Fees $2.38 Estimated Strategy Capacity $260000000.00 Lowest Capacity Asset QQQ RIWIV7K5Z9LX Portfolio Turnover 0.27% |
# region imports from AlgorithmImports import * # endregion from datetime import datetime, timedelta import xml.etree.ElementTree as ET from System.Net import WebRequest from QuantConnect.Data import BaseData class FredM2Data(BaseData): """Custom data class for importing M2 Money Supply data from FRED""" def __init__(self): self.Symbol = None self.Value = 0 self.Time = datetime.min def GetSource(self, config, date, isLiveMode): """Return the URL for downloading the data""" # Replace YOUR_API_KEY with your actual FRED API key api_key = "f156cff53e458a5f18a8f421ebb944c1" # Build the FRED API endpoint URL # M2SL is the series ID for M2 Money Stock, Seasonally Adjusted url = f"https://api.stlouisfed.org/fred/series/observations?series_id=M2SL&api_key={api_key}&file_type=xml" # For backtesting, we want to get all historical data at once to avoid multiple API calls return url def Reader(self, config, line, date, isLiveMode): """Parse the XML data from FRED""" if line == "" or line is None: return None # Create a new instance data = FredM2Data() data.Symbol = config.Symbol try: # Parse the XML string root = ET.fromstring(line) # Find the most recent observation before our requested date observations = root.findall(".//observation") closest_observation = None for observation in observations: obs_date = datetime.strptime(observation.get('date'), "%Y-%m-%d") # Skip future dates if obs_date > date: continue # If this is the first valid observation or it's more recent than our current closest if closest_observation is None or obs_date > closest_observation[0]: closest_observation = (obs_date, observation) if closest_observation is not None: value_str = closest_observation[1].get('value') # Check if the value is available (not "." which FRED uses for missing values) if value_str != ".": # Convert value to float (it's in billions of dollars) data.Value = float(value_str) data.Time = closest_observation[0] return data return None except Exception as e: # Log the error and return None for unsuccessful parsing print(f"Error parsing FRED M2 data: {e}") return None
# region imports from AlgorithmImports import * from datetime import datetime, timedelta import json from System.Net import WebRequest #from FredM2Data import FredM2Data # endregion class M2AdjustedNasdaqAlgorithm(QCAlgorithm): def Initialize(self): self.SetStartDate(2020, 1, 1) self.SetEndDate(2020, 12, 31) self.SetCash(100000) # Add Nasdaq index data (using QQQ as a proxy) self.nasdaq = self.AddEquity("QQQ", Resolution.Daily).Symbol # Add custom M2 data from FRED self.m2 = self.add_data(FredM2Data, "M2SL").Symbol # Set benchmark to Nasdaq self.SetBenchmark(self.nasdaq) # Dictionary to store M2 data for each month self.m2_data = {} # Store initial values for normalization self.initial_nasdaq = None self.initial_m2 = None # Dictionary to store calculated values self.m2_adjusted_values = {} # Flag to track if we've done initial calculations self.initialized = False # Schedule monthly calculations self.Schedule.On(self.DateRules.MonthStart(), self.TimeRules.At(9, 30), self.CalculateM2AdjustedIndex) self.m2_counter = 0 # Add a counter def OnData(self, data): """OnData event is the primary entry point for your algorithm.""" if self.m2 in data: self.m2_counter += 1 m2_value = data[self.m2].Value if m2_value > 0: # Make sure we have valid data month_key = f"{self.Time.year}-{self.Time.month}" self.m2_data[month_key] = m2_value self.Debug(f"M2 data received for {month_key}: {m2_value} billion (Count: {self.m2_counter})") # Initialize values once we have both Nasdaq and M2 data if not self.initialized and self.nasdaq in data and len(self.m2_data) > 0: self.initial_nasdaq = data[self.nasdaq].Price # Use the last available M2 data point as the baseline self.initial_m2 = list(self.m2_data.values())[-1] self.initialized = True self.Log(f"Initial values set - Nasdaq: {self.initial_nasdaq}, M2: {self.initial_m2} billion") # Only trade if we're initialized if self.initialized and not self.Portfolio.Invested: self.SetHoldings(self.nasdaq, 1.0) def CalculateM2AdjustedIndex(self): """Calculate the M2-adjusted Nasdaq index value on a monthly basis""" if not self.initialized: return # Get latest data nasdaq_price = self.Securities[self.nasdaq].Price # Get current month M2 data month_key = f"{self.Time.year}-{self.Time.month}" if month_key not in self.m2_data: # Use the most recent available M2 data if len(self.m2_data) > 0: m2_value = list(self.m2_data.values())[-1] else: return # No M2 data available yet else: m2_value = self.m2_data[month_key] # Calculate M2-adjusted Nasdaq index # Formula: (Current Nasdaq / Initial Nasdaq) / (Current M2 / Initial M2) * Initial Nasdaq # This effectively deflates the Nasdaq by the M2 money supply growth m2_ratio = self.initial_m2 / m2_value adjusted_nasdaq = nasdaq_price * m2_ratio # Store the calculated value self.m2_adjusted_values[self.Time] = adjusted_nasdaq # Update chart #self.Plot("Nasdaq", "Nasdaq", nasdaq_price) self.Plot("M2 Money Supply (Billions)", "M2 Money Supply (Billions)", m2_value) self.Plot("M2-Adjusted Nasdaq", "M2-Adjusted Nasdaq", adjusted_nasdaq) # Log values self.Log(f"Date: {self.Time}, Nasdaq: {nasdaq_price}, M2: {m2_value}, M2-Adjusted Nasdaq: {adjusted_nasdaq}") # Show the inflation-adjusted performance comparison real_performance = (adjusted_nasdaq / self.initial_nasdaq - 1) * 100 nominal_performance = (nasdaq_price / self.initial_nasdaq - 1) * 100 monetary_effect = nominal_performance - real_performance self.Log(f"Nasdaq nominal growth: {nominal_performance:.2f}%, M2-adjusted growth: {real_performance:.2f}%") self.Log(f"Monetary expansion effect: {monetary_effect:.2f}%") class FredM2Data(PythonData): """Custom data class for importing M2 Money Supply data from FRED using JSON""" def __init__(self): self.Value = 0 self.Time = datetime.min # Dictionary to cache parsed observation data self.observations = {} def get_source(self, config: SubscriptionDataConfig, date: datetime, is_live: bool) -> SubscriptionDataSource: """Return the URL for downloading the JSON data from FRED""" # Replace YOUR_API_KEY with your actual FRED API key api_key = "f156cff53e458a5f18a8f421ebb944c1" # Append file_type=json to receive JSON data url = f"https://api.stlouisfed.org/fred/series/observations?series_id=M2SL&api_key={api_key}&file_type=json" return SubscriptionDataSource(url, SubscriptionTransportMedium.REMOTE_FILE) def Reader(self, config, line, date, isLiveMode): """Parse the JSON data from FRED and return data for the specific date""" if line == "" or line is None: return None try: # If we haven't parsed the JSON data yet if not self.observations: # Parse the JSON string data_dict = json.loads(line) # Extract all observations and store in a dictionary by date for observation in data_dict.get('observations', []): obs_date_str = observation.get('date') value_str = observation.get('value') # Skip missing values if value_str == ".": continue obs_date = datetime.strptime(obs_date_str, "%Y-%m-%d") # Convert to QuantConnect DateTime format qc_date = DateTime(obs_date.year, obs_date.month, obs_date.day) # Store the observation self.observations[qc_date] = float(value_str) # Log how many observations were parsed print(f"Parsed {len(self.observations)} FRED M2 observations from JSON") # Create data points for all observations that match our date # Convert Python datetime to QuantConnect DateTime qc_date = DateTime(date.year, date.month, date.day) # For daily resolution, we need to forward-fill data # Find the most recent observation on or before the requested date valid_dates = [d for d in self.observations.keys() if d <= qc_date] if not valid_dates: return None # Get the most recent date most_recent_date = max(valid_dates) value = self.observations[most_recent_date] # Create data point data = FredM2Data() data.Symbol = config.Symbol data.Time = date data.Value = value return data except Exception as e: # Log the error and return None for unsuccessful parsing print(f"Error parsing FRED M2 JSON data: {e}") return None def Clone(self, fillForward): """Clone method required by QuantConnect""" return self.MemberwiseClone()