Skip to content

Commit f019509

Browse files
TexasCodingclaude
andcommitted
feat(order-management): implement automatic order cleanup when positions close
This commit fixes the automatic order cleanup functionality that should trigger when positions are closed (either by stop loss, take profit, or manual closure). ## Key Changes ### 🔧 Core Fix - **Fix position manager initialization**: Added missing `order_manager` parameter to `position_manager.initialize()` in `create_trading_suite()` function - **Enables automatic order synchronization**: Position manager can now notify order manager when positions close, triggering automatic cleanup of related orders ### 📝 New Demo Script - **Comprehensive demo**: Complete rewrite of `08_order_and_position_tracking.py` - **Interactive monitoring**: Real-time status updates showing positions, orders, and P&L - **Manual testing support**: Instructions for testing via broker platform - **Graceful cleanup**: Proper signal handling and exit cleanup procedures - **Error handling**: Robust handling of None values and missing order attributes ### 🎯 Automatic Cleanup Flow 1. Position closes (stop/target hit or manual closure) 2. Position manager detects closure via real-time feed 3. Position manager automatically calls `order_manager.on_position_closed()` 4. Order manager cancels all related orders (stop loss, take profit) 5. System logs cleanup actions for transparency ### 🧹 Additional Improvements - **Code formatting**: Applied linter fixes to `06_multi_timeframe_strategy.py` - **Better error messages**: Improved error handling in demo script - **Type safety**: Better None checking and attribute validation ## Testing - ✅ Verified automatic cleanup when stop loss triggers - ✅ Verified automatic cleanup when take profit triggers - ✅ Verified proper cleanup on script exit - ✅ Confirmed integration works with `create_trading_suite()` This resolves the issue where bracket orders would leave orphaned stop/target orders after position closure, ensuring clean order management. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 24d8b22 commit f019509

File tree

3 files changed

+547
-59
lines changed

3 files changed

+547
-59
lines changed

examples/06_multi_timeframe_strategy.py

Lines changed: 102 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -350,78 +350,84 @@ def display_strategy_analysis(strategy):
350350
_cleanup_managers = {}
351351
_cleanup_initiated = False
352352

353+
353354
def _emergency_cleanup(signum=None, frame=None):
354355
"""Emergency cleanup function called on signal interruption."""
355356
global _cleanup_initiated
356357
if _cleanup_initiated:
357358
print("\n🚨 Already cleaning up, please wait...")
358359
return
359-
360+
360361
_cleanup_initiated = True
361-
362+
362363
if signum:
363364
signal_name = signal.Signals(signum).name
364365
print(f"\n🚨 Received {signal_name} signal - initiating emergency cleanup!")
365366
else:
366367
print("\n🚨 Initiating emergency cleanup!")
367-
368+
368369
if _cleanup_managers:
369370
print("⚠️ Emergency position and order cleanup in progress...")
370-
371+
371372
try:
372373
order_manager = _cleanup_managers.get("order_manager")
373374
position_manager = _cleanup_managers.get("position_manager")
374375
data_manager = _cleanup_managers.get("data_manager")
375-
376+
376377
if order_manager and position_manager:
377378
# Get current state
378379
positions = position_manager.get_all_positions()
379380
orders = order_manager.search_open_orders()
380-
381+
381382
if positions or orders:
382-
print(f"🚫 Emergency: Cancelling {len(orders)} orders and closing {len(positions)} positions...")
383-
383+
print(
384+
f"🚫 Emergency: Cancelling {len(orders)} orders and closing {len(positions)} positions..."
385+
)
386+
384387
# Cancel all orders immediately
385388
for order in orders:
386389
try:
387390
order_manager.cancel_order(order.id)
388391
print(f" ✅ Cancelled order {order.id}")
389392
except:
390393
print(f" ❌ Failed to cancel order {order.id}")
391-
394+
392395
# Close all positions with market orders
393396
for pos in positions:
394397
try:
395398
close_side = 1 if pos.type == 1 else 0
396399
close_response = order_manager.place_market_order(
397400
contract_id=pos.contractId,
398401
side=close_side,
399-
size=pos.size
402+
size=pos.size,
400403
)
401404
if close_response.success:
402-
print(f" ✅ Emergency close order: {close_response.order_id}")
405+
print(
406+
f" ✅ Emergency close order: {close_response.order_id}"
407+
)
403408
except:
404409
print(f" ❌ Failed to close position {pos.contractId}")
405-
410+
406411
print("⏳ Waiting 3 seconds for emergency orders to process...")
407412
time.sleep(3)
408413
else:
409414
print("✅ No positions or orders to clean up")
410-
415+
411416
# Stop data feed
412417
if data_manager:
413418
try:
414419
data_manager.stop_realtime_feed()
415420
print("🧹 Real-time feed stopped")
416421
except:
417422
pass
418-
423+
419424
except Exception as e:
420425
print(f"❌ Emergency cleanup error: {e}")
421-
426+
422427
print("🚨 Emergency cleanup completed - check your trading platform!")
423428
sys.exit(1)
424429

