Spaces:
Running
Running
Download engine.py from SZLHOLDINGS/finance: direct link, hf CLI and curl.
- Browser
- Download file 7.82 kB
-
https://huggingface.co/spaces/SZLHOLDINGS/finance/resolve/main/engine.py
- Command line
-
hf download hf://spaces/SZLHOLDINGS/finance/engine.py
-
curl -L -o engine.py https://huggingface.co/spaces/SZLHOLDINGS/finance/resolve/main/engine.py
7.82 kB
| """ | |
| SZL Finance Engine v2 — sovereign market-intelligence core. | |
| Doctrine v11: | |
| - Five-state honesty contract: MEASURED / BLOCKED / INVALID / FAILED / PROMOTED | |
| - Every advisory output is advisory_only, paper_only, not_financial_advice | |
| - Fail-closed: missing or malformed input -> BLOCKED / INVALID, never fabricated | |
| - Pure stdlib math; deterministic; every input digest-able for receipts | |
| """ | |
| from __future__ import annotations | |
| import hashlib | |
| import json | |
| import math | |
| from typing import Any | |
| SCHEMA_VERSION = "szl.finance-engine/v2" | |
| STATE_MEASURED = "MEASURED" | |
| STATE_BLOCKED = "BLOCKED" | |
| STATE_INVALID = "INVALID" | |
| STATE_FAILED = "FAILED" | |
| STATE_PROMOTED = "PROMOTED" | |
| TRUTH_LABELS = { | |
| "advisory_only": True, | |
| "paper_only": True, | |
| "not_financial_advice": True, | |
| } | |
| class EngineBlocked(Exception): | |
| """Raised when a computation cannot proceed honestly.""" | |
| def canonical_digest(obj: Any) -> str: | |
| blob = json.dumps(obj, sort_keys=True, separators=(",", ":")).encode("utf-8") | |
| return hashlib.sha256(blob).hexdigest() | |
| def _require_series(values, min_len, name): | |
| if not isinstance(values, (list, tuple)): | |
| raise EngineBlocked(f"{name}: not a series") | |
| if len(values) < min_len: | |
| raise EngineBlocked(f"{name}: need >= {min_len} points, got {len(values)}") | |
| out = [] | |
| for v in values: | |
| try: | |
| f = float(v) | |
| except (TypeError, ValueError): | |
| raise EngineBlocked(f"{name}: non-numeric point {v!r}") | |
| if not math.isfinite(f): | |
| raise EngineBlocked(f"{name}: non-finite point") | |
| out.append(f) | |
| return out | |
| def sma(values, window: int): | |
| s = _require_series(values, window, "sma") | |
| return sum(s[-window:]) / window | |
| def ema_series(values, window: int): | |
| s = _require_series(values, window, "ema") | |
| k = 2.0 / (window + 1) | |
| out = [sum(s[:window]) / window] | |
| for v in s[window:]: | |
| out.append(v * k + out[-1] * (1 - k)) | |
| return out | |
| def rsi(closes, period: int = 14) -> float: | |
| s = _require_series(closes, period + 1, "rsi") | |
| deltas = [s[i] - s[i - 1] for i in range(1, len(s))] | |
| gains = [max(d, 0.0) for d in deltas] | |
| losses = [max(-d, 0.0) for d in deltas] | |
| avg_gain = sum(gains[:period]) / period | |
| avg_loss = sum(losses[:period]) / period | |
| for g, l in zip(gains[period:], losses[period:]): | |
| avg_gain = (avg_gain * (period - 1) + g) / period | |
| avg_loss = (avg_loss * (period - 1) + l) / period | |
| if avg_loss == 0.0: | |
| return 100.0 | |
| rs = avg_gain / avg_loss | |
| return 100.0 - 100.0 / (1.0 + rs) | |
| def macd(closes, fast: int = 12, slow: int = 26, signal_p: int = 9): | |
| s = _require_series(closes, slow + signal_p, "macd") | |
| ef = ema_series(s, fast) | |
| es = ema_series(s, slow) | |
| macd_line = [f - sl for f, sl in zip(ef[-len(es):], es)] | |
| if len(macd_line) < signal_p: | |
| raise EngineBlocked("macd: insufficient history for signal line") | |
| k = 2.0 / (signal_p + 1) | |
| sig = [sum(macd_line[:signal_p]) / signal_p] | |
| for v in macd_line[signal_p:]: | |
| sig.append(v * k + sig[-1] * (1 - k)) | |
| return {"macd": macd_line[-1], "signal": sig[-1], "histogram": macd_line[-1] - sig[-1]} | |
| def bollinger_z(closes, window: int = 20) -> float: | |
| s = _require_series(closes, window, "bollinger") | |
| w = s[-window:] | |
| mu = sum(w) / window | |
| var = sum((x - mu) ** 2 for x in w) / window | |
| sd = math.sqrt(var) | |
| if sd == 0.0: | |
| return 0.0 | |
| return (s[-1] - mu) / sd | |
| def returns_series(closes): | |
| s = _require_series(closes, 2, "returns") | |
| out = [] | |
| for i in range(1, len(s)): | |
| if s[i - 1] == 0.0: | |
| raise EngineBlocked("returns: zero price") | |
| out.append(s[i] / s[i - 1] - 1.0) | |
| return out | |
| def volatility(closes, periods_per_year: int = 252) -> float: | |
| r = returns_series(closes) | |
| if len(r) < 2: | |
| raise EngineBlocked("volatility: need >= 2 returns") | |
| mu = sum(r) / len(r) | |
| var = sum((x - mu) ** 2 for x in r) / (len(r) - 1) | |
| return math.sqrt(var) * math.sqrt(periods_per_year) | |
| def sharpe(closes, risk_free_daily: float = 0.0, periods_per_year: int = 252) -> float: | |
| r = returns_series(closes) | |
| if len(r) < 2: | |
| raise EngineBlocked("sharpe: need >= 2 returns") | |
| excess = [x - risk_free_daily for x in r] | |
| mu = sum(excess) / len(excess) | |
| var = sum((x - mu) ** 2 for x in excess) / (len(excess) - 1) | |
| sd = math.sqrt(var) | |
| if sd == 0.0: | |
| return 0.0 | |
| return (mu / sd) * math.sqrt(periods_per_year) | |
| def max_drawdown(closes) -> float: | |
| s = _require_series(closes, 2, "max_drawdown") | |
| peak = s[0] | |
| worst = 0.0 | |
| for v in s: | |
| peak = max(peak, v) | |
| dd = (v - peak) / peak | |
| worst = min(worst, dd) | |
| return worst | |
| def beta(closes, benchmark_closes) -> float: | |
| r = returns_series(closes) | |
| b = returns_series(benchmark_closes) | |
| n = min(len(r), len(b)) | |
| if n < 2: | |
| raise EngineBlocked("beta: insufficient paired returns") | |
| r, b = r[-n:], b[-n:] | |
| mr = sum(r) / n | |
| mb = sum(b) / n | |
| cov = sum((x - mr) * (y - mb) for x, y in zip(r, b)) / (n - 1) | |
| vb = sum((y - mb) ** 2 for y in b) / (n - 1) | |
| if vb == 0.0: | |
| raise EngineBlocked("beta: zero-variance benchmark") | |
| return cov / vb | |
| def signal_suite(closes, symbol: str, data_origin: str): | |
| """Full indicator stack -> advisory verdict. Fails closed.""" | |
| s = _require_series(closes, 35, "signal_suite") | |
| ind = { | |
| "sma_fast": sma(s, 10), | |
| "sma_slow": sma(s, 30), | |
| "rsi_14": rsi(s, 14), | |
| "macd": macd(s, 12, 26, 9), | |
| "bollinger_z_20": bollinger_z(s, 20), | |
| } | |
| votes = 0 | |
| votes += 1 if ind["sma_fast"] > ind["sma_slow"] else -1 | |
| votes += 1 if ind["macd"]["histogram"] > 0 else -1 | |
| r = ind["rsi_14"] | |
| votes += 1 if r < 30 else (-1 if r > 70 else 0) | |
| z = ind["bollinger_z_20"] | |
| votes += 1 if z < -2 else (-1 if z > 2 else 0) | |
| verdict = "BULLISH" if votes >= 2 else ("BEARISH" if votes <= -2 else "NEUTRAL") | |
| return { | |
| "schema": SCHEMA_VERSION, | |
| "state": STATE_MEASURED, | |
| "symbol": symbol.upper(), | |
| "data_origin": data_origin, | |
| "series_len": len(s), | |
| "input_digest": canonical_digest(s), | |
| "indicators": ind, | |
| "verdict": verdict, | |
| "vote_score": votes, | |
| **TRUTH_LABELS, | |
| } | |
| def portfolio_report(holdings, periods_per_year: int = 252): | |
| """holdings: {symbol: closes(list)} — per-asset analytics over measured series.""" | |
| if not holdings or not isinstance(holdings, dict): | |
| raise EngineBlocked("portfolio: empty holdings") | |
| reports = {} | |
| for sym, closes in holdings.items(): | |
| reports[sym.upper()] = { | |
| "volatility": volatility(closes, periods_per_year), | |
| "sharpe": sharpe(closes, periods_per_year=periods_per_year), | |
| "max_drawdown": max_drawdown(closes), | |
| "input_digest": canonical_digest(_require_series(closes, 2, sym)), | |
| "series_len": len(closes), | |
| } | |
| keys = sorted(reports) | |
| port = { | |
| "schema": SCHEMA_VERSION, | |
| "state": STATE_MEASURED, | |
| "constituents": keys, | |
| "per_asset": reports, | |
| "aggregate": { | |
| "mean_volatility": sum(reports[k]["volatility"] for k in keys) / len(keys), | |
| "mean_sharpe": sum(reports[k]["sharpe"] for k in keys) / len(keys), | |
| "worst_drawdown": min(reports[k]["max_drawdown"] for k in keys), | |
| }, | |
| **TRUTH_LABELS, | |
| } | |
| port["report_digest"] = canonical_digest({k: v for k, v in port.items() if k != "report_digest"}) | |
| return port | |