Overall Statistics
Total Orders
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Start Equity
100000
End Equity
100000
Net Profit
0%
Sharpe Ratio
0
Sortino Ratio
0
Probabilistic Sharpe Ratio
0%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0
Annual Variance
0
Information Ratio
-1.804
Tracking Error
0.13
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
Portfolio Turnover
0%
Drawdown Recovery
0
#region imports
    using System;
    using System.Linq;
    using QuantConnect;
    using QuantConnect.Util;
    using QuantConnect.Algorithm;
    using QuantConnect.Securities;
    using QuantConnect.Data.Market;
using System.Collections.Generic;
using QuantConnect.Data.UniverseSelection;
#endregion
public class LiquidNonPennyStocksUniverseAlgorithm : QCAlgorithm
{
    public override void Initialize()
    {
        SetStartDate(2021, 1, 1);
        SetEndDate(2021, 4, 1);

        AddUniverse(fundamental =>
        {
            var stocks = fundamental
                .Where(c => c.HasFundamentalData && c.AdjustedPrice > 5 && c.AdjustedPrice < 50)
                .Select(a => a.Symbol).ToHashSet();

            Log($"Coarse filter located {stocks.Count()} stocks");

            var historyBySymbol = stocks.ToDictionary(k=>k, v=> new List<TradeBar>());
            foreach(var bars in History<TradeBar>(stocks, 20, Resolution.Daily))
            {
                foreach(var (symbol, bar) in bars)
                {
                    historyBySymbol[symbol].Add(bar);
                }
            }

            var rsiFiltered = historyBySymbol.Where(a =>
                {
                    if (a.Value == null || a.Value.Count < 20)
                        return false;

                    var rsi = CalculateRsi(a.Value.Select(a => a.Close).ToList(), 3);
                    return rsi <= 10;
                }).Select(a => a.Key)
                .ToList();

            Log($"RSI filter located {rsiFiltered.Count()} stocks");

            return rsiFiltered;
        });
    }

    public override void OnSecuritiesChanged(SecurityChanges changes)
    {
        Log(changes.ToString());
    }

    public static decimal CalculateRsi(List<decimal> prices, int period = 5)
    {
        if (prices == null || prices.Count < period + 1)
        {
            throw new ArgumentException("Not enough price data to compute RSI");
        }

        // 1) Compute price deltas
        var deltas = new decimal[prices.Count - 1];
        for (int i = 1; i < prices.Count; i++)
        {
            deltas[i - 1] = prices[i] - prices[i - 1];
        }

        // 2) Separate gains and losses
        var gain = new decimal[deltas.Length];
        var loss = new decimal[deltas.Length];
        for (int i = 0; i < deltas.Length; i++)
        {
            gain[i] = deltas[i] > 0 ? deltas[i] : 0m;
            loss[i] = deltas[i] < 0 ? -deltas[i] : 0m;
        }

        // 3) First average gain/loss
        decimal avgGain = 0m, avgLoss = 0m;
        for (int i = 0; i < period; i++)
        {
            avgGain += gain[i];
            avgLoss += loss[i];
        }
        avgGain /= period;
        avgLoss /= period;

        // 4) Wilder's smoothing for subsequent points
        for (int i = period; i < gain.Length; i++)
        {
            avgGain = ((period - 1) * avgGain + gain[i]) / period;
            avgLoss = ((period - 1) * avgLoss + loss[i]) / period;
        }

        // 5) Compute final RSI
        if (Math.Abs(avgLoss) < 1e-14M)
        {
            return 100m;
        }
        decimal rs = avgGain / avgLoss;
        decimal rsi = 100m - (100m / (1m + rs));
        return rsi;
    }
}