| 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 ResolveGlobalEquityMomentum(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)