Yahoo Finance analyst data, aggregated into BUY / SELL / HOLD signals for equities and ETFs, with position sizing and CSV+HTML reports.
Created by plessas, an eToro Popular Investor. This tool provides analysis only, not investment advice. Every investment decision is the user's own responsibility.
etorotrade is a Python-based investment-signals engine. It pulls analyst consensus, price targets, and fundamentals from Yahoo Finance (yfinance + yahooquery), applies tier and region-specific thresholds from config.yaml, and writes color-coded reports to yahoofinance/output/.
The universe it scans is the ~12k tickers offered on eToro, plus your own portfolio. A full nightly pass runs on GitHub Actions in six parallel shards and commits the fresh CSVs back to the repo.
- eToro users who want a systematic, second-opinion read on their positions.
- Retail investors who prefer analyst consensus + fundamentals over discretionary picks.
- Developers who want a reproducible, testable signal pipeline they can extend.
- Not investment advice, not a robo-advisor, not an automated trading system.
- Not a source of proprietary data. Everything downstream of Yahoo Finance.
- Not a promise of returns. Historical signal accuracy is measured by the built-in backtester, and results are advisory.
flowchart TD
A[Ticker universe<br/>yahoofinance/input/*.csv] --> B[AsyncHybridProvider<br/>yfinance + yahooquery]
B --> C[Analysis engine<br/>tier + region gates from config.yaml]
C --> D{BS classifier}
D -->|B| E[buy.csv / .html]
D -->|S| F[sell.csv / .html]
D -->|H| G[hold.csv / .html]
D -->|I| H[market.csv / .html]
C --> I[etoro.csv<br/>full scored universe]
E & F & G --> J[signal_log.jsonl]
J --> K[Backtester<br/>T+7 / T+30 vs SPY]
Python 3.10, 3.11, or 3.12.
git clone https://github.com/weirdapps/etorotrade
cd etorotrade
# Create a venv and install the pinned, SHA256-hashed lockfile.
# Same install path CI uses. Poetry is NOT needed to install; only to change deps.
python3 -m venv venv && source venv/bin/activate
pip install --only-binary :all: --require-hashes -r requirements-dev-lock.txtOr run scripts/dev/setup.sh, which does the same and also installs pre-commit hooks and copies .env.example to .env.
- Export your positions from eToro (Portfolio, Export to CSV).
- Save the file as
yahoofinance/input/portfolio.csv. Seeyahoofinance/input/portfolio.csv.examplefor the schema. - Run:
python trade.py -o pResults print to the console and land in yahoofinance/output/portfolio.csv and portfolio.html.
trade.py is the single entry point. It runs interactively when called with no arguments, or takes an operation via -o and a target via -t.
python trade.py # interactive menu
python trade.py -o p # portfolio analysis
python trade.py -o p -t n # portfolio, fetch fresh eToro data first
python trade.py -o m # market screening
python trade.py -o m -t 10 # market, first 10 tickers
python trade.py -o e # full eToro universe scan (~12k tickers)
python trade.py -o t -t b # BUY opportunities (from etoro.csv, excludes holdings)
python trade.py -o t -t s # SELL opportunities (portfolio holdings with S signal)
python trade.py -o t -t h # HOLD opportunities (from etoro.csv, excludes holdings)
python trade.py -o i -t AAPL,MSFT # ad-hoc analysis for specific tickers
python trade.py -o b # backtest signals (T+7 / T+30 forward validation)
python trade.py -o sc # signal scorecard
python trade.py --validate-config # validate config + exitpython scripts/analyze_geography.py # ETF geographic-exposure decomposition
python scripts/analyze_industry.py # sector-allocation analysis
python scripts/refresh_etoro_universe.py # refresh the eToro ticker universe
python scripts/market_snapshot.py # quick market snapshotReports are written to yahoofinance/output/:
| File | Content |
|---|---|
etoro.csv / .html |
Full scored eToro universe with BS signal per row |
portfolio.csv / .html |
Your current holdings, scored |
buy.csv / .html |
Non-holdings with a BUY signal, ranked by market cap |
sell.csv / .html |
Holdings with a SELL signal |
hold.csv / .html |
Non-holdings with a HOLD signal |
market.csv / .html |
Market screening pass |
manual.csv / .html |
Ad-hoc -o i runs |
backtest_*.csv/json |
Forward-validation results, vs SPY |
signal_log.jsonl |
Append-only signal history (input to the backtester) |
The scored files share a common schema:
TKR, NAME, CAP, PRC, TGT, UP%, #T, %B, #A, AM, A, EXR, B, 52W,
2H, PET, PEF, P/S, PEG, DV, SI, EG, PP, ROE, DE, FCF, ERN, SZ, BS,
SIGNAL_TRACK, SIGNAL_HORIZON
Key fields:
TKR/NAME/CAP: ticker, company, market cap.PRC/TGT/UP%: current price, mean analyst target, implied upside.#T/#A: number of analyst targets and analysts.%B/AM: percent BUY consensus, analyst momentum.EXR: expected return (upside * buy% / 100).PET/PEF/PEG/P/S: valuation multiples.DV/SI/EG/PP: dividend, short interest, earnings growth, price performance.ROE/DE/FCF: return on equity, debt/equity, free-cash-flow yield.ERN: next earnings date.SZ: recommended position size (fromPositionSizer).BS: the signal itself, one ofB,S,H,I(INCONCLUSIVE).SIGNAL_TRACK/SIGNAL_HORIZON: metadata for the backtester.
-
Universe load: tickers are read from
yahoofinance/input/etoro.csv,portfolio.csv, ormarket.csvdepending on operation. -
Data fetch:
AsyncHybridProvidercalls yfinance for the bulk of fields and falls back toyahooqueryfor the fields yfinance drops (PEG, some fundamentals). Rate limiting and a disk cache sit in front. -
Tier + region gating: each ticker is classified into one of five buckets. Cutoffs live in
config.yaml:Tier Market cap MEGA >= $500B LARGE $100B to $500B MID $10B to $100B SMALL $2B to $10B (below $2B) Marked INCONCLUSIVE, hard floor Universal analyst gates apply on top: $2B to $5B needs 6+ analysts, $5B and up needs 4+ (per-region tier blocks tighten further, e.g. US MEGA/LARGE/MID min_analysts: 8, HK MEGA: 15). Region blocks exist for US (baseline), EU (
.L,.PA,.AS,.DE,.MI, etc.), and HK (.HK). -
Signal classification:
BSis set toB,S,H, orIbased on the tier/region-specificbuyandsellblocks inconfig.yaml(upside, buy%, PE, PEG, ROE, DE, beta, FCF yield, analyst momentum, and more). -
Position sizing:
PositionSizerintrade_modules/trade_engine.pyproducesSZbased on market-cap tier, expected return, and risk constraints. -
Persist: results are written to CSV and HTML, and appended to
signal_log.jsonlfor backtesting.
All thresholds live in a single ~1,000-line YAML file: tier gates, per-region buy and sell blocks, position-sizing parameters, calibration metadata. Parameters are calibrated quarterly against the T+7 / T+30 backtester and edited only by human review.
yahoofinance/input/portfolio.csv (mirror of the eToro export):
symbol,totalInvestmentPct,totalNetProfitPct,instrumentDisplayName
AAPL,5.2,12.5,Apple Inc
MSFT,4.8,8.3,Microsoft CorporationOptional. Copy .env.example to .env.
| Variable | Purpose |
|---|---|
ETORO_API_KEY, ETORO_USER_KEY, ETORO_USERNAME |
eToro Public API credentials, used by scripts/refresh_etoro_universe.py |
ALPHA_VANTAGE_API_KEY |
Alternative data provider |
POLYGON_API_KEY |
Alternative data provider |
NEWS_API_KEY |
News sentiment enrichment |
YFINANCE_MAX_CALLS |
Rate-limit override |
YFINANCE_CACHE_TTL |
Cache TTL override (seconds) |
YFINANCE_API_TIMEOUT |
Request timeout override |
YFINANCE_CIRCUIT_BREAKER_ENABLED |
true / false |
YAHOOFINANCE_LOG_LEVEL |
DEBUG, INFO, WARNING, etc. |
YAHOOFINANCE_DEBUG |
true enables debug logging |
SHARD_COUNT, SHARD_INDEX |
Universe sharding, used by the daily-signals workflow |
trade.py # CLI entry point
trade_modules/ # Trading logic
trade_cli.py # argparse + async orchestration
trade_engine.py # TradingEngine, PositionSizer
analysis_engine.py # signal generation
config_manager.py # ConfigManager, ticker substitutions
backtest_engine.py # T+7 / T+30 forward validation
signal_scorecard.py # per-signal accuracy scorecard
committee_*.py # optional multi-agent research committee
signals_v2/, riskfirst/ # next-gen signal pipeline (WIP)
yahoofinance/ # Data layer
api/providers/ # AsyncHybridProvider, AsyncYahooFinance,
# AsyncYahooQuery, AlphaVantage, Polygon
analysis/ # StockAnalyzer, market filters, tiers
core/ # DI container, logging, errors, config
utils/ # trade criteria, async helpers, market utils
presentation/ # console + HTML renderers
input/ # portfolio.csv, etoro.csv, region files
output/ # committed CSV + HTML reports
scripts/ # standalone analysis + ops scripts
dev/ # setup.sh, test.sh, lint.sh, format.sh, relock.sh
config.yaml # buy/sell thresholds per tier/region
docs/ # USER_GUIDE, TECHNICAL, POSITION_SIZING, CI_CD, FAQ
tests/ # unit, integration, e2e, benchmarks
Nine workflow files in .github/workflows/:
| Workflow | Schedule | Purpose |
|---|---|---|
ci.yml |
push, PR, nightly 02:00 UTC | Test matrix (3.10, 3.11, 3.12), bandit, flake8, mypy, coverage, quality-gates, lockfile-sync, yfinance-compat smoke test |
codeql.yml |
push to master, PR, Mon 06:00 UTC | GitHub CodeQL static analysis |
sonarcloud.yml |
push, PR | Quality gate on SonarCloud (project weirdapps_etorotrade) |
daily-signals.yml |
daily 22:00 UTC | Full universe scan in 6 parallel shards, merges + commits etoro.csv and derived buy/sell/hold |
weekly-universe-refresh.yml |
Sun 21:00 UTC | Refreshes the ~12k-ticker universe from the eToro market-data API |
weekly-backtest.yml |
Sat 23:00 UTC | Runs T+7 / T+30 backtest pipeline and commits report |
deps-refresh.yml |
monthly, 4th at 04:17 UTC | Regenerates the lockfiles and opens a PR |
dependabot-relock.yml, dependabot-auto-merge.yml |
on Dependabot PRs | Auto-relock + auto-merge for green updates |
There is a forward-validation backtester in trade_modules/backtest_engine.py. Since Yahoo Finance does not expose historical analyst recommendations, the engine uses signal_log.jsonl (accumulated by every run since January 2026) and compares each signal against actual price movements at T+7 and T+30 trading days.
python trade.py -o bOutput:
- Per-signal accuracy (BUY, SELL, HOLD).
- Tier and region breakdowns.
- Comparison against SPY as benchmark.
- Reports in
yahoofinance/output/backtest_*.csvandbacktest_report.json.
The weekly-backtest.yml workflow runs the same pipeline on GitHub Actions and commits the reports.
pytest tests/ # full suite
pytest tests/unit/ # unit tests only (fast)
pytest tests/integration/ # requires network
pytest -m "not slow" # skip slow tests
pytest --cov=yahoofinance --cov=trade_modules --cov-report=html
scripts/dev/test.sh # wrapper with coverageCI runs the suite with --cov-fail-under=58.
scripts/dev/lint.sh # ruff format --check + ruff check + mypy
scripts/dev/format.sh # ruff check --fix + ruff formatLine length: 100. ruff is the only formatter and linter: ruff format for layout, ruff check for lint including import order (the I rule). Config lives in [tool.ruff] in pyproject.toml and is shared by the dev scripts, .pre-commit-config.yaml and CI, so all three agree.
CI gates on both (ruff check and ruff format --check in ci.yml). black, isort and flake8 were removed on 2026-09-03: none of them was enforced anywhere, and each disagreed with the tree (isort on 291 files, black on 45) so running the old dev scripts fought ruff. Note that ruff format also rewrites python blocks inside markdown, which is why **/*.md is in extend-exclude. Type checker: mypy runs in lenient mode and does not gate CI.
Three requirements-*-lock.txt files (production, production+dev, production+smoketest) are exported from poetry.lock, committed, and enforced by the lockfile-sync CI job. To bump a package:
# 1. Edit pyproject.toml
# 2. Regenerate poetry.lock + the three exported requirements files
scripts/dev/relock.sh
# 3. Commit pyproject.toml + poetry.lock + requirements-*-lock.txt togetherrelock.sh takes two optional modes:
| Mode | Behaviour |
|---|---|
| (none) | poetry lock + export. Moves only what pyproject.toml forces to move. |
--regenerate |
Discards the existing resolution and re-resolves everything to the newest allowed versions, then exports. Used by deps-refresh.yml. |
--export-only |
Re-exports the requirements files from the existing poetry.lock. Used by dependabot-relock.yml. |
Plain poetry lock keeps every already-locked package pinned, so it will not pull transitive dependencies forward on its own. Reach for --regenerate when the goal is "everything at latest".
A fourth lockfile, requirements-universe-lock.txt, is not generated by relock.sh. It is a small hand-maintained hash list (5 packages, Python 3.11 / manylinux2014_x86_64) used only by weekly-universe-refresh.yml; regeneration instructions are in its header comment.
Poetry is only needed to change dependencies. Installing them uses pip directly with --require-hashes.
A Dockerfile is provided for containerised runs.
- User Guide: getting started, common workflows.
- Technical Architecture: system design, providers, DI.
- Position Sizing: the risk-adjusted sizing algorithm.
- CI/CD: pipeline stages, quality gates.
- Examples, FAQ, Troubleshooting.
- Evidence-based threshold changes and Large/Mega threshold changes: historical calibration decisions.
See SECURITY.md. Do not open a public issue for a vulnerability; report it privately through this repository's Security tab (Report a vulnerability).
MIT, see LICENSE.
This tool is designed for quantitative analysis and research. It does not constitute investment advice. Users should conduct their own due diligence and consider consulting a qualified financial advisor before making investment decisions. Past signal performance does not guarantee future results.

