Skip to content

How to Create a Custom Bar Policy

This guide shows how to create your own bar closing policy for the BarAggregator.

When to Use

Create a custom policy when built-in types (Time, Tick, Volume, Renko, Range) don't fit your needs:

  • Dollar bars (close after fixed dollar volume)
  • Volatility bars (close based on ATR)
  • Session bars (close at specific times)
  • Hybrid bars (combine multiple conditions)

The BarPolicy Concept

Your policy must satisfy this interface:

template <typename T>
concept BarPolicy = requires(T& p, const TradeEvent& t, Bar& b) {
  { p.shouldClose(t, b) } noexcept -> std::same_as<bool>;
  { p.update(t, b) } noexcept -> std::same_as<void>;
  { p.initBar(t, b) } noexcept -> std::same_as<void>;
  { T::kBarType } -> std::convertible_to<BarType>;
};

Example: Dollar Bar Policy

Closes when notional volume reaches a threshold:

#include "flox/aggregator/aggregation_policy.h"
#include "flox/aggregator/bar.h"
#include "flox/book/events/trade_event.h"

namespace flox
{

class DollarBarPolicy
{
 public:
  static constexpr BarType kBarType = BarType::Volume;

  explicit DollarBarPolicy(Volume threshold) : _threshold(threshold) {}

  static DollarBarPolicy fromDouble(double dollars)
  {
    return DollarBarPolicy(Volume::fromDouble(dollars));
  }

  bool shouldClose(const TradeEvent& trade, const Bar& bar) noexcept
  {
    return bar.volume.raw() >= _threshold.raw();
  }

  void update(const TradeEvent& trade, Bar& bar) noexcept
  {
    updateOHLCV(trade, bar);  // Use built-in helper
  }

  void initBar(const TradeEvent& trade, Bar& bar) noexcept
  {
    initializeBar(trade, bar);  // Use built-in helper
  }

 private:
  Volume _threshold;
};

}  // namespace flox

Usage

BarAggregator<DollarBarPolicy> aggregator(
  DollarBarPolicy::fromDouble(1000000.0),  // $1M bars
  &bus
);

Example: Session Bar Policy

Closes at specific session boundaries:

class SessionBarPolicy
{
 public:
  static constexpr BarType kBarType = BarType::Time;

  explicit SessionBarPolicy(std::vector<uint64_t> closeTimesNs)
      : _closeTimes(std::move(closeTimesNs))
  {
    std::sort(_closeTimes.begin(), _closeTimes.end());
  }

  bool shouldClose(const TradeEvent& trade, const Bar& bar) noexcept
  {
    uint64_t tradeNs = trade.trade.exchangeTsNs;
    uint64_t barStartNs = bar.startTime.time_since_epoch().count();

    for (uint64_t closeTime : _closeTimes)
    {
      if (tradeNs >= closeTime && barStartNs < closeTime)
      {
        return true;
      }
    }
    return false;
  }

  void update(const TradeEvent& trade, Bar& bar) noexcept
  {
    updateOHLCV(trade, bar);
  }

  void initBar(const TradeEvent& trade, Bar& bar) noexcept
  {
    initializeBar(trade, bar);
  }

 private:
  std::vector<uint64_t> _closeTimes;
};

Example: Volatility Bar Policy

Closes when price range exceeds N times recent average:

class VolatilityBarPolicy
{
 public:
  static constexpr BarType kBarType = BarType::Range;

  explicit VolatilityBarPolicy(double multiplier, Price baseRange)
      : _multiplier(multiplier), _threshold(baseRange)
  {
  }

  bool shouldClose(const TradeEvent& trade, const Bar& bar) noexcept
  {
    Price range = Price::fromRaw(bar.high.raw() - bar.low.raw());
    Price dynamicThreshold = Price::fromRaw(
      static_cast<int64_t>(_threshold.raw() * _multiplier)
    );
    return range.raw() >= dynamicThreshold.raw();
  }

  void update(const TradeEvent& trade, Bar& bar) noexcept
  {
    updateOHLCV(trade, bar);

    // Update rolling average for next bar
    Price range = Price::fromRaw(bar.high.raw() - bar.low.raw());
    _threshold = Price::fromRaw(
      (_threshold.raw() * 9 + range.raw()) / 10  // EMA-like
    );
  }

  void initBar(const TradeEvent& trade, Bar& bar) noexcept
  {
    initializeBar(trade, bar);
  }

 private:
  double _multiplier;
  Price _threshold;
};

Helper Functions

Use these built-in helpers in your policy:

updateOHLCV

Updates high, low, close, volume, tradeCount, buyVolume, endTime:

void update(const TradeEvent& trade, Bar& bar) noexcept
{
  updateOHLCV(trade, bar);
  // Add custom logic here
}

initializeBar

Initializes a new bar from the first trade:

void initBar(const TradeEvent& trade, Bar& bar) noexcept
{
  initializeBar(trade, bar);
  // Add custom initialization here
}

Adding Custom State

Store additional state in your policy:

class ImbalanceBarPolicy
{
 public:
  static constexpr BarType kBarType = BarType::Volume;

  explicit ImbalanceBarPolicy(double imbalanceThreshold)
      : _threshold(imbalanceThreshold)
  {
  }

  bool shouldClose(const TradeEvent& trade, const Bar& bar) noexcept
  {
    if (bar.volume.raw() == 0) return false;

    double buyRatio = static_cast<double>(bar.buyVolume.raw()) /
                      static_cast<double>(bar.volume.raw());

    // Close when buy/sell imbalance exceeds threshold
    return buyRatio > _threshold || buyRatio < (1.0 - _threshold);
  }

  void update(const TradeEvent& trade, Bar& bar) noexcept
  {
    updateOHLCV(trade, bar);
  }

  void initBar(const TradeEvent& trade, Bar& bar) noexcept
  {
    initializeBar(trade, bar);
  }

 private:
  double _threshold;  // e.g., 0.7 = 70% imbalance
};

Using with MultiTimeframeAggregator

Custom policies work with MultiTimeframeAggregator via the variant-based interface:

// Currently supports Time, Tick, Volume via addXxxInterval()
// For custom policies, use separate BarAggregator instances:

BarAggregator<DollarBarPolicy> dollarAgg(
  DollarBarPolicy::fromDouble(1000000.0), &bus
);

BarAggregator<ImbalanceBarPolicy> imbalanceAgg(
  ImbalanceBarPolicy(0.7), &bus
);

// Feed trades to both
void onTrade(const TradeEvent& trade) {
  dollarAgg.onTrade(trade);
  imbalanceAgg.onTrade(trade);
}

Testing Your Policy

TEST(MyPolicyTest, ClosesAtThreshold)
{
  BarBus bus;
  bus.enableDrainOnStop();

  std::vector<Bar> bars;
  // ... set up test subscriber ...

  BarAggregator<MyPolicy> agg(MyPolicy(...), &bus);
  bus.subscribe(&subscriber);
  bus.start();
  agg.start();

  // Feed test trades
  agg.onTrade(makeTrade(...));

  agg.stop();
  bus.stop();

  EXPECT_EQ(bars.size(), expectedCount);
}

See Also