Overall Statistics
Total Orders
2300
Average Win
0.09%
Average Loss
-0.08%
Compounding Annual Return
3.336%
Drawdown
22.900%
Expectancy
0.043
Start Equity
1000000
End Equity
1178440.85
Net Profit
17.844%
Sharpe Ratio
-0.088
Sortino Ratio
-0.11
Probabilistic Sharpe Ratio
2.596%
Loss Rate
52%
Win Rate
48%
Profit-Loss Ratio
1.17
Alpha
-0.053
Beta
0.654
Annual Standard Deviation
0.104
Annual Variance
0.011
Information Ratio
-1.123
Tracking Error
0.068
Treynor Ratio
-0.014
Total Fees
$2292.00
Estimated Strategy Capacity
$35000000.00
Lowest Capacity Asset
CYBR VU6BPZ7ECBQD
Portfolio Turnover
1.23%
Drawdown Recovery
1037
# region imports
from AlgorithmImports import *
# endregion


class WellDressedSkyBlueSardine(QCAlgorithm):

    def initialize(self):
        self.set_start_date(self.end_date - timedelta(5*365))
        self.set_cash(1_000_000)
        self.settings.automatic_indicator_warm_up = True
        self.settings.seed_initial_prices = True
        # Define some parameters.
        self._percentage_buy = 0.01
        # Add a universe of US Equities.
        self.universe_settings.resolution = Resolution.DAILY
        self._universe = self.add_universe(self._select_assets)
        # Add a Scheduled Event to rebalance the portfolio.
        self.schedule.on(self.date_rules.every_day('SPY'), self.time_rules.at(8, 0), self._rebalance)

    def _select_assets(self, fundamentals):
        # Select the most liquid stocks that are trading above $10.
        filtered = [f for f in fundamentals if f.price > 10 and f.has_fundamental_data]
        filtered = sorted(filtered, key=lambda x: x.dollar_volume)[-1000:]
        # Further filter the stocks to those with a market cap above $10 billion and take the smallest 500.
        filtered = sorted([f for f in filtered if f.market_cap > 10e9], key=lambda f: f.market_cap)[:500]
        return [f.symbol for f in filtered]

    def on_securities_changed(self, changes):
        # As assets enter the universe, add indicators to them.
        for security in changes.added_securities:
            security.ema_200 = self.ema(security, 200)
            security.ema_50 = self.ema(security, 50)
            security.roc_125 = self.roc(security, 125)
            security.roc_20 = self.roc(security, 20)
            security.rsi_14 = self.rsi(security, 14, MovingAverageType.SIMPLE)
            security.sma_200 = self.sma(security, 200)
            security.slow_stoch_5 = self.sto(security, 5)
            security.ppo = self.ppo(security, 12, 26, MovingAverageType.EXPONENTIAL)
            # Populate the PPO indicator with historical data to ensure it's ready for use.
            security.ppo.window[2]
            for bar in self.history[TradeBar](security, security.ppo.samples + security.ppo.window.size):
                security.ppo.update(bar.end_time, bar.close)
        # As assets leave the universe, liquidate them.
        for security in changes.removed_securities:
            self.liquidate(security)

    def _rebalance(self):
        # Calculate the SCTR score for each security in the universe.
        sctr_by_security = {}
        for symbol in self._universe.selected:
            security = self.securities[symbol]
            if not security.sma_200.is_ready: 
                continue
            sctr_by_security[security] = (
                security.ema_200.current.value * 0.30
                + security.ema_50.current.value * 0.15
                + security.roc_125.current.value * 0.30
                + security.roc_20.current.value * 0.15
                + (security.ppo.window[0].value - security.ppo.window[2].value) / (3 * security.ppo.window[2].value) * 0.05
                + security.rsi_14.current.value * 0.05
            )
        # Select the top 15% of securities based on their SCTR score.
        sctr_universe = sorted(
            sctr_by_security, 
            key=lambda security: sctr_by_security[security]
        )[-int(len(sctr_by_security)*0.15):]
        # Scan for trade entries.
        for security in sctr_universe:
            if security.invested: 
                continue
            if self.portfolio.margin_remaining <= 0.9 * self._percentage_buy * self.portfolio.total_portfolio_value: 
                continue
            if security.close > security.sma_200.current.value and security.slow_stoch_5.current.value <= 30:
                self.set_holdings(security, self._percentage_buy)
        # Liquidate any securities that are no longer in the SCTR universe.
        for symbol in self._universe.selected:
            security = self.securities[symbol]
            if security not in sctr_universe:
                self.liquidate(security)