Overall Statistics
Total Orders
1496
Average Win
0.55%
Average Loss
-0.59%
Compounding Annual Return
1.819%
Drawdown
15.800%
Expectancy
0.017
Start Equity
100000
End Equity
104643.6
Net Profit
4.644%
Sharpe Ratio
-0.258
Sortino Ratio
-0.247
Probabilistic Sharpe Ratio
5.429%
Loss Rate
47%
Win Rate
53%
Profit-Loss Ratio
0.94
Alpha
-0.056
Beta
0.207
Annual Standard Deviation
0.13
Annual Variance
0.017
Information Ratio
-0.865
Tracking Error
0.163
Treynor Ratio
-0.163
Total Fees
$3216.40
Estimated Strategy Capacity
$120000000.00
Lowest Capacity Asset
NQ YYFADOG4CO3L
Portfolio Turnover
599.80%
Drawdown Recovery
270
"""
NQ Baseline — Pure 8/20 MA Crossover
========================================
No HMM, no tensor, no gates. Every bull crossover → long 7 bars.
This exists to prove (or disprove) that the tensor adds value.
"""
from AlgorithmImports import *
import numpy as np


class NQBaseline(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2023, 6, 1)
        self.SetEndDate(2026, 3, 4)
        self.SetCash(100000)

        self.nq = self.AddFuture(
            Futures.Indices.NASDAQ100EMini,
            Resolution.Minute,
            dataNormalizationMode=DataNormalizationMode.Raw
        )
        self.nq.SetFilter(0, 90)
        self.contract = None

        # Parameters
        self.MA_FAST = 8
        self.MA_SLOW = 20
        self.HOLD_BARS = 7
        self.WARMUP_BARS = 6
        self.RTH_START = time(9, 30)
        self.RTH_END = time(15, 55)
        self.NO_TRADE_AFTER = time(15, 0)

        # State
        self.close_buffer = []
        self.bar_count = 0
        self.current_session = None
        self.open_trade = None
        self.total_trades = 0

        # 5-min accumulator
        self.acc_open = None
        self.acc_high = None
        self.acc_low = None
        self.acc_close = None
        self.acc_vol = 0
        self.acc_count = 0

    def OnData(self, data):
        # Get active contract
        for chain in data.FutureChains:
            contracts = sorted(
                [c for c in chain.Value if c.OpenInterest > 0],
                key=lambda c: c.OpenInterest, reverse=True
            )
            if contracts:
                self.contract = contracts[0]
                break

        if self.contract is None:
            return

        bar = data.Bars.get(self.contract.Symbol)
        if bar is None:
            return

        t = bar.EndTime.time()
        d = bar.EndTime.date()

        if t < self.RTH_START or t > self.RTH_END:
            return

        # Session reset
        if d != self.current_session:
            self.close_buffer = []
            self.bar_count = 0
            self.acc_count = 0
            self.acc_vol = 0
            self._close_position("session_end")
            self.current_session = d

        # Accumulate 5-min bars
        if self.acc_count == 0:
            self.acc_open = float(bar.Open)
            self.acc_high = float(bar.High)
            self.acc_low = float(bar.Low)
        else:
            self.acc_high = max(self.acc_high, float(bar.High))
            self.acc_low = min(self.acc_low, float(bar.Low))

        self.acc_close = float(bar.Close)
        self.acc_vol += int(bar.Volume)
        self.acc_count += 1

        if self.acc_count >= 5:
            self._on_5min(self.acc_close, bar.EndTime)
            self.acc_count = 0
            self.acc_vol = 0

    def _on_5min(self, c, end_time):
        self.bar_count += 1

        if self.bar_count <= self.WARMUP_BARS:
            self.close_buffer.append(c)
            return

        self.close_buffer.append(c)

        # Check exit first
        if self.open_trade is not None:
            held = self.bar_count - self.open_trade["bar"]
            if held >= self.HOLD_BARS:
                self._close_position("fixed_hold")

        # Check entry
        if self.open_trade is None and end_time.time() < self.NO_TRADE_AFTER:
            if self._bull_crossover():
                self._enter(c, end_time)

    def _bull_crossover(self):
        buf = self.close_buffer
        if len(buf) < self.MA_SLOW + 1:
            return False
        fast_now = np.mean(buf[-self.MA_FAST:])
        slow_now = np.mean(buf[-self.MA_SLOW:])
        fast_prev = np.mean(buf[-self.MA_FAST - 1:-1])
        slow_prev = np.mean(buf[-self.MA_SLOW - 1:-1])
        return (fast_now > slow_now) and (fast_prev <= slow_prev)

    def _enter(self, price, end_time):
        if self.contract is None:
            return
        self.MarketOrder(self.contract.Symbol, 1)
        self.open_trade = {"bar": self.bar_count, "price": price}
        self.total_trades += 1
        self.Debug("ENTRY #{}: @ {:.2f} at {}".format(
            self.total_trades, price, end_time))

    def _close_position(self, reason):
        if self.open_trade is None:
            return
        if not self.Portfolio.Invested:
            self.open_trade = None
            return
        if self.contract and self.Portfolio[self.contract.Symbol].Invested:
            pnl = self.Securities[self.contract.Symbol].Price - self.open_trade["price"]
            self.Liquidate(self.contract.Symbol)
            self.Debug("EXIT: {} | PnL={:+.2f}".format(reason, pnl))
        self.open_trade = None

    def OnEndOfAlgorithm(self):
        self.Debug("=== BASELINE COMPLETE: {} trades ===".format(self.total_trades))