← Quay lại danh sách bài viết
March 23, 2026
5 phút đọc

Các Loại Lệnh Trong Giao Dịch Thuật Toán: Từ Lệnh Limit Đuổi Giá Đến Lệnh Ảo

Các Loại Lệnh Trong Giao Dịch Thuật Toán: Từ Lệnh Limit Đuổi Giá Đến Lệnh Ảo
#lệnh
#algotrading
#limit
#đuổi-giá
#lệnh-ảo
#grid-bot
#market-making

Khi một người mới mở terminal sàn giao dịch, họ thấy hai nút: "Mua" và "Bán." Khi một algo trader mở codebase của mình, họ thấy hai mươi bảy loại lệnh, ba tầng trừu tượng, và một đống edge case khiến họ muốn đóng laptop lại rồi đi bán dưa chuột ở chợ. Nhưng tiếc thay, bán dưa chuột không cho phép bạn chạy arbitrage funding rate lúc 3:59 UTC — vậy nên hãy cùng đào sâu vào.

Trong bài viết này, chúng ta sẽ đi qua toàn bộ hành trình từ các lệnh sàn cơ bản đến các cấu trúc ảo tổng hợp chỉ tồn tại trong hệ thống của bạn và không bao giờ xuất hiện trên order book. Hãy chuẩn bị cho TypeScript, Python, một chút đau đầu, và đôi chút khai sáng.


1. Lệnh Sàn Tiêu Chuẩn: Nền Tảng Không Thể Bỏ Qua

Standard orders Phân loại các loại lệnh tiêu chuẩn: từ market đến iceberg

Trước khi xây dựng bất cứ thứ gì phức tạp, chúng ta cần đảm bảo hiểu đúng các khối xây dựng cơ bản. Thật đáng ngạc nhiên khi nhiều người nhầm lẫn stop-limit và stop-market, rồi thắc mắc tại sao lệnh stop của họ "không kích hoạt" (spoiler: nó đã kích hoạt, nhưng lệnh limit không khớp do slippage).

Lệnh Market (Lệnh thị trường)

Loại đơn giản nhất và đồng thời nguy hiểm nhất. Bạn nói với sàn: "Mua/bán ngay bây giờ, ở bất kỳ giá nào có sẵn." Sàn lấy thanh khoản từ order book, bắt đầu từ giá tốt nhất. Nếu khối lượng ở mức tốt nhất không đủ — nó trượt sâu hơn.

Khi nào dùng: thoát vị thế khẩn cấp, thực thi tín hiệu khi tốc độ quan trọng hơn giá.

Rủi ro: trên thị trường mỏng, một lệnh market cho 100 BTC có thể dịch chuyển giá vài phần trăm. Backtest mô hình hóa lệnh market mà không tính đến market impact là thuần túy ảo tưởng.

Lệnh Limit

Bạn chỉ định một mức giá chính xác. Lệnh vào order book và chờ cho đến khi ai đó đồng ý với giá của bạn. Nếu giá của một lệnh limit mua cao hơn thị trường hiện tại — nó khớp ngay lập tức (giống lệnh market, nhưng với mức giá tối đa được đảm bảo).

Điểm quan trọng: lệnh limit không đảm bảo thực thi. Giá có thể chạm mức của bạn rồi đảo chiều, để bạn ngồi chờ trong hàng (xem thêm trong bài viết của chúng tôi về vị trí hàng chờ).

Stop-market và Stop-limit

Đây là nơi sự nhầm lẫn bắt đầu. Cả hai loại đều là các lệnh "ngủ" được kích hoạt khi đạt đến giá trigger (giá stop). Nhưng:

  • Stop-market: khi kích hoạt, chuyển thành lệnh market. Đảm bảo thực thi, nhưng không đảm bảo giá.
  • Stop-limit: khi kích hoạt, chuyển thành lệnh limit. Đảm bảo giá (không tệ hơn mức đã chỉ định), nhưng không đảm bảo thực thi.

