← Maqolalarga qaytish
March 22, 2026
5 daqiqa o'qish

Algoritmik treyding uchun bar turlari va agregatsiya usullari

Algoritmik treyding uchun bar turlari va agregatsiya usullari
#algotrading
#candles
#market microstructure
#Lopez de Prado
#order flow
#backtesting
#research
📖
Part 4 of 6 · Collection
Order Book & Market Microstructure

Binance, TradingView yoki istalgan birja interfeysida ko'rgan har qanday sham grafigi bir xil usulda quriladi: belgilangan vaqt oynasi ichida — 1 daqiqa, 5 daqiqa, 1 soat — savdolarni yig'ib, OHLCV bar hosil qilish. Bu shu qadar keng tarqalganki, ko'pchilik treyderlar buni hech qachon shubha ostiga olishmaydi. Ammo algoritmik treyding uchun bar turi va agregatsiya usulini tanlash — ikkita mustaqil qaror, va ko'pgina tizimlar ularni aralashtirib yuboradi.

Ushbu maqola sham qurilishining ikki o'qini ajratib ko'rsatadi: qanday turdagi bar qurasiz (17 turi) va ularni yuqori vaqt oralig'iga qanday agregatsiya qilasiz (3 usul). Kombinatsiya 51 ta mumkin bo'lgan konfiguratsiyani beradi, ularning har birining backtesting, jonli savdo va signal generatsiyasi uchun turli xususiyatlari bor.

Xom savdolar standart shamlarga qanday aylanishi haqida kirish uchun qarang Trading Candles Demystified.


TL;DR

  • Sham qurilishida ikki mustaqil o'q bor: bar turi va agregatsiya usuli
  • 17 asosiy bar turi: vaqt, tik, hajm, dollar, Renko, diapazon, o'zgaruvchanlik, Heikin-Ashi, Kagi, Line Break, P&F, tik nomutanosibligi (TIB), hajm nomutanosibligi (VIB), seriya, CUSUM, entropiya, delta
  • 3 agregatsiya usuli: kalendarga moslashtirilgan, siljuvchi oyna, moslashuvchan siljuvchi
  • 17 × 3 = 51 mumkin bo'lgan kombinatsiya, har birining xususiyatlari turlicha
  • Ko'pgina tizimlar faqat bitta kombinatsiyadan foydalanadi: kalendarga moslashtirilgan vaqt barlari. Qolgan 50 tasi ishlatilmagan.
  • Amaliy tavsiya: bir nechta kombinatsiyani qatlamlar shaklida ishlating — signallar uchun siljuvchi vaqt barlari, bozor tuzilishi uchun kalendar vaqt barlari, mikrotuzilish uchun axborotga asoslangan barlar

Sham qurilishining ikki o'qi

An'anaviy qarash barcha bar turlarini tekis ro'yxatga qo'yadi: vaqt barlari, tik barlari, hajm barlari, Renko va h.k. Bu chalg'ituvchi. Aslida ikkita ortogonal tanlov mavjud:

1-o'q — asosiy bar turi (17 turi): Yangi bar qachon yopilishini qanday hal qilasiz? Belgilangan vaqt oralig'idan keyinmi? N ta savdodan keyinmi? Narx harakatidan keyinmi? Axborot mazmuni o'zgarganda mi? Bu "bitta bar" nimani anglatishini belgilaydi.

2-o'q — agregatsiya usuli (3 usul): Asosiy barlarni yuqori vaqt oralig'idagi shamlarga qanday tuzasiz? Kalendar chegaralariga (00:00, 01:00, ...) moslashtirasizmi? Oxirgi N ta barning siljuvchi oynasidan foydalanasizmi? Oyna o'lchamini o'zgaruvchanlikka moslashtirasizmi?

Bu ikki o'q mustaqil. Sizda quyidagilar bo'lishi mumkin:

  • Kalendarga moslashtirilgan tik barlar — 14:00 dan 14:59 gacha yopilgan tik barlarni bir soatlik shamga yig'ish
  • Siljuvchi hajm barlar — ular qachon yopilganidan qat'i nazar oxirgi 24 ta hajm barini olish
  • Moslashuvchan delta barlar — delta barlarga o'zgaruvchanlikka asoslangan oynani qo'llash

Standart "1 soatlik sham" ushbu 17×3 matritsada faqat bitta nuqta: vaqt barlari + kalendar moslashuvi. Boshqa har bir kombinatsiya ko'rib chiqishga arziydigan muqobildir.


1. Vaqt barlari (standart)

Calendar time bars problem Notekis axborot zichligi: qattiq vaqt chegaralari 200 ta savdoli tinch soatlarni 50,000 ta savdoli e'lon soatlari bilan bir xil deb hisoblaydi.

Standart. Belgilangan vaqt oralig'idan keyin yangi bar shakllanadi: 1 daqiqa, 5 daqiqa, 1 soat. Har bir birja buni tabiiy ravishda taqdim etadi.

