Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 8 additions & 27 deletions api/routes/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
_position_manager = None
_app_state = None
_ws_manager = None # 🔥 MIGRATION COMPLÈTE: WebSocket natif uniquement
_sio = None # 🔥 LEGACY: Gardé pour compatibilité (sera remplacé par _ws_manager)


def set_scheduler(scheduler):
Expand All @@ -38,17 +37,16 @@ def set_app_state(app_state):


def set_websocket_manager(ws_manager):
"""Injecter l'instance WebSocketManager"""
global _ws_manager, _sio
"""🔥 MIGRATION COMPLÈTE: Injecter l'instance WebSocketManager (WebSocket natif uniquement)"""
global _ws_manager
_ws_manager = ws_manager
_sio = ws_manager # 🔥 LEGACY: Garder _sio pour compatibilité (sera supprimé plus tard)


def set_socketio(sio):
"""Injecter l'instance SocketIO (alias pour compatibilité)"""
global _sio, _ws_manager
_sio = sio
_ws_manager = sio if hasattr(sio, 'emit') else None # 🔥 LEGACY: Si c'est un ws_manager, l'utiliser
"""🔥 LEGACY: Alias pour compatibilité (déprécié - utiliser set_websocket_manager)"""
global _ws_manager
# Si c'est un ws_manager, l'utiliser
_ws_manager = sio if hasattr(sio, 'emit') and not hasattr(sio, 'on') else None


# Créer le router
Expand Down Expand Up @@ -211,7 +209,7 @@ async def start_scanner():
if _app_state:
_app_state['is_scanning'] = True

# 🔥 MIGRATION COMPLÈTE: Émettre l'état via WebSocket natif
# 🔥 MIGRATION COMPLÈTE: Émettre l'état via WebSocket natif uniquement
if _ws_manager:
status_data = {
'is_scanning': True,
Expand All @@ -221,15 +219,6 @@ async def start_scanner():
}
await _ws_manager.emit('status', status_data)
await _ws_manager.emit('scan_started', {'timestamp': time.time()})
elif _sio: # 🔥 LEGACY: Fallback Socket.IO (sera supprimé)
status_data = {
'is_scanning': True,
'active_position': _app_state.get('active_position'),
'stats': _app_state.get('stats', {}),
'top_pairs': _app_state.get('top_pairs', [])
}
await _sio.emit('status', status_data)
await _sio.emit('scan_started', {'timestamp': time.time()})

