← Zurück zu den Artikeln
March 23, 2026
5 min read

Ordertypen im algorithmischen Handel: Von der nachziehenden Limit-Order bis zu virtuellen Orders

Ordertypen im algorithmischen Handel: Von der nachziehenden Limit-Order bis zu virtuellen Orders
#orders
#algotrading
#limit
#chasing
#virtual-orders
#grid-bot
#market-making
📖
Part 2 of 6 · Collection
Order Book & Market Microstructure

Wenn ein Anfänger ein Börsenterminal öffnet, sieht er zwei Schaltflächen: "Kaufen" und "Verkaufen". Wenn ein Algo-Trader seine Codebasis öffnet, sieht er siebenundzwanzig Ordertypen, drei Abstraktionsebenen und einen Haufen Sonderfälle, die ihn dazu bringen, den Laptop zuzuklappen und lieber Gurken auf dem Bauernmarkt zu verkaufen. Aber Gurken lassen dich leider keine Funding-Rate-Arbitrage um 3:59 UTC fahren - also legen wir los.

In diesem Artikel gehen wir den gesamten Weg von einfachen Börsenorders bis zu synthetischen virtuellen Konstruktionen durch, die nur innerhalb deines Systems existieren und niemals im Orderbuch auftauchen. Erwarte TypeScript, Python, ein wenig Schmerz und ein bisschen Erleuchtung.


1. Standard-Börsenorders: Das Fundament, das du nicht überspringen kannst

Standard-Orders Klassifizierung der Standard-Ordertypen: von Market bis Iceberg

Bevor wir etwas Komplexes bauen, müssen wir sicherstellen, dass wir die grundlegenden Bausteine richtig verstehen. Es ist erstaunlich, wie viele Menschen Stop-Limit und Stop-Market verwechseln und sich dann wundern, warum ihr Stop "nicht ausgelöst hat" (Spoiler: er hat ausgelöst, aber die Limit-Order wurde wegen Slippage nicht ausgeführt).

Market-Order

Der einfachste und zugleich gefährlichste Typ. Du sagst der Börse: "Kaufe/verkaufe genau jetzt, zu jedem verfügbaren Preis." Die Börse entnimmt dem Orderbuch Liquidität, beginnend beim besten Preis. Reicht das Volumen auf der besten Ebene nicht aus - rutscht sie weiter.

Wann einsetzen: Not-Ausstieg aus einer Position, Ausführung eines Signals, bei dem Geschwindigkeit wichtiger ist als der Preis.

Fallstricke: In einem dünnen Markt kann eine Market-Order über 100 BTC den Preis um mehrere Prozent bewegen. Backtests, die Market-Orders ohne Berücksichtigung des Market Impact modellieren, sind reine Fantasie.

Limit-Order

Du gibst einen exakten Preis an. Die Order gelangt ins Orderbuch und wartet, bis jemand deinem Preis zustimmt. Liegt der Preis einer Limit-Kauforder über dem aktuellen Markt - wird sie sofort ausgeführt (wie eine Market-Order, aber mit garantiertem Höchstpreis).

Kernpunkt: Eine Limit-Order garantiert keine Ausführung. Der Preis kann dein Niveau erreichen und umkehren, sodass du in der Warteschlange sitzen bleibst (mehr dazu in unserem Artikel über die Warteschlangenposition).

Stop-Market und Stop-Limit

Hier beginnt die Verwirrung. Beide Typen sind "schlafende" Orders, die aktiviert werden, wenn der Auslösepreis (Stop-Preis) erreicht wird. Aber:

  • Stop-Market: wandelt sich beim Auslösen in eine Market-Order um. Garantiert Ausführung, aber nicht den Preis.
  • Stop-Limit: wandelt sich beim Auslösen in eine Limit-Order um. Garantiert den Preis (nicht schlechter als angegeben), aber nicht die Ausführung.

