← Terug naar artikelen
March 23, 2026
5 min leestijd

Ordertypes in Algorithmic Trading: van Limit met Chasing tot Virtuele Orders

Ordertypes in Algorithmic Trading: van Limit met Chasing tot Virtuele Orders
#orders
#algotrading
#limit
#chasing
#virtual-orders
#grid-bot
#market-making
📖
Part 2 of 6 · Collection
Order Book & Market Microstructure

Wanneer een beginner een exchange-terminal opent, ziet hij twee knoppen: "Kopen" en "Verkopen". Wanneer een algo-trader zijn codebase opent, ziet hij zevenentwintig ordertypes, drie abstractieniveaus en een stapel edge cases die hem doen verlangen naar het dichtklappen van de laptop en het verkopen van komkommers op de boerenmarkt. Maar komkommers laten je helaas geen funding rate arbitrage om 3:59 UTC uitvoeren — dus laten we erin duiken.

In dit artikel doorlopen we de hele reis van basis exchange-orders tot synthetische virtuele constructies die alleen binnen jouw systeem bestaan en nooit in het orderboek verschijnen. Verwacht TypeScript, Python, wat pijn en een beetje verlichting.


1. Standaard Exchange-Orders: de Basis die Je Niet Kunt Overslaan

Standaardorders Classificatie van standaard ordertypes: van market tot iceberg

Voordat we iets complex bouwen, moeten we ervoor zorgen dat we de basisbouwstenen goed begrijpen. Het is verbazingwekkend hoeveel mensen stop-limit en stop-market door elkaar halen, en zich vervolgens afvragen waarom hun stop "niet triggerde" (spoiler: hij triggerde wel, maar de limit-order werd niet gevuld door slippage).

Market order

Het eenvoudigste en tegelijkertijd gevaarlijkste type. Je zegt tegen de exchange: "koop/verkoop nu, tegen elke beschikbare prijs." De exchange neemt liquiditeit uit het orderboek, beginnend bij de beste prijs. Als het volume op het beste niveau niet genoeg is — glijdt hij verder.

Wanneer gebruiken: noodmatige positie-uitgang, het uitvoeren van een signaal waarbij snelheid belangrijker is dan prijs.

Valkuilen: op een dunne markt kan een market order voor 100 BTC de prijs meerdere procenten bewegen. Backtests die market orders modelleren zonder rekening te houden met impact zijn pure fantasie.

Limit order

Je geeft een exacte prijs op. De order komt in het orderboek en wacht totdat iemand akkoord gaat met jouw prijs. Als de prijs van een limit-biedorder boven de huidige markt ligt — wordt deze onmiddellijk gevuld (zoals een market order, maar met een gegarandeerde maximumprijs).

Belangrijk punt: een limit order garandeert geen uitvoering. De prijs kan je niveau bereiken en omkeren, waardoor je in de wachtrij blijft zitten (meer hierover in ons artikel over wachtrijpositie).

Stop-market en Stop-limit

Hier begint de verwarring. Beide types zijn "slapende" orders die geactiveerd worden wanneer de trigger-prijs (stop price) wordt bereikt. Maar:

  • Stop-market: bij het triggeren wordt deze omgezet naar een market order. Garandeert uitvoering, maar niet de prijs.
  • Stop-limit: bij het triggeren wordt deze omgezet naar een limit order. Garandeert de prijs (niet slechter dan opgegeven), maar niet de uitvoering.

Op de volatiele cryptomarkt kan een stop-limit "missen" — de prijs blies door de stop heen, de limit order werd geplaatst, maar de markt is al voorbijgevlogen. Je blijft achter met een niet-gevulde limit order en een groeiend verlies. Precies daarom wordt stop-market vaker gebruikt voor stop-losses.

Trailing stop

Een stop die de prijs "volgt" op een ingestelde afstand. Prijs stijgt — de stop beweegt mee omhoog. Prijs daalt — de stop blijft op zijn plaats. Nuttig om winsten te beschermen in trendvolgende strategieën.

Exchange-ondersteuning: niet alle exchanges ondersteunen native trailing stops. Algo-traders implementeren deze vaak programmatisch — dit geeft meer controle over parameters (callback rate, activatieprijs, stapgrootte).

Iceberg order

