Research-Stack/5-Applications/scripts/crypto_rgflow_bulk.py

260 lines
9.9 KiB
Python
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Crypto RGFlow Bulk Analyzer — Major Commodities
Fetches historical price data for 20+ major cryptocurrencies and runs
RGFlow analysis (sigma_q, mu_q, lawfulness) on each. Produces a
comparative report of manifold stability across the crypto ecosystem.
Suitable for MEV bot regime classification and cross-asset arbitrage
detection.
"""
import json
import subprocess
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Tuple
import numpy as np
# ═══════════════════════════════════════════════════════════════════════════
# Asset registry: (ticker, yahoo_symbol, approx_launch_year, launch_month)
# ═══════════════════════════════════════════════════════════════════════════
ASSETS = [
("BTC", "BTC-USD", 2009, 1),
("ETH", "ETH-USD", 2015, 7),
("SOL", "SOL-USD", 2020, 4),
("ADA", "ADA-USD", 2017, 10),
("XRP", "XRP-USD", 2013, 8),
("DOT", "DOT-USD", 2020, 8),
("LINK", "LINK-USD", 2017, 9),
("LTC", "LTC-USD", 2011, 10),
("BCH", "BCH-USD", 2017, 8),
("AVAX", "AVAX-USD", 2020, 7),
("MATIC", "MATIC-USD", 2019, 4),
("UNI", "UNI-USD", 2020, 9),
("AAVE", "AAVE-USD", 2020, 10),
("ATOM", "ATOM-USD", 2019, 3),
("NEAR", "NEAR-USD", 2020, 10),
("ALGO", "ALGO-USD", 2019, 6),
("XTZ", "XTZ-USD", 2018, 6),
("XLM", "XLM-USD", 2014, 8),
("XMR", "XMR-USD", 2014, 4),
("DOGE", "DOGE-USD", 2013, 12),
]
# Q16.16 constants
Q16_ONE = 65536
Q16_HALF = 32768
Q16_ZERO35 = 22937
Q16_EIGHT = 524288
Q16_LAMBDA = 32768
OUTPUT_DIR = Path(__file__).parent.parent.parent / "data" / "crypto_rgflow"
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
# ═══════════════════════════════════════════════════════════════════════════
# Data fetching
# ═══════════════════════════════════════════════════════════════════════════
def fetch_prices(symbol: str, start_year: int, start_month: int) -> List[float]:
"""Fetch daily close prices from Yahoo Finance."""
start_ts = int(datetime(start_year, start_month, 1).timestamp())
end_ts = int(datetime.now().timestamp())
url = (
f"https://query1.finance.yahoo.com/v8/finance/chart/{symbol}"
f"?interval=1d&period1={start_ts}&period2={end_ts}"
)
cmd = ["curl", "-s", "-H", "User-Agent: Mozilla/5.0", "--max-time", "30", url]
try:
out = subprocess.check_output(cmd)
data = json.loads(out)
result = data.get("chart", {}).get("result", [None])[0]
if result is None:
return []
prices = result["indicators"]["quote"][0]["close"]
return [p for p in prices if p is not None]
except Exception as e:
print(f" [WARN] Failed to fetch {symbol}: {e}")
return []
# ═══════════════════════════════════════════════════════════════════════════
# Q16.16 + RGFlow (same logic as ethereum_rgflow_fetch.py)
# ═══════════════════════════════════════════════════════════════════════════
def prices_to_q1616(prices: List[float]) -> np.ndarray:
arr = np.array(prices, dtype=np.float64)
log_prices = np.log(arr)
min_log, max_log = np.min(log_prices), np.max(log_prices)
if max_log - min_log > 0:
scaled = ((log_prices - min_log) / (max_log - min_log) * 65535).astype(np.int64)
else:
scaled = np.zeros_like(log_prices, dtype=np.int64)
return scaled
def q16_div(a: int, b: int) -> int:
return (a << 16) // b if b != 0 else 0
def q16_mul(a: int, b: int) -> int:
return (a * b) >> 16
def q16_sqrt_approx(x: int) -> int:
norm = q16_div(x, Q16_ONE)
return q16_mul(norm, (49152 - q16_mul(Q16_HALF, norm)))
def log_returns_q16(prices: np.ndarray) -> np.ndarray:
if len(prices) < 2:
return np.array([], dtype=np.int64)
returns = []
for i in range(len(prices) - 1):
p0, p1 = prices[i], prices[i + 1]
if p0 > 0 and p1 > 0:
ratio = q16_div(p1, p0)
diff = ratio - Q16_ONE
log_approx = diff - q16_mul(Q16_HALF, q16_mul(diff, diff))
returns.append(log_approx)
return np.array(returns, dtype=np.int64)
def safe_std_q16(xs: np.ndarray) -> int:
if len(xs) <= 1:
return 0
mean = int(np.mean(xs))
diffs = xs - mean
var = int(np.mean(diffs * diffs))
return q16_sqrt_approx(var)
def compute_sigma_q16(returns: np.ndarray, i: int, window: int = 30) -> int:
if len(returns) < 2:
return Q16_ONE
ri = max(0, i - 1)
start = max(0, ri - window + 1)
wd = returns[start:ri + 1]
if len(wd) < 2:
return Q16_ONE
vol = safe_std_q16(wd)
mean = int(np.mean(wd))
abs_mean = abs(mean)
coherence = q16_div(abs_mean, vol + 1)
raw = Q16_ONE + q16_mul(Q16_ZERO35, coherence) - q16_mul(Q16_EIGHT, vol)
return max(16384, min(196608, raw))
def compute_mu_q16(returns: np.ndarray, i: int, window: int = 30) -> int:
if len(returns) < 2:
return 0
ri = max(0, i - 1)
start = max(0, ri - window + 1)
wd = returns[start:ri + 1]
if len(wd) < 2:
return 0
return int(np.mean(wd))
def is_lawful(sigma_q: int, mu_q: int) -> bool:
return sigma_q > (Q16_ONE + q16_mul(Q16_LAMBDA, mu_q))
def analyze_asset(prices: List[float], window: int = 30) -> Dict:
if len(prices) < window + 2:
return {"error": "insufficient data", "count": len(prices)}
prices_q16 = prices_to_q1616(prices)
returns = log_returns_q16(prices_q16)
results = []
for i in range(len(prices_q16)):
sigma_q = compute_sigma_q16(returns, i, window)
mu_q = compute_mu_q16(returns, i, window)
results.append((sigma_q, mu_q, is_lawful(sigma_q, mu_q)))
sigmas = [r[0] / Q16_ONE for r in results]
lawful = sum(1 for r in results if r[2])
collapse = sum(1 for s in sigmas if s < 1.0)
return {
"positions": len(results),
"lawful_count": lawful,
"lawful_pct": round(lawful / len(results) * 100, 2),
"collapse_count": collapse,
"avg_sigma": round(float(np.mean(sigmas)), 4),
"min_sigma": round(float(min(sigmas)), 4),
"max_sigma": round(float(max(sigmas)), 4),
"price_min": round(min(prices), 2),
"price_max": round(max(prices), 2),
"latest_price": round(prices[-1], 2),
}
# ═══════════════════════════════════════════════════════════════════════════
# Main
# ═══════════════════════════════════════════════════════════════════════════
def process_asset(ticker: str, symbol: str, year: int, month: int) -> Tuple[str, Dict]:
print(f"\n[{ticker}] Fetching {symbol}...")
prices = fetch_prices(symbol, year, month)
if not prices:
return ticker, {"error": "no data fetched"}
print(f"{len(prices)} price points | ${prices[0]:.2f} → ${prices[-1]:.2f}")
stats = analyze_asset(prices)
# Save per-asset detail
detail = {
"ticker": ticker,
"symbol": symbol,
"timestamp": datetime.now().isoformat(),
"prices": prices,
"statistics": stats,
}
with open(OUTPUT_DIR / f"{ticker.lower()}_rgflow.json", "w") as f:
json.dump(detail, f, indent=2)
return ticker, stats
def main():
print("=" * 70)
print("CRYPTO RGFLOW BULK ANALYZER")
print("=" * 70)
print(f"\nAnalyzing {len(ASSETS)} major crypto commodities...")
print(f"Output directory: {OUTPUT_DIR}")
all_results = {}
with ThreadPoolExecutor(max_workers=4) as executor:
futures = {
executor.submit(process_asset, t, s, y, m): t
for t, s, y, m in ASSETS
}
for future in as_completed(futures):
ticker, stats = future.result()
all_results[ticker] = stats
# Comparative summary
summary = []
for ticker in sorted(all_results.keys()):
s = all_results[ticker]
if "error" in s:
summary.append((ticker, 0.0, 0, 0, "ERROR"))
else:
summary.append((
ticker,
s["avg_sigma"],
s["lawful_pct"],
s["collapse_count"],
f"${s['latest_price']:,.2f}"
))
print("\n" + "=" * 70)
print("COMPARATIVE RGFLOW SUMMARY")
print("=" * 70)
print(f"{'Asset':>6s} {'Avg σ_q':>10s} {'Lawful%':>8s} {'Collapse':>9s} {'Price':>14s}")
print("-" * 55)
for ticker, avg_sigma, lawful_pct, collapse, price in sorted(summary, key=lambda x: -x[1]):
print(f"{ticker:>6s} {avg_sigma:>10.4f} {lawful_pct:>7.1f}% {collapse:>8d} {price:>14s}")
# Save master summary
master = {
"timestamp": datetime.now().isoformat(),
"assets_analyzed": len(ASSETS),
"results": all_results,
}
with open(OUTPUT_DIR / "master_summary.json", "w") as f:
json.dump(master, f, indent=2)
print(f"\n[OK] All results saved to: {OUTPUT_DIR}")
if __name__ == "__main__":
main()