Overall Statistics
Total Orders
937
Average Win
0.56%
Average Loss
-0.47%
Compounding Annual Return
30.207%
Drawdown
27.800%
Expectancy
0.647
Start Equity
100000
End Equity
478922.33
Net Profit
378.922%
Sharpe Ratio
0.957
Sortino Ratio
1.239
Probabilistic Sharpe Ratio
50.448%
Loss Rate
24%
Win Rate
76%
Profit-Loss Ratio
1.17
Alpha
0.131
Beta
0.709
Annual Standard Deviation
0.202
Annual Variance
0.041
Information Ratio
0.624
Tracking Error
0.168
Treynor Ratio
0.272
Total Fees
$1410.18
Estimated Strategy Capacity
$5600000.00
Lowest Capacity Asset
PHM R735QTJ8XC9X
Portfolio Turnover
1.39%
Drawdown Recovery
748
# region imports
from AlgorithmImports import *
# endregion

class SquareAsparagusHippopotamus(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2020, 1, 1)
        self.set_cash(100000)
        
        # Add universe selection for US equities
        self.add_universe(self.coarse_selection, self.fine_selection)
        
        # Rebalance monthly
        self.schedule.on(
            self.date_rules.month_start(),
            self.time_rules.after_market_open("SPY", 30),
            self.rebalance
        )
        
        self._selected_stocks = []
        self._rebalance_flag = False

    def coarse_selection(self, coarse):
        # Filter for liquid stocks with price > $5
        filtered = [x for x in coarse if x.has_fundamental_data and x.price > 5 and x.dollar_volume > 10000000]
        
        # Sort by dollar volume and take top 500
        sorted_by_volume = sorted(filtered, key=lambda x: x.dollar_volume, reverse=True)
        return [x.symbol for x in sorted_by_volume[:500]]

    def fine_selection(self, fine):
        # Select stocks with PE ratio < 10
        filtered = [x for x in fine if x.valuation_ratios.pe_ratio > 0 and x.valuation_ratios.pe_ratio < 10]
        
        # Take top 20 by market cap for diversification
        sorted_by_market_cap = sorted(filtered, key=lambda x: x.market_cap, reverse=True)
        self._selected_stocks = [x.symbol for x in sorted_by_market_cap[:20]]
        self._rebalance_flag = True
        
        return self._selected_stocks

    def rebalance(self):
        if not self._rebalance_flag or len(self._selected_stocks) == 0:
            return
        
        # Equal weight allocation
        weight = 1.0 / len(self._selected_stocks)
        
        # Liquidate stocks not in current selection
        for symbol in self.portfolio.keys():
            if symbol not in self._selected_stocks and self.portfolio[symbol].invested:
                self.liquidate(symbol)
        
        # Equal weight the selected stocks
        for symbol in self._selected_stocks:
            self.set_holdings(symbol, weight)
        
        self._rebalance_flag = False