Skip to main content
Glama
hplin

tastytrade-research-mcp

by hplin
README.md
# tastytrade-research-mcp

Research-only Model Context Protocol (MCP) server for tastytrade historical
options research, bounded live SPX/SPXW option evidence, package-price
evidence, and strategy regression testing.

The project is intentionally separate from the official
[tastytrade/tastytrade-mcp](https://github.com/tastytrade/tastytrade-mcp):

- **official tastytrade MCP**: general live market data, account workflows,
  and broker dry-run validation
- **this project**: historical candles, Backtester access, bounded direct
  SPX/SPXW Gamma+OI snapshots, package-pricing research, and post-session fill
  verification
- **never exposed here**: brokerage order placement, replacement, or
  cancellation

## MCP tools

### Provider-native Backtester tools

| Tool | Upstream endpoint | Purpose |
| --- | --- | --- |
| `tastytrade_get_backtest_available_dates` | `GET /available-dates` | Discover symbols and historical coverage |
| `tastytrade_list_backtests` | `GET /backtests` | List submitted research jobs |
| `tastytrade_create_backtest` | `POST /backtests` | Submit a provider-native historical strategy |
| `tastytrade_get_backtest` | `GET /backtests/{id}` | Poll status and retrieve results |
| `tastytrade_get_backtest_logs` | `GET /backtests/{id}/logs` | Retrieve trials and execution logs |
| `tastytrade_cancel_backtest` | `POST /backtests/{id}/cancel` | Cancel only a Backtester job |
| `tastytrade_simulate_trade` | `POST /simulate-trade` | Simulate one exact historical trade |

### Research and regression tools

| Tool | Purpose |
| --- | --- |
| `tastytrade_get_live_option_snapshot` | Auto-chunk and merge exact SPX/SPXW DXLink Quote, Greeks, and `Summary.openInterest` cohorts, then calculate a research-only unsigned Gamma concentration proxy |
| `tastytrade_compute_heuristic_signed_gex` | Apply an explicit versioned research signing hypothesis to one unified snapshot, with separate current-Gamma signed GEX and optional bounded spot-repriced heuristic gamma flip |
| `tastytrade_price_option_package` | Price verticals, iron condors, and double diagonals with explicit native/synthetic provenance |
| `tastytrade_discover_historical_spx_candidates` | Reconstruct timestamp-safe historical SPXW candidates under a versioned resolution profile, with exact-timestamp Backtester fallback |
| `tastytrade_discover_historical_spx_candidates_range` | Run the same candidate discovery over an explicit trading calendar with bounded concurrency, deadlines, immutable-cache diagnostics, and resumable partial progress |
| `tastytrade_get_historical_spx_candidate_universe` | Return a bounded multi-strike, multi-expiration SPXW universe and optional versioned RESEARCH_ONLY DD selected-leg/matched-delta IV handoff |
| `tastytrade_get_historical_option_package_at_checkpoint` | Reconstruct an exact-leg SPX package reference at an RFC3339 or IANA-local checkpoint with explicit age and skew controls |
| `tastytrade_get_historical_option_package_path` | Return profile-aligned completed-candle package points and explicit gaps without interpolation |
| `tastytrade_get_historical_option_package_horizons` | Reconstruct a frozen exact-leg candidate inventory at caller-supplied ENTRY/+3/+5 trading sessions, with an opt-in research-only model fallback for candle-missing legs |
| `tastytrade_normalize_historical_execution_evidence` | Normalize immutable exact-leg quote snapshots/windows into signed, provider-neutral simulated-execution inputs without claiming a fill |
| `tastytrade_simulate_historical_execution` | Apply one caller-frozen, hashed execution profile to immutable exact-leg evidence and return a separate simulated fill/P&L result |
| `tastytrade_build_historical_replay_report` | Aggregate frozen baseline/research decisions and immutable execution simulations into deterministic +3/+5-day metrics without changing grading or paper state |
| `tastytrade_prepare_spx_spread` | Deterministically normalize SPX legs without calling an upstream service |
| `tastytrade_simulate_spx_spread` | Run exact-leg SPX historical simulation and normalize its result |
| `tastytrade_create_spx_spread_backtest` | Submit supported SPX structures through relative Backtester selectors |
| `tastytrade_verify_historical_fill` | Check a frozen paper limit against a caller-supplied historical package path or a forward Backtester path |
| `tastytrade_get_historical_candles` | Retrieve normalized DXLink OHLCV candles without resampling |

Use MCP `tools/list` for the complete JSON input schemas.
The direct live snapshot, bounded DXLink batch provenance, event/cohort
freshness contract, OI-based unsigned Gamma concentration methodology,
semantic limits, regression handoff, and opt-in live gate are documented in
[`docs/live-option-snapshot.md`](docs/live-option-snapshot.md).
The separate Level 3 signing hypothesis, model/result identities,
current-Gamma versus spot-repriced evidence, bounded heuristic gamma-flip
search, and research-only live gate are documented in
[`docs/heuristic-signed-gex.md`](docs/heuristic-signed-gex.md).
The shared profile contract and the optional seven-date live capability gate
are documented in
[`docs/resolution-profiles.md`](docs/resolution-profiles.md).
The separate Double Diagonal IV measurement contract, legacy-preserving
migration, and reproducible grading/regression handoff are documented in
[`docs/dd-iv-measurements.md`](docs/dd-iv-measurements.md).
Private immutable source-cache configuration, exact offline replay, and
migration guidance are documented in
[`docs/evidence-cache.md`](docs/evidence-cache.md).
The external historical-source capability review, explicit no-spend decision,
and approval gates are documented in
[`docs/historical-provider-decision.md`](docs/historical-provider-decision.md).
The quote-backed exact-leg handoff, signed inventory convention, and
valuation/simulation/broker separation are documented in
[`docs/historical-execution-evidence.md`](docs/historical-execution-evidence.md).
Caller-frozen execution profiles, fill statuses, exact P&L arithmetic, and
fee semantics are documented in
[`docs/historical-execution-model.md`](docs/historical-execution-model.md).
The replay acceptance matrix, denominator rules, one-week smoke manifest, and
runner guidance are documented in
[`docs/historical-replay.md`](docs/historical-replay.md).

## Private historical evidence cache

The historical candle, SPX candidate/universe, and exact-package tools accept
an opt-in `evidence_cache` policy. The server exposes 24 tools, including the
local-only historical quote normalizer, deterministic execution-model
adapter, and historical replay report builder.
`READ_WRITE` stores sanitized source observations and normalized results as
separate content-addressed objects; `REFRESH` creates a new immutable revision
and diff; `CACHE_ONLY` replays only caller-supplied exact manifest IDs and
never contacts the provider.

The filesystem backend is disabled until
`TASTYTRADE_EVIDENCE_CACHE_DIR` points to a private volume. It uses atomic
writes, read-back checksum verification, bounded provider concurrency,
request deduplication, short-lived retryable-failure indexes, and a hard disk
quota without evicting immutable evidence. Cache identity includes exact
symbols, range/as-of, provider/dataset/license scope, aggregation/session/
alignment/price type, the full resolution profile, resource policy, and
normalization/model/source revisions. Retrieval time is frozen in each
immutable manifest and never replaces bar availability time.
Run `npm run report:evidence-cache` for a deterministic synthetic cache
hit/miss, byte-count, normalized-hash, and provider-call-reduction report.

## Approval-gated bounded history

The repository contains a provider-neutral bounded-history contract for a
future approved source. It is intentionally dormant: no vendor transport,
credential, paid fallback, environment configuration, or MCP tool is enabled.
Construction requires an exact approval scope, confirmed field and
expired-symbol entitlements, a provider-specific native-field allowlist, a
fixed HTTPS host, separate credentials, and explicit cache storage/reuse
permission. Synthetic tests validate the contract but do not establish
external SPX/SPXW coverage.

## Execution evidence contract

Every normalized research result uses execution-evidence contract `1.0.0`.
The contract distinguishes:

- `NATIVE_PACKAGE`
- `SYNTHETIC_NATURAL`
- `SYNTHETIC_MID_REFERENCE`
- `HISTORICAL_OPTION_PACKAGE_REFERENCE`
- `HISTORICAL_PATH`
- `BACKTESTER_SIMULATION`
- `BROKER_DRY_RUN`

The machine-readable schema is
[`docs/execution-evidence.schema.json`](docs/execution-evidence.schema.json).
Migration guidance for existing paper-simulation logs is in
[`docs/execution-evidence-migration.md`](docs/execution-evidence-migration.md).

`BROKER_DRY_RUN` validates a caller-supplied order; it never implies
fillability. Synthetic midpoint evidence is valuation-only.

## Package pricing

Package arithmetic uses an in-repository exact decimal implementation rather
than binary floating-point arithmetic.

- `NATIVE_PACKAGE` is selected only when the caller supplies a valid,
  non-crossed upstream package market.
- `SYNTHETIC_NATURAL` buys each leg at its ask and sells each leg at its bid.
- `SYNTHETIC_MID_REFERENCE` uses leg midpoints and always reports
  `guaranteed_executable: false`.
- Native package bid/ask fields are never populated with synthetic arithmetic.
- Every leg retains its exact symbol, action, quantity, expiration, timestamp,
  and provider source.
- `as_of`, freshness, and temporal alignment are computed from the
  decision-critical observations.
- Missing, crossed, stale, future-dated, and materially misaligned quotes are
  returned as explicit warnings. Unusable evidence is never success-shaped.

Supported families are `DEBIT_VERTICAL`, `CREDIT_VERTICAL`, `IRON_CONDOR`, and
multi-expiration `DOUBLE_DIAGONAL`.

## SPX spread adapter

The high-level adapter preserves each exact provider/OCC option symbol in
`/simulate-trade` requests and returns stable `1.0.0` result envelopes. Each
result repeats the immutable normalized legs and carries a deterministic
SHA-256 `request_id`, so persisted evidence remains attributable even though
the provider response itself contains only prices and timestamps.

Aggregate `/backtests` requests use relative selectors (`delta`,
`percentageOTM`, `currentPriceOffset`, or `premium`) and therefore cannot
preserve an exact historical strike or expiration. The adapter exposes that
limitation in its capability flags.

Double diagonals remain exact-simulation only because the aggregate
Backtester cannot faithfully preserve their multi-expiration identity. 0DTE
legs are rejected unless the caller explicitly sets `allow_0dte: true`.

This MCP supplies evidence only. It does not assign grades or make production
entry decisions.

## Historical SPX candidate discovery

`tastytrade_discover_historical_spx_candidates` is the
`REGRESSION_RESEARCH` bridge between timestamp-safe selector evidence and
exact contract identity.
`tastytrade_discover_historical_spx_candidates_range` applies that same
single-checkpoint contract to an explicit caller-supplied trading calendar.
It preserves chronological output order while using a bounded worker pool,
isolates checkpoint failures, retries only transient provider failures, and
returns an opaque continuation cursor for unresolved or deferred sessions.
Continuation scheduling completes a first pass over never-attempted sessions
before retrying failures. Retry-eligible checkpoints run before checkpoints
whose provider cooldown is still active, and unresolved retries rotate to the
queue tail so rate-limited dates cannot block later checkpoints.
All Backtester endpoints in one server process share a request-start gate.
It spaces starts by at least 100 ms by default, honors `Retry-After` before
`X-RateLimit-Reset`, and falls back to bounded jittered 10/30/90-second
cooldowns. Override only the pacing interval with
`TASTYTRADE_BACKTESTER_MIN_REQUEST_INTERVAL_MS`; continuation retry timestamps
remain provider- or scheduler-derived.
Per-checkpoint stage timings identify cache lookup, provider bootstrap,
contract-universe construction, candle reconstruction, selector evaluation,
and the stage interrupted by a deadline. Operational settings and diagnostic
callbacks do not alter the logical batch request ID or candle-cache
fingerprints.

The official option-chain and REST quote endpoints do not document an
historical `as_of` parameter. Backtester logs currently expose exact selected
strike, expiration, side, and a provider-internal symbol, but those log fields
are undocumented. The documented Backtester `EntryConditions` also has no
time-of-day field, and a live request containing undocumented
`entryTime: "14:30:00Z"` was silently ignored: the SPX trial still opened at
`19:45:00Z`.

For `DELTA` and `PERCENTAGE_OTM`, the adapter reconstructs a bounded SPXW
universe from generated OCC/streamer symbols and completed DXLink candles.
Candidate discovery uses the provider-supported maximum of 100 option symbols
per candle batch; the standalone bounded-universe tool retains its existing
20-symbol batch policy.
Omitting `resolution_profile` preserves the 5-minute cohort; strict native-hour RTH
research requires explicit `HOURLY_VALUATION_RESEARCH`. When DXLink labels SPXW
hour bars on its provider clock instead of the 09:30 ET session anchor, callers
must opt into the separate `HOURLY_PROVIDER_ALIGNED_RESEARCH` cohort; those bars
are never relabeled as session-aligned. It enforces
`available_at <= as_of`, profile age/skew limits, aligned call/put parity for
the forward, and contract-candle IV for Black-76-style delta. Selection is
deterministic by selector error, observation age, DTE distance, and strike.

Generated contract identity becomes eligible only when DXLink returns
historical evidence for that exact symbol. The result preserves checkpoint
selection time, observation age, price, contract IV, reconstructed delta,
volume/OI when present, OCC identity, and field-level provenance.

Exact-timestamp Backtester selection remains the fallback for unsupported
selectors or insufficient reconstruction evidence. It never sends or trusts
`entryTime`, and it accepts only a trial and opening order exactly at
`as_of`. Stale and future trials remain fail-closed.

The capability returns `HISTORICAL_SELECTOR_CANDIDATE_SET`, never a
full-chain snapshot. Historical bid/ask, ATM IV surface, skew, and term
structure remain unavailable. Exact symbols are `/simulate-trade` compatible,
but the capability separately reports whether an exact simulation snapshot
exists at the arbitrary checkpoint.

The live 2026-08-25 07:30 PT smoke test returns four timestamp-safe CALL/PUT
Delta-20 and 1%-OTM candidates without creating Backtester jobs.

The full provider findings, contract, anti-lookahead rules, and limitations
are documented in
[`docs/historical-spx-candidates.md`](docs/historical-spx-candidates.md).

## Historical SPX candidate universe

`tastytrade_get_historical_spx_candidate_universe` expands checkpoint-safe
evidence from selector winners into a caller-bounded strike grid. By default
it covers the nearest min/mid/max DTE SPXW expirations, both option sides, and
a 25-point strike step.

Generated OCC identity is returned only after DXLink supplies a completed
historical candle for that exact symbol. Contracts without timestamp-safe
evidence are omitted and counted as coverage gaps. The endpoint preserves
price, reconstructed delta, contract IV, OI, volume, observation age, and
field-level provenance when available; it never selects the final spread.
Delta reconstruction prefers a timestamp-aligned put/call parity forward.
When parity is unavailable but historical IV exists, the universe endpoint
uses the completed checkpoint SPX price as an explicitly warned zero-carry
forward approximation. Missing IV remains null, and this fallback does not
change the stricter selector-discovery path.

The default freshness maximum is 60 minutes. A caller may explicitly extend
it to 24 hours; older pre-checkpoint observations are then retained only as
`STALE`/`LOW` evidence. Coverage gaps, provider batch errors, and per-field
availability counts remain machine-readable, and aggregate field capability
flags are true only when every returned contract supports the field.

Both historical SPX tools accept either RFC3339 `as_of` or an unambiguous
IANA `local_checkpoint`. They preserve `bar_start`, `bar_end`,
`available_at`, and `retrieved_at`, and return the normalized resolution
profile with deterministic requested/effective cohort IDs. An opaque
versioned `candidate_construction_profile` is returned unchanged; this MCP
does not duplicate grading, DD-bucket, or final-leg-selection rules.

An optional `dd_iv_measurement` request freezes an exact four-leg Double
Diagonal candidate and returns distinct `SELECTED_LEG_IV_DIFFERENCE` and
`MATCHED_DELTA` / `MATCHED_FORWARD_MONEYNESS` cohorts. The handoff is always
`RESEARCH_ONLY`, preserves provider/derived models, timestamp lineage, and
optional immutable evidence-manifest IDs, and explicitly does not replace
legacy production `term_structure`.

See
[`docs/historical-spx-universe.md`](docs/historical-spx-universe.md)
for the input contract, reconstruction rules, and live checkpoint findings.

## Historical exact-leg package reconstruction

`tastytrade_get_historical_option_package_at_checkpoint` selects only
completed option candles available by the requested checkpoint and preserves
each leg's exact OCC identity, derived streamer symbol, historical close, IV,
bar-start timestamp, availability timestamp, age, and provenance. Explicit
age and temporal-skew limits fail closed. Candle-derived values are always
`VALUATION_ONLY`; historical bid/ask is not fabricated.

The checkpoint and path results include deterministic request IDs plus
requested/native/effective aggregation, session, alignment, age/skew,
fallback, and cohort metadata. The default checkpoint behavior remains the
existing 5-minute compatibility path. Native-hour New York RTH valuation is
explicit opt-in and does not silently fall into the 5-minute cohort.
Checkpoint legs also return a structured reconstruction status and failure
reason. Their provenance carries the requested lifecycle, resolution profile,
provider failure reasons, source revision, and exact cache manifest/content
IDs when available.

`tastytrade_get_historical_option_package_horizons` accepts a bounded frozen
candidate inventory and a caller-supplied ordered trading-session calendar.
It resolves `ENTRY`, `OUTCOME_3_TRADING_DAYS`, and
`OUTCOME_5_TRADING_DAYS` by session index rather than assuming weekdays are
trading days. Every horizon reuses the same OCC symbols, actions, quantities,
roles, strategy family, and resolution profile. The result keeps candle
references labeled `VALUATION_ONLY` / `CANDLE_REFERENCE` and reports complete
package counts, missing legs by role, missing-reason counts, and coverage by
strategy, expiration, entry DTE, and resolution profile. Exact 21/35-DTE
Double Diagonals are supported without changing grading or leg selection.

An optional `valuation_fallback` can supply checkpoint-frozen SPX levels,
per-exact-leg IV or surface inputs, rates, dividends, immutable source IDs,
and an IV-shift uncertainty assumption. The strict candle `status`, `package`,
legs, and failure taxonomy remain unchanged. A separate `valuation` object
classifies each result as `EXACT_PACKAGE_REFERENCE`,
`MIXED_OBSERVED_MODELED`, or `MODEL_SURFACE`; observed leg values are copied
unchanged and only unavailable exact legs are modeled. Cache/provider errors,
stale observations, and alignment failures remain non-modelable.

Any package containing a modeled leg is `MODEL_REFERENCE` /
`VALUATION_ONLY`, carries `guaranteed_executable: false`, and is explicitly
not bid/ask, NBBO, midpoint, touch, or fill evidence. Its
`execution_evidence_input` can be passed to
`tastytrade_normalize_historical_execution_evidence` and then evaluated only
through a separately frozen `REFERENCE_COST` execution profile. Coverage
keeps strict package counts separate from valued exact, mixed, and fully
modeled cohorts.

`tastytrade_get_historical_option_package_path` emits a point only when every
leg has an exact timestamp-aligned completed bar. It never interpolates or
forward-fills missing legs. If a bounded old 1-minute DXLink replay exhausts a
configured local resource budget or the provider returns a clipped snapshot,
the result records the failed attempt and explicitly selects the finest
retrievable coarser resolution. A local budget result is not described as a
provider limit.

The returned `fill_verification_path` can be passed directly to
`tastytrade_verify_historical_fill`. Full rules and the 2026-08-27 07:30 PT
acceptance finding are documented in
[`docs/historical-option-package.md`](docs/historical-option-package.md).

## Historical fill verification

`tastytrade_verify_historical_fill`:

- evaluates only `[submitted_at, valid_until]`;
- accepts either caller-supplied historical package points or the existing
  exact-leg Backtester mode;
- supports both `ENTRY` and `EXIT`;
- preserves `paper_order_id`, `checkpoint_id`, and `position_id` references;
- returns legacy `status` values `TOUCHED`, `NOT_TOUCHED`, or
  `NOT_VERIFIABLE`, plus `assessment_status: NOT_ASSESSABLE` for an
  insufficient path;
- distinguishes `LIMIT_TOUCH` from `CONSERVATIVE_CROSS`;
- reports an exact observed touch timestamp when defensible, otherwise a
  bounded interval for a sparse path;
- records disagreement with a live paper assumption without mutating the
  original paper event.

All verification is marked `POST_SESSION_REGRESSION` and includes
`HISTORICAL_EVIDENCE_ONLY_DO_NOT_REWRITE_LIVE_EVENT`.

## DXLink historical candles

`tastytrade_get_historical_candles` obtains an API quote token from
`GET /api-quote-tokens`, connects only to a `wss://` host under
`dxfeed.com`, completes the DXLink handshake, and waits for the indexed-event
snapshot boundary.

The normalized output includes:

- deterministic request and cohort identity;
- `dxlink_auth` recovery provenance and a sanitized `provider_error` when
  authentication required recovery;
- requested and actual UTC ranges;
- `source_time`, `bar_start`, `bar_end`, `available_at`, and `retrieved_at`
  per bar;
- explicit instrument type and interval;
- `ALL`, US `REGULAR`, or caller-defined `CUSTOM` session filtering with an
  IANA timezone;
- OHLC, volume, VWAP, bid/ask volume, implied volatility, and open interest
  when supplied;
- missing-bar, empty-result, and snapshot-truncation warnings;
- `resampled: false`.

`REGULAR` requests use provider-native `a=s,tho=true` candle attributes, so
bars are aligned to and built only from the instrument's regular trading
session. `CUSTOM` windows filter complete provider bars by their source
timestamp; they are never reaggregated and include an explicit warning about
that limitation.

One-unit periods use DXLink's native normalized spelling (`m`, `h`, `d`, or
`w`). In particular, public interval `1h` subscribes to native HOUR `{=h}`;
`60m` remains `{=60m}` and is not treated as equivalent. Request/response
matching accepts only provider-defined canonical aliases such as omitted
defaults and attribute ordering differences.

