Repository navigation
Configuration
Only need keys? Providers has the sign-up links and tells you which ones actually matter.
Run uv run setup to configure interactively, or copy .env.example to .env and override only what you need. Every setting is optional; without an LLM key, the app starts in mock mode. The groups below mirror the sections of .env.example, the defaults live in app/config.py, and everything here is also editable in the app's Settings page without a restart.
Everything on this page is also editable in the app: launch it and open Settings (the /settings page). How the two layers work:
-
.envvalues are the defaults; a value saved in the app becomes an override stored in the SQLite database and applies immediately (no restart). - API keys saved in the app never touch the database: they go to the OS credential vault via keyring (Windows Credential Manager, macOS Keychain, Linux Secret Service) and override
.envkeys. The API never returns a secret value, only a "configured" flag and where the secret is stored. - Reset to .env defaults drops every override and deletes the keychain entries. Without an OS keychain (some headless Linux setups), secrets fall back to the local database unencrypted and the Settings page warns about it.
- The sidebar dots show which sections are ready: green = configured, hollow = optional and unset, amber = a caveat (for example the Alpaca kill switch is off), red = missing (no LLM key).
- Keychain keys do not travel with a copied project folder; re-enter them on a new machine.
.env stays gitignored, and uv run setup remains the fastest first-run path.
uv run setup (or python scripts/setup_wizard.py before dependencies are installed) walks you through everything:
- Provider presets with verified base URLs: OpenAI, Z.AI (GLM), DeepSeek, Alibaba Cloud Qwen (DashScope), OpenRouter, or any custom OpenAI-compatible endpoint.
- Your API key is hidden while typing, and can be validated with a one-token request (a 401 reads as "key rejected", a 404 as "check base URL or model").
- Optional market-data keys, each with its fallback explained.
- Existing
.envvalues are preserved; Ctrl+C at any prompt writes nothing. - Colors on terminals (plain when piped or
NO_COLORis set;BTA_WIZARD_COLOR=1forces them on).
Two jobs, two models: a fast one for the researchers and debaters, a stronger one for the manager's call. Sign-up links live on Providers.
For fast agent work, we recommend one of:
| Provider |
LLM_MODEL value |
Why choose it |
|---|---|---|
| OpenAI | gpt-6-luna |
Fast agent work at list prices that stay cheap per run |
| Z.AI | glm-5.3-flash |
Efficient reasoning with strong quality at a lower serving cost |
| DeepSeek | deepseek-4.1-flash |
Cost-efficient general reasoning for multi-agent runs |
For reasoning and the report (the manager override), we recommend one of:
| Provider |
LLM_MODEL_MANAGER value |
Why choose it |
|---|---|---|
| OpenAI | gpt-6.1-sol |
Deeper reasoning for the final BUY/HOLD/SELL call |
| Z.AI | glm-5.3 |
Strong judgment without premium pricing |
| MiniMax | minimax-v2.6-pro |
Strong long-form analysis for the decision report |
Set the fast one as LLM_MODEL and the strong one as LLM_MODEL_MANAGER (see Per-role models below). Model ids shift over time: copy the exact id from your provider's console. Pricing and availability vary by region.
Set LLM_MODEL to the cheap fast model, then override the manager so the final judgment runs on the stronger one; the researchers and debaters stay fast and inexpensive while the BUY/HOLD/SELL call gets the deep model. Each role can also point at its own endpoint and key:
LLM_MODEL=zai-org/GLM-5.3-Flash
LLM_MODEL_MANAGER=zai-org/GLM-5.3
LLM_BASE_URL_MANAGER=https://your-glm-53-endpoint/v1Any OpenAI-compatible endpoint.
| Variable | Default | Purpose |
|---|---|---|
LLM_BASE_URL |
https://api.openai.com/v1 |
OpenAI-compatible API endpoint |
LLM_API_KEY |
Not set | Enables the LLM agents (researchers, debaters, manager) |
LLM_MODEL |
gpt-5.6-luna |
Model used by every agent without a per-role override |
LLM_TEMPERATURE |
0.2 |
Sampling temperature |
LLM_TIMEOUT_SECONDS |
90 |
Timeout for each agent call |
LLM_REASONING_EFFORT |
Not set | Optional provider-specific reasoning effort (e.g. none/low for GLM) |
LLM_PRICE_IN / LLM_PRICE_OUT
|
0 |
Cost-estimate override in USD per 1M input/output tokens for every role (custom/proxied pricing); 0 uses the built-in list-price table in app/cost.py
|
LLM_MODEL_MANAGER |
LLM_MODEL |
Per-role model for the final BUY/HOLD/SELL call |
LLM_BASE_URL_MANAGER / LLM_API_KEY_MANAGER
|
global values | Optional endpoint/key just for the manager |
LLM_MODEL_ANALYSTS (+ _BASE_URL_ / _API_KEY_) |
global values | Per-role overrides for the 5 researchers |
LLM_MODEL_DEBATE (+ _BASE_URL_ / _API_KEY_) |
global values | Per-role overrides for the bull/bear debaters |
All optional; each has a built-in fallback.
| Variable | Default | Purpose |
|---|---|---|
FINNHUB_API_KEY |
Not set | Company profiles, fundamentals, and news; falls back to yfinance |
OLOSTEP_API_KEY |
Not set | News search/scraping fallback and Reddit/StockTwits sentiment search |
NIXTLA_API_KEY |
Not set | Nixtla TimeGPT 5-day forecast; falls back to the next hosted provider |
TSFM_API_KEY |
Not set | TSFM.ai hosted Chronos-2 5-day forecast, used when TimeGPT is unavailable; falls back to the local trend model |
SEC_EDGAR_USER_AGENT |
Not set | Contact string sent to SEC EDGAR when replaying as-filed fundamentals in backtests; a project default is used unless set |
| Variable | Default | Purpose |
|---|---|---|
MAX_TICKERS |
5 |
Maximum tickers accepted in one analysis |
DEBATE_ROUNDS |
2 |
Debate gate: 2+ enables the judge round at Max depth (single round below it; Fast runs no debate at all) |
RUN_TIMEOUT_MINUTES |
30 |
Wall-clock deadline for one analysis run; a hung provider call fails the run instead of leaving it running forever (P2.8) |
STREAM_REASONING |
0 |
Live reasoning stream: 1 streams agent tokens to the UI (off by default: the stream is mostly the final JSON and reads as noise) |
EXPERIMENTAL_FEATURES |
0 |
Master switch for the Settings Experimentation tab (1 shows it) |
FEATURE_ACCURACY |
0 |
1 serves the experimental Accuracy page (needs the master switch too) |
FEATURE_COMPARE |
0 |
1 serves the experimental Compare page (needs the master switch too) |
FEATURE_WATCHLIST |
0 |
1 serves the experimental Watchlist page (needs the master switch too) |
| Variable | Default | Purpose |
|---|---|---|
DB_PATH |
portfolio.db |
SQLite app database path for run history and the paper order lifecycle |
Holdings live in the Alpaca paper account, not in the app: there is no local
portfolio ledger. STARTING_CASH and DEFAULT_POSITION_SIZE were removed with
the demo portfolio; the paper account's own balance is the starting cash.
Optional. The feature is dormant until both key variables exist. Keys come
from the paper section of the Alpaca dashboard, stay server-side, and are
never returned by an endpoint or logged. The adapter is paper-only by
construction: the SDK client is built with paper=True hardcoded and no
base-url override exists.
| Variable | Default | Purpose |
|---|---|---|
ALPACA_API_KEY_ID |
empty | Paper account key id (ALPACA_API_KEY accepted as an alias) |
ALPACA_API_SECRET_KEY |
empty | Paper account secret (ALPACA_SECRET_KEY accepted as an alias) |
ALPACA_TRADING_ENABLED |
0 |
Kill switch: 0 = read-only, order submissions refused |
ALPACA_MAX_ORDER_USD |
10000 |
Per-order notional cap |
ALPACA_MAX_ORDERS_PER_DAY |
20 |
Non-rejected submissions per UTC day |
ALPACA_MAX_DECISION_AGE_HOURS |
72 |
Research older than this cannot be traded |
BROKER_SYNC_INTERVAL_MINUTES |
15 |
Interval of the reconciliation sweep (minimum 5): refreshes non-terminal local orders by client order id and diffs Alpaca's open orders against them for the out-of-sync banner; a startup sweep runs before autopilot starts |
Automated paper-trading sessions scan the market, add held tickers, run the full pipeline, and submit risk-gated trades on a schedule. Each session always re-checks every held position (sell-or-hold) first, then fills the rest of the session from the market scan, skipping names it analyzed in the last few days so the shortlist rotates instead of repeating for weeks. The on/off toggle lives in the database (Trades page and Settings page) and is seeded from AUTOMATION_ENABLED on first boot. Full behavior in Usage.
| Variable | Default | Purpose |
|---|---|---|
AUTOMATION_ENABLED |
0 |
Seeds the persisted toggle on first boot |
AUTOMATION_INTERVAL_MINUTES |
240 |
Minutes between sessions (minimum 15) |
AUTOMATION_CANDIDATES |
3 |
Discovered candidates per session (the "I Am Feeling Lucky" screen) |
AUTOMATION_CANDIDATE_COOLDOWN_DAYS |
3 |
Names analyzed in the last N days rest, so the shortlist rotates; 0 disables (held positions are always checked) |
AUTOMATION_MIN_CONFIDENCE |
0.65 |
BUYs need at least this final (risk-gated) confidence; SELLs always act |
AUTOMATION_OUTLOOK |
short_term |
Session outlook: day_trade, short_term, or long_term
|
AUTOMATION_DEPTH |
pro |
Session depth: fast, pro, or max (legacy names medium/expert still accepted) |
AUTOMATION_ALLOW_SELLS |
1 |
A SELL decision exits the held position |
AUTOMATION_SESSION_TIMEOUT_MINUTES |
40 |
A session longer than this is cancelled and recorded as failed |
Fractions of total equity applied to every BUY. The last row is a warning-only correlated-exposure brake (roadmap P1.3): it flags a BUY whose group exposure breaches the cap, it never blocks the trade.
| Variable | Default | Purpose |
|---|---|---|
MAX_POSITION_PCT |
0.10 |
Max fraction of equity in one ticker |
MAX_INVESTED_PCT |
0.60 |
Max fraction of equity invested |
MIN_CASH_PCT |
0.10 |
Min cash buffer after a BUY |
MAX_CORRELATED_PCT |
0.25 |
Cap on the BUY ticker plus holdings whose daily returns correlate at/above 0.7 (app/risk.py); past it the result carries a concentration warning with before/after exposure. Missing price history reports unknown instead of a clean pass |
| Variable | Default | Purpose |
|---|---|---|
MEMORY_HORIZON_DAYS |
21 |
Fallback grading window in days; new runs grade on their outlook horizon (1 day day-trade, 5 short term, 21 long term) |
MEMORY_SETTLE_INTERVAL_MINUTES |
60 |
Interval of the scheduled settle pass (minimum 15) that grades every decision whose horizon window closed, even if its ticker is never analyzed again (P1.17) |
MEMORY_REFLECT_WITH_LLM |
0 |
1 asks the LLM for reflection lessons instead of deterministic sentences |
CALIBRATION_MIN_OBSERVATIONS |
30 |
Minimum mature graded decisions before a confidence bucket shows a track record instead of Track record unavailable
|
| Variable | Default | Purpose |
|---|---|---|
BACKTEST_CACHE |
backtests/cache.db |
SQLite snapshot cache location (gitignored) |
BACKTEST_OFFLINE |
0 |
1 makes cache misses fail instead of hitting the network (proves warm re-runs are truly offline) |