Trên thị trường crypto biến động, stop-limit có thể "bỏ lỡ" — giá đã xuyên qua stop, lệnh limit được đặt, nhưng thị trường đã bay vọt qua. Bạn còn lại với lệnh limit chưa khớp và khoản lỗ ngày càng tăng. Đó chính xác là lý do tại sao stop-market thường được dùng cho stop-loss hơn.

Trailing Stop

Một stop "theo" giá ở một khoảng cách nhất định. Giá đi lên — stop di chuyển lên. Giá đi xuống — stop giữ nguyên. Hữu ích để bảo vệ lợi nhuận trong các chiến lược theo xu hướng.

Hỗ trợ từ sàn: không phải tất cả sàn đều hỗ trợ trailing stop gốc. Các algo trader thường triển khai chúng theo cách lập trình — điều này cho phép kiểm soát nhiều hơn về các tham số (callback rate, activation price, step size).

Lệnh Iceberg

Một lệnh trong đó chỉ một phần nhỏ của tổng khối lượng được hiển thị trên order book. Bạn muốn mua 1.000 BTC, nhưng chỉ hiện 10 trên sách. Khi 10 đầu tiên khớp — 10 tiếp theo xuất hiện.

Tại sao: để ẩn ý định thật sự của bạn khỏi thị trường. Một lệnh lớn trên sách báo hiệu với mọi người rằng "ai đó lớn muốn mua/bán." Để đáp lại, các thuật toán HFT bắt đầu front-running, và giá di chuyển xa bạn.

Lưu ý: trên nhiều sàn crypto, lệnh iceberg không được hỗ trợ hoặc dễ dàng bị phát hiện qua mẫu khối lượng giống nhau. Các thuật toán nâng cao ngẫu nhiên hóa kích thước phần hiển thị.

Tham số time-in-force: GTC, GTD, IOC, FOK

Đây không phải là các loại lệnh riêng biệt mà là tham số time-in-force — lệnh tồn tại bao lâu:

Tham số Tên đầy đủ Hành vi
GTC Good Till Cancelled Tồn tại cho đến khi hủy. Tiêu chuẩn mặc định
GTD Good Till Date Tồn tại đến ngày/giờ cụ thể
IOC Immediate or Cancel Thực thi ngay (toàn bộ hoặc một phần), phần còn lại bị hủy
FOK Fill or Kill Thực thi chỉ toàn bộ và ngay lập tức. Nếu không thể — hủy hoàn toàn

IOC vs FOK: sự khác biệt rất quan trọng. IOC có thể khớp một phần — bạn muốn mua 100 BTC, mua được 3, phần còn lại bị hủy. FOK là hoặc 100 hoặc không có gì.

Post-only (Maker-only)

Một lệnh được đảm bảo vào order book với tư cách maker và không bao giờ thực thi như taker. Nếu tại thời điểm đặt lệnh giá sẽ gây ra thực thi ngay lập tức — sàn từ chối nó (hoặc điều chỉnh giá, tùy thuộc vào sàn).

Tại sao: phí maker thường thấp hơn phí taker (trên Binance — 0,02% vs 0,04% cho các tầng VIP). Đối với một market maker đặt hàng nghìn lệnh mỗi ngày, chênh lệch phí là ranh giới giữa lợi nhuận và thua lỗ.


2. TWAP và VWAP: Cách Các Tổ Chức Giấu Con Voi Trong Order Book

Khi một quỹ phòng hộ muốn mua vị thế trị giá 50 triệu đô, họ không đặt một lệnh market duy nhất. Họ sử dụng các thuật toán thực thi — các thuật toán chia một lệnh lớn thành nhiều lệnh nhỏ hơn và thực thi theo thời gian, giảm thiểu market impact.

TWAP (Time-Weighted Average Price — Giá Trung Bình Theo Thời Gian)

Ý tưởng cực kỳ đơn giản: chia tổng khối lượng thành các phần bằng nhau và thực thi ở các khoảng thời gian đều nhau.

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 — Giá Trung Bình Theo Khối Lượng)

