Overall Statistics
Total Orders
205
Average Win
2.00%
Average Loss
-2.41%
Compounding Annual Return
33.927%
Drawdown
27.200%
Expectancy
0.498
Start Equity
100000
End Equity
430406.74
Net Profit
330.407%
Sharpe Ratio
0.991
Sortino Ratio
1.268
Probabilistic Sharpe Ratio
56.785%
Loss Rate
18%
Win Rate
82%
Profit-Loss Ratio
0.83
Alpha
0.157
Beta
0.914
Annual Standard Deviation
0.21
Annual Variance
0.044
Information Ratio
0.922
Tracking Error
0.165
Treynor Ratio
0.227
Total Fees
$272.63
Estimated Strategy Capacity
$520000000.00
Lowest Capacity Asset
XLK RGRPZX100F39
Portfolio Turnover
1.66%
Drawdown Recovery
541
# region imports
from AlgorithmImports import *
# endregion


class TradingCompetitionAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_cash(100000)
        self.set_start_date(self.end_date - timedelta(5 * 365))
        self._securities = []
        self._security_by_ticker = {}
        for ticker in ["SPY", "QQQ", "XLK", "SMH", "NVDA", "MSFT", "AAPL", "LLY", "COST", "UNH", "GLD", "SHY"]:
            security = self.add_equity(ticker, Resolution.DAILY)
            security.mom_3m = self.roc(security, 63)
            security.mom_6m = self.roc(security, 126)
            security.mom_12m = self.roc(security, 252)
            security.sma_200 = self.sma(security, 200)
            self._securities.append(security)
            self._security_by_ticker[ticker] = security
        self.set_warm_up(260, Resolution.DAILY)
        self.schedule.on(self.date_rules.month_start("SPY"), self.time_rules.at(8, 0), self._rebalance)

    def on_warmup_finished(self) -> None:
        self._rebalance()

    def _rebalance(self) -> None:
        if self.is_warming_up:
            return
        # Score trend-confirmed names by a weighted blend of 3/6/12-month momentum.
        score_by_security = {}
        for security in self._securities:
            if not security.has_data or security.price <= 0:
                continue
            if not (security.mom_3m.is_ready and security.mom_6m.is_ready and security.mom_12m.is_ready and security.sma_200.is_ready):
                continue
            if security.price < security.sma_200.current.value and security.symbol.value != "SHY":
                continue
            score_by_security[security] = 0.45 * security.mom_3m.current.value + 0.35 * security.mom_6m.current.value + 0.20 * security.mom_12m.current.value
        # Take the three strongest positive momentum names, excluding the cash proxy.
        selected = []
        for security in sorted(score_by_security, key=lambda s: score_by_security[s], reverse=True):
            if score_by_security[security] > 0 and security.symbol.value != "SHY":
                selected.append(security)
            if len(selected) == 3:
                break
        # Fall back to a defensive SHY/GLD split when nothing qualifies.
        if not selected:
            selected = [self._security_by_ticker["SHY"], self._security_by_ticker["GLD"]]
        target_weight = 0.97 / len(selected)
        targets = [PortfolioTarget(security, target_weight if security in selected else 0) for security in self._securities]
        self.set_holdings(targets)