尚未收集仓库信息。
尚未检测到更新。GitHub 发布版本后会显示在这里。
Stock, ETF and crypto analysis plus a paper-trading bot, built only on free data (yfinance, FRED, SEC EDGAR, news + FinBERT, Fama-French). Research use for investment purposes
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗An autonomous stock-analysis and paper-trading platform built entirely on free, public data: no Bloomberg, no paid APIs, no API keys.
| Source | Used for | Access |
|---|---|---|
Yahoo Finance (yfinance) | Daily + 15-min OHLCV, VIX, indices, quotes | free, ~15 min delayed |
| FRED, St. Louis Fed | Inflation, Treasury yields, employment, credit spreads | pandas-datareader, falling back to FRED's public CSV |
SEC EDGAR (data.sec.gov) | 10-K / 10-Q financials via XBRL | official free API |
| Google News, Yahoo Finance, CNBC, MarketWatch RSS | Headlines | feedparser + BeautifulSoup |
FinBERT (ProsusAI/finbert, HuggingFace) | Financial sentiment | open-source model, runs locally |
| Kenneth R. French Data Library | Fama-French 3 factors + momentum, daily | pandas-datareader, falling back to the library's zipped CSVs |
Paper trading only. Nothing here places real orders, and nothing here is investment advice.
quant-platform/
├── requirements.txt
├── requirements-quantum.txt optional: pinned Qiskit for the website's quantum simulations
├── .env.example
├── quant/
│ ├── config.py settings, watchlist, FRED series [P1]
│ ├── db.py engine, sessions, SQLite/Postgres upsert [P1]
│ ├── models.py ORM tables [P1, extended in P4]
│ ├── ingestion/
│ │ ├── http.py retrying HTTP sessions [P1]
│ │ ├── market_data.py yfinance OHLCV + quotes [P1]
│ │ ├── macro_data.py FRED [P1]
│ │ ├── sec_edgar.py EDGAR 10-K / 10-Q metrics [P1]
│ │ ├── news_scraper.py RSS scraping, sentiment pass, news-to-chart map [P1]
│ │ ├── sentiment.py FinBERT with a lexicon fallback [P1]
│ │ ├── relevance.py company-name relevance filter for headlines [P2]
│ │ ├── factors.py Fama-French factor returns [P3]
│ │ ├── index_membership.py point-in-time S&P 500 membership (Wikipedia) [P6]
│ │ └── pipeline.py orchestrator + CLI [P1]
│ ├── analytics.py 50/200 MA, RSI, MACD, Bollinger, regime, anomalies [P2]
│ ├── quant_models.py Fama-French 3F, momentum, mean reversion, scoring [P3]
│ ├── trading_bot.py signals, paper ledger, Kelly/vol sizing, stops [P4]
│ ├── scheduler.py APScheduler daily run [P4]
│ └── web/ static website: export, publish, quantum simulations
├── dashboard/ Streamlit + Plotly [P5]
│ ├── app.py entry point and navigation
│ ├── common.py cached data access, palette, chart styling
│ └── pages/ market_overview · stock_deep_dive · financials_research · trading_bot
├── data/ quant.db (SQLite) and cache/
└── tests/
The dashboard is Streamlit + Plotly, so the whole platform is one Python codebase reading one database.
python -m venv .venv
.venv\Scripts\activate # macOS/Linux: source .venv/bin/activate
pip install -r requirements.txt --extra-index-url https://download.pytorch.org/whl/cpu
copy .env.example .env # then set SEC_USER_AGENT to "Your Name [email protected]"
python -m quant.ingestion.pipeline # everything
python -m quant.ingestion.pipeline --steps prices macro # a subset
python -m quant.ingestion.pipeline --tickers AAPL NVDA --no-finbert
python -m quant.ingestion.pipeline --steps fundamentals --refresh-fundamentals # re-extract after adding a metric
python -m pytest # offline tests
The first run backfills 10 years of daily bars and downloads FinBERT (~440 MB). After that, each run fetches only what's new: recent bars, revised FRED prints, filings it hasn't seen, and fresh headlines.
| Table | Contents |
|---|---|
price_bars, stock_splits | 1d bars keyed by session date; 15m bars keyed by UTC bar start; split history |
factor_returns | Daily Fama-French Mkt-RF, SMB, HML, RF and momentum (decimals), lagging today by 1-2 months |
macro_series, macro_observations | FRED metadata and values (daily, weekly or monthly, depending on the series) |
companies, fundamentals | One row per 10-K / 10-Q (about 11 years): revenue, net income, EPS, diluted shares, assets, liabilities, equity, total debt, cash, operating cash flow, capex, FCF, D/E, net margin, link to the filing |
news_articles | Headline, publisher, UTC time, FinBERT label/score, and its intraday chart alignment |
index_members | One row per stint of a stock in the S&P 500: ticker, name, added, removed (only with --universe sp500; see the research-universe section) |
companyfacts API carries the accession number of the filing that reported it,
so metrics are matched to an exact 10-K / 10-Q. A 10-Q reports both quarterly and
year-to-date figures; the parser takes the quarter for the income statement.
Cash-flow statements in a 10-Q are year-to-date only, so cash_flow_period_days
records the length. Total debt is approximated from the standard debt tags, with
guards against double counting.reaction_return is the move from the last close before the headline
to the end of a 60-minute trading window. Market-wide headlines are mapped
onto SPY.sentiment_model column records which scorer produced each score.http.py sets each one accordingly.python -m quant.analytics # current market regime
python -m quant.analytics AAPL JNJ # + indicators and explained daily anomalies (90 days)
python -m quant.analytics TSLA --interval 15m # intraday anomalies (30 days)
Everything is computed on demand from the database. The core functions in
quant/analytics.py take plain DataFrames, so later phases can reuse them for
backtests.
add_indicators, via the ta library): 50/200-day SMA with
golden/death-cross events, RSI(14), MACD(12,26,9), Bollinger Bands(20,2), ATR(14)
and 20-day realised volatility.market_regime_for), computed for every day of history:
explain_anomalies):
ingestion/relevance.py): scores each headline 0–1 for
whether the company is its subject. It uses curated aliases plus the SEC
registrant name, and catches name variants ("Johnson and Johnson").
All Phase 2 thresholds are reproducible with scripts/calibrate_phase2.py. The goal
is robust settings (plateaus, stability, clear separation of forward risk), not
maximum backtest returns, so Phase 4 backtests on the same history aren't silently
overfit.
python scripts/calibrate_phase2.py regime # trend band, drawdown rule, VIX cut-offs (1990-today)
python scripts/calibrate_phase2.py regime-confirm # confirmation window: switches/yr vs detection lag
python scripts/calibrate_phase2.py regime-stance # risk-score stance cut-offs, credit-stress input
python scripts/calibrate_phase2.py anomalies # move rules vs SEC 8-K Item 2.02 earnings dates
python scripts/calibrate_phase2.py relevance # headline filter vs the hand-labeled fixture
| Setting | Before | Tuned | Evidence (1990–2026 unless noted) |
|---|---|---|---|
| Trend band / confirmation | 1.5% / none | 1.0% / 5 days | trend switches 6.2 → 4.2/yr, median spell 8 → 22 days, bear detected 6 days sooner |
| VIX spike rule | on | removed | forward vol on spike days 16.7% vs 16.2% at the same VIX level (no signal) |
| Volatility confirmation | none | 5 days | switches 24 → 11/yr; Volatile still carries 2.2× Calm forward vol |
| Credit-stress input | HY OAS (3 yrs on FRED) | Baa−10Y spread | coverage 12% → 100% of days; stressed days' forward vol 20.7% vs 11.9% |
| Stance confirmation | none | 5 days | stance switches 7.0 → 4.2/yr; Risk-Off forward vol 23.3% vs 10.9% Risk-On |
| Daily anomaly rule | 5% / 3σ / 2% | 8% / 3σ / 2% | per stock 13.8 → 8.7 flags/yr; busiest/calmest 5.8× → 2.9× (2016–2026) |
| Intraday opening bar | shared σ | own σ | opening-gap flags 0.30 → 0.04 per ticker-day |
| Intraday minimum move | 1.0% | 1.25% | TSLA 16 → 3 flags in 30 sessions, others barely change (provisional: small sample) |
| Relevance filter | 7 rule fixes | precision 85.4% → 99.0% (568 labels) |
python -m quant.ingestion.pipeline --steps factors # daily Fama-French factors
python -m quant.quant_models # exposures, backtests, signals, composite scores
python -m quant.quant_models --model carhart # add the momentum factor to the regressions
python -m quant.quant_models --evaluate # monthly score history + information coefficients
quant/quant_models.py loads prices, filings, splits and factors once into a
Universe, so scores can be recomputed cheaply for any past date. That is what
Phase 4 needs for backtests.
factor_regression):
rolling_exposures
charts how betas drift over time.run_strategies). Every strategy is rank-based on the watchlist,
trades on the next day's returns (no look-ahead), and is charged 5 bps per unit
of turnover:
strategy_signals gives today's momentum rank and leg, the 5-day reversal
rank, and a Bollinger z-score (oversold/overbought).factor_scores):
--evaluate): the Spearman rank IC of each pillar against the
next 21-day return, over about 106 monthly cross-sections.Read the results with two caveats. First, the watchlist is today's mega-caps, chosen with hindsight, so backtests are survivorship-biased. Second, ranking within 10 stocks is very noisy. On current data no pillar predicts next-month returns (every |t| < 2), and 12-1 momentum adds return only in its long-only form. Treat the score as a structured summary of value, quality and trend, not as a proven edge. Phase 6 re-runs these tests on point-in-time S&P 500 members: the momentum alpha disappears there, and the score still shows no predictive power.
python -m quant.trading_bot signals # today's BUY / SELL / HOLD with rationales (no trading)
python -m quant.trading_bot run # trade the latest session(s) in the "paper" portfolio
python -m quant.trading_bot status # positions, P&L, stops, win rate, recent trades
python -m quant.trading_bot backtest --save # walk-forward test since 2018 (also seeds Kelly stats)
python -m quant.scheduler # run everything automatically (APScheduler)
Signals (make_signal). Four components in [−1, 1] are blended into one score:
| Component | Weight | Built from |
|---|---|---|
| Trend | 35% | 50/200-day MA alignment (golden/death cross, price vs 200-day) 60%, plus the 12-1 momentum rank 40% |
| Fundamentals | 25% | SEC quality pillar 60%, plus latest-quarter net income growth year-on-year 40%. A TTM loss or negative operating cash flow caps it at −0.5 |
| Valuation | 20% | Fama-French-style value pillar (E/P, FCF/P, B/M, S/P) 75%, plus the FF3 alpha t-stat 25% |
| Sentiment | 20% | FinBERT score of on-topic headlines over the last 7 days, half-life 2 days, at least 2 headlines |
signals with a rationale that leads with the reasons
pointing the way of the action. For example: "Buy XOM (score +0.53): 50 MA 7.9%
above 200 MA, …; value 89/100 (E/P 4.4%) …; bullish news sentiment (FinBERT +0.33,
29 headlines); positive SEC earnings report (10-Q for 2026-06-30: net income
+105% y/y) …".Risk management:
--sizing kelly,
where half Kelly (f = W − (1−W)/R, from closed round trips) sets the weight of a
median-volatility position, scaled by vol. Until the live ledger has 20 closed
trades, Kelly is bootstrapped from the saved backtest.Ledger (SQLite or Postgres):
portfolios holds cash, last session processed and parameters.positions holds holdings with their stop state.trades logs every fill with its reason (entry/add/trim/exit/stop), a
plain-English rationale, the full signal JSON and round-trip P&L.portfolio_snapshots stores the daily equity curve.signals stores every day's signals.Conventions: market-on-close fills with 5 bps slippage, whole shares, long only, no leverage. News counts only up to the close.
Automation (quant/scheduler.py, US/Eastern):
data/logs/scheduler.log. The scheduler only runs while the process is
up. On a laptop, Windows Task Scheduler is more reliable.Windows scheduled task (registered on this machine as QuantPlatform daily cycle):
.venv\Scripts\pythonw.exe -m quant.scheduler --run-now in the project
folder. pythonw means no console window; printed output goes to
data/logs/scheduler.out.scheduler.log.Get-ScheduledTaskInfo -TaskName "QuantPlatform daily cycle" # last / next run, last result
Start-ScheduledTask -TaskName "QuantPlatform daily cycle" # run now
Unregister-ScheduledTask -TaskName "QuantPlatform daily cycle" # remove
Backtest (2018–2026, 10 stocks, signals weekly, stops daily):
| Sizing | CAGR | Vol | Sharpe | Max drawdown | Avg invested | Win rate | Payoff |
|---|---|---|---|---|---|---|---|
| Volatility parity (default) | 8.7% | 14.3% | 0.46 | −24.8% | 66% | 44% | 1.86 |
| Half-Kelly (full Kelly ≈ 14%) | 6.0% | 8.8% | 0.39 | −20.1% | 37% | 44% | 1.94 |
| SPY buy-and-hold | 14.4% | 100% |
The bot is a risk-reduced trend follower. It cuts drawdowns but lags buy-and-hold in this bull-market decade, and it shows no edge over SPY, even on a survivorship-biased universe. On point-in-time S&P 500 members (Phase 6) it loses money: −1.8% a year.
trading_bot.BotParams.This is a research tool, not investment advice.
Phases 3 and 4 were tested on today's 10 mega-caps, picked with hindsight. That universe is survivorship-biased, and ranks over 10 names are mostly noise. Phase 6 adds an optional research universe: every stock that was in the S&P 500 at some point in the price history, each one eligible only on the days it was actually in the index. The watchlist still drives news, live signals, the paper portfolio and the dashboard.
python -m quant.ingestion.pipeline --universe sp500 --steps universe prices fundamentals # first run ~20 min
python -m quant.quant_models --evaluate --universe sp500 # deciles, point-in-time membership
python -m quant.quant_models --evaluate --universe sp500 --no-pit # today's members back-filled (biased on purpose)
python -m quant.trading_bot backtest --universe sp500 # the Phase 4 rules on the same universe (~1 h)
python scripts/calibrate_phase2.py anomalies --universe sp500 # anomaly rules, member sessions only (~50 min)
To make the S&P 500 the default for these commands and for the daily pipeline, set
RESEARCH_UNIVERSE=sp500 in .env. To extend headlines and FinBERT to the research
universe as well, set NEWS_UNIVERSE=research; it is slow, so it's off by default.
Membership (quant/ingestion/index_membership.py, table index_members):
data/cache/.ticker, name, added, removed): added is inclusive, removed exclusive, and
NULL means "before the log starts" or "still a member". A change takes effect
before the open, so a stock removed on day D is no longer a member on D.RENAMES lists 44
dated symbol changes from 2016 to 2026, so reused symbols resolve correctly ("Q"
was QuintilesIMS until 2017 and is Qnity today). Rows that only record a symbol
change (WLTW → WTW) are not counted as membership changes.Ingestion (--universe sp500):
market_data.update_daily_batch downloads daily bars and split events
for 100 tickers per Yahoo request: 682 tickers and 1.44 M bars in about 6 minutes.
Later runs fetch only the last week and stop asking for delisted names.
Intraday bars stay watchlist-only. Tickers Yahoo has no data for are remembered in
data/cache/yahoo_no_data.json and re-tried monthly.NEWS_UNIVERSE=research.Point in time in the models:
Universe.eligible(date): index members on that date with a price that day. A
plain watchlist (members=None) keeps every ticker eligible, so watchlist results
are unchanged; they were checked byte for byte against the pre-change outputs.factor_scores, score_history, ICs) rank only the stocks eligible on
each date.run_strategies) mask non-members' signals. Each leg holds a
decile of the stocks ranked that day (--quantile) instead of a fixed 3 names.
The equal-weight benchmark holds the previous session's members.trading_bot backtest): signals and entries come only from members. A
holding that leaves the index is sold at the last close. Live trading stays on the
watchlist, and --save stores the run as backtest_sp500, so the backtest
portfolio used for the Kelly bootstrap is not touched.Same prices, same 2016–2026 window, net of 5 bps per unit of turnover. The middle
column (--no-pit) ranks today's 503 members over the whole history: the usual
survivorship-biased setup, kept only to measure the bias.
| Strategy: CAGR, Carhart alpha (t) | 10 mega-caps, top/bottom 3 (before) | Today's S&P 500, back-filled, deciles | Point-in-time S&P 500, deciles |
|---|---|---|---|
| Momentum 12-1, long only | 37.0%, +14.8% (2.6) | 28.3%, +8.8% (2.8) | 14.4%, −2.2% (−0.8) |
| Momentum 12-1, long/short | 1.4%, −1.2% (−0.1) | 2.2%, −1.3% (−0.3) | −3.7%, −5.8% (−1.4) |
| Reversal 1-week, long/short | −10.5%, −8.9% (−0.9) | −1.6%, −2.8% (−0.4) | −0.8%, −2.3% (−0.3) |
| Reversal 1-month, long/short | −8.7%, −4.5% (−0.5) | −6.8%, −4.9% (−0.8) | −2.0%, −0.4% (−0.1) |
| Equal-weight universe | 28.9%, +10.3% (3.8) | 17.1%, +3.6% (2.6) | 12.1%, −0.5% (−0.4) |
| Score IC vs next 21-day return (t) | 10 mega-caps | Today's S&P 500, back-filled | Point-in-time S&P 500 |
|---|---|---|---|
| Stocks per monthly cross-section | 10 | 472–503 | 434–503 |
| Value | −0.056 (−1.27) | −0.003 (−0.14) | +0.001 (+0.04) |
| Quality | +0.033 (+0.86) | +0.018 (+1.55) | +0.017 (+1.46) |
| Momentum | −0.001 (−0.02) | −0.002 (−0.11) | −0.006 (−0.29) |
| Composite | −0.026 (−0.75) | +0.006 (+0.46) | +0.006 (+0.43) |
What changed:
Bot backtest (Phase 4 rules unchanged, volatility parity, 2018-01-03 to 2026-10-19; about an hour on the S&P 500, since it scores ~500 stocks every week):
| Universe | CAGR | Vol | Sharpe | Max drawdown | Round trips | Win rate | Payoff | Full Kelly |
|---|---|---|---|---|---|---|---|---|
| 10 mega-caps (before) | 8.7% | 14.3% | 0.46 | −24.8% | 292 | 44% | 1.86 | 14.2% |
| Point-in-time S&P 500 | −1.8% | 15.6% | −0.22 | −49.1% | 385 | 35% | 1.62 | 0% |
| SPY buy-and-hold | 14.4% |
The bot's modest result on 10 names came from trading only stocks already known to be winners. Picking 6 names from ~500 by a score with no predictive power, and exiting almost entirely on trailing stops (384 of 385 exits), loses about 0.5% per round trip, and full Kelly is 0%, so Kelly sizing would invest nothing. Most of the damage came in 2020 (−24.7%) and 2023 (−11.7%); 2024–2026 were positive (+8.5%, +12.9%, +13.3%).
In the COVID crash the bot bought a fresh set of six names on three weekly signal days in a row (26 Feb, 4 Mar and 11 Mar 2020). The 50/200-day trend filter lags, and new buys are blocked only once the regime turns Bear. Each set was stopped out within days, realizing −$21.7k on a $100k book in a month. The bot was then mostly Risk-Off and only 37% invested during the rebound (+3.2% from March to August). Tightening entries in fast drawdowns (blocking buys while the regime is Volatile, or after a market stop-out) is the obvious next experiment, but it should be judged on this universe, not on the 10 names. A trade-by-trade check found no data artifacts: the worst round trip is −20.5% (Humana gapping below its stop in March 2020), and no fill is at an implausible price.
Phase 2 anomaly rule (calibrate_phase2.py anomalies --universe sp500). The
run covers 560 stocks with at least a year of member sessions (4,629 member-years,
19,698 earnings releases from 8-K Item 2.02 filings). The tuned daily rule holds
up, so it was not changed:
| Daily rule (abs / z / min) | Universe | Flags per stock-year | Spread across stocks | Earnings reactions caught | Flags on earnings days | Market-driven flags |
|---|---|---|---|---|---|---|
| 5% / 3σ / 2% (old) | 10 mega-caps | 13.8 | 5.8× busiest/calmest | 43% | 15% | 33% |
| 8% / 3σ / 2% (tuned) | 10 mega-caps | 8.7 | 2.9× busiest/calmest | 35% | 19% | 32% |
| 5% / 3σ / 2% (old) | point-in-time S&P 500 | 12.6 | 2.9× p90/p10 | 40% | 15% | 24% |
| 8% / 3σ / 2% (tuned) | point-in-time S&P 500 | 8.8 | 1.7× p90/p10 | 37% | 18% | 25% |
Across the index, a single busiest/calmest ratio is dominated by one outlier, so the spread is shown as p90/p10. A reaction day moves 3.9× a normal day for the median stock (1.1× the day before, 1.5× the day after), so the 8-K timing holds beyond mega-caps. The intraday rules use 15-minute bars, which exist only for the watchlist, so they are unchanged.
Missing prices: 7.4% of member-days since October 2016 can't be ranked. Yahoo deletes the history of most companies that stop trading:
A stock without data is never ranked, so the screen still knows, with hindsight, which companies will vanish. Fixing this needs a delisting-aware price source such as CRSP or Norgate, and neither is free.
Membership comes from Wikipedia. It is volunteer-maintained and cited to S&P
press releases, but it is not S&P's official file. The symbol-change list
(RENAMES) was compiled by hand. Effective dates are used, while index changes
are announced about a week earlier.
History is short: about 10 years of prices (2016–2026) and 44 filings per company. The Fama-French factors end on 2026-08-31, so alphas stop there.
Share classes double-count: GOOG/GOOGL, FOX/FOXA and NWS/NWSA rank as two names with nearly identical scores, as they do in the index itself.
Splits: for the research tickers, split history covers only the 10-year download window. A split between a stock's earliest filings (late 2015) and the start of the window (October 2016) would leave those filings' share counts on the wrong basis.
Same caveats as before: 5 bps per unit of turnover, no borrow costs on shorts, and no historical news sentiment for the bot.
streamlit run dashboard/app.py # http://localhost:8501
There are four pages, all reading the same database the pipeline and the bot update. The heavy point-in-time context is loaded once per process and cached for 15 minutes. Use Reload data in the sidebar after a pipeline run. The sidebar also shows how fresh each source is.
| Page | What it shows |
|---|---|
| Market Overview | Regime, stance and risk score with the reasons behind them; the S&P 500 with its 50/200-day MAs and Risk-On / Neutral / Risk-Off shading; ten FRED indicator tiles with 2-year sparklines; watchlist movers with factor scores; breaking news with FinBERT tone and links |
| Stock Deep Dive | Daily or 15-minute candlesticks with the 50/200-day MAs, optional Bollinger bands, and separate volume and RSI panels; ▲/▼/● news markers on the bar each headline first affected; ★ unusual moves whose hover shows the Phase 2 explanation; tables of moves (with catalyst links) and headlines (with the 1-hour price reaction) |
| Financials & Research | TTM revenue, net income, margins, FCF, EPS and D/E from SEC XBRL; quarterly revenue, net income and margins (Q4 derived); the filings list with SEC links; Fama-French / Carhart exposures with t-stats; a return-attribution waterfall; rolling 1-year betas; the watchlist exposure table; composite factor scores; academic strategy backtests |
| Live Trading Bot | Paper or backtest portfolio: equity, return vs SPY, cash, win rate and payoff; equity vs SPY indexed to 100 with drawdown; open positions with weights and distance to stop; the latest signals as component-contribution bars with full rationales; the filterable trade ledger (CSV download); the distribution of closed-trade returns by exit reason |
Charts follow one validated palette. It was checked with a colorblind-safety validator in light and dark mode, and dark mode uses its own steps rather than an inversion.
tests/test_dashboard.py renders every page headlessly with Streamlit's AppTest
(it is skipped until the pipeline has filled the database).
python -m quant.universe.refresh # discover, prices, fundamentals, score (~30 min daily)
python -m quant.universe.refresh --steps score # re-score only
python -m quant.universe.refresh --fund-limit 4000 # fetch more fundamentals in one run
The dashboard's Global Overview page (now the start page) screens the whole investable universe with the same processes the deep tier uses. You can then promote picks to your watchlist for the full treatment.
Two tiers:
| Broad tier (Global Overview) | Deep tier (watchlist) | |
|---|---|---|
| Coverage | ~8,700 stocks ≥ $1B in 21 markets, ~2–3k liquid US-listed ETFs, top 250 coins | The default 10 + anything you promote |
| Prices | 2 years daily, Parquet (data/universe/) | 10 years daily + 15-minute bars, database |
| Analysis | Trend (50/200 MA, crosses), momentum, risk, unusual moves, value, quality, analyst consensus, market regime per region; BUY / HOLD / AVOID with a rationale | Plus news with FinBERT sentiment and the relevance filter, explained moves, SEC filings (US), Fama-French, the paper-trading bot (USD-quoted names) |
Sources (all free, no keys):
How instruments are selected:
UNI7083-USD), and a plain ticker can price a
different coin (ARB-USD is not Arbitrum). Coins Yahoo doesn't carry are listed
without technical scores. Results are cached in data/universe/crypto_symbols.json.How instruments are scored (quant/universe/scoring.py):
Watchlist → deep tier:
--no-universe skips that).Limits:
https://chanwoo777.github.io/quant-platform/ is a static copy of the dashboard, refreshed once a day, plus tools that run in the browser. GitHub Pages can't run Python, so the PC that runs the pipeline exports the data as JSON and pushes it.
python -m quant.web.export # build the site in data/site (~1 min)
python -m quant.web.publish # export + push to the gh-pages branch
python -m quant.scheduler --run-now --publish-site # daily cycle, then publish
| Page | What it does |
|---|---|
| Global | The screener (every stock ≥ $1B, liquid US ETF and top coin) with filters, per-symbol charts and signal reasons, plus a sector heatmap of the day's moves. ★ adds a name to your watchlist. |
Compare (#/compare/<A,B,C>) | 2-5 tickers side by side: returns, risk, factor exposures, fundamentals and a correlation heatmap. Details below. |
| Markets | Regime and stance, the S&P 500 stance chart, FRED tiles and news. Your watchlist: search to add any stock, ETF, coin or index, ✕ to remove. It is saved in the browser (localStorage). |
Stock (#/stock/<ticker>) | Any of ~11,400 symbols: candlesticks with every indicator below, plus returns, risk and peer percentiles. Deep-tier names add news markers and explained moves. The chart loads about two years of daily bars in the browser (see Price history below). |
Financials (#/financials/<ticker>) | A research view for any ticker: fundamentals, the Analysts pillar (with Finnhub's recommendation trends when a key is set), a peer table, financial statements and the factor model. Statements come from SEC filings for the deep tier, or Finnhub's reported statements for US names when a key is set. |
Quantum (#/quantum) | A beginner's guide to quantum algorithms for investors: basics, a qubit lab, option pricing, VaR/CVaR and portfolio labs with classical baselines, a readiness scorecard and the nightly simulator run. Details below. |
Live (#/live/<symbol>) | The Pro chart: a TradingView-style chart drawn by the site, with every indicator and setting under your control, and price alerts. Details below. |
Methodology (#/methodology/<id>) | How every score, signal and metric is calculated, from the code; every "?" on the dashboard links here. |
The header search autocompletes over the whole universe. Pretty links such as
/quant-platform/stock/AAPL also work: 404.html turns them into the hash route.
Factor models in the browser. docs/assets/quant.js reimplements
quant_models.factor_regression and rolling_exposures: OLS with Newey-West
(5-lag) errors, variance shares, return attribution and tilts. So FF3, Carhart and
FF5 run for any stock, from factors.json and the daily prices the browser loads. The
loadings chart shows 95% confidence intervals. tests/test_site_js.py runs the
JavaScript under Node.js against the Python models, on synthetic data with missing
values; they agree to about one part in a million. Fixes in this round:
smb5, built from more sorts than
SMB). quant_models.FF5 and the Streamlit Financials page offer it, and the site
estimates it per ticker.Indicators. These are the popular TradingView indicators, implemented in
quant.js from the Pine Script built-ins:
They are drawn on the Stock pages and the Markets S&P 500 chart. Your choices are
remembered in the browser. tests/test_site_js.py checks each one under Node.js
against Python versions of the Pine definitions, and against the ta library where
it uses the same formulas. Every chart has highs, lows and volume, because prices
load in the browser as full daily bars (indices have no volume).
Pro chart (Live). A TradingView-style chart built on TradingView's open-source Lightweight Charts v5, so indicators aren't limited the way an embedded widget is.
Toolbar: symbol search, intervals from 1 minute to 1 month, and chart types (candles, hollow candles, bars, line, area, Heikin Ashi). Also log and percentage scales, screenshot and full screen.
Indicators: a searchable dialog with 26 studies, each with TradingView's default inputs:
Add any study as often as you like. Each opens with editable inputs and colours (⚙ in the legend), can be hidden or removed, and oscillators get their own resizable pane.
Legend: OHLC and every indicator value under the cursor, as on TradingView. It folds away on phones.
Drawing tools: trend line, ray, horizontal and vertical lines, Fibonacci retracement, rectangle and a measure tool. Drawings are saved per symbol in the browser.
Shortcuts: TradingView's own, such as / for indicators and typing 5, 60,
1D or W then Enter for the interval. Also Alt+T, H, V and F for drawing
tools, Alt+L for log scale, Alt+S for a screenshot, Esc, Delete and Ctrl+Z.
Investor playbooks: the indicator sets well-known traders are known for, with their published settings and usual interval:
Fundamental investors like Buffett and Lynch don't use chart indicators, and the page says so rather than inventing a set.
Data: every source is keyless and called from the visitor's browser:
Research tools. Five tools added on top of the pages above. Each uses only the nightly export and the in-browser price history (see Price history); no new data sources.
Compare (#/compare/AAPL,MSFT,NVDA): 2-5 tickers side by side. The tickers
live in the URL, so a link shares the comparison. The table has a column per
ticker, best/worst highlighting per row and a CSV download.
Correlation heatmap: Pearson correlation of daily returns (3M, 6M, 1Y) or
weekly returns (3Y). It can be ordered by average-linkage clustering on 1 −
correlation, or alphabetically, and shows the average pairwise correlation and
the most and least correlated pairs. One component (drawCorrelationPanel)
serves both Compare and the Quantum portfolio lab.
Sector heatmap (Global → Sector heatmap): a squarified treemap of the latest nightly close's moves by sector. Tile size is market cap; colour is the cap-weighted 1-day change. It drills down to each sector's largest 150 stocks. The sector is the exported Yahoo sector field, which every stock has, so no SIC mapping is needed. Missing values show as grey n/a tiles.
Price alerts (Live): price at or above or below a level, or a % move from the previous close. They are saved in the browser and checked every 15 s while the tab is open, on any page:
Each alert fires once, as a browser notification, or an in-page toast if permission is denied. It can then be re-armed or deleted.
Methodology (#/methodology/<id>): 43 entries written from the code, each with
a description, the formula (MathML), inputs and sources, window, update
frequency, limitations, and the file and function. A "?" next to each dashboard
metric links to its entry.
tests/test_research_js.py checks the maths under Node.js against numpy:
Quantum tab (#/quantum). A beginner's guide to quantum algorithms and
where they might help investors, with a classical baseline next to every quantum
number. Twelve sections:
market.json). The page shows
Black-Scholes, Monte Carlo with a 95% interval and its convergence, and the
nightly amplitude estimate with its error budget. A six-step explainer covers
how QAE prices the option.The maths in docs/assets/quantum.js is tested under Node.js
(tests/test_quantum_js.py):
Nightly quantum simulations (quant/web/quantum.py). These use Qiskit's
statevector simulator only: no quantum hardware, no accounts and no keys.
The export runs it in a subprocess with a 5-minute limit; it takes about 2.5
minutes here. The module replaces data/quantum.json only when every job
succeeds. The export then copies the last good file into the site, so a failed or
timed-out run leaves the previous night's results online. With no file at all,
the page says the results aren't published yet, and every classical part still
works. tests/test_quantum_job.py covers the failure paths and runs small real
simulations.
pip install -r requirements-quantum.txt # pinned: qiskit 2.6.1, qiskit-algorithms 0.4.0,
# qiskit-finance 0.4.1, qiskit-optimization 0.7.0
python -m quant.web.quantum # run the simulations alone (~2.5 min)
python -m quant.web.export --no-quantum # export without re-running them
Limits. Every quantum number is labelled "Simulated on a classical computer - educational, not a trading signal".
Price history. The site publishes no price history, because Yahoo's terms
allow personal use only. The nightly export publishes calculated numbers instead
(scores, percentiles, returns, volatility, drawdowns, betas, signals), plus the
latest close and fundamentals. Analyst ratings aren't published either: they are
licensed data, so the site shows only the calculated Analysts pillar, and the
signal reasons name that percentile instead of the rating. Every chart, the
Financials factor model and
the Quantum labs load past prices in the visitor's browser
(window.LIVE.dailyHistory in live.js):
The nightly quantum simulations still use the platform's own price store on the PC; their inputs and results are published, not the prices.
Data sources and keys:
data-api.binance.vision, data-stream.binance.vision).How it works:
Code: the site's code is versioned on main under docs/:
index.html, about.html, privacy.html and 404.htmlassets/app.js: the pagesassets/quant.js: statistics, factor models and indicatorsassets/quantum.js: the Quantum page and its maths (state-vector simulator,
option pricing, VaR, frontier, QUBO)assets/live.js: the Pro chart (Lightweight Charts v5, Binance and Finnhub
WebSockets, CNBC polling, studies, drawings, shortcuts), the in-browser price
history and price alertsassets/research.js: Compare and the shared correlation heatmapassets/methodology.js: the Methodology pageassets/app.cssIt is plain JavaScript, with Plotly and Lightweight Charts from jsDelivr and no build step.
Data: quant/web/export.py calls the same functions as the Streamlit pages
(dashboard/common.py), so both show the same numbers. It writes JSON to
data/site/data/:
meta, global (the screener), symbols (search) and indicessym/<xx>: fundamentals and statistics per symbol (no prices), in 256
FNV-1a bucketsfactors, market (with four years of daily DGS3MO for Compare's Sharpe),
deep/<T>, research and quantumThe total is about 11 MB raw; GitHub Pages compresses it.
Publishing: quant/web/publish.py commits data/site as a fresh one-commit
history and force-pushes it to gh-pages. Each day's snapshot replaces the last,
so data never piles up in the repository. Pushing uses your normal git
credentials (the GitHub CLI's credential helper here).
Schedule: the Windows scheduled task runs --run-now --publish-site, so the site
updates after each 07:30 KST cycle.
What differs from the local dashboard:
The site publishes no Yahoo price history and no analyst ratings. It still
publishes the latest close and fundamentals from Yahoo Finance, whose terms allow
personal use only; publishing those is the owner's decision. BUY / HOLD / AVOID are
mechanical labels from ranking past data against peers, and the site says so next
to them: they are not recommendations. To take the site down, disable Pages in the
repository settings or delete the gh-pages branch.
Start here (#/start) is where a first visit lands. It has three one-click examples:
Samsung against its big-tech peers on Compare, a basket in the VaR / CVaR lab, and an
option priced classically and by the nightly quantum run.
Shareable links. Every page writes what it shows after the route, for example
#/global?sig=BUY&mkt=US&sort=r1m&page=2 or #/quantum/risk?syms=AAPL,TSM&w=50,50.
docs/assets/og-preview.png).Daily brief (Global). Each night quant/web/brief.py writes a few sentences by rule, not AI,
in English and Korean. It covers:
The last 30 briefs are a click away.
Watchlist. ☆ next to any ticker adds it. The list is kept in your browser and shown on Global and Live, and it can be exported and imported as JSON.
PNG and CSV. Every chart downloads as a PNG marked with the site's address and the data's as-of date. Every table and chart downloads as CSV.
Feedback. "Suggest a feature / report a bug" opens GitHub Issues, with forms for bugs, data
errors and ideas (.github/ISSUE_TEMPLATE).
한국어 / English. The button in the header switches the language. The choice is remembered in the browser, and a Korean-language browser starts in Korean.
docs/assets/i18n.js translates
the interface as it draws, from the dictionary in docs/assets/i18n.ko.js, keyed by the
English text.I18N.report() in the browser console, or on screen when ?i18n=missing is added to a
page's address. That list is how to find hard-coded text.Every number on the site can be traced to its source, its date and the code that computed it.
Check the freshness badge. Every panel heading carries a badge that names the panel's sources and the oldest as-of date among them.
Read the Data status page (#/status, linked from the footer). The nightly job writes
data/status.json (quant/web/status.py). For each source it lists:
quant/web/quality.py): repeated rows, gaps of 3+ sessions,
closes at or below zero, zero volume, and 20x one-day jumps (unit changes).A failing series is never dropped silently. Its symbol gets a ⚠ in the screener and a note on its pages. A missing file or a failed export step shows red, and the page that needs it says so instead of drawing a partial chart.
Look up the formula. The "?" next to a metric opens its Methodology entry (#/methodology/<id>).
Each entry gives the formula, the inputs and their source, the window, the limitations, and the
file and function that compute it.
Download the data. Every table and chart has a CSV button. Each file starts with the panel's title, its sources and as-of dates, the download time and page, and the disclaimer. The screener's file holds every matching row, not just the visible page.
Recompute it. The code is public ("View the code" in the header and footer). Run
python -m quant.web.export to rebuild the same JSON from the same sources. The in-browser maths
(docs/assets/quant.js) is tested against numpy in tests/test_research_js.py.
Check the signals' record. The Signal track record (#/track-record) shows forward returns of
the BUY / HOLD / AVOID labels after 5, 20 and 60 trading days, against each market's index, with
the hit rate, the number of signal dates and the start date.
Research and education only, not investment advice. Data may be delayed or wrong. Past performance does not predict future results.
.env, which git
ignores..github/workflows/tests.yml runs python -m pytest on
Python 3.14 with Node.js 24, for every push to main and every pull request.
requirements.txt without torch and transformers (the tests use the
lexicon sentiment scorer, so CI never downloads FinBERT), plus
requirements-quantum.txt..github/dependabot.yml opens weekly pull requests for outdated
Python packages (Qiskit grouped on its own) and actions, and the tests check each one.
The JavaScript libraries loaded from jsDelivr (Plotly 2.35.2, Lightweight Charts 5.2.1)
are pinned in the HTML and JS and updated by hand.tests/fixtures/relevance_labels.csv)
at the 0.5 threshold: 99.0% precision and 99.5% recall, up from 85.4% / 98.9%
before tuning. These figures are in-sample; on 28 headlines fetched after
tuning, the filter made 3 errors.news_articles.relevance. Anomaly matching requires the
company to be named in the headline (score ≥ 0.4), and sentiment signals should
use on-topic headlines only (score ≥ 0.5).The earlier version embedded TradingView's widget for US stocks; it is no longer used.
tests/test_i18n.pydata/trackquant/web/track.pydata/ folder, which isn't in git.See what changed. The Changelog (#/changelog) is built from commits tagged feat: or fix:.
Commits that change a calculation are marked methodology and listed first.