QuestDB untuk Dagangan Algoritmik: Sambungan SQL yang Mengubah Permainan
Bahagian 2 daripada 3 — juga tersedia dalam RU · ZH
Penafian: Maklumat yang diberikan dalam artikel ini adalah untuk tujuan pendidikan dan maklumat sahaja dan tidak merupakan nasihat kewangan, pelaburan, atau dagangan. Berdagang mata wang kripto melibatkan risiko kerugian yang ketara.
Selamat datang ke Bahagian 2 siri QuestDB kami. Dalam Bahagian 1, kami membincangkan seni bina storan tiga peringkat dan prinsip reka bentuk skema. Kini kami akan mendalami set ciri yang benar-benar membezakan — sambungan SQL yang menjadikan QuestDB seolah-olah direka oleh pedagang, untuk pedagang.
SQL standard dicipta pada tahun 1970-an untuk data hubungan. Ia tidak mengenali masa sebagai konsep utama. Setiap operasi siri masa dalam PostgreSQL atau MySQL memerlukan penyelesaian panjang — fungsi tetingkap, cantuman lateral, CTE berlapis tiga. Sambungan QuestDB memampatkan pertanyaan berbilang perenggan ini kepada penyataan tunggal yang ekspresif.
Mari kita telusuri setiap satunya dengan contoh dagangan sebenar.

SAMPLE BY: Agregasi Baldi Masa Natif
Jika ada satu pertanyaan yang dijalankan oleh setiap sistem dagangan lebih daripada yang lain, ia adalah agregasi OHLCV — menukar dagangan mentah kepada data lilin. Dalam SQL standard, anda akan menulis sesuatu seperti ini:
-- SQL Standard: panjang dan lambat
SELECT
date_trunc('minute', timestamp) AS bucket,
symbol,
(array_agg(price ORDER BY timestamp))[1] AS open,
max(price) AS high,
min(price) AS low,
(array_agg(price ORDER BY timestamp DESC))[1] AS close,
sum(quantity) AS volume
FROM trades
WHERE timestamp >= now() - interval '1 hour'
GROUP BY bucket, symbol
ORDER BY bucket;
Dalam QuestDB, ini menjadi:
SELECT timestamp, symbol,
first(price) AS open,
max(price) AS high,
min(price) AS low,
last(price) AS close,
sum(quantity) AS volume
FROM trades
WHERE timestamp IN today()
SAMPLE BY 1m;
Itulah sahaja. SAMPLE BY 1m memberitahu QuestDB untuk membaldi data ke dalam selang 1 minit, dan first() / last() adalah fungsi agregasi natif yang menghormati susunan masa dalam setiap baldi. Tiada date_trunc, tiada akrobatik array_agg, tiada GROUP BY eksplisit.
Selang yang tersedia adalah fleksibel: 1s, 5s, 15m, 1h, 1d, 7d — sebarang kombinasi unit masa. Untuk pasaran kripto yang tidak pernah tidur, anda boleh menyelaraskan ke sempadan kalendar dengan kesedaran zon waktu:
SAMPLE BY 1d ALIGN TO CALENDAR TIME ZONE 'UTC';
FILL: Menangani Jurang dalam Data
Pasaran sebenar mempunyai jurang — pasangan kecairan rendah mungkin tidak berdagang selama beberapa minit atau jam. SAMPLE BY menyokong beberapa strategi FILL:
-- Isikan dengan nilai sebelumnya (isian ke hadapan — biasa dalam kewangan)
SAMPLE BY 15m FILL(PREV);
-- Isikan dengan interpolasi linear
SAMPLE BY 15m FILL(LINEAR);
-- Isikan dengan pemalar
SAMPLE BY 15m FILL(0);
-- Tiada isian — kembalikan NULL (lalai)
SAMPLE BY 15m FILL(NONE);
FILL(PREV) adalah asas untuk papan pemuka dagangan — jika tiada dagangan berlaku dalam baldi 15 minit, bawa ke hadapan harga terakhir yang diketahui. FILL(LINEAR) lebih sesuai untuk isyarat berterusan seperti kadar pembiayaan atau kadar faedah.