VWAP thông minh hơn: nó tính đến hồ sơ khối lượng giao dịch điển hình. Nếu 30% khối lượng hàng ngày thường được giao dịch trong khoảng từ 9:00 đến 10:00, VWAP sẽ thực thi 30% lệnh trong khoảng đó. Mục tiêu là đưa giá thực thi trung bình càng gần VWAP thị trường càng tốt.

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)

Sự khác biệt TWAP vs VWAP: TWAP đơn giản hơn và dễ đoán hơn. VWAP cho giá trung bình tốt hơn nhưng đòi hỏi hồ sơ khối lượng đáng tin cậy. Trên thị trường crypto, nơi khối lượng có thể bị wash-trade, hồ sơ VWAP cần được xây dựng cẩn thận.


3. Limit Đuổi Giá: Khi Lệnh Của Bạn Biết Cách Đuổi Theo Giá

Chasing limit orders Chasing limit: lệnh đuổi theo giá di chuyển với mức độ tích cực có thể cấu hình

Bây giờ mọi thứ trở nên thực sự thú vị. Một lệnh limit tiêu chuẩn là một thực thể thụ động: nó ngồi trong order book và chờ. Nếu giá di chuyển — lệnh vẫn chưa khớp. Đối với một algo trader, điều này thường không thể chấp nhận được: tín hiệu vào lệnh đã kích hoạt, nhưng vị thế không được xây dựng vì thị trường di chuyển 0,1%.

Lệnh limit đuổi giá (Chasing limit order) là một wrapper lập trình xung quanh lệnh limit:

  1. Đặt lệnh limit ở giá tốt nhất hiện tại (hoặc với một offset nhỏ)
  2. Theo dõi giá qua WebSocket
  3. Nếu giá rời xa lệnh — hủy và đặt lại gần giá hiện tại hơn
  4. Lặp lại cho đến khi lệnh khớp hoặc vượt quá độ lệch cho phép

Các Tham Số Quan Trọng

  • chase_interval_ms — tần suất kiểm tra và thay thế lệnh. 100ms — tích cực, 1000ms — nhàn hạ.
  • max_chase_distance — độ lệch tối đa từ giá ban đầu trước khi lệnh bị hủy. Bảo vệ khỏi việc đuổi theo thị trường đang bỏ chạy.
  • aggression_level — đặt lệnh limit gần giá thị trường bao nhiêu. 0 — ở bid/ask tốt nhất (thụ động), 1 — vượt qua spread (tích cực, thực tế là taker).
  • chase_on_partial — có tiếp tục đuổi nếu lệnh được khớp một phần không.

Triển Khai TypeScript

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Khi Nào Đuổi Giá Gây Hại

Đuổi giá là công cụ mạnh mẽ, nhưng dễ biến thành máy tạo thua lỗ:

  1. Spam hủy/đặt lại. Mỗi lần hủy và đặt lại là tải lên API. Các sàn giới hạn tốc độ yêu cầu, và việc đuổi giá tích cực có thể khiến API key của bạn bị ban.
  2. Adverse selection (Lựa chọn bất lợi). Nếu giá đang chạy khỏi bạn — thị trường có thể biết điều gì đó bạn không biết. Đuổi theo giá trong tình huống này có nghĩa là mua ở đỉnh.
  3. Chuyển đổi Maker sang Taker. Với mức độ tích cực cao, bạn thực tế đang trả phí taker, nhưng với độ trễ (hủy + lệnh mới). Đôi khi đơn giản hơn là chỉ đặt lệnh market.

4. Lệnh Theo Thời Gian: Độ Chính Xác Mili Giây

Có những tình huống bạn cần thực thi lệnh không phải "ở giá X" mà là "vào thời điểm T." Nghe lạ? Thực ra đây là cả một lớp chiến lược.

Các Trường Hợp Sử Dụng

Arbitrage funding rate. Trên hợp đồng tương lai vĩnh cửu, funding được thanh toán mỗi 8 giờ (00:00, 08:00, 16:00 UTC trên Binance). Nếu funding rate = +0,1%, bạn cần ở trạng thái short vào thời điểm thanh toán. Chiến lược: mở short vài giây trước khi thanh toán, thu funding, đóng vị thế. Thời điểm là yếu tố quyết định — một giây trễ có nghĩa là bỏ lỡ funding.