Im volatilen Krypto-Markt kann eine Stop-Limit "verfehlen" - der Preis durchbricht den Stop, die Limit-Order wird platziert, aber der Markt ist bereits vorbeigeflogen. Du bleibst mit einer nicht ausgeführten Limit-Order und einem wachsenden Verlust zurück. Genau deshalb wird für Stop-Losses häufiger Stop-Market verwendet.

Trailing-Stop

Ein Stop, der dem Preis in festgelegtem Abstand "folgt". Preis steigt - der Stop wandert nach oben. Preis fällt - der Stop bleibt stehen. Nützlich, um Gewinne in trendfolgenden Strategien zu schützen.

Börsenunterstützung: Nicht alle Börsen unterstützen native Trailing-Stops. Algo-Trader implementieren sie oft programmatisch - das gibt mehr Kontrolle über die Parameter (Callback-Rate, Aktivierungspreis, Schrittweite).

Iceberg-Order

Eine Order, bei der nur ein Bruchteil des Gesamtvolumens im Orderbuch sichtbar ist. Du willst 1.000 BTC kaufen, zeigst aber nur 10 im Buch. Wenn die ersten 10 ausgeführt sind - erscheinen die nächsten 10.

Warum: um deine wahren Absichten vor dem Markt zu verbergen. Eine große Order im Buch signalisiert allen, dass "jemand Großes kaufen/verkaufen will". Als Reaktion beginnen HFT-Algorithmen mit Front-Running, und der Preis entfernt sich von dir.

Vorbehalt: Auf vielen Krypto-Börsen werden Iceberg-Orders entweder nicht unterstützt oder anhand des Musters identischer Volumina leicht erkannt. Fortgeschrittene Algorithmen randomisieren die Größe des sichtbaren Teils.

Time-in-Force-Parameter: GTC, GTD, IOC, FOK

Das sind keine eigenen Ordertypen, sondern Time-in-Force-Parameter - wie lange eine Order lebt:

Parameter Vollständiger Name Verhalten
GTC Good Till Cancelled Lebt bis zur Stornierung. Der Standard-Default
GTD Good Till Date Lebt bis zu einem festgelegten Datum/Zeitpunkt
IOC Immediate or Cancel Wird sofort ausgeführt (ganz oder teilweise), der Rest wird storniert
FOK Fill or Kill Wird nur vollständig und sofort ausgeführt. Ist das unmöglich - wird sie komplett storniert

IOC vs FOK: Der Unterschied ist entscheidend. IOC kann teilweise ausgeführt werden - du wolltest 100 BTC kaufen, hast 3 gekauft, der Rest wurde storniert. FOK ist entweder 100 oder nichts.

Post-only (Maker-only)

Eine Order, die garantiert als Maker ins Orderbuch gelangt und niemals als Taker ausgeführt wird. Würde der Preis zum Zeitpunkt der Platzierung eine sofortige Ausführung verursachen - lehnt die Börse sie ab (oder passt den Preis an, je nach Börse).

Warum: Maker-Gebühren sind in der Regel niedriger als Taker-Gebühren (bei Binance - 0,02 % vs 0,04 % für VIP-Stufen). Für einen Market Maker, der täglich Tausende Orders platziert, ist der Gebührenunterschied der Unterschied zwischen Gewinn und Verlust.


2. TWAP und VWAP: Wie Institutionen einen Elefanten im Orderbuch verstecken

Wenn ein Hedgefonds eine Position von 50 Mio. $ kaufen will, platziert er keine einzelne Market-Order. Er nutzt Ausführungsalgorithmen - Algorithmen, die eine große Order in viele kleinere aufteilen und über die Zeit ausführen, um den Market Impact zu minimieren.

TWAP (Time-Weighted Average Price)

