Overall Statistics
Total Orders
45
Average Win
44.61%
Average Loss
-13.06%
Compounding Annual Return
5.604%
Drawdown
60.500%
Expectancy
0.405
Start Equity
100000
End Equity
126729.64
Net Profit
26.730%
Sharpe Ratio
0.267
Sortino Ratio
0.257
Probabilistic Sharpe Ratio
4.518%
Loss Rate
68%
Win Rate
32%
Profit-Loss Ratio
3.42
Alpha
0.068
Beta
0.647
Annual Standard Deviation
0.511
Annual Variance
0.261
Information Ratio
0.071
Tracking Error
0.432
Treynor Ratio
0.211
Total Fees
$156.51
Estimated Strategy Capacity
$110000000.00
Lowest Capacity Asset
TSLA UNU3P8Y3WFAD
Portfolio Turnover
4.87%
Drawdown Recovery
513
#region imports
from AlgorithmImports import *
#endregion


"""
PARAMETERIZED EMA + RSI STRATEGY

This strategy demonstrates how QuantConnect parameters can be used to calibrate
a trading model without changing the code.

The model trades TSLA using two signals:

1. EMA trend signal:
   The strategy compares a fast EMA with a slow EMA. The slow EMA is read from the
   parameter "ema-slow". The fast EMA is calculated as:

       fast EMA = ema-slow - ema-spread

   If the fast EMA is above the slow EMA, the EMA signal contributes a positive
   portfolio weight. The size of this contribution is controlled by "_Theta".
   For example, if _Theta = 50, the EMA component contributes +50%.

2. RSI signal:
   The RSI period is read from "period_rsi". The lower RSI threshold is read from
   "low_rsi", and the upper RSI threshold is calculated as:

       high RSI = low_rsi + spread-rsi

   If RSI is above the high threshold, the RSI component contributes a positive
   weight controlled by "_Rho". If RSI is between the low and high thresholds, the
   RSI component contributes zero. If RSI is below the lower threshold, the RSI
   component contributes a negative weight.

The final unlevered target is:

    EMA component + RSI component

This target is capped between -100% and +100% before applying the leverage
parameter "_newParameter". The final portfolio target is therefore:

    final target = leverage * capped signal

The strategy is intentionally simple and pedagogical. It shows how parameterized
technical signals can be combined into one target portfolio weight.
"""


