| 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