Skip to content

Claude/connect branch 012 dw yg mqz bs g5 vu9 zueq lyz#32

Open
chpeu wants to merge 71 commits into
windfrom
claude/connect-branch-012DwYgMQZBsG5VU9ZueqLYZ
Open

Claude/connect branch 012 dw yg mqz bs g5 vu9 zueq lyz#32
chpeu wants to merge 71 commits into
windfrom
claude/connect-branch-012DwYgMQZBsG5VU9ZueqLYZ

Conversation

@chpeu

@chpeu chpeu commented Nov 15, 2025

Copy link
Copy Markdown
Owner

PR Type

Enhancement, Bug fix, Tests


Description

  • Implement WebSocket reconnection callback to resubscribe to monitored symbols after connection loss

  • Fix price formatting inconsistency by changing tickSize to tick_size in position data

  • Prevent WebSocket updates during active positions to maintain price stability

  • Add comprehensive test coverage for dashboard routes, MEXC API, and PostgreSQL logger

  • Improve frontend price formatting using significant decimals from entry price


Diagram Walkthrough

flowchart LR
  WS["WebSocket Manager"] -->|reconnect_callback| PR["Price Provider"]
  PR -->|resubscribe| WS
  PR -->|monitored_symbols| RECON["Reconnection Logic"]
  RECON -->|check active_position| SCANNER["Scanner Loop"]
  SCANNER -->|restart WS| PR
  SCALABILITY["Scalability Refresh"] -->|check position| PR
  FRONTEND["Frontend PositionCard"] -->|getSignificantDecimals| FORMAT["Format Utils"]
  FORMAT -->|formatWithoutTrailingZeros| DISPLAY["Price Display"]
Loading

File Walkthrough

Relevant files
Enhancement
6 files
price_provider.py
Add WebSocket reconnection callback and symbol resubscription
+68/-8   
reliability.py
Add reconnect_callback attribute to WebSocketManager         
+11/-0   
scanner_loop.py
Restart WebSocket with only position symbol when opening position
+16/-0   
main.py
Restart WebSocket for position symbol and add double position check
+32/-16 
PositionCard.svelte
Use significant decimals from entry price for formatting 
+19/-15 
format.js
Add functions for significant decimal calculation and formatting
+57/-0   
Bug fix
3 files
scalability_refresh.py
Prevent WebSocket updates during active positions               
+12/-3   
position_manager.py
Change tickSize to tick_size for Python naming convention
+1/-1     
postgresql_datalogger.py
Initialize pool to None when psycopg2 unavailable               
+1/-0     
Tests
4 files
test_dashboard_routes.py
Add comprehensive test suite for dashboard API routes       
+320/-0 
test_mexc.py
Add comprehensive test suite for MEXC API client                 
+355/-0 
test_postgresql_datalogger.py
Skip obsolete tests and fix connection pool initialization
+9/-7     
test_simple_pg_logger.py
Add comprehensive test suite for PostgreSQL logger             
+518/-0 

chpeu and others added 13 commits November 11, 2025 02:29
- Add tests for core/simple_pg_logger.py (12.03% → 88.61% coverage)
- Add tests for api/mexc.py (43.66% → 88.73% coverage)
- Add tests for api/routes/dashboard.py

These tests significantly improve coverage for critical low-coverage files.
48/49 tests passing for simple_pg_logger and mexc modules.
**Tests fixes:**
- Fix test_dashboard_routes.py (11 tests)
  - Properly mock verify_api_key using dependency_overrides
  - Fix TRADING_CONFIG patch (use config.TRADING_CONFIG)
  - Fix init_instances patch (use main.init_instances)
  - Fix test_get_status_exception with proper unserializable object
  - Fix test_set_socketio_with_ws_manager mock logic

- Fix test_simple_pg_logger.py (1 test)
  - Fix reconnection test to properly simulate connection closing after error

- Fix test_postgresql_datalogger.py (3 tests)
  - Add self.pool = None when PSYCOPG2_AVAILABLE is False
  - Skip obsolete tests (log_scan_error, log_market_context methods no longer exist)

