@@ -453,18 +453,18 @@ def execute_sell_order_with_stop_loss(self, symbol: str, bought_quantity: float,
453453
454454 # Calcul des quantités et prix
455455 if future_transfer_enabled :
456- sell_ratio = ( 100 - profit_target ) / 100
456+ sell_ratio = profit_target / 100 # 🔧 CORRECTION: Logique corrigée
457457 sell_quantity = bought_quantity * sell_ratio
458458 kept_quantity = bought_quantity - sell_quantity
459- target_price = self . calculate_sell_price_limit ( buy_price , profit_target )
459+ target_price = buy_price * ( 1 + profit_target / 100 ) # 🔧 CORRECTION
460460 else :
461461 sell_quantity = bought_quantity
462462 kept_quantity = 0
463463 target_price = buy_price * (1 + profit_target / 100 )
464464
465465 # Prix stop-loss
466466 stop_price = buy_price * (1 + stop_loss_percentage / 100 )
467- stop_limit_buffer = self .risk_config .get ('stop_limit_buffer' , 0.02 )
467+ stop_limit_buffer = self .risk_config .get ('stop_limit_buffer' , 0.001 ) # 🔧 CORRECTION
468468 stop_limit_price = stop_price * (1 - stop_limit_buffer )
469469
470470 # Informations du symbole pour formatage
@@ -499,7 +499,7 @@ def execute_sell_order_with_stop_loss(self, symbol: str, bought_quantity: float,
499499 if future_transfer_enabled :
500500 self .logger .info (f" 📦 Quantité achetée: { bought_quantity :.8f} " )
501501 self .logger .info (f" 🏪 Quantité à vendre: { sell_quantity :.8f} ({ sell_ratio * 100 :.1f} %)" )
502- self .logger .info (f" 💎 Quantité gardée: { kept_quantity :.8f} ({ profit_target :.1f} %)" )
502+ self .logger .info (f" 💎 Quantité gardée: { kept_quantity :.8f} ({ ( 100 - profit_target ) :.1f} %)" )
503503
504504 self .logger .info (f"📊 ORDRE OCO { symbol } :" )
505505 self .logger .info (f" 🎯 Profit: { target_price :.{price_precision }f} (+{ profit_target } %)" )
@@ -533,18 +533,37 @@ def execute_sell_order_with_stop_loss(self, symbol: str, bought_quantity: float,
533533
534534 self .logger .info (f"✅ ORDRE OCO PLACÉ { symbol } " )
535535
536- # EXTRACTION DES IDs D'ORDRES
536+ # 🔥 EXTRACTION AMÉLIORÉE DES IDs D'ORDRES
537537 profit_order_id = None
538538 stop_order_id = None
539539 oco_order_list_id = oco_order .get ('orderListId' , '' )
540540
541- for order in oco_order .get ('orders' , []):
542- if order .get ('type' ) == 'LIMIT_MAKER' :
543- profit_order_id = order ['orderId' ]
541+ self .logger .debug (f"🔍 OCO Response: orderListId={ oco_order_list_id } " )
542+
543+ orders = oco_order .get ('orders' , [])
544+ self .logger .debug (f"🔍 Orders in OCO: { len (orders )} " )
545+
546+ for i , order in enumerate (orders ):
547+ order_id = order .get ('orderId' )
548+ order_type = order .get ('type' )
549+ order_side = order .get ('side' , '' )
550+
551+ self .logger .debug (f" Order { i + 1 } : ID={ order_id } , Type={ order_type } , Side={ order_side } " )
552+
553+ if order_type == 'LIMIT_MAKER' :
554+ profit_order_id = order_id
544555 self .logger .info (f" 📈 Limite profit: { profit_order_id } " )
545- elif order . get ( 'type' ) == 'STOP_LOSS_LIMIT' :
546- stop_order_id = order [ 'orderId' ]
556+ elif order_type == 'STOP_LOSS_LIMIT' :
557+ stop_order_id = order_id
547558 self .logger .info (f" 🛡️ Stop-loss: { stop_order_id } " )
559+ else :
560+ self .logger .warning (f" ❓ Type d'ordre inattendu: { order_type } " )
561+
562+ # Vérification finale
563+ if not profit_order_id :
564+ self .logger .warning (f"⚠️ PROFIT_ORDER_ID non trouvé dans la réponse OCO !" )
565+ if not stop_order_id :
566+ self .logger .warning (f"⚠️ STOP_ORDER_ID non trouvé dans la réponse OCO !" )
548567
549568 # 🔥 INSERTION EN BASE (PARTIE CRUCIALE!)
550569 try :
@@ -639,36 +658,98 @@ def monitor_oco_orders(self):
639658 self .logger .error (f"❌ Erreur surveillance OCO: { e } " )
640659
641660 def _check_oco_status (self , oco_order : Dict ):
642- """Vérifie le statut d'un ordre OCO sur Binance"""
661+ """Vérifie le statut d'un ordre OCO sur Binance avec méthode robuste """
643662 try :
644663 symbol = oco_order ['symbol' ]
645664 oco_order_id = oco_order ['oco_order_id' ]
665+ profit_order_id = oco_order .get ('profit_order_id' )
666+ stop_order_id = oco_order .get ('stop_order_id' )
646667
647- # Récupérer le statut depuis Binance
668+ # 🔥 MÉTHODE 1: Vérifier via les ordres ouverts (plus fiable)
648669 try :
649- order_status = self .binance_client ._make_request_with_retry (
650- self .binance_client .client .get_order_list ,
651- orderListId = int (oco_order_id ),
670+ open_orders = self .binance_client ._make_request_with_retry (
671+ self .binance_client .client .get_open_orders ,
652672 symbol = symbol
653673 )
654- except Exception as api_error :
655- if "does not exist" in str (api_error ).lower ():
656- self .logger .warning (f"⚠️ Ordre OCO { oco_order_id } introuvable sur Binance (déjà exécuté?)" )
657- # Marquer comme executed pour éviter les vérifications futures
658- self .database .update_oco_execution (oco_order_id , 'UNKNOWN_EXECUTED' , 0 , 0 , 'UNKNOWN' )
659- return
660-
661- list_status = order_status .get ('listStatus' , 'UNKNOWN' )
662-
663- if list_status == 'EXECUTING' :
664- # Ordre toujours actif
665- self .logger .debug (f"📊 OCO { symbol } toujours actif" )
666- return
674+
675+ # Chercher nos ordres dans la liste
676+ profit_found = False
677+ stop_found = False
678+ oco_found = False
679+
680+ for order in open_orders :
681+ order_list_id = str (order .get ('orderListId' , - 1 ))
682+ order_id = str (order .get ('orderId' ))
683+
684+ # Vérifier si c'est notre OCO
685+ if order_list_id == str (oco_order_id ):
686+ oco_found = True
687+
688+ # Vérifier les ordres individuels aussi (backup)
689+ if profit_order_id and order_id == str (profit_order_id ):
690+ profit_found = True
691+ if stop_order_id and order_id == str (stop_order_id ):
692+ stop_found = True
693+
694+ if oco_found or profit_found or stop_found :
695+ # OCO encore actif
696+ self .logger .debug (f"📊 OCO { symbol } toujours actif (oco:{ oco_found } , profit:{ profit_found } , stop:{ stop_found } )" )
697+ return
698+
699+ # OCO plus dans les ordres ouverts = exécuté !
700+ self .logger .info (f"🎯 OCO { symbol } n'est plus actif → Recherche dans l'historique" )
701+
702+ except Exception as open_orders_error :
703+ self .logger .warning (f"⚠️ Erreur vérification ordres ouverts: { open_orders_error } " )
667704
668- # L'ordre OCO s'est exécuté !
669- self .logger .info (f"🎯 ORDRE OCO EXÉCUTÉ { symbol } (statut: { list_status } )" )
670- self ._handle_oco_execution (oco_order , order_status )
705+ # 🔥 MÉTHODE 2: Vérifier dans l'historique récent
706+ try :
707+ from datetime import timedelta
708+
709+ # Historique des dernières 24h
710+ yesterday = datetime .now () - timedelta (hours = 24 )
711+ start_time = int (yesterday .timestamp () * 1000 )
712+
713+ all_orders = self .binance_client ._make_request_with_retry (
714+ self .binance_client .client .get_all_orders ,
715+ symbol = symbol ,
716+ startTime = start_time ,
717+ limit = 100
718+ )
719+
720+ # Chercher les ordres de notre OCO
721+ executed_orders = []
722+ for order in all_orders :
723+ order_list_id = str (order .get ('orderListId' , - 1 ))
724+
725+ if order_list_id == str (oco_order_id ):
726+ executed_orders .append (order )
727+
728+ if executed_orders :
729+ self .logger .info (f"📜 Trouvé { len (executed_orders )} ordres dans l'historique pour OCO { oco_order_id } " )
730+
731+ # Analyser les ordres exécutés
732+ for order in executed_orders :
733+ status = order .get ('status' )
734+ order_type = order .get ('type' )
735+
736+ self .logger .info (f" - Status: { status } , Type: { order_type } " )
671737
738+ if status == 'FILLED' :
739+ # Ordre exécuté ! Traiter l'exécution
740+ self ._handle_oco_execution_from_history (oco_order , executed_orders )
741+ return
742+
743+ # Si aucun FILLED mais ordres trouvés = probablement annulés
744+ self .logger .warning (f"⚠️ OCO { oco_order_id } trouvé dans l'historique mais aucun ordre FILLED" )
745+ self .database .update_oco_execution (oco_order_id , 'EXPIRED_OR_CANCELED' , 0 , 0 , 'UNKNOWN' )
746+
747+ else :
748+ self .logger .warning (f"❓ OCO { oco_order_id } non trouvé dans l'historique récent" )
749+
750+ except Exception as history_error :
751+ self .logger .error (f"❌ Erreur vérification historique: { history_error } " )
752+
672753 except Exception as e :
673754 self .logger .error (f"❌ Erreur vérification OCO { oco_order .get ('symbol' , 'UNKNOWN' )} : { e } " )
674755
@@ -752,3 +833,63 @@ def _handle_oco_execution(self, oco_order: Dict, binance_status: Dict):
752833 self .logger .error (f"❌ Erreur traitement exécution OCO: { e } " )
753834 import traceback
754835 self .logger .debug (traceback .format_exc ())
836+
837+ def _handle_oco_execution_from_history (self , oco_order : Dict , executed_orders : List [Dict ]):
838+ """Traite l'exécution OCO depuis l'historique"""
839+ try :
840+ symbol = oco_order ['symbol' ]
841+ oco_order_id = oco_order ['oco_order_id' ]
842+
843+ # Trouver l'ordre FILLED
844+ filled_order = None
845+ execution_type = None
846+
847+ for order in executed_orders :
848+ if order ['status' ] == 'FILLED' :
849+ filled_order = order
850+
851+ # Déterminer le type d'exécution
852+ order_type = order .get ('type' )
853+ if order_type == 'LIMIT_MAKER' :
854+ execution_type = 'PROFIT'
855+ elif order_type in ['STOP_LOSS_LIMIT' , 'STOP_LOSS' ]:
856+ execution_type = 'STOP_LOSS'
857+ break
858+
859+ if filled_order and execution_type :
860+ exec_price = float (filled_order ['price' ])
861+ exec_qty = float (filled_order ['executedQty' ])
862+
863+ if execution_type == 'PROFIT' :
864+ self .logger .info (f"🎯 PROFIT HISTORIQUE DÉTECTÉ { symbol } !" )
865+ new_status = 'PROFIT_FILLED'
866+ else :
867+ self .logger .warning (f"🛡️ STOP-LOSS HISTORIQUE DÉTECTÉ { symbol } " )
868+ new_status = 'STOP_FILLED'
869+
870+ # Mettre à jour la DB
871+ self .database .update_oco_execution (
872+ oco_order_id ,
873+ new_status ,
874+ exec_price ,
875+ exec_qty ,
876+ execution_type
877+ )
878+
879+ # Enregistrer la transaction de vente
880+ self .database .insert_transaction (
881+ symbol = symbol ,
882+ order_id = str (filled_order ['orderId' ]),
883+ transact_time = str (filled_order .get ('updateTime' , int (time .time () * 1000 ))),
884+ order_type = filled_order .get ('type' , 'LIMIT' ),
885+ order_side = 'SELL' ,
886+ price = exec_price ,
887+ qty = exec_qty ,
888+ commission = 0.0 , # Historique : commission pas toujours disponible
889+ commission_asset = 'USDC'
890+ )
891+
892+ self .logger .info (f"💾 Exécution OCO historique enregistrée" )
893+
894+ except Exception as e :
895+ self .logger .error (f"❌ Erreur traitement exécution historique: { e } " )
0 commit comments