Xususiyatlari:

  • Osiyo sessiyasi (00:00–08:00 UTC) davomida 1 soatlik shamda 200 ta savdo bo'lishi mumkin. Binance listing e'loni paytida xuddi shu oynada 50,000 ta savdo bo'lishi mumkin. Vaqt barlari ikkalasini ham bir xil deb hisoblaydi. Bunday faollik sakrashlarini aniqlash botlarni himoya qilish uchun juda muhim — qarang Anomaly Detection for Trading Bots.
  • Barcha bozor ishtirokchilari bir xil sham chegaralarini ko'rishadi — Shelling nuqtasi. Bu vaqt barlarini ko'pchilik xatti-harakatini tahlil qilish uchun muhim qiladi.
  • Qisman shamlarda (qayta ishga tushirilgandan keyin) hisoblangan indikatorlar noto'g'ri qiymatlar beradi.
from datetime import datetime

def time_until_valid_hourly_candle():
    """How long until the first complete hourly candle after restart."""
    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. Faollikka asoslangan barlar

Activity-based bars Tik, hajm va dollar barlar: bar chegaralarini belgilashda soat o'rniga bozor ishtirokini ishlatishning uch yo'li.

Belgilangan vaqt oralig'ida tanlash o'rniga, belgilangan miqdordagi bozor faolligidan keyin tanlang. Bu kunning qaysi vaqti bo'lishidan qat'i nazar taxminan bir xil "axborot mazmuni"ga ega barlarni beradi.

2. Tik barlar

Har N ta savdodan (tiklardan) keyin yangi bar shakllanadi. Yuqori faollik davrida barlar tez shakllanadi. Tinch davrlarda bitta bar soatlab davom etishi mumkin.

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:
    """
    Generates a new bar every `threshold` trades.
    Each bar contains equal number of market "opinions".
    """

    def __init__(self, threshold: int = 1000):
        self.threshold = threshold
        self.trades: list[tuple[float, float]] = []  # (price, qty)
        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

Afzalliklari: Bozor faolligiga tabiiy ravishda moslashadi. Tik barlaridan olingan daromadlar vaqt-bar daromadlariga qaraganda normal taqsimotga yaqinroq bo'ladi — bu ko'pgina statistik modellarning samaradorligini yaxshilaydigan xususiyat.

Kamchiliklari: Xom savdo oqimini talab qiladi (tarixiy ma'lumotlar uchun barcha ma'lumot provayderlarida mavjud emas). Bar vaqti oldindan noma'lum — "keyingi bar aynan X vaqtida yopiladi" deb ayta olmaysiz.

3. Hajm barlari

N ta shartnoma (yoki kriptoda tanga) savdo qilingandan keyin yangi bar paydo bo'ladi. Tik barlariga o'xshash, lekin savdo hajmiga qarab tortilgan — bitta 100-BTC savdosi 1-BTC savdosidan 100 marta ko'proq hissa qo'shadi.

class VolumeBarGenerator:
    """
    Generates a new bar every `threshold` units of volume.
    Normalizes for trade size: one large order ≠ one small order.
    """

    def __init__(self, threshold: float = 100.0):
        self.threshold = threshold
        self.accumulated_volume = 0.0
        self.trades: list[tuple[int, float, float]] = []  # (ts, price, qty)
        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. Dollar barlari

Belgilangan nominal qiymat (USD/USDT da) almashgandan keyin yangi bar paydo bo'ladi. Faollikka asoslangan barlar orasida eng mustahkami, chunki u ham savdo sonini, ham narx darajasini normallashtiradi.

O'ylab ko'ring: agar ETH 1,000dan1,000 dan 4,000 gacha ko'tarilsa, 10,000turadiganETHsotishuchun10,000 turadigan ETH sotish uchun 4,000 narxda 2.5 ETH kerak, lekin $1,000 narxda 10 ETH kerak. Hajm barlari bularni har xil ko'radi; dollar barlari bularni bir xil ko'radi.

class DollarBarGenerator:
    """
    Generates a new bar every `threshold` dollars (USDT) of notional volume.
    Most robust normalization: independent of price level.

    Lopez de Prado (2018) recommends dollar bars as the default
    for most quantitative applications.
    """

    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

Chegarani tanlash

Faollikka asoslangan barlar uchun chegara siz almashtirayotgan vaqt barlari bilan taxminan bir xil kunlik bar sonini berishi kerak. Binance dagi BTCUSDT uchun:

Bar Type Typical Threshold ~Bars/Day Equivalent TF
Tick 1,000 trades ~1,400 ~1m
Tick 50,000 trades ~28 ~1h
Volume 100 BTC ~600 ~2-3m
Volume 2,400 BTC ~25 ~1h
Dollar $1M ~1,400 ~1m
Dollar $50M ~28 ~1h

Bu raqamlar taxminiy va bozor rejimiga qarab keskin o'zgaradi. Ralli yoki qulash paytida faollikka asoslangan barlar odatdagidan 5-10 marta ko'proq bar hosil qiladi — bu aynan kerakli narsa.

5–7. Narxga asoslangan barlar

Price-based bars Renko g'ishtlari, diapazon barlari va o'zgaruvchanlik barlari: narx muhim darajada harakatlanganda tanlash.

Narxga asoslangan barlar vaqt va faollikni ham e'tiborga olmaydi. Yangi bar faqat narx belgilangan miqdorda harakatlanganda paydo bo'ladi. Bu tabiiy ravishda yon shovqinni filtrlaydi va trendlarni ajratib ko'rsatadi.

