# The Market Profile 80% rule on ES futures, 1-minute data - specification (RIP Backtest protocol v1.0 plus the 8 Oct 2026 rules)

Specification written and **frozen 2026-11-13 (approved by the owner the same day: "aprob"; draft 1 unchanged)**, before any value area, signal or trade of this test was computed on any market. Once frozen, any change of a rule is documented and treated as a separate test; bugs may be fixed.

## Why this test, and what was looked at before
- **Chosen by the owner** on 2026-11-13 ("Regula 80% pe ES"), after being asked which popular futures strategy to test next. Search volumes were not measured (no keyword tool).
- **The idea (the classic published rule):** Market Profile, the way of charting price by time spent at each price, comes from J. Peter Steidlmayer at the Chicago Board of Trade. The 80% rule is set out in *Mind Over Markets* (James Dalton, Eric Jones and Robert Dalton, 1990). It says:
  - take the **value area** of the previous regular session, the prices that hold about 70% of its activity around the busiest price (the Point of Control, POC);
  - if today's session **opens outside that value area**,
  - and price comes **back into it and stays there for two consecutive 30-minute periods**,
  - then price is likely to **cross the whole value area to its other edge**. The "80%" is the claimed share of such days.
  - The trade: short after an open above the value area that comes back in, long after an open below it; the target is the opposite edge of the value area.
- **What the classic texts leave open, and how it is fixed here:**
  - The value area can be built from **time at price (TPO)** or from **volume**. The original is TPO from 30-minute periods; most modern platforms default to volume. Both are tested (variants below).
  - "Two consecutive periods in the value area" is read in two ways: each period **closes** inside it, or each period **trades** inside it (a TPO printed inside). Both are tested on the TPO profile.
  - No stop is given. We fix one structural stop for all variants, before any result: 1 tick beyond the day's extreme on the side price came from.
- **Looked at before (disclosed):**
  - **POC bounce on NQ** (published 2026-11-09): the related volume-profile test. It faded a pierce of the previous session's volume POC on 5-minute bars. 3 variants, all Buried (best 2 of 6). That test built its profile from the same kind of 1-minute data (each minute's volume spread over its ticks); the volume value area here uses the same spreading.
  - On **ES**, published: the opening gap fill (closest in spirit: an open away from yesterday, faded back; 3 variants, Buried for costs), the Initial Balance breakout (a Market Profile idea; Buried), VWAP, VWAP bands, Fibonacci scalping, noise area, intraday momentum, NR7, TheStrat, Silver Bullet, Standard Error Bands and others, all Buried. Unpublished ES work without a protocol (September 2026): opening range breakouts and the owner's ORB with fair value gaps, no edge.
  - **No value area, Market Profile TPO count or 80% rule has been computed on the site**, on any market.
