← Retour aux articles
March 23, 2026
5 min de lecture

Types d'ordres dans le trading algorithmique : de la limite avec poursuite aux ordres virtuels

Types d'ordres dans le trading algorithmique : de la limite avec poursuite aux ordres virtuels
#orders
#algotrading
#limit
#chasing
#virtual-orders
#grid-bot
#market-making
📖
Part 2 of 6 · Collection
Order Book & Market Microstructure

Lorsqu'un débutant ouvre un terminal d'exchange, il voit deux boutons : "Acheter" et "Vendre". Lorsqu'un trader algo ouvre sa base de code, il voit vingt-sept types d'ordres, trois niveaux d'abstraction et une pile de cas limites qui lui donnent envie de refermer son ordinateur portable et d'aller vendre des concombres au marché fermier. Mais les concombres, hélas, ne vous permettront pas de faire de l'arbitrage de funding rate à 3h59 UTC, alors mettons-nous au travail.

Dans cet article, nous parcourrons tout le chemin, des ordres d'exchange de base jusqu'aux constructions virtuelles synthétiques qui n'existent qu'au sein de votre système et n'apparaissent jamais dans le carnet d'ordres. Attendez-vous à du TypeScript, du Python, un peu de douleur et un brin d'illumination.


1. Les ordres standard des exchanges : la base que vous ne pouvez pas sauter

Ordres standard Classification des types d'ordres standard : de market à iceberg