5. Renko barlari

Yangi Renko "g'ishti" yopilish narxi oldingi g'ishtning yopilishidan kamida N birlikka harakatlanganda paydo bo'ladi. G'ishtlar har doim bir xil o'lchamda bo'ladi, bu trend yo'nalishining toza vizual ko'rinishini yaratadi.

class RenkoBarGenerator:
    """
    Generates Renko bricks based on price movement.

    Key property: during sideways movement, no new bricks form.
    During strong trends, bricks form rapidly.
    """

    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 belgilangan g'isht o'lchami o'rniga ATR (o'rtacha haqiqiy diapazon) dan foydalanadi, o'zgaruvchanlikka avtomatik ravishda moslashadi.

6. Diapazon barlari

Har bir barning belgilangan yuqori-past diapazoni bor. Diapazon oshib ketganda bar yopiladi va yangisi boshlanadi. Renkodan farqli o'laroq, diapazon barlarida piltalar (wicks) bor va bar ichidagi o'zgaruvchanlikni ko'rsatishi mumkin.

class RangeBarGenerator:
    """
    Generates bars with a fixed high-low range.

    Difference from Renko: range bars show the full OHLC within
    the range, not just brick direction. More information-rich.
    """

    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 va diapazon barlari orasidagi asosiy farq: Renko faqat yopilish narxlarini kuzatadi va yo'nalishni ko'rsatadi; diapazon barlari to'liq narx oralig'ini kuzatadi va bar ichidagi tuzilishni ko'rsatadi. Diapazon barlari algoritmik treyding uchun umuman olganda foydaliroq, chunki ular stop-loss va take-profit simulyatsiyasi uchun zarur bo'lgan yuqori-past ma'lumotlarni saqlaydi.

7. O'zgaruvchanlik barlari

Yangi bar bar ichidagi o'zgaruvchanlik dinamik chegaraga yetganda paydo bo'ladi — masalan, so'nggi ATR ning ko'paytmasi. Diapazon barlaridan (belgilangan chegara) farqli o'laroq, o'zgaruvchanlik barlari bozor sharoitlariga moslashadi.

class VolatilityBarGenerator:
    """
    Generates bars when intra-bar volatility reaches a threshold.

    Similar to range bars, but the threshold adapts to market conditions
    using a rolling ATR measure. In calm markets, bars need less
    absolute movement to close; in volatile markets, more.
    """

    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 (silliqlashtirilgan transformatsiya)

Heikin-Ashi transformation Heikin-Ashi: o'rtachalash shovqinli shamlarni silliq trend signallariga aylantiradi — ammo aniq narx ma'lumotlari hisobiga.

Heikin-Ashi (yapon tilida "o'rtacha bar") bar turi emas — bu istalgan asosiy bar turi ustiga qo'llanilishi mumkin bo'lgan transformatsiya. U joriy va oldingi bar qiymatlarini o'rtachalash orqali shamlarni silliqlaydi:

  • HA Close = (Open + High + Low + Close) / 4
  • HA Open = (Previous HA Open + Previous HA Close) / 2
  • HA High = max(High, HA Open, HA Close)
  • HA Low = min(Low, HA Open, HA Close)

Trendlar pastki piltasiz (ko'tarilish trendida) yoki yuqori piltasiz (pasayish trendida) bir xil rangdagi shamlar ketma-ketligi sifatida ko'rinadi.

class HeikinAshiTransformer:
    """
    Transforms standard OHLCV candles into Heikin-Ashi candles.

    Can be applied on top of ANY bar type: time bars, volume bars,
    rolling bars, etc. It's a transformation, not a sampling method.

    WARNING: HA prices are synthetic — they don't represent real
    traded prices. Never use HA close for order placement or
    PnL calculation. Use HA only for signal generation, then
    execute at real prices.
    """

    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]:
        """Transform an entire series. Resets state first."""
        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:
    """
    Simple HA trend signal.

    Returns:
        +1: bullish (N consecutive green HA candles with no lower wick)
        -1: bearish (N consecutive red HA candles with no upper wick)
         0: no clear trend
    """
    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 uchun muhim ogohlantirish: Heikin-Ashi narxlari sintetik. Agar backtestingiz kirish narxi sifatida HA close dan foydalansa, natijalar noto'g'ri bo'ladi. HA ni har doim faqat signal generatsiyasi uchun ishlating va haqiqiy OHLC narxlarida bajaring.

HA qachon foydali: Toza "ushlab turish" signallarini talab qiladigan trendga ergashish strategiyalari. HA ni istalgan asosiy bar turi — vaqt, hajm, dollar barlari — ustiga qo'llang, soxta kesishmalarni filtrlash uchun.

HA qachon zararli: Aniq narx darajalarini talab qiladigan har qanday strategiya — qo'llab-quvvatlash/qarshilik, order kitobini tahlil qilish, PIQ (Position In Queue). O'rtachalash aniq narx ma'lumotlarini yo'q qiladi.

9–11. Yapon reversiya grafiklari

Japanese charting methods Kagi, Line Break va Point & Figure: faqat narx tuzilishiga e'tibor qaratadigan vaqtsiz grafik usullari.

