| Overall Statistics |
|
Total Orders 650 Average Win 1.09% Average Loss -1.25% Compounding Annual Return 6.432% Drawdown 58.500% Expectancy 0.043 Start Equity 100000 End Equity 136538.84 Net Profit 36.539% Sharpe Ratio 0.179 Sortino Ratio 0.21 Probabilistic Sharpe Ratio 2.888% Loss Rate 44% Win Rate 56% Profit-Loss Ratio 0.87 Alpha -0.013 Beta 0.939 Annual Standard Deviation 0.295 Annual Variance 0.087 Information Ratio -0.064 Tracking Error 0.264 Treynor Ratio 0.056 Total Fees $764.04 Estimated Strategy Capacity $3700000.00 Lowest Capacity Asset OPEN XKJAZ588SHNP Portfolio Turnover 3.14% Drawdown Recovery 1456 |
from AlgorithmImports import *
class FundamentalFactorAlgorithm(QCAlgorithm):
def initialize(self):
self.set_start_date(self.end_date - timedelta(5*365))
self.set_cash(100_000)
# Add TLT and SPY.
self.settings.automatic_indicator_warm_up = True
self._tlt = self.add_equity('TLT')
self._spy = self.add_equity('SPY')
self._spy.sma = self.sma(self._spy, 120, Resolution.DAILY)
# Add a universe of US Equities.
self._universe_size = 100
self._date_rule = self.date_rules.month_start(self._spy)
self.universe_settings.schedule.on(self._date_rule)
self._universe = self.add_universe(self._select_assets)
# Define some trading paramters.
self._formation_days = 200
self._portfolio_size = 10
# Add a warmup so the algorithm trades upon deployment.
self.set_warm_up(timedelta(45))
def _select_assets(self, fundamentals):
# Select the most liquid stocks trading above $5.
fundamentals = [f for f in fundamentals if f.has_fundamental_data and f.price > 5]
fundamentals = sorted(fundamentals, key=lambda f: f.dollar_volume)[-200:]
# Select the subset of stocks with the greatest EV/EBITDA.
fundamentals = [
f for f in fundamentals
if (f.valuation_ratios.ev_to_ebitda > 0 and
f.earning_reports.basic_average_shares.three_months * f.price > 2e9)
]
selected = sorted(fundamentals, key=lambda f: f.valuation_ratios.ev_to_ebitda)[-self._universe_size:]
return [f.symbol for f in selected]
def on_securities_changed(self, changes):
for security in changes.added_securities:
# Increase the Session, so we can track daily prices from n days ago.
security.session.size = self._formation_days
# Warm up the Session history.
for bar in self.history[TradeBar](security, self._formation_days, Resolution.DAILY):
security.session.update(bar)
def on_warmup_finished(self):
# Add a Scheduled event to rebalance the portfolio monthly.
time_rule = self.time_rules.at(10, 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):
# Wait until the warmup ends.
if self.is_warming_up:
return
# If SPY is below its SMA, just hold TLT.
if self._spy.price < self._spy.sma.current.value:
self.set_holdings(self._tlt, 1, True)
return
# Otherwise, calculate the momentum of all assets in the universe.
roc_by_security = {}
for symbol in self._universe.selected:
security = self.securities[symbol]
if not security.session.is_ready:
continue
# Compare the closing price n days ago to the intraday price right now.
start_value = security.session[-1].close
if not start_value:
continue
roc_by_security[security] = security.price / start_value - 1
if not roc_by_security:
return
# Form an equal-weighted portfolio of the assets with the greatest momentum.
selected = sorted(roc_by_security, key=lambda security: roc_by_security[security])[-self._portfolio_size:]
weight = 1 / len(selected)
self.set_holdings([PortfolioTarget(equity, weight) for equity in selected], True)