US examples
Penny stock risk filter
On this page4
A revenue momentum strategy with price, volume and size filters, so it doesn't buy illiquid, low-quality or financially distressed stocks. Penny stocks, micro-caps and low-volume names carry outsized risks that can silently erode a strategy's performance; the filters here can be added to any US strategy.
Strategy logic
- Revenue momentum: trailing 3-month cumulative revenue at a 2-year high.
- No penny stocks: price above $5.
- Enough volume: 20-day average daily volume above 100,000 shares.
- No micro-caps: market cap above $300 million.
- Among stocks passing all four, hold the 10 with the highest year-over-year revenue growth, rebalanced monthly.
Why filter these stocks
US exchanges do not flag a category of financially distressed stocks (Taiwan, for example, flags "full cash delivery" stocks that require full upfront payment), but similar risks exist in other forms.
- Penny stocks: the US Securities and Exchange Commission (SEC) defines penny stocks as securities trading below $5. They have wide bid-ask spreads that inflate transaction costs, low institutional ownership and analyst coverage, a higher exposure to pump-and-dump manipulation and frequent delisting risk.
- Low-volume stocks: with average daily volume below 100,000 shares, large orders can move the price significantly, and positions may be hard to exit quickly during market stress.
- Micro-caps: companies below $300 million in market cap are more likely to have volatile and unpredictable earnings, limited financial disclosure quality and higher bankruptcy risk. They also bring survivorship bias into backtests: many delisted stocks were once small caps.
Code
from finlab import data
from finlab.backtest import sim
from finlab.markets.us import USMarket
data.set_market('us')
close = data.get('price:adj_close')
close = close[close.index.dayofweek < 5] # remove weekends
# Revenue momentum: quarterly revenue at 2-year high
revenue = data.get('us_income_statement:revenue')
rev_trailing_3q = revenue.rolling(3).sum()
cond_rev = rev_trailing_3q == rev_trailing_3q.rolling(8, min_periods=4).max()
# Revenue growth rate for ranking
rev_growth = revenue / revenue.shift(4) - 1
# --- Risk Filters ---
# Filter 1: Remove penny stocks (price < $5)
cond_price = close > 5
# Filter 2: Volume filter (average daily volume > 100K shares)
volume = data.get('price:volume')
volume = volume[volume.index.dayofweek < 5]
avg_volume = volume.rolling(20).mean()
cond_volume = avg_volume > 100000
# Filter 3: Market cap filter (> $300M)
market_cap = data.get('etl:us_market_value')
cond_mcap = market_cap > 300000000
# Combine all conditions
cond_all = cond_rev & cond_price & cond_volume & cond_mcap
# Rank by revenue growth, select top 10
result = rev_growth * cond_all
position = result[result > 0].is_largest(10)
report = sim(position, resample='M', market=USMarket(), fee_ratio=0.001, tax_ratio=0)
report.display()How to adjust it
The filters are additive: to apply them to any existing strategy, add & cond_price & cond_volume & cond_mcap to its final position condition.
close > 5: removes every stock trading below $5. This single filter eliminates the vast majority of pump-and-dump candidates, shell companies and near-delisting stocks. It also makes the backtest more realistic, since historical penny stock data often suffers from survivorship bias.avg_volume > 100000: uses a 20-day rolling average of daily share volume. 100,000 shares lets a strategy allocating reasonable capital (for example $100K–$1M per position) enter and exit without significant market impact.market_cap > 300000000: a $300 million floor removes micro-caps, which are disproportionately represented in backtested alpha signals. Many academic anomalies (such as value and momentum) earn much of their historical alpha from micro-caps that are practically untradeable at scale.revenue.rolling(3).sum(): trailing 3-quarter cumulative revenue smooths out single-quarter noise while staying responsive to recent trends.rolling(8, min_periods=4).max(): a 2-year (8-quarter) rolling maximum that flags revenue breakouts.rev_growth = revenue / revenue.shift(4) - 1: year-over-year quarterly growth removes seasonality and gives a clean ranking metric.is_largest(10): the 10 stocks with the highest revenue growth among those passing every filter.resample='M': monthly rebalancing balances the quarterly cadence of revenue updates with the need to respond to price and volume changes.fee_ratio=0.001: a 0.1% commission.tax_ratio=0: US stock trades carry no transaction tax.
The thresholds ($5, 100K shares, $300M) are reasonable defaults; adjust them to your capital size and risk tolerance. For larger portfolios, consider raising the market cap floor to $1B and the volume floor to 500K shares.
Related pages
- Quality small caps Small caps picked with cash flow, ROE and valuation checks.
- ADR financial filter Another add-on filter: drop foreign listings.
- Backtesting Check a strategy's capacity with dollar volume.
- Risk management Position sizing, stops and drawdown control.
Was this page helpful?