Skip to content

Commit 5dc2c46

Browse files
author
Mabyre
committed
add ticker name in json + filter opened positions
1 parent a5224e6 commit 5dc2c46

File tree

2 files changed

+101
-36
lines changed

2 files changed

+101
-36
lines changed
Binary file not shown.

TradingInPython/_internal/portfolios/portfolio.py

Lines changed: 101 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -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 sil é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

Comments
 (0)