Distinguish Volume from Ticks per the Tick Stream

TLDR: How does Tradovate distinguish actual market orders or traded volume from a tick chart? It appears like the tick price and volume in the packets is a last traded price & volume, so this ends up persisting across packets until new last traded price and volume information arrives. Because of the persistent last traded price and volume, one could inadvertently overestimate the actual quantity of market orders arriving.


I’m attempting identify market orders (volume) from a tick chart series. The Tick Stream panel in the Trader Desktop app seems to appropriately identify market orders (volume) as there are generally more DOM changes than ticks coming through the Tick Stream panel for a given time period (i.e., more limit order changes in the market than there is actual market order volume). Intuitively, that should be the case as the limit order book sees more modifications and activity than the actual traded volume.

To evaluate the issue, I compare a 1-tick chart and 1-volume chart. The 1-tick chart is identical to the one requested by the Trader Desktop app if only a single Tick Stream panel is present (the chart/subscribeQuote request that is also made by the app). However, rather than getting a timestamp-based chart, I go for an equal number of elements, so I can compare like for like. In theory, a 1-volume chart returning 1000 bars should have barTotalVolume >= 1000, and while a 1-tick chart returning 1000 ticks could have any total volume, because we expect more ticks coming from limit order changes at the bid / ask than from traded volume, we should anticipate that tickTotalVolume <= barTotalVolume.

1-Tick Chart

md/getchart\n4\n\n{"symbol":"MESU1","chartDescription":{"underlyingType":"Tick","elementSize":1,"elementSizeUnit":"UnderlyingUnits"},"timeRange":{"asMuchAsElements":1000}}

1-Volume Chart

md/getchart\n5\n\n{"symbol":"MESU1","chartDescription":{"underlyingType":"Tick","elementSize":1,"elementSizeUnit":"Volume"},"timeRange":{"asMuchAsElements":1000}}

I process the charts using the below code, which is a modification of the example in the documentation. The intent is to process ticks and bars into a nearly-similar format where I can compare bidVolume and offerVolume.

charts?.forEach(({id, td, bars, bp, bt, ts, tks, eoh}) => {
	
	// Handle bars
	bars?.forEach(obj => {
	
		// Ensure `timestamp` is accessible as a numerical value for simplicity
		let { open, high, low, close, upVolume, downVolume, upTicks, downTicks, bidVolume, offerVolume} = obj;
		let timestamp = new Date(obj?.timestamp).getTime();
		let bar = { ...obj, timestamp };

		// In a 1-volume bar chart, each bar will contain 1-volume. Orders greater than 1-volume will be sent as multiple bars, sharing the same timestamp. As such, we simply add all observed bars to the state.bars array.
		state.bars.push(bar);

	})

	// Handle ticks
	tks?.forEach(tick => {    
		let {t, p, s, b, a, bs, as} = tick;

		const timestamp = bt + t;   // Actual tick timestamp
		const price = (bp + p) * ts;   // Tick price as contract price

		// Actual bid price as contract price (if bid size defined)
		const bidPrice = bs && ((bp + b) * ts);   

		// Actual ask price as contract price (if ask size defined)
		const askPrice = as && ((bp + a) * ts);    

		state.ticks.push({
			id: tick.id,
			timestamp: new Date(timestamp).getTime(),

			price, // Traded price (e.g., market order price)
			size: s, // Tick size (tick volume, that is, traded volume, market order volume)

			bidPrice,
			bidSize: bs,

			askPrice,
			askSize: as,

			// Custom parameters to make it easier to post-process market orders
			// Sell market orders should by definition be less than the ask, but not necessarily equal to the bid if this is the final bid before a price level shift, and vice versa for a buy market order
			offerVolume: (price > bidPrice) ? s : 0, // Buy market orders
			bidVolume: (price < askPrice) ? s : 0 // Sell market orders
		});

	})
})

What I notice is that the price and size (namely, the tks.<tick>.s field, and thus, a non-zero bidVolume or offerVolume) is present in every tick / message received from the tick chart subscription. This leads me to believe that the tick message is sending the last traded price and volume in every message, even if no actual volume occurred for that particular tick (i.e., the tick was sent to reflect the limit orders at the bid or ask changing, not to reflect volume occurring).

