Memory guard: 512MB hard cap, GC at 256MB, +swap

This commit is contained in:
ramseshk
2026-08-06 14:30:34 +08:00
parent bf137a08a3
commit e5a81132ef
+87
View File
@@ -29,6 +29,35 @@ import sys
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from config.fee_tiers import get_perp_fees, PERPS_TIERS, STAKING_TIERS, STRATEGY_FEE_MODELS
from common.risk import risk_summary
from strategies.quant_report import compute_quant_report
# ═══════════════════════════════════════════════════════════
# Memory guard: cap RSS at 512MB, GC-aggressive at 256MB
# ═══════════════════════════════════════════════════════════
import resource, gc, signal
MEM_SOFT_LIMIT = 256 * 1024 * 1024 # 256 MB — force GC
MEM_HARD_LIMIT = 512 * 1024 * 1024 # 512 MB — terminate
resource.setrlimit(resource.RLIMIT_AS, (MEM_HARD_LIMIT, MEM_HARD_LIMIT))
def check_memory():
"""Check RSS, force GC if over soft limit, raise if over hard limit."""
try:
with open("/proc/self/status") as f:
for line in f:
if line.startswith("VmRSS:"):
rss_kb = int(line.split()[1])
rss = rss_kb * 1024
if rss > MEM_HARD_LIMIT:
print(f"[CRIT] RSS {rss_kb // 1024}MB > 512MB — exiting", flush=True)
os._exit(1)
if rss > MEM_SOFT_LIMIT:
gc.collect()
gc.collect()
return
except Exception:
pass
import uvicorn
# ═══════════════════════════════════════════════════════════
@@ -109,6 +138,7 @@ def broadcast_loop():
"""Continuously read metrics and broadcast to all clients."""
while True:
time.sleep(1)
check_memory()
data = read_metrics()
payload = json.dumps(data, default=str)
for ws in list(connected_clients):
@@ -437,6 +467,63 @@ app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static")
# Main
# ═══════════════════════════════════════════════════════════
@app.get("/api/quant-report/{name}")
async def get_quant_report(name: str):
"""Compute full QF-Lib quant report from a backtest file.
Accepts strategy name and auto-maps to filename prefix.
"""
# Strategy name → file prefix mapping
NAME_MAP = {
"order book imbalance": "ofi",
"avellaneda-stoikov": "avellaneda",
"funding rate arb": "funding_arb",
"iceberg detection": "iceberg",
"momentum breakout": "momentum",
"mean reversion": "mean_rev",
"kalman pairs": "kalman_pairs",
"pairs trading": "pairs",
}
name_lower = name.lower()
prefix = NAME_MAP.get(name_lower, name_lower.replace(" ", "_"))
# Build candidate paths
candidates = []
exact_path = os.path.join(BACKTEST_DIR, name)
hist_exact = os.path.join(HISTORICAL_DIR, name)
candidates.extend([exact_path, hist_exact])
# Try exact match
for path in candidates:
if os.path.exists(path):
backtest_path = path
break
else:
# Fuzzy match: find files starting with the mapped prefix
fuzzy = []
for d in [BACKTEST_DIR, HISTORICAL_DIR]:
if not os.path.exists(d): continue
for f in os.listdir(d):
f_clean = f.lower()
# Match by prefix, then prefer BTC/ETH files
if f_clean.startswith(f"{prefix}_"):
fuzzy.append(os.path.join(d, f))
if fuzzy:
backtest_path = fuzzy[0]
else:
return JSONResponse({"error": f"Backtest '{name}' not found"}, status_code=404)
try:
with open(backtest_path) as f:
data = json.load(f)
trades = data.get("trades", data.get("trade_history", []))
strategy_name = data.get("name", data.get("strategy", name))
strategy_id = data.get("id", name)
report = compute_quant_report(strategy_name, strategy_id, trades, 100.0)
return JSONResponse(report)
except Exception as e:
return JSONResponse({"error": str(e)}, status_code=500)
def main():
import argparse
parser = argparse.ArgumentParser()