Overall Statistics
Total Orders
1079
Average Win
0.11%
Average Loss
-0.18%
Compounding Annual Return
8.300%
Drawdown
31.400%
Expectancy
0.373
Start Equity
100000
End Equity
247184.55
Net Profit
147.185%
Sharpe Ratio
0.337
Sortino Ratio
0.333
Probabilistic Sharpe Ratio
3.870%
Loss Rate
16%
Win Rate
84%
Profit-Loss Ratio
0.63
Alpha
-0.017
Beta
0.695
Annual Standard Deviation
0.111
Annual Variance
0.012
Information Ratio
-0.663
Tracking Error
0.062
Treynor Ratio
0.054
Total Fees
$1078.40
Estimated Strategy Capacity
$2200000.00
Lowest Capacity Asset
WOOD U3RDKMG7QNHH
Portfolio Turnover
0.17%
Drawdown Recovery
637
#region imports
from AlgorithmImports import *
#endregion
from clr import AddReference
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Algorithm.Framework")

from QuantConnect import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Alphas import AlphaModel, Insight, InsightType, InsightDirection


class constantAlphaModel(AlphaModel):
    

    def __init__(self, type, direction, period, magnitude = None, confidence = None):
        
        self.type = type
        self.direction = direction
        self.period = period
        self.magnitude = magnitude
        self.confidence = confidence
        self.securities = []
        self.insightsTimeBySymbol = {}

        typeString = Extensions.GetEnumString(type, InsightType)
        directionString = Extensions.GetEnumString(direction, InsightDirection)

        self.Name = '{}({},{},{}'.format(self.__class__.__name__, typeString, directionString, strfdelta(period))
        if magnitude is not None:
            self.Name += ',{}'.format(magnitude)
        if confidence is not None:
            self.Name += ',{}'.format(confidence)

        self.Name += ')';


    def Update(self, algorithm, data):
        
        insights = []

        for security in self.securities:
            if security.Price != 0 and self.ShouldEmitInsight(algorithm.UtcTime, security.Symbol):
                insights.append(Insight(security.Symbol, self.period, self.type, self.direction, self.magnitude, self.confidence))

        return insights


    def OnSecuritiesChanged(self, algorithm, changes):
        for added in changes.AddedSecurities:
            self.securities.append(added)

        for removed in changes.RemovedSecurities:
            if removed in self.securities:
                self.securities.remove(removed)
            if removed.Symbol in self.insightsTimeBySymbol:
                self.insightsTimeBySymbol.pop(removed.Symbol)


    def ShouldEmitInsight(self, utcTime, symbol):

        generatedTimeUtc = self.insightsTimeBySymbol.get(symbol)

        if generatedTimeUtc is not None:
            if utcTime - generatedTimeUtc < self.period:
                return False

        self.insightsTimeBySymbol[symbol] = utcTime
        return True

def strfdelta(tdelta):
    d = tdelta.days
    h, rem = divmod(tdelta.seconds, 3600)
    m, s = divmod(rem, 60)
    return "{}.{:02d}:{:02d}:{:02d}".format(d,h,m,s)
#region imports
from AlgorithmImports import *
from Execution.ImmediateExecutionModel import ImmediateExecutionModel
from Portfolio.EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel
#endregion


"""
COMPREHENSIVE STRATEGY EXPLANATION

This strategy uses the QuantConnect Algorithm Framework. The model has five
framework components: universe selection, alpha generation, portfolio construction,
risk management, and execution.

The universe is a manually defined multi-asset ETF universe. It includes U.S.
equities, international equities, emerging markets, bonds, real estate, gold,
commodities, equity factors, sectors, and industry ETFs. The objective is to build
a broad ETF allocation model rather than a single-market equity strategy.

The alpha model is intentionally simple and pedagogical. It emits long-only Up
insights for every ETF in the selected universe once per month. This means the
model is not attempting to forecast relative returns. Instead, it expresses the
view that the selected ETF universe should be held as a diversified allocation.
Insights last for 35 days, which aligns with monthly portfolio refreshes and avoids
unnecessary daily re-emission of identical signals.

The portfolio construction model is QuantConnect's
EqualWeightingPortfolioConstructionModel. Active securities with live Up insights
are equally weighted. The strategy is long-only, does not short, and does not use
explicit leverage.

Risk management is handled by MaximumDrawdownPercentPortfolio. If the portfolio
drawdown breaches the configured threshold, the risk model can reduce risk. This
is a portfolio-level risk rule, not a security-selection rule.

Execution is handled by the ImmediateExecutionModel, which submits orders as soon
as portfolio targets are created.

The benchmark is a balanced reference portfolio: 60% SPY and 40% AGG. This is more
appropriate than 100% SPY because the strategy itself is a diversified multi-asset
ETF allocation that includes both equities and fixed income.

The model also includes enhanced visual diagnostics. In addition to plotting the
strategy equity curve and benchmark, it plots invested weight, cash weight, number
of active holdings, strategy drawdown, benchmark drawdown, and asset-class
exposures. These plots do not change the investment logic. They only make the
framework behavior easier to interpret.
"""


