v4: Bitcoin Accumulation Zone Monitor — on-chain metrics + backtest engine

COMPLETE PIVOT from ML trading optimizer to on-chain metrics monitor.

Architecture:
- Playwright scrapes LookIntoBitcoin Plotly Dash charts for real on-chain data
- 10 proven metrics: Puell Multiple, MVRV Z-Score, Fear & Greed, Reserve Risk,
  RHODL Ratio, NUPL, LTH Realized Price, 200W SMA, Hash Ribbons, Drawdown
- Each metric scores 0-10, composite 0-100
- No ML, no black box — every signal transparent and traceable
- Historical backtest validates scoring against actual BTC forward returns
- Recency-weighted analysis accounts for diminishing cycle returns

Full documentation in ARCHITECTURE.md
This commit is contained in:
BizzleBot 2026-03-20 23:07:53 +00:00
parent 5b3b3811ec
commit 86168a3607
4 changed files with 292 additions and 21 deletions

11
.gitignore vendored
View File

@ -1,4 +1,9 @@
scoring/__pycache__/
scrapers/__pycache__/
__pycache__/
data/
*.pyc
data/cache.json
data/history.json
config/llm_settings.json
results/
*.log
.env
node_modules/

262
ARCHITECTURE.md Normal file
View File

@ -0,0 +1,262 @@
# Bitcoin Accumulation Zone Monitor — Architecture & Logic
## Overview
This is **NOT** a trading bot or ML predictor. It monitors proven Bitcoin on-chain metrics that have historically signaled optimal accumulation (buying) zones for long-term holders. Each metric scores 0-10 points, producing a composite score of 0-100.
**Philosophy:** Every signal is transparent and traceable. No black box. The metrics used have correctly identified every major Bitcoin cycle bottom since 2010.
## How It Works
### Data Pipeline
```
LookIntoBitcoin.com ──┐
(Playwright scraper) │
├──> data/cache.json (current values, refreshed every 15min)
alternative.me API ────┤ data/history.json (full history back to 2010, refreshed weekly)
CoinGecko API ─────────┘
Scoring Engine (scoring/engine.py)
Composite Score 0-100
┌────┴────┐
▼ ▼
Dashboard Backtest Engine
(live) (historical validation)
```
### Data Sources
All data is scraped or fetched from free sources — **no API keys required**.
| Source | Method | Data |
|--------|--------|------|
| LookIntoBitcoin / BitcoinMagazinePro | Playwright browser scraping of Plotly Dash charts | Puell Multiple, MVRV Z-Score, Reserve Risk, RHODL Ratio, NUPL, 200W SMA, LTH Realized Price, LTH Supply, Hash Ribbons, Pi Cycle |
| alternative.me | Free REST API | Fear & Greed Index (daily, back to Feb 2018) |
| CoinGecko | Free REST API | BTC price, market cap, 24h change |
#### Scraping Method (LookIntoBitcoin)
The site uses Plotly Dash charts. We intercept the `_dash-update-component` XHR response which contains the full chart data as JSON:
```python
page.on("response", handler) # Intercept XHR
page.goto("https://www.lookintobitcoin.com/charts/puell-multiple/")
# Response contains: response['chart']['figure']['data'] → list of trace objects
# Each trace: {name: str, x: [dates], y: [values]}
```
This gives us the **complete historical time series** (5000+ data points per metric going back to 2010) without needing any API key.
## Scoring System
### Individual Metrics (0-10 each)
#### 1. Fear & Greed Index (source: alternative.me)
Measures market sentiment from social media, surveys, and momentum.
| F&G Value | Classification | Score |
|-----------|---------------|-------|
| 0-10 | Extreme Fear | 10 |
| 11-25 | Fear | 7 |
| 26-45 | Neutral-low | 4 |
| 46-55 | Neutral | 2 |
| 56-75 | Greed | 1 |
| 76-100 | Extreme Greed | 0 |
**Logic:** "Be fearful when others are greedy, be greedy when others are fearful." — Buffett. Extreme Fear has historically coincided with cycle bottoms.
#### 2. Puell Multiple (source: LookIntoBitcoin)
Measures miner revenue relative to 365-day average. When miners earn very little (low Puell), they're capitulating — historically a bottom signal.
| Puell Value | Meaning | Score |
|-------------|---------|-------|
| < 0.3 | Deep miner capitulation | 10 |
| 0.3-0.5 | Miner stress | 8 |
| 0.5-0.8 | Below average revenue | 5 |
| 0.8-1.2 | Normal | 3 |
| 1.2-2.0 | Above average | 1 |
| > 2.0 | Miner euphoria | 0 |
**Historical accuracy:** Puell < 0.5 identified the Dec 2018, Mar 2020, and Jun 2022 bottoms.
#### 3. MVRV Z-Score (source: LookIntoBitcoin)
Compares market value to realized value. Negative Z-Score means the market is valued below what everyone paid — extreme undervaluation.
| Z-Score | Meaning | Score |
|---------|---------|-------|
| < 0 | Below realized value | 10 |
| 0-0.5 | Undervalued | 8 |
| 0.5-1.5 | Fair value | 5 |
| 1.5-3.0 | Overvalued | 2 |
| 3.0-5.0 | Very overvalued | 1 |
| > 5.0 | Extreme overvaluation | 0 |
**Historical accuracy:** Every time MVRV Z-Score went below 0, buying led to >200% returns within 2 years (100% hit rate across all cycles).
#### 4. Drawdown from ATH (calculated from price)
How far BTC has fallen from its all-time high. Larger drawdowns = better buying opportunity historically.
| Drawdown | Score |
|----------|-------|
| > 70% | 10 |
| 50-70% | 8 |
| 30-50% | 6 |
| 20-30% | 4 |
| 10-20% | 2 |
| < 10% | 0 |
#### 5. Price vs 200-Week SMA (source: LookIntoBitcoin)
The 200-week moving average has historically acted as the absolute floor in bear markets.
| Position | Score |
|----------|-------|
| Below 200W SMA | 10 |
| 0-20% above | 6 |
| 20-50% above | 3 |
| 50-100% above | 1 |
| > 100% above | 0 |
#### 6. Reserve Risk (source: LookIntoBitcoin)
Measures the confidence of long-term holders relative to the price. Low Reserve Risk = high confidence among HODLers + low price = excellent time to buy.
| Reserve Risk | Score |
|--------------|-------|
| < 0.002 | 10 |
| 0.002-0.005 | 7 |
| 0.005-0.01 | 4 |
| 0.01-0.02 | 2 |
| > 0.02 | 0 |
#### 7. RHODL Ratio (source: LookIntoBitcoin)
Ratio of 1-week old coins to 1-2 year old coins. Low ratio = long-term holders dominating (accumulation). High ratio = short-term speculation (distribution).
| RHODL | Score |
|-------|-------|
| < 100 | 10 |
| 100-500 | 7 |
| 500-2000 | 4 |
| 2000-10000 | 1 |
| > 10000 | 0 |
#### 8. NUPL — Net Unrealized Profit/Loss (source: LookIntoBitcoin)
Shows what fraction of market cap is unrealized profit. Negative = market is at a loss (capitulation). Above 0.75 = euphoria.
| NUPL | Phase | Score |
|------|-------|-------|
| < 0 | Capitulation | 10 |
| 0-0.25 | Hope/Fear | 7 |
| 0.25-0.5 | Optimism | 4 |
| 0.5-0.75 | Belief/Greed | 1 |
| > 0.75 | Euphoria | 0 |
#### 9. LTH Realized Price vs Spot (source: LookIntoBitcoin)
Long-Term Holder Realized Price = average cost basis of coins held >155 days. When spot price drops below this, even diamond hands are underwater — extreme value.
| Position | Score |
|----------|-------|
| Price below LTH RP | 10 |
| 0-20% above | 6 |
| 20-50% above | 3 |
| > 50% above | 1 |
#### 10. Hash Ribbons / Miner Capitulation (source: LookIntoBitcoin)
When miners capitulate (hash rate declining), it signals maximum pain. The recovery signal (hash rate resuming growth) has been a reliable buy signal.
| Signal | Score |
|--------|-------|
| Active buy signal | 10 |
| Recent recovery | 6 |
| Normal | 3 |
| Miner euphoria | 0 |
### Composite Score
```
Total Score = Sum of all individual metric scores (0-100)
```
| Score Range | Assessment | Action |
|-------------|------------|--------|
| 85-100 | Extreme Accumulation Zone | Strong buy — historically rare, ~4x per decade |
| 70-84 | Strong Accumulation | Buy — excellent long-term entry |
| 55-69 | Moderate Opportunity | Consider buying — decent entry |
| 40-54 | Neutral | Hold — not compelling either way |
| 25-39 | Caution | Reduce or wait — market heating up |
| 0-24 | Extreme Caution | Do NOT buy — historically the worst times |
## Backtest Engine
### Purpose
Reconstruct the composite score historically and compare against actual BTC forward returns to validate the scoring system's accuracy.
### Methodology
1. **Historical Reconstruction:** Using scraped historical data (2010-present), calculate what each metric's score would have been on every day
2. **Forward Returns:** For each historical day, calculate what BTC actually returned over the next 30, 90, 180, and 365 days
3. **Score Bracket Analysis:** Group days by score bracket and calculate average forward returns, win rates, max drawdowns
4. **Recency Weighting:** More recent cycles weighted higher because BTC's cycle-over-cycle returns diminish as it matures:
- 2022-present: 4x weight
- 2020-2021: 3x weight
- 2018-2019: 2x weight
- Before 2018: 1x weight
5. **Cycle-Separated Results:** Returns shown per cycle (Cycle 3: 2016-2019, Cycle 4: 2020-2023, Cycle 5: 2024+)
### Diminishing Returns Adjustment
Bitcoin's gains decrease every cycle. A score of 90 in 2018 led to different outcomes than a score of 90 in 2022:
- The backtest separates results by cycle
- Current expectations are based on the 2 most recent comparable cycles
- Adaptive thresholds recalculate based on rolling 2-year windows
## Architecture
```
/opt/apps/btc-ml-optimizer/
├── dashboard/
│ └── server.py # FastAPI + inline HTML/JS dashboard
├── scrapers/
│ ├── __init__.py
│ ├── lookintobitcoin.py # Playwright scraper for on-chain charts
│ ├── history_collector.py # Full historical data collection
│ ├── fear_greed.py # alternative.me Fear & Greed API
│ └── price.py # CoinGecko BTC price API
├── scoring/
│ ├── __init__.py
│ └── engine.py # Scoring logic and thresholds
├── backtesting/
│ ├── __init__.py
│ └── engine.py # Historical backtest calculations
├── data/
│ ├── cache.json # Current metric values (refreshed every 15min)
│ └── history.json # Full historical data (refreshed weekly)
├── config/
│ ├── thresholds.json # Configurable scoring thresholds
│ └── llm_settings.json # Optional LLM provider config for AI commentary
├── llm_client/
│ └── analyzer.py # Optional LLM integration for signal analysis
└── README.md
```
## Infrastructure
- **Server:** Main VPS (Hostinger), Tailscale IP 100.94.106.120
- **Port:** 3088
- **Process Manager:** pm2 (`btc-ml-optimizer`)
- **Dashboard URL:** http://100.94.106.120:3088
- **Backtest URL:** http://100.94.106.120:3088/backtest
- **Git Repo:** https://git.bizzle.lol/bizzle/btc-accumulation-monitor
## Dependencies
- Python 3.13
- FastAPI + uvicorn
- Playwright (Chromium, headless)
- requests
- No ML libraries required
- No paid API keys required

