Overall Statistics
Total Orders
1
Average Win
0%
Average Loss
0%
Compounding Annual Return
20.538%
Drawdown
18.700%
Expectancy
0
Start Equity
100000
End Equity
188879.18
Net Profit
88.879%
Sharpe Ratio
0.77
Sortino Ratio
0.815
Probabilistic Sharpe Ratio
62.752%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
-0.009
Beta
0.903
Annual Standard Deviation
0.119
Annual Variance
0.014
Information Ratio
-0.514
Tracking Error
0.038
Treynor Ratio
0.101
Total Fees
$1.26
Estimated Strategy Capacity
$0
Lowest Capacity Asset
SPY R735QTJ8XC9X
Portfolio Turnover
0.08%
Drawdown Recovery
126
# region imports
from AlgorithmImports import *
# endregion


class EmaCrossAlgorithm(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2023, 1, 1)
        self.set_cash(100_000)

        # Get parameters from optimization or use defaults
        fast_period = int(self.get_parameter("ema_fast") or 50)
        slow_period = int(self.get_parameter("ema_slow") or 200)

        # Ensure fast < slow
        if fast_period > slow_period:
            fast_period, slow_period = slow_period, fast_period

        self.settings.automatic_indicator_warm_up = True

        # Add SPY equity
        equity = self.add_equity("SPY", Resolution.DAILY)
        self._symbol = equity.symbol

        # Create EMA indicators
        self._fast_ema = self.ema(self._symbol, fast_period, Resolution.DAILY)
        self._slow_ema = self.ema(self._symbol, slow_period, Resolution.DAILY)

        # Plot the indicators
        self.plot_indicator("EMA", self._fast_ema)
        self.plot_indicator("EMA", self._slow_ema)

        # Track previous crossover state
        self._previous_fast_above = False

        # Schedule daily rebalance after market open
        self.schedule.on(
            self.date_rules.every_day(self._symbol),
            self.time_rules.after_market_open(self._symbol, 1),
            self._rebalance,
        )

    def _rebalance(self) -> None:
        if not self._fast_ema.is_ready or not self._slow_ema.is_ready:
            return

        fast_value = self._fast_ema.current.value
        slow_value = self._slow_ema.current.value
        fast_above = fast_value > slow_value

        # Detect crossover
        crossover_up = fast_above and not self._previous_fast_above
        crossover_down = not fast_above and self._previous_fast_above

        if crossover_up:
            self.set_holdings(self._symbol, 1.0)
            self.log(f"BUY: Fast EMA ({fast_value:.2f}) crossed above Slow EMA ({slow_value:.2f})")
        elif crossover_down:
            self.liquidate(self._symbol)
            self.log(f"SELL: Fast EMA ({fast_value:.2f}) crossed below Slow EMA ({slow_value:.2f})")

        self._previous_fast_above = fast_above

# region imports
from AlgorithmImports import *
# endregion

def Add(x: float, y: float) -> float:
    return x + y