| Overall Statistics |
|
Total Orders 5961 Average Win 0.29% Average Loss -0.26% Compounding Annual Return 27.324% Drawdown 36.200% Expectancy 0.157 Start Equity 1000000 End Equity 3349949.09 Net Profit 234.995% Sharpe Ratio 0.792 Sortino Ratio 0.924 Probabilistic Sharpe Ratio 21.001% Loss Rate 45% Win Rate 55% Profit-Loss Ratio 1.11 Alpha 0.089 Beta 1.311 Annual Standard Deviation 0.209 Annual Variance 0.044 Information Ratio 1.031 Tracking Error 0.104 Treynor Ratio 0.126 Total Fees $26701.45 Estimated Strategy Capacity $400000000.00 Lowest Capacity Asset CTSH RBOKUIZLS38L Portfolio Turnover 20.37% Drawdown Recovery 560 |
# region imports
from AlgorithmImports import *
# endregion
class SpectralPeriodicityPremiumAlgorithm(QCAlgorithm):
def initialize(self) -> None:
self.set_start_date(2021, 7, 1)
self.set_end_date(2026, 7, 1)
self.set_cash(1_000_000)
self.settings.seed_initial_prices = True
# Define some parameters.
self._universe_size = 100
self._ema_period = 3*21
self._quantiles = 5
# Add a universe of US Equities.
self.universe_settings.resolution = Resolution.DAILY
self._date_rule = self.date_rules.week_start("SPY")
self.universe_settings.schedule.on(self._date_rule)
self._universe = self.add_universe(lambda fundamental:
[f.symbol for f in sorted([f for f in fundamental if f.has_fundamental_data], key=lambda f: f.dollar_volume)[-self._universe_size:]]
)
# Add a warm-up period so the algorithm trades right away.
self.set_warm_up(timedelta(14))
def on_warmup_finished(self) -> None:
# Rebalance weekly at 8 AM.
time_rule = self.time_rules.at(8, 0)
self.schedule.on(self._date_rule, time_rule, self._rebalance)
# Rebalance today too.
if self.live_mode:
self._rebalance()
else:
self.schedule.on(self.date_rules.today, time_rule, self._rebalance)
def on_securities_changed(self, changes: SecurityChanges) -> None:
# As stocks enter the universe, add their spectral tick flow signals and warm-up its EMA.
for security in changes.added_securities:
security.dataset_symbol = self.add_data(QuantConnectSpectralTickFlowSignal, security, Resolution.DAILY).symbol
security.exec_intensity = ExponentialMovingAverage(self._ema_period)
for data_point in self.history[QuantConnectSpectralTickFlowSignal](security.dataset_symbol, timedelta(int(self._ema_period*1.5))):
self._update_factors(security, data_point)
for security in changes.removed_securities:
self.remove_security(security.dataset_symbol)
def _update_factors(self, security: Equity, data_point: QuantConnectSpectralTickFlowSignal) -> None:
# Save the latest spectral tick flow signal and update the EMA.
security.signal = data_point
security.exec_intensity.update(data_point.end_time, data_point.execution_score)
# As new spectral tick flow signals arrive, update the factors of each stock.
def on_data(self, data: Slice) -> None:
for dataset_symbol, data_point in data.get(QuantConnectSpectralTickFlowSignal).items():
self._update_factors(self.securities[dataset_symbol.underlying], data_point)
def _rebalance(self) -> None:
if self.is_warming_up:
return
# Get the set of stocks in the universe that have enough signals.
securities = [self.securities[symbol] for symbol in self._universe.selected]
eligible = [security for security in securities if security.exec_intensity.is_ready]
if not eligible:
return
# Select the subset of stocks that have the strongest periodicity.
selected = sorted(eligible, key=lambda security: security.signal.volume_variance_explained)[-int(len(eligible)/self._quantiles):]
# Give each stock a raw weight of one plus its execution intensity relative to the peak.
peak = max((security.exec_intensity.current.value for security in selected))
weight_by_security = {security: 1.0 + security.exec_intensity.current.value / peak for security in selected}
# Normalize the raw weights so the portfolio exposure sums to one.
weight_by_security = {security: weight / sum(weight_by_security.values()) for security, weight in weight_by_security.items()}
# Place orders to rebalance the portfolio.
targets = [PortfolioTarget(security, weight) for security, weight in weight_by_security.items()]
self.set_holdings(targets, True)