Die Idee ist denkbar einfach: das Gesamtvolumen in gleiche Teile aufteilen und in gleichen Zeitintervallen ausführen.

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 ist intelligenter: Es berücksichtigt das typische Handelsvolumenprofil. Wenn typischerweise 30 % des Tagesvolumens zwischen 9:00 und 10:00 gehandelt werden, führt VWAP 30 % der Order in diesem Fenster aus. Ziel ist es, den durchschnittlichen Ausführungspreis so nah wie möglich an den Markt-VWAP zu bringen.

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)

Unterschied TWAP vs VWAP: TWAP ist einfacher und vorhersehbarer. VWAP liefert einen besseren Durchschnittspreis, erfordert aber ein zuverlässiges Volumenprofil. Auf dem Krypto-Markt, wo Volumina durch Wash-Trading verfälscht sein können, muss das VWAP-Profil sorgfältig aufgebaut werden.


3. Limit mit Nachziehen: Wenn deine Order dem Preis hinterherjagt

Nachziehende Limit-Orders Nachziehende Limit-Order: Die Order jagt einem sich bewegenden Preis mit konfigurierbarer Aggressivität hinterher

Jetzt wird es richtig interessant. Eine Standard-Limit-Order ist ein passives Objekt: Sie liegt im Orderbuch und wartet. Bewegte sich der Preis - bleibt die Order unausgeführt. Für einen Algo-Trader ist das oft inakzeptabel: Das Einstiegssignal feuerte, aber die Position wurde nicht aufgebaut, weil sich der Markt um 0,1 % bewegte.

Eine nachziehende Limit-Order ist ein programmatischer Wrapper um eine Limit-Order, der:

  1. Eine Limit-Order zum aktuellen besten Preis platziert (oder mit einem kleinen Versatz)
  2. Den Preis über WebSocket überwacht
  3. Wenn sich der Preis von der Order entfernt - sie storniert und näher am aktuellen Preis neu platziert
  4. Dies wiederholt, bis die Order ausgeführt ist oder die zulässige Abweichung überschritten wird

Wichtige Parameter

  • chase_interval_ms - wie oft die Order geprüft und neu platziert wird. 100 ms - aggressiv, 1000 ms - entspannt.
  • max_chase_distance - maximale Abweichung vom Ausgangspreis, bevor die Order storniert wird. Schutz vor dem Nachjagen eines davonlaufenden Marktes.
  • aggression_level - wie nah am Marktpreis die Limit-Order platziert wird. 0 - beim besten Bid/Ask (passiv), 1 - Überkreuzen des Spreads (aggressiv, faktisch ein Taker).
  • chase_on_partial - ob das Nachziehen fortgesetzt wird, wenn die Order teilweise ausgeführt ist.

TypeScript-Implementierung

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

Wann Nachziehen schädlich ist

Nachziehen ist ein mächtiges Werkzeug, das sich aber leicht in einen Verlustgenerator verwandeln lässt:

  1. Cancel/Replace-Spam. Jedes Stornieren und Neuplatzieren belastet die API. Börsen begrenzen Anfragen per Rate-Limit, und aggressives Nachziehen kann deinen API-Schlüssel sperren lassen.
  2. Adverse Selection. Wenn der Preis vor dir davonläuft - weiß der Markt vielleicht etwas, das du nicht weißt. Dem Preis in dieser Situation hinterherzujagen bedeutet, am Höchststand zu kaufen.
  3. Übergang von Maker zu Taker. Bei hoher Aggressivität zahlst du faktisch Taker-Gebühren, aber mit Verzögerung (Cancel + neue Order). Manchmal ist es einfacher, gleich eine Market-Order zu platzieren.

4. Zeitbasierte Orders: Präzision im Millisekundenbereich

Es gibt Situationen, in denen du eine Order nicht "zum Preis X", sondern "zur Zeit T" ausführen musst. Klingt seltsam? Es ist tatsächlich eine ganze Klasse von Strategien.

Anwendungsfälle