The server sends `fromTime` as epoch **milliseconds**, matching the current
production DXLink service. The published AsyncAPI description currently says
seconds, but seconds cause the service to replay the full available history.
Production currently appears to ignore `toTime`. The client reports a
continuous-calendar replay estimate for planning, but that estimate is
explicitly advisory: it does not account for sessions, closures, sparse
options, or expiration, and never blocks a request before connecting.

Streaming state is indexed and deduplicated per symbol, retains only rows in
the requested range and session, serializes message processing, and does not
infer completion from timestamp order. Completion still requires DXLink
snapshot protocol evidence. Each result includes:

- `status`, `snapshot_complete`, `snapshot_truncated`, and
  `provider_snapshot_complete`;
- machine-readable `failure_reasons`;
- per-symbol and aggregate request counters for received events, valid events,
  unique observations, retained rows/bytes, and returned rows;
- configured limits and the advisory calendar-slot estimates.

`snapshot_complete` means a provider END marker was observed without local or
provider truncation. It does not assert that every interval traded;
`status` and `failure_reasons` separately report missing or uncovered evidence.

The independent client controls are:

| Input | Scope | Default | Maximum |
| --- | --- | ---: | ---: |
| `max_output_candles` | retained/returned rows per symbol | 10,000 | 250,000 |
| `max_received_events` | aggregate Candle protocol rows per request | 10,000 | 1,000,000 |
| `max_buffer_bytes` | aggregate queued wire data plus accounted retained state | 16 MiB | 128 MiB |
| `deadline_ms` | complete DXLink snapshot lifecycle | 15,000 ms | 60,000 ms |

