Algoritmik İşlem için Bar Türleri ve Birleştirme Yöntemleri
Binance, TradingView veya herhangi bir borsa arayüzünde gördüğünüz her mum grafiği aynı şekilde oluşturulur: işlemleri sabit bir zaman penceresinde birleştirin — 1 dakika, 5 dakika, 1 saat — ve bir OHLCV barı üretin. Bu o kadar yaygındır ki çoğu işlemci bunu hiç sorgulamaz. Ancak algoritmik işlem için bar türü seçimi ve birleştirme yöntemi iki bağımsız karardır — çoğu sistem bunları birbiriyle karıştırır.
Bu makale mum oluşturmanın iki eksenini ayırır: ne tür bir bar oluşturduğunuz (17 tür) ve bunları daha yüksek zaman dilimlerine nasıl birleştirdiğiniz (3 yöntem). Kombinasyon, backtesting, canlı işlem ve sinyal üretimi için farklı özelliklere sahip 51 olası yapılandırma verir.
Ham işlemlerin standart mumlara dönüşümüne giriş için bkz. İşlem Mumları Açıklandı.
Özet
- Mum oluşturmanın iki bağımsız ekseni vardır: bar türü ve birleştirme yöntemi
- 17 temel bar türü: zaman, tick, hacim, dolar, Renko, aralık, oynaklık, Heikin-Ashi, Kagi, Line Break, P&F, tick dengesizliği (TIB), hacim dengesizliği (VIB), run, CUSUM, entropi, delta
- 3 birleştirme yöntemi: takvime hizalanmış, kayan pencere, adaptif kayan
- 17 × 3 = 51 olası kombinasyon, her biri farklı özelliklere sahip
- Çoğu sistem yalnızca tek bir kombinasyon kullanır: takvime hizalanmış zaman barları. Diğer 50'si kullanılmamış durumda.
- Pratik öneri: katmanlarda birden fazla kombinasyon kullanın — sinyaller için kayan zaman barları, piyasa yapısı için takvim zaman barları, mikroyapı için bilgi odaklı barlar
Mum Oluşturmanın İki Ekseni
Geleneksel görüş tüm bar türlerini düz bir listede sıralar: zaman barları, tick barları, hacim barları, Renko, vb. Bu yanıltıcıdır. Aslında iki dik seçim vardır:
Eksen 1 — Temel Bar Türü (17 tür): Yeni bir barın ne zaman kapanacağına nasıl karar veriyorsunuz? Sabit bir zaman aralığından sonra mı? N işlemden sonra mı? Bir fiyat hareketinin ardından mı? Bilgi içeriği değiştiğinde mi? Bu "bir bar"ın ne anlama geldiğini belirler.
Eksen 2 — Birleştirme Yöntemi (3 yöntem): Temel barları daha yüksek zaman dilimi mumlarında nasıl birleştiriyorsunuz? Takvim sınırlarına hizalayın mı (00:00, 01:00, ...)? Son N barın kayan penceresini mi kullanın? Pencere boyutunu oynaklığa uyarlayın mı?
Bu iki eksen bağımsızdır. Şunlara sahip olabilirsiniz:
- Takvime hizalanmış tick barları — 14:00 ile 14:59 arasında kapanan tick barlarını tek bir saatlik muma birleştirin
- Kayan hacim barları — ne zaman kapandıklarına bakılmaksızın son 24 hacim barını alın
- Adaptif delta barları — delta barları üzerinde oynaklık odaklı bir pencere kullanın
Standart "1 saatlik mum", bu 17×3 matrisinde yalnızca bir noktadır: zaman barları + takvim hizalaması. Diğer her kombinasyon değerlendirmeye değer bir alternatiftir.
1. Zaman Barları (Standart)
Eşitsiz bilgi yoğunluğu: katı zaman sınırları, 200 işlemlik sakin saatleri 50.000 işlemlik duyuru saatleriyle aynı şekilde değerlendirir.
Varsayılan. Sabit bir zaman aralığından sonra yeni bir bar oluşur: 1 dakika, 5 dakika, 1 saat. Her borsa bunları doğal olarak sağlar.
Özellikler:
- Asya seansı sırasında (00:00–08:00 UTC), 1 saatlik bir mum 200 işlem içerebilir. Bir Binance listeleme duyurusu sırasında, aynı pencere 50.000 işlem içerebilir. Zaman barları her ikisini de eşdeğer olarak değerlendirir. Bu tür aktivite artışlarını tespit etmek bot koruması için kritiktir — bkz. İşlem Botları için Anomali Tespiti.
- Tüm piyasa katılımcıları aynı mum sınırlarını görür — bir Schelling noktası. Bu durum zaman barlarını kalabalık davranışını analiz etmek için vazgeçilmez kılar.
- Kısmi mumlarda hesaplanan göstergeler (yeniden başlatmanın ardından) anlamsız değerler üretir.
from datetime import datetime
def time_until_valid_hourly_candle():
"""Yeniden başlatmanın ardından ilk tam saatlik muma kadar geçen süre."""
now = datetime.utcnow()
minutes_into_hour = now.minute
seconds_into_minute = now.second
wait_seconds = (60 - minutes_into_hour) * 60 - seconds_into_minute
wait_seconds += 3600
return wait_seconds
2–4. Aktivite Tabanlı Barlar
Tick, hacim ve dolar barları: bar sınırlarını saatin değil piyasa katılımının belirlemesinin üç yolu.
Sabit zaman aralıklarında örnekleme yapmak yerine, sabit miktarda piyasa aktivitesinin ardından örnekleme yapın. Bu, günün saatinden bağımsız olarak yaklaşık eşit "bilgi içeriğine" sahip barlar üretir.
2. Tick Barları
Her N işlemden (tickten) sonra yeni bir bar oluşur. Yüksek aktivite dönemlerinde barlar hızla oluşur. Sakin dönemlerde tek bir bar saatler sürebilir.
from collections import deque
from dataclasses import dataclass
@dataclass
class OHLCV:
timestamp: int
open: float
high: float
low: float
close: float
volume: float
class TickBarGenerator:
"""
Her `threshold` işlemde bir yeni bar üretir.
Her bar eşit sayıda piyasa "görüşü" içerir.
"""
def __init__(self, threshold: int = 1000):
self.threshold = threshold
self.trades: list[tuple[float, float]] = [] # (fiyat, miktar)
self.bars: list[OHLCV] = []
def on_trade(self, timestamp: int, price: float, qty: float):
self.trades.append((price, qty))
if len(self.trades) >= self.threshold:
self._close_bar(timestamp)
def _close_bar(self, timestamp: int):
prices = [t[0] for t in self.trades]
volumes = [t[1] for t in self.trades]
bar = OHLCV(
timestamp=timestamp,
open=prices[0],
high=max(prices),
low=min(prices),
close=prices[-1],
volume=sum(volumes),
)
self.bars.append(bar)
self.trades = []
return bar
Avantajlar: Piyasa aktivitesine doğal olarak uyum sağlar. Tick barlarından elde edilen getiriler, zaman barı getirilerine kıyasla normal dağılıma daha yakın olma eğilimindedir — bu özellik birçok istatistiksel modelin performansını iyileştirir.
Dezavantajlar: Ham işlem akışı gerektirir (tüm veri sağlayıcılarından tarihsel veriler için mevcut değildir). Bar zamanlaması öngörülemezidir — "sonraki bar X'te kapanacak" diyemezsiniz.
3. Hacim Barları
N sözleşme (veya kriptoda coin) işlem gördükten sonra yeni bir bar oluşur. Tick barlarına benzer ancak işlem büyüklüğüne göre ağırlıklandırılmıştır — tek bir 100 BTC'lik işlem, 1 BTC'lik işleme göre 100 kat daha fazla katkıda bulunur.
class VolumeBarGenerator:
"""
Her `threshold` birim hacimde bir yeni bar üretir.
İşlem büyüklüğünü normalize eder: büyük bir emir ≠ küçük bir emir.
"""
def __init__(self, threshold: float = 100.0):
self.threshold = threshold
self.accumulated_volume = 0.0
self.trades: list[tuple[int, float, float]] = [] # (zaman damgası, fiyat, miktar)
self.bars: list[OHLCV] = []
def on_trade(self, timestamp: int, price: float, qty: float):
self.trades.append((timestamp, price, qty))
self.accumulated_volume += qty
if self.accumulated_volume >= self.threshold:
self._close_bar()
def _close_bar(self):
prices = [t[1] for t in self.trades]
volumes = [t[2] for t in self.trades]
bar = OHLCV(
timestamp=self.trades[-1][0],
open=prices[0],
high=max(prices),
low=min(prices),
close=prices[-1],
volume=sum(volumes),
)
self.bars.append(bar)
self.accumulated_volume = 0.0
self.trades = []
return bar
4. Dolar Barları
Sabit bir nominal değer (USD/USDT cinsinden) el değiştirdikten sonra yeni bir bar oluşur. Aktivite tabanlı barların en sağlamlısıdır çünkü hem işlem sayısını hem de fiyat düzeyini normalize eder.
Düşünün: ETH, 1.000'a çıkarsa, 10.000'da 2,5 ETH, 1.000$'da ise 10 ETH gerekir. Hacim barları bunları farklı şekilde değerlendirirdi; dolar barları ise her ikisini de aynı şekilde değerlendirir.
class DollarBarGenerator:
"""
Her `threshold` dolar (USDT) nominal hacimde bir yeni bar üretir.
En sağlam normalleştirme: fiyat düzeyinden bağımsız.
Lopez de Prado (2018), çoğu nicel uygulama için
dolar barlarını varsayılan olarak önerir.
"""
def __init__(self, threshold: float = 1_000_000.0):
self.threshold = threshold
self.accumulated_dollars = 0.0
self.trades: list[tuple[int, float, float]] = []
self.bars: list[OHLCV] = []
def on_trade(self, timestamp: int, price: float, qty: float):
self.trades.append((timestamp, price, qty))
self.accumulated_dollars += price * qty
if self.accumulated_dollars >= self.threshold:
self._close_bar()
def _close_bar(self):
prices = [t[1] for t in self.trades]
volumes = [t[2] for t in self.trades]
bar = OHLCV(
timestamp=self.trades[-1][0],
open=prices[0],
high=max(prices),
low=min(prices),
close=prices[-1],
volume=sum(volumes),
)
self.bars.append(bar)
self.accumulated_dollars = 0.0
self.trades = []
return bar
Eşik Değerini Seçmek
Aktivite tabanlı barlar için eşik değeri, günlük yaklaşık olarak değiştirdiğiniz zaman barlarıyla aynı sayıda bar üretmelidir. Binance'teki BTCUSDT için:
| Bar Türü | Tipik Eşik | ~Bar/Gün | Eşdeğer Zaman Dilimi |
|---|---|---|---|
| Tick | 1.000 işlem | ~1.400 | ~1d |
| Tick | 50.000 işlem | ~28 | ~1s |
| Hacim | 100 BTC | ~600 | ~2-3d |
| Hacim | 2.400 BTC | ~25 | ~1s |
| Dolar | 1M$ | ~1.400 | ~1d |
| Dolar | 50M$ | ~28 | ~1s |
Bu rakamlar yaklaşıktır ve piyasa rejimiyle birlikte önemli ölçüde değişir. Bir yükseliş veya çöküş sırasında aktivite tabanlı barlar normalden 5-10 kat daha fazla bar üretecektir — ki bu tam olarak amacıdır.
5–7. Fiyat Tabanlı Barlar
Renko tuğlaları, aralık barları ve oynaklık barları: yalnızca fiyat yeterince hareket ettiğinde örnekleme yapmak.
Fiyat tabanlı barlar hem zamanı hem de aktiviteyi göz ardı eder. Fiyat belirli bir miktarda hareket ettiğinde yalnızca yeni bir bar oluşur. Bu doğal olarak yatay gürültüyü filtreler ve trendleri vurgular.
5. Renko Barları
Kapanış fiyatı önceki tuğlanın kapanışından en az N birim hareket ettiğinde yeni bir Renko "tuğlası" oluşur. Tuğlalar her zaman aynı büyüklüktedir ve trend yönünün temiz bir görsel temsilini oluşturur.
class RenkoBarGenerator:
"""
Fiyat hareketine göre Renko tuğlaları üretir.
Temel özellik: yatay hareket sırasında yeni tuğla oluşmaz.
Güçlü trendler sırasında tuğlalar hızla oluşur.
"""
def __init__(self, brick_size: float = 10.0):
self.brick_size = brick_size
self.bricks: list[dict] = []
self.last_close: float | None = None
def on_price(self, timestamp: int, price: float, volume: float = 0.0):
if self.last_close is None:
self.last_close = price
return []
new_bricks = []
diff = price - self.last_close
num_bricks = int(abs(diff) / self.brick_size)
if num_bricks == 0:
return []
direction = 1 if diff > 0 else -1
for i in range(num_bricks):
brick_open = self.last_close
brick_close = self.last_close + direction * self.brick_size
brick = {
'timestamp': timestamp,
'open': brick_open,
'high': max(brick_open, brick_close),
'low': min(brick_open, brick_close),
'close': brick_close,
'volume': volume / num_bricks if num_bricks > 0 else 0,
'direction': direction,
}
new_bricks.append(brick)
self.last_close = brick_close
self.bricks.extend(new_bricks)
return new_bricks
Dinamik Renko, sabit bir tuğla boyutu yerine ATR (Average True Range) kullanarak oynaklığa otomatik olarak uyum sağlar.
6. Aralık Barları
Her bar sabit bir yüksek-düşük aralığına sahiptir. Aralık aşıldığında bar kapanır ve yenisi başlar. Renko'nun aksine, aralık barları fitiller içerir ve bar içi oynaklığı gösterebilir.
class RangeBarGenerator:
"""
Sabit yüksek-düşük aralığıyla barlar üretir.
Renko'dan farkı: aralık barları yalnızca tuğla yönünü değil,
aralık içindeki tam OHLC'yi gösterir. Daha fazla bilgi içerir.
"""
def __init__(self, range_size: float = 20.0):
self.range_size = range_size
self.current_high: float | None = None
self.current_low: float | None = None
self.current_open: float | None = None
self.current_volume: float = 0.0
self.current_start_ts: int = 0
self.bars: list[OHLCV] = []
def on_trade(self, timestamp: int, price: float, qty: float):
if self.current_open is None:
self.current_open = price
self.current_high = price
self.current_low = price
self.current_start_ts = timestamp
self.current_high = max(self.current_high, price)
self.current_low = min(self.current_low, price)
self.current_volume += qty
if self.current_high - self.current_low >= self.range_size:
bar = OHLCV(
timestamp=timestamp,
open=self.current_open,
high=self.current_high,
low=self.current_low,
close=price,
volume=self.current_volume,
)
self.bars.append(bar)
self.current_open = price
self.current_high = price
self.current_low = price
self.current_volume = 0.0
self.current_start_ts = timestamp
return bar
return None
Renko ve Aralık barları arasındaki temel fark: Renko yalnızca kapanış fiyatlarını izler ve yönü gösterir; aralık barları tam fiyat aralığını izler ve bar içindeki yapıyı gösterir. Aralık barları, stop-loss ve kar-al simülasyonu için gereken yüksek-düşük bilgisini koruduğundan genellikle algoritmik işlem için daha kullanışlıdır.
7. Oynaklık Barları
Bar içi oynaklık dinamik bir eşiğe ulaştığında yeni bir bar oluşur — örneğin son ATR'nin bir katı. Aralık barlarının aksine (sabit eşik), oynaklık barları piyasa koşullarına uyum sağlar.
class VolatilityBarGenerator:
"""
Bar içi oynaklık bir eşiğe ulaştığında barlar üretir.
Aralık barlarına benzer, ancak eşik kayan ATR ölçüsü kullanılarak
piyasa koşullarına uyum sağlar. Sakin piyasalarda barların kapanması
için daha az mutlak hareket gerekir; oynaklı piyasalarda daha fazla.
"""
def __init__(
self,
atr_period: int = 14,
atr_multiplier: float = 1.0,
initial_threshold: float = 20.0,
):
self.atr_period = atr_period
self.atr_multiplier = atr_multiplier
self.threshold = initial_threshold
self.recent_ranges: list[float] = []
self.current_open: float | None = None
self.current_high: float | None = None
self.current_low: float | None = None
self.current_volume: float = 0.0
self.bars: list[OHLCV] = []
def on_trade(self, timestamp: int, price: float, qty: float):
if self.current_open is None:
self.current_open = price
self.current_high = price
self.current_low = price
self.current_high = max(self.current_high, price)
self.current_low = min(self.current_low, price)
self.current_volume += qty
intra_bar_range = self.current_high - self.current_low
if intra_bar_range >= self.threshold:
bar = OHLCV(
timestamp=timestamp,
open=self.current_open,
high=self.current_high,
low=self.current_low,
close=price,
volume=self.current_volume,
)
self.bars.append(bar)
self.recent_ranges.append(intra_bar_range)
if len(self.recent_ranges) > self.atr_period:
self.recent_ranges = self.recent_ranges[-self.atr_period:]
if len(self.recent_ranges) >= self.atr_period:
avg_range = sum(self.recent_ranges) / len(self.recent_ranges)
self.threshold = avg_range * self.atr_multiplier
self.current_open = price
self.current_high = price
self.current_low = price
self.current_volume = 0.0
return bar
return None
8. Heikin-Ashi (Düzleştirilmiş Dönüşüm)
Heikin-Ashi: ortalama alma, gürültülü mumları düzgün trend sinyallerine dönüştürür — ancak kesin fiyat bilgisi pahasına.
Heikin-Ashi (Japonca'da "ortalama bar") bir bar türü değildir — herhangi bir temel bar türünün üzerine uygulanabilen bir dönüşümdür. Mevcut ve önceki bar değerlerini ortalayarak mumları düzleştirir:
- HA Kapanış = (Açılış + Yüksek + Düşük + Kapanış) / 4
- HA Açılış = (Önceki HA Açılış + Önceki HA Kapanış) / 2
- HA Yüksek = max(Yüksek, HA Açılış, HA Kapanış)
- HA Düşük = min(Düşük, HA Açılış, HA Kapanış)
Trendler, alt fitili olmayan (yükseliş trendi) veya üst fitili olmayan (düşüş trendi) aynı renkteki mum dizileri olarak görünür.
class HeikinAshiTransformer:
"""
Standart OHLCV mumlarını Heikin-Ashi mumlarına dönüştürür.
HERHANGİ bir bar türünün üzerine uygulanabilir: zaman barları,
hacim barları, kayan barlar vb. Bu bir örnekleme yöntemi değil,
bir dönüşümdür.
UYARI: HA fiyatları sentetiktir — gerçek işlem fiyatlarını temsil etmez.
Emir yerleştirmek veya K&Z hesaplaması için asla HA kapanışını kullanmayın.
HA'yı yalnızca sinyal üretimi için kullanın, ardından gerçek fiyatlardan
işlem yapın.
"""
def __init__(self):
self.prev_ha_open: float | None = None
self.prev_ha_close: float | None = None
def transform(self, candle: OHLCV) -> OHLCV:
ha_close = (candle.open + candle.high + candle.low + candle.close) / 4
if self.prev_ha_open is None:
ha_open = (candle.open + candle.close) / 2
else:
ha_open = (self.prev_ha_open + self.prev_ha_close) / 2
ha_high = max(candle.high, ha_open, ha_close)
ha_low = min(candle.low, ha_open, ha_close)
self.prev_ha_open = ha_open
self.prev_ha_close = ha_close
return OHLCV(
timestamp=candle.timestamp,
open=ha_open,
high=ha_high,
low=ha_low,
close=ha_close,
volume=candle.volume,
)
def transform_series(self, candles: list[OHLCV]) -> list[OHLCV]:
"""Tüm bir seriyi dönüştürür. Önce durumu sıfırlar."""
self.prev_ha_open = None
self.prev_ha_close = None
return [self.transform(c) for c in candles]
def ha_trend_signal(ha_candles: list[OHLCV], lookback: int = 3) -> int:
"""
Basit HA trend sinyali.
Döndürür:
+1: yükseliş (alt fitili olmayan N ardışık yeşil HA mumu)
-1: düşüş (üst fitili olmayan N ardışık kırmızı HA mumu)
0: belirgin trend yok
"""
if len(ha_candles) < lookback:
return 0
recent = ha_candles[-lookback:]
all_bullish = all(
c.close > c.open and abs(c.low - min(c.open, c.close)) < 1e-10
for c in recent
)
all_bearish = all(
c.close < c.open and abs(c.high - max(c.open, c.close)) < 1e-10
for c in recent
)
if all_bullish:
return 1
elif all_bearish:
return -1
return 0
Backtesting için kritik uyarı: Heikin-Ashi fiyatları sentetiktir. Backtestiniz giriş fiyatı olarak HA kapanışını kullanıyorsa sonuçlar yanlış olacaktır. HA'yı her zaman yalnızca sinyal üretimi için kullanın ve gerçek OHLC fiyatlarından işlem yapın.
HA ne zaman kullanışlıdır: Temiz "içeride kal" sinyallerine ihtiyaç duyan trend takip stratejileri. Yanlış kesişimleri filtrelemek için herhangi bir temel bar türü üzerine — zaman barları, hacim barları, dolar barları — HA uygulayın.
HA ne zaman zararlıdır: Kesin fiyat düzeylerine ihtiyaç duyan herhangi bir strateji — destek/direnç, emir defteri analizi, PIQ (Kuyruktaki Konum). Ortalama alma, kesin fiyat bilgisini yok eder.
9–11. Japon Geri Dönüş Grafikleri
Kagi, Line Break ve Nokta & Şekil: zamanı tamamen göz ardı eden ve yalnızca fiyat yapısına odaklanan grafikler.
Bunlar (Renko ile birlikte) zamanı tamamen atan ve fiyat yapısına odaklanan geleneksel Japon grafik yöntemleridir.
9. Kagi Grafikleri
Kagi grafikleri, fiyat belirli bir miktarda geri döndüğünde yön değiştiren dikey çizgilerden oluşur. Fiyat önceki bir yüksek noktayı kırdığında (kalın = "yang" = talep) veya önceki bir düşük noktayı kırdığında (ince = "yin" = arz) çizgiler kalınlık değiştirir.
class KagiChartGenerator:
"""
Fiyat geri dönüşlerine göre Kagi grafik çizgileri üretir.
Renko'nun aksine (sabit tuğla boyutu), Kagi her hareketin
gerçek büyüklüğünü izler ve kırılma noktalarında çizgi
kalınlığını değiştirir.
Zaman gürültüsü olmadan destek/direnç kırılmalarını ve
arz/talep değişimlerini tespit etmek için kullanışlıdır.
"""
def __init__(self, reversal_amount: float = 10.0):
self.reversal_amount = reversal_amount
self.lines: list[dict] = []
self.current_direction: int = 0 # 1=yukarı, -1=aşağı
self.current_price: float | None = None
self.extreme_price: float | None = None
self.prev_high: float | None = None
self.prev_low: float | None = None
self.line_type: str = 'yang' # 'yang' (kalın) veya 'yin' (ince)
def on_price(self, timestamp: int, price: float):
if self.current_price is None:
self.current_price = price
self.extreme_price = price
return None
if self.current_direction == 0:
if price - self.current_price >= self.reversal_amount:
self.current_direction = 1
self.extreme_price = price
elif self.current_price - price >= self.reversal_amount:
self.current_direction = -1
self.extreme_price = price
return None
if self.current_direction == 1:
if price > self.extreme_price:
self.extreme_price = price
if self.prev_high is not None and price > self.prev_high:
self.line_type = 'yang'
elif self.extreme_price - price >= self.reversal_amount:
line = {
'timestamp': timestamp,
'start': self.current_price,
'end': self.extreme_price,
'direction': 'up',
'type': self.line_type,
}
self.lines.append(line)
self.prev_high = self.extreme_price
self.current_price = self.extreme_price
self.extreme_price = price
self.current_direction = -1
if self.prev_low is not None and price < self.prev_low:
self.line_type = 'yin'
return line
else:
if price < self.extreme_price:
self.extreme_price = price
if self.prev_low is not None and price < self.prev_low:
self.line_type = 'yin'
elif price - self.extreme_price >= self.reversal_amount:
line = {
'timestamp': timestamp,
'start': self.current_price,
'end': self.extreme_price,
'direction': 'down',
'type': self.line_type,
}
self.lines.append(line)
self.prev_low = self.extreme_price
self.current_price = self.extreme_price
self.extreme_price = price
self.current_direction = 1
if self.prev_high is not None and price > self.prev_high:
self.line_type = 'yang'
return line
return None
10. Line Break Grafikleri
Line break grafikleri, yalnızca kapanış fiyatı önceki N çizginin (genellikle 3) yüksek veya düşüğünü aştığında yeni bir çizgi (kutu) çizer. Fiyat aralık içinde kalırsa yeni çizgi çizilmez.
class LineBreakGenerator:
"""
Line Break barları üretir (varsayılan olarak Three Line Break).
Yeni bir bar yalnızca kapanış son N barın yüksek veya düşüğünü
aştığında çizilir. Fiyatın çok barlı bir aralığı kırmasını gerektirerek
küçük gürültüyü filtreler.
'N' parametresi (line_count) hassasiyeti kontrol eder:
- N=2: daha hassas, daha fazla bar, daha fazla gürültü
- N=3: standart (Three Line Break)
- N=4+: daha az hassas, daha az bar, daha güçlü sinyaller
"""
def __init__(self, line_count: int = 3):
self.line_count = line_count
self.lines: list[dict] = []
def on_close(self, timestamp: int, close: float) -> dict | None:
if not self.lines:
self.lines.append({
'timestamp': timestamp,
'open': close,
'close': close,
'high': close,
'low': close,
'direction': 0,
})
return None
lookback = self.lines[-self.line_count:] if len(self.lines) >= self.line_count else self.lines
highest = max(l['high'] for l in lookback)
lowest = min(l['low'] for l in lookback)
last = self.lines[-1]
new_line = None
if close > highest:
new_line = {
'timestamp': timestamp,
'open': last['close'],
'close': close,
'high': close,
'low': last['close'],
'direction': 1,
}
elif close < lowest:
new_line = {
'timestamp': timestamp,
'open': last['close'],
'close': close,
'high': last['close'],
'low': close,
'direction': -1,
}
if new_line:
self.lines.append(new_line)
return new_line
return None
11. Nokta & Şekil Grafikleri
Nokta & Şekil (P&F) grafikleri, X sütunları (yükselen fiyatlar) ve O sütunları (düşen fiyatlar) kullanır. Sütun değişikliği genellikle 3 kutu büyüklüğünde bir geri dönüş gerektirir. Gürültüyü filtrelemek ve destek/direnci belirlemek için en eski yöntemlerden biri.
class PointAndFigureGenerator:
"""
Nokta & Şekil grafik verileri üretir.
X sütunu: box_size artışlarıyla yükselen fiyat.
O sütunu: box_size azalmalarıyla düşen fiyat.
Sütun değişimi: ters yönde reversal_boxes * box_size hareketi gerektirir.
Klasik ayar: ATR tabanlı box_size, reversal_boxes = 3.
"""
def __init__(self, box_size: float = 10.0, reversal_boxes: int = 3):
self.box_size = box_size
self.reversal_boxes = reversal_boxes
self.reversal_amount = box_size * reversal_boxes
self.columns: list[dict] = []
self.current_direction: int = 0
self.current_top: float | None = None
self.current_bottom: float | None = None
def on_price(self, timestamp: int, price: float):
if self.current_top is None:
box_price = self._round_to_box(price)
self.current_top = box_price
self.current_bottom = box_price
self.current_direction = 1
return None
events = []
if self.current_direction == 1:
while price >= self.current_top + self.box_size:
self.current_top += self.box_size
events.append(('X', self.current_top, timestamp))
if price <= self.current_top - self.reversal_amount:
col = {
'type': 'X',
'top': self.current_top,
'bottom': self.current_bottom,
'boxes': int((self.current_top - self.current_bottom) / self.box_size) + 1,
'timestamp': timestamp,
}
self.columns.append(col)
self.current_direction = -1
self.current_top = self.current_top - self.box_size
self.current_bottom = self._round_to_box(price)
events.append(('new_column', 'O', timestamp))
else:
while price <= self.current_bottom - self.box_size:
self.current_bottom -= self.box_size
events.append(('O', self.current_bottom, timestamp))
if price >= self.current_bottom + self.reversal_amount:
col = {
'type': 'O',
'top': self.current_top,
'bottom': self.current_bottom,
'boxes': int((self.current_top - self.current_bottom) / self.box_size) + 1,
'timestamp': timestamp,
}
self.columns.append(col)
self.current_direction = 1
self.current_bottom = self.current_bottom + self.box_size
self.current_top = self._round_to_box(price)
events.append(('new_column', 'X', timestamp))
return events if events else None
def _round_to_box(self, price: float) -> float:
return round(price / self.box_size) * self.box_size
Algoritmik işlemde Kagi, Line Break ve P&F: Öncelikle uzun vadeli trend tespiti ve destek/direnç belirleme için kullanılır. Bir filtre katmanı olarak — "Kagi grafiği yin modundayken uzun pozisyon sinyali alma" — işlemleri makro yapıyla hizalayarak değer katarlar.
12–14. Bilgi Odaklı Barlar
Dengesizlik barları, run barları, CUSUM filtreleri ve entropi barları: piyasa bize bir şeylerin değiştiğini söylediğinde örnekleme.
Marcos Lopez de Prado'nun Advances in Financial Machine Learning (2018) kitabından en sofistike yaklaşım. Temel içgörü: sabit aralıklarda değil, piyasaya yeni bilgi geldiğinde örnekleme yapın.
12. Tick Dengesizlik Barları (TIB)
Piyasa denge halindeyse, alıcı başlatmalı ve satıcı başlatmalı işlemler yaklaşık olarak dengelenmelidir. Dengesizlik beklentimizi aştığında bir şeyler değişmiştir. O anda bir bar örnekleyin.
Her işlem, tick kuralı kullanılarak alıcı başlatmalı (+1) veya satıcı başlatmalı (-1) olarak sınıflandırılır. Kümülatif dengesizlik θ'yı izleriz ve |θ| dinamik bir eşiği aştığında bir bar örnekleriz.
class TickImbalanceBarGenerator:
"""
Kümülatif tick dengesizliği beklenen düzeyleri aştığında barlar üretir
— yani "yeni bilgi" geldiğinde.
Lopez de Prado (2018), Bölüm 2'ye dayanmaktadır.
"""
def __init__(
self,
expected_ticks_init: int = 1000,
ewma_window: int = 100,
min_ticks: int = 100,
max_ticks: int = 50000,
):
self.expected_ticks_init = expected_ticks_init
self.ewma_window = ewma_window
self.min_ticks = min_ticks
self.max_ticks = max_ticks
self.theta = 0.0
self.prev_price: float | None = None
self.prev_sign = 1
self.trades: list[tuple[int, float, float]] = []
self.bar_lengths: list[int] = []
self.imbalances: list[float] = []
self.expected_ticks = float(expected_ticks_init)
self.expected_imbalance = 0.0
self.bars: list[OHLCV] = []
def _tick_sign(self, price: float) -> int:
"""İşlemi tick kuralıyla alış (+1) veya satış (-1) olarak sınıflandırır."""
if self.prev_price is None:
self.prev_price = price
return 1
if price > self.prev_price:
sign = 1
elif price < self.prev_price:
sign = -1
else:
sign = self.prev_sign
self.prev_price = price
self.prev_sign = sign
return sign
def on_trade(self, timestamp: int, price: float, qty: float):
sign = self._tick_sign(price)
self.theta += sign
self.trades.append((timestamp, price, qty))
threshold = self.expected_ticks * abs(self.expected_imbalance)
if threshold == 0:
threshold = self.expected_ticks_init * 0.5
if abs(self.theta) >= threshold and len(self.trades) >= self.min_ticks:
return self._close_bar()
if len(self.trades) >= self.max_ticks:
return self._close_bar()
return None
def _close_bar(self):
prices = [t[1] for t in self.trades]
volumes = [t[2] for t in self.trades]
bar = OHLCV(
timestamp=self.trades[-1][0],
open=prices[0],
high=max(prices),
low=min(prices),
close=prices[-1],
volume=sum(volumes),
)
self.bars.append(bar)
self.bar_lengths.append(len(self.trades))
self.imbalances.append(self.theta / len(self.trades))
if len(self.bar_lengths) >= 2:
alpha = 2.0 / (self.ewma_window + 1)
self.expected_ticks = (
alpha * self.bar_lengths[-1]
+ (1 - alpha) * self.expected_ticks
)
self.expected_ticks = max(
self.min_ticks,
min(self.max_ticks, self.expected_ticks)
)
self.expected_imbalance = (
alpha * self.imbalances[-1]
+ (1 - alpha) * self.expected_imbalance
)
self.theta = 0.0
self.trades = []
return bar
13. Hacim Dengesizlik Barları (VIB)
TIB'lerin uzantısı: her işlemi ±1 olarak saymak yerine işaretli hacimle ağırlıklandırın. 100 BTC'lik bir alış +100 katkıda bulunurken, 1 BTC'lik bir satış -1 katkıda bulunur. Birçok küçük işleme bölünmüş büyük bilgili emirleri yakalar.
class VolumeImbalanceBarGenerator:
"""
TIB'ler gibi, ancak işaretli tick yerine işaretli hacim kullanır.
100 BTC'lik alış sinyalinin 1 BTC'lik alış sinyalinden 100 kat daha
bilgilendirici olduğu içgörüsünü yakalar.
"""
def __init__(
self,
expected_ticks_init: int = 1000,
ewma_window: int = 100,
):
self.expected_ticks_init = expected_ticks_init
self.ewma_window = ewma_window
self.theta = 0.0
self.prev_price: float | None = None
self.prev_sign = 1
self.trades: list[tuple[int, float, float]] = []
self.bar_lengths: list[int] = []
self.volume_imbalances: list[float] = []
self.expected_ticks = float(expected_ticks_init)
self.expected_vol_imbalance = 0.0
self.bars: list[OHLCV] = []
def _tick_sign(self, price: float) -> int:
if self.prev_price is None:
self.prev_price = price
return 1
if price > self.prev_price:
sign = 1
elif price < self.prev_price:
sign = -1
else:
sign = self.prev_sign
self.prev_price = price
self.prev_sign = sign
return sign
def on_trade(self, timestamp: int, price: float, qty: float):
sign = self._tick_sign(price)
self.theta += sign * qty
self.trades.append((timestamp, price, qty))
threshold = self.expected_ticks * abs(self.expected_vol_imbalance)
if threshold == 0:
threshold = self.expected_ticks_init * 0.5
if abs(self.theta) >= threshold and len(self.trades) >= 10:
return self._close_bar()
return None
def _close_bar(self):
prices = [t[1] for t in self.trades]
volumes = [t[2] for t in self.trades]
bar = OHLCV(
timestamp=self.trades[-1][0],
open=prices[0],
high=max(prices),
low=min(prices),
close=prices[-1],
volume=sum(volumes),
)
self.bars.append(bar)
self.bar_lengths.append(len(self.trades))
self.volume_imbalances.append(self.theta / len(self.trades))
alpha = 2.0 / (self.ewma_window + 1)
if len(self.bar_lengths) >= 2:
self.expected_ticks = (
alpha * self.bar_lengths[-1] + (1 - alpha) * self.expected_ticks
)
self.expected_vol_imbalance = (
alpha * self.volume_imbalances[-1]
+ (1 - alpha) * self.expected_vol_imbalance
)
self.theta = 0.0
self.trades = []
return bar
Patlama Sorunu
Dengesizlik barlarıyla bilinen bir sorun: EWMA tabanlı eşik pozitif bir geri besleme döngüsüne girebilir. Çözüm: min_ticks ve max_ticks sınırlarıyla kısıtlama.
self.expected_ticks = max(
self.min_ticks, # Taban: 100 tickten az olamaz
min(
self.max_ticks, # Tavan: 50000 tickten fazla olamaz
new_expected_ticks
)
)
14. Run Barları
Run barları, mevcut yönsel serinin uzunluğunu izler — ardışık alış veya satışların en uzun dizisi. Büyük bilgili bir işlemci bir emri birçok küçük işleme böldüğünde, seri olağandışı derecede uzun hale gelir. Run barları bunu tespit eder.
class TickRunBarGenerator:
"""
Yönsel bir serinin uzunluğu beklentileri aştığında barlar üretir.
Lopez de Prado (2018), Bölüm 2'ye dayanmaktadır.
Dengesizlik barlarından farkı:
- Dengesizlik barları NET dengesizliği izler (alışlar eksi satışlar)
- Run barları MAKSİMUM seri uzunluğunu izler (ardışık alışlar VEYA satışlar)
"""
def __init__(
self,
expected_ticks_init: int = 1000,
ewma_window: int = 100,
min_ticks: int = 100,
max_ticks: int = 50000,
):
self.expected_ticks_init = expected_ticks_init
self.ewma_window = ewma_window
self.min_ticks = min_ticks
self.max_ticks = max_ticks
self.prev_price: float | None = None
self.prev_sign = 1
self.trades: list[tuple[int, float, float]] = []
self.buy_run = 0
self.sell_run = 0
self.max_buy_run = 0
self.max_sell_run = 0
self.bar_lengths: list[int] = []
self.max_runs: list[float] = []
self.expected_ticks = float(expected_ticks_init)
self.expected_max_run = 0.0
self.bars: list[OHLCV] = []
def _tick_sign(self, price: float) -> int:
if self.prev_price is None:
self.prev_price = price
return 1
if price > self.prev_price:
sign = 1
elif price < self.prev_price:
sign = -1
else:
sign = self.prev_sign
self.prev_price = price
self.prev_sign = sign
return sign
def on_trade(self, timestamp: int, price: float, qty: float):
sign = self._tick_sign(price)
self.trades.append((timestamp, price, qty))
if sign == 1:
self.buy_run += 1
self.sell_run = 0
else:
self.sell_run += 1
self.buy_run = 0
self.max_buy_run = max(self.max_buy_run, self.buy_run)
self.max_sell_run = max(self.max_sell_run, self.sell_run)
theta = max(self.max_buy_run, self.max_sell_run)
threshold = self.expected_ticks * self.expected_max_run if self.expected_max_run > 0 else self.expected_ticks_init * 0.3
if theta >= threshold and len(self.trades) >= self.min_ticks:
return self._close_bar()
if len(self.trades) >= self.max_ticks:
return self._close_bar()
return None
def _close_bar(self):
prices = [t[1] for t in self.trades]
volumes = [t[2] for t in self.trades]
bar = OHLCV(
timestamp=self.trades[-1][0],
open=prices[0],
high=max(prices),
low=min(prices),
close=prices[-1],
volume=sum(volumes),
)
self.bars.append(bar)
max_run = max(self.max_buy_run, self.max_sell_run) / len(self.trades)
self.bar_lengths.append(len(self.trades))
self.max_runs.append(max_run)
alpha = 2.0 / (self.ewma_window + 1)
if len(self.bar_lengths) >= 2:
self.expected_ticks = alpha * self.bar_lengths[-1] + (1 - alpha) * self.expected_ticks
self.expected_ticks = max(self.min_ticks, min(self.max_ticks, self.expected_ticks))
self.expected_max_run = alpha * self.max_runs[-1] + (1 - alpha) * self.expected_max_run
self.trades = []
self.buy_run = 0
self.sell_run = 0
self.max_buy_run = 0
self.max_sell_run = 0
return bar
Run barları hacim serileri ve dolar serileri olarak genişletilebilir.
15. CUSUM Filtre Barları
CUSUM (Kümülatif Toplam) filtresi, kümülatif getirileri izleyerek ne zaman örnekleme yapılacağını belirler. Dengesizlik barlarının aksine (ham işlemler üzerinde çalışır), CUSUM mevcut 1 dakikalık OHLCV verilerine uygulanabilir — tick verisi gerekmez.
class CUSUMFilterBarGenerator:
"""
Olay tabanlı örnekleme için simetrik CUSUM filtresi.
Lopez de Prado (2018), Bölüm 2.5'e dayanmaktadır.
Bollinger Bantlarına göre temel avantaj: CUSUM tetiklenmeden önce
eşik büyüklüğünde TAM bir seri gerektirir. Bollinger Bantları,
fiyat banda yakın seyrettiğinde tekrar tekrar tetiklenir.
1d OHLCV verilerine uygulanabilir — tick verisi gerekmez.
"""
def __init__(self, threshold: float = 0.01):
self.threshold = threshold
self.s_pos = 0.0
self.s_neg = 0.0
self.prev_price: float | None = None
self.buffer: list[OHLCV] = []
self.bars: list[OHLCV] = []
def on_candle_1m(self, candle: OHLCV) -> OHLCV | None:
self.buffer.append(candle)
if self.prev_price is None:
self.prev_price = candle.close
return None
import math
log_ret = math.log(candle.close / self.prev_price)
self.prev_price = candle.close
self.s_pos = max(0.0, self.s_pos + log_ret)
self.s_neg = min(0.0, self.s_neg + log_ret)
triggered = False
if self.s_pos > self.threshold:
self.s_pos = 0.0
triggered = True
if self.s_neg < -self.threshold:
self.s_neg = 0.0
triggered = True
if triggered and len(self.buffer) >= 2:
bars = self.buffer
bar = OHLCV(
timestamp=bars[-1].timestamp,
open=bars[0].open,
high=max(b.high for b in bars),
low=min(b.low for b in bars),
close=bars[-1].close,
volume=sum(b.volume for b in bars),
)
self.bars.append(bar)
self.buffer = []
return bar
return None
CUSUM + Üçlü Engel Yöntemi: Lopez de Prado'nun çerçevesinde, CUSUM olayları Üçlü Engel yöntemi için giriş noktaları olarak kullanılır — her olay stop-loss, kar-al ve süre sonu engelleriyle bir işlem tetikler. Bu tür olay odaklı stratejilerin güçlü doğrulaması için bkz. Walk-Forward Optimizasyonu ve Backtesting için Monte Carlo Bootstrap.
16. Entropi Barları
En teorik açıdan zarif yaklaşım: bar içi fiyat serisinin bilgi içeriği (Shannon entropisi) bir eşiği aştığında örnekleme yapın.
class EntropyBarGenerator:
"""
Bar içi getirilerin entropisi bir eşiği aştığında barlar üretir.
Shannon'ın bilgi teorisine dayanmaktadır: barlar "yeni bilgi"
geldiğinde örneklenir; bu mevcut bar içindeki getiri dağılımının
entropisi olarak ölçülür.
Bu teorik açıdan en "saf" bilgi odaklı bardır.
"""
def __init__(
self,
entropy_threshold: float = 2.0,
min_trades: int = 50,
n_bins: int = 10,
):
self.entropy_threshold = entropy_threshold
self.min_trades = min_trades
self.n_bins = n_bins
self.trades: list[tuple[int, float, float]] = []
self.bars: list[OHLCV] = []
def on_trade(self, timestamp: int, price: float, qty: float):
self.trades.append((timestamp, price, qty))
if len(self.trades) < self.min_trades:
return None
entropy = self._compute_entropy()
if entropy >= self.entropy_threshold:
return self._close_bar()
return None
def _compute_entropy(self) -> float:
import math
prices = [t[1] for t in self.trades]
if len(prices) < 2:
return 0.0
returns = [
math.log(prices[i] / prices[i-1])
for i in range(1, len(prices))
if prices[i-1] > 0
]
if not returns:
return 0.0
min_r = min(returns)
max_r = max(returns)
if max_r == min_r:
return 0.0
bin_width = (max_r - min_r) / self.n_bins
bins = [0] * self.n_bins
for r in returns:
idx = min(int((r - min_r) / bin_width), self.n_bins - 1)
bins[idx] += 1
total = sum(bins)
entropy = 0.0
for count in bins:
if count > 0:
p = count / total
entropy -= p * math.log2(p)
return entropy
def _close_bar(self):
prices = [t[1] for t in self.trades]
volumes = [t[2] for t in self.trades]
bar = OHLCV(
timestamp=self.trades[-1][0],
open=prices[0],
high=max(prices),
low=min(prices),
close=prices[-1],
volume=sum(volumes),
)
self.bars.append(bar)
self.trades = []
return bar
Pratik not: Entropi barları hesaplama açısından pahalıdır ve öncelikle araştırma ilgisi taşır — ancak ML tabanlı stratejiler için her bar yaklaşık olarak eşit "bilgi" içerdiğinden daha iyi istatistiksel özelliklere sahip özellikler üretirler.
17. Delta Barları (Emir Akışı)
Kümülatif delta: agresif alıcıların ve satıcıların gerçek zamanlı net kuvvetini ölçmek.
Delta barları, kümülatif deltaya — alış hacmi ile satış hacmi arasındaki kümülatif fark — dayalı olarak örnekleme yapar. Dengesizlik barlarının aksine (±1 tick işaretleri kullanan), delta barları gerçek hacim ağırlıklı emir akışını kullanır.
class DeltaBarGenerator:
"""
Kümülatif emir akışı deltasına göre barlar üretir.
Delta = Alış Hacmi - Satış Hacmi (agresör tarafına göre sınıflandırılmış).
Taraf sınıflandırmasıyla işlem düzeyinde veri gerektirir
(Binance aggTrades, Bybit trades vb. üzerinden mevcut)
"""
def __init__(self, threshold: float = 500.0):
self.threshold = threshold
self.cumulative_delta = 0.0
self.trades: list[tuple[int, float, float, int]] = []
self.bars: list[OHLCV] = []
def on_trade(self, timestamp: int, price: float, qty: float, is_buyer_maker: bool):
side = -1 if is_buyer_maker else 1
signed_qty = side * qty
self.cumulative_delta += signed_qty
self.trades.append((timestamp, price, qty, side))
if abs(self.cumulative_delta) >= self.threshold:
return self._close_bar()
return None
def _close_bar(self):
prices = [t[1] for t in self.trades]
volumes = [t[2] for t in self.trades]
bar = OHLCV(
timestamp=self.trades[-1][0],
open=prices[0],
high=max(prices),
low=min(prices),
close=prices[-1],
volume=sum(volumes),
)
bar.delta = self.cumulative_delta # type: ignore
bar.buy_volume = sum(t[2] for t in self.trades if t[3] == 1) # type: ignore
bar.sell_volume = sum(t[2] for t in self.trades if t[3] == -1) # type: ignore
self.bars.append(bar)
self.cumulative_delta = 0.0
self.trades = []
return bar
Delta diverjansı: En güçlü sinyallerden biri — kümülatif delta negatifken yükselen fiyat (satıcılar agresif ama fiyat yine de yükseliyor, bu limit alış emrinin absorpsiyonuna işaret eder). Dijital Parmak İzi: İşlemci Tanımlama makalesinde açıklanan davranışsal parmak izi yaklaşımıyla doğrudan ilgilidir. Avellaneda-Stoikov modelini kullanan piyasa yapıcılar için delta barları, envanter riski ve agresör baskısının gerçek zamanlı bir görünümünü sağlar.
Temel barların döngüsel bir tamponu: yeni veriler girer, eski veriler çıkar ve birleştirilmiş mum her zaman geçerlidir.
Birleştirme yöntemleri, temel barların daha yüksek zaman dilimi (HTF) mumlarına nasıl birleştirileceğini belirler. Bar türünden bağımsızdır — herhangi bir birleştirme yöntemini herhangi bir bar türüne uygulayabilirsiniz.
Yöntem A: Takvime Hizalanmış Birleştirme
Sabit bir takvim sınırı içinde düşen tüm temel barları birleştirin. "1 saatlik" mum, 14:00:00 ile 14:59:59 arasındaki tüm barları kapsar.
Özellikler:
- Tüm piyasa katılımcıları aynı sınırları görür — piyasa yapısı analizi, destek/direnç, PIQ tetikleyicileri için gerekli
- Soğuk başlangıç sorunu: yeniden başlatma sonrası kısmi mum
- Zaman barları için doğal (borsaların doğal olarak sağladığı budur)
- Zaman dışı barlar için de çalışır: "14:00 ile 15:00 arasında kapanan tüm hacim barları" = hacim barlarından takvime hizalanmış saatlik mum
Yöntem B: Kayan Pencere Birleştirmesi
Her yeni barda yeniden hesaplanan son N kapalı temel barı birleştirin. "1 saatlik" kayan mum = son 60 kapalı 1 dakikalık zaman barı, her dakika güncellenir.
Atomik birim kapalı temel bardır. Bu tasarım seçimi şunu sağlar:
- Soğuk başlangıç yok. N bardan sonra mum geçerlidir. Kısmi mum gürültüsü yoktur.
- Backtest eşliği. Canlı işlem, backtest motoruyla aynı atomik birimi kullanıyorsa sinyaller özdeştir.
- Basit doğrulama. Tek kural:
tampon dolmadıysa: atla.
import numpy as np
class RollingCandleAggregator:
"""
Kapalı temel barlardan kayan yüksek zaman dilimi mumları üretir.
HERHANGİ bir bar türüyle çalışır: zaman barları, tick barları,
hacim barları, dolar barları, delta barları — OHLCV çıktısı
üreten her şey.
Örnek: 1d zaman barlarıyla RollingCandleAggregator(window=60),
her dakika güncellenen "1s" mumu üretir.
Örnek: Hacim barlarıyla RollingCandleAggregator(window=24),
son 24 hacim barını kapsayan bir mum üretir.
"""
def __init__(self, window: int):
self.window = window
self.buffer: deque[OHLCV] = deque(maxlen=window)
def push(self, bar: OHLCV) -> OHLCV | None:
"""
Kapalı temel bar ekle. Yalnızca tampon dolduğunda
(= mum geçerli olduğunda) birleştirilmiş mumu döndürür.
"""
self.buffer.append(bar)
if len(self.buffer) < self.window:
return None
return self._aggregate()
def _aggregate(self) -> OHLCV:
bars = list(self.buffer)
return OHLCV(
timestamp=bars[-1].timestamp,
open=bars[0].open,
high=max(b.high for b in bars),
low=min(b.low for b in bars),
close=bars[-1].close,
volume=sum(b.volume for b in bars),
)
@property
def is_valid(self) -> bool:
return len(self.buffer) == self.window
Faz kayması ödünleşimi: :37'de başladıysanız kayan mumlar :37'de kapanır, herkes gibi :00'da değil. Bu, kalabalık tarafından görülebilen seviyelere bağlı stratejiler için önemlidir. Çözüm: ikisini de kullanın — piyasa yapısı için takvim, sinyaller için kayan.
Yöntem C: Adaptif Kayan Birleştirme
Kayan gibi, ancak pencere boyutu mevcut oynaklığa uyum sağlar. Sakin piyasalar → daha geniş pencere (daha fazla düzleştirme). Oynaklı piyasalar → daha dar pencere (daha hızlı tepki).
class AdaptiveRollingAggregator:
"""
Pencere boyutunun oynaklığa uyum sağladığı kayan pencere.
Herhangi bir temel bar türüyle çalışır. Oynaklık ölçüsü olarak
son barların ATR'sini kullanır.
Düşük oynaklık → daha geniş pencere (daha fazla düzleştirme, daha az sinyal)
Yüksek oynaklık → daha dar pencere (daha hızlı tepki)
"""
def __init__(
self,
base_window: int = 60,
min_window: int = 15,
max_window: int = 240,
atr_period: int = 14,
atr_base: float | None = None,
):
self.base_window = base_window
self.min_window = min_window
self.max_window = max_window
self.atr_period = atr_period
self.atr_base = atr_base
self.all_candles: deque[OHLCV] = deque(maxlen=max_window)
self.atr_values: deque[float] = deque(maxlen=atr_period * 2)
self.current_window = base_window
def push(self, bar: OHLCV) -> OHLCV | None:
self.all_candles.append(bar)
tr = bar.high - bar.low
self.atr_values.append(tr)
if len(self.atr_values) < self.atr_period:
return None
current_atr = sum(list(self.atr_values)[-self.atr_period:]) / self.atr_period
if self.atr_base is None and len(self.atr_values) >= self.atr_period * 2:
self.atr_base = sum(self.atr_values) / len(self.atr_values)
if self.atr_base is None or self.atr_base == 0:
return None
vol_ratio = current_atr / self.atr_base
self.current_window = int(self.base_window / vol_ratio)
self.current_window = max(self.min_window, min(self.max_window, self.current_window))
if len(self.all_candles) < self.current_window:
return None
bars = list(self.all_candles)[-self.current_window:]
return OHLCV(
timestamp=bars[-1].timestamp,
open=bars[0].open,
high=max(b.high for b in bars),
low=min(b.low for b in bars),
close=bars[-1].close,
volume=sum(b.volume for b in bars),
)
Her temel bar türü her birleştirme yöntemiyle birleştirilebilir. Bazı kombinasyonlar standarttır (takvim zaman barları = borsaların sağladığı), diğerleri egzotik ama güçlüdür.
Kombinasyon Örnekleri
| Temel Bar Türü | Takvim | Kayan | Adaptif |
|---|---|---|---|
| Zaman | Standart borsa mumları | Her zaman geçerli HTF, soğuk başlangıç yok | Oynaklık-adaptif zaman dilimi |
| Hacim | "Bu saatteki tüm hacim barları" | Son 24 hacim barı | Sakin piyasalarda daha geniş pencere |
| Dolar | Saatlik dolar barı toplamı | Son N dolar barı | Adaptif dolar pencereleri |
| Tick Dengesizliği | Saatlik dengesizlik toplamı | Son N dengesizlik olayı | Oynaklı rejimlerde hızlı tepki |
| Delta | Saatlik net emir akışı | Kayan delta anlık görüntüsü | Adaptif akış penceresi |
| Renko | "Bu saatteki tuğlalar" | Son N tuğla | Adaptif tuğla sayısı |
Hibrit Motor: Takvim + Kayan
Pratikte hem takvim hem de kayan birleştirmeyi eş zamanlı olarak istersiniz. Bellek yükü ihmal edilebilir — sembol başına zaman dilimi başına iki deque tamponu.
class HybridCandleEngine:
"""
Herhangi bir temel bar türü için hem takvime hizalanmış hem de
kayan mumları korur.
Takvim mumları: piyasa yapısı, destek/direnç, PIQ için.
Kayan mumlar: göstergeler, sinyal üretimi, girişler/çıkışlar için.
"""
def __init__(self):
self.rolling = {
'1h': RollingCandleAggregator(60),
'4h': RollingCandleAggregator(240),
}
self.calendar: dict[str, list[OHLCV]] = {
'1h': [],
'4h': [],
}
self._calendar_buffer: dict[str, list[OHLCV]] = {
'1h': [],
'4h': [],
}
def on_bar(self, bar: OHLCV):
"""Herhangi bir temel bar türünü işle — zaman, hacim, tick, delta vb."""
rolling_results = {}
for tf, agg in self.rolling.items():
rolling_results[tf] = agg.push(bar)
self._update_calendar(bar)
return rolling_results
def _update_calendar(self, bar: OHLCV):
from datetime import datetime
ts = datetime.utcfromtimestamp(bar.timestamp)
for tf, minutes in [('1h', 60), ('4h', 240)]:
self._calendar_buffer[tf].append(bar)
total_minutes = ts.hour * 60 + ts.minute
if (total_minutes + 1) % minutes == 0:
bars = self._calendar_buffer[tf]
if bars:
agg = OHLCV(
timestamp=bars[-1].timestamp,
open=bars[0].open,
high=max(b.high for b in bars),
low=min(b.low for b in bars),
close=bars[-1].close,
volume=sum(b.volume for b in bars),
)
self.calendar[tf].append(agg)
self._calendar_buffer[tf] = []
Zaman-Hacim Hibrit: Hacim Bölmeli Takvim
Özel bir birleştirme varyantı: hacim bir eşiği aştığında erken kapanan takvime hizalanmış mumlar. Aktivite artışlarına uyum sağlarken zaman senkronizasyonunu korur.
class TimeVolumeHybridGenerator:
"""
Hacim artışlarında bölünen takvime hizalanmış mumlar.
Kural: mumu takvim sınırında VEYA biriken hacim vol_threshold'u
aştığında kapat, hangisi önce gelirse.
Herhangi bir temel bar türüyle çalışır — hacim tetikleyicisi,
takvim hizalamasının üzerine ekstra bir bölme boyutu ekler.
"""
def __init__(
self,
interval_minutes: int = 60,
vol_threshold: float = 5000.0,
):
self.interval_minutes = interval_minutes
self.vol_threshold = vol_threshold
self.buffer: list[OHLCV] = []
self.accumulated_volume = 0.0
self.bars: list[OHLCV] = []
def on_bar(self, bar: OHLCV) -> OHLCV | None:
self.buffer.append(bar)
self.accumulated_volume += bar.volume
from datetime import datetime
ts = datetime.utcfromtimestamp(bar.timestamp)
total_minutes = ts.hour * 60 + ts.minute
at_boundary = (total_minutes + 1) % self.interval_minutes == 0
vol_spike = self.accumulated_volume >= self.vol_threshold
if at_boundary or vol_spike:
return self._close_bar(split_reason='volume' if vol_spike else 'time')
return None
def _close_bar(self, split_reason: str) -> OHLCV:
bars = self.buffer
bar = OHLCV(
timestamp=bars[-1].timestamp,
open=bars[0].open,
high=max(b.high for b in bars),
low=min(b.low for b in bars),
close=bars[-1].close,
volume=sum(b.volume for b in bars),
)
bar.split_reason = split_reason # type: ignore
bar.num_bars = len(bars) # type: ignore
self.bars.append(bar)
self.buffer = []
self.accumulated_volume = 0.0
return bar
Pratik Birleştirme: Kademeli Ön Yükleme
Kademeli ön yükleme: günlük mumları saatlik, saatlik mumları dakikalık mumlardan oluşturmak — API sınırlarını aşmak.
Borsalar ne kadar tarihsel veri sunduklarını sınırlar. Binance REST isteği başına ~1000 mum sunarken OKX 300 ile sınırlıdır. Kayan 1G muma (1440 dakika) ihtiyacınız varsa, her zaman yeterli 1d geçmiş elde edemezsiniz. WebSocket aracılığıyla işlemler ve emir defterlerinin gerçek zamanlı akışı için bkz. CCXT Pro WebSocket Yöntemleri.
Çözüm: kademeli birleştirme — her derinlikte mevcut en yüksek çözünürlükten daha yüksek zaman dilimlerini oluşturun, ardından birleştirin.
Kayan 1H mum:
├── 6 tamamlanmış 1G mum ← REST /klines?interval=1d'den getir
├── 1 kısmi gün:
│ ├── 23 tamamlanmış 1s mum ← REST /klines?interval=1h'den getir
│ └── 1 kısmi saat:
│ └── N tamamlanmış 1d mum ← REST /klines?interval=1m'den getir
└── Canlı: her yeni kapalı 1d mum tüm zinciri günceller
Bu çalışır çünkü OHLCV birleştirmesi birleştirilebilirdir: 1G mumun yüksek değeri 24 saatlik yüksek değerlerin maksimumudur, bu da 1440 dakikalık yüksek değerlerin maksimumudur.
Çok Borsa Sınırları
| Borsa | Maks. 1d Mum | Maks. 1s Mum | Önemli Aralıklar |
|---|---|---|---|
| Binance | 1.000 | 1.000 | 1d–1A, tam aralık |
| Bybit | 1.000 | 1.000 | 1–720, G/H/A |
| OKX | 300 | 300 | 1d–1A (daha kısıtlayıcı) |
| Gate.io | 1.000 | 1.000 | 10sn–30g |
Birleştirme Tutarlılık Kontrolü
REST API'dan alınan 1 saatlik mum, 60 adet 1 dakikalık mumdan hesaplayacağınız mumla eşleşmeyebilir. Her zaman doğrulayın:
def validate_aggregation(
candle_htf: OHLCV,
candles_ltf: list[OHLCV],
tolerance_pct: float = 0.001,
) -> dict[str, bool]:
agg = OHLCV(
timestamp=candles_ltf[-1].timestamp,
open=candles_ltf[0].open,
high=max(c.high for c in candles_ltf),
low=min(c.low for c in candles_ltf),
close=candles_ltf[-1].close,
volume=sum(c.volume for c in candles_ltf),
)
def close_enough(a: float, b: float) -> bool:
if a == 0 and b == 0:
return True
return abs(a - b) / max(abs(a), abs(b)) < tolerance_pct
return {
'open': close_enough(candle_htf.open, agg.open),
'high': close_enough(candle_htf.high, agg.high),
'low': close_enough(candle_htf.low, agg.low),
'close': close_enough(candle_htf.close, agg.close),
'volume': close_enough(candle_htf.volume, agg.volume),
}
Doğrulama tutarlı bir şekilde başarısız olursa, her zaman 1d'den kendiniz birleştirin — backtesting eşliği için borsanın HTF mumuna asla güvenmeyin.
Karşılaştırma Matrisi
Eksen 1: Temel Bar Türleri
| # | Bar Türü | Tetikleyici | Tick Verisi Gerekli | En İyi Kullanım |
|---|---|---|---|---|
| 1 | Zaman | Sabit aralık | Hayır | Piyasa yapısı, kalabalık davranışı |
| 2 | Tick | N işlem | Evet | ML özellikleri, eşit-görüş örneklemesi |
| 3 | Hacim | N birim işlem | Evet | Normalize edilmiş aktivite analizi |
| 4 | Dolar | N$ nominal | Evet | Çapraz varlık karşılaştırması |
| 5 | Renko | Fiyat ± N birim | Hayır | Trend takibi, gürültü filtreleme |
| 6 | Aralık | Yüksek-Düşük ≥ N | Evet | Kırılım tespiti |
| 7 | Oynaklık | Adaptif aralık | Evet | Rejim-adaptif analiz |
| 8 | Heikin-Ashi | Dönüşüm | Hayır | Trend onayı (sentetik fiyatlar!) |
| 9 | Kagi | Fiyat geri dönüşü | Hayır | Arz/talep yapısı |
| 10 | Line Break | N-çizgi kırılımı | Hayır | Makro trend filtresi |
| 11 | Nokta & Şekil | Kutu + geri dönüş | Hayır | Destek/direnç haritası |
| 12 | TIB | Tick dengesizliği | Evet | Bilgili akış tespiti |
| 13 | VIB | Hacim dengesizliği | Evet | Büyük emir tespiti |
| 14 | Run | Seri uzunluğu | Evet | Emir bölme tespiti |
| 15 | CUSUM | Kümülatif getiri | Hayır (1d kapanışları) | Yapısal kırılma olayları |
| 16 | Entropi | Shannon entropisi | Evet | ML araştırması, özellik saflığı |
| 17 | Delta | Emir akışı deltası | Evet (aggTrades) | Agresör akış analizi |
Eksen 2: Birleştirme Yöntemleri
| Yöntem | Hizalama | Soğuk Başlangıç | Faz Kayması | En İyi Kullanım |
|---|---|---|---|---|
| Takvim | Duvar saati | Kısmi bar riski | Yok (kalabalık hizalı) | Piyasa yapısı, PIQ, D/D |
| Kayan | N bar | Yok (ısınma sonrası) | Evet (:00'dan kaymış) | Göstergeler, sinyaller |
| Adaptif | Oynaklık odaklı N | ATR kalibrasyonundan sonra | Evet | Oynaklık-adaptif stratejiler |
Pratik Öneriler
Dört katmanlı mum mimarisi: kayan sinyaller, takvim yapısı, mikroyapı akışı ve trend filtreleri.
Backtest motorunuz 1d OHLCV verisinde çalışıyorsa:
- Kayan zaman barları — en basit yükseltme. Ek veri yok. Soğuk başlangıcı ortadan kaldırır.
- Hibrit (kayan + takvim) zaman barları — piyasa yapısı için takvim, sinyaller için kayan.
- CUSUM filtresi — 1d kapanışlarında çalışır, tick verisi gerekmez. "Yeterince hareket etti, ilginç."
Tick/işlem veriniz varsa:
- Dolar barları + kayan — nicel finans literatüründen önerilen varsayılan.
- Hacim dengesizlik barları + kayan — bilgili akışı tespit eder, önemli olaylar sırasında daha fazla örnekleme yapar.
- Delta barları + takvim — agresör tarafı sınıflandırmanız varsa, piyasayı kimin ittiğinin en doğrudan görünümü.
Filtreler olarak (Heikin-Ashi veya Line Break'i herhangi bir temel+birleştirme kombinasyonunun üzerine uygulayın):
- Kayan hacim barları üzerine Heikin-Ashi — aktivite normalize edilmiş veride temiz trend sinyalleri.
- Günlük takvim barları üzerine Line Break / Kagi — makro trend filtresi.
Marketmaker.cc için özellikle — katmanlı bir yaklaşım:
- Katman 1 (sinyaller): Göstergeler ve giriş/çıkış sinyalleri için zaman barlarının kayan birleştirmesi. Soğuk başlangıç yok, mükemmel backtest eşliği.
- Katman 2 (piyasa yapısı): Destek/direnç, saatlik kapanış analizi ve PIQ tetikleyicileri için takvime hizalanmış zaman barları.
- Katman 3 (mikroyapı): Ham işlem akışından bilgili akışı tespit etmek, emir bölmeyi ve büyük hareketleri öngörmek için hacim dengesizlik barları + delta barları. Emir akışı verilerinde davranışsal örüntü tanıma için ayrıca bkz. Dijital Parmak İzi: İşlemci Tanımlama.
- Katman 4 (trend filtresi): Sinyalleri makro yönle hizalamak için kayan barlar üzerine Heikin-Ashi dönüşümü veya 4s takvim kapanışları üzerine Line Break.
Sonuç
Mum oluşturma tek bir seçim değildir — iki bağımsız karardır:
-
Ne tür bir bar? Zaman, saat aralıklarını yakalar. Aktivite (tick, hacim, dolar) piyasa katılımını yakalar. Fiyat (Renko, aralık, oynaklık) hareketleri yakalar. Bilgi (dengesizlik, run'lar, CUSUM, entropi) yeni bilginin varışını yakalar. Emir akışı (delta) agresif baskıyı yakalar.
-
Daha yüksek zaman dilimlerine nasıl birleştirileceği? Takvim kalabalıkla hizalanır. Kayan soğuk başlangıcı ortadan kaldırır. Adaptif oynaklığa tepki verir.
Standart "Binance'ten 1 saatlik mum" bir 17×3 matrisinde yalnızca tek bir hücredir. Diğer 50 kombinasyon bunları uygulamaya istekli olan herkes için mevcuttur. Bir üretim sistemi için yanıt şudur: "karar motorunuzun her katmanı için doğru kombinasyonu seçin."
Atomik birim — kapalı temel bar — temel olmaya devam eder. Geri kalan her şey birleştirmedir.
Ayrıntılı verilerle backtest doğruluğu hakkında daha fazla bilgi için bkz. Adaptif Detaylandırma: Değişken Granülerlikle Backtest. Çok zaman dilimli stratejilerde gösterge ön hesaplamasının etkisi için bkz. Birleştirilmiş Parquet Önbelleği.
Faydalı Bağlantılar
- Lopez de Prado — Finansal Makine Öğrenmesinde Gelişmeler (2018)
- Easley, Lopez de Prado, O'Hara — Hacim Saati: Yüksek Frekanslı Paradigmaya İçgörüler (2012)
- mlfinlab — Bilgi odaklı barları uygulayan Python kütüphanesi
- Binance — Tarihsel Piyasa Verisi
- Apache Parquet — sütun depolama formatı
Atıf
@article{soloviov2026bartypes,
author = {Soloviov, Eugen},
title = {Bar Types and Aggregation Methods for Algorithmic Trading},
year = {2026},
url = {https://marketmaker.cc/tr/blog/post/beyond-time-bars-candle-construction},
description = {Mum oluşturmanın iki eksenli sınıflandırması: 17 temel bar türü × 3 birleştirme yöntemi = 51 kombinasyon; kripto algotrading için uygulama kodu ve pratik önerilerle.}
}
Yazarlar
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.