diff options
| author | TheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com> | 2026-04-02 10:26:52 +0900 |
|---|---|---|
| committer | TheSiahxyz <164138827+TheSiahxyz@users.noreply.github.com> | 2026-04-02 10:26:52 +0900 |
| commit | 53cadcf7e34f05f77082e84f0696b56bcbcbae36 (patch) | |
| tree | e02650e10c4d5727bc1e32e27788c17327fa46f7 /services/strategy-engine | |
| parent | f5521da2876a2c19afc24f370b3258f2be95f81a (diff) | |
refactor: remove all crypto/Binance code, update to US stock symbols
Diffstat (limited to 'services/strategy-engine')
17 files changed, 29 insertions, 561 deletions
diff --git a/services/strategy-engine/src/strategy_engine/main.py b/services/strategy-engine/src/strategy_engine/main.py index d62f886..30de528 100644 --- a/services/strategy-engine/src/strategy_engine/main.py +++ b/services/strategy-engine/src/strategy_engine/main.py @@ -8,7 +8,6 @@ from shared.healthcheck import HealthCheckServer from shared.logging import setup_logging from shared.metrics import ServiceMetrics from shared.notifier import TelegramNotifier -from shared.sentiment import SentimentProvider from strategy_engine.config import StrategyConfig from strategy_engine.engine import StrategyEngine @@ -22,28 +21,6 @@ STRATEGIES_DIR = Path(__file__).parent.parent.parent.parent / "strategies" # order-executor: +2 (8082), portfolio-manager: +3 (8083) HEALTH_PORT_OFFSET = 1 -SENTIMENT_REFRESH_INTERVAL = 300 # 5 minutes - - -async def sentiment_loop(provider: SentimentProvider, strategies: list, log) -> None: - """Periodically fetch sentiment and update strategies that support it.""" - while True: - try: - sentiment = await provider.get_sentiment("SOL") - log.info( - "sentiment_updated", - fear_greed=sentiment.fear_greed_value, - news=sentiment.news_sentiment, - netflow=sentiment.exchange_netflow, - should_block=sentiment.should_block, - ) - for strategy in strategies: - if hasattr(strategy, "update_sentiment"): - strategy.update_sentiment(sentiment) - except Exception as exc: - log.warning("sentiment_fetch_failed", error=str(exc)) - await asyncio.sleep(SENTIMENT_REFRESH_INTERVAL) - async def process_symbol(engine: StrategyEngine, stream: str, log) -> None: """Process candles for a single symbol stream.""" @@ -74,8 +51,6 @@ async def run() -> None: engine = StrategyEngine(broker=broker, strategies=strategies) - provider = SentimentProvider() - health = HealthCheckServer( "strategy-engine", port=config.health_port + HEALTH_PORT_OFFSET, @@ -87,15 +62,11 @@ async def run() -> None: tasks = [] try: - # Sentiment updater - tasks.append(asyncio.create_task(sentiment_loop(provider, strategies, log))) - # Symbol processors for symbol in config.symbols: stream = f"candles.{symbol.replace('/', '_')}" task = asyncio.create_task(process_symbol(engine, stream, log)) tasks.append(task) - # Wait for all tasks (they run forever until cancelled) await asyncio.gather(*tasks) except Exception as exc: log.error("fatal_error", error=str(exc)) @@ -106,7 +77,6 @@ async def run() -> None: task.cancel() metrics.service_up.labels(service="strategy-engine").set(0) await notifier.close() - await provider.close() await broker.close() diff --git a/services/strategy-engine/strategies/asian_session_rsi.py b/services/strategy-engine/strategies/asian_session_rsi.py deleted file mode 100644 index 1874591..0000000 --- a/services/strategy-engine/strategies/asian_session_rsi.py +++ /dev/null @@ -1,266 +0,0 @@ -"""Asian Session RSI Strategy — 한국시간 9:00~11:00 단타. - -규칙: -- SOL/USDT 5분봉 -- 매수: RSI(14) < 25 + 볼륨 > 평균 + 센티먼트 OK -- 익절: +1.5%, 손절: -0.7%, 시간청산: 11:00 KST (02:00 UTC) -- 하루 최대 3회, 2연패 시 중단 -- 센티먼트 필터: Fear & Greed > 80이면 매수 차단, 뉴스 극도 부정이면 차단 -""" - -from collections import deque -from decimal import Decimal -from datetime import datetime - -import pandas as pd - -from shared.models import Candle, Signal, OrderSide -from shared.sentiment import SentimentData -from strategies.base import BaseStrategy - - -class AsianSessionRsiStrategy(BaseStrategy): - name: str = "asian_session_rsi" - - def __init__(self) -> None: - super().__init__() - self._rsi_period: int = 14 - self._rsi_oversold: float = 25.0 - self._rsi_overbought: float = 75.0 - self._quantity: Decimal = Decimal("0.1") - self._take_profit_pct: float = 1.5 - self._stop_loss_pct: float = 0.7 - # Session: 00:00~02:00 UTC = 09:00~11:00 KST - self._session_start_utc: int = 0 - self._session_end_utc: int = 2 - self._max_trades_per_day: int = 3 - self._max_consecutive_losses: int = 2 - self._use_sentiment: bool = True - self._ema_period: int = 20 - self._require_bullish_candle: bool = True - self._prev_candle_bullish: bool = False - # Sentiment (updated externally before each session) - self._sentiment: SentimentData | None = None - # State - self._closes: deque[float] = deque(maxlen=200) - self._volumes: deque[float] = deque(maxlen=50) - self._today: str | None = None - self._trades_today: int = 0 - self._consecutive_losses: int = 0 - self._in_position: bool = False - self._entry_price: float = 0.0 - - @property - def warmup_period(self) -> int: - return self._rsi_period + 1 - - def configure(self, params: dict) -> None: - self._rsi_period = int(params.get("rsi_period", 14)) - self._rsi_oversold = float(params.get("rsi_oversold", 25.0)) - self._rsi_overbought = float(params.get("rsi_overbought", 75.0)) - self._quantity = Decimal(str(params.get("quantity", "0.1"))) - self._take_profit_pct = float(params.get("take_profit_pct", 1.5)) - self._stop_loss_pct = float(params.get("stop_loss_pct", 0.7)) - self._session_start_utc = int(params.get("session_start_utc", 0)) - self._session_end_utc = int(params.get("session_end_utc", 2)) - self._max_trades_per_day = int(params.get("max_trades_per_day", 3)) - self._max_consecutive_losses = int(params.get("max_consecutive_losses", 2)) - self._use_sentiment = bool(params.get("use_sentiment", True)) - self._ema_period = int(params.get("ema_period", 20)) - self._require_bullish_candle = bool(params.get("require_bullish_candle", True)) - - if self._quantity <= 0: - raise ValueError(f"Quantity must be positive, got {self._quantity}") - if self._stop_loss_pct <= 0: - raise ValueError(f"Stop loss must be positive, got {self._stop_loss_pct}") - if self._take_profit_pct <= 0: - raise ValueError(f"Take profit must be positive, got {self._take_profit_pct}") - - self._init_filters( - require_trend=False, - adx_threshold=25.0, - min_volume_ratio=0.5, - atr_stop_multiplier=1.5, - atr_tp_multiplier=2.0, - ) - - def reset(self) -> None: - super().reset() - self._closes.clear() - self._volumes.clear() - self._today = None - self._trades_today = 0 - self._consecutive_losses = 0 - self._in_position = False - self._entry_price = 0.0 - self._sentiment = None - self._prev_candle_bullish = False - - def update_sentiment(self, sentiment: SentimentData) -> None: - """Update sentiment data. Call before each trading session.""" - self._sentiment = sentiment - - def _check_sentiment(self) -> bool: - """Check if sentiment allows buying. Returns True if OK.""" - if not self._use_sentiment or self._sentiment is None: - return True # No sentiment data, allow by default - return not self._sentiment.should_block - - def _is_session_active(self, dt: datetime) -> bool: - """Check if current time is within trading session.""" - hour = dt.hour - if self._session_start_utc <= self._session_end_utc: - return self._session_start_utc <= hour < self._session_end_utc - # Wrap around midnight - return hour >= self._session_start_utc or hour < self._session_end_utc - - def _compute_rsi(self) -> float | None: - if len(self._closes) < self._rsi_period + 1: - return None - series = pd.Series(list(self._closes)) - delta = series.diff() - gain = delta.clip(lower=0) - loss = -delta.clip(upper=0) - avg_gain = gain.ewm(com=self._rsi_period - 1, min_periods=self._rsi_period).mean() - avg_loss = loss.ewm(com=self._rsi_period - 1, min_periods=self._rsi_period).mean() - rs = avg_gain / avg_loss.replace(0, float("nan")) - rsi = 100 - (100 / (1 + rs)) - val = rsi.iloc[-1] - if pd.isna(val): - return None - return float(val) - - def _volume_above_average(self) -> bool: - if len(self._volumes) < 20: - return True # Not enough data, allow - avg = sum(self._volumes) / len(self._volumes) - return self._volumes[-1] >= avg - - def _price_above_ema(self) -> bool: - """Check if current price is above short-term EMA.""" - if len(self._closes) < self._ema_period: - return True # Not enough data, allow by default - series = pd.Series(list(self._closes)) - ema_val = series.ewm(span=self._ema_period, adjust=False).mean().iloc[-1] - return self._closes[-1] >= ema_val - - def on_candle(self, candle: Candle) -> Signal | None: - self._update_filter_data(candle) - - close = float(candle.close) - self._closes.append(close) - self._volumes.append(float(candle.volume)) - - # Track candle direction for bullish confirmation - is_bullish = float(candle.close) >= float(candle.open) - - # Daily reset - day = candle.open_time.strftime("%Y-%m-%d") - if self._today != day: - self._today = day - self._trades_today = 0 - # Don't reset consecutive_losses — carries across days - - # Check exit conditions first (if in position) - if self._in_position: - pnl_pct = (close - self._entry_price) / self._entry_price * 100 - - # Take profit - if pnl_pct >= self._take_profit_pct: - self._in_position = False - self._consecutive_losses = 0 - return self._apply_filters( - Signal( - strategy=self.name, - symbol=candle.symbol, - side=OrderSide.SELL, - price=candle.close, - quantity=self._quantity, - conviction=0.9, - reason=f"Take profit {pnl_pct:.2f}% >= {self._take_profit_pct}%", - ) - ) - - # Stop loss - if pnl_pct <= -self._stop_loss_pct: - self._in_position = False - self._consecutive_losses += 1 - return self._apply_filters( - Signal( - strategy=self.name, - symbol=candle.symbol, - side=OrderSide.SELL, - price=candle.close, - quantity=self._quantity, - conviction=1.0, - reason=f"Stop loss {pnl_pct:.2f}% <= -{self._stop_loss_pct}%", - ) - ) - - # Time exit: session ended while in position - if not self._is_session_active(candle.open_time): - self._in_position = False - if pnl_pct < 0: - self._consecutive_losses += 1 - else: - self._consecutive_losses = 0 - return self._apply_filters( - Signal( - strategy=self.name, - symbol=candle.symbol, - side=OrderSide.SELL, - price=candle.close, - quantity=self._quantity, - conviction=0.5, - reason=f"Time exit (session ended), PnL {pnl_pct:.2f}%", - ) - ) - - return None # Still in position, no action - - # Entry conditions - if not self._is_session_active(candle.open_time): - return None # Outside trading hours - - if self._trades_today >= self._max_trades_per_day: - return None # Daily limit reached - - if self._consecutive_losses >= self._max_consecutive_losses: - return None # Consecutive loss limit - - if not self._check_sentiment(): - return None # Sentiment blocked (extreme greed or very negative news) - - rsi = self._compute_rsi() - if rsi is None: - return None - - if rsi < self._rsi_oversold and self._volume_above_average() and self._price_above_ema(): - if self._require_bullish_candle and not is_bullish: - return None # Wait for bullish candle confirmation - self._in_position = True - self._entry_price = close - self._trades_today += 1 - - # Conviction: lower RSI = stronger signal - conv = min((self._rsi_oversold - rsi) / self._rsi_oversold, 1.0) - conv = max(conv, 0.3) - - sl = candle.close * (1 - Decimal(str(self._stop_loss_pct / 100))) - tp = candle.close * (1 + Decimal(str(self._take_profit_pct / 100))) - - return self._apply_filters( - Signal( - strategy=self.name, - symbol=candle.symbol, - side=OrderSide.BUY, - price=candle.close, - quantity=self._quantity, - conviction=conv, - stop_loss=sl, - take_profit=tp, - reason=f"RSI {rsi:.1f} < {self._rsi_oversold} (session active, vol OK)", - ) - ) - - return None diff --git a/services/strategy-engine/strategies/config/asian_session_rsi.yaml b/services/strategy-engine/strategies/config/asian_session_rsi.yaml deleted file mode 100644 index bc7c5c9..0000000 --- a/services/strategy-engine/strategies/config/asian_session_rsi.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Asian Session RSI — SOL/USDT 5분봉 단타 -# 한국시간 9:00~11:00 (UTC 0:00~2:00) -rsi_period: 14 -rsi_oversold: 25 -rsi_overbought: 75 -quantity: "0.5" # SOL 0.5개 (~$75, 100만원의 10%) -take_profit_pct: 1.5 # 익절 1.5% -stop_loss_pct: 0.7 # 손절 0.7% -session_start_utc: 0 # UTC 0시 = KST 9시 -session_end_utc: 2 # UTC 2시 = KST 11시 -max_trades_per_day: 3 # 하루 최대 3회 -max_consecutive_losses: 2 # 2연패 시 중단 -ema_period: 20 -require_bullish_candle: true diff --git a/services/strategy-engine/strategies/config/grid_strategy.yaml b/services/strategy-engine/strategies/config/grid_strategy.yaml index 607f3df..338bb4c 100644 --- a/services/strategy-engine/strategies/config/grid_strategy.yaml +++ b/services/strategy-engine/strategies/config/grid_strategy.yaml @@ -1,4 +1,4 @@ -lower_price: 60000 -upper_price: 70000 +lower_price: 170 +upper_price: 190 grid_count: 5 -quantity: "0.01" +quantity: "1" diff --git a/services/strategy-engine/tests/test_asian_session_rsi.py b/services/strategy-engine/tests/test_asian_session_rsi.py deleted file mode 100644 index db031f0..0000000 --- a/services/strategy-engine/tests/test_asian_session_rsi.py +++ /dev/null @@ -1,190 +0,0 @@ -"""Tests for Asian Session RSI strategy.""" - -import sys -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).resolve().parents[1])) - -from datetime import datetime, timezone -from decimal import Decimal - -from shared.models import Candle, OrderSide -from strategies.asian_session_rsi import AsianSessionRsiStrategy - - -def _candle(price, hour=0, minute=30, volume=100.0, day=1): - return Candle( - symbol="SOLUSDT", - timeframe="5m", - open_time=datetime(2025, 1, day, hour, minute, tzinfo=timezone.utc), - open=Decimal(str(price)), - high=Decimal(str(price + 1)), - low=Decimal(str(price - 1)), - close=Decimal(str(price)), - volume=Decimal(str(volume)), - ) - - -def _make_strategy(**overrides): - s = AsianSessionRsiStrategy() - params = { - "rsi_period": 5, - "rsi_oversold": 30, - "rsi_overbought": 70, - "quantity": "0.5", - "take_profit_pct": 1.5, - "stop_loss_pct": 0.7, - "session_start_utc": 0, - "session_end_utc": 2, - "max_trades_per_day": 3, - "max_consecutive_losses": 2, - } - params.update(overrides) - s.configure(params) - return s - - -def test_no_signal_outside_session(): - s = _make_strategy() - # Hour 5 UTC = outside session (0-2 UTC) - for i in range(10): - sig = s.on_candle(_candle(100 - i * 3, hour=5)) - assert sig is None - - -def test_buy_signal_during_session_on_oversold(): - s = AsianSessionRsiStrategy() - s._rsi_period = 5 - s._rsi_oversold = 30 - s._quantity = Decimal("0.5") - s._take_profit_pct = 1.5 - s._stop_loss_pct = 0.7 - s._session_start_utc = 0 - s._session_end_utc = 2 - s._max_trades_per_day = 3 - s._max_consecutive_losses = 10 # High limit so test isn't blocked - - # Feed declining prices — collect all signals - signals = [] - for i in range(10): - sig = s.on_candle(_candle(100 - i * 3, hour=0, minute=i * 5)) - if sig is not None: - signals.append(sig) - - # Should have generated at least one BUY signal - buy_signals = [s for s in signals if s.side == OrderSide.BUY] - assert len(buy_signals) > 0 - assert buy_signals[0].strategy == "asian_session_rsi" - - -def test_take_profit_exit(): - s = _make_strategy(rsi_period=5, rsi_oversold=40) - # Force entry - for i in range(8): - s.on_candle(_candle(100 - i * 2, hour=0, minute=i * 5)) - - # Should be in position now — push price up for TP - sig = s.on_candle(_candle(100, hour=0, minute=50)) # entry ~around 84-86 - if s._in_position: - tp_price = s._entry_price * (1 + s._take_profit_pct / 100) - sig = s.on_candle(_candle(tp_price + 1, hour=1, minute=0)) - if sig is not None: - assert sig.side == OrderSide.SELL - assert "Take profit" in sig.reason - - -def test_stop_loss_exit(): - s = _make_strategy(rsi_period=5, rsi_oversold=40) - for i in range(8): - s.on_candle(_candle(100 - i * 2, hour=0, minute=i * 5)) - - if s._in_position: - sl_price = s._entry_price * (1 - s._stop_loss_pct / 100) - sig = s.on_candle(_candle(sl_price - 1, hour=1, minute=0)) - if sig is not None: - assert sig.side == OrderSide.SELL - assert "Stop loss" in sig.reason - - -def test_time_exit_when_session_ends(): - s = _make_strategy(rsi_period=5, rsi_oversold=40) - for i in range(8): - s.on_candle(_candle(100 - i * 2, hour=0, minute=i * 5)) - - if s._in_position: - # Session ends at hour 2 - sig = s.on_candle(_candle(s._entry_price, hour=3, minute=0)) - if sig is not None: - assert sig.side == OrderSide.SELL - assert "Time exit" in sig.reason - - -def test_max_trades_per_day(): - s = _make_strategy(rsi_period=3, rsi_oversold=40, max_trades_per_day=1) - # Force one trade - for i in range(6): - s.on_candle(_candle(100 - i * 5, hour=0, minute=i * 5)) - # Exit - if s._in_position: - s.on_candle(_candle(200, hour=0, minute=35)) # TP exit - # Try to enter again — should be blocked - for i in range(6): - s.on_candle(_candle(100 - i * 5, hour=1, minute=i * 5)) - # After 1 trade, no more allowed - assert not s._in_position or s._trades_today >= 1 - - -def test_consecutive_losses_stop(): - s = _make_strategy(rsi_period=3, rsi_oversold=40, max_consecutive_losses=2) - # Simulate 2 losses - s._consecutive_losses = 2 - # Even with valid conditions, should not enter - for i in range(6): - sig = s.on_candle(_candle(100 - i * 5, hour=0, minute=i * 5)) - assert sig is None - - -def test_reset_clears_all(): - s = _make_strategy() - s.on_candle(_candle(100, hour=0)) - s._in_position = True - s._trades_today = 2 - s._consecutive_losses = 1 - s.reset() - assert not s._in_position - assert s._trades_today == 0 - assert len(s._closes) == 0 - - -def test_warmup_period(): - s = _make_strategy(rsi_period=14) - assert s.warmup_period == 15 - - -def test_ema_filter_blocks_below_ema(): - """Entry blocked when price is below EMA.""" - s = AsianSessionRsiStrategy() - s._rsi_period = 5 - s._rsi_oversold = 40 - s._quantity = Decimal("0.5") - s._take_profit_pct = 1.5 - s._stop_loss_pct = 0.7 - s._session_start_utc = 0 - s._session_end_utc = 2 - s._max_trades_per_day = 3 - s._max_consecutive_losses = 10 - s._ema_period = 5 - s._require_bullish_candle = False # Test EMA only - - # Feed rising prices to set EMA high, then sharp drop - for i in range(10): - s.on_candle(_candle(200 + i * 5, hour=0, minute=i * 5)) - # Now feed low price -- below EMA, RSI should be low - signals = [] - for i in range(5): - sig = s.on_candle(_candle(100 - i * 5, hour=0, minute=(15 + i) * 5 % 60)) - if sig is not None: - signals.append(sig) - # Should have no BUY signals because price is way below EMA - buy_sigs = [s for s in signals if s.side == OrderSide.BUY] - assert len(buy_sigs) == 0 diff --git a/services/strategy-engine/tests/test_base_filters.py b/services/strategy-engine/tests/test_base_filters.py index 3e55973..ae9ca05 100644 --- a/services/strategy-engine/tests/test_base_filters.py +++ b/services/strategy-engine/tests/test_base_filters.py @@ -43,7 +43,7 @@ def _candle(price=100.0, volume=10.0, high=None, low=None): h = high if high is not None else price + 5 lo = low if low is not None else price - 5 return Candle( - symbol="BTCUSDT", + symbol="AAPL", timeframe="1h", open_time=datetime(2025, 1, 1, tzinfo=timezone.utc), open=Decimal(str(price)), diff --git a/services/strategy-engine/tests/test_bollinger_strategy.py b/services/strategy-engine/tests/test_bollinger_strategy.py index 7761f2d..8261377 100644 --- a/services/strategy-engine/tests/test_bollinger_strategy.py +++ b/services/strategy-engine/tests/test_bollinger_strategy.py @@ -10,7 +10,7 @@ from strategies.bollinger_strategy import BollingerStrategy def make_candle(close: float) -> Candle: return Candle( - symbol="BTC/USDT", + symbol="AAPL", timeframe="1m", open_time=datetime(2024, 1, 1, tzinfo=timezone.utc), open=Decimal(str(close)), diff --git a/services/strategy-engine/tests/test_combined_strategy.py b/services/strategy-engine/tests/test_combined_strategy.py index 20a572e..8a4dc74 100644 --- a/services/strategy-engine/tests/test_combined_strategy.py +++ b/services/strategy-engine/tests/test_combined_strategy.py @@ -72,7 +72,7 @@ class NeutralStrategy(BaseStrategy): def _candle(price=100.0): return Candle( - symbol="BTCUSDT", + symbol="AAPL", timeframe="1m", open_time=datetime(2025, 1, 1, tzinfo=timezone.utc), open=Decimal(str(price)), diff --git a/services/strategy-engine/tests/test_ema_crossover_strategy.py b/services/strategy-engine/tests/test_ema_crossover_strategy.py index 67a20bf..7028eb0 100644 --- a/services/strategy-engine/tests/test_ema_crossover_strategy.py +++ b/services/strategy-engine/tests/test_ema_crossover_strategy.py @@ -10,7 +10,7 @@ from strategies.ema_crossover_strategy import EmaCrossoverStrategy def make_candle(close: float) -> Candle: return Candle( - symbol="BTC/USDT", + symbol="AAPL", timeframe="1m", open_time=datetime(2024, 1, 1, tzinfo=timezone.utc), open=Decimal(str(close)), diff --git a/services/strategy-engine/tests/test_engine.py b/services/strategy-engine/tests/test_engine.py index ac9a596..2623027 100644 --- a/services/strategy-engine/tests/test_engine.py +++ b/services/strategy-engine/tests/test_engine.py @@ -13,7 +13,7 @@ from strategy_engine.engine import StrategyEngine def make_candle_event() -> dict: candle = Candle( - symbol="BTC/USDT", + symbol="AAPL", timeframe="1m", open_time=datetime(2024, 1, 1, tzinfo=timezone.utc), open=Decimal("50000"), @@ -28,7 +28,7 @@ def make_candle_event() -> dict: def make_signal() -> Signal: return Signal( strategy="test", - symbol="BTC/USDT", + symbol="AAPL", side=OrderSide.BUY, price=Decimal("50050"), quantity=Decimal("0.01"), @@ -46,12 +46,12 @@ async def test_engine_dispatches_candle_to_strategies(): strategy.on_candle = MagicMock(return_value=None) engine = StrategyEngine(broker=broker, strategies=[strategy]) - await engine.process_once("candles.BTC_USDT", "0") + await engine.process_once("candles.AAPL", "0") strategy.on_candle.assert_called_once() candle_arg = strategy.on_candle.call_args[0][0] assert isinstance(candle_arg, Candle) - assert candle_arg.symbol == "BTC/USDT" + assert candle_arg.symbol == "AAPL" @pytest.mark.asyncio @@ -64,7 +64,7 @@ async def test_engine_publishes_signal_when_strategy_returns_one(): strategy.on_candle = MagicMock(return_value=make_signal()) engine = StrategyEngine(broker=broker, strategies=[strategy]) - await engine.process_once("candles.BTC_USDT", "0") + await engine.process_once("candles.AAPL", "0") broker.publish.assert_called_once() call_args = broker.publish.call_args diff --git a/services/strategy-engine/tests/test_grid_strategy.py b/services/strategy-engine/tests/test_grid_strategy.py index 9823f98..878b900 100644 --- a/services/strategy-engine/tests/test_grid_strategy.py +++ b/services/strategy-engine/tests/test_grid_strategy.py @@ -10,7 +10,7 @@ from strategies.grid_strategy import GridStrategy def make_candle(close: float) -> Candle: return Candle( - symbol="BTC/USDT", + symbol="AAPL", timeframe="1m", open_time=datetime(2024, 1, 1, tzinfo=timezone.utc), open=Decimal(str(close)), diff --git a/services/strategy-engine/tests/test_macd_strategy.py b/services/strategy-engine/tests/test_macd_strategy.py index 17dd2cf..556fd4c 100644 --- a/services/strategy-engine/tests/test_macd_strategy.py +++ b/services/strategy-engine/tests/test_macd_strategy.py @@ -10,7 +10,7 @@ from strategies.macd_strategy import MacdStrategy def _candle(price: float) -> Candle: return Candle( - symbol="BTC/USDT", + symbol="AAPL", timeframe="1m", open_time=datetime(2024, 1, 1, tzinfo=timezone.utc), open=Decimal(str(price)), diff --git a/services/strategy-engine/tests/test_multi_symbol.py b/services/strategy-engine/tests/test_multi_symbol.py index cb8088c..671a9d3 100644 --- a/services/strategy-engine/tests/test_multi_symbol.py +++ b/services/strategy-engine/tests/test_multi_symbol.py @@ -22,7 +22,7 @@ async def test_engine_processes_multiple_streams(): broker = AsyncMock() candle_btc = Candle( - symbol="BTCUSDT", + symbol="AAPL", timeframe="1m", open_time=datetime(2025, 1, 1, tzinfo=timezone.utc), open=Decimal("50000"), @@ -32,7 +32,7 @@ async def test_engine_processes_multiple_streams(): volume=Decimal("10"), ) candle_eth = Candle( - symbol="ETHUSDT", + symbol="MSFT", timeframe="1m", open_time=datetime(2025, 1, 1, tzinfo=timezone.utc), open=Decimal("3000"), @@ -45,16 +45,16 @@ async def test_engine_processes_multiple_streams(): btc_events = [CandleEvent(data=candle_btc).to_dict()] eth_events = [CandleEvent(data=candle_eth).to_dict()] - # First call returns BTC event, second ETH, then empty - call_count = {"btc": 0, "eth": 0} + # First call returns AAPL event, second MSFT, then empty + call_count = {"aapl": 0, "msft": 0} async def mock_read(stream, **kwargs): - if "BTC" in stream: - call_count["btc"] += 1 - return btc_events if call_count["btc"] == 1 else [] - elif "ETH" in stream: - call_count["eth"] += 1 - return eth_events if call_count["eth"] == 1 else [] + if "AAPL" in stream: + call_count["aapl"] += 1 + return btc_events if call_count["aapl"] == 1 else [] + elif "MSFT" in stream: + call_count["msft"] += 1 + return eth_events if call_count["msft"] == 1 else [] return [] broker.read = AsyncMock(side_effect=mock_read) @@ -65,8 +65,8 @@ async def test_engine_processes_multiple_streams(): engine = StrategyEngine(broker=broker, strategies=[strategy]) # Process both streams - await engine.process_once("candles.BTCUSDT", "$") - await engine.process_once("candles.ETHUSDT", "$") + await engine.process_once("candles.AAPL", "$") + await engine.process_once("candles.MSFT", "$") # Strategy should have been called with both candles assert strategy.on_candle.call_count == 2 diff --git a/services/strategy-engine/tests/test_rsi_strategy.py b/services/strategy-engine/tests/test_rsi_strategy.py index b2aecc9..6d31fd5 100644 --- a/services/strategy-engine/tests/test_rsi_strategy.py +++ b/services/strategy-engine/tests/test_rsi_strategy.py @@ -10,7 +10,7 @@ from strategies.rsi_strategy import RsiStrategy def make_candle(close: float, idx: int = 0) -> Candle: return Candle( - symbol="BTC/USDT", + symbol="AAPL", timeframe="1m", open_time=datetime(2024, 1, 1, tzinfo=timezone.utc), open=Decimal(str(close)), diff --git a/services/strategy-engine/tests/test_sentiment_wiring.py b/services/strategy-engine/tests/test_sentiment_wiring.py deleted file mode 100644 index e0052cb..0000000 --- a/services/strategy-engine/tests/test_sentiment_wiring.py +++ /dev/null @@ -1,32 +0,0 @@ -"""Test sentiment is wired into strategy engine.""" - -import sys -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) -sys.path.insert(0, str(Path(__file__).resolve().parents[1])) - -from shared.sentiment import SentimentData -from strategies.asian_session_rsi import AsianSessionRsiStrategy - - -def test_strategy_accepts_sentiment(): - s = AsianSessionRsiStrategy() - data = SentimentData(fear_greed_value=20, fear_greed_label="Extreme Fear") - s.update_sentiment(data) - assert s._sentiment is not None - assert s._sentiment.fear_greed_value == 20 - - -def test_strategy_blocks_on_extreme_greed(): - s = AsianSessionRsiStrategy() - data = SentimentData(fear_greed_value=85) - s.update_sentiment(data) - assert not s._check_sentiment() - - -def test_strategy_allows_on_fear(): - s = AsianSessionRsiStrategy() - data = SentimentData(fear_greed_value=20) - s.update_sentiment(data) - assert s._check_sentiment() diff --git a/services/strategy-engine/tests/test_volume_profile_strategy.py b/services/strategy-engine/tests/test_volume_profile_strategy.py index f40261c..65ee2e8 100644 --- a/services/strategy-engine/tests/test_volume_profile_strategy.py +++ b/services/strategy-engine/tests/test_volume_profile_strategy.py @@ -10,7 +10,7 @@ from strategies.volume_profile_strategy import VolumeProfileStrategy def make_candle(close: float, volume: float = 1.0) -> Candle: return Candle( - symbol="BTC/USDT", + symbol="AAPL", timeframe="1m", open_time=datetime(2024, 1, 1, tzinfo=timezone.utc), open=Decimal(str(close)), diff --git a/services/strategy-engine/tests/test_vwap_strategy.py b/services/strategy-engine/tests/test_vwap_strategy.py index 0312972..2c34b01 100644 --- a/services/strategy-engine/tests/test_vwap_strategy.py +++ b/services/strategy-engine/tests/test_vwap_strategy.py @@ -22,7 +22,7 @@ def make_candle( if open_time is None: open_time = datetime(2024, 1, 1, tzinfo=timezone.utc) return Candle( - symbol="BTC/USDT", + symbol="AAPL", timeframe="1m", open_time=open_time, open=Decimal(str(close)), |