Een order waarbij slechts een fractie van het totale volume zichtbaar is in het orderboek. Je wilt 1.000 BTC kopen, maar toont slechts 10 in het boek. Wanneer de eerste 10 gevuld zijn — verschijnen de volgende 10.

Waarom: om je werkelijke intenties voor de markt te verbergen. Een grote order in het boek signaleert aan iedereen dat "iemand groot wil kopen/verkopen." Als reactie beginnen HFT-algoritmes met front-running, en de prijs beweegt van je weg.

Kanttekening: op veel crypto-exchanges worden iceberg-orders óf niet ondersteund óf gemakkelijk gedetecteerd door het patroon van identieke volumes. Geavanceerde algoritmes randomiseren de grootte van het zichtbare deel.

Time-in-force parameters: GTC, GTD, IOC, FOK

Dit zijn geen aparte ordertypes maar time-in-force parameters — hoe lang een order leeft:

Parameter Volledige Naam Gedrag
GTC Good Till Cancelled Leeft totdat geannuleerd. De standaard default
GTD Good Till Date Leeft tot een opgegeven datum/tijd
IOC Immediate or Cancel Voert onmiddellijk uit (volledig of gedeeltelijk), rest wordt geannuleerd
FOK Fill or Kill Voert alleen volledig en onmiddellijk uit. Indien onmogelijk — volledig geannuleerd

IOC vs FOK: het verschil is cruciaal. IOC kan gedeeltelijk gevuld worden — je wilde 100 BTC kopen, kocht 3, de rest werd geannuleerd. FOK is alles of niets.

Post-only (Alleen Maker)

Een order die gegarandeerd als maker in het orderboek terechtkomt en nooit als taker wordt uitgevoerd. Als op het moment van plaatsen de prijs onmiddellijke uitvoering zou veroorzaken — wijst de exchange deze af (of past de prijs aan, afhankelijk van de exchange).

Waarom: maker-kosten zijn meestal lager dan taker-kosten (op Binance — 0,02% vs 0,04% voor VIP-niveaus). Voor een market maker die duizenden orders per dag plaatst, is het kostenverschil het verschil tussen winst en verlies.


2. TWAP en VWAP: Hoe Instituties een Olifant in het Orderboek Verbergen

Wanneer een hedgefonds een positie van $50 miljoen wil kopen, plaatst het geen enkele market order. Het gebruikt uitvoeringsalgoritmes — algoritmes die een grote order in veel kleinere orders opsplitsen en deze in de loop van de tijd uitvoeren, om de marktimpact te minimaliseren.

TWAP (Time-Weighted Average Price)

Het idee is doodeenvoudig: splits het totale volume in gelijke delen en voer uit op gelijke tijdsintervallen.

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 is slimmer: het houdt rekening met het typische handelsvolumeprofiel. Als 30% van het dagvolume doorgaans tussen 9:00 en 10:00 wordt verhandeld, zal VWAP 30% van de order uitvoeren tijdens dat venster. Het doel is om de gemiddelde uitvoeringsprijs zo dicht mogelijk bij de markt-VWAP te krijgen.

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)

Verschil TWAP vs VWAP: TWAP is eenvoudiger en voorspelbaarder. VWAP levert een betere gemiddelde prijs maar vereist een betrouwbaar volumeprofiel. Op de cryptomarkt, waar volumes wash-traded kunnen worden, moet het VWAP-profiel zorgvuldig worden opgebouwd.


3. Limit met Chasing: Wanneer Jouw Order de Prijs Kan Achtervolgen

Chasing limit orders Chasing limit: de order achtervolgt een bewegende prijs met configureerbare agressie

Nu wordt het echt interessant. Een standaard limit order is een passieve entiteit: hij zit in het orderboek en wacht. Als de prijs beweegt — blijft de order ongevuld. Voor een algo-trader is dit vaak onacceptabel: het instapsignaal ging af, maar de positie werd niet opgebouwd omdat de markt 0,1% bewoog.

Chasing limit order is een programmatische wrapper rond een limit order die:

  1. Een limit order plaatst tegen de huidige beste prijs (of met een kleine offset)
  2. De prijs monitort via WebSocket
  3. Als de prijs zich van de order verwijdert — annuleert en herplaatst dichter bij de huidige prijs
  4. Herhaalt totdat de order gevuld is of de toegestane afwijking overschrijdt

