"""Institutional execution module for stealth trading.

Impact-aware execution algorithms that minimise market footprint when
building or unwinding large positions in prediction markets.

Algorithms (extend :class:`ExecAlgo`):
- **AdaptiveTWAP** -- fill-rate adaptive time-slicing with spread filter.
- **IcebergPlus** -- randomised visible size with jittered refill delay.
- **SniperAlgo** -- waits for tight spread + favourable imbalance, then fires.

Functions:
- :func:`estimate_impact` -- pre-trade impact via Kyle's lambda / spread / vol.
- :func:`smart_route` -- greedy lowest-cost venue routing.
- :func:`stealth_execute` -- one-call: pick algo, route, start execution.
- :func:`stealth_executor` -- pipeline factory for ``hz.run()``.

All public functions require the ``ultra`` license tier.
"""

from __future__ import annotations

import logging
import math
import random
import time
from dataclasses import dataclass, field
from typing import Any, Callable

from horizon._horizon import (
    Engine,
    OrderRequest,
    OrderSide,
    Side,
    auth_require_ultra,
    kyles_lambda,
    effective_spread,
    lob_imbalance,
    weighted_mid,
    estimate_volatility,
)
from horizon.algos import ExecAlgo
from horizon.context import Context

logger = logging.getLogger("horizon.stealth")


# ---------------------------------------------------------------------------
# Data classes
# ---------------------------------------------------------------------------


@dataclass(frozen=True)
class ImpactEstimate:
    """Pre-trade market impact estimation."""

    market_id: str
    size: float
    estimated_slippage_bps: float
    kyle_lambda: float
    effective_spread_bps: float
    volatility: float
    recommended_strategy: str
    estimated_duration_secs: float


@dataclass(frozen=True)
class SmartRoute:
    """Single venue-level routing decision."""

    exchange: str
    size_fraction: float
    limit_price: float
    estimated_fill_prob: float
    fee_rate: float
    estimated_cost_bps: float


@dataclass(frozen=True)
class ExecutionPlan:
    """Full execution plan returned by :func:`stealth_execute`."""

    strategy: str
    child_orders: list[dict]
    total_size: float
    estimated_cost_bps: float
    estimated_duration_secs: float
    routes: list[SmartRoute]


@dataclass
class StealthConfig:
    """Mutable configuration for stealth execution algorithms."""

    randomize: bool = True
    min_delay_secs: float = 0.5
    max_delay_secs: float = 5.0
    max_visible_pct: float = 0.10
    impact_limit_bps: float = 50.0
    patience_secs: float = 120.0
    adaptive_aggression: bool = True


# ---------------------------------------------------------------------------
# Module-level registry for active algos created by stealth_execute()
# ---------------------------------------------------------------------------

_active_algos: dict[str, list[ExecAlgo]] = {}


# ---------------------------------------------------------------------------
# Execution algorithms
# ---------------------------------------------------------------------------


