
    j+                         d Z ddlZddlmc mZ ddlZddl	Z
ddlZddlmZ  e
j                  d      Zd Zd ZddZd Zd	 Zd
 Zd Zd Zd Zy)u  
RED→GREEN acceptance test for the trailing-stop time-gating parity fix.

Pine gates BOTH `strategy.close` calls (score-exit and trailing-stop) on
`timeCondition = time >= startDate and time <= endDate`. So a position that is
open when the window ends is held open to the chart end — Pine cannot close it
after `endDate`.

Python's `_apply_trailing_stop` previously fired the trailing stop regardless of
the time window, so a trade open across `endDate` would trail-out in OOS while
TV held it open (the 4H "TV#106 open trade" parity break).

Given/When/Then:
  Given a trade entered just before `endDate` whose price drops below the trail
        stop only on bars AFTER `endDate`,
  When  the signals are generated with a Pine time gate (`_pine_time_end`),
  Then  no exit (trail or score) fires after `endDate` — the position is held
        open, matching Pine.

A second case (no time gate) asserts the trailing stop still fires normally, so
the fix does not disable the trail inside the trading window.
    N)_score_to_signalsz
2026-02-28c                     t        j                  ddd      } t        j                  g dt              }t        j                  g dt              }t        j                  g dt              }t        j
                  | |||d      S )	Nz
2026-02-24   D)periodsfreq)
      (   r   r   r   r   r   dtype)2   r   <   r   d   F   r   r   )timeactivation_scoreclosehigh)pd
date_rangenparrayfloat	DataFrame)timesscorer   r   s       w/Users/jameslopez/projects/TradingBot25/.worktrees/experiment-mlp-extreme-event-study/tests/test_mlp_trail_time_gate.py_fixture_dfr   "   sj     MM,<EHH=UKE HH=UKE88<EJD<<E"'7 8 8    c            	          dddddddddS )	Ng      Y@g      I@g     g      ?Fg      4@   )!i_long_entry_activation_threshold i_long_exit_activation_threshold-i_long_exit_activation_confirmation_thresholdi_use_long_exit_confirmationi_use_long_entry_confirmationi_trailing_stop_thresholdi_exit_score_windowi_entry_score_window r+   r    r   _base_paramsr,   1   s&     .3,09A(+).%)  !	 	r    c           	         t               }t        j                  t        |      t        j                        }t        || ||d   j                  t        j                        |d   j                  t        j                        ||      }|S )Nr   r   r   )entry_allowedforced_exit)r   r   zeroslenfloat64r   to_numpy)paramsr.   r/   df
stoch_peakouts         r   _runr8   @   sq    	B#b'4J

FJ
72::.
6
"**-#C Jr    c                     t               } t        j                  d      | d<   t        | d<   t	        |       }|d   j
                  d   }|sNt        j                  d      dz   dt        j                  |      iz  }t        t        j                  |            d	}|d
   t        kD  }|j                  |df   }|j                  } |       }| }|svt        j                  d      dz   t        j                  |      t        j                  |      t        j                  |      dz  }t        t        j                  |            d	x}x}x}}|d   j
                  d   }d}	||	k(  }|st        j                  d|fd||	f      t        j                  |      t        j                  |	      dz  }
