+
    yjRX                        R t ^ RIt^ RIt^ RIHtHt ^ RIHt ]! ]4      P                  t
]P                  P                  ^ ]! ]
4      4       ^ RIHt ^ RIHt ]! 4       '       g.   ]! R]P$                  ! 4        R24       ]P&                  ! ^ 4       ]
R,          t]
R	,          t]
R
,          t]
R,          tRtRt^t^
t^t^tRtRtRR lt R t!R t"R t#R t$R t%R t&R t'R t(R t)R t*R t+RR lt,R t-].R8X  d
   ]-! 4        R# R# ) uj  
Builds retrospective.json: a daily hits/misses/missed-opportunities summary
for the dashboard's Retrospective panel.

Hits/misses come from realized P/L on positions.json's closed_positions —
real fills only, never estimated. Missed opportunities are new_buys
candidates from daily_snapshots/ that were never entered, checked against a
live TMX quote to see if they ran without us.

Run manually:  python3 generate_retrospective.py
Cron (once daily, after TSX close, MDT):
  5 14 * * 1-5 /usr/bin/python3 /media/raid/rshare/SwingTrader/generate_retrospective.py >> /media/raid/rshare/SwingTrader/daily_run.log 2>&1
N)date	timedelta)Path)is_trading_day)	get_quotez[generate_retrospective] z% is not a TSX trading day -- skippingzpositions.jsondaily_snapshotszscreen_criteria.jsonzretrospective.jsonz
2026-08-01g      @z)funnel_breadth.recurring_megacap_handlingz
2026-08-10c                     V P                  4       '       g   V# \        V 4      ;_uu_ 4       p\        P                  ! V4      uuR R R 4       #   + '       g   i     R # ; iN)existsopenjsonload)pathdefaultfs   && 8/media/raid/rshare/SwingTrader/generate_retrospective.py	load_jsonr   <   s4    ;;==	dqyy| 
s   AA	c                     V P                  R 4      pV P                  R4      pV P                  R4      pVe	   Ve   Vf   R# R\        W,
          V,          ^4      R\        W,
          V,          ^d,          ^4      /# )exit_price_cadentry_price_cadsharesN
pl_dollarspl_pct)getround)pos
exit_priceentry_pricer   s   &   r   realized_plr   C   sw    ''*+J''+,K''(#F[0FNeZ5?CeZ5DsJAN     c                    . . . r2pV P                  R . 4       EF  pVP                  R4      '       d   K  RVP                  R4      RVP                  RR4      RVP                  R4      RVP                  R4      RVP                  R4      RVP                  R4      R	VP                  R	4      R
VP                  R
R4      RVP                  RR4      /	p\        V4      pVf   VP                  V4       K  VP                  V4       VR,          ^ 8  d   TMTP                  V4       EK  	  VP	                  R RR7       VP	                  R R7       WV3# )closed_positions_exampletickercompany 
entry_date
close_dater   r   r   
setup_typeclose_reasonr   c                     V R ,          # r    xs   &r   <lambda>#build_hits_misses.<locals>.<lambda>e   s    AlOr   Tkeyreversec                     V R ,          # r+   r,   r-   s   &r   r/   r0   f   s    aor   )r2   )r   r   appendupdatesort)	positionshitsmisses
incompletepentrypls   &      r   build_hits_missesr?   O   s6   !2r*D]]-r255quuXquuY3quu\2quu\2quu%67quu%56quuXquu\26quu^R8

 ^:e$RL!A%699%@' 3( 	II+TI:
KK-K.##r   c                     \         P                  ! V4      pT\        \        R7      ,           pTP                  R. 4      TP                  R. 4      ,           pT F  pTP                  R4      ;'       g    RP                  4       T P                  4       8w  d   KA  TP                  R4      pT'       g   K\   \         P                  ! T4      pY8u;8:  d   T8:  g   K    R#  K  	  R #   \         d     R # i ; i  \         d     K  i ; i)F)daysopen_positionsr!   r#   r%   r&   T)r   fromisoformat
ValueErrorr   ENTRY_WINDOW_DAYSr   upper)	r#   scan_date_strr8   scan_dt
window_endall_positionsr<   edentry_dts	   &&&      r   already_acted_onrM   j   s    $$]3 9*;<<JMM"2B7)--HZ\^:__MEE(O!!r((*flln<UU< 	))"-H ,*, -  !    		s#   C( 7C:(C76C7:D	D	c                 d   . . . r2p\         P                  4       '       g   WV3# \        4       p\        P                  ! 4       p\        \         P                  R 4      4       EF!  p\        V/ 4      ;'       g    / pVP                  R4      pV'       d   V\        8  d   K@  VP                  R/ 4      ;'       g    / P                  R. 4      ;'       g    .  EF  p	V	P                  R4      p
V	P                  R4      pV
'       d   Vf   K4  V
P                  4       V3pW9   d   KN  VP                  V4       \        WV 4      '       d   Ks  RV
RV	P                  RR4      RVRVR	V	P                  R	R4      R
V	P                  R
4      /p \        P                  ! V4      pY^,
          P                  p\!        T
