"""Executable research contract for K-ArtSell Aegis sell/reentry policies. This module is intentionally dependency-free. It is not a production trading engine. It exists to make policy ordering, units, invariants, and golden vectors reproducible. """ from __future__ import annotations from dataclasses import dataclass from datetime import datetime from decimal import Decimal, ROUND_HALF_EVEN from enum import Enum from typing import Iterable, Optional, Protocol ZERO = Decimal("0") ONE = Decimal("1") class SellAction(str, Enum): HOLD = "HOLD" PARTIAL_SELL = "PARTIAL_SELL" FULL_SELL = "FULL_SELL" @dataclass(frozen=True) class SellInput: position_lot_id: str cycle_id: str evidence_id: str dataset_id: str model_version: str config_version: str code_sha: str as_of: datetime published_at_cutoff: datetime current_portfolio_weight: Decimal strategic_core_floor_weight: Decimal hard_impairment_approved: bool = False capital_floor_breached: bool = False survival_sell_ratio_of_lot: Decimal = ZERO gap_below_floor_atr: Decimal = ZERO consecutive_close_breaches: int = 0 cooldown_satisfied: bool = True concentration_sell_ratio_of_lot: Decimal = ZERO opportunity_edge_lower_bound: Decimal = ZERO opportunity_sell_ratio_of_lot: Decimal = ZERO def validate(self) -> None: if self.published_at_cutoff > self.as_of: raise ValueError("published_at_cutoff cannot be later than as_of") for name, value in ( ("current_portfolio_weight", self.current_portfolio_weight), ("strategic_core_floor_weight", self.strategic_core_floor_weight), ("survival_sell_ratio_of_lot", self.survival_sell_ratio_of_lot), ("concentration_sell_ratio_of_lot", self.concentration_sell_ratio_of_lot), ("opportunity_sell_ratio_of_lot", self.opportunity_sell_ratio_of_lot), ): if not ZERO <= value <= ONE: raise ValueError(f"{name} must be between 0 and 1") if self.strategic_core_floor_weight > self.current_portfolio_weight: raise ValueError("strategic core cannot exceed current portfolio weight") if self.gap_below_floor_atr < ZERO: raise ValueError("gap_below_floor_atr cannot be negative") if self.consecutive_close_breaches < 0: raise ValueError("consecutive_close_breaches cannot be negative") def max_sell_ratio_preserving_core(self) -> Decimal: if self.current_portfolio_weight <= ZERO: return ZERO sellable_weight = max(ZERO, self.current_portfolio_weight - self.strategic_core_floor_weight) return min(ONE, sellable_weight / self.current_portfolio_weight) def target_weight_after(self, ratio: Decimal) -> Decimal: bounded = min(ONE, max(ZERO, ratio)) return (self.current_portfolio_weight * (ONE - bounded)).quantize( Decimal("0.00000001"), rounding=ROUND_HALF_EVEN ) @dataclass(frozen=True) class SellDecision: action: SellAction sell_ratio_of_lot: Decimal target_portfolio_weight_after: Decimal policy_id: str priority: int reason_code: str reentry_eligible: bool class SellPolicy(Protocol): priority: int policy_id: str def evaluate(self, value: SellInput) -> Optional[SellDecision]: ... class HardImpairmentPolicy: priority = 1000 policy_id = "ALG-SELL-001" def evaluate(self, value: SellInput) -> Optional[SellDecision]: if not value.hard_impairment_approved: return None return _decision(value, ONE, self.policy_id, self.priority, "HARD_IMPAIRMENT", False) class PortfolioSurvivalPolicy: priority = 900 policy_id = "ALG-SELL-PORT-001" def evaluate(self, value: SellInput) -> Optional[SellDecision]: if not value.capital_floor_breached or not value.cooldown_satisfied: return None ratio = min(ONE, max(ZERO, value.survival_sell_ratio_of_lot)) return None if ratio == ZERO else _decision( value, ratio, self.policy_id, self.priority, "PORTFOLIO_SURVIVAL", True ) class GapFloorPolicy: priority = 800 policy_id = "ALG-SELL-002" def evaluate(self, value: SellInput) -> Optional[SellDecision]: if not value.cooldown_satisfied or value.gap_below_floor_atr < Decimal("1.5"): return None ratio = min(Decimal("0.40"), value.max_sell_ratio_preserving_core()) return None if ratio == ZERO else _decision( value, ratio, self.policy_id, self.priority, "GAP_FLOOR_BREACH", True ) class TwoClosePolicy: priority = 700 policy_id = "ALG-SELL-003" def evaluate(self, value: SellInput) -> Optional[SellDecision]: if not value.cooldown_satisfied or value.consecutive_close_breaches < 2: return None ratio = min(Decimal("0.20"), value.max_sell_ratio_preserving_core()) return None if ratio == ZERO else _decision( value, ratio, self.policy_id, self.priority, "TWO_CLOSE_FLOOR_BREACH", True ) class ConcentrationPolicy: priority = 600 policy_id = "ALG-SELL-004" def evaluate(self, value: SellInput) -> Optional[SellDecision]: if not value.cooldown_satisfied: return None ratio = min(value.concentration_sell_ratio_of_lot, value.max_sell_ratio_preserving_core()) return None if ratio <= ZERO else _decision( value, ratio, self.policy_id, self.priority, "CONCENTRATION_OR_LIQUIDITY", True ) class OpportunityCostPolicy: priority = 500 policy_id = "ALG-SELL-005" def evaluate(self, value: SellInput) -> Optional[SellDecision]: if not value.cooldown_satisfied or value.opportunity_edge_lower_bound <= ZERO: return None requested = min(Decimal("0.25"), max(Decimal("0.10"), value.opportunity_sell_ratio_of_lot)) ratio = min(requested, value.max_sell_ratio_preserving_core()) return None if ratio <= ZERO else _decision( value, ratio, self.policy_id, self.priority, "OPPORTUNITY_REPLACEMENT", True ) class SellPolicyChain: def __init__(self, policies: Iterable[SellPolicy]): self._policies = tuple(sorted(policies, key=lambda p: (-p.priority, p.policy_id))) def evaluate(self, value: SellInput) -> SellDecision: value.validate() for policy in self._policies: decision = policy.evaluate(value) if decision is not None: return decision return SellDecision( SellAction.HOLD, ZERO, value.current_portfolio_weight, "ALG-HOLD-001", 0, "NO_SELL_CONDITION", False, ) def default_chain() -> SellPolicyChain: return SellPolicyChain( [ OpportunityCostPolicy(), ConcentrationPolicy(), TwoClosePolicy(), GapFloorPolicy(), PortfolioSurvivalPolicy(), HardImpairmentPolicy(), ] ) def _decision( value: SellInput, ratio: Decimal, policy_id: str, priority: int, reason: str, reentry_eligible: bool, ) -> SellDecision: action = SellAction.FULL_SELL if ratio == ONE else SellAction.PARTIAL_SELL return SellDecision( action, ratio, value.target_weight_after(ratio), policy_id, priority, reason, reentry_eligible, )