return JSONResponse({
'success': True,
Expand Down Expand Up @@ -294,7 +283,7 @@ async def stop_scanner():
if _app_state:
_app_state['is_scanning'] = False

# 🔥 MIGRATION COMPLÈTE: Émettre l'état via WebSocket natif
# 🔥 MIGRATION COMPLÈTE: Émettre l'état via WebSocket natif uniquement
if _ws_manager:
status_data = {
'is_scanning': False,
Expand All @@ -303,14 +292,6 @@ async def stop_scanner():
'top_pairs': _app_state.get('top_pairs', [])
}
await _ws_manager.emit('status', status_data)
elif _sio: # 🔥 LEGACY: Fallback Socket.IO (sera supprimé)
status_data = {
'is_scanning': False,
'active_position': _app_state.get('active_position'),
'stats': _app_state.get('stats', {}),
'top_pairs': _app_state.get('top_pairs', [])
}
await _sio.emit('status', status_data)

return JSONResponse({
'success': True,
Expand Down
20 changes: 13 additions & 7 deletions api/routes/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
_analyzer = None
_price_provider = None
_app_state = None
_sio = None
_ws_manager = None # 🔥 MIGRATION COMPLÈTE: WebSocket natif uniquement


def set_scanner(scanner):
Expand All @@ -42,10 +42,16 @@ def set_app_state(app_state):
_app_state = app_state


def set_websocket_manager(ws_manager):
"""🔥 MIGRATION COMPLÈTE: Injecter l'instance WebSocketManager (WebSocket natif uniquement)"""
global _ws_manager
_ws_manager = ws_manager

def set_socketio(sio):
"""Injecter l'instance SocketIO"""
global _sio
_sio = sio
"""🔥 LEGACY: Alias pour compatibilité (déprécié - utiliser set_websocket_manager)"""
global _ws_manager
# Si c'est un ws_manager, l'utiliser
_ws_manager = sio if hasattr(sio, 'emit') and not hasattr(sio, 'on') else None


# Créer le router
Expand Down Expand Up @@ -107,9 +113,9 @@ async def start_scanner(request: Request):
_app_state['top_pairs'] = pairs
_app_state['scanner_running'] = True

# Émettre l'événement via SocketIO si disponible
if _sio:
await _sio.emit('scanner_started', {
# 🔥 MIGRATION COMPLÈTE: Utiliser WebSocket natif uniquement
if _ws_manager:
await _ws_manager.emit('scanner_started', {
'status': 'success',
'top_n': top_n,
'pairs_found': len(pairs)
Expand Down
20 changes: 9 additions & 11 deletions core/callbacks/position_check_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,9 @@ async def position_check_loop_callback():
except Exception as e:
logger.warning(f"⚠️ Erreur arrêt WebSocket: {e}")

# 🔥 FIX: Utiliser emit() au lieu de send_position_closed()
if _sio:
await _sio.emit('position_closed', result)
# 🔥 MIGRATION COMPLÈTE: Utiliser WebSocket natif uniquement
if _ws_manager:
await _ws_manager.emit('position_closed', result)

except Exception as e:
logger.error(f"❌ Erreur position_check_loop_callback: {e}")
Expand All @@ -180,7 +180,7 @@ async def _emit_position_update(position, current_price: float):
position: Objet position active
current_price: Prix actuel du marché
"""
if not _position_manager or not _sio:
if not _position_manager or not _ws_manager:
return

try:
Expand Down Expand Up @@ -236,20 +236,18 @@ async def _emit_position_update(position, current_price: float):
'tp_sl_mode': TRADING_CONFIG.get('tp_sl_mode', 'FIXE')
}

# 🔥 FIX: Utiliser emit() au lieu de send_position_update()
if _sio:
await _sio.emit('position_update', update_data)
# 🔥 MIGRATION COMPLÈTE: Utiliser WebSocket natif uniquement
if _ws_manager:
await _ws_manager.emit('position_update', update_data)
# 🔥 FIX: Émettre aussi status pour synchronisation temps réel complète
if _app_state:
if _app_state and _ws_manager:
status_data = {
'is_scanning': _app_state.get('is_scanning', False),
'active_position': update_data,
'stats': _app_state.get('stats', {}),
'top_pairs': _app_state.get('top_pairs', [])
}
# 🔥 FIX: Utiliser emit() au lieu de send_status()
if _sio:
await _sio.emit('status', status_data)
await _ws_manager.emit('status', status_data)

logger.debug(
f"📡 position_update émis: {position.symbol} | "
Expand Down
6 changes: 3 additions & 3 deletions core/callbacks/scalability_refresh.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ async def _refresh_top_pairs():

logger.info(f"✅ Top pairs rafraîchies: {len(top_pairs)} paires")

# 🔥 FIX: Utiliser emit() au lieu de send_top_pairs_update()
if _sio:
await _sio.emit('top_pairs_update', {'pairs': top_pairs})
# 🔥 MIGRATION COMPLÈTE: Utiliser WebSocket natif uniquement
if _ws_manager:
await _ws_manager.emit('top_pairs_update', {'pairs': top_pairs})
logger.debug(f"📡 top_pairs_update émis: {len(top_pairs)} paires")

# Mettre à jour WebSocket si price_provider disponible
Expand Down
16 changes: 3 additions & 13 deletions core/callbacks/scanner_loop.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,9 @@ async def _scan_initial_top_pairs():
except Exception as e:
logger.warning(f"⚠️ Erreur démarrage WebSocket: {e}")

# 🔥 MIGRATION COMPLÈTE: Utiliser WebSocket natif
# 🔥 MIGRATION COMPLÈTE: Utiliser WebSocket natif uniquement
if _ws_manager:
await _ws_manager.emit('top_pairs_update', {'pairs': top_pairs})
elif _sio: # Fallback legacy
await _sio.emit('top_pairs_update', {'pairs': top_pairs})

except Exception as e:
logger.error(f"❌ Erreur scan initial: {e}")
Expand Down Expand Up @@ -268,30 +266,22 @@ async def _scan_top_pairs():
if _app_state is not None:
_app_state['active_position'] = position_result.to_dict()

# 🔥 MIGRATION COMPLÈTE: Utiliser WebSocket natif
# 🔥 MIGRATION COMPLÈTE: Utiliser WebSocket natif uniquement
if _ws_manager:
await _ws_manager.emit('position_opened', position_result.to_dict())
elif _sio: # Fallback legacy
await _sio.emit('position_opened', position_result.to_dict())

except ValueError as e:
logger.error(f"❌ Erreur validation position: {e}")
except Exception as e:
logger.error(f"❌ Erreur ouverture position: {e}", exc_info=True)

# 🔥 MIGRATION COMPLÈTE: Utiliser WebSocket natif
# 🔥 MIGRATION COMPLÈTE: Utiliser WebSocket natif uniquement
if _ws_manager:
await _ws_manager.emit('volume_stats_update', {
'total': len(results),
'validated': len(valid_setups),
'ratio': (len(valid_setups) / len(results) * 100) if results else 0
})
elif _sio: # Fallback legacy
await _sio.emit('volume_stats_update', {
'total': len(results),
'validated': len(valid_setups),
'ratio': (len(valid_setups) / len(results) * 100) if results else 0
})

except Exception as e:
logger.error(f"❌ Erreur scan top pairs: {e}")
Expand Down
3 changes: 1 addition & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
"@capacitor/push-notifications": "^7.0.3",
"@tauri-apps/api": "^2.9.0",
"chart.js": "^4.5.1",
"date-fns": "^3.6.0",
"socket.io-client": "^4.7.4"
"date-fns": "^3.6.0"
}
}
50 changes: 49 additions & 1 deletion frontend/src/lib/components/SettingsPanel.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script>
import { settings, updateSetting, resetSettings, exportSettings, importSettings } from '$lib/stores/settings';
import { onMount } from 'svelte';
import { sendCommandViaWS } from '$lib/utils/websocket';
import { sendCommandViaWS, getWebSocket } from '$lib/utils/websocket';

let fileInput;
let showResetConfirm = false;
Expand All @@ -11,10 +11,58 @@
// ✅ Charger la config depuis le backend au démarrage
onMount(async () => {
await loadBackendConfig();

// 🔥 BIDIRECTIONNEL: Écouter les mises à jour de config depuis le backend
const ws = getWebSocket();
if (ws) {
ws.on('config_updated', (data: any) => {
console.log('🔄 Config mise à jour depuis backend dans SettingsPanel:', data.updated);
// Synchroniser les paramètres avec les changements du backend
if (data.updated) {
if (data.updated.sl_percent !== undefined) {
updateSetting('stopLossPercent', data.updated.sl_percent);
}
if (data.updated.tp_percent !== undefined) {
updateSetting('takeProfitPercent', data.updated.tp_percent);
}
if (data.updated.trailing_trigger_pnl !== undefined) {
updateSetting('trailingStopPercent', data.updated.trailing_trigger_pnl);
}
// Mettre à jour backendConfig pour affichage
backendConfig = { ...backendConfig, ...data.updated };
}
});
}
});

async function loadBackendConfig() {
try {
// 🔥 BIDIRECTIONNEL: Utiliser WebSocket pour charger la config (priorité)
const { getWebSocket, sendRequestViaWS } = await import('$lib/utils/websocket');
const ws = getWebSocket();
if (ws && ws.connected) {
try {
const stateData = await sendRequestViaWS('state', {});
if (stateData && stateData.config) {
backendConfig = stateData.config;
// ✅ Synchroniser les paramètres qui existent dans le backend
if (stateData.config.sl_percent !== undefined) {
updateSetting('stopLossPercent', stateData.config.sl_percent);
}
if (stateData.config.tp_percent !== undefined) {
updateSetting('takeProfitPercent', stateData.config.tp_percent);
}
if (stateData.config.trailing_trigger_pnl !== undefined) {
updateSetting('trailingStopPercent', stateData.config.trailing_trigger_pnl);
}
return;
}
} catch (wsErr) {
console.warn('⚠️ Erreur chargement config via WebSocket, fallback REST:', wsErr);
}
}

// Fallback REST si WebSocket non disponible
const res = await fetch('/api/state');
if (res.ok) {
const data = await res.json();
Expand Down
25 changes: 21 additions & 4 deletions frontend/src/lib/components/VariablesPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -247,10 +247,27 @@
});
}

// 🔥 REMPLACEMENT: WebSocket natif gère déjà les changements de config via websocket.js
// Les changements de config sont automatiquement synchronisés via le store logs
// Plus besoin d'écouter manuellement les événements Socket.IO
// Le composant rechargera automatiquement la config après sauvegarde via saveConfig()
// 🔥 BIDIRECTIONNEL: Écouter les mises à jour de config depuis le backend
onMount(async () => {
const { getWebSocket } = await import('$lib/utils/websocket');
const ws = getWebSocket();
if (ws) {
ws.on('config_updated', (data: any) => {
console.log('🔄 Config mise à jour depuis backend:', data.updated);
// Synchroniser la config locale avec les changements du backend
if (data.updated) {
Object.keys(data.updated).forEach(key => {
if (key in config) {
config[key] = data.updated[key];
}
});
if (data.updated.tp_sl_mode) {
viewMode = data.updated.tp_sl_mode;
}
}
});
}
});
</script>

<div class="variables-panel">
Expand Down
Loading