**Price formatting fix:**
- Change 'tickSize' to 'tick_size' in position_manager.py to_dict()
  - Maintains consistency with Python snake_case convention
  - Frontend already handles both formats (tickSize || tick_size)
  - Fixes price decimal precision inconsistency between backend/frontend

**Test results:**
- 56 passed, 2 skipped
- All critical functionality tests passing
- Coverage improvements maintained
…itions

Problem:
- When a position opens, the scalability scan continued running
- WebSocket would restart with multiple symbols instead of staying on the position symbol
- This caused current_price to freeze during active positions

Solution:
1. scalability_refresh.py: Added check in _update_websocket() to prevent
   WebSocket changes when an active position exists
2. scanner_loop.py: After opening position, explicitly restart WebSocket
   with ONLY the position symbol to ensure price updates continue

This ensures that during a position:
- WebSocket stays focused on the position symbol only
- current_price, TP, and SL prices update correctly
- Scalability refresh doesn't interfere with position tracking
…ce freezing

PROBLÈME CRITIQUE résolu:
- Quand une position s'ouvrait, le WebSocket continuait de monitorer tous les symboles précédents (top_pairs)
- subscribe_ticker() AJOUTAIT juste le symbole de la position aux symboles existants
- Résultat: current_price se figeait car le WebSocket ne se concentrait pas sur le bon symbole

SOLUTION dans main.py ligne 898-919:
- ARRÊTER complètement le WebSocket actuel après ouverture de position
- REDÉMARRER le WebSocket avec UNIQUEMENT le symbole de la position
- Ceci garantit que current_price, TP et SL sont mis à jour en temps réel

Logs ajoutés:
- "🔌 WebSocket arrêté avant position"
- "✅ WebSocket redémarré pour position: {symbol} UNIQUEMENT"

Protection double:
1. scalability_refresh_loop_callback (ligne 1716) vérifie déjà position active
2. Le WebSocket est maintenant focalisé uniquement sur le symbole de la position

Sécurité pour live trading:
- Plus de risque de prix figé pendant une position active
- Suivi en temps réel garanti pour TP/SL/current_price
…itions

PROBLÈME CRITIQUE:
- Le scan de scalabilité prend ~20 secondes (batches 1-19)
- Si une position s'ouvre PENDANT le scan (ex: après batch 6)
- Le scan continue jusqu'à la fin (batches 7-19)
- À la fin du scan, le WebSocket redémarre avec 7 symboles au lieu de rester sur le symbole de la position
- Résultat: current_price se fige car WebSocket ne monitore plus le bon symbole

LOGS DU PROBLÈME:
19:15:52 - Position ouverte SHIB/USDT
19:15:53 - Batch 7/19 continue (scan déjà commencé avant position)
...
19:16:09 - Batch 19/19 termine
19:16:10 - Scalability refresh
19:16:11 - ✅ WebSocket démarré pour 7 symboles  ← MAUVAIS!

SOLUTION (ligne 1736-1741):
Ajout d'une vérification DOUBLE dans scalability_refresh_loop_callback():
1. Vérification AVANT scan (existait déjà ligne 1716) - empêche nouveau scan
2. Vérification APRÈS scan (NOUVELLE ligne 1736-1741) - empêche mise à jour WebSocket

Si position active détectée APRÈS scan:
- top_pairs mis à jour quand même (pas de problème)
- WebSocket N'EST PAS touché (protection)
- Log: "⏸️ Mise à jour WebSocket ignorée - Position ouverte pendant le scan"

RÉSULTAT:
- Le scan de scalabilité peut continuer pendant une position (pas grave)
- Mais le WebSocket reste focalisé sur le symbole de la position
- current_price ne se fige plus
- Sécurisé pour live trading
…ection

PROBLÈME CRITIQUE RÉSOLU:
Le prix se figeait indéfiniment pendant une position car:
1. Position ouverte → WebSocket redémarre avec symbole position ✅
2. WebSocket se déconnecte (timeout, erreur réseau)
3. WebSocket se reconnecte automatiquement ✅
4. MAIS ne se réabonne à AUCUN symbole ❌
5. Résultat: plus de mise à jour de prix → current_price fige indéfiniment

