Skip to content

mstixenberger/eigensignal

Repository files navigation

EigenSignal

📈 Stock screening and portfolio management toolkit with:

  • Trend-state screening (Uptrend, Downtrend, Undecided) from a 4-line SMMA engine
  • Daily/weekly RSI context
  • Watchlist management with per-user persistence
  • Portfolio tracking with cash, positions, and daily valuation snapshots
  • Chart generation (10Y + 1Y) with volume-profile S/R overlays
  • Short-TTL market-data cache to reduce duplicate fetches in report/snapshot flows

Telegram is the primary interface in this repository, but the core logic is the screening/portfolio toolkit behind it.

What It Does

1) 🔎 Watchlist Screening

  • Fetches market data from Yahoo Finance (yfinance)
  • Computes:
    • Trend Indicator state (Uptrend, Downtrend, Undecided)
    • Spread between fast and slow outer SMMAs
    • Daily and weekly RSI
    • Performance windows (1D, WoW, 1M, 6M, 1Y) in detail/report views
  • Tracks state changes ("flips") with history
  • Optional daily price-alerts on large moves (single or multi-threshold ±% levels, deduplicated once per ticker/direction/threshold/day)
  • Sends scheduled and on-demand status/report outputs

2) 💼 Portfolio Management

  • User-scoped portfolios in SQLite
  • Position CRUD (ticker, quantity, category, optional display name)
  • Portfolio position adds auto-sync the ticker into the same user's watchlist
  • Cash tracking in USD
  • On-demand portfolio valuation (positions + cash)
  • Portfolio summaries include an allocation pie chart
  • Daily scheduled snapshots
  • Daily scheduled portfolio performance report (1D, WoW, 1M, 6M, 1Y)
    • Includes full-timeline two-panel history chart (value line + stacked holdings composition)
    • Includes allocation pie chart
  • Portfolio history chart from snapshots (value + holdings composition)
  • Snapshot failure logging + retry attempts
  • Snapshot maintenance commands (backup/export, delete/redact, rebuild latest)

3) 📊 Charting

  • Single-ticker pair charts:
    • 10Y context view
    • 1Y detail view
  • Category-level multi-ticker report charts
  • Volume profile + point-of-control highlight + S/R intensity lines
  • Portfolio history chart with stacked holdings (snapshot-based)
  • Portfolio allocation pie chart

Chart Examples

Single Ticker (10Y Context)

Single ticker 10Y chart

Single Ticker (1Y Detail)

Single ticker 1Y chart

Multi-Ticker Overview

Multi-ticker overview chart

Portfolio Value History

Portfolio value chart

Portfolio History + Holdings Composition

Portfolio history + holdings chart

Portfolio Allocation Pie

Portfolio allocation pie chart

Architecture

  • src/eigensignal/engine.py: SMMA, Trend Indicator, RSI
  • src/eigensignal/fetcher.py: market data fetch + retries
  • src/eigensignal/charts.py: chart generation
  • src/eigensignal/store.py: SQLite persistence (users, watchlists, portfolios, snapshots)
  • src/eigensignal/state.py: JSON state + flip history (atomic writes)
  • src/eigensignal/scheduler.py: periodic jobs
  • src/eigensignal/bot.py: Telegram command handlers (interface layer)
  • src/eigensignal/dev.py: local dev CLI (no Telegram required)

Requirements

  • Python >=3.11
  • uv (recommended package manager)
  • Telegram bot token (for bot interface mode)
  • Authorized Telegram user IDs

Installation

Option A: Interactive setup script (recommended)

./scripts/setup.sh

Setup is intentionally dead simple: it bootstraps dependencies, guides config creation, runs verification, and sets up a service when supported. It also shows timezone examples during setup (for example America/New_York, Europe/Vienna, UTC). It validates interactive numeric/time inputs, applies secure config.yaml permissions, and falls back to manual run mode when systemd user services are unavailable.

Option B: Manual dependency install

uv sync --frozen --extra dev

Telegram Setup (BotFather + Token + User ID)

  1. Create a bot with BotFather:
  • Open https://t.me/botfather
  • Run /newbot
  • Follow prompts for bot name and username
  • Copy the token you get back (this is your Telegram Bot API token)
  1. Put the token in config:
  • Set telegram.bot_token in config.yaml
  1. Get your Telegram user ID:
  • Method A (@userinfobot): open https://t.me/userinfobot (or search Telegram for @userinfobot), start the bot, and copy your numeric user ID from its reply.
  • Method B (built-in): run your bot once, message it, and if your ID is not authorized yet it replies with your user ID.
  • Method C (API): message your bot (/start), then run:
curl -s "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates"
  • In the JSON response, read message.from.id.
  1. Add your ID to telegram.authorized_users in config.yaml.

Notes:

  • Treat the bot token like a password.
  • No separate market-data API key is required for default data fetching (yfinance / Yahoo Finance).

Configuration

Copy the example and edit values:

cp config.example.yaml config.yaml

