import os import sys PROJECT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) if PROJECT_DIR not in sys.path: sys.path.insert(0, PROJECT_DIR) from app.services import price_tracker def test_watch_only_recommendation_only_updates_observation_pnl(monkeypatch): rec = { "id": 1, "symbol": "QNT/USDT", "entry_price": 100.0, "stop_loss": 95.0, "tp1": 110.0, "tp2": 120.0, "entry_plan": {}, "entry_triggered": 0, "display_bucket": "watch_pool", "execution_status": "observe", "action_status": "观察", } calls = {"tracking": 0, "state_transition": 0} monkeypatch.setattr(price_tracker, "get_active_recommendations", lambda actionable_only=False: [rec]) monkeypatch.setattr(price_tracker.exchange, "fetch_ticker", lambda symbol: {"last": 102.0}) monkeypatch.setattr(price_tracker, "update_latest_price_cache", lambda *args, **kwargs: None) def fake_update_tracking(rec_id, current_price): calls["tracking"] += 1 return {"status": "active", "pnl_pct": 2.0} monkeypatch.setattr(price_tracker, "update_recommendation_tracking", fake_update_tracking) monkeypatch.setattr(price_tracker, "analyze_tracking_signals", lambda symbol, rec, current_price: { "action_status": "持有", "sell_signals": [], "buy_signals": [], "exhaustion": {"severity": "low"}, "pnl_pct": 0.0, }) def fake_state_transition(*args, **kwargs): calls["state_transition"] += 1 return {"action_status": "观察", "push_required": False} monkeypatch.setattr(price_tracker, "apply_recommendation_state_transition", fake_state_transition) monkeypatch.setattr(price_tracker, "expire_old_recommendations", lambda: None) monkeypatch.setattr(price_tracker, "get_stats", lambda: {"active_count": 1}) output = price_tracker.track_prices() assert output["tracked_count"] == 0 assert output["observed_count"] == 1 assert output["results"][0]["status"] == "observed_watch_only" assert output["results"][0]["current_price"] == 102.0 assert output["results"][0]["pnl_pct"] == 2.0 assert calls["tracking"] == 1 assert calls["state_transition"] == 0