class AdaptiveTWAP(ExecAlgo):
    """Time-sliced execution with adaptive pacing and spread filter."""

    def __init__(
        self,
        engine: Engine,
        duration_secs: float,
        num_slices: int,
        config: StealthConfig | None = None,
    ) -> None:
        super().__init__(engine)
        self.duration_secs = duration_secs
        self.num_slices = max(num_slices, 1)
        self.config = config or StealthConfig()
        self._slice_interval: float = 0.0
        self._next_slice_time: float = 0.0
        self._slices_sent: int = 0
        self._slice_size: float = 0.0
        self._fill_rate_history: list[float] = []
        self._last_spread: float = 0.0

    def _on_start(self) -> None:
        self._slice_interval = self.duration_secs / self.num_slices
        self._slice_size = self._target_size / self.num_slices
        self._next_slice_time = time.time()
        self._slices_sent = 0
        self._fill_rate_history = []
        self._last_spread = 0.0
        logger.info(
            "AdaptiveTWAP started: %d slices of %.4f over %.1fs",
            self.num_slices, self._slice_size, self.duration_secs,
        )

    def on_tick(self, current_price: float, timestamp: float) -> None:
        if self._complete or self._parent is None:
            return
        self._update_filled()
        if self._complete:
            logger.info("AdaptiveTWAP complete (filled during update)")
            return

        # Adaptive pacing based on fill rate
        if self._slices_sent > 0 and self._slice_size > 0:
            fill_rate = self._total_filled / (self._slices_sent * self._slice_size)
            self._fill_rate_history.append(round(fill_rate, 4))
            if self.config.adaptive_aggression:
                if fill_rate > 0.8:
                    self._slice_interval *= 1.5
                    logger.debug("AdaptiveTWAP slowing: fill_rate=%.2f", fill_rate)
                elif fill_rate < 0.2:
                    self._slice_interval *= 0.75
                    logger.debug("AdaptiveTWAP speeding: fill_rate=%.2f", fill_rate)

        # Spread-widening filter
        market_id = self._parent.market_id
        try:
            snap = self.engine.feed_snapshot(market_id)
            if snap is not None and snap.bid > 0 and snap.ask > 0:
                mid = (snap.bid + snap.ask) / 2.0
                is_buy = self._parent.order_side == OrderSide.Buy
                cur_spread = effective_spread(current_price, mid, is_buy)
                if self._last_spread > 0 and cur_spread > 2.0 * self._last_spread:
                    logger.debug("AdaptiveTWAP skip: spread %.4f->%.4f", self._last_spread, cur_spread)
                    return
                self._last_spread = cur_spread
        except Exception:
            pass

        # Slice submission
        now = time.time()
        if self._slices_sent >= self.num_slices or now < self._next_slice_time:
            return

        slice_size = self._slice_size
        if self.config.randomize:
            slice_size *= random.uniform(0.8, 1.2)
            slice_size = round(slice_size, 4)

        order_id = self._submit_child(slice_size, current_price)
        if order_id is not None:
            logger.debug(
                "AdaptiveTWAP slice %d/%d: size=%.4f price=%.4f id=%s",
                self._slices_sent + 1, self.num_slices,
                slice_size, current_price, order_id,
            )
        self._slices_sent += 1
        self._next_slice_time = now + self._slice_interval

        if self._slices_sent >= self.num_slices:
            self._update_filled()
            logger.info(
                "AdaptiveTWAP all slices sent, filled=%.4f/%.4f",
                self._total_filled, self._target_size,
            )


class IcebergPlus(ExecAlgo):
    """Iceberg with randomised visible size and jittered refill delay."""

    def __init__(
        self,
        engine: Engine,
        show_size: float,
        config: StealthConfig | None = None,
    ) -> None:
        super().__init__(engine)
        self.base_show_size = show_size
        self.config = config or StealthConfig()
        self._visible_order_id: str | None = None
        self._last_refill_time: float = 0.0

    def _on_start(self) -> None:
        self._visible_order_id = None
        self._last_refill_time = 0.0
        logger.info(
            "IcebergPlus started: show=%.4f target=%.4f max_vis=%.2f",
            self.base_show_size, self._target_size, self.config.max_visible_pct,
        )

    def on_tick(self, current_price: float, timestamp: float) -> None:
        if self._complete or self._parent is None:
            return
        self._update_filled()
        if self._complete:
            logger.info("IcebergPlus complete (filled during update)")
            return

        # Check if visible order is still live
        need_new = self._visible_order_id is None
        if not need_new:
            try:
                open_orders = self.engine.open_orders()
                if not any(o.id == self._visible_order_id for o in open_orders):
                    logger.debug("IcebergPlus visible %s filled", self._visible_order_id)
                    need_new = True
            except Exception as exc:
                logger.warning("IcebergPlus open_orders failed: %s", exc)
                need_new = True

        if not need_new:
            return

        # Enforce random inter-refill delay
        now = time.time()
        if self._last_refill_time > 0:
            delay = random.uniform(self.config.min_delay_secs, self.config.max_delay_secs)
            if now - self._last_refill_time < delay:
                return

        # Compute visible size
        remaining = self._target_size - self._total_filled
        if remaining <= 0:
            self._complete = True
            logger.info("IcebergPlus complete (no remaining)")
            return

        if self.config.randomize:
            show_size = random.uniform(0.5 * self.base_show_size, 1.5 * self.base_show_size)
        else:
            show_size = self.base_show_size

        max_visible = self.config.max_visible_pct * remaining
        if max_visible > 0:
            show_size = min(show_size, max_visible)
        show_size = min(round(show_size, 4), remaining)

        if show_size <= 0:
            self._complete = True
            return

        order_id = self._submit_child(show_size, current_price)
        self._visible_order_id = order_id
        self._last_refill_time = now
        if order_id is not None:
            logger.debug(
                "IcebergPlus slice: size=%.4f price=%.4f id=%s remain=%.4f",
                show_size, current_price, order_id, remaining - show_size,
            )