Received-event counts include marker, remove, invalid, duplicate, and unmatched
Candle rows. Unique observations count distinct in-window indexed events
admitted to bounded state; retained rows reflect removals, and returned rows
reflect the final normalized output. Retained-byte accounting uses serialized
field sizes plus conservative per-object/index overhead rather than claiming
exact V8 heap usage.

`max_candles` remains as a deprecated compatibility shorthand, capped at
20,000, that applies the same value to per-symbol output and aggregate receive
budgets. It cannot be combined with either explicit field. `timeout_ms` is a
deprecated alias for `deadline_ms` and cannot be combined with it.

`LOCAL_RECEIVE_BUDGET_EXCEEDED`, `LOCAL_BUFFER_BUDGET_EXCEEDED`, and
`LOCAL_OUTPUT_BUDGET_EXCEEDED` are client facts. `PROVIDER_SNAPSHOT_SNIPPED`
is provider protocol evidence. `REQUESTED_WINDOW_NOT_COVERED`,
`SNAPSHOT_TIMEOUT`, and `MISSING_CONTRACT_EVIDENCE` describe observed
availability; none is automatically classified as an entitlement failure.
Completed symbols remain usable when another batch symbol fails locally or is
snipped.

Canonical identity, snapshot transaction rules, the sanitized timeout root
cause, and live native-hour findings are documented in
[`docs/historical-candles.md`](docs/historical-candles.md).

