
    iG$                    n    U d Z ddlmZ ddlmZmZ ddlmZ ddlm	Z	m
Z
mZmZ  G d de      ZdZd	ed
<   y)uA  
APEX V16 — Brain abstract interface.

Both Brain TF (Trend Following) and Brain MR (Mean Reversion) inherit
from BrainBase. This enforces a SINGLE contract for the Brain<->Orchestrator
boundary, fixing V15-BUG-4 (manage_exit signature differed between TF and MR).

Brain responsibilities:
  - evaluate_entry: given current tech indicators, decide if a new
    trade should be opened. Returns EntryDecision or None.
  - manage_exit: given a BrainContext (entry snapshot + runtime + tech_now),
    decide HOLD / EXIT / PARTIAL_50 / MOVE_SL.

Brain MUST NOT:
  - Place orders directly (orchestrator's job)
  - Mutate state files (persistence layer's job)
  - Talk to broker (broker layer's job)

Brain MAY:
  - Call AI (Gemini) for decision support
  - Read tech_now and entry snapshot
  - Log to brain_log.jsonl via injected logger
    )annotations)ABCabstractmethod)Optional)BrainContextBrainDecisionEntryDecisionTradeActionc                      e Zd ZU dZdZded<   ddZe	 	 	 	 dd       Zedd       Z	ddd	Z
	 	 	 	 	 	 dd
ZdddZddZeddd       Z	 	 d	 	 	 	 	 	 	 	 	 ddZedd       Zedd       Ze	 	 	 	 	 	 	 	 dd       Zy)	BrainBasez
    Abstract base class for all Brains.

    Subclasses MUST implement evaluate_entry() and manage_exit().
    Subclasses MAY override _hold(), _exit() helpers if desired,
    but the default implementations should suffice.
    BASEstrnamec                    || _         y)zg
        Args:
            config: RuntimeConfig instance (for ai_model, risk_per_trade, etc.)
        N)config)selfr   s     '/home/work/apex_v16/brain/brain_base.py__init__zBrainBase.__init__1   s    
     c                  K   t         w)u|  
        Evaluate whether to open a new trade for `symbol` given current tech.

        Async because implementations may call AIClient.ask_for_decision().

        Args:
            symbol: instrument root.
            tech: TechSnapshot (analysis.tech_snapshot.TechSnapshot).
                  Untyped here to avoid a circular import; subclasses
                  consume it via attribute access (snap.rsi, snap.atr_ratio,
                  etc.). V15-BUG-3 fix: no more dict-with-fallbacks.
            bias_data: BiasData from BiasResolver (Source A — algo + AI
                  override). Subclasses MUST read allowed_direction / bias
                  from this parameter, NOT from tech.* (Source B is algo-
                  only and ignores AI override).

        Returns:
            EntryDecision if a setup is found, None otherwise.

        Implementations must:
          - Check Brain-specific entry rules (RSI extremes for MR,
            pullback patterns for TF, etc.)
          - Compute proposed entry/SL/TP prices
          - Assign confidence 0-100
          - Return None if no valid setup (most calls return None)
        NotImplementedError)r   symboltech	bias_datas       r   evaluate_entryzBrainBase.evaluate_entry<   s     D "!   	c                   K   t         w)u  
        Decide HOLD / EXIT / PARTIAL_50 / MOVE_SL for an open trade.

        Async because implementations may call AIClient.ask_for_decision().

        Args:
            ctx: BrainContext with entry snapshot, runtime state, tech_now.

        Returns:
            BrainDecision with action and reason.

        Implementations must:
          - Default to HOLD when uncertain (conservative)
          - Use ctx.entry (immutable snapshot) for "vs entry" comparisons,
            NEVER fall back to ctx.tech_now for entry-side data
          - Respect grace periods (e.g. minutes_open < N -> HOLD)
          - Be deterministic where possible (logged decisions reproducible)
          - NOT mutate ctx.runtime — orchestrator owns runtime updates.
            Brain may signal updates via BrainDecision.metadata
            (e.g. {"evaluated_candle_time": <ts>}) for the orchestrator
            to apply before persisting.
        r   )r   ctxs     r   manage_exitzBrainBase.manage_exitd   s     0 "!r   Nc                V    t        t        j                  j                  ||xs i       S N)actionreasonmetadata)r   r
   HOLDvaluer   r$   r%   s      r   _holdzBrainBase._hold   (    ##))^
 	
r   c                l   t        | dd      y|j                  xs i }|j                  dd      }	 | j                  j                  j                  d|j                  j                  | j                  |j                  ||j                  d      t        |j                  j                  d      t        |j                  j                  d      t        |j                  j                  d      t!        t        |j"                  d	d
      xs d
      |j$                  xs ddd        y# t&        $ r Y yw xY w)z?Observability: every manage_exit return is logged to brain_log.loggerNtriggerunknownmanage_exit_decisionai_      priceg            )