430+
425431
def wait_for_user_confirmation(message: str) -> bool:
426432
"""Wait for user confirmation before proceeding."""
427433
print(f"\n⚠️ {message}")
@@ -437,11 +443,11 @@ def wait_for_user_confirmation(message: str) -> bool:
437443
def main():
438444
"""Demonstrate multi-timeframe trading strategy."""
439445
global _cleanup_managers
440-
446+
441447
# Register signal handlers for emergency cleanup
442-
signal.signal(signal.SIGINT, _emergency_cleanup) # Ctrl+C
448+
signal.signal(signal.SIGINT, _emergency_cleanup) # Ctrl+C
443449
signal.signal(signal.SIGTERM, _emergency_cleanup) # Termination signal
444-
450+
445451
logger = setup_logging(level="INFO")
446452
print("🚀 Multi-Timeframe Trading Strategy Example")
447453
print("=" * 60)
@@ -491,7 +497,7 @@ def main():
491497
data_manager = trading_suite["data_manager"]
492498
order_manager = trading_suite["order_manager"]
493499
position_manager = trading_suite["position_manager"]
494-
500+
495501
# Store managers for emergency cleanup
496502
_cleanup_managers["data_manager"] = data_manager
497503
_cleanup_managers["order_manager"] = order_manager
@@ -653,12 +659,14 @@ def main():
653659
print(" Position Details:")
654660
for pos in final_positions:
655661
direction = "LONG" if pos.type == 1 else "SHORT"
656-
662+
657663
# Get current price for P&L calculation
658664
try:
659665
current_price = data_manager.get_current_price()
660666
if current_price:
661-
pnl_info = position_manager.calculate_position_pnl(pos, current_price)
667+
pnl_info = position_manager.calculate_position_pnl(
668+
pos, current_price
669+
)
662670
pnl = pnl_info.get("unrealized_pnl", 0) if pnl_info else 0
663671
print(
664672
f" {pos.contractId}: {direction} {pos.size} @ ${pos.averagePrice:.2f} (P&L: ${pnl:+.2f})"
@@ -714,25 +722,31 @@ def main():
714722
finally:
715723
# Comprehensive cleanup - close positions and cancel orders
716724
cleanup_performed = False
717-
725+
718726
if "order_manager" in locals() and "position_manager" in locals():
719727
try:
720728
print("\n" + "=" * 50)
721729
print("🧹 STRATEGY CLEANUP")
722730
print("=" * 50)
723-
731+
724732
# Get current positions and orders
725733
final_positions = position_manager.get_all_positions()
726734
final_orders = order_manager.search_open_orders()
727-
735+
728736
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-
737+
print(
738+
f"⚠️ Found {len(final_positions)} open positions and {len(final_orders)} open orders"
739+
)
740+
print(
741+
" For safety, all positions and orders should be closed when exiting."
742+
)
743+
732744
# Ask for user confirmation to close everything
733-
if wait_for_user_confirmation("Close all positions and cancel all orders?"):
745+
if wait_for_user_confirmation(
746+
"Close all positions and cancel all orders?"
747+
):
734748
cleanup_performed = True
735-
749+
736750
# Cancel all open orders first
737751
if final_orders:
738752
print(f"\n🚫 Cancelling {len(final_orders)} open orders...")
@@ -743,74 +757,104 @@ def main():
743757
cancelled_count += 1
744758
print(f" ✅ Cancelled order {order.id}")
745759
else:
746-
print(f" ❌ Failed to cancel order {order.id}")
760+
print(
761+
f" ❌ Failed to cancel order {order.id}"
762+
)
747763
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-
764+
print(
765+
f" ❌ Error cancelling order {order.id}: {e}"
766+
)
767+
768+
print(
769+
f" 📊 Successfully cancelled {cancelled_count}/{len(final_orders)} orders"
770+
)
771+
752772
# Close all open positions
753773
if final_positions:
754-
print(f"\n📤 Closing {len(final_positions)} open positions...")
774+
print(
775+
f"\n📤 Closing {len(final_positions)} open positions..."
776+
)
755777
closed_count = 0
756-
778+
757779
for pos in final_positions:
758780
try:
759781
direction = "LONG" if pos.type == 1 else "SHORT"
760-
print(f" 🎯 Closing {direction} {pos.size} {pos.contractId} @ ${pos.averagePrice:.2f}")
761-
782+
print(
783+
f" 🎯 Closing {direction} {pos.size} {pos.contractId} @ ${pos.averagePrice:.2f}"
784+
)
785+
762786
# Get current market price for market order
763-
current_price = data_manager.get_current_price() if "data_manager" in locals() else None
764-
787+
current_price = (
788+
data_manager.get_current_price()
789+
if "data_manager" in locals()
790+
else None
791+
)
792+
765793
# Close position with market order (opposite side)
766-
close_side = 1 if pos.type == 1 else 0 # Opposite of position type
767-
794+
close_side = (
795+
1 if pos.type == 1 else 0
796+
) # Opposite of position type
797+
768798
close_response = order_manager.place_market_order(
769799
contract_id=pos.contractId,
770800
side=close_side,
771-
size=pos.size
801+
size=pos.size,
772802
)
773-
803+
774804
if close_response.success:
775805
closed_count += 1
776-
print(f" ✅ Close order placed: {close_response.order_id}")
806+
print(
807+
f" ✅ Close order placed: {close_response.order_id}"
808+
)
777809
else:
778-
print(f" ❌ Failed to place close order: {close_response.error_message}")
779-
810+
print(
811+
f" ❌ Failed to place close order: {close_response.error_message}"
812+
)
813+
780814
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-
815+
print(
816+
f" ❌ Error closing position {pos.contractId}: {e}"
817+
)
818+
819+
print(
820+
f" 📊 Successfully placed {closed_count}/{len(final_positions)} close orders"
821+
)
822+
785823
# Give orders time to fill
786824
if closed_count > 0:
787825
print(" ⏳ Waiting 5 seconds for orders to fill...")
788826
time.sleep(5)
789-
827+
790828
# Check final status
791-
remaining_positions = position_manager.get_all_positions()
829+
remaining_positions = (
830+
position_manager.get_all_positions()
831+
)
792832
if remaining_positions:
793-
print(f" ⚠️ {len(remaining_positions)} positions still open - monitor manually")
833+
print(
834+
f" ⚠️ {len(remaining_positions)} positions still open - monitor manually"
835+
)
794836
else:
795837
print(" ✅ All positions successfully closed")
796838
else:
797-
print(" ℹ️ Cleanup skipped by user - positions and orders remain open")
839+
print(
840+
" ℹ️ Cleanup skipped by user - positions and orders remain open"
841+
)
798842
print(" ⚠️ IMPORTANT: Monitor your positions manually!")
799843
else:
800844
print("✅ No open positions or orders to clean up")
801845
cleanup_performed = True
802-
846+
803847
except Exception as e:
804848
print(f"❌ Error during cleanup: {e}")
805-
849+
806850
# Stop real-time feed
807851
if "data_manager" in locals():
808852
try:
809853
data_manager.stop_realtime_feed()
810854
print("\n🧹 Real-time feed stopped")
811855
except Exception as e:
812856
print(f"⚠️ Feed stop warning: {e}")
813-
857+
814858
# Final safety message
815859
if not cleanup_performed:
816860
print("\n" + "⚠️ " * 20)

0 commit comments

Comments
 (0)