ASOF JOIN: "Lakukan Apa yang Saya Maksudkan" untuk Penjajaran Data Pasaran
Ini adalah permata mahkota QuestDB, dan jika anda pernah bekerja dengan data pasaran, anda akan faham mengapa.
Masalah asas: anda mempunyai dagangan dalam satu jadual dan sebut harga (bid/ask) dalam jadual lain. Anda ingin mengetahui apakah sebut harga yang berlaku pada tepat saat setiap dagangan dilaksanakan. Dalam pangkalan data biasa, cap masa hampir tidak pernah sejajar dengan sempurna — dagangan pada 12:00:00.123 perlu dipadankan dengan sebut harga pada 12:00:00.098, bukan 12:00:00.201.
ASOF JOIN menyelesaikan ini dalam satu baris:
SELECT trades.*, quotes.bid, quotes.ask
FROM trades
ASOF JOIN quotes ON (symbol);
Bagi setiap baris dalam trades, QuestDB mencari baris dalam quotes dengan cap masa terkini yang kurang daripada atau sama dengan cap masa dagangan, dipadankan pada lajur symbol. Tiada subkueri berkorelasi. Tiada fungsi tetingkap. Tiada logik peringkat aplikasi.
Ini adalah asas Analisis Kos Transaksi (TCA) — membandingkan harga pelaksanaan anda dengan pasaran yang berlaku pada masa pelaksanaan. Dalam PostgreSQL, yang setara memerlukan LATERAL JOIN dengan ORDER BY dan LIMIT 1 untuk setiap baris, yang lebih perlahan beberapa darjat magnitud pada set data besar.
TOLERANCE: Mencegah Cantuman Data Lapuk
Berikut adalah kehalusan yang memisahkan pelaksanaan mainan daripada sistem pengeluaran. Bagaimana jika sebut harga untuk aset yang jarang didagangkan berusia 5 minit pada masa dagangan? Dalam pasaran yang tidak menentu, sebut harga berusia 5 minit adalah pada dasarnya sampah. ASOF JOIN lalai masih akan menggunakannya — ia mencari padanan terkini, tidak kira betapa lapuknya.
Klausa TOLERANCE QuestDB membetulkan ini:
SELECT trades.*, quotes.bid, quotes.ask
FROM trades
ASOF JOIN quotes ON (symbol)
TOLERANCE 1s;
Kini, jika tiada sebut harga yang sepadan wujud dalam 1 saat daripada dagangan, cantuman mengembalikan NULL dan bukannya data lapuk. Ini adalah kritikal untuk TCA yang tepat dan untuk sebarang analitik di mana kesegaran data penting.
Sebagai bonus, TOLERANCE boleh meningkatkan prestasi pertanyaan dengan ketara. Tanpanya, enjin mungkin mengimbas jauh ke belakang ke dalam jadual sebut harga mencari padanan. Dengan TOLERANCE, ia boleh menamatkan imbasan ke belakang lebih awal apabila rekod terlalu lama untuk layak.
LT JOIN dan SPLICE JOIN
Dua variasi yang perlu diketahui: LT JOIN adalah seperti ASOF JOIN tetapi dipadankan sebelum cap masa (tidak pernah sama). Ini berguna apabila anda perlu mengelakkan berat sebelah ke hadapan dalam ujian balik — anda mahukan sebut harga yang wujud sebelum dagangan anda, bukan yang tiba pada mikrosaat yang sama.
SPLICE JOIN adalah ASOF penuh dalam kedua-dua arah: bagi setiap rekod dalam jadual kiri ia mencari rekod jadual kanan yang berlaku, dan bagi setiap rekod dalam jadual kanan ia mencari rekod jadual kiri yang berlaku. Hasilnya adalah garis masa bergabung dan diselingi daripada kedua-dua sumber data. Ini amat berguna untuk mencipta garis masa acara bersatu daripada berbilang strim data.
HORIZON JOIN: Analisis Selepas Dagangan dalam Satu Pertanyaan
Diperkenalkan dalam QuestDB 9.3.3, HORIZON JOIN dibina khas untuk analisis markout — asas penilaian kualiti pelaksanaan dan penyelidikan mikrostruktur pasaran.
Soalan yang dijawabnya: "Selepas dagangan dilaksanakan, bagaimana harga berkembang dalam N saat berikutnya?" Secara tradisinya, ini memerlukan cantuman diri, UNION ALL merentasi berbilang pertanyaan ASOF, atau menolak logik ke kod aplikasi. HORIZON JOIN memampatkan semuanya:
SELECT h.offset / 1000000 AS offset_sec,
avg(mid.price - fill.price) AS avg_markout
FROM fills
HORIZON JOIN mid_prices ON (symbol)
RANGE BETWEEN 0 AND 60s STEP 1s;
Ini mengira pergerakan harga purata pada selang 1 saat sehingga 60 saat selepas setiap isian. Enjin mengendalikan pengiraan offset masa, pemadanan ASOF pada setiap titik ufuk, dan agregasi — semua dalam satu laluan.
Untuk ufuk tidak seragam — atau untuk melihat sebelum acara — gunakan sintaks LIST:
HORIZON JOIN mid_prices ON (symbol)
LIST (-5s, -1s, 0, 1s, 5s, 30s, 60s);
Ini memberi anda lengkung markout pra dan pasca dagangan. Digabungkan dengan penapisan mengikut tempat, strategi, atau saiz pesanan, anda boleh membina kerangka kualiti pelaksanaan yang lengkap dalam pangkalan data.
Buku masakan QuestDB merangkumi lima corak analisis pasca dagangan yang dibina di atas HORIZON JOIN: analisis gelinciran (pelaksanaan vs. harga pertengahan), lengkung markout, kekurangan pelaksanaan (penguraian Perold), pemarkahan tempat untuk penghalaan pesanan pintar, dan pengesanan ketoksikan aliran (VPIN). Setiap satunya berjalan pada demo langsung mereka dengan data sebenar.
WINDOW JOIN: Mengkorelasikan Acara dengan Data Sekeliling
WINDOW JOIN diperkenalkan dalam QuestDB 9.3. Ia membolehkan setiap baris daripada jadual utama dicantumkan dengan tetingkap masa baris daripada jadual lain, dengan agregasi dikira ke atas baris yang sepadan.
Pertimbangkan senario dagangan FX di mana anda ingin mengkorelasikan setiap dagangan dengan harga bid dan ask purata dalam 10 saat selepas dagangan:
SELECT
trades.timestamp,
trades.symbol,
trades.price,
avg(quotes.bid) AS avg_bid,
avg(quotes.ask) AS avg_ask
FROM trades
WINDOW JOIN quotes ON (symbol)
RANGE BETWEEN 0 AND 10s FOLLOWING
INCLUDE PREVAILING;
Klausa INCLUDE PREVAILING memastikan anda mendapat harga terkini walaupun tiada padanan tepat pada sempadan tetingkap. Ini menghapuskan halaman subkueri yang biasanya diperlukan oleh analitik ufuk pendek.
Kes penggunaan dalam dagangan: mengira keadaan pasaran purata sekitar setiap pelaksanaan, mengesan tingkah laku harga luar biasa dalam tetingkap masa sebelum pesanan besar, mengkorelasikan acara IoT/infrastruktur (lonjakan latensi rangkaian) dengan kualiti pelaksanaan.
LATEST ON: Keadaan Semasa Segera
Sambungan yang kelihatan mudah tetapi sangat berguna. LATEST ON mengembalikan baris terakhir bagi setiap nilai lajur pembahagian:
SELECT * FROM trades
LATEST ON timestamp PARTITION BY symbol, side
ORDER BY timestamp DESC;
Ini memberi anda dagangan terakhir bagi setiap pasangan (symbol, side) — pada dasarnya syot kilat "keadaan semasa" masa nyata. Dalam pangkalan data tradisional, ini memerlukan subkueri berkorelasi atau fungsi tetingkap dengan ROW_NUMBER().
Untuk papan pemuka dagangan yang menunjukkan harga terkini merentasi ratusan simbol, LATEST ON adalah serta-merta. Digabungkan dengan pandangan terwujud (yang akan kami bincangkan dalam Bahagian 3), ia menjadi asas untuk syot kilat portfolio sub-milisaat.
TWAP: Purata Berwajaran Masa Natif
Agregat twap(price, timestamp) ditambah dalam QuestDB 9.3.3. Tidak seperti VWAP yang memberi berat mengikut isipadu, TWAP memberi berat mengikut tempoh — setiap harga bertahan sehingga pemerhatian seterusnya, dan hasilnya adalah kawasan di bawah fungsi langkah dibahagi dengan jumlah masa.
SELECT symbol,
twap(price, timestamp) AS twap_value,
vwap(price, quantity) AS vwap_value
FROM trades
WHERE timestamp IN today()
SAMPLE BY 1h;
TWAP adalah penanda aras pelaksanaan standard untuk pesanan algoritmik. Memilikinya sebagai agregat natif yang menyokong GROUP BY selari dan SAMPLE BY dengan semua mod FILL bermakna anda tidak memerlukan sebarang integrasi sisi pelanggan — pengiraan berjalan sepenuhnya dalam enjin pertanyaan.
Fungsi Tetingkap: Asas Analisis Teknikal
QuestDB menyokong fungsi tetingkap SQL standard, yang membentuk tulang belakang pengiraan penunjuk teknikal:
-- Jalur Bollinger menggunakan pandangan OHLC terwujud
WITH stats AS (
SELECT timestamp, close,
AVG(close) OVER (
ORDER BY timestamp
ROWS BETWEEN 19 PRECEDING AND CURRENT ROW
) AS sma20,
AVG(close * close) OVER (
ORDER BY timestamp
ROWS BETWEEN 19 PRECEDING AND CURRENT ROW
) AS avg_close_sq
FROM trades_OHLC_15m
WHERE timestamp BETWEEN dateadd('h', -24, now()) AND now()
AND symbol = 'BTC-USDT'
)
SELECT timestamp,
sma20,
sma20 + 2 * sqrt(avg_close_sq - sma20 * sma20) AS upper_band,
sma20 - 2 * sqrt(avg_close_sq - sma20 * sma20) AS lower_band
FROM stats;
QuestDB 9.3.3 juga memperkenalkan klausa WINDOW standard SQL — takrifkan spesifikasi tetingkap sekali, rujuknya mengikut nama merentasi berbilang fungsi. Tidak perlu lagi mengulangi PARTITION BY dan ORDER BY yang sama dalam setiap ungkapan:
SELECT timestamp, symbol,
avg(price) OVER w AS avg_price,
stddev(price) OVER w AS std_price,
min(price) OVER w AS min_price,
max(price) OVER w AS max_price
FROM trades
WINDOW w AS (PARTITION BY symbol ORDER BY timestamp ROWS BETWEEN 99 PRECEDING AND CURRENT ROW);
Pertanyaan lebih bersih, kurang salin-tampal, prestasi pelaksanaan sama.
Corak Pertanyaan Dunia Sebenar
Izinkan saya menunjukkan beberapa corak yang sering muncul dalam sistem dagangan pengeluaran:
Korelasi Silang Aset
-- Korelasi jam dan harian bergulir antara ETH dan aset lain
WITH data AS (
SELECT ETHUSD.timestamp,
corr(ETHUSD.price, asset.price) AS corr
FROM ETHUSD
ASOF JOIN asset
SAMPLE BY 1m
)
SELECT timestamp,
avg(corr) OVER (ORDER BY timestamp
RANGE BETWEEN 1 HOUR PRECEDING AND CURRENT ROW) AS hourly_corr,
avg(corr) OVER (ORDER BY timestamp
RANGE BETWEEN 24 HOUR PRECEDING AND CURRENT ROW) AS daily_corr
FROM data;
RSI Merentasi Semua Simbol
-- RSI 14 hari untuk semua pasangan USDT
WITH gains_losses AS (
SELECT timestamp, symbol,
CASE WHEN close > lag(close) OVER (PARTITION BY symbol ORDER BY timestamp)
THEN close - lag(close) OVER (PARTITION BY symbol ORDER BY timestamp)
ELSE 0 END AS gain,
CASE WHEN close < lag(close) OVER (PARTITION BY symbol ORDER BY timestamp)
THEN lag(close) OVER (PARTITION BY symbol ORDER BY timestamp) - close
ELSE 0 END AS loss
FROM trades_latest_1d
WHERE symbol LIKE '%-USDT'
)
SELECT timestamp, symbol,
100 - 100 / (1 + avg_gain / NULLIF(avg_loss, 0)) AS rsi
FROM (
SELECT timestamp, symbol,
AVG(gain) OVER (PARTITION BY symbol ORDER BY timestamp
ROWS BETWEEN 13 PRECEDING AND CURRENT ROW) AS avg_gain,
AVG(loss) OVER (PARTITION BY symbol ORDER BY timestamp
ROWS BETWEEN 13 PRECEDING AND CURRENT ROW) AS avg_loss
FROM gains_losses
);
ATR (Julat Benar Purata)
-- ATR 14 tempoh daripada bar OHLC 15 minit
WITH tr AS (
SELECT timestamp, symbol,
GREATEST(
high - low,
ABS(high - lag(close) OVER (PARTITION BY symbol ORDER BY timestamp)),
ABS(low - lag(close) OVER (PARTITION BY symbol ORDER BY timestamp))
) AS true_range
FROM trades_OHLC_15m
)
SELECT timestamp, symbol,
AVG(true_range) OVER (PARTITION BY symbol ORDER BY timestamp
ROWS BETWEEN 13 PRECEDING AND CURRENT ROW) AS atr_14
FROM tr;
Kelebihan SQL Berbanding Kod Tersuai
Anda mungkin tertanya-tanya: mengapa mengira penunjuk ini dalam pangkalan data apabila anda boleh menarik data mentah ke dalam Python dan menggunakan ta-lib atau pandas?
Tiga sebab. Pertama, lokaliti data — memindahkan terabait data tik merentasi rangkaian untuk mengira purata bergulir adalah pembaziran. Pengiraan seharusnya berada di mana data berada. Kedua, selarian — enjin tervektorkan QuestDB dengan kompilasi JIT boleh memproses pertanyaan ini merentasi berbilang teras dengan arahan SIMD, selalunya lebih pantas daripada kod Python berbenang tunggal. Ketiga, konsistensi — apabila berbilang papan pemuka, strategi, dan sistem pemantauan semuanya memerlukan penunjuk yang sama, mempunyai satu sumber kebenaran dalam pangkalan data menghapuskan pepijat penyegerakan.
Walau bagaimanapun, QuestDB tidak cuba menggantikan keseluruhan tindanan analitik anda. Interop Parquet bermakna anda masih boleh menarik data sejarah terus ke dalam saluran paip ML anda tanpa melalui pangkalan data untuk beban kerja kelompok.
Yang Akan Datang dalam Bahagian 3
Dalam bahagian akhir, kami akan membincangkan pandangan terwujud untuk OHLC masa nyata (termasuk pandangan berperingkat pada berbilang kerangka masa), tatasusunan 2D untuk analitik buku pesanan natif, dan seni bina rujukan untuk platform dagangan algoritmik yang dikuasakan QuestDB secara lengkap.
Rujukan
@software{soloviov2025questdb_algotrading_p2,
author = {Soloviov, Eugen},
title = {QuestDB for Algorithmic Trading: SQL Extensions That Change the Game},
year = {2025},
url = {https://marketmaker.cc/ms/blog/post/questdb-algotrading-sql},
version = {0.1.0},
description = {Kajian mendalam tentang sambungan SQL siri masa QuestDB: SAMPLE BY, ASOF JOIN, HORIZON JOIN, WINDOW JOIN, LATEST ON, dan corak pertanyaan dagangan dunia sebenar.}
}
Pengarang
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.