Overall Statistics
Total Orders
898
Average Win
0.55%
Average Loss
-0.21%
Compounding Annual Return
31.414%
Drawdown
42.200%
Expectancy
1.498
Start Equity
100000
End Equity
384958.61
Net Profit
284.959%
Sharpe Ratio
0.875
Sortino Ratio
0.957
Probabilistic Sharpe Ratio
37.251%
Loss Rate
32%
Win Rate
68%
Profit-Loss Ratio
2.66
Alpha
0.106
Beta
1.23
Annual Standard Deviation
0.252
Annual Variance
0.063
Information Ratio
0.926
Tracking Error
0.137
Treynor Ratio
0.179
Total Fees
$1095.04
Estimated Strategy Capacity
$420000000.00
Lowest Capacity Asset
COST R735QTJ8XC9X
Portfolio Turnover
4.13%
# region imports
from AlgorithmImports import *
import math
# endregion

class DeterminedGreenKangaroo(QCAlgorithm):

    def initialize(self):

        # self.set_start_date(2007, 1, 2)
        # self.set_start_date(2008, 4, 2)
        # self.set_start_date(2009, 7, 2)
        self.set_start_date(2020, 1, 2)
        # self.set_start_date(2022, 1, 2)
        # self.set_start_date(2024, 1, 2)
        # self.set_start_date(2024, 10, 2)

        self.set_cash(100000)

        self.universe_settings.resolution = Resolution.Daily # Hour # 
        self._universe = self.add_universe(self.universe.dollar_volume.top(100))

        self.creamOnTop = 10
        # self.min_portfolio_size = 20
        self._portfolio = []

        self.vwap_size = 1
        self.ema_size = 63 # int(3 * 6.5) # 5, 21, 63, 252

        self.set_warmup(2 * self.ema_size)

        self.expected_value_threshold = 1 # 1 + (0.96 - 1) / (252 * 6.5)

        # self.settings.free_portfolio_value_percentage = 0.1
        # # https://www.quantconnect.com/docs/v2/writing-algorithms/trading-and-orders/position-sizing

        self.buy_spy = True
        self.spy_symbol = self.add_equity("SPY", Resolution.DAILY).symbol
        self.portfolio_target_len = self.creamOnTop

    def on_securities_changed(self, changes: SecurityChanges):

        for security in changes.added_securities:

            # indicator outline

            # rocp_of_vwap = rocp( vwap )
            # i.e. how fast the price is changing

            # ema_of_rocp = ema( rocp_of_vwap)
            # i.e. exponential moving average

            # ema_of_roc = ema( roc( rocp_of_vwap))
            # i.e. how fast the price is accelerating

            # ema_of_std = sqrt( ema( square( minus( rocp_of_vwap, ema_of_rocp ))))
            # i.e. exponential moving average of standard deviation of rocp_of_vwap

            vwap = self.vwap(security.symbol, self.vwap_size)

            rocp_of_vwap = IndicatorExtensions.of(
                RateOfChangePercent(1), vwap)

            security.ema_of_rocp = IndicatorExtensions.of(
                ExponentialMovingAverage(self.ema_size), rocp_of_vwap)

            roc_of_rocp = IndicatorExtensions.of(
                RateOfChange(1), rocp_of_vwap)

            # security.ema_of_roc = IndicatorExtensions.of(
            #     ExponentialMovingAverage(self.ema_size), roc_of_rocp)

            # security.ema_of_std = 0 # sqrt( ema( square( minus( rocp_of_vwap, ema_of_rocp ))))
            rocp_minus_ema = IndicatorExtensions.minus(rocp_of_vwap, security.ema_of_rocp)
            square_of_minus = IndicatorExtensions.times(rocp_minus_ema, rocp_minus_ema)
            ema_of_square = IndicatorExtensions.of(
                ExponentialMovingAverage(self.ema_size), square_of_minus)
            security.ema_of_variance = ema_of_square

            security.initialize = True

        for security in changes.removed_securities:

            if security.Invested:
                self.Liquidate(security.Symbol)

            if security.Symbol in self._portfolio:
                self._portfolio.remove(security.Symbol)

    def on_data(self, data: Slice):

        if not self.is_warming_up and data.bars:

            cash = self.portfolio.cash
            price = data[self.spy_symbol].price
            shares = math.floor(cash / price)
            self.market_order("SPY", shares)

            securities = [self.securities[symbol] for symbol in self._universe.selected]
            currentValues = {}
            for security in securities:

                conditions = []
                conditions.append(security.ema_of_rocp.is_ready)
                # conditions.append(security.ema_of_roc.is_ready)
                conditions.append(security.ema_of_variance.is_ready)
                if all(conditions):

                    ema_of_rocp = security.ema_of_rocp.Current.Value
                    # ema_of_roc = security.ema_of_roc.Current.Value
                    ema_of_variance = security.ema_of_variance.Current.Value
                    ema_of_standard_deviation = math.sqrt(ema_of_variance)

                    uncertainty_of_rocp = 1.14 * ema_of_standard_deviation / self.ema_size
                    
                    # if security.initialize and ema_of_rocp >= 0:
                    #     if security.Symbol not in self._portfolio:
                    #         self._portfolio.append(security.Symbol)

                    # if ema_of_rocp <= 0 and ema_of_rocp + ema_of_roc > 0:
                    #     if security.Symbol not in self._portfolio:
                    #         self._portfolio.append(security.Symbol)

                    # if ema_of_rocp >= 0 and ema_of_rocp + ema_of_roc < 0:
                    #     if security.Symbol in self._portfolio:
                    #         self._portfolio.remove(security.Symbol)

                    # if ema_of_rocp > uncertainty_of_rocp:
                    #     if security.Symbol not in self._portfolio:
                    #         self._portfolio.append(security.Symbol)

                    # if ema_of_rocp < uncertainty_of_rocp:
                    #     if security.Symbol in self._portfolio:
                    #         self._portfolio.remove(security.Symbol)

                    # currentValues[security.Symbol] = ema_of_rocp / uncertainty_of_rocp

                    next_step = ema_of_rocp - 2 * uncertainty_of_rocp #+ ema_of_roc
                    rocp_fraction = 1 + next_step / 100
                    log_rocp_fraction = np.log(rocp_fraction)
                    std_fraction = 1 + (next_step + ema_of_standard_deviation) / 100
                    rocp_std = log_rocp_fraction - np.log(std_fraction)
                    expected_value = np.exp(log_rocp_fraction + rocp_std **2 / 2)

                    currentValues[security.Symbol] = expected_value

            currentValues = dict(sorted(currentValues.items(), key=lambda item: item[1]))
            current_value_keys = list(currentValues.keys())
            # current_value_keys.reverse()

            notCream = len(currentValues) - self.creamOnTop
            ranks = {}
            for symbol in current_value_keys[:notCream]:
                # self.SetHoldings(symbol, 0)
                if symbol in self._portfolio:
                    self._portfolio.remove(symbol)
            for n, symbol in enumerate(current_value_keys[notCream:]):
                ranks[symbol] = n
                if symbol not in self._portfolio and symbol != self.spy_symbol:
                    self._portfolio.append(symbol)
                if currentValues[symbol] < self.expected_value_threshold:
                    if symbol in self._portfolio:
                        self._portfolio.remove(symbol)

            self.Plot("len(self._portfolio)", "len", len(self._portfolio))

            # if len(self._portfolio) > 0:
            #     # cash_on_hand = 0.5 / self.creamOnTop # percent of portfolio to remain cash

            #     # gld_weight = 0
            #     # if len(self._portfolio) < self.min_portfolio_size:
            #     #     gld_weight = self.min_portfolio_size - len(self._portfolio)
            #     # symbol_weight = (1 - cash_on_hand) / (len(self._portfolio) )#+ gld_weight)
            #     # gld_weight = (1 - cash_on_hand) * gld_weight / self.min_portfolio_size

            #     # if len(self._portfolio) < self.min_portfolio_size:
            #     #     pass

            #     # symbol_weight = (1 - cash_on_hand) / self.creamOnTop
            #     portfolio_targets = []
            #     for symbol in self._portfolio:
            #         # self.SetHoldings(symbol, 1 / self.creamOnTop)
            #         # symbol_weight = 2 * ranks[symbol] / self.creamOnTop ** 2
            #         # self.SetHoldings(symbol, symbol_weight)
            #         portfolio_targets.append(PortfolioTarget(symbol, 1 / self.creamOnTop))
            #         # portfolio_targets.append(PortfolioTarget(symbol, symbol_weight))
            #     # self.SetHoldings("GLD", gld_weight)
            #     self.set_holdings(portfolio_targets)

            spy_price = self.portfolio["SPY"].price
            spy_holding = self.portfolio["SPY"].quantity * spy_price

            if len(self._portfolio) > 0:
                for symbol in self._portfolio:

                    already_invested = self.portfolio[symbol].quantity > 0
                    if not already_invested:

                        value = self.portfolio.total_portfolio_value
                        target = value / self.portfolio_target_len
                        if spy_holding > target and symbol in data.keys():

                            sell_spy = math.floor(target / spy_price)
                            self.market_order("SPY", -sell_spy)

                            cash_value = sell_spy * spy_price
                            spy_holding -= cash_value
                            buy_symbol = math.floor(target / data[symbol].price)
                            self.market_order(symbol, buy_symbol)

            for symbol in self.portfolio.keys():
                if symbol not in self._portfolio and symbol != self.spy_symbol:
                    if self.portfolio[symbol].holdings_cost < self.portfolio[symbol].holdings_value:

                        quantity = self.portfolio[symbol].quantity
                        self.market_order(symbol, -quantity)

                        cash_value = quantity * self.portfolio[symbol].price
                        buy_spy = math.floor(cash_value / data[self.spy_symbol].price)
                        self.market_order("SPY", buy_spy)

                        cash_value = buy_spy * spy_price
                        spy_holding += cash_value

# vwap
# https://www.quantconnect.com/docs/v2/writing-algorithms/indicators/supported-indicators/volume-weighted-average-price-indicator

# rocp
# https://www.quantconnect.com/docs/v2/writing-algorithms/indicators/supported-indicators/rate-of-change-percent

# ema
# https://www.quantconnect.com/docs/v2/writing-algorithms/indicators/supported-indicators/exponential-moving-average

# Combining Indicators
# https://www.quantconnect.com/docs/v2/writing-algorithms/indicators/combining-indicators

# weighted standard deviation
# https://www.itl.nist.gov/div898/software/dataplot/refman2/ch2/weightsd.pdf