@@ -39,6 +39,7 @@ def __init__( self, filename ):
3939 self .filename = filename
4040 self .transactions = []
4141 self .current_prices = {}
42+ self .ticker_names = {} # Cache pour les noms des tickers
4243 self .load_data ()
4344
4445 def load_data ( self ):
@@ -55,6 +56,22 @@ def save_data( self ):
5556 with open (self .filename , 'w' , encoding = 'utf-8' ) as f :
5657 json .dump (self .transactions , f , indent = 2 , ensure_ascii = False )
5758
59+ def get_ticker_name ( self , ticker : str ) -> str :
60+ """Récupère le nom du ticker depuis yfinance (avec cache)"""
61+ if ticker in self .ticker_names :
62+ return self .ticker_names [ticker ]
63+
64+ try :
65+ _ticker = yfinance .Ticker ( ticker )
66+ stock_info = _ticker .get_info ()
67+ _short_name = stock_info .get ( 'shortName' , 'N/A' )
68+ self .ticker_names [ticker ] = _short_name
69+ return _short_name
70+ except Exception as e :
71+ print (f"Erreur lors de la récupération du nom pour { ticker } : { e } " )
72+ self .ticker_names [ticker ] = 'N/A'
73+ return 'N/A'
74+
5875 def add_transaction ( self , type_transaction : str , ticker : str , quantity : float ,
5976 price : float , date : str = None , fees : float = 0.0 ):
6077 """Ajoute une transaction (achat ou vente)"""
@@ -66,10 +83,15 @@ def add_transaction( self, type_transaction: str, ticker: str, quantity: float,
6683 else :
6784 total = quantity * price - fees
6885
86+ # Récupération du nom du ticker
87+ ticker_upper = ticker .upper ()
88+ name = self .get_ticker_name ( ticker_upper )
89+
6990 transaction = {
7091 'id' : len (self .transactions ) + 1 ,
7192 'type' : type_transaction .lower (),
72- 'ticker' : ticker .upper (),
93+ 'ticker' : ticker_upper ,
94+ 'name' : name ,
7395 'quantity' : quantity ,
7496 'price' : price ,
7597 'date' : date ,
@@ -94,7 +116,8 @@ def compute_positions( self ) -> Dict[str, Dict[str, float]]:
94116 "quantity" : 0.0 ,
95117 "total_cost" : 0.0 ,
96118 "total_sales" : 0.0 ,
97- "realized_pnl" : 0.0
119+ "realized_pnl" : 0.0 ,
120+ "name" : "N/A"
98121 })
99122
100123 for t in self .transactions :
@@ -103,6 +126,11 @@ def compute_positions( self ) -> Dict[str, Dict[str, float]]:
103126 price = float (t ["price" ])
104127 fees = float (t .get ("fees" , 0.0 ))
105128 side = t ["type" ].lower ()
129+ name = t .get ("name" , "N/A" )
130+
131+ # Stocker le nom si disponible
132+ if name != "N/A" :
133+ positions [ticker ]["name" ] = name
106134
107135 # Achat : on augmente la position
108136 if side == "achat" :
@@ -113,7 +141,7 @@ def compute_positions( self ) -> Dict[str, Dict[str, float]]:
113141 elif side == "vente" :
114142 current_qty = positions [ticker ]["quantity" ]
115143 if current_qty <= 0 :
116- # Vente sans position existante — on ignore ou on log
144+ # Vente sans position existante – on ignore ou on log
117145 continue
118146
119147 avg_cost = positions [ticker ]["total_cost" ] / current_qty
@@ -137,6 +165,11 @@ def compute_positions( self ) -> Dict[str, Dict[str, float]]:
137165 else :
138166 pos ["avg_cost" ] = 0.0
139167
168+ # Si le nom n'est toujours pas disponible, on le récupère
169+ # Compatibilité ascendante avec les transactions sans le champ 'name'
170+ #
171+ if pos ["name" ] == "N/A" :
172+ pos ["name" ] = self .get_ticker_name ( ticker )
140173 return dict ( positions )
141174
142175 # -------------------------------------------------------------------------
@@ -283,7 +316,7 @@ def setup_styles( self ):
283316 }
284317
285318 # 'flat' ▭ Plat Aucun relief, sans bordure apparente
286- # 'raised' ⧉ Relief sortant Fait ressortir le widget, comme s’ il était au-dessus du plan
319+ # 'raised' ⧉ Relief sortant Fait ressortir le widget, comme s' il était au-dessus du plan
287320 # 'sunken' ⧋ Relief enfoncé Fait paraître le widget enfoncé dans le plan
288321 # 'ridge' ⧠ Bord en relief double Une bordure double légèrement surélevée
289322 # 'groove' ⧠ Bord en creux double Une bordure double légèrement enfoncée
@@ -409,7 +442,7 @@ def setup_ui( self ):
409442
410443 # Onglet Transactions
411444 transactions_frame = ttk .Frame ( notebook , style = 'Main.TFrame' )
412- notebook .add ( transactions_frame , text = "📝 Transactions" )
445+ notebook .add ( transactions_frame , text = "📋 Transactions" )
413446 self .create_transactions_tab ( transactions_frame )
414447
415448 # Onglet Positions
@@ -498,7 +531,7 @@ def create_portfolio_json( self ):
498531 else :
499532 messagebox .showwarning (
500533 "Avertissement" ,
501- f"Le fichier existe déjà!\n "
534+ f"Le fichier existe déjà !\n "
502535 f"Chemin: { file_path } \n "
503536 f"Voulez-vous le remplacer ?" ,
504537 icon = 'warning'
@@ -616,6 +649,7 @@ def create_header( self, parent ):
616649 style = 'Primary.TButton'
617650 )
618651 self .update_button .pack ()
652+ self .update_button .config ( state = tk .DISABLED , text = "⏳ Mise à jour..." )
619653
620654 self .last_update_label = ttk .Label (
621655 update_frame ,
@@ -710,7 +744,7 @@ def create_transactions_tab( self, parent ):
710744
711745 self .transactions_tree = ttk .Treeview (
712746 tree_frame ,
713- columns = ("ID" , "Date" , "Type" , "Ticker" , "Quantité" , "Prix" , "Frais" , "Total" ),
747+ columns = ("ID" , "Date" , "Type" , "Ticker" , "Nom" , " Quantité" , "Prix" , "Frais" , "Total" ),
714748 show = "headings" ,
715749 yscrollcommand = scrollbar .set ,
716750 height = 6
@@ -722,16 +756,17 @@ def create_transactions_tab( self, parent ):
722756
723757 columns_config = [
724758 ("ID" , 40 ),
725- ("Date" , 100 ),
759+ ("Date" , 90 ),
726760 ("Type" , 80 ),
727- ("Ticker" , 80 ),
728- ("Quantité" , 100 ),
729- ("Prix" , 100 ),
761+ ("Ticker" , 70 ),
762+ ("Nom" , 120 ),
763+ ("Quantité" , 80 ),
764+ ("Prix" , 120 ),
730765 ("Frais" , 80 ),
731766 ("Total" , 120 )
732767 ]
733768
734- self .transactions_tree ["displaycolumns" ] = ("Date" , "Type" , "Ticker" , "Quantité" , "Prix" , "Frais" , "Total" )
769+ self .transactions_tree ["displaycolumns" ] = ("Date" , "Type" , "Ticker" , "Nom" , " Quantité" , "Prix" , "Frais" , "Total" )
735770
736771 for col , width in columns_config :
737772 self .transactions_tree .heading ( col , text = col )
@@ -751,16 +786,33 @@ def create_transactions_tab( self, parent ):
751786
752787 def create_positions_tab ( self , parent ):
753788 """Crée l'onglet des positions du portefeuille"""
754- positions_frame = ttk .Frame ( parent , style = 'Card.TFrame' , padding = 15 )
755- positions_frame .pack ( fill = tk .BOTH , expand = True , padx = 10 , pady = 10 )
789+ positions_frame = ttk .Frame ( parent , style = 'Card.TFrame' , padding = 15 )
790+ positions_frame .pack ( fill = tk .BOTH , expand = True , padx = 10 , pady = 10 )
791+
792+ # Frame pour aligner le titre et le checkbutton
793+ header_frame = ttk .Frame ( positions_frame , style = 'Card.TFrame' )
794+ header_frame .pack ( fill = tk .X , pady = ( 15 , 15 ) )
795+
796+ # Frame vide à gauche pour équilibrer
797+ left_spacer = ttk .Frame ( header_frame , style = 'Card.TFrame' )
798+ left_spacer .pack ( side = tk .LEFT , expand = True )
756799
757800 title = ttk .Label (
758- positions_frame ,
801+ header_frame ,
759802 text = "📊 Positions Actuelles du Portefeuille" ,
760803 style = 'Subtitle.TLabel'
761804 )
762- title .pack ( pady = ( 0 , 15 ))
763-
805+ title .pack ( side = tk .LEFT )
806+
807+ # Frame à droite contenant le checkbutton
808+ right_frame = ttk .Frame ( header_frame , style = 'Card.TFrame' )
809+ right_frame .pack ( side = tk .LEFT , expand = True )
810+
811+ self .var_filter_positions = tk .BooleanVar ( False )
812+ _chk = ttk .Checkbutton ( right_frame , text = "Positions Ouvertes" , variable = self .var_filter_positions , command = self .command_filter_position )
813+ _chk .pack ( side = tk .LEFT )
814+ Tooltip ( _chk , "Filtrer les positions ouvertes" )
815+
764816 tree_frame = ttk .Frame ( positions_frame )
765817 tree_frame .pack ( fill = tk .BOTH , expand = True )
766818
@@ -778,9 +830,9 @@ def create_positions_tab( self, parent ):
778830 scrollbar .config ( command = self .positions_tree .yview )
779831
780832 columns_config = [
781- ("Ticker" , 90 ),
782- ("Nom" , 100 ),
783- ("Quantité" , 90 ),
833+ ("Ticker" , 70 ),
834+ ("Nom" , 120 ),
835+ ("Quantité" , 80 ),
784836 ("Coût Moyen" , 120 ),
785837 ("Prix Courant" , 120 ),
786838 ("Valeur Actuelle" , 130 ),
@@ -874,6 +926,13 @@ def delete_transaction(self):
874926
875927 # -------------------------------------------------------------------------
876928
929+ def command_filter_position ( self ):
930+ """ Filtre les positions ouvertes """
931+
932+ self .update_positions_list ()
933+
934+ # -------------------------------------------------------------------------
935+
877936 def on_transactions_tree_item_selected ( self , event ):
878937
879938 selected = self .transactions_tree .selection ()
@@ -911,7 +970,7 @@ def on_complete():
911970
912971 def _on_prices_updated ( self ):
913972 """Callback après mise à jour des prix"""
914- self .update_button .config (state = tk .NORMAL , text = "🔄 Actualiser les Prix" )
973+ self .update_button .config ( state = tk .NORMAL , text = "🔄 Actualiser les Prix" )
915974 self .last_update_label .config (
916975 text = f"Dernière mise à jour: { datetime .now ().strftime ('%H:%M:%S' )} "
917976 )
@@ -962,6 +1021,7 @@ def update_transactions_list( self ):
9621021 t ['date' ],
9631022 t ['type' ].upper (),
9641023 t ['ticker' ],
1024+ t .get ('name' , 'N/A' ),
9651025 f"{ t ['quantity' ]:.0f} " ,
9661026 f"{ t ['price' ]:.2f} €" ,
9671027 f"{ t ['fees' ]:.2f} €" ,
@@ -977,11 +1037,12 @@ def update_transactions_list( self ):
9771037
9781038 def update_positions_list ( self ):
9791039 """Met à jour la liste des positions"""
1040+
9801041 for item in self .positions_tree .get_children ():
9811042 self .positions_tree .delete ( item )
9821043
9831044 positions = self .portfolio .compute_positions ()
984-
1045+
9851046 for ticker , pos in sorted ( positions .items () ):
9861047 quantity = pos ['quantity' ]
9871048 avg_cost = pos ['total_cost' ] / quantity if quantity != 0 else 0.0
@@ -994,20 +1055,11 @@ def update_positions_list( self ):
9941055 unrealized_pnl_text = f"{ '+' if unrealized_pnl >= 0 else '' } { unrealized_pnl :.2f} €" if current_price > 0 else "N/A"
9951056 realized_pnl_text = f"{ '+' if pos ['realized_pnl' ] >= 0 else '' } { pos ['realized_pnl' ]:.2f} €"
9961057
997- # Dont try to get stock info if current_value_text is N/A
998- # current_value_text == "N/A" means ticker does not exist
999- #
1000- _short_name = "N/A"
1001- if current_value_text != "N/A" :
1002- _ticker = yfinance .Ticker ( ticker )
1003- stock_info = _ticker .get_info ()
1004- _short_name = stock_info .get ( 'shortName' )
1058+ # Récupération du nom depuis la position (déjà mis en cache)
1059+ _short_name = pos .get ('name' , 'N/A' )
10051060
10061061 _condition_positive = pos ['realized_pnl' ] + unrealized_pnl >= 0
1007- self .positions_tree .insert (
1008- '' ,
1009- 'end' ,
1010- values = (
1062+ val = (
10111063 ticker ,
10121064 _short_name ,
10131065 f"{ pos ['quantity' ]:.0f} " ,
@@ -1016,7 +1068,20 @@ def update_positions_list( self ):
10161068 current_value_text ,
10171069 unrealized_pnl_text ,
10181070 realized_pnl_text
1019- ),
1071+ )
1072+
1073+ # Filtrer les positions fermée
1074+ #
1075+ if self .var_filter_positions .get () == True :
1076+
1077+ # N'afficher que les positions avec 'quantity' != 0
1078+ if pos ['quantity' ] == 0 : # quantité
1079+ continue
1080+
1081+ self .positions_tree .insert (
1082+ '' ,
1083+ 'end' ,
1084+ values = val ,
10201085 tags = ( 'positive' if _condition_positive else 'negative' ,)
10211086 )
10221087
@@ -1064,7 +1129,7 @@ def update_report(self):
10641129 report += "\n "
10651130
10661131 # Transactions
1067- report += "📝 HISTORIQUE DES TRANSACTIONS\n "
1132+ report += "📋 HISTORIQUE DES TRANSACTIONS\n "
10681133 report += "-" * 80 + "\n "
10691134
10701135 if self .portfolio .transactions :
0 commit comments