Mở/đóng phiên giao dịch. Trên các thị trường truyền thống và một số phái sinh crypto, có các phiên cố định. Phiên đấu giá mở (NYSE, CME) là thời điểm thanh khoản ở mức đỉnh. Đặt lệnh 100ms trước phiên đấu giá là một lợi thế.

Thực thi dựa trên tin tức. Dữ liệu lạm phát được công bố vào thời điểm đã lên lịch. Thuật toán phân tích con số từ feed tin tức và đặt lệnh trong vòng 50ms. Ở đây, thực thi theo thời gian kết hợp với logic event-driven.

Triển Khai

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

Lưu ý quan trọng: độ chính xác của lệnh theo thời gian bị giới hạn không phải bởi code của bạn mà bởi độ trễ mạng đến sàn. Nếu ping của bạn đến API là 50ms, ngay cả một busy-wait hoàn hảo cũng sẽ có delta 50ms. Đối với HFT nghiêm túc, co-location được sử dụng — máy chủ đặt vật lý ngay cạnh matching engine của sàn.


5. Lệnh Ảo/Tổng Hợp: Những Thứ Vô Hình Trong Hệ Thống Của Bạn

Virtual orders for grid bots Lệnh ảo: các lệnh chỉ tồn tại trong bộ nhớ của bot cho đến khi điều kiện kích hoạt

Đây có lẽ là công cụ bị đánh giá thấp nhất trong kho vũ khí của algo trader. Một lệnh ảo (còn gọi là lệnh tổng hợp) là một lệnh chỉ tồn tại trong hệ thống của bạn. Nó không được gửi đến sàn cho đến khi điều kiện kích hoạt được đáp ứng (thông thường — giá đạt đến một mức nhất định).

Cách Hoạt Động

  1. Thuật toán của bạn quyết định: "Tôi muốn mua BTC ở $40.000"
  2. Thay vì gửi lệnh limit đến sàn, nó tạo một lệnh ảo trong bộ nhớ
  3. Đăng ký luồng giá WebSocket
  4. Khi bid/ask đạt $40.000 — gửi lệnh market hoặc limit thực đến sàn

Tại Sao Lệnh Ảo Quan Trọng

Không rò rỉ thông tin. Lệnh của bạn vô hình trên order book. Không ai — không trader khác, không thuật toán HFT, thậm chí không cả sàn — biết về ý định của bạn cho đến thời điểm thực thi. Điều này thay đổi cơ bản cán cân quyền lực.

Bảo vệ khỏi front-running. Trên các sàn crypto, đặc biệt là những sàn ít minh bạch hơn, có nghi ngờ hợp lý rằng thông tin về các lệnh limit lớn có thể được sử dụng để front-running (thậm chí có các nghiên cứu về điều này). Lệnh ảo loại bỏ rủi ro này.

Grid bot. Một grid bot cổ điển đặt lưới 50-200 lệnh ở các mức giá khác nhau. Nếu bạn gửi tất cả chúng đến sàn — đó là 200 lệnh trên sách mà: (a) hiển thị cho mọi người, (b) sử dụng hết giới hạn lệnh trên sàn (thường 200-300 lệnh mở mỗi tài khoản), (c) nếu giá di chuyển mạnh, tất cả chúng đều khớp và bạn kết thúc với một vị thế khổng lồ. Lệnh ảo giải quyết cả ba vấn đề.

Bắt dao rơi. Chiến lược: đặt lệnh mua ảo ở các mức -5%, -10%, -15% dưới giá hiện tại. Nếu thị trường giảm — lệnh kích hoạt dần dần. Nếu không giảm — bạn không mạo hiểm gì và không sử dụng slot lệnh sàn.

