import asyncio import core.backup # Restore DB from HF before database session is initialized! import logging from typing import List, Dict, Any, Optional from datetime import datetime, timezone, timedelta import random import os from fastapi import FastAPI, Request, Form, Depends, HTTPException, status from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from fastapi.middleware.cors import CORSMiddleware from database import SessionLocal, AlphaRecord from wq_client import BrainClient import config import main import requests from requests.auth import HTTPBasicAuth logger = logging.getLogger("App") app = FastAPI(title="QuantForge Dashboard") # Allow Chrome extensions (chrome-extension://*) and any origin to POST /login-cookie app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=False, allow_methods=["POST", "GET", "OPTIONS"], allow_headers=["*"], ) @app.on_event("startup") async def startup_event(): # Start background database backup loop core.backup.start_backup() # Automatically start the miner loop task on application startup logger.info("Application startup: auto-starting miner loop...") main.MINER_RUNNING = True main.MINER_TASK = asyncio.create_task(main.miner_loop()) os.makedirs("templates", exist_ok=True) app.mount("/static", StaticFiles(directory="templates"), name="static") templates = Jinja2Templates(directory="templates") import time # Simple mock session management for the demo SESSION_TOKEN = "quantforge_secure_session_token" def is_system_logged_in() -> bool: """ Checks if there is a valid WQ session cookie or credentials configured globally and successfully authenticated with the WorldQuant Brain API. Caches the validation status for 15 seconds to avoid flooding the API. """ cookie = os.getenv("WQ_SESSION_COOKIE") username = getattr(config, "WQ_USERNAME", None) password = getattr(config, "WQ_PASSWORD", None) if not username: return False now = time.time() # Cache key: WQ credentials and cookie hash to auto-invalidate if updated cache_key = (username, password, cookie) if ( hasattr(is_system_logged_in, "_cached_time") and now - is_system_logged_in._cached_time < 15 and getattr(is_system_logged_in, "_cached_key", None) == cache_key ): return is_system_logged_in._cached_val val = False if cookie: if "session=" in cookie or "t=" in cookie or ";" in cookie: cookie_header = cookie else: cookie_header = f"t={cookie}; session={cookie}" try: r = requests.get("https://api.worldquantbrain.com/users/self", headers={"Cookie": cookie_header}, timeout=5) val = (r.status_code == 200) except Exception: val = False elif password: try: r = requests.post("https://api.worldquantbrain.com/authentication", auth=HTTPBasicAuth(username, password), timeout=5) val = (r.status_code == 201) except Exception: val = False is_system_logged_in._cached_val = val is_system_logged_in._cached_time = now is_system_logged_in._cached_key = cache_key return val def is_authenticated(request: Request) -> bool: # Rule 3: If the session isn't logged in anywhere, everyone needs to log in if not is_system_logged_in(): return False # Rule 1: If anyone on the same wifi/ip address tries to open the link, # they can see everything without logging in, if the session is logged in somewhere. if is_home_wifi(request): return True # Rule 2: If another person tries with different ip, they must enter correct credentials token = request.cookies.get("session_token") return token == SESSION_TOKEN def get_client_ip(request: Request) -> str: x_forwarded_for = request.headers.get("x-forwarded-for") if x_forwarded_for: return x_forwarded_for.split(",")[0].strip() return request.client.host def is_home_wifi(request: Request) -> bool: client_ip = get_client_ip(request) whitelist = config.get_home_ip_whitelist() logger.debug(f"is_home_wifi check: client_ip={client_ip}, whitelist={whitelist}") if client_ip in ("127.0.0.1", "localhost", "::1", "testclient"): return True if not whitelist: return False return client_ip in whitelist def check_auth(request: Request): if not is_authenticated(request): raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated") def update_env_file(username, password): env_path = os.path.join(os.path.dirname(__file__), ".env") if os.path.exists(env_path): with open(env_path, "r") as f: lines = f.readlines() new_lines = [] has_username = False has_password = False for line in lines: if line.startswith("WQ_USERNAME="): new_lines.append(f"WQ_USERNAME={username}\n") has_username = True elif line.startswith("WQ_PASSWORD="): new_lines.append(f"WQ_PASSWORD={password}\n") has_password = True else: new_lines.append(line) if not has_username: new_lines.append(f"WQ_USERNAME={username}\n") if not has_password: new_lines.append(f"WQ_PASSWORD={password}\n") with open(env_path, "w") as f: f.writelines(new_lines) # Also persist to miner_settings.json for persistence across Hugging Face Space restarts try: config.save_wq_credentials(username=username, password=password) except Exception as e: logger.error(f"Failed to persist credentials to miner_settings.json: {e}") def update_env_cookie(username, cookie): env_path = os.path.join(os.path.dirname(__file__), ".env") if os.path.exists(env_path): with open(env_path, "r") as f: lines = f.readlines() new_lines = [] has_username = False has_cookie = False for line in lines: if line.startswith("WQ_USERNAME="): new_lines.append(f"WQ_USERNAME={username}\n") has_username = True elif line.startswith("WQ_SESSION_COOKIE="): new_lines.append(f"WQ_SESSION_COOKIE={cookie}\n") has_cookie = True else: new_lines.append(line) if not has_username: new_lines.append(f"WQ_USERNAME={username}\n") if not has_cookie: new_lines.append(f"WQ_SESSION_COOKIE={cookie}\n") with open(env_path, "w") as f: f.writelines(new_lines) # Also persist to miner_settings.json for persistence across Hugging Face Space restarts try: config.save_wq_credentials(username=username, session_cookie=cookie) except Exception as e: logger.error(f"Failed to persist session cookie to miner_settings.json: {e}") def test_wq_credentials(username, password): url = "https://api.worldquantbrain.com/authentication" try: response = requests.post(url, auth=HTTPBasicAuth(username, password), timeout=10) return response.status_code == 201 except Exception as e: logger.error(f"Error authenticating with WQ: {e}") return False @app.get("/health") async def health(): return {"status": "healthy"} @app.get("/", response_class=HTMLResponse) async def read_root(request: Request): authenticated = is_authenticated(request) client_ip = get_client_ip(request) return templates.TemplateResponse( request=request, name="index.html", context={ "authenticated": authenticated, "logged_in": authenticated, "client_ip": client_ip } ) @app.post("/login") async def login(request: Request, username: str = Form(...), password: str = Form(...)): # Validate locally against current memory state first if username == config.WQ_USERNAME and password == config.WQ_PASSWORD: try: client = BrainClient() except Exception as e: return JSONResponse(status_code=400, content={"message": f"WorldQuant Auth Failed: {e}"}) response = JSONResponse(content={"message": "Success"}) response.set_cookie( key="session_token", value=SESSION_TOKEN, httponly=True, max_age=2592000, # 30 days in seconds samesite="none", secure=True ) return response # If not matching or placeholder, test against WQ Brain API directly if test_wq_credentials(username, password): # Update .env update_env_file(username, password) # Update memory config.WQ_USERNAME = username config.WQ_PASSWORD = password response = JSONResponse(content={"message": "Success"}) response.set_cookie( key="session_token", value=SESSION_TOKEN, httponly=True, max_age=2592000, # 30 days in seconds samesite="none", secure=True ) return response return JSONResponse(status_code=401, content={"message": "Invalid Credentials"}) @app.post("/logout") async def logout(): response = JSONResponse(content={"message": "Logged out"}) response.delete_cookie("session_token", samesite="none", secure=True) return response @app.get("/logout") async def logout_get(): response = RedirectResponse(url="/", status_code=302) response.delete_cookie("session_token", samesite="none", secure=True) return response # JSON variant of login-cookie used by the dashboard JS fetch calls @app.post("/login-cookie") async def login_cookie_json(request: Request): """Accepts both JSON body {cookie: '...'} and Form data.""" content_type = request.headers.get("content-type", "") if "application/json" in content_type: body = await request.json() cookie = body.get("cookie", "").strip() username = body.get("username", config.WQ_USERNAME or "").strip() else: form = await request.form() cookie = str(form.get("cookie", "")).strip() username = str(form.get("username", config.WQ_USERNAME or "")).strip() if not cookie: return JSONResponse(status_code=400, content={"status": "error", "detail": "Cookie is required"}) if "session=" in cookie or "t=" in cookie or ";" in cookie: cookie_header = cookie else: cookie_header = f"t={cookie}; session={cookie}" headers = {"Cookie": cookie_header} url = "https://api.worldquantbrain.com/users/self" try: response = requests.get(url, headers=headers, timeout=10) if response.status_code == 200: update_env_cookie(username, cookie) config.WQ_USERNAME = username os.environ["WQ_SESSION_COOKIE"] = cookie # Signal the Telegram recovery loop to wake up and resume the miner try: main.COOKIE_UPDATED_EVENT.set() logger.info("COOKIE_UPDATED_EVENT set — waking up recovery loop.") except Exception as e: logger.warning(f"Could not set COOKIE_UPDATED_EVENT: {e}") resp = JSONResponse(content={"status": "success"}) resp.set_cookie(key="session_token", value=SESSION_TOKEN, httponly=True, max_age=2592000, samesite="none", secure=True) resp.headers["Access-Control-Allow-Origin"] = "*" return resp else: return JSONResponse(status_code=401, content={"status": "error", "detail": f"Session cookie validation failed (HTTP {response.status_code})"}) except Exception as e: logger.error(f"Error validating session cookie: {e}") return JSONResponse(status_code=400, content={"status": "error", "detail": f"Connection error: {e}"}) # Simple pause/resume for miner (used by dashboard toggle button) @app.post("/pause") async def pause_miner(request: Request): main.MINER_RUNNING = False return {"status": "paused"} @app.post("/resume") async def resume_miner(request: Request): if not main.MINER_RUNNING: main.MINER_RUNNING = True main.MINER_TASK = asyncio.create_task(main.miner_loop()) return {"status": "running"} # MINER CONTROL @app.post("/api/miner/{action}") async def control_miner(action: str, request: Request): check_auth(request) if action == "start": if main.MINER_RUNNING: return {"status": "Already running"} main.MINER_RUNNING = True main.MINER_TASK = asyncio.create_task(main.miner_loop()) return {"status": "Started"} elif action == "stop": main.MINER_RUNNING = False return {"status": "Stopped"} elif action == "status": return {"running": main.MINER_RUNNING} return {"error": "Invalid action"} # DATA APIs def categorize_alpha(expression: str) -> str: """Derive category from expression syntax.""" expr = expression.lower() if any(x in expr for x in ["volume", "vwap", "close", "open", "high", "low"]): return "PV" if any(x in expr for x in ["ebit", "capex", "sales", "assets", "debt", "income"]): return "Fundamental" if any(x in expr for x in ["sentiment", "news", "relevance"]): return "Sentiment" if any(x in expr for x in ["implied_vol", "put_call", "strike"]): return "Options" if any(x in expr for x in ["analyst", "eps", "recommendation"]): return "Analyst" return "Other" def generate_mock_pnl(final_return: float) -> List[float]: """Generates a synthetic timeseries for the line chart.""" days = 60 pnl = [0.0] drift = final_return / days volatility = 0.005 # 0.5% daily volatility mock for _ in range(days): step = drift + random.gauss(0, volatility) pnl.append(pnl[-1] + step) return pnl @app.get("/api/stats") async def get_stats(request: Request): from database.db import SessionLocal as SyncSessionLocal from database.models import Alpha as AlphaV2 from sqlalchemy import func # Try to get the active LLM model from main module active_llm = "Unknown" try: import main as main_module active_llm = getattr(main_module, "ACTIVE_LLM_MODEL", "Unknown") except Exception: pass # Legacy DB stats (AlphaRecord from old app.py DB) db = SessionLocal() try: legacy_generated = db.query(AlphaRecord).count() legacy_simulating = db.query(AlphaRecord).filter_by(status="SIMULATING").count() legacy_submitted = db.query(AlphaRecord).filter_by(status="SUBMITTED").count() finally: db.close() # V2 DB stats (Alpha from database.models, used by core pipeline) try: v2_db = SyncSessionLocal() try: total_generated = v2_db.query(AlphaV2).count() pending_gen = v2_db.query(AlphaV2).filter(AlphaV2.status == "PENDING_GEN").count() simulating = v2_db.query(AlphaV2).filter(AlphaV2.status == "SIMULATING").count() pending_submit = v2_db.query(AlphaV2).filter(AlphaV2.status == "PENDING_SUBMIT").count() submitted = v2_db.query(AlphaV2).filter(AlphaV2.status == "SUBMITTED").count() total_trashed = v2_db.query(AlphaV2).filter(AlphaV2.status == "TRASHED").count() # Trash reason breakdown trash_rows = ( v2_db.query(AlphaV2.trash_reason, func.count(AlphaV2.id)) .filter(AlphaV2.status == "TRASHED") .group_by(AlphaV2.trash_reason) .all() ) trash_breakdown = { "No Trades / Null Metrics": 0, "Low Sharpe / Fitness": 0, "High Correlation": 0, "Syntax / Schema Error": 0, } for reason, cnt in trash_rows: if reason in trash_breakdown: trash_breakdown[reason] = cnt else: trash_breakdown["Syntax / Schema Error"] += cnt # bucket unknowns # Avg metrics for passed alphas avg_row = v2_db.query( func.avg(AlphaV2.sharpe), func.avg(AlphaV2.fitness), func.avg(AlphaV2.turnover), ).filter(AlphaV2.status.in_(["PENDING_SUBMIT", "SUBMITTED"])).first() avg_sharpe = round(avg_row[0] or 0, 4) avg_fitness = round(avg_row[1] or 0, 4) avg_turnover = round((avg_row[2] or 0) * 100, 2) # as % total_evaluated = total_trashed + submitted + pending_submit success_rate = round((submitted + pending_submit) / total_evaluated * 100, 1) if total_evaluated > 0 else 0.0 finally: v2_db.close() except Exception as e: # Fallback if V2 DB not accessible total_generated = legacy_generated pending_gen = 0 simulating = legacy_simulating pending_submit = 0 submitted = legacy_submitted total_trashed = 0 trash_breakdown = {"No Trades / Null Metrics": 0, "Low Sharpe / Fitness": 0, "High Correlation": 0, "Syntax / Schema Error": 0} avg_sharpe = 0.0 avg_fitness = 0.0 avg_turnover = 0.0 success_rate = 0.0 return { "total_generated": total_generated, "pending_gen": pending_gen, "simulating": simulating, "pending_submit": pending_submit, "submitted": submitted, "total_trashed": total_trashed, "trash_breakdown": trash_breakdown, "avg_sharpe": avg_sharpe, "avg_fitness": avg_fitness, "avg_turnover": avg_turnover, "success_rate": success_rate, "active_llm_model": active_llm, } @app.get("/api/queue") async def get_queue(request: Request): db = SessionLocal() try: pending = db.query(AlphaRecord).filter( AlphaRecord.status.in_(["PENDING_CORRELATION", "PASSED_CORRELATION"]) ).all() authenticated = is_authenticated(request) return [ { "id": a.id, "expression": a.expression if authenticated else "[REDACTED (Home WiFi Only)]", "sharpe": a.sharpe, "fitness": a.fitness, "turnover": a.turnover, "returns": a.returns, "score": (a.fitness or 0.0) * (a.sharpe or 0.0), "status": a.status, "region": a.region, "universe": a.universe, "delay": a.delay, "decay": a.decay, "truncation": a.truncation, "neutralization": a.neutralization, "alpha_id": a.alpha_id } for a in pending ] finally: db.close() @app.get("/api/failed") async def get_failed(request: Request): db = SessionLocal() try: failed = db.query(AlphaRecord).filter(AlphaRecord.status.like("%FAIL%")).order_by(AlphaRecord.id.desc()).limit(10).all() authenticated = is_authenticated(request) return [ { "id": a.id, "expression": a.expression if authenticated else "[REDACTED (Home WiFi Only)]", "error_log": a.error_log if authenticated else "[REDACTED (Home WiFi Only)]" } for a in failed ] finally: db.close() @app.get("/api/logs") async def get_logs(request: Request): check_auth(request) log_file = config.DATA_DIR / "miner_production.log" if not log_file.exists(): return {"logs": "No logs yet."} try: with open(log_file, "r") as f: lines = f.readlines() return {"logs": "".join(lines[-100:])} except Exception as e: return {"logs": f"Error reading logs: {e}"} @app.get("/api/system_status") async def get_system_status(request: Request): import agent authenticated = is_authenticated(request) current_model = agent.CURRENT_MODEL if authenticated else "[REDACTED]" running = main.MINER_RUNNING db = SessionLocal() try: cutoff = datetime.utcnow() - timedelta(hours=24) daily_submissions = db.query(AlphaRecord).filter( AlphaRecord.status == "SUBMITTED", AlphaRecord.date_submitted >= cutoff ).count() finally: db.close() res = { "running": running, "current_model": current_model, "daily_submissions": daily_submissions, "daily_limit": 20, "concurrent_limit": config.get_concurrent_limit() if authenticated else 0 } if authenticated: res["client_ip"] = get_client_ip(request) res["home_ip_whitelist"] = ",".join(config.get_home_ip_whitelist()) return res @app.post("/api/settings") async def update_settings(request: Request): check_auth(request) try: data = await request.json() # 1. Update concurrent limit if "concurrent_limit" in data: limit = int(data.get("concurrent_limit", 3)) if limit < 1 or limit > 50: raise HTTPException(status_code=400, detail="Invalid limit value") config.set_concurrent_limit(limit) # 2. Update home IP whitelist if "home_ip_whitelist" in data: whitelist_str = data.get("home_ip_whitelist", "") ips = [ip.strip() for ip in whitelist_str.split(",") if ip.strip()] config.set_home_ip_whitelist(ips) # 3. Update Telegram settings telegram_token = data.get("telegram_bot_token") telegram_chat = data.get("telegram_chat_id") telegram_proxy = data.get("telegram_api_proxy") if telegram_token is not None or telegram_chat is not None or telegram_proxy is not None: config.save_wq_credentials( telegram_bot_token=telegram_token, telegram_chat_id=telegram_chat, telegram_api_proxy=telegram_proxy ) return {"status": "success"} except Exception as e: raise HTTPException(status_code=400, detail=str(e)) @app.get("/quick-auth", response_class=HTMLResponse) async def quick_auth(): return """ QuantForge — Cookie Sync