Bular (Renko bilan birga) vaqtni butunlay olib tashlab, narx tuzilishiga e'tibor qaratadigan an'anaviy yapon grafik usullari.

9. Kagi grafiklari

Kagi grafiklari narx belgilangan miqdorda teskari yo'nalganda yo'nalishini o'zgartiradigan vertikal chiziqlardan iborat. Narx oldingi yuqori darajani (qalin = "yang" = talab) yoki oldingi past darajani (yupqa = "yin" = taklif) buzganda chiziqlar qalinligini o'zgartiradi.

class KagiChartGenerator:
    """
    Generates Kagi chart lines based on price reversals.

    Unlike Renko (fixed brick size), Kagi tracks the actual magnitude
    of each move and changes line thickness at breakout points.

    Useful for identifying support/resistance breaks and
    supply/demand shifts without time noise.
    """

    def __init__(self, reversal_amount: float = 10.0):
        self.reversal_amount = reversal_amount
        self.lines: list[dict] = []
        self.current_direction: int = 0  # 1=up, -1=down
        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' (thick) or 'yin' (thin)

    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 grafiklari

Line Break grafiklari yangi chiziqni (qutini) faqat yopilish narxi oldingi N chiziqning (odatda 3) yuqori yoki past darajasidan oshganda chizadi. Agar narx oraliqda qolsa, yangi chiziq chizilmaydi.

class LineBreakGenerator:
    """
    Generates Line Break bars (Three Line Break by default).

    A new bar is drawn only when the close exceeds the high or low
    of the last N bars. Filters out minor noise by requiring price
    to break through a multi-bar range.

    The 'N' parameter (line_count) controls sensitivity:
    - N=2: more sensitive, more bars, more noise
    - N=3: standard (Three Line Break)
    - N=4+: less sensitive, fewer bars, stronger signals
    """

    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. Point & Figure grafiklari

