Skip to main content

Workflow et Diagramme des Positions Swing

🎯 Vue d’ensemble

Ce document décrit le workflow complet des positions swing avec toutes les conditions et transitions d’état. Il inclut un diagramme Mermaid pour visualiser le flux complet.

📊 Diagramme de Workflow Complet

🔄 Transitions d’État Détaillées

NEW → RUNNING

Conditions requises :
  1. buy1Order.status === 'closed'
  2. buy1Order.average OU buy1Order.price existe
  3. netAmount > 0 (buy1Amount - reserveAmount)
Actions :
  • Calcul de buy1Amount, buy1Price, avgEntryPrice
  • Calcul de relativeAmount = buy1Amount - reserveAmount
  • Calcul des prix TP1/TP2 basés sur avgMaxPnlForPair avec valeur par défaut de 2.0% si non disponible
  • Calcul des montants TP1/TP2 (70% / 30% de relativeAmount)
  • Création des ordres TP1 et TP2
  • Mise à jour status = RUNNING

RUNNING → CLOSING

Conditions de fermeture :
  1. Changement de tendance :
    • LONG : candle.wma50 < wma50_htf
    • SHORT : candle.wma50 > wma50_htf
Note : Le Stop Loss est désormais géré par sharkModeCron.ts qui surveille les bougies 1m et met à jour le SL de manière agressive basé sur le Heikin Ashi. La fermeture par SL est gérée directement dans processStopLossOrder() qui passe la position en CLOSED. Actions :
  • Annulation des ordres TP1/TP2 ouverts
  • Création d’un ordre de fermeture (market)
  • status = CLOSING

🦈 Gestion du Shark Mode

Le Shark Mode est un mode de gestion agressive du Stop Loss activé automatiquement lorsque le prix évolue favorablement d’au moins 0.5% par rapport au prix d’entrée.

Activation du Shark Mode

Conditions :
  • Position en statut RUNNING ou NEW
  • Évolution du prix ≥ 0.5% par rapport au prix d’entrée (avgEntryPrice ou relativeEntryPrice)
  • Calcul basé sur les bougies Heikin Ashi 1m
Actions lors de l’activation :
  • sharkModeEnabled = true
  • sharkModeEnabledAt = Date.now()
  • Ajout d’une activité SHARK_MODE_ACTIVATED avec le pourcentage d’évolution

Mise à jour du Stop Loss en Shark Mode

Fréquence : Chaque minute (cron job) Calcul du SL agressif :
  • LONG : SL = haLow (low de la dernière bougie Heikin Ashi)
  • SHORT : SL = haHigh (high de la dernière bougie Heikin Ashi)
  • Le SL ne peut que se rapprocher du prix (monter pour LONG, descendre pour SHORT)
Redéfinition de sharkModePrice :
  • Si buy2Order.status === 'closed' : sharkModePrice = avgEntryPrice
  • Le sharkModePrice représente le prix d’entrée de référence pour le Shark Mode après BUY2

Gestion du SL Touché en Shark Mode

Cas 1 : BUY2 non fermé + SL touché

Comportement :
  • Le status reste RUNNING (ne pas passer en CLOSING)
  • Créer un ordre Close pour vendre relativeAmount (si > 0)
  • ✅ Ajouter une activité SL_TOUCHED
  • ✅ Utiliser processClose() pour créer l’ordre Close (position reste en RUNNING)
Calcul du montant à vendre :
amountToClose = relativeAmount - totalBaseAssetFees
Raison : BUY2 n’ayant pas été exécuté, on vend seulement le relativeAmount restant (montant de BUY1 moins TP1/TP2 déjà vendus). La position reste active car BUY2 peut encore être exécuté.

Cas 2 : BUY2 fermé + SL touché

Comportement :
  • Passer en CLOSING avec closedReason = REACHED_STOP_LOSS
  • Créer un ordre Close pour vendre relativeAmount + reserveAmount (si relativeAmount > 0)
  • ✅ Ajouter une activité SL_TOUCHED
  • ✅ Utiliser processClose() pour gérer la fermeture (annulation TP1/TP2, création ordre market)
Calcul du montant à vendre :
amountToClose = relativeAmount + reserveAmount - totalBaseAssetFees
Raison : BUY2 étant fermé, on vend tout ce qui reste (relativeAmount + reserveAmount) pour sécuriser les gains et fermer complètement la position.

SL de Sécurité basé sur lastCandle

En plus du SL agressif basé sur le Heikin Ashi, un SL de sécurité est surveillé basé sur lastCandle.low (LONG) ou lastCandle.high (SHORT). Conditions de déclenchement :
  • LONG : Prix 1m (haClose) ≤ lastCandle.low
  • SHORT : Prix 1m (haClose) ≥ lastCandle.high
