Touched up notebooks + webapp
This commit is contained in:
+123
@@ -0,0 +1,123 @@
|
||||
"""FastAPI web demo for the Adaptive Barrier Monitor."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any, Literal
|
||||
|
||||
import numpy as np
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
# Support both an installed package (`pip install -e .`) and direct launches
|
||||
# from a source checkout (`uvicorn webapp.app:app`).
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
SRC_DIR = REPO_ROOT / "src"
|
||||
if str(SRC_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(SRC_DIR))
|
||||
|
||||
from adaptive_barrier import __version__
|
||||
from adaptive_barrier.engine import (
|
||||
DROP_FRACTION,
|
||||
LOG_BARRIER,
|
||||
RISE_FRACTION,
|
||||
TYPICAL_ANNUAL_VOL,
|
||||
WINDOW_MINUTES,
|
||||
run_monte_carlo_simulation,
|
||||
)
|
||||
|
||||
STATIC_DIR = REPO_ROOT / "webapp" / "static"
|
||||
|
||||
|
||||
class SimulateRequest(BaseModel):
|
||||
"""Validated Monte Carlo and detector-comparison parameters."""
|
||||
|
||||
S0: float = Field(100.0, gt=0.0, le=10000.0)
|
||||
sigma_annual: float = Field(TYPICAL_ANNUAL_VOL, gt=0.0, le=2.0)
|
||||
mu_annual: float = Field(0.0, ge=-0.5, le=0.5)
|
||||
window_minutes: float = Field(1950.0, gt=0.0, le=39000.0)
|
||||
n_paths: int = Field(20, ge=1, le=100)
|
||||
n_steps: int = Field(500, ge=50, le=2000)
|
||||
use_jumps: bool = Field(
|
||||
False,
|
||||
description="Use Merton jump diffusion. The Brownian miss proxy does not control jumps.",
|
||||
)
|
||||
jump_intensity: float = Field(25.0, ge=0.0, le=500.0)
|
||||
jump_mean: float = Field(-0.02, ge=-0.5, le=0.5)
|
||||
jump_sigma: float = Field(0.05, ge=0.0, le=0.5)
|
||||
eps: float = Field(1e-3, gt=0.0, lt=1.0)
|
||||
dt_cap_minutes: float | None = Field(None, gt=0.0, le=1440.0)
|
||||
rng_seed: int | None = Field(None, ge=0, le=2_147_483_647)
|
||||
drop_fraction: float = Field(DROP_FRACTION, gt=0.0, lt=1.0)
|
||||
rise_fraction: float = Field(RISE_FRACTION, gt=0.0, le=1.0)
|
||||
max_detection_lag_steps: int | None = Field(3, ge=0, le=100)
|
||||
comparison_mode: Literal["equal_budget", "fixed_cadence"] = "equal_budget"
|
||||
fixed_cadence_minutes: float = Field(60.0, gt=0.0, le=39000.0)
|
||||
|
||||
|
||||
def _file_version(path: Path) -> str:
|
||||
try:
|
||||
stat = path.stat()
|
||||
return f"{int(stat.st_mtime)}-{stat.st_size}"
|
||||
except FileNotFoundError:
|
||||
return "missing"
|
||||
|
||||
|
||||
def _json_safe(obj: Any) -> Any:
|
||||
if isinstance(obj, dict):
|
||||
return {str(key): _json_safe(value) for key, value in obj.items()}
|
||||
if isinstance(obj, (list, tuple)):
|
||||
return [_json_safe(value) for value in obj]
|
||||
if isinstance(obj, np.integer):
|
||||
return int(obj)
|
||||
if isinstance(obj, np.floating):
|
||||
value = float(obj)
|
||||
return value if np.isfinite(value) else None
|
||||
if isinstance(obj, np.ndarray):
|
||||
return _json_safe(obj.tolist())
|
||||
if isinstance(obj, np.bool_):
|
||||
return bool(obj)
|
||||
return obj
|
||||
|
||||
|
||||
app = FastAPI(title="Adaptive Barrier Monitor", version=__version__)
|
||||
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def index() -> HTMLResponse:
|
||||
html = (STATIC_DIR / "index.html").read_text(encoding="utf-8")
|
||||
html = re.sub(
|
||||
r'app\.css\?v=[^\s"\']+',
|
||||
f"app.css?v={_file_version(STATIC_DIR / 'app.css')}",
|
||||
html,
|
||||
)
|
||||
html = re.sub(
|
||||
r'app\.js\?v=[^\s"\']+',
|
||||
f"app.js?v={_file_version(STATIC_DIR / 'app.js')}",
|
||||
html,
|
||||
)
|
||||
return HTMLResponse(content=html)
|
||||
|
||||
|
||||
@app.get("/api/health")
|
||||
def health() -> dict:
|
||||
return {
|
||||
"ok": True,
|
||||
"version": __version__,
|
||||
"default_sigma_annual": TYPICAL_ANNUAL_VOL,
|
||||
"default_window_minutes": WINDOW_MINUTES,
|
||||
"drop_fraction": DROP_FRACTION,
|
||||
"rise_fraction": RISE_FRACTION,
|
||||
"log_barrier": LOG_BARRIER,
|
||||
}
|
||||
|
||||
|
||||
@app.post("/api/simulate")
|
||||
def simulate(req: SimulateRequest) -> dict:
|
||||
result = run_monte_carlo_simulation(**req.model_dump())
|
||||
return _json_safe(result)
|
||||
Reference in New Issue
Block a user