4      pTe   TP                  R4      f   RTR&   TP#                  T4       EK  TR,          p\%        TT,
          T,          ^d,          ^4      pTTR&   TTR&   T\&        8  d   RTR&   TP#                  T4       EKl  T\(        8  d   RTR&   TP#                  T4       EK  RTR&   TP#                  T4       EK  	  EK$  	  VP+                  R RR7       WV3#   \         d     EK  i ; i)swing_trade_*.json	scan_datesignalsnew_buysr#   	price_cadr$   r%   price_at_scan_cadr(   setup_scorepriceprice_unavailablestatuscurrent_price_cadmove_pct	too_earlymissedpassed_correctlyc                     V R ,          # rZ   r,   r-   s   &r   r/   ,build_missed_opportunities.<locals>.<lambda>       a
mr   Tr1   )SNAPSHOTS_DIRr
   setr   todaysortedglobr   r   CANDIDATE_TRACKING_STARTrF   addrM   rC   rD   rA   r   r5   r   MIN_DAYS_BEFORE_JUDGINGMISSED_OPP_THRESHOLD_PCTr7   )r8   r\   	passed_okpendingseenrd   	snap_pathsnaprP   cr#   	ref_pricer2   recordrH   
days_sincequotecurrent_pricerZ   s   &                  r   build_missed_opportunitiesrv      sw   !#RwF!!'))5DJJLEM../CDE	B'--2HH[)	I(@@((9b)//R44ZDJJJAhFk*IY.<<>9-C{HHSM9== 6155B#79#Y155r#:155#7F,,Y7  ///Jf%E}		' 2 :#6x v&!'NMmi79DsJANH*7F&'*2F:33#.x v&55#+x f%#5x   (_ K Fn KK+TK:g%%5  s   JJ/.J/c                   . . . r2p\         P                  4       '       g   WV3# \        4       p\        P                  ! 4       p\        \         P                  R4      4       EF  p\        V/ 4      ;'       g    / pVP                  R4      pV'       d   V\        8  d   K@  VP                  R. 4      ;'       g    .  EF  p	V	P                  R4      p
V	P                  R4      ;'       g    RpV	P                  R4      pV
'       d   \        V9  g   Vf   KZ  V
P                  4       V3pW9   d   Kt  VP                  V4       \        WV 4      '       d   K  RV
RVRV/p \        P                  ! V4      pY_,
          P                   p\#        T
4      pTe   TP                  R	4      f   R
TR&   TP%                  T4       EK  TR	,          p\'        TT,
          T,          ^d,          ^4      pTTR&   TTR&   T\(        8  d   RTR&   TP%                  T4       EK]  T\*        8  d   RTR&   TP%                  T4       EK  RTR&   TP%                  T4       EK  	  EK  	  VP-                  R RR7       WV3#   \         d     EK  i ; i)a  Checks excluded entries tagged recurring_megacap_handling for a move.

