
    p$BjP              
         U d Z ddlmZ ddlZddlZddlmZ ddlmZmZ ddl	m
Z
  ej        e          Zg dZ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!d!d"Zd#e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!d0Zd#ed1<   g d2Zg d3ZdQdRd8ZdSd;ZdSd<ZdTd?ZdUdBZdVdEZdWdHZdXdJZdYdNZ G dO dP          ZdS )ZuQ   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_BUCKETzAA+zAA-zA+zA-zBBB+zBBB-zBB+zBB-zB+zB-zCCC+)r   zCCC-r   r%   r   SD_SP_TO_BUCKET)<1yr2yr5yr10yr20yr30yr+issuerrating_peerssector
sub_sectorrating_sectorrating_sub_sectorlower_rating_sub_sectorupper_rating_sub_sectormoodysOptional[str]spreturnc                    t                               | pd                                          }|r|S t                              |pd                                          pdS )z2Moody's first; fall back to S&P if Moody's absent. N)r)   getstripr+   )r;   r=   ms      '/app/app/services/peer_curve_service.py_rating_bucketrE   2   s[    fl113344A bhB--//008D8    bucketstrc                    | t           vrd S t                               |           }|dz   t          t                     k    rd S t           |dz            S )N   )RATING_HIERARCHYindexlenrG   idxs     rD   _lower_rating_bucketrP   :   sR    %%%t

 
 
(
(C
Qw#&''''tC!G$$rF   c                    | t           vrd S t                               |           }|dz
  dk     rd S t           |dz
           S )NrJ   r   )rK   rL   rN   s     rD   _upper_rating_bucketrR   C   sG    %%%t

 
 
(
(C
Qw{{tC!G$$rF   maturity_strOptional[float]c                    | sd S 	 t          j        | d d         d                                          }|t          j                    z
  j        dz  }|dk    r|nd S # t
          t          f$ r Y d S w xY w)N
   z%Y-%m-%dg     v@r   )r   strptimer   todaydays
ValueError	TypeError)rS   matdeltas      rD   _years_to_maturityr^   L   s     tSbS 1:>>CCEEtz||#)F2		uut+	"   tts   AA A21A2yearsfloatc                V    | dk    rdS | dk    rdS | dk    rdS | dk    rdS | d	k    rd
S dS )NrJ   r,      r-      r.      r/      r0   r1    )r_   s    rD   _maturity_bucketrg   W   sH    zzFFzzEEzzEE{{FF{{FF7rF   bonddictc                F    dD ]}|                      |          }||c S d S )N)oasz_spreadasset_swap_spreadrA   )rh   keyvs      rD   _best_spreadrq   `   s8    7  HHSMM=HHH 4rF   bonds
list[dict]c                    fd| D             }|r-t          t          |          t          |          z  d          nd S )Nc                6    g | ]}t          |          xS N)rq   ).0bss     rD   
<listcomp>z_avg_spread.<locals>.<listcomp>i   s)    FFFQa$8A#Eq#E#E#ErF   rJ   )roundsumrM   )rr   spreadsry   s     @rD   _avg_spreadr~   h   sG    FFFF%FFFG4;E5WG,a000ErF   fieldc                    fd| D             }|r.t          |                              d          d         d         nd S )Nc                B    g | ]}|                               xS rf   rn   )rw   rx   r   rp   s     rD   rz   z_dominant.<locals>.<listcomp>n   s-    444!aeeEll!24A444rF   rJ   r   )r   most_common)rr   r   valsrp   s    ` @rD   	_dominantr   m   sN    44444u444D15?74==$$Q''*1--4?rF   	genealogydepthintc                    | sdS |                      d          }t          |          |k    rd                    |d|                   nd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\)splitrM   join)r   r   partss      rD   _genealogy_prefixr   r   sQ      tOOD!!E'*5zzU':':499U6E6]###DrF   c                  n    e Zd ZU i Zded<    e            Zded<   dZdd
Zd Z	ddZ
ddZddZd dZdS )!PeerCurveServicezdict[str, tuple[float, dict]]_cacheset[str]_in_progressi  app_keyrH   usernamepasswordc                >    || _         || _        || _        d | _        d S rv   )_app_key	_username	_password_client)selfr   r   r   s       rD   __init__zPeerCurveService.__init__   s"    !!rF   c                n    | j         (ddlm}  || j        | j        | j                  | _         | j         S )Nr   )LsegApiClient)r   r   r   )r   app.providers.lseg_apir   r   r   r   )r   r   s     rD   _get_clientzPeerCurveService._get_client   sM    <<<<<<<(=  DL
 |rF   issuers	list[str]display_name	cache_keyr>   Nonec                   	 |                      ||          }t          j                    |f| j        |<   t                              d|           n^# t
          $ rQ}t                              d|           t          j                    dt          |          if| j        |<   Y d }~nd }~ww xY w| j        	                    |           d S # | j        	                    |           w xY w)Nz-PeerCurve: background build complete for '%s'z+PeerCurve: background build failed for '%s'	__error__)