Funding-Rate-Arbitrage. Bei Perpetual-Futures wird das Funding alle 8 Stunden gezahlt (00:00, 08:00, 16:00 UTC bei Binance). Beträgt die Funding-Rate = +0,1 %, musst du im Moment der Abrechnung short sein. Strategie: einige Sekunden vor der Abrechnung einen Short eröffnen, das Funding kassieren, die Position schließen. Das Timing ist entscheidend - eine Sekunde Verzögerung bedeutet verpasstes Funding.

Session-Eröffnungen/-Schließungen. Auf traditionellen Märkten und einigen Krypto-Derivaten gibt es feste Sessions. Die Eröffnungsauktion (NYSE, CME) ist der Moment, in dem die Liquidität ihren Höhepunkt erreicht. Eine Order 100 ms vor der Auktion zu platzieren, ist ein Edge.

Nachrichtenbasierte Ausführung. Inflationsdaten werden zu einem festgelegten Zeitpunkt veröffentlicht. Der Algorithmus parst die Zahl aus einem News-Feed und platziert innerhalb von 50 ms eine Order. Hier wird zeitbasierte Ausführung mit ereignisgesteuerter Logik kombiniert.

Implementierung

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

Wichtiger Vorbehalt: Die Präzision einer zeitbasierten Order wird nicht durch deinen Code begrenzt, sondern durch die Netzwerklatenz zur Börse. Beträgt dein Ping zur API 50 ms, wird selbst ein perfektes Busy-Wait ein Delta von 50 ms haben. Für ernsthaftes HFT wird Co-Location verwendet - der Server steht physisch direkt neben der Matching-Engine der Börse.


5. Virtuelle/synthetische Orders: Die Unsichtbaren in deinem System

Virtuelle Orders für Grid-Bots Virtuelle Orders: Orders existieren nur im Speicher des Bots, bis der Trigger auslöst

Dies ist vielleicht das am meisten unterschätzte Werkzeug im Arsenal des Algo-Traders. Eine virtuelle Order (auch synthetische Order genannt) ist eine Order, die nur in deinem System existiert. Sie wird nicht an die Börse gesendet, bis eine Auslösebedingung erfüllt ist (typischerweise - der Preis erreicht ein bestimmtes Niveau).

Wie es funktioniert

  1. Dein Algorithmus entscheidet: "Ich will BTC bei 40.000 $ kaufen"
  2. Statt eine Limit-Order an die Börse zu senden, erstellt er eine virtuelle Order im Speicher
  3. Abonniert den WebSocket-Preisstrom
  4. Wenn Bid/Ask 40.000 $ erreicht - sendet er eine echte Market- oder Limit-Order an die Börse

Warum virtuelle Orders wichtig sind

Kein Informationsleck. Deine Order ist im Orderbuch unsichtbar. Niemand - weder andere Trader noch HFT-Algorithmen noch die Börse selbst - kennt deine Absichten bis zum Moment der Ausführung. Das verschiebt das Kräfteverhältnis grundlegend.

Front-Running-Schutz. Auf Krypto-Börsen, besonders auf weniger transparenten, besteht begründeter Verdacht, dass Informationen über große Limit-Orders für Front-Running genutzt werden können (dazu gibt es sogar Studien). Virtuelle Orders eliminieren dieses Risiko.

Grid-Bots. Ein klassischer Grid-Bot platziert ein Gitter aus 50-200 Orders auf verschiedenen Preisniveaus. Wenn du sie alle an die Börse sendest - sind das 200 Orders im Buch, die: (a) für alle sichtbar sind, (b) das Order-Limit der Börse aufbrauchen (typischerweise 200-300 offene Orders pro Konto), (c) wenn sich der Preis stark bewegt, alle ausgeführt werden und du mit einer riesigen Position endest. Virtuelle Orders lösen alle drei Probleme.

Fallende Messer fangen. Strategie: virtuelle Kauforders auf den Niveaus -5 %, -10 %, -15 % unter dem aktuellen Preis platzieren. Fällt der Markt - werden die Orders schrittweise ausgelöst. Fällt er nicht - riskierst du nichts und belegst keine Order-Slots der Börse.

