
    /j1              
      x   % S r SSKJr  SSKrSSKrSSKJr  SSKJrJr  SSK	J
r
  \R                  " \5      r/ SQr0 SS	_S
S_SS_SS_SS_SS_SS_SS_SS_SS_SS_SS_SS_SS_SS_SS_SS_SSS S S!S!S!S!S".ErS#\S$'   / S%Qr/ S&QrS1S' jrS2S( jrS3S) jrS4S* jrS5S+ jrS6S, jrS7S- jrS8S. jr " S/ S05      rg)9uQ   Credit Peer Curve — computes average OAS term structures across peer universes.    )annotationsN)Counter)datedatetime)Optional)	AAAAAABBBBBBCCCCCDAaar   Aa1r	   Aa2Aa3A1r
   A2A3Baa1r   Baa2Baa3Ba1r   Ba2Ba3B1r   B2B3Caa1r   r   r   )Caa2Caa3CaCr   DDDDDWDzdict[str, str]_MOODY_TO_BUCKET)<1yr2yr5yr10yr20yr30yr+issuerrating_peerssector
sub_sectorrating_sectorrating_sub_sectorlower_rating_sub_sectorc                Z    [         R                  U =(       d    SR                  5       5      $ )N )r)   getstrip)moodyss    </root/lseg-credit/backend/app/services/peer_curve_service.py_rating_bucketr>   %   s     2 4 4 677    c                    U [         ;  a  g [         R                  U 5      nUS-   [        [         5      :  a  g [         US-      $ N   )RATING_HIERARCHYindexlen)bucketidxs     r=   _lower_rating_bucketrH   )   sB    %%

 
 
(C
Qw#&''C!G$$r?   c                    U (       d  g  [         R                  " U S S S5      R                  5       nU[        R                  " 5       -
  R                  S-  nUS:  a  U$ S $ ! [
        [        4 a     g f = f)N
   z%Y-%m-%dg     v@r   )r   strptimer   todaydays
ValueError	TypeError)maturity_strmatdeltas      r=   _years_to_maturityrS   2   sr    Sb 1:>CCEtzz|#))F2	u+t+	" s   AA! A! !A43A4c                J    U S::  a  gU S::  a  gU S::  a  gU S::  a  gU S	::  a  g
g)NrB   r*      r+      r,      r-      r.   r/    )yearss    r=   _maturity_bucketr[   =   s2    zFzEzE{F{Fr?   c                F    S H  nU R                  U5      nUc  M  Us  $    g )N)oasz_spreadasset_swap_spreadr:   )bondkeyvs      r=   _best_spreadrd   F   s(    7HHSM=H 8 r?   c                    U  Vs/ s H  n[        U5      =nc  M  UPM     nnU(       a!  [        [        U5      [        U5      -  S5      $ S $ s  snf rA   )rd   roundsumrE   )bondsbsspreadss       r=   _avg_spreadrl   N   sH    F%Qa$8Aq%GF4;5WG,a0EE Gs
   AAc                    U  Vs/ s H  o"R                  U5      =n(       d  M  UPM      nnU(       a   [        U5      R                  S5      S   S   $ S $ s  snf )NrB   r   )r:   r   most_common)rh   fieldri   rc   valss        r=   	_dominantrq   S   sQ    4u!eeEl!2!2AuD41574=$$Q'*1-?4? 5s
   AAc                ~    U (       d  gU R                  S5      n[        U5      U:  a  SR                  USU 5      $ S$ )u   Extract first `depth` code segments from a TRBC genealogy path.

depth=3 → Industry Group (sector level, e.g. 'B:180\B:181\B:182')
depth=4 → Industry (sub-sector level, e.g. 'B:180\B:181\B:182\B:183')
N\)splitrE   join)	genealogydepthpartss      r=   _genealogy_prefixry   X   s=     OOD!E'*5zU':499U6E]#DDr?   c                  V    \ rS rSr% 0 rS\S'   SrSS jrS rSS jr	SS jr
SS	 jrS
rg)PeerCurveServicef   zdict[str, tuple[float, dict]]_cachei  c                6    Xl         X l        X0l        S U l        g )N)_app_key	_username	_password_client)selfapp_keyusernamepasswords       r=   __init__PeerCurveService.__init__j   s    !!r?   c                    U R                   c1  SSKJn  U" U R                  U R                  U R
                  S9U l         U R                   $ )Nr   )LsegApiClient)r   r   r   )r   app.providers.lseg_apir   r   r   r   )r   r   s     r=   _get_clientPeerCurveService._get_clientp   s<    <<<(DL
 ||r?   c                h   UR                  5       R                  5       nX R                  ;   aO  U R                  U   u  p4[        R                  " 5       U-
  U R                  :  a  [
        R                  SU5        U$ U R                  U5      n[        R                  " 5       U4U R                  U'   U$ )NzPeerCurve: cache hit for '%s')r;   lowerr}   time
_CACHE_TTLloggerinfo_compute)r   r1   	cache_keytsresults        r=   buildPeerCurveService.buildz   s    LLN((*	#Y/JByy{R$//1;VDv&"&))+v!6Ir?   c                  ^'^( U R                  5       n[        R                  SU5        UR                  U/5      nU(       d  [	        SU< 35      eU Vs1 s H  oDR                  S5      (       d  M  US   iM!     nn[        US5      m'[        US5      m([        U Vs/ s H#  n0 UES[        UR                  S5      5      0EPM%     snS5      n[        R                  SUT'T(U5        [        US	5      n[        US
S9n[        R                  SXx5        U(       a  UR                  USS9n	OZT'=(       d    T(=(       d    SR                  S5      S   R                  5       n
[        R                  SU
5        UR                  U
SS9n	U	 Vs/ s H  oR                  S5      U;  d  M  UPM     nn[        R                  S[        U5      5        0 nU(       a%  U Vs/ s H  oS   PM	     nnUR                  U5      n/ nU H  nUS   nUR                  U0 5      nUR!                  0 UEUR                  S5      =(       d    UR                  S5      UR                  S5      UR                  S5      UR                  S5      UR                  S5      UR                  S5      UR                  S5      UR                  S5      UR                  S5      UR                  S5      SS.E5        M     U H  nSUS '   M
     X?-   nU HR  n[        UR                  S5      5      US!'   [#        UR                  S"5      5      nUUS#'   Ub  [%        U5      OS US$'   MT     [        R                  S%[        U5      [        U5      [        U5      5        U(       a  U/O/ nU(       a  ['        U5      OS nS/U'U(4S& jjnU Vs/ s H  oDR                  S 5      (       d  M  UPM     snU(       a)  U Vs/ s H  oDR                  S!5      U;   d  M  UPM     snO/ U Vs/ s H  nU" U5      (       d  M  UPM     snT((       a)  U Vs/ s H  oDR                  S5      T(:X  d  M  UPM     snO/ U(       a9  U Vs/ s H+  nUR                  S!5      U;   d  M  U" U5      (       d  M)  UPM-     snO/ U(       aH  T((       aA  U Vs/ s H3  nUR                  S!5      U;   d  M  UR                  S5      T(:X  d  M1  UPM5     snO/ U(       aH  T((       aA  U Vs/ s H3  nUR                  S!5      U:X  d  M  UR                  S5      T(:X  d  M1  UPM5     snO/ S'.n0 n[(         HZ  n0 nUR+                  5        H<  u  nnU Vs/ s H  oDR                  S$5      U:X  d  M  UPM     nn[-        U5      UU'   M>     UUU'   M\     UR+                  5        VVs0 s H  u  nnU[-        U5      _M     nnnUUS('   US)   n [/        S* U  5       5      n![/        S+ U  5       5      n"[        U 5      =(       d    S,n#U!U#-  S-:  a  Sn$OU"U#-  S-:  a  Sn$OSn$UT'T(UUUUUR+                  5        V%V&s0 s H  u  n%n&U%[        U&5      _M     sn&n%U R1                  U5      U$S..
$ s  snf s  snf s  snf s  snf s  snf s  snf s  snf s  snf s  snf s  snf s  snf s  snf s  snnf s  sn&n%f )0Nz)PeerCurve: fetching issuer bonds for '%s'zNo bonds found for issuer: ricr3   r4   _rbrating_moodysz9PeerCurve: issuer=%s  sector=%s  sub_sector=%s  rating=%srv   rU   )rw   z)PeerCurve: genealogy=%s  sector_prefix=%si,  )topr9   z & r   z8PeerCurve: no genealogy, falling back to text query='%s'z)PeerCurve: %d unique peer bonds to enrichprice	bid_priceytmytwr^   r]   r_   countrydurationF)r   r   r   r^   r]   r_   r   r3   r4   r   
_is_issuerTr   rating_bucketmaturity_dateyears_to_maturitymaturity_bucketz0PeerCurve: %d total bonds (%d issuer + %d peers)c                b   > T(       a  U R                  S5      T:H  $ U R                  S5      T:H  $ )Nr3   r4   r`   )ri   r3   r4   s    r=   _sector_match0PeerCurveService._compute.<locals>._sector_match   s-    uuX&0055&*44r?   r0   Allr1   c              3  L   #    U  H  oR                  S 5      c  M  Sv   M     g7f)r]   NrB   r`   .0ri   s     r=   	<genexpr>,PeerCurveService._compute.<locals>.<genexpr>  s     LZ55<!!Z   $	$c              3  L   #    U  H  oR                  S 5      c  M  Sv   M     g7f)r^   NrB   r`   r   s     r=   r   r     s     QZ553D!!Zr   rB   g      ?)
r1   r3   r4   r   rating_peer_bucketslower_rating_buckettablebond_counts
chart_dataspread_source)ri   dictreturnbool)r   r   r   fetch_bonds_for_issuersrN   r:   rq   r>   ry   search_peers_by_sectorrt   r;   warningsearch_bonds_by_queryrE   fetch_market_dataappendrS   r[   rH   MATURITY_BUCKETSitemsrl   rg   _chart_rows))r   r1   clientissuer_bondsri   issuer_ricsr   rv   sector_prefix	peer_refsfallback_queryrunique_peer_refspeer_market	peer_rics
peer_bondsrefr   m	all_bondsyrspeer_bucketslower_bucketr   	universesr   rF   rowrb   rh   bucket_bondsall_row
all_issuer	oas_countz_countnr   krc   r3   r4   s)                                          @@r=   r   PeerCurveService._compute   s   !!# 	?H55vh?:6*EFF3? P<a55<5< P !x8 |<
!KWX<aAA5.)?@A<X

 	GFJ	
 |[9	))1=?Z55m5MI %8
8b??FqIOOQNNNUWef44^4MI (1Ty!EE%L4SAyT?EUAVW (*+;<+;a5+;I< 229=K "$
#Ce*CR(A %(WWW%5%K{9K%&UU5\%&UU5\%&UU:%6%&UU5\%&UU+>%?%&UU9%5%&UU8_%&UU<%8%&UU:%6%*  $& A"AlO  !-	A#1!%%2H#IAo#5aeeO6L#MC%(A!"<?O#3C#8QUA 	  	F	NC$5s:	H +8R>K+M:QU	5 	5 #,C)Quu\/Bq)C   &PIq)?<)OIP%'"+@)Q}Q/?q)@  &KIq|)<
)JIK#%
   & QIqEE/*l: ?LQ?O I Q%'  J & 8IqEE/*l: UU<(J6 I 8 57  J & 8IqEE/*l: UU<(J6 I 8 575,
	> "$&FC'oo/
U+0W5aEE:K4LPV4V5W&|4C 0  E&M ' >G__=NO=NzsE3E**=NOe x(
LZLL	QZQQ*o*q=3!Mq[3&M/M %+$*$.$1$0$0$);D??;L$M;L41aQAY;L$M$($4$4Y$?$1
 	
U !Q Y. U =d DP AKQ
88  X P. %Ns   Y>2	Y>*ZZ'ZZ7ZZ'ZZZ&Z9Z!
Z!
*Z&Z&Z&2Z+Z+&Z+Z0Z06Z0*Z5Z5<Z:[ c                    / n[          Hf  nSU0nUR                  5        H:  u  pVU Vs/ s H  owR                  S5      U:X  d  M  UPM     nn[        U5      XE'   M<     UR	                  U5        Mh     U$ s  snf )NrF   r   )r   r   r:   rl   r   )	r   r   rowsrF   r   rb   rh   ri   bbs	            r=   r   PeerCurveService._chart_rows%  sw    &F!6*C'oo/
!&MA%%0A*Bf*LaM&r? 0 KK '  Ns   A5A5)r   r   r   r   N)r   strr   r   r   r   )r1   r   r   r   )r   zdict[str, list[dict]]r   
list[dict])__name__
__module____qualname____firstlineno__r}   __annotations__r   r   r   r   r   r   __static_attributes__rY   r?   r=   r{   r{   f   s-    ,.F).J	^
@r?   r{   )r<   Optional[str]r   r   )rF   r   r   r   )rP   r   r   Optional[float])rZ   floatr   r   )ra   r   r   r   )rh   r   r   r   )rh   r   ro   r   r   r   )rv   r   rw   intr   r   )__doc__
__future__r   loggingr   collectionsr   r   r   typingr   	getLoggerr   r   rC   r)   r   r   UNIVERSE_KEYSr>   rH   rS   r[   rd   rl   rq   ry   r{   rY   r?   r=   <module>r      sg   W "    # 			8	$ J 
$	5
$	4
$
$#T
$ 	#
$ s
$ #C
$ E	
$ 5	
$ #)%	
$
 
4
$
 $
$
 #(
$ 	#
$ 
$ #'
$ E
$ !%
T	S3
$ . 
 C 8%F
@
	EG Gr?   