Files
nexus-quant-terminal/backend/app/routers/institutional.py
T
Kansaram 61e145a442 feat: launch NEXUS TERMINAL — Bloomberg-style FX options analytics platform
Full-stack forex options analytics terminal with Bloomberg-inspired UI.

Backend (FastAPI + Python):
- Garman-Kohlhagen options pricing engine with full Greeks
- Goldman Sachs gs-quant AI signals (RSI, MACD, Bollinger, Hurst, OU)
- Monte Carlo GBM simulation and volatility surface generation
- CFTC COT institutional positioning + Forex Factory economic calendar
- Live data proxy: OpenSky aircraft + USGS earthquakes (CORS-safe)
- Multi-leg strategy library (straddle, iron condor, butterfly, spreads)

Frontend (React 18 + Vite):
- NEXUS animated orbital logo (3-ring SVG) + canvas favicon animation
- Bloomberg terminal design: JetBrains Mono, color-mix() tokens
- 11 dashboard tabs: Greeks, Chart, AI Signals, 3D Surfaces, Breakeven,
  Scenarios, Monte Carlo, Institutional, Calendar, Live Map, Live Feeds
- Live World Map (react-leaflet): aircraft, earthquakes, weather radar
- Live Feeds: CoinGecko crypto top-12 + Windy.com global webcams
- Economic calendar with filters + institutional flow (CFTC COT)
- Animated landing page + session-based routing
- Fully responsive dark-only terminal design system

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 18:19:23 +05:30

271 lines
10 KiB
Python