Comportement selon l’état de BUY2 :

Cas 1 : BUY2 non fermé + SL de sécurité touché

Comportement :
  • Le status reste RUNNING (ne pas passer en CLOSING)
  • Créer un ordre Close pour vendre relativeAmount (si > 0)
  • ✅ Ajouter une activité SAFETY_SL_TOUCHED
  • ✅ Utiliser processClose() pour créer l’ordre Close (position reste en RUNNING)
Calcul du montant à vendre :
amountToClose = relativeAmount

Cas 2 : BUY2 fermé + SL de sécurité touché

Comportement :
  • Passer en CLOSING avec closedReason = REACHED_STOP_LOSS
  • Créer un ordre Close pour vendre relativeAmount + reserveAmount (si relativeAmount > 0)
  • ✅ Ajouter une activité SAFETY_SL_TOUCHED
  • ✅ Utiliser processClose() pour gérer la fermeture (annulation TP1/TP2, création ordre market)
Calcul du montant à vendre :
amountToClose = relativeAmount + reserveAmount
Note : Le SL de sécurité est vérifié en priorité avant le SL agressif (Heikin Ashi). Si les deux sont touchés simultanément, le SL de sécurité prend le dessus.

Ordre de fermeture (processClose)

Calcul du montant à fermer selon le contexte :
  1. SL touché + BUY2 non fermé :
    amountToClose = relativeAmount - totalBaseAssetFees
    
  2. SL touché + BUY2 fermé :
    amountToClose = relativeAmount + reserveAmount - totalBaseAssetFees
    
  3. Fermeture normale (changement de tendance) :
    amountToClose = relativeAmount + reserveAmount - totalBaseAssetFees
    
Vérification préalable :
  • Si relativeAmount <= 0 : Ne pas créer d’ordre Close, passer directement en CLOSED

CLOSING → CLOSED

Conditions :
  • closeOrder.status === 'closed'
Actions :
  • Calcul des coûts finaux (finalBuyCost, finalSellCost)
  • Calcul des frais finaux (finalBuyFee, finalSellFee)
  • status = CLOSED
  • closedReason = TREND_CHANGED | REACHED_STOP_LOSS
  • closedAt = new Date()
  • Notification envoyée

📋 Conditions de Création de Position

Condition LONG

longCondition && 
candle.wma50 >= wma50_htf && 
(previousCandle.rangeFilterLow <= wma50_htf || 
 previousCandle.rangeFilterLow <= candle.wma50)

Condition SHORT

shortCondition && 
candle.wma50 <= wma50_htf && 
(previousCandle.rangeFilterHigh >= wma50_htf || 
 previousCandle.rangeFilterHigh >= candle.wma50)

Vérifications de Budget

  • totalBudget <= availableBudget (balance libre - budget actif)
  • minNotional respecté pour TP2 après BUY1
  • reserveAmount calculé pour garantir minNotional sur TP2

🔍 Gestion des Ordres en RUNNING

BUY2 (processBuy2)

Conditions de création/remplacement :
  • buy2Order.status !== 'closed'
  • buy2Order n’existe pas OU prix différent OU annulé
Logique si BUY2 fermé :
  • Mise à jour de buy2Order, buy2Amount, buy2Price
  • Recalcul de relativeEntryPrice (moyenne pondérée de BUY1 et BUY2)
  • Mise à jour de relativeAmount via calculateRelativeAmount()
Note : Les ajustements de buy2Amount après fermeture de TP1/TP2 sont gérés dans processTp1() et processTp2(), pas dans processBuy2(). Les recalculs et archivages des TP sont également gérés dans leurs fonctions respectives.

TP1 (processTp1)

Conditions de création/remplacement :
  • tp1Order.status !== 'closed'
  • tp1Order n’existe pas OU montant différent OU annulé
Logique si TP1 fermé :
  • Archive de TP1 dans archivedTp1Orders (si nécessaire)
  • Calcul de relativeAmount via calculateRelativeAmount() qui prend en compte tous les ordres fermés
  • Si BUY2 pas encore fermé : ajustement de buy2Amount pour inclure le montant TP1 vendu

TP2 (processTp2)

Conditions de création/remplacement :
  • tp2Order.status !== 'closed'
  • tp2Order n’existe pas OU montant différent OU annulé
Logique si TP2 fermé :
  • Archive de TP2 dans archivedTp2Orders (si nécessaire)
  • Calcul de relativeAmount via calculateRelativeAmount() qui prend en compte tous les ordres fermés
  • Si BUY2 pas encore fermé : ajustement de buy2Amount pour inclure le montant TP2 vendu
