# Scans that say "most of the last ten days"

> How count() and countstreak() let a scan ask for seven of the last ten days instead of all ten, with four NSE scans built on them and the limits of both functions.

Canonical: https://patternsradar.com/blog/count-the-bars-scans-for-most-of-the-time

Published 2026-08-29.

A healthy uptrend closes below its 20-day average now and then. One red day in ten is normal and two is not alarming, which is why the persistence scan everyone writes first is the wrong scan. Demand ten consecutive closes above the line and you do not get healthy uptrends. You get the small subset that happened not to blink, and the scan drops a stock the morning after one ordinary pullback.

I ran the strict version for weeks and kept wondering why it returned four names on a good day. The question I meant was never "every day". It was "most days", and no form-based screener I had used could ask it.

## What the two functions do

Both take a condition and a window, and both hand back a number.

```
count(close > open, 10)
```

is how many of the last ten sessions closed above their open, somewhere between 0 and 10. Compare that number to something and you have a scan:

```
where count(close > open, 10) >= 7
```

Seven up days out of ten. Any seven. The three down days can sit anywhere in the fortnight.

```
countstreak(close > sma(20), 60)
```

measures something else. It counts back from today and stops at the first session where the condition failed, so it answers "how many in a row, ending now". Sixty is only how far it may look. A stock that closed under the line today reads 0 whatever it did for the fifty-nine sessions before, and a stock that has not closed under it anywhere in the window reads 60.

The condition inside can be anything a `where` clause accepts: a comparison, a volume multiple, a crossing, a pattern (`count(pattern is doji, 10)`), even another count. A bar where the condition cannot be evaluated, because the window is not full yet or a field is empty, counts as unmatched, so the result is never null. It is a number and it behaves like one. Sort by it, divide it by the window to get a fraction, or feed it to an event operator: `count(close > open, 10) rising for 3 bars` finds a stock whose fortnight is getting greener.

## Why this could not be written before

The workaround in a form-based screener is a stack of offsets, `close[-1] > open[-1] and close[-2] > open[-2] and …`. That says "every one of them", which is what `has been above … for 10 bars` compiles to underneath anyway. It cannot say "at least seven" without enumerating every combination of which three days are allowed to fail, and for ten choose seven that is 120 clauses. Nobody writes 120 clauses. So the persistence scans in circulation are all the strict kind, and everybody quietly wonders why they return so little.

Offsets also cannot tell you *how many*, as a value rather than a yes or no. `count` returns the number, which is why `sort by count(volume > 2x avg(volume, 20), 20) desc` fits on one line: this month's most-spiked stocks, most first.

## Four ready scans

Each is a preset you can open, run and edit, and each scan page carries a hit-rate replay over the past 250 NSE sessions. That replay is a sketch, close to close, with no costs or slippage in it, and it is enough to see what past matches did over the next 1, 5 and 20 sessions.

**[Seven up days in ten](https://patternsradar.com/screener/seven-of-ten-up.md)**, persistence with the tolerance built in:

```
where count(close > open, 10) >= 7
  and close > sma(50)
  and rsi(14) < 70
```

The 50-day filter keeps it to established uptrends. The RSI ceiling exists because I added it after the first version handed me a list of names that had already sprinted, which a seven-of-ten condition is prone to on its own.

**[Three volume spikes this month](https://patternsradar.com/screener/repeated-volume-spikes.md)**, accumulation in instalments:

```
where count(volume > 2x avg(volume, 20), 20) >= 3
  and close > sma(50)
```

One session at twice normal volume is a news day. Three of them inside a month, in a stock holding above its 50-day, is somebody building a position across several fills. The count is what lets the scan find the stock on the quiet days in between, when the entry is cheaper than on the spike itself.

**[Longest streaks above the 21 EMA](https://patternsradar.com/screener/longest-streaks-above-21-ema.md)**, the unbroken version, ranked by how unbroken:

```
where countstreak(close > ema(21), 60) >= 20
  and rsi(14) < 75
sort by countstreak(close > ema(21), 60) desc
```

Twenty consecutive closes above the 21-day EMA is a month without a day off. Sorting by the streak puts the most relentless trends at the top, which a `has been above` condition can find but never rank.

**[First up day after a selloff](https://patternsradar.com/screener/first-up-day-after-selloff.md)** uses a count for the setup and exact conditions for the trigger:

```
where count(close < open, 10) >= 7
  and close > open
  and close > high[-1]
```

Seven or more down sessions in ten is a selloff with some persistence behind it, and one green bar in the middle should not disqualify the stock, so the run is counted rather than demanded. The turn itself is checked exactly: today closed up and above yesterday's high. That is the one bar that has to be right.

## Choosing the window and the threshold

Both numbers are decisions. The window sets the timeframe of the claim, with ten sessions a fortnight, twenty a month, sixty a quarter. The threshold sets strictness, and it is worth thinking about as a fraction. Seven of ten and fourteen of twenty both come to 70%, but the second is the stronger statement, because being right 70% of the time over a longer stretch is harder to do by luck. It will also return fewer names.

When the window is large I write the fraction out: `count(close > sma(20), 60) / 60 > 0.8` reads as "above the 20-day four days in five over the quarter".

For streaks the window hardly matters as long as it exceeds any streak you care about. It caps how far the scan looks and is not a parameter of the setup. Sixty is a fine default on daily bars.

## One limit

A count re-asks its condition once per bar of its window, so a count inside a count multiplies. `count(count(close > open, 5) >= 3, 20)`, meaning how many of the last twenty sessions ended a fortnight with a green majority, expands to a hundred comparisons, and deeper nesting grows quickly from there. The compiler budgets a whole query at 20,000 bar comparisons and refuses anything past that, marking the offending span. Nothing I have wanted to scan for has come close. The limit is there so nothing accidental gets there either.

A count cannot sit inside a window function, so `avg(count(…), 20)` is refused: a count already is one. Put the window on the count's condition, or widen the count.

## Run the scans from this guide

Each opens live on today's data, and each can be replayed against a year of sessions before you trust it.

- [Seven up days in ten](https://patternsradar.com/screener/seven-of-ten-up.md): Seven of the last ten sessions closed above their open, in a stock above its 50-day with RSI under 70.
- [Three volume spikes this month](https://patternsradar.com/screener/repeated-volume-spikes.md): At least three sessions of twice-normal volume inside the last twenty, in a stock above its 50-day average. One news day does not clear that bar.
- [Longest streaks above the 21 EMA](https://patternsradar.com/screener/longest-streaks-above-21-ema.md): Twenty or more consecutive closes above the 21-day EMA, longest streak first.
- [First up day after a selloff](https://patternsradar.com/screener/first-up-day-after-selloff.md): Seven or more down days in the last ten, then a close above the previous day's high. The first session the sellers lost.

---

Price and delivery data from the [eod2](https://github.com/BennyThadikaran/eod2) dataset: National Stock Exchange of India end-of-day files, split- and bonus-adjusted, updated after each close. Not affiliated with or endorsed by NSE. PatternsRadar is a research tool. Nothing here is investment advice or a recommendation to buy or sell anything.
