finterm
A terminal-based financial analysis tool built with Go and Bubbletea.
Trend-following signals · Real-time quotes · Macro dashboard · Sentiment-scored news
Finterm is a keyboard-driven financial analysis tool that runs entirely in your terminal. It provides three independent trend signal systems (EMA crossover, BLITZ, and DESTINY), RSI-based valuation, macroeconomic indicators, and sentiment-scored news — all powered by the Alpha Vantage API.
It's designed for traders and analysts who live in the terminal and want a fast, unified view of market data without leaving their workflow.
Key principles:
- TradingView parity — RSI and EMA calculations match TradingView's
ta.rsi()andta.ema()exactly. RSI uses Wilder's RMA smoothing (alpha = 1/length), EMA seeds with the first source value. - Bar close only — All trend and valuation signals use completed (closed) bars exclusively. No intra-bar repainting. Once a bar closes, its signal is final.
- Dual-path indicators — Equities use Alpha Vantage's server-side technicals. Crypto uses locally computed indicators (since AV's technical endpoints don't cover weekend data). Local implementations also serve as a fallback.
- Single binary —
go buildproduces one static binary. No runtime dependencies, no Docker, no Node.
Trend Following — Watchlist table with a TPI composite score and three independent signal systems per ticker:
- TPI — Trend Probability Indicator: averages FTEMA, BLITZ, and DESTINY into a single score (-1 to +1) with a color-gradient gauge. TPI > 0 = LONG, TPI ≤ 0 = CASH.
- FTEMA — Fast/slow EMA crossover (EMA 10 vs EMA 20). The classic trend direction signal.
- BLITZ — Correlation-based scoring combining TSI (Pearson correlation), adaptive RSI, and threshold confirmation. Fast and reactive.
- DESTINY — Consensus-based scoring using 7 moving averages (SMA, EMA, DEMA, TEMA, WMA, HMA, LSMA) confirmed by adaptive RSI. Conservative, high-conviction.
SYMBOL TPI FTEMA BLITZ DESTINY PRICE RSI VALUATION
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
QQQ ░░░░░▓▓▓░░ LONG ▲ LONG ▼ SHORT $604.52 67.97 ◇ Overval
SPY ░░░░░▓▓▓░░ LONG ▲ LONG ▼ SHORT $673.59 67.56 ◇ Overval
BTC ░░░░░▓░░░░ LONG ▲ LONG ▼ SHORT $74,446 52.84 ○ Fair val
SOL ░░▓▓▓░░░░░ CASH ▼ SHORT ▼ SHORT ▼ SHORT $86.56 44.64 ◇ Underval
Quote Lookup — Type any ticker to get real-time price, volume, change, RSI gauge, and signal system analysis (FTEMA, BLITZ, DESTINY, TPI composite with gauge).
Macro Dashboard — Paneled view of GDP, CPI, inflation, federal funds rate, treasury yields, unemployment, and nonfarm payroll.
News Feed — Scrollable, sentiment-scored articles with ticker/topic filters, color-coded by sentiment (bullish/bearish/neutral).
Navigation — Fully keyboard-driven: 1-4 for tabs, j/k or arrows for rows, r to refresh, ? for help, q to quit.
Finterm follows a strict layered architecture with clear dependency boundaries. The domain layer is pure business logic with zero HTTP or TUI imports.
| Layer | Package | Responsibility |
|---|---|---|
| Presentation | internal/tui/ |
Bubbletea models, views, Lipgloss styling, tab routing |
| Domain | internal/domain/ |
Indicator computation (RSI, EMA), trend scoring, valuation, BLITZ + DESTINY engines |
| Data | internal/alphavantage/, internal/cache/ |
HTTP client with rate limiting and retry, TTL cache |
| Config | internal/config/ |
YAML + env var loading, validation |
The domain layer communicates with the data layer through interfaces — never concrete types. cmd/finterm/main.go wires everything together via constructor injection.
| Indicator | Method | Details |
|---|---|---|
| RSI | Wilder's RMA smoothing | alpha = 1/period, seeded with SMA. Default period: 14 |
| EMA | Standard exponential | alpha = 2/(period+1), seeded with first source value. Periods: 10 (fast), 20 (slow) |
| Signal | Rule |
|---|---|
| FTEMA | EMA(10) > EMA(20) → LONG, EMA(10) < EMA(20) → SHORT |
| TPI | Average of FTEMA (+1/-1), BLITZ (+1/-1/0), DESTINY (+1/-1/0). TPI > 0 → LONG, TPI ≤ 0 → CASH |
| Valuation | RSI < 30 → Oversold, 30–45 → Undervalued, 45–55 → Fair, 55–70 → Overvalued, > 70 → Overbought |
The BLITZ trend following system uses three independent confirmations to generate high-conviction signals:
| Component | Computation | Purpose |
|---|---|---|
| TSI | Pearson correlation of close vs bar index over 14 bars | Trend direction filter (+1 = up, -1 = down) |
| Dynamic RSI | Wilder's RSI with adaptive lookback (min(12, bars_available)) |
Momentum strength |
| RSI Smooth | EMA of Dynamic RSI, same adaptive length | Noise reduction |
Signal rules: LONG when TSI > 0 AND RSI Smooth is rising AND RSI Smooth > 48. SHORT when TSI < 0 AND RSI Smooth is falling AND RSI Smooth < 48. Score latches — holds until the opposite signal fires.
Dynamic length adaptation: Unlike standard indicators that produce NaN for the first N bars, BLITZ adapts its lookback period to available data. At bar 5, a "12-period RSI" uses a 5-period lookback. This means signals start earlier with no warmup gap.
The DESTINY trend following system uses a consensus of 7 moving averages to build the Trend Probability Indicator (TPI), confirmed by Dynamic RSI:
| Component | Computation | Purpose |
|---|---|---|
| SMA | Simple MA, period 45 | Baseline trend |
| EMA | Exponential MA, period 45 | Responsive trend |
| DEMA | Double EMA, period 90 | Lag-reduced trend |
| TEMA | Triple EMA, period 135 | Minimal-lag trend |
| WMA | Weighted MA, period 45 | Recency-biased trend |
| HMA | Hull MA, period 45 | Fast-response trend |
| LSMA | Least Squares MA, period 45, offset 6 | Regression-projected trend |
Each MA is scored: +1 (rising), -1 (falling), 0 (flat). The TPI is the average of all 7 scores, ranging from -1 to +1.
Signal rules: LONG when TPI > 0.5 AND RSI Smooth is rising AND RSI Smooth > 56. SHORT when TPI < -0.5 OR (RSI Smooth is falling AND RSI Smooth < 56). The asymmetry is deliberate — entries require consensus, exits are more aggressive.
Dynamic length adaptation: All 7 MAs and the RSI use the same adaptive-length pattern as BLITZ, allowing signals on limited data.
User input → tea.Cmd → fetch (AV client + cache) → domain engine → tea.Msg → view update
For equities, RSI and EMA are fetched from Alpha Vantage's server-side endpoints. For crypto, raw OHLCV data is fetched and indicators are computed locally in Go, because AV's technical endpoints follow equity market hours and exclude weekends.
- Go 1.26+
- Alpha Vantage API key — get one here (paid tier recommended for 75 req/min)
- A terminal with 256-color support (iTerm2, Alacritty, kitty, Windows Terminal, etc.)
git clone https://github.com/shinsekai/finterm.git
cd finterm
make build
./bin/fintermgo install github.com/shinsekai/finterm/cmd/finterm@latestFinterm loads configuration from config.yaml in the working directory. Environment variables take precedence.
# Minimum: set your API key
export FINTERM_AV_API_KEY="your_key_here"
# Or copy and edit the example config
cp config.example.yaml config.yamlapi:
key: "" # Or use FINTERM_AV_API_KEY env var
base_url: "https://www.alphavantage.co/query"
rate_limit: 70 # Requests per minute (AV premium: 75)
timeout: 10s
max_retries: 3
watchlist:
equities: [AAPL, MSFT, GOOGL, AMZN, NVDA]
crypto: [BTC, ETH, SOL]
trend:
rsi_period: 14
ema_fast: 10
ema_slow: 20
valuation:
rsi_period: 14
oversold: 30
undervalued: 45
fair_low: 45
fair_high: 55
overvalued: 70
overbought: 70
blitz:
rsi_length: 12 # Dynamic RSI lookback period
tsi_period: 14 # Pearson correlation lookback period
threshold: 48 # RSI smooth threshold for signal generation
destiny:
ma_length: 45 # Base period for 7 MAs (DEMA uses 2×, TEMA uses 3×)
rsi_length: 18 # Dynamic RSI lookback period
rsi_threshold: 56 # RSI smooth threshold for signal confirmation
lsma_offset: 6 # LSMA projection offset
cache:
intraday_ttl: 60s
daily_ttl: 1h
macro_ttl: 6h
news_ttl: 5m
crypto_ttl: 5m
theme:
style: "default" # default | minimal | colorblind| Key | Action |
|---|---|
1 / 2 / 3 / 4 |
Switch to Trend / Quote / Macro / News tab |
Tab |
Cycle to next tab |
j / k or ↑ / ↓ |
Navigate rows |
Enter |
Submit ticker (Quote view) / Open article (News view) |
r |
Refresh current view |
f |
Toggle filter (News view) |
s |
Toggle sort (News view) |
? |
Show help overlay |
q / Ctrl+C |
Quit |
# Install dependencies
go mod download
# Install linting tools
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
go install golang.org/x/vuln/cmd/govulncheck@latestmake build # Build binary to ./bin/finterm
make run # Build and run
make test # Run all tests
make test-race # Run tests with race detector
make lint # Run golangci-lint
make vet # Run go vet
make fmt # Format all code
make clean # Remove build artifacts# All tests
go test ./...
# With coverage
go test -coverprofile=coverage.out ./internal/...
go tool cover -func=coverage.out
# Domain layer only (indicator logic)
go test ./internal/domain/...
# With race detector
go test -race ./...finterm/
├── cmd/finterm/main.go # Entry point — config, DI, bootstrap
├── internal/
│ ├── tui/ # Bubbletea views and components
│ │ ├── app.go # Root model + tab router
│ │ ├── trend/ # Trend following view
│ │ ├── quote/ # Quote lookup view
│ │ ├── macro/ # Macro dashboard view
│ │ ├── news/ # News feed view
│ │ └── components/ # Shared UI (spinner, table, help)
│ ├── domain/ # Pure business logic
│ │ ├── trend/ # Trend engine + scoring
│ │ │ └── indicators/ # RSI, EMA (local + remote)
│ │ ├── valuation/ # RSI-based valuation
│ │ ├── dynamo/ # Shared dynamic-length MA primitives (SMA, EMA, RMA, WMA, DEMA, TEMA, HMA, LSMA, RSI, TSI)
│ │ ├── blitz/ # BLITZ signal engine
│ │ └── destiny/ # DESTINY signal engine
│ ├── alphavantage/ # API client + typed models
│ ├── cache/ # In-memory TTL cache
│ └── config/ # YAML + env var loading
├── config.example.yaml
├── CLAUDE.md # Project guidelines (for AI-assisted dev)
├── PRD.md # Product requirements
├── EPICS.md # Epics and tasks
└── Makefile
- Read
CLAUDE.md— it's the project constitution covering coding standards, architecture rules, and testing requirements. - Pick a task from
EPICS.md— each task is self-contained with acceptance criteria and test definitions. - Follow the conventions:
gofmt,go vet, table-driven tests, error wrapping with context. - Run
make test lint vetbefore submitting. - Commit messages:
<scope>: <description>(e.g.,trend: add local RSI computation).
Why a composite TPI instead of one signal? Each signal system captures a different dimension of trend. FTEMA is a simple lagging direction indicator. BLITZ is fast and reactive, using Pearson correlation. DESTINY is conservative, polling 7 MA types for consensus. The TPI averages all three into a single score: LONG when the average is positive, CASH when it isn't. This prevents over-reliance on any single method — a ticker is only LONG when the weight of evidence supports it.
Why compute crypto indicators locally? Alpha Vantage's technical endpoints (RSI, EMA) map crypto symbols to exchange-listed instruments that follow equity market hours — no weekend data, which creates gaps in 24/7 crypto markets. Fetching raw OHLCV from the crypto endpoints and computing locally ensures continuous, accurate signals.
Why bar close only? Intra-bar computation causes "repainting" — a signal that appears mid-bar might vanish by the time the bar closes. Using completed bars only means every signal is final. This matches how TradingView strategies execute by default.
Why TradingView as the reference? It's what most retail traders use. If Finterm's RSI says 52.3 and TradingView says 52.3 for the same data, trust is established immediately. The key subtlety: TradingView's RSI uses RMA smoothing (alpha = 1/length), not standard EMA smoothing (alpha = 2/(length+1)). Getting this wrong produces visibly different values.
Why three trend signals (EMA + BLITZ + DESTINY)? Each operates at a different speed and philosophy. EMA crossover is a simple, lagging binary — the trend is up or down. BLITZ uses Pearson correlation for trend direction and is fast and reactive (3 conditions, all AND). DESTINY polls 7 different moving averages for consensus and is more conservative — it waits for broad agreement before calling a trend. The asymmetric exit logic (OR for shorts) makes DESTINY quick to protect but slow to commit. Three signals, three speeds: when all agree, conviction is highest; when they diverge progressively, risk is rising.
Why 7 moving averages in DESTINY? Each MA type captures a different aspect of trend: SMA is stable but laggy, EMA reacts faster, DEMA and TEMA progressively reduce lag, WMA biases toward recent prices, HMA is the fastest responder, and LSMA projects the regression line forward. Averaging their direction votes creates a "wisdom of the crowd" signal — if 5+ out of 7 agree the trend is up, it's probably up. The TPI (Trend Probability Indicator) quantifies this consensus as a single number from -1 to +1.
Why does BLITZ use dynamic-length indicators? Standard indicators produce NaN for the first N bars, which is fine on TradingView with thousands of bars but problematic when data is limited. The dynamic length pattern adapts: at bar 5, a "12-period RSI" uses a 5-period lookback. This is ported directly from the Pine Script getDynamicLength() pattern to preserve exact signal parity.
- Charm — Bubbletea, Lipgloss, and Bubbles
- Alpha Vantage — Financial data API
- TradingView — Reference implementation for indicator calculations