| Overall Statistics |
|
Total Orders 2891 Average Win 0.09% Average Loss -0.08% Compounding Annual Return 14.946% Drawdown 3.000% Expectancy 0.102 Start Equity 1000000 End Equity 1106841.84 Net Profit 10.684% Sharpe Ratio 0.707 Sortino Ratio 0.774 Probabilistic Sharpe Ratio 66.549% Loss Rate 49% Win Rate 51% Profit-Loss Ratio 1.15 Alpha 0.032 Beta 0.315 Annual Standard Deviation 0.069 Annual Variance 0.005 Information Ratio -0.042 Tracking Error 0.098 Treynor Ratio 0.155 Total Fees $3855.15 Estimated Strategy Capacity $1300000000.00 Lowest Capacity Asset DISCA TAHT8L1LVDR9 Portfolio Turnover 23.06% Drawdown Recovery 89 |
# 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
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 and f.market_cap > 5e9],
key=lambda f: f.dollar_volume)[-150:]])
# 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:
continue
if 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
# Replay recent history so the first weekly rebalance already has a signal value.
history = self.history[QuantConnectSpectralTickFlowSignal](
security.dataset_symbol, timedelta(days=10), Resolution.DAILY)
for point in history:
security.signal = point
self._update_intensity(security, point.execution_score)
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 _update_intensity(self, security, execution_score):
# Exponentially smooth the sparse exec score into a persistent execution-intensity proxy.
security.exec_intensity = (self._exec_decay * security.exec_intensity
+ (1.0 - self._exec_decay) * execution_score)
def on_data(self, data):
# Ingest the day's signal rows and refresh each name's execution-intensity proxy.
points = data.get(QuantConnectSpectralTickFlowSignal)
if points:
for point in points.values():
security = self._security_by_dataset_symbol.get(point.symbol)
if security is None:
continue
security.signal = point
self._update_intensity(security, point.execution_score)
def _rebalance(self):
# Rank fresh names by Track 1 periodicity; long the top, short the bottom, dollar-neutral.
if self.is_warming_up:
return
# Keep only securities whose latest signal day is recent enough to act on.
today = self.time.date()
eligible = []
for security in self._securities:
if security.signal is None:
continue
if (today - 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)