Minimum required sections:

  • telegram.bot_token
  • telegram.authorized_users
  • tickers (can be empty)
  • schedule
  • paths

Notes:

  • Watchlists are user-scoped in SQLite and managed via commands.
  • tickers in config are best treated as seed/default symbols and are used directly by --dry-run.
  • schedule and price_alerts in config.yaml act as global defaults.
  • Users can override selected settings (timezone, daily report times, and price-alert options) via Telegram or CLI settings commands.
  • check_interval_minutes remains a global runtime cadence for now.
  • Paths in config.yaml can be relative; they are resolved relative to the config file location.
  • runtime.data_cache_ttl_seconds controls in-process market data cache TTL (120 by default).
  • price_alerts.enabled plus price_alerts.daily_change_pct_threshold (single level) or price_alerts.daily_change_pct_thresholds (multiple levels) control automatic daily move alerts on watchlist tickers.
  • Restore/import safety can be tuned via:
    • runtime.max_restore_payload_bytes
    • runtime.max_restore_watchlist_items
    • runtime.max_restore_portfolios
  • schedule.daily_portfolio_report_time controls scheduled portfolio performance reports; if omitted, it falls back to schedule.daily_report_time.

Per-User Settings Model

Effective runtime settings are resolved per user:

  1. Start with global defaults from config.yaml.
  2. Apply optional per-user overrides from SQLite (user_preferences).

This affects /config, startup info messages, and all user-scheduled jobs.

Supported per-user keys:

  • schedule.timezone
  • schedule.daily_report_time
  • schedule.daily_portfolio_report_time
  • price_alerts.enabled
  • price_alerts.daily_change_pct_threshold
  • price_alerts.daily_change_pct_thresholds

Price-alert thresholds:

  • daily_change_pct_threshold is the legacy single-threshold field.
  • daily_change_pct_thresholds enables multiple levels (for example 3,5,8).
  • The system keeps backward compatibility by mirroring the first sorted threshold into the legacy single-threshold field.
  • Deduplication is per ticker + direction + threshold + local day.

Examples (Telegram):

  • /settings
  • /settings_set timezone Europe/Vienna
  • /settings_set daily_report_time 07:30
  • /settings_set price_alerts.enabled true
  • /settings_set price_alert_thresholds 3,5,8
  • /settings_reset price_alert_thresholds

Examples (CLI):

  • uv run eigensignal --config config.yaml settings get --user-id 123
  • uv run eigensignal --config config.yaml settings set --user-id 123 --key timezone --value Europe/Vienna
  • uv run eigensignal --config config.yaml settings set --user-id 123 --key price_alert_thresholds --value 3,5,8
  • uv run eigensignal --config config.yaml settings reset --user-id 123 --key schedule.timezone
  • uv run eigensignal --config config.yaml settings reset --user-id 123 --all

Running

Toolkit (Telegram interface mode)

uv run eigensignal --config config.yaml

Dry run (no Telegram sends, no state writes)

uv run eigensignal --config config.yaml --dry-run

Dev CLI (no config required)

uv run eigensignal dev analyze AAPL
uv run eigensignal dev chart BTC-USD --no-open
uv run eigensignal dev pipeline AAPL SPY QQQ
uv run eigensignal dev pipeline --config config.yaml

Data Backup/Restore CLI

uv run eigensignal --config config.yaml data wl-backup --user-id 123 --output watchlist.json
uv run eigensignal --config config.yaml data wl-restore --user-id 123 --input watchlist.json
uv run eigensignal --config config.yaml data pf-backup --user-id 123 --output portfolios.json
uv run eigensignal --config config.yaml data pf-restore --user-id 123 --input portfolios.json

User Settings CLI

uv run eigensignal --config config.yaml settings get --user-id 123
uv run eigensignal --config config.yaml settings set --user-id 123 --key timezone --value Europe/Vienna
uv run eigensignal --config config.yaml settings set --user-id 123 --key daily_report_time --value 07:30
uv run eigensignal --config config.yaml settings set --user-id 123 --key price_alert_thresholds --value 3,5,8
uv run eigensignal --config config.yaml settings reset --user-id 123 --key schedule.timezone
uv run eigensignal --config config.yaml settings reset --user-id 123 --all

Doctor CLI

uv run eigensignal --config config.yaml doctor
uv run eigensignal --config config.yaml doctor --user-id 123

Prints a JSON diagnostics report covering runtime paths, SQLite/state readiness, and optionally user-scoped schedule, holdings, mute status, and recent snapshot failures.

Scheduler Jobs

Configured from schedule:

  • Flip check every check_interval_minutes
  • Daily watchlist price-alert checks happen inside the flip-check cycle when price_alerts.enabled: true
  • Daily watchlist report at daily_report_time
  • Daily portfolio snapshot at each user's effective daily_report_time
  • Daily portfolio performance report at each user's effective daily_portfolio_report_time (or their effective daily_report_time if omitted)
  • Effective user schedule/timezone comes from global config defaults plus optional per-user overrides.
    • On restart, if a user's local scheduled minute has already passed and today's run was not recorded yet, the dispatcher performs a one-time catch-up send.
    • Sends full-timeline two-panel portfolio history chart (max range) with 1D/WoW/1M/6M/1Y annotations
    • Sends allocation pie chart for the latest snapshot
  • Systemd watchdog heartbeat every 60s (no-op outside systemd)

