Overall Statistics |
Total Orders 293 Average Win 0.90% Average Loss -0.67% Compounding Annual Return 16.467% Drawdown 20.100% Expectancy 0.068 Start Equity 100000 End Equity 113822.53 Net Profit 13.823% Sharpe Ratio 0.411 Sortino Ratio 0.503 Probabilistic Sharpe Ratio 35.586% Loss Rate 54% Win Rate 46% Profit-Loss Ratio 1.33 Alpha -0.002 Beta 0.898 Annual Standard Deviation 0.184 Annual Variance 0.034 Information Ratio -0.063 Tracking Error 0.163 Treynor Ratio 0.084 Total Fees $1630.98 Estimated Strategy Capacity $63000.00 Lowest Capacity Asset HNVR XYDJ6GBNI8V9 Portfolio Turnover 13.50% |
# region imports from AlgorithmImports import * from datetime import datetime, timedelta from dataclasses import dataclass import csv from io import StringIO # endregion @dataclass class Signal: Number: int Ticker: str startDate: str endDate: str startPrice: float endPrice: float class StockTradingAlgorithm(QCAlgorithm): def Initialize(self): self.SetCash(100000) self.current_index = 0 self.active_stocks = {} self.number_of_stocks = int( self.GetParameter("number_of_stocks", "7") ) self.holding_period = int( self.GetParameter("holding_period", "7") ) self.frequency = int( self.GetParameter("frequency", "4") ) self.dropbox_url =str( # 5 stocks - FZR5_NOOTC_1W_1YR # self.GetParameter("dropbox_url", "https://www.dropbox.com/scl/fi/07loovahjlaxmuujg41i7/FZR5_NOOTC_1W_1YR.csv?rlkey=cl7mm9h7usfashr7xjdd1iim3&st=n012ed20&dl=1") # 7 stocks - VM1_NOOTC_1W_1YR self.GetParameter("dropbox_url", "https://www.dropbox.com/scl/fi/323i4t3lq5u6524flyb76/VM1_NOOTC_1W_1YR.csv?rlkey=2jimy0z492xq6przr4540arr3&st=w0v3ky1s&dl=1") # 3 stocks - BMZ_NOOTC_1W_1YR # self.GetParameter("dropbox_url", "https://www.dropbox.com/scl/fi/ax36xcsx8bcf1zon6rnzz/BMZ_NOOTC_1W_1YR.csv?rlkey=4xhilujlygqm8dkpg3t9q25cl&st=yqlbitlx&dl=1") ) self.signals = self.GetAllSignals(self.dropbox_url) self.SetStartDate(self.signals[0].startDate - timedelta(days = 10)) self.SetEndDate(self.signals[len(self.signals) -1].endDate + timedelta(days = 10)) self.next_rotation_date = self.signals[0].startDate - timedelta(days = 1) self.Schedule.On(self.DateRules.EveryDay(), self.TimeRules.At(9, 45), self.CheckForRotation) def GetAllSignals(self, dropbox_url) -> List[Signal]: data = self.Download(dropbox_url) signals= [] if data: csv_data = StringIO(data) reader = csv.DictReader(csv_data) signals = [Signal( Number=int(row['Number']), Ticker=row['Ticker'], startDate=datetime.strptime(row['Start Date'], "%m/%d/%Y"), endDate=datetime.strptime(row['End Date'], "%m/%d/%Y"), startPrice=float(row['Start Price']), endPrice = float(row['End Price']) ) for row in reader] return signals def CheckForRotation(self): if self.Time >= self.next_rotation_date: self.RotateStocks() self.next_rotation_date+=timedelta(days=self.holding_period) def RotateStocks(self): if self.current_index >= len(self.signals): for ticker in list(self.active_stocks): self.Schedule.On(self.DateRules.On(self.active_stocks[ticker]['endDate']), self.TimeRules.BeforeMarketClose(ticker, 1), self.ActionOnEnd(ticker, self.active_stocks[ticker]['endPrice'])) del self.active_stocks[ticker] self.Debug("All signals processed") return # Extract the next batch of 3/7 stocks next_stocks_data = self.signals[self.current_index:self.current_index + self.number_of_stocks] self.current_index += self.number_of_stocks next_tickers = {stock.Ticker for stock in next_stocks_data} # Liquidate stocks not in the new list for ticker in list(self.active_stocks): if ticker not in next_tickers: self.Schedule.On(self.DateRules.On(self.active_stocks[ticker]['endDate']), self.TimeRules.BeforeMarketClose(ticker, 1), self.ActionOnEnd(ticker,self.active_stocks[ticker]['endPrice'])) del self.active_stocks[ticker] # Initialize or continue trading the new/continuing stocks for signal in next_stocks_data: if signal.Ticker not in self.active_stocks: self.AddEquity(signal.Ticker, self.frequency) # we buy a minute later than we sell to make sure there is a balance self.Schedule.On(self.DateRules.On(signal.startDate), self.TimeRules.BeforeMarketClose(signal.Ticker, 0), self.ActionOnStart(signal)) self.active_stocks[signal.Ticker] = { 'endDate': signal.endDate, 'endPrice': signal.endPrice } # Buy function - execute open position def ActionOnStart(self, signal): def Action(): # if (self.active_stocks): self.SetHoldings(signal.Ticker, 1.0 / len(self.active_stocks), False, f"Open ${signal.startPrice}") self.Debug(f"Bought {signal.Ticker} on {signal.startDate}") return Action # Sell function def ActionOnEnd(self, ticker, endPrice): def Action(): self.Liquidate(ticker, f"Close ${endPrice}") self.Debug(f"Sold {ticker}") return Action def OnData(self, data): pass