To debug, I total the observed volumes between the same tick and volume charts, as soon as the first batch of 1000 elements was received. Because I’ve given the 1-volume bars the same offerVolume and bidVolume fields as the 1-tick messages, I can compare apples to apples.

// Debug ticks vs. volume bars
// Note: sum(), min(), and max() are custom functions, and work as one would expect

// Accumulate tick info
let numTicks = state.ticks.length;
let tickOfferVolume = sum(state.ticks.map(x=>x.offerVolume));
let tickBidVolume = sum(state.ticks.map(x=>x.bidVolume));
let tickVolumes = state.ticks.map(x=>x.bidVolume + x.offerVolume);
let tickMinVolume = min(tickVolumes);
let tickMinCount = tickVolumes.filter(x=>x===tickMinVolume).length;
let tickMaxVolume = max(tickVolumes);
let tickMinCount = tickVolumes.filter(x=>x===tickMaxVolume).length;
let tickTotalVolume = sum(state.ticks.map(x=>x.bidVolume + x.offerVolume));
	
// Accumulate bar info
let numBars = state.bars.length;
let barOfferVolume = sum(state.bars.map(x=>x.offerVolume));
let barBidVolume = sum(state.bars.map(x=>x.bidVolume));
let barVolumes = state.bars.map(x=>x.bidVolume + x.offerVolume);
let barMinVolume = min(barVolumes);
let barMinCount = barVolumes.filter(x=>x===barMinVolume).length;
let barMaxVolume = max(barVolumes);
let barMaxCount = barVolumes.filter(x=>x===barMaxVolume).length;
let barTotalVolume = sum(state.bars.map(x=>x.bidVolume + x.offerVolume));
	
// Log the output
console.log(`${numTicks} Ticks (${tickOfferVolume} A + ${tickBidVolume} B = ${tickTotalVolume} V), Min: ${tickMinCount} ticks x ${tickMinVolume} V, Max: ${tickMaxCount} ticks x ${tickMaxVolume} V\n${numBars} Bars  (${barOfferVolume} A + ${barBidVolume} B = ${barTotalVolume} V), Min: ${barMinCount} bars  x ${barMinVolume} V, Max: ${barMaxCount} bars  x ${barMaxVolume} V`)

I observe that there tend to be less volume from bars than tick messages. Because I see 1000 volume in a 1000 elements load of 1-volume bars, this checks out; however, because I see a total volume of 2038 in the 1000 elements load of 1-tick bars with a minimum tick volume of 1 (it should be 0 if ticks arrive that only contain bid / ask changes), I’m inclined to believe that all ticks contain a volume, even when they shouldn’t, thus indicating that the tick chart shows last traded volume.

1000 Ticks (1399 A + 639 B = 2038 V), Min: 691  ticks x 1 V, Max: 1    ticks x 63 V
1000 Bars  (616  A + 384 B = 1000 V), Min: 1000 bars  x 1 V, Max: 1000 bars  x 1  V

That being said, how does Tradovate determine the actual market orders for the Tick Stream, if not using the 1-volume bars? The quotes returned also seem to exhibit the same behavior as the tick chart (i.e., carrying the last traded price and size, rather than omitting it if no volume during that quote), so it’s unclear to me how the Tick Stream actually functions or if there’s a more elegant way to estimate market order activity.

For my purposes, subscribing to a 1-volume chart is perfectly fine, only complicates my logic slightly. Just trying to understand if there’s a more formal way to determine if a traded price and size in a 1-tick chart is real.

Thanks!

wow! certainly this would be very much a complicated process even without any latency, of which there is a lot of! Have you considered just using multiple tick charts say 1,000 vs 2,000 and letting the frequency distribution of trades work itself out so your signals are synched up.? You can then use drawing tools to identify support and resistance to the 2000 tick chart and synch it up with the 1,000 tick chart and keep both open at the same time. This eliminates the uncertainty of the parsing of volume type information between a 1 minute chart and a tick chart of any magnitude. Also use another screen to add any number of permutations and voila! you now can look for confluence across different periods of # of transactions/ or minutes. Remember, Time is relative and idiosyncratic depending on the pace of the trade while Tick charts are always consistent in parcing out the number of transactions per Candle or Bar. I trust that from the nature of your posts you want both and can have both provided you have enough computer power and monitors… U can have your cake and eat it as well!
Happy Trading!