Footprint Chart¶
Footprint charts track bid and ask volume separately at each price level, enabling detailed order flow analysis and imbalance detection.
Overview¶
#include "flox/aggregator/custom/footprint_bar.h"
FootprintBar<64> footprint;
footprint.setTickSize(Price::fromDouble(0.50));
// Add trades
footprint.addTrade(tradeEvent);
// Analyze
Quantity delta = footprint.totalDelta();
Price buyPressure = footprint.highestBuyingPressure();
Price sellPressure = footprint.highestSellingPressure();
Price imbalance = footprint.strongestImbalance(0.7);
Key Concepts¶
Bid vs Ask Volume¶
- Ask Volume: Buy market orders hitting the ask (aggressive buyers)
- Bid Volume: Sell market orders hitting the bid (aggressive sellers)
const auto* level = footprint.levelAt(price);
Quantity askVol = level->askVolume; // Buyers
Quantity bidVol = level->bidVolume; // Sellers
Delta¶
The difference between buying and selling aggression at each level:
Quantity levelDelta = level->delta(); // askVolume - bidVolume
Quantity totalDelta = footprint.totalDelta();
Positive delta = more aggressive buyers. Negative delta = more aggressive sellers.
Imbalance¶
When one side dominates at a price level:
double ratio = level->imbalanceRatio(); // askVolume / totalVolume
if (ratio > 0.7) {
// Strong buying (70%+ ask volume)
} else if (ratio < 0.3) {
// Strong selling (70%+ bid volume)
}
API Reference¶
Configuration¶
FootprintBar<MaxLevels> footprint;
footprint.setTickSize(Price::fromDouble(0.25)); // Price quantization
Adding Trades¶
Trade is classified as:
- isBuy = true → Ask volume (buyer aggression)
- isBuy = false → Bid volume (seller aggression)
Aggregate Metrics¶
Quantity delta = footprint.totalDelta(); // Sum of all level deltas
Quantity volume = footprint.totalVolume(); // Total volume (bid + ask)
size_t levels = footprint.numLevels(); // Active price levels
Level Access¶
// By index
const Level* lvl = footprint.level(0);
// By price
const Level* lvl = footprint.levelAt(Price::fromDouble(100.0));
// Level structure
struct Level {
Price price;
Quantity bidVolume; // Sellers hitting bid
Quantity askVolume; // Buyers lifting ask
Quantity totalVolume() const; // bid + ask
Quantity delta() const; // ask - bid
double imbalanceRatio() const; // ask / total (0.0 to 1.0)
};
All three are noexcept. There is no isSinglePrint() on FootprintBar::Level — that method belongs
to MarketProfile::Level.
Key Levels¶
// Price with most aggressive buying
Price buyPressure = footprint.highestBuyingPressure();
// Price with most aggressive selling
Price sellPressure = footprint.highestSellingPressure();
// Strongest imbalance above threshold
Price imbalance = footprint.strongestImbalance(0.7);
strongestImbalance(threshold) is direction-agnostic. It scores each level by
|imbalanceRatio - 0.5| * 2 and returns the price of the highest-scoring level whose score is at
least (threshold - 0.5) * 2. So it finds the strongest imbalance in either direction, not the
strongest buy-side imbalance. Returns a default-constructed Price when no level qualifies.
Because the gate is (threshold - 0.5) * 2, a threshold at or below 0.5 disables the gate entirely
and the call returns the single strongest imbalanced level regardless of direction. To distinguish
buying from selling pressure, read imbalanceRatio() at the returned price, or use
highestBuyingPressure() / highestSellingPressure().
Reset¶
Trading Strategies¶
Support/Resistance from Imbalances¶
Price extreme = footprint.strongestImbalance(0.7);
if (extreme.raw() != 0) {
// Check the direction at that price: ratio > 0.5 = aggressive buying
// (potential support), ratio < 0.5 = aggressive selling (potential
// resistance). strongestImbalance itself does not tell you which.
}
Do not call strongestImbalance(0.3) expecting a sell-side result — a threshold below 0.5 turns the
gate off and returns the same direction-agnostic extreme.
Delta Divergence¶
void onBar(const BarEvent& ev) {
if (ev.bar.close > ev.bar.open) { // Up bar
if (footprint.totalDelta().raw() < 0) {
// Price up but sellers dominate - bearish divergence
}
}
}
Absorption Detection¶
const auto* highLevel = footprint.levelAt(highPrice);
if (highLevel && highLevel->bidVolume.raw() > highLevel->askVolume.raw() * 2) {
// Heavy selling absorbed at high - potential reversal
}
Footprint Visualization¶
+--------+--------+--------+--------+
| Price | Bid | Ask | Delta |
+--------+--------+--------+--------+
| 101.0 | 18.0 | 2.0 | - 16.0 | <- Heavy selling
| 100.5 | 4.0 | 5.0 | + 1.0 |
| 100.0 | 3.0 | 27.0 | + 24.0 | <- Heavy buying
| 99.5 | 6.0 | 4.0 | - 2.0 |
| 99.0 | 8.0 | 3.0 | - 5.0 |
+--------+--------+--------+--------+
Combining with Bars¶
Reset footprint at each bar close to get per-bar order flow:
FootprintBar<64> currentFootprint;
void onTrade(const TradeEvent& ev) {
currentFootprint.addTrade(ev);
}
void onBar(const BarEvent& ev) {
// Analyze footprint for completed bar
Quantity delta = currentFootprint.totalDelta();
Price imbalance = currentFootprint.strongestImbalance();
// Store for later analysis
footprintHistory.push_back({ev.bar, currentFootprint});
// Reset for next bar
currentFootprint.clear();
}
Performance¶
| Operation | Complexity |
|---|---|
| addTrade | O(n) worst case |
| totalDelta | O(n) |
| highestBuyingPressure | O(n) |
| strongestImbalance | O(n) |
| levelAt | O(n) |
Where n = number of active price levels (≤ MaxLevels).
Example Output¶
=== Order Flow Analysis ===
Total Volume: 80
Total Delta: 2
Highest buying pressure: $100
Highest selling pressure: $101
Strong imbalance at $100 (ratio: 90% buy)
=== Trading Signals ===
[BIAS] Bullish - Net buying pressure
[SUPPORT] Strong buying at $100.0 - potential support
[RESISTANCE] Strong selling at $101.0 - potential resistance
Files¶
| File | Description |
|---|---|
aggregator/custom/footprint_bar.h |
FootprintBar template |
See Also¶
- Volume Profile - Aggregate volume distribution
- Market Profile - Time-based analysis (TPO)
- Bar Aggregator - OHLCV bar generation