r   brainr#   r-   	ai_calledminutes_opennet_profit_usdprogress_pctcurrent_pricer$   )getattrr%   getr,   	brain_logwriteentryr   r   r#   
startswithroundruntimer8   r9   r:   floattech_nowr$   	Exception)r   r   decisionmetar-   s        r   _emit_manage_decision_logz#BrainBase._emit_manage_decision_log   s    44(0  &B((9i0	KK!!''&yy''ii!,,U3"3;;#;#;Q?$S[[%?%?C"3;;#;#;Q?#GCLL'3$G$N3O -2t4 (   		s   C4D' '	D32D3c                V    t        t        j                  j                  ||xs i       S r"   )r   r
   EXITr'   r(   s      r   _exitzBrainBase._exit   r*   r   c                L    t        t        j                  j                  |      S )N)r#   r$   )r   r
   
PARTIAL_50r'   )r   r$   s     r   _partial_50zBrainBase._partial_50   s    K$:$:$@$@PPr   c                F    |dk  r| S t        t        | |z        |z  |      S )ur  
        Align `price` to the instrument's tick grid. Returns `price`
        unchanged when `tick_size <= 0` (defensive — should not happen
        for live instruments). The `digits` arg trims float artifacts
        introduced by the multiply (V16 incident 29 apr: 6C 0.7324928... at
        16 decimals on a 0.00005 tick grid silently rejected by broker).
        r   )rB   )r3   	tick_sizedigitss      r   _round_to_tickzBrainBase._round_to_tick   s,     >LU59,-	96BBr   c                ~    d|i}|r|j                  |       t        t        j                  j                  |||      S )uW  
        Emit a MOVE_SL decision. `sl_price` MUST already be tick-aligned
        by the caller (use `_round_to_tick(price, tech.tick_size)` at the
        site where `sl_price` is computed). Brain emits the same value
        the broker will receive — keeps logs and broker state in sync.

        Args:
            sl_price: new stop-loss price (entry price for breakeven,
                      computed price for trailing). Tick-aligned.
            reason: human-readable rationale (logged).
            target: "breakeven" or "trailing" — encodes intent in metadata
                    so callers (orchestrator, log analytics) can tell
                    them apart without parsing the reason string.
            extra_metadata: any additional fields to merge into metadata
                            (e.g. {"trailing_atr_mult": 1.5}).
        	sl_target)r#   r$   
move_sl_tor%   )updater   r
   MOVE_SLr'   )r   sl_pricer$   targetextra_metadatarH   s         r   _move_slzBrainBase._move_sl   sB    . V$KK'&&,,	
 	
r   c                (    | j                         dk(  S )NBUYupper	directions    r   is_longzBrainBase.is_long   s     E))r   c                (    | j                         dk(  S )NSELLr_   ra   s    r   is_shortzBrainBase.is_short   s     F**r   c                <    | j                         dk(  r||z
  S ||z
  S )a  
        Returns positive value when RSI has moved AGAINST the trade.
        Negative or zero means RSI moved with the trade.

        For LONG: against = RSI dropped (rsi_at_entry - rsi_now positive)
        For SHORT: against = RSI rose (rsi_now - rsi_at_entry positive)
        r^   r_   )rb   rsi_at_entryrsi_nows      r   rsi_moved_againstzBrainBase.rsi_moved_against   s)     ??%'))%%r   )returnNone)r   r   rk   Optional[EntryDecision])r   r   rk   r   )N)r$   r   r%   Optional[dict]rk   r   )r   r   rG   r   rk   rl   )r$   r   rk   r   )   )r3   rD   rQ   rD   rR   intrk   rD   )	breakevenN)
rY   rD   r$   r   rZ   r   r[   rn   rk   r   )rb   r   rk   bool)rb   r   rh   rD   ri   rD   rk   rD   )__name__
__module____qualname____doc__r   __annotations__r   r   r   r    r)   rI   rL   rO   staticmethodrS   r\   rc   rf   rj    r   r   r   r   &   s4    D# !"!" 
!!" !"N " ":
+8	2
Q 
C 
C  ")-

 
 	

 '
 

J * * + + &&& & 
	& &r   r   Nrm   NO_ENTRY)rv   
__future__r   abcr   r   typingr   core.contractsr   r   r	   r
   r   rz   rw   ry   r   r   <module>r      s;   0 # #  O& O&l %)
! (r   