← К списку статей
March 23, 2026
5 мин. чтения

Виды ордеров в алготрейдинге: от limit с chasing до virtual orders

Виды ордеров в алготрейдинге: от limit с chasing до virtual orders
#ордера
#алготрейдинг
#limit
#chasing
#virtual-orders
#grid-bot
#маркетмейкинг

Когда новичок открывает терминал биржи, он видит две кнопки: «Купить» и «Продать». Когда алготрейдер открывает свой codebase, он видит двадцать семь типов ордеров, три уровня абстракции и пачку edge cases, от которых хочется закрыть ноутбук и пойти торговать огурцами на рынке. Но огурцами, к сожалению, не сделаешь funding rate арбитраж в 3:59 UTC — поэтому давайте разбираться.

В этой статье мы пройдём весь путь от базовых биржевых ордеров до синтетических виртуальных конструкций, которые живут только в вашей системе и никогда не появляются в стакане. Будет TypeScript, Python, боль и немного просветления.


1. Стандартные биржевые ордера: база, без которой никуда

Стандартные ордера Классификация стандартных типов ордеров: от market до iceberg

Прежде чем конструировать что-то сложное, нужно убедиться, что мы правильно понимаем базовые строительные блоки. Удивительно, сколько людей путают stop-limit и stop-market, а потом удивляются, почему их стоп «не сработал» (спойлер: сработал, но лимитный ордер не исполнился из-за проскальзывания).

Market order

Самый простой и одновременно самый опасный тип. Вы говорите бирже: «Купи/продай прямо сейчас, по любой доступной цене». Биржа берёт ликвидность из стакана, начиная с лучшей цены. Если объёма на лучшем уровне не хватает — проскальзывает дальше.

Когда использовать: экстренный выход из позиции, исполнение сигнала, где скорость важнее цены.

Подводные камни: на тонком рынке маркет-ордер на 100 BTC может сдвинуть цену на несколько процентов. Бэктесты, которые моделируют маркет-ордера без учёта impact — фантазия.

Limit order

Вы указываете конкретную цену. Ордер попадает в стакан и ждёт, пока кто-то согласится на вашу цену. Если цена лимитного bid-ордера выше текущей рыночной — он исполнится немедленно (как маркет, но с гарантией максимальной цены).

Ключевое: лимитный ордер не гарантирует исполнение. Цена может дойти до вашего уровня и развернуться, а вы останетесь в очереди (об этом подробно — в нашей статье про queue position).

Stop-market и Stop-limit

Тут начинается зона путаницы. Оба типа — это «спящие» ордера, которые активируются при достижении trigger price (стоп-цены). Но:

  • Stop-market: при срабатывании превращается в маркет-ордер. Гарантия исполнения, но не цены.
  • Stop-limit: при срабатывании превращается в лимитный ордер. Гарантия цены (не хуже указанной), но не исполнения.

На волатильном крипторынке stop-limit может «промахнуться» — цена пробила стоп, лимитный ордер выставился, но рынок уже улетел дальше. Вы остались с нерабочим лимитным ордером и растущим убытком. Именно поэтому для стоп-лоссов чаще используют stop-market.

Trailing stop

Стоп, который «следует» за ценой на заданном расстоянии. Цена растёт — стоп подтягивается вверх. Цена падает — стоп остаётся на месте. Полезен для защиты прибыли в трендовых стратегиях.

Реализация на биржах: не все биржи поддерживают нативный trailing stop. Часто алготрейдеры реализуют его программно — так больше контроля над параметрами (callback rate, activation price, step size).

Iceberg order

Или «айсберг» — ордер, у которого в стакане видна только часть объёма. Вы хотите купить 1000 BTC, но показываете в стакане только 10. Когда первые 10 исполняются — появляются следующие 10.

Зачем: чтобы не показывать рынку свои истинные намерения. Крупный ордер в стакане — это сигнал для всех, что «кто-то большой хочет купить/продать». В ответ HFT-алгоритмы начинают front-running, и цена уезжает от вас.

