# OptionChain-Simulator — environment variables
#
# A BLANK VALUE MEANS UNSET. `KNOB=` and `KNOB=" "` are read exactly as if the
# line were not here at all, and the documented default applies, so an optional
# knob is switched off by commenting it out rather than by emptying it.
#
# ONE EXCEPTION: CLICKHOUSE_PASSWORD. An empty ClickHouse password is a real
# configuration — a stock `default` user has none — so a PRESENT variable is
# taken as written, empty included, and only an absent one falls back.
#
# VALUES ARE NOT TRIMMED. Whitespace decides whether a value is blank, and
# nothing more: a credential written with a leading space authenticates with
# that space. Numbers and host names are trimmed where they are parsed, so
# `REDIS_PORT= 6379 ` still works.
#
# Every variable the service reads is listed here with its default and its
# accepted range. Copy this file to `.env` and change what you need; anything
# left unset takes the default shown.
#
# Two behaviours to know before editing:
#
# * The v2 knobs (OCS_V2_*, OCS_MAX_CACHED_*) and the server knobs
# (OCS_BIND_ADDRESS, OCS_PORT) are VALIDATED AT STARTUP. An
# invalid value fails the process with a message naming the variable,
# because silently reverting to a default would change how long simulations
# live or how much memory the service holds without anyone noticing.
#
# * The v1 request caps (OCS_MAX_STEPS, OCS_MAX_CHAIN_SIZE,
# OCS_MAX_HISTORICAL_PRICES) and OCS_MAX_CACHED_WALKS warn and fall back to
# their defaults instead, because a bad value there degrades one request
# rather than the service.
# ---------------------------------------------------------------------------
# Logging
# ---------------------------------------------------------------------------
# Verbosity of the tracing subscriber. Case-insensitive. An unrecognised value
# warns once at startup and falls back to the default rather than aborting. The
# effective level is logged at that same level, so setting WARN or ERROR still
# confirms itself.
#
# DEBUG is loud: it includes hyper connection traces on every request, which for
# a batch consumer materialising hundreds of tapes buries anything worth reading.
# Values: TRACE | DEBUG | INFO | WARN | ERROR. Default: INFO
LOGLEVEL=INFO
# ---------------------------------------------------------------------------
# HTTP server
# ---------------------------------------------------------------------------
# The interface to bind. Accepts an IP address, or the words `all` (0.0.0.0)
# and `localhost` (127.0.0.1). Unlike the request caps, an unusable value here
# FAILS STARTUP naming the variable: a service that quietly ignored it would
# listen somewhere nobody asked for.
#
# The default is loopback, which is a CHANGE: the address used to be a constant
# 0.0.0.0. This service has no authentication and no rate limiting, so being
# reachable off the host is now a decision. A container must set 0.0.0.0 for
# its published port to lead anywhere; Docker/docker-compose.yml does.
# Default: localhost
# OCS_BIND_ADDRESS=localhost
# The port to bind, 1-65535. Also fails startup when unusable. 0 is refused
# along with the rest: the operating system would pick a free port, which is
# the opposite of what someone running several instances on known ports wants.
# Default: 7070
# OCS_PORT=7070
# ---------------------------------------------------------------------------
# Binary exports
# ---------------------------------------------------------------------------
# Rows per block in the `arrow` and `packed` export encodings. Both are
# columnar, so a column cannot be written until its last row is known: rows are
# buffered a block at a time, and this is what an export's memory is a function
# of rather than the number of steps. Wider blocks amortise the per-block
# overhead; narrower ones lower the memory floor. An invalid value warns and
# falls back.
# Default: 4096
# OCS_EXPORT_BLOCK_ROWS=4096
# ---------------------------------------------------------------------------
# Request caps (v1 and v2)
# ---------------------------------------------------------------------------
# These bound what a single request may ask for, so a pathological one cannot
# exhaust memory or CPU. Unset or invalid values fall back to the default and
# emit a warning.
# Maximum simulation steps per session or simulation.
# Range: >= 1. Default: 10000
OCS_MAX_STEPS=10000
# Maximum option-chain size (strikes) per request.
# Range: >= 1. Default: 500
OCS_MAX_CHAIN_SIZE=500
# Maximum number of embedded prices a Historical walk request may carry.
# Range: >= 1. Default: 100000
OCS_MAX_HISTORICAL_PRICES=100000
# How many CPU-heavy pricing jobs may run at once, across the whole service.
# One job is up to OCS_MAX_SNAPSHOT_CONTRACTS contracts of pricing plus the
# encoding of the result, so this is the bound that stops a burst of requests
# from taking every core and starving the ones that asked for nothing expensive.
# It covers the v1 and v2 greek renderers AND the v2 snapshot build, since they
# compete for the same cores. Requests above the bound WAIT rather than fail,
# and they wait in async code, so a client that disconnects while queued costs
# nothing. A `greeks=none` render never takes a permit.
# Range: >= 1. Default: 4
OCS_MAX_CONCURRENT_PRICING_JOBS=4
# ---------------------------------------------------------------------------
# v1 domain cache
# ---------------------------------------------------------------------------
# Maximum random walks held in the v1 simulation cache, evicted
# least-recently-used. Eviction never changes what a client sees: a rebuilt
# walk reproduces the identical tape from the same seed.
# Range: >= 1. Default: 1000
OCS_MAX_CACHED_WALKS=1000
# ---------------------------------------------------------------------------
# v2 rolling simulations
# ---------------------------------------------------------------------------
# These are about REAL time and REAL memory. None of them touch the simulated
# clock, which may span years regardless of how long a simulation is kept.
# How long an idle v2 simulation is kept before the retention sweep reaps it,
# in seconds. Measured from the last WRITE, not the last read: peeking a
# snapshot persists nothing, so a client that only peeks does not refresh the
# window.
#
# Deliberately longer than v1's 1800 s: a v2 simulation is walked one request
# at a time over a long simulated horizon, and losing it to a short idle
# timeout would make that horizon unusable.
#
# Range: 1 .. 2592000 (30 days). Default: 3600
OCS_V2_RETENTION_SECS=3600
# How often the retention sweep runs, in seconds. Each pass reaps expired
# simulations and evicts the factor tapes and snapshots they left behind.
# Range: 1 .. 3600. Default: 60
OCS_V2_CLEANUP_INTERVAL_SECS=60
# Maximum v2 factor tapes held resident, evicted least-recently-used. A tape is
# one small row per step, so this bound is generous relative to the snapshot
# one.
# Range: 1 .. 1000000. Default: 64
OCS_MAX_CACHED_TAPES=64
# Maximum v2 snapshots held resident, evicted least-recently-used. A snapshot
# holds every strike of every live expiration, so this bound is deliberately
# small. Eviction is never observable in what is served — a rebuilt snapshot
# prices identically, because it is a pure function of the factor row and the
# planner.
# Range: 1 .. 1000000. Default: 256
OCS_MAX_CACHED_SNAPSHOTS=256
# Maximum contracts one v2 snapshot may price: strikes x the sum of every
# schedule rule's target_count. The chain-size and expiration caps are each
# reasonable alone and multiply into half a million contracts for one /snapshot
# call, which this bounds. Reported against chain_size, the field a client can
# lower without changing what the simulation means.
# Range: 1 .. 10000000. Default: 200000
OCS_MAX_SNAPSHOT_CONTRACTS=200000
# Maximum contracts held resident across every cached v2 snapshot. This is the
# bound that protects memory — OCS_MAX_CACHED_SNAPSHOTS bounds the map, and one
# entry is a few hundred contracts in the reference configuration and hundreds
# of thousands in a large one. Roughly a gigabyte at the default: a contract is
# 848 bytes resident since the greek snapshots landed, so 1 200 000 of them is
# about 1.02 GB. A snapshot heavier than this whole budget is served without
# being cached, rather than evicting everything to make room for something that
# still would not fit.
# Range: 1 .. 100000000. Default: 1200000
OCS_MAX_CACHED_SNAPSHOT_CONTRACTS=1200000
# Maximum steps one export request may cover. Bounds the WORK, not the memory:
# streaming already keeps the response bounded, but an option_chains export is
# steps x expirations x strikes priced contracts, so an unbounded range is
# minutes of CPU. A client wanting more pages the range with from_step/to_step.
# Range: 1 .. 10000000. Default: 100000
OCS_MAX_EXPORT_ROWS=100000
# ---------------------------------------------------------------------------
# v2 snapshot persistence (ClickHouse)
# ---------------------------------------------------------------------------
# Files every ADVANCED v2 step in ClickHouse as an immutable, queryable tape:
# one metadata row per step plus one flattened row per expiration and strike.
# A peek and an export replay and persist nothing.
# OFF by default — a deployment without ClickHouse does not have to name the
# feature to not use it, and with it off nothing is added to the serving path.
# Turning it ON makes ClickHouse a HARD STARTUP DEPENDENCY: the tables are
# created at boot, so a schema or connectivity problem fails the boot rather
# than surfacing later as a warning. Writes themselves are detached from the
# request, so a warehouse that degrades later cannot delay a response.
# Values: true/false/1/0/yes/no/on/off. Default: false
OCS_SNAPSHOT_PERSISTENCE_ENABLED=false
# Maximum quote rows one snapshot may carry into storage. Every quote row of a
# snapshot goes in ONE insert, so this bounds the snapshot and the statement
# alike: a snapshot above it is rejected before a row is written. It must be at
# least OCS_MAX_SNAPSHOT_CONTRACTS, or the API would accept simulations the
# warehouse could never persist — startup refuses that combination rather than
# leaving it to be found in the logs.
# Range: 1 .. 5000000. Default: 200000 (matches OCS_MAX_SNAPSHOT_CONTRACTS)
OCS_SNAPSHOT_BATCH_ROWS=200000
# Maximum rows one read may return, so a range query cannot be unbounded.
# Range: 1 .. 10000000. Default: 1000000
OCS_SNAPSHOT_MAX_READ_ROWS=1000000
# How long an insert may take before it is an error the advance tolerates.
# Range: 1 .. 600. Default: 30
OCS_SNAPSHOT_INSERT_TIMEOUT_SECS=30
# How long persisted snapshots are kept, as a row-level TTL. Anchored on the
# ingestion timestamp, so a backfilled step is kept for its full window.
# Changing this on a live deployment needs an explicit ALTER TABLE MODIFY TTL.
# Range: 1 .. 3650. Default: 90
# NOTE (issue #74): a quote row now stores the full twelve-greek set per option
# style, which measured 121 -> 363 compressed bytes per row on a real tape. The
# same RETENTION_DAYS therefore buys roughly a THIRD of the history it used to
# for the same disk. The greek columns are added to an existing table
# automatically at startup; nothing has to be run by hand.
OCS_SNAPSHOT_RETENTION_DAYS=90
# ---------------------------------------------------------------------------
# Redis — session and simulation storage
# ---------------------------------------------------------------------------
# Never commit a real password. This file documents the shape; `.env` holds the
# values and is gitignored.
# Host and port of the Redis server. Defaults: 127.0.0.1 / 6379
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
# Logical database index. Default: 0
REDIS_DB=0
# Credentials. Both optional; leave them commented out for an unauthenticated
# server. A BLANK value means unset everywhere in this file, so writing
# `REDIS_PASSWORD=` is the same as not writing it — which is what you want
# against a password-less Redis, and used to be an AUTH attempt with an empty
# password instead.
#
# Write the raw credential: punctuation is percent-encoded into the connection
# URL for you, so a password containing `/`, `#`, `?`, `@`, `:` or a space is
# sent as written. Pre-encoding one by hand would send the escapes themselves.
#REDIS_USER=
#REDIS_PASSWORD=
# Connection timeout, in seconds. Default: 30
REDIS_TIMEOUT=30
# Timeout for establishing the connection, in seconds. Default: 5
REDIS_CONNECT_TIMEOUT=5
# ---------------------------------------------------------------------------
# MongoDB — event and step logging
# ---------------------------------------------------------------------------
# Logging is best-effort: a MongoDB failure never fails a request.
# Connection URI. Default: mongodb://admin:password@localhost:27017
MONGODB_URI=mongodb://admin:password@localhost:27017
# Database and collections.
# Defaults: optionchain_simulator / steps / events
MONGODB_DATABASE=optionchain_simulator
MONGODB_STEPS_COLLECTION=steps
MONGODB_EVENTS_COLLECTION=events
# Server-selection timeout, in seconds. Bounded so a missing MongoDB cannot
# stall startup indefinitely.
# Default: 30
MONGODB_TIMEOUT=30
# ---------------------------------------------------------------------------
# ClickHouse — historical price data
# ---------------------------------------------------------------------------
# Host and port. Defaults: localhost / 8123
CLICKHOUSE_HOST=localhost
CLICKHOUSE_PORT=8123
# Database and credentials. Defaults: default / admin / password
#
# CLICKHOUSE_PASSWORD is the one variable where an empty value means something:
# `CLICKHOUSE_PASSWORD=` configures a user with NO password, which is how a
# stock ClickHouse `default` user ships. Comment the line out to get the
# default below instead.
CLICKHOUSE_DB=default
CLICKHOUSE_USER=admin
CLICKHOUSE_PASSWORD=password
# ─────────────────────────────────────────────────────────────────────────────
# Integration tests (examples/integration)
# ─────────────────────────────────────────────────────────────────────────────
#
# The service the integration suite tests, scheme and port included. The
# service itself never reads this: it belongs to `make test-integration`.
#
# Unset or blank, every integration test SKIPS and says so, which keeps
# `cargo test --workspace` hermetic and offline. Set, the suite runs against
# whatever is DEPLOYED there, which can be an older build than the working
# tree; the tests report the version they found and skip a feature that is not
# deployed yet rather than failing it.
#
# Where your deployment lives is your configuration; do not commit it.
# OCS_INTEGRATION_BASE_URL=http://localhost:7070