A Telegram bot that monitors SEC filings and insider trading for a configurable watchlist.
Single-file Python, two-language UI, no cloud required. Runs on Android (Termux) or any Linux box.
| π 13 Form Types | 10-K, 10-Q, 8-K, Form 4, SC 13G/D, S-1, DEF 14A, and more |
| π Two languages | English + Turkish UI, switch live with /setlang en / /setlang tr |
| π Insider Trading | Form 4 analysis with portfolio-wide sentiment score |
| π Sentiment trend | /sentiment trend [days] β compare current vs. N-day-ago insider mood |
| π Side-by-side compare | /compare AAPL MSFT 10-K β single LLM call, four-axis comparison |
| π Watchlist groups | /addgroup tech AAPL MSFT NVDA β /scangroup tech |
| π Price action | Filing date β +N day stock change appended automatically (free, no API key) |
| πΉ On-demand prices | /checkprice AAPL [days] β last-N-day window summary (yfinance) |
| π° Yahoo Finance news | /checknews AAPL [count] β latest headlines with publisher links (yfinance) |
| π§ Smart Caching | Never re-analyzes a filing already processed; old entries auto-pruned |
| π€ Guided Setup | First-run wizard, starts with language picker β no config file editing needed |
| βοΈ Full Telegram Control | Manage tickers, forms, model, language, schedule, prompts from chat |
| β° Auto Scheduling | Daily auto-scan at a time you set + hourly filing alarm (probe-only) |
| π Weekly Digest | Sunday summary of everything analyzed that week |
| π Filing Diff | Risk-factor comparison between current and previous 10-K / 10-Q |
| βοΈ Custom Prompts | Override the analysis prompt per form type |
| π Inline Original | Button after each analysis to receive the raw filing as .txt |
| π Markdown Reports | /report sends this week's full analyses as a .md file |
| π Webhook Mode | Optional β faster response, lower battery use |
| π‘ Health Monitoring | /status shows uptime, error counts, last scan/alarm times |
| π§ͺ Tested | 327 pytest tests for pure helpers, i18n, config cache, thread safety |
| β‘ OpenRouter Free LLM | openrouter/free, $0 cost |
| π± Lightweight | Single-file bot.py (~2900 lines), runs on a mid-range Android phone via Termux |
sec-analyzer/
βββ bot.py # Main bot β everything in one file
βββ config.py # Thin loader β reads secrets from .env
βββ .env.example # Secrets template β copy to .env, fill in
βββ requirements.txt # pip dependencies
βββ .gitignore
βββ lang/
β βββ en.json # English UI strings (default)
β βββ tr.json # Turkish UI strings
βββ tests/ # 327 pytest tests
β βββ conftest.py
β βββ test_alarm_buttons.py # Interactive alarm + on-demand .md button
β βββ test_cfg.py # Config cache + atomic mutate
β βββ test_checkprice_news.py # /checkprice + /checknews formatters
β βββ test_collect_8k.py # 8-K EX-99.* exhibit collection
β βββ test_compare.py # /compare prompt builder
β βββ test_groups.py # Watchlist groups
β βββ test_hotfixes.py # Atomic JSON IO, wizard guard, digest week
β βββ test_i18n.py # Language loader, t(), fallback
β βββ test_price.py # Stooq parsing + price snippet
β βββ test_probe.py # Alarm probe β whole-watchlist hit list
β βββ test_pure.py # render, extract_section, build_prompt, β¦
β βββ test_sentiment.py # Sentiment parse + trend rendering
β βββ test_startup_company.py # Startup checks + Company cache
β βββ test_state.py # Locks, raw store, cache TTL
β βββ test_tg_llm.py # tg() + llm() characterization tests
βββ README.md
Runtime files (created automatically under ~/sec-analyzer/, not in the repo):
~/sec-analyzer/
βββ bot_config.json # Live settings managed via Telegram
βββ cache.json # Analyzed filings cache
βββ weekly_log.json # Buffer for digest + /report
βββ sentiment_history.json # /sentiment trend history
βββ price_cache.json # Stooq price snippets cache
βββ previous_filings/ # For risk-factor diff
βββ reports/
βββ bot.log
git clone https://github.com/authorturker/sec-analyzer-ai.git
cd sec-analyzer-ai
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # then edit .env with your 4 keys
python bot.pyOn first run the bot launches a bilingual wizard β pick a language, choose default form types, add tickers, done.
Secrets live in a .env file (git-ignored). Copy the template and fill in four values:
cp .env.example .envEDGAR_IDENTITY=Your Name yourname@email.com # Required by SEC
OPENROUTER_API_KEY=sk-or-v1-... # From openrouter.ai
TELEGRAM_BOT_TOKEN=123456:ABC... # From @BotFather
TELEGRAM_CHAT_ID=123456789 # From @userinfobotconfig.py is a thin loader that reads these via python-dotenv β you never edit config.py itself. At startup the bot runs a health check and refuses to run if any of the four is missing, malformed, or still a placeholder.
Everything else β tickers, default forms, model, schedule, language, custom prompts, webhook URL, price-action lookforward, raw-filing cap (default 100), cache TTL β is managed live via Telegram commands and stored in ~/sec-analyzer/bot_config.json.
| Command | Action |
|---|---|
Any news? Β· Check Β· /sec |
Scan watchlist with default forms |
Insider Β· /insider |
Form 4 only across watchlist |
Check all Β· /all |
SEC + Insider combined |
/sentiment |
Portfolio-wide insider sentiment score |
/sentiment trend [days] |
Compare current sentiment vs. N days ago (default 30) |
/scanticker AAPL |
Scan single ticker, default forms, not added to watchlist |
/scanticker AAPL 10-K 4 |
Scan single ticker with specific forms |
/compare AAPL MSFT [FORM] |
Side-by-side comparison (default form: 10-K) |
/checkprice AAPL [days] |
Last-N-day price summary β change, open/close, high/low (default 7) |
/checknews AAPL [count] |
Recent Yahoo Finance headlines + publisher links (default 5, max 20) |
| Command | Action |
|---|---|
/addticker AAPL |
Add ticker to watchlist |
/addticker AAPL MSFT NVDA |
Bulk add |
/removeticker AAPL |
Remove ticker |
/listtickers |
Show full watchlist |
| Command | Action |
|---|---|
/addgroup tech AAPL MSFT NVDA |
Create or replace a named group |
/removegroup tech |
Delete a group |
/listgroups |
Show all groups and their members |
/scangroup tech |
Scan a group with default forms |
/scangroup tech 10-K 4 |
Scan a group with specific forms |
| Command | Action |
|---|---|
/listforms |
All 13 supported forms + which are active |
/addform SC 13G |
Add form to default scan |
/removeform 8-K |
Remove form from default scan |
| Command | Action |
|---|---|
/setprompt 10-K <text> |
Override analysis prompt for a form type |
/getprompt 10-K |
Show current custom prompt |
/resetprompt 10-K |
Revert to default prompt |
/listprompts |
Show all active custom prompts |
| Command | Action |
|---|---|
/report |
Send this week's full analyses as a .md file |
| (inline button after analysis) | Receive the raw filing as a .txt |
| Command | Action |
|---|---|
/setschedule 08:00 |
Auto-scan daily at 08:00 |
/setschedule off |
Disable auto-scan |
/alarm |
Enable hourly filing alarm (probe only β no LLM, no cache write) |
/alarm off |
Disable alarm |
/digest |
Enable weekly Sunday digest |
/digest now |
Send digest immediately |
/digest off |
Disable weekly digest |
| Command | Action |
|---|---|
/setlang en / /setlang tr |
Switch UI + LLM-output language |
/setwebhook <url> |
Switch to webhook mode (requires Flask + public URL) |
/delwebhook |
Switch back to polling mode |
| Command | Action |
|---|---|
/settings |
Show all current settings |
/status |
Bot uptime, error counts, last scan time |
/setmodel <model> |
Switch LLM model |
/setlookback 60 |
Set lookback window in days (1β365) |
/setchars 15000 |
Set max characters per section (1000β50000) |
/setrawmax 500 |
Cap in-memory raw-filing cache (0 = unlimited) |
/priceaction on / off |
Toggle the per-filing price-action snippet |
/setlookforward 5 |
Days after filing for price change (1β90) |
| Form | Description |
|---|---|
10-K |
Annual report |
10-Q |
Quarterly report |
8-K |
Current events / material events |
4 |
Insider buy/sell transactions |
144 |
Restricted stock sale notice |
SC 13G |
Passive major shareholder (>5%) |
SC 13D |
Active / activist major shareholder |
S-1 |
IPO registration statement |
424B4 |
Prospectus |
20-F |
Foreign company annual report |
6-K |
Foreign company current report |
DEF 14A |
Proxy / shareholder vote statement |
11-K |
Employee retirement plan report |
Each form has its own form-specific prompt β Form 4 triggers insider-sentiment analysis, S-1 triggers IPO attractiveness scoring, DEF 14A triggers proxy-vote analysis, and so on. Override per form with /setprompt.
Install Termux from F-Droid β not the Play Store β then:
pkg update -y && pkg upgrade -y
pkg install -y python git tmux
git clone https://github.com/authorturker/sec-analyzer-ai.git
cd sec-analyzer-ai
pip install -r requirements.txt
cp .env.example .env && nano .envRun in background with tmux:
tmux new -s sec
python bot.py
# Ctrl+B then D to detachWebhook mode requires a publicly accessible HTTPS URL and Flask. On Android use Cloudflare Tunnel or ngrok:
pip install flask
pkg install cloudflared # Termux
cloudflared tunnel --url localhost:5050Register the tunnel URL from Telegram:
/setwebhook https://your-tunnel-url.trycloudflare.com
Restart the bot, and it switches to webhook delivery. Revert with /delwebhook and restart.
π’ MU β 10-Q
π
2026-04-03
π Quarter Performance
- Revenue: $8.7B (+18% YoY) β strong DRAM demand
- Gross margin: 34.2% β 38.1% (HBM contribution)
π Key Messages from Management
- AI server demand tracking above expectations
- HBM3E capacity expansion on track for Q3
β οΈ Notable Changes
- NAND pricing pressure continues
- China export restrictions remain an overhang
π 3 Factors to Watch
1. HBM ramp cadence and yield rates
2. PC/mobile DRAM pricing recovery
3. Further export control developments
π Risk Factor Changes (vs previous 10-Q)
β Added: "Increased competition from Chinese DRAM manufacturers..."
β Removed: "Supply chain disruptions related to legacy node capacity..."
π Price action: +3.42% (2026-04-03 β 2026-04-10)
ββββββββββββββββββββββββββββ
[π View original filing]
python -m pytest tests/ -q231 passed in <1s
The suite covers the pure helpers (render_filing_message, extract_section, build_prompt, _parse_stooq_csv, _compute_price_change, parse_sentiment_signal, build_trend_lines, build_compare_prompt, _format_price_check, _news_extract, _format_news_list, _md_escape, β¦), the i18n loader (key parity, fallbacks, language switching, LLM-language hint), the config layer (snapshot isolation, atomic mutate, race protection), the thread-safe state stores, and the alarm probe (proves the hourly alarm makes no LLM calls and no cache writes). Network IO is not exercised β tests run offline.
$0. OpenRouter free tier β 50 requests/day per model. Loading $10 credit raises the limit to 1,000/day (only drawn on paid models). Stooq price data is also free.
| Error | Fix |
|---|---|
EDGAR identity invalid / startup check failed |
Edit .env and set the flagged value (e.g. EDGAR_IDENTITY=Real Name your@email.com) |
403 Forbidden from SEC |
Same as above β SEC requires a real-looking identity |
No time zone found with key UTC |
pip install tzdata (already in requirements.txt) |
429 Too Many Requests |
Free-tier daily limit β switch model with /setmodel |
β οΈ Analysis unavailable |
OpenRouter timeout/5xx β bot retries with exponential backoff |
409 Conflict on getUpdates |
Two bot instances running β pkill -f bot.py then restart |
400 Bad Request on sendMessage |
Markdown parse error β bot automatically retries as plain text |
| Bot goes silent after network error | Reconnects automatically; check /status for error count |
| Webhook not receiving updates | Verify public HTTPS URL; run /delwebhook to fall back to polling |
| Wizard shows English even though I want Turkish | Step 0 of the wizard is bilingual β type /lang tr first |
/checkprice or /checknews says "yfinance required" |
pip install yfinance (optional dep, ~50 MB) |
Alarm fires but /check finds nothing |
Pre-v2.5 bug β update to current release (alarm is now probe-only) |
- π Fix: 8-K analysis now delivers real content.
extract_section()now recognises all standard 8-K items (1.01β9.01, including the 2023 cybersecurity item 1.05). A new_collect_8k_text()helper fetches the primary cover document and all EX-99.* attachments (press releases, earnings releases), concatenates them, and feeds the combined text to the LLM. Previouslyf.text()returned only the cover page boilerplate; the actual item body was never seen by the model. - Telegram hard-chunk.
_split_message()now performs character-level hard-splitting for lines that exceed Telegram's 4096-char limit, preventing message loss on very long single-line outputs. - 8-K form-sensitive char limit.
_FORM_MAX_CHARS["8-K"] = 20000(up from the global 10 000) so press releases fit without truncation. Other form limits are unchanged. llm()prompt clamp. Prompts > 30 000 chars are clamped with a log warning β a last-resort guard for multi-source prompts.f.text()null guard. Empty orNonefiling text is now logged as a warning and the filing is skipped cleanly instead of propagating aTypeError.llm()response guard. Missing or emptychoices[0].message.contentraises aValueErrorimmediately instead of causing a silentKeyErrorretry cascade.- Atomic JSON writes are now thread-safe.
_atomic_write_jsonuses per-thread unique.tmpfilenames ({name}.{pid}.{tid}.tmp) β prevents file corruption under concurrent writes. weekly_log.jsonFIFO cap. Capped at 500 entries so the log cannot grow unbounded when the digest is disabled or failing.- fetch_new_filings
fetch_text=False. The hourly alarm probe no longer downloads full filing text from EDGAR β it only checks existence, eliminating unnecessary rate-limit exposure. load_*type guards.load_cache,load_price_cache,load_sentiment_historyreturn a clean{}instead of crashing when JSON root is the wrong type.- All external network calls use
retry().fetch_yfinance_history,fetch_yfinance_news,fetch_stooq_daily,tg_send_document,tg_with_keyboardβ all now wrapped with the centralretry()+_backoff()infrastructure. - 327 pytest tests (up from 231 in v2.5) β characterization tests for
tg()/llm()bespoke behaviour, corruption recovery, 8-K section extraction, EX-99.* collection. /exportβ download this week's analysis log as a CSV file.setMyCommandsβ the bot menu (/) is now populated via Telegram's command registry.- Ticker validation.
/addtickerand/addgroupreject entries that don't match^[A-Z0-9.\-]{1,10}$. - Form input normalisation.
SC13G,10k,def14aand Unicode dash variants are all accepted and normalised. - Config lock (
_cfg_lock).get_cfgreturns a deep-copy;update_cfg/reset_cfghold the lock across the read-modify-write cycle.
- π Fix: alarm probe-only. The hourly
/alarmpreviously called the full scan pipeline in quiet mode β it silently consumed LLM quota, wrote to the cache, and announced an alert; then/checkwould return "no new filings" because the cache was already populated. The alarm now usesprobe_new_filings_for_watchlist()which only asks EDGAR whether anything new exists and short-circuits on the first hit. Zero LLM calls, zero writes β the user runs/checkmanually after the alert. - Multi-language UI β single
bot.py+lang/en.json+lang/tr.json, switch with/setlang. Replaces the oldbot_en.py/bot_tr.pysplit. /sentiment trend [days]β compare insider mood vs. N days ago, persistent history insentiment_history.json./compare AAPL MSFT [FORM]β side-by-side LLM comparison of two tickers' latest filings./checkprice TICKER [days]β on-demand price summary via yfinance (optional dep). Default 7 days./checknews TICKER [count]β recent Yahoo Finance headlines + publisher direct links via yfinance.- Watchlist groups β
/addgroup,/removegroup,/listgroups,/scangroup. - Price action snippet β every filing analysis gets
π +3.4% (filing-date β +5d)from Stooq (free, no API key). Toggle with/priceaction, tune with/setlookforward. - Wizard step 0 β language picker β first-run setup now starts bilingual.
- In-memory config cache + atomic
mutate_cfgβ no more TOCTOU races on concurrent edits. - Thread-safe
_raw_filingsand_statusstores with helper accessors. scan_tickerrefactored intofetch_new_filings/analyze_filing/render_filing_message/send_filing_resultβ testable, single-responsibility.- Startup validation of
EDGAR_IDENTITY(placeholder/empty/format) β bot refuses to run with bad identity. - Cache TTL β entries older than
cache_max_age_days(default 365) auto-pruned at startup. - Raw-filing cap β
/setrawmax Nenables FIFO eviction for long-running bots. - MarkdownV2-safe escape in digest snippets β characters like
*and_no longer get stripped. - 231 pytest tests β pure helpers, i18n, config cache, thread safety, sentiment, compare, groups, price, alarm probe.
- Auto-discover languages β drop
lang/<code>.jsonand it's picked up. build_promptsimplified to a dict-dispatch with form aliases.
- Inline original-document button, Markdown report export, webhook mode, Markdown parse fallback.
- Connection health monitoring +
/status, exponential backoff on all API calls.
- Weekly digest, custom analysis prompts, portfolio insider sentiment.
- Auto scheduling, hourly filing alarm, risk-factor diff.
- First-run wizard, 13 form types, form-specific prompts, Telegram settings management.
- Initial release, OpenRouter migration, insider trading scan, smart caching.
If you want to support this work, you can buy me a coffee.
: 178hyCd89p2QQnyUCL5y6hpzyJqu7QHz34
: MXpoKvp1ZojjZ1fXYhgLCYfUo3R9U43jiCF8cEA1q1Y
This tool is for informational purposes only. Nothing it produces constitutes investment advice. Always do your own research before making investment decisions.
MIT