Jenis-Jenis Order dalam Algorithmic Trading: Dari Limit dengan Chasing hingga Virtual Orders
Ketika seorang pemula membuka terminal bursa, mereka melihat dua tombol: "Beli" dan "Jual." Ketika seorang algo trader membuka basis kodenya, mereka melihat dua puluh tujuh jenis order, tiga tingkat abstraksi, dan setumpuk edge case yang membuat mereka ingin menutup laptop dan pergi berjualan mentimun di pasar. Namun mentimun, sayangnya, tidak akan memungkinkan Anda menjalankan arbitrase funding rate pada pukul 3:59 UTC — jadi mari kita gali lebih dalam.
Dalam artikel ini, kita akan menelusuri seluruh perjalanan dari order bursa dasar hingga konstruksi virtual sintetis yang hanya ada di dalam sistem Anda dan tidak pernah muncul di order book. Harapkan TypeScript, Python, sedikit rasa sakit, dan secercah pencerahan.
1. Order Standar di Bursa: Fondasi yang Tidak Bisa Dilewati
Klasifikasi jenis order standar: dari market hingga iceberg
Sebelum membangun sesuatu yang kompleks, kita perlu memastikan bahwa kita benar-benar memahami blok pembangun dasarnya. Mengejutkan betapa banyak orang yang membingungkan stop-limit dan stop-market, lalu bertanya-tanya mengapa stop mereka "tidak terpicu" (spoiler: memang terpicu, tetapi order limit tidak terisi karena slippage).
Order Market
Jenis yang paling sederhana sekaligus paling berbahaya. Anda memberi tahu bursa: "Beli/jual sekarang juga, dengan harga berapa pun yang tersedia." Bursa mengambil likuiditas dari order book, dimulai dari harga terbaik. Jika volume pada level terbaik tidak mencukupi — harga bergeser lebih jauh.
Kapan digunakan: keluar dari posisi darurat, mengeksekusi sinyal di mana kecepatan lebih penting daripada harga.
Jebakan: di pasar yang tipis, order market untuk 100 BTC dapat menggerakkan harga beberapa persen. Backtesting yang memodelkan order market tanpa memperhitungkan dampak adalah khayalan semata.
Order Limit
Anda menentukan harga yang tepat. Order masuk ke order book dan menunggu sampai ada yang setuju dengan harga Anda. Jika harga order bid limit berada di atas harga pasar saat ini — order terisi segera (seperti order market, tetapi dengan harga maksimum yang terjamin).
Poin utama: order limit tidak menjamin eksekusi. Harga bisa mencapai level Anda lalu berbalik arah, meninggalkan Anda dalam antrian (selengkapnya di artikel kami tentang posisi antrian).
Stop-Market dan Stop-Limit
Di sinilah kebingungan dimulai. Kedua jenis ini adalah order "tidur" yang aktif ketika harga trigger (stop price) tercapai. Namun:
- Stop-market: saat terpicu, berubah menjadi order market. Menjamin eksekusi, tetapi bukan harga.
- Stop-limit: saat terpicu, berubah menjadi order limit. Menjamin harga (tidak lebih buruk dari yang ditentukan), tetapi bukan eksekusi.
Di pasar kripto yang bergejolak, stop-limit bisa "meleset" — harga menembus stop, order limit ditempatkan, tetapi pasar sudah melesat jauh. Anda tertinggal dengan order limit yang tidak terisi dan kerugian yang terus membesar. Inilah tepatnya mengapa stop-market lebih umum digunakan untuk stop-loss.
Trailing Stop
Stop yang "mengikuti" harga pada jarak yang ditetapkan. Harga naik — stop ikut naik. Harga turun — stop tetap di tempat. Berguna untuk melindungi keuntungan dalam strategi mengikuti tren.
Dukungan bursa: tidak semua bursa mendukung trailing stop native. Algo trader sering mengimplementasikannya secara programatik — ini memberikan lebih banyak kontrol atas parameter (callback rate, activation price, step size).
Order Iceberg
Order di mana hanya sebagian kecil dari total volume yang terlihat di order book. Anda ingin membeli 1.000 BTC, tetapi hanya menampilkan 10 di buku. Ketika 10 pertama terisi — 10 berikutnya muncul.
Mengapa: untuk menyembunyikan niat sejati Anda dari pasar. Order besar di buku memberi sinyal kepada semua orang bahwa "ada yang besar ingin membeli/menjual." Sebagai respons, algoritma HFT mulai front-running, dan harga bergerak menjauh dari Anda.
Peringatan: di banyak bursa kripto, order iceberg tidak didukung atau mudah dideteksi dari pola volume yang identik. Algoritma canggih mengacak ukuran bagian yang terlihat.
Parameter Time-in-force: GTC, GTD, IOC, FOK
Ini bukan jenis order terpisah melainkan parameter time-in-force — seberapa lama order bertahan:
| Parameter | Nama Lengkap | Perilaku |
|---|---|---|
| GTC | Good Till Cancelled | Hidup sampai dibatalkan. Standar bawaan |
| GTD | Good Till Date | Hidup sampai tanggal/waktu tertentu |
| IOC | Immediate or Cancel | Dieksekusi segera (penuh atau sebagian), sisanya dibatalkan |
| FOK | Fill or Kill | Dieksekusi hanya secara penuh dan segera. Jika tidak memungkinkan — dibatalkan seluruhnya |
IOC vs FOK: perbedaannya krusial. IOC bisa terisi sebagian — Anda ingin membeli 100 BTC, membeli 3, sisanya dibatalkan. FOK adalah 100 atau tidak sama sekali.
Post-only (Maker-only)
Order yang dijamin masuk ke order book sebagai maker dan tidak pernah dieksekusi sebagai taker. Jika pada saat penempatan harga akan menyebabkan eksekusi segera — bursa menolaknya (atau menyesuaikan harga, tergantung bursa).
Mengapa: biaya maker biasanya lebih rendah dari biaya taker (di Binance — 0,02% vs 0,04% untuk tingkat VIP). Bagi market maker yang menempatkan ribuan order per hari, perbedaan biaya adalah perbedaan antara untung dan rugi.
2. TWAP dan VWAP: Bagaimana Institusi Menyembunyikan Gajah di Order Book
Ketika sebuah hedge fund ingin membeli posisi senilai $50 juta, mereka tidak menempatkan satu order market. Mereka menggunakan algoritma eksekusi — algoritma yang membagi order besar menjadi banyak bagian kecil dan mengeksekusinya dari waktu ke waktu, meminimalkan dampak pasar.
TWAP (Time-Weighted Average Price)
Idenya sangat sederhana: bagi total volume menjadi bagian yang sama dan eksekusikan pada interval waktu yang sama.
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 lebih cerdas: ia memperhitungkan profil volume trading yang khas. Jika 30% volume harian biasanya diperdagangkan antara pukul 9:00 dan 10:00, VWAP akan mengeksekusi 30% order selama jendela waktu tersebut. Tujuannya adalah mendapatkan harga eksekusi rata-rata sedekat mungkin dengan VWAP pasar.
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)
Perbedaan TWAP vs VWAP: TWAP lebih sederhana dan lebih dapat diprediksi. VWAP memberikan harga rata-rata yang lebih baik tetapi memerlukan profil volume yang andal. Di pasar kripto, di mana volume bisa dimanipulasi (wash trading), profil VWAP perlu dibangun dengan hati-hati.
3. Limit dengan Chasing: Ketika Order Anda Tahu Cara Mengejar Harga
Chasing limit: order mengejar harga yang bergerak dengan agresivitas yang dapat dikonfigurasi
Sekarang semakin menarik. Order limit standar adalah entitas pasif: ia duduk di order book dan menunggu. Jika harga bergerak — order tetap tidak terisi. Bagi algo trader, ini sering kali tidak dapat diterima: sinyal masuk telah terpicu, tetapi posisi tidak terbangun karena pasar bergerak 0,1%.
Order limit dengan chasing adalah pembungkus programatik di sekitar order limit yang:
- Menempatkan order limit pada harga terbaik saat ini (atau dengan sedikit offset)
- Memantau harga melalui WebSocket
- Jika harga menjauh dari order — membatalkan dan menggantikannya lebih dekat ke harga saat ini
- Mengulangi sampai order terisi atau melebihi deviasi yang diizinkan
Parameter Utama
- chase_interval_ms — seberapa sering memeriksa dan mengganti order. 100ms — agresif, 1000ms — santai.
- max_chase_distance — deviasi maksimum dari harga awal sebelum order dibatalkan. Perlindungan terhadap mengejar pasar yang lari.
- aggression_level — seberapa dekat dengan harga pasar untuk menempatkan order limit.
0— pada bid/ask terbaik (pasif),1— menyilangkan spread (agresif, secara efektif menjadi taker). - chase_on_partial — apakah akan terus mengejar jika order terisi sebagian.
Implementasi 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));
}
}
Kapan Chasing Berbahaya
Chasing adalah alat yang ampuh, tetapi mudah berubah menjadi mesin kerugian:
- Spam cancel/replace. Setiap pembatalan dan penggantian adalah beban pada API. Bursa membatasi laju permintaan, dan chasing yang agresif dapat membuat kunci API Anda diblokir.
- Adverse selection. Jika harga lari dari Anda — pasar mungkin tahu sesuatu yang tidak Anda ketahui. Mengejar harga dalam situasi ini berarti membeli di puncak.
- Transisi Maker ke Taker. Dengan agresivitas tinggi Anda secara efektif membayar biaya taker, tetapi dengan penundaan (cancel + order baru). Terkadang lebih sederhana untuk langsung menempatkan order market.
4. Order Berbasis Waktu: Presisi Milidetik
Ada situasi di mana Anda perlu mengeksekusi order bukan "pada harga X" melainkan "pada waktu T." Terdengar aneh? Sebenarnya ini adalah seluruh kelas strategi tersendiri.
Kasus Penggunaan
Arbitrase funding rate. Pada futures perpetual, funding dibayarkan setiap 8 jam (00:00, 08:00, 16:00 UTC di Binance). Jika funding rate = +0,1%, Anda perlu short pada saat penyelesaian. Strategi: buka short beberapa detik sebelum penyelesaian, kumpulkan funding, tutup posisi. Timing sangat kritis — sedetik keterlambatan berarti funding yang terlewat.
Pembukaan/penutupan sesi. Pada pasar tradisional dan beberapa derivatif kripto, ada sesi tetap. Lelang pembukaan (NYSE, CME) adalah saat ketika likuiditas berada pada puncaknya. Menempatkan order 100ms sebelum lelang adalah keunggulan.
Eksekusi berbasis berita. Data inflasi dirilis pada waktu yang terjadwal. Algoritma mengurai angka dari umpan berita dan menempatkan order dalam 50ms. Di sini, eksekusi berbasis waktu dikombinasikan dengan logika berbasis peristiwa.
Implementasi
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);
Catatan penting: presisi order berbasis waktu tidak dibatasi oleh kode Anda, melainkan oleh latensi jaringan ke bursa. Jika ping Anda ke API adalah 50ms, bahkan busy-wait yang sempurna sekalipun akan memiliki delta 50ms. Untuk HFT serius, digunakan co-location — server secara fisik berada di sebelah mesin matching engine bursa.
5. Virtual/Sintetis Orders: Entitas Tak Terlihat dalam Sistem Anda
Virtual orders: order hanya ada di memori bot sampai trigger terpicu
Ini mungkin alat yang paling diremehkan dalam arsenal algo trader. Virtual order (juga dikenal sebagai synthetic order) adalah order yang hanya ada di dalam sistem Anda. Order ini tidak dikirim ke bursa sampai kondisi trigger terpenuhi (biasanya — harga mencapai level tertentu).
Cara Kerjanya
- Algoritma Anda memutuskan: "Saya ingin membeli BTC seharga $40.000"
- Alih-alih mengirim order limit ke bursa, ia membuat virtual order di memori
- Berlangganan ke stream harga WebSocket
- Ketika bid/ask mencapai $40.000 — mengirim order market atau limit nyata ke bursa
Mengapa Virtual Orders Penting
Tidak ada kebocoran informasi. Order Anda tidak terlihat di order book. Tidak ada yang tahu — baik trader lain, algoritma HFT, maupun bursa itu sendiri — tentang niat Anda sampai saat eksekusi. Ini secara fundamental menggeser keseimbangan kekuatan.
Perlindungan dari front-running. Di bursa kripto, terutama yang kurang transparan, ada kecurigaan wajar bahwa informasi tentang order limit besar dapat digunakan untuk front-running (bahkan ada penelitian tentang ini). Virtual orders menghilangkan risiko ini.
Grid bots. Grid bot klasik menempatkan grid 50-200 order pada level harga yang berbeda. Jika Anda mengirim semuanya ke bursa — itu 200 order di buku yang: (a) terlihat oleh semua orang, (b) menggunakan batas order di bursa (biasanya 200-300 order terbuka per akun), (c) jika harga bergerak tajam, semuanya terisi dan Anda berakhir dengan posisi besar. Virtual orders menyelesaikan ketiga masalah tersebut.
Menangkap pisau yang jatuh. Strategi: tempatkan virtual buy order pada level -5%, -10%, -15% di bawah harga saat ini. Jika pasar turun — order terpicu secara bertahap. Jika tidak turun — Anda tidak mengambil risiko apa pun dan tidak menggunakan slot order bursa.
Implementasi 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
);
}
}
Jebakan Virtual Orders
-
Celah latensi. Antara saat Anda melihat harga dan saat order nyata mencapai bursa, ada waktu yang berlalu. Di pasar yang bergejolak, harga bisa melesat dalam 20-100ms tersebut. Solusi: kirim order limit yang sedikit agresif (dengan buffer).
-
Fill yang terlewat. Jika harga "menembus" level Anda dalam satu tick (flash crash) lalu memantul kembali — Anda mungkin tidak bereaksi cukup cepat. Order limit biasa yang duduk di buku akan terisi; virtual order — tidak.
-
Manajemen state. Virtual orders hidup di memori. Jika proses crash — order hilang. Solusi: penyimpanan persisten (Redis, SQLite, file) dengan pemulihan saat restart.
6. Order Conditional/Smart: Kombinatorik Order
Ketika satu order tidak cukup, trader menggabungkannya menjadi konstruksi kondisional. Beberapa didukung secara native di bursa, lainnya diimplementasikan secara programatik.
OCO (One Cancels Other)
Dua order dihubungkan: jika satu dieksekusi — yang lain secara otomatis dibatalkan. Contoh klasik: Anda berada dalam posisi long dan ingin menetapkan take-profit dan stop-loss sekaligus. Yang mana pun yang terpicu lebih dulu — yang lain harus dibatalkan.
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
Konstruksi tiga komponen: order masuk utama + OCO untuk keluar (take-profit + stop-loss). Pada dasarnya, siklus hidup trading lengkap dalam satu panggilan:
- Entry: order limit buy
- Take-profit: order limit sell (di atas)
- Stop-loss: order stop-market sell (di bawah)
Ketika entry terisi, TP dan SL ditempatkan secara otomatis. Ketika salah satunya terisi — yang lain dibatalkan.
Logika If-Then
Opsi paling fleksibel — rantai order dengan kondisi arbitrer:
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},
},
]
}
]
Konstruksi seperti ini tidak didukung secara native oleh bursa mana pun — hanya implementasi programatik. Ini adalah salah satu alasan mengapa sistem algotrading pasti mengembangkan lapisan manajemen order mereka sendiri.
7. Bagaimana Market Maker Menggunakan Jenis Order Khusus
Market making adalah alam semesta tersendiri, dan perangkat order pun menyesuaikan. Tugas market maker adalah terus-menerus mengutip bid dan ask, menghasilkan dari spread, sambil meminimalkan adverse selection (situasi di mana trader terinformasi bertransaksi melawan Anda).
Post-only sebagai Keharusan
Bagi market maker, post-only bukan sebuah pilihan — ini adalah persyaratan. Jika order Anda secara tidak sengaja dieksekusi sebagai taker — alih-alih menerima rebate maker, Anda membayar biaya taker. Dalam ribuan order per hari, itu sangat merugikan.
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
Di beberapa bursa (Kraken, Bitfinex), tersedia hidden order — tidak muncul di order book tetapi ada di bursa dan berpartisipasi dalam matching. Komprominya: Anda membayar biaya taker meskipun sebagai maker, tetapi mendapatkan anonimitas.
Bagi market maker, ini adalah alat untuk manajemen inventaris: jika posisi besar telah terakumulasi, Anda dapat menempatkan hidden order untuk melepasnya tanpa mengungkapkan niat Anda kepada pasar.
Pegged orders
Order yang dipatok pada bid/ask terbaik. Di Coinbase Advanced Trade, misalnya, Anda dapat menempatkan order yang secara otomatis mengikuti bid terbaik dan selalu berada di depan antrian. Ini adalah chasing order native di tingkat bursa — tetapi jauh dari tersedia secara universal.
Manajemen order massal
Market maker profesional menggunakan batch API untuk secara bersamaan membatalkan dan menempatkan puluhan order dalam satu permintaan HTTP. Di Binance ini adalah batchOrders, di Bybit — place-batch-order. Ini mengurangi latensi dan tekanan rate limit.
8. Tabel Perbandingan Jenis Order
| Jenis Order | Jaminan Eksekusi | Jaminan Harga | Terlihat di Buku | Native di Bursa | Kompleksitas Implementasi |
|---|---|---|---|---|---|
| Market | Ya | Tidak | Tidak (segera) | Ya | Tidak ada |
| Limit | Tidak | Ya | Ya | Ya | Tidak ada |
| Stop-market | Ya (setelah trigger) | Tidak | Tidak | Ya | Tidak ada |
| Stop-limit | Tidak | Ya | Tidak (hingga trigger) | Ya | Tidak ada |
| Trailing stop | Ya (setelah trigger) | Tidak | Tidak | Sebagian | Rendah |
| Iceberg | Tidak | Ya | Sebagian | Sebagian | Sedang |
| Post-only | Tidak | Ya | Ya | Ya | Tidak ada |
| TWAP | Tidak (tergantung slice) | Tidak | Sebagian | Tidak | Sedang |
| VWAP | Tidak | Tidak | Sebagian | Tidak | Tinggi |
| Chasing limit | Lebih tinggi dari limit | Sebagian | Ya (order saat ini) | Tidak | Sedang |
| Berbasis waktu | Tergantung jenis | Tergantung jenis | Tidak (hingga waktu T) | Tidak | Rendah |
| Virtual/Sintetis | Lebih rendah dari limit | Tergantung jenis | Tidak | Tidak | Sedang |
| OCO | Ya (salah satu dari dua) | Sebagian | Ya (keduanya) | Sebagian | Sedang |
| Bracket | Ya | Sebagian | Ya | Jarang | Tinggi |
| Hidden | Tidak | Ya | Tidak | Jarang | Tidak ada |
| Pegged | Tidak | Dinamis | Ya | Sangat jarang | Tinggi (jika programatik) |
Kesimpulan: Order sebagai Blok Pembangun Strategi
Jenis-jenis order bukan sekadar "tombol di antarmuka." Mereka adalah primitif fundamental yang menjadi dasar lapisan eksekusi sistem trading apa pun. Perbedaan antara "strategi menguntungkan dalam backtesting" dan "strategi menguntungkan dalam produksi" sering kali terletak tepat di sini — pada bagaimana tepatnya Anda mengirim order ke bursa.
Beberapa poin praktis:
- Mulai dengan order standar, pastikan Anda memahami nuansanya (stop-limit vs stop-market, IOC vs FOK). Sebagian besar kesalahan terjadi di sini.
- Virtual orders adalah keharusan untuk grid bots. Jika Anda menempatkan lebih dari 50 order — jangan kirim semuanya ke bursa.
- Chasing diperlukan ketika tingkat fill lebih penting dari harga. Tetapi selalu tetapkan max_chase_distance — jika tidak, Anda bisa hanyut sangat jauh.
- Eksekusi berbasis waktu bersifat niche tetapi kuat untuk funding arb dan strategi berbasis peristiwa.
- Lapisan manajemen order kustom tidak dapat dihindari untuk sistem algotrading serius mana pun. Jenis order native bursa tidak cukup.
Jika Anda sedang membangun sistem trading dan ingin mendalaminya — lihat artikel kami tentang posisi antrian di order book, metode WebSocket di CCXT, dan arbitrase funding rate.
Referensi dan Sumber
- CCXT Library — pustaka terpadu untuk bekerja dengan bursa kripto, mendukung 100+ bursa
- Binance API Documentation — dokumentasi jenis order Binance
- Bybit API v5 — dokumentasi Bybit, termasuk 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 — materi tentang estimasi posisi antrian
- Trading Technologies (TT) — platform profesional dengan jenis order canggih
Penulis
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.