The classical Net Volume indicator operates on OHLCV bars, not on raw trades.
- Uptick is registered when the bar's close is higher than open
- Downtick is registered when the bar's close is lower than open
- Signed (directional) volume is positive on uptick, negative on downtick
- Net Volume is the cumulative sum of signed volumes
Let me illustrate the concept with an example using 5-minute bars.
| datetime | open() | close() | tick | volume() | sign_volume | net_volume |
|----------|--------:|--------:|-----:|---------:|------------:|-----------:|
| 09:30:00 | 62.35 | 62.48 | 1 | 95611 | 95611 | 95611 |
| 09:35:00 | 62.42 | 62.97 | 1 | 50887 | 50887 | 146498 |
| 09:40:00 | 62.94 | 62.82 | -1 | 45114 | -45114 | 101384 |
| 09:45:00 | 62.90 | 62.30 | -1 | 24267 | -24267 | 77117 |
| 09:50:00 | 62.29 | 61.45 | -1 | 72576 | -72576 | 4541 |
| 09:55:00 | 61.45 | 61.43 | -1 | 28622 | -28622 | -24081 |
| 10:00:00 | 61.48 | 61.98 | 1 | 28092 | 28092 | 4011 |
| 10:05:00 | 62.00 | 62.02 | 1 | 42849 | 42849 | 46860 |
The SQL query below fetches raw trades, calculates OHLCV bars, classifies the bars by price direction (tick
column), and finally sums up signed volumes.
SELECT *, SUM(signed_volume) AS net_volume FROM (
SELECT symbol, datetime, open(), close(),
CASE
WHEN close()>open() THEN 1
WHEN close()<open() THEN -1
ELSE 0
END AS tick,
volume(),
tick*volume() AS signed_volume
FROM atsd_trade WHERE symbol = 'NTAP.US'
AND datetime BETWEEN '2021-02-26' AND '2021-02-27'
GROUP BY exchange, class, symbol, PERIOD(5 minute)
) WITH ROW_NUMBER(symbol ORDER BY datetime) > 0
Net Volume over OHLCV bars is sensitive to the bar period.
You can perhaps stabilize it by using vwap and/or by tightening the tick condition so that it takes a larger price move to cause a change in net volume.
WHEN vwap()>prev_vwap THEN 1
WHEN vwap()<prev_vwap THEN -1
ELSE 0
WHEN close()>open()*1.001 THEN 1
WHEN close()<open()*0.999 THEN -1
ELSE 0
Trade direction (buy or sell) is not part of Net Volume calculation. Also, it's not the same as the order direction. In limit order books, a transaction is classified as BUY when an incoming buy order is instantly matched with a standing sell order. If the incoming buy order is not matched, it is placed in the order book, and the trade will be classified as SELL when a later sell order takes it. In other words, as @user47399 mentioned, the trade direction == liquidity taker's order direction. During auctions, when the limit order book is not used, trade direction is based on order numbers as assigned by the exchange. Of the two participating auction orders, the trade direction is inferred from the later order. To correctly determine trade direction one needs access to full order log or to a trade feed where records are pre-classified by the exchange.
Buy trades can be downtick trades and sell trades can be uptick trades. In the example below BUY trade 3657601171 is a downtick, whereas SELL trade 3657602494 caused an uptick.
| datetime | price | quantity | side | prev_price | tick | trade_num |
|----------------------------|-------:|---------:|------|-----------:|-----:|-----------:|
| 2021-02-26 11:01:43.482121 | 35.251 | 1 | B | | 0 | 3657600807 |
| 2021-02-26 11:01:47.289811 | 35.243 | 90 | B | 35.251 | -1 | 3657601171 |
| 2021-02-26 11:01:47.289811 | 35.243 | 10 | B | 35.243 | 0 | 3657601172 |
| 2021-02-26 11:01:51.535774 | 35.242 | 11 | B | 35.243 | -1 | 3657601628 |
| 2021-02-26 11:01:59.153104 | 35.270 | 1 | S | 35.242 | 1 | 3657602494 |
| 2021-02-26 11:01:59.153104 | 35.260 | 1 | S | 35.270 | -1 | 3657602495 |
| 2021-02-26 11:01:59.153104 | 35.252 | 11 | S | 35.260 | -1 | 3657602496 |
| 2021-02-26 11:01:59.682744 | 35.252 | 79 | S | 35.252 | 0 | 3657602528 |
| 2021-02-26 11:01:59.682744 | 35.252 | 21 | S | 35.252 | 0 | 3657602529 |
SELECT * FROM (
SELECT datetime, price, quantity, side, LAG(price) AS prev_price,
CASE
WHEN price>prev_price THEN 1
WHEN price<prev_price THEN -1
ELSE 0
END AS tick, trade_num
FROM atsd_trade WHERE symbol = '-- redacted
AND datetime BETWEEN '2021-02-26 11:01:40' AND '2021-02-26 11:02:00'
) --WHERE (side = 'B' AND tick = -1 OR side = 'S' AND tick = 1)
For meaningful results, Net Volume calculated over raw trades would need to group quantities by the exact trade time (and order number) so that simultaneous trades triggered by the same order are treated as one transaction.
For instance, how would you classify SELL trades 3657602494, 3657602495, 3657602496, all originated by the same SELL order and registered at the same time? I think it should be treated as an uptick of 13 lots.