Overall Statistics
Total Trades
1643
Average Win
0.12%
Average Loss
-0.24%
Compounding Annual Return
-14.163%
Drawdown
57.600%
Expectancy
-0.105
Net Profit
-21.022%
Sharpe Ratio
-0.215
Probabilistic Sharpe Ratio
3.424%
Loss Rate
40%
Win Rate
60%
Profit-Loss Ratio
0.50
Alpha
-0.075
Beta
0.859
Annual Standard Deviation
0.289
Annual Variance
0.083
Information Ratio
-0.31
Tracking Error
0.25
Treynor Ratio
-0.072
Total Fees
$1704.62
Estimated Strategy Capacity
$3400000.00
Lowest Capacity Asset
LNTH W1O47LFNB1PH
# region imports
from AlgorithmImports import *
# endregion

class PensiveSkyBlueBison(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2021, 5, 17)  # Set Start Date
        self.SetEndDate(2022, 12, 1)
        self.SetCash(100000)  # Set Strategy Cash

        self.factor_by_symbol = {}
        self.universe_symbols = []
        self.market_cap_threshold = int(self.GetParameter("market_cap_threshold"))
        self.universe_size = int(self.GetParameter("universe_size"))
        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.CoarseFilterFunction, self.FineFundamentalFunction)
        
        # 300-day warm
        self.SetWarmUp(300, Resolution.Daily)


    def CoarseFilterFunction(self, coarse):
        for item in coarse:
            symbol = item.Symbol
            if symbol not in self.factor_by_symbol:
                self.factor_by_symbol[symbol] = Factor()
            self.factor_by_symbol[symbol].Update(item.EndTime, item.AdjustedPrice)

        if self.IsWarmingUp:
            return Universe.Unchanged

        return [c.Symbol for c in coarse if c.HasFundamentalData]

    def FineFundamentalFunction(self, fine):
        selected = {}
        for item in fine:
            if item.MarketCap > self.market_cap_threshold and item.AssetClassification.MorningstarSectorCode == MorningstarSectorCode.Healthcare:
                symbol = item.Symbol
                factor = self.factor_by_symbol[symbol]
                if factor.IsReady and factor.ScaledDelta > 0:
                    selected[symbol] = factor
        self.universe_symbols = [kvp[0] for kvp in sorted(selected.items(), key = lambda kvp: kvp[1].ScaledDelta, reverse=True)[:self.universe_size]]
        return self.universe_symbols

    def OnData(self, slice):
        weight = 1 / len(self.universe_symbols)
        self.SetHoldings([PortfolioTarget(symbol, weight) for symbol in self.universe_symbols])

    def OnSecuritiesChanged(self, changes):
        for security in changes.RemovedSecurities:
            self.Liquidate(security.Symbol)
 

class Factor:
    def __init__(self):
        self.IsReady = False
        self.ScaledDelta = -999
        self.fast_ema = ExponentialMovingAverage(100)
        self.slow_ema = ExponentialMovingAverage(300)    

    def Update(self, time, value):
        self.IsReady = self.slow_ema.Update(time, value) & self.fast_ema.Update(time, value)
        if self.IsReady:
            fast = self.fast_ema.Current.Value
            slow = self.slow_ema.Current.Value
            self.ScaledDelta = (fast - slow) / ((fast + slow) / 2)
        return self.IsReady