Avant de construire quoi que ce soit de complexe, nous devons nous assurer de bien comprendre les blocs de base. Il est surprenant de voir combien de gens confondent stop-limit et stop-market, puis se demandent pourquoi leur stop "ne s'est pas déclenché" (spoiler : il s'est bien déclenché, mais l'ordre limite n'a pas été exécuté à cause du slippage).

Ordre market

Le type le plus simple et en même temps le plus dangereux. Vous dites à l'exchange : "Achète/vends immédiatement, à n'importe quel prix disponible." L'exchange prend de la liquidité dans le carnet d'ordres, en commençant par le meilleur prix. Si le volume au meilleur niveau ne suffit pas, il glisse plus loin.

Quand l'utiliser : sortie d'urgence d'une position, exécution d'un signal où la vitesse compte plus que le prix.

Pièges : sur un marché peu liquide, un ordre market de 100 BTC peut faire bouger le prix de plusieurs pour cent. Les backtests qui modélisent des ordres market sans tenir compte de l'impact relèvent de la pure fantaisie.

Ordre limite

Vous spécifiez un prix exact. L'ordre entre dans le carnet d'ordres et attend que quelqu'un accepte votre prix. Si le prix d'un ordre limite d'achat est au-dessus du marché actuel, il s'exécute immédiatement (comme un ordre market, mais avec un prix maximal garanti).

Point clé : un ordre limite ne garantit pas l'exécution. Le prix peut atteindre votre niveau puis repartir, vous laissant assis dans la file d'attente (plus de détails dans notre article sur la position dans la file).

Stop-market et Stop-limit

C'est ici que la confusion commence. Les deux types sont des ordres "endormis" qui s'activent lorsque le prix de déclenchement (stop price) est atteint. Mais :

  • Stop-market : au déclenchement, se convertit en ordre market. Garantit l'exécution, mais pas le prix.
  • Stop-limit : au déclenchement, se convertit en ordre limite. Garantit le prix (pas pire que celui spécifié), mais pas l'exécution.

Sur le marché crypto volatil, un stop-limit peut "manquer" : le prix a percé le stop, l'ordre limite a été placé, mais le marché s'est déjà envolé plus loin. Il vous reste un ordre limite non exécuté et une perte qui grandit. C'est exactement pourquoi le stop-market est plus couramment utilisé pour les stop-loss.

Trailing stop

Un stop qui "suit" le prix à une distance fixée. Le prix monte, le stop remonte. Le prix baisse, le stop reste en place. Utile pour protéger les profits dans les stratégies de suivi de tendance.

Prise en charge par les exchanges : tous les exchanges ne prennent pas en charge les trailing stops natifs. Les traders algo les implémentent souvent de manière programmatique, ce qui donne plus de contrôle sur les paramètres (callback rate, prix d'activation, taille du pas).

Ordre iceberg

Un ordre dont seule une fraction du volume total est visible dans le carnet d'ordres. Vous voulez acheter 1 000 BTC, mais vous n'en affichez que 10 dans le carnet. Quand les 10 premiers sont exécutés, les 10 suivants apparaissent.

Pourquoi : pour cacher vos véritables intentions au marché. Un gros ordre dans le carnet signale à tout le monde que "quelqu'un de gros veut acheter/vendre". En réaction, les algorithmes HFT commencent le front-running et le prix s'éloigne de vous.

Réserve : sur de nombreux exchanges crypto, les ordres iceberg sont soit non pris en charge, soit facilement détectés par le motif de volumes identiques. Les algorithmes avancés randomisent la taille de la portion visible.

Paramètres time-in-force : GTC, GTD, IOC, FOK

Ce ne sont pas des types d'ordres distincts mais des paramètres de time-in-force, c'est-à-dire combien de temps un ordre vit :

Paramètre Nom complet Comportement
GTC Good Till Cancelled Vit jusqu'à annulation. Le standard par défaut
GTD Good Till Date Vit jusqu'à une date/heure spécifiée
IOC Immediate or Cancel S'exécute immédiatement (en totalité ou en partie), le reste est annulé
FOK Fill or Kill S'exécute uniquement en totalité et immédiatement. Si c'est impossible, il est entièrement annulé

IOC vs FOK : la différence est cruciale. IOC peut s'exécuter partiellement : vous vouliez acheter 100 BTC, vous en avez acheté 3, le reste a été annulé. FOK, c'est 100 ou rien.

Post-only (Maker-only)

Un ordre qui a la garantie d'entrer dans le carnet en tant que maker et qui ne s'exécute jamais comme taker. Si, au moment du placement, le prix provoquerait une exécution immédiate, l'exchange le rejette (ou ajuste le prix, selon l'exchange).

Pourquoi : les frais maker sont généralement plus bas que les frais taker (sur Binance, 0,02 % contre 0,04 % pour les paliers VIP). Pour un market maker qui place des milliers d'ordres par jour, la différence de frais fait la différence entre profit et perte.


2. TWAP et VWAP : comment les institutions cachent un éléphant dans le carnet d'ordres

Quand un hedge fund veut acheter une position de 50 M$, il ne place pas un seul ordre market. Il utilise des algorithmes d'exécution : des algorithmes qui découpent un gros ordre en de nombreux plus petits et les exécutent dans le temps, en minimisant l'impact sur le marché.

TWAP (Time-Weighted Average Price)

L'idée est on ne peut plus simple : découper le volume total en parts égales et les exécuter à intervalles de temps égaux.

import asyncio
from datetime import datetime, timedelta

class TWAPExecutor:
    """
    TWAP executor: splits a large order into equal parts
    and executes them at equal time intervals.
    """
    def __init__(self, exchange, symbol: str, side: str,
                 total_qty: float, duration_minutes: int, num_slices: int):
        self.exchange = exchange
        self.symbol = symbol
        self.side = side
        self.total_qty = total_qty
        self.slice_qty = total_qty / num_slices
        self.interval = (duration_minutes * 60) / num_slices
        self.num_slices = num_slices
        self.executed_qty = 0.0
        self.fills: list[dict] = []

    async def execute(self):
        for i in range(self.num_slices):
            remaining = self.total_qty - self.executed_qty
            qty = min(self.slice_qty, remaining)
            if qty <= 0:
                break

            try:
                order = await self.exchange.create_order(
                    symbol=self.symbol,
                    type="market",
                    side=self.side,
                    amount=qty,
                )
                self.executed_qty += float(order["filled"])
                self.fills.append(order)
                print(f"[TWAP] slice {i+1}/{self.num_slices}: "
                      f"filled {order['filled']} @ {order['average']}")
            except Exception as e:
                print(f"[TWAP] slice {i+1} failed: {e}")

            if i < self.num_slices - 1:
                await asyncio.sleep(self.interval)

        avg_price = (
            sum(f["cost"] for f in self.fills) /
            sum(f["filled"] for f in self.fills)
        ) if self.fills else 0
        print(f"[TWAP] done: {self.executed_qty}/{self.total_qty} "
              f"avg price: {avg_price:.2f}")

VWAP (Volume-Weighted Average Price)

VWAP est plus malin : il tient compte du profil de volume de trading typique. Si 30 % du volume quotidien se traite habituellement entre 9h00 et 10h00, VWAP exécutera 30 % de l'ordre pendant cette fenêtre. L'objectif est de rapprocher le prix moyen d'exécution le plus possible du VWAP du marché.

class VWAPExecutor:
    """
    VWAP executor: distributes volume proportionally
    to the historical volume profile.
    """
    def __init__(self, exchange, symbol: str, side: str,
                 total_qty: float, volume_profile: list[float]):
        self.exchange = exchange
        self.symbol = symbol
        self.side = side
        self.total_qty = total_qty
        total_weight = sum(volume_profile)
        self.weights = [w / total_weight for w in volume_profile]

    async def execute(self, interval_seconds: float = 60.0):
        executed = 0.0
        for i, weight in enumerate(self.weights):
            qty = self.total_qty * weight
            remaining = self.total_qty - executed
            qty = min(qty, remaining)

            if qty <= 0:
                break

            order = await self.exchange.create_order(
                symbol=self.symbol,
                type="market",
                side=self.side,
                amount=qty,
            )
            executed += float(order["filled"])
            print(f"[VWAP] period {i+1}: weight={weight:.2%}, "
                  f"filled={order['filled']} @ {order['average']}")

            await asyncio.sleep(interval_seconds)

Différence TWAP vs VWAP : TWAP est plus simple et plus prévisible. VWAP offre un meilleur prix moyen mais nécessite un profil de volume fiable. Sur le marché crypto, où les volumes peuvent être gonflés par du wash-trading, le profil VWAP doit être construit avec soin.


3. La limite avec poursuite : quand votre ordre sait poursuivre le prix

Ordres limites avec poursuite Limite avec poursuite : l'ordre poursuit un prix en mouvement avec une agressivité configurable

Là, ça devient vraiment intéressant. Un ordre limite standard est une entité passive : il reste dans le carnet d'ordres et attend. Si le prix a bougé, l'ordre reste non exécuté. Pour un trader algo, c'est souvent inacceptable : le signal d'entrée s'est déclenché, mais la position n'a pas été construite parce que le marché a bougé de 0,1 %.

Un ordre limite avec poursuite est un wrapper programmatique autour d'un ordre limite qui :

  1. Place un ordre limite au meilleur prix actuel (ou avec un léger décalage)
  2. Surveille le prix via WebSocket
  3. Si le prix s'éloigne de l'ordre, l'annule et le replace plus près du prix actuel
  4. Répète jusqu'à ce que l'ordre soit exécuté ou dépasse l'écart autorisé

Paramètres clés

  • chase_interval_ms : à quelle fréquence vérifier et replacer l'ordre. 100 ms : agressif, 1000 ms : détendu.
  • max_chase_distance : écart maximal par rapport au prix initial avant l'annulation de l'ordre. Protection contre la poursuite d'un marché qui s'emballe.
  • aggression_level : à quelle distance du prix de marché placer l'ordre limite. 0 : au meilleur bid/ask (passif), 1 : en traversant le spread (agressif, de fait un taker).
  • chase_on_partial : s'il faut continuer la poursuite lorsque l'ordre est partiellement exécuté.

Implémentation en TypeScript

interface ChasingOrderParams {
  symbol: string;
  side: "buy" | "sell";
  totalQty: number;
  /** 0 = passive (at best bid/ask), 1 = cross spread */
  aggression: number;
  /** max price deviation from initial price */
  maxChaseDistance: number;
  /** how often to re-evaluate, ms */
  chaseIntervalMs: number;
  /** stop chasing after this many ms */
  timeoutMs: number;
}

class ChasingLimitOrder {
  private currentOrderId: string | null = null;
  private filledQty = 0;
  private initialPrice: number | null = null;
  private startTime = Date.now();

  constructor(
    private exchange: any, // ccxt exchange instance
    private params: ChasingOrderParams
  ) {}

  async execute(): Promise<{ filledQty: number; avgPrice: number }> {
    const fills: Array<{ qty: number; price: number }> = [];

    while (this.filledQty < this.params.totalQty) {
      // Timeout
      if (Date.now() - this.startTime > this.params.timeoutMs) {
        console.log("[CHASE] timeout reached, cancelling");
        await this.cancelCurrent();
        break;
      }

      // Get current order book
      const book = await this.exchange.fetchOrderBook(
        this.params.symbol, 5
      );
      const bestBid = book.bids[0][0];
      const bestAsk = book.asks[0][0];
      const spread = bestAsk - bestBid;

      // Calculate target price
      let targetPrice: number;
      if (this.params.side === "buy") {
        targetPrice = bestBid + spread * this.params.aggression;
      } else {
        targetPrice = bestAsk - spread * this.params.aggression;
      }

      // Remember the initial price
      if (this.initialPrice === null) {
        this.initialPrice = targetPrice;
      }

      // Check max chase distance
      const deviation = Math.abs(targetPrice - this.initialPrice);
      if (deviation > this.params.maxChaseDistance) {
        console.log(
          `[CHASE] max deviation exceeded: ${deviation.toFixed(4)} > ` +
          `${this.params.maxChaseDistance}`
        );
        await this.cancelCurrent();
        break;
      }

      // Check current order
      if (this.currentOrderId) {
        const order = await this.exchange.fetchOrder(
          this.currentOrderId, this.params.symbol
        );

        if (order.status === "closed") {
          fills.push({ qty: order.filled, price: order.average });
          this.filledQty += order.filled;
          this.currentOrderId = null;
          continue;
        }

        // Update filledQty for partial fills
        if (order.filled > 0) {
          const newFilled = order.filled - (
            fills.reduce((s, f) => s + f.qty, 0) - this.filledQty
          );
          // Order is in place — do we need to reprice?
        }

        const currentPrice = parseFloat(order.price);
        const priceDiff = Math.abs(currentPrice - targetPrice);
        const tickSize = spread * 0.1 || 0.01;

        if (priceDiff > tickSize) {
          // Price moved — reprice
          console.log(
            `[CHASE] repricing: ${currentPrice} -> ` +
            `${targetPrice.toFixed(4)}`
          );
          await this.cancelCurrent();
        } else {
          // Order is at the right price — wait
          await this.sleep(this.params.chaseIntervalMs);
          continue;
        }
      }

      // Place new order
      const remainingQty = this.params.totalQty - this.filledQty;
      const order = await this.exchange.createLimitOrder(
        this.params.symbol,
        this.params.side,
        remainingQty,
        targetPrice
      );
      this.currentOrderId = order.id;
      console.log(
        `[CHASE] placed ${this.params.side} ${remainingQty} ` +
        `@ ${targetPrice.toFixed(4)}`
      );

      await this.sleep(this.params.chaseIntervalMs);
    }

    const totalCost = fills.reduce((s, f) => s + f.qty * f.price, 0);
    const avgPrice = this.filledQty > 0 ? totalCost / this.filledQty : 0;
    return { filledQty: this.filledQty, avgPrice };
  }

  private async cancelCurrent(): Promise<void> {
    if (this.currentOrderId) {
      try {
        await this.exchange.cancelOrder(
          this.currentOrderId, this.params.symbol
        );
      } catch { /* order already filled or cancelled */ }
      this.currentOrderId = null;
    }
  }

  private sleep(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
}

Quand la poursuite est nuisible

La poursuite est un outil puissant, mais il est facile de la transformer en générateur de pertes :

  1. Spam de cancel/replace. Chaque annulation et replacement est une charge pour l'API. Les exchanges limitent les requêtes (rate-limit), et une poursuite agressive peut faire bannir votre clé d'API.
  2. Sélection adverse. Si le prix vous échappe, le marché sait peut-être quelque chose que vous ignorez. Poursuivre le prix dans cette situation, c'est acheter au sommet.
  3. Transition de maker à taker. Avec une forte agressivité, vous payez de fait des frais taker, mais avec un délai (annulation + nouvel ordre). Parfois, il est plus simple de placer directement un ordre market.

4. Ordres temporisés : précision à la milliseconde

Il existe des situations où vous devez exécuter un ordre non pas "au prix X" mais "à l'instant T". Ça paraît étrange ? C'est en réalité toute une classe de stratégies.

Cas d'usage

Arbitrage de funding rate. Sur les futures perpétuels, le funding est payé toutes les 8 heures (00:00, 08:00, 16:00 UTC sur Binance). Si le funding rate = +0,1 %, vous devez être short au moment du settlement. Stratégie : ouvrir un short quelques secondes avant le settlement, encaisser le funding, fermer la position. Le timing est crucial : une seconde de retard signifie un funding manqué.

Ouvertures/clôtures de session. Sur les marchés traditionnels et certains dérivés crypto, il y a des sessions fixes. L'enchère d'ouverture (NYSE, CME) est le moment où la liquidité est à son pic. Placer un ordre 100 ms avant l'enchère est un edge.

Exécution sur actualité. Les données d'inflation sont publiées à une heure programmée. L'algorithme extrait le chiffre d'un fil d'actualités et place un ordre en 50 ms. Ici, l'exécution temporisée se combine à une logique événementielle.

Implémentation

class TimeBasedOrder {
  constructor(
    private exchange: any,
    private symbol: string,
    private side: "buy" | "sell",
    private qty: number,
    private orderType: "market" | "limit",
    private limitPrice?: number
  ) {}

  /**
   * Schedule execution at a precise time.
   * Uses a busy-wait loop for maximum precision.
   */
  async executeAt(targetTime: Date): Promise<any> {
    const targetMs = targetTime.getTime();

    // Phase 1: coarse wait (sleep)
    const coarseWait = targetMs - Date.now() - 500; // wake up 500ms early
    if (coarseWait > 0) {
      console.log(
        `[TIME-ORDER] sleeping for ${(coarseWait / 1000).toFixed(1)}s`
      );
      await new Promise((r) => setTimeout(r, coarseWait));
    }

    // Phase 2: precise wait (busy-wait)
    while (Date.now() < targetMs) {
      // spin — burns CPU, but achieves ~1ms precision
    }

    // Phase 3: execution
    const sendTime = Date.now();
    const order = await this.exchange.createOrder(
      this.symbol,
      this.orderType,
      this.side,
      this.qty,
      this.limitPrice
    );

    console.log(
      `[TIME-ORDER] executed at ${new Date(sendTime).toISOString()}, ` +
      `target was ${targetTime.toISOString()}, ` +
      `delta: ${sendTime - targetMs}ms`
    );

    return order;
  }
}

// Example: place an order exactly at 00:00:00 UTC (funding settlement)
const executor = new TimeBasedOrder(exchange, "BTC/USDT", "sell", 0.1, "market");
const target = new Date("2026-03-24T00:00:00.000Z");
await executor.executeAt(target);

Réserve importante : la précision d'un ordre temporisé n'est pas limitée par votre code mais par la latence réseau vers l'exchange. Si votre ping vers l'API est de 50 ms, même un busy-wait parfait aura un delta de 50 ms. Pour du HFT sérieux, on utilise la co-location : le serveur est physiquement placé à côté du moteur de matching de l'exchange.


5. Ordres virtuels/synthétiques : les invisibles dans votre système

Ordres virtuels pour grid bots Ordres virtuels : les ordres n'existent que dans la mémoire du bot jusqu'au déclenchement du trigger

C'est peut-être l'outil le plus sous-estimé de l'arsenal du trader algo. Un ordre virtuel (également appelé ordre synthétique) est un ordre qui n'existe que dans votre système. Il n'est pas envoyé à l'exchange tant qu'une condition de déclenchement n'est pas remplie (généralement, le prix atteignant un certain niveau).

Comment ça marche

  1. Votre algorithme décide : "Je veux acheter du BTC à 40 000 $"
  2. Au lieu d'envoyer un ordre limite à l'exchange, il crée un ordre virtuel en mémoire
  3. S'abonne au flux de prix WebSocket
  4. Quand le bid/ask atteint 40 000 $, il envoie un vrai ordre market ou limite à l'exchange

Pourquoi les ordres virtuels comptent

Aucune fuite d'information. Votre ordre est invisible dans le carnet d'ordres. Personne — ni les autres traders, ni les algorithmes HFT, ni même l'exchange lui-même — ne connaît vos intentions jusqu'au moment de l'exécution. Cela déplace fondamentalement le rapport de force.

Protection contre le front-running. Sur les exchanges crypto, surtout les moins transparents, il existe un soupçon raisonnable que l'information sur les gros ordres limites puisse être utilisée pour du front-running (il existe même des études à ce sujet). Les ordres virtuels éliminent ce risque.

Grid bots. Un grid bot classique place une grille de 50 à 200 ordres à différents niveaux de prix. Si vous les envoyez tous à l'exchange, ce sont 200 ordres dans le carnet qui : (a) sont visibles par tous, (b) consomment la limite d'ordres de l'exchange (généralement 200 à 300 ordres ouverts par compte), (c) si le prix bouge brusquement, s'exécutent tous et vous vous retrouvez avec une position gigantesque. Les ordres virtuels résolvent ces trois problèmes.

Attraper des couteaux qui tombent. Stratégie : placer des ordres d'achat virtuels aux niveaux -5 %, -10 %, -15 % sous le prix actuel. Si le marché chute, les ordres se déclenchent progressivement. S'il ne chute pas, vous ne risquez rien et n'occupez aucun slot d'ordre de l'exchange.

Implémentation en TypeScript

interface VirtualOrder {
  id: string;
  symbol: string;
  side: "buy" | "sell";
  triggerPrice: number;
  qty: number;
  /** Order type sent to the exchange upon triggering */
  executionType: "market" | "limit";
  /** For limit: offset from trigger price */
  limitOffset?: number;
  status: "pending" | "triggered" | "filled" | "failed";
}

class VirtualOrderManager {
  private orders: Map<string, VirtualOrder> = new Map();
  private orderCounter = 0;

  constructor(private exchange: any) {}

  /**
   * Create a virtual order. Nothing is sent to the exchange.
   */
  addOrder(params: Omit<VirtualOrder, "id" | "status">): string {
    const id = `virt_${++this.orderCounter}`;
    this.orders.set(id, { ...params, id, status: "pending" });
    console.log(
      `[VIRTUAL] created ${params.side} ${params.qty} ` +
      `${params.symbol} @ trigger ${params.triggerPrice}`
    );
    return id;
  }

  /**
   * Called on every price tick (from WebSocket).
   */
  async onPriceUpdate(
    symbol: string, bestBid: number, bestAsk: number
  ): Promise<void> {
    for (const [id, order] of this.orders) {
      if (order.symbol !== symbol || order.status !== "pending") continue;

      const triggered =
        (order.side === "buy" && bestAsk <= order.triggerPrice) ||
        (order.side === "sell" && bestBid >= order.triggerPrice);

      if (!triggered) continue;

      order.status = "triggered";
      console.log(
        `[VIRTUAL] ${id} triggered! bid=${bestBid} ask=${bestAsk}`
      );

      try {
        let realOrder: any;

        if (order.executionType === "market") {
          realOrder = await this.exchange.createMarketOrder(
            order.symbol, order.side, order.qty
          );
        } else {
          const limitPrice = order.side === "buy"
            ? order.triggerPrice + (order.limitOffset ?? 0)
            : order.triggerPrice - (order.limitOffset ?? 0);
          realOrder = await this.exchange.createLimitOrder(
            order.symbol, order.side, order.qty, limitPrice
          );
        }

        order.status = "filled";
        console.log(
          `[VIRTUAL] ${id} filled: ${realOrder.filled} ` +
          `@ ${realOrder.average ?? realOrder.price}`
        );
      } catch (err) {
        order.status = "failed";
        console.error(`[VIRTUAL] ${id} execution failed:`, err);
      }
    }
  }

  /**
   * Get all active virtual orders.
   */
  getPendingOrders(): VirtualOrder[] {
    return [...this.orders.values()].filter(
      (o) => o.status === "pending"
    );
  }

  cancelOrder(id: string): boolean {
    const order = this.orders.get(id);
    if (order && order.status === "pending") {
      this.orders.delete(id);
      return true;
    }
    return false;
  }
}

// --- Example: Grid bot with virtual orders ---

async function gridBot(exchange: any) {
  const manager = new VirtualOrderManager(exchange);
  const currentPrice = 42000;
  const gridStep = 200;      // grid step
  const gridLevels = 20;     // levels in each direction
  const qtyPerLevel = 0.01;  // BTC per level

  // Create virtual grid
  for (let i = 1; i <= gridLevels; i++) {
    // Buy orders below current price
    manager.addOrder({
      symbol: "BTC/USDT",
      side: "buy",
      triggerPrice: currentPrice - gridStep * i,
      qty: qtyPerLevel,
      executionType: "limit",
      limitOffset: 1,  // limit price = trigger + 1 USDT
    });

    // Sell orders above current price
    manager.addOrder({
      symbol: "BTC/USDT",
      side: "sell",
      triggerPrice: currentPrice + gridStep * i,
      qty: qtyPerLevel,
      executionType: "limit",
      limitOffset: 1,
    });
  }

  console.log(
    `[GRID] created ${gridLevels * 2} virtual orders, ` +
    `0 on exchange`
  );

  // WebSocket subscription (pseudocode for ccxt.pro)
  while (true) {
    const ticker = await exchange.watchTicker("BTC/USDT");
    await manager.onPriceUpdate(
      "BTC/USDT", ticker.bid, ticker.ask
    );
  }
}

Pièges des ordres virtuels

  1. Écart de latence. Entre le moment où vous voyez le prix et le moment où le vrai ordre atteint l'exchange, du temps s'écoule. Sur un marché volatil, le prix peut s'envoler pendant ces 20 à 100 ms. Solution : envoyer un ordre limite légèrement agressif (avec une marge).

  2. Exécutions manquées. Si le prix a "percé" votre niveau en un seul tick (flash crash) puis a rebondi, vous pourriez ne pas réagir à temps. Un ordre limite ordinaire présent dans le carnet aurait été exécuté ; un ordre virtuel, non.

  3. Gestion de l'état. Les ordres virtuels vivent en mémoire. Si le processus plante, les ordres sont perdus. Solution : stockage persistant (Redis, SQLite, fichier) avec récupération au redémarrage.


6. Ordres conditionnels/intelligents : combinatoire des ordres

Quand un seul ordre ne suffit pas, les traders les combinent en constructions conditionnelles. Certaines sont prises en charge nativement par les exchanges, d'autres sont implémentées de manière programmatique.

OCO (One Cancels Other)

Deux ordres sont liés : si l'un s'exécute, l'autre est automatiquement annulé. Exemple classique : vous êtes en position longue et voulez placer à la fois un take-profit et un stop-loss. Celui qui se déclenche en premier, l'autre doit être annulé.

class OCOHandler:
    """
    OCO: when one order fills, the other is cancelled.
    """
    def __init__(self, exchange, symbol: str):
        self.exchange = exchange
        self.symbol = symbol
        self.order_a_id: str | None = None
        self.order_b_id: str | None = None

    async def place(
        self,
        take_profit_price: float,
        stop_loss_price: float,
        qty: float,
    ):
        tp = await self.exchange.create_limit_sell_order(
            self.symbol, qty, take_profit_price
        )
        self.order_a_id = tp["id"]

        sl = await self.exchange.create_order(
            self.symbol, "stop", "sell", qty,
            None, {"stopPrice": stop_loss_price}
        )
        self.order_b_id = sl["id"]

        print(f"[OCO] TP @ {take_profit_price}, SL @ {stop_loss_price}")

    async def monitor(self):
        """Checks statuses and cancels the paired order."""
        while True:
            if self.order_a_id:
                a = await self.exchange.fetch_order(
                    self.order_a_id, self.symbol
                )
                if a["status"] == "closed":
                    print("[OCO] take-profit filled, cancelling stop-loss")
                    await self.exchange.cancel_order(
                        self.order_b_id, self.symbol
                    )
                    break

            if self.order_b_id:
                b = await self.exchange.fetch_order(
                    self.order_b_id, self.symbol
                )
                if b["status"] == "closed":
                    print("[OCO] stop-loss filled, cancelling take-profit")
                    await self.exchange.cancel_order(
                        self.order_a_id, self.symbol
                    )
                    break

            await asyncio.sleep(0.5)

Ordre bracket

Une construction à trois composantes : un ordre d'entrée principal + OCO pour la sortie (take-profit + stop-loss). En substance, un cycle de vie complet de la position en un seul appel :

  1. Entrée : ordre limite d'achat
  2. Take-profit : ordre limite de vente (au-dessus)
  3. Stop-loss : ordre stop-market de vente (en dessous)

Quand l'entrée est exécutée, TP et SL sont placés automatiquement. Quand l'un des deux est exécuté, l'autre est annulé.

Logique If-Then

L'option la plus flexible : des chaînes d'ordres avec des conditions arbitraires :


rules = [
    {
        "condition": {"symbol": "BTC/USDT", "price_above": 50000},
        "action": {"type": "market_buy", "symbol": "ETH/USDT", "qty": 10},
        "then": [
            {
                "condition": {"symbol": "ETH/USDT", "price_above": 4000},
                "action": {"type": "market_sell", "symbol": "ETH/USDT", "qty": 10},
            },
            {
                "condition": {"symbol": "ETH/USDT", "price_below": 3500},
                "action": {"type": "market_sell", "symbol": "ETH/USDT", "qty": 10},
            },
        ]
    }
]

De telles constructions ne sont prises en charge nativement par aucun exchange, seulement par implémentation programmatique. C'est l'une des raisons pour lesquelles les systèmes d'algotrading finissent inévitablement par développer leur propre couche de gestion des ordres.


7. Comment les market makers utilisent les types d'ordres spécialisés

Le market making est un univers à part entière, et la boîte à outils d'ordres est à l'avenant. Le travail d'un market maker est de coter en continu bid et ask, de gagner sur le spread, tout en minimisant la sélection adverse (la situation où un trader informé opère contre vous).

Post-only comme incontournable

Pour un market maker, post-only n'est pas une option, c'est une exigence. Si votre ordre s'exécute accidentellement comme taker, au lieu de recevoir un rebate maker, vous payez des frais taker. Sur des milliers d'ordres par jour, c'est catastrophique.

async def quote(exchange, symbol, mid_price, half_spread, qty):
    bid_price = mid_price - half_spread
    ask_price = mid_price + half_spread

    bid = await exchange.create_order(
        symbol, "limit", "buy", qty, bid_price,
        {"postOnly": True}  # CRITICAL for market makers
    )
    ask = await exchange.create_order(
        symbol, "limit", "sell", qty, ask_price,
        {"postOnly": True}
    )
    return bid, ask

Ordres cachés (hidden)

Sur certains exchanges (Kraken, Bitfinex), les ordres cachés sont disponibles : ils n'apparaissent pas dans le carnet d'ordres mais sont sur l'exchange et participent au matching. Le compromis : vous payez des frais taker même en tant que maker, mais vous gagnez en anonymat.

Pour un market maker, c'est un outil de gestion d'inventaire : si une grosse position s'est accumulée, vous pouvez placer un ordre caché pour la déboucler sans révéler votre intention au marché.

Ordres ancrés (pegged)

Un ordre ancré au meilleur bid/ask. Sur Coinbase Advanced Trade, par exemple, vous pouvez placer un ordre qui suit automatiquement le meilleur bid et se tient toujours en tête de la file. C'est un ordre à poursuite natif au niveau de l'exchange, mais il est loin d'être universellement disponible.

Gestion groupée des ordres

Les market makers professionnels utilisent des API par lots pour annuler et placer simultanément des dizaines d'ordres en une seule requête HTTP. Sur Binance, c'est batchOrders, sur Bybit, place-batch-order. Cela réduit la latence et la pression sur le rate limit.


8. Tableau comparatif des types d'ordres

Type d'ordre Garantie d'exécution Garantie de prix Visible dans le carnet Natif sur les exchanges Complexité d'implémentation
Market Oui Non Non (instantané) Oui Aucune
Limite Non Oui Oui Oui Aucune
Stop-market Oui (après déclenchement) Non Non Oui Aucune
Stop-limit Non Oui Non (jusqu'au déclenchement) Oui Aucune
Trailing stop Oui (après déclenchement) Non Non Partiel Faible
Iceberg Non Oui Partiel Partiel Moyenne
Post-only Non Oui Oui Oui Aucune
TWAP Non (selon les slices) Non Partiel Non Moyenne
VWAP Non Non Partiel Non Élevée
Limite avec poursuite Plus élevée que limite Partielle Oui (ordre actuel) Non Moyenne
Temporisé Selon le type Selon le type Non (jusqu'à l'instant T) Non Faible
Virtuel/Synthétique Plus faible que limite Selon le type Non Non Moyenne
OCO Oui (un des deux) Partielle Oui (les deux) Partiel Moyenne
Bracket Oui Partielle Oui Rare Élevée
Hidden Non Oui Non Rare Aucune
Pegged Non Dynamique Oui Très rare Élevée (si programmatique)

Conclusion : l'ordre comme brique de construction de la stratégie

Les types d'ordres ne sont pas juste des "boutons dans une interface". Ce sont des primitives fondamentales à partir desquelles se construit la couche d'exécution de tout système de trading. La différence entre "la stratégie est rentable en backtesting" et "la stratégie est rentable en production" réside souvent précisément ici : dans la manière exacte dont vous envoyez les ordres à l'exchange.

Quelques enseignements pratiques :

  1. Commencez par les ordres standard, assurez-vous de comprendre les nuances (stop-limit vs stop-market, IOC vs FOK). La plupart des erreurs se produisent ici.
  2. Les ordres virtuels sont incontournables pour les grid bots. Si vous placez plus de 50 ordres, ne les envoyez pas tous à l'exchange.
  3. La poursuite est nécessaire quand le taux d'exécution compte plus que le prix. Mais fixez toujours max_chase_distance, sinon vous pourriez dériver très loin.
  4. L'exécution temporisée est de niche mais puissante pour l'arbitrage de funding et les stratégies événementielles.
  5. Une couche de gestion des ordres sur mesure est inévitable pour tout système d'algotrading sérieux. Les types d'ordres natifs des exchanges ne suffisent pas.

Si vous construisez un système de trading et voulez aller plus loin, consultez nos articles sur la position dans la file du carnet d'ordres, les méthodes WebSocket dans CCXT et l'arbitrage de funding rate.


Références et sources

  • CCXT Library - une bibliothèque unifiée pour travailler avec les exchanges crypto, prenant en charge plus de 100 exchanges
  • Binance API Documentation - documentation des types d'ordres de Binance
  • Bybit API v5 - documentation de Bybit, y compris les ordres par lots
  • Moallemi, C. & Yuan, K. (2017). The Value of Queue Position in a Limit Order Book. Columbia Business School Research Paper
  • Cartea, A., Jaimungal, S., & Penalva, J. (2015). Algorithmic and High-Frequency Trading. Cambridge University Press
  • Avellaneda, M. & Stoikov, S. (2008) - High-frequency trading in a limit order book. Quantitative Finance
  • Erik Rigtorp — Order Queue Position Estimation - documents sur l'estimation de la position dans la file
  • Trading Technologies (TT) - une plateforme professionnelle avec des types d'ordres avancés
blog.disclaimer

Authors

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

Gardez une longueur d'avance sur le marché

Abonnez-vous à notre newsletter pour des insights exclusifs sur le trading IA, des analyses de marché et des mises à jour de la plateforme.

Nous respectons votre vie privée. Désabonnement possible à tout moment.