TypeScript-Implementierung

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

Fallstricke virtueller Orders

  1. Latenzlücke. Zwischen dem Moment, in dem du den Preis siehst, und dem Moment, in dem die echte Order die Börse erreicht, vergeht Zeit. In einem volatilen Markt kann der Preis in diesen 20-100 ms davonfliegen. Lösung: eine leicht aggressive Limit-Order senden (mit Puffer).

  2. Verpasste Ausführungen. Wenn der Preis dein Niveau in einem einzigen Tick "durchstach" (Flash Crash) und zurückprallte - reagierst du möglicherweise nicht rechtzeitig. Eine reguläre Limit-Order im Buch wäre ausgeführt worden; eine virtuelle - nicht.

  3. State-Management. Virtuelle Orders leben im Speicher. Stürzt der Prozess ab - sind die Orders verloren. Lösung: persistenter Speicher (Redis, SQLite, Datei) mit Wiederherstellung beim Neustart.


6. Bedingte/Smart-Orders: Order-Kombinatorik

Wenn eine einzelne Order nicht ausreicht, kombinieren Trader sie zu bedingten Konstruktionen. Einige werden nativ an Börsen unterstützt, andere programmatisch implementiert.

OCO (One Cancels Other)

Zwei Orders sind verknüpft: Wenn eine ausgeführt wird - wird die andere automatisch storniert. Klassisches Beispiel: Du bist in einer Long-Position und willst sowohl einen Take-Profit als auch einen Stop-Loss setzen. Was zuerst auslöst - das andere muss storniert werden.

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

Eine dreiteilige Konstruktion: eine primäre Einstiegsorder + OCO für den Ausstieg (Take-Profit + Stop-Loss). Im Grunde ein vollständiger Trade-Lebenszyklus in einem einzigen Aufruf:

  1. Einstieg: Limit-Kauforder
  2. Take-Profit: Limit-Verkaufsorder (darüber)
  3. Stop-Loss: Stop-Market-Verkaufsorder (darunter)

Wenn der Einstieg ausgeführt wird, werden TP und SL automatisch platziert. Wenn eine der beiden ausgeführt wird - wird die andere storniert.

If-Then-Logik

Die flexibelste Option - Order-Ketten mit beliebigen Bedingungen:


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

Solche Konstruktionen werden von keiner Börse nativ unterstützt - nur programmatische Implementierung. Das ist einer der Gründe, warum Algotrading-Systeme unweigerlich ihre eigene Order-Management-Schicht entwickeln.


7. Wie Market Maker spezialisierte Ordertypen nutzen

Market Making ist ein eigenes Universum, und das Order-Toolkit passt dazu. Die Aufgabe eines Market Makers ist es, kontinuierlich Bid und Ask zu quotieren, am Spread zu verdienen und dabei Adverse Selection zu minimieren (die Situation, in der ein informierter Trader gegen dich handelt).

Post-only als Must-Have

Für einen Market Maker ist Post-only keine Option - es ist eine Anforderung. Wenn deine Order versehentlich als Taker ausgeführt wird - zahlst du statt eines Maker-Rebates eine Taker-Gebühr. Über Tausende Orders pro Tag ist das katastrophal.

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

Auf einigen Börsen (Kraken, Bitfinex) sind Hidden-Orders verfügbar - sie erscheinen nicht im Orderbuch, sind aber an der Börse und nehmen am Matching teil. Der Kompromiss: Du zahlst Taker-Gebühren auch als Maker, gewinnst aber Anonymität.

Für einen Market Maker ist das ein Werkzeug für das Inventory-Management: Hat sich eine große Position angesammelt, kannst du eine Hidden-Order platzieren, um sie aufzulösen, ohne deine Absicht dem Markt preiszugeben.

Pegged-Orders