class SniperAlgo(ExecAlgo):
    """Opportunistic single-shot execution on favourable microstructure."""

    def __init__(
        self,
        engine: Engine,
        target_spread: float,
        config: StealthConfig | None = None,
    ) -> None:
        super().__init__(engine)
        self.target_spread = target_spread
        self.config = config or StealthConfig()
        self._start_time: float = 0.0
        self._fired: bool = False

    def _on_start(self) -> None:
        self._start_time = time.time()
        self._fired = False
        logger.info(
            "SniperAlgo started: spread=%.4f patience=%.1fs size=%.4f",
            self.target_spread, self.config.patience_secs, self._target_size,
        )

    def on_tick(self, current_price: float, timestamp: float) -> None:
        if self._fired or self._complete:
            return
        self._update_filled()
        if self._complete or self._parent is None:
            return

        market_id = self._parent.market_id

        # Read spread
        try:
            snap = self.engine.feed_snapshot(market_id)
            if snap is None or snap.bid <= 0 or snap.ask <= 0:
                return
            mid = (snap.bid + snap.ask) / 2.0
            is_buy = self._parent.order_side == OrderSide.Buy
            cur_spread = effective_spread(current_price, mid, is_buy)
        except Exception as exc:
            logger.debug("SniperAlgo spread err: %s", exc)
            return

        # Read imbalance
        try:
            bids = [(snap.bid, 1.0)]
            asks = [(snap.ask, 1.0)]
            imbalance = lob_imbalance(bids, asks, 1)
        except Exception as exc:
            logger.debug("SniperAlgo imbalance err: %s", exc)
            return

        # Patience-based threshold relaxation
        elapsed = time.time() - self._start_time
        patience_secs = max(self.config.patience_secs, 1.0)
        patience_fraction = min(elapsed / patience_secs, 1.0)
        adjusted_target = self.target_spread * (1.0 + patience_fraction)

        logger.debug(
            "SniperAlgo: spread=%.6f adj_target=%.6f imb=%.4f patience=%.0f%%",
            cur_spread, adjusted_target, imbalance, patience_fraction * 100,
        )

        # Fire conditions
        spread_ok = cur_spread < adjusted_target
        is_buy = self._parent.order_side == OrderSide.Buy
        imbalance_ok = (is_buy and imbalance > 0) or (not is_buy and imbalance < 0)
        if patience_fraction >= 1.0:
            imbalance_ok = True

        if spread_ok and imbalance_ok:
            remaining = self._target_size - self._total_filled
            if remaining <= 0:
                self._complete = True
                return
            logger.info(
                "SniperAlgo firing: size=%.4f price=%.4f spread=%.6f imb=%.4f",
                remaining, current_price, cur_spread, imbalance,
            )
            self._submit_child(remaining, current_price)
            self._fired = True


# ---------------------------------------------------------------------------
# Impact estimation
# ---------------------------------------------------------------------------