View File

@ -7,7 +7,7 @@
"use_volume": true,
"use_cycle": true,
"use_pca": false,
"pca_variance": 0.95,
"pca_variance": 0.85,
"use_scaler": true
},
"target": {
@ -33,31 +33,31 @@
]
},
"hyperparameters": {
"learning_rate": 0.01,
"max_depth": 4,
"n_estimators": 300,
"subsample": 0.8,
"colsample_bytree": 0.8,
"min_child_weight": 20,
"gamma": 0.3,
"reg_alpha": 0.5,
"reg_lambda": 3.0,
"lstm_hidden_size": 128,
"learning_rate": 0.005,
"max_depth": 5,
"n_estimators": 800,
"subsample": 0.7,
"colsample_bytree": 0.7,
"min_child_weight": 15,
"gamma": 0.5,
"reg_alpha": 0.3,
"reg_lambda": 1.0,
"lstm_hidden_size": 64,
"lstm_num_layers": 2,
"lstm_dropout": 0.3,
"lstm_epochs": 100,
"lstm_dropout": 0.4,
"lstm_epochs": 80,
"lstm_batch_size": 64,
"lstm_sequence_length": 30,
"lstm_patience": 10
"lstm_patience": 15
},
"strategy": {
"strong_buy_threshold": 65,
"good_buy_threshold": 55,
"poor_threshold": 35
"strong_buy_threshold": 55,
"good_buy_threshold": 35,
"poor_threshold": 20
},
"training": {
"rolling_window": true,
"rolling_train_size": 2500,
"rolling_train_size": 3500,
"rolling_test_size": 300,
"walk_forward_windows": 5,
"train_pct": 0.7,

4
data/score_history.jsonl Normal file
View File

@ -0,0 +1,4 @@
{"timestamp": "2026-03-20T22:26:50.475811+00:00", "composite_score": 32.5, "scored_count": 8, "metrics": {"fear_greed": {"score": 7, "value": 11}, "puell_multiple": {"score": 5, "value": 0.6602699608966011}, "mvrv_zscore": {"score": 5, "value": 0.5211180167687892}, "drawdown": {"score": 6, "value": 43.891180203045685}, "price_vs_200w_sma": {"score": null, "value": 0.0}, "reserve_risk": {"score": 0, "value": 69871.0}, "rhodl_ratio": {"score": 0, "value": 69871.0}, "nupl": {"score": 0, "value": 69871.0}, "lth_realized_price": {"score": null, "value": null}, "hash_ribbons": {"score": 3, "value": null}}}
{"timestamp": "2026-03-20T22:30:13.547149+00:00", "composite_score": 51.0, "scored_count": 10, "metrics": {"fear_greed": {"score": 7, "value": 11}, "puell_multiple": {"score": 5, "value": 0.6602699608966011}, "mvrv_zscore": {"score": 5, "value": 0.5211180167687892}, "drawdown": {"score": 6, "value": 43.910215736040605}, "price_vs_200w_sma": {"score": 3, "value": 58895.78086828114}, "reserve_risk": {"score": 10, "value": 0.0012985709697654493}, "rhodl_ratio": {"score": 4, "value": 1230.6243545314708}, "nupl": {"score": 7, "value": 0.22243290955405431}, "lth_realized_price": {"score": 1, "value": 43346.58756410873}, "hash_ribbons": {"score": 3, "value": null}}}
{"timestamp": "2026-03-20T22:46:34.952569+00:00", "composite_score": 51.0, "scored_count": 10, "metrics": {"fear_greed": {"score": 7, "value": 11}, "puell_multiple": {"score": 5, "value": 0.6602699608966011}, "mvrv_zscore": {"score": 5, "value": 0.5211180167687892}, "drawdown": {"score": 6, "value": 43.931630710659896}, "price_vs_200w_sma": {"score": 3, "value": 58895.78086828114}, "reserve_risk": {"score": 10, "value": 0.0012985709697654493}, "rhodl_ratio": {"score": 4, "value": 1230.6243545314708}, "nupl": {"score": 7, "value": 0.22243290955405431}, "lth_realized_price": {"score": 1, "value": 43346.58756410873}, "hash_ribbons": {"score": 3, "value": null}}}
{"timestamp": "2026-03-20T22:51:27.724327+00:00", "composite_score": 54.0, "scored_count": 10, "metrics": {"fear_greed": {"score": 7, "value": 11}, "puell_multiple": {"score": 5, "value": 0.6602699608966011}, "mvrv_zscore": {"score": 5, "value": 0.5211180167687892}, "drawdown": {"score": 6, "value": 43.94907994923858}, "price_vs_200w_sma": {"score": 6, "value": 58895.78086828114}, "reserve_risk": {"score": 10, "value": 0.0012985709697654493}, "rhodl_ratio": {"score": 4, "value": 1230.6243545314708}, "nupl": {"score": 7, "value": 0.22243290955405431}, "lth_realized_price": {"score": 1, "value": 43346.58756410873}, "hash_ribbons": {"score": 3, "value": null}}}