Point & Figure (P&F) grafiklari X (ko'tarilayotgan narx) va O (tushayotgan narx) ustunlaridan foydalanadi. Ustun almashishi odatda 3 quti o'lchamining teskari harakatini talab qiladi. Shovqinni filtrlash va qo'llab-quvvatlash/qarshilikni aniqlashning eng qadimiy usullaridan biri.

class PointAndFigureGenerator:
    """
    Generates Point & Figure chart data.

    X column: price rising by box_size increments.
    O column: price falling by box_size increments.
    Column switch: requires reversal_boxes * box_size movement
    in the opposite direction.

    Classic setting: box_size based on ATR, 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 treydingda Kagi, Line Break va P&F: Asosan uzoq muddatli trendni aniqlash va qo'llab-quvvatlash/qarshilikni identifikatsiya qilish uchun ishlatiladi. Filtr qatlami sifatida — "Kagi grafigi yin rejimida bo'lganda long signallarini olmang" — ular savdolarni makro tuzilish bilan moslashtirish orqali qiymat qo'shadi.

12–14. Axborotga asoslangan barlar

Information-driven bars Nomutanosiblik barlari, seriya barlari, CUSUM filtrlari va entropiya barlari: bozor bizga biror narsa o'zgarganini aytganda tanlash.

Marcos Lopez de Prado ning Advances in Financial Machine Learning (2018) kitobidan eng murakkab yondashuv. Asosiy tushuncha: belgilangan oraliqlarda emas, balki bozorga yangi axborot kelganda tanlash.

12. Tik nomutanosiblik barlari (TIB)

Agar bozor muvozanatda bo'lsa, xaridor boshlagan va sotuvchi boshlagan savdolar taxminan tenglashishi kerak. Nomutanosiblik bizning kutganimizdan oshib ketganda, biror narsa o'zgardi. Shu paytda bar tanlang.

Har bir savdo tik qoidasidan foydalanib xaridor boshlagan (+1) yoki sotuvchi boshlagan (-1) sifatida tasniflanadi. Biz to'plangan nomutanosiblik θ ni kuzatib boramiz va |θ| dinamik chegaradan oshganda tanlaymiz.

class TickImbalanceBarGenerator:
    """
    Generates bars when the cumulative tick imbalance exceeds
    expected levels — i.e., when "new information" arrives.

    Based on Lopez de Prado (2018), Chapter 2.
    """

    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:
        """Classify trade as buy (+1) or sell (-1) using tick rule."""
        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. Hajm nomutanosibligi barlari (VIB)

TIB ning kengaytmasi: har bir savdoni ±1 deb hisoblash o'rniga, belgilangan hajmga qarab tortish. 100-BTC xarid +100 hissa qo'shadi, 1-BTC sotish -1 hissa qo'shadi. Ko'plab kichik savdolarga bo'linishi mumkin bo'lgan katta axborotlashtirilgan buyurtmalarni aniqlaydi.

class VolumeImbalanceBarGenerator:
    """
    Like TIBs, but uses signed volume instead of signed ticks.

    Captures the insight that a 100-BTC buy signal is 100x more
    informative than a 1-BTC buy signal.
    """

    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

Portlash muammosi

Nomutanosiblik barlari bilan bog'liq ma'lum muammo: EWMA ga asoslangan chegara ijobiy teskari aloqa siklga tushib qolishi mumkin. Yechim: min_ticks va max_ticks chegaralari bilan siqish.


self.expected_ticks = max(
    self.min_ticks,    # Floor: never less than 100 ticks
    min(
        self.max_ticks,  # Ceiling: never more than 50000 ticks
        new_expected_ticks
    )
)

14. Seriya barlari

Seriya barlari joriy yo'nalishli seriyaning uzunligini kuzatadi — xaridlar yoki sotishlarning eng uzun ketma-ketligi. Katta axborotlashtirilgan treyder buyurtmani ko'plab kichik savdolarga bo'lganida, ketma-ketlik g'ayrioddiy darajada uzayadi. Seriya barlari buni aniqlaydi.

class TickRunBarGenerator:
    """
    Generates bars when the length of a directional run exceeds expectations.

    Based on Lopez de Prado (2018), Chapter 2.

    Difference from imbalance bars:
    - Imbalance bars track NET imbalance (buys minus sells)
    - Run bars track the MAXIMUM run length (consecutive buys OR sells)
    """

    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

Seriya barlarini hajm seriyalariga va dollar seriyalariga qadar kengaytirish mumkin.

15. CUSUM filtr barlari

CUSUM (kumulyativ yig'indi) filtri to'plangan daromadlarni kuzatish orqali qachon tanlash kerakligini aniqlaydi. Nomutanosiblik barlaridan (xom savdolar bilan ishlaydigan) farqli o'laroq, CUSUM mavjud 1 daqiqalik OHLCV ma'lumotlariga qo'llanilishi mumkin — tik ma'lumotlari talab qilinmaydi.

class CUSUMFilterBarGenerator:
    """
    Symmetric CUSUM filter for event-based sampling.

    Based on Lopez de Prado (2018), Chapter 2.5.

    Key advantage over Bollinger Bands: CUSUM requires a FULL
    run of threshold magnitude before triggering. Bollinger Bands
    trigger repeatedly when price hovers near the band.

    Can be applied to 1m OHLCV data — no tick data required.
    """

    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 + uchlik to'siq usuli: Lopez de Prado usulida CUSUM voqealari uchlik to'siq usuli uchun kirish nuqtalari sifatida qo'llaniladi — bunda har bir voqea stop-loss, take-profit va muddati tugash to'siqlari bilan savdoni ishga tushiradi. Bunday voqeaga asoslangan strategiyalarni mustahkam tekshirish uchun qarang Walk-Forward Optimization va Monte Carlo Bootstrap for Backtesting.

16. Entropiya barlari

Nazariy jihatdan eng nafis yondashuv: bar ichidagi narx qatorining axborot mazmuni (Shennon entropiyasi) chegaradan oshganda tanlash.

class EntropyBarGenerator:
    """
    Generates bars when the entropy of intra-bar returns exceeds
    a threshold.

    Based on Shannon's information theory: bars are sampled when
    "new information" arrives, measured as the entropy of the
    return distribution within the current bar.

    This is the most theoretically "pure" information-driven bar.
    """

    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

Amaliy eslatma: Entropiya barlari hisoblash jihatidan qimmat va asosan tadqiqot qiziqishini bildiradi — ammo ML ga asoslangan strategiyalar uchun, ular yaxshiroq statistik xususiyatlarga ega belgilarni beradi, chunki har bir barda taxminan bir xil "axborot" mavjud.

17. Delta barlari (order oqimi)

Delta bars and order flow Kumulyativ delta: real vaqtda tajovuzkor xaridorlar va sotuvchilarning sof kuchini o'lchash.

Delta barlari kumulyativ delta asosida tanlanadi — xarid hajmi va sotish hajmi orasidagi davom etayotgan farq. Nomutanosiblik barlaridan (tik belgilarini ±1 ishlatuvchi) farqli o'laroq, delta barlari haqiqiy hajm bo'yicha tortilgan order oqimidan foydalanadi.

class DeltaBarGenerator:
    """
    Generates bars based on cumulative order flow delta.

    Delta = Buy Volume - Sell Volume (classified by aggressor side).

    Requires trade-level data with side classification
    (available from Binance aggTrades, Bybit trades, etc.)
    """

    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 divergensiyasi: Eng kuchli signallardan biri — narx ko'tarilayotganda kumulyativ delta manfiy bo'lganda (sotuvchilar tajovuzkor, ammo narx baribir ko'tariladi, bu limitli xaridning so'rilishini bildiradi). Digital Fingerprint: Trader Identification maqolasida tasvirlangan xulq-atvor barmoq izi yondashuviga bevosita aloqador. Avellaneda-Stoikov modelidan foydalanuvchi market-meykerlar uchun delta barlari zaxira xavfi va tajovuzkor bosimning real vaqtdagi ko'rinishini beradi.


Rolling window aggregation Asosiy barlarning aylanma buferi: yangi ma'lumot kiradi, eski ma'lumot chiqadi, va agregatsiyalangan sham har doim amal qiladi.

Agregatsiya usullari asosiy barlarning yuqori vaqt oralig'idagi (HTF) shamlarga qanday tuzilishini belgilaydi. Ular bar turidan mustaqil — istalgan agregatsiya usulini istalgan asosiy bar turiga qo'llashingiz mumkin.

A usuli: kalendarga moslashtirilgan agregatsiya

Belgilangan kalendar chegarasi ichiga tushadigan barcha asosiy barlarni agregatsiya qilish. "1 soatlik" sham 14:00:00 dan 14:59:59 gacha bo'lgan barcha barlarni qamrab oladi.

Xususiyatlari:

  • Barcha bozor ishtirokchilari bir xil chegaralarni ko'radi — bozor tuzilishini tahlil qilish, qo'llab-quvvatlash/qarshilik, PIQ trigerlar uchun zarur
  • Sovuq start muammosi: qayta ishga tushirilgandan keyin qisman sham
  • Vaqt barlari uchun tabiiy (bu birjalar tabiiy ravishda taqdim etadigan narsa)
  • Vaqtsiz barlar uchun ham ishlaydi: "14:00 va 15:00 orasida yopilgan barcha hajm barlari" = hajm barlaridan olingan kalendarga moslashtirilgan soatlik sham

B usuli: siljuvchi oyna agregatsiyasi

Oxirgi N ta yopilgan asosiy barni agregatsiya qilish, har bir yangi barda qayta hisoblanadi. "1 soatlik" siljuvchi sham = oxirgi 60 ta yopilgan 1 daqiqalik vaqt bari, har daqiqada yangilanadi.

Atom birligi — yopilgan asosiy bar. Ushbu dizayn qarori quyidagilarni beradi:

  1. Sovuq start yo'q. N ta bardan keyin sham amal qiladi. Hech qanday qisman sham shovqini yo'q.
  2. Backtest pariteti. Agar jonli savdo backtest dvigateli bilan bir xil atom birligidan foydalansa, signallar bir xil bo'ladi.
  3. Oddiy tekshirish. Bitta qoida: if buffer not full: skip.
import numpy as np

class RollingCandleAggregator:
    """
    Produces rolling higher-timeframe candles from closed base bars.

    Works with ANY bar type: time bars, tick bars, volume bars,
    dollar bars, delta bars — anything that produces OHLCV output.

    Example: RollingCandleAggregator(window=60) with 1m time bars
    produces a "1h" candle updated every minute.

    Example: RollingCandleAggregator(window=24) with volume bars
    produces a candle spanning the last 24 volume bars.
    """

    def __init__(self, window: int):
        self.window = window
        self.buffer: deque[OHLCV] = deque(maxlen=window)

    def push(self, bar: OHLCV) -> OHLCV | None:
        """
        Add a closed base bar. Returns aggregated candle
        only when buffer is full (= candle is valid).
        """
        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

Faza siljishi savdo-o'zaro almashuvi: Siljuvchi shamlar agar siz :37 da boshlagan bo'lsangiz :37 da yopiladi, hammasi kabi :00 da emas. Bu ko'pchilikka ko'rinadigan darajalarga bog'liq strategiyalar uchun muhim. Yechim: ikkalasidan ham foydalanish — bozor tuzilishi uchun kalendar, signallar uchun siljuvchi.

C usuli: moslashuvchan siljuvchi agregatsiya

Siljuvchiga o'xshash, lekin oyna o'lchami joriy o'zgaruvchanlikka moslashadi. Tinch bozorlar → kengroq oyna (ko'proq silliqlash). O'zgaruvchan bozorlar → torroq oyna (tezroq reaktsiya).

class AdaptiveRollingAggregator:
    """
    Rolling window where the window size adapts to volatility.

    Works with any base bar type. Uses ATR of recent bars
    as the volatility measure.

    Low volatility → wider window (more smoothing, fewer signals)
    High volatility → narrower window (faster reaction)
    """

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

Har bir asosiy bar turi har bir agregatsiya usuli bilan birlashtirilishi mumkin. Ba'zi kombinatsiyalar standart (kalendar vaqt barlari = birjalar sizga beradigan narsa), boshqalari ekzotik lekin kuchli.

Kombinatsiya misollari

Base Bar Type Calendar Rolling Adaptive
Time Standard exchange candles Always-valid HTF, no cold start Vol-adaptive timeframe
Volume "All volume bars this hour" Last 24 volume bars Wider window in calm markets
Dollar Hourly dollar-bar aggregate Last N dollar bars Adaptive dollar windows
Tick Imbalance Hourly imbalance aggregate Last N imbalance events Fast reaction in volatile regimes
Delta Hourly net order flow Rolling delta snapshot Adaptive flow window
Renko "Bricks this hour" Last N bricks Adaptive brick count

Gibrid dvigatel: kalendar + siljuvchi

Amalda sizga kalendar va siljuvchi agregatsiyaning ikkalasi ham bir vaqtda kerak. Xotira sarfi arzimas — belgi boshiga, vaqt oralig'i boshiga ikkita deque buferi.

class HybridCandleEngine:
    """
    Maintains both calendar-aligned and rolling candles
    for any base bar type.

    Calendar candles: for market structure, support/resistance, PIQ.
    Rolling candles: for indicators, signal generation, entries/exits.
    """

    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):
        """Process any base bar type — time, volume, tick, delta, etc."""
        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] = []

Vaqt-hajm gibridi: hajm bo'linmalari bilan kalendar

Maxsus agregatsiya varianti: hajm belgilangan chegaradan oshganda erta majburiy yopiladigan kalendarga moslashtirilgan shamlar. Vaqt sinxronizatsiyasini saqlagan holda faollik sakrashlariga moslashadi.

class TimeVolumeHybridGenerator:
    """
    Calendar-aligned candles that split when volume spikes.

    Rule: close the candle at the calendar boundary OR when
    accumulated volume exceeds vol_threshold, whichever comes first.

    Works with any base bar type — the volume trigger adds an
    extra split dimension on top of calendar alignment.
    """

    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

Amaliy agregatsiya: kaskadli oldindan yuklash

Cascading aggregation Kaskadli oldindan yuklash: soatlik shamlardan kunlik, daqiqalik shamlardan soatlik shamlarni qurish — API chegaralarini chetlab o'tish.

Birjalar qancha tarixiy ma'lumot berishini cheklaydi. Binance bitta REST so'roviga ~1000 sham beradi, OKX 300 ta bilan cheklangan. Agar sizga siljuvchi 1D sham (1440 daqiqa) kerak bo'lsa, har doim yetarli 1 daqiqalik tarixni ola olmaysiz. WebSocket orqali savdolar va order kitobining real vaqtdagi oqimi uchun qarang CCXT Pro WebSocket Methods.

Yechim: kaskadli agregatsiya — har bir chuqurlikda mavjud bo'lgan eng yuqori tasvir aniqligidan yuqori vaqt oraliqlarini qurish, keyin ularni birlashtirish.

Rolling 1W candle:
├── 6 completed 1D candles ← fetch from REST /klines?interval=1d
├── 1 partial day:
│   ├── 23 completed 1h candles ← fetch from REST /klines?interval=1h
│   └── 1 partial hour:
│       └── N completed 1m candles ← fetch from REST /klines?interval=1m
└── Live: each new closed 1m candle updates the entire chain

Bu ishlaydi, chunki OHLCV agregatsiyasi tuzilishi mumkin: 1D shamning yuqori darajasi 24 ta soatlik yuqori darajalarning maksimumi, u esa 1440 ta daqiqalik yuqori darajalarning maksimumi.

Ko'p birjali chegaralar

Exchange Max 1m Candles Max 1h Candles Notable Intervals
Binance 1,000 1,000 1m–1M, full range
Bybit 1,000 1,000 1–720, D/W/M
OKX 300 300 1m–1M (more restrictive)
Gate.io 1,000 1,000 10s–30d

Agregatsiya izchilligini tekshirish

REST API dan olingan 1 soatlik sham 60 ta 1 daqiqalik shamdan hisoblanganga mos kelmasligi mumkin. Har doim tekshiring:

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

Agar tekshirish doimiy ravishda muvaffaqiyatsiz bo'lsa, har doim o'zingiz 1 daqiqadan agregatsiya qilingbacktest pariteti uchun hech qachon birjaning HTF shamiga ishonmang.


Taqqoslash matritsasi

1-o'q: asosiy bar turlari

# Bar Type Trigger Tick Data Required Best For
1 Time Fixed interval No Market structure, crowd behavior
2 Tick N trades Yes ML features, equal-opinion sampling
3 Volume N units traded Yes Normalized activity analysis
4 Dollar $N notional Yes Cross-asset comparison
5 Renko Price ± N units No Trend following, noise filtering
6 Range High-Low ≥ N Yes Breakout detection
7 Volatility Adaptive range Yes Regime-adaptive analysis
8 Heikin-Ashi Transformation No Trend confirmation (synthetic prices!)
9 Kagi Price reversal No Supply/demand structure
10 Line Break N-line breakout No Macro trend filter
11 Point & Figure Box + reversal No Support/resistance mapping
12 TIB Tick imbalance Yes Informed flow detection
13 VIB Volume imbalance Yes Large order detection
14 Run Run length Yes Order splitting detection
15 CUSUM Cumulative return No (1m closes) Structural break events
16 Entropy Shannon entropy Yes ML research, feature purity
17 Delta Order flow delta Yes (aggTrades) Aggressor flow analysis

2-o'q: agregatsiya usullari

Method Alignment Cold Start Phase Shift Best For
Calendar Wall clock Partial bar risk None (crowd-aligned) Market structure, PIQ, S/R
Rolling N bars None (after warmup) Yes (shifted from :00) Indicators, signals
Adaptive Volatility-driven N After ATR calibration Yes Vol-adaptive strategies

Amaliy tavsiyalar

Layered architecture To'rt qatlamli sham arxitekturasi: siljuvchi signallar, kalendar tuzilishi, mikrotuzilish oqimi va trend filtrlari.

Agar backtest dvigatelingiz 1 daqiqalik OHLCV ma'lumotlarida ishlasa:

  1. Siljuvchi vaqt barlari — eng oddiy yangilanish. Qo'shimcha ma'lumot yo'q. Sovuq startni yo'q qiladi.
  2. Gibrid (siljuvchi + kalendar) vaqt barlari — bozor tuzilishi uchun kalendar, signallar uchun siljuvchi.
  3. CUSUM filtri — 1 daqiqalik yopilishlarda ishlaydi, tik ma'lumotlari kerak emas. "Biror narsa qiziqarli bo'lish uchun yetarlicha harakatlandi."

Agar sizda tik/savdo ma'lumotlari bo'lsa:

  1. Dollar barlari + siljuvchi — kvant moliya adabiyotidan tavsiya etilgan standart.
  2. Hajm nomutanosibligi barlari + siljuvchi — axborotlashtirilgan oqimni aniqlaydi, muhim voqealar davomida ko'proq tanlaydi.
  3. Delta barlari + kalendar — agar sizda tajovuzkor-tomon tasnifi bo'lsa, bozorni kim itarayotganini ko'rishning eng to'g'ridan-to'g'ri yo'li.

Filtr sifatida (istalgan asos+agregatsiya kombinatsiyasi ustiga Heikin-Ashi yoki Line Break qo'llash):

  1. Siljuvchi hajm barlariga Heikin-Ashi — faollikka normallashtirilgan ma'lumotlarda toza trend signallari.
  2. Kunlik kalendar barlariga Line Break / Kagi — makro trend filtri.

Marketmaker.cc uchun xususan — qatlamli yondashuv:

  • 1-qatlam (signallar): Indikatorlar va kirish/chiqish signallari uchun vaqt barlarining siljuvchi agregatsiyasi. Sovuq start yo'q, mukammal backtest pariteti.
  • 2-qatlam (bozor tuzilishi): Qo'llab-quvvatlash/qarshilik, soatlik yopilish tahlili va PIQ trigerlar uchun kalendarga moslashtirilgan vaqt barlari.
  • 3-qatlam (mikrotuzilish): Axborotlashtirilgan oqimni aniqlash, buyurtma bo'linishini aniqlash va katta harakatlarni oldindan bashorat qilish uchun xom savdo oqimidan olingan hajm nomutanosibligi barlari + delta barlari. Order oqimi ma'lumotlaridagi xulq-atvor namunasini tanish uchun Digital Fingerprint: Trader Identification maqolasiga ham qarang.
  • 4-qatlam (trend filtri): Siljuvchi barlarga Heikin-Ashi transformatsiyasi, yoki 4 soatlik kalendar yopilishlariga Line Break, signallarni makro yo'nalish bilan moslashtirib turish uchun.

Xulosa

Sham qurilishi bitta tanlov emas — bu ikkita mustaqil qaror:

  1. Qanday turdagi bar? Vaqt soat oraliqlarini qamrab oladi. Faollik (tik, hajm, dollar) bozor ishtirokini qamrab oladi. Narx (Renko, diapazon, o'zgaruvchanlik) harakatlarni qamrab oladi. Axborot (nomutanosiblik, seriya, CUSUM, entropiya) yangi axborotning kelishini qamrab oladi. Order oqimi (delta) tajovuzkor bosimni qamrab oladi.

  2. Yuqori vaqt oraliqlariga qanday agregatsiya qilish kerak? Kalendar ko'pchilik bilan moslashadi. Siljuvchi sovuq startni yo'q qiladi. Moslashuvchan o'zgaruvchanlikka javob beradi.

Binance dan olingan standart "1 soatlik sham" 17×3 matritsasidagi bitta katakcha xolos. Qolgan 50 ta kombinatsiya ularni amalga oshirishga tayyor bo'lgan har qanday kishi uchun mavjud. Ishlab chiqarish tizimi uchun javob — "qaror qabul qilish dvigatelingizning har bir qatlami uchun to'g'ri kombinatsiyani tanlash".

Atom birligi — yopilgan asosiy bar — asos bo'lib qoladi. Qolganining hammasi agregatsiya.

Nozik granulyatsiyali ma'lumotlar bilan backtest aniqligi haqida ko'proq bilish uchun qarang Adaptive Drill-Down: Backtest with Variable Granularity. Ko'p vaqt oraliqli strategiyalarda indikatorni oldindan hisoblashning ta'siri uchun qarang Aggregated Parquet Cache.


Foydali havolalar

  1. Lopez de Prado — Advances in Financial Machine Learning (2018)
  2. Easley, Lopez de Prado, O'Hara — The Volume Clock: Insights into the High Frequency Paradigm (2012)
  3. mlfinlab — Python library implementing information-driven bars
  4. Binance — Historical Market Data
  5. Apache Parquet — columnar storage format

Iqtibos

@article{soloviov2026bartypes,
  author = {Soloviov, Eugen},
  title = {Bar Types and Aggregation Methods for Algorithmic Trading},
  year = {2026},
  url = {https://marketmaker.cc/en/blog/post/beyond-time-bars-candle-construction},
  description = {Two-axis classification of candle construction: 17 base bar types × 3 aggregation methods = 51 combinations, with implementation code and practical recommendations for crypto algotrading.}
}
blog.disclaimer

Authors

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

Bozordan bir qadam oldinda bo'ling

Sun'iy intellekt savdo tahlillari, bozor tahlili va platforma yangiliklari uchun bizning xabarnomaga obuna bo'ling.

Biz sizning maxfiyligingizni hurmat qilamiz. Istalgan vaqtda obunadan chiqishingiz mumkin.