| Overall Statistics |
|
Total Orders 1895 Average Win 0.14% Average Loss -0.13% Compounding Annual Return 19.834% Drawdown 3.100% Expectancy 0.111 Start Equity 1000000 End Equity 1140947.79 Net Profit 14.095% Sharpe Ratio 0.979 Sortino Ratio 1.178 Probabilistic Sharpe Ratio 70.425% Loss Rate 47% Win Rate 53% Profit-Loss Ratio 1.11 Alpha 0.064 Beta 0.355 Annual Standard Deviation 0.084 Annual Variance 0.007 Information Ratio 0.284 Tracking Error 0.104 Treynor Ratio 0.232 Total Fees $2922.54 Estimated Strategy Capacity $890000000.00 Lowest Capacity Asset PGR R735QTJ8XC9X Portfolio Turnover 22.89% Drawdown Recovery 22 |
# region imports
from AlgorithmImports import *
# endregion
class SpectralPeriodicityPremiumAlgorithm(QCAlgorithm):
"""Long high / short low intraday-volume periodicity (Track 1 volume_variance_explained), with long
weights tilted toward names that persistently host execution programs (Track 2). Reproduces the
SSRN 4230610 section 6.3 Periodic-minus-Smooth premium, refined so both tracks contribute each
rebalance. Because same-day Track 2 signatures are sparse (~3% of ticker-days), the exec score is
smoothed into a persistent execution-intensity proxy rather than used as a same-day hard filter."""
def initialize(self):
# Bound the run to the dataset coverage window (explicit end date is required here).
self.set_start_date(2025, 10, 1)
self.set_end_date(2026, 6, 23)
self.set_cash(1_000_000)
self.set_benchmark("SPY")
self.settings.seed_initial_prices = True
self.universe_settings.resolution = Resolution.DAILY
self._spy = self.add_equity("SPY", Resolution.DAILY)
self._rank_fraction = 0.2
self._min_per_side = 5
self._gross_exposure = 1.0
# The freshest signal at a Monday 8 AM rebalance is Friday's, already 3 calendar days old
# (4 after a Monday holiday), so the max signal age must be at least 4 to ever qualify.
self._max_signal_age_days = 4
self._tilt_by_exec = True
# Smooth the sparse Track 2 score into a persistent execution-intensity proxy.
self._exec_decay = 0.94
self._tilt_strength = 1.0
# Long enough that a fresh EMA replay each rebalance converges to the same steady state on_data would reach.
self._intensity_lookback_days = 90
self._securities = []
self._security_by_dataset_symbol = {}
# Liquid large-cap S&P proxy matching the dataset coverage; broad enough for quintile sorts.
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)[-100:]]
)
# Rebalance weekly at 8 AM, the daily-data cadence; the premium is a next-session hold (section 6.3).
self.schedule.on(self.date_rules.week_start(self._spy), self.time_rules.at(8, 0), self._rebalance)
def on_securities_changed(self, changes):
# Subscribe each added equity to its signal feed; drop feeds and holdings for removed names.
for security in changes.added_securities:
if security == self._spy or security.symbol.security_type != SecurityType.EQUITY or security in self._securities:
continue
self._securities.append(security)
security.signal = None
security.exec_intensity = 0.0
security.dataset_symbol = self.add_data(QuantConnectSpectralTickFlowSignal, security, Resolution.DAILY).symbol
self._security_by_dataset_symbol[security.dataset_symbol] = security
for security in changes.removed_securities:
if security not in self._securities:
continue
self._securities.remove(security)
self._security_by_dataset_symbol.pop(security.dataset_symbol, None)
self.remove_security(security.dataset_symbol)
if security.invested:
self.liquidate(security)
def _rebalance(self):
# Rank fresh names by Track 1 periodicity; long the top, short the bottom, dollar-neutral.
if self.is_warming_up:
return
for security in self._securities:
security.signal = None
security.exec_intensity = 0.0
dataset_symbols = [security.dataset_symbol for security in self._securities]
if dataset_symbols:
history = self.history[QuantConnectSpectralTickFlowSignal](dataset_symbols, timedelta(days=self._intensity_lookback_days), Resolution.DAILY)
for data_dictionary in history:
for point in data_dictionary.values():
security = self._security_by_dataset_symbol.get(point.symbol)
if security is None:
continue
security.signal = point
security.exec_intensity = self._exec_decay * security.exec_intensity + (1.0 - self._exec_decay) * point.execution_score
# Keep only securities whose latest signal day is recent enough to act on.
eligible = []
for security in self._securities:
if (security.signal is None or
(self.time.date() - security.signal.time.date()).days > self._max_signal_age_days):
continue
eligible.append(security)
if len(eligible) < 2 * self._min_per_side:
self.set_holdings([], True)
self._plot_selection(0, 0, 0.0, 0.0, 0.0)
return
ranked = sorted(eligible, key=lambda security: security.signal.volume_variance_explained)
count = max(self._min_per_side, int(len(ranked) * self._rank_fraction))
count = min(count, len(ranked) // 2)
short_securities = ranked[:count]
long_securities = ranked[-count:]
# Equal weight, optionally tilted toward names with stronger persistent Track 2 execution.
leg_budget = self._gross_exposure / 2.0
peak = max((security.exec_intensity for security in long_securities), default=0.0)
if not self._tilt_by_exec or peak <= 0.0:
weight_by_security = {security: leg_budget / len(long_securities) for security in long_securities}
else:
raw_by_security = {security: 1.0 + self._tilt_strength * security.exec_intensity / peak
for security in long_securities}
total = sum(raw_by_security.values())
weight_by_security = {security: leg_budget * raw / total for security, raw in raw_by_security.items()}
short_weight = (self._gross_exposure / 2.0) / len(short_securities)
targets = [PortfolioTarget(security, weight) for security, weight in weight_by_security.items()]
targets += [PortfolioTarget(security, -short_weight) for security in short_securities]
self.set_holdings(targets, True)
self._plot_selection(
len(long_securities), len(short_securities),
self._mean_periodicity(long_securities), self._mean_periodicity(short_securities),
sum(1 for security in long_securities if security.exec_intensity > 0.0) / len(long_securities)
if long_securities else 0.0
)
def _mean_periodicity(self, securities):
# Average Track 1 periodicity strength across a leg, for diagnostics.
if not securities:
return 0.0
return sum(security.signal.volume_variance_explained for security in securities) / len(securities)
def _plot_selection(self, long_count, short_count, long_mean_periodicity, short_mean_periodicity,
exec_active_long_fraction):
# Chart leg sizes and the periodicity spread that drives the premium.
self.plot("Spectral Selection", "Long Count", long_count)
self.plot("Spectral Selection", "Short Count", short_count)
self.plot("Spectral Signal", "Long Mean Periodicity", long_mean_periodicity)
self.plot("Spectral Signal", "Short Mean Periodicity", short_mean_periodicity)
self.plot("Spectral Signal", "Exec-Active Long Fraction", exec_active_long_fraction)