- **Development bar t >= 2.5.** This test has 3 variants. Counted with the 3 variants of the POC NQ test (both are rules on the previous session's profile), more than four versions of the family have been tried, so the stricter bar applies.
- **Why it might not work (written before any result):** the 80% figure is an old trader's estimate from pit-traded markets, not a published dataset; ES often trends away after an open outside value; and price may already have crossed part of the value area by the time two periods have closed inside it, so what is left to gain can be small next to the stop.

## Instrument and data
- **ES** (E-mini S&P 500 futures), `ES_1min.csv` (NinjaTrader, back-adjusted continuous contract), the file of all our ES tests.
  - 1-minute bars on the New York clock, stamped at their close, all trading hours, with volume.
  - 2020-01-01 18:01 to 2026-09-25 16:59; cut at **2026-09-25 17:00 New York** as in every ES test.
  - $50 a point, tick 0.25 point ($12.50).
- **Back-adjustment:** at each roll the older prices are shifted by the roll gap, so yesterday's value area and today's open are always on the same price scale. Volume before a roll is the old contract's.
- **Regular session (RTH):** 09:30 to 16:00 New York, the minutes stamped 09:31 to 16:00, with the exchange calendar of our ES tests (`rb_lib.js`):
  - early closes at 13:15 (the day after Thanksgiving, 24 December, 3 July when not the holiday): **short regular sessions**, used, ending at 13:15;
  - the 13:00 closes on US market holidays (Martin Luther King Day, Presidents Day, Memorial Day, Juneteenth from 2022, Independence Day, Labor Day, Thanksgiving): **the stock market is closed**, the futures trade a short holiday session. These days are **not regular sessions**: no value area is built from them and no trade is taken on them. The next day uses the last regular session before the holiday.
  - days with no regular-session minute (Good Friday, Christmas, New Year's Day, 9 January 2025) are not sessions.
- **The previous session** of a day is the last earlier regular session in the data. The first session (2020-01-02) has none and gives no trade.
- **Open:** the open of the session's first minute (normally stamped 09:31).

## The value area of the previous session
Built from the previous regular session's minutes, 09:31 to its close. Prices are counted in rows of **one tick (0.25 point)**.
- **TPO profile (the classic one, variants MP80-TC and MP80-TP):**
  - the session is cut into **30-minute periods** on the clock: 09:30-10:00 (minutes stamped 09:31 to 10:00), 10:00-10:30, and so on; 13 periods on a full day, 8 on a 13:15 day (the last one 15 minutes);
  - each period marks **one TPO at every tick from its low to its high** (its range from its 1-minute bars);
  - a 1-minute time-at-price count was not chosen: the classic profile uses 30-minute periods.
- **Volume profile (variant MP80-VC):** each minute's volume is spread evenly over the ticks from its low to its high, both included, as in the POC NQ test. This approximates a tick-by-tick volume profile, and it is said so.
- **POC:** the row with the highest count.
  - TPO ties: the row nearest the middle of the session's range, then the lower row (the usual Market Profile rule).
  - Volume ties: as in the POC NQ test, the row nearest the session VWAP, then the lower row.
- **Value area, 70%, built outward from the POC two rows at a time** (the method published with the Market Profile):
  1. Start with the POC row.
  2. Compare the sum of the next two rows above the area with the sum of the next two rows below it. Where fewer than two rows remain on a side, use what is left; a side with nothing left is not compared.
  3. Add the larger pair to the area. On a tie, add both pairs.
  4. Repeat until the area holds **at least 70%** of the session's total count.
  - **VAH** = the top row of the area, **VAL** = the bottom row.
- Platforms often group rows (for example 1 point), and some add one row at a time. These choices move the edges by a point or so; ours are fixed here.

## Signals (one trade a day per variant)
- **Setup:** the session **opens above the VAH** (open > VAH) or **below the VAL** (open < VAL) of the previous session. An open at or between the edges gives no trade. The direction is fixed by the open:
  - **open above:** the trade is a **short**, target the **VAL**;
  - **open below:** a **long**, target the **VAH**.
- **Today's 30-minute periods** are cut on the clock as above. Two periods are consecutive when they are next to each other on the clock and both have at least one minute.
- **"Accepted" in the value area, the close reading (MP80-TC, MP80-VC):**
  - two consecutive periods each **close** at or between VAL and VAH (the close of the period's last minute);
  - the first such pair of the day counts. It can start with the opening period;
  - **entry at the open of the next minute** after the second period, if that minute is in the session. A pair whose second period ends the session gives no trade.
- **"Accepted", the TPO print reading (MP80-TP):**
  - a period **prints inside** the value area when its range reaches it (its low <= VAH and its high >= VAL);
  - once a period has printed inside, the trade is entered **in the next period, at the first minute that reaches the value area**:
    - if that minute opens between VAL and VAH, the fill is at its open;
    - if it opens beyond the edge price came from and trades back to it, the fill is at that edge (the VAH for a short, the VAL for a long);
  - if the next period never reaches the value area, the count starts again from the next period that prints inside.
- **The value area already filled:** if the target edge has traded at any minute from the open up to the entry, the value area is already crossed. That day gives **no trade**.
- **Stop:** 1 tick beyond the **session's extreme on the side price came from**, from the open up to the minute before the entry: the session high + 0.25 for a short, the session low - 0.25 for a long. R = the distance from the entry to the stop, in points.
- **Untradable first signal:** if the entry is at or beyond the stop, or at or beyond the target, there is no trade that day. Only the day's first acceptance counts, and no new trade is taken after an exit.
- **Exits on 1-minute bars:**
  - **target** at the opposite edge of the value area;
  - **stop** as above;
  - otherwise **at the session close**: the close of the last minute stamped at or before 16:00, or the early close.
  - A level that a minute opens beyond fills at that open. If the stop and the target are touched in the same minute, the stop is taken.
  - When the entry is at a minute's open, that whole minute is after the entry: stop, then target, by range. When the fill is at the edge inside a minute (MP80-TP), the order inside that minute is unknown, so only the stop is checked in it.

## Variants (3)
1. **MP80-TC:** TPO value area, two 30-minute closes inside. The main reading.
2. **MP80-TP:** TPO value area, two consecutive periods printing inside; entry when the second one reaches the value area.
3. **MP80-VC:** volume value area, two 30-minute closes inside.

The volume profile with the print reading is not run. Each variant changes one choice from the main reading.

## Costs
- **$30.76 per round trip per contract** (commission $5.76 plus one tick of slippage, $12.50, on each fill), as in all our ES tests. A limit fill at the target may get no slippage in reality; the full cost is kept.
- Results in dollars per contract and in R.

## Periods, and the expected number of trades
- **Periods (by entry date), as in all ES tests:**
  - development 2020-01-03 to 2023-12-31, about 1,000 regular sessions;
  - validation 2024-01 to 2025-03, about 310;
  - holdout 2025-04 to 2026-09-25, about 370, **already used** by earlier ES tests.
- **Estimate of the trade count, written before any computation.** It is from assumptions, not from the data:
  - an open outside the previous value area on **35-50%** of sessions;
  - re-entry with acceptance on **25-45%** of those days for the close reading, **40-60%** for the print reading.

  | | development | validation | holdout |
  |---|---|---|---|
  | close reading (MP80-TC, MP80-VC), range | 90-225 | 25-70 | 30-85 |
  | close reading, middle guess | ~150 | **~45** | ~55 |
  | print reading (MP80-TP), middle guess | ~210 | ~65 | ~80 |

- **The 50-trade gate may fail by construction** in validation for the two close-reading variants. If it does, the protocol rule applies unchanged:
  - fewer than 30 trades in all: **Inconclusive**;
  - otherwise a variant that fails only the trade count is **Buried by its trade count**, as RSI(2) RS2-5 on SPY was, and that is said plainly.
  - The periods are not changed to avoid this.

## Gates and control
- **Six gates per variant** (net dollars per contract):
  1. validation profit factor >= 1.2;
  2. at least 50 trades in each period;
  3. development **t >= 2.5**;
  4. above the 95th percentile of the control;
  5. positive without the 10 best trades;
  6. holdout net positive (flagged as used).

  A variant with fewer than 30 trades is Inconclusive.
- **Control (moved entries), as in the POC NQ test:**
  - every real trade keeps its entry time of day, direction, stop distance and target distance in points, and the session-close exit;
  - all trades move together to other eligible sessions (regular sessions with a previous value area) by one random circular offset of at least 5% of the sessions;
  - on the new day each trade enters at the market, at the open of the first minute at or after the same time; a trade that would start after that day's close is dropped;
  - same exits and costs; 500 runs, seeds 1000 + run; statistic: the average net dollars per trade.
  - It asks whether the 80% rule picks better moments than the same trades placed on other days.
- **Rule 3 (data holes):** the known ES holes (`data_holes.json`) plus any run of 10 or more missing minutes inside regular hours found by the data check. A trade is flagged if a hole lies inside the regular hours of the session its value area came from, or between its entry and its exit. Flagged trades are reported, and every gate and verdict is recomputed without them; a changed verdict becomes Inconclusive.

## Also reported (descriptive; nothing here can change a rule or a verdict)
- **The 80% claim itself:** of the days with an acceptance, the share where price reached the opposite edge by the close (with its Wilson interval), and the share where the trade reached its target before its stop.
- How often the session opened above and below the previous value area; how often price came back in; how often the value area was already crossed before the entry.
- The value area width, the target and stop distances (medians), and the cost in R.
- Win rate with its Wilson interval; exit reasons; longs and shorts; by year; by entry hour; drawdown on closed trades.
- How often the TPO and the volume value areas agree (the same edges, or within 1 point).

## Data check (automated, after the freeze, before any signal)
`mp80_datacheck.js`, written to `mp80_out/mp80_datacheck.txt`:
1. First and last stamp, the cut; the file sorted, no duplicate stamp; every price on the 0.25 grid; high >= open, close >= low; no zero or negative price; minutes with zero volume counted.
2. Every New York weekday classed by the calendar: regular (16:00), short (13:15), market holiday with a futures session (13:00), closed. Expected and found regular-hours minutes per session (390, or 225 on 13:15 days). Sessions per year and per period.
3. Missing minutes inside regular hours: every run of 10 or more listed; sessions whose first minute is not 09:31 or whose last minute is not the close listed. Compared with `data_holes.json`.
4. Roll days (ES rolls on the Thursday eight days before the third-Friday expiry in March, June, September and December): the session-open gap and the largest 1-minute move on those days, against the other days; any 1-minute move above 1% listed.
5. Volume by session: sessions with unusually low regular-hours volume (below 30% of the median of the previous 20 sessions) listed.

The check computes no value area, signal or trade. Its result is reported to the owner with the run.

## Engine and self-test (before the run)
- An engine, `mp80_lib.js`.
- A **separate re-implementation**, `mp80_ref.js`, written from this specification without the engine's code: its own reading of the file, its own profiles and value areas, its own walk. It is written by the same author (Claude), not by another agent, to save cost; that is stated.
- A self-test, `mp80_selftest.js`:
  1. TPO and volume profiles, the POC and the value area by hand on built minutes, including a tie between the pairs;
  2. hand-built days: an open above and below; an open exactly at an edge; two closes inside; a broken pair; the print reading's fill at the edge and at the open; the value area already filled; an entry beyond the stop; the target, the stop, the same-minute rule and the session close (16:00 and 13:15); a market holiday skipped as a previous session;
  3. engine against reference, trade by trade, for the three variants on the real data;
  4. the control at offset 0 equal to the real trades;
  5. **mutations** that must be detected (the engine changed, the reference not): one period of acceptance instead of two; the target at the POC instead of the opposite edge; rows added one at a time; the stop without its extra tick.
- `mp80_run.js` refuses to run unless this file is frozen and the self-test passed on the current code. One run; outputs in `mp80_out/`.

## Before the article
Results go to the owner first (in Romanian), with every variant and every gate. One article, only if the owner asks for it, not before next week; deploys only with the owner's explicit agreement.

## Reading the result
"Buried" or "Inconclusive" mean a convincing edge was not demonstrated, not proof that none exists. One market, profiles approximated from 1-minute bars, one fixed stop, and our readings of a rule that its authors left open on several points.

### Run log
- 2026-11-13: draft 1 written. Nothing computed on the rule or the data.
- 2026-11-13: frozen (owner: "aprob"; draft 1 unchanged). sha256 of this file above the "### Run log" heading: f5d3ef935d2b04412d7ab7861a775ab7665835de6c5a8580a6a5440844b2fadd.
- 2026-11-13, the data check (`node mp80_datacheck.js`, `mp80_out/mp80_datacheck.txt`), before any value area, signal or trade:
  - 2,378,027 minutes, 2020-01-01 18:01 to 2026-09-25 16:59 (file SHA-256 ce7d4408...); no backward or duplicate stamp, every price on the 0.25 grid, OHLC consistent, no zero volume.
  - Calendar: 1,679 regular and 12 short (13:15) sessions, 46 market holidays with a 13:00 futures session, 20 closed weekdays. rb_lib's calendar plus the data agrees with an independent list of NYSE closures and early closes on every day but one: 2020-10-23, a normal trading day with no regular-hours minute in the file (a known hole). Regular sessions by period: 1,005 / 312 / 374.
  - Every regular session starts at 09:31 and ends at its close. 1,668 of 1,691 have every regular-hours minute; 188 minutes are missing in all. Seven runs of 10 or more missing minutes inside regular hours (four in March 2020, 2021-08-25, 2026-06-11, and 56 minutes on 2026-07-24, the only one already in `data_holes.json`); saved in `mp80_out/mp80_datacheck.json` for rule 3.
  - Roll days: no price jump (Globex-open and 09:31 gaps small). But on the roll days from June 2020 to March 2022 the regular-hours volume is about a tenth of normal (the file takes the thin contract that day), and 2026-06-12 is also thin.
  - **The week of 2020-10-19 to 2020-10-22 is mis-stamped:** each day's heavy trading appears from about 22:00 to 05:49 New York and the regular hours hold thin overnight-like trading, around the known one-hour holes at 05:49; 2020-10-23 has no regular hours.
  - 16 sessions have regular-hours volume below 30% of the previous 20 sessions' median (check 5): those roll days, that week, three days after Thanksgiving and 2026-06-12.
- 2026-11-13, a rule-3 addition fixed after the data check and before any signal was computed: the sessions listed by check 5 are treated as suspect data. A trade is also flagged under rule 3 if it is taken on such a session or its value area comes from one. This only widens the robustness recomputation; the main run keeps every trade, as written above.
- 2026-11-13, before the run: `mp80_lib.js` (engine), `mp80_ref.js` (the separate re-implementation: its own reader, its own NYSE calendar lists instead of rb_lib, its own profiles, value area, signals and walk) and `mp80_selftest.js` written. The reference reported trade counts only: 270, 519, 278. The first self-test run failed 2 checks, both on an expectation written in the test itself: it required every entry to lie inside the value area, but the close reading enters at the next minute's open, which lay 1 tick outside the area on 2 trades (2020-06-02, 2023-08-01). The rule allows this, so the check was limited to the print reading; no engine or reference code changed. The self-test then PASSED: 21 hand-built checks; the same 1,691 sessions in both calendars; TPO and volume value areas equal on all 3,382 profiles; engine = reference trade by trade (270, 519, 278); the control at offset 0; four mutations detected (one period of acceptance, the target at the POC, rows one at a time, the stop without its tick).
- 2026-11-13, the run (`node mp80_run.js`, once, 500 control runs, outputs in `mp80_out/`): **all three Buried.**
  - **MP80-TC 4 of 6:** 270 trades (170 / 51 / 49), +$100.86 a trade net (gross +$131.62), PF 1.32; fails the trade count (holdout 49) and development t 0.19 (DEV +$14.61 a trade); passes VAL PF 2.03, control 99.4th percentile, without the 10 best +$2,815, holdout +$12,918.
  - **MP80-TP 5 of 6:** 519 trades (315 / 97 / 107), +$69.60 a trade, PF 1.18; fails only development t 1.43; VAL PF 1.23, control 99.8th percentile, without the 10 best +$2,543, holdout +$1,209 (PF 1.02).
  - **MP80-VC 2 of 6:** 278 trades, +$14.52 a trade; fails VAL PF 0.95, development t 0.33, control 84.4th percentile, without the 10 best -$19,856.
  - The 80% claim: on the accepted days the whole value area was crossed the same day on 61.5% (TC), 51.4% (TP), 59.3% (VC); after the entry the opposite edge traded on 51.1%, 47.2%, 49.6% of trades.
  - Rule 3 (7, 14 and 7 trades flagged) changes no verdict. Nothing was rerun or changed.
- 2026-11-13, the article: the owner asked for it ("Da, scrie-l"). `build_mp80_post.js` builds it from the outputs of the single run, unchanged; `wire_mp80.js` wires it on the publication day (next week). A rerun from the post's own files (the data check, the self-test and the run, with ES_1min.csv one folder above) reproduced every output byte for byte, apart from the dated first lines. One precision on the data-check entry above: the thin roll-day volumes were 8% to 23% of the previous session's, not "about a tenth" for all of them (`mp80_datacheck.txt`, check 4).
- 2026-11-13: published as /posts/market-profile-80-percent-rule-es/ (the owner asked for the article on 2026-11-13: "Da, scrie-l"; and to publish it the same day: "azi", although this specification had planned next week; published on 13 Nov 2026).
