Overall Statistics |
Total Orders 10 Average Win 0% Average Loss 0% Compounding Annual Return 205.756% Drawdown 0.800% Expectancy 0 Start Equity 10000 End Equity 10374.27 Net Profit 3.743% Sharpe Ratio 8.168 Sortino Ratio 37.678 Probabilistic Sharpe Ratio 86.790% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha 1.742 Beta -0.585 Annual Standard Deviation 0.158 Annual Variance 0.025 Information Ratio 2.828 Tracking Error 0.184 Treynor Ratio -2.209 Total Fees $23.64 Estimated Strategy Capacity $160000.00 Lowest Capacity Asset TLSA WZ7AA7WL40H1 Portfolio Turnover 4.15% |
from AlgorithmImports import * class MarketNeutralPennyStocksAlgorithm(QCAlgorithm): def Initialize(self): self.SetStartDate(2024, 6, 1) self.set_end_date(2024, 7, 1) self.SetCash(10_000) self.SetSecurityInitializer(BrokerageModelSecurityInitializer( self.BrokerageModel, FuncSecuritySeeder(self.GetLastKnownPrices) )) self.UniverseSettings.Resolution = Resolution.Daily self.momentum_indicators = {} self.lookback_period = 252 self.num_coarse = 100 self.num_fine = 100 self.num_long = 5 self.num_short = 5 self.current_month = -1 self.rebalance_flag = True self.SetWarmUp(self.lookback_period, Resolution.Daily) self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction) def CoarseSelectionFunction(self, coarse): if self.current_month == self.Time.month: return Universe.Unchanged self.rebalance_flag = True self.current_month = self.Time.month selected = sorted( [x for x in coarse if x.HasFundamentalData and x.Price < 1 and x.MarketCap < 1e9], key=lambda x: x.DollarVolume, reverse=True ) self.Debug(f"Coarse selected: {len(selected)} securities") return [x.Symbol for x in selected[:self.num_coarse]] def FineSelectionFunction(self, fine): selected = sorted(fine, key=lambda f: f.MarketCap, reverse=True) self.Debug(f"Fine selected: {len(selected)} securities") return [x.Symbol for x in selected[:self.num_fine]] def OnData(self, data): for symbol, momentum in self.momentum_indicators.items(): if symbol in data and data[symbol] is not None: momentum.Update(self.Time, data[symbol].Close) if not self.rebalance_flag: return sorted_momentum = sorted( [k for k, v in self.momentum_indicators.items() if v.IsReady], key=lambda x: self.momentum_indicators[x].Current.Value, reverse=True ) self.Debug(f"Sorted momentum: {len(sorted_momentum)} securities") long_selected = sorted_momentum[:self.num_long] self.Debug(f"LONG {long_selected}") short_selected = sorted_momentum[-self.num_short:] self.Debug(f"SHORT {short_selected}") for symbol in list(self.Portfolio.Keys): if symbol not in long_selected and symbol not in short_selected: self.Liquidate(symbol, 'Not selected') initial_investment = 500 additional_investment = 250 for symbol in long_selected: current_investment = self.Portfolio[symbol].Invested if current_investment: self.MarketOnOpenOrder(symbol, additional_investment / data[symbol].Close) stop_price = data[symbol].Close * 1.18 self.stop_limit_order(symbol, self.Portfolio[symbol].Quantity , stop_price, stop_price) self.Debug(f"Long {symbol} Price {data[symbol].Close} Qty {int(additional_investment / data[symbol].Close)} Stop price {stop_price}") else: self.MarketOnOpenOrder(symbol, initial_investment / data[symbol].Close) stop_price = data[symbol].Close * 1.18 self.stop_limit_order(symbol, self.Portfolio[symbol].Quantity , stop_price, stop_price) self.Debug(f"Long {symbol} Price {data[symbol].Close} Qty {int(initial_investment / data[symbol].Close)} Stop price {stop_price}") for symbol in short_selected: current_investment = self.Portfolio[symbol].Invested if current_investment: if self.securities[symbol].is_tradable: self.MarketOnOpenOrder(symbol, - int(additional_investment / data[symbol].Close)) stop_price = data[symbol].Close * 1.02 self.stop_limit_order(symbol, self.Portfolio[symbol].Quantity , stop_price, stop_price) self.Debug(f"Short {symbol} Price {data[symbol].Close} Qty {- int(additional_investment / data[symbol].Close)} Stop price {stop_price}") else: if self.securities[symbol].is_tradable: self.MarketOnOpenOrder(symbol, - int(initial_investment / data[symbol].Close)) stop_price = data[symbol].Close * 1.02 self.stop_limit_order(symbol, self.Portfolio[symbol].Quantity , stop_price, stop_price) self.Debug(f"Short {symbol} Price {data[symbol].Close} Qty {- int(initial_investment / data[symbol].Close)} Stop price {stop_price}") self.rebalance_flag = False def OnSecuritiesChanged(self, changes): for security in changes.RemovedSecurities: symbol = security.Symbol if symbol in self.momentum_indicators: self.momentum_indicators.pop(symbol) self.Liquidate(symbol, 'Removed from universe') for security in changes.AddedSecurities: symbol = security.Symbol if symbol not in self.momentum_indicators: self.momentum_indicators[symbol] = MomentumPercent(self.lookback_period) history = self.History(symbol, self.lookback_period, Resolution.Daily) # Check if history data is available for the symbol if not history.empty and symbol in history.index.levels[0]: for time, row in history.loc[symbol].iterrows(): self.momentum_indicators[symbol].Update(time, row['close']) else: self.Debug(f"No historical data available for symbol {symbol}")