| Overall Statistics |
|
Total Orders 13 Average Win 0.03% Average Loss -1.13% Compounding Annual Return 27.773% Drawdown 6.300% Expectancy -0.488 Start Equity 1000000 End Equity 1277443.06 Net Profit 27.744% Sharpe Ratio 1.477 Sortino Ratio 1.978 Probabilistic Sharpe Ratio 76.739% Loss Rate 50% Win Rate 50% Profit-Loss Ratio 0.02 Alpha 0.07 Beta 0.506 Annual Standard Deviation 0.111 Annual Variance 0.012 Information Ratio -0.195 Tracking Error 0.111 Treynor Ratio 0.324 Total Fees $122.43 Estimated Strategy Capacity $0 Lowest Capacity Asset AMD R735QTJ8XC9X Portfolio Turnover 0.42% |
from AlgorithmImports import *
class MomentumRSIBBAlgorithm(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2019, 1, 1)
self.SetEndDate(2020, 1, 1)
self.SetCash(1000000)
# Define stocks
self.symbols = [self.AddEquity("AMD", Resolution.Daily).Symbol,
self.AddEquity("JNJ", Resolution.Daily).Symbol]
# Initialize indicators
self.indicators = {
symbol: {
'rsi': self.RSI(symbol, 14), ## 14-day lookback
'bb': self.BB(symbol, 20, 2, MovingAverageType.Simple), ## 2 standard deviations, 20-day period
'sma': self.SMA(symbol, 50) ## 50-day SMA
} for symbol in self.symbols
}
# Schedule daily rebalancing after market opens
self.Schedule.On(self.DateRules.EveryDay(),
self.TimeRules.AfterMarketOpen("AMD", 10), ## when we specify "AMD" here, it's just used as a reference time. The rebalancing happens for both stocks. It's like saying "10 minutes after the market opens based on AMD's trading hours"
self.Rebalance)
def Rebalance(self):
for symbol in self.symbols:
if not all(indicator.IsReady for indicator in self.indicators[symbol].values()):
continue # Skip if indicators aren't ready
# Get indicator values
price = self.Securities[symbol].Price
rsi = self.indicators[symbol]['rsi'].Current.Value
upper_bb = self.indicators[symbol]['bb'].UpperBand.Current.Value
lower_bb = self.indicators[symbol]['bb'].LowerBand.Current.Value
sma = self.indicators[symbol]['sma'].Current.Value
# Determine trend direction
trend = "Up" if price > sma else "Down"
# trend reversing element:
## Long when price is below BB lower band, and RSI shows oversold (< 30)
## Short when price is above BB Upper band, and RSI shows overbought (> 70)
# trend-following element with the SMA:
## Only goes long if price > SMA (uptrend) and only goes short if price < SMA (downtrend)
# Trading logic
if trend == "Up" and rsi < 30 and price < lower_bb: ## indicate oversold if stock starts passing lower than 30
self.SetHoldings(symbol, 0.5) # Buy signal: 50% long
elif trend == "Down" and rsi > 70 and price > upper_bb: ## indicate overbought if stock starts passing higher than 70
self.SetHoldings(symbol, -0.25) # Sell signal: 25% short
# Ensure minimum 25% absolute weight per stock
current_weight = abs(self.Portfolio[symbol].HoldingsValue / self.Portfolio.TotalPortfolioValue)
if current_weight < 0.25:
self.SetHoldings(symbol, 0.25 if trend == "Up" else -0.25)