diff options
Diffstat (limited to 'services/strategy-engine/tests')
| -rw-r--r-- | services/strategy-engine/tests/test_volume_profile_strategy.py | 107 |
1 files changed, 107 insertions, 0 deletions
diff --git a/services/strategy-engine/tests/test_volume_profile_strategy.py b/services/strategy-engine/tests/test_volume_profile_strategy.py new file mode 100644 index 0000000..be123b0 --- /dev/null +++ b/services/strategy-engine/tests/test_volume_profile_strategy.py @@ -0,0 +1,107 @@ +"""Tests for the Volume Profile strategy.""" +from datetime import datetime, timezone +from decimal import Decimal + +import pytest + +from shared.models import Candle, OrderSide +from strategies.volume_profile_strategy import VolumeProfileStrategy + + +def make_candle(close: float, volume: float = 1.0) -> Candle: + return Candle( + symbol="BTC/USDT", + timeframe="1m", + open_time=datetime(2024, 1, 1, tzinfo=timezone.utc), + open=Decimal(str(close)), + high=Decimal(str(close)), + low=Decimal(str(close)), + close=Decimal(str(close)), + volume=Decimal(str(volume)), + ) + + +def test_volume_profile_warmup_period(): + strategy = VolumeProfileStrategy() + strategy.configure({"lookback_period": 10, "num_bins": 5}) + assert strategy.warmup_period == 10 + + +def test_volume_profile_no_signal_insufficient_data(): + strategy = VolumeProfileStrategy() + strategy.configure({"lookback_period": 10, "num_bins": 5}) + # Feed fewer candles than lookback_period + for _ in range(5): + result = strategy.on_candle(make_candle(100.0, 10.0)) + assert result is None + + +def test_volume_profile_buy_at_value_area_low(): + """Concentrate volume around 95-105, price drops to 88, bounces back to 99.""" + strategy = VolumeProfileStrategy() + strategy.configure({ + "lookback_period": 10, + "num_bins": 5, + "value_area_pct": 0.7, + "quantity": "0.01", + }) + + # Build profile: 10 candles with volume concentrated around 95-105 + profile_data = [ + (95, 50), (97, 50), (99, 100), (100, 100), (101, 100), + (103, 50), (105, 50), (100, 100), (99, 100), (101, 50), + ] + for price, vol in profile_data: + strategy.on_candle(make_candle(price, vol)) + + # Price drops below value area low + strategy.on_candle(make_candle(88.0, 1.0)) + + # Price bounces back into value area (between va_low and poc) + signal = strategy.on_candle(make_candle(99.0, 1.0)) + + assert signal is not None + assert signal.side == OrderSide.BUY + + +def test_volume_profile_sell_at_value_area_high(): + """Concentrate volume around 95-105, price rises to 112, pulls back to 101.""" + strategy = VolumeProfileStrategy() + strategy.configure({ + "lookback_period": 10, + "num_bins": 5, + "value_area_pct": 0.7, + "quantity": "0.01", + }) + + # Build profile: 10 candles with volume concentrated around 95-105 + profile_data = [ + (95, 50), (97, 50), (99, 100), (100, 100), (101, 100), + (103, 50), (105, 50), (100, 100), (99, 100), (101, 50), + ] + for price, vol in profile_data: + strategy.on_candle(make_candle(price, vol)) + + # Price rises above value area high + strategy.on_candle(make_candle(112.0, 1.0)) + + # Price pulls back into value area (between poc and va_high) + signal = strategy.on_candle(make_candle(101.0, 1.0)) + + assert signal is not None + assert signal.side == OrderSide.SELL + + +def test_volume_profile_reset_clears_state(): + strategy = VolumeProfileStrategy() + strategy.configure({"lookback_period": 10, "num_bins": 5}) + + # Feed enough candles to establish profile + for _ in range(10): + strategy.on_candle(make_candle(100.0, 10.0)) + + strategy.reset() + + # After reset, should not have enough data + result = strategy.on_candle(make_candle(100.0, 10.0)) + assert result is None |
