Egress

Documentation

Egress, end to end

What it measures, how to run it yourself, what the record looks like on disk, and what it cannot tell you. Written so that someone who has never seen the repository can reproduce a number on this site.

Overview

Egress answers one question: what does it cost to leave a tokenized US stock position, at a stated size, right now. Entering is easy and every venue advertises it. Leaving is the half nobody prices, and on a tokenized equity it is the half that moves, because the share underneath stops trading at the New York close while the token does not.

The project is two halves that never touch. A crawler reads every listed instrument on a fixed interval and appends what it saw to an append-only record. Everything else - the phase table, the validation, the estimator, this site - reads that record afterwards. Collection never imports interpretation, so a change in how a number is read can never change what was recorded.

No credentials anywhere. Every venue endpoint used is public. The crawler runs unattended on a shared runner with nothing to leak. The only secret in the project is the reader key for the desk, and it lives in the serverless function, never in a page.

Quickstart

Python 3.10 or newer, tested on 3.10 through 3.13. No runtime dependencies at all - the standard library does the HTTP, the gzip and the CSV. Ruff and mypy are dev tools, not requirements.

git clone https://github.com/Ritapossible/Egress.git
cd Egress

# one pass over the whole listed universe, written to state/
python -m egress.crawl --once

# what the record holds so far
python -m egress.crawl --coverage

# what leaving 25,000 USDT of TSLA costs, from the live book
python -c "from egress import exitcost; print(exitcost.for_symbol('RTSLAUSDT', 25000))"

# rebuild this site into docs/
python -m egress.page

A continuous record is a long loop rather than a long process: python -m egress.crawl --loop --hours 6 takes a snapshot every 5 minutes for six hours and exits cleanly, which is what the scheduled job on the repository runs.

Architecture

Twelve modules, each with one job, listed in the order data moves through them.

ModuleWhat it does
configPaths, endpoints and the few constants. No key appears here or anywhere else.
marketThe only module that speaks HTTP. A venue that will not answer raises a typed MarketUnavailable, never a bare exception.
universeClassifies every listed instrument by the venue's own symbolType field. Guessing from the symbol name is wrong for dozens of symbols.
sessionsTurns a timestamp into open, pre, post, overnight, weekend or holiday. Pure arithmetic, no network.
storeThe append-only record. Rows are written first and the snapshot is entered in the manifest second, so a run killed halfway leaves data no reader will trust.
crawlThe loop. Reads the universe, writes a snapshot, sleeps. It never imports sessions or exitcost: collection is separate from interpretation.
exitcostWalks the book from the mid and returns what an exit costs, with a floor where the book ran out and a bound where slicing is involved.
validateHolds displayed size against printed volume, and refuses to compare a symbol whose own two volume feeds disagree.
llmThe reader. Plain English in, a ticker and a size out - never a price, never a cost. Its output is validated against a shape and clamped before anything uses it.
deskThe question answerer. The reader chooses a ticker and a size; every number after that is computed. One venue round trip per question, so the quote, the plan and the max-exit search describe one same book.
factsReads the whole record and assembles the fact set every page renders from. Nothing here fetches; it only interprets what the crawler stored.
pageGenerates this site. Every measured number is interpolated from facts, never typed, so a figure cannot go stale without the record going stale with it.

The reader compiles, the code computes. The language model is only ever asked for a symbol and a notional. It is never asked for a price, a spread or a cost, and it cannot select a ticker that is not listed on the venue right now - resolution is a lookup against the live instrument list, so an invented ticker fails loudly instead of being priced.

Data reference

The record lives in state/. Each snapshot is one gzipped CSV under state/snapshots/, and state/manifest.jsonl holds one JSON line per completed snapshot. A snapshot absent from the manifest is skipped on read even if its file exists.

ColumnMeaning
snap_tsEpoch milliseconds the snapshot was started, stamped once for every row in it.
symbolVenue symbol, for example RTSLAUSDT or BTCUSDT.
bidBest bid at the moment of the read.
askBest ask at the moment of the read.
bid_sizeSize resting at the best bid, in base units.
ask_sizeSize resting at the best ask, in base units.
lastLast traded price reported by the ticker feed.
turnover24hRolling 24h quote turnover for the symbol.
venue_tsThe venue's own timestamp for the quote, which is what phase is decided from.

Current record: 8,591 snapshots, 26,870,963 rows, 142 gaps in coverage, across 3,602 listed instruments (3,019 tokenized stocks, 581 crypto pairs, 2 metals).

Silence is not zero. A symbol missing from a snapshot means the venue did not report it, not that its spread was nothing. Missing rows are absent from every median rather than counted as a value.

