Skip to content

Commit 067bb1d

Browse files
author
fvetter
committed
🛡️ Security: Vente market d'urgence si limites ordres atteintes
🚨 Protection critique ajoutée: - Échec OCO → Fallback LIMIT ✅ - Échec LIMIT → Vente MARKET complète d'urgence ✅ - Vente de TOUTE la quantité achetée (pas partielle) - Prévention perte crypto orpheline - Logs détaillés des urgences - Commissions exactes même pour MARKET 🎯 Logique bulletproof: 1. OCO (idéal avec stop-loss) 2. LIMIT (sans stop-loss) 3. MARKET (protection anti-perte) 4. Log critique si tout échoue 🔥 Impact: Zéro risque crypto non vendue + protection totale
1 parent bb4253b commit 067bb1d

File tree

1 file changed

+141
-35
lines changed

1 file changed

+141
-35
lines changed

src/trading_engine.py

Lines changed: 141 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ def execute_buy_order(self, symbol: str, usdc_amount: float) -> Dict:
492492
return {'success': False, 'error': str(e)}
493493

494494
def execute_sell_order_with_stop_loss(self, symbol: str, bought_quantity: float, buy_price: float, profit_target: float, buy_transaction_id: int = None) -> Dict:
495-
"""Exécute un ordre OCO avec profit + stop-loss + INSERTION EN BASE BULLETPROOF"""
495+
"""Exécute un ordre OCO avec profit + stop-loss + INSERTION EN BASE BULLETPROOF + VENTE MARKET D'URGENCE"""
496496
try:
497497
# Configuration des ordres
498498
hold = self.advanced_config.get('hold', True)
@@ -620,7 +620,7 @@ def execute_sell_order_with_stop_loss(self, symbol: str, bought_quantity: float,
620620
self.logger.debug(f" Step size: {step_size} -> Qty précision: {qty_precision}")
621621
self.logger.debug(f" Quantité finale: {sell_quantity:.{qty_precision}f}")
622622

623-
self.logger.info(f"🔄 Future transfer: {'Activé' if hold else 'Désactivé'}")
623+
self.logger.info(f"🔄 Hold strategy: {'Activé' if hold else 'Désactivé'}")
624624
if hold:
625625
self.logger.info(f" 📦 Quantité achetée: {bought_quantity:.8f}")
626626
self.logger.info(f" 🏪 Quantité à vendre: {sell_quantity:.8f} ({(sell_quantity/bought_quantity)*100:.1f}%)")
@@ -763,46 +763,152 @@ def execute_sell_order_with_stop_loss(self, symbol: str, bought_quantity: float,
763763

764764
if not use_oco_orders:
765765
# Ordre limite classique (fallback)
766-
limit_order = self.binance_client._make_request_with_retry(
767-
self.binance_client.client.order_limit_sell,
768-
symbol=symbol,
769-
quantity=sell_quantity,
770-
price=f"{target_price:.{price_precision}f}",
771-
timeInForce='GTC'
772-
)
773-
774-
self.logger.info(f"✅ ORDRE LIMITE PLACÉ {symbol}")
775-
self.logger.info(f" 📈 ID: {limit_order['orderId']}")
776-
self.logger.warning(f"⚠️ Pas de protection stop-loss (ordre limite simple)")
777-
778-
# 🆕 ENREGISTRER EN BASE
779766
try:
780-
limit_db_id = self.database.insert_limit_order(
767+
limit_order = self.binance_client._make_request_with_retry(
768+
self.binance_client.client.order_limit_sell,
781769
symbol=symbol,
782-
order_id=str(limit_order['orderId']),
783-
buy_transaction_id=buy_transaction_id or 0,
784-
profit_target=profit_target,
785-
target_price=target_price,
786770
quantity=sell_quantity,
787-
kept_quantity=kept_quantity
771+
price=f"{target_price:.{price_precision}f}",
772+
timeInForce='GTC'
788773
)
789774

790-
self.logger.info(f"💾 Ordre LIMIT enregistré en base (DB ID: {limit_db_id})")
775+
self.logger.info(f"✅ ORDRE LIMITE PLACÉ {symbol}")
776+
self.logger.info(f" 📈 ID: {limit_order['orderId']}")
777+
self.logger.warning(f"⚠️ Pas de protection stop-loss (ordre limite simple)")
778+
779+
# 🆕 ENREGISTRER EN BASE
780+
try:
781+
limit_db_id = self.database.insert_limit_order(
782+
symbol=symbol,
783+
order_id=str(limit_order['orderId']),
784+
buy_transaction_id=buy_transaction_id or 0,
785+
profit_target=profit_target,
786+
target_price=target_price,
787+
quantity=sell_quantity,
788+
kept_quantity=kept_quantity
789+
)
790+
791+
self.logger.info(f"💾 Ordre LIMIT enregistré en base (DB ID: {limit_db_id})")
792+
793+
except Exception as db_error:
794+
self.logger.error(f"❌ Erreur insertion LIMIT en base: {db_error}")
795+
limit_db_id = None
791796

792-
except Exception as db_error:
793-
self.logger.error(f"❌ Erreur insertion LIMIT en base: {db_error}")
794-
limit_db_id = None
797+
return {
798+
'success': True,
799+
'order_type': 'LIMIT',
800+
'order': limit_order,
801+
'target_price': target_price,
802+
'quantity': sell_quantity,
803+
'kept_quantity': kept_quantity,
804+
'limit_db_id': limit_db_id,
805+
'simulation': False
806+
}
795807

796-
return {
797-
'success': True,
798-
'order_type': 'LIMIT',
799-
'order': limit_order,
800-
'target_price': target_price,
801-
'quantity': sell_quantity,
802-
'kept_quantity': kept_quantity,
803-
'limit_db_id': limit_db_id,
804-
'simulation': False
805-
}
808+
except Exception as limit_error:
809+
# 🚨 DERNIER RECOURS : VENTE MARKET IMMÉDIATE
810+
self.logger.error(f"❌ Échec ordre LIMIT {symbol}: {limit_error}")
811+
812+
# Vérifier si c'est une erreur de limite d'ordres
813+
if "MAX_NUM_ORDERS" in str(limit_error) or "filter failure" in str(limit_error).lower():
814+
self.logger.critical(f"🚨 LIMITE ORDRES ATTEINTE pour {symbol} - VENTE MARKET D'URGENCE!")
815+
816+
try:
817+
# 🔥 CORRECTION: Vendre TOUTE la quantité achetée
818+
emergency_sell_quantity = bought_quantity # ✅ PAS sell_quantity !
819+
820+
# Respecter les filtres LOT_SIZE pour la vente complète
821+
symbol_info = self.binance_client._make_request_with_retry(
822+
self.binance_client.client.get_symbol_info,
823+
symbol=symbol
824+
)
825+
826+
lot_size_filter = next(f for f in symbol_info['filters'] if f['filterType'] == 'LOT_SIZE')
827+
step_size = float(lot_size_filter['stepSize'])
828+
829+
# Arrondir la quantité totale selon step_size
830+
qty_precision = max(0, -int(np.log10(step_size)))
831+
emergency_sell_quantity = round(emergency_sell_quantity / step_size) * step_size
832+
emergency_sell_quantity = round(emergency_sell_quantity, qty_precision)
833+
834+
self.logger.warning(f"⚡ VENTE MARKET D'URGENCE:")
835+
self.logger.warning(f" 📦 Quantité achetée: {bought_quantity:.8f}")
836+
self.logger.warning(f" 🏪 Quantité à vendre: {emergency_sell_quantity:.8f}")
837+
self.logger.warning(f" ⚠️ Mode: RÉCUPÉRATION COMPLÈTE (pas hold strategy)")
838+
839+
# VENTE MARKET IMMÉDIATE de TOUT
840+
market_order = self.binance_client._make_request_with_retry(
841+
self.binance_client.client.order_market_sell,
842+
symbol=symbol,
843+
quantity=emergency_sell_quantity
844+
)
845+
846+
# Prix estimé pour les calculs
847+
ticker = self.binance_client._make_request_with_retry(
848+
self.binance_client.client.get_symbol_ticker,
849+
symbol=symbol
850+
)
851+
market_price = float(ticker['price'])
852+
executed_qty = float(market_order.get('executedQty', emergency_sell_quantity))
853+
854+
self.logger.warning(f"✅ VENTE MARKET RÉALISÉE {symbol}:")
855+
self.logger.warning(f" 💸 Prix market: {market_price:.6f} USDC")
856+
self.logger.warning(f" 📊 Quantité vendue: {executed_qty:.8f}")
857+
self.logger.warning(f" 💰 Valeur récupérée: {market_price * executed_qty:.2f} USDC")
858+
self.logger.warning(f" 💎 Crypto gardée: 0 (vente complète d'urgence)")
859+
860+
# Récupérer commissions réelles
861+
try:
862+
commission, commission_asset = self.database.get_order_commissions_from_binance(
863+
self.binance_client, symbol, str(market_order['orderId'])
864+
)
865+
except:
866+
commission = market_price * executed_qty * 0.001 # Estimation
867+
commission_asset = 'USDC'
868+
869+
# Enregistrer la transaction de vente market
870+
self.database.insert_transaction(
871+
symbol=symbol,
872+
order_id=str(market_order['orderId']),
873+
transact_time=str(market_order.get('transactTime', int(time.time() * 1000))),
874+
order_type='MARKET',
875+
order_side='SELL',
876+
price=market_price,
877+
qty=executed_qty,
878+
commission=commission,
879+
commission_asset=commission_asset
880+
)
881+
882+
self.logger.warning(f"📝 Transaction VENTE MARKET COMPLÈTE enregistrée")
883+
884+
return {
885+
'success': True,
886+
'order_type': 'MARKET_EMERGENCY',
887+
'order': market_order,
888+
'target_price': market_price, # Prix réel obtenu
889+
'quantity': executed_qty, # Quantité réellement vendue
890+
'kept_quantity': 0.0, # 🔥 AUCUNE crypto gardée en urgence
891+
'emergency_sale': True,
892+
'simulation': False,
893+
'warning': 'Vente market d\'urgence complète - limites ordres atteintes'
894+
}
895+
896+
except Exception as market_error:
897+
self.logger.critical(f"🚨 ÉCHEC VENTE MARKET {symbol}: {market_error}")
898+
self.logger.critical(f"💀 CRYPTO ACHETÉE MAIS NON VENDUE - INTERVENTION MANUELLE REQUISE!")
899+
self.logger.critical(f"📦 Quantité non vendue: {bought_quantity:.8f} {symbol.replace('USDC', '')}")
900+
901+
return {
902+
'success': False,
903+
'error': f'Échec complet vente {symbol}: OCO/LIMIT/MARKET tous échoués',
904+
'critical': True,
905+
'manual_intervention_required': True,
906+
'bought_quantity': bought_quantity,
907+
'symbol': symbol
908+
}
909+
else:
910+
# Autre type d'erreur LIMIT, la relancer
911+
raise limit_error
806912

807913
except Exception as e:
808914
self.logger.error(f"❌ Erreur ordre {symbol}: {e}")

0 commit comments

Comments
 (0)