TSE Itayose Auction Pricing: The Five-Condition Algorithm
The Tokyo Stock Exchange (TSE) opens and closes every trading day with an auction — not continuous matching. This auction mechanism, called Itayose (板寄せ, literally “gathering orders on a board”), collects all orders and determines a single clearing price that maximizes execution volume while respecting a precise sequence of five tiebreaking conditions.
Most exchanges use some form of auction at open and close, but TSE’s five-condition specification is uniquely detailed and deterministic. Understanding it is essential for anyone building TSE market data systems, and the algorithm itself is a fascinating case study in how financial microstructure balances competing objectives.
This article walks through the complete Itayose algorithm based on TSE’s official specification, with a production C++ implementation that processes real FLEX Full MBO historical data from pcap files.
Itayose vs. Continuous Matching
In continuous matching (Zaraba), orders execute immediately when they cross the spread. Price is determined order-by-order.
In Itayose, no execution happens until the exchange collects all orders, then computes a single Indicative Auction Price (IAP) and Indicative Auction Volume (IAV). Every order that can be filled at the IAP is executed simultaneously.
TSE uses Itayose in three scenarios:
- Opening auction (9:00 AM)
- Closing auction (3:30 PM)
- Special quote during intraday halts (circuit breakers, volatility interruptions)
The FLEX market data feed signals Itayose mode via the O (Order Status) tag with pricing_method = 1.
The FLEX Protocol: Building the Order Book
Before we can compute the Itayose price, we need to reconstruct the order book from raw network packets.
Packet Structure
TSE FLEX Full MBO data arrives over UDP multicast. Each packet has a FlexPacketHeader followed by TLV (Type-Length-Value) encoded tags:
┌─────────────────────────────────────────┐
│ FlexPacketHeader (25 bytes) │
├─────────────────────────────────────────┤
│ Tag 1: [length][type][data...] │
│ Tag 2: [length][type][data...] │
│ ... │
└─────────────────────────────────────────┘
The header fields:
| Field | Size | Endian | Purpose |
|---|---|---|---|
multicast_group | 1 byte | — | Channel identifier |
sys_reboots | 1 byte | — | Reboot counter (for dedup) |
seq_num | 4 bytes | Big | Sequence number |
issue_code | 12 bytes | ASCII | Symbol, left-padded with spaces |
msg_count | 1 byte | — | Number of tags in this packet |
Tag Types
| Tag | Name | Action |
|---|---|---|
A | Add Order | Insert into order book (limit or market) |
D | Delete Order | Remove (cancel, expire, or qty-reduction) |
E | Execution | Partial fill |
C | Execution with Price | Fill at a specific price |
O | Order Status | Trading mode (Itayose/Zaraba) + reference price |
T | Time | Timestamp |
R | Reset | Clear all order books |
L | Communication Control | Informational |
MBO Order Book
The FLEX Full feed provides Market By Order (MBO) data — every individual order is visible, not just aggregated price levels. We maintain an unordered_map<orderId, Order>:
struct Order {
uint64_t order_id;
uint64_t price;
uint64_t qty;
char side; // 'B' or 'S'
uint32_t time_usec; // priority timestamp
};
Market orders use a sentinel price 0xFFFFFFFFFFFFFFFF — they participate at every price level.
Modification Handling
TSE encodes order modifications as a Delete + Add pair with modification_flag = 1:
D(orderId, flag=1)— saves the original order’stime_usecin aPendingModstructA(orderId, flag=1)— inherits the savedtime_usec, preserving queue priority
This is crucial: partial quantity reductions keep the original time priority, while price changes or full cancels do not.
Packet Deduplication
FLEX packets may be retransmitted. We track seen packets using a composite key:
struct PacketKey {
uint8_t group; // multicast_group
uint8_t reboots; // sys_reboots
uint32_t seq; // sequence number
};
VLAN Support
Some capture environments insert 802.1Q VLAN tags (4 extra bytes in the Ethernet header). We detect this by checking the EtherType at offset 12:
uint16_t ether_type = ntohs(*(const uint16_t*)(packet + 12));
if (ether_type == 0x8100) return 18; // VLAN-tagged
return 14; // Standard Ethernet
Price Precision: Integer × 10,000
TSE wire format represents prices as 64-bit big-endian integers scaled by 10,000. This eliminates floating-point errors entirely:
| Wire Value | Actual Price |
|---|---|
17770000 | ¥1,777.0000 |
34565000 | ¥3,456.5000 |
6746000 | ¥674.6000 |
All arithmetic in the Itayose algorithm operates on these scaled integers. We only convert to decimal at output time.
Market Order Sentinel
Market orders carry price = 0xFFFFFFFFFFFFFFFF (the maximum uint64_t value). They participate in volume computation at every price level but are never selected as the IAP themselves.
TSE Tick Size Tables
TSE has two tick size schedules depending on the instrument’s classification:
Table 3: TOPIX500 Constituents (Finer Ticks)
| Price Range (¥) | Tick Size (¥) |
|---|---|
| ≤ 1,000 | 0.1 |
| ≤ 3,000 | 0.5 |
| ≤ 10,000 | 1.0 |
| ≤ 30,000 | 5.0 |
| ≤ 100,000 | 10.0 |
| ≤ 300,000 | 50.0 |
| ≤ 1,000,000 | 100.0 |
| ≤ 3,000,000 | 500.0 |
| ≤ 10,000,000 | 1,000.0 |
| ≤ 30,000,000 | 5,000.0 |
| > 30,000,000 | 10,000.0 |
Table 1: Other Issues (Coarser Ticks)
| Price Range (¥) | Tick Size (¥) |
|---|---|
| ≤ 3,000 | 1.0 |
| ≤ 5,000 | 5.0 |
| ≤ 30,000 | 10.0 |
| ≤ 50,000 | 50.0 |
| ≤ 300,000 | 100.0 |
| ≤ 500,000 | 500.0 |
| ≤ 3,000,000 | 1,000.0 |
| ≤ 5,000,000 | 5,000.0 |
| ≤ 30,000,000 | 10,000.0 |
| ≤ 50,000,000 | 50,000.0 |
| > 50,000,000 | 100,000.0 |
Key implication: tick size is price-dependent. A ¥1,000 stock under Table 3 has a ¥0.1 tick, but a ¥3,000 stock has a ¥0.5 tick. The Itayose algorithm must look up the correct tick size at each candidate price level.
Source: TSE Tick Size Schedule
The Five Itayose Conditions
Now the core algorithm. Given the current order book, Itayose determines the IAP by applying five conditions in strict order. Each condition narrows the set of candidate prices until a single price remains.
Condition 1: Valid Price Range
The IAP must be within one tick above the highest order price to one tick below the lowest order price.
This defines the range where at least some cross can occur:
max_valid = highest_order_price + tick_at(highest_order_price)
min_valid = lowest_order_price - tick_at(lowest_order_price)
“Order price” includes both bids and asks. Market orders don’t count for range calculation (they have no limit price).
Special case: If only market orders exist (no limit orders at all), no price can satisfy Condition 1, so there is no transaction.
Condition 2: Maximum Executable Volume
Among prices in the valid range, select those where the executable volume is maximized.
At any price P:
- Executable ask volume = market ask qty + all asks at price ≤ P
- Executable bid volume = market bid qty + all bids at price ≥ P
- Executable volume = min(ask_volume, bid_volume)
We compute this efficiently using prefix sums:
// Ascending pass: cum_ask_le[i] = mkt_ask + asks at price ≤ price_levels[i]
std::vector<uint64_t> cum_ask_le(N);
{
uint64_t running = mkt_ask_qty;
auto it = asks.begin();
for (size_t i = 0; i < N; ++i) {
while (it != asks.end() && it->first <= price_levels[i]) {
running += it->second;
++it;
}
cum_ask_le[i] = running;
}
}
// Descending pass: cum_bid_ge[i] = mkt_bid + bids at price ≥ price_levels[i]
std::vector<uint64_t> cum_bid_ge(N);
{
uint64_t running = mkt_bid_qty;
auto it = bids.rbegin();
for (int i = (int)N - 1; i >= 0; --i) {
while (it != bids.rend() && it->first >= price_levels[i]) {
running += it->second;
++it;
}
cum_bid_ge[i] = running;
}
}
Both passes are O(N + M) where N = number of price levels and M = number of distinct order prices. This is far more efficient than a naive O(N × M) approach.
Condition 3: Minimum Imbalance (Surplus)
Among prices with maximum volume, select those with the smallest surplus.
Surplus (imbalance) at price P:
surplus = |ask_volume_at_P - bid_volume_at_P|
We track both the magnitude and direction:
int dir = (bid_vol > ask_vol) ? 1 : // buy-heavy
(ask_vol > bid_vol) ? -1 : // sell-heavy
0; // perfectly balanced
uint64_t imb = abs_diff(ask_vol, bid_vol);
Condition 4: Imbalance Direction
If all remaining candidates have the same imbalance direction, choose:
- All sell-heavy → lowest candidate price
- All buy-heavy → highest candidate price
Intuition: if every candidate has more supply than demand (sell-heavy), pick the lowest price to maximize buyer participation. If every candidate has more demand than supply (buy-heavy), pick the highest price to maximize seller participation.
bool all_buy_imb = std::all_of(c3_cands.begin(), c3_cands.end(),
[](const LevelStat& s){ return s.imbalance_dir == 1; });
bool all_sell_imb = std::all_of(c3_cands.begin(), c3_cands.end(),
[](const LevelStat& s){ return s.imbalance_dir == -1; });
if (all_sell_imb) return {lowest_price, matched_vol};
if (all_buy_imb) return {highest_price, matched_vol};
Condition 5: Reference Price Fallback
When imbalances are mixed (some buy-heavy, some sell-heavy), narrow the range and use the reference price (base price).
First, narrow the candidate range:
narrowed_low = lowest sell-heavy price
narrowed_high = highest buy-heavy price
Then apply the reference price (base_price):
| Case | Rule |
|---|---|
base_price > narrowed_high | Choose narrowed_high |
base_price < narrowed_low | Choose narrowed_low |
base_price within range | Choose base_price |
The reference price is initially the previous day’s closing price (from the venue JSON), but gets updated in real-time when the exchange broadcasts an O tag with isItayose() == true:
void processOrderStatus(const uint8_t* tag_ptr, ..., const std::string& symbol) {
const TradingStatus* ts = reinterpret_cast<const TradingStatus*>(tag_ptr);
if (ts->isItayose()) {
uint64_t bcp = ts->getBookCenterPrice();
if (bcp != 0) market[symbol].setBasePrice(bcp);
}
}
This is critical for intraday halts: after a circuit breaker, the reference price changes and the exchange broadcasts the new value.
Candidate Price Construction
A subtle but important detail: the Itayose algorithm must evaluate not just the prices where orders sit, but also the boundary prices and prices in gaps wider than one tick.
// Start with: min_valid, all bid prices, all ask prices, max_valid
std::vector<uint64_t> price_levels;
price_levels.push_back(min_valid);
for (const auto& [px, _] : bids) price_levels.push_back(px);
for (const auto& [px, _] : asks) price_levels.push_back(px);
price_levels.push_back(max_valid);
// Fill gaps wider than one tick
for (size_t i = 0; i + 1 < n; ++i) {
uint64_t lo = price_levels[i];
uint64_t hi = price_levels[i + 1];
uint64_t gap_tick = getTickSizeForPrice(lo, tick_table);
if (hi > lo + gap_tick) {
extras.push_back(lo + gap_tick); // Price one tick above lower boundary
uint64_t hi_tick = getTickSizeForPrice(hi, tick_table);
if (hi >= hi_tick)
extras.push_back(hi - hi_tick); // Price one tick below upper boundary
}
}
Without gap filling, we might miss the optimal Itayose price that sits between two order levels.
Volume Rounding
The IAV (Indicative Auction Volume) must be a multiple of the instrument’s lot size (unitOfTrading):
uint64_t matched_vol = (raw_matched_volume / lot_size) * lot_size;
This truncates downward — you can’t execute a partial lot.
Worked Example
Consider this simplified order book:
| Side | Price (¥) | Quantity |
|---|---|---|
| Bid | Market | 200 |
| Bid | 1,000 | 300 |
| Bid | 990 | 500 |
| Ask | Market | 100 |
| Ask | 1,010 | 400 |
| Ask | 1,020 | 300 |
Condition 1: Valid range = [1,000 + 1 tick, 1,010 - 1 tick] = [1,001, 1,009]
Condition 2: Evaluate executable volume at each candidate price:
| Price | Cum Ask | Cum Bid | Exec Vol | Surplus | Direction |
|---|---|---|---|---|---|
| 1,001 | 100 | 1,000 | 100 | 900 | Buy |
| 1,000 | 100 | 1,000 | 100 | 900 | Buy |
| 1,010 | 500 | 500 | 500 | 0 | Balanced |
| 1,009 | 100 | 500 | 100 | 400 | Buy |
Wait — let me redo this correctly. At price 1,010:
- Cum ask (price ≤ 1,010) = 100 (mkt) + 400 = 500
- Cum bid (price ≥ 1,010) = 200 (mkt) + 300 = 500
- Exec vol = 500, surplus = 0
At price 1,000:
- Cum ask (price ≤ 1,000) = 100 (mkt) = 100
- Cum bid (price ≥ 1,000) = 200 (mkt) + 300 + 500 = 1,000
- Exec vol = 100, surplus = 900
Maximum volume is 500 at price 1,010, with zero surplus. Condition 3 immediately resolves: IAP = ¥1,010, IAV = 500.
Itayose vs. A-Share Call Auction
TSE’s Itayose differs from the A-Share call auction in important ways:
| Feature | TSE Itayose | A-Share Call Auction |
|---|---|---|
| Condition 1 | Tick-based range from order extremes | Same principle |
| Condition 2 | Maximum executable volume | Same |
| Condition 3 | Minimum surplus | Same |
| Condition 4 | Direction check with specific rules | Similar but different tiebreaker |
| Condition 5 | Reference price with three cases | Close price of previous day |
| Tick size | Price-dependent, two tables | Fixed per instrument class |
| Market orders | Explicit sentinel price | Not supported in call auction |
| MBO data | Full order visibility | Only MBP in call auction |
The most distinctive TSE feature is the price-dependent tick size combined with explicit market order handling — neither exists in the A-Share call auction.
Complete Processing Pipeline
Putting it all together:
1. Load venue JSON → symbol info (base_price, tick_table, lot_size)
2. For each pcap file:
a. Parse Ethernet → IP → UDP → FLEX payload
b. Deduplicate by (group, reboots, seq_num)
c. For each tag in packet:
A → addOrder()
D → deleteOrder()
E/C → executeOrder()
O → updateBasePrice() if Itayose
R → reset() all books
3. For each symbol's order book:
a. calculateIndicativeMatch() → (IAP, IAV)
4. Write indicative_match.csv
Common Pitfalls
1. Price-Dependent Tick Size at Boundaries
Using a fixed tick size at the max_valid / min_valid boundaries is wrong. The tick size at ¥1,000 is different from ¥1,001 under Table 3. Always call getTickSizeForPrice() at the specific price level.
2. Market Order Handling in Volume Calculation
Market orders must be added to both the ask cumulative sum (ascending) and bid cumulative sum (descending). A common mistake is to add them only once.
3. Gap Filling Between Price Levels
If bids sit at ¥1,000 and asks at ¥1,050 with a ¥1 tick, there are 49 candidate prices between them that must be evaluated. Missing them can produce the wrong IAP.
4. Volume Truncation
The IAV must be rounded down to a multiple of lot_size, not rounded to nearest. Rounding up would imply executing more shares than are actually matchable.
5. Base Price Updates During Intraday Halts
The reference price isn’t static — it changes when the exchange broadcasts an O tag during Itayose mode. Using only the initial venue JSON base price will produce wrong results for intraday auctions.
6. Big-Endian Fields
All multi-byte fields in FLEX packets are big-endian. Forgetting __builtin_bswap32 / __builtin_bswap64 will silently produce wrong data on little-endian machines.
Output
The application produces indicative_match.csv with one row per symbol:
symbol, iap, iav
6954, 4165.0000, 332200
6857, 8779.0000, 294200
6486, 2010.0000, 1900
1382, 1743.0000, 300
Where:
- iap: Indicative Auction Price in yen (4 decimal places)
- iav: Indicative Auction Volume in shares (multiple of lot_size)
A price of 0.0000 means the symbol had no Itayose session in the processed pcap data (e.g., only continuous trading).
Summary
The TSE Itayose algorithm is a five-level cascade:
- Condition 1 narrows to the valid price range
- Condition 2 picks prices with maximum executable volume
- Condition 3 picks prices with minimum surplus
- Condition 4 uses imbalance direction (all-buy → highest, all-sell → lowest)
- Condition 5 falls back to the reference price within the narrowed range
Each condition acts as a filter, and the algorithm terminates as soon as a single price remains. The combination of price-dependent tick sizes, market order handling, and the reference price mechanism makes TSE’s Itayose more nuanced than typical exchange call auctions.
For trading firms operating on TSE, correctly implementing Itayose is not optional — the indicative auction price directly affects order routing, pre-trade analytics, and post-trade compliance. A single tick error in Condition 5 can cascade into millions of yen of execution difference at the opening auction.
References
- TSE Itayose Conditions and Pricing Examples (official specification document)
- TSE Tick Size Schedule
- TSE FLEX Full MBO Historical Data Format Specification