Overall Statistics
Total Trades
546
Average Win
1.45%
Average Loss
-1.20%
Compounding Annual Return
14.886%
Drawdown
16.700%
Expectancy
0.267
Net Profit
129.150%
Sharpe Ratio
0.81
Sortino Ratio
0.737
Probabilistic Sharpe Ratio
46.142%
Loss Rate
42%
Win Rate
58%
Profit-Loss Ratio
1.20
Alpha
0.057
Beta
0.379
Annual Standard Deviation
0.105
Annual Variance
0.011
Information Ratio
0.076
Tracking Error
0.134
Treynor Ratio
0.224
Total Fees
$1213.12
Estimated Strategy Capacity
$960000000.00
Lowest Capacity Asset
SPY R735QTJ8XC9X
Portfolio Turnover
24.94%
# region imports
from AlgorithmImports import *
# endregion

from collections import defaultdict
from collections import deque
from QuantConnect.Securities.Equity import Equity

# reference from https://github.com/QuantConnect/Lean/blob/master/Algorithm.Python/CustomIndicatorAlgorithm.py#L31 for custom Indicator
# reference from https://www.investopedia.com/terms/a/advancedeclineline.asp for Advance/Decline (A/D) Line Logic

'''
A/D=Net Advances+{ 
PA, if PA value exists
0, if no PA value

where:
Net Advances=Difference between number of daily
ascending and declining stocks
PA=Previous Advances
Previous Advances=Prior indicator reading
'''
class AdvanceDeclineLine(PythonIndicator):
    def __init__(self, name: str = "ADL", period: int = 1) -> None:
        self.advance_decline_values = deque(maxlen=period)
        self.previous_close = {}
        self.previous_adline = None
        self.value = None
        super().__init__(name)
        
    
    def Update(self, slice):
            self.advance = 0
            self.decline = 0

            if len(self.advance_decline_values) > 0:
                    advance, decline = self.advance_decline_values[-1]
            
            for data in slice:
                if data.Key.Value == 'SPY':
                    continue
                if data.Key.Value in self.previous_close and self.previous_close[data.Key.Value] is not None:
                    if data.Value.Close > self.previous_close[data.Key.Value]:
                        self.advance += 1
                    elif data.Value.Close < self.previous_close[data.Key.Value]:
                        self.decline += 1
        
                self.previous_close[data.Key.Value] = data.Value.Close

            self.advance_decline_values.append((self.advance, self.decline))
            
            if self.previous_adline is not None:
                self.value = sum(adv - dec for adv, dec in self.advance_decline_values) + self.previous_adline
            
            self.previous_adline = sum(adv - dec for adv, dec in self.advance_decline_values)

    @property
    def IsReady(self):
        return bool(self.advance_decline_values) and self.value is not None

    def Reset(self) -> None:
        self.advance_decline_values.clear()
        super().Reset()

    def GetValue(self) -> float:
        return self.value

class DancingYellowGreenGorilla(QCAlgorithm):

    def Initialize(self) -> None:
        self.SetStartDate(2018, 1, 1)
        self.SetCash(100000)
        self.index = self.AddEquity("SPY", Resolution.Daily).Symbol
        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.Universe.ETF(self.index, self.UniverseSettings, self.ETFConstituentsFilter))
        self.adl_indicator = AdvanceDeclineLine(period=1)
        self.SetWarmup(10)

    def ETFConstituentsFilter(self, constituents: List[ETFConstituentData]) -> List[Symbol]:
        return [x.Symbol for x in constituents]


    def OnData(self, slice: Slice) -> None:
        self.Debug(f"Triggered Ondata : {self.Time}")
        self.adl_indicator.Update(slice)

        if self.adl_indicator.IsReady:
            ad_line_value = self.adl_indicator.GetValue()
            self.Plot("AD for SP500", "AD Line", ad_line_value)

            # simple trading logic
            if ad_line_value > 0 and not self.Portfolio.Invested:
                self.SetHoldings("SPY", 1.0)
            if ad_line_value < 0 and self.Portfolio.Invested:
                self.Liquidate()