Those entries are hand-waved past without full scoring to save research
budget (see screen_criteria.json funnel_breadth.recurring_megacap_handling) --
this is the same shape as build_missed_opportunities, but starting from a
deferral instead of a scored-and-passed new_buys candidate, to catch the
case where the deferral itself cost something.
rO   rP   excludedr#   reasonr%   rS   price_at_deferral_cadrV   rW   rX   rY   rZ   r[   r\   r]   c                     V R ,          # r_   r,   r-   s   &r   r/   /build_deferred_megacap_review.<locals>.<lambda>  ra   r   Tr1   )rb   r
   rc   r   rd   re   rf   r   r   DEFERRAL_TRACKING_STARTRECURRING_MEGACAP_TAGrF   rh   rM   rC   rD   rA   r   r5   r   ri   rj   r7   )r8   r\   rk   rl   rm   rd   rn   ro   rP   rp   r#   ry   rq   r2   rr   rH   rs   rt   ru   rZ   s   &                   r   build_deferred_megacap_reviewr      sR    "$RwF!!'))5DJJLEM../CDE	B'--2HH[)	I(??*b)//R/AhFh--2Fk*I2&@IDU<<>9-C{HHSM9== 'F,,Y7  ///Jf%E}		' 2 :#6x v&!'NMmi79DsJANH*7F&'*2F:33#.x v&55#+x f%#5x   ([ 0 Fj KK+TK:g%%5  s   I66JJc                 *   \        4       pV P                  R . 4      V P                  R. 4      ,            F[  pVP                  R4      '       g   K  VP                  R4      '       d   K5  VP                  VR,          P                  4       4       K]  	  V# )rB   r!   r#   r"   )rc   r   rh   rF   )r8   tickersr<   s   &  r   distinct_traded_tickersr   
  sp    eG]]+R09==ASUW3XXX55??155#4#4KK())+, Y Nr   c                    . p\         P                  ! 4       pV P                  R . 4       EF	  pVP                  R4      '       g   VP                  R4      '       g   K4   VP                  R4      '       d   \         P                  ! VR,          4      MRpV'       d   W5,
          P
                  MRpR Uu. uF  qtP                  V4      e   K  VNK  	  ppV'       g   K  Ve	   Wa8  g   K  TP                  RRRRRVR,          R	TR
VR,           RRP                  V4       RVe   TMR R2/4       EK  	  V#   \         d    Rp Li ; iu upi )rB   r"   r#   r&   Ncodeunenriched_positionseverityhigh	days_heldsummaryz is missing , z after zan unknown number ofz= days held -- exit/risk signals can't be computed accurately.)stop_loss_cadtarget_1_cad)r   rd   r   rC   rD   rA   r5   join)	r8   
grace_dayslessonsrd   r<   rL   r   r   missings	   &&       r   !build_unenriched_position_lessonsr     s<   GJJLE]]+R055AEE(OO	>?eeL>Q>Qt))!L/:W[H 08U%++T	?T?55811?T7	)Y-DNN2VQx[Y{m<		'0B/C D*3*?YE[\ ]FF
 
 1* N!  	H	 Us$   D:6D:5EE:E
	E
c                 |   . pV'       g   V# V P                  R . 4       Uu/ uFM  pVP                  R4      '       g   K  VP                  R4      '       d   K5  VR,          P                  4       VbKO  	  ppVP                  R/ 4      ;'       g    / P                  R. 4      ;'       g    .  F  pVP                  R4      ;'       g    RP                  4       pVP                  V4      pVf   KC   VP                  R4      '       d   \        P                  ! VR,          4      MRpV'       d'   \        P
                  ! 4       V,
          P                  MRp	TP                  RR	R
RRTRT	RVP                  R4      RT RVP                  R4       RV	e   T	MR R2/4       K  	  V# u upi   \         d    Rp Li ; i)rB   r#   r"   rQ   exitsr%   Nr&   r   overdue_exit_not_executedr   criticalr   rP   r   z+ was flagged EXIT in the most recent scan (z) and is still open (?z6 days held). The recommendation has not been acted on.)r   rF   r   rC   rD   rd   rA   r5   )
r8   latest_snapshotr   r<   open_by_tickerer#   r   rL   r   s
   &&        r   build_overdue_exit_lessonsr   -  s   G(16F(K(K155? 	#$55#4 	(Q(K   !!)R066B;;GRHNNBN%%/''R..0  (;	@C@U@Ut))#l*;<[_H 7?TZZ\H,22D	5*&)/--k:(EoFYFYZeFfEg h&3<3Hic%R S<<
 	 O, N5  	H	s(   F&F&F&0F+F++F;:F;c                    / pV P                  R . 4       EF  pVP                  R4      '       d   K  \        V4      pVP                  R4      ;'       g    / P                  4        F  w  rEVRJd   K  VP                  VR^ R^ R^ R. /4      pVR;;,          ^,          uu&   VR,          P	                  VP                  R4      4       Vf   Ki  VR	,          ^ 8  d   VR;;,          ^,          uu&   K  VR	,          ^ 8  g   K  VR;;,          ^,          uu&   K  	  EK  	  . pVP                  4        FQ  w  rHVP	                  R
RV 2RVRVR,          RVR,          RVR,          RVR,          RVR,          \
        8  /4       KS  	  V# )r!   r"   review_flagsTcountlossesgainsr   r#   r   r   pattern_flag
actionable)r   r   items
setdefaultr5   MIN_SAMPLE_FOR_PATTERN_RULE)	r8   tallyr<   r>   r   valbucketpatternsstatss	   &        r   build_review_flag_patternsr   N  si   E]]-r255^%%/552<<>ID$%%dWa1gqR[]_,`aF7Oq O9$$QUU8_5~l#a'7Oq(O%)8$)$ ?	 3 H{{}HTF+$%.%/%.%	*%.,GG
 	 % Or   c                `   VP                  RR4      p/ pV P                  R. 4       F}  pVP                  R4      ;'       g    RP                  4       pV'       d   VP                  R4      '       d   KM  VP                  V. 4      P                  VP                  R4      4       K  	  . pVP	                  4        Fp  w  rW\        V4      ^8  d   K  VR8X  d   R	MR
pTP                  RRRTRTRTRTR\        V4       RV RRP                  V4       R2VR8X  d   RMR,           /4       Kr  	  V# )aY  Code-level check, not a prompt note the LLM is trusted to remember.

Always flags 2+ open positions sharing a sector, with severity keyed off
active_mode -- aggressive mode explicitly allows concentration in the
day's leading sector (screen_criteria.json aggressive_overrides.trade_management
.sector_concentration), but this script has no way to know which sector was
"the leading one" on entry day, so it surfaces every concentration as real,
counted evidence and leaves the "is this the intentional play" call to the
scan (which has today's macro context) rather than silently assuming either way.
active_modeconservativerB   sectorr%   r"   r#   
aggressiveinfor   r   sector_concentrationr   r   r   z$ open positions are concentrated in z (r   z). zAggressive mode allows this if it is the day's intentional leading-sector play, but still counts toward max_simultaneous_positions -- confirm that's actually why before adding another.zGCorrelated risk: a single sector move could hit multiple stops at once.)r   stripr   r5   r   lenr   )	r8   criteriar   	by_sectorr<   r   r   r   r   s	   &&       r   "build_sector_concentration_lessonsr   m  s7    ,,}n=KI]]+R0%%/''R..0z**VR(//h@	 1 G$??,w<!(L86f1867;w<. DVH MIIg&'s,
 #l2J ^
 		 -* Nr   c                      \         P                  4       '       g   R # \        \         P                  R4      4      p V '       d
   V R,          # R # )NrO   )rb   r
   re   rf   )filess    r   latest_snapshot_filer     s;    !!=%%&:;<E59'4'r   c                    \        4       pV'       d   \        VR 4      MR p\        \        / 4      ;'       g    / pVP                  R/ 4      P                  R^4      pT;'       g    . p\	        W4      p\        W4      p\        V 4      p\        W4      p	\        V 4      p