def estimate_impact(
    engine: Engine,
    market_id: str,
    size: float,
    feed_name: str | None = None,
) -> ImpactEstimate:
    """Estimate market impact of executing *size* contracts.

    Uses Kyle's lambda, effective spread, and volatility.  Falls back
    to conservative defaults when live data is unavailable.
    """
    auth_require_ultra()

    name = feed_name or market_id
    logger.debug("estimate_impact: market=%s size=%.4f", market_id, size)

    # Kyle's lambda
    kyle_lam = 0.1
    try:
        snapshots = engine.all_feed_snapshots()
        prices = [s.price for s in snapshots.values() if s.price > 0]
        if len(prices) >= 2:
            changes = [prices[i] - prices[i - 1] for i in range(1, len(prices))]
            volumes = [1.0] * len(changes)
            kyle_lam = kyles_lambda(changes, volumes)
            kyle_lam = abs(kyle_lam) if not math.isnan(kyle_lam) else 0.1
    except Exception as exc:
        logger.debug("estimate_impact: kyle fallback (%s)", exc)

    # Effective spread
    eff_spread = 0.02
    try:
        snap = engine.feed_snapshot(name)
        if snap is not None and snap.bid > 0 and snap.ask > 0:
            mid = (snap.bid + snap.ask) / 2.0
            eff_spread = effective_spread(snap.ask, mid, True)
            if math.isnan(eff_spread) or eff_spread <= 0:
                eff_spread = 0.02
    except Exception as exc:
        logger.debug("estimate_impact: spread fallback (%s)", exc)

    # Volatility
    vol = 0.1
    try:
        snapshots = engine.all_feed_snapshots()
        price_series = [s.price for s in snapshots.values() if s.price > 0]
        if len(price_series) >= 3:
            vol = estimate_volatility(price_series)
            if math.isnan(vol) or vol <= 0:
                vol = 0.1
    except Exception as exc:
        logger.debug("estimate_impact: vol fallback (%s)", exc)

    # Slippage
    slippage_bps = kyle_lam * math.sqrt(abs(size)) * 10_000
    eff_spread_bps = eff_spread * 10_000

    # Strategy recommendation
    if slippage_bps < 10:
        strategy, duration = "market", 0.0
    elif slippage_bps < 30:
        strategy, duration = "twap", 300.0
    elif slippage_bps < 100:
        strategy, duration = "iceberg", 600.0
    else:
        strategy, duration = "sniper", 120.0

    logger.info(
        "estimate_impact: slippage=%.2f bps -> %s (%.0fs)",
        slippage_bps, strategy, duration,
    )

    return ImpactEstimate(
        market_id=market_id,
        size=round(size, 2),
        estimated_slippage_bps=round(slippage_bps, 4),
        kyle_lambda=round(kyle_lam, 4),
        effective_spread_bps=round(eff_spread_bps, 4),
        volatility=round(vol, 4),
        recommended_strategy=strategy,
        estimated_duration_secs=round(duration, 2),
    )


# ---------------------------------------------------------------------------
# Smart routing
# ---------------------------------------------------------------------------

_FEE_RATES: dict[str, float] = {
    "paper": 0.0,
    "polymarket": 0.002,
    "kalshi": 0.07,
}