Note : La vérification que TP1 doit être fermé avant TP2 est gérée dans processTp1() qui doit détecter et signaler les anomalies si TP2 est fermé avant TP1.

⚠️ Incohérences Détectées et Solutions

1. Incohérences de Budget

Problème : buy1Budget ou buy2Budgetamount × price Cause possible :
  • Calculs de budget effectués avant l’exécution réelle
  • Arrondis lors de la création d’ordre
  • Frais non pris en compte dans le budget initial
Solution :
  • Utiliser amount × price comme source de vérité
  • Recalculer le budget réel après exécution
  • Stocker actualBuy1Cost et actualBuy2Cost

2. Incohérences des Montants TP

Problème : tp1Amount + tp2Amount ≠ buy1Amount + buy2Amount Cause possible :
  • Ajustements après fermeture de TP avant BUY2
  • Calculs de relativeAmount incorrects
  • Archives non prises en compte
Solution :
  • Toujours utiliser calculateRelativeAmount() pour calculer relativeAmount
  • Vérifier que tp1Amount + tp2Amount + relativeAmount = buy1Amount + buy2Amount
  • Prendre en compte les TP archivés dans les calculs

3. Incohérences de relativeAmount

Problème : relativeAmounttotalBuy - totalTP Cause possible :
  • Calculs non synchronisés avec les ordres fermés
  • TP archivés non pris en compte
  • Ajustements de BUY2 non reflétés
Solution :
  • Utiliser calculateRelativeAmount() qui prend en compte tous les ordres
  • Vérifier la cohérence après chaque transition d’état
  • Ajouter verifyPositionIntegrity() après chaque modification

4. Erreur de Prix TP pour SHORT

Problème : tp1Price >= buy1Price pour une position SHORT Cause possible :
  • Calcul des prix TP incorrect pour SHORT
  • Formule inversée
Solution :
  • Vérifier la formule : tp1Price = relativeEntryPrice * (1 - (0.5 * avgMaxPnl / 100))
  • Pour SHORT : TP1 doit être < entryPrice, TP2 < TP1
  • Ajouter une validation avant création d’ordre TP

5. Ordre Chronologique des Activités

Problème : Timestamps d’activités non chronologiques Cause possible :
  • Activités ajoutées en parallèle
  • Timestamps non synchronisés
  • Retards dans l’enregistrement
Solution :
  • Utiliser new Date() au moment de l’ajout
  • Trier les activités par timestamp avant affichage
  • Ajouter un index de séquence si nécessaire

🔧 Fonctions de Vérification

verifyPositionIntegrity()

Vérifie la cohérence de la position après chaque modification :
- buy1Amount + buy2Amount = totalBuy
- tp1Amount + tp2Amount + relativeAmount = totalBuy (ou proche)
- relativeAmount = calculateRelativeAmount(position)
- Prix TP cohérents avec orderSide
- Ordres fermés ont des montants cohérents

calculateRelativeAmount()

Calcule le montant relatif en prenant en compte tous les ordres :
relativeAmount = 0

// Ajouter les achats fermés
+ (buy1Amount si buy1Order.status === 'closed')
+ (buy2Amount si buy2Order.status === 'closed')

// Soustraire les réserves et ventes
- reserveAmount
- (tp1Amount si tp1Order.status === 'closed')
- (tp2Amount si tp2Order.status === 'closed')
- (tp1Amount des archivedTp1Orders fermés)
- (tp2Amount des archivedTp2Orders fermés)
- (slAmount si slOrder.status === 'closed')
- (closeAmount si closeOrder.status === 'closed')

// Gérer les ordres partiellement exécutés puis annulés
- (tp1Order.filled si tp1Order.status === 'canceled' && filled > 0)
- (tp2Order.filled si tp2Order.status === 'canceled' && filled > 0)

return Math.max(0, relativeAmount) // Ne jamais retourner négatif
Points importants :
  • Prend en compte les ordres TP archivés (cycles multiples)
  • Gère les ordres partiellement exécutés puis annulés
  • Ne retourne jamais un montant négatif
  • Doit être utilisé après chaque modification d’ordre

📝 Checklist de Validation

Avant chaque transition d’état, vérifier :
  • Budgets cohérents avec les coûts réels
  • Montants TP cohérents avec les achats
  • relativeAmount calculé correctement
  • Prix TP cohérents avec orderSide
  • Ordres archivés correctement
  • Activités enregistrées avec timestamps corrects
  • Status cohérent avec l’état des ordres
  • Pas de double comptage des montants

🔗 Références