Function bodies 642 total
Separator function · typescript · L8-L26 (19 LOC)components/ui/separator.tsx
function Separator({
className,
orientation = "horizontal",
decorative = true,
...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
return (
<SeparatorPrimitive.Root
data-slot="separator"
decorative={decorative}
orientation={orientation}
className={cn(
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
className
)}
{...props}
/>
)
}UserMenu function · typescript · L8-L100 (93 LOC)components/UserMenu.tsx
export default function UserMenu() {
const { data: session, status } = useSession();
const [open, setOpen] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
useEffect(() => {
function handleClickOutside(e: MouseEvent) {
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
setOpen(false);
}
}
if (open) {
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}
}, [open]);
if (status === 'loading') {
return (
<div className="w-8 h-8 rounded-full bg-slate-100 dark:bg-zinc-800 flex items-center justify-center">
<Loader2 className="w-3.5 h-3.5 text-slate-400 dark:text-zinc-500 animate-spin" />
</div>
);
}
if (!session?.user) {
return null;
}
const initials = (session.user.name || session.user.email || '?')
.split(' ')
.map((w) => w[0])
.join('')
.slice(0, 2)
handleClickOutside function · typescript · L14-L18 (5 LOC)components/UserMenu.tsx
function handleClickOutside(e: MouseEvent) {
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
setOpen(false);
}
}VolumeOIChart function · typescript · L53-L363 (311 LOC)components/VolumeOIChart.tsx
export default function VolumeOIChart({
data: initialData,
symbol = 'GC',
displayName = 'Gold',
color = '#fbbf24'
}: VolumeOIChartProps) {
const { theme } = useTheme();
const isDark = theme === 'dark';
const [metric, setMetric] = useState<'volume' | 'oi' | 'both'>('both');
const [data, setData] = useState<HistoricalDataPoint[]>(initialData || sampleData);
const [isLoading, setIsLoading] = useState(!initialData);
const chartRef = useRef<HTMLDivElement>(null);
// Fetch historical data from API if no data provided
useEffect(() => {
if (initialData) {
setData(initialData);
setIsLoading(false);
return;
}
const fetchHistory = async () => {
try {
const response = await fetch(`/api/bulletin/history?symbol=${symbol}&days=30`);
if (response.ok) {
const result = await response.json();
if (result.history && result.history.length > 0) {
setData(result.history);
}
}
get_db_connection function · python · L41-L61 (21 LOC)forecast.py
def get_db_connection(retries: int = 3, delay: float = 2.0):
"""Get a psycopg2 connection from DATABASE_URL with retry logic for Neon serverless."""
import time
url = os.environ.get("DATABASE_URL_UNPOOLED") or os.environ.get("DATABASE_URL")
if not url:
raise RuntimeError("DATABASE_URL not set")
is_pooled = "-pooler" in (url or "")
for attempt in range(1, retries + 1):
try:
kwargs = dict(connect_timeout=10)
if not is_pooled:
kwargs["options"] = "-c statement_timeout=30000"
conn = psycopg2.connect(url, **kwargs)
return conn
except psycopg2.OperationalError as e:
if attempt < retries:
print(f" DB connection attempt {attempt}/{retries} failed, retrying in {delay}s...")
time.sleep(delay)
delay *= 2
else:
raise_fetch_yahoo_history function · python · L86-L140 (55 LOC)forecast.py
def _fetch_yahoo_history(symbol: str, days: int = 365) -> pd.DataFrame | None:
"""Fetch historical daily price data from Yahoo Finance as a fallback."""
import requests as req
yf_symbol = YAHOO_SYMBOLS.get(symbol)
if not yf_symbol:
return None
try:
# Yahoo Finance chart API: free, no key needed
period1 = int((datetime.now() - timedelta(days=days)).timestamp())
period2 = int(datetime.now().timestamp())
url = (
f"https://query1.finance.yahoo.com/v8/finance/chart/{yf_symbol}"
f"?period1={period1}&period2={period2}&interval=1d"
)
resp = req.get(url, headers={"User-Agent": "Mozilla/5.0"}, timeout=15)
if resp.status_code != 200:
return None
data = resp.json()
result = data.get("chart", {}).get("result", [])
if not result:
return None
timestamps = result[0].get("timestamp", [])
indicators = result[0].get("indicators", {_fetch_yahoo_chart_price function · python · L143-L183 (41 LOC)forecast.py
def _fetch_yahoo_chart_price(ticker: str) -> float | None:
"""Fetch the latest price for any Yahoo Finance ticker.
Uses the chart API with 1-day range / 1-minute interval to get
the most recent trade price.
"""
import requests as req
try:
url = (
f"https://query1.finance.yahoo.com/v8/finance/chart/{ticker}"
f"?range=1d&interval=1m"
)
resp = req.get(url, headers={"User-Agent": "Mozilla/5.0"}, timeout=10)
if resp.status_code != 200:
return None
data = resp.json()
result = data.get("chart", {}).get("result", [])
if not result:
return None
meta = result[0].get("meta", {})
price = meta.get("regularMarketPrice")
if price and float(price) > 0:
return float(price)
# Fallback: last close from intraday data
indicators = result[0].get("indicators", {}).get("quote", [{}])[0]
closes = indicators.get("close",Open data scored by Repobility · https://repobility.com
_fetch_yahoo_realtime_price function · python · L186-L209 (24 LOC)forecast.py
def _fetch_yahoo_realtime_price(symbol: str) -> float | None:
"""Fetch the latest real-time spot price for a COMEX metal symbol.
Strategy:
1. Try the physical-metal ETF (spot-tracking) and convert share price
to per-ounce spot using the oz-per-share factor.
2. Fall back to COMEX futures if the ETF is unavailable.
"""
# ── Try spot ETF first ──
etf_ticker, oz_per_share = SPOT_SOURCES.get(symbol, ("", 1.0))
if etf_ticker:
etf_price = _fetch_yahoo_chart_price(etf_ticker)
if etf_price and etf_price > 0:
spot = etf_price / oz_per_share
return round(spot, 2)
# ── Fallback to COMEX futures ──
futures_ticker = YAHOO_SYMBOLS.get(symbol)
if futures_ticker:
futures_price = _fetch_yahoo_chart_price(futures_ticker)
if futures_price and futures_price > 0:
return round(futures_price, 2)
return None_load_local_json function · python · L212-L221 (10 LOC)forecast.py
def _load_local_json(filename: str) -> dict | list | None:
"""Load a JSON file from the public directory."""
path = os.path.join(BASE_DIR, filename)
if os.path.exists(path):
try:
with open(path) as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
return None
return None_build_local_inventory_df function · python · L224-L245 (22 LOC)forecast.py
def _build_local_inventory_df(metal: str) -> pd.DataFrame:
"""Build inventory DataFrame from local data.json."""
data = _load_local_json("data.json")
if not data:
return pd.DataFrame()
metal_key = metal.lower()
metal_data = data.get(metal_key, {})
if not metal_data:
return pd.DataFrame()
rows = []
date_str = data.get("last_updated", "")
date = pd.to_datetime(date_str, errors="coerce") if date_str else pd.Timestamp.now()
reg = metal_data.get("registered", 0)
elig = metal_data.get("eligible", 0)
total = metal_data.get("total", reg + elig)
rows.append({"date": date, "registered": reg, "eligible": elig, "total": total})
df = pd.DataFrame(rows)
df["date"] = pd.to_datetime(df["date"])
df.set_index("date", inplace=True)
for c in ["registered", "eligible", "total"]:
df[c] = pd.to_numeric(df[c], errors="coerce")
return df_build_local_delivery_df function · python · L248-L271 (24 LOC)forecast.py
def _build_local_delivery_df(metal: str) -> pd.DataFrame:
"""Build delivery DataFrame from local delivery.json."""
data = _load_local_json("delivery.json")
if not data:
return pd.DataFrame()
metals_data = data.get("metals", {})
metal_data = metals_data.get(metal, {})
if not metal_data:
return pd.DataFrame()
date_str = data.get("report_date", "")
date = pd.to_datetime(date_str, errors="coerce") if date_str else pd.Timestamp.now()
rows = [{
"date": date,
"settlement_price": metal_data.get("settlement_price", 0),
"daily_issued": metal_data.get("daily_issued", 0),
"daily_stopped": metal_data.get("daily_stopped", 0),
"month_to_date": metal_data.get("month_to_date", 0),
}]
df = pd.DataFrame(rows)
df["date"] = pd.to_datetime(df["date"])
df.set_index("date", inplace=True)
for c in ["settlement_price", "daily_issued", "daily_stopped", "month_to_date"]:
df[c] = pd.to_numeric(df_build_local_oi_df function · python · L274-L297 (24 LOC)forecast.py
def _build_local_oi_df(metal: str) -> pd.DataFrame:
"""Build open interest DataFrame from local volume_summary.json."""
data = _load_local_json("volume_summary.json")
if not data:
return pd.DataFrame()
symbol = METALS[metal]["symbol"]
products = data.get("products", [])
for p in products:
if p.get("symbol") == symbol:
date_str = data.get("trade_date", "")
date = pd.to_datetime(date_str, errors="coerce") if date_str else pd.Timestamp.now()
rows = [{
"date": date,
"open_interest": p.get("open_interest", 0),
"oi_change": p.get("oi_change", 0),
"total_volume": p.get("total_volume", 0),
}]
df = pd.DataFrame(rows)
df["date"] = pd.to_datetime(df["date"])
df.set_index("date", inplace=True)
for c in ["open_interest", "oi_change", "total_volume"]:
df[c] = pd.to_numeric(df[c], errors=_build_local_risk_df function · python · L300-L326 (27 LOC)forecast.py
def _build_local_risk_df(metal: str) -> pd.DataFrame:
"""Build risk DataFrame from local analysis_summary.json."""
data = _load_local_json("analysis_summary.json")
if not data:
return pd.DataFrame()
metal_key = metal.lower()
metal_data = data.get(metal_key, {})
risk = metal_data.get("risk_score", {})
if not risk:
return pd.DataFrame()
date_str = data.get("generated_at", "")
date = pd.to_datetime(date_str, errors="coerce") if date_str else pd.Timestamp.now()
rows = [{
"date": date,
"composite_score": risk.get("composite_score", 50),
"coverage_risk": risk.get("coverage_risk", 50),
"paper_physical_risk": risk.get("paper_physical_risk", 50),
"inventory_trend_risk": risk.get("inventory_trend_risk", 50),
"delivery_velocity_risk": risk.get("delivery_velocity_risk", 50),
"market_activity_risk": risk.get("market_activity_risk", 50),
}]
df = pd.DataFrame(rows)
df["date"] = fetch_all_data function · python · L329-L500 (172 LOC)forecast.py
def fetch_all_data(metal: str, days: int = 365) -> dict:
"""Fetch all historical data for a single metal from the database,
falling back to Yahoo Finance + local JSON files on DB failure."""
symbol = METALS[metal]["symbol"]
bulletin_rows = []
inventory_rows = []
delivery_rows = []
oi_rows = []
pp_rows = []
risk_rows = []
db_ok = False
try:
conn = get_db_connection()
try:
cur = conn.cursor()
cur.execute("""
SELECT date, front_month_settle, total_volume, total_open_interest, total_oi_change
FROM bulletin_snapshots
WHERE symbol = %s AND date >= CURRENT_DATE - %s
ORDER BY date ASC
""", (symbol, days))
bulletin_rows = cur.fetchall()
cur.execute("""
SELECT report_date, registered, eligible, total
FROM metal_snapshots
WHERE metal = %s AND report_date >= CURRENTcompute_trend_signals function · python · L507-L644 (138 LOC)forecast.py
def compute_trend_signals(prices: pd.DataFrame) -> dict:
"""Compute SMA/EMA crossovers, Bollinger Bands, RSI, MACD."""
result = {
"score": 50,
"details": "Insufficient price data",
"indicators": {},
}
if prices.empty or "settle" not in prices.columns:
return result
s = prices["settle"].dropna()
if len(s) < 20:
return result
# ── Moving Averages ──────────────────────────────────────────────────
sma5 = s.rolling(5).mean()
sma10 = s.rolling(10).mean()
sma20 = s.rolling(20).mean()
sma50 = s.rolling(min(50, len(s))).mean()
ema12 = s.ewm(span=12, adjust=False).mean()
ema26 = s.ewm(span=26, adjust=False).mean()
latest = s.iloc[-1]
latest_sma5 = sma5.iloc[-1] if not np.isnan(sma5.iloc[-1]) else latest
latest_sma20 = sma20.iloc[-1] if not np.isnan(sma20.iloc[-1]) else latest
latest_sma50 = sma50.iloc[-1] if not np.isnan(sma50.iloc[-1]) else latest
# SMA crossover score: +1 for eaSource: Repobility analyzer · https://repobility.com
compute_physical_signals function · python · L651-L857 (207 LOC)forecast.py
def compute_physical_signals(
inventory: pd.DataFrame,
delivery: pd.DataFrame,
oi: pd.DataFrame,
pp: pd.DataFrame,
metal_config: dict,
) -> dict:
"""Compute physical market stress signals: inventory drawdown,
delivery acceleration, squeeze score, coverage erosion, eligible flow."""
result = {
"score": 50,
"details": "Insufficient physical data",
"signals": {},
}
signals = {}
score_components = []
# ── Inventory Drawdown Signal ────────────────────────────────────────
if not inventory.empty and "registered" in inventory.columns and len(inventory) >= 10:
reg = inventory["registered"].dropna()
if len(reg) >= 10:
# 5-day change
change_5d = reg.diff(5)
# Z-score vs 60-day distribution
roll_mean = change_5d.rolling(min(60, len(change_5d))).mean()
roll_std = change_5d.rolling(min(60, len(change_5d))).std()
latest_change = changerun_arima_forecast function · python · L864-L933 (70 LOC)forecast.py
def run_arima_forecast(prices: pd.DataFrame, horizons: list = None) -> dict:
"""Fit auto-ARIMA and produce point forecasts with confidence intervals."""
if horizons is None:
horizons = [5, 20]
result = {
"score": 50,
"details": "Insufficient data for ARIMA",
"forecasts": {},
}
if prices.empty or "settle" not in prices.columns:
return result
s = prices["settle"].dropna()
if len(s) < 30:
return result
current_price = float(s.iloc[-1])
try:
import pmdarima as pm
# Use log returns for stationarity
log_prices = np.log(s.values.astype(float))
model = pm.auto_arima(
log_prices,
start_p=0, max_p=3,
start_q=0, max_q=3,
d=None, # auto-detect differencing
max_d=2,
seasonal=False,
stepwise=True,
suppress_warnings=True,
error_action="ignore",
trace=False,
compute_market_activity function · python · L940-L1009 (70 LOC)forecast.py
def compute_market_activity(prices: pd.DataFrame, oi: pd.DataFrame) -> dict:
"""Analyze OI expansion, volume trends, speculative pressure."""
result = {
"score": 50,
"details": "Insufficient market data",
"metrics": {},
}
scores = []
# ── OI trend ─────────────────────────────────────────────────────────
oi_series = None
if not prices.empty and "open_interest" in prices.columns:
oi_series = prices["open_interest"].dropna()
elif not oi.empty and "open_interest" in oi.columns:
oi_series = oi["open_interest"].dropna()
if oi_series is not None and len(oi_series) >= 10:
oi_latest = oi_series.iloc[-1]
oi_10d_ago = oi_series.iloc[-10] if len(oi_series) >= 10 else oi_series.iloc[0]
oi_pct = ((oi_latest - oi_10d_ago) / oi_10d_ago * 100) if oi_10d_ago > 0 else 0
# Rising OI = new money = directional (bullish if price rising too)
oi_score = 50 + oi_pct * 2
oi_score = maxrun_correlation_analysis function · python · L1016-L1131 (116 LOC)forecast.py
def run_correlation_analysis(prices: pd.DataFrame, data: dict) -> dict:
"""Compute Pearson/Spearman correlations and Granger causality tests
between physical indicators and future price returns."""
correlations = {}
if prices.empty or "settle" not in prices.columns:
return correlations
s = prices["settle"].dropna()
if len(s) < 30:
return correlations
# Forward returns at various horizons
returns_5d = s.pct_change(5).shift(-5) # 5-day forward return
returns_10d = s.pct_change(10).shift(-10)
inventory = data.get("inventory", pd.DataFrame())
delivery = data.get("delivery", pd.DataFrame())
# ── Inventory change vs future returns ───────────────────────────────
if not inventory.empty and "registered" in inventory.columns:
inv_change = inventory["registered"].pct_change(5)
# Align on shared dates
combined = pd.DataFrame({
"inv_change": inv_change,
"fwd_return_5d": returnsdetect_anomalies function · python · L1138-L1184 (47 LOC)forecast.py
def detect_anomalies(data: dict) -> list:
"""Flag z-score anomalies across all metrics."""
anomalies = []
series_checks = []
# Inventory changes
inv = data.get("inventory", pd.DataFrame())
if not inv.empty and "registered" in inv.columns:
series_checks.append(("registered_inventory_change", inv["registered"].diff()))
# Delivery
deliv = data.get("delivery", pd.DataFrame())
if not deliv.empty and "daily_issued" in deliv.columns:
series_checks.append(("daily_deliveries", deliv["daily_issued"]))
# Volume
prices = data.get("prices", pd.DataFrame())
if not prices.empty and "volume" in prices.columns:
series_checks.append(("trading_volume", prices["volume"]))
# OI change
if not prices.empty and "oi_change" in prices.columns:
series_checks.append(("oi_change", prices["oi_change"]))
for name, series in series_checks:
s = series.dropna()
if len(s) < 20:
continue
rdetect_regime function · python · L1187-L1218 (32 LOC)forecast.py
def detect_regime(prices: pd.DataFrame) -> str:
"""Classify market regime based on volatility structure."""
if prices.empty or "settle" not in prices.columns:
return "UNKNOWN"
s = prices["settle"].dropna()
if len(s) < 30:
return "UNKNOWN"
returns = s.pct_change().dropna()
if len(returns) < 20:
return "UNKNOWN"
vol_short = returns.rolling(5).std().iloc[-1]
vol_long = returns.rolling(20).std().iloc[-1]
if np.isnan(vol_short) or np.isnan(vol_long) or vol_long == 0:
return "UNKNOWN"
vol_ratio = vol_short / vol_long
# ADX-like trend strength: absolute returns vs volatility
abs_return_20 = abs(float(s.iloc[-1] / s.iloc[-20] - 1)) if len(s) >= 20 else 0
# Annualized vol
ann_vol = float(vol_long) * np.sqrt(252)
if vol_ratio > 1.5:
return "VOLATILE"
elif abs_return_20 > ann_vol * 0.3:
return "TRENDING"
else:
return "RANGING"composite_forecast function · python · L1225-L1326 (102 LOC)forecast.py
def composite_forecast(
trend: dict,
physical: dict,
arima: dict,
market: dict,
) -> dict:
"""Combine all signal categories into a single directional forecast.
v1.2.0 tuning (Feb 20, 2026) based on 14-sample accuracy analysis:
- Physical stress: best predictor (67% bullish, 100% bearish) → raised to 0.45
- Trend momentum: informative when directional → kept at 0.25
- ARIMA model: inverted accuracy (43% when bullish) → halved to 0.10
- Market activity: no predictive value (50/50 both ways) → raised slightly
to 0.20 but dampened toward 50 to reduce noise from extreme readings
- Direction thresholds narrowed: 55/45 (was 60/40) to capture the 7 missed
BULLISH calls in the 55-60 composite band
- Confidence formula reweighted: physical agreement now weighted higher
"""
weights = {
"trend_momentum": 0.25,
"physical_stress": 0.45,
"arima_model": 0.10,
"market_activity": 0.20,
run_forecast_for_metal function · python · L1333-L1438 (106 LOC)forecast.py
def run_forecast_for_metal(metal: str) -> dict:
"""Run the full forecast pipeline for a single metal."""
print(f" Fetching data for {metal}...")
data = fetch_all_data(metal, days=365)
prices = data["prices"]
symbol = METALS[metal]["symbol"]
# Get the last DB settle price (used for ARIMA model training)
db_price = 0.0
if not prices.empty and "settle" in prices.columns:
s = prices["settle"].dropna()
if len(s) > 0:
db_price = float(s.iloc[-1])
# Fetch real-time FUTURES price from Yahoo Finance.
# Must use futures (GC=F etc.) — not spot ETFs — because the frontend
# compares forecast current_price against live futures from /api/futures.
print(f" Fetching real-time futures price from Yahoo Finance...")
futures_ticker = YAHOO_SYMBOLS.get(symbol)
realtime_price = _fetch_yahoo_chart_price(futures_ticker) if futures_ticker else None
if realtime_price and realtime_price > 0:
current_price = rouRepobility · severity-and-effort ranking · https://repobility.com
_py function · python · L1441-L1453 (13 LOC)forecast.py
def _py(val):
"""Convert numpy types to Python native for psycopg2."""
if val is None:
return None
if isinstance(val, (np.integer,)):
return int(val)
if isinstance(val, (np.floating,)):
return float(val)
if isinstance(val, np.ndarray):
return val.tolist()
if isinstance(val, float) and (np.isnan(val) or np.isinf(val)):
return None
return valupdate_forecast_history function · python · L1456-L1740 (285 LOC)forecast.py
def update_forecast_history(output: dict, json_default):
"""Write forecast snapshots to DB, evaluate past accuracy, track prices."""
today = datetime.now().strftime("%Y-%m-%d")
try:
conn = get_db_connection()
except Exception as e:
print(f" DB connection failed for forecast history, writing local JSON only: {e}")
_write_accuracy_json_backup(output)
return
try:
cur = conn.cursor()
# ── 1. Create tables if they don't exist ─────────────────────────
cur.execute("""
CREATE TABLE IF NOT EXISTS forecast_snapshots (
id SERIAL PRIMARY KEY,
metal VARCHAR(50) NOT NULL,
forecast_date DATE NOT NULL,
direction VARCHAR(20) NOT NULL,
confidence INTEGER NOT NULL DEFAULT 0,
composite_score DECIMAL(6, 2) NOT NULL DEFAULT 50,
price_at_forecast DECIMAL(15, 4) NOT NULL DEFAULT 0,
squeeze_prbackfill_tracking_from_settlements function · python · L1743-L1871 (129 LOC)forecast.py
def backfill_tracking_from_settlements(conn):
"""Backfill forecast_price_tracking and forecast_accuracy with official
COMEX settlement (close) prices from bulletin_snapshots.
For each forecast in forecast_snapshots, finds every settlement price
that occurred on or after the forecast date and upserts a tracking row.
Also re-evaluates forecast_accuracy using the settlement price closest
to the eval horizon (5 trading days) for maximum reliability.
"""
cur = conn.cursor()
symbol_for_metal = {m: cfg["symbol"] for m, cfg in METALS.items()}
# Get all forecasts (last 90 days to keep it bounded)
cur.execute("""
SELECT id, metal, forecast_date, direction, price_at_forecast
FROM forecast_snapshots
WHERE forecast_date >= CURRENT_DATE - 90
ORDER BY forecast_date DESC
""")
forecasts = cur.fetchall()
if not forecasts:
print(" No forecast snapshots found for settlement backfill")
cur.close()
_query_accuracy_from_db function · python · L1874-L1935 (62 LOC)forecast.py
def _query_accuracy_from_db(conn) -> dict | None:
"""Pull the full accuracy summary + history from the DB for JSON backup."""
try:
cur = conn.cursor()
cur.execute("""
SELECT metal, forecast_date, direction, price_at_forecast,
eval_date, eval_horizon_days, price_at_eval,
price_change_pct, correct
FROM forecast_accuracy
WHERE forecast_date >= CURRENT_DATE - 90
ORDER BY forecast_date DESC, metal
""")
rows = cur.fetchall()
cur.close()
if not rows:
return None
metals: dict = {}
total_correct = 0
total_evaluated = 0
history = []
for metal, fdate, direction, p_fore, e_date, horizon, p_eval, pct, correct in rows:
if metal not in metals:
metals[metal] = {"total_forecasts": 0, "correct": 0, "incorrect": 0, "pending": 0, "hit_rate": 0}
metals[metal]["total_forecas_write_accuracy_json_backup function · python · L1938-L1981 (44 LOC)forecast.py
def _write_accuracy_json_backup(output: dict, db_accuracy: dict | None = None):
"""Write a local JSON backup of forecast history for API fallback."""
history_path = os.path.join(BASE_DIR, "forecast_history.json")
today = datetime.now().strftime("%Y-%m-%d")
history = {"forecasts": [], "accuracy": {}}
if os.path.exists(history_path):
try:
with open(history_path) as f:
history = json.load(f)
except (json.JSONDecodeError, IOError):
history = {"forecasts": [], "accuracy": {}}
if "forecasts" not in history:
history["forecasts"] = []
existing_dates = {e["date"] for e in history["forecasts"]}
if today not in existing_dates:
entry = {"date": today, "generated_at": output["generated_at"], "calls": {}}
for metal, fc in output["metals"].items():
entry["calls"][metal] = {
"direction": fc.get("direction", "NEUTRAL"),
"confidence": fc.get("confmain function · python · L1984-L2064 (81 LOC)forecast.py
def main():
print("=" * 60)
print("COMEX METALS PRICE FORECASTING ENGINE")
print(f"Run time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 60)
output = {
"generated_at": datetime.now(tz=__import__('datetime').timezone.utc).isoformat(),
"model_version": "1.2.0",
"metals": {},
}
for metal in METALS:
print(f"\n{'─' * 40}")
print(f"Processing {metal}...")
try:
forecast = run_forecast_for_metal(metal)
output["metals"][metal] = forecast
except Exception as e:
print(f" ERROR forecasting {metal}: {e}")
output["metals"][metal] = {
"direction": "NEUTRAL",
"confidence": 0,
"composite_score": 50,
"current_price": 0,
"forecast_5d": None,
"forecast_20d": None,
"squeeze_probability": 0,
"regime": "UNKNOWN",
"sig_fetch_yahoo_chart_price function · python · L85-L117 (33 LOC)forecast_sd.py
def _fetch_yahoo_chart_price(ticker: str) -> float | None:
"""Fetch the latest price for a Yahoo Finance ticker via the chart API."""
import requests as req
try:
url = (
f"https://query1.finance.yahoo.com/v8/finance/chart/{ticker}"
f"?range=1d&interval=1m"
)
resp = req.get(url, headers={"User-Agent": "Mozilla/5.0"}, timeout=10)
if resp.status_code != 200:
return None
data = resp.json()
result = data.get("chart", {}).get("result", [])
if not result:
return None
meta = result[0].get("meta", {})
price = meta.get("regularMarketPrice")
if price and float(price) > 0:
return float(price)
indicators = result[0].get("indicators", {}).get("quote", [{}])[0]
closes = indicators.get("close", [])
if closes:
for c in reversed(closes):
if c is not None:
return float(c)
fetch_current_price function · python · L120-L153 (34 LOC)forecast_sd.py
def fetch_current_price(metal: str) -> float:
"""Get the current futures price for a metal. Falls back to DB settlement."""
symbol = METALS[metal]["symbol"]
ticker = YAHOO_FUTURES.get(symbol)
if ticker:
price = _fetch_yahoo_chart_price(ticker)
if price and price > 0:
return round(price, 2)
conn = get_db_connection()
try:
cur = conn.cursor()
cur.execute("""
SELECT settlement_price FROM open_interest_snapshots
WHERE symbol = %s AND settlement_price IS NOT NULL
ORDER BY report_date DESC LIMIT 1
""", (symbol,))
row = cur.fetchone()
if row and row[0]:
cur.close()
return round(float(row[0]), 2)
cur.execute("""
SELECT front_month_settle FROM bulletin_snapshots
WHERE symbol = %s AND front_month_settle IS NOT NULL
ORDER BY date DESC LIMIT 1
""", (symbol,))
row = cur.fetchone()
Same scanner, your repo: https://repobility.com — Repobility
get_db_connection function · python · L160-L178 (19 LOC)forecast_sd.py
def get_db_connection(retries: int = 3, delay: float = 2.0):
import time
url = os.environ.get("DATABASE_URL_UNPOOLED") or os.environ.get("DATABASE_URL")
if not url:
raise RuntimeError("DATABASE_URL not set")
is_pooled = "-pooler" in (url or "")
for attempt in range(1, retries + 1):
try:
kwargs = dict(connect_timeout=10)
if not is_pooled:
kwargs["options"] = "-c statement_timeout=30000"
return psycopg2.connect(url, **kwargs)
except psycopg2.OperationalError as e:
if attempt < retries:
print(f" DB attempt {attempt}/{retries} failed, retrying in {delay}s...")
time.sleep(delay)
delay *= 2
else:
raisefetch_sd_data function · python · L181-L253 (73 LOC)forecast_sd.py
def fetch_sd_data(metal: str, days: int = 365) -> dict:
"""Fetch inventory, delivery, paper/physical, and OI data from NeonDB."""
symbol = METALS[metal]["symbol"]
conn = get_db_connection()
try:
cur = conn.cursor()
cur.execute("""
SELECT report_date, registered, eligible, total
FROM metal_snapshots
WHERE metal = %s AND report_date >= CURRENT_DATE - %s
ORDER BY report_date ASC
""", (metal, days))
inv_rows = cur.fetchall()
cur.execute("""
SELECT report_date, daily_issued, daily_stopped, month_to_date
FROM delivery_snapshots
WHERE metal = %s AND report_date >= CURRENT_DATE - %s
ORDER BY report_date ASC
""", (metal, days))
del_rows = cur.fetchall()
cur.execute("""
SELECT report_date, paper_physical_ratio, registered_inventory, open_interest
FROM paper_physical_snapshots
WHERE mecompute_supply_signals function · python · L260-L349 (90 LOC)forecast_sd.py
def compute_supply_signals(inventory: pd.DataFrame, pp: pd.DataFrame) -> dict:
"""Compute inventory trend, eligible flow, and paper/physical signals."""
signals = {}
scores = []
# ── Inventory Trend (15%) ─────────────────────────────────────────────
if not inventory.empty and "registered" in inventory.columns:
reg = inventory["registered"].dropna()
if len(reg) >= 5:
window = min(30, len(reg) - 1)
pct_change = ((reg.iloc[-1] - reg.iloc[-window - 1]) / reg.iloc[-window - 1] * 100
if reg.iloc[-window - 1] > 0 else 0)
z_window = min(60, len(reg))
change_5d = reg.diff(5).dropna()
if len(change_5d) >= 5:
roll_mean = change_5d.rolling(z_window, min_periods=5).mean().iloc[-1]
roll_std = change_5d.rolling(z_window, min_periods=5).std().iloc[-1]
latest_change = change_5d.iloc[-1]
z = (latest_change - roll_meancompute_demand_signals function · python · L356-L463 (108 LOC)forecast_sd.py
def compute_demand_signals(
delivery: pd.DataFrame,
monthly_agg: list,
metal: str,
) -> dict:
"""Compute delivery velocity, MTD pace, and YTD momentum signals."""
signals = {}
scores = []
now = datetime.now()
current_month = MONTH_NAMES[now.month - 1]
baseline = BASELINE_2025.get(metal, {})
# ── Delivery Velocity (20%) ───────────────────────────────────────────
if not delivery.empty and "daily_issued" in delivery.columns:
issued = delivery["daily_issued"].dropna()
if len(issued) >= 5:
avg_20 = issued.rolling(window=20, min_periods=5).mean()
latest = issued.iloc[-1]
latest_avg = avg_20.iloc[-1] if not np.isnan(avg_20.iloc[-1]) else 1
accel = latest / latest_avg if latest_avg > 0 else 1.0
score = 50 + (accel - 1.0) * 30
score = max(0, min(100, score))
label = ("Surging" if accel > 1.5 else
"Elevated" if accel > 1.2 else
compute_coverage_pressure function · python · L470-L523 (54 LOC)forecast_sd.py
def compute_coverage_pressure(
inventory: pd.DataFrame,
delivery: pd.DataFrame,
metal_config: dict,
) -> dict:
"""Coverage erosion with level + 5d rate-of-change blend."""
signals = {}
scores = []
if (not inventory.empty and not delivery.empty
and "registered" in inventory.columns and "daily_issued" in delivery.columns):
reg = inventory["registered"].dropna()
issued = delivery["daily_issued"].dropna()
contract_size = metal_config["contract_size"]
if len(reg) >= 2 and len(issued) >= 5 and contract_size > 0:
rolling_avg = issued.rolling(window=20, min_periods=5).mean()
avg_aligned = rolling_avg.reindex(reg.index, method="ffill")
cov_series = (reg / (avg_aligned * contract_size)).replace(
[np.inf, -np.inf], np.nan
).dropna()
if len(cov_series) >= 2:
coverage_days = cov_series.iloc[-1]
level_score = max(0, min_fetch_settlement_history function · python · L539-L555 (17 LOC)forecast_sd.py
def _fetch_settlement_history(metal: str, days: int = 60) -> list[float]:
"""Return recent settlement prices (oldest→newest) from bulletin_snapshots."""
symbol = METALS[metal]["symbol"]
conn = get_db_connection()
try:
cur = conn.cursor()
cur.execute("""
SELECT front_month_settle
FROM bulletin_snapshots
WHERE symbol = %s AND front_month_settle IS NOT NULL
ORDER BY date ASC
""", (symbol,))
rows = cur.fetchall()
cur.close()
return [float(r[0]) for r in rows if r[0] and float(r[0]) > 0]
finally:
conn.close()compute_sd_price_projection function · python · L558-L627 (70 LOC)forecast_sd.py
def compute_sd_price_projection(
metal: str,
current_price: float,
composite_score: float,
confidence: int,
) -> dict:
"""
Generate 5-day and 20-day price projections from S&D fundamentals.
Unlike ARIMA (which extrapolates the time-series pattern), this method:
- Derives *direction* from the S&D composite score
- Sizes the *move magnitude* using the score's deviation from neutral
- Scales *range width* using realized volatility blended with long-run
defaults (to stabilise the estimate when settlement history is short)
- Narrows bands when S&D signal confidence is higher
"""
if current_price <= 0:
return {"forecast_5d": None, "forecast_20d": None}
default_annual = DEFAULT_ANNUAL_VOL.get(metal, 0.20)
default_daily = default_annual / np.sqrt(TRADING_DAYS_PER_YEAR)
prices = _fetch_settlement_history(metal)
# Deduplicate consecutive identical prices (weekends / holidays)
deduped = [prices[0]] composite_sd_forecast function · python · L634-L704 (71 LOC)forecast_sd.py
def composite_sd_forecast(
supply: dict,
demand: dict,
pressure: dict,
) -> dict:
"""Combine supply, demand, and pressure into a directional call."""
all_scores = (
supply.get("component_scores", [])
+ demand.get("component_scores", [])
+ pressure.get("component_scores", [])
)
if not all_scores:
return {
"direction": "NEUTRAL",
"confidence": 0,
"composite_score": 50,
"supply_score": 50,
"demand_score": 50,
"pressure_score": 50,
"signals": {},
"key_drivers": ["Insufficient data"],
}
total_weight = sum(w for _, _, w in all_scores)
composite = sum(s * w for _, s, w in all_scores) / total_weight if total_weight > 0 else 50
composite = round(max(0, min(100, composite)), 1)
# Wider neutral band for a model with fewer signals
if composite >= 57:
direction = "BULLISH"
elif composite <= 43:
Open data scored by Repobility · https://repobility.com
run_sd_forecast_for_metal function · python · L711-L751 (41 LOC)forecast_sd.py
def run_sd_forecast_for_metal(metal: str) -> dict:
"""Run the full S&D forecast pipeline for one metal."""
print(f" Fetching S&D data for {metal}...")
data = fetch_sd_data(metal, days=365)
print(f" Inventory rows: {len(data['inventory'])}, Delivery rows: {len(data['delivery'])}")
print(f" Computing supply signals...")
supply = compute_supply_signals(data["inventory"], data["pp"])
print(f" Computing demand signals...")
demand = compute_demand_signals(data["delivery"], data["monthly_agg"], metal)
print(f" Computing coverage pressure...")
pressure = compute_coverage_pressure(data["inventory"], data["delivery"], METALS[metal])
print(f" Building composite forecast...")
forecast = composite_sd_forecast(supply, demand, pressure)
print(f" Fetching current futures price...")
current_price = fetch_current_price(metal)
forecast["current_price"] = current_price
print(f" Current price: ${current_price:,.2f}"main function · python · L754-L816 (63 LOC)forecast_sd.py
def main():
print("=" * 60)
print("COMEX SUPPLY & DEMAND FORECASTER (sd-1.0.0)")
print(f"Run time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
print("=" * 60)
output = {
"generated_at": datetime.now(tz=timezone.utc).isoformat(),
"model_version": "sd-1.0.0",
"metals": {},
}
for metal in METALS:
print(f"\n{'─' * 40}")
try:
output["metals"][metal] = run_sd_forecast_for_metal(metal)
except Exception as e:
print(f" ERROR: {e}")
output["metals"][metal] = {
"direction": "NEUTRAL",
"confidence": 0,
"composite_score": 50,
"current_price": 0,
"forecast_5d": None,
"forecast_20d": None,
"supply_score": 50,
"demand_score": 50,
"pressure_score": 50,
"signals": {},
"key_drivers": [f"Forecast unavailable: {str(_py function · python · L819-L830 (12 LOC)forecast_sd.py
def _py(val):
"""Coerce numpy / pandas scalars to native Python types for psycopg2."""
if val is None:
return None
if isinstance(val, (np.integer,)):
return int(val)
if isinstance(val, (np.floating,)):
v = float(val)
return None if (np.isnan(v) or np.isinf(v)) else v
if isinstance(val, float) and (np.isnan(val) or np.isinf(val)):
return None
return valupdate_sd_forecast_history function · python · L833-L1005 (173 LOC)forecast_sd.py
def update_sd_forecast_history(output: dict, json_default):
"""Write S&D forecast snapshots to DB and evaluate past accuracy."""
today = datetime.now().strftime("%Y-%m-%d")
try:
conn = get_db_connection()
except Exception as e:
print(f" DB connection failed for S&D forecast history: {e}")
return
try:
cur = conn.cursor()
cur.execute("""
CREATE TABLE IF NOT EXISTS forecast_sd_snapshots (
id SERIAL PRIMARY KEY,
metal VARCHAR(50) NOT NULL,
forecast_date DATE NOT NULL,
direction VARCHAR(20) NOT NULL,
confidence INTEGER NOT NULL DEFAULT 0,
composite_score DECIMAL(6, 2) NOT NULL DEFAULT 50,
price_at_forecast DECIMAL(15, 4) NOT NULL DEFAULT 0,
supply_score DECIMAL(6, 2) DEFAULT 50,
demand_score DECIMAL(6, 2) DEFAULT 50,
pressure_score DECIMAL(6, 2) DEFAULT 50,
hashApiKey function · typescript · L22-L24 (3 LOC)lib/api-key.ts
export function hashApiKey(raw: string): string {
return createHash('sha256').update(raw).digest('hex');
}validateApiKey function · typescript · L33-L46 (14 LOC)lib/api-key.ts
export async function validateApiKey(request: Request): Promise<ValidatedKey | null> {
const raw = extractKey(request);
if (!raw) return null;
const hash = hashApiKey(raw);
const key = await getApiKeyByHash(hash);
if (!key) return null;
return {
keyId: key.id,
userId: key.user_id,
tier: key.tier as 'free' | 'paid',
};
}checkApiRateLimit function · typescript · L49-L52 (4 LOC)lib/api-key.ts
export async function checkApiRateLimit(
keyId: number,
tier: 'free' | 'paid',
): Promise<{ error: string; retryAfter?: number } | null> {trackUsage function · typescript · L76-L78 (3 LOC)lib/api-key.ts
export async function trackUsage(keyId: number): Promise<void> {
await incrementApiUsage(keyId);
}Source: Repobility analyzer · https://repobility.com
extractKey function · typescript · L82-L100 (19 LOC)lib/api-key.ts
function extractKey(request: Request): string | null {
// 1. Authorization header: Bearer hms_...
const authHeader = request.headers.get('authorization');
if (authHeader?.startsWith('Bearer ')) {
const token = authHeader.slice(7).trim();
if (token.startsWith(API_KEY_PREFIX)) return token;
}
// 2. Query parameter: ?apikey=hms_...
try {
const url = new URL(request.url);
const param = url.searchParams.get('apikey');
if (param?.startsWith(API_KEY_PREFIX)) return param;
} catch {
// ignore URL parse errors
}
return null;
}isAuthorized function · typescript · L7-L15 (9 LOC)lib/auth.ts
export function isAuthorized(request: Request): boolean {
const authHeader = request.headers.get('authorization');
const cronSecret = process.env.CRON_SECRET;
if (!cronSecret) return false;
const expected = `Bearer ${cronSecret}`;
// Constant-time comparison to prevent timing attacks
if (authHeader === null || authHeader.length !== expected.length) return false;
return timingSafeEqual(Buffer.from(authHeader), Buffer.from(expected));
}calculatePaperPhysicalRatio function · typescript · L75-L102 (28 LOC)lib/data.ts
export function calculatePaperPhysicalRatio(
openInterest: number,
contractSize: number,
registeredInventory: number
): PaperPhysicalData {
const openInterestOz = openInterest * contractSize;
const ratio = registeredInventory > 0 ? openInterestOz / registeredInventory : 0;
// Risk levels based on how many paper claims exist per unit of physical
let riskLevel: PaperPhysicalData['riskLevel'];
if (ratio <= 2) {
riskLevel = 'LOW';
} else if (ratio <= 5) {
riskLevel = 'MODERATE';
} else if (ratio <= 10) {
riskLevel = 'HIGH';
} else {
riskLevel = 'EXTREME';
}
return {
openInterest,
openInterestOz,
registeredInventory,
ratio,
riskLevel,
};
}