Нюанс: на многих криптобиржах iceberg-ордера либо не поддерживаются, либо легко детектируются по паттерну одинаковых объёмов. Продвинутые алгоритмы рандомизируют размер видимой части.

Параметры времени жизни: GTC, GTD, IOC, FOK

Это не отдельные типы ордеров, а time-in-force параметры — как долго ордер живёт:

Параметр Расшифровка Поведение
GTC Good Till Cancelled Живёт до отмены. Стандарт по умолчанию
GTD Good Till Date Живёт до указанной даты/времени
IOC Immediate or Cancel Исполняется немедленно (целиком или частично), остаток отменяется
FOK Fill or Kill Исполняется только целиком и немедленно. Если невозможно — отменяется полностью

IOC vs FOK: разница критична. IOC может исполниться частично — вы хотели купить 100 BTC, купили 3, остальное отменилось. FOK или 100, или ничего.

Post-only (Maker-only)

Ордер, который гарантированно попадает в стакан как мейкер и никогда не исполняется как тейкер. Если при выставлении цена такова, что ордер бы исполнился немедленно — биржа его отклоняет (или сдвигает цену, в зависимости от биржи).

Зачем: maker fee обычно ниже taker fee (на Binance — 0.02% vs 0.04% для VIP-уровней). Для маркетмейкера, который выставляет тысячи ордеров в день, разница в комиссиях — это разница между прибылью и убытком.


2. TWAP и VWAP: как институционалы прячут слона в стакане

Когда хедж-фонд хочет купить позицию на $50 млн, он не ставит один маркет-ордер. Он использует execution algorithms — алгоритмы, которые разбивают крупный ордер на множество мелких и исполняют их во времени, минимизируя market impact.

TWAP (Time-Weighted Average Price)

Идея предельно проста: разбиваем общий объём на равные части и исполняем через равные промежутки времени.

import asyncio
from datetime import datetime, timedelta

class TWAPExecutor:
    """
    TWAP-исполнитель: разбивает крупный ордер на равные части
    и исполняет их через равные промежутки времени.
    """
    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 умнее: он учитывает типичный профиль объёма торгов. Если с 9:00 до 10:00 обычно торгуется 30% дневного объёма, то VWAP исполнит 30% ордера именно в это время. Цель — чтобы средняя цена исполнения была как можно ближе к рыночному VWAP.

class VWAPExecutor:
    """
    VWAP-исполнитель: распределяет объём пропорционально
    историческому профилю объёма.
    """
    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)

Разница TWAP vs VWAP: TWAP проще и предсказуемее. VWAP даёт лучшую среднюю цену, но требует reliable volume profile. На крипторынке, где объёмы могут быть wash-traded, VWAP-профиль нужно строить аккуратно.


3. Limit с chasing: когда ваш ордер умеет бегать за ценой

Chasing limit ордера Chasing limit: ордер преследует уходящую цену с настраиваемой агрессивностью

А вот тут начинается самое интересное. Стандартный лимитный ордер — это пассивная сущность: он стоит в стакане и ждёт. Если цена ушла — ордер остался неисполненным. Для алготрейдера это часто неприемлемо: сигнал на вход получен, а позиция не набрана, потому что рынок сдвинулся на 0.1%.

Chasing limit order — это программная обёртка вокруг лимитного ордера, которая:

  1. Выставляет лимитный ордер по текущей лучшей цене (или с небольшим смещением)
  2. Следит за ценой через WebSocket
  3. Если цена уходит от ордера — отменяет его и перевыставляет ближе к текущей цене
  4. Повторяет, пока ордер не исполнится или не превысит допустимое отклонение

