2222Date: July 2025
2323"""
2424
25+ import signal
26+ import sys
2527import time
2628from decimal import Decimal
2729
@@ -344,23 +346,106 @@ def display_strategy_analysis(strategy):
344346 return signal_data
345347
346348
349+ # Global variables for cleanup
350+ _cleanup_managers = {}
351+ _cleanup_initiated = False
352+
353+ def _emergency_cleanup (signum = None , frame = None ):
354+ """Emergency cleanup function called on signal interruption."""
355+ global _cleanup_initiated
356+ if _cleanup_initiated :
357+ print ("\n 🚨 Already cleaning up, please wait..." )
358+ return
359+
360+ _cleanup_initiated = True
361+
362+ if signum :
363+ signal_name = signal .Signals (signum ).name
364+ print (f"\n 🚨 Received { signal_name } signal - initiating emergency cleanup!" )
365+ else :
366+ print ("\n 🚨 Initiating emergency cleanup!" )
367+
368+ if _cleanup_managers :
369+ print ("⚠️ Emergency position and order cleanup in progress..." )
370+
371+ try :
372+ order_manager = _cleanup_managers .get ("order_manager" )
373+ position_manager = _cleanup_managers .get ("position_manager" )
374+ data_manager = _cleanup_managers .get ("data_manager" )
375+
376+ if order_manager and position_manager :
377+ # Get current state
378+ positions = position_manager .get_all_positions ()
379+ orders = order_manager .search_open_orders ()
380+
381+ if positions or orders :
382+ print (f"🚫 Emergency: Cancelling { len (orders )} orders and closing { len (positions )} positions..." )
383+
384+ # Cancel all orders immediately
385+ for order in orders :
386+ try :
387+ order_manager .cancel_order (order .id )
388+ print (f" ✅ Cancelled order { order .id } " )
389+ except :
390+ print (f" ❌ Failed to cancel order { order .id } " )
391+
392+ # Close all positions with market orders
393+ for pos in positions :
394+ try :
395+ close_side = 1 if pos .type == 1 else 0
396+ close_response = order_manager .place_market_order (
397+ contract_id = pos .contractId ,
398+ side = close_side ,
399+ size = pos .size
400+ )
401+ if close_response .success :
402+ print (f" ✅ Emergency close order: { close_response .order_id } " )
403+ except :
404+ print (f" ❌ Failed to close position { pos .contractId } " )
405+
406+ print ("⏳ Waiting 3 seconds for emergency orders to process..." )
407+ time .sleep (3 )
408+ else :
409+ print ("✅ No positions or orders to clean up" )
410+
411+ # Stop data feed
412+ if data_manager :
413+ try :
414+ data_manager .stop_realtime_feed ()
415+ print ("🧹 Real-time feed stopped" )
416+ except :
417+ pass
418+
419+ except Exception as e :
420+ print (f"❌ Emergency cleanup error: { e } " )
421+
422+ print ("🚨 Emergency cleanup completed - check your trading platform!" )
423+ sys .exit (1 )
424+
347425def wait_for_user_confirmation (message : str ) -> bool :
348426 """Wait for user confirmation before proceeding."""
349427 print (f"\n ⚠️ { message } " )
350428 try :
351429 response = input ("Continue? (y/N): " ).strip ().lower ()
352430 return response == "y"
353- except EOFError :
354- # Handle EOF when input is piped (default to no for safety)
355- print ("N (EOF detected - defaulting to No for safety)" )
431+ except ( EOFError , KeyboardInterrupt ) :
432+ # Handle EOF when input is piped or Ctrl+C during input
433+ print ("\n N (Interrupted - defaulting to No for safety)" )
356434 return False
357435
358436
359437def main ():
360438 """Demonstrate multi-timeframe trading strategy."""
439+ global _cleanup_managers
440+
441+ # Register signal handlers for emergency cleanup
442+ signal .signal (signal .SIGINT , _emergency_cleanup ) # Ctrl+C
443+ signal .signal (signal .SIGTERM , _emergency_cleanup ) # Termination signal
444+
361445 logger = setup_logging (level = "INFO" )
362446 print ("🚀 Multi-Timeframe Trading Strategy Example" )
363447 print ("=" * 60 )
448+ print ("📋 Emergency cleanup registered (Ctrl+C will close positions/orders)" )
364449
365450 # Safety warning
366451 print ("⚠️ WARNING: This strategy can place REAL ORDERS!" )
@@ -406,18 +491,25 @@ def main():
406491 data_manager = trading_suite ["data_manager" ]
407492 order_manager = trading_suite ["order_manager" ]
408493 position_manager = trading_suite ["position_manager" ]
494+
495+ # Store managers for emergency cleanup
496+ _cleanup_managers ["data_manager" ] = data_manager
497+ _cleanup_managers ["order_manager" ] = order_manager
498+ _cleanup_managers ["position_manager" ] = position_manager
409499
410500 print ("✅ Trading suite created successfully" )
411501 print (f" Timeframes: { ', ' .join (timeframes )} " )
502+ print ("🛡️ Emergency cleanup protection activated" )
412503
413504 except Exception as e :
414505 print (f"❌ Failed to create trading suite: { e } " )
415506 return False
416507
417- # Initialize with historical data
508+ # Initialize with historical data (need enough for 50-period SMA on 4hr timeframe)
418509 print ("\n 📚 Initializing with historical data..." )
419- if data_manager .initialize (initial_days = 10 ):
420- print ("✅ Historical data loaded (10 days)" )
510+ # 50 periods * 4 hours = 200 hours ≈ 8.3 days, so load 15 days to be safe
511+ if data_manager .initialize (initial_days = 15 ):
512+ print ("✅ Historical data loaded (15 days)" )
421513 else :
422514 print ("❌ Failed to load historical data" )
423515 return False
@@ -537,6 +629,7 @@ def main():
537629
538630 except KeyboardInterrupt :
539631 print ("\n ⏹️ Strategy monitoring stopped by user" )
632+ # Signal handler will take care of cleanup
540633
541634 # Final analysis and statistics
542635 print ("\n " + "=" * 50 )
@@ -560,11 +653,24 @@ def main():
560653 print (" Position Details:" )
561654 for pos in final_positions :
562655 direction = "LONG" if pos .type == 1 else "SHORT"
563- pnl_info = position_manager .get_position_pnl (pos .contractId )
564- pnl = pnl_info .get ("unrealized_pnl" , 0 ) if pnl_info else 0
565- print (
566- f" { pos .contractId } : { direction } { pos .size } @ ${ pos .averagePrice :.2f} (P&L: ${ pnl :+.2f} )"
567- )
656+
657+ # Get current price for P&L calculation
658+ try :
659+ current_price = data_manager .get_current_price ()
660+ if current_price :
661+ pnl_info = position_manager .calculate_position_pnl (pos , current_price )
662+ pnl = pnl_info .get ("unrealized_pnl" , 0 ) if pnl_info else 0
663+ print (
664+ f" { pos .contractId } : { direction } { pos .size } @ ${ pos .averagePrice :.2f} (P&L: ${ pnl :+.2f} )"
665+ )
666+ else :
667+ print (
668+ f" { pos .contractId } : { direction } { pos .size } @ ${ pos .averagePrice :.2f} (P&L: N/A)"
669+ )
670+ except Exception as e :
671+ print (
672+ f" { pos .contractId } : { direction } { pos .size } @ ${ pos .averagePrice :.2f} (P&L: Error - { e } )"
673+ )
568674
569675 # Show final signal analysis
570676 print ("\n 🧠 Final Strategy Analysis:" )
@@ -599,19 +705,121 @@ def main():
599705
600706 except KeyboardInterrupt :
601707 print ("\n ⏹️ Example interrupted by user" )
708+ # Signal handler will handle emergency cleanup
602709 return False
603710 except Exception as e :
604711 logger .error (f"❌ Multi-timeframe strategy example failed: { e } " )
605712 print (f"❌ Error: { e } " )
606713 return False
607714 finally :
608- # Cleanup
715+ # Comprehensive cleanup - close positions and cancel orders
716+ cleanup_performed = False
717+
718+ if "order_manager" in locals () and "position_manager" in locals ():
719+ try :
720+ print ("\n " + "=" * 50 )
721+ print ("🧹 STRATEGY CLEANUP" )
722+ print ("=" * 50 )
723+
724+ # Get current positions and orders
725+ final_positions = position_manager .get_all_positions ()
726+ final_orders = order_manager .search_open_orders ()
727+
728+ if final_positions or final_orders :
729+ print (f"⚠️ Found { len (final_positions )} open positions and { len (final_orders )} open orders" )
730+ print (" For safety, all positions and orders should be closed when exiting." )
731+
732+ # Ask for user confirmation to close everything
733+ if wait_for_user_confirmation ("Close all positions and cancel all orders?" ):
734+ cleanup_performed = True
735+
736+ # Cancel all open orders first
737+ if final_orders :
738+ print (f"\n 🚫 Cancelling { len (final_orders )} open orders..." )
739+ cancelled_count = 0
740+ for order in final_orders :
741+ try :
742+ if order_manager .cancel_order (order .id ):
743+ cancelled_count += 1
744+ print (f" ✅ Cancelled order { order .id } " )
745+ else :
746+ print (f" ❌ Failed to cancel order { order .id } " )
747+ except Exception as e :
748+ print (f" ❌ Error cancelling order { order .id } : { e } " )
749+
750+ print (f" 📊 Successfully cancelled { cancelled_count } /{ len (final_orders )} orders" )
751+
752+ # Close all open positions
753+ if final_positions :
754+ print (f"\n 📤 Closing { len (final_positions )} open positions..." )
755+ closed_count = 0
756+
757+ for pos in final_positions :
758+ try :
759+ direction = "LONG" if pos .type == 1 else "SHORT"
760+ print (f" 🎯 Closing { direction } { pos .size } { pos .contractId } @ ${ pos .averagePrice :.2f} " )
761+
762+ # Get current market price for market order
763+ current_price = data_manager .get_current_price () if "data_manager" in locals () else None
764+
765+ # Close position with market order (opposite side)
766+ close_side = 1 if pos .type == 1 else 0 # Opposite of position type
767+
768+ close_response = order_manager .place_market_order (
769+ contract_id = pos .contractId ,
770+ side = close_side ,
771+ size = pos .size
772+ )
773+
774+ if close_response .success :
775+ closed_count += 1
776+ print (f" ✅ Close order placed: { close_response .order_id } " )
777+ else :
778+ print (f" ❌ Failed to place close order: { close_response .error_message } " )
779+
780+ except Exception as e :
781+ print (f" ❌ Error closing position { pos .contractId } : { e } " )
782+
783+ print (f" 📊 Successfully placed { closed_count } /{ len (final_positions )} close orders" )
784+
785+ # Give orders time to fill
786+ if closed_count > 0 :
787+ print (" ⏳ Waiting 5 seconds for orders to fill..." )
788+ time .sleep (5 )
789+
790+ # Check final status
791+ remaining_positions = position_manager .get_all_positions ()
792+ if remaining_positions :
793+ print (f" ⚠️ { len (remaining_positions )} positions still open - monitor manually" )
794+ else :
795+ print (" ✅ All positions successfully closed" )
796+ else :
797+ print (" ℹ️ Cleanup skipped by user - positions and orders remain open" )
798+ print (" ⚠️ IMPORTANT: Monitor your positions manually!" )
799+ else :
800+ print ("✅ No open positions or orders to clean up" )
801+ cleanup_performed = True
802+
803+ except Exception as e :
804+ print (f"❌ Error during cleanup: { e } " )
805+
806+ # Stop real-time feed
609807 if "data_manager" in locals ():
610808 try :
611809 data_manager .stop_realtime_feed ()
612- print ("🧹 Real-time feed stopped" )
810+ print ("\n 🧹 Real-time feed stopped" )
613811 except Exception as e :
614- print (f"⚠️ Cleanup warning: { e } " )
812+ print (f"⚠️ Feed stop warning: { e } " )
813+
814+ # Final safety message
815+ if not cleanup_performed :
816+ print ("\n " + "⚠️ " * 20 )
817+ print ("🚨 IMPORTANT SAFETY NOTICE:" )
818+ print (" - Open positions and orders were NOT automatically closed" )
819+ print (" - Please check your trading platform immediately" )
820+ print (" - Manually close any unwanted positions or orders" )
821+ print (" - Monitor your account for any unexpected activity" )
822+ print ("⚠️ " * 20 )
615823
616824
617825if __name__ == "__main__" :
0 commit comments