def smart_route(
    engine: Engine,
    market_id: str,
    side: Side,
    order_side: OrderSide,
    size: float,
    exchanges: list[str] | None = None,
) -> list[SmartRoute]:
    """Compute cost-minimising routing plan across venues.

    Estimates fill probability from spread and total cost (half-spread +
    fees).  Greedily allocates to cheapest venue first.
    """
    auth_require_ultra()

    if exchanges is None:
        exchanges = ["paper"]
    logger.debug("smart_route: market=%s size=%.4f venues=%s", market_id, size, exchanges)

    raw: list[dict[str, Any]] = []
    for exchange in exchanges:
        limit_price, spread = 0.50, 0.02
        try:
            snap = engine.feed_snapshot(market_id)
            if snap is not None and snap.bid > 0 and snap.ask > 0:
                spread = snap.ask - snap.bid
                if order_side == OrderSide.Buy:
                    limit_price = snap.bid + spread * 0.4
                else:
                    limit_price = snap.ask - spread * 0.4
        except Exception as exc:
            logger.debug("smart_route: feed err on %s: %s", exchange, exc)

        fill_prob = max(0.1, min(1.0, 1.0 - spread * 10.0))
        fee_rate = _FEE_RATES.get(exchange, 0.01)
        cost_bps = (spread / 2.0 + fee_rate) * 10_000

        raw.append({
            "exchange": exchange,
            "limit_price": round(limit_price, 4),
            "fill_prob": round(fill_prob, 4),
            "fee_rate": fee_rate,
            "cost_bps": round(cost_bps, 4),
        })

    raw.sort(key=lambda r: r["cost_bps"])

    result: list[SmartRoute] = []
    remaining = size
    for i, route in enumerate(raw):
        if remaining <= 0:
            break
        if i < len(raw) - 1:
            fraction = 0.6 if i == 0 else 0.3
            alloc = min(size * fraction, remaining)
        else:
            alloc = remaining
        if alloc <= 0:
            continue
        size_frac = round(alloc / size, 4) if size > 0 else 0.0
        remaining -= alloc
        result.append(SmartRoute(
            exchange=route["exchange"],
            size_fraction=size_frac,
            limit_price=route["limit_price"],
            estimated_fill_prob=route["fill_prob"],
            fee_rate=route["fee_rate"],
            estimated_cost_bps=route["cost_bps"],
        ))

    # Dump any unallocated remainder into first route
    if remaining > 0 and result:
        f = result[0]
        result[0] = SmartRoute(
            exchange=f.exchange,
            size_fraction=round(f.size_fraction + remaining / size, 4),
            limit_price=f.limit_price,
            estimated_fill_prob=f.estimated_fill_prob,
            fee_rate=f.fee_rate,
            estimated_cost_bps=f.estimated_cost_bps,
        )

    logger.info("smart_route: %d routes for %.4f in %s", len(result), size, market_id)
    return result


# ---------------------------------------------------------------------------
# One-call stealth execution
# ---------------------------------------------------------------------------


