
    \Xj                       d dl mZ d dlmZ d dlZd dlmZmZmZm	Z	m
Z
mZ d dlmZ d dlmZmZ d dlmZmZmZmZ d dlmZ d d	lmZ d
 Zd-d.dZd/dZd0dZd0dZd0dZd0dZ d0dZ!d0dZ"d0dZ#d0dZ$d0dZ%d0dZ&d0dZ'd0dZ( G d d      Z)d0dZ*d0dZ+d0dZ,d0dZ-d0dZ.d0d Z/d0d!Z0d0d"Z1d0d#Z2d0d$Z3d0d%Z4d0d&Z5d0d'Z6d0d(Z7d0d)Z8d0d*Z9d0d+Z:d0d,Z;y)1    )annotations)DecimalN)add_equity_initial_snapshotadd_equity_transaction add_manual_position_from_catalogcreate_manual_catalog_entryget_equity_management_optionssearch_instrument_candidates)calculate_positions)CatalogSearchResultinstrument_provider_statuses)FmpEquityPriceProviderMockEquityPriceProviderrefresh_market_pricesstore_market_price)connect_memory)apply_migrationsc                 0    t               } t        |        | S N)r   r   )conns     tests/unit/test_equity_manage.py
setup_connr      s    DTK    c                   d|j                         j                  dd       }d|j                         j                  dd       }| j                  d||f       | j                  d||f       | j                          |S )Nz	platform- -zaccount-zINSERT INTO platforms(platform_id, name, platform_type, country, default_currency, created_at) VALUES (?, ?, 'broker', 'CH', 'CHF', '2026-01-01')zINSERT INTO accounts(account_id, platform_id, account_name, account_type, currency, created_at) VALUES (?, ?, 'Synthetic Depot', 'brokerage', 'CHF', '2026-01-01'))lowerreplaceexecutecommit)r   platform_nameplatform_id
account_ids       r   seed_accountr$      s    m113;;CEFGKM//199#sCDEJLL 	\	m$ 	LL 	m	[! 	KKMr   c                J    | j                  d|f      j                         d   S )Nz2SELECT COUNT(*) AS c FROM audit_log WHERE action=?c)r   fetchone)r   actions     r   audit_countr)   +   s%    <<LviXaacdghhr   c                    t               } t        |       }t        | |ddddddddd	d
dd      }| j                  d|j                  f      j                         }| j                  d|j                  f      j                         }|d   dk(  sJ |d   dk(  sJ |d   dk(  sJ |d   dk(  sJ |d   dk(  sJ |d   dk(  sJ |d   dk(  sJ |d   d
k(  sJ t        | d      dk(  sJ |j                  g k(  sJ y )NstockzSynthetic NestleCH0038863350NESNSIXCHFz3.5z350.00
2025-12-31Corez%synthetic incomplete history acceptedTr#   asset_classnameisintickerexchangecurrencyquantity_textcost_basis_original_textsnapshot_datecategorynoteconfirm1SELECT * FROM transactions WHERE transaction_id=?/SELECT * FROM instruments WHERE instrument_id=?transaction_typeinitial_position_snapshotquantitygross_amount_originalfx_rate_to_chf1	fx_status
not_neededr5   r3   position_category equity_initial_position_snapshot   )	r   r$   r   r   transaction_idr'   instrument_idr)   warnings)r   r#   resulttx
instruments        r   Htest_add_stock_initial_snapshot_creates_instrument_transaction_and_auditrR   /   sS   <Dd#J(!)"4F" 
IFLaLaKc	d	m	m	oBORXRfRfQhirrtJ !%@@@@j>U"""%&(2223&&&k?l***f///m$///)*f444t?@AEEE??b   r   c                    t               } t        | d      }t        | f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(}| j                  d)|j                  f      j                         }| j                  d*|j                  f      j                         }|d+   dk(  sJ |d   d k(  sJ |d!   d"k(  sJ |d#   d$k(  sJ |d%   d&k(  sJ |d,   dk(  sJ |d-   dk(  sJ |d.   d/k(  sJ y )0NzSynthetic True Wealthr#   r3   ETFr4   zSynthetic World ETFr5   IE00B4L5Y983r6   SWLDr7   LSEr8   USDr9   10r:   z1000.00r;   r0   r<   r1   r=   z$synthetic ETF snapshot with known FXfx_rate_to_chf_textz0.91	fx_sourcezsynthetic fixtureter_textz0.20distribution_policyaccumulating
index_namezMSCI World Syntheticfund_domicileIE	benchmarkzSynthetic Benchmarkr>   Tr@   r?   tercurrency_originalrE   gross_amount_chfz910.0000)r   r$   r   r   rM   r'   rL   )r   r#   rO   rQ   rP   s        r   :test_add_etf_initial_snapshot_prepares_optional_etf_fieldsrf   R   s   <Dd$;<J(  #	
      "+ #  4 # &  !" +#$ *%& '( ()* +F0 ORXRfRfQhirrtJ	IFLaLaKc	d	m	m	oBe&&&+,>>>l#'====o&$...k"&;;;;!"e+++6))) !Z///r   c                 J   t               } t        | d      }t        | |dddddddd	d
dddd      }| j                  d|j                  f      j                         }|d   dk(  sJ |d   J d|j                  v sJ | j                  d      j                         d   dk(  sJ y )NzSynthetic Raiffeisenr+   zSynthetic US StockUS0378331005AAPLNASDAQrX   2z400.00r0   Opportunity(synthetic missing FX accepted for reviewmissingT)r#   r3   r4   r5   r6   r7   r8   r9   r:   r;   r<   r=   rG   r>   r?   rG   re   
missing_fxz;SELECT COUNT(*) AS c FROM alerts WHERE rule_id='missing_fx'r&   rK   )r   r$   r   r   rL   r'   rN   )r   r#   rO   rP   s       r   ;test_usd_stock_requires_fx_or_explicit_missing_status_alertrp   z   s    <Dd$:;J(!!)"7F$ 
IFLaLaKc	d	m	m	oBk?i''' !)))6??***<<UV__abefjkkkkr   c                    t               } t        |       }t        | |ddddddddd	d
dd      }t        | ||j                  ddddddd
       t        |       j                  ||j                  f   }|j                  t        d      k(  sJ t        j                  t        d      5  t        | ||j                  ddddddd
       d d d        y # 1 sw Y   y xY w)Nr+   zSynthetic SellableCH0000000001SYNr.   r/   5500r0   Unknownsynthetic initialTr2   partial_sell
2026-01-10rk   220zsynthetic partial sell	r#   rM   rA   
trade_dater9   gross_amount_original_textr8   r=   r>   3negativematch
2026-01-11rY   1000zsynthetic oversellr   r$   r   r   rM   r   	positionsrC   r   pytestraises
ValueErrorr   r#   snappositions       r   .test_sell_reduces_position_and_blocks_negativer      s    <Dd#J&!!&" D" (('#(% #4(22J@R@R3STH,,,	z	4 
!,,+#'-%	

 
 
s   ' CCc                    t               } t        |       }t        | |ddddddddd	d
dd      }t        | ||j                  ddddddd
       t        |       j                  ||j                  f   j                  t        d      k(  sJ t        j                  t        d      5  t        | ||j                  ddddddd
       d d d        y # 1 sw Y   y xY w)NrT   zSynthetic Income ETFIE0000000001INCr.   r/   7700r0   r1   rw   Tr2   dividend
2026-02-010z12.50synthetic dividendr{   r=   r   manual_adjustmentz
2026-02-02rF    r   )r   r#   r   s      r   Jtest_dividend_does_not_change_quantity_and_manual_adjustment_requires_noter      s    <Dd#J&#!&" D" ((##*! t$..
D<N<N/OPYY]deh]iiii	z	0 
!,,0#'*	

 
 
s   % CCc                    t               } t        |       }t        | |ddddddddd	d
dd      }t        j                  t
        d      5  t        | ||j                  ddddddd
       d d d        t        | ||j                  ddddddd
       t        |       j                  ||j                  f   }|j                  t        d      k(  sJ t        | d      dk(  sJ y # 1 sw Y   xxY w)Nr+   zSynthetic Full SellCH0000000002FULr.   r/   4400r0   rv   rw   Tr2   zfull_sell quantityr   	full_sellry   r~   330zsynthetic wrong full sellr{   r   440zsynthetic full sellr   equity_full_sellrK   )r   r$   r   r   r   r   r   rM   r   r   rC   r   r)   r   s       r   Atest_full_sell_requires_exact_current_quantity_and_zeros_positionr      s   <Dd#J&"!&" D" 
z)=	> 
!,,(#',,	

 (($#(" #4(22J@R@R3STH,,,t/0A555;
 
s   
 C!!C*c                     t               } t        | d       t        |       }|d   sJ ddlm} d|D cg c]  }|j
                   c}v sJ y c c}w )NSynthetic PostFinanceaccountsr   )PAGESzEquity/ETF Manage)r   r$   r	   jarvis_finance.dashboard.pagesr   
PAGE_TITLE)r   optionsr   pages       r   8test_equity_management_options_and_dashboard_page_importr   .  sO    <D./+D1G:4u"Et4??"EEEE"Es   Ac                    t               } t        | d      }t        | ddddddd	      }t        | dd
ddddd	       t        | d      }t	        |d         dk(  sJ |d   d   d   dk(  sJ t        | d      }t	        |d         dk\  sJ |d   du sJ t        | d      }t	        |d         dk\  sJ |d   du sJ | j                  d      j                         d   dk(  sJ t        j                  t        d      5  t        | ||dddddd !	       d d d        t        | ||ddddd d"d#
      }| j                  d$|j                  f      j                         }|d%   d&k(  sJ |d'   dk(  sJ |d(   d)k(  sJ d*|j                  v sJ | j                  d+      j                         d   dk(  sJ | j                  d,      j                         d   dk(  sJ y # 1 sw Y   xY w)-NzSynthetic Manual Brokerr+   zSynthetic Alpha AGr/   CH1111111111ALPr.   zsynthetic catalog sourcer3   r4   r8   r5   r6   r7   r=   zSynthetic Alpha US ListingrX   US1111111111rj   )queryresultsrK   r   mapping_statusexact_isin_matchzSynthetic Alpha   selection_requiredTz&SELECT COUNT(*) AS c FROM transactionsr&   r>   r   initial_snapshotz1.25r0   z"synthetic review must be confirmedF)catalog_entry_idr#   position_typer9   r|   r8   r=   r>   z,synthetic manually reviewed initial snapshot	r   r#   r   r9   r|   r8   r:   r=   r>   r?   rA   rB   rC   rD   r   cost_basis_uncertainzFSELECT COUNT(*) AS c FROM audit_log WHERE action='manual_position_add'zESELECT COUNT(*) AS c FROM alerts WHERE rule_id='cost_basis_uncertain')r   r$   r   r
   lenr   r'   r   r   r   r   rL   rN   )r   r#   firstisin_resultname_resultticker_resultrO   rP   s           r   4test_manual_instrument_search_and_wizard_safety_flowr   8  si   <Dd$=>J'!'	E  )'	 /t>JK{9%&!+++y!!$%56:LLLL.t;LMK{9%&!++++,4440UCM}Y'(A----.$666<<@AJJLSQUVVVV	z	3 
("!, #5
	

 .(!%;F 
IFLaLaKc	d	m	m	oB !%@@@@j>V###%&#---!V__444<<`ajjlmpquvvvv<<_`iikloptuuuu?
 
s   GG$c                     t               } t        | d      }t        | ddddddd	      }t        | ||d
dddddddd      }| j	                  d|j
                  f      j                         }|d   dk(  sJ |d   dk(  sJ |d   dk(  sJ t        | ddddddd	      }t        | ||dd d!dd"d#d$d%      }| j	                  d|j
                  f      j                         }|d&   d#k(  sJ |d   J d'|j                  v sJ y )(NzSynthetic FX BrokeretfzSynthetic USD ETFrX   IE2222222222USDENYSEzsynthetic manual ETF sourcer   r   rk   r0   z100.00z0.90zsynthetic fx sourcezsynthetic USD ETF with FXT)r   r#   r   r9   r|   r8   r:   rZ   r[   r=   r>   r?   rd   rE   re   z90.0000r+   zSynthetic EUR StockEURDE3333333333EURSXETRAzsynthetic manual stock sourcebuyrF   
2026-01-05z50.00rn   rm   
r   r#   r   r9   r|   r8   r:   rG   r=   r>   rG   ro   )r   r$   r   r   r   rL   r'   rN   )r   r#   
catalog_idrO   rP   missing_fx_catalogrn   
tx_missings           r   1test_manual_catalog_entry_and_foreign_fx_handlingr     sx   <Dd$9:J, *	J .#(!)"'(F 
IFLaLaKc	d	m	m	oB!"e+++6))) !Y...4",	 /+!(7G QT[TjTjSlmvvxJk"i///()1117+++++r   c                    t               } t        | d      }t        | ddddddd	      }t        | ||d
dddddd
      }t        | ||ddddddd
       t        | ||ddddddd
       t        | ||ddddddd
       t	        |       j
                  ||j                  f   j                  t        d       k(  sJ d!d"l	m
}  |d#      j                         }d$|v sJ d%|v sJ |j                  d&d'      d!   }d(|vsJ d)|vsJ y )*NzSynthetic Trading Brokerr+   zSynthetic Tradabler/   CH4444444444TRDr.   zsynthetic tradable sourcer   r   rt   r0   ru   zsynthetic starting pointTr   r   rF   r   110zsynthetic buyrx   rk   ry   rz   zsynthetic sellr   r   r   z4.50r   r   r   )Pathz;src/jarvis_finance/dashboard/views/page_13_equity_manage.pyzsearch_instrument_candidates(zProvider-Suche explizitzwith tabs[1]:rK   zst.write({"instrument_id"zst.write({"catalog_entry_id")r   r$   r   r   r   r   rM   rC   r   pathlibr   	read_textsplit)r   r#   r   r   r   r   wizard_sources          r   Ntest_manual_wizard_supports_buy_sell_dividend_and_no_dashboard_live_api_or_idsr     s   <Dd$>?J,!(	J ,D:Zd  uG  WZ  gs  ~C  ^c  jD  NR  SD$TJS]mr  CF  S_  jo  JO  Ve  os  t$TJS]m{  LO  \h  sx  SX  _o  y}  ~$TJS]mw  HK  Xd  ot  OU  \p  z~  t$..
D<N<N/OPYY]deh]iiiiMNXXZD*d222$,,,JJ215M(===+=@@@r   c                   ddl m}m}m}  |       D cg c]  }|j                   c}g dk(  sJ t               }| j                  dd       | j                  dd       | j                  dt        |             |d	z  j                          |d	z  d
z  j                  dd        |       j                  dk(  sJ |d	z  d
z  j                           ||d |       g      \  }}|g k(  sJ d|v sJ  G d d|      }	 ||d |	d      g      \  }}|g k(  sJ |d   j                  dk(  sJ |j                  d      j                         d   dk(  sJ y c c}w )Nr   )OpenFigiLookupProvider#default_instrument_lookup_providerssearch_instruments)openfigifmpfinnhub
twelvedatamassiveOPENFIGI_API_KEYFraisingJARVIS_OPENFIGI_API_KEYJARVIS_FINANCE_RUNTIME_DIRsecrets.envz"OPENFIGI_API_KEY=runtime-file-key
utf-8encodingruntime-file-keyrh   	providersopenfigi_api_key_missingc                      e Zd ZddZy)Vtest_provider_lookup_classes_skip_missing_keys_and_cache_results.<locals>.FakeOpenFigic                    dddddddgS )Nz	Apple Incri   USzCommon StockEquityBBG000B9XRY4)r4   r6   exchCodesecurityTypemarketSecDescompositeFIGI selfr   s     r   _requestz_test_provider_lookup_classes_skip_missing_keys_and_cache_results.<locals>.FakeOpenFigi._request  s,    (F^l  G  Zh  i  j  jr   Nr   str__name__
__module____qualname__r   r   r   r   FakeOpenFigir     s    	jr   r  Applezruntime-only-keyapi_keyr   zNSELECT COUNT(*) AS c FROM instrument_catalog_entries WHERE provider='openfigi'r&   rK   )"jarvis_finance.market_data.catalogr   r   r   r4   r   delenvsetenvr  mkdir
write_textr
  unlinksourcer   r'   )
monkeypatchtmp_pathr   r   r   pr   r   rN   r  s
             r   @test_provider_lookup_classes_skip_missing_keys_and_cache_resultsr    s    C  C?ABqAFFBF}}}}<D)590%@3S]C	  "	F"../Ydk.l!#++/AAAA	F"**,*4LbLdKefGXb==%111j- j +4\ZlEmDnoGXr>>1:
***<<hirrtuxy}~~~~) Cs   Ec                   ddl m} ddlm t	               }ddi G fdd      | j                  |dfd       t        |d	d
       d   dk(  sJ t        |d	d      }d   dk(  sJ |d   d   d   dk(  sJ y )Nr   )manager   nc                  $    e Zd ZdZdd fd
Zy)[test_search_candidates_uses_external_providers_only_on_explicit_click.<locals>.FakeProviderfake_providerNr3   c               L    dxx   dz  cc<    d |xs ddddddd	d
d
      gS )Nr  rK   r+   zSynthetic Provider AGCH5555555555SPVr.   r/   fakemediumr  )r5   r6   r7   trading_currencyprovider
confidencer  r   )r   r   r3   r   callss      r   searchzbtest_search_candidates_uses_external_providers_only_on_explicit_click.<locals>.FakeProvider.search  s^    #J!OJ'k.DWF]dr  |A  LQ  di  tz  GO  Xg  h  i  ir   r  r  r  r4   r'  )r   r&  s   r   FakeProviderr    s    /3 	i 	ir   r)  r   c                              gS r   r   )r)  s   r   <lambda>zWtest_search_candidates_uses_external_providers_only_on_explicit_click.<locals>.<lambda>  s    P\P^O_ r   r   F)r   include_externalTrK   r   data_sourcer  )jarvis_finance.equityr  r  r   r   setattrr
   )r  r  r   rO   r   r)  r&  s       @@@r   Etest_search_candidates_uses_external_providers_only_on_explicit_clickr0    s    ,F<D!HEi i  EG_` UUK:??)$edSF:??)Q./AAAr   c                  &    e Zd ZdZddZddddZy)CountingProvidercountingc                    d| _         y )Nr   )r&  )r   s    r   __init__zCountingProvider.__init__
  s	    
r   Nr  c               d    | xj                   dz  c_         t        d |xs ddddddddd	dd
      gS )NrK   r   zProvider Test ETFUS9229087690VTIr   rX   r3  r"  zsynthetic provider result)	r5   r6   r7   r#  r$  provider_symbolr%  r  evidence_note)r&  r   r   r   r3   s      r   r'  zCountingProvider.search  sg    

a
#D+*>@SZhqv  BH  [`  ku  GL  Ya  jt  D_  `  a  	ar   returnNone)r   r  r3   z
str | None)r  r  r  r4   r5  r'  r   r   r   r2  r2    s    D ?C ar   r2  c            
        t               } t        | ddddddd       t        | dd	dddd
d       t        | dd      }|d   sJ |d   d   d   j                  d      sJ t	        d |d   D              sJ t        d |d   D              sJ t        | dd      }t        |d         dk\  sJ |d   du sJ t        | dd      }t        |d         dk\  sJ |d   du sJ t        d |d   D              sJ y )Nr   Vanguard Total Stock Market ETFrX   r7  r8  r   zsynthetic VTI catalog entryr   z1Vanguard Total Stock Market ETF Secondary ListingARCAzsynthetic VTI alternate listingr   r3   r   r   r4   zVanguard Total Stock Marketc              3  @   K   | ]  }|d    dk(  xr |d   dk(    yw)r6   r8  r8   rX   Nr   .0rs     r   	<genexpr>zQtest_vti_examples_are_searchable_and_ticker_requires_selection.<locals>.<genexpr>  s*     [1q{e#>*(>>[s   c              3  ,   K   | ]  }|d    dk(    yw)trust_status	EindeutigNr   rD  s     r   rG  zQtest_vti_examples_are_searchable_and_ticker_requires_selection.<locals>.<genexpr>  s     LAq K/L   Vanguard Total Stockr   r   Tc              3  ,   K   | ]  }|d    dk(    yw)rI  u   Manuell prüfenNr   rD  s     r   rG  zQtest_vti_examples_are_searchable_and_ticker_requires_selection.<locals>.<genexpr>$  s     T!q $55TrK  )r   r   r
   
startswithanyallr   )r   by_isinby_name	by_tickers       r   >test_vti_examples_are_searchable_and_ticker_requires_selectionrT    sn   <D%>_jo  wE  NS  ^d  kH  I%>q  }B  IW  `e  pv  }^  _*4~SXYG99a (334QRRR[PYHZ[[[[L9KLLLL*47M[`aGwy!"a''''(D000,TERIy#$))))*d222Ty?STTTTr   c                 P   t               } t               }ddlm} t	        | ddd      }|d   g k(  sJ |j
                  dk(  sJ  || dd|g      \  }}|g k(  sJ |j
                  d	k(  sJ |d   j                  d
k(  sJ t	        | ddd      }|d   sJ |d   d   d   dk(  sJ y )Nr   r   r7  r   Fr   r3   r,  r   r   rK   r8  r-  local_catalog)r   r2  r  r   r
   r&  r6   )r   r$  r   
local_onlyr   rN   cacheds          r   Ktest_online_provider_is_only_used_when_explicitly_enabled_and_caches_resultr[  '  s    <D!HE-d.V[nstJi B&&&>>Q*4S[R\]GXr>>>>Q1:%%%)$nRWjopF))Q./AAAr   c                   dD ]  }| j                  |d        | j                  dt        |             t               }|D ch c]  }|d   	 c}h dk\  sJ t	        d |D              d   d	k(  sJ t        d
 |D              rJ y c c}w )N)r   r   FINNHUB_API_KEYJARVIS_FINNHUB_API_KEYFMP_API_KEYJARVIS_FMP_API_KEYFr   r   Provider>   Lokale SucheManuelle AnlageFMPFinnhubOpenFIGIc              3  2   K   | ]  }|d    dk(  s|  ywra  rf  Nr   rE  rows     r   rG  zStest_provider_statuses_report_missing_keys_without_secret_values.<locals>.<genexpr>?       I3z?j+HI   StatuszAPI-Key fehltc              3  p   K   | ].  }d t        |      j                         v xs dt        |      v  0 yw)secretr   N)r  r   ri  s     r   rG  zStest_provider_statuses_report_missing_keys_without_secret_values.<locals>.<genexpr>@  s3     fVY8s3x~~//Q3ES3QQfs   46)r  r  r  r   nextrO  )r  r  keystatusesrj  s        r   @test_provider_statuses_report_missing_keys_without_secret_valuesrs  9  s     Q /3./3S]C+-H'/0C
O04uuuuIxII(SWfffff]efffff 1s   B	c                     t               } t        | d      }t        | ddddddd	      }t        | ||d
ddddddd      }| j	                  d|j
                  f      j                         }|d   dk(  sJ y )NzSynthetic Category Brokerr   zSynthetic Core ETFr/   r  COREr.   zsynthetic core entryr   r   rF   r0   100zsynthetic category testr1   T)
r   r#   r   r9   r|   r8   r:   r=   r<   r>   z?SELECT position_category FROM instruments WHERE instrument_id=?rI   )r   r$   r   r   r   rM   r'   )r   r#   r   rO   rj  s        r   7test_category_is_saved_for_manual_position_from_catalogrw  C  s    <Dd$?@J,TuK_jo  wE  NT  _d  kA  BJ-dZ\f  wI  Y\  iu  @E  `e  lE  PV  `d  eF
,,X[a[o[oZq
r
{
{
}C"#v---r   c                    ddl m} m}  G d d|      } G d d|       } |d      } |d      }|j                  d	d
      }|j                  dd
      }|d   j                  d	k(  sJ |d   j
                  dk(  sJ |d   j                  dk(  sJ |d   j                  dk(  sJ |d   j                  d	k(  sJ |d   j
                  dk(  sJ |d   j                  dk(  sJ y )Nr   )FmpSearchProviderr   c                      e Zd ZddZy)Vtest_openfigi_isin_and_fmp_name_search_candidates_are_structured.<locals>.FakeOpenFigic                *    |dk(  sJ dddddddd	gigS )
Nr7  datar@  r8  r   rX   rT   BBG000PSKYX7r4   r6   r   r8   r   r   r   r   s     r   r   z_test_openfigi_isin_and_fmp_name_search_candidates_are_structured.<locals>.FakeOpenFigi._requestP  sK    N***'HTYgky~  QV  iw  x  y  z  {  {r   Nr  r  r   r   r   r  r{  O  s    	{r   r  c                      e Zd ZddZddZy)Qtest_openfigi_isin_and_fmp_name_search_candidates_are_structured.<locals>.FakeFmpc                     |dk(  sJ dddddgS )NrL  r8  r@  AMEXrX   symbolr4   exchangeShortNamer8   r   r   s     r   _request_manyz_test_openfigi_isin_and_fmp_name_search_candidates_are_structured.<locals>.FakeFmp._request_manyU  s/    2222$.Oflz  A  B  Br   c                    ddddddS )Nr7  rX   Tr   p=
Co@)r5   r8   isEtfcountrypricer   )r   r  s     r   _profilezZtest_openfigi_isin_and_fmp_name_search_candidates_are_structured.<locals>.FakeFmp._profileX  s    *Y]hnoor   N)r   r  r=  zlist[dict[str, object]])r  r  )r  r  r  r  r  r   r   r   FakeFmpr  T  s    	B	pr   r  ztest-keyr	  r7  r   rB  rL  r   r~  highr   r"  )r  ry  r   r'  r5   r$  r9  r%  )ry  r   r  r  r   r   	figi_rowsfmp_rowss           r   @test_openfigi_isin_and_fmp_name_search_candidates_are_structuredr  L  s   \{- {
p# p J/H
*
%Cn%HIzz 6EzJHQ<...Q<  J...Q<''>999Q<""f,,,A;~---A;5(((A;!!X---r   c                    ddl m}   G d d      } G d d      } G d d      }t               } | |d	d
 |        |       g      \  }}|g k(  sJ t        |      dk(  sJ |d   j                  dk(  sJ |d   j
                  dk(  sJ t               } | |d	d
 |        |       g      \  }}|g k(  sJ t        |      dk(  sJ |d   j
                  dk(  sJ t        |d	d
d      }|d   du sJ y )Nr   rV  c                      e Zd ZdZdddZy)\test_provider_merge_raises_confidence_and_conflict_needs_manual_review.<locals>.FakeOpenFigir   Nr  c               8    t        d |xs ddddddddd	d
      gS )Nr   r@  r7  r8  r   rX   r   r~  r  r5   r6   r7   r#  r$  r9  r%  r  r  r;  s      r   r'  zctest_provider_merge_raises_confidence_and_conflict_needs_manual_review.<locals>.FakeOpenFigi.searchm  sW    'k.BUDelz  DI  TX  kp  {E  We  rx  AK  L  M  Mr   r(  r   r   r   r  r  k  s    /3 	Mr   r  c                      e Zd ZdZdddZy)[test_provider_merge_raises_confidence_and_conflict_needs_manual_review.<locals>.FakeFmpSamer   Nr  c               8    t        d |xs dddddddddd	      gS )
Nr   r@  r7  r8  r   rX   r   r"  r  r  r;  s      r   r'  zbtest_provider_merge_raises_confidence_and_conflict_needs_manual_review.<locals>.FakeFmpSame.searchr  sW    'k.BUDelz  DI  TX  kp  {@  RW  dl  uz  {  |  |r   r(  r   r   r   FakeFmpSamer  p  s    /3 	|r   r  c                      e Zd ZdZdddZy)_test_provider_merge_raises_confidence_and_conflict_needs_manual_review.<locals>.FakeFmpConflictr   Nr  c               8    t        d |xs dddddddddd	      gS )
Nr   r@  r7  VTIXr   rX   r   r"  r  r  r;  s      r   r'  zftest_provider_merge_raises_confidence_and_conflict_needs_manual_review.<locals>.FakeFmpConflict.searchw  sW    'k.BUDelz  DJ  UY  lq  |A  SY  fn  w|  }  ~  ~r   r(  r   r   r   FakeFmpConflictr  u  s    /3 	~r   r  r7  r   r   rK   zfmp+openfigir  lowFrW  r   T)r  r   r   r   r$  r%  r
   )	r   r  r  r  r   mergedrN   
conflictedrO   s	            r   Ftest_provider_merge_raises_confidence_and_conflict_needs_manual_reviewr  h  s'   EM M
| |
~ ~
 <D)$R^R`bmboQpqFHr>>v;!!9///!96)))<D-dNEVbVdfufwUxyJr>>z?aa=##u,,,)$nRWjopF&'4///r   c                    ddl m}  t               }t        |d      }t	        |dddddd	d
      }t        |||ddddddd
d      }t        ||j                  dt        d      ddd       t        |dd      d   }|d   d   dk(  sJ |d   d   dk(  sJ d | |d         v sJ  | ddd      dk(  sJ d  | ddd      vsJ y )!Nr   )_price_previewzSynthetic Price Brokerr   r@  rX   r7  r8  r   zsynthetic price previewr   r   rF   
2026-01-01rv  rn   Tr   
2026-01-02z250.12r   )rM   
price_datecloser8   r$  r9  rB  r   
last_priceprice_sourcez
250.12 USDzNo Pricer   )r4   r  u   Preis noch nicht verfügbarr   )
3jarvis_finance.dashboard.views.page_17_position_addr  r   r$   r   r   r   rM   r   r
   )r  r   r#   r   rO   rowss         r   Itest_price_preview_uses_local_market_prices_and_missing_price_is_not_zeror    sR   R<Dd$<=J,TuKlw|  DR  [`  ko  vO  PJ-dZ\f  wI  Y\  iu  @E  `e  qz  AZ  dh  iFt6+?+?L`ghp`q  }B  MR  di  j'NPUVW`aD7< H,,,7>"e+++>$q'2222:R@AEbbbbnj%KLLLLr   c                 Z   t               } t        | d      }t        | ddddddddd	

      }t        | ||ddddddd	d      }t	        | t        ddi      dd      }|j                  dk(  sJ | j                  d|j                  f      j                         }|d   dk(  sJ |d   dk(  sJ y )NzSynthetic Update Brokerr   r@  rX   r7  r8  r   r   zsynthetic price update)	r3   r4   r8   r5   r6   r7   r$  r9  r=   r   rF   r  rv  rn   Tr   251.34r  )r$  r3   r  rK   z?SELECT close, provider FROM market_prices WHERE instrument_id=?r  r$  mock)
r   r$   r   r   r   r   updated_countr   rM   r'   )r   r#   r   rO   summaryrj  s         r   Etest_update_equity_prices_cli_workflow_uses_explicit_provider_mappingr    s    <Dd$=>J,TuKlw|  DR  [`  ko  z  QV  ]u  vJ-dZ\f  wI  Y\  iu  @E  `e  qz  AY  cg  hF#D3JES[K\3]kp  ~J  KG  A%%%
,,X[a[o[oZq
r
{
{
}Cw<8###z?f$$$r   c                    | j                  dd       | j                  dt        |             t               j	                  d      }|j
                  J |j                  dk(  sJ d|j                  k(  sJ y )Nr_  Fr   r   r8  fmp_api_key_missing)r  r  r  r   	get_pricer  error_messager9  )r  r  quotes      r   0test_fmp_price_provider_missing_key_is_sanitizedr    sv    }e43S]C"$..u5E;;"7777E)))))r   c                .   ddl m}m}m} dD ]  }| j	                  |d        |dz  }|dz  }|j                  d	       |d
z  j                  dd       | j                  dt        |              |d      dk(  sJ  |       j                  dk(  sJ  |       j                  dk(  sJ t               j                  dk(  sJ t               }t        d |D              d   dk(  sJ t        d |D              d   dk(  sJ t        d |D              rJ y )Nr   )ry  r   _runtime_secret_value)r   r   r_  r`  Fr   runtimer   Tparentsr   z6OPENFIGI_API_KEY=dummy-openfigi
FMP_API_KEY=dummy-fmp
r   r   r   )r   r   dummy-openfigi	dummy-fmpc              3  2   K   | ]  }|d    dk(  s|  ywrh  r   ri  s     r   rG  zFtest_runtime_secret_file_is_loaded_without_repo_env.<locals>.<genexpr>  rk  rl  rm  aktivc              3  2   K   | ]  }|d    dk(  s|  yw)ra  rd  Nr   ri  s     r   rG  zFtest_runtime_secret_file_is_loaded_without_repo_env.<locals>.<genexpr>  s     D3z?e+CDrl  	Kategorie
key_loadedc              3  T   K   | ]   }d t        |      v xs dt        |      v  " yw)r  r  N)r  ri  s     r   rG  zFtest_runtime_secret_file_is_loaded_without_repo_env.<locals>.<genexpr>  s+     _s#s3x/J;#c(3JJ_s   &()r  ry  r   r  r  r  r  r  r  r
  r   r   rp  rO  )	r  r  ry  r   r  rq  r  r   rr  s	            r   3test_runtime_secret_file_is_loaded_without_repo_envr    s4   ssc /3./"G	!GMM$Mv!!"kv}!~3S\B !PQUeeee!#++/????&&+555!#++{:::+-HIxII(SW^^^^DxDD[QUaaaa_V^_____r   c                     ddl m}  i  G fdd|       } |d      j                  dd	      }|sJ d
   dk(  sJ dt        d
         j	                         vsJ d   ddik(  sJ y )Nr   r   c                      e Zd Zdd fd
Zy)Ntest_openfigi_key_is_sent_as_header_not_query_param.<locals>.CapturingOpenFigiNheadersc               D    |d<   |d<   |xs i d<   dddddd	d
dgigS )Nurlpayloadr  r}  r@  r8  r   rX   rT   r~  r  r   )r   r  r  r  captureds       r   
_post_jsonzYtest_openfigi_key_is_sent_as_header_not_query_param.<locals>.CapturingOpenFigi._post_json  s_    !HUO")HY")-RHY'HTYgky~  QV  iw  x  y  z  {  {r   r  r  r  r  r  s   r   CapturingOpenFigir    s    6: 	{r   r  r  r	  r7  r   rB  r  z#https://api.openfigi.com/v3/mappingapikeyr  zX-OPENFIGI-APIKEY)r  r   r'  r  r   )r   r  r  r  s      @r   3test_openfigi_key_is_sent_as_header_not_query_paramr    s    I"$H{2 { %56==N`e=fDK4E?CCCC3x/557777I#68H"IIIIr   c                     ddl m} m} ddlm} g  G fdd|      } |d      j                  dd	
      }|sJ  |  |d         j                        }|d   dgk(  sJ |d   dgk(  sJ y )Nr   parse_qsurlparsery  c                      e Zd Zdd fd
Zy);test_fmp_key_is_sent_as_api_parameter.<locals>.CapturingFmpNr  c               N    j                  |       d|v rdddddgS ddd	dd
gS )N/stable/profiler7  rX   Tr  )r5   r8   r  r  r8  r@  r  r  appendr   r  r  r  s      r   	_get_jsonzEtest_fmp_key_is_sent_as_api_parameter.<locals>.CapturingFmp._get_json  sH    OOC  C'!/UT\bcdd$.Oflz  A  B  Br   r  r  r  r  r  s   r   CapturingFmpr    s    ,0 	Br   r  r  r	  rL  r   rB  r  r   )urllib.parser  r  r  ry  r'  r   )r  r  ry  r  r  parsedr  s         @r   %test_fmp_key_is_sent_as_api_parameterr    s    /DHB( B ,33:P^c3dDK4hx{+112F(},,,'?56666r   c                   dd l mc m} ddlm  G fdd      } G fdd      } G d d      }| j                  |d	|       | j                  |d
|       | j                  |d|       |j                  d      }|D ci c]  }|d   |
 }}|d   d   dk(  sJ |d   d   dk(  sJ |d   d   dk(  sJ |d   d   dk(  sJ |d   d   dk(  sJ |d   d   dk(  sJ y c c}w )Nr   LookupUnavailablec                  &    e Zd ZdZdZdd fd
Zy)Utest_provider_status_probe_distinguishes_error_categories.<locals>.AuthFailedProviderr   dummyNr  c                    d      )Nopenfigi_auth_failedr   r   r   r3   r  s      r   r'  z\test_provider_status_probe_distinguishes_error_categories.<locals>.AuthFailedProvider.search  s    #$:;;r   r  r  r  r4   r
  r'  r  s   r   AuthFailedProviderr    s    /3 	<r   r  c                  &    e Zd ZdZdZdd fd
Zy)Xtest_provider_status_probe_distinguishes_error_categories.<locals>.NetworkFailedProviderr   r  Nr  c                    d      )Nfmp_network_errorr   r  s      r   r'  z_test_provider_status_probe_distinguishes_error_categories.<locals>.NetworkFailedProvider.search  s    #$788r   r  r  s   r   NetworkFailedProviderr    s    /3 	9r   r  c                       e Zd ZdZdZdddZy)Ttest_provider_status_probe_distinguishes_error_categories.<locals>.NoResultsProviderr   r  Nr  c                   g S r   r   r;  s      r   r'  z[test_provider_status_probe_distinguishes_error_categories.<locals>.NoResultsProvider.search  s    Ir   r  r   r   r   NoResultsProviderr    s    /3 	r   r  r   ry  FinnhubSymbolLookupProviderT)probera  rf  r  auth_failedrm  zAuth fehlgeschlagenrd  network_errorNetzwerkfehlerre  
no_resultszKeine Treffer)r  market_datacatalog%jarvis_finance.market_data.candidatesr  r/  r   )	r  r  r  r  r  rr  rj  by_providerr  s	           @r   9test_provider_status_probe_distinguishes_error_categoriesr
    s.   88G< <9 9  !9;MN!46KL!>@QR33$3?H3;<C3z?C'<K<z";/=@@@z"8,0EEEEuk*o===uh'+;;;;y!+.,>>>y!(+>>> =s   C c                   |dz  }|dz  }|j                  d       |dz  j                  dd       | j                  d	t        |             | j	                  d
d       | j	                  dd       t               }|j                         }t        |      |j                  z   |j                  z   }d|vsJ d|vsJ y )Nr  r   Tr  r   zDOPENFIGI_API_KEY=super-secret-openfigi
FMP_API_KEY=super-secret-fmp
r   r   r   r   Fr   r_  zsuper-secret-openfigizsuper-secret-fmp)	r  r  r  r  r  r   
readouterrouterr)r  r  capsysr  r   rr  r  texts           r   9test_provider_status_does_not_log_or_return_secret_valuesr    s    "G	!GMM$Mv!!"y  EL!  M3S\B)59}e4+-H  "Hx=8<<'(,,6D"$...T)))r   c                 (   ddl m} m} ddlm} g  G fdd|      } |d      j                  dd	
      } |d      j                  dd	
      } |d      j                  dd	
      }|r|r|sJ |d   j                  dk(  sJ |d   j                  dk(  sJ |d   j                  dk(  sJ t        d D              sJ t        d D              sJ t        d D              sJ  |  |d         j                        }|d   dgk(  sJ y )Nr   r  r  c                      e Zd Zdd fd
Zy)\test_fmp_stable_isin_symbol_name_and_profile_price_preview_are_normalized.<locals>.StableFmpNr  c                   j                  |       d|v r	ddddddgS d|v rddddd	gS d
|v rddddd	gS d|v rddddd	gS d|v r
dddddddgS g S )N/stable/search-isinr8  r@  r  r7  rX   )r  r4   r  r5   r8   /stable/search-symbolr  z /stable/search-exchange-variants/stable/search-namer  Tr   {Gjo@)r  r5   r8   r  r  r  r  r  s      r   r  zftest_fmp_stable_isin_symbol_name_and_profile_price_preview_are_normalized.<locals>.StableFmp._get_json$  s    OOC $+#(2Sjp  {I  W\  ]  ^  ^&#-#(2Sjp  D  E  F  F1S8#(2Sjp  D  E  F  F$+#(2Sjp  D  E  F  F C'#(.e^bos  E  F  G  GIr   r  r  s   r   	StableFmpr  #  s    ,0 	r   r  r  r	  r7  r   rB  rL  r8  r  zfmp profilec              3  $   K   | ]  }d |v  
 yw)r  Nr   rE  r  s     r   rG  z\test_fmp_stable_isin_symbol_name_and_profile_price_preview_are_normalized.<locals>.<genexpr>9       @$+@   c              3  $   K   | ]  }d |v  
 yw)r  Nr   r  s     r   rG  z\test_fmp_stable_isin_symbol_name_and_profile_price_preview_are_normalized.<locals>.<genexpr>:  s     B#&#-Br  c              3  $   K   | ]  }d |v  
 yw)r  Nr   r  s     r   rG  z\test_fmp_stable_isin_symbol_name_and_profile_price_preview_are_normalized.<locals>.<genexpr>;  r  r  r  )r  r  r  r  ry  r'  r5   r  r  rO  r   )	r  r  ry  r  	isin_rows	name_rowsticker_rowsfirst_qsr  s	           @r   Itest_fmp_stable_isin_symbol_name_and_profile_price_preview_are_normalizedr%    s.   /DH%  +.55NX]5^I+.55<R`e5fIK077eQV7WK{22Q<...Q<""h...Q<$$555@x@@@@BBBBB@x@@@@!-334HH+...r   c                    ddl m} m} ddlm} g  G fdd|      } |d      j                  d      }|j                  J t        |j                        d	k(  sJ |j                  d
k(  sJ d   j                  d      sJ y )Nr   r  )r   c                      e Zd Zd fdZy)Ttest_fmp_price_provider_uses_stable_profile_without_logging_key.<locals>.StablePricec           
         j                  |dz   dj                  d t        |j                               D              z          ddddgS )N?&c              3  0   K   | ]  \  }}| d |   yw)=Nr   )rE  kvs      r   rG  zqtest_fmp_price_provider_uses_stable_profile_without_logging_key.<locals>.StablePrice._get_json.<locals>.<genexpr>H  s     1`AQCq*1`s   r8  r  rX   )r  r  r8   )r  joinsorteditems)r   pathparamsr  s      r   r  z^test_fmp_price_provider_uses_stable_profile_without_logging_key.<locals>.StablePrice._get_jsonG  sF    OOD3J1`PVP\P\P^I_1`)``a$v5IJJr   N)r3  r  r4  zdict[str, str]r  r  s   r   StablePricer(  F  s	    	Kr   r5  r  r	  r8  r  rX   z/stable/profile?)
r  r  r  !jarvis_finance.market_data.pricesr   r  r  r  r8   rN  )r  r  r   r5  r  r  s        @r   ?test_fmp_price_provider_uses_stable_profile_without_logging_keyr7  @  s    /HHK, K
 ,66u=E;;"""u{{x'''>>U"""A;!!"4555r   c                    ddl m} ddlm} ddlm} ddlm  G fdd|      }	  |d	      j                  d
d       t        d      # $ r)}t        |      dk(  sJ dt        |      vsJ Y d }~y d }~ww xY w)Nr   )	HTTPError)BytesIOr  r  c                      e Zd Zdd fd
Zy)Ytest_openfigi_429_is_reported_as_rate_limited_without_secret.<locals>.RateLimitedOpenFigiNr  c                    d      )Nopenfigi_rate_limitedr   )r   r  r  r  r  s       r   r  zdtest_openfigi_429_is_reported_as_rate_limited_without_secret.<locals>.RateLimitedOpenFigi._post_jsonY  s    #$;<<r   r  r  s   r   RateLimitedOpenFigir<  X  s    6: 	=r   r?  r  r	  r7  r   rB  zexpected rate limitr>  )urllib.errorr9  ior:  r  r   r  r  r'  AssertionErrorr  )r  r9  r:  r   r?  excr  s         @r   <test_openfigi_429_is_reported_as_rate_limited_without_secretrD  R  s    &IG=4 =4$45<<>_d<e
 233	  03x2222s3x///0s   A A;A66A;c                 v    ddl m} m}  G d d      }t               } ||ddg       \  }}|g k(  sJ |g k(  sJ y )Nr   )r   r   c                      e Zd ZdZdddZy)Ttest_online_search_not_called_when_include_external_false.<locals>.ExplodingProvidershould_not_runNr  c                   t        d      )Nz,provider was called during local-only search)rB  r;  s      r   r'  z[test_online_search_not_called_when_include_external_false.<locals>.ExplodingProvider.searchj  s     !OPPr   r(  r   r   r   ExplodingProviderrG  h  s    /3 	Qr   rJ  r8  r   r   )r  r   r   r   )r   r   rJ  r   r  rN   s         r   9test_online_search_not_called_when_include_external_falserK  e  sF    ZQ Q
 <D'eUbIND(r>>2::r   )r   )r!   r  r=  r  )r(   r  r=  intr<  )<
__future__r   decimalr   r   jarvis_finance.equity.manager   r   r   r   r	   r
   jarvis_finance.ledger.positionsr   r  r   r   r6  r   r   r   r   jarvis_finance.storage.databaser   !jarvis_finance.storage.migrationsr   r   r$   r)   rR   rf   rp   r   r   r   r   r   r   r   r  r0  r2  rT  r[  rs  rw  r  r  r  r  r  r  r  r  r
  r  r%  r7  rD  rK  r   r   r   <module>rS     s    "    @ ` I  I : >i !F%0Pl:/
d.
b16hFDvN:,zA84B*a aU*B$g..80DM 	%*`*J&7( ?F* /F6$4&r   