Belangrijkste Parameters

  • chase_interval_ms — hoe vaak de order te controleren en te herplaatsen. 100ms — agressief, 1000ms — ontspannen.
  • max_chase_distance — maximale afwijking van de initiële prijs voordat de order wordt geannuleerd. Bescherming tegen het achtervolgen van een weglopende markt.
  • aggression_level — hoe dicht bij de marktprijs de limit order te plaatsen. 0 — op de beste bid/ask (passief), 1 — de spread overschrijden (agressief, effectief een taker).
  • chase_on_partial — of het achtervolgen moet doorgaan als de order gedeeltelijk gevuld is.

TypeScript-implementatie

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));
  }
}

Wanneer Chasing Schadelijk Is

Chasing is een krachtig hulpmiddel, maar het is gemakkelijk om het te veranderen in een verliesgenerator:

  1. Cancel/replace spam. Elke annulering en herplaatsing is een belasting voor de API. Exchanges beperken verzoeken (rate limit), en agressieve chasing kan ervoor zorgen dat je API-sleutel wordt verbannen.
  2. Adverse selectie. Als de prijs van je wegloopt — weet de markt misschien iets dat jij niet weet. De prijs achtervolgen in deze situatie betekent kopen op de top.
  3. Overgang van Maker naar Taker. Bij hoge agressie betaal je effectief taker-kosten, maar met vertraging (cancel + nieuwe order). Soms is het eenvoudiger om gewoon een market order te plaatsen.

4. Tijdgebaseerde Orders: Milliseconde-precisie

Er zijn situaties waarin je een order niet "tegen prijs X" maar "op tijdstip T" moet uitvoeren. Klinkt vreemd? Het is eigenlijk een hele klasse van strategieën.

Use cases

Funding rate arbitrage. Bij perpetual futures wordt funding elke 8 uur betaald (00:00, 08:00, 16:00 UTC op Binance). Als de funding rate = +0,1%, moet je short zijn op het moment van afwikkeling. Strategie: open een short een paar seconden voor afwikkeling, incasseer de funding, sluit de positie. Timing is cruciaal — een seconde vertraging betekent gemiste funding.

Sessieopeningen/-sluitingen. Op traditionele markten en sommige crypto-derivaten bestaan vaste sessies. De openingsveiling (NYSE, CME) is het moment waarop de liquiditeit op zijn hoogst is. Een order 100ms voor de veiling plaatsen is een voordeel.

Nieuwsgebaseerde uitvoering. Inflatiecijfers worden op een gepland tijdstip vrijgegeven. Het algoritme parseert het getal uit een nieuwsfeed en plaatst een order binnen 50ms. Hier wordt tijdgebaseerde uitvoering gecombineerd met event-driven logica.

Implementatie

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);

Belangrijke kanttekening: de precisie van een tijdgebaseerde order wordt niet beperkt door jouw code, maar door de netwerklatentie naar de exchange. Als jouw ping naar de API 50ms is, zal zelfs een perfecte busy-wait een delta van 50ms hebben. Voor serieuze HFT wordt co-locatie gebruikt — de server staat fysiek naast de matching engine van de exchange.


5. Virtuele/Synthetische Orders: de Onzichtbaren in Jouw Systeem

Virtuele orders voor grid bots Virtuele orders: orders bestaan alleen in het geheugen van de bot totdat de trigger afgaat

Dit is misschien wel het meest onderschatte hulpmiddel in het arsenaal van de algo-trader. Een virtuele order (ook bekend als synthetische order) is een order die alleen in jouw systeem bestaat. Hij wordt niet naar de exchange gestuurd totdat aan een triggervoorwaarde is voldaan (doorgaans — het bereiken van een bepaald prijsniveau).

Hoe Het Werkt

  1. Jouw algoritme beslist: "Ik wil BTC kopen tegen $40.000"
  2. In plaats van een limit order naar de exchange te sturen, maakt het een virtuele order aan in het geheugen
  3. Het abonneert zich op de WebSocket-prijsstream
  4. Wanneer bid/ask $40.000 bereikt — stuurt het een echte market- of limit-order naar de exchange

Waarom Virtuele Orders Belangrijk Zijn

