270 lines
14 KiB
Markdown
270 lines
14 KiB
Markdown
# Bitcoin Accumulation Zone Monitor
|
||
|
||
> Bitcoin on-chain metrics dashboard with classic equal-weight scoring, ML-optimized scoring, historical backtesting, and click-to-select metric context for long-term BTC accumulation decisions.
|
||
|
||

|
||
|
||
## What It Does
|
||
|
||
Monitors Bitcoin accumulation conditions using 16 scored market/on-chain indicators plus optional informational cycle metrics. Each scored metric receives a 0-10 score and rolls into a 0-100 accumulation score.
|
||
|
||
The dashboard now supports two scoring modes:
|
||
|
||
- **Classic** — transparent equal-weight scoring across every active metric.
|
||
- **ML** — feature-importance weights trained against historical 365-day forward returns, with displayed per-metric weights and point contributions.
|
||
|
||
Historical backtests show score-vs-price behavior, score bracket performance, major signal events, and current-score context. Metric cards are clickable: selecting a metric overlays its historical series on the score chart and shows comparable historical periods with forward returns.
|
||
|
||
## Screenshots
|
||
|
||
### Main Dashboard — ML mode + metric context
|
||

|
||
*Live BTC price, Classic/ML scoring toggle, 16 active scored metrics, ML weights/contributions, metric sparklines, and click-to-select historical context.*
|
||
|
||
### Historical Backtest
|
||

|
||
*Current signal percentile, comparable historical periods by cycle, score-vs-BTC chart, bracket performance, and major signal events.*
|
||
|
||
### Settings
|
||

