Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- __pycache__/app.cpython-314.pyc +0 -0
- app.py +89 -44
- hf_app.py +50 -24
- static/app.js +27 -11
- static/index.html +4 -2
__pycache__/app.cpython-314.pyc
CHANGED
|
Binary files a/__pycache__/app.cpython-314.pyc and b/__pycache__/app.cpython-314.pyc differ
|
|
|
app.py
CHANGED
|
@@ -168,24 +168,24 @@ async def preview_portfolio(req: PortfolioRequest, x_access_key: Optional[str] =
|
|
| 168 |
raise HTTPException(status_code=401, detail="Unauthorized")
|
| 169 |
|
| 170 |
try:
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
'single_asset_min': -1.0 if req.allow_shorting else 0.0,
|
| 179 |
-
'tax_enabled': req.tax_enabled,
|
| 180 |
-
'garch_enabled': req.garch_enabled,
|
| 181 |
-
'custom_constraints': req.custom_constraints
|
| 182 |
}
|
| 183 |
|
|
|
|
| 184 |
loop = asyncio.get_running_loop()
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
|
|
|
|
|
|
|
|
|
| 189 |
|
| 190 |
return {
|
| 191 |
"status": "success",
|
|
@@ -209,26 +209,59 @@ async def generate_portfolio(req: PortfolioRequest, request: Request, x_access_k
|
|
| 209 |
task_id = str(uuid.uuid4())
|
| 210 |
BACKGROUND_TASKS[task_id] = {"status": "running", "message": "Initializing...", "target_weights": {}}
|
| 211 |
|
| 212 |
-
def _run_optimization(tid,
|
| 213 |
try:
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
'tax_enabled': request.tax_enabled,
|
| 223 |
-
'garch_enabled': request.garch_enabled,
|
| 224 |
-
'custom_constraints': request.custom_constraints
|
| 225 |
}
|
| 226 |
|
| 227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
except Exception as e:
|
| 233 |
BACKGROUND_TASKS[tid]["status"] = "error"
|
| 234 |
BACKGROUND_TASKS[tid]["message"] = str(e)
|
|
@@ -260,23 +293,35 @@ async def get_report():
|
|
| 260 |
raise HTTPException(status_code=404, detail="Report not generated yet.")
|
| 261 |
|
| 262 |
def _alert_daemon():
|
| 263 |
-
"""Background daemon to check for market drops."""
|
| 264 |
import time
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
while True:
|
| 266 |
try:
|
| 267 |
-
# Wake up every
|
| 268 |
-
time.sleep(
|
|
|
|
| 269 |
|
| 270 |
-
#
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
except Exception as e:
|
| 281 |
pass # Suppress daemon errors
|
| 282 |
|
|
|
|
| 168 |
raise HTTPException(status_code=401, detail="Unauthorized")
|
| 169 |
|
| 170 |
try:
|
| 171 |
+
import requests
|
| 172 |
+
HF_URL = os.environ.get("HF_MATH_URL", "https://engineportf-math-backend.hf.space")
|
| 173 |
+
HF_SECRET = os.environ.get("HF_SECRET_KEY", "EngineSecret2026")
|
| 174 |
+
|
| 175 |
+
headers = {
|
| 176 |
+
"X-API-Key": HF_SECRET,
|
| 177 |
+
"Content-Type": "application/json"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
}
|
| 179 |
|
| 180 |
+
# Send request to Hugging Face backend
|
| 181 |
loop = asyncio.get_running_loop()
|
| 182 |
+
def _call_hf_preview():
|
| 183 |
+
res = requests.post(f"{HF_URL}/api/preview", json=req.model_dump() if hasattr(req, "model_dump") else req.dict(), headers=headers, timeout=60)
|
| 184 |
+
if res.status_code != 200:
|
| 185 |
+
raise Exception(f"HF Engine Error: {res.text}")
|
| 186 |
+
return res.json()
|
| 187 |
+
|
| 188 |
+
result = await loop.run_in_executor(engine_executor, _call_hf_preview)
|
| 189 |
|
| 190 |
return {
|
| 191 |
"status": "success",
|
|
|
|
| 209 |
task_id = str(uuid.uuid4())
|
| 210 |
BACKGROUND_TASKS[task_id] = {"status": "running", "message": "Initializing...", "target_weights": {}}
|
| 211 |
|
| 212 |
+
def _run_optimization(tid, request_obj):
|
| 213 |
try:
|
| 214 |
+
import requests
|
| 215 |
+
import time
|
| 216 |
+
HF_URL = os.environ.get("HF_MATH_URL", "https://engineportf-math-backend.hf.space")
|
| 217 |
+
HF_SECRET = os.environ.get("HF_SECRET_KEY", "EngineSecret2026")
|
| 218 |
+
|
| 219 |
+
headers = {
|
| 220 |
+
"X-API-Key": HF_SECRET,
|
| 221 |
+
"Content-Type": "application/json"
|
|
|
|
|
|
|
|
|
|
| 222 |
}
|
| 223 |
|
| 224 |
+
BACKGROUND_TASKS[tid]["message"] = "Initializing on Hugging Face..."
|
| 225 |
+
|
| 226 |
+
payload = request_obj.model_dump() if hasattr(request_obj, "model_dump") else request_obj.dict()
|
| 227 |
+
response = requests.post(f"{HF_URL}/api/generate", json=payload, headers=headers, timeout=60)
|
| 228 |
+
|
| 229 |
+
if response.status_code != 200:
|
| 230 |
+
raise Exception(f"HF Engine Error: {response.text}")
|
| 231 |
+
|
| 232 |
+
init_data = response.json()
|
| 233 |
+
if init_data.get("status") != "queued":
|
| 234 |
+
raise Exception("Failed to queue task on Hugging Face.")
|
| 235 |
+
|
| 236 |
+
hf_task_id = init_data.get("task_id")
|
| 237 |
|
| 238 |
+
# Poll Hugging Face
|
| 239 |
+
while True:
|
| 240 |
+
time.sleep(2.0)
|
| 241 |
+
status_res = requests.get(f"{HF_URL}/api/status/{hf_task_id}", headers=headers, timeout=20)
|
| 242 |
+
if status_res.status_code != 200:
|
| 243 |
+
continue # Network blip, retry
|
| 244 |
+
|
| 245 |
+
status_data = status_res.json()
|
| 246 |
+
if status_data.get("status") == "completed":
|
| 247 |
+
# Task finished, retrieve HTML
|
| 248 |
+
report_html = status_data.get("report_html", "")
|
| 249 |
+
if report_html:
|
| 250 |
+
report_path = os.path.join(OUTPUT_DIR, "portfolio_report.html")
|
| 251 |
+
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
| 252 |
+
with open(report_path, "w", encoding="utf-8") as f:
|
| 253 |
+
f.write(report_html)
|
| 254 |
+
|
| 255 |
+
BACKGROUND_TASKS[tid]["status"] = "completed"
|
| 256 |
+
BACKGROUND_TASKS[tid]["message"] = "Report generated."
|
| 257 |
+
BACKGROUND_TASKS[tid]["target_weights"] = status_data.get("target_weights", {})
|
| 258 |
+
break
|
| 259 |
+
elif status_data.get("status") == "error":
|
| 260 |
+
raise Exception(status_data.get("message", "Unknown error on HF backend."))
|
| 261 |
+
else:
|
| 262 |
+
# Still running
|
| 263 |
+
BACKGROUND_TASKS[tid]["message"] = "Calculating (Running on HF 16GB Cluster)..."
|
| 264 |
+
|
| 265 |
except Exception as e:
|
| 266 |
BACKGROUND_TASKS[tid]["status"] = "error"
|
| 267 |
BACKGROUND_TASKS[tid]["message"] = str(e)
|
|
|
|
| 293 |
raise HTTPException(status_code=404, detail="Report not generated yet.")
|
| 294 |
|
| 295 |
def _alert_daemon():
|
| 296 |
+
"""Background daemon to check for market drops and ping HF."""
|
| 297 |
import time
|
| 298 |
+
import requests
|
| 299 |
+
|
| 300 |
+
HF_URL = os.environ.get("HF_MATH_URL", "https://engineportf-math-backend.hf.space")
|
| 301 |
+
loops = 0
|
| 302 |
while True:
|
| 303 |
try:
|
| 304 |
+
# Wake up every 15 minutes (900 seconds)
|
| 305 |
+
time.sleep(900)
|
| 306 |
+
loops += 1
|
| 307 |
|
| 308 |
+
# 1. Ping Hugging Face to keep the 16GB math cluster awake
|
| 309 |
+
try:
|
| 310 |
+
requests.get(HF_URL, timeout=10)
|
| 311 |
+
except Exception:
|
| 312 |
+
pass
|
| 313 |
+
|
| 314 |
+
# 2. Every 4th loop (1 hour), check for SPY drops
|
| 315 |
+
if loops % 4 == 0:
|
| 316 |
+
ticker = yf.Ticker("SPY")
|
| 317 |
+
hist = ticker.history(period="2d")
|
| 318 |
+
if len(hist) >= 2:
|
| 319 |
+
current = float(hist['Close'].iloc[-1])
|
| 320 |
+
prev = float(hist['Close'].iloc[-2])
|
| 321 |
+
pct_change = ((current - prev) / prev) * 100
|
| 322 |
+
|
| 323 |
+
if pct_change <= -5.0:
|
| 324 |
+
access_manager.send_telegram_alert(f"🚨 **MARKET ALERT**\nSPY has dropped by {pct_change:.2f}%!\nCheck the portfolio engine.")
|
| 325 |
except Exception as e:
|
| 326 |
pass # Suppress daemon errors
|
| 327 |
|
hf_app.py
CHANGED
|
@@ -7,12 +7,16 @@ from fastapi.security import APIKeyHeader
|
|
| 7 |
from pydantic import BaseModel
|
| 8 |
import concurrent.futures
|
| 9 |
import base64
|
|
|
|
| 10 |
|
| 11 |
import core_engine
|
| 12 |
from config import OUTPUT_DIR
|
| 13 |
|
| 14 |
app = FastAPI(title="Portfolio Engine Math Backend")
|
| 15 |
|
|
|
|
|
|
|
|
|
|
| 16 |
# Security configuration
|
| 17 |
api_key_header = APIKeyHeader(name="X-API-Key")
|
| 18 |
SECRET_KEY = os.environ.get("HF_SECRET_KEY", "default-unsafe-key")
|
|
@@ -57,34 +61,56 @@ def preview_portfolio(req: PortfolioRequest, api_key: str = Security(get_api_key
|
|
| 57 |
|
| 58 |
@app.post("/api/generate")
|
| 59 |
def generate_portfolio(req: PortfolioRequest, api_key: str = Security(get_api_key)):
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
html_content = ""
|
| 78 |
-
if os.path.exists(report_path):
|
| 79 |
-
with open(report_path, "r", encoding="utf-8") as f:
|
| 80 |
-
html_content = f.read()
|
| 81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
return {
|
| 83 |
-
"status": "
|
| 84 |
-
"
|
| 85 |
-
"
|
| 86 |
}
|
| 87 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
@app.get("/")
|
| 89 |
def health_check():
|
| 90 |
return {"status": "HF Engine is running. Use X-API-Key to authenticate."}
|
|
|
|
| 7 |
from pydantic import BaseModel
|
| 8 |
import concurrent.futures
|
| 9 |
import base64
|
| 10 |
+
import time
|
| 11 |
|
| 12 |
import core_engine
|
| 13 |
from config import OUTPUT_DIR
|
| 14 |
|
| 15 |
app = FastAPI(title="Portfolio Engine Math Backend")
|
| 16 |
|
| 17 |
+
BACKGROUND_TASKS = {}
|
| 18 |
+
engine_executor = concurrent.futures.ThreadPoolExecutor(max_workers=3)
|
| 19 |
+
|
| 20 |
# Security configuration
|
| 21 |
api_key_header = APIKeyHeader(name="X-API-Key")
|
| 22 |
SECRET_KEY = os.environ.get("HF_SECRET_KEY", "default-unsafe-key")
|
|
|
|
| 61 |
|
| 62 |
@app.post("/api/generate")
|
| 63 |
def generate_portfolio(req: PortfolioRequest, api_key: str = Security(get_api_key)):
|
| 64 |
+
task_id = str(uuid.uuid4())
|
| 65 |
+
BACKGROUND_TASKS[task_id] = {"status": "running", "message": "Starting...", "target_weights": {}, "report_html": ""}
|
| 66 |
+
|
| 67 |
+
def _run_optimization(tid, request_obj):
|
| 68 |
+
try:
|
| 69 |
+
overrides = {
|
| 70 |
+
'tickers': request_obj.tickers,
|
| 71 |
+
'capital': request_obj.capital,
|
| 72 |
+
'risk_input': request_obj.risk_input,
|
| 73 |
+
'risk_factor': {1:0.1, 2:0.5, 3:1.0, 4:2.0, 5:3.0, 6:5.0, 7:7.5, 8:10.0, 9:15.0, 10:25.0}.get(request_obj.risk_input, 3.0),
|
| 74 |
+
'model': request_obj.model,
|
| 75 |
+
'allocation_engine': request_obj.allocation_engine,
|
| 76 |
+
'single_asset_min': -1.0 if request_obj.allow_shorting else 0.0,
|
| 77 |
+
'tax_enabled': request_obj.tax_enabled,
|
| 78 |
+
'garch_enabled': request_obj.garch_enabled,
|
| 79 |
+
'custom_constraints': request_obj.custom_constraints
|
| 80 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
+
result = core_engine.run_engine(overrides=overrides, serve=False)
|
| 83 |
+
|
| 84 |
+
report_path = os.path.join(OUTPUT_DIR, "portfolio_report.html")
|
| 85 |
+
html_content = ""
|
| 86 |
+
if os.path.exists(report_path):
|
| 87 |
+
with open(report_path, "r", encoding="utf-8") as f:
|
| 88 |
+
html_content = f.read()
|
| 89 |
+
|
| 90 |
+
BACKGROUND_TASKS[tid]["status"] = "completed"
|
| 91 |
+
BACKGROUND_TASKS[tid]["target_weights"] = result.get("target_weights", {})
|
| 92 |
+
BACKGROUND_TASKS[tid]["report_html"] = html_content
|
| 93 |
+
except Exception as e:
|
| 94 |
+
BACKGROUND_TASKS[tid]["status"] = "error"
|
| 95 |
+
BACKGROUND_TASKS[tid]["message"] = str(e)
|
| 96 |
+
|
| 97 |
+
engine_executor.submit(_run_optimization, task_id, req)
|
| 98 |
+
|
| 99 |
return {
|
| 100 |
+
"status": "queued",
|
| 101 |
+
"task_id": task_id,
|
| 102 |
+
"message": "Optimization started on HF."
|
| 103 |
}
|
| 104 |
|
| 105 |
+
@app.get("/api/status/{task_id}")
|
| 106 |
+
def get_task_status(task_id: str, api_key: str = Security(get_api_key)):
|
| 107 |
+
task = BACKGROUND_TASKS.get(task_id)
|
| 108 |
+
if not task:
|
| 109 |
+
raise HTTPException(status_code=404, detail="Task not found")
|
| 110 |
+
# To save bandwidth, don't send massive HTML if it's not completed yet
|
| 111 |
+
# Or send it, but the client will handle it
|
| 112 |
+
return task
|
| 113 |
+
|
| 114 |
@app.get("/")
|
| 115 |
def health_check():
|
| 116 |
return {"status": "HF Engine is running. Use X-API-Key to authenticate."}
|
static/app.js
CHANGED
|
@@ -299,24 +299,40 @@ async function generateFullReport() {
|
|
| 299 |
"Compiling institutional HTML report matrix..."
|
| 300 |
];
|
| 301 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
let logIdx = 0;
|
|
|
|
| 303 |
const interval = setInterval(() => {
|
|
|
|
| 304 |
if(logIdx < steps.length) {
|
| 305 |
-
const el = document.createElement('div');
|
| 306 |
el.innerHTML = `<span style="color: #fff">></span> ${steps[logIdx]}`;
|
| 307 |
-
matrixLogs.appendChild(el);
|
| 308 |
-
|
| 309 |
-
// Auto scroll
|
| 310 |
-
const maxScroll = matrixLogs.scrollHeight;
|
| 311 |
-
if (logIdx > 5) {
|
| 312 |
-
matrixLogs.style.transform = `translateY(-${(logIdx - 5) * 20}px)`;
|
| 313 |
-
}
|
| 314 |
-
|
| 315 |
-
// Progress bar
|
| 316 |
matrixProgress.style.width = `${((logIdx + 1) / steps.length) * 100}%`;
|
| 317 |
logIdx++;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
}
|
| 319 |
-
},
|
| 320 |
|
| 321 |
try {
|
| 322 |
const res = await fetch('/api/generate', {
|
|
|
|
| 299 |
"Compiling institutional HTML report matrix..."
|
| 300 |
];
|
| 301 |
|
| 302 |
+
const infiniteSteps = [
|
| 303 |
+
"Calculating gradients...",
|
| 304 |
+
"Rebalancing tensors...",
|
| 305 |
+
"Analyzing tail risks...",
|
| 306 |
+
"Synchronizing with Hugging Face ML Cluster...",
|
| 307 |
+
"Backpropagating loss function...",
|
| 308 |
+
"Filtering noise from alpha signals...",
|
| 309 |
+
"Executing stochastic bounds check...",
|
| 310 |
+
"Optimizing L2 regularization weights..."
|
| 311 |
+
];
|
| 312 |
+
|
| 313 |
let logIdx = 0;
|
| 314 |
+
let infiniteIdx = 0;
|
| 315 |
const interval = setInterval(() => {
|
| 316 |
+
const el = document.createElement('div');
|
| 317 |
if(logIdx < steps.length) {
|
|
|
|
| 318 |
el.innerHTML = `<span style="color: #fff">></span> ${steps[logIdx]}`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 319 |
matrixProgress.style.width = `${((logIdx + 1) / steps.length) * 100}%`;
|
| 320 |
logIdx++;
|
| 321 |
+
} else {
|
| 322 |
+
// Infinite loading loop
|
| 323 |
+
el.innerHTML = `<span style="color: #3b82f6">></span> <span style="opacity: 0.8;">[ML CLUSTER]</span> ${infiniteSteps[infiniteIdx % infiniteSteps.length]} <span class="spinner-anim">◓</span>`;
|
| 324 |
+
infiniteIdx++;
|
| 325 |
+
// Keep progress bar pulsing
|
| 326 |
+
matrixProgress.style.opacity = (Math.sin(infiniteIdx) * 0.5 + 0.5).toString();
|
| 327 |
+
}
|
| 328 |
+
matrixLogs.appendChild(el);
|
| 329 |
+
|
| 330 |
+
// Auto scroll
|
| 331 |
+
const scrollAmt = Math.max(0, matrixLogs.children.length - 6);
|
| 332 |
+
if (scrollAmt > 0) {
|
| 333 |
+
matrixLogs.style.transform = `translateY(-${scrollAmt * 20}px)`;
|
| 334 |
}
|
| 335 |
+
}, 800); // Cinematic log streaming
|
| 336 |
|
| 337 |
try {
|
| 338 |
const res = await fetch('/api/generate', {
|
static/index.html
CHANGED
|
@@ -18,10 +18,12 @@
|
|
| 18 |
.glass-panel:hover { transform: translateY(-5px) scale(1.02); }
|
| 19 |
</style>
|
| 20 |
</head>
|
| 21 |
-
<body
|
|
|
|
|
|
|
| 22 |
|
| 23 |
<!-- Cinematic Matrix Loading Overlay -->
|
| 24 |
-
<div id="matrix-loader" style="display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background:
|
| 25 |
<div class="noise-overlay" style="opacity: 0.05;"></div>
|
| 26 |
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; z-index: 2;">
|
| 27 |
<div style="font-size: 2rem; font-weight: bold; letter-spacing: 2px; margin-bottom: 2rem; text-shadow: 0 0 20px rgba(59,130,246,0.5);">ENGINE OPTIMIZING</div>
|
|
|
|
| 18 |
.glass-panel:hover { transform: translateY(-5px) scale(1.02); }
|
| 19 |
</style>
|
| 20 |
</head>
|
| 21 |
+
<body class="theme-dark">
|
| 22 |
+
<!-- Vanta JS Fixed Background Container -->
|
| 23 |
+
<div id="vanta-bg" style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: -2;"></div>
|
| 24 |
|
| 25 |
<!-- Cinematic Matrix Loading Overlay -->
|
| 26 |
+
<div id="matrix-loader" style="display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(5, 8, 20, 0.65); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); z-index: 9999; flex-direction: column; justify-content: center; align-items: center; color: #3b82f6; font-family: monospace; overflow: hidden;">
|
| 27 |
<div class="noise-overlay" style="opacity: 0.05;"></div>
|
| 28 |
<div style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; z-index: 2;">
|
| 29 |
<div style="font-size: 2rem; font-weight: bold; letter-spacing: 2px; margin-bottom: 2rem; text-shadow: 0 0 20px rgba(59,130,246,0.5);">ENGINE OPTIMIZING</div>
|