CAUSE RACINE:
- price_provider.py start_websocket() recevait les symboles en paramètre
- MAIS ne les stockait NULLE PART
- reliability.py _reconnect_loop() reconnectait le WebSocket
- MAIS n'avait aucun moyen de savoir quels symboles réabonner
- Résultat: WebSocket connecté mais n'écoute RIEN

SOLUTION COMPLÈTE:

1. api/price_provider.py (ligne 44):
   - Ajout self.monitored_symbols: list = []
   - Stocke les symboles actuellement monitorés

2. api/price_provider.py start_websocket() (ligne 150):
   - self.monitored_symbols = symbols
   - Stocke les symboles pour réabonnement futur
   - self.ws_manager.reconnect_callback = self._resubscribe_after_reconnect
   - Configure le callback appelé après reconnexion

3. api/price_provider.py _resubscribe_after_reconnect() (ligne 200-248):
   - Nouvelle méthode appelée automatiquement après reconnexion
   - Vérifie si position active via app_state et position_manager
   - Si position active: réabonne UNIQUEMENT au symbole de la position
   - Sinon: réabonne à tous les symboles dans monitored_symbols
   - Logs: "🔄 Réabonnement WebSocket (position active): SYMBOL UNIQUEMENT"

4. api/reliability.py __init__() (ligne 212):
   - Ajout self.reconnect_callback: Optional[Callable] = None
   - Permet de configurer un callback après reconnexion

5. api/reliability.py _reconnect_loop() (ligne 385-390):
   - Après reconnexion réussie, appelle reconnect_callback si configuré
   - Permet au PriceProvider de réabonner aux symboles

LOGS ATTENDUS:
Quand WebSocket se déconnecte puis reconnecte pendant une position:
- 🐕 Watchdog arrêté
- 🔄 WebSocket: Tentative reconnexion...
- ✅ WebSocket reconnecté
- 🔄 Réabonnement WebSocket (position active): SHIB/USDT UNIQUEMENT
- ✅ WebSocket réabonné à 1 symbole(s)

RÉSULTAT:
- WebSocket se reconnecte ET réabonne aux symboles automatiquement
- Si position active: focus UNIQUEMENT sur symbole position
- current_price ne se fige plus jamais
- Sécurisé pour live trading
…g zeros

PROBLÈME:
Lors de l'affichage d'une position active dans le frontend, les prix
(current_price, TP, SL) affichaient un nombre de décimales incohérent
et gardaient des zéros superflus à la fin.

Exemple:
- Backend Entry: 1.132200
- Frontend affichait: "1.132200" (avec zéros de fin)
- Souhaité: "1.1322" (4 décimales significatives, sans zéros)

SOLUTION:

1. frontend/src/lib/utils/format.js:
   - Ajout getSignificantDecimals(value): calcule le nombre de décimales
     significatives d'un nombre (sans compter les zéros finaux)
     Exemple: 1.132200 → 4 (car 1.1322 a 4 décimales significatives)

   - Ajout formatWithoutTrailingZeros(value, decimals): formate un nombre
     avec un nombre précis de décimales puis supprime les zéros de fin
     Exemple: formatWithoutTrailingZeros(1.128000, 4) → "1.128"

2. frontend/src/lib/components/PositionCard.svelte:
   - Import des nouvelles fonctions
   - Calcul réactif entryDecimals depuis entry price
   - Remplacement de formatPriceWithPrecision():
     * Utilise entryDecimals comme nombre de décimales de référence
     * Applique formatWithoutTrailingZeros() pour supprimer les zéros
     * Tous les prix (current, TP, SL) utilisent le même formatage

   - Logique:
     entry=1.132200 → entryDecimals=4
     current=1.128000 → affiché "1.128" (4 décimales max, sans zéros)
     tp=1.138993 → affiché "1.139" (4 décimales max, arrondi)
     sl=1.129369 → affiché "1.1294" (4 décimales max)

RÉSULTAT:
- Tous les prix affichés ont le même nombre de décimales (basé sur entry)
- Les zéros superflus à la fin sont supprimés
- Affichage cohérent et lisible
- Minimum 2 décimales, maximum 8 (pour lisibilité)

Exemple avec ASTER/USDT:
- Entry backend: 1.132200 → frontend: "1.1322"
- Current: 1.128000 → frontend: "1.128"
- TP: 1.138993 → frontend: "1.139"
- SL: 1.129369 → frontend: "1.1294"
@qodo-code-review