|
||
*LLM provider configuration for optional AI-powered signal commentary and local/cloud model selection.*
|
||
|
||
## Feature Highlights
|
||
|
||
- **16 scored metrics** from market sentiment, miner stress, valuation, holder behavior, network activity, and velocity signals.
|
||
- **Classic vs ML scoring toggle** on the dashboard and backtest API.
|
||
- **ML score explainability**: metric cards show learned weight and contribution in points.
|
||
- **Leakage-resistant ML validation**: training uses purged time-series splits so 365-day forward-return labels do not overlap validation windows.
|
||
- **Historical context panel**: compares the current composite score against historical periods and forward returns.
|
||
- **Clickable metric cards**: select any metric to see percentile, similar historical levels, forward returns, example dates by market cycle, and highlighted chart periods.
|
||
- **Score history chart** with BTC price overlay, range controls, and selected-metric overlay.
|
||
- **Backtest dashboard** with current signal context, score bracket performance, and signal-crossing events.
|
||
- **Quick vs full refresh**: quick refresh updates BTC price and Fear & Greed; full refresh re-scrapes on-chain sources.
|
||
- **LLM settings UI** for Ollama, LM Studio, OpenAI, Anthropic, and OpenRouter.
|
||
|
||
## Metrics
|
||
|
||
| # | Metric | Source | Accumulation Signal |
|
||
|---|--------|--------|-------------------|
|
||
| 1 | Fear & Greed Index | alternative.me API | Extreme fear / capitulation sentiment |
|
||
| 2 | Puell Multiple | LookIntoBitcoin | Miner revenue stress |
|
||
| 3 | MVRV Z-Score | LookIntoBitcoin | Market near/below realized value |
|
||
| 4 | Drawdown from ATH | Calculated from BTC price | Deep correction from cycle high |
|
||
| 5 | Price vs 200W SMA | LookIntoBitcoin + BTC price | Price near/below long-term trend |
|
||
| 6 | Reserve Risk | LookIntoBitcoin | High holder confidence relative to price |
|
||
| 7 | RHODL Ratio | LookIntoBitcoin | Long-term holder dominance |
|
||
| 8 | Net Unrealized Profit/Loss (NUPL) | LookIntoBitcoin | Capitulation / early recovery zones |
|
||
| 9 | LTH Realized Price | LookIntoBitcoin | Price near long-term holder cost basis |
|
||
| 10 | Hash Ribbons | LookIntoBitcoin | Miner capitulation/recovery signal |
|
||
| 11 | SOPR | CheckOnChain | Spent outputs near loss / reset territory |
|
||
| 12 | Sell-side Risk Ratio | CheckOnChain | Low realized profit/loss pressure |
|
||
| 13 | Active Address Momentum | CheckOnChain | Network activity momentum extremes |
|
||
| 14 | Transaction Count Momentum | CheckOnChain | Transaction activity momentum extremes |
|
||
| 15 | NVT Price | CheckOnChain | Network-value valuation discount/premium |
|
||
| 16 | VDD Multiple | CheckOnChain | Coin-days/velocity reset conditions |
|
||
|
||
Informational cards may also appear when data is available, such as **Long-Term Holder Supply** and **Pi Cycle Bottom**. These are displayed for context and are not included in the composite score.
|
||
|
||
## Score Interpretation
|
||
|
||
| Score | Assessment | Interpretation |
|
||
|-------|-----------|----------------|
|
||
| 80-100 | 🟢 Extreme Accumulation Zone | Broad capitulation/value conditions across active metrics |
|
||
| 65-79 | 🟢 Strong Accumulation Zone | Historically attractive long-term entry territory |
|
||
| 50-64 | 🟡 Moderate Opportunity | DCA-friendly, but not maximum-signal conditions |
|
||
| 35-49 | 🟡 Neutral | Mixed signals; not compelling either direction |
|
||
| 20-34 | 🔴 Caution — Overheated | Market conditions becoming less favorable |
|
||
| 0-19 | 🔴 Extreme Caution | Historically poor accumulation setup |
|
||
|
||
Backtest tables provide actual historical forward-return statistics per score bracket, including 30d/90d/180d/1yr averages, win rate, max gain/loss, and average max drawdown.
|
||
|
||
## ML-Optimized Scoring
|
||
|
||
The ML mode uses a `GradientBoostingClassifier` trained on historical feature rows to predict whether a day was a good long-term buy based on 365-day forward return. Training features include:
|
||
|
||
- Classic metric scores.
|
||
- Raw metric values.
|
||
- 30-day metric deltas.
|
||
- Interaction features such as MVRV × NUPL and Puell × Reserve Risk.
|
||
- Cycle-position context such as days since ATH.
|
||
|
||
The resulting feature importances are aggregated back into transparent metric weights stored in `config/ml_weights.json`. The UI displays normalized weight and contribution for each active metric.
|
||
|
||
Validation uses purged expanding time-series splits: because each label uses a 365-day forward-return window, training rows whose label windows overlap validation are removed before scoring validation folds.
|
||
|
||
## Tech Stack
|
||
|
||
| Component | Technology |
|
||
|-----------|-----------|
|
||
| Backend | Python 3.13 + FastAPI |
|
||
| Frontend | Inline HTML/CSS/JS dark trading-terminal UI |
|
||
| Charts | Chart.js |
|
||
| Scraping | requests + Playwright-style browser scraping where needed |
|
||
| Data APIs | alternative.me, CoinGecko, LookIntoBitcoin, CheckOnChain |
|
||
| ML | NumPy + pandas + scikit-learn GradientBoostingClassifier |
|
||
| Process Manager | pm2 or uvicorn |
|
||
| Default Port | 3088 |
|
||
|
||
## How Data Is Collected
|
||
|
||
Data is collected from free/public sources and cached locally under `data/`.
|
||
|
||
- Fast live refreshes update BTC price, ATH/drawdown, 200D SMA/Mayer where possible, and Fear & Greed.
|
||
- On-chain metrics are cached and reused because they update slowly.
|
||
- Full refresh re-scrapes on-chain metrics from LookIntoBitcoin/CheckOnChain.
|
||
- Historical backtest data lives in `data/history.json` and supports charting, backtests, and metric-context lookups.
|
||
- Score history appends to `data/score_history.jsonl`.
|
||
|
||
## Project Structure
|
||
|
||
```
|
||
├── dashboard/
|
||
│ └── server.py # FastAPI server + inline dashboard/backtest/settings UI
|
||
├── scrapers/
|
||
│ ├── lookintobitcoin.py # LookIntoBitcoin metric scraping
|
||
│ ├── checkonchain.py # CheckOnChain metric scraping
|
||
│ ├── history_collector.py # Full historical data collection
|
||
│ ├── history_updater.py # Incremental historical updates
|
||
│ ├── fear_greed.py # Fear & Greed Index API
|
||
│ └── price.py # BTC price, ATH, drawdown, SMA helpers
|
||
├── scoring/
|
||
│ └── engine.py # Classic + ML-weighted scoring logic
|
||
├── backtesting/
|
||
│ └── engine.py # Historical backtest engine
|
||
├── ml/
|
||
│ └── optimizer.py # ML training, purged CV, weight export
|
||
├── tests/
|
||
│ ├── test_ml_optimizer_validation.py
|
||
│ └── test_scoring_engine_ml.py
|
||
├── data/
|
||
│ ├── cache.json # Live metric cache
|
||
│ ├── history.json # Historical metric/time-series data
|
||
│ └── score_history.jsonl # Live score history
|
||
├── config/
|
||
│ ├── thresholds.json # Classic scoring thresholds
|
||
│ ├── ml_weights.json # Learned ML metric weights
|
||
│ └── llm_settings.json # Optional AI commentary provider config
|
||
├── screenshots/ # README screenshots
|
||
├── scripts/run.sh # Locked local launcher with Playwright path
|
||
├── .gitea/workflows/ci.yml # Gitea Actions test/compile gates
|
||
├── Dockerfile # Non-root Chromium-enabled image
|
||
├── docker-compose.yml # Port, healthcheck, restart, persistent volumes
|
||
├── pyproject.toml # Runtime, ML, and development dependency groups
|
||
├── uv.lock # Exact reproducible dependency resolution
|
||
├── ARCHITECTURE.md
|
||
└── README.md
|
||
```
|
||
|
||
## Reproducible Setup
|
||
|
||
Install [uv](https://docs.astral.sh/uv/) and use Python 3.11-3.13. Dependencies are declared in explicit `runtime`, `ml`, and `dev` groups in `pyproject.toml`; exact cross-platform resolutions are committed in `uv.lock`.
|
||
|
||
```bash
|
||
git clone <repository-url>
|
||
cd btc-accumulation-monitor
|
||
uv sync --locked --group runtime --group ml --group dev
|
||
```
|
||
|
||
Install the Chromium binary once for full on-chain refreshes. Keep its path explicit so installation and runtime use the same browser cache:
|
||
|
||
```bash
|
||
export PLAYWRIGHT_BROWSERS_PATH="$PWD/.playwright"
|
||
uv run --frozen playwright install chromium
|
||
```
|
||
|
||
`requirements_vps.txt` is a lock-derived, hash-pinned compatibility export for pip-based hosts. `pyproject.toml` and `uv.lock` remain authoritative; regenerate the compatibility file after dependency changes with:
|
||
|
||
```bash
|
||
uv export --frozen --no-dev --group runtime --group ml \
|
||
--no-emit-project --no-header --output-file requirements_vps.txt
|
||
```
|
||
|
||
## Running
|
||
|
||
The executable launcher fixes `PYTHONPATH`, preserves an explicitly supplied `PLAYWRIGHT_BROWSERS_PATH`, and starts port 3088 from the locked environment:
|
||
|
||
```bash
|
||
./scripts/run.sh
|
||
```
|
||
|
||
Equivalent exact command:
|
||
|
||
```bash
|
||
PLAYWRIGHT_BROWSERS_PATH="$PWD/.playwright" PYTHONPATH=. \
|
||
uv run --frozen --no-dev --group runtime --group ml \
|
||
python -m uvicorn dashboard.server:app --host 0.0.0.0 --port 3088
|
||
```
|
||
|
||
Then visit `http://localhost:3088`.
|
||
|
||
## Container Deployment
|
||
|
||
The image uses a multi-architecture Python base, installs Playwright Chromium and its OS libraries during the build, and runs the application as non-root UID `10001`. Compose publishes port 3088, restarts unless stopped, and persists `/app/data` and `/app/config` in named volumes.
|
||
|
||
```bash
|
||
docker compose build
|
||
docker compose up -d
|
||
```
|
||
|
||
The Docker and Compose healthchecks probe `GET /health/live`. The deployment must include the reliability revision that supplies that endpoint; without it, Docker correctly reports the container unhealthy even if the older application server is accepting requests.
|
||
|
||
Named volumes are initialized from the image on first use. Back up both before replacing or deleting them:
|
||
|
||
```bash
|
||
docker volume inspect btc-accumulation-monitor_btc-monitor-data
|
||
docker volume inspect btc-accumulation-monitor_btc-monitor-config
|
||
```
|
||
|
||
For bind-mounted deployments, ensure the host directories are writable by UID `10001` and do not replace `config/` with an empty directory.
|
||
|
||
## First Run and Data Freshness
|
||
|
||
1. Visit `http://localhost:3088` for the dashboard.
|
||
2. Use **Quick Refresh** for price and Fear & Greed updates while retaining cached slow-moving on-chain metrics.
|
||
3. Use **Full Refresh** when on-chain source data must be re-scraped; this requires the installed Playwright Chromium browser and external source availability.
|
||
4. Visit `http://localhost:3088/backtest` for historical analysis.
|
||
5. If historical data is missing, populate `data/history.json` through the existing collection flow.
|
||
|
||
Freshness is metric-specific. Price and sentiment APIs can update frequently, while public on-chain chart sources commonly update daily and may be reused from cache. A successful refresh is not proof that every upstream metric has a new observation. Check source timestamps/status exposed by the running revision, and treat missing, stale, or scrape-failed metrics as unavailable rather than silently current. `data/` is operational state and should be persisted and backed up.
|
||
|
||
## ML and Backtest Caveats
|
||
|
||
ML weights and backtest output are research artifacts, not investment advice or evidence of future performance. Any reported ML result must retain its provenance: source-data snapshot/range, feature and label definitions, training window, purge/embargo policy, code revision, dependency lock, random seed (when applicable), and generated weight/config artifact.
|
||
|
||
Model selection and threshold tuning must use training/validation data only. Report final performance on a genuinely untouched out-of-sample (OOS) period; do not describe in-sample fit, cross-validation used for selection, or the best result from repeated experiments as OOS. Forward-return labels require purging overlapping label horizons, but purged cross-validation alone does not create an untouched final test set. Results without reproducible provenance and a reserved OOS evaluation should be labeled exploratory.
|
||
|
||
## Useful API Endpoints
|
||
|
||
| Endpoint | Description |
|
||
|----------|-------------|
|
||
| `GET /api/data?mode=classic` | Current metrics using equal-weight scoring |
|
||
| `GET /api/data?mode=ml` | Current metrics using ML-optimized weights |
|
||
| `GET /api/history` | Recent live score history |
|
||
| `POST /api/refresh` | Quick refresh |
|
||
| `POST /api/refresh?full=true` | Full on-chain refresh |
|
||
| `GET /api/backtest?mode=classic` | Historical backtest with classic scoring |
|
||
| `GET /api/backtest?mode=ml` | Historical backtest with ML scoring |
|
||
| `GET /api/metric-context?metric=mvrv_zscore&mode=ml` | Similar historical levels and forward returns for one metric |
|
||
| `GET /api/settings` | Safe LLM settings payload |
|
||
|
||
## Testing and CI
|
||
|
||
Run the committed test suite and the same static compilation gate used by Gitea Actions:
|
||
|
||
```bash
|
||
uv sync --locked --group runtime --group ml --group dev
|
||
uv run --frozen python -m compileall -q \
|
||
dashboard scrapers scoring backtesting ml ml_engine llm_client scripts orchestrator.py
|
||
uv run --frozen pytest
|
||
```
|
||
|
||
`.gitea/workflows/ci.yml` runs lock validation/install, static compilation, and tests for pull requests and pushes to `main`.
|
||
|
||
## Architecture
|
||
|
||
See [ARCHITECTURE.md](ARCHITECTURE.md) for deeper implementation details on scoring, data collection, and backtesting.
|
||
|
||
## License
|
||
|
||
Private — not for public distribution.
|