### Rate limits and retries

- API quote tokens use the provider's `expires-at` timestamp with a one-minute
  safety margin. The previous 23-hour bound remains only as a fallback when
  the provider omits expiry metadata.
- Historical candles and live option snapshots share one quote-token lifecycle
  in the production service. A post-`AUTH` `UNAUTHORIZED` invalidates both the
  cached quote token and cached OAuth access token, reacquires credentials,
  rebuilds the WebSocket connection, and retries exactly once.
- Successful recovery returns `dxlink_auth.status = REFRESHED` with
  `retry_count = 1`. A second rejection returns explicit `AUTH_FAILED`
  diagnostics; a timeout before `AUTH_STATE/AUTHORIZED` is
  `NOT_CONFIRMED`. Tokens are never included.
- The quote-token REST request retries network errors, `429`, and `5xx` up to
  three attempts with capped exponential backoff and jitter.
- A candle snapshot has a configurable `deadline_ms` (maximum 60 seconds).
- Non-authentication WebSocket failures are returned explicitly and are not
  silently retried or merged.
- Received events, returned rows, and memory are bounded independently as
  documented above.
- DXLink permits at most 5 concurrent sessions and 100 Candle subscriptions
  per session. Callers should batch work rather than fan out unbounded calls.

## Security model

OAuth client credentials and refresh tokens are sent only to
`api.tastyworks.com`. The short-lived OAuth token is sent to the fixed
Backtester host and to the tastytrade quote-token endpoint. The resulting
quote token is sent only over `wss://` to a host under `dxfeed.com`.

Do not commit credentials. If using Node's `--env-file`, unset inherited
`TASTYTRADE_*` variables first because inherited values override the file.
All API timestamps must be RFC3339 values containing `Z` or an explicit UTC
offset; timezone-less timestamps are rejected.

The remote HTTP entrypoint supports two authentication modes:

- `api-key` for local development and emergency rollback, using a constant-time
  comparison against `MCP_API_KEY`;
- `oauth` for production, validating Entra JWT signature, issuer, audience,
  expiration, and `mcp.read` scope through cached JWKS.

OAuth mode publishes RFC 9728 protected-resource metadata at both supported
well-known paths and includes that URL and the required scope in every 401
challenge. MCP is accepted only at `POST /mcp`, request bodies are limited to
1 MiB, and `GET /healthz` remains unauthenticated with health metadata only.

## Requirements and setup

- Node.js 22+
- tastytrade OAuth API grant:
  - `TASTYTRADE_CLIENT_ID`
  - `TASTYTRADE_CLIENT_SECRET`
  - `TASTYTRADE_REFRESH_TOKEN`
- A fully onboarded tastytrade customer for DXLink quote tokens

```bash
npm ci
npm run build

export TASTYTRADE_CLIENT_ID="..."
export TASTYTRADE_CLIENT_SECRET="..."
export TASTYTRADE_REFRESH_TOKEN="..."

npm start
```

