-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmonitor.py
More file actions
94 lines (82 loc) · 3.82 KB
/
monitor.py
File metadata and controls
94 lines (82 loc) · 3.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
from datetime import datetime
import pytz
import portfolio
import prices
import orders
PARIS = pytz.timezone("Europe/Paris")
def check_positions(send_fn) -> None:
"""Scan des positions 4x/jour. Alerte si SL ou TP atteint."""
data = portfolio.load()
positions = data.get("positions", {})
now = datetime.now(PARIS).strftime("%H:%M")
print(f"\n[{datetime.now(PARIS).strftime('%Y-%m-%d %H:%M:%S')}] Scan positions...")
if not positions:
send_fn(f"⚠️ STATUS {now} — Aucune position active.")
return
alerts = []
status_lines = [f"📊 STATUS {now}"]
for name, cfg in positions.items():
quote = prices.get_quote(cfg["ticker"])
price = quote.get("price")
sym = prices.currency_symbol(quote.get("currency", "EUR"))
if price is None:
if quote.get("status") in ("suspended", "error"):
status_lines.append(f" ⛔ {name}: COURS SUSPENDU — non vendable")
else:
status_lines.append(f" ⚠️ {name}: prix indisponible")
continue
change_pct = ((price - cfg["entry_price"]) / cfg["entry_price"]) * 100
pnl = (price - cfg["entry_price"]) * cfg["qty"]
icon = "📈" if change_pct >= 0 else "📉"
status_lines.append(
f" {icon} {name}: {sym}{price} ({change_pct:+.2f}%) | P&L: {sym}{pnl:+.0f}"
f"\n SL {sym}{cfg['target_low']} — TP {sym}{cfg['target_high']}"
)
if price >= cfg["target_high"]:
alerts.append({"type": "TP", "name": name, "cfg": cfg, "price": price, "change": change_pct, "pnl": pnl, "sym": sym})
elif price <= cfg["target_low"]:
if not cfg.get("sl_breach_notified", False):
# First detection only — send once, then silence
alerts.append({"type": "SL_BREACH", "name": name, "cfg": cfg, "price": price, "change": change_pct, "pnl": pnl, "sym": sym})
elif price <= cfg["target_low"] * 1.05:
# Approaching SL (within 5%) — send instructions
alerts.append({"type": "SL_PROCHE", "name": name, "cfg": cfg, "price": price, "change": change_pct, "pnl": pnl, "sym": sym})
sent_any = False
for a in alerts:
cfg = a["cfg"]
sym = a["sym"]
if a["type"] == "TP":
msg = (
f"🎯 TAKE-PROFIT ATTEINT — {a['name']}\n\n"
f"Prix: {sym}{a['price']} ({a['change']:+.2f}%)\n"
f"P&L: {sym}{a['pnl']:+.0f}\n\n"
+ orders.take_profit(cfg["ticker"], cfg["qty"], cfg["target_high"])
+ "\n\n💡 Vente conseillée. Confirme sur Bourse Direct."
)
elif a["type"] == "SL_BREACH":
msg = (
f"🚨 STOP-LOSS DÉPASSÉ — {a['name']}\n\n"
f"Prix: {sym}{a['price']} ({a['change']:+.2f}%)\n"
f"P&L: {sym}{a['pnl']:+.0f}\n"
f"SL: {sym}{cfg['target_low']}\n\n"
f"Analyse nécessaire pour décision — /research {cfg['ticker']}\n\n"
f"Cette alerte ne sera plus répétée. Revue mensuelle le 1er du mois."
)
send_fn(msg)
portfolio.mark_sl_breach(a["name"])
sent_any = True
continue
else:
msg = (
f"⚠️ STOP-LOSS PROCHE — {a['name']}\n\n"
f"Prix: {sym}{a['price']} ({a['change']:+.2f}%)\n"
f"P&L: {sym}{a['pnl']:+.0f}\n"
f"Seuil SL: {sym}{cfg['target_low']}\n\n"
f"Vérifier que votre ordre stop est actif sur Bourse Direct.\n\n"
+ orders.stop_loss(cfg["ticker"], cfg["qty"], cfg["target_low"])
)
send_fn(msg)
sent_any = True
if not sent_any:
send_fn("\n".join(status_lines))
print("✅ Status envoyé — pas d'alerte")