\        V
4      \        8  p. pV F"  pVP                  VR,          R,           4       K$  	  V F"  pVP                  VR,          R,           4       K$  	  V	 F2  pVR,          R8X  g   K  VP                  VR,          R,           4       K4  	  V'       dA   VP                  R	\        V
4       R
RP                  \        V
4      4       R\         R24       V Fg  pVR,          '       g   K  VP                  RVR,           RVR,           RVR,           RVR,           RRP                  VR,          4       R24       Ki  	  V FQ  pVP                  VR,           RVR,           RVR,          R R VR!,           R"VR#,           R$VR,           R%24       KS  	  R&VR'VR(VR)V	R*VR+\        V
4      R,\        V
4      R-VR.\        /	pVV3# )/Ntrade_managementunenriched_position_grace_daysr   z~ Repeat the EXIT call today with escalated urgency and state the cumulative days ignored (see daily_scan_prompt.md Step 1a/5).z6 Prioritize enriching it in Step 1b before continuing.r   r   z= Do not add another correlated position in this sector today.zOnly z? distinct ticker(s) have been traded across the whole program (r   z), below the z-ticker breadth target. Apply screen_criteria.json's funnel_breadth rules today: use every screener_sources entry, resolve stale watchlist items first, and hit min_candidates_evaluated_per_scan / min_sectors_represented before finalizing output.r   z	Pattern: r   z closed trades were flagged 'r   z' (r   z
 losses / r   z gains -- tickers: r   z1). Factor this into today's entry/risk decisions.r#   zX was deferred without reconfirmation under funnel_breadth.recurring_megacap_handling on rP   z and has since moved rZ   z+.1fz% ($rz   z -> $rY   z+). Force a full Step 3/4 reconfirmation on z" today, regardless of day-of-week.unenriched_positionsoverdue_exitsreview_flag_patternsr   deferred_megacap_moveddistinct_tickers_tradeddistinct_ticker_countnarrow_funnel_flagnarrow_funnel_threshold)r   r   CRITERIA_FILEr   r   r   r   r   r   r   NARROW_FUNNEL_TICKER_THRESHOLDr5   r   re   )r8   deferred_missedrn   latest_snapr   r   