def stealth_execute(
    engine: Engine,
    market_id: str,
    side: Side,
    order_side: OrderSide,
    size: float,
    price: float,
    strategy: str = "auto",
    config: StealthConfig | None = None,
    exchanges: list[str] | None = None,
) -> ExecutionPlan:
    """Start stealth execution and return the plan.

    If *strategy* is ``"auto"``, :func:`estimate_impact` picks the best
    algorithm.  The algo is started immediately and registered in the
    module-level ``_active_algos`` dict for pipeline ticking.
    """
    auth_require_ultra()

    config = config or StealthConfig()
    logger.info(
        "stealth_execute: market=%s %s/%s size=%.4f price=%.4f strat=%s",
        market_id, side, order_side, size, price, strategy,
    )

    # Strategy selection
    if strategy == "auto":
        impact = estimate_impact(engine, market_id, size)
        strategy = impact.recommended_strategy
        estimated_duration = impact.estimated_duration_secs
        estimated_cost = impact.estimated_slippage_bps
        logger.info("stealth_execute: auto -> %s (%.2f bps)", strategy, estimated_cost)
    else:
        estimated_duration = {
            "twap": 300.0, "iceberg": 600.0,
            "sniper": config.patience_secs, "market": 0.0,
        }.get(strategy, 300.0)
        estimated_cost = 0.0

    # Create algo
    algo: ExecAlgo
    if strategy == "twap":
        n = max(int(estimated_duration / 10.0), 5)
        algo = AdaptiveTWAP(engine, estimated_duration, n, config)
    elif strategy == "iceberg":
        show = max(size * config.max_visible_pct, 1.0)
        algo = IcebergPlus(engine, show_size=show, config=config)
    elif strategy == "sniper":
        ts = 0.01
        try:
            snap = engine.feed_snapshot(market_id)
            if snap is not None and snap.bid > 0 and snap.ask > 0:
                ts = max((snap.ask - snap.bid) * 0.5, 0.001)
        except Exception:
            pass
        algo = SniperAlgo(engine, target_spread=ts, config=config)
    else:
        logger.warning("stealth_execute: unknown '%s', falling back to twap", strategy)
        strategy = "twap"
        n = max(int(estimated_duration / 10.0), 5)
        algo = AdaptiveTWAP(engine, estimated_duration, n, config)

    # Start execution
    request = OrderRequest(
        market_id=market_id, side=side, order_side=order_side,
        size=size, price=price,
    )
    algo.start(request)

    # Clean up _active_algos if it exceeds 100 entries
    total_algos = sum(len(v) for v in _active_algos.values())
    if total_algos > 100:
        # Remove completed algos first
        for k in list(_active_algos.keys()):
            _active_algos[k] = [a for a in _active_algos[k] if not a.is_complete]
            if not _active_algos[k]:
                del _active_algos[k]
        # If still over 100, clear the oldest entries
        total_algos = sum(len(v) for v in _active_algos.values())
        if total_algos > 100:
            keys = list(_active_algos.keys())
            while keys and sum(len(v) for v in _active_algos.values()) > 100:
                del _active_algos[keys.pop(0)]

    # Register in module-level registry
    if market_id not in _active_algos:
        _active_algos[market_id] = []
    _active_algos[market_id].append(algo)
    logger.debug("stealth_execute: registered in _active_algos[%s]", market_id)

    # Routing
    routes = smart_route(engine, market_id, side, order_side, size, exchanges)

    # Build child order plan
    child_orders: list[dict] = []
    if strategy == "twap" and isinstance(algo, AdaptiveTWAP):
        for i in range(algo.num_slices):
            child_orders.append({
                "sequence": i + 1, "type": "twap_slice",
                "size": round(algo._slice_size, 4),
                "delay_secs": round(algo._slice_interval * i, 2),
                "market_id": market_id,
                "side": str(side), "order_side": str(order_side),
            })
    elif strategy == "iceberg" and isinstance(algo, IcebergPlus):
        est_refills = max(int(size / algo.base_show_size), 1)
        for i in range(est_refills):
            child_orders.append({
                "sequence": i + 1, "type": "iceberg_refill",
                "size": round(algo.base_show_size, 4),
                "market_id": market_id,
                "side": str(side), "order_side": str(order_side),
            })
    elif strategy == "sniper":
        child_orders.append({
            "sequence": 1, "type": "sniper_fire",
            "size": round(size, 4), "market_id": market_id,
            "side": str(side), "order_side": str(order_side),
            "condition": "spread + imbalance",
        })

    # Aggregate cost from routes
    if estimated_cost <= 0 and routes:
        estimated_cost = sum(r.estimated_cost_bps * r.size_fraction for r in routes)

    plan = ExecutionPlan(
        strategy=strategy,
        child_orders=child_orders,
        total_size=round(size, 2),
        estimated_cost_bps=round(estimated_cost, 4),
        estimated_duration_secs=round(estimated_duration, 2),
        routes=routes,
    )
    logger.info(
        "stealth_execute: plan ready -- %s %d children %.2f bps %.0fs %d routes",
        plan.strategy, len(plan.child_orders), plan.estimated_cost_bps,
        plan.estimated_duration_secs, len(plan.routes),
    )
    return plan


# ---------------------------------------------------------------------------
# Pipeline factory
# ---------------------------------------------------------------------------