t        j                  d      dz   d|
iz  }t        t        j                  |            d	x}x}}	y	)z?Given a Pine time gate, the trailing stop must NOT fire in OOS.z
2017-12-01_pine_time_start_pine_time_endexecute_entry   expected entry at idx2
>assert %(py1)spy1Nr   execute_exitu}   trailing stop fired after endDate — Python should hold the position open to match Pine's timeCondition-gated strategy.closezD
>assert not %(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py1)s.any
}()
}r@   py3py5positionr"   ==z%(py1)s == %(py4)sr@   py4z(position should remain open past endDate
>assert %(py6)spy6)r,   r   	TimestampENDr8   iloc
@pytest_ar_format_assertmsg	_safereprAssertionError_format_explanationlocany_call_reprcompare)r4   r7   @py_assert0@py_format2	after_end@py_assert2@py_assert4@py_assert6@py_format7@py_assert3@py_format5s              r   6test_trail_does_not_fire_after_endDate_when_time_gatedrb   M   s   ^F!#l!;F"F
v,C $$Q'A'AA)AAAA'AAAAA Fc!Iwwy.01 155 57 77 7  	B   2    6    8      
 z?#TqT#q(TTT#qTTT#TTTqTTT*TTTTTTTTr    c                     t               } t        |       }|d   j                  d   }|sNt        j                  d      dz   dt        j
                  |      iz  }t        t        j                  |            d}|d   j                  d   }|sNt        j                  d	      dz   dt        j
                  |      iz  }t        t        j                  |            d}|d
   j                  d   }d}||k(  }|st        j                  d|fd||f      t        j
                  |      t        j
                  |      dz  }t        j                  d      dz   d|iz  }t        t        j                  |            dx}x}}y)zFSanity: without a time gate, the trailing stop fires normally at idx5.r<   r=   r>   r?   r@   NrA      z5trailing stop should fire at idx5 without a time gaterE   rF   r   rG   rI   rJ   z/position should be closed after the trail firesrL   rM   )	r,   r8   rP   rQ   rR   rS   rT   rU   rX   )r4   r7   rY   rZ   r`   r\   ra   r_   s           r   *test_trail_still_fires_when_not_time_gatedre   a   s    ^F
v,C$$Q'A'AA)AAAA'AAAAA~##A&_&__(____&_____z?#[q[#q([[[#q[[[#[[[q[[[*[[[[[[[[r    c                     t        j                  dt              } d| d<   t        t	               |       }|d   }|j
                  } |       }| }|s`dt        j                  |      t        j                  |      t        j                  |      dz  }t        t        j                  |            d x}x}x}}|d	   }|j
                  } |       }| }|s`dt        j                  |      t        j                  |      t        j                  |      dz  }t        t        j                  |            d x}x}x}}|d
   }|j
                  } |       }| }|s`dt        j                  |      t        j                  |      t        j                  |      dz  }t        t        j                  |            d x}x}x}}y )Nr   r   Fr=   r.   r<   zBassert not %(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py1)s.any
}()
}rB   rA   rE   )
r   onesboolr8   r,   rW   rQ   rS   rT   rU   )allowedr7   rY   r\   r]   r^   r_   s          r   :test_entry_permission_mask_blocks_only_the_requested_entryrk   l   sC   ggat$GGAJ
|~W
5C?#)#'')'))))))))#)))'))))))))))>"("&&(&(((((((("(((&((((((((((:$""$"$$$$$$$$$$$"$$$$$$$$$$r    c            
         t        t                     } t        t               t        j                  dt                    }dD ]  }||   }|j
                  } |       }| |   }|j
                  } |       }||k(  }	|	st        j                  d|	fd||f      t        j                  |      t        j                  |      t        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd	|
iz  }t        t        j                  |            d x}x}x}x}	x}x}} y )
Nr   r   rg   rE   r<   rA   rG   z|%(py5)s
{%(py5)s = %(py3)s
{%(py3)s = %(py1)s.tolist
}()
} == %(py12)s
{%(py12)s = %(py10)s
{%(py10)s = %(py8)s.tolist
}()
}r@   rC   rD   py8py10py12assert %(py14)spy14)r8   r,   r   rh   ri   tolistrQ   rX   rS   rT   rU   baselinemaskedcolumnrY   r\   r]   @py_assert7@py_assert9@py_assert11r^   @py_format13@py_format15s               r   >test_all_true_entry_permission_mask_preserves_baseline_signalsr   w   s    LN#H,.0FGF?f~C~$$C$&C(6*:C*:*A*AC*A*CC&*CCCCC&*CCCC~CCC$CCC&CCC*:CCC*ACCC*CCCCCCCCC @r    c                  L   t        j                  dt              } d| d<   t        t	               |       }|d   j
                  d   }|s8dd	t        j                  |      iz  }t        t        j                  |            d }|d
   j
                  d   }|s8dd	t        j                  |      iz  }t        t        j                  |            d }|d   j
                  d   }| }|s8dd	t        j                  |      iz  }t        t        j                  |            d x}}y )Nr   r   T   r/   r<   r=   zassert %(py1)sr@   rA   rE   zassert not %(py1)s)
r   r0   ri   r8   r,   rP   rQ   rS   rT   rU   )r/   r7   rY   rZ   r\   @py_format3s         r   6test_forced_exit_mask_closes_an_existing_position_onlyr      s    ((1D)KKN
|~;
7C$$Q''''''''''''~##A&&&&&&&&&&&&:##A&&&&&&&&&&&&&&&&r    c            
         t        t                     } t        t               t        j                  dt                    }dD ]  }||   }|j
                  } |       }| |   }|j
                  } |       }||k(  }	|	st        j                  d|	fd||f      t        j                  |      t        j                  |      t        j                  |      t        j                  |      t        j                  |      t        j                  |      dz  }
dd	|
iz  }t        t        j                  |            d x}x}x}x}	x}x}} y )
Nr   r   r   rm   rG   rn   ro   rs   rt   )r8   r,   r   r0   ri   ru   rQ   rX   rS   rT   rU   rv   s               r   :test_all_false_forced_exit_mask_preserves_baseline_signalsr      s    LN#H,.bhhq.EFF?f~C~$$C$&C(6*:C*:*A*AC*A*CC&*CCCCC&*CCCC~CCC$CCC&CCC*:CCC*ACCC*CCCCCCCCC @r    )NN)__doc__builtins@py_builtins_pytest.assertion.rewrite	assertionrewriterQ   numpyr   pandasr   pyteststrategies.strategy_mlp_scoresr   rN   rO   r   r,   r8   rb   re   rk   r   r   r   r+   r    r   <module>r      s`   .      < bll< 8
U(\%D'Dr    