| Overall Statistics |
|
Total Orders 1592 Average Win 0.38% Average Loss -0.52% Compounding Annual Return -11.373% Drawdown 49.500% Expectancy -0.141 Start Equity 100000 End Equity 54703.62 Net Profit -45.296% Sharpe Ratio -0.939 Sortino Ratio -0.963 Probabilistic Sharpe Ratio 0.000% Loss Rate 50% Win Rate 50% Profit-Loss Ratio 0.72 Alpha -0.115 Beta 0.061 Annual Standard Deviation 0.118 Annual Variance 0.014 Information Ratio -1.018 Tracking Error 0.177 Treynor Ratio -1.815 Total Fees $3612.39 Estimated Strategy Capacity $0 Lowest Capacity Asset KELYB R735QTJ8XC9X Portfolio Turnover 2.15% Drawdown Recovery 617 |
#region imports
from AlgorithmImports import *
from collections import deque
#endregion
# https://quantpedia.com/Screener/Details/66
class CombiningMomentumEffectWithVolume(QCAlgorithm):
def initialize(self):
self.set_start_date(self.end_date - timedelta(5*365))
self.set_cash(100_000)
self.settings.seed_initial_prices = True
self._date_rule = self.date_rules.month_start('SPY')
self.universe_settings.schedule.on(self._date_rule)
self.universe_settings.resolution = Resolution.DAILY
self.add_universe(self._select_assets)
# Define some members to help with the ROC calculation.
self._lookback = 252
self._roc_by_symbol = {}
self.set_warmup(timedelta(45))
self._portfolios = deque(maxlen=3)
def _select_assets(self, fundamentals):
# Drop stocks which have no fundamental data.
fundamentals = [f for f in fundamentals if f.has_fundamental_data]
# Update the ROC of each asset and calculate its turnover.
candidates = []
turnover_by_symbol = {}
for f in fundamentals:
if f.symbol not in self._roc_by_symbol:
roc = self._roc_by_symbol.setdefault(f.symbol, RateOfChange(self._lookback))
for bar in self.history[TradeBar](f.symbol, self._lookback, Resolution.DAILY):
roc.update(bar.end_time, bar.close)
else:
roc = self._roc_by_symbol[f.symbol]
roc.update(self.time, f.adjusted_price)
if (not roc.is_ready or
not f.earning_reports.basic_average_shares.three_months or
not f.volume):
continue
candidates.append(f.symbol)
turnover_by_symbol[f.symbol] = f.earning_reports.basic_average_shares.three_months / f.volume
# Split candidates into top and bottom baskets based on their ROC.
sorted_by_roc = sorted(candidates, key=lambda symbol: self._roc_by_symbol[symbol])
roc_size = int(0.2*len(sorted_by_roc))
top_roc = sorted_by_roc[-roc_size:]
bottom_roc = sorted_by_roc[:roc_size]
# Select the assets with the greatest turnover for the long & short baskets.
assets_per_side = int(0.01*roc_size)
self._longs = sorted(top_roc, key=lambda symbol: turnover_by_symbol[symbol])[-assets_per_side:]
self._shorts = sorted(bottom_roc, key=lambda symbol: turnover_by_symbol[symbol])[-assets_per_side:]
return self._longs + self._shorts
def on_warmup_finished(self):
# Add a Scheduled event to rebalance the portfolio yearly.
time_rule = self.time_rules.at(8, 0)
self.schedule.on(self._date_rule, time_rule, self._rebalance)
# Rebalance the portfolio today too.
if self.live_mode:
self._rebalance()
else:
self.schedule.on(self.date_rules.today, time_rule, self._rebalance)
def _rebalance(self):
if self.is_warming_up:
return
# 1/3 of the portfolio is rebalanced every month
if len(self._portfolios) == self._portfolios.maxlen:
for position in list(self._portfolios)[0]:
position.exit()
self._portfolios.append(
self._open_trades(self._shorts, -1) +
self._open_trades(self._longs, 1)
)
def _open_trades(self, symbols, direction):
new_positions = []
for symbol in symbols:
security = self.securities[symbol]
quantity = direction * int(
0.5*self.portfolio.total_portfolio_value
/ security.price
/ len(symbols)
/ self._portfolios.maxlen
)
if quantity:
new_positions.append(MonthlyPosition(self, security, quantity))
return new_positions
class MonthlyPosition:
def __init__(self, algorithm, equity, quantity):
self._algorithm = algorithm
self._equity = equity
self._quantity = quantity
# Enter the position.
algorithm.market_order(equity, quantity)
def exit(self):
if self._equity.is_tradable:
self._algorithm.market_order(self._equity, -self._quantity)