repo hygiene: track the PINK launcher import closure
67 production .py modules that the running PINK service imports but which were never committed: prod/bingx/ (HTTP client, market/user streams, journal, config), prod/clean_arch/ adapters/persistence/runtime/dita/dita_v2 production modules and their co-located tests. Rule going forward: every module imported by launch_dolphin_pink.py / pink_direct.py must appear in git ls-files. Excludes _backup dirs, __pycache__, and non-code files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
132
prod/clean_arch/dita/intent.py
Normal file
132
prod/clean_arch/dita/intent.py
Normal file
@@ -0,0 +1,132 @@
|
||||
"""Intent planning layer."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from .contracts import Decision, DecisionAction, DecisionConfig, DecisionContext, Intent, IntentContext, TradePosition, TradeSide, TradeStage
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class IntentPlanResult:
|
||||
intent: Intent
|
||||
trade_id_created: bool
|
||||
|
||||
|
||||
class IntentEngine:
|
||||
"""Converts a pure decision into an executable intent.
|
||||
|
||||
This is where sizing and trade identity are attached.
|
||||
"""
|
||||
|
||||
def __init__(self, config: Optional[DecisionConfig] = None):
|
||||
self.config = config or DecisionConfig()
|
||||
|
||||
def plan(
|
||||
self,
|
||||
decision: Decision,
|
||||
context: IntentContext,
|
||||
position: Optional[TradePosition] = None,
|
||||
) -> IntentPlanResult:
|
||||
if decision.action == DecisionAction.ENTER:
|
||||
return self._plan_entry(decision, context)
|
||||
if decision.action == DecisionAction.EXIT and position is not None:
|
||||
return self._plan_exit(decision, context, position)
|
||||
return IntentPlanResult(
|
||||
intent=Intent(
|
||||
timestamp=decision.timestamp,
|
||||
trade_id=decision.decision_id.replace("-D-", "-T-"),
|
||||
decision_id=decision.decision_id,
|
||||
asset=decision.asset,
|
||||
action=decision.action,
|
||||
side=decision.side,
|
||||
reason=decision.reason,
|
||||
target_size=0.0,
|
||||
leverage=1.0,
|
||||
reference_price=decision.reference_price,
|
||||
confidence=decision.confidence,
|
||||
bars_held=0,
|
||||
stage=TradeStage.INTENT_CREATED,
|
||||
exit_leg_ratios=self.config.exit_leg_ratios,
|
||||
metadata={"policy_version": self.config.policy_version, **decision.metadata},
|
||||
),
|
||||
trade_id_created=False,
|
||||
)
|
||||
|
||||
def _plan_entry(self, decision: Decision, context: IntentContext) -> IntentPlanResult:
|
||||
price = decision.reference_price
|
||||
confidence = max(0.05, min(1.0, decision.confidence))
|
||||
# Honor the decision's sizing when present (BLUE-parity cubic sizer
|
||||
# attaches leverage + target_size in DecisionEngine._decide_entry).
|
||||
# For legacy decisions the recompute below yields the identical values,
|
||||
# so preferring the decision's numbers is behavior-preserving.
|
||||
if decision.leverage and decision.leverage > 0 and decision.target_size and decision.target_size > 0:
|
||||
leverage = float(decision.leverage)
|
||||
target_size = float(decision.target_size)
|
||||
target_exposure = target_size * price if price > 0 else 0.0
|
||||
else:
|
||||
leverage = min(self.config.max_leverage, max(1.0, 1.0 + confidence * (self.config.max_leverage - 1.0)))
|
||||
target_exposure = context.capital * self.config.capital_fraction * leverage
|
||||
target_size = target_exposure / price if price > 0 else 0.0
|
||||
trade_id = self._trade_id(decision.asset, context.trade_seq + 1)
|
||||
return IntentPlanResult(
|
||||
intent=Intent(
|
||||
timestamp=decision.timestamp,
|
||||
trade_id=trade_id,
|
||||
decision_id=decision.decision_id,
|
||||
asset=decision.asset,
|
||||
action=decision.action,
|
||||
side=decision.side,
|
||||
reason=decision.reason,
|
||||
target_size=target_size,
|
||||
leverage=leverage,
|
||||
reference_price=price,
|
||||
confidence=confidence,
|
||||
bars_held=0,
|
||||
stage=TradeStage.INTENT_CREATED,
|
||||
exit_leg_ratios=self.config.exit_leg_ratios,
|
||||
metadata={
|
||||
"policy_version": self.config.policy_version,
|
||||
"target_exposure": target_exposure,
|
||||
"entry_velocity_divergence": decision.velocity_divergence,
|
||||
"entry_irp_alignment": decision.irp_alignment,
|
||||
**decision.metadata,
|
||||
},
|
||||
),
|
||||
trade_id_created=True,
|
||||
)
|
||||
|
||||
def _plan_exit(self, decision: Decision, context: IntentContext, position: TradePosition) -> IntentPlanResult:
|
||||
exit_ratio = position.next_exit_ratio()
|
||||
target_size = position.size * exit_ratio if exit_ratio > 0 else position.size
|
||||
return IntentPlanResult(
|
||||
intent=Intent(
|
||||
timestamp=decision.timestamp,
|
||||
trade_id=position.trade_id,
|
||||
decision_id=decision.decision_id,
|
||||
asset=position.asset,
|
||||
action=decision.action,
|
||||
side=position.side,
|
||||
reason=decision.reason,
|
||||
target_size=target_size,
|
||||
leverage=position.leverage,
|
||||
reference_price=decision.reference_price,
|
||||
confidence=decision.confidence,
|
||||
bars_held=position.bars_held,
|
||||
stage=TradeStage.INTENT_CREATED,
|
||||
exit_leg_ratios=position.exit_leg_ratios,
|
||||
metadata={
|
||||
"policy_version": self.config.policy_version,
|
||||
"exit_ratio": exit_ratio,
|
||||
"remaining_size_before": position.size,
|
||||
**decision.metadata,
|
||||
},
|
||||
),
|
||||
trade_id_created=False,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _trade_id(symbol: str, seq: int) -> str:
|
||||
return f"{symbol}-T-{seq:012d}"
|
||||
Reference in New Issue
Block a user