| Overall Statistics |
|
Total Trades 625 Average Win 1.84% Average Loss -1.75% Compounding Annual Return 25.869% Drawdown 32.500% Expectancy 0.415 Net Profit 865.359% Sharpe Ratio 0.894 Probabilistic Sharpe Ratio 32.882% Loss Rate 31% Win Rate 69% Profit-Loss Ratio 1.05 Alpha 0.15 Beta 0.422 Annual Standard Deviation 0.202 Annual Variance 0.041 Information Ratio 0.52 Tracking Error 0.21 Treynor Ratio 0.428 Total Fees $5808.53 Estimated Strategy Capacity $66000000.00 Lowest Capacity Asset UNH R735QTJ8XC9X Portfolio Turnover 7.75% |
from AlgorithmImports import *
class BBandsAlgorithm(QCAlgorithm):
def Initialize(self) -> None:
# backtest settings
self.SetCash(100000)
self.SetStartDate(2014, 1, 1)
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Cash)
self.BBstd = float(self.GetParameter("stdBB")) #4
self.BBvalue = float(self.GetParameter("BB")) #17.6
self.ROCvalue = float(self.GetParameter("ROC")) #24.8
# Initialize an empty dictionary to store last trade times (new addition)
self.lastTradeTimes = {}
# settings
self.EnableAutomaticIndicatorWarmUp = True
self.Settings.FreePortfolioValuePercentage = 0.05
self.UniverseSettings.Resolution = Resolution.Daily
# ETF universe
self.etf = self.AddEquity("SPY", self.UniverseSettings.Resolution).Symbol
self.AddUniverseSelection(
ETFConstituentsUniverseSelectionModel(self.etf, self.UniverseSettings, self.ETFConstituentsFilter))
# Alternative investments
self.alternatives = {
'UUP': self.AddEquity('UUP', self.UniverseSettings.Resolution).Symbol,
'TLT': self.AddEquity('TLT', self.UniverseSettings.Resolution).Symbol,
'GLD': self.AddEquity('GLD', self.UniverseSettings.Resolution).Symbol
}
self.SetBenchmark(self.etf)
self.symbolData = {}
self.universe = []
self.buy_prices = {}
def OnData(self, data: Slice) -> None:
# liquidate assets that we should not trade
for symbol in [x.Key for x in self.Portfolio if x.Value.Invested]:
if symbol not in self.universe or not self.Securities[symbol].IsTradable:
self.Liquidate(symbol)
# Calculate the ROC for all symbols and select the max ROC symbol
roc_values = {symbol: self.symbolData[symbol].roc.Current.Value for symbol in self.universe if
symbol in self.symbolData}
if roc_values:
max_roc_symbol = max(roc_values, key=roc_values.get)
max_roc = roc_values[max_roc_symbol]
else:
max_roc_symbol = None
max_roc = None
# Check if all ROCs are negative and liquidate/reallocate if necessary
if all(value < 0 for value in roc_values.values()):
self.Liquidate()
self.Reallocate()
return
# Implement the Buy Conditions
if max_roc_symbol and self.CanInvest() and max_roc > 0:
price = self.Securities[max_roc_symbol].Price
symbolData = self.symbolData[max_roc_symbol]
if symbolData.bb.MiddleBand.Current.Value < price < symbolData.bb.UpperBand.Current.Value:
self.SetHoldings(max_roc_symbol, 1)
self.buy_prices[max_roc_symbol] = price
self.lastTradeTimes[
max_roc_symbol] = self.Time # Update the last trade time when a new holding is set (new addition)
# Implement the Sell Conditions
for symbol in self.Portfolio.Keys:
price = self.Securities[symbol].Price
if self.Portfolio[symbol].Invested:
# Profit taking
if price >= 1.05 * self.buy_prices.get(symbol, 0):
self.Liquidate(symbol)
continue
# Stop Loss
if price <= 0.95 * self.buy_prices.get(symbol, self.Portfolio[symbol].AveragePrice):
self.Liquidate(symbol)
# self.stop_loss_triggered = True
continue
# Time-based Exit
if symbol in self.lastTradeTimes: # Check if the last trade time exists (new addition)
time_invested = (self.Time - self.lastTradeTimes[
symbol]).days # Use the stored last trade time (modified line)
if time_invested > 10:
self.Liquidate(symbol)
continue
def ETFConstituentsFilter(self, constituents: List[ETFConstituentData]) -> List[Symbol]:
# validate in-data
if constituents is None:
return Universe.Unchanged
selected = [c for c in constituents if c.Weight]
sorted_selected = sorted([c for c in selected], key=lambda c: c.Weight, reverse=True)[:(int(self.GetParameter("selected")))] #20
self.universe = [c.Symbol for c in sorted_selected]
return self.universe
def OnSecuritiesChanged(self, changes: SecurityChanges) -> None:
# validate in-data
if changes is None:
return
for security in changes.AddedSecurities:
self.symbolData[security.Symbol] = SymbolData(self, security.Symbol)
for security in changes.RemovedSecurities:
self.Liquidate(security.Symbol)
symbolData = self.symbolData.pop(security.Symbol, None)
if symbolData:
symbolData.dispose()
def CanInvest(self) -> bool:
return sum(1 for x in self.Portfolio if x.Value.Invested) < 10
def Reallocate(self) -> None:
for symbol, security in self.alternatives.items():
self.SetHoldings(security, 1 / len(self.alternatives))
def OnOrderEvent(self, orderEvent: OrderEvent) -> None:
if orderEvent.Status == OrderStatus.Filled:
self.Debug(str(orderEvent))
class SymbolData(object):
def __init__(self, algorithm, symbol):
self.algorithm = algorithm
self.symbol = symbol
self.stdBB = float(algorithm.BBstd)
self.BBi = int(algorithm.BBvalue)
self.ROCi = int(algorithm.ROCvalue)
# Assuming 'BB' and 'ROC' are methods provided by QuantConnect's QCAlgorithm class
self.bb = algorithm.BB(symbol, self.BBi, self.stdBB, MovingAverageType.Simple, Resolution.Daily)
self.roc = algorithm.ROC(symbol, self.ROCi, Resolution.Daily)
def dispose(self):
# deregister indicators and remove consolidator
pass