Ключевые параметры

  • chase_interval_ms — как часто проверять и перевыставлять ордер. 100ms — агрессивно, 1000ms — спокойно.
  • max_chase_distance — максимальное отклонение от начальной цены, после которого ордер отменяется. Защита от погони за улетевшим рынком.
  • aggression_level — насколько близко к рыночной цене ставить лимитный ордер. 0 — на лучший bid/ask (пассивно), 1 — переход через спред (агрессивно, де-факто тейкер).
  • chase_on_partial — преследовать ли цену, если ордер частично исполнен.

Реализация на 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) {
      // Таймаут
      if (Date.now() - this.startTime > this.params.timeoutMs) {
        console.log("[CHASE] timeout reached, cancelling");
        await this.cancelCurrent();
        break;
      }

      // Получаем текущий стакан
      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;

      // Рассчитываем целевую цену
      let targetPrice: number;
      if (this.params.side === "buy") {
        targetPrice = bestBid + spread * this.params.aggression;
      } else {
        targetPrice = bestAsk - spread * this.params.aggression;
      }

      // Запоминаем начальную цену
      if (this.initialPrice === null) {
        this.initialPrice = targetPrice;
      }

      // Проверяем 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;
      }

      // Проверяем текущий ордер
      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;
        }

        // Обновляем filledQty для частичного исполнения
        if (order.filled > 0) {
          const newFilled = order.filled - (
            fills.reduce((s, f) => s + f.qty, 0) - this.filledQty
          );
          // Ордер на месте — нужно ли перевыставлять?
        }

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

        if (priceDiff > tickSize) {
          // Цена ушла — перевыставляем
          console.log(
            `[CHASE] repricing: ${currentPrice} -> ` +
            `${targetPrice.toFixed(4)}`
          );
          await this.cancelCurrent();
        } else {
          // Ордер на нужной цене — ждём
          await this.sleep(this.params.chaseIntervalMs);
          continue;
        }
      }

      // Выставляем новый ордер
      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 { /* ордер уже исполнен или отменён */ }
      this.currentOrderId = null;
    }
  }

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

Когда chasing — зло

Chasing — мощный инструмент, но его легко превратить в генератор убытков:

  1. Cancel/replace spam. Каждая отмена и перевыставление — это нагрузка на API. Биржи лимитируют частоту запросов, и при агрессивном chasing вы можете получить бан API ключа.
  2. Adverse selection. Если цена убегает от вас — возможно, рынок знает что-то, чего не знаете вы. Погоня за ценой в такой ситуации — покупка на верхушке.
  3. Maker → Taker transition. При высокой aggression вы де-факто платите taker fee, но с задержкой (cancel + new order). Иногда проще сразу поставить маркет-ордер.

4. Time-based ордера: точность до миллисекунды

Есть ситуации, когда нужно исполнить ордер не «по цене X», а «в момент времени T». Звучит странно? На самом деле это целый класс стратегий.

Use cases

Funding rate арбитраж. На бессрочных фьючерсах funding выплачивается каждые 8 часов (00:00, 08:00, 16:00 UTC на Binance). Если funding rate = +0.1%, нужно быть в шорте в момент расчёта. Стратегия: открыть шорт за несколько секунд до settlement, получить funding, закрыть позицию. Timing здесь критичен — секунда опоздания означает пропущенный funding.

Открытие/закрытие сессий. На традиционных рынках и некоторых крипто-деривативах есть фиксированные сессии. Аукцион открытия (NYSE, CME) — момент, когда ликвидность максимальна. Выставить ордер за 100ms до аукциона — преимущество.

News-based execution. Данные по инфляции выходят в 15:30 MSK. Алгоритм парсит число из новостного фида и за 50ms выставляет ордер. Тут time-based execution сочетается с event-driven логикой.

