Overall Statistics
Total Orders
1418
Average Win
0.22%
Average Loss
-0.16%
Compounding Annual Return
14.854%
Drawdown
28.600%
Expectancy
0.591
Start Equity
100000
End Equity
199909.02
Net Profit
99.909%
Sharpe Ratio
0.501
Sortino Ratio
0.611
Probabilistic Sharpe Ratio
21.691%
Loss Rate
34%
Win Rate
66%
Profit-Loss Ratio
1.39
Alpha
0.006
Beta
0.993
Annual Standard Deviation
0.15
Annual Variance
0.023
Information Ratio
0.096
Tracking Error
0.055
Treynor Ratio
0.076
Total Fees
$1434.88
Estimated Strategy Capacity
$300000000.00
Lowest Capacity Asset
GART R735QTJ8XC9X
Portfolio Turnover
1.10%
Drawdown Recovery
784
from AlgorithmImports import *


class FundamentalFactorAlphaModel(AlphaModel):
    
    def __init__(self, algorithm, date_rule):
        self._algorithm = algorithm
        self._equities_by_sector = {}
        # Update the insights at the start of each quarter.
        self._insights = []
        algorithm.schedule.on(date_rule, algorithm.time_rules.at(8, 0), self._create_insights)

    def _create_insights(self):
        # Update insights at the start of each quarter.
        if self._algorithm.time.month not in [1, 4, 7, 10]: 
            return
        self._insights.clear()
        # Iterate through each sector.
        for equities in self._equities_by_sector.values():
            # Rank the assets by their fundamental factors.
            sorted_by_roe = sorted(equities, key=lambda x: x.fundamentals.operation_ratios.roe.value)
            sorted_by_net_margin = sorted(equities, key=lambda x: x.fundamentals.operation_ratios.net_margin.value)
            sorted_by_pe = sorted(equities, key=lambda x: x.fundamentals.valuation_ratios.pe_ratio, reverse=True)
            # Calculate a score for each asset.
            score_by_equity = {
                equity: sum([
                    sorted_by_roe.index(equity), 
                    sorted_by_net_margin.index(equity), 
                    sorted_by_pe.index(equity)
                ])
                for equity in equities
            }
            # Buy the 20% of stocks (minimum 1) in this sector that had the greatest scores.
            length = max(int(len(score_by_equity)/5), 1)
            for security in sorted(score_by_equity, key=lambda e: score_by_equity[e])[-length:]:
                self._insights.append(Insight.price(security, Expiry.END_OF_QUARTER, InsightDirection.UP))

    def update(self, algorithm, data):
        insights = self._insights.copy()
        self._insights.clear()
        return insights

    def on_securities_changed(self, algorithm, changes):
        # As assets enter the universe, add them to `self._equities_by_sector`.
        for security in changes.added_securities:
            sector = security.fundamentals.asset_classification.morningstar_sector_code
            self._equities_by_sector.setdefault(sector, set()).add(security)
        # As assets leave the universe, remove them from `self._equities_by_sector`.
        for security in changes.removed_securities:
            sector = security.fundamentals.asset_classification.morningstar_sector_code
            if security in self._equities_by_sector[sector]:
                self._equities_by_sector[sector].remove(security)
from alpha import FundamentalFactorAlphaModel
from AlgorithmImports import *


class VerticalTachyonRegulators(QCAlgorithm):

    def initialize(self):
        self.set_start_date(self.end_date - timedelta(5*365))
        self.set_cash(100_000)
        self.settings.seed_initial_prices = True

        # Universe selection
        self._sectors = [
            MorningstarSectorCode.FINANCIAL_SERVICES,
            MorningstarSectorCode.REAL_ESTATE,
            MorningstarSectorCode.HEALTHCARE,
            MorningstarSectorCode.UTILITIES,
            MorningstarSectorCode.TECHNOLOGY
        ]
        date_rule = self.date_rules.month_start('SPY')
        self.universe_settings.schedule.on(date_rule)
        self.universe_settings.resolution = Resolution.DAILY
        self.add_universe(self._select_assets)
        
        # Alpha Model
        self.add_alpha(FundamentalFactorAlphaModel(self, date_rule))

        # Portfolio construction model
        self.settings.rebalance_portfolio_on_security_changes = False
        self.set_portfolio_construction(EqualWeightingPortfolioConstructionModel(lambda time: None))
        
        # Risk model
        self.set_risk_management(NullRiskManagementModel())

        # Execution model
        self.set_execution(ImmediateExecutionModel())
        
        # Add a warm-up so that algorithm trades right away.
        self.set_warm_up(timedelta(120))

    def _select_assets(self, fundamentals):
        # Update the universe only at the start of each new quarter.
        if self.time.month not in [1, 4, 7, 10]: 
            return Universe.UNCHANGED
        # Select the most liquid stocks that have fundamental data and are trading above $5.
        selected = [f for f in fundamentals if f.has_fundamental_data and f.price > 5]
        selected = sorted(selected, key=lambda f: f.dollar_volume)[-500:]
        # Select the subset of stocks that IPO'd more than 5 years ago in the targeted sectors.
        return [
            f.symbol for f in selected 
            if (f.security_reference.ipo_date + timedelta(365*5) < self.time and
                f.asset_classification.MorningstarSectorCode in self._sectors and
                f.operation_ratios.roe.value > 0 and
                f.operation_ratios.net_margin.value > 0 and
                f.valuation_ratios.pe_ratio > 0)
        ]