-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnotification_handler.py
More file actions
349 lines (285 loc) · 15.1 KB
/
Copy pathnotification_handler.py
File metadata and controls
349 lines (285 loc) · 15.1 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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# notification_handler.py
# Module 5: Notification Handler - Email + SMS alerts
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import logging
import os
from dotenv import load_dotenv
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
load_dotenv()
class NotificationHandler:
"""
Send trading signal alerts via Email and SMS
"""
def __init__(self, use_email=True, use_sms=False):
"""
Args:
use_email: Send email notifications (default: True)
use_sms: Send SMS via Twilio (default: False - requires paid account)
"""
self.use_email = use_email
self.use_sms = use_sms
# Email setup
if use_email:
self.email_sender = os.getenv('EMAIL_SENDER')
self.email_password = os.getenv('EMAIL_PASSWORD')
self.email_recipient = os.getenv('EMAIL_RECIPIENT')
if not all([self.email_sender, self.email_password, self.email_recipient]):
logger.warning("⚠️ Email credentials incomplete. Email notifications disabled.")
self.use_email = False
# SMS setup (Twilio)
if use_sms:
try:
from twilio.rest import Client
account_sid = os.getenv('TWILIO_ACCOUNT_SID')
auth_token = os.getenv('TWILIO_AUTH_TOKEN')
self.twilio_client = Client(account_sid, auth_token)
self.twilio_phone = os.getenv('TWILIO_PHONE_NUMBER')
self.recipient_phone = os.getenv('RECIPIENT_PHONE')
if not all([account_sid, auth_token, self.twilio_phone, self.recipient_phone]):
logger.warning("⚠️ Twilio credentials incomplete. SMS disabled.")
self.use_sms = False
except Exception as e:
logger.warning(f"⚠️ Twilio setup failed: {e}. SMS disabled.")
self.use_sms = False
logger.info(f"✓ NotificationHandler initialized (Email: {self.use_email}, SMS: {self.use_sms})")
def send_signal(self, signal_type, signal_details, capital=100000):
"""
Send trading signal via Email and/or SMS
Args:
signal_type: 'BUY' or 'SELL'
signal_details: Dict with entry, SL, target, etc.
capital: Total capital (for risk % calculation)
Returns:
bool: True if at least one notification sent successfully
"""
if signal_type == 'HOLD':
return False
try:
risk_percent = (signal_details['risk'] / capital) * 100
# Format email body
email_body = self._format_email_body(signal_details, risk_percent)
# Format SMS body (shorter)
sms_body = self._format_sms_body(signal_details, risk_percent)
success = False
# Send Email
if self.use_email:
if self._send_email(signal_details['symbol'], email_body):
success = True
# Send SMS
if self.use_sms:
if self._send_sms(sms_body):
success = True
return success
except Exception as e:
logger.error(f"✗ Error sending notification: {str(e)}")
return False
def _send_email(self, symbol, body):
"""Send email notification"""
try:
msg = MIMEMultipart()
msg['From'] = self.email_sender
msg['To'] = self.email_recipient
msg['Subject'] = f"🎯 SWING TRADE SIGNAL: {symbol}"
msg.attach(MIMEText(body, 'html'))
# Connect to Gmail SMTP
with smtplib.SMTP('smtp.gmail.com', 587) as server:
server.starttls()
server.login(self.email_sender, self.email_password)
server.send_message(msg)
logger.info(f"✓ Email sent to {self.email_recipient}")
return True
except Exception as e:
logger.error(f"✗ Email send failed: {str(e)}")
return False
def _send_sms(self, body):
"""Send SMS via Twilio"""
try:
message = self.twilio_client.messages.create(
body=body,
from_=self.twilio_phone,
to=self.recipient_phone
)
logger.info(f"✓ SMS sent (SID: {message.sid})")
return True
except Exception as e:
logger.error(f"✗ SMS send failed: {str(e)}")
return False
def _format_email_body(self, signal_details, risk_percent):
"""Format email with HTML styling"""
entry_price = signal_details['entry_price']
stop_loss = signal_details['stop_loss']
target = signal_details['target_price']
rsi = signal_details['indicators']['rsi']
macd = signal_details['indicators']['macd']
adx = signal_details['indicators']['adx']
html = f"""
<html>
<body style="font-family: Arial, sans-serif; background-color: #f5f5f5; padding: 20px;">
<div style="background-color: white; border-radius: 10px; padding: 20px; max-width: 600px; margin: 0 auto;">
<h2 style="color: #27ae60; text-align: center;">🎯 SWING TRADE SIGNAL - BUY</h2>
<hr>
<h3 style="color: #2c3e50;">{signal_details['symbol']}</h3>
<p><strong>Current Price:</strong> ₹{entry_price}</p>
<p><strong>Time:</strong> {signal_details['timestamp'].strftime('%Y-%m-%d %H:%M IST')}</p>
<hr>
<h3 style="color: #2c3e50;">📊 Technical Analysis</h3>
<table style="width: 100%; border-collapse: collapse;">
<tr style="background-color: #ecf0f1;">
<td style="padding: 10px; border: 1px solid #bdc3c7;"><strong>RSI (14)</strong></td>
<td style="padding: 10px; border: 1px solid #bdc3c7;">{rsi:.2f}</td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid #bdc3c7;"><strong>MACD</strong></td>
<td style="padding: 10px; border: 1px solid #bdc3c7;">{macd:.4f} (Bullish)</td>
</tr>
<tr style="background-color: #ecf0f1;">
<td style="padding: 10px; border: 1px solid #bdc3c7;"><strong>ADX (Trend)</strong></td>
<td style="padding: 10px; border: 1px solid #bdc3c7;">{adx:.2f} (Strong)</td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid #bdc3c7;"><strong>Entry Type</strong></td>
<td style="padding: 10px; border: 1px solid #bdc3c7;">{'Breakout' if signal_details['breakout'] else 'Pullback'}</td>
</tr>
</table>
<h3 style="color: #2c3e50;">📋 Fundamentals</h3>
<table style="width: 100%; border-collapse: collapse;">
<tr style="background-color: #ecf0f1;">
<td style="padding: 10px; border: 1px solid #bdc3c7;"><strong>P/E Ratio</strong></td>
<td style="padding: 10px; border: 1px solid #bdc3c7;">{signal_details['fundamentals']['pe_ratio']}</td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid #bdc3c7;"><strong>Debt-to-Equity</strong></td>
<td style="padding: 10px; border: 1px solid #bdc3c7;">{signal_details['fundamentals']['debt_to_equity']}</td>
</tr>
<tr style="background-color: #ecf0f1;">
<td style="padding: 10px; border: 1px solid #bdc3c7;"><strong>ROE</strong></td>
<td style="padding: 10px; border: 1px solid #bdc3c7;">{signal_details['fundamentals']['roe']}</td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid #bdc3c7;"><strong>Revenue Growth</strong></td>
<td style="padding: 10px; border: 1px solid #bdc3c7;">{signal_details['fundamentals']['revenue_growth']}</td>
</tr>
</table>
<h3 style="color: #2c3e50;">💰 Trade Setup (1:3 Risk:Reward)</h3>
<table style="width: 100%; border-collapse: collapse;">
<tr style="background-color: #ecf0f1;">
<td style="padding: 10px; border: 1px solid #bdc3c7;"><strong>Entry Price</strong></td>
<td style="padding: 10px; border: 1px solid #bdc3c7; color: #27ae60; font-weight: bold;">₹{entry_price:.2f}</td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid #bdc3c7;"><strong>Stop-Loss</strong></td>
<td style="padding: 10px; border: 1px solid #bdc3c7; color: #e74c3c; font-weight: bold;">₹{stop_loss:.2f}</td>
</tr>
<tr style="background-color: #ecf0f1;">
<td style="padding: 10px; border: 1px solid #bdc3c7;"><strong>Target Price</strong></td>
<td style="padding: 10px; border: 1px solid #bdc3c7; color: #3498db; font-weight: bold;">₹{target:.2f}</td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid #bdc3c7;"><strong>Position Size</strong></td>
<td style="padding: 10px; border: 1px solid #bdc3c7;">{int(signal_details['position_size'])} shares</td>
</tr>
<tr style="background-color: #ecf0f1;">
<td style="padding: 10px; border: 1px solid #bdc3c7;"><strong>Risk Amount</strong></td>
<td style="padding: 10px; border: 1px solid #bdc3c7;">₹{signal_details['risk']} ({risk_percent:.1f}%)</td>
</tr>
<tr>
<td style="padding: 10px; border: 1px solid #bdc3c7;"><strong>Reward Amount</strong></td>
<td style="padding: 10px; border: 1px solid #bdc3c7;">₹{signal_details['reward']}</td>
</tr>
<tr style="background-color: #ecf0f1;">
<td style="padding: 10px; border: 1px solid #bdc3c7;"><strong>Risk:Reward Ratio</strong></td>
<td style="padding: 10px; border: 1px solid #bdc3c7; font-weight: bold;">1:{signal_details['risk_reward_ratio']:.1f}</td>
</tr>
</table>
<h3 style="color: #2c3e50; margin-top: 20px;">⚡ Action Required</h3>
<p style="background-color: #f9f9f9; padding: 15px; border-left: 4px solid #27ae60;">
<strong>Place a BUY LIMIT order at ₹{entry_price:.2f}</strong> with Stop-Loss at ₹{stop_loss:.2f}<br>
Hold until target ₹{target:.2f} or stop-loss is hit.<br>
<strong>Expected hold duration:</strong> 3-10 trading days
</p>
<hr>
<p style="color: #7f8c8d; font-size: 12px; text-align: center;">
This is an automated trading signal. Do your own due diligence before trading.
</p>
</div>
</body>
</html>
"""
return html
def _format_sms_body(self, signal_details, risk_percent):
"""Format SMS (160 chars max)"""
symbol = signal_details['symbol']
entry = signal_details['entry_price']
sl = signal_details['stop_loss']
target = signal_details['target_price']
sms = f"🎯 BUY {symbol} @ ₹{entry:.0f} | SL: ₹{sl:.0f} | Target: ₹{target:.0f} | Risk: {risk_percent:.0f}%"
return sms
def send_alert(self, subject, body):
"""
Generic operational/system-health alert email — distinct from
send_signal() (which expects a trade-signal-shaped dict) and
_send_email() (which hardcodes a "SWING TRADE SIGNAL" subject line).
Added to fix a real bug: run_paper_trading.py already calls
alert_notifier.send_alert(subject=..., body=...) at two safety
checkpoints (price-fetch health check failing, and the drawdown
circuit breaker activating) — but this method never existed here,
so triggering EITHER condition raised AttributeError and crashed
the entire run_eod() outright, since neither call site is wrapped
in a try/except. Worse, this meant the exact conditions meant to
warn Tanmay something was wrong (degraded price data, or a
drawdown halt) would silently fail to notify him at all — the
crash happened inside the call meant to send that warning.
Never raises — an alert failing to send should never crash the run
that was trying to warn about a problem in the first place. Always
logs the alert (visible in GitHub Actions logs) regardless of
whether email is configured or delivery succeeds.
"""
logger.warning(f"🔔 ALERT: {subject}\n{body}")
if not self.use_email:
return False
try:
msg = MIMEMultipart()
msg['From'] = self.email_sender
msg['To'] = self.email_recipient
msg['Subject'] = f"⚠️ {subject}"
msg.attach(MIMEText(body.replace('\n', '<br>'), 'html'))
with smtplib.SMTP('smtp.gmail.com', 587) as server:
server.starttls()
server.login(self.email_sender, self.email_password)
server.send_message(msg)
logger.info(f"✓ Alert email sent to {self.email_recipient}")
return True
except Exception as e:
logger.error(f"✗ Alert email failed: {str(e)}")
return False
def send_test_email(self):
"""Send test email to verify setup"""
try:
test_html = """
<html>
<body style="font-family: Arial, sans-serif;">
<div style="background-color: white; border-radius: 10px; padding: 20px;">
<h2 style="color: #27ae60;">✓ Test Email Successful!</h2>
<p>Your NSE Swing Trading Bot email notifications are working.</p>
<p>You will receive trading signals at this email address.</p>
</div>
</body>
</html>
"""
msg = MIMEMultipart()
msg['From'] = self.email_sender
msg['To'] = self.email_recipient
msg['Subject'] = "✓ NSE Trading Bot - Email Test"
msg.attach(MIMEText(test_html, 'html'))
with smtplib.SMTP('smtp.gmail.com', 587) as server:
server.starttls()
server.login(self.email_sender, self.email_password)
server.send_message(msg)
logger.info("✓ Test email sent successfully!")
return True
except Exception as e:
logger.error(f"✗ Test email failed: {str(e)}")
return False