Реализация

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

  /**
   * Запланировать исполнение на точное время.
   * Использует busy-wait loop для максимальной точности.
   */
  async executeAt(targetTime: Date): Promise<any> {
    const targetMs = targetTime.getTime();

    // Фаза 1: грубое ожидание (sleep)
    const coarseWait = targetMs - Date.now() - 500; // просыпаемся за 500ms
    if (coarseWait > 0) {
      console.log(
        `[TIME-ORDER] sleeping for ${(coarseWait / 1000).toFixed(1)}s`
      );
      await new Promise((r) => setTimeout(r, coarseWait));
    }

    // Фаза 2: точное ожидание (busy-wait)
    while (Date.now() < targetMs) {
      // spin — тратим CPU, но получаем точность ~1ms
    }

    // Фаза 3: исполнение
    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;
  }
}

// Пример: выставить ордер ровно в 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);

Важный нюанс: точность time-based ордера ограничена не вашим кодом, а сетевой задержкой до биржи. Если пинг до API — 50ms, то даже идеальный busy-wait даст дельту в 50ms. Для серьёзного HFT используют co-location — сервер стоит физически рядом с matching engine биржи.


5. Virtual/synthetic orders: невидимки в вашей системе

Virtual orders для grid-ботов Virtual orders: ордера существуют только в памяти бота до момента триггера

Это, пожалуй, самый недооценённый инструмент в арсенале алготрейдера. Virtual order (он же synthetic order) — это ордер, который существует только в вашей системе. На биржу он не отправляется до тех пор, пока не выполнится триггерное условие (обычно — цена достигла определённого уровня).

Как это работает

  1. Ваш алгоритм определяет: «Хочу купить BTC по $40,000»
  2. Вместо отправки лимитного ордера на биржу, он создаёт виртуальный ордер в памяти
  3. Подписывается на WebSocket-поток цен
  4. Когда bid/ask достигает $40,000 — отправляет реальный market или limit ордер на биржу

Зачем нужны virtual orders

Отсутствие information leakage. Ваш ордер не виден в стакане. Никто — ни другие трейдеры, ни HFT-алгоритмы, ни сама биржа — не знает о ваших намерениях до момента исполнения. Это принципиально меняет баланс сил.

Защита от front-running. На криптобиржах, особенно менее прозрачных, существует обоснованное подозрение, что информация о крупных лимитных ордерах может использоваться для front-running (есть даже исследования об этом). Virtual orders элиминируют этот риск.

Grid-боты. Классический grid-бот выставляет сетку из 50-200 ордеров на разных ценовых уровнях. Если выставить все на биржу — это 200 ордеров в стакане, которые: (а) видны всем, (б) занимают лимит ордеров на бирже (обычно 200-300 открытых ордеров на аккаунт), (в) при резком движении все исполнятся, и вы получите огромную позицию. Virtual orders решают все три проблемы.

Catching falling knives. Стратегия: выставить виртуальные ордера на покупку на уровнях -5%, -10%, -15% от текущей цены. Если рынок падает — ордера постепенно триггерятся. Если не падает — вы ничем не рискуете и не занимаете лимиты на бирже.

Реализация на TypeScript

interface VirtualOrder {
  id: string;
  symbol: string;
  side: "buy" | "sell";
  triggerPrice: number;
  qty: number;
  /** Тип ордера, который отправится на биржу при срабатывании */
  executionType: "market" | "limit";
  /** Для limit: смещение от 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) {}

  /**
   * Создать виртуальный ордер. Ничего не отправляется на биржу.
   */
  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;
  }

  /**
   * Вызывается на каждый тик цены (из 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);
      }
    }
  }

  /**
   * Получить все активные виртуальные ордера.
   */
  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;
  }
}

// --- Пример: Grid-бот с virtual orders ---