Geen informatielekkage. Jouw order is onzichtbaar in het orderboek. Niemand — geen andere traders, geen HFT-algoritmes, zelfs de exchange zelf niet — weet van jouw intenties tot het moment van uitvoering. Dit verschuift fundamenteel de machtsbalans.

Bescherming tegen front-running. Op crypto-exchanges, vooral minder transparante, bestaat een redelijk vermoeden dat informatie over grote limit-orders gebruikt kan worden voor front-running (er zijn zelfs studies hierover). Virtuele orders elimineren dit risico.

Grid bots. Een klassieke grid bot plaatst een raster van 50-200 orders op verschillende prijsniveaus. Als je ze allemaal naar de exchange stuurt — zijn dat 200 orders in het boek die: (a) voor iedereen zichtbaar zijn, (b) de orderlimiet op de exchange opgebruiken (doorgaans 200-300 open orders per account), (c) als de prijs sterk beweegt, worden ze allemaal gevuld en eindig je met een enorme positie. Virtuele orders lossen alle drie de problemen op.

Vallende messen opvangen. Strategie: plaats virtuele koop-orders op niveaus -5%, -10%, -15% onder de huidige prijs. Als de markt daalt — triggeren orders geleidelijk. Als hij niet daalt — riskeer je niets en gebruik je geen exchange-orderslots.

TypeScript-implementatie

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
    );
  }
}

Valkuilen van Virtuele Orders

  1. Latentiegat. Tussen het moment dat je de prijs ziet en het moment dat de echte order de exchange bereikt, verstrijkt tijd. Op een volatiele markt kan de prijs in die 20-100ms wegvliegen. Oplossing: stuur een licht agressieve limit order (met een buffer).

  2. Gemiste fills. Als de prijs je niveau "doorboorde" in één tick (flash crash) en terugveerde — reageer je mogelijk niet op tijd. Een gewone limit order die in het boek zit, zou gevuld zijn; een virtuele — niet.

  3. Statusbeheer. Virtuele orders leven in het geheugen. Als het proces crasht — gaan orders verloren. Oplossing: persistente opslag (Redis, SQLite, bestand) met herstel bij herstart.


6. Conditionele/Slimme Orders: Ordercombinatoriek

Wanneer een enkele order niet voldoende is, combineren traders ze in conditionele constructies. Sommige worden native ondersteund op exchanges, andere worden programmatisch geïmplementeerd.

OCO (One Cancels Other)

Twee orders zijn gekoppeld: als de ene wordt uitgevoerd — wordt de andere automatisch geannuleerd. Klassiek voorbeeld: je hebt een longpositie en wilt zowel een take-profit als een stop-loss instellen. Welke er ook eerst triggert — de andere moet geannuleerd worden.

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)

Bracket order

Een constructie met drie componenten: een primaire instaporder + OCO voor de uitstap (take-profit + stop-loss). In wezen een compleet trade-levenscyclus in één enkele aanroep:

  1. Instap: limit koop-order
  2. Take-profit: limit verkoop-order (boven)
  3. Stop-loss: stop-market verkoop-order (onder)

Wanneer de instap wordt gevuld, worden TP en SL automatisch geplaatst. Wanneer een van beide wordt gevuld — wordt de andere geannuleerd.

If-Then Logica

De flexibelste optie — orderketens met willekeurige voorwaarden:


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},
            },
        ]
    }
]

Dergelijke constructies worden door geen enkele exchange native ondersteund — alleen programmatische implementatie. Dit is een van de redenen waarom algotrading-systemen onvermijdelijk hun eigen order-managementlaag ontwikkelen.


7. Hoe Market Makers Gespecialiseerde Ordertypes Gebruiken

Market making is een eigen universum, en het gereedschap voor orders past daarbij. Het werk van een market maker is om continu bid en ask te quoten, geld verdienen aan de spread, terwijl adverse selectie wordt geminimaliseerd (de situatie waarin een geïnformeerde trader tegen je handelt).

Post-only als Must-Have

Voor een market maker is post-only geen optie — het is een vereiste. Als jouw order per ongeluk wordt uitgevoerd als taker — in plaats van een maker-rebate te ontvangen, betaal je een taker-fee. Over duizenden orders per dag is dat catastrofaal.

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

Hidden orders

Op sommige exchanges (Kraken, Bitfinex) zijn verborgen orders beschikbaar — ze verschijnen niet in het orderboek maar staan wel op de exchange en nemen deel aan matching. De afweging: je betaalt taker-kosten zelfs als maker, maar je krijgt anonimiteit.

