One Price, and Why It Isn't
The same asset, two prices, one question
The same token often trades at different prices on different exchanges at the same moment. That gap looks like free money. Most of the time it isn't, because moving the token from the cheap venue to the expensive one takes a working deposit door, a working withdrawal door, a shared blockchain between the two exchanges, and proof that both exchanges are actually listing the same contract under that ticker. This chapter is about the machinery that tells the difference between a price gap you can walk through and one that only looks like a door.
The One Price map
The One Price page is NightWatch's live map of these gaps. Every tile is tagged with a verdict:
| Tag | Meaning |
|---|---|
| CAPTURABLE | A real, transferable route exists. The door is open. |
| MIRAGE | The price gap is real, but the transfer route behind it is shut. |
| UNVERIFIED | Not enough data exists to call it either way. |
The page runs one shared 60-second poller (paused automatically when the browser tab isn't visible) with an error boundary around each individual tile, so one broken tile can't crash the whole page; that replaced three older components that each polled independently. This page used to live at /research/hub; old links still work, redirected here with the token preserved in the URL.
Two lenses on who lists what
Before asking whether a gap is capturable, it helps to know how unusual a listing pattern actually is. The Listings Hub offers two lenses:
- The 83 (Unanimous Club): every token whose base ticker is listed on literally every exchange NightWatch currently tracks. The number in the name reflects the count at the time this page was built; the live count moves as exchanges and listings change, and the page always shows the current figure, not a fixed "83."
- Notable Absences: tokens that are widely listed (present on most tracked exchanges) yet missing from one or more of five specific high-signal venues (Binance, Upbit, Bithumb, Coinbase, Bybit). This is deliberately a neutral, factual lens: the code that generates it is explicit that an absence is reported as a plain fact, never as a claim about an exchange's intent.
The Pair Gate: the one call an AI must make first
Before proposing or even reasoning about a "buy here, transfer, sell there" trade, there is exactly one machine call that answers whether it's real: GET /arb/pair_gate. It is not an approximation of the arb engine's logic; it is the arb engine's own gate code, reused verbatim, so a reader (human or AI) gets the identical verdict a real trade would face.
The gate checks things in this order:
- Contract identity. If a token has already been identified as a known "same-name stranger" (the same ticker mapping to genuinely different contracts on different exchanges), the gate refuses regardless of anything else.
- Data honesty. If there is no contract or chain data at all for either exchange and this token, the gate returns "unknown support" and refuses the route outright, on the rule "no route data, no trade." This is stricter than the visual board elsewhere on the site, which may show the same situation as merely "unverified" rather than blocked, and it is checked before the gate ever looks at doors.
- Doors, on shared chains. For a shared blockchain to count as a real route, withdrawals must be enabled at the buying exchange and deposits must be enabled at the selling exchange, on that same chain. Chain names are canonicalized first (so "ERC20" and "ethereum," or "gobob" and "bob," are recognized as the same network rather than falsely rejected as unrelated). On a chain where both exchanges do publish a contract address and the two addresses differ, that specific route is blocked as a collision, even short of a prior same-name-stranger designation; if a contract address is unknown on just one side, the route can still pass, since the gate deliberately does not over-block on partial data.
| Reason code | What it means |
|---|---|
ok | A viable route exists |
identity_collision | Same ticker, different contracts across venues; treat as separate assets |
unknown_support | No chain or contract data for at least one venue; unverifiable |
no_common_network | The two venues share no transfer network for this asset |
route_closed_or_collision | A shared network exists but every option is closed or a collision |
mainnet_only | Only the slow Bitcoin mainnet route is open |
A fourth rule filters out routes whose only open path runs over the Bitcoin mainnet by default, because a 30-to-60-minute settlement time doesn't fit a trading engine built around minute-scale windows; the slower Ethereum mainnet is allowed on its own, since its settlement clock is considered acceptable. A route still passes if a cheaper, faster chain is also open alongside a mainnet one.
The route ships free today (its metered price is set to zero), and results are cached for 60 seconds per (token, buy exchange, sell exchange) combination.
Bot-Tradability Index (BTI)
While the pair gate answers one specific route question, the Bot-Tradability Index answers a broader one: for a given token, can an automated strategy be trusted with it at all? It gives a separate verdict and reason for each of four strategy types (arbitrage, momentum trading, market making, lending-based shorting); a dollar size limit is attached where one can actually be derived from the data, which today means depth-capped arbitrage (sized off 7-day average order-book depth) and lending-based shorting (sized off executable lending liquidity). Momentum and market-making verdicts, and any strategy marked not viable, carry a verdict and a reason but no dollar figure. When the underlying data has gone stale, BTI says so explicitly rather than guessing: "unknown" is a valid, and expected, answer.
Microburst: measuring the order book itself
BTI and the pair gate both lean on a shorter-horizon signal called Microburst quality. It runs in two stages: first, brief high-frequency captures (about 3 seconds of data every 200 milliseconds) for priority tokens; then a 5-minute rollup that turns those captures into a single 0-to-100 composite score built from four equally weighted sub-scores: spread stability, depth resilience, bid/ask symmetry, and volume consistency. A weak score can come from any one of those four collapsing; it doesn't mean all of them are bad.
Watching the doors themselves change
Deposit and withdrawal doors open and close constantly, and until recently that history was simply lost: the database only ever stored the current state. Two workers fixed that:
- Transfer Freeze Watch (
nw_route_snapshot_v1) takes a full snapshot of every route's door state four times a day, so a daily article can answer "what did the whole board look like yesterday versus today," not just list the handful of things that flipped. - The DW status-change log (
nw_dw_monitor) runs every 30 minutes and diffs the current state against the last logged state, writing only the changes. To avoid flooding the log with tens of thousands of "first seen: open" rows on day one, a route that is open the first time it's ever observed is simply skipped, while a route that is closed the first time it's observed gets a baseline row, so "closed since first observed" is knowable later.
What gets published about a real transfer, and what never does
When NightWatch actually moves funds between exchanges to test a route, the results become the Live Rail Transfer Report. Its publication rule is a hard structural line:
| Published | Never published |
|---|---|
| Route (A → B), chain, success/failure | Amounts |
| Duration, failure-type category | Account balances or identifiers |
| Fee rate (a percentage) | Wallet addresses |
| Transaction IDs |
Even the fee is shown only as a rate, never an absolute figure, because a percentage is a policy fact about the rail, not a private balance. A companion worker, the Quartermaster Rail Note, promotes these same raw observations from a local ledger file into a shared database table with a short narrative attached (cause, factors, time window), because the public API runs on a different host than the worker that actually performs transfers and cannot read that worker's local files directly. When repeated reconciliation failures made it clear that not tracking amounts internally at all was actively hurting bookkeeping, four new, optional database columns for amount and plan tracking were added; the public safety guarantee did not weaken, because the public-facing route that reads this table was written to never select those columns, and that fact is written directly into its code as a warning against ever changing it to select everything.
The fee-discipline lesson
One open question behind this whole layer is how much an unhedged transfer window actually costs. A shadow-tracking worker (nw_hedged_shadow) exists specifically to measure this: it takes no capital and places no real trades, and its entire job is to test, side by side, whether hedging an arbitrage candidate at the rich venue recovers the roughly 61% of edge that an earlier live-trading measurement ("livescan") found an unhedged transfer window destroys, against livescan's own retention estimate of about 39%. That 61%/39% split is the premise the shadow worker is testing, not yet a confirmed finding of its own; whether hedging-before-transfer becomes NightWatch's standard practice for this strategy, or stays under evaluation, is what its results will decide.
A more specific and concrete lesson came from Bithumb. When NightWatch traced one live withdrawal in detail, it found that Bithumb charges its withdrawal fee on top of the amount requested, rather than deducting it from that amount: a request for roughly 3,565 units of a token actually debited about 3,600 units, with the receiving exchange getting exactly the requested 3,565. Nobody had modeled that. The driver's own bookkeeping logic was quietly interpreting that fee as an unsold "stranded" leftover asset, on every single Bithumb-origin trade. Once found, it turned out to affect 61 already-settled trades, worth about $94.20 across 8,809 tokens, all mislabeled as leftover inventory rather than as a fee already paid. Rather than rewrite the historical trade records, the fix was a small, forward-looking reclassification module that relabels the figure honestly (as a measured fee when the data supports it, as this specific known Bithumb pattern when it doesn't, or as genuinely unknown when neither applies), while leaving every profit-and-loss number in the historical record untouched.
What you can do now
- Before trusting a price gap on the One Price map, check its tag: only CAPTURABLE means a route has actually been verified as open.
- Use the Listings Hub's two lenses as a sanity check on how normal a listing pattern is before reading meaning into it.
- If you are proposing, or evaluating, any "buy here, sell there" trade: call the pair gate first. A same-ticker match across two exchanges is not proof it's the same asset.
- Treat a BTI "unknown" verdict as a real answer, not a missing one; it means the data needed to judge is stale.
- Remember that the Live Rail Transfer Report and Quartermaster notes will never show you an amount, address, or transaction ID by design; that isn't a bug in the page, it's the privacy contract.
- If you withdraw from Bithumb yourself, budget for the fee being added on top of the request, not deducted from it.