async function gridBot(exchange: any) {
  const manager = new VirtualOrderManager(exchange);
  const currentPrice = 42000;
  const gridStep = 200;      // шаг сетки
  const gridLevels = 20;     // уровней в каждую сторону
  const qtyPerLevel = 0.01;  // BTC на уровень

  // Создаём виртуальную сетку
  for (let i = 1; i <= gridLevels; i++) {
    // Ордера на покупку ниже текущей цены
    manager.addOrder({
      symbol: "BTC/USDT",
      side: "buy",
      triggerPrice: currentPrice - gridStep * i,
      qty: qtyPerLevel,
      executionType: "limit",
      limitOffset: 1,  // limit price = trigger + 1 USDT
    });

    // Ордера на продажу выше текущей цены
    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 (псевдокод для ccxt.pro)
  while (true) {
    const ticker = await exchange.watchTicker("BTC/USDT");
    await manager.onPriceUpdate(
      "BTC/USDT", ticker.bid, ticker.ask
    );
  }
}

Подводные камни virtual orders

  1. Latency gap. Между моментом, когда вы видите цену, и моментом, когда реальный ордер доходит до биржи, проходит время. На волатильном рынке цена может улететь за эти 20-100ms. Решение: отправлять слегка агрессивный лимитный ордер (с запасом).

  2. Missed fills. Если цена «прошила» ваш уровень за один тик (flash crash) и вернулась обратно — вы можете не успеть. Обычный лимитный ордер в стакане бы исполнился, virtual — нет.

  3. State management. Virtual orders живут в памяти. Если процесс упал — ордера потеряны. Решение: персистентное хранение (Redis, SQLite, файл) с recovery при рестарте.


6. Conditional/smart orders: комбинаторика ордеров

Когда одного ордера недостаточно, трейдеры комбинируют их в условные конструкции. Некоторые поддерживаются нативно на биржах, другие реализуются программно.

OCO (One Cancels Other)

Два ордера связаны: если исполняется один — второй автоматически отменяется. Классический пример: вы в позиции long и хотите поставить и тейк-профит, и стоп-лосс. Какой бы ни сработал первым — второй должен быть отменён.

class OCOHandler:
    """
    OCO: при исполнении одного ордера второй отменяется.
    """
    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):
        """Проверяет статусы и отменяет парный ордер."""
        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

Трёхкомпонентная конструкция: основной ордер на вход + OCO для выхода (take-profit + stop-loss). По сути, полный цикл сделки в одном вызове:

  1. Entry: лимитный ордер на покупку
  2. Take-profit: лимитный ордер на продажу (выше)
  3. Stop-loss: stop-market ордер на продажу (ниже)

При исполнении entry автоматически выставляются TP и SL. При исполнении любого из них — второй отменяется.

If-Then логика

Самый гибкий вариант — цепочки ордеров с произвольными условиями:


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

Такие конструкции не поддерживаются ни одной биржей нативно — только программная реализация. Это одна из причин, почему алготрейдинг-системы неизбежно обрастают собственным order management layer.


7. Как маркетмейкеры используют специальные типы ордеров

Маркетмейкинг — это отдельная вселенная, и набор ордеров здесь соответствующий. Задача маркетмейкера — постоянно котировать bid и ask, зарабатывая на спреде, при этом минимизируя adverse selection (ситуацию, когда информированный трейдер торгует против вас).

Post-only как must-have

Для маркетмейкера post-only — не опция, а обязательное требование. Если ваш ордер случайно исполнится как тейкер — вместо получения maker rebate вы заплатите taker fee. На тысячах ордеров в день это катастрофа.

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}  # КРИТИЧНО для маркетмейкера
    )
    ask = await exchange.create_order(
        symbol, "limit", "sell", qty, ask_price,
        {"postOnly": True}
    )
    return bid, ask

Hidden orders

На некоторых биржах (Kraken, Bitfinex) доступны hidden (скрытые) ордера — они не отображаются в стакане, но при этом находятся на бирже и участвуют в матчинге. Компромисс: вы платите taker fee даже как мейкер, но получаете анонимность.

Для маркетмейкера это инструмент для управления инвентарём: если накопилась большая позиция, можно поставить hidden ордер на разгрузку, не показывая рынку своё намерение.

Pegged orders

