Overall Statistics
Total Orders
159
Average Win
1.28%
Average Loss
-0.77%
Compounding Annual Return
6.674%
Drawdown
35.400%
Expectancy
0.437
Start Equity
100000
End Equity
138145.81
Net Profit
38.146%
Sharpe Ratio
0.134
Sortino Ratio
0.164
Probabilistic Sharpe Ratio
7.195%
Loss Rate
46%
Win Rate
54%
Profit-Loss Ratio
1.66
Alpha
-0.026
Beta
0.54
Annual Standard Deviation
0.112
Annual Variance
0.013
Information Ratio
-0.574
Tracking Error
0.105
Treynor Ratio
0.028
Total Fees
$187.18
Estimated Strategy Capacity
$38000000.00
Lowest Capacity Asset
TLT SGNKIKYGE9NP
Portfolio Turnover
1.28%
Drawdown Recovery
1060
# region imports
from AlgorithmImports import *
# endregion


class ResolveGEM(QCAlgorithm):

    def initialize(self):
        self.set_start_date(self.end_date - timedelta(5*365))
        self.set_cash(100_000)
        # Add the assets to trade.
        self._equities = [self.add_equity(ticker) for ticker in ['SPY', 'EFA']]
        self._bonds = self.add_equity('TLT')
        # Define the leverage to use.
        self._leverage = 1
        # Create multiple strategies with different settings.
        lookbacks = range(1, 19)
        ma_periods = range(2, 19)
        trend_styles = ['SPY', 'Multi']
        momentum_styles = ['Price', 'MA']
        columns = ['Momentum Style', 'Trend Style', 'Momentum Lookback', 'Trend Lookback', 'MA Period']
        settings = []
        for trend in trend_styles:
            for momentum in momentum_styles:
                if momentum == 'Price':
                    for m_look in lookbacks:
                        for t_look in lookbacks:
                            settings.append([momentum, trend, m_look, t_look, 0])
                else:
                    for ma in ma_periods:
                        for ma_look in ma_periods:
                            settings.append([momentum, trend, 0, ma_look, ma])
        self._strategies = pd.DataFrame(settings, columns=columns)
        # Create a member to track the allocation for each asset.
        for security in self.securities.values():
            security.allocation = 0
        # Add a Scheduled Event to update the signals and portfolio weights.
        self.schedule.on(
            self.date_rules.month_end('SPY'), 
            self.time_rules.before_market_close('SPY', 10),
            self._rebalance
        )
                        
    def _rebalance(self):
        # Update the `allocation` member of each asset.
        h = self.history(self._equities, 252 * 2, Resolution.DAILY).unstack(0).close
        for strategy in range(self._strategies.iloc[:,0].count()):
            if self._strategies.iloc[strategy]['Momentum Style'] == 'Price':
                spy_trend = h['SPY'].iloc[-1] / h['SPY'].iloc[-22 * self._strategies.iloc[strategy]['Trend Lookback']] - 1 > 0
                efa_trend = h['EFA'].iloc[-1] / h['EFA'].iloc[-22 * self._strategies.iloc[strategy]['Trend Lookback']] - 1 > 0
                # Select highest momentum equity if SPY > Moving Average
                if (self._strategies.iloc[strategy]['Trend Style'] == 'SPY' and spy_trend or
                    # Select highest momentum equity if SPY and EFA > Moving Average
                    self._strategies.iloc[strategy]['Trend Style'] == 'Multi' and spy_trend and efa_trend):
                    # Calculate momentum as % change in price
                    max(
                        self._equities, 
                        key=lambda equity: (
                            h[equity.symbol].iloc[-1] 
                            / h[equity.symbol].iloc[-22 * self._strategies.iloc[strategy]['Momentum Lookback']] -1
                        )
                    ).allocation += 1
                    continue
            else:
                spy_trend = h['SPY'].iloc[-1] > h['SPY'].iloc[-22 * self._strategies.iloc[strategy]['Trend Lookback']:].mean()
                efa_trend = h['EFA'].iloc[-1] > h['EFA'].iloc[-22 * self._strategies.iloc[strategy]['Trend Lookback']:].mean()
                # Select highest momentum equity if SPY > Moving Average
                if (self._strategies.iloc[strategy]['Trend Style'] == 'SPY' and spy_trend or
                    # Select highest momentum equity if SPY and EFA > Moving Average   
                    self._strategies.iloc[strategy]['Trend Style'] == 'Multi' and spy_trend and efa_trend):
                    # Calculate momentum as % difference to moving average
                    max(
                        self._equities, 
                        key=lambda equity: (
                            h[equity.symbol].iloc[-1] 
                            / h[equity.symbol].iloc[-22 * self._strategies.iloc[strategy]['MA Period']:].mean() -1
                        )
                    ).allocation += 1
                    continue
            self._bonds.allocation += 1 
        
        # Rebalance the portfolio.
        allocation_sum = sum([security.allocation for security in self.securities.values()])
        targets = []
        for security in self.securities.values():
            weight = security.allocation / allocation_sum
            targets.append(PortfolioTarget(security, self._leverage * weight))
            security.allocation = 0
        self.set_holdings(targets)