engineportf commited on
Commit
b513eec
·
verified ·
1 Parent(s): 25e90db

Upload folder using huggingface_hub

Browse files
Files changed (5) hide show
  1. __pycache__/app.cpython-314.pyc +0 -0
  2. app.py +89 -44
  3. hf_app.py +50 -24
  4. static/app.js +27 -11
  5. 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
- overrides = {
172
- 'tickers': req.tickers,
173
- 'capital': req.capital,
174
- 'risk_input': req.risk_input,
175
- '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(req.risk_input, 3.0),
176
- 'model': req.model,
177
- 'allocation_engine': req.allocation_engine,
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
- result = await loop.run_in_executor(
186
- engine_executor,
187
- lambda: core_engine.run_engine(overrides=overrides, serve=False, preview_only=True)
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, request):
213
  try:
214
- overrides = {
215
- 'tickers': request.tickers,
216
- 'capital': request.capital,
217
- 'risk_input': request.risk_input,
218
- '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.risk_input, 3.0),
219
- 'model': request.model,
220
- 'allocation_engine': request.allocation_engine,
221
- 'single_asset_min': -1.0 if request.allow_shorting else 0.0,
222
- 'tax_enabled': request.tax_enabled,
223
- 'garch_enabled': request.garch_enabled,
224
- 'custom_constraints': request.custom_constraints
225
  }
226
 
227
- result = core_engine.run_engine(overrides=overrides, serve=False)
 
 
 
 
 
 
 
 
 
 
 
 
228
 
229
- BACKGROUND_TASKS[tid]["status"] = "completed"
230
- BACKGROUND_TASKS[tid]["message"] = "Report generated."
231
- BACKGROUND_TASKS[tid]["target_weights"] = result.get("target_weights", {})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 1 hour (3600 seconds)
268
- time.sleep(3600)
 
269
 
270
- # Simple check for SPY drops
271
- ticker = yf.Ticker("SPY")
272
- hist = ticker.history(period="2d")
273
- if len(hist) >= 2:
274
- current = float(hist['Close'].iloc[-1])
275
- prev = float(hist['Close'].iloc[-2])
276
- pct_change = ((current - prev) / prev) * 100
277
-
278
- if pct_change <= -5.0:
279
- access_manager.send_telegram_alert(f"🚨 **MARKET ALERT**\nSPY has dropped by {pct_change:.2f}%!\nCheck the portfolio engine.")
 
 
 
 
 
 
 
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
- overrides = {
61
- 'tickers': req.tickers,
62
- 'capital': req.capital,
63
- 'risk_input': req.risk_input,
64
- '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(req.risk_input, 3.0),
65
- 'model': req.model,
66
- 'allocation_engine': req.allocation_engine,
67
- 'single_asset_min': -1.0 if req.allow_shorting else 0.0,
68
- 'tax_enabled': req.tax_enabled,
69
- 'garch_enabled': req.garch_enabled,
70
- 'custom_constraints': req.custom_constraints
71
- }
72
-
73
- # Run the engine. This generates output/portfolio_report.html
74
- result = core_engine.run_engine(overrides=overrides, serve=False)
75
-
76
- report_path = os.path.join(OUTPUT_DIR, "portfolio_report.html")
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": "success",
84
- "target_weights": result.get("target_weights", {}),
85
- "report_html": html_content
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
- }, 600); // Fast cinematic log streaming
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 id="vanta-bg">
 
 
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: #050814; z-index: 9999; flex-direction: column; justify-content: center; align-items: center; color: #3b82f6; font-family: monospace; overflow: hidden;">
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>