Overall Statistics |
Total Orders 385 Average Win 4.81% Average Loss -1.18% Compounding Annual Return 455.319% Drawdown 16.500% Expectancy 1.356 Start Equity 10000 End Equity 31457.32 Net Profit 214.573% Sharpe Ratio 6.978 Sortino Ratio 10.353 Probabilistic Sharpe Ratio 99.768% Loss Rate 54% Win Rate 46% Profit-Loss Ratio 4.09 Alpha 0 Beta 0 Annual Standard Deviation 0.356 Annual Variance 0.127 Information Ratio 7.133 Tracking Error 0.356 Treynor Ratio 0 Total Fees $697.99 Estimated Strategy Capacity $31000.00 Lowest Capacity Asset XELB W2NKAYLQ27HH Portfolio Turnover 4.44% |
from AlgorithmImports import * class MarketNeutralPennyStocksAlgorithm(QCAlgorithm): def Initialize(self): self.SetStartDate(2023, 9, 1) self.set_end_date(2024, 5, 1) self.SetCash(10_000) self.set_brokerage_model(BrokerageName.INTERACTIVE_BROKERS_BROKERAGE, AccountType.Margin) self.SetSecurityInitializer(BrokerageModelSecurityInitializer( self.BrokerageModel, FuncSecuritySeeder(self.GetLastKnownPrices) )) self.UniverseSettings.Resolution = Resolution.Daily self.momentum_indicators = {} self.lookback_period = 252 self.num_coarse = 300 self.num_fine = 300 self.num_long = 10 self.num_short = 10 self.current_month = 6 self.rebalance_flag = True 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.MarketOrder(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.MarketOrder(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.MarketOrder(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.MarketOrder(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}")