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:
parent
5b3b3811ec
commit
86168a3607
11
.gitignore
vendored
11
.gitignore
vendored
@ -1,4 +1,9 @@
|
|||||||
scoring/__pycache__/
|
|
||||||
scrapers/__pycache__/
|
|
||||||
__pycache__/
|
__pycache__/
|
||||||
data/
|
*.pyc
|
||||||
|
data/cache.json
|
||||||
|
data/history.json
|
||||||
|
config/llm_settings.json
|
||||||
|
results/
|
||||||
|
*.log
|
||||||
|
.env
|
||||||
|
node_modules/
|
||||||
|
|||||||
262
ARCHITECTURE.md
Normal file
262
ARCHITECTURE.md
Normal 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
|
||||||
@ -7,7 +7,7 @@
|
|||||||
"use_volume": true,
|
"use_volume": true,
|
||||||
"use_cycle": true,
|
"use_cycle": true,
|
||||||
"use_pca": false,
|
"use_pca": false,
|
||||||
"pca_variance": 0.95,
|
"pca_variance": 0.85,
|
||||||
"use_scaler": true
|
"use_scaler": true
|
||||||
},
|
},
|
||||||
"target": {
|
"target": {
|
||||||
@ -33,31 +33,31 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hyperparameters": {
|
"hyperparameters": {
|
||||||
"learning_rate": 0.01,
|
"learning_rate": 0.005,
|
||||||
"max_depth": 4,
|
"max_depth": 5,
|
||||||
"n_estimators": 300,
|
"n_estimators": 800,
|
||||||
"subsample": 0.8,
|
"subsample": 0.7,
|
||||||
"colsample_bytree": 0.8,
|
"colsample_bytree": 0.7,
|
||||||
"min_child_weight": 20,
|
"min_child_weight": 15,
|
||||||
"gamma": 0.3,
|
"gamma": 0.5,
|
||||||
"reg_alpha": 0.5,
|
"reg_alpha": 0.3,
|
||||||
"reg_lambda": 3.0,
|
"reg_lambda": 1.0,
|
||||||
"lstm_hidden_size": 128,
|
"lstm_hidden_size": 64,
|
||||||
"lstm_num_layers": 2,
|
"lstm_num_layers": 2,
|
||||||
"lstm_dropout": 0.3,
|
"lstm_dropout": 0.4,
|
||||||
"lstm_epochs": 100,
|
"lstm_epochs": 80,
|
||||||
"lstm_batch_size": 64,
|
"lstm_batch_size": 64,
|
||||||
"lstm_sequence_length": 30,
|
"lstm_sequence_length": 30,
|
||||||
"lstm_patience": 10
|
"lstm_patience": 15
|
||||||
},
|
},
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"strong_buy_threshold": 65,
|
"strong_buy_threshold": 55,
|
||||||
"good_buy_threshold": 55,
|
"good_buy_threshold": 35,
|
||||||
"poor_threshold": 35
|
"poor_threshold": 20
|
||||||
},
|
},
|
||||||
"training": {
|
"training": {
|
||||||
"rolling_window": true,
|
"rolling_window": true,
|
||||||
"rolling_train_size": 2500,
|
"rolling_train_size": 3500,
|
||||||
"rolling_test_size": 300,
|
"rolling_test_size": 300,
|
||||||
"walk_forward_windows": 5,
|
"walk_forward_windows": 5,
|
||||||
"train_pct": 0.7,
|
"train_pct": 0.7,
|
||||||
|
|||||||
4
data/score_history.jsonl
Normal file
4
data/score_history.jsonl
Normal 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}}}
|
||||||
Loading…
x
Reference in New Issue
Block a user