title: Interface Layer
summary: >-
How TerraFin's FastAPI app is started, how routes are organized, and how
session-scoped state works.
read_when:
- Adding or modifying API endpoints
- Integrating with the chart or dashboard from Python
- Debugging session isolation or caching
- Building or serving the frontend
Interface Layer
The interface layer exposes TerraFin through one FastAPI application, six page
routes, and several API families. It lives under src/TerraFin/interface/.
The key design choice is that interactive state is session-scoped. Unless a
request says otherwise, TerraFin uses the default session.
For deployment and upstream data-usage responsibilities, see License & Data Rights.
Server
Source: src/TerraFin/interface/server.py
The server owns:
- application startup and shutdown
- router registration
- cache manager lifecycle
- readiness and health endpoints
- static frontend serving
App factory
create_app(
initial_data: TimeSeriesDataFrame | None = None,
base_path: str = "",
) -> FastAPI
create_app(...) resets session state, registers routers, installs exception
handlers, wires the private-data cache callbacks, and mounts the frontend static
assets.
CLI
python server.py [run|start|stop|status|restart]
Run these commands from src/TerraFin/interface/.
| Command | Behavior |
|---|---|
run |
Start in the foreground |
start |
Start in the background and write a PID file |
stop |
Stop the background process if it exists |
status |
Show whether the background process is running |
restart |
Stop and start again |
Runtime config comes from src/TerraFin/interface/config.py.
| Field | Default | Env var | Notes |
|---|---|---|---|
host |
127.0.0.1 |
TERRAFIN_HOST |
Empty values fall back to the default |
port |
8001 |
TERRAFIN_PORT |
Must be an integer in 1..65535 |
base_path |
"" |
TERRAFIN_BASE_PATH |
Normalized to leading slash, no trailing slash |
cache_timezone |
"UTC" |
TERRAFIN_CACHE_TIMEZONE |
Must be a valid IANA timezone; used for cache/date-bound scheduling |
Root routes
| Method | Path | Behaviour |
|---|---|---|
GET |
/ |
Redirect to the dashboard page, respecting base_path |
GET |
/resolve-ticker?q=... |
Resolve a query string to a ticker symbol and company name |
GET |
/health |
Multi-component status page (HTML) |
GET |
/health.json |
Same data as JSON for scripting |
GET |
/ready |
Readiness endpoint with cache-manager and private-data checks |
/health, /health.json, and /ready stay at the root even when
TERRAFIN_BASE_PATH is set. Feature routes are prefixed by the base path.
/health runs active probes on each request (no background polling) for
three components — Agent (provider auth env vars set), Telegram
(getMe against the configured bot token), and Signals Provider
(proxies the upstream monitor's /health, surfacing per-broker WS state
and last-tick age). Each probe has a 2 s timeout; results are cached
in-process for 30 s. Append ?refresh=1 to force a fresh probe.
Error handling
Errors use a uniform JSON envelope:
{"error": {"code": "...", "message": "...", "request_id": "..."}}
details is included when the handler has structured extra context to return.
Session isolation
Stateful APIs read X-Session-ID and default to "default". Chart payloads,
chart selections, and calendar selections are all stored per session.
In browser flows, TerraFin usually generates a per-tab session id and sends it
on every chart request. Notebook and direct Python helpers intentionally use the
default chart session unless an explicit session id is provided.
Use the accessors in chart/state.py and calendar/state.py instead of
touching their internal storage directly.
Page routes
| Route | Purpose |
|---|---|
/chart |
Interactive chart page |
/dashboard |
Watchlist, breadth, valuation, and cache status |
/market-insights |
Regime summary, guru portfolios, top companies |
/calendar |
Earnings and macro event calendar |
/stock and /stock/{ticker} |
Stock Analysis page with chart-first loading |
/watchlist |
Personal watchlist management page |
Each page route respects TERRAFIN_BASE_PATH.
Chart
Source: src/TerraFin/interface/chart/
The chart is TerraFin's main visualization surface. It stores a session-scoped source payload, display payload, named series, pin state, and per-series history metadata. Stock Analysis, Market Insights, the chart page, and notebook helpers all use this same backend chart session model.
For the full processing and management flow, see chart-architecture.md.
API endpoints
| Method | Path | Description |
|---|---|---|
GET |
/chart/api/chart-data |
Get the current display payload plus entries and historyBySeries |
POST |
/chart/api/chart-data |
Set the current session from raw payload data and mark those series complete |
POST |
/chart/api/chart-view |
Rebuild the display payload for a new view such as daily or monthly |
GET |
/chart/api/chart-selection |
Read the current chart selection |
POST |
/chart/api/chart-selection |
Save the current chart selection |
POST |
/chart/api/chart-series/add |
Add a named series by TerraFin lookup name, seeded with recent history |
POST |
/chart/api/chart-series/set |
Reset the session to one named series, seeded with recent history |
POST |
/chart/api/chart-series/progressive/set |
Explicit progressive seed route for one named series |
POST |
/chart/api/chart-series/progressive/backfill |
Backfill older history for a seeded series |
POST |
/chart/api/chart-series/remove |
Remove a named series |
GET |
/chart/api/chart-series/names |
List currently loaded named series |
GET |
/chart/api/chart-series/search |
Search available indicator, index, and economic names |
GET |
/chart |
Serve the chart page |
Auto-computed indicators
When the payload contains exactly one candlestick series, TerraFin appends:
| Indicator | Default params | Indicator group |
|---|---|---|
| Moving Averages | SMA 20, 60, 120, 200 | ma-20, ma-60, ma-120, ma-200 |
| Bollinger Bands | window 20, ±2σ | bb |
| RSI | window 14, levels at 70/30 | rsi |
| MACD | fast 12, slow 26, signal 9 | macd |
| Realized Volatility | window 21 | realized-vol |
| Range Volatility | window 20 (Parkinson) | range-vol |
| Mandelbrot Fractal Dimension | windows 65, 130, 260 | mfd |
Indicator adapter source: src/TerraFin/interface/chart/indicators/adapter.py.
Chart client (Python)
Source: src/TerraFin/interface/chart/client.py
| Function | Description |
|---|---|
display_chart(df) |
Open chart in browser. Starts server if needed. Blocks. |
display_chart_notebook(data) |
Display in Jupyter notebook. Waits for readiness, seeds the default chart session, and returns an IFrame bound to that same session. |
update_chart(data, pinned=False, session_id=None) |
POST data to a running server. Returns True on success. |
get_chart_selection() |
GET the current selection from the server. |
These helpers accept TimeSeriesDataFrame or list[TimeSeriesDataFrame].
Single OHLC series render as candlesticks; multi-series payloads render as
comparison lines.
Notebook and embedded use:
import TerraFindoes not auto-load.env- env-backed features lazy-load
.envon first use unlessTERRAFIN_DISABLE_DOTENV=1 - for deterministic notebook or script setup, call:
from TerraFin import configure
configure()
- if the kernel runs outside the repo root, use
configure(dotenv_path="/absolute/path/to/.env") - if you need the resolved typed settings, inspect
load_terrafin_config()instead of reading env vars directly
Dashboard
Source: src/TerraFin/interface/dashboard/
The dashboard is the main consumer of PrivateDataService. It mixes private
source data, cache status, and a few valuation-style summary endpoints. If the
private source is unavailable, TerraFin falls back to bundled public-safe
fixtures or empty defaults for those widgets.
Important boundary:
- dashboard/widget payloads are not automatically chart-series contracts
- if a private-source feature needs optimized chart serving, promote it into
the data layer as a
TimeSeriesDataFrameseries first - otherwise keep it as a widget payload in
PrivateDataService
API endpoints
| Method | Path | Description |
|---|---|---|
GET |
/dashboard/api/watchlist |
Watchlist snapshot (symbols, names, moves) |
GET |
/dashboard/api/market-breadth |
Market breadth metrics (label, value, tone) |
GET |
/dashboard/api/trailing-forward-pe-spread |
Trailing minus forward P/E spread summary and history |
GET |
/dashboard/api/cape |
Current CAPE snapshot |
GET |
/dashboard/api/fear-greed |
Fear and Greed summary if available |
GET |
/dashboard/api/cache-status |
Status of all registered cache sources |
POST |
/dashboard/api/cache-refresh |
Refresh cache sources (?force=bool) |
GET |
/dashboard/api/gex/spx |
SPX GEX live snapshot from CBOE options (regime, spot, zero-gamma, call/put walls, per-strike and per-expiration buckets) |
GET |
/dashboard/api/gex/spx/history |
SPX GEX daily history from SqueezeMetrics (2011–present) |
The practical rule for future private-source additions is:
- chart/search/progressive use case -> build a private series contract first
Examples:
Fear & Greed,Net Breadth - dashboard-only use case -> keep a private widget payload
Calendar
Source: src/TerraFin/interface/calendar/
The calendar merges private calendar data and TerraFin-fetched macro events into
one session-aware view. Events are categorized as earning, macro, or
event. This page remains usable in public/demo mode because earnings and
macro events are still fetched through TerraFin's local provider paths, while
private calendar events use the same fallback chain as the dashboard. As with
the dashboard, a warmed private-source cache is not a substitute public data
source.
API endpoints
| Method | Path | Query params | Description |
|---|---|---|---|
GET |
/calendar/api/events |
month, year, categories, limit |
Filtered events |
POST |
/calendar/api/events |
— | Upsert events |
GET |
/calendar/api/selection |
— | Get selection state |
POST |
/calendar/api/selection |
— | Set selection state |
Market Insights
Source: src/TerraFin/interface/market_insights/
Market insights provides higher-level market context and institutional positioning. The regime endpoint is currently a static placeholder response; guru portfolio data is fully backed by the SEC EDGAR provider. The top-companies widget also degrades cleanly when the private source is not configured.
API endpoints
| Method | Path | Description |
|---|---|---|
GET |
/market-insights/api/regime |
Market regime (placeholder) |
GET |
/market-insights/api/macro-info |
Macro instrument summary (?name=) |
GET |
/market-insights/api/investor-positioning/gurus |
List available guru names |
GET |
/market-insights/api/investor-positioning/holdings |
Guru portfolio (?guru=, optional ?filing_date=) |
GET |
/market-insights/api/investor-positioning/history |
Filing index for period dropdown (?guru=) |
GET |
/market-insights/api/top-companies |
Private-source top-companies snapshot |
SPX Gamma Exposure
The Market Insights page includes an SPX GEX accordion panel. GEX data is
fetched eagerly at page mount (not on accordion open) via
GET /dashboard/api/gex/spx, so the panel renders immediately when the user
expands it. Historical data comes from GET /dashboard/api/gex/spx/history.
The snapshot card hides while loading or when CBOE data is unavailable
(available: false).
Investor positioning loading strategy
Three-tier loading minimises time-to-first-render:
- Fast path (
/holdingswithoutfiling_date) — fetches exactly 2 XMLs (latest + previous quarter) so colour coding works immediately. - Index only (
/history) — returns the SEC submissions index with no XML fetch; used to populate the period dropdown. - Background prefetch — triggered by
/history; caches remaining quarters viaasyncio.Taskso historical periods load instantly when selected.
Cancellation pattern (data_routes.py: _submit_prefetch):
_prefetch_tasks: dict[str, asyncio.Task] = {}
async def _submit_prefetch(guru, filings):
for task in _prefetch_tasks.values():
if not task.done():
task.cancel() # cancel ALL existing prefetch tasks
_prefetch_tasks.clear()
_prefetch_tasks[f"prefetch:{guru}"] = asyncio.create_task(
_prefetch_holdings_async(guru, filings)
)
When the user switches gurus, every in-flight prefetch is cancelled immediately. run_in_executor runs the blocking SEC download in a thread; CancelledError fires between iterations (not mid-download). To add a new cancellable background job type, follow the same pattern: key the task dict by a domain-specific string, clear all on new submission if mutual exclusion is desired, or key per-domain for independent queues.
Market Insights now uses the shared TerraFin chart routes directly:
POST /chart/api/chart-series/progressive/setfor initial seedPOST /chart/api/chart-series/addfor warm addPOST /chart/api/chart-series/removefor warm remove
macro-info is the page-specific helper for the focused header block. The
chart session itself is no longer managed through separate macro-focus
routes.
Stock Analysis
Source: src/TerraFin/interface/stock/
Stock Analysis combines a chart-first page route with a small API family for
company profile, earnings history, financials, SEC filings, and search routing.
The page itself uses the shared TerraFin chart session and progressive
3Y -> full history loading described in
chart-architecture.md.
Page routes
| Method | Path | Description |
|---|---|---|
GET |
/stock and /stock/ |
Stock landing page |
GET |
/stock/{ticker} |
Stock Analysis page for one ticker |
API endpoints
| Method | Path | Description |
|---|---|---|
GET |
/stock/api/company-info |
Company profile and price summary (?ticker=) |
GET |
/stock/api/earnings |
Earnings history (?ticker=) |
GET |
/stock/api/financials |
Financial statements (?ticker=, statement=, period=) |
GET |
/stock/api/fcf-history |
Annual FCF/share history + 3yr-avg/latest-annual/TTM candidates and the source the auto cascade would pick (?ticker=, years=10). Also returns ttmFcfPerShare + ttmSource (how the TTM value was computed: quarterly_ttm or annual). See api-reference.md. |
GET / POST |
/stock/api/dcf |
Forward DCF. POST body accepts projectionYears (5/10/15), fcfBaseSource (auto/3yr_avg/ttm/latest_annual), and turnaround inputs (breakevenYear, breakevenCashFlowPerShare, postBreakevenGrowthPct) on top of the base overrides. |
GET / POST |
/stock/api/reverse-dcf |
Reverse DCF (market-implied growth). POST accepts projectionYears, growthProfile, base overrides. |
GET |
/stock/api/beta-estimate |
TerraFin's beta_5y_monthly estimate against the mapped benchmark. |
GET |
/stock/api/gex |
GEX snapshot for a ticker (?ticker=). Returns regime, spot, zero-gamma strike, call/put walls, by-strike and by-expiration GEX buckets. |
GET |
/stock/api/filings |
Recent 10-K / 10-Q / 8-K list with EDGAR URLs (?ticker=, limit=) |
GET |
/stock/api/filing-document |
Parsed markdown + TOC for one filing (?ticker=, accession=, primaryDocument=, form=, includeImages=) |
GET |
/resolve-ticker?q=... |
Resolve a query string to a ticker symbol and company name (root route — no /stock/api/ prefix) |
Page layout (/stock/{ticker})
The stock-detail page is a vertical stack of sections. Row 2 (Earnings + FCF history) is height-capped on desktop so the page stays scannable; longer content scrolls inside the cards rather than expanding them.
| Row | Left card | Right card |
|---|---|---|
| 1 | Market Chart (price history + indicators) | Overview & Valuation (company profile, price context, key metrics) |
| 2 (capped at 280px desktop) | Earnings History (EPS estimate / reported / surprise table, vertical scroll) | FCF / Share History (annual FCF/share bars, latest-TTM right-gutter callout, 3yr-Avg dashed reference line) |
| 3 | DCF Valuation (input form + Projected FCF chart at the bottom) | DCF Valuation Result (intrinsic value tiles, sensitivity heatmap, projection table) |
| 4 | Reverse DCF (toggled, collapsed by default) — when expanded, shows input + result side-by-side mirroring Row 3. The Reverse DCF Result card carries its own Projected FCF chart at the bottom. | |
| 5 | SEC Filings (US-listed issuers only; auto-hidden otherwise). |
DCF Valuation card
The forward-DCF input card hosts:
- Forecast Horizon — segmented control (
5/10/15years) and a Turnaround Mode checkbox. Turnaround mode swapsBase Growth %forBreakeven Year/Breakeven FCF / Share/Post-Breakeven Growth %inputs. - FCF Base Source — segmented control (
Auto/3yr Avg/TTM/Latest Annual). Selecting a source auto-fills the Base FCF / Share field with the corresponding candidate value (read from/stock/api/fcf-history'scandidates). If the user types over the auto-filled value, a↺ Revert to {source} ($X)chip surfaces under the field; clicking it restores the source's value. - Model Inputs grid — Base FCF / Share, Base Growth %, Terminal Growth %,
Beta (with a
Compute Betabutton that runsbeta_5y_monthly), Equity Risk Premium %. - Explain inputs toggle (top-right of the card header). OFF by default;
hides every "i" icon for clean entry by power users. ON reveals all input
hints. State is persisted in
localStorage(terrafin.dcf.explainInputs) via theuseExplainInputshook. Implemented through anInfoHintVisibilityContextprovider — theInfoHintcomponent reads the context and returnsnullwhen hidden. - Projected FCF / Share chart — appears at the bottom after running DCF. Bars for ≤15-year horizons; line + shaded band (bear/bull envelope, base line) for longer horizons. In bar mode with multi-scenario data, each base bar carries a vertical whisker from bear to bull with colored end-caps so the scenario spread is visible. The Reverse DCF Result card uses the same component (single-scenario, implied-schedule label).
FCF / Share History chart
FcfHistoryChart renders historical annual FCF/share as filled bars
(green/red), the latest TTM as a small blue pill in the right gutter
connected by a dashed leader line to the last annual bar's top, and the 3yr
Avg as a teal dashed horizontal line with a halo'd inline label at the
left-inside of the plot. Y-axis uses nice-number ticks tightly clipped to the
data range (no forced 0 inclusion when all values share a sign). Hover on any
bar / TTM marker / 3yr Avg line shows a small white tooltip with the value.
SEC Filings panel
The /stock/{ticker} page includes a SEC Filings card for every US-listed
issuer. The card is hidden automatically for tickers without an SEC CIK (e.g.
KOSPI / TSE / HKEX issuers) so non-US pages stay uncluttered.
For supported tickers the card surfaces:
- a form dropdown derived from
df.form.unique()(covers 10-K, 10-Q, amendments, 8-K, 20-F, 40-F, etc.); - a chronological filing list with a View on EDGAR link per row pointing
at the SEC inline-XBRL viewer (
/ix?doc=/Archives/...); - a reader that opens inline below the list, with:
- a two-level accordion preserving Part I / Part II as outer collapsibles and Items (Item 1, Item 2 MD&A, …) as nested inner collapsibles;
- a compact custom markdown renderer that handles our
parse_sec_filingoutput (##/###headings, paragraphs, GFM pipe tables, blockquote fallbacks, inline-image placeholders) without pulling in a general markdown dep; - a "View source on EDGAR" pill in the reader header.
The parsed markdown is cached for 30 days via the shared sec_filings
CacheManager namespace (see caching.md), so reopening a filing
is free across sessions. See data-layer.md for the
underlying parse_sec_filing / build_toc / fetch_and_parse_filing helpers.
For 8-K (and 8-K/A) filings, the route returns the parsed cover doc plus any
EX-99.x exhibits (earnings press release as ## Exhibit 99.1 — Press Release,
CFO commentary as ## Exhibit 99.2 — ..., etc.) so the substantive content is
reachable from the sidebar TOC. The sidebar bumps to max_level=3 for 8-Ks so
exhibit-body subheadings (e.g. ### Q1 FY27 Summary) surface as navigable
entries.
Agent integration
When the user opens a filing, the panel publishes the currently-focused
section to the agent side-panel via publishAgentViewContext. The selection
carries ticker, form, accession, primaryDocument, sectionSlug,
sectionTitle, a bounded sectionExcerpt (≤ 4 KB), and EDGAR URLs. The
hosted agent's current_view_context tool reads this payload, and the agent
can call sec_filings, sec_filing_document, or sec_filing_section to
fetch the full body when the excerpt is not enough (e.g. "summarize their
business" on a 10-Q will trigger a cross-filing pivot to the most recent
10-K's Item 1. Business). See the sec_filings row in the common-tasks
table at agent/usage.md.
For the view-context pipeline (how publishAgentViewContext reaches the
agent, session/context identity, and how current_view_context() reads
the current panel), see agent/architecture.md
and agent/hosted-runtime.md.
Watchlist
Source: src/TerraFin/interface/watchlist/
The watchlist page is a dedicated personal-management surface. It reuses the
dashboard watchlist API family rather than exposing a separate /watchlist/api
namespace.
Page routes
| Method | Path | Description |
|---|---|---|
GET |
/watchlist and /watchlist/ |
Personal watchlist page |
API endpoints
The watchlist page uses the /dashboard/api/watchlist API family.
| Method | Path | Description |
|---|---|---|
GET |
/dashboard/api/watchlist |
Full watchlist snapshot (items with symbol, name, move %, tags) |
POST |
/dashboard/api/watchlist |
Add a symbol (body: {symbol, tags?: []}) |
DELETE |
/dashboard/api/watchlist/{symbol} |
Remove a symbol. Pass ?group=<tag> to remove only from that group. |
PATCH |
/dashboard/api/watchlist/{symbol}/tags |
Update tags (`body: {tags, mode: "set" |
GET |
/dashboard/api/watchlist/groups |
List groups with item counts |
POST |
/dashboard/api/watchlist/groups |
Create an empty named group (body: {name}) |
DELETE |
/dashboard/api/watchlist/groups/{tag} |
Delete a group and remove its tag from all items |
POST |
/dashboard/api/watchlist/groups/rename |
Rename a group (body: {old, new}) |
PUT |
/dashboard/api/watchlist/groups/order |
Persist group display order (body: {groups: [name, ...]}) |
PUT |
/dashboard/api/watchlist/groups/{group}/item-order |
Persist item order within a group (body: {symbols: [...]}) |
PUT |
/dashboard/api/watchlist |
Bulk-update all symbols and tags (body: {symbols: [{symbol, tags}]}) |
Drag reorder
The watchlist frontend uses @dnd-kit/core for touch- and pointer-compatible drag reorder. Groups and ticker rows are independently sortable. Order is persisted optimistically: the client applies the new order immediately via itemOrderOverride state, then POSTs to the backend. On error the override is cleared and the server order is restored.
Agent API
Source: src/TerraFin/interface/agent/data_routes.py
The Agent API exposes TerraFin's optimized processing pipeline for programmatic
consumers. It is backed by the shared service in src/TerraFin/agent/service.py
rather than a separate simplified path.
That means:
- market and macro requests use the same progressive-history aware data contract
- view transforms match the chart stack
- indicator computation matches chart indicator math
- every response includes top-level
processingmetadata
For most consumers, prefer the Python client in src/TerraFin/agent/client.py
or the terrafin-agent CLI over calling raw routes directly.
API endpoints
| Method | Path | Query Params | Description |
|---|---|---|---|
GET |
/agent/api/resolve |
q |
Resolve a free-form name into TerraFin's stock or macro path |
GET |
/agent/api/market-data |
ticker, depth, view |
Market or macro series plus processing metadata |
GET |
/agent/api/indicators |
ticker, indicators, depth, view |
Raw indicator results computed from the shared processing pipeline |
GET |
/agent/api/market-snapshot |
ticker, depth, view |
Price action + indicator summaries + breadth + watchlist |
GET |
/agent/api/company |
ticker |
Company profile and price summary |
GET |
/agent/api/earnings |
ticker |
Earnings history |
GET |
/agent/api/financials |
ticker, statement, period |
Financial statement table |
GET |
/agent/api/portfolio |
guru |
Guru portfolio holdings |
GET |
/agent/api/economic |
indicators (comma-separated FRED codes) |
Economic indicator series |
GET |
/agent/api/macro-focus |
name, depth, view |
Macro instrument summary plus series data |
GET |
/agent/api/lppl |
name, depth, view |
LPPL bubble-confidence summary from the shared agent/chart processing pipeline |
GET |
/agent/api/calendar |
year, month, categories, limit |
Calendar events with processing metadata |
GET |
/agent/api/runtime/agents |
- | Hosted runtime agent catalog plus exposed tools and runtime readiness metadata |
POST |
/agent/api/runtime/sessions |
body: agentName, optional sessionId, systemPrompt, metadata |
Create a hosted runtime conversation session when the selected hosted model is configured |
GET |
/agent/api/runtime/sessions |
- | List hosted sessions from the transcript-derived session index |
GET |
/agent/api/runtime/sessions/{session_id} |
- | Read hosted runtime session state, transcript-derived message history, and tools |
DELETE |
/agent/api/runtime/sessions/{session_id} |
- | Archive a hosted session transcript and remove it from active history |
POST |
/agent/api/runtime/sessions/{session_id}/messages |
body: content |
Append a user turn and run the hosted model/tool loop |
GET |
/agent/api/runtime/sessions/{session_id}/tasks |
- | List background tasks for a hosted session |
GET |
/agent/api/runtime/sessions/{session_id}/approvals |
- | List approval requests for a hosted session |
GET |
/agent/api/runtime/tasks/{task_id} |
- | Read a hosted background task |
POST |
/agent/api/runtime/tasks/{task_id}/cancel |
- | Cancel a hosted background task |
GET |
/agent/api/runtime/approvals/{approval_id} |
- | Read one approval request |
POST |
/agent/api/runtime/approvals/{approval_id}/approve |
body: optional note |
Approve a pending request |
POST |
/agent/api/runtime/approvals/{approval_id}/deny |
body: optional note |
Deny a pending request |
Time-series endpoints use depth=auto|recent|full.
autostarts with the optimized recent/progressive path for market and macro seriesfullforces complete-history loading from the start
LPPL route note:
/agent/api/lppl uses TerraFin's calibrated default LPPL scan from the shared
analytics helper. The full article-style 750→50 ladder is kept as a notebook /
research option via lppl(..., n_windows=None) and is not exposed over HTTP.
Runtime route note:
/agent/api/runtime/* is the stateful hosted-agent family. It uses the same
shared capability kernel as the Python client, CLI, and stateless
/agent/api/* routes, but persists conversation history through append-only
local transcripts plus a transcript-derived session index. Tasks, approvals,
audit, and published view context remain in the hosted runtime store. Hosted
model execution is provider-driven rather than OpenAI-only.
Supported view values:
dailyweeklymonthlyyearly
Supported indicator names for /agent/api/indicators:
rsimacdbbsma_Nsuch assma_20realized_volrange_volmfdmfd_65mfd_130mfd_260
Unknown indicator names are skipped and returned in the unknown field.
For chart overlays, TerraFin shows the medium-horizon MFD 130 line by
default to preserve readability. The agent/API still exposes the explicit
mfd_65, mfd_130, and mfd_260 series plus the aggregate mfd response.
Python client and CLI
Preferred public entrypoints:
TerraFin.agent.TerraFinAgentClientterrafin-agent
Both wrap the same service layer and normalized response shapes exposed by the HTTP API.
OpenAPI
The FastAPI schema is available at /openapi.json. The agent routes use
explicit response models so generic agents can inspect the contract without
reverse-engineering route handlers.
Frontend
Source: src/TerraFin/interface/frontend/
The frontend is a React SPA. Built assets live in frontend/build/ and are
served directly by FastAPI, so Node.js is only needed when you are editing the
frontend itself.
Building from source
cd src/TerraFin/interface/frontend
npm install
npm run build
Do not commit node_modules/. The built output is committed so the server can
run in environments without a frontend toolchain.
Signals
Source: src/TerraFin/signals/ (umbrella for alerting + reports + channels), src/TerraFin/interface/signals/ (HTTP routes for inbound webhook).
The signals/ module groups two related output paths:
signals/alerting/— real-time threshold alerts (RSI, breakout, etc.) pushed when an external monitoring service POSTs to TerraFin.signals/reports/— scheduled narrative briefings (currently weekly) generated locally and surfaced on the dashboard.signals/channels/— shared output sinks (Telegram, webhook, stdout) used by both.
Alerting
TerraFin supports a push-model alert pipeline: an external real-time service monitors tickers and POSTs signals back to TerraFin, which forwards them to Telegram.
Architecture
External alert API ──register tickers──▶ TerraFin (outbound)
External alert API ──POST /signals/api/signal──▶ TerraFin (inbound)
TerraFin ──sendMessage──▶ Telegram Bot API ──▶ User
TerraFin never handles real-time tick data. Signal computation stays in the external service.
Inbound webhook
| Method | Path | Description |
|---|---|---|
POST |
/signals/api/signal |
Receive signal from external API → forward to Telegram |
POST |
/alerting/api/signal |
Legacy alias for the above (kept for existing senders) |
Request body (InboundSignal):
{
"ticker": "AAPL",
"signal": "20-day MA touch",
"severity": "high",
"signal_id": "uuid-from-sender",
"fired_at": "2026-04-30T09:00:00",
"name": "Apple Inc.",
"snapshot": {"close": 192.5, "rsi": 68.2}
}
fired_at: optional ISO 8601 datetime string. Naive datetimes (no timezone offset) are accepted and stored as-is — TerraFin does not normalize to UTC. Omit or passnullif unknown.signal_idis optional but required for deduplication (sender-provided UUID, not TerraFin-generated)name: optional company/indicator display name; if blank, TerraFin enriches it from the watchlist cache before forwardingsnapshot: optional open key/value map of detector context at fire time (e.g. OHLCV fields, indicator values). The Telegram formatter readscloseto derive price direction (▲ ifclose> previous close, ▼ if lower, — if absent or equal). All other keys are informational and forwarded as-is.snapshot: nullor omitting the field produces — for direction.X-Signatureheader: HMAC-SHA256 of request body, keyed withTERRAFIN_SIGNALS_WEBHOOK_SECRET- If
TERRAFIN_SIGNALS_WEBHOOK_SECRETis unset, the endpoint returns503and refuses all signals — the secret is required, not optional - Per-IP rate limit: 60 requests / 60 seconds; excess returns
429
Env vars
| Variable | Purpose | Required |
|---|---|---|
TERRAFIN_SIGNALS_PROVIDER_URL |
External alert API base URL | To enable outbound registration |
TERRAFIN_SIGNALS_PROVIDER_KEY |
Bearer token for external API | If API requires auth |
TERRAFIN_SIGNALS_WEBHOOK_SECRET |
HMAC secret for inbound verification | Required (endpoint returns 503 if unset) |
TERRAFIN_SIGNALS_CHANNEL |
telegram to forward via Telegram Bot |
To enable Telegram |
Telegram setup
Create a bot via @BotFather:
/newbot→ copy tokenSave token:
terrafin-signals telegram setup 123456789:AAHfiq...
- Pair — run the command, then DM your bot on Telegram:
terrafin-signals telegram pair
Chat ID is captured automatically and saved to ~/.terrafin/telegram.json.
- Test:
terrafin-signals telegram test
- Add to
.env:
TERRAFIN_SIGNALS_CHANNEL=telegram
TERRAFIN_SIGNALS_PROVIDER_URL=https://your-alert-api.com
TERRAFIN_SIGNALS_WEBHOOK_SECRET=your-hmac-secret
Reports
Source: src/TerraFin/signals/reports/
The dashboard auto-generates a weekly markdown report every Friday at 16:30 ET (TERRAFIN_CACHE_TIMEZONE). Reports persist under ~/.terrafin/reports/weekly/<as_of>.{md,json} indefinitely — disk cost is negligible and trend stacking is the value.
Pipeline
- Universe:
watchlist_service.get_watchlist_snapshot(). Falls back to Magnificent 7 (AAPL/MSFT/NVDA/GOOGL/AMZN/META/TSLA) when the watchlist is empty / MongoDB unavailable. M7 reports are explicitly labeled "Sample" in the title and footer CTA. - Per-ticker: yfinance close + volume → 5-trading-day WoW; intra-week ≥4% moves; volume ratio vs 20-day avg; ticker-relevant Google News headlines (date-ranged via
after:/before:); upcoming earnings (yfinance). - Action wording driven by
(anomaly_flag, has_headline, vol_ratio)decision tree. - Optional agent enrichment (when TerraFin agent runtime is configured): index context paragraph (vs ^GSPC/^SOX/^RUT), SEC 8-K drill on unattributed events, macro calendar context. Each section independently optional; failures don't block the deterministic core.
Dashboard surface
| Method | Path | Description |
|---|---|---|
GET |
/dashboard/api/reports/weekly |
List most recent reports (max 12) |
GET |
/dashboard/api/reports/weekly/{as_of} |
Fetch markdown for a specific report |
POST |
/dashboard/api/reports/weekly/run |
Manually trigger generation (CLI/cron) |
The dashboard top bar shows a 🔔 bell that opens a panel rendering report markdown. A red dot on the bell indicates an unread report (compared against localStorage["tf-weekly-report-seen"]).
CLI
# Generate the current week's report (writes to disk + sends to channel if TERRAFIN_SIGNALS_CHANNEL set)
terrafin-signals weekly
# Backtest: anchor to a historical date — useful for verifying pipeline determinism
terrafin-signals weekly --as-of 2026-03-13 --out /tmp/backtest.md
Telegram delivery
When TERRAFIN_SIGNALS_CHANNEL=telegram, the Friday scheduler also pushes the report. Markdown is converted to Telegram-flavored HTML (<b>, <i>, bullets, code) and chunked under the 4096-char per-message limit.
Outbound alert provider
Server startup registers current watchlist with the external API and starts a 60-second heartbeat to re-register on provider restart.
Extending
To swap the external API provider, implement AlertProvider from data/contracts/alert_provider.py and replace HttpAlertProvider in server.py. The inbound webhook and Telegram forwarding are provider-agnostic.
See also
- data-layer.md for the provider and output model underneath these APIs
- chart-architecture.md for chart sessions, mutations, progressive history, and notebook flow
- feature-integration.md for the cross-layer checklist when exposing a new feature through UI or APIs
- analytics.md for the indicator functions used by chart and agent routes
- caching.md for cache-manager behavior exposed through the dashboard