Telegram Command Surface

User

  • /start
  • /user_name <name>
  • /help
  • /ping
  • /doctor

Watchlist Manager

  • /wl_add <ticker> (alias: /watchlist_add)
  • /wl_remove <ticker> (alias: /watchlist_remove)
  • /wl_list (alias: /watchlist_list)
  • /wl_status (alias: /watchlist_status)
  • /wl_detail <ticker> (alias: /watchlist_detail)
  • /wl_chart <ticker> (alias: /watchlist_chart)
  • /wl_report (alias: /watchlist_report)
  • /wl_backup (alias: /watchlist_backup)
  • /wl_restore [replace] (alias: /watchlist_restore) then upload backup JSON document
  • /wl_flips [count] (alias: /watchlist_flips)
  • /wl_check (alias: /watchlist_check)

Portfolio Manager

  • /pf_create <name> (alias: /portfolio_create)
  • /pf_delete <name> (alias: /portfolio_delete)
  • /pf_list (alias: /portfolio_list)
  • /pf_set <name> <ticker> <quantity> (alias: /portfolio_set) - syncs added tickers to watchlist and confirms in chat
  • /pf_remove <name> <ticker> (alias: /portfolio_remove)
  • /pf_positions <name> (alias: /portfolio_positions)
  • /pf_cash_set <name> <amount> (alias: /portfolio_cash_set)
  • /pf_cash_add <name> <amount> (alias: /portfolio_cash_add)
  • /pf_cash_sub <name> <amount> (alias: /portfolio_cash_sub)
  • /pf_summary <name> (alias: /portfolio_summary) - sends valuation text plus allocation pie chart
  • /pf_report <name> (alias: /portfolio_report) - refreshes live valuation first, then sends history chart and allocation pie chart
  • /pf_chart <name> [30d|90d|1y|max] (alias: /portfolio_chart) - two-panel history chart (value + stacked holdings)
  • /pf_backup [name] (alias: /portfolio_backup)
  • /pf_restore [replace] (alias: /portfolio_restore) then upload backup JSON document
  • /pf_snapshot_backup <name> (alias: /portfolio_snapshot_backup)
  • /pf_snapshot_delete <name> <YYYY-MM-DD|all> (alias: /portfolio_snapshot_delete)
  • /pf_snapshot_rebuild <name> (alias: /portfolio_snapshot_rebuild)

Portfolio name parsing:

  • <name> supports spaces (for example: /portfolio_set Long Term AAPL 2).

Utility

  • /config
  • /doctor
  • /settings
  • /settings_set <key> <value>
  • /settings_reset <key|all>
  • /auth_add <user_id>
  • /auth_revoke <user_id> (alias: /auth_remove)
  • /mute <minutes>
  • /unmute
  • /export
  • /import [replace] then upload scoped state.json document

Command menu autocomplete:

  • On startup, the bot automatically calls Telegram setMyCommands (default scope) so typing / shows the registered command list.

Export/import behavior:

  • /export returns only the requesting user's scoped state entries.
  • /import accepts only the requesting user's scoped entries and rejects mismatched telegram_user_id payloads.
  • [replace] mode clears the requesting user's existing scoped state keys before import.

Backup restore safety:

  • Watchlist and portfolio restore payloads enforce telegram_user_id matching the target user when present in the JSON payload.

Data and Storage

Configured paths.data_dir:

  • data.db: SQLite primary store
  • state.json: latest computed screening state per user_id:ticker
  • flips.json: append-style flip history

Configured paths.logs_dir:

  • bot.log: rotating file log

Configured paths.charts_dir:

  • temporary/generated chart PNG files

Setup and Service Scripts

  • scripts/setup.sh: interactive setup + dependency install + config generation + verification
  • scripts/start.sh, scripts/stop.sh, scripts/restart.sh, scripts/status.sh: systemd user service helpers
  • scripts/uninstall.sh: remove service and optionally data

On non-systemd environments, run directly with:

uv run eigensignal --config config.yaml

Testing

Run all tests:

uv run pytest

Run only unit tests:

uv run pytest -m unit

Run only integration tests:

uv run pytest -m integration

Quick command examples:

uv run eigensignal --config config.yaml --dry-run
uv run eigensignal --config config.yaml data wl-backup --user-id 123 --output watchlist.json
uv run eigensignal dev chart AAPL --no-open

Operational Notes

  • First-time authorized users must set a display name (onboarding prompt).
  • Unauthorized users receive a message containing their Telegram user ID.
  • Category auto-detection supports stocks, crypto, and funds; uncertain tickers prompt for category selection.
  • Portfolio values use latest fetched close prices from Yahoo Finance.
  • /mute and /unmute are user-scoped.
  • This project does not execute trades or connect to brokerage APIs.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors