from datetime import datetime from zoneinfo import ZoneInfo from app.analysis.vwap import build_vwap_level from app.bars.models import Bar, Timeframe EASTERN = ZoneInfo("America/New_York") def at(year: int, month: int, day: int, hour: int, minute: int = 0) -> int: return int(datetime(year, month, day, hour, minute, tzinfo=EASTERN).timestamp()) def minute(t: int, price: float, volume: int) -> Bar: return Bar(Timeframe.M1, t, price, price, price, price, volume, True, "ES=F", "test") def test_vwap_is_volume_weighted_not_a_simple_mean(): bars = [minute(at(2026, 8, 10, 19), 100, 1), minute(at(2026, 8, 10, 20), 200, 3)] level = build_vwap_level(bars)[0] assert level.anchor_p == (100 * 1 + 200 * 3) / 4 # 175, not 150 def test_prior_session_bars_are_excluded(): bars = [ # Before Monday's 18:00 open, so part of the previous session. minute(at(2026, 8, 10, 17), 500, 10), minute(at(2026, 8, 10, 19), 100, 1), minute(at(2026, 8, 10, 20), 200, 1), ] level = build_vwap_level(bars)[0] assert level.anchor_p == 150 assert level.first_t == at(2026, 8, 10, 19) def test_zero_volume_minutes_do_not_divide_by_zero(): bars = [minute(at(2026, 8, 10, 19), 100, 0), minute(at(2026, 8, 10, 20), 200, 2)] level = build_vwap_level(bars)[0] assert level.anchor_p == 200 # The zero-volume minute contributes no point rather than a NaN. assert len(level.points) == 1 def test_no_volume_at_all_yields_no_level(): assert build_vwap_level([minute(at(2026, 8, 10, 19), 100, 0)]) == [] def test_side_tracks_price_relative_to_vwap(): bars = [minute(at(2026, 8, 10, 19), 100, 1), minute(at(2026, 8, 10, 20), 200, 1)] # Last close 200 sits above VWAP 150, so VWAP is support beneath price. assert build_vwap_level(bars)[0].side.value == "support"