Overall Statistics
Total Orders
506
Average Win
3.88%
Average Loss
-3.85%
Compounding Annual Return
0.478%
Drawdown
55.800%
Expectancy
0.029
Start Equity
100000
End Equity
102412.38
Net Profit
2.412%
Sharpe Ratio
0.016
Sortino Ratio
0.017
Probabilistic Sharpe Ratio
1.120%
Loss Rate
49%
Win Rate
51%
Profit-Loss Ratio
1.01
Alpha
-0.043
Beta
0.674
Annual Standard Deviation
0.277
Annual Variance
0.077
Information Ratio
-0.247
Tracking Error
0.264
Treynor Ratio
0.006
Total Fees
$3553.16
Estimated Strategy Capacity
$390000000.00
Lowest Capacity Asset
MU R735QTJ8XC9X
Portfolio Turnover
25.67%
Drawdown Recovery
453
from AlgorithmImports import *


class BBandsAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(self.end_date - timedelta(5*365))
        self.set_cash(100_000)
        self.settings.automatic_indicator_warm_up = True
        self.settings.free_portfolio_value_percentage = 0.05
        # Define some parameters.
        self._universe_size = self.get_parameter("selected", 20)
        self._bb_std = self.get_parameter("stdBB", 4)
        self._bb_period = self.get_parameter("BB", 15)
        self._roc_period = self.get_parameter("ROC", 79)
        # Add a universe of US Equities.
        self.universe_settings.resolution = Resolution.DAILY
        self._universe = self.add_universe(self.universe.etf('SPY', universe_filter_func=self._select_assets))
        # Add some alternative investments.
        self._uup, self._tlt, self._gld = [
            self.add_equity(ticker, Resolution.DAILY) 
            for ticker in ['UUP', 'TLT', 'GLD']
        ]
    
    def _select_assets(self, constituents: List[ETFConstituentData]) -> List[Symbol]:
        # Select the largets stocks in the ETF.
        filtered = sorted([c for c in constituents if c.weight], key=lambda c: c.weight)[-self._universe_size:]
        return [f.symbol for f in filtered]

    def on_securities_changed(self, changes: SecurityChanges) -> None:
        # As assets enter the universe, add some indicators for them.
        for security in changes.added_securities:
            security.bb = self.bb(security, self._bb_period, self._bb_std)
            security.roc = self.roc(security, self._roc_period)
            security.last_trade_time = None
        for security in changes.removed_securities:
            self.liquidate(security)
            self.deregister_indicator(security.bb)
            self.deregister_indicator(security.roc)     

    def on_data(self, data: Slice) -> None:
        if not data.bars or not self._universe.selected:
            return
        # Get the ROC value for all assets in the universe.
        roc_by_security = {}
        for symbol in self._universe.selected:
            security = self.securities[symbol]
            if security.roc.is_ready and security.roc.current.value > 0:
                roc_by_security[security] = security.roc.current.value
        # If no assets in the universe have a positive ROC, resort to 
        # one of the alternative investements.
        if not roc_by_security:
            # Decide which alternative to buy based on their ROC values.
            roc_uup = self._uup.roc.current.value
            roc_tlt = self._tlt.roc.current.value
            if roc_uup > 0 or roc_tlt > 0:
                if roc_uup > roc_tlt:
                    self.set_holdings(self._uup, 1, True, tag="UUP ROC > 0")
                else:
                    self.set_holdings(self._tlt, 1, True, tag="TLT ROC > 0")
            else:
                # If both UUP and TLT have negative ROC, invest in GLD.
                self.set_holdings(self._gld, 1, True, tag="GLD ROC > 0")
            return
        # Scan for an entry: If the assets with the greatest ROC is in the buy zone of the BB, buy it.
        security = max(roc_by_security, key=lambda security: roc_by_security[security])
        if (not security.invested and 
            security.bb.middle_band.Current.Value < security.price < security.bb.upper_band.Current.Value):
            self.set_holdings(security, 1, True, tag='BUY CONDITIONS MET')
            security.last_trade_time = self.time
        # Scan for exits.
        for symbol in self._universe.selected:
            security = self.securities[symbol]
            if not security.invested:
                continue
            # Profit taking
            tag = ''
            if security.holdings.unrealized_profit_percent >= 0.05:
                tag = "5%+ PROFIT"
            # Stop Loss
            elif security.holdings.unrealized_profit_percent <= -0.05:
                tag = "STOP LOSS"
            # Time-based exit
            elif (self.time - security.last_trade_time).days >= 10:
                tag = "HELD FOR 10 DAYS"
            else:
                continue
            self.liquidate(security, tag)
            security.last_trade_time = None