What "3,019 tokenized stocks" counts. Rows with symbolType == "stock" from GET /api/v3/market/instruments?category=SPOT — the venue's own classification, not ours. All 3,019 are status: online, all are USDT-quoted, and there are 3,019 distinct base coins, so no symbol is counted twice.

Why another count will disagree. The v2 endpoint most people reach for, /api/v2/spot/public/symbols, returns the same 2,710 rows but carries no symbolType field at all. Without it the only way to split the list is the ticker prefix, and that is wrong in both directions: 25 ordinary crypto pairs start with R (RAYUSDT, ROSEUSDT, RLCUSDT — iExec, not Royal Caribbean) and two tokenized stocks carry no prefix (PRESPCXUSDT, PREOPAIUSDT). The prefix heuristic returns 2,150. Ours returns 3,019 because it reads the field.

Bitget Skills

Two are wired, and both are on the Ask path. bitget-mcp-server answers equity_price_quote behind every desk answer — it is where the basis against the listed share comes from, and without it this site could price an exit but not say what the position was worth. bitget-signal is asked news_feed/latest for the name in the question, on a six-second fuse, and every answer carries what it said. It cannot move the number. The cost comes from the venue's own book; the news block is built after that number is already fixed, and a test asserts the whole answer is identical whether the service replies, returns nothing, or dies. Today it returns nothing, and the desk prints that rather than a briefing assembled out of an empty feed.

bitget-signal, probed 2026-10-22T10:44:48+00:00. 3 of its catalog calls answer; 0 of its live-data calls return anything. The aggregator lists 44 feeds and reports zero articles from all of them, and crypto_price fails with the service’s own ConnectTimeout — so the Skill is running and its outbound fetches are not.

It would not help here even if it worked. Of those 44 feeds, the ones that could carry a US stock story at all are: cnbc. The rest are crypto, tech and general news. This cannot explain why a tokenized stock is wide; it is a control, not an explanation, and it is on this page as a measured result rather than a logo.

CallKindResult
news_feed / sourcescataloganswered — 44 feeds
news_feed / latestliveanswered — 0 articles across 44 feeds
macro_indicators / series_listcataloganswered — 18 entries
cross_asset / assets_listcataloganswered — 15 entries
crypto_price / priceliveno answer
Error executing tool crypto_price: ConnectTimeout('')

HTTP API

One endpoint, used by the desk on the front page. It exists because a reader key cannot live in a browser.

POST /api/ask
Content-Type: application/json

  "q": "What does leaving 40,000 USDT of TSLA cost?"

The response states a symbol, the compiled size, the quote walked off the live book, a slicing bound, and a plain-English reading. Every failure is a stated answer rather than an exception: an unreadable question, a ticker that is not listed, or a venue that will not answer each come back as a sentence saying so. Bodies over 4 KB are refused.

Estimated, never observed. Every figure the endpoint returns is marked estimated. It is what the displayed book says an exit would cost, not a fill anyone received.

Command line

CommandWhat it does
crawl --onceOne snapshot of the whole listed universe.
crawl --loop --hours NSnapshot every five minutes for N hours, then exit.
crawl --interval SOverride the interval, in seconds.
crawl --universeRefresh the instrument list and print the counts by type.
crawl --coverageWhat the record holds, and where the gaps are.
python -m egress.pageRebuild every page in docs/ from the record.

Limitations

Stated here rather than buried, because a research tool that hides its limits is a marketing tool.

Glossary

TermMeaning here
bpBasis point, one hundredth of a percent. 100 bp is 1%.
midMidpoint of the best bid and best ask. The reference every cost on this site is measured from.
touchThe top of the book - the best bid and best ask and the size resting on each.
floorA cost marked >: the displayed book ran out before the position did, so the true cost is at least this.
phaseopen, pre, post, overnight, weekend or holiday, decided from the venue's own timestamp against US equity market hours. Pre is 04:00-09:30 ET and post is 16:00-20:00 ET: the reference venue quotes in both, thinly, so they are not the same regime as the genuinely shut hours.
listed 30d+A name listed on the venue for at least thirty days, by its own launchTime. The headline medians cover these; recently listed names are reported beside them, because a median over whatever is listed today moves with the venue's listing calendar rather than with its liquidity.
usual for this nameA quote compared against that same symbol's own recent quotes in the same phase, never against a cost or against other symbols. A median spread is not a cost, so the two are never divided by one another.
controlThe crypto pairs. Same venue, same engine, same fees, no reason to care whether New York is open.
tokenized stockAn instrument the venue reports with symbolType stock, tracking a US listed share.
Every number on this site is regenerated from the record each time the crawler commits. If a figure here disagrees with the repository, the repository is right and this page is stale - check the generation stamp in the footer.