def stealth_executor(
    config: StealthConfig | None = None,
) -> Callable[[Context], None]:
    """Pipeline function that drives stealth execution algorithms.

    Reads ``ctx.params["stealth_requests"]`` (list of request dicts),
    starts algos, ticks active ones each cycle, and cleans up completed.
    Injects ``ctx.params["stealth_active_count"]``.
    """
    auth_require_ultra()

    config = config or StealthConfig()
    active_algos: dict[str, list[ExecAlgo]] = {}
    logger.info("stealth_executor: factory created (rand=%s patience=%.1fs)", config.randomize, config.patience_secs)

    def _inner(ctx: Context) -> None:
        nonlocal active_algos

        engine: Engine | None = ctx.params.get("engine")
        if engine is None:
            return
        market_id = ctx.market.id if ctx.market is not None else ""
        if not market_id:
            return

        # Process pending stealth requests
        requests: list[dict[str, Any]] = ctx.params.get("stealth_requests", [])
        for req in requests:
            try:
                req_market = req.get("market_id", market_id)
                req_size = float(req.get("size", 0))
                req_price = float(req.get("price", 0.5))
                req_strategy = req.get("strategy", "twap")
                if req_size <= 0:
                    logger.warning("stealth_executor: skip size=%.4f", req_size)
                    continue

                # Resolve side / order_side
                raw_s = req.get("side", "Yes")
                req_side = (Side.Yes if (isinstance(raw_s, str) and raw_s.lower() == "yes") else Side.No) if isinstance(raw_s, str) else raw_s
                raw_os = req.get("order_side", "Buy")
                req_os = (OrderSide.Buy if (isinstance(raw_os, str) and raw_os.lower() == "buy") else OrderSide.Sell) if isinstance(raw_os, str) else raw_os

                # Create algo
                algo: ExecAlgo
                if req_strategy == "iceberg":
                    show = max(req_size * config.max_visible_pct, 1.0)
                    algo = IcebergPlus(engine, show_size=show, config=config)
                elif req_strategy == "sniper":
                    ts = 0.01
                    try:
                        snap = engine.feed_snapshot(req_market)
                        if snap is not None and snap.bid > 0 and snap.ask > 0:
                            ts = max((snap.ask - snap.bid) * 0.5, 0.001)
                    except Exception:
                        pass
                    algo = SniperAlgo(engine, target_spread=ts, config=config)
                else:
                    dur = float(req.get("duration_secs", 300.0))
                    n = int(req.get("num_slices", max(int(dur / 10), 5)))
                    algo = AdaptiveTWAP(engine, dur, n, config)

                order_req = OrderRequest(
                    market_id=req_market, side=req_side,
                    order_side=req_os, size=req_size, price=req_price,
                )
                algo.start(order_req)

                if req_market not in active_algos:
                    active_algos[req_market] = []
                active_algos[req_market].append(algo)
                logger.info(
                    "stealth_executor: started %s for %s size=%.4f",
                    req_strategy, req_market, req_size,
                )
            except Exception as exc:
                logger.warning("stealth_executor: request failed: %s", exc)

        if requests:
            ctx.params["stealth_requests"] = []

        # Tick all active algos for this market
        current_price = 0.5
        try:
            snap = engine.feed_snapshot(market_id)
            if snap is not None and snap.price > 0:
                current_price = snap.price
        except Exception:
            pass

        ts = time.time()
        for algo in active_algos.get(market_id, []):
            if not algo.is_complete:
                try:
                    algo.on_tick(current_price, ts)
                except Exception as exc:
                    logger.warning("stealth_executor: tick failed: %s", exc)

        # Clean up completed
        before = len(active_algos.get(market_id, []))
        active_algos[market_id] = [a for a in active_algos.get(market_id, []) if not a.is_complete]
        after = len(active_algos[market_id])
        if before != after:
            logger.info("stealth_executor: cleaned %d algos for %s", before - after, market_id)
        if after == 0 and market_id in active_algos:
            del active_algos[market_id]

        # Inject status
        ctx.params["stealth_active_count"] = sum(len(v) for v in active_algos.values())

    _inner.__name__ = "stealth_executor"
    return _inner