"""
institutional.py — Free institutional flow data.
Sources:
1. CFTC TFF (Traders in Financial Futures) — Socrata API on publicreporting.cftc.gov
Dataset: gpe5-46if (no API key needed, weekly, official CFTC data)
Categories: Dealers, Asset Managers, Leveraged Money (hedge funds), Other, Non-reportable
2. Volume Profile — approximated from yfinance OHLCV daily bars
(distributes bar volume proportionally across the High-Low range)
"""
import numpy as np
import httpx
import urllib.parse
import yfinance as yf
from fastapi import APIRouter, HTTPException
router = APIRouter(prefix="/institutional", tags=["institutional"])
CFTC_BASE = "https://publicreporting.cftc.gov"
CFTC_DATASET = "gpe5-46if"
# Map forex pair → CFTC market name + yfinance symbol
COT_MAP = {
"EURUSD": {"cot": "EURO FX - CHICAGO MERCANTILE EXCHANGE", "sym": "EURUSD=X"},
"GBPUSD": {"cot": "BRITISH POUND STERLING - CHICAGO MERCANTILE EXCHANGE", "sym": "GBPUSD=X"},
"USDJPY": {"cot": "JAPANESE YEN - CHICAGO MERCANTILE EXCHANGE", "sym": "USDJPY=X"},
"AUDUSD": {"cot": "AUSTRALIAN DOLLAR - CHICAGO MERCANTILE EXCHANGE", "sym": "AUDUSD=X"},
"USDCAD": {"cot": "CANADIAN DOLLAR - CHICAGO MERCANTILE EXCHANGE", "sym": "USDCAD=X"},
"NZDUSD": {"cot": "NEW ZEALAND DOLLAR - CHICAGO MERCANTILE EXCHANGE", "sym": "NZDUSD=X"},
"USDCHF": {"cot": "SWISS FRANC - CHICAGO MERCANTILE EXCHANGE", "sym": "USDCHF=X"},
"EURJPY": {"cot": "EURO FX - CHICAGO MERCANTILE EXCHANGE", "sym": "EURJPY=X"},
"GBPJPY": {"cot": "BRITISH POUND STERLING - CHICAGO MERCANTILE EXCHANGE", "sym": "GBPJPY=X"},
"EURGBP": {"cot": "EURO FX - CHICAGO MERCANTILE EXCHANGE", "sym": "EURGBP=X"},
"XAUUSD": {"cot": "GOLD - COMMODITY EXCHANGE INC.", "sym": "GC=F"},
}
def _safe_int(v) -> int:
try: return int(v or 0)
except: return 0
def _safe_float(v) -> float:
try: return float(v or 0)
except: return 0.0
def _fetch_cot(market_name: str, weeks: int = 52) -> list[dict]:
"""Fetch TFF COT data from CFTC Socrata API (publicreporting.cftc.gov)."""
where = urllib.parse.quote(f"market_and_exchange_names='{market_name}'")
url = (
f"{CFTC_BASE}/resource/{CFTC_DATASET}.json"
f"?$where={where}"
f"&$order=report_date_as_yyyy_mm_dd+DESC"
f"&$limit={weeks}"
)
try:
resp = httpx.get(url, timeout=20, follow_redirects=True)
resp.raise_for_status()
return resp.json()
except Exception:
return []
def _parse_tff(records: list[dict]) -> dict:
"""Parse TFF records into structured signal data (newest-first records → oldest-first output)."""
if not records:
return {}
rows = list(reversed(records)) # oldest first for charting
dates = []
oi, dealer_net, assetmgr_net, levmoney_net, other_net = [], [], [], [], []
chg_oi, chg_am_long, chg_am_short, chg_lm_long, chg_lm_short = [], [], [], [], []
pct_am_long, pct_am_short, pct_lm_long, pct_lm_short = [], [], [], []
am_long_list, am_short_list, lm_long_list, lm_short_list = [], [], [], []
for r in rows:
d = (r.get("report_date_as_yyyy_mm_dd") or "")[:10]
if not d:
continue
dates.append(d)
aml = _safe_int(r.get("asset_mgr_positions_long"))
ams = _safe_int(r.get("asset_mgr_positions_short"))
lml = _safe_int(r.get("lev_money_positions_long"))
lms = _safe_int(r.get("lev_money_positions_short"))
dl = _safe_int(r.get("dealer_positions_long_all"))
ds = _safe_int(r.get("dealer_positions_short_all"))
ol = _safe_int(r.get("other_rept_positions_long"))
os_ = _safe_int(r.get("other_rept_positions_short"))
total_oi = _safe_int(r.get("open_interest_all"))
oi.append(total_oi)
dealer_net.append(dl - ds)
assetmgr_net.append(aml - ams)
levmoney_net.append(lml - lms)
other_net.append(ol - os_)
am_long_list.append(aml)
am_short_list.append(ams)
lm_long_list.append(lml)
lm_short_list.append(lms)
chg_oi.append(_safe_int(r.get("change_in_open_interest_all")))
chg_am_long.append(_safe_int(r.get("change_in_asset_mgr_long")))
chg_am_short.append(_safe_int(r.get("change_in_asset_mgr_short")))
chg_lm_long.append(_safe_int(r.get("change_in_lev_money_long")))
chg_lm_short.append(_safe_int(r.get("change_in_lev_money_short")))
pct_am_long.append(_safe_float(r.get("pct_of_oi_asset_mgr_long")))
pct_am_short.append(_safe_float(r.get("pct_of_oi_asset_mgr_short")))
pct_lm_long.append(_safe_float(r.get("pct_of_oi_lev_money_long")))
pct_lm_short.append(_safe_float(r.get("pct_of_oi_lev_money_short")))
if not dates:
return {}
def _cot_index(series):
lo, hi = min(series), max(series)
return round((series[-1] - lo) / max(hi - lo, 1) * 100, 1) if hi > lo else 50.0
am_idx = _cot_index(assetmgr_net)
lm_idx = _cot_index(levmoney_net)
def _bias(idx):
return "BULLISH" if idx >= 65 else ("BEARISH" if idx <= 35 else "NEUTRAL")
am_bias = _bias(am_idx)
lm_bias = _bias(lm_idx)
# Composite: weight asset managers 60%, leveraged money 40%
composite_idx = round(am_idx * 0.6 + lm_idx * 0.4, 1)
composite_bias = _bias(composite_idx)
wk_chg_am = (assetmgr_net[-1] - assetmgr_net[-2]) if len(assetmgr_net) >= 2 else 0
wk_chg_lm = (levmoney_net[-1] - levmoney_net[-2]) if len(levmoney_net) >= 2 else 0
return {
"dates": dates,
"open_interest": oi,
"change_oi": chg_oi,
# Asset Managers (institutional — real money)
"am_net": assetmgr_net,
"am_long": am_long_list,
"am_short": am_short_list,
"am_pct_long": pct_am_long,
"am_pct_short": pct_am_short,
"am_index": am_idx,
"am_bias": am_bias,
"am_current_net": assetmgr_net[-1],
"am_wk_change": wk_chg_am,
# Leveraged Money (hedge funds, CTAs)
"lm_net": levmoney_net,
"lm_long": lm_long_list,
"lm_short": lm_short_list,
"lm_pct_long": pct_lm_long,
"lm_pct_short": pct_lm_short,
"lm_index": lm_idx,
"lm_bias": lm_bias,
"lm_current_net": levmoney_net[-1],
"lm_wk_change": wk_chg_lm,
# Dealers
"dealer_net": dealer_net,
# Other
"other_net": other_net,
# Composite
"composite_index": composite_idx,
"composite_bias": composite_bias,
"weeks": len(dates),
"latest_date": dates[-1],
"source": "CFTC TFF — Traders in Financial Futures (Socrata API)",
}
def _volume_profile(sym: str, period: str = "3mo", buckets: int = 40) -> dict:
"""
Approximate volume profile from daily OHLCV.
Distributes each bar's volume uniformly across its High-Low range.
"""
try:
hist = yf.download(sym, period=period, interval="1d",
progress=False, auto_adjust=True)
if hist.empty:
return {}
if hasattr(hist.columns, "droplevel"):
try: hist.columns = hist.columns.droplevel(1)
except Exception: pass
highs = hist["High"].dropna().values.astype(float)
lows = hist["Low"].dropna().values.astype(float)
volumes = hist["Volume"].dropna().values.astype(float)
closes = hist["Close"].dropna().values.astype(float)
global_lo = float(np.min(lows))
global_hi = float(np.max(highs))
if global_hi <= global_lo:
return {}
bucket_size = (global_hi - global_lo) / buckets
vol_profile = np.zeros(buckets)
for i in range(len(highs)):
lo_b = int((lows[i] - global_lo) / bucket_size)
hi_b = int((highs[i] - global_lo) / bucket_size)
lo_b = max(0, min(lo_b, buckets - 1))
hi_b = max(0, min(hi_b, buckets - 1))
span = max(hi_b - lo_b + 1, 1)
vol_per_bucket = volumes[i] / span if volumes[i] > 0 else 0
vol_profile[lo_b:hi_b + 1] += vol_per_bucket
prices = [round(global_lo + (j + 0.5) * bucket_size, 5) for j in range(buckets)]
poc_idx = int(np.argmax(vol_profile))
poc = prices[poc_idx]
total_vol = float(np.sum(vol_profile))
va_target = total_vol * 0.70
lo_idx, hi_idx = poc_idx, poc_idx
va_vol = float(vol_profile[poc_idx])
while va_vol < va_target and (lo_idx > 0 or hi_idx < buckets - 1):
expand_lo = vol_profile[lo_idx - 1] if lo_idx > 0 else 0
expand_hi = vol_profile[hi_idx + 1] if hi_idx < buckets - 1 else 0
if expand_hi >= expand_lo:
hi_idx = min(hi_idx + 1, buckets - 1); va_vol += expand_hi
else:
lo_idx = max(lo_idx - 1, 0); va_vol += expand_lo
return {
"prices": prices,
"volumes": [round(float(v), 0) for v in vol_profile],
"poc": poc,
"vah": prices[hi_idx],
"val": prices[lo_idx],
"current_price": round(float(closes[-1]), 5),
"global_hi": round(global_hi, 5),
"global_lo": round(global_lo, 5),
"total_volume": round(total_vol, 0),
}
except Exception:
return {}
@router.get("/{pair}")
def get_institutional(pair: str, weeks: int = 26):
"""
Institutional flow data: CFTC TFF positioning + Volume Profile.
Data sources are 100% free — no API keys required.
"""
pair = pair.upper()
if pair not in COT_MAP:
raise HTTPException(404, f"No institutional data for '{pair}'.")
meta = COT_MAP[pair]
weeks = min(max(weeks, 4), 52)
raw = _fetch_cot(meta["cot"], weeks)
cot = _parse_tff(raw)
vp = _volume_profile(meta["sym"])
if not cot and not vp:
raise HTTPException(503, "CFTC API and volume profile both unavailable.")
return {
"pair": pair,
"cot": cot,
"volume_profile": vp,
"data_sources": [
"CFTC TFF (Traders in Financial Futures) — publicreporting.cftc.gov, weekly, free",
"Volume Profile — yfinance OHLCV daily, 3 months",
],
}