feat: strategy builder

This commit is contained in:
OpenSquared
2026-07-19 00:14:03 +02:00
parent 0d437922c9
commit 85f48bb374
3 changed files with 40 additions and 12 deletions

View File

@@ -18,6 +18,24 @@ from services.vol_surface import Surface, ScenarioSurface
DEFAULT_SPREAD_PCT = 0.05 # fallback relative bid/ask spread when no live quote is found
def to_native(obj: Any) -> Any:
"""Recursively converts numpy scalars (bool_, int64, float64, ...) to native Python
types. Comparisons/aggregations over numpy-typed inputs (e.g. bid/ask sourced from a
DB row that came back as a numpy type, or scipy's norm.cdf) can leave a stray
numpy.bool_/numpy.float64 buried in a nested result — FastAPI's default JSON encoder
doesn't know those types and fails ('X object is not iterable', then a secondary
'vars() argument must have __dict__ attribute' from its own fallback). Applied once
at the API boundary (price_combo/payoff_curves/optimizer results) rather than chasing
the exact field through the whole pricing pipeline."""
if isinstance(obj, dict):
return {k: to_native(v) for k, v in obj.items()}
if isinstance(obj, (list, tuple)):
return [to_native(v) for v in obj]
if isinstance(obj, np.generic):
return obj.item()
return obj
def _sign(leg: Dict[str, Any]) -> int:
return 1 if leg.get("position", "long") == "long" else -1
@@ -131,7 +149,7 @@ def price_combo(
delta_now = greeks_at(legs, spot_now, 0, surface_now, r)["delta"]
delta_scenario = greeks_at(legs, spot_scenario, horizon_days, surface_scenario, r)["delta"]
return {
return to_native({
"entry_cost": round(entry_ref, 2),
"entry_cost_mid": round(entry_ref_mid, 2),
"scenario_value": round(scenario_exec, 2),
@@ -145,7 +163,7 @@ def price_combo(
"greeks_scenario": greeks_at(legs, spot_scenario, horizon_days, surface_scenario, r),
"net_delta_now": delta_now,
"net_delta_scenario": delta_scenario,
}
})
def check_bounded_risk(legs: List[Dict[str, Any]], entry_ref: float, surface: Any, spot: float) -> Dict[str, Any]:

View File

@@ -10,7 +10,7 @@ from typing import Any, Dict, List, Optional
from services.option_chain import get_chain_slice
from services.vol_surface import Surface, ScenarioSurface, build_surface, apply_scenario
from services.strategy_engine import price_combo, expected_pnl_scenario
from services.strategy_engine import price_combo, expected_pnl_scenario, to_native
from services.strategy_templates import generate_all, strikes_for
MAX_SEEDS_FOR_RESIDUAL_SEARCH = 40
@@ -168,4 +168,4 @@ def optimize(
scored.extend(refined)
scored.sort(key=lambda c: c["score"], reverse=True)
return _dedup_top_n(scored, top_n)
return to_native(_dedup_top_n(scored, top_n))