o
    ¢�—jb  ã                
   @   s¼   d Z ddlZddlm  mZ ddlZddlZddl	Z	ddl
Z
ej dej ej ej e¡¡¡¡ ddlmZmZmZ dd„ ZG dd„ de
jƒZG dd	„ d	e
jƒZed
kr\e
 ¡  dS dS )aÖ  
test_config_validation.py

Covers config_loader.validate_config() -- the [FEATURE 2026-09-01] basic
sanity gate added per explicit request ("confirm all parameters are
positive... instead of being more restrictive on entry"). Deliberately
narrow in scope: only flags values that can never be legitimate (a
lookback window of 0, a negative percentage/ratio/score), never touches
entry-strategy tuning itself. See config_loader.py's
_STRICTLY_POSITIVE_PATHS comment for the exact rationale and what's
intentionally excluded (documented 0-means-disabled sentinels, and
credit/weight fields that can legitimately be 0).

Run:
    python -m pytest tests/test_config_validation.py -v
or:
    python tests/test_config_validation.py
é    N)Ú
get_configÚvalidate_configÚConfigValidationErrorc                   C   sê   ddddœdddœdd	d
œddddddddœdœi dd“dd“dd“dd“dd“dd“dd“dd“dd “d!d"“d#d“d$d“d%d“d&d'“d(d d d)œ“d*d+“d,d-“dd.ddd/d0d1dd d+d2œ
d3d d"d4dd5œd-d-d3d3dd/d6d7d4d8œ	dd9d:d;œd<œ	S )=zmA minimal but complete config covering every path
    validate_config() checks, all set to legitimate values.g      @g      .@i ¡ )Ú	price_minÚ	price_maxÚmin_avg_daily_volumeé   é   )Úpremarket_candidate_countÚfinal_candidate_counté   éx   )Úpoll_interval_seconds_intradayÚ eod_liquidation_max_wait_secondsi N  g      ø?é   gffffffò?)Úprice_qualityÚvolume_quality)Úmin_premarket_volumeÚmin_rvolÚvwap_lookback_barsÚmomentum_lookback_snapshotsÚacceleration_min_ratioÚweightsÚeval_interval_secondsé<   Úslope_lookback_barsé   Ústructure_lookback_barsé   Ú
atr_periodé   Úhealthy_confirm_readsé   Úunhealthy_confirm_readsé   Ú"remove_after_consecutive_unhealthyÚfull_rescan_interval_minutesÚfull_rescan_pool_sizeé   Úfull_rescan_lookback_hoursé   Úentry_shortlist_sizeÚ volume_participation_rvol_targetÚ%max_consecutive_confirmation_failuresÚ%confirmation_failure_cooldown_secondsé´   r   )Úprice_slopeÚvwap_supportÚprice_slope_negative_creditç        Úvwap_support_weak_creditgš™™™™™¹?gffffffö?g333333ó?g      @éA   )
Úvwap_rising_lookback_barsÚopening_volume_expansion_ratioÚbreakout_hold_barsÚhigher_lows_lookback_barsÚmax_spread_pct_entryÚmax_extension_from_vwap_pctÚmin_confirmation_scoreÚ
rsi_periodÚcancel_unfilled_after_secondsÚmin_extension_from_vwap_pctg      ð?g      @)Úaccount_risk_pct_per_tradeÚ#max_position_notional_pct_of_equityÚ
min_sharesÚmax_daily_loss_pctÚmax_consecutive_losses_pauseg      Ð?g¸…ëQ¸ž?)	Úinitial_distanceÚtrailing_distanceÚpercentage_initial_pctÚpercentage_trailing_pctr   Úatr_multiplier_initialÚatr_multiplier_trailingÚmin_stop_distance_centsÚmax_stop_distance_pctg     jø@r   )Úmax_positionsÚsimulated_equity_defaultÚ$same_symbol_reentry_cooldown_minutes)	ÚuniverseÚ
candidatesÚscheduleÚpremarket_scoringÚintraday_healthÚentryÚriskÚstopÚtrading© rY   rY   rY   ú;/var/www/screener/premarket/tests/test_config_validation.pyÚ_valid_config   s~   
þýÿÿÿþþþýýüüûûúú
ù	÷
öù
þüýÕr[   c                   @   s,   e Zd Zdd„ Zdd„ Zdd„ Zdd„ Zd	S )
Ú#TestValidateConfigAcceptsGoodConfigc                 C   ó   t tƒ ƒ d S ©N)r   r[   ©ÚselfrY   rY   rZ   Ú test_minimal_valid_config_passesU   ó   zDTestValidateConfigAcceptsGoodConfig.test_minimal_valid_config_passesc                 C   r]   r^   )r   r   r_   rY   rY   rZ   Útest_real_config_json_passesX   rb   z@TestValidateConfigAcceptsGoodConfig.test_real_config_json_passesc                 C   sN   t ƒ }|  |d d d¡ |  |d d d¡ |  |d d d¡ t|ƒ d S )	NrT   r2   r3   rU   r?   rX   rO   r   )r[   ÚassertEqualr   ©r`   ÚcfgrY   rY   rZ   Ú.test_documented_zero_sentinels_are_not_flagged[   s
   zRTestValidateConfigAcceptsGoodConfig.test_documented_zero_sentinels_are_not_flaggedc                 C   s"   t ƒ }d|d d d< t|ƒ d S )Nr   rS   r   r   ©r[   r   re   rY   rY   rZ   Útest_zero_weight_is_allowedb   s   z?TestValidateConfigAcceptsGoodConfig.test_zero_weight_is_allowedN)Ú__name__Ú
