Overall Statistics
Total Orders
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Start Equity
100000
End Equity
100000
Net Profit
0%
Sharpe Ratio
0
Sortino Ratio
0
Probabilistic Sharpe Ratio
0%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0
Annual Variance
0
Information Ratio
-1.947
Tracking Error
0.104
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
Portfolio Turnover
0%
from AlgorithmImports import *

class MarketSentimentAlgorithm(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2024, 1, 1)
        self.SetEndDate(2024, 12, 31)
        self.SetCash(100000)
        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.CoarseSelectionFunction)
        
        # Sentiment variables
        self.ad_line = 0
        self.total_sentiment_score = 0
        
        # Dictionary to hold SMA indicators
        self.sma_indicators = {}
        
        # Log control
        self.max_logs_per_day = 50
        self.current_log_count = 0
        self.last_log_date = self.Time.date()
        
        # Initialize plots
        self.Plot("Sentiment", "Market Sentiment", 0)
        self.Plot("Indicators", "A/D Line", 0)
        self.Plot("Indicators", "A/D Ratio", 0)
        self.Plot("Indicators", "TRIN", 0)
        self.Plot("Indicators", "% Above MA", 0)
        self.Plot("Indicators", "Volume Breadth", 0)

        """
        Plot Descriptions:
        
        1. Sentiment - Market Sentiment:
        - **Description**: This plot represents the composite sentiment score derived from multiple market indicators, including the Advance/Decline Ratio (A/D Ratio), Percentage of Stocks Above 50-Day Moving Average (% Above MA), and the TRIN (Arms Index).
        
        - **Calculation**:
            - **A/D Ratio**: Calculated as the ratio of advancing stocks to declining stocks. A higher ratio indicates more stocks are advancing compared to declining.
            - **% Above MA**: Represents the percentage of stocks trading above their 50-day Simple Moving Average (SMA). A higher percentage suggests a majority of stocks are in an upward trend.
            - **TRIN**: The TRIN Index is calculated as the ratio of the A/D Ratio to the Volume Breadth (difference between advancing and declining volumes). It provides insights into market pressure and investor sentiment.
            - **Composite Sentiment Score**: Combines the normalized values of the above indicators using weighted factors (e.g., 0.4 for A/D Ratio, 0.4 for % Above MA, and 0.2 for TRIN) to produce a single sentiment score ranging between -1 and 1.
        
        - **Purpose**:
            - **Overall Market Sentiment**: Provides a unified measure of the market's bullish or bearish sentiment based on breadth indicators.
            - **Trend Identification**: Helps in identifying prevailing market trends and potential reversals by analyzing the combined signals from multiple indicators.
            - **Decision-Making**: Assists in making informed trading decisions by offering a consolidated view of market health and momentum.
        
        - **Interpretation**:
            - **Positive Sentiment Score**: Indicates a bullish market sentiment where the majority of stocks are advancing, and more volume is supporting upward movements.
            - **Negative Sentiment Score**: Reflects a bearish market sentiment with more declining stocks and higher volume on the downside.
            - **Neutral Range**: Scores around zero suggest a balanced market with no clear bullish or bearish dominance.
        
        - **Usage in Strategy**:
            - **Entry and Exit Signals**: Utilize the sentiment score to determine optimal points for entering or exiting positions based on prevailing market conditions.
            - **Risk Management**: Adjust position sizing or implement hedging strategies in response to significant shifts in market sentiment.
            - **Confirmation Tool**: Use the sentiment score in conjunction with other technical or fundamental indicators to validate trading signals.
        
        - **Visualization Enhancements**:
            - **Threshold Lines**: Incorporate horizontal lines at key sentiment score levels (e.g., -0.5, 0, +0.5) to quickly identify overbought or oversold conditions.
            - **Color Coding**: Apply color gradients to the sentiment score plot to visually distinguish between bullish (e.g., green), bearish (e.g., red), and neutral (e.g., gray) sentiments.
            - **Annotations**: Add markers or annotations for significant sentiment score changes to highlight potential trading opportunities or risks.
        
        - **Benefits**:
            - **Holistic View**: Combines multiple indicators into a single metric, simplifying the analysis of market conditions.
            - **Dynamic Adaptation**: Adjusts to changing market environments by reflecting real-time sentiment shifts.
            - **Enhanced Clarity**: Facilitates easier interpretation and quicker decision-making through a consolidated sentiment measure.

        2. Indicators - A/D Line:
           - **Description**: The Advance/Decline Line, a cumulative indicator that sums the difference between advancing and declining stocks.
           - **Purpose**: Measures the breadth of the market, showing the net number of advancing versus declining stocks over time.
           - **Usage**: An upward trend indicates increasing market strength, whereas a downward trend suggests weakening market conditions.
        
        3. Indicators - A/D Ratio:
           - **Description**: The ratio of advancing stocks to declining stocks.
           - **Purpose**: Provides a quick snapshot of market sentiment by comparing the number of advancing stocks against declining ones.
           - **Usage**: Ratios above 1 indicate more advancing stocks, signaling bullish sentiment. Ratios below 1 suggest bearish sentiment.
        
        4. Indicators - TRIN:
           - **Description**: The TRIN (Arms Index), calculated as the ratio of the A/D Ratio to the Volume Breadth.
           - **Purpose**: Combines market breadth and volume to assess overall market pressure and investor sentiment.
           - **Usage**: Values below 1 typically indicate bullish sentiment, while values above 1 suggest bearish sentiment.
        
        5. Indicators - % Above MA:
           - **Description**: The percentage of stocks trading above their 50-day Simple Moving Average (SMA).
           - **Purpose**: Measures the overall market trend by determining the proportion of stocks in an uptrend.
           - **Usage**: Higher percentages indicate a majority of stocks are in an uptrend, signaling bullish market conditions. Lower percentages suggest a bearish trend.
        
        6. Indicators - Volume Breadth:
           - **Description**: The difference between advancing and declining volumes.
           - **Purpose**: Assesses the strength behind market movements by comparing the volume of advancing stocks against declining ones.
           - **Usage**: Positive values indicate more volume in advancing stocks, reinforcing bullish sentiment. Negative values point to stronger volume in declining stocks, supporting bearish sentiment.
        """        
        
    def CoarseSelectionFunction(self, coarse):
        # Enhanced Filtering:
        # 1. Has Fundamental Data
        # 2. Security Type is Equity
        # 3. Dollar Volume > $1,000,000
        # 4. Symbol does not contain spaces
        filtered = [
            x for x in coarse 
            if x.HasFundamentalData 
            and x.Symbol.SecurityType == SecurityType.Equity
            and x.DollarVolume > 1e6
            and ' ' not in x.Symbol.Value
        ]
        
        # Select top 500 based on dollar volume
        selected = sorted(filtered, key=lambda x: x.DollarVolume, reverse=True)[:500]
        self.LogLimited(f"Coarse Selection: {len(selected)} equity symbols selected based on dollar volume.")
        
        # Initialize SMA for new symbols
        for coarse_fundamental in selected:
            symbol = coarse_fundamental.Symbol
            if symbol not in self.sma_indicators:
                try:
                    sma = self.SMA(symbol, 50, Resolution.Daily)
                    self.sma_indicators[symbol] = sma
                    self.RegisterIndicator(symbol, sma, Resolution.Daily)
                    self.LogLimited(f"SMA(50) initialized for {symbol}.")
                except Exception as e:
                    self.LogLimited(f"Error initializing SMA for {symbol}: {e}")
                    continue
        
        return [x.Symbol for x in selected]

    def OnSecuritiesChanged(self, changes):
        for security in changes.AddedSecurities:
            symbol = security.Symbol
            if symbol not in self.sma_indicators:
                try:
                    sma = self.SMA(symbol, 50, Resolution.Daily)
                    self.sma_indicators[symbol] = sma
                    self.RegisterIndicator(symbol, sma, Resolution.Daily)
                    self.LogLimited(f"SMA(50) initialized for newly added {symbol}.")
                except Exception as e:
                    self.LogLimited(f"Error initializing SMA for newly added {symbol}: {e}")
        
        for security in changes.RemovedSecurities:
            symbol = security.Symbol
            if symbol in self.sma_indicators:
                self.sma_indicators.pop(symbol)
                self.LogLimited(f"SMA(50) removed for {symbol}.")

    def OnData(self, data):
        if not data.Bars:
            return  # Skip if no data available
        
        advancing_stocks = 0
        declining_stocks = 0
        advancing_volume = 0
        declining_volume = 0
        total_stocks = len(data.Bars)
        
        # Loop through each symbol in the current data
        for symbol, bar in data.Bars.items():
            try:
                if bar.Close > bar.Open:
                    advancing_stocks += 1
                    advancing_volume += bar.Volume
                elif bar.Close < bar.Open:
                    declining_stocks += 1
                    declining_volume += bar.Volume
                # If bar.Close == bar.Open, neither advancing nor declining
            except Exception as e:
                self.LogLimited(f"Error processing symbol {symbol}: {e}")
                continue  # Skip to the next symbol
        
        self.LogLimited(f"Advancing Stocks: {advancing_stocks}, Declining Stocks: {declining_stocks}")
        self.LogLimited(f"Advancing Volume: {advancing_volume}, Declining Volume: {declining_volume}")
        
        # Calculate Indicators
        ad_ratio = (advancing_stocks / declining_stocks) if declining_stocks > 0 else float('inf')
        volume_breadth = advancing_volume - declining_volume
        percent_above_ma = self.CalculatePercentAboveMovingAverage(data, total_stocks)
        trin = ((advancing_stocks / declining_stocks) / (advancing_volume / declining_volume)) if declining_volume > 0 else 0
        
        self.LogLimited(f"A/D Ratio: {ad_ratio:.2f}, Volume Breadth: {volume_breadth}")
        self.LogLimited(f"Percent Above MA: {percent_above_ma:.2f}%, TRIN: {trin:.2f}")
        
        # Update A/D Line
        net_advances = advancing_stocks - declining_stocks
        self.ad_line += net_advances
        
        # Sentiment Score
        self.total_sentiment_score = self.CalculateSentimentScore(ad_ratio, percent_above_ma, trin)
        
        # Log the sentiment metrics
        self.LogLimited(f"Market Sentiment: {self.total_sentiment_score:.2f} | A/D Line: {self.ad_line} | A/D Ratio: {ad_ratio:.2f} | TRIN: {trin:.2f}")
        
        # Plotting
        self.Plot("Sentiment", "Market Sentiment", self.total_sentiment_score)
        self.Plot("Indicators", "A/D Line", self.ad_line)
        self.Plot("Indicators", "A/D Ratio", ad_ratio)
        self.Plot("Indicators", "TRIN", trin)
        self.Plot("Indicators", "% Above MA", percent_above_ma)
        self.Plot("Indicators", "Volume Breadth", volume_breadth)

    def CalculatePercentAboveMovingAverage(self, data, total_stocks):
        """Calculate the percentage of stocks above their 50-day moving average."""
        count_above_ma = 0
        for symbol, bar in data.Bars.items():
            if symbol in self.sma_indicators:
                sma = self.sma_indicators[symbol].Current.Value
                if sma is not None and self.Securities[symbol].Price > sma:
                    count_above_ma += 1
        percent_above_ma = (count_above_ma / total_stocks) * 100 if total_stocks > 0 else 0
        self.LogLimited(f"Count Above MA: {count_above_ma}, Total Stocks: {total_stocks}, Percent Above MA: {percent_above_ma:.2f}%")
        return percent_above_ma

    def CalculateSentimentScore(self, ad_ratio, percent_above_ma, trin):
        """Combine multiple indicators into a composite sentiment score."""
        # Normalize and weight indicators (example weights: 0.4, 0.4, 0.2)
        normalized_ad_ratio = min(max(ad_ratio / 2, -1), 1)  # Normalize between -1 and 1
        normalized_percent_above_ma = (percent_above_ma - 50) / 50  # Normalize 50% as neutral
        normalized_trin = min(max(1 - trin, -1), 1)  # Invert TRIN (lower is bullish)
        
        sentiment_score = (0.4 * normalized_ad_ratio +
                           0.4 * normalized_percent_above_ma +
                           0.2 * normalized_trin)
        self.LogLimited(f"Normalized A/D Ratio: {normalized_ad_ratio:.2f}, Normalized % Above MA: {normalized_percent_above_ma:.2f}, Normalized TRIN: {normalized_trin:.2f}")
        self.LogLimited(f"Composite Sentiment Score: {sentiment_score:.2f}")
        return sentiment_score

    def LogLimited(self, message):
        current_date = self.Time.date()
        if current_date != self.last_log_date:
            self.current_log_count = 0
            self.last_log_date = current_date
        if self.current_log_count < self.max_logs_per_day:
            self.Log(message)
            self.current_log_count += 1