Voor een market maker is dit een hulpmiddel voor voorraadbeheer: als een grote positie is opgebouwd, kun je een verborgen order plaatsen om deze af te bouwen zonder jouw intentie aan de markt te onthullen.

Pegged orders

Een order die vastgezet is aan de beste bid/ask. Op Coinbase Advanced Trade, bijvoorbeeld, kun je een order plaatsen die automatisch de beste bid volgt en altijd vooraan in de wachtrij staat. Dit is een native chasing-order op exchange-niveau — maar verre van universeel beschikbaar.

Bulk order management

Professionele market makers gebruiken batch-API's om tientallen orders tegelijkertijd in één HTTP-verzoek te annuleren en te plaatsen. Op Binance is dit batchOrders, op Bybitplace-batch-order. Dit vermindert latentie en rate-limit-druk.


8. Vergelijkingstabel Ordertypes

Ordertype Uitvoeringsgarantie Prijsgarantie Zichtbaar in Boek Native op Exchanges Implementatiecomplexiteit
Market Ja Nee Nee (direct) Ja Geen
Limit Nee Ja Ja Ja Geen
Stop-market Ja (na trigger) Nee Nee Ja Geen
Stop-limit Nee Ja Nee (tot trigger) Ja Geen
Trailing stop Ja (na trigger) Nee Nee Gedeeltelijk Laag
Iceberg Nee Ja Gedeeltelijk Gedeeltelijk Middel
Post-only Nee Ja Ja Ja Geen
TWAP Nee (afhankelijk van slices) Nee Gedeeltelijk Nee Middel
VWAP Nee Nee Gedeeltelijk Nee Hoog
Chasing limit Hoger dan limit Gedeeltelijk Ja (huidige order) Nee Middel
Tijdgebaseerd Afhankelijk van type Afhankelijk van type Nee (tot tijd T) Nee Laag
Virtueel/Synthetisch Lager dan limit Afhankelijk van type Nee Nee Middel
OCO Ja (een van twee) Gedeeltelijk Ja (beide) Gedeeltelijk Middel
Bracket Ja Gedeeltelijk Ja Zelden Hoog
Hidden Nee Ja Nee Zelden Geen
Pegged Nee Dynamisch Ja Zeer zelden Hoog (indien programmatisch)

Conclusie: De Order als Bouwsteen van Strategie

Ordertypes zijn niet zomaar "knoppen in een interface." Het zijn fundamentele primitieven waaruit de uitvoeringslaag van elk handelssysteem wordt opgebouwd. Het verschil tussen "strategie is winstgevend in backtesting" en "strategie is winstgevend in productie" ligt vaak precies hier — in hoe je orders exact naar de exchange stuurt.

Een paar praktische inzichten:

  1. Begin met standaardorders, zorg dat je de nuances begrijpt (stop-limit vs stop-market, IOC vs FOK). De meeste fouten gebeuren hier.
  2. Virtuele orders zijn een must-have voor grid bots. Als je meer dan 50 orders plaatst — stuur ze niet allemaal naar de exchange.
  3. Chasing is nodig wanneer fill rate belangrijker is dan prijs. Maar stel altijd max_chase_distance in — anders kun je ver afdrijven.
  4. Tijdgebaseerde uitvoering is niche maar krachtig voor funding arb en event-driven strategieën.
  5. Een custom order-managementlaag is onvermijdelijk voor elk serieus algotrading-systeem. Native exchange-ordertypes zijn niet genoeg.

Als je een handelssysteem bouwt en dieper wilt gaan — bekijk onze artikelen over wachtrijpositie in het orderboek, WebSocket-methodes in CCXT, en funding rate arbitrage.


Referenties en Bronnen

Disclaimer: De informatie in dit artikel is uitsluitend bedoeld voor educatieve en informatieve doeleinden en vormt geen financieel, beleggings- of handelsadvies. Het handelen in cryptovaluta brengt een aanzienlijk risico op verlies met zich mee.

Auteurs

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

Blijf de markt voor

Abonneer je op onze nieuwsbrief voor exclusieve AI-handelsinzichten, marktanalyses en platformupdates.

We respecteren je privacy. Je kunt je op elk moment afmelden.