Triển Khai TypeScript

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

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

  constructor(private exchange: any) {}

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

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

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

      if (!triggered) continue;

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

      try {
        let realOrder: any;

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

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

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

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

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

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

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

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

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

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

Rủi Ro Của Lệnh Ảo

  1. Khoảng cách trễ. Giữa thời điểm bạn thấy giá và thời điểm lệnh thực đến sàn, thời gian trôi qua. Trên thị trường biến động, giá có thể bay đi trong 20-100ms đó. Giải pháp: gửi lệnh limit hơi tích cực (với buffer).

  2. Bỏ lỡ khớp lệnh. Nếu giá "xuyên qua" mức của bạn trong một tick duy nhất (flash crash) rồi bật lại — bạn có thể không phản ứng kịp. Một lệnh limit thông thường ngồi trong sách đã khớp; một lệnh ảo — thì không.

  3. Quản lý trạng thái. Lệnh ảo sống trong bộ nhớ. Nếu tiến trình crash — lệnh bị mất. Giải pháp: lưu trữ bền vững (Redis, SQLite, file) với khôi phục khi khởi động lại.


6. Lệnh Có Điều Kiện/Thông Minh: Tổ Hợp Lệnh

Khi một lệnh không đủ, các trader kết hợp chúng thành các cấu trúc có điều kiện. Một số được hỗ trợ gốc trên sàn, số khác được triển khai theo cách lập trình.

OCO (One Cancels Other — Một Hủy Cái Kia)

Hai lệnh được liên kết: nếu một lệnh thực thi — lệnh kia tự động bị hủy. Ví dụ cổ điển: bạn đang ở vị thế long và muốn đặt cả take-profit và stop-loss. Lệnh nào kích hoạt trước — cái kia phải bị hủy.

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 (Lệnh Khung)

Một cấu trúc ba thành phần: lệnh vào chính + OCO để thoát (take-profit + stop-loss). Về cơ bản, một vòng đời giao dịch hoàn chỉnh trong một lần gọi:

  1. Vào lệnh: lệnh limit mua
  2. Take-profit: lệnh limit bán (phía trên)
  3. Stop-loss: lệnh stop-market bán (phía dưới)

Khi lệnh vào khớp, TP và SL được đặt tự động. Khi một trong hai khớp — cái kia bị hủy.

Logic If-Then

Tùy chọn linh hoạt nhất — chuỗi lệnh với các điều kiện tùy ý:


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

Các cấu trúc như vậy không được bất kỳ sàn nào hỗ trợ gốc — chỉ triển khai lập trình. Đây là một trong những lý do tại sao các hệ thống algotrading không thể tránh khỏi việc phát triển lớp quản lý lệnh riêng của mình.


7. Cách Market Maker Sử Dụng Các Loại Lệnh Chuyên Biệt

Market making là một vũ trụ riêng, và bộ công cụ lệnh phù hợp với điều đó. Công việc của market maker là liên tục chào giá bid và ask, kiếm tiền từ spread, đồng thời giảm thiểu adverse selection (tình huống mà một trader có thông tin giao dịch chống lại bạn).

Post-only Là Bắt Buộc

Đối với market maker, post-only không phải là tùy chọn — nó là yêu cầu. Nếu lệnh của bạn vô tình thực thi như taker — thay vì nhận rebate maker, bạn phải trả phí taker. Trên hàng nghìn lệnh mỗi ngày, điều đó là thảm họa.

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

Lệnh Hidden (Ẩn)

Trên một số sàn (Kraken, Bitfinex), có sẵn lệnh ẩn — chúng không xuất hiện trên order book nhưng có trên sàn và tham gia matching. Sự đánh đổi: bạn trả phí taker ngay cả khi là maker, nhưng bạn có được sự ẩn danh.

Đối với market maker, đây là công cụ để quản lý tồn kho: nếu đã tích lũy một vị thế lớn, bạn có thể đặt lệnh ẩn để giải phóng nó mà không tiết lộ ý định của mình cho thị trường.

Lệnh Pegged (Gắn Giá)

Một lệnh được gắn vào bid/ask tốt nhất. Trên Coinbase Advanced Trade chẳng hạn, bạn có thể đặt lệnh tự động theo dõi bid tốt nhất và luôn ở đầu hàng chờ. Đây là lệnh đuổi giá gốc ở cấp độ sàn — nhưng nó không phổ biến rộng rãi.

Quản Lý Lệnh Hàng Loạt

Các market maker chuyên nghiệp sử dụng batch API để đồng thời hủy và đặt hàng chục lệnh trong một HTTP request duy nhất. Trên Binance đây là batchOrders, trên Bybitplace-batch-order. Điều này giảm độ trễ và áp lực giới hạn tốc độ.


8. Bảng So Sánh Các Loại Lệnh

Loại lệnh Đảm bảo thực thi Đảm bảo giá Hiển thị trên sách Gốc trên sàn Độ phức tạp triển khai
Market Không Không (tức thì) Không
Limit Không Không
Stop-market Có (sau kích hoạt) Không Không Không
Stop-limit Không Không (cho đến kích hoạt) Không
Trailing stop Có (sau kích hoạt) Không Không Một phần Thấp
Iceberg Không Một phần Một phần Trung bình
Post-only Không Không
TWAP Không (tùy thuộc vào slice) Không Một phần Không Trung bình
VWAP Không Không Một phần Không Cao
Chasing limit Cao hơn limit Một phần Có (lệnh hiện tại) Không Trung bình
Theo thời gian Tùy thuộc vào loại Tùy thuộc vào loại Không (cho đến thời điểm T) Không Thấp
Ảo/Tổng hợp Thấp hơn limit Tùy thuộc vào loại Không Không Trung bình
OCO Có (một trong hai) Một phần Có (cả hai) Một phần Trung bình
Bracket Một phần Hiếm Cao
Hidden Không Không Hiếm Không
Pegged Không Động Rất hiếm Cao (nếu lập trình)

Kết Luận: Lệnh Là Khối Xây Dựng Chiến Lược

Các loại lệnh không chỉ là "các nút trong giao diện." Chúng là các nguyên thủy cơ bản mà từ đó lớp thực thi của bất kỳ hệ thống giao dịch nào được xây dựng. Sự khác biệt giữa "chiến lược có lợi nhuận trong backtesting" và "chiến lược có lợi nhuận trong thực tế" thường nằm chính xác ở đây — trong cách bạn gửi lệnh đến sàn như thế nào.

Một vài điểm thực tiễn:

  1. Bắt đầu với lệnh tiêu chuẩn, đảm bảo bạn hiểu các sắc thái (stop-limit vs stop-market, IOC vs FOK). Hầu hết các lỗi xảy ra ở đây.
  2. Lệnh ảo là bắt buộc cho grid bot. Nếu bạn đặt hơn 50 lệnh — đừng gửi tất cả chúng đến sàn.
  3. Đuổi giá cần thiết khi tỷ lệ khớp lệnh quan trọng hơn giá. Nhưng luôn đặt max_chase_distance — nếu không bạn có thể trôi rất xa.
  4. Thực thi theo thời gian là đặc thù nhưng mạnh mẽ cho arbitrage funding và các chiến lược event-driven.
  5. Một lớp quản lý lệnh tùy chỉnh là không thể tránh khỏi cho bất kỳ hệ thống algotrading nghiêm túc nào. Các loại lệnh gốc của sàn là không đủ.

Nếu bạn đang xây dựng một hệ thống giao dịch và muốn đào sâu hơn — hãy xem các bài viết của chúng tôi về vị trí hàng chờ trong order book, các phương thức WebSocket trong CCXT, và arbitrage funding rate.


Tài Liệu Tham Khảo và Nguồn

Tuyên bố miễn trừ trách nhiệm: Thông tin được cung cấp trong bài viết này chỉ nhằm mục đích giáo dục và thông tin, không cấu thành lời khuyên về tài chính, đầu tư hoặc giao dịch. Giao dịch tiền mã hóa tiềm ẩn rủi ro thua lỗ đáng kể.

Tác Giả

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

Đi Trước Thị Trường

Đăng ký nhận bản tin của chúng tôi để có những thông tin chuyên sâu độc quyền về AI trading, phân tích thị trường và các cập nhật nền tảng.

Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất kỳ lúc nào.