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()