Eine an das beste Bid/Ask gekoppelte Order. Bei Coinbase Advanced Trade kannst du beispielsweise eine Order platzieren, die automatisch dem besten Bid folgt und immer an der Spitze der Warteschlange sitzt. Das ist eine native nachziehende Order auf Börsenebene - aber sie ist bei weitem nicht universell verfügbar.

Bulk-Order-Management

Professionelle Market Maker nutzen Batch-APIs, um Dutzende Orders in einer einzigen HTTP-Anfrage gleichzeitig zu stornieren und zu platzieren. Bei Binance ist das batchOrders, bei Bybit - place-batch-order. Das reduziert Latenz und Rate-Limit-Druck.


8. Vergleichstabelle der Ordertypen

Ordertyp Ausführungsgarantie Preisgarantie Sichtbar im Buch Nativ an Börsen Implementierungsaufwand
Market Ja Nein Nein (sofort) Ja Keiner
Limit Nein Ja Ja Ja Keiner
Stop-Market Ja (nach Auslösung) Nein Nein Ja Keiner
Stop-Limit Nein Ja Nein (bis zur Auslösung) Ja Keiner
Trailing-Stop Ja (nach Auslösung) Nein Nein Teilweise Gering
Iceberg Nein Ja Teilweise Teilweise Mittel
Post-only Nein Ja Ja Ja Keiner
TWAP Nein (abhängig von Slices) Nein Teilweise Nein Mittel
VWAP Nein Nein Teilweise Nein Hoch
Nachziehende Limit Höher als Limit Teilweise Ja (aktuelle Order) Nein Mittel
Zeitbasiert Abhängig vom Typ Abhängig vom Typ Nein (bis Zeit T) Nein Gering
Virtuell/Synthetisch Niedriger als Limit Abhängig vom Typ Nein Nein Mittel
OCO Ja (eine von zwei) Teilweise Ja (beide) Teilweise Mittel
Bracket Ja Teilweise Ja Selten Hoch
Hidden Nein Ja Nein Selten Keiner
Pegged Nein Dynamisch Ja Sehr selten Hoch (falls programmatisch)

Fazit: Die Order als Baustein der Strategie

Ordertypen sind nicht nur "Schaltflächen in einer Oberfläche". Sie sind fundamentale Primitive, aus denen die Ausführungsschicht jedes Handelssystems aufgebaut wird. Der Unterschied zwischen "Strategie ist im Backtesting profitabel" und "Strategie ist in Produktion profitabel" liegt oft genau hier - darin, wie genau du Orders an die Börse sendest.

Ein paar praktische Erkenntnisse:

  1. Beginne mit Standard-Orders, stelle sicher, dass du die Feinheiten verstehst (Stop-Limit vs Stop-Market, IOC vs FOK). Die meisten Fehler passieren hier.
  2. Virtuelle Orders sind ein Must-Have für Grid-Bots. Wenn du mehr als 50 Orders platzierst - sende sie nicht alle an die Börse.
  3. Nachziehen wird gebraucht, wenn die Fill-Rate wichtiger ist als der Preis. Aber setze immer max_chase_distance - sonst kannst du sehr weit abdriften.
  4. Zeitbasierte Ausführung ist eine Nische, aber mächtig für Funding-Arb und ereignisgesteuerte Strategien.
  5. Eine eigene Order-Management-Schicht ist für jedes ernsthafte Algotrading-System unvermeidlich. Börsennative Ordertypen reichen nicht aus.

Wenn du ein Handelssystem baust und tiefer einsteigen willst - schau dir unsere Artikel über die Warteschlangenposition im Orderbuch, WebSocket-Methoden in CCXT und Funding-Rate-Arbitrage an.


Referenzen und Quellen

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

Dem Markt einen Schritt voraus

Abonniere unseren Newsletter für exklusive KI-Trading-Einblicke, Marktanalysen und Plattform-Updates.

Wir respektieren deine Privatsphäre. Jederzeit abbestellbar.