For remote Streamable HTTP:

```bash
export MCP_API_KEY="$(openssl rand -hex 32)"
export MCP_HTTP_HOST=127.0.0.1
export MCP_HTTP_PORT=8000

npm run start:http
```

Connect to `http://127.0.0.1:8000/mcp` with
`Authorization: Bearer <MCP_API_KEY>`. Azure Container Apps deployment and
Key Vault guidance are documented in
[`docs/azure-deployment.md`](docs/azure-deployment.md).

Production OAuth configuration additionally requires `MCP_PUBLIC_URL`,
`OAUTH_ISSUER`, `OAUTH_JWKS_URL`, `OAUTH_AUDIENCE`,
`OAUTH_REQUIRED_SCOPE`, and `OAUTH_TOKEN_SCOPE`. ChatGPT discovers the Entra
authorization server from
`/.well-known/oauth-protected-resource`, then sends its access token in the
standard `Authorization` header.

For an MCP client:

```json
{
  "mcpServers": {
    "tastytrade-research": {
      "command": "node",
      "args": ["/absolute/path/to/tastytrade-research-mcp/dist/index.js"],
      "env": {
        "TASTYTRADE_CLIENT_ID": "...",
        "TASTYTRADE_CLIENT_SECRET": "...",
        "TASTYTRADE_REFRESH_TOKEN": "..."
      }
    }
  }
}
```