class EmaCrossParameterAlgorithm(QCAlgorithm):

    def Initialize(self):

        # ------------------------------------------------------------
        # 1. BACKTEST SETTINGS
        # ------------------------------------------------------------
        self.SetStartDate(2022, 1, 1)
        self.SetEndDate(2026, 5, 5)

        self.initial_cash = 100000
        self.SetCash(self.initial_cash)

        # ------------------------------------------------------------
        # 2. LOAD PARAMETERS WITH SAFE DEFAULTS
        # ------------------------------------------------------------
        self.slow = self.GetIntParameter("ema-slow", 30)
        self.spread_ema = self.GetIntParameter("ema-spread", 10)

        self.fast = self.slow - self.spread_ema

        # Make sure the fast EMA is valid.
        if self.fast < 1:
            self.fast = 1

        # Make sure slow EMA is above fast EMA.
        if self.slow <= self.fast:
            self.slow = self.fast + 1

        self._theta = self.GetIntParameter("_Theta", 50)

        self.p_rsi = self.GetIntParameter("period_rsi", 14)
        self.l_rsi = self.GetIntParameter("low_rsi", 35)
        self.spread_rsi = self.GetIntParameter("spread-rsi", 30)

        self.h_rsi = self.l_rsi + self.spread_rsi

        # Keep RSI thresholds inside a reasonable range.
        if self.l_rsi < 0:
            self.l_rsi = 0

        if self.h_rsi > 100:
            self.h_rsi = 100

        if self.h_rsi <= self.l_rsi:
            self.h_rsi = self.l_rsi + 1

        self._rho = self.GetIntParameter("_Rho", 50)

        self._leverage = self.GetFloatParameter("_newParameter", 1.0)

        # ------------------------------------------------------------
        # 3. ASSET
        # ------------------------------------------------------------
        self.symbol = self.AddEquity(
            "TSLA",
            Resolution.Hour
        ).Symbol

        # ------------------------------------------------------------
        # 4. INDICATORS
        # ------------------------------------------------------------
        self.emaFast = self.EMA(
            self.symbol,
            self.fast,
            Resolution.Hour
        )

        self.emaSlow = self.EMA(
            self.symbol,
            self.slow,
            Resolution.Hour
        )

        self.rsi = self.RSI(
            self.symbol,
            self.p_rsi,
            MovingAverageType.Wilders,
            Resolution.Hour
        )

        # Warm up enough data for the slowest indicator.
        warmup_period = max(
            self.slow,
            self.p_rsi
        )

        self.SetWarmUp(
            warmup_period,
            Resolution.Hour
        )

        # ------------------------------------------------------------
        # 5. BENCHMARK
        # ------------------------------------------------------------
        self._benchmark = self.symbol
        self._benchmark_price = None

        self.SetBenchmark(self.symbol)

        # ------------------------------------------------------------
        # 6. TRADE CONTROL
        # ------------------------------------------------------------
        self.current_target_weight = 0
        self.rebalance_threshold = 0.02

        # ------------------------------------------------------------
        # 7. DEBUG PARAMETERS
        # ------------------------------------------------------------
        self.Debug(
            "Parameters: "
            + "fast EMA="
            + str(self.fast)
            + ", slow EMA="
            + str(self.slow)
            + ", theta="
            + str(self._theta)
            + ", RSI period="
            + str(self.p_rsi)
            + ", low RSI="
            + str(self.l_rsi)
            + ", high RSI="
            + str(self.h_rsi)
            + ", rho="
            + str(self._rho)
            + ", leverage="
            + str(self._leverage)
        )

    def OnData(self, data):

        # ------------------------------------------------------------
        # 1. CHECK DATA
        # ------------------------------------------------------------
        if self.symbol not in data or data[self.symbol] is None:
            return

        price = self.Securities[self.symbol].Price

        if price <= 0:
            return

        if self._benchmark_price is None:
            self._benchmark_price = price

        # ------------------------------------------------------------
        # 2. WAIT FOR INDICATORS
        # ------------------------------------------------------------
        if self.IsWarmingUp:
            return

        if not self.emaFast.IsReady:
            return

        if not self.emaSlow.IsReady:
            return

        if not self.rsi.IsReady:
            return

        # ------------------------------------------------------------
        # 3. CALCULATE TARGET WEIGHT
        # ------------------------------------------------------------
        signal_weight = self.InvestmentDecision(
            self._theta,
            self._rho
        )

        # Cap the raw signal between -100% and +100%.
        signal_weight = max(
            -1.0,
            min(1.0, signal_weight)
        )

        final_target_weight = self._leverage * signal_weight

        # ------------------------------------------------------------
        # 4. EXECUTE ONLY WHEN TARGET CHANGES MEANINGFULLY
        # ------------------------------------------------------------
        if abs(final_target_weight - self.current_target_weight) > self.rebalance_threshold:

            self.SetHoldings(
                self.symbol,
                final_target_weight
            )

            self.current_target_weight = final_target_weight

        # ------------------------------------------------------------
        # 5. PLOTS
        # ------------------------------------------------------------
        benchmark_value = (
            self.initial_cash
            * price
            / self._benchmark_price
        )

        self.Plot(
            "Strategy Equity",
            "Buy Hold TSLA",
            benchmark_value
        )

        self.Plot(
            "Strategy Equity",
            "Portfolio Value",
            self.Portfolio.TotalPortfolioValue
        )

        self.Plot(
            "Moving Averages",
            "EMA Fast",
            self.emaFast.Current.Value
        )

        self.Plot(
            "Moving Averages",
            "EMA Slow",
            self.emaSlow.Current.Value
        )

        self.Plot(
            "Moving Averages",
            "TSLA Price",
            price
        )

        self.Plot(
            "RSI",
            "RSI",
            self.rsi.Current.Value
        )

        self.Plot(
            "RSI",
            "Low RSI",
            self.l_rsi
        )

        self.Plot(
            "RSI",
            "High RSI",
            self.h_rsi
        )

        self.Plot(
            "Signal",
            "Raw Signal Weight",
            signal_weight
        )

        self.Plot(
            "Signal",
            "Leveraged Target Weight",
            final_target_weight
        )

    # ------------------------------------------------------------
    # PARAMETER HELPERS
    # ------------------------------------------------------------
    def GetIntParameter(self, name, default_value):

        value = self.GetParameter(name)

        if value is None or value == "":
            return default_value

        return int(value)

    def GetFloatParameter(self, name, default_value):

        value = self.GetParameter(name)

        if value is None or value == "":
            return default_value

        return float(value)

    # ------------------------------------------------------------
    # INVESTMENT DECISION METHOD
    # ------------------------------------------------------------
    def InvestmentDecision(self, theta, rho):

        # ------------------------------------------------------------
        # EMA COMPONENT
        # ------------------------------------------------------------
        if self.emaFast.Current.Value > self.emaSlow.Current.Value:

            weight_ema = theta / 100.0

        else:

            weight_ema = 0.0

        # ------------------------------------------------------------
        # RSI COMPONENT
        # ------------------------------------------------------------
        rsi_value = self.rsi.Current.Value

        if rsi_value > self.h_rsi:

            weight_rsi = rho / 100.0

        elif rsi_value > self.l_rsi and rsi_value <= self.h_rsi:

            weight_rsi = 0.0

        else:

            weight_rsi = -rho / 100.0

        return weight_ema + weight_rsi