One-Tap Cookie Sync

Sync your biometric WorldQuant session cookie to the miner in one tap, even on mobile!
1
Click below to copy the Bookmarklet JS code to your clipboard:
javascript:(function(){let t=null;const ck=document.cookie.split('; ').find(r=>r.startsWith('t=')||r.startsWith('session='));if(ck){t=ck.split('=')[1];}if(!t){for(let i=0;i{if(r.ok){alert('🚀 QuantForge: Cookie synced successfully! Miner is resuming.');}else{alert('❌ Failed to sync cookie: '+r.status);}}).catch(e=>alert('❌ Network error: '+e));})();
2
Bookmark any page in your browser (e.g. this page), edit the bookmark, rename it to "Sync Cookie", and paste the copied code as the bookmark URL.
3
Go to WorldQuant BRAIN and log in. Complete your face/fingerprint scan.
4
Tap your browser address bar, type "Sync Cookie", and select the bookmarklet you created. Your session will sync instantly!
""" @app.post("/api/whitelist/add") async def add_whitelist_ip(request: Request): check_auth(request) try: data = await request.json() ip = data.get("ip", "").strip() if not ip: ip = get_client_ip(request) current_whitelist = config.get_home_ip_whitelist() if ip not in current_whitelist: current_whitelist.append(ip) config.set_home_ip_whitelist(current_whitelist) logger.info(f"Added IP {ip} to whitelist") return {"status": "success", "ip": ip, "whitelist": current_whitelist} except Exception as e: raise HTTPException(status_code=400, detail=str(e)) @app.post("/api/whitelist/remove") async def remove_whitelist_ip(request: Request): check_auth(request) try: data = await request.json() ip = data.get("ip", "").strip() if not ip: raise HTTPException(status_code=400, detail="IP address is required") current_whitelist = config.get_home_ip_whitelist() if ip in current_whitelist: current_whitelist.remove(ip) config.set_home_ip_whitelist(current_whitelist) logger.info(f"Removed IP {ip} from whitelist") return {"status": "success", "ip": ip, "whitelist": current_whitelist} except Exception as e: raise HTTPException(status_code=400, detail=str(e)) @app.get("/api/archive") async def get_archive(request: Request): db = SessionLocal() try: alphas = db.query(AlphaRecord).filter( AlphaRecord.status.notin_(["GENERATED", "SIMULATING"]) ).order_by(AlphaRecord.id.desc()).all() authenticated = is_authenticated(request) return [ { "id": a.id, "expression": a.expression if authenticated else "[REDACTED (Home WiFi Only)]", "sharpe": a.sharpe, "fitness": a.fitness, "turnover": a.turnover, "returns": a.returns, "status": a.status, "error_log": a.error_log if authenticated else "[REDACTED (Home WiFi Only)]", "date_created": a.date_created.replace(tzinfo=timezone.utc).isoformat() if a.date_created else None, "region": a.region, "universe": a.universe, "delay": a.delay, "decay": a.decay, "truncation": a.truncation, "neutralization": a.neutralization, "alpha_id": a.alpha_id } for a in alphas ] finally: db.close() @app.get("/api/submission_queue") async def get_submission_queue(request: Request): db = SessionLocal() try: alphas = db.query(AlphaRecord).filter( AlphaRecord.status.in_(["PENDING_CORRELATION", "PASSED_CORRELATION"]) ).all() authenticated = is_authenticated(request) queue_list = [] for a in alphas: score = (a.fitness or 0.0) * (a.sharpe or 0.0) queue_list.append({ "id": a.id, "expression": a.expression if authenticated else "[REDACTED (Home WiFi Only)]", "sharpe": a.sharpe, "fitness": a.fitness, "turnover": a.turnover, "returns": a.returns, "score": score, "status": a.status, "region": a.region, "universe": a.universe, "delay": a.delay, "decay": a.decay, "truncation": a.truncation, "neutralization": a.neutralization, "alpha_id": a.alpha_id }) queue_list.sort(key=lambda x: x["score"], reverse=True) return queue_list finally: db.close() @app.get("/api/submittable_archive") async def get_submittable_archive(request: Request): db = SessionLocal() try: alphas = db.query(AlphaRecord).filter( AlphaRecord.status.in_(["PASSED_CORRELATION", "SUBMITTED"]) ).order_by(AlphaRecord.id.desc()).all() authenticated = is_authenticated(request) archive_list = [] for a in alphas: score = (a.fitness or 0.0) * (a.sharpe or 0.0) archive_list.append({ "id": a.id, "expression": a.expression if authenticated else "[REDACTED (Home WiFi Only)]", "sharpe": a.sharpe, "fitness": a.fitness, "turnover": a.turnover, "returns": a.returns, "score": score, "status": a.status, "region": a.region, "universe": a.universe, "delay": a.delay, "decay": a.decay, "truncation": a.truncation, "neutralization": a.neutralization, "alpha_id": a.alpha_id, "date_created": a.date_created.replace(tzinfo=timezone.utc).isoformat() if a.date_created else None, "date_submitted": a.date_submitted.replace(tzinfo=timezone.utc).isoformat() if a.date_submitted else None }) archive_list.sort(key=lambda x: x["score"], reverse=True) return archive_list finally: db.close() @app.get("/api/alpha/{alpha_id}/pnl") async def get_alpha_pnl(alpha_id: int, request: Request): authenticated = is_authenticated(request) if not authenticated: return {"pnl": []} db = SessionLocal() try: alpha = db.query(AlphaRecord).filter_by(id=alpha_id).first() if not alpha or alpha.returns is None: return {"pnl": []} return {"pnl": generate_mock_pnl(alpha.returns)} finally: db.close() if __name__ == "__main__": import uvicorn uvicorn.run(app, host="127.0.0.1", port=8000)