summaryrefslogtreecommitdiff
path: root/services/strategy-engine/tests
diff options
context:
space:
mode:
authorTheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com>2026-04-01 16:18:18 +0900
committerTheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com>2026-04-01 16:18:18 +0900
commite0320a4d4b7d22d7d663ef474c7d5e081f4e83a1 (patch)
tree311d2a87b289292004e2e53006822357206c1a93 /services/strategy-engine/tests
parentbdffabc630c0cc296fc164d5fa2ca8569626fd7e (diff)
feat(strategy): add Volume Profile strategy
Diffstat (limited to 'services/strategy-engine/tests')
-rw-r--r--services/strategy-engine/tests/test_volume_profile_strategy.py107
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