qodo-code-review Bot commented Nov 15, 2025

Copy link
Copy Markdown

You are nearing your monthly Qodo Merge usage quota. For more information, please visit here.

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Action Logging: New critical actions like restarting WebSocket on position open and scalability refresh do
not clearly add structured audit logs with user/context, making audit trail completeness
uncertain.

Referred Code
# 🔥 FIX CRITIQUE: Redémarrer WebSocket UNIQUEMENT sur le symbole de la position
# Ceci garantit que current_price sera mis à jour correctement pendant la position
# PROBLÈME: subscribe_ticker() ajoutait juste le symbole aux symboles existants
# SOLUTION: Redémarrer complètement le WebSocket avec UNIQUEMENT le symbole de la position
if price_provider:
    try:
        # Arrêter WebSocket actuel
        if hasattr(price_provider, 'stop_websocket'):
            await price_provider.stop_websocket()
            logger.info(f"🔌 WebSocket arrêté avant position")

        # Redémarrer WebSocket uniquement sur le symbole de la position
        if hasattr(price_provider, 'start_websocket'):
            await price_provider.start_websocket([symbol])
            logger.info(f"✅ WebSocket redémarré pour position: {symbol} UNIQUEMENT")

        # Configurer callback pour suivre position active
        if hasattr(price_provider, 'set_socketio_callback'):
            price_provider.set_socketio_callback(None, symbol)
            logger.debug(f"📡 WebSocket configuré pour suivre {symbol} (prix en temps réel dans cache)")
    except Exception as e:


 ... (clipped 1 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Weak Exception: Broad exception handling during reconnection resubscribe logs only the error message
without actionable context about symbols or state, which may hinder debugging.

Referred Code
except Exception as e:
    logger.error(f"❌ Erreur réabonnement WebSocket: {e}")

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Input Validation: Formatting helpers accept values without explicit validation or type checks beyond isNaN,
which may be insufficient if external inputs reach these functions.

Referred Code
export function getSignificantDecimals(value) {
	if (value === null || value === undefined || isNaN(value)) {
		return 2;
	}

	const str = value.toString();

	// If no decimal point, return 0
	if (!str.includes('.')) {
		return 0;
	}

	// Get decimal part
	const decimalPart = str.split('.')[1];

	// Remove trailing zeros
	const withoutTrailingZeros = decimalPart.replace(/0+$/, '');

	// Return length (number of significant decimals)
	return withoutTrailingZeros.length;
}

Learn more about managing compliance generic rules or creating your own custom rules

  • Update
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review

qodo-code-review Bot commented Nov 15, 2025

Copy link
Copy Markdown

You are nearing your monthly Qodo Merge usage quota. For more information, please visit here.

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Simplify WebSocket subscription management logic

Instead of stopping and restarting the WebSocket to change symbol subscriptions,
manage them dynamically. Use subscribe and unsubscribe commands on a single,
persistent connection to simplify logic and reduce errors.

Examples:

core/callbacks/scanner_loop.py [417-431]
                # 🔥 FIX: Redémarrer WebSocket UNIQUEMENT sur le symbole de la position
                # Ceci garantit que current_price sera mis à jour correctement pendant la position
                if _price_provider:
                    try:
                        # Arrêter WebSocket actuel
                        if hasattr(_price_provider, 'stop_websocket'):
                            await _price_provider.stop_websocket()
                            logger.debug("🔌 WebSocket arrêté pour position")

                        # Redémarrer WebSocket uniquement sur le symbole de la position

 ... (clipped 5 lines)
main.py [898-919]
                                    # 🔥 FIX CRITIQUE: Redémarrer WebSocket UNIQUEMENT sur le symbole de la position
                                    # Ceci garantit que current_price sera mis à jour correctement pendant la position
                                    # PROBLÈME: subscribe_ticker() ajoutait juste le symbole aux symboles existants
                                    # SOLUTION: Redémarrer complètement le WebSocket avec UNIQUEMENT le symbole de la position
                                    if price_provider:
                                        try:
                                            # Arrêter WebSocket actuel
                                            if hasattr(price_provider, 'stop_websocket'):
                                                await price_provider.stop_websocket()
                                                logger.info(f"🔌 WebSocket arrêté avant position")

 ... (clipped 12 lines)

Solution Walkthrough:

Before:

# In scanner_loop.py / main.py (when opening a position)
async def open_position(symbol):
    # ...
    # Stop the entire WebSocket connection
    await price_provider.stop_websocket()
    
    # Restart the WebSocket with only the single active symbol
    await price_provider.start_websocket([symbol])
    # ...

# In scalability_refresh.py
async def _update_websocket(top_pairs):
    # Check if a position is active to avoid restarting
    if position_manager.active_position:
        return
    
    # Stop and restart WebSocket with new pairs
    await _price_provider.stop_websocket()
    await _price_provider.start_websocket(new_symbols)

After:

# In a centralized subscription manager (e.g., price_provider.py)
class PriceProvider:
    async def set_active_subscriptions(self, symbols_to_monitor):
        current_symbols = self.monitored_symbols
        symbols_to_add = set(symbols_to_monitor) - set(current_symbols)
        symbols_to_remove = set(current_symbols) - set(symbols_to_monitor)

        for symbol in symbols_to_remove:
            await self.ws_manager.unsubscribe_ticker(symbol)
        
        for symbol in symbols_to_add:
            await self.ws_manager.subscribe_ticker(symbol)
        
        self.monitored_symbols = symbols_to_monitor

# In scanner_loop.py / main.py (when opening a position)
await price_provider.set_active_subscriptions([position_symbol])

# In scalability_refresh.py
await price_provider.set_active_subscriptions(new_top_pairs_symbols)
Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies that the PR's method of stopping and restarting the WebSocket to manage subscriptions is complex and brittle, and proposes a more robust architectural solution using subscribe/unsubscribe commands, which would simplify logic across multiple files.

High
Possible issue
Initialize all instance attributes

In the PostgreSQLDataLogger constructor, initialize scan_buffer,
opportunity_buffer, and flush_thread within the if not PSYCOPG2_AVAILABLE: block
to prevent potential AttributeErrors.

core/postgresql_datalogger.py [187-191]

 if not PSYCOPG2_AVAILABLE:
     logger.error("❌ psycopg2 non disponible - PostgreSQL DataLogger désactivé")
     self.enabled = False
     self.pool = None
+    self.scan_buffer = deque()
+    self.opportunity_buffer = deque()
+    self.flush_thread = None
     return
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies that several instance attributes are not initialized if psycopg2 is unavailable, which could lead to an AttributeError, and proposes initializing them to default values for robustness.

Low
General
Simplify active position detection logic

Simplify the active position detection logic by first checking
position_manager.active_position and then falling back to
app_state.get('active_position') to avoid redundancy.

api/price_provider.py [220-231]

 # 🔥 FIX: Vérifier via app_state pour détecter position active
 from main import app_state, position_manager
-if app_state and (app_state.get('active_position') or (
-    position_manager and position_manager.active_position
-)):
+
+active_pos = None
+if position_manager and position_manager.active_position:
+    active_pos = position_manager.active_position
+elif app_state and app_state.get('active_position'):
+    active_pos = app_state.get('active_position')
+
+if active_pos:
     # Position active: réabonner UNIQUEMENT au symbole de la position
-    active_pos = position_manager.active_position if position_manager else app_state.get('active_position')
-    if active_pos:
-        position_symbol = active_pos.symbol if hasattr(active_pos, 'symbol') else active_pos.get('symbol')
-        if position_symbol:
-            symbols_to_subscribe = [position_symbol]
-            logger.info(f"🔄 Réabonnement WebSocket (position active): {position_symbol} UNIQUEMENT")
+    position_symbol = active_pos.symbol if hasattr(active_pos, 'symbol') else active_pos.get('symbol')
+    if position_symbol:
+        symbols_to_subscribe = [position_symbol]
+        logger.info(f"🔄 Réabonnement WebSocket (position active): {position_symbol} UNIQUEMENT")
  • Apply / Chat
Suggestion importance[1-10]: 4

__

Why: The suggestion proposes a valid refactoring that simplifies the logic for finding the active position, making the code more readable and less redundant.

Low
  • Update

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants