Overall Statistics
Total Orders
2412
Average Win
0.80%
Average Loss
-1.04%
Compounding Annual Return
1.528%
Drawdown
76.800%
Expectancy
0.039
Start Equity
10000000
End Equity
11110901.04
Net Profit
11.109%
Sharpe Ratio
0.167
Sortino Ratio
0.185
Probabilistic Sharpe Ratio
0.685%
Loss Rate
41%
Win Rate
59%
Profit-Loss Ratio
0.77
Alpha
0.015
Beta
0.602
Annual Standard Deviation
0.394
Annual Variance
0.155
Information Ratio
-0.048
Tracking Error
0.387
Treynor Ratio
0.109
Total Fees
$858586.37
Estimated Strategy Capacity
$9300000.00
Lowest Capacity Asset
NOVA X6G7QZUBDY05
Portfolio Turnover
8.32%
# region imports
from AlgorithmImports import *

import itertools
# endregion

class VirtualYellowGreenLlama(QCAlgorithm):

    _blocked_assets = [
        'AMMA WEHWVFZT6VZ9', # Halted for several months in 2020, causes trading issues. See https://finance.yahoo.com/news/nasdaq-halts-scworx-corp-135720862.html and  https://www.stocktitan.net/news/WORX/sc-worx-to-resume-trading-on-nasdaq-monday-august-10-doocrwwrw9f6.html
        'PAY T8834TOLIRFP',  # https://www.quantconnect.com/datasets/issue/16989
        'HCP R735QTJ8XC9X',  # https://www.quantconnect.com/datasets/issue/17042
        'TOPT T0KDYN9C3IHX', # https://www.quantconnect.com/datasets/issue/15180
        'VSCI R735QTJ8XC9X'  # https://www.quantconnect.com/datasets/issue/18448
    ]

    def initialize(self):
        self.set_start_date(2018, 1, 1)
        self.set_cash(10_000_000)
        self.settings.automatic_indicator_warm_up = True
        self._universe_size = 1_000
        self._beta_period = self.get_parameter('beta_period', 6) * 21
        self._assets_per_industry = self.get_parameter('assets_per_industry', 3)
        self._spy = self.add_equity('SPY', Resolution.DAILY)
        self.universe_settings.resolution = Resolution.DAILY
        self.universe_settings.schedule.on(self.date_rules.week_start(self._spy.symbol))
        self._universe = self.add_universe(self._select_assets)
        self.schedule.on(self.date_rules.week_start(self._spy.symbol), self.time_rules.at(0, 1), self._rebalance)

    def _select_assets(self, fundamentals):
        #return [f.symbol for f in fundamentals if f.asset_classification.morningstar_industry_code and str(f.symbol.id) not in self._blocked_assets]
        # Narrow the universe to the most liquid for now and remove blocked assets.
        return [f.symbol for f in sorted([f for f in fundamentals if f.asset_classification.morningstar_industry_code and str(f.symbol.id) not in self._blocked_assets], key=lambda f: f.dollar_volume)[-self._universe_size:]]
        
    def on_securities_changed(self, changes):
        # Create a Beta indicator for each asset that enters the universe.
        for security in changes.added_securities:
            security.beta = self.b(security.symbol, self._spy.symbol, self._beta_period)
        
    def _rebalance(self):
        # Select securities with negative beta and sort them by their beta values.
        securities = [self.securities[symbol] for symbol in self._universe.selected]
        self.plot('Securities', 'Have a Price', len([s for s in securities if s.price]))
        self.plot('Securities', 'Beta is Ready', len([s for s in securities if s.beta.is_ready]))
        self.plot('Securities', 'Beta is Negative', len([s for s in securities if s.beta.current.value < 0]))
        securities = sorted([s for s in securities if s.price and s.beta.is_ready and s.beta.current.value < 0], key=lambda s: (s.fundamentals.asset_classification.morningstar_industry_code, s.beta.current.value))
        # Group assets by their industry.
        securities_by_industry = {
            industry_code: list(industry_securities)[:self._assets_per_industry] # Select the assets with the lowest beta values in this industry.
            for industry_code, industry_securities in itertools.groupby(securities, lambda s: s.fundamentals.asset_classification.morningstar_industry_code)
        }
        # Give an equal weight to each industry and give an equal weight to each asset in each industry.
        targets = []
        for industry_securities in securities_by_industry.values():
            for industry_security in industry_securities:
                targets.append(PortfolioTarget(industry_security.symbol, 1/len(securities_by_industry)/len(industry_securities)))
        # Rebalance the portfolio. 
        self.set_holdings(targets, True)