Ордер, привязанный к лучшему bid/ask. На Coinbase Advanced Trade, например, можно выставить ордер, который автоматически следит за best bid и всегда стоит на первом месте в очереди. Это нативный chasing order на уровне биржи — но доступен далеко не везде.

Bulk order management

Профессиональные маркетмейкеры используют batch-API для одновременной отмены и выставления десятков ордеров в одном HTTP-запросе. На Binance это batchOrders, на Bybitplace-batch-order. Это снижает задержку и нагрузку на rate limits.


8. Сравнительная таблица типов ордеров

Тип ордера Гарантия исполнения Гарантия цены Виден в стакане Нативный на биржах Сложность реализации
Market Да Нет Нет (мгновенный) Да Нулевая
Limit Нет Да Да Да Нулевая
Stop-market Да (после триггера) Нет Нет Да Нулевая
Stop-limit Нет Да Нет (до триггера) Да Нулевая
Trailing stop Да (после триггера) Нет Нет Частично Низкая
Iceberg Нет Да Частично Частично Средняя
Post-only Нет Да Да Да Нулевая
TWAP Нет (зависит от слайсов) Нет Частично Нет Средняя
VWAP Нет Нет Частично Нет Высокая
Chasing limit Выше лимитного Частично Да (текущий ордер) Нет Средняя
Time-based Зависит от типа Зависит от типа Нет (до момента T) Нет Низкая
Virtual/Synthetic Ниже лимитного Зависит от типа Нет Нет Средняя
OCO Да (один из двух) Частично Да (оба) Частично Средняя
Bracket Да Частично Да Редко Высокая
Hidden Нет Да Нет Редко Нулевая
Pegged Нет Динамическая Да Очень редко Высокая (если программно)

Заключение: ордер как строительный блок стратегии

Типы ордеров — это не просто «кнопки в интерфейсе». Это фундаментальные примитивы, из которых строится execution layer любой торговой системы. Разница между «стратегия прибыльная в бэктесте» и «стратегия прибыльная в продакшене» часто лежит именно здесь — в том, как именно вы отправляете ордера на биржу.

Несколько практических выводов:

  1. Начните со стандартных ордеров, убедитесь, что понимаете нюансы (stop-limit vs stop-market, IOC vs FOK). Большинство ошибок — здесь.
  2. Virtual orders — must-have для grid-ботов. Если вы выставляете больше 50 ордеров — не отправляйте их все на биржу.
  3. Chasing нужен, когда fill rate важнее цены. Но всегда ставьте max_chase_distance — иначе можно уехать очень далеко.
  4. Time-based execution — нишевый, но мощный инструмент для funding arb и event-driven стратегий.
  5. Собственный order management layer — неизбежность для любой серьёзной алготрейдинг-системы. Биржевых типов ордеров недостаточно.

Если вы строите торговую систему и хотите разобраться глубже — смотрите наши статьи про queue position в ордербуке, WebSocket-методы CCXT и funding rate арбитраж.


Ссылки и источники

  • CCXT Library — унифицированная библиотека для работы с криптобиржами, поддержка 100+ бирж
  • Binance API Documentation — документация по типам ордеров Binance
  • Bybit API v5 — документация Bybit, включая batch orders
  • 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 — материалы по оценке позиции в очереди
  • Trading Technologies (TT) — профессиональная платформа с продвинутыми типами ордеров
Дисклеймер: Информация в этой статье предоставлена исключительно в образовательных и ознакомительных целях и не является финансовым, инвестиционным или торговым советом. Торговля криптовалютами сопряжена с высоким риском убытков.

MarketMaker.cc Team

Количественные исследования и стратегии

Обсудить в Telegram
Newsletter

Будьте в курсе событий

Подпишитесь на нашу рассылку, чтобы получать эксклюзивную аналитику по AI-трейдингу и обновления платформы.

Мы уважаем вашу конфиденциальность. Отписаться можно в любой момент.