## Development

```bash
npm ci
npm run typecheck
npm test
```

The regression suite covers clean and degraded package markets, all supported
spread families, regular and custom candle sessions, both fill models,
ambiguous paths, Backtester normalization, and MCP tool dispatch.

## Upstream documentation

- Backtesting guide: https://developer.tastytrade.com/docs/guides/backtesting/
- Backtesting API: https://developer.tastytrade.com/reference/backtesting/
- Streaming guide: https://developer.tastytrade.com/docs/guides/stream-market-data/
- Streaming concepts: https://developer.tastytrade.com/docs/concepts/streaming/
- Rate limits: https://developer.tastytrade.com/docs/guides/rate-limits-and-backoff/

## Disclaimer

This is an independent research project and is not an official tastytrade
product. Historical simulations can differ materially from live execution
because of fills, liquidity, spreads, slippage, data availability, and model
assumptions.

MIT License.

TDQS

A3.6/5.0

Scored across 13 tools

Disambiguation4/5

Most tools are clearly distinct by resource and action, but the backtest/simulation cluster (create_backtest, simulate_trade, simulate_spx_spread, create_spx_spread_backtest) risks confusion despite clarifying descriptions. The price/prepare/simulate spread pipeline is well-separated with explicit roles.

Naming Consistency5/5

All tools follow a consistent tastytrade_ prefix with snake_case verb_noun naming (list_backtests, create_backtest, get_backtest_logs, cancel_backtest). Verbs and objects are uniform and predictable across the entire set.

Tool Count5/5

13 tools is well within the ideal 3-15 range for a research-focused server. Each tool contributes to distinct workflows such as backtest management, option pricing, spread simulation, and historical data access without redundancy.

Completeness4/5

The core research workflow is well covered: backtest lifecycle (list, create, get, cancel, logs), historical simulation, option pricing, spread normalization, and candle retrieval. Minor gaps exist such as lack of a delete-backtest or batch operation, but no critical dead ends are apparent.

Maintenance

ActivityMaintained
ResponsivenessResponsive