mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
260 lines
9.9 KiB
Python
Executable file
260 lines
9.9 KiB
Python
Executable file
#!/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()
|