VWAP weights prices by traded quantity. TWAP weights prices by time. They can describe the same window and still give different answers, because a busy minute counts more toward VWAP than a quiet one.
In Python, the arithmetic takes only a few lines. The more useful question is what your input data lets you calculate. A file of one-minute OHLCV bars supports a volume-weighted estimate and an equally spaced price-sampling estimate. It does not automatically reproduce a benchmark calculated from every eligible trade or a continuous price path.
This guide works through both estimates, shows why they differ, and includes a runnable example using only Python's standard library. The numerical examples are invented to make the calculations easy to check.
VWAP vs TWAP: the difference is the weighting#
Volume-weighted average price, or VWAP, gives each trade a weight equal to its quantity. For a defined set of trades:
trade VWAP = sum(trade price × trade quantity) / sum(trade quantity)
Time-weighted average price, or TWAP, gives equal lengths of time equal weight. One common discrete approximation samples the same kind of price at equally spaced times, then takes their arithmetic mean:
sampled TWAP estimate = sum(equally spaced price samples) / sample count
The CFA Institute's trade strategy and execution reading discusses VWAP and TWAP as intraday execution benchmarks. A benchmark is a comparison measure; calculating one does not place an order or establish the price at which an order could have filled.
| Calculation | What gets more weight? | Input needed | What the result describes |
|---|---|---|---|
| Trade-level VWAP | Trades with larger quantities | Eligible trade prices and quantities | The quantity-weighted price of that trade set |
| Close-based VWAP estimate | Bars with larger reported volumes | One close and a compatible volume per bar | A bar approximation using each close as the bar's representative price |
| Equally sampled TWAP estimate | Each equal-length sampling interval has the same weight | Consistently sampled prices | An approximation to the time average over the chosen window |
The comparison only makes sense when the instrument, price basis and measurement window match. A full-session VWAP and a last-hour TWAP answer different questions.
A three-minute example#
Suppose a fictional instrument has these three completed one-minute bars. Volume is measured in the same traded units in every row.
| Minute | Closing price | Volume | Close × volume |
|---|---|---|---|
| 1 | 100 | 100 | 10,000 |
| 2 | 102 | 100 | 10,200 |
| 3 | 104 | 800 | 83,200 |
| Total | 1,000 | 103,400 |
Using each bar's close as its representative price:
close-based VWAP estimate = 103,400 / 1,000 = 103.4
sampled TWAP estimate = (100 + 102 + 104) / 3 = 102
The final minute contributes 80% of the volume but one-third of the time samples. That is why the volume-weighted estimate sits closer to 104.
Neither result is inherently the better price. One summarizes where reported volume was concentrated under the close-price approximation. The other summarizes the prices observed at regular intervals.
Why OHLCV cannot recover exact trade VWAP#
OHLCV stores the open, high, low, close and total volume of a bar. It usually does not preserve how much quantity traded at each price inside that bar.
Consider two possible trade sequences:
| Trade sequence inside one bar | OHLCV summary | Exact trade VWAP |
|---|---|---|
| 9 units at 100, then 1 unit at 110 | O 100, H 110, L 100, C 110, V 10 | 101 |
| 1 unit at 100, then 9 units at 110 | O 100, H 110, L 100, C 110, V 10 | 109 |
The bars are identical. The trade VWAPs are not.
Replacing the close with the typical price, (high + low + close) / 3, does not recover the lost information. Both bars have the same typical price of approximately 106.67. It is another representative-price choice, rather than proof of the actual quantity-weighted price.
For a chart or a research feature, a clearly labelled bar estimate can be useful. For a trade-exact execution benchmark, establish the required trade coverage, eligibility rules and calculation window first. Shorter bars can improve the approximation in some conditions, but they do not guarantee exact reconstruction.
Prepare one consistent window of market data#
SiftingIO provides real-time and historical data for US stocks and ETFs, forex, commodities and crypto. For this calculation, choose a dataset whose volume field has a documented meaning appropriate to the instrument.
Open Market Data Export and choose an instrument such as AAPL. The account-free preview covers the most recent 24 hours at intervals down to five minutes; signing in and connecting an API key unlocks longer periods and finer intervals. For the one-minute bars this example uses, confirm that interval is available to your account, or pull them from the API as described below. Download CSV, then keep only the completed bars in the uninterrupted window you want to measure.
The export includes Timestamp (ms), Close and Volume. The timestamp identifies the bar's opening instant in Unix milliseconds. The close is observed at the end of that bar, so a cumulative result containing it is available only after that bar has completed.
For a daily session benchmark, select the intended session first and run the calculation separately for each session. For an anchored calculation, select the explicit anchor and end time instead. Do not group every market by UTC calendar date and assume that creates the right trading session. See the daily bar boundaries guide when choosing the window.
Keep the Excel download alongside the CSV when you need its Metadata sheet. Check the volume basis there and in the dataset documentation. If the unit is marked unknown, do not assume the number means shares or base-asset quantity.
Conventional trade VWAP weights prices by traded quantity. Quote-currency turnover, quote-update counts and tick counts are different weights and must not be silently substituted. An unavailable volume field is not zero trading activity. Any volume-weighted result also remains specific to the source coverage represented by its inputs.
Calculate both estimates in Python#
The function below expects CSV-style rows for one instrument and one already selected window. It uses the close for both estimates so the difference comes from weighting, rather than from choosing different price columns.
Save it as benchmarks.py. No additional packages are required.
from decimal import Decimal
def bar_estimates(rows, interval_ms=60_000):
if type(interval_ms) is not int or interval_ms <= 0:
raise ValueError("interval_ms must be a positive integer")
points = sorted(
(int(row["Timestamp (ms)"]),
Decimal(row["Close"]), Decimal(row["Volume"]))
for row in rows
)
if not points:
raise ValueError("Select a non-empty measurement window")
for i, (timestamp, price, volume) in enumerate(points):
if not price.is_finite() or not volume.is_finite() or volume < 0:
raise ValueError("Prices must be finite; volumes finite and non-negative")
if i and timestamp - points[i - 1][0] != interval_ms:
raise ValueError("Duplicate, missing or irregular bars in this window")
price_volume = volume_sum = price_sum = Decimal("0")
result = []
for count, (timestamp, price, volume) in enumerate(points, start=1):
price_volume += price * volume
volume_sum += volume
price_sum += price
result.append({
"bar_open_ms": timestamp,
"vwap_estimate": price_volume / volume_sum if volume_sum else None,
"twap_sample_estimate": price_sum / count,
})
return result
The decimal inputs are read from strings, rather than first converted to binary floating-point values. Python's Decimal documentation explains that distinction. This keeps the worked decimal arithmetic easy to audit; it does not make the underlying market data more precise.
Check the three-minute example without a market-data file or API key. These placeholder timestamps are one minute apart and do not represent a historical market observation:
from benchmarks import bar_estimates
demo = [
{"Timestamp (ms)": str(i * 60_000), "Close": str(price), "Volume": str(volume)}
for i, (price, volume) in enumerate([(100, 100), (102, 100), (104, 800)])
]
last = bar_estimates(demo)[-1]
print(last["vwap_estimate"]) # 103.4
print(last["twap_sample_estimate"]) # 102
To use your selected CSV window, save it as bars.csv beside benchmarks.py and run:
import csv
from benchmarks import bar_estimates
with open("bars.csv", newline="", encoding="utf-8-sig") as source:
result = bar_estimates(csv.DictReader(source), interval_ms=60_000)
last = result[-1]
print("Final included bar opens at:", last["bar_open_ms"], "Unix ms")
print("Close-based VWAP estimate:", last["vwap_estimate"])
print("Equally sampled TWAP estimate:", last["twap_sample_estimate"])
Each output row uses all included bars from the beginning of the selected window through that row. It is cumulative, not a rolling last-N-bar calculation. Start a new call when the measurement window resets.
For an API workflow, the US stock historical bars reference documents the response fields. Map the documented timestamp, close and volume values into the three input columns above, preserving their units and the same measurement window.
Handle gaps and zero volume before interpreting the line#
The function rejects duplicate timestamps and irregular spacing. A simple average of irregularly spaced observations weights the observations equally, not elapsed time. It also rejects non-finite values and negative volume. Blank or missing required fields fail parsing instead of silently becoming zero.
A known zero-volume bar contributes zero weight to the VWAP estimate. If cumulative volume is still zero, the function returns None, not a price of zero. The sampled TWAP estimate can still be calculated when that bar has a valid observed or explicitly defined carried price; whether such a price belongs in your sampling policy is a separate decision.
Do not insert invented bars to satisfy the spacing check. A closure, a period without eligible trades and a missing source row are different cases. Select an appropriate uninterrupted window or investigate the gap using the missing-candles guide.
The code checks spacing between the rows supplied. It cannot detect a missing first or final row, infer the intended session, or verify that the latest bar has closed. Check the selected boundaries and expected row count against your source before using the result.
Keep the benchmark definition with the result#
A useful saved result includes the symbol, quote currency, data source, interval, start and end times, representative-price rule and volume units. Label the lines close-based VWAP estimate and equally sampled TWAP estimate, rather than implying a trade-exact benchmark.
That small amount of context makes comparisons reproducible. If two charts disagree, you can check whether they measured the same window, used the same price samples and assigned the same weights before treating the difference as a data error.
Start with a short, inspectable window in Market Data Export. Once the calculation matches the definition you need, use the SiftingIO API documentation to apply the same method to your data workflow.