unenrichedr   r   concentrationtradednarrow_funnel
directiveslr<   dr   s   &&               r   build_lessonsr     s   $&I09)It,tK+11rH0"599:Z\]^J%++O5iLJ.yFM.y9H6yKM+I6FK"@@MJiL V V	
 
 iLSS	
  Z=F")^^ 
 CK= !		&.)*-8V7W Xdd	
 \??AgJ<'DQvYK PhK=
1W:,6I$))TUV_T`JaIb cAA  {m <<=kN;KK`}T"$q)@'A&B%J]H^G_ `778{mCeg	
  	:=8= ?!6&>3v;=!#A
G Jr   c                  (   \        \        / 4      ;'       g    / p \        V 4      w  rp\        V 4      w  rEp\	        V 4      w  rxp	\        W4      w  r\        \        R  V 4       4      \        R V 4       4      ,           ^4      p\        V4      \        V4      ,           pV'       d$   \        \        V4      V,          ^d,          ^4      MRpR\        P                  ! 4       P                  4       RRVRVR\        V4      R\        V4      R	\        V4      R
\        V4      R\        V4      /RVRVRVRVRVRVRRVRVRV	/RV
RVRR\         R2R\        R R\         R2RR./p\        \         R 4      ;_uu_ 4       p\"        P$                  ! VV^R!7       RRR4       \'        R"\        V4       R#\        V4       R$\        V4       R%\        V4       R&\        V4       R'V R(\        V4       R)V
R*,           24       R#   + '       g   i     Lr; i)+c              3   2   "   T F  qR ,          x  K  	  R# 5ir   Nr,   ).0hs   & r   	<genexpr>main.<locals>.<genexpr>  s     *TlOOT   c              3   2   "   T F  qR ,          x  K  	  R# 5ir   r,   )r   ms   & r   r   r     s     0Q&Q<&r   Ngenerated_atr   realized_pl_cadwin_rate_pct	hit_count
miss_countincomplete_countmissed_opportunity_countdeferred_megacap_missed_countr9   r:   r;   missed_opportunitiesr]   pending_reviewdeferred_megacap_reviewr\   rl   r   lessons_for_next_scannotesz9Missed-opportunity tracking only covers daily scans from z1 onward -- earlier scan history was not retained.z>A passed-on candidate is flagged "missed" once it is at least z.0fz(% above its scan-day price and at least z calendar days have passed.a  Only new_buys candidates are tracked for missed opportunities -- watchlist/excluded tickers and anything the screeners never surfaced are out of scope, EXCEPT excluded entries tagged funnel_breadth.recurring_megacap_handling (see deferred_megacap_review) -- those carry a price_cad captured at deferral time specifically so a hand-waved name that moved doesn't go unnoticed. Coverage starts 2026-08-10, when price_cad capture was added.zlessons_for_next_scan is read and applied by daily_scan_prompt.md Step 1a -- this is not just a dashboard display, it changes what the next scan does.w)indentz[generate_retrospective] hits=z misses=z incomplete=z missed_opps=z deferred_megacap_missed=z realized_pl=$z	 lessons=z distinct_tickers=r   )r   POSITIONS_FILEr?   rv   r   r   r   sumr   r   rd   	isoformatrg   rj   ri   r   OUTPUT_FILEr   dumpprint)r8   r9   r:   r;   r\   rk   rl   r   deferred_okdeferred_pendingr   r   realized_totaln_closedwin_rateoutputr   s                    r   mainr     sR   ."-33I0;D*!;I!FFw5RS\5]2O"2%29%N"G*T**S0Q&0Q-QQSTN 4y3v;&H7?uSY)C/3TH 	

..0D	FJ&F+S-A
 	! 0$

 	!6GH`Ga  bS  TLMefiLj  kS  Tk  Sl  lG  H D e	
1F@ 
k3		1		&!A& 
  

(T8CK= I*o&mCK= A##&#7"8 9&'y5J1K0L M#$;<=		? 
 	s   =HH	__main__r	   )/__doc__r   sysdatetimer   r   pathlibr   __file__parentBASEr   insertstrr   
tmx_quotesr   r   rd   exitr   rb   r   r   rg   rj   ri   rE   r   r   r~   r}   r   r   r?   rM   rv   r   r   r   r   r   r   r   r   r   __name__r,   r   r   <module>r     s8    
 $ H~ 3t9  )  	%djjl^3X
YZHHQK(())..,, (      
 "$   D & 	$6,?&DE&P6B>)X(?D6r zF r   