Spaces:
Running
Running
DevWizard-Vandan
Implement wake_event synchronization to instantly resume recovery loop upon HTTP cookie updates
f168c51 | 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=["*"], | |
| ) | |
| 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 | |
| async def health(): | |
| return {"status": "healthy"} | |
| 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 | |
| } | |
| ) | |
| 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"}) | |
| async def logout(): | |
| response = JSONResponse(content={"message": "Logged out"}) | |
| response.delete_cookie("session_token", samesite="none", secure=True) | |
| return response | |
| 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 | |
| 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) | |
| async def pause_miner(request: Request): | |
| main.MINER_RUNNING = False | |
| return {"status": "paused"} | |
| 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 | |
| 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 | |
| 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, | |
| } | |
| 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() | |
| 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() | |
| 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}"} | |
| 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 | |
| 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)) | |
| async def quick_auth(): | |
| return """ | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>QuantForge — Cookie Sync</title> | |
| <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&display=swap" rel="stylesheet"> | |
| <style> | |
| } | |
| .step { | |
| display: flex; | |
| gap: 16px; | |
| margin-bottom: 24px; | |
| } | |
| .step-number { | |
| width: 28px; | |
| height: 28px; | |
| background: var(--primary-accent); | |
| border-radius: 50%; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| font-weight: 600; | |
| font-size: 14px; | |
| flex-shrink: 0; | |
| box-shadow: 0 0 10px var(--primary-glow); | |
| } | |
| .step-text { | |
| font-size: 15px; | |
| line-height: 1.5; | |
| color: #d1d5db; | |
| } | |
| .code-box { | |
| background: rgba(0,0,0,0.2); | |
| border: 1px solid var(--border-color); | |
| border-radius: 12px; | |
| padding: 16px; | |
| font-family: monospace; | |
| font-size: 12px; | |
| word-break: break-all; | |
| color: var(--text-muted); | |
| margin: 16px 0; | |
| max-height: 100px; | |
| overflow-y: auto; | |
| } | |
| .btn { | |
| background: var(--primary-accent); | |
| color: white; | |
| border: none; | |
| border-radius: 12px; | |
| padding: 14px 24px; | |
| font-size: 15px; | |
| font-weight: 600; | |
| width: 100%; | |
| cursor: pointer; | |
| transition: all 0.2s ease; | |
| box-shadow: 0 4px 12px var(--primary-glow); | |
| } | |
| .btn:hover { | |
| opacity: 0.9; | |
| transform: translateY(-1px); | |
| } | |
| .btn:active { | |
| transform: translateY(0); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>One-Tap Cookie Sync</h1> | |
| <div class="subtitle">Sync your biometric WorldQuant session cookie to the miner in one tap, even on mobile!</div> | |
| <div class="step"> | |
| <div class="step-number">1</div> | |
| <div class="step-text"> | |
| Click below to copy the Bookmarklet JS code to your clipboard: | |
| <div class="code-box" id="codeText">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<localStorage.length;i++){const k=localStorage.key(i);const v=localStorage.getItem(k);if(v){const m=v.match(/eyJ[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+/);if(m){t=m[0];break;}}}}if(!t){for(let i=0;i<sessionStorage.length;i++){const k=sessionStorage.key(i);const v=sessionStorage.getItem(k);if(v){const m=v.match(/eyJ[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+/);if(m){t=m[0];break;}}}}if(!t){alert('❌ Active session cookie or token not found. Please log in first!');return;}fetch('https://vanam56-quantforge-miner.hf.space/login-cookie',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({cookie:t})}).then(r=>{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));})();</div> | |
| <button class="btn" onclick="copyCode()">Copy Sync Script</button> | |
| </div> | |
| </div> | |
| <div class="step"> | |
| <div class="step-number">2</div> | |
| <div class="step-text"> | |
| Bookmark <b>any</b> page in your browser (e.g. this page), edit the bookmark, rename it to <b>"Sync Cookie"</b>, and paste the copied code as the bookmark URL. | |
| </div> | |
| </div> | |
| <div class="step"> | |
| <div class="step-number">3</div> | |
| <div class="step-text"> | |
| Go to <a href="https://worldquantbrain.com" target="_blank" style="color: #818cf8; text-decoration: none;">WorldQuant BRAIN</a> and log in. Complete your face/fingerprint scan. | |
| </div> | |
| </div> | |
| <div class="step"> | |
| <div class="step-number">4</div> | |
| <div class="step-text"> | |
| Tap your browser address bar, type <b>"Sync Cookie"</b>, and select the bookmarklet you created. Your session will sync instantly! | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| function copyCode() { | |
| const code = document.getElementById('codeText').innerText; | |
| navigator.clipboard.writeText(code).then(() => { | |
| alert('Copied to clipboard! Ready to save as bookmark.'); | |
| }); | |
| } | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| 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)) | |
| 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)) | |
| 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() | |
| 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() | |
| 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() | |
| 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) | |