_computetimer   loggerinfo	Exception	exceptionrH   r   discard)r   r   r   r   resultexcs         rD   _background_computez$PeerCurveService._background_compute   s    	1]]7L99F&*ikk6%:DK	"KKGVVVV 	L 	L 	LJLYYY&*ikkKS3J%KDK	""""""	L %%i00000D%%i0000s1   AA C 
B,AB'"C 'B,,C C'Optional[dict]c                    ddl }d                    t          d |D                                 }|| j        v rT| j        |         \  }}t	          j                    |z
  | j        k     rt                              d|           |S | j        |= || j        vrg| j        	                    |           |
                    | j        |||fd                                           t                              d|           dS )	zWReturn cached result immediately, or None if still computing (starts background build).r   N|c              3  b   K   | ]*}|                                                                 V  +d S rv   )rB   lower)rw   is     rD   	<genexpr>z)PeerCurveService.build.<locals>.<genexpr>   s4      #G#G!AGGIIOO$5$5#G#G#G#G#G#GrF   zPeerCurve: cache hit for '%s'T)targetargsdaemonz,PeerCurve: started background build for '%s')	threadingr   sortedr   r   
_CACHE_TTLr   r   r   addThreadr   start)r   r   r   r   r   tsr   s          rD   buildzPeerCurveService.build   s   HHV#G#Gw#G#G#GGGHH	##Y/JBy{{R$/11;\JJJI&D---!!),,,/wi6Xae   egggKKFUUUtrF   ri   c                $  3456789:;<=> |                                  }t                              d|           |                    |          }|s%t	          dd                    |                     d |D             }t          d |D             d          }t          |d          }t          |d          }|                    |d	                   }	t                              d
||	|||           t          |d          }
t          |
d          }t                              d|
|           |r|	                    |d          }nc|p|pd
                    d          d	                                         }t                              d|           |                    |d          }g }|r@|                    |d          }t                              d|t          |                     t!          j                                                    6dS6fd} |||          } |||          }d |D             ==fd|D             }||z   }t                              dt          |          t          |          t          |          t          |                     i <|r!d |D             }|                    |          <dT<fd!33fd"|D             }3fd#|D             }|D ]}d$|d%<   ||z   }dUd(}|D ]} ||           |D ]} ||           t          |          }d) |D             }d* |D             }|t          |          z
  }|rt                              d+|           t                              d,t          |          t)          d- |D                       t)          d. |D                       t          |                     h d/}d0h}|	                    d1d          }|	                    d2d          }|	                    d3d          } ||v r|}!| }"d4\  }#}$|9|:n#||v r|}!| }"d5\  }#}$|9|:n|p|}!|p|}"d6\  }#}$|9|:|D ]2}|                    d          |d7<   |                    d          |d8<   3t                              d9|!|#|"|$9:           |r|gng ;|rt-          |          nd 8|rt/          |          nd >dV9fd;4dV:fd<5d= |D             }%|%|z   }&|%;r;fd>|&D             ng 4fd?|D             :r5fd@|D             ng ;r4;fdA|D             ng ;r:r5;fdB|D             ng 8r:r58fdC|D             ng >r:r5>fdD|D             ng dE}'i }(t0          D ]C7i })|'                                D ]%\  }*}+7fdF|+D             },t5          |,          |)|*<   &|)|(7<   DdG |'                                D             }-|-|(dH<   |'dI         }.t)          dJ |.D                       }/t)          dK |.D                       }0t          |.          pdL}1|/|1z  dMk    rdN}2n|0|1z  dMk    rdO}2ndP}2||!|"|#|$|;8>|(dQ |'                                D             |                     |'          |2dRS )WNz)PeerCurve: fetching issuer bonds for '%s'zNo bonds found for: z, c                H    h | ]}|                     d           |d           S ricrn   rw   rx   s     rD   	<setcomp>z,PeerCurveService._compute.<locals>.<setcomp>   s+     P P Pa155<< P5 P P PrF   c           
         g | ]>}i |d t          |                    d          |                    d                    i?S )_rbrating_moodys	rating_sp)rE   rA   r   s     rD   rz   z-PeerCurveService._compute.<locals>.<listcomp>   sK    lllZ[UU5.)?)?{ASASTTUUlllrF   r   trbc_igtrbc_industryr   zFPeerCurve: issuer=%s  gics=%s  trbc_ig=%s  trbc_industry=%s  rating=%sr      )r   z-PeerCurve: genealogy=%s  sector_prefix(d2)=%si,  )topr@   z & z1PeerCurve: no genealogy, text fallback query='%s'u/   PeerCurve: cross-sector rating '%s' → %d refsrefsrs   exclude_ricsr   r>   c                   t                      }g }| D ]v}|                    d          }|r||v s||v r"|                    d          r|d         d d         k     rL|                    |           |                    |           w|S )Nr   maturity_daterV   )setrA   r   append)r   r   seenoutrr   _todays         rD   _valid_refsz.PeerCurveService._compute.<locals>._valid_refs   s     UUDC  eeEll c\11SD[[uu_-- ?1CCRC1H61Q1Q

1JrF   c                    h | ]
}|d          S r   rf   rw   r   s     rD   r   z,PeerCurveService._compute.<locals>.<setcomp>   s    888q!E(888rF   c                (    g | ]}|d          v|S r   rf   )rw   r   sector_ric_sets     rD   rz   z-PeerCurveService._compute.<locals>.<listcomp>   s'    \\\1QuX^=[=[Q=[=[=[rF   zLPeerCurve: %d sector refs, %d rating refs (%d new), %d total for market datac                    g | ]
}|d          S r   rf   r   s     rD   rz   z-PeerCurveService._compute.<locals>.<listcomp>  s    ===!QuX===rF   refri   c                
   | d         }                     |i           }i | |                      d          p|                     d          |                     d          |                     d          |                     d          |                     d          |                     d          |                     d	          |                     d
          |                     d          |                     d          ddS )Nr   price	bid_priceytmytwrl   rk   rm   countryr   r   durationF)r   r   r   rl   rk   rm   r   r   r   r   
_is_issuerrn   )r   r   rC   peer_markets      rD   _enrichz*PeerCurveService._compute.<locals>._enrich	  s    e*CR((A%(WWW%5%5%K{9K9K%&UU5\\%&UU5\\%&UU:%6%6%&UU5\\%&UU+>%?%?%&UU9%5%5%&UU9%5%5%&UU?%;%;%&UU:%6%6%*   rF   c                &    g | ]} |          S rf   rf   rw   r   r   s     rD   rz   z-PeerCurveService._compute.<locals>.<listcomp>  s!    666Qggajj666rF   c                &    g | ]} |          S rf   rf   r   s     rD   rz   z-PeerCurveService._compute.<locals>.<listcomp>  s!    CCCAWWQZZCCCrF   Tr   rx   r   c                    t          |                     d          |                     d                    | d<   t          |                     d                    }|| d<   |t          |          nd | d<   d S )Nr   r   rating_bucketr   years_to_maturitymaturity_bucket)rE   rA   r^   rg   )rx   yrss     rD   _enrich_derivedz2PeerCurveService._compute.<locals>._enrich_derived'  sw    %3AEE/4J4JAEER]L^L^%_%_Ao%7o8N8N%O%OC%(A!">Ao%5c%:%:%:SWA   rF   c                f    g | ].}|                     d           s|                     d          ,|/S )r   r   rn   r   s     rD   rz   z-PeerCurveService._compute.<locals>.<listcomp>4  s;    iii1QUU<-@-@iAEEJ[D\D\DhQDhDhDhrF   c                <    g | ]}|                     d           |S r   rn   r   s     rD   rz   z-PeerCurveService._compute.<locals>.<listcomp>5  s*    bbb1QUUCT=U=U=aQ=a=a=arF   z>PeerCurve: dropped %d sector peer bonds with no valid maturityzVPeerCurve: %d sector bonds (%d issuer + %d sector peers), %d cross-sector rating peersc              3  D   K   | ]}|                     d           dV  dS r   rJ   Nrn   r   s     rD   r   z,PeerCurveService._compute.<locals>.<genexpr>=  s3      <<al(;(;<<<<<<<rF   c              3  D   K   | ]}|                     d           dV  dS r   rn   r   s     rD   r   z,PeerCurveService._compute.<locals>.<genexpr>>  s3      @@aAEE,,?,?@@@@@@@rF   >   Energy	Materials	UtilitiesIndustrialsr5   industry_groupindustry)SectorIndustry)Industry Groupr  )r  r  
eff_sectoreff_sub_sectorzRPeerCurve: GICS display sector=%s (%s)  sub=%s (%s)  match_sector=%s  match_sub=%sboolc                >    r|                      d          k    ndS )Nr  Frn   )rx   match_sectors    rD   _smatchz*PeerCurveService._compute.<locals>._smatchu  s$    :FQ155&&,66EQrF   c                >    r|                      d          k    ndS )Nr  Frn   )rx   	match_subs    rD   _ssmatchz+PeerCurveService._compute.<locals>._ssmatchx  s%    ;DO155)**i77%OrF   c                <    g | ]}|                     d           |S )r   rn   r   s     rD   rz   z-PeerCurveService._compute.<locals>.<listcomp>}  s)    CCCQquu\/B/BCqCCCrF   c                B    g | ]}|                     d           v |S r   rn   )rw   rx   peer_bucketss     rD   rz   z-PeerCurveService._compute.<locals>.<listcomp>  s/    RRRq155+A+A\+Q+Q+Q+Q+QrF   c                *    g | ]} |          |S rf   rf   )rw   rx   r
  s     rD   rz   z-PeerCurveService._compute.<locals>.<listcomp>  s&    >>>771::>1>>>rF   c                *    g | ]} |          |S rf   rf   )rw   rx   r  s     rD   rz   z-PeerCurveService._compute.<locals>.<listcomp>  s&    ???88A;;?1???rF   c                X    g | ]&}|                     d           v  |          $|'S r  rn   )rw   rx   r
  r  s     rD   rz   z-PeerCurveService._compute.<locals>.<listcomp>  sA    ___q)?)?<)O)OT[T[\]T^T^)O)O)O)OrF   c                X    g | ]&}|                     d           v  |          $|'S r  rn   )rw   rx   r  r  s     rD   rz   z-PeerCurveService._compute.<locals>.<listcomp>  sA    ```q)?)?<)O)OT\T\]^T_T_)O)O)O)OrF   c                \    g | ](}|                     d           k     |          &|)S r  rn   )rw   rx   r  lower_buckets     rD   rz   z-PeerCurveService._compute.<locals>.<listcomp>  A    ```q)?)?<)O)OT\T\]^T_T_)O)O)O)OrF   c                \    g | ](}|                     d           k     |          &|)S r  rn   )rw   rx   r  upper_buckets     rD   rz   z-PeerCurveService._compute.<locals>.<listcomp>  r  rF   r2   c                F    g | ]}|                     d           k    |S r   rn   rw   rx   rG   s     rD   rz   z-PeerCurveService._compute.<locals>.<listcomp>  s1    WWWaAEE:K4L4LPV4V4V4V4V4VrF   c                4    i | ]\  }}|t          |          S rf   )r~   )rw   ro   rr   s      rD   
<dictcomp>z-PeerCurveService._compute.<locals>.<dictcomp>  s&    OOOzsE3E**OOOrF   Allr3   c              3  D   K   | ]}|                     d           dV  dS )rk   NrJ   rn   r   s     rD   r   z,PeerCurveService._compute.<locals>.<genexpr>  s1      LL155<<3K!3K3K3K3KLLrF   c              3  D   K   | ]}|                     d           dV  dS )rl   NrJ   rn   r   s     rD   r   z,PeerCurveService._compute.<locals>.<genexpr>  s3      QQ1553D3D3P!3P3P3P3PQQrF   rJ   g      ?rk   rl   rm   c                4    i | ]\  }}|t          |          S rf   )rM   )rw   krp   s      rD   r  z-PeerCurveService._compute.<locals>.<dictcomp>  s$    $M$M$M41aQA$M$M$MrF   )r3   r5   r6   sector_labelsub_sector_labelr   rating_peer_bucketslower_rating_bucketupper_rating_buckettablebond_counts
chart_dataspread_source)r   rs   r   r   r>   rs   )r   ri   r>   ri   )rx   ri   r>   r   )rx   ri   r>   r  )r   r   r   fetch_bonds_for_issuersrZ   r   r   lookup_issuer_gicsr   search_peers_by_sectorr   rB   warningsearch_bonds_by_querysearch_peers_by_ratingrM   r   rX   	isoformatfetch_market_datar|   rA   rP   rR   MATURITY_BUCKETSitemsr~   _chart_rows)?r   r   r   clientissuer_bondsissuer_ricsr   trbc_ig_domtrbc_ind_domgicsr   sector_prefix	peer_refsfallback_queryrating_refsr   sector_refsclean_rating_refsextra_rating_refsall_peer_refsall_peer_rics
peer_bondsrating_peer_bondsrx   	all_bondsr   beforedropped_USE_INDUSTRY_SKIP_SECTORgics_sectorgics_iggics_indr5   r6   r$  r%  issuer_onlyrating_pool	universesr)  rowro   rr   bucket_bondsall_row
all_issuer	oas_countz_countnr,  r   r
  r  r   rG   r  r	  r  r  r   r   r  s?                                                      @@@@@@@@@@@@rD   r   zPeerCurveService._compute   sM	   !!## 	?NNN55g>> 	JHDIIg4F4FHHIII P P< P P P "ll_klll
 
 !y99 ?? ((44T$\=	
 	
 	
 #<==	*9A>>>CYP]^^^ 	N55m5MMII)?\?RFFuMMaPVVXXNNNNP^___44^4MMI #% 	l 7737OOKKKI=Z]^iZjZjkkk ''))	 	 	 	 	 	 #{9k::'K[AA 98K888\\\\(9\\\#&77Zc"344c:K6L6LcR_N`N`	
 	
 	
 (* 	B==}===M 22=AAK	 	 	 	 	 	$ 7666+666
CCCC1BCCC  	# 	#A"AlOO
 !:-		X 	X 	X 	X  	 	AOA" 	 	AOA Yii	iii	bb(9bbb3y>>) 	cKKXZabbbd	NN<<9<<<<<@@9@@@@@!""	
 	
 	
" =<<&hhx,,hh/44hhz2..-'''F$J-A*L*)L*IIL((#F$J-I*L*)L*II'6;F#6;J-G*L*)L*I  	9 	9A"#%%	"2"2AlO"#%%"8"8A`L*.>i	
 	
 	
 +8?R>KU+M:::QU>KU+M:::QU	R 	R 	R 	R 	R 	R	P 	P 	P 	P 	P 	P
 DC)CCC!$55 "  (RRRRKRRRR%'>>>>i>>>CLT????i????RT  (_____I____%'  6$-6`````I````35  6$-6`````I````35  6$-6`````I````35-,
 ,
	6 "$& 	  	 FC'oo// 5 5
UWWWW5WWW&|44CE&MMOOY__=N=NOOOe x(
LLZLLLLL	QQZQQQQQ*oo*q=3!MMq[3&MM/M %1$*$.$0$4$1$0$0$0$)$M$M9??;L;L$M$M$M$($4$4Y$?$?$1
 
 	
rF   rS  dict[str, list[dict]]rs   c                    g }t           D ]Udi}|                                D ]%\  }}fd|D             }t          |          ||<   &|                    |           V|S )NrG   c                F    g | ]}|                     d           k    |S r   rn   r  s     rD   rz   z0PeerCurveService._chart_rows.<locals>.<listcomp>  s0    MMMA!%%0A*B*Bf*L*La*L*L*LrF   )r5  r6  r~   r   )r   rS  rowsrT  ro   rr   bbrG   s          @rD   r7  zPeerCurveService._chart_rows  s    & 	 	F!6*C'oo// + +
UMMMMMMM&r??CKKrF   N)r   rH   r   rH   r   rH   )r   r   r   rH   r   rH   r>   r   )r   r   r   rH   r>   r   )r   r   r   rH   r>   ri   )rS  r[  r>   rs   )__name__
__module____qualname__r   __annotations__r   r   r   r   r   r   r   r   r7  rf   rF   rD   r   r      s         ,.F.... SUUL""""J     	1 	1 	1 	1   $N
 N
 N
 N