class MonthlyConstantAlphaModel(AlphaModel):

    def __init__(self, insight_duration_days=35):
        self.insight_duration = timedelta(days=insight_duration_days)
        self.symbols = []
        self.last_emit_month = None
        self.last_emission_date = None

    def Update(self, algorithm, data):

        insights = []
        current_month = (algorithm.Time.year, algorithm.Time.month)

        # Emit insights only once per month.
        if self.last_emit_month == current_month:
            return insights

        for symbol in self.symbols:

            if not algorithm.Securities.ContainsKey(symbol):
                continue

            security = algorithm.Securities[symbol]

            if not security.HasData:
                continue

            insights.append(
                Insight.Price(
                    symbol,
                    self.insight_duration,
                    InsightDirection.Up
                )
            )

        self.last_emit_month = current_month
        self.last_emission_date = algorithm.Time.date()

        return insights

    def OnSecuritiesChanged(self, algorithm, changes):

        for security in changes.AddedSecurities:

            if security.Symbol not in self.symbols:
                self.symbols.append(security.Symbol)

        for security in changes.RemovedSecurities:

            if security.Symbol in self.symbols:
                self.symbols.remove(security.Symbol)


class EnergeticSkyBlueFrog(QCAlgorithm):

    def Initialize(self):

        # ------------------------------------------------------------
        # 1. BACKTEST SETTINGS
        # ------------------------------------------------------------
        self.SetStartDate(2015, 1, 1)
        self.SetEndDate(2026, 5, 5)

        self.initial_cash = 100000
        self.SetCash(self.initial_cash)

        # ------------------------------------------------------------
        # 2. UNIVERSE SELECTION MODEL
        # ------------------------------------------------------------
        self.UniverseSettings.Resolution = Resolution.Daily

        tickers = [
            # Aggregate indices
            "SPY", "IEFA", "AGG", "IWM", "EEM", "EWJ", "EPP",

            # Fixed income and real estate
            "IYR", "LQD", "EMB", "IEF", "IEI",

            # Commodities
            "IAU", "GSG", "COMT",

            # Factors
            "USMV", "DGRO", "QUAL", "DVY", "MTUM", "VLUE",
            "EFAV", "EEMV", "IDV", "IQLT",

            # Sectors and industries
            "IBB", "IHI", "IYW", "IGF", "IYH", "IYF",
            "IXC", "PICK", "IYE", "KXI", "WOOD"
        ]

        symbols = [
            Symbol.Create(ticker, SecurityType.Equity, Market.USA)
            for ticker in tickers
        ]

        self.SetUniverseSelection(
            ManualUniverseSelectionModel(symbols)
        )

        # ------------------------------------------------------------
        # 3. ALGORITHM FRAMEWORK MODELS
        # ------------------------------------------------------------

        # Alpha model:
        # Monthly long-only insights for all ETFs in the manual universe.
        self.alpha_model = MonthlyConstantAlphaModel(
            insight_duration_days=35
        )

        self.AddAlpha(self.alpha_model)

        # Portfolio construction:
        # Equal-weight all ETFs with active Up insights.
        self.SetPortfolioConstruction(
            EqualWeightingPortfolioConstructionModel()
        )

        # Risk management:
        # Portfolio-level drawdown control.
        self.SetRiskManagement(
            MaximumDrawdownPercentPortfolio(0.05)
        )

        # Execution:
        # Immediate market execution for generated targets.
        self.SetExecution(
            ImmediateExecutionModel()
        )

        # ------------------------------------------------------------
        # 4. BENCHMARK
        # ------------------------------------------------------------
        self._benchmark_spy = self.AddEquity(
            "SPY",
            Resolution.Daily,
            Market.USA
        ).Symbol

        self._benchmark_agg = self.AddEquity(
            "AGG",
            Resolution.Daily,
            Market.USA
        ).Symbol

        self.SetBenchmark(self._benchmark_spy)

        self.initial_spy_price = None
        self.initial_agg_price = None

        self.benchmark_spy_weight = 0.60
        self.benchmark_agg_weight = 0.40

        # ------------------------------------------------------------
        # 5. DIAGNOSTIC STATE VARIABLES
        # ------------------------------------------------------------
        self.strategy_peak = self.initial_cash
        self.benchmark_peak = self.initial_cash

        # ------------------------------------------------------------
        # 6. ASSET CLASS GROUPS FOR VISUALIZATION
        # ------------------------------------------------------------
        self.equity_tickers = [
            "SPY", "IEFA", "IWM", "EEM", "EWJ", "EPP",
            "USMV", "DGRO", "QUAL", "DVY", "MTUM", "VLUE",
            "EFAV", "EEMV", "IDV", "IQLT",
            "IBB", "IHI", "IYW", "IGF", "IYH", "IYF",
            "IXC", "PICK", "IYE", "KXI", "WOOD"
        ]

        self.bond_tickers = [
            "AGG", "LQD", "EMB", "IEF", "IEI"
        ]

        self.commodity_tickers = [
            "IAU", "GSG", "COMT"
        ]

        self.real_estate_tickers = [
            "IYR"
        ]

    def OnData(self, data):

        # ------------------------------------------------------------
        # 1. CHECK BENCHMARK DATA
        # ------------------------------------------------------------
        if self._benchmark_spy not in data or data[self._benchmark_spy] is None:
            return

        if self._benchmark_agg not in data or data[self._benchmark_agg] is None:
            return

        spy_price = self.Securities[self._benchmark_spy].Price
        agg_price = self.Securities[self._benchmark_agg].Price

        if spy_price <= 0 or agg_price <= 0:
            return

        if self.initial_spy_price is None:
            self.initial_spy_price = spy_price

        if self.initial_agg_price is None:
            self.initial_agg_price = agg_price

        # ------------------------------------------------------------
        # 2. CALCULATE BENCHMARK VALUE
        # ------------------------------------------------------------
        benchmark_value = (
            self.initial_cash
            * (
                self.benchmark_spy_weight
                * spy_price
                / self.initial_spy_price
                +
                self.benchmark_agg_weight
                * agg_price
                / self.initial_agg_price
            )
        )

        # ------------------------------------------------------------
        # 3. PLOT STRATEGY EQUITY AND BENCHMARK
        # ------------------------------------------------------------
        self.Plot(
            "Strategy Equity",
            "Portfolio Value",
            self.Portfolio.TotalPortfolioValue
        )

        self.Plot(
            "Strategy Equity",
            "Benchmark 60 pct SPY 40 pct AGG",
            benchmark_value
        )

        # ------------------------------------------------------------
        # 4. PORTFOLIO STATE DIAGNOSTICS
        # ------------------------------------------------------------
        invested_value = 0
        active_holdings = 0

        for holding in self.Portfolio.Values:

            if holding.Invested:
                invested_value += abs(holding.HoldingsValue)
                active_holdings += 1

        if self.Portfolio.TotalPortfolioValue > 0:

            invested_weight = (
                invested_value
                / self.Portfolio.TotalPortfolioValue
            )

            cash_weight = 1 - invested_weight

            self.Plot(
                "Portfolio State",
                "Invested Weight",
                invested_weight
            )

            self.Plot(
                "Portfolio State",
                "Cash Weight",
                cash_weight
            )

            self.Plot(
                "Portfolio Diagnostics",
                "Active Holdings",
                active_holdings
            )

        # ------------------------------------------------------------
        # 5. STRATEGY AND BENCHMARK DRAWDOWN
        # ------------------------------------------------------------
        self.strategy_peak = max(
            self.strategy_peak,
            self.Portfolio.TotalPortfolioValue
        )

        self.benchmark_peak = max(
            self.benchmark_peak,
            benchmark_value
        )

        strategy_drawdown = (
            self.Portfolio.TotalPortfolioValue
            / self.strategy_peak
            - 1
        )

        benchmark_drawdown = (
            benchmark_value
            / self.benchmark_peak
            - 1
        )

        self.Plot(
            "Drawdown",
            "Strategy Drawdown",
            strategy_drawdown
        )

        self.Plot(
            "Drawdown",
            "Benchmark Drawdown",
            benchmark_drawdown
        )

        # ------------------------------------------------------------
        # 6. ASSET CLASS EXPOSURE DIAGNOSTICS
        # ------------------------------------------------------------
        equity_weight = 0
        bond_weight = 0
        commodity_weight = 0
        real_estate_weight = 0
        other_weight = 0

        if self.Portfolio.TotalPortfolioValue > 0:

            for holding in self.Portfolio.Values:

                if not holding.Invested:
                    continue

                symbol_value = holding.Symbol.Value

                weight = (
                    holding.HoldingsValue
                    / self.Portfolio.TotalPortfolioValue
                )

                if symbol_value in self.equity_tickers:
                    equity_weight += weight

                elif symbol_value in self.bond_tickers:
                    bond_weight += weight

                elif symbol_value in self.commodity_tickers:
                    commodity_weight += weight

                elif symbol_value in self.real_estate_tickers:
                    real_estate_weight += weight

                else:
                    other_weight += weight

        self.Plot(
            "Asset Class Exposure",
            "Equity",
            equity_weight
        )

        self.Plot(
            "Asset Class Exposure",
            "Bonds",
            bond_weight
        )

        self.Plot(
            "Asset Class Exposure",
            "Commodities",
            commodity_weight
        )

        self.Plot(
            "Asset Class Exposure",
            "Real Estate",
            real_estate_weight
        )

        if other_weight != 0:
            self.Plot(
                "Asset Class Exposure",
                "Other",
                other_weight
            )

        # ------------------------------------------------------------
        # 7. MONTHLY INSIGHT REFRESH MARKER
        # ------------------------------------------------------------
        insight_marker = 0

        if self.alpha_model.last_emission_date == self.Time.date():
            insight_marker = 1

        self.Plot(
            "Portfolio Diagnostics",
            "Monthly Insight Refresh",
            insight_marker
        )