__module__Ú__qualname__ra   rc   rg   ri   rY   rY   rY   rZ   r\   T   s
    r\   c                   @   sD   e Zd Zdd„ Zdd„ Zdd„ Zdd„ Zd	d
„ Zdd„ Zdd„ Z	dS )Ú"TestValidateConfigRejectsBadConfigc                 C   óZ   t ƒ }d|d d< |  t¡�}t|ƒ W d   ƒ n1 sw   Y  |  dt|jƒ¡ d S )NéöÿÿÿrU   r<   úentry.min_confirmation_score©r[   ÚassertRaisesr   r   ÚassertInÚstrÚ	exception©r`   rf   ÚctxrY   rY   rZ   Ú0test_negative_min_confirmation_score_is_rejectedi   ó   
ÿzSTestValidateConfigRejectsBadConfig.test_negative_min_confirmation_score_is_rejectedc                 C   rn   )Nr   rS   r   z$premarket_scoring.vwap_lookback_barsrq   rv   rY   rY   rZ   Ú#test_zero_lookback_bars_is_rejectedp   ry   zFTestValidateConfigRejectsBadConfig.test_zero_lookback_bars_is_rejectedc                 C   rn   )Ng      Ð¿rW   rJ   ústop.atr_multiplier_trailingrq   rv   rY   rY   rZ   Ú(test_negative_atr_multiplier_is_rejectedw   ry   zKTestValidateConfigRejectsBadConfig.test_negative_atr_multiplier_is_rejectedc                 C   s^   t ƒ }d|d d d< |  t¡�}t|ƒ W d   ƒ n1 sw   Y  |  dt|jƒ¡ d S )NéûÿÿÿrT   r   r0   z#intraday_health.weights.price_sloperq   rv   rY   rY   rZ   Ú test_negative_weight_is_rejected~   s   
ÿzCTestValidateConfigRejectsBadConfig.test_negative_weight_is_rejectedc                 C   rn   )NÚfiverX   rM   ztrading.max_positionsrq   rv   rY   rY   rZ   Ú"test_non_numeric_value_is_rejected…   ry   zETestValidateConfigRejectsBadConfig.test_non_numeric_value_is_rejectedc                 C   sv   t ƒ }d|d d< d|d d< |  t¡�}t|ƒ W d   ƒ n1 s#w   Y  t|jƒ}|  d|¡ |  d|¡ d S )	Nro   rU   r<   r   rW   rJ   rp   r{   )r[   rr   r   r   rt   ru   rs   )r`   rf   rw   ÚmessagerY   rY   rZ   Ú'test_all_problems_are_reported_togetherŒ   s   
ÿ
zJTestValidateConfigRejectsBadConfig.test_all_problems_are_reported_togetherc                 C   s   t ƒ }|d d= t|ƒ d S )NrU   r<   rh   re   rY   rY   rZ   Ú*test_missing_key_is_not_this_functions_job–   s   
zMTestValidateConfigRejectsBadConfig.test_missing_key_is_not_this_functions_jobN)
rj   rk   rl   rx   rz   r|   r~   r€   r‚   rƒ   rY   rY   rY   rZ   rm   h   s    
rm   Ú__main__)Ú__doc__ÚbuiltinsÚ@py_builtinsÚ_pytest.assertion.rewriteÚ	assertionÚrewriteÚ
@pytest_arÚsysÚosÚcopyÚunittestÚpathÚinsertÚdirnameÚabspathÚ__file__Úconfig_loaderr   r   r   r[   ÚTestCaser\   rm   rj   ÚmainrY   rY   rY   rZ   Ú<module>   s    "&64ÿ