@@ -461,6 +461,21 @@ def buy(symbol, amount=None):
461461 'trade_time' : time .time ()
462462 }
463463 print (f"[DEBUG] Actual USDC balance after buy attempt: { balance ['usd' ]} " )
464+
465+ # Send investment alert
466+ import asyncio
467+ try :
468+ asyncio .create_task (send_alarm_message (
469+ f"π° INVESTMENT MADE\n \n "
470+ f"π { symbol } \n "
471+ f"π΅ Amount: ${ trade_amount :.2f} \n "
472+ f"π Price: ${ price :.6f} \n "
473+ f"π’ Quantity: { qty :.6f} \n "
474+ f"πΌ Remaining USDC: ${ balance ['usd' ]:.2f} "
475+ ))
476+ except Exception as e :
477+ print (f"[ALERT ERROR] Could not send investment alert: { e } " )
478+
464479 return positions [symbol ]
465480 except BinanceAPIException as e :
466481 print (f"[BUY ERROR] { symbol } : { e } " )
@@ -578,21 +593,41 @@ def auto_sell_momentum_positions(min_profit=MIN_PROFIT, trailing_stop=TRAIL_STOP
578593
579594 if should_sell :
580595 exit_price , fee , _ = sell (symbol , qty )
581- exit_time = time .time ()
582- tax = estimate_trade_tax (entry , exit_price , qty , trade_time , exit_time )
583- log_trade (
584- symbol = symbol ,
585- entry = entry ,
586- exit_price = exit_price ,
587- qty = qty ,
588- trade_time = trade_time ,
589- exit_time = exit_time ,
590- fees = fee ,
591- tax = tax ,
592- action = "sell"
593- )
594- print (f"[MOMENTUM SELL] { symbol } : { reason } " )
595- del positions [symbol ]
596+ if exit_price is not None :
597+ exit_time = time .time ()
598+ tax = estimate_trade_tax (entry , exit_price , qty , trade_time , exit_time )
599+ pnl_dollar = (exit_price - entry ) * qty
600+
601+ log_trade (
602+ symbol = symbol ,
603+ entry = entry ,
604+ exit_price = exit_price ,
605+ qty = qty ,
606+ trade_time = trade_time ,
607+ exit_time = exit_time ,
608+ fees = fee ,
609+ tax = tax ,
610+ action = "sell"
611+ )
612+
613+ # Send sell alert
614+ import asyncio
615+ try :
616+ asyncio .create_task (send_alarm_message (
617+ f"πΈ POSITION SOLD\n \n "
618+ f"π { symbol } \n "
619+ f"π Entry: ${ entry :.6f} \n "
620+ f"π Exit: ${ exit_price :.6f} \n "
621+ f"π’ Quantity: { qty :.6f} \n "
622+ f"π° PnL: ${ pnl_dollar :.2f} ({ pnl_pct :.2f} %)\n "
623+ f"β±οΈ Hold Time: { held_for / 60 :.1f} min\n "
624+ f"π Reason: { reason } "
625+ ))
626+ except Exception as e :
627+ print (f"[ALERT ERROR] Could not send sell alert: { e } " )
628+
629+ print (f"[MOMENTUM SELL] { symbol } : { reason } " )
630+ del positions [symbol ]
596631 except Exception as e :
597632 print (f"[AUTO-SELL ERROR] { symbol } : { e } " )
598633
@@ -1461,6 +1496,8 @@ async def check_and_alarm_high_volume(context=None):
14611496 if not stats :
14621497 return
14631498 alarmed = []
1499+ full_momentum_alerts = []
1500+
14641501 for symbol , s in stats .items ():
14651502 vol = s .get ("volume_1d" , 0 ) or 0
14661503 if vol > MIN_VOLUME :
@@ -1471,18 +1508,42 @@ async def check_and_alarm_high_volume(context=None):
14711508 m5 , ind5 = check_advanced_momentum (symbol , '5m' , d5 )
14721509 m15 ,ind15 = check_advanced_momentum (symbol , '15m' , d15 )
14731510
1474- pct1 = ind1 .get ('pct_change' , 0 ) if ind1 else 0
1475- pct5 = ind5 .get ('pct_change' , 0 ) if ind5 else 0
1476- pct15 = ind15 .get ('pct_change' , 0 ) if ind15 else 0
1477-
1478- diff1 = (d1 * 100 ) - pct1
1479- diff5 = (d5 * 100 ) - pct5
1480- diff15 = (d15 * 100 ) - pct15
1481-
1482- alarmed .append ((symbol , vol , m1 , m5 , m15 , pct1 , pct5 , pct15 , diff1 , diff5 , diff15 , d1 , d5 , d15 ))
1511+ # Count momentum stages met
1512+ momentum_count = sum ([m1 , m5 , m15 ])
1513+
1514+ # Full momentum alert (all 3 stages)
1515+ if momentum_count == 3 :
1516+ pct1 = ind1 .get ('pct_change' , 0 ) if ind1 else 0
1517+ pct5 = ind5 .get ('pct_change' , 0 ) if ind5 else 0
1518+ pct15 = ind15 .get ('pct_change' , 0 ) if ind15 else 0
1519+
1520+ full_momentum_alerts .append ((symbol , vol , pct1 , pct5 , pct15 , d1 , d5 , d15 ))
1521+
1522+ # Partial momentum alert (2 out of 3 stages)
1523+ elif momentum_count >= 2 :
1524+ pct1 = ind1 .get ('pct_change' , 0 ) if ind1 else 0
1525+ pct5 = ind5 .get ('pct_change' , 0 ) if ind5 else 0
1526+ pct15 = ind15 .get ('pct_change' , 0 ) if ind15 else 0
1527+
1528+ diff1 = (d1 * 100 ) - pct1
1529+ diff5 = (d5 * 100 ) - pct5
1530+ diff15 = (d15 * 100 ) - pct15
1531+
1532+ alarmed .append ((symbol , vol , m1 , m5 , m15 , pct1 , pct5 , pct15 , diff1 , diff5 , diff15 , d1 , d5 , d15 ))
1533+
1534+ # Send full momentum alerts first (highest priority)
1535+ if full_momentum_alerts :
1536+ msg = "π FULL MOMENTUM ALERT - ALL 3 STAGES MET! π\n \n "
1537+ for (symbol , vol , pct1 , pct5 , pct15 , d1 , d5 , d15 ) in full_momentum_alerts :
1538+ msg += f"π― { symbol } : Volume = { vol :,.0f} \n "
1539+ msg += f" 1m: β
{ pct1 :.2f} % (Target { d1 * 100 :.2f} %)\n "
1540+ msg += f" 5m: β
{ pct5 :.2f} % (Target { d5 * 100 :.2f} %)\n "
1541+ msg += f" 15m: β
{ pct15 :.2f} % (Target { d15 * 100 :.2f} %)\n \n "
1542+ await send_alarm_message (msg )
14831543
1544+ # Send partial momentum alerts (2/3 stages)
14841545 if alarmed :
1485- msg = "π¨ High Volume Alert (dynamic thresholds ):\n \n "
1546+ msg = "π¨ High Volume Alert (2/3 momentum stages reached ):\n \n "
14861547 for (symbol , vol , m1 , m5 , m15 , pct1 , pct5 , pct15 , diff1 , diff5 , diff15 , d1 , d5 , d15 ) in alarmed :
14871548 msg += f"π { symbol } : Volume = { vol :,.0f} \n "
14881549 msg += f" 1m: { 'β
' if m1 else 'β' } { pct1 :.2f} % (Target { d1 * 100 :.2f} %, need { max (0 ,diff1 ):.2f} %)\n "
@@ -1501,6 +1562,30 @@ async def alarm_job(context: CallbackContext):
15011562 positions .update (rebuild_cost_basis (trade_log ))
15021563 reconcile_positions_with_binance (client , positions )
15031564 print (f"[INFO] Bot paused state on startup: { is_paused ()} " )
1565+
1566+ # Send startup alert
1567+ import asyncio
1568+ try :
1569+ async def send_startup_alert ():
1570+ fetch_usdc_balance ()
1571+ total_positions = len ([p for p in positions .items () if get_latest_price (p [0 ]) and p [1 ]['qty' ] * get_latest_price (p [0 ]) > DUST_LIMIT ])
1572+
1573+ await send_alarm_message (
1574+ f"π€ BOT STARTED\n \n "
1575+ f"β
System initialized successfully\n "
1576+ f"π° USDC Balance: ${ balance ['usd' ]:.2f} \n "
1577+ f"π Active Positions: { total_positions } \n "
1578+ f"βΈοΈ Trading Status: { 'PAUSED' if is_paused () else 'ACTIVE' } \n "
1579+ f"π Startup Time: { datetime .now ().strftime ('%Y-%m-%d %H:%M:%S' )} "
1580+ )
1581+
1582+ loop = asyncio .new_event_loop ()
1583+ asyncio .set_event_loop (loop )
1584+ loop .run_until_complete (send_startup_alert ())
1585+ loop .close ()
1586+ except Exception as e :
1587+ print (f"[ALERT ERROR] Could not send startup alert: { e } " )
1588+
15041589 try :
15051590 trading_thread = threading .Thread (target = trading_loop , daemon = True )
15061591 trading_thread .start ()
0 commit comments