`     rF   r   rv   )r;   r<   r=   r<   r>   r<   )rG   rH   r>   r<   )rS   r<   r>   rT   )r_   r`   r>   rH   )rh   ri   r>   rT   )rr   rs   r>   rT   )rr   rs   r   rH   r>   r<   )r   r<   r   r   r>   r<   )__doc__
__future__r   loggingr   collectionsr   r   r   typingr   	getLoggerr`  r   rK   r)   rc  r+   r5  UNIVERSE_KEYSrE   rP   rR   r^   rg   rq   r~   r   r   r   rf   rF   rD   <module>rk     s   W W W " " " " " "         # # # # # # # #      		8	$	$ JII 
$	5
$	4
$
$#T
$ 	#
$ s
$ #C
$ E	
$ 5	
$ #)%	
$
 
4
$
 $
$
 #(
$ 	#
$ 
$ #'
$ E
$ !%
T	S3
$ 
$ 
$  
 
 
 

!	5
!	4
!t
!"D
! 	#
! c
! "3
! E	
! %	
! "(	
!
 
4
!
 
!
 "'
! 	#
! 
! "&s
! E
!  
T	C
! 
! 
! 
 
 
 
 CBB   9 9 9 9 9% % % %% % % %         F F F F
@ @ @ @
	E 	E 	E 	EJ J J J J J J J J JrF   