SymbolStateMap¶
SymbolStateMap is an O(1) container for per-symbol state, indexed by SymbolId.
Purpose¶
- Provide O(1) access to per-symbol state
- Optimize for the common case (symbol IDs < 256)
- Handle overflow gracefully for large symbol counts
- Cache-line aligned for optimal memory access
Design¶
The container uses a two-tier approach:
- Flat array (symbols 0-255): Direct O(1) access, cache-line aligned
- Overflow vector (symbols >= 256): Linear search, rare case
alignas(64) std::array<State, kMaxSymbols> _flat{};
std::array<bool, kMaxSymbols> _initialized{};
OverflowStorage<State, std::is_move_constructible_v<State>> _overflowStorage;
OverflowStorage is conditional: for a move-constructible State it holds a
std::vector<std::pair<SymbolId, State>>; for a non-move-constructible State it is an empty stub
whose size() is always 0. So a State that cannot be moved gets no overflow tier at all — only the
flat array is available, and symbols at or above kMaxSymbols are not storable.
API¶
Access¶
// Get or create state for symbol (O(1) for sym < 256)
State& operator[](SymbolId symbol) noexcept;
const State& operator[](SymbolId symbol) const noexcept;
// Get pointer if exists, nullptr otherwise
State* tryGet(SymbolId symbol) noexcept;
const State* tryGet(SymbolId symbol) const noexcept;
// Check if symbol has state
bool contains(SymbolId symbol) const noexcept;
Iteration¶
// Iterate over all initialized symbols
template <typename Func>
void forEach(Func&& fn);
// Func signature: void(SymbolId, State&)
Utilities¶
void clear() noexcept; // Clear all state
size_t size() const noexcept; // Count of initialized symbols
Example¶
// Per-symbol position tracking
struct PositionState
{
double quantity{0.0};
double avgPrice{0.0};
double realizedPnl{0.0};
};
SymbolStateMap<PositionState> positions;
// Update position
positions[btcSymbol].quantity += 10.0;
positions[btcSymbol].avgPrice = 50000.0;
// Check if tracked
if (positions.contains(ethSymbol))
{
auto& state = positions[ethSymbol];
// ...
}
// Iterate all positions
positions.forEach([](SymbolId sym, PositionState& pos) {
std::cout << "Symbol " << sym << ": " << pos.quantity << "\n";
});
// Accumulate total PnL
double totalPnl = 0.0;
positions.forEach([&totalPnl](SymbolId, const PositionState& pos) {
totalPnl += pos.realizedPnl;
});
Performance¶
| Operation | Complexity (sym < 256) | Complexity (sym >= 256) |
|---|---|---|
operator[] |
O(1) | O(n) overflow search |
contains |
O(1) | O(n) overflow search |
tryGet |
O(1) | O(n) overflow search |
forEach |
O(MaxSymbols) | O(MaxSymbols + n) |
For most trading systems with < 256 symbols, all operations are O(1).
Memory¶
- Flat array:
MaxSymbols * sizeof(State)bytes (64-byte aligned) - Initialized flags:
MaxSymbolsbytes - Overflow: dynamic allocation only when needed
Default with SymbolContext: ~2 MB for 256 symbols. SymbolContext is ~8 KB, dominated by
NLevelOrderBook<512>'s two std::array<Quantity, 512> (2 x 512 x 8 bytes = 8192 bytes).
Template Parameters¶
| Parameter | Default | Description |
|---|---|---|
State |
- | Per-symbol state type |
MaxSymbols |
256 | Flat array size, symbols >= this go to overflow |
See Also¶
- SymbolContext - Per-symbol state struct
- Strategy - Uses SymbolStateMap internally