Stop hunts at prior-day highs and lows
Every number in the note, and where it came from: enough method to check the arithmetic by hand or to rebuild an equivalent study from the public Binance and OKX archives. The snippets illustrate the logic rather than reproduce the production code.
Data
Binance data comes from data.binance.vision/data/futures/um/, which is public and needs no credentials:
- 1-minute klines per contract, the only price series the study reads;
- funding events at their true settlement timestamps, with the interval published per event;
- daily aggTrades files, used twice: for the measured spread of each contract-month and for resolving the order of trades inside ambiguous minutes;
- 1-minute mark-price klines for the ten most traded contracts each month.
OKX data comes from the exchange's public archive at static.okx.com/cdn/okex/traderecords/:
- monthly 1-minute candlestick files for USDT swaps, from September 2021, including contracts that were later delisted;
- daily trade files, used to resolve ambiguous minutes.
Both OKX series come in UTC+8 months and days. Files are concatenated and de-duplicated on the candle open time, and each trade file is mapped to the UTC+8 day it covers.
Binance prices are stored as 32-bit floats, so every touch compares stored values on both sides, and round-number levels, the only levels not taken from a stored bar, are cast to the same precision before comparison while OKX prices are parsed and compared as 64-bit floats.
Universe
On the first day of each month every USDT-margined Binance perpetual with at least 45 days of history is ranked by median daily dollar volume over the 30 days before that day, and the top 50 form that month's universe, leaving out the index perpetuals BTCDOMUSDT, DEFIUSDT, FOOTBALLUSDT and BLUEBIRDUSDT. Commodity- and stock-linked USDT perpetuals stay in whenever they rank: 25 contract-months, mostly PAXG and gold.
Ranking runs over the archive's full listing history, not the exchange's current contract list, so contracts that later died count at the dates they traded. Fewer than 50 contracts qualify before November 2020 (2 in February 2020, 8 in March, between 21 and 40 through October). The result is 3,751 contract-months over 308 contracts, February 2020 to September 2026.
For OKX the universe is the OKX swap on the same underlying as each Binance member, found by stripping multiplier prefixes such as 1000 and mapping to BASE-USDT-SWAP, wherever OKX has data. That gives 2,697 contract-months over 201 swaps, October 2021 to September 2026, with liquidity terciles taken from the Binance rank.
Levels
Daily bars are built from 1-minute bars on UTC days, and a day is complete with at least 1,380 bars. The volatility scale A for day D is the mean true range over days D−14 to D−1, all 14 of which must be complete; every level activated on day D uses that day's A.
TR[d] = max(H[d], C[d-1]) - min(L[d], C[d-1]) # H - L when day d-1 has no bars A[D] = mean(TR[D-14 : D]) # requires all 14 days complete
| Level | Definition | Active window |
|---|---|---|
| Visible (V) | high and low of UTC day D−1 | D 00:00 to D+1 00:00 |
| I10, I13 | high and low of the 24 h window ending at 10:00 or 13:00 UTC on day D | 24 h from the anchor |
| Weekly visible | high and low of the previous ISO week | the whole week |
| Weekly control | 7-day window ending Thursday 10:00 UTC | the next 7 days |
| Round | nearest multiple of g = 10^(floor(log10 C[D−1]) − 1) above and below the prior close, at least 0.1·A away | day D |
| Round control | the same rule on the grid (k + 0.37)·g | day D |
A level is eligible only if the first open of its active window lies at least 0.1·A on the near side. Two levels coincide when they are on the same side, both active at the touch bar and within 0.1·A of each other. The test uses only levels active at the event's own touch bar, so it reads nothing from after the touch.
The two controls
Registered control. It pools I10 and I13; any visible event coinciding with an I10 or I13 level is dropped, as is a control event that coincides with a daily or weekly visible level. When an I10 and an I13 level coincide, the I10 record is kept.
Clean control. It is the subset of I10 and I13 levels whose extreme was set on day D−1. The extreme minute is the first minute in the window that attains the extreme, and a level is clean when that minute falls before 00:00 UTC of its activation day:
clean = kind.isin([I10, I13]) & (extreme_minute < activation_day * 1440)
A window ending at 10:00 UTC contains the first ten hours of the current UTC day, and an extreme set in those hours is today's running high or low, which a daily candle draws. Across the Binance sample 57.7% of resolved registered-control touches are of that kind: 29,387 against 21,578 clean. They are reported as their own group.
The outcome bracket
At the first 1-minute bar inside the active window that trades through the level, a symmetric bracket of h = 0.1·A records the first passage. Upper barrier U = L + h and lower barrier D = L − h, described here for a low with highs mirrored, over a horizon of 1,440 bars.
# touch bar b0 if low[b0] <= D: outcome = AMBIG_A1 if close[b0] >= U else CONT # A2 flag if high[b0] >= U elif close[b0] >= U: outcome = REV else: continue to later bars # A3 flag if high[b0] >= U # later bars: both barriers in one bar -> AMBIG_A4; else the first barrier hit decides
Events are dropped when a run of five or more missing minutes comes before resolution or when the horizon passes unresolved, which leaves unresolved shares below 0.01% in both arms.
Tick resolution
Flags A1 to A4 mark minutes whose internal order matters, which covers 11.0% of visible and 12.7% of registered-control touches on Binance. The clean control on Binance is at 14.8%, and OKX is at 10.2% visible and 13.3% clean.
Because the share is above the registered 3% trigger everywhere, a stratified random sample of up to 400 flagged events per arm is resolved trade by trade. Each event's day file is downloaded and cut to the minutes needed:
- the first trade at or through the level is the touch;
- whichever barrier the trades reach first after it decides the outcome;
- the touch comparison allows a relative 1e-6 tolerance for the 32-bit storage of bar extremes.
Every flagged event then takes its arm-and-flag rate. A cell with fewer than 10 tick events is pooled with the other both-barrier flag (A1 with A4) inside its arm, a rule that applies only to the clean-control estimates.
| Sample | Events resolved | Seed |
|---|---|---|
| Registered test, Binance | 799 of 799 | 7 |
| Clean control, Binance (visible events as above; 200 clean events from the same sample plus 201 more) | 800 of 800 | 17 for the additional draw |
| Clean control, OKX | 802 of 802 | 7 |
Strata and weights
The difference is Δ = Σ ws (pV,s − pC,s) / Σ ws, where ws is the number of resolved visible events in stratum s. A stratum with fewer than 10 events in either arm is dropped, and every tercile cutpoint comes from the pooled visible and control sample of that venue.
| Estimator | Dimensions | Strata | Events dropped |
|---|---|---|---|
| Registered | side × 4-hour block of touch × era × liquidity × distance × speed (terciles) | 972 | 49% |
| Clean-control primary | side × era × liquidity × distance × speed × elapsed time (terciles) | 486 | 3.9% Binance, 7.6% OKX |
The clock-hour block nearly separates the arms, because visible levels activate at 00:00 and the controls at 10:00 or 13:00, which is why half the registered sample sits in strata where one arm is missing. The clean-control estimator replaces the clock hour with the time elapsed from activation to touch. Approach speed is |log(open at touch / open 60 bars earlier)| divided by A / C[D−1].
Inference
Intervals in the registered test come from a cluster bootstrap over ISO calendar weeks, all contracts in a week together, with 10,000 replicates and seed 20261008. The two-stage interval also redraws the tick-resolved events inside each arm-and-flag cell in every replicate, then recomputes the rates before Δ:
for b in range(10_000):
w = np.bincount(rng.integers(0, W, W), minlength=W) # weeks
rates = {cell: rng.choice(x, len(x)).mean() for cell, x in tick.items()}
draws[b] = delta(w, rates)On the registered sample this reproduces the registered point estimate exactly, −0.019865, and widens the interval from [−0.0366, −0.0053] to [−0.0399, −0.0026], with 53.2% of draws at or below −0.02. The clean-control tests use it as their primary interval (seed 20261009). Two-sided p-values are twice the smaller tail share of the draws.
Method gates
| Gate | Requirement | Binance | OKX |
|---|---|---|---|
| Shuffle null, 20 seeds | mean Δ within ±0.005 | registered +0.0015 (SD 0.0060); clean −0.0011 | clean −0.0005 |
| Pollute test, 200 events | bit-identical level, A, eligibility, coincidence, strata, extreme minute | 200/200 (both designs) | 200/200 |
| Leaked level (day D's own high or low) | |Δ| > 0.1 | +0.506 | n/a |
| Clock-anchor placebo (I10 vs I13) | interval includes 0, or |Δ| < 0.01 | registered +0.011 [−0.015, +0.037]; clean +0.008 [−0.021, +0.038] | clean −0.013 [−0.054, +0.027] |
| Compiled kernel vs plain-Python reference | 0 mismatches | 0 of 30,000 (ETH, BTC, 1000PEPE) | same code |
The shuffle permutes whole 1-minute bars as relative shapes (open to high, low and close) within each calendar month, then chains them from the series' first open so each bar opens at the previous shuffled close; levels, A, strata and outcomes are recomputed with unchanged code.
Power is low for the anchor placebo: its half-width of about 2.6 points exceeds the registered effect, so it can rule out only clock-anchor effects larger than about 3.7 points.
Results by test
| Test | Δ | 95% interval | Visible events entering / resolved | Label |
|---|---|---|---|---|
| Registered, tick-corrected | −0.0199 | −0.0366 to −0.0053 | 18,689 / 37,355 | registered: "real but immaterial" |
| Registered, two-stage interval | −0.0199 | −0.0399 to −0.0026 | same | n/a |
| Registered, 1-minute | −0.0190 | −0.0371 to −0.0034 | 18,511 | n/a |
| Clean control, Binance | −0.0511 | −0.0673 to −0.0362 | 35,707 / 37,551 | material continuation |
| Clean control, OKX | −0.0436 | −0.0625 to −0.0267 | 22,717 / 24,841 | material continuation |
Clean-control labels follow the addendum's interval-based table: material means the interval lies below zero and the point estimate is at or beyond −0.02.
| Variant | Binance | OKX |
|---|---|---|
| Registered strata | −0.041 (−0.061 to −0.022) | −0.034 (−0.059 to −0.012) |
| Primary strata plus level age | −0.031 (−0.048 to −0.017) | −0.037 (−0.055 to −0.022) |
| No time strata | −0.046 (−0.060 to −0.032) | −0.035 (−0.052 to −0.020) |
| Unstratified | −0.046 (−0.059 to −0.032) | −0.034 (−0.050 to −0.020) |
| Visible events coinciding with weekly levels dropped | −0.048 (−0.064 to −0.034) | −0.041 (−0.059 to −0.025) |
| I10 clean only | −0.045 (−0.062 to −0.029) | −0.035 (−0.055 to −0.016) |
| I13 clean only | −0.077 (−0.102 to −0.054) | −0.064 (−0.096 to −0.035) |
| Today's running extreme as control | +0.001 (−0.013 to +0.013) | −0.009 (−0.026 to +0.007) |
| Registered design applied to OKX | n/a | −0.028 (−0.051 to −0.007) |
Clean-control variants, Δ with its 95% interval.
Exploratory, after the clean-control results. Clean-control levels touched on their activation day, inside the prior day's range, give −0.049 (−0.065 to −0.035) on Binance and −0.052 (−0.074 to −0.034) on OKX. Touched the next day, as a break of the previous day's extreme, the same levels give −0.004 (−0.024 to +0.015) and −0.015 (−0.038 to +0.009).
Secondary tests.
- Offset profile (1-minute, registered control). −0.019 at the level, −0.027 at 0.05 ATR, −0.020 at 0.1 ATR, −0.002 at 0.2, +0.005 at 0.3 and −0.010 at 0.5. Offsets of 0.75 and 1.0 ATR are not reported, because fewer than 2,000 visible events enter those estimates.
- Weekly levels. −0.039 (−0.060 to −0.019).
- Round numbers. −0.029 (−0.034 to −0.024) on 80,987 round and 82,121 control touches; the control grid reverses at 0.517. Both the weekly and round-number results reject under Holm.
- Heterogeneity. Every era, side, liquidity tercile and touch-hour block is negative, from −0.002 to −0.028.
Trading rules, costs and funding
Two rules run on each primary-sample touch, one trade per level per rule:
- Sweep fade. Within 60 bars of the touch, the first bar that closes back inside while the deepest excursion stays within 0.3·A. The entry is at the next open, the stop sits 0.05·A beyond the excursion and the target at twice the risk.
- Breakout. The first close at least 0.1·A through the level within 60 bars, with entry at the next open, the stop at L + 0.05·A and the target at twice the risk.
Exits read full 1-minute OHLC with the stop taken when stop and target fall in the same bar, a gap through either filling at the open and a time exit 1,440 minutes after entry. The walk-forward variant selects among nine configurations on 12 months and tests the next 3, advancing a quarter at a time from 2021 Q1; training uses entry time with no purge, which leaves 729 of 1,059,975 trades exiting inside the next test quarter.
gross = direction * (exit / entry - 1) fee = 2 * 5e-4 # taker, both fills slip = 2 * 0.75 * spread_month # half spread plus half again, per fill fund = -direction * sum(rate_k for settlement k in (entry, exit]) net = gross + fund - fee - slip
Spread is the median inter-trade bounce between aggressor flips, measured on the 8th and 22nd of each contract-month with up to three forward fallbacks. Of the 3,751 contract-months, 2,123 were measured for this study and 4 have no trade file, while the rest come from an earlier measurement with the same estimator, validated against bookTicker on BTCUSDT for 15 July 2023 at 0.0330 bp.
The four tests on visible levels form one Benjamini-Hochberg family, all four with one-sided p = 1.0, and the deflated Sharpe of the walk-forward books with 9 trials is effectively zero.
The locked plans
Three documents were locked by SHA-256. They are available on request at daniel@daru.finance.
| Document | SHA-256 | Locked (UTC) |
|---|---|---|
| Pre-registration (PREREG.md) | 51a8744e512bb0423f9d7f39f013d6c03186049fcab506b2427c795dc99dcc2f | 2026-10-08 21:03:57 |
| Revision recording every departure from it (PREREG-v2.md) | 3694a506513d74a900a5f2b354ccae96b54fd70d2820fb520f6a93222d04889a | 2026-10-09 02:37:03 |
| Clean-control, OKX and forward-test plan (ADDENDUM-A.md) | d5f046bb6f53183e583448a20aa6d1c48613e290101681016109476af0f726b1 | 2026-10-09 02:39:06 |
sha256sum PREREG.md PREREG-v2.md ADDENDUM-A.md
The pre-registration was locked before any outcome was computed, whereas the revision was written after the registered results existed and says so in its first line. The Binance clean-control test was registered after the v1 results; the OKX replication was registered before any OKX level or outcome was computed.
Registered ruling, verbatim: "Real but immaterial." The registered answer template that goes with it, "Measurable, too small to move a stop", is withdrawn in the revision because the interval extends to −0.037, beyond the materiality threshold of 0.02.
Answer line produced by the addendum's locked rule: "No: they break through more often, by about 4 points". Its conditions all hold: every interval is below zero and both clean-control tests are material, with N the floor of the smaller estimate, 4.36.
Answer line shown with the note: "No: they break at least as often as other levels". It states only what every specification supports, since the exploratory split shows the 4-point size holds against undrawn levels inside the prior day's range and largely disappears against undrawn levels whose touch also breaks a recent extreme.
Departures from the pre-registration
The revision records each of these with its hash.
- Shuffle construction. The locked text re-chains each month from that month's original first open, which places a price jump at 00:00 UTC on the first of every month because a shuffled month never ends exactly where the next real month starts. Levels still active from the previous day are touched by that jump bar, 54,391 times over 20 seeds with reversal in 2% to 3% of cases, so the shuffle gate as locked returned +0.0323 and failed, and under the literal locked text the registered test is "not credited: method". Chaining the shuffled path continuously across months removes the jump, after which the gate returns +0.0015; every result in the note uses the continuous construction.
- Round-number precision. Round and control levels were first compared in 64-bit precision against 32-bit bar extremes, which drops some exact prints at the level. In 32-bit precision the estimate moves from −0.0331 to −0.0292, and the Holm decision is unchanged.
- The too-small rule. Requiring 20,000 resolved visible events, the rule is applied to resolved events before the stratum filter: 37,355 visible and 50,662 control. Counted after the filter, 18,689 visible events enter the estimate, and under that reading the registered test would be "too small to judge".
- Implementation readings. The index list, the first true range, cutpoints, era, the tick tolerance, coincidence for weekly and round levels, the tick-correction scope and the trading-rule entry bar are written into the revision. Some were recorded after the run, with times taken from file timestamps.
- Kernel check timing. The comparison of the compiled kernel against a plain-Python reference ran after the full run rather than before it as planned, with 0 mismatches.
- Reporting additions and withdrawals. The two-stage interval is added. The mark-price split, read as stops against liquidations, is withdrawn because touches seen only on last price are shallow wicks by construction, so the split measures penetration depth. Offsets beyond 0.5 ATR are not reported.
- One exploratory analysis. The split of the clean control by touch day ran after the clean-control results and is labelled exploratory wherever it appears.
Forward test
Defined in the addendum and not yet run. It covers Binance and OKX levels activated on or after 19 September 2026, with the clean-control estimator, venue added as a stratum and the same decision table.
It runs at the first month-end with at least 30,000 resolved visible events pooled across venues. About 10,700 accrue per year, so that is expected around 2029; should the threshold not be reached by 31 December 2029, the test runs on that date labelled underpowered.
Limitations
- Range extremes. Visibility and range-extreme status are not separated by this design.
- Day boundaries. 00:00 UTC is also a Binance funding time, and OKX's default candles use UTC+8.
- OKX is not independent history. Its prices co-move with Binance on the same underlyings, so the replication is not an independent price history.
- Unobserved orders. Stops and liquidations are never observed.
- Universe. It holds fewer than 50 contracts in 2020 and includes 25 non-crypto contract-months.
- Coincidence at universe edges. Levels on the first or last day of a contract's universe spell are checked only against levels built for universe days, which affects under 2% of events.
- Thin tick cells. The A1 and A4 tick cells rest on 5 to 17 events each.
Requesting the full pipeline
The figures on the note are drawn from the study's published result table. The per-trade ledgers, the event tables and the full pipeline are not distributed from this page; to request them, email daniel@daru.finance.

