Composites#
This section contains 6 examples for Composites using the onetick-py.
Each example is a self-contained script that can be run against the OneTick Cloud sample databases.
# onetick-py WebAPI configuration for OneTick Cloud
import os
os.environ['OTP_WEBAPI'] = '1'
os.environ['OTP_HTTP_ADDRESS'] = 'https://rest.cloud.onetick.com'
os.environ['OTP_ACCESS_TOKEN_URL'] = 'https://cloud-auth.parent.onetick.com/realms/OMD/protocol/openid-connect/token'
os.environ['OTP_CLIENT_ID'] = '__FILL_IN__'
os.environ['OTP_CLIENT_SECRET'] = '__FILL_IN__'
Total Volume Per Venue Across Symbols#
Calculating the Volume Traded across all symbols for each venue within the Composite. e.g. US_COMP.
The DAY summary table includes the daily Volume rollup,
making it unnecessary to calculate across the Trade table TRD.
The venue is stored in the field EXCHANGE for US_COMP, and QUOTE_VENUE for other composites.
The composite for all venues is typically added as empty quotes e.g. ''.
The primary venue is identified as PRIM.
import onetick.py as otp
# Retrieve DAY Records from US_COMP_DAILY
data = otp.DataSource(db='US_COMP_SAMPLE_DAILY', tick_type='DAY')
# Limit Fields
data = data[['EXCHANGE', 'VOLUME']]
# Merge across all symbols
data = otp.merge([data], symbols=otp.Symbols(db='US_COMP_SAMPLE_DAILY'), identify_input_ts=True)
# Aggregate: count number of quotes per venue
data = data.agg({'VOLUME': otp.agg.sum('VOLUME')}, group_by=['EXCHANGE'])
# Filter out the Composite
data = data.where(data['EXCHANGE'] != '')
# Filter out the Primary
data = data.where(data['EXCHANGE'] != 'PRIM')
# Run query across the time range.
result = otp.run(data,
start=otp.dt(2024, 1, 3),
end=otp.dt(2024, 1, 4),
timezone='America/New_York')
result
| Time | EXCHANGE | VOLUME | |
|---|---|---|---|
| 0 | 2024-01-04 | A | 33890564 |
| 1 | 2024-01-04 | B | 45310224 |
| 2 | 2024-01-04 | C | 47420646 |
| 3 | 2024-01-04 | D | 5101343473 |
| 4 | 2024-01-04 | H | 220129350 |
| ... | ... | ... | ... |
| 14 | 2024-01-04 | U | 366155659 |
| 15 | 2024-01-04 | V | 234670455 |
| 16 | 2024-01-04 | X | 27713620 |
| 17 | 2024-01-04 | Y | 121288137 |
| 18 | 2024-01-04 | Z | 578921698 |
19 rows × 3 columns
Trade Count Per Venue#
Calculating the trade count for each venue within the Composite. e.g. CA_COMP.
The quotes are stored in the TRD table.
The venue is stored in the field TRADE_VENUE, except for US_COMP which uses EXCHANGE.
import onetick.py as otp
# Retrieve Trades from CA_COMP
data = otp.DataSource(db='CA_COMP_SAMPLE', tick_type='TRD')
# Aggregate: count number of trades per venue
data = data.agg({'TRADE_COUNT': otp.agg.count()}, group_by=['TRADE_VENUE'])
# Define Output fields
data = data[['TRADE_VENUE', 'TRADE_COUNT']]
# Run query for selected symbol (for TD) and time range.
result = otp.run(data,
start=otp.dt(2024, 1, 3, 9, 30),
end=otp.dt(2024, 1, 3, 16, 0),
timezone='America/Toronto',
symbols='TD')
result
| Time | TRADE_VENUE | TRADE_COUNT | |
|---|---|---|---|
| 0 | 2024-01-03 16:00:00 | CHIC | 8746 |
| 1 | 2024-01-03 16:00:00 | CSE2 | 18 |
| 2 | 2024-01-03 16:00:00 | LYNX | 93 |
| 3 | 2024-01-03 16:00:00 | MATN | 2879 |
| 4 | 2024-01-03 16:00:00 | NEOD | 18 |
| ... | ... | ... | ... |
| 10 | 2024-01-03 16:00:00 | XATX | 14 |
| 11 | 2024-01-03 16:00:00 | XCX2 | 1296 |
| 12 | 2024-01-03 16:00:00 | XCXD | 1749 |
| 13 | 2024-01-03 16:00:00 | XICX | 13 |
| 14 | 2024-01-03 16:00:00 | XTSE | 21023 |
15 rows × 3 columns
Quote Count Per Venue#
Calculating the quote count for each venue within the Composite. e.g. CA_COMP.
The quotes are stored in the QTE table.
The venue is stored in the field QUOTE_VENUE, except for US_COMP which uses EXCHANGE.
import onetick.py as otp
# Retrieve Quotes from CA_COMP
data = otp.DataSource(db='CA_COMP_SAMPLE', tick_type='QTE')
# Aggregate: count number of quotes per venue
data = data.agg({'QUOTE_COUNT': otp.agg.count()}, group_by=['QUOTE_VENUE'])
# Define Output fields
data = data[['QUOTE_VENUE', 'QUOTE_COUNT']]
# Run query for selected symbol (for TD) and time range.
result = otp.run(data,
start=otp.dt(2024, 1, 3, 9, 30),
end=otp.dt(2024, 1, 3, 16, 0),
timezone='America/Toronto',
symbols='TD')
result
| Time | QUOTE_VENUE | QUOTE_COUNT | |
|---|---|---|---|
| 0 | 2024-01-03 16:00:00 | CHIC | 350854 |
| 1 | 2024-01-03 16:00:00 | CSE2 | 49538 |
| 2 | 2024-01-03 16:00:00 | LYNX | 59278 |
| 3 | 2024-01-03 16:00:00 | NEOE | 202624 |
| 4 | 2024-01-03 16:00:00 | NEON | 288203 |
| 5 | 2024-01-03 16:00:00 | OMGA | 168343 |
| 6 | 2024-01-03 16:00:00 | PURE | 81993 |
| 7 | 2024-01-03 16:00:00 | XATS | 144204 |
| 8 | 2024-01-03 16:00:00 | XATX | 856 |
| 9 | 2024-01-03 16:00:00 | XCX2 | 246433 |
| 10 | 2024-01-03 16:00:00 | XTSE | 317337 |
EBBO Custom Calculation#
Calculate a Custom EBBO / NBBO based on Selected Venues using the European Composite Dataset EU_COMP.
The European Composite includes quotes from all European Venues, plus a consolidated NBBO.
A custom NBBO can be calculated by filtering consolidated quotes on venue using the QUOTE_VENUE field
and currency using the CURRENCY field.
The virtual_ob() method is used in association with the
ob_snapshot_wide() method to construct the new consolidated book.
Using parameter max_levels=1 to limit the resulting book to Top of Book (ToB) to produce the NBBO.
import onetick.py as otp
# Retrieve Quotes from EU_COMP
data = otp.DataSource(db='EU_COMP_SAMPLE', tick_type='QTE')
# Filter on Continuous Trading Periods
data = data.where(data['OMD_STATUS'] == 'T')
# Filter on Required Currency
data = data.where(data['CURRENCY'] == 'GBX')
# Filter on Selected Venues
data = data.where(data['QUOTE_VENUE'].isin('AQXE', 'BATE', 'CHIX', 'EQTC', 'TRQX', 'XLON'))
# Create a order book data format based on the quotes from each venue
data = data.virtual_ob(['QUOTE_VENUE'])
# Rebuild the NBBO from the order book data
data = data.ob_snapshot_wide(running=True, max_levels=1)
# Add Spread
data['SPREAD'] = data['ASK_PRICE'] - data['BID_PRICE']
# Filter out events that occur at the same timestamp, keeping the last event per timestamp
data['NEXT_TS'] = data['TIMESTAMP'][+1]
data = data.where(data['NEXT_TS'] > data['TIMESTAMP'])
# Define Output fields
data = data[['BID_PRICE', 'BID_SIZE', 'ASK_PRICE', 'ASK_SIZE', 'SPREAD']]
# Return first 100 Rows
data = data.limit(100)
# Run query for selected ISIN (for Vodafone) and time range.
result = otp.run(data,
start=otp.dt(2024, 1, 3, 8, 0),
end=otp.dt(2024, 1, 3, 16, 0),
timezone='Europe/London',
symbols='GB00BH4HKS39')
result
| Time | BID_PRICE | BID_SIZE | ASK_PRICE | ASK_SIZE | SPREAD | |
|---|---|---|---|---|---|---|
| 0 | 2024-01-03 08:00:00.072000 | 69.34 | 21964 | 70.27 | 757 | 0.93 |
| 1 | 2024-01-03 08:00:00.076000 | 69.34 | 44497 | 70.27 | 757 | 0.93 |
| 2 | 2024-01-03 08:00:01.024000 | 69.34 | 44300 | 70.27 | 757 | 0.93 |
| 3 | 2024-01-03 08:00:01.122000 | 69.34 | 43160 | 70.68 | 49218 | 1.34 |
| 4 | 2024-01-03 08:00:01.163000 | 69.34 | 43160 | 70.68 | 50617 | 1.34 |
| ... | ... | ... | ... | ... | ... | ... |
| 95 | 2024-01-03 08:00:07.420198 | 70.06 | 3152 | 70.18 | 10252 | 0.12 |
| 96 | 2024-01-03 08:00:07.421000 | 70.07 | 2260 | 70.17 | 3461 | 0.10 |
| 97 | 2024-01-03 08:00:07.424000 | 70.08 | 100 | 70.17 | 3461 | 0.09 |
| 98 | 2024-01-03 08:00:07.427000 | 70.08 | 100 | 70.17 | 409 | 0.09 |
| 99 | 2024-01-03 08:00:07.435000 | 70.08 | 100 | 70.18 | 2300 | 0.10 |
100 rows × 6 columns
CBBO Custom Calculation#
Calculate a Custom CBBO / NBBO based on Selected Venues using the Canadian Composite Dataset CA_COMP.
The Canadian Composite includes quotes from all Canadian Venues, plus a consolidated NBBO.
A custom NBBO can be calculated by filtering on venue using the QUOTE_VENUE field.
The virtual_ob() method is used in association with the
ob_snapshot_wide() method to construct the new consolidated book.
Using parameter max_levels=1 to limit the resulting book to Top of Book (ToB) to produce the NBBO.
import onetick.py as otp
# Retrieve Quotes from CA_COMP
data = otp.DataSource(db='CA_COMP_SAMPLE', tick_type='QTE')
# Filter on Continuous Trading Periods
data = data.where(data['OMD_STATUS'] == 'T')
# Filter on Selected Venues
data = data.where(
data['QUOTE_VENUE'].isin('CHIC', 'CSE2', 'LYNX', 'NEOE', 'NEON', 'OMGA', 'PURE', 'XATS', 'XATX', 'XCX2', 'XTSE')
)
# Create a order book data format based on the quotes from each venue
data = data.virtual_ob(['QUOTE_VENUE'])
# Rebuild the NBBO from the order book data
data = data.ob_snapshot_wide(running=True, max_levels=1)
# Add Spread
data['SPREAD'] = data['ASK_PRICE'] - data['BID_PRICE']
# Filter out events that occur at the same timestamp, keeping the last event per timestamp
data['NEXT_TS'] = data['TIMESTAMP'][+1]
data = data.where(data['NEXT_TS'] > data['TIMESTAMP'])
# Define Output fields
data = data[['BID_PRICE', 'BID_SIZE', 'ASK_PRICE', 'ASK_SIZE', 'SPREAD']]
# Return first 100 Rows
data = data.limit(100)
# Run query for selected ISIN (for Vodafone) and time range.
result = otp.run(data,
start=otp.dt(2024, 1, 3, 9, 30),
end=otp.dt(2024, 1, 3, 16, 0),
timezone='America/Toronto',
symbols='TD')
result
| Time | BID_PRICE | BID_SIZE | ASK_PRICE | ASK_SIZE | SPREAD | |
|---|---|---|---|---|---|---|
| 0 | 2024-01-03 09:30:00.063 | 85.19 | 100 | 86.08 | 1100 | 0.89 |
| 1 | 2024-01-03 09:30:00.067 | 85.24 | 800 | 86.08 | 1100 | 0.84 |
| 2 | 2024-01-03 09:30:00.070 | 85.22 | 400 | 86.08 | 1100 | 0.86 |
| 3 | 2024-01-03 09:30:00.072 | 84.28 | 100 | 86.08 | 1100 | 1.80 |
| 4 | 2024-01-03 09:30:00.073 | 84.28 | 100 | 86.08 | 1100 | 1.80 |
| ... | ... | ... | ... | ... | ... | ... |
| 95 | 2024-01-03 09:30:01.954 | 85.25 | 2700 | 85.31 | 1400 | 0.06 |
| 96 | 2024-01-03 09:30:01.963 | 85.25 | 2700 | 85.31 | 1300 | 0.06 |
| 97 | 2024-01-03 09:30:01.985 | 85.25 | 2700 | 85.31 | 1000 | 0.06 |
| 98 | 2024-01-03 09:30:01.986 | 85.25 | 3000 | 85.31 | 1000 | 0.06 |
| 99 | 2024-01-03 09:30:01.988 | 85.25 | 3600 | 85.31 | 1000 | 0.06 |
100 rows × 6 columns
Trade Volume to NBBO Calculation#
Calculate trade volume relative to NBBO levels for each trade.
Joins trades (TRD) with prevailing NBBO quotes and classifies each trade by NBBO level.
Classifies each trade volume by NBBO level (AT_MID, INSIDE_NBBO, AT_NBBO, OUTSIDE_NBBO).
Aggregates total volume by exchange and symbol for each NBBO classification.
Calculate trade volume relative to NBBO levels for each trade.
Step 1: For each trade, retrieve the prevailing NBBO at that moment
Step 2: Classify each trade volume by NBBO level (
AT_MID,INSIDE_NBBO,AT_NBBO,OUTSIDE_NBBO)Step 3: Aggregate total volume by exchange for each NBBO classification
import onetick.py as otp
# Retrieve trade ticks with PRICE and SIZE fields
trades = otp.DataSource(db='US_COMP', tick_type='TRD')
trades = trades[['PRICE', 'SIZE', 'EXCHANGE']]
# Retrieve prevailing NBBO quotes with BID_PRICE and ASK_PRICE fields
nbbo = otp.DataSource(db='US_COMP', tick_type='NBBO', back_to_first_tick=86400)
nbbo = nbbo[['BID_PRICE', 'ASK_PRICE']]
# Join trades with prevailing NBBO at trade time
joined = otp.join_by_time([trades, nbbo])
# Calculate mid price
joined['MID_PRICE'] = (joined['BID_PRICE'] + joined['ASK_PRICE']) / 2
# Classify trade volume by NBBO level using conditional logic (boolean * size = size or 0)
joined['VOLUME_AT_MID'] = (joined['PRICE'] == joined['MID_PRICE']) * joined['SIZE']
joined['VOLUME_INSIDE_NBBO'] = (
(joined['PRICE'] > joined['BID_PRICE']) & (joined['PRICE'] < joined['ASK_PRICE'])
) * joined['SIZE']
joined['VOLUME_AT_NBBO'] = (
(joined['PRICE'] == joined['BID_PRICE']) | (joined['PRICE'] == joined['ASK_PRICE'])
) * joined['SIZE']
joined['VOLUME_OUTSIDE_NBBO'] = (
(joined['PRICE'] < joined['BID_PRICE']) | (joined['PRICE'] > joined['ASK_PRICE'])
) * joined['SIZE']
# Aggregate by exchange and symbol
data = joined.agg({
'VOLUME_AT_MID': otp.agg.sum('VOLUME_AT_MID'),
'VOLUME_INSIDE_NBBO': otp.agg.sum('VOLUME_INSIDE_NBBO'),
'VOLUME_AT_NBBO': otp.agg.sum('VOLUME_AT_NBBO'),
'VOLUME_OUTSIDE_NBBO': otp.agg.sum('VOLUME_OUTSIDE_NBBO'),
'TRADE_COUNT': otp.agg.count()
}, group_by=['EXCHANGE'])
result = otp.run(
data,
symbols=['CSCO'],
start=otp.dt(2024, 1, 3),
end=otp.dt(2024, 1, 4),
timezone='America/New_York'
)
result
| Time | EXCHANGE | VOLUME_AT_MID | VOLUME_INSIDE_NBBO | VOLUME_AT_NBBO | VOLUME_OUTSIDE_NBBO | TRADE_COUNT | |
|---|---|---|---|---|---|---|---|
| 0 | 2024-01-04 | A | 7541 | 5108 | 14257 | 504 | 236 |
| 1 | 2024-01-04 | B | 2629 | 2569 | 30459 | 500 | 485 |
| 2 | 2024-01-04 | C | 7102 | 5942 | 107844 | 597 | 1832 |
| 3 | 2024-01-04 | D | 1551492 | 3702729 | 1936908 | 245550 | 37598 |
| 4 | 2024-01-04 | H | 71108 | 6834 | 228942 | 1319 | 2537 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 11 | 2024-01-04 | U | 143049 | 36803 | 484555 | 7797 | 5444 |
| 12 | 2024-01-04 | V | 162738 | 139259 | 259411 | 2846 | 4408 |
| 13 | 2024-01-04 | X | 7165 | 4350 | 39389 | 2120 | 604 |
| 14 | 2024-01-04 | Y | 12848 | 8303 | 144428 | 2580 | 1519 |
| 15 | 2024-01-04 | Z | 286876 | 110179 | 859658 | 13999 | 11603 |
16 rows × 7 columns