| 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