From 9bcc4221a4ec8205d73ccf7526663aec7f9fbbec Mon Sep 17 00:00:00 2001 From: cillin Date: Thu, 26 Mar 2026 13:38:51 +0800 Subject: [PATCH] refactor: remove mock trading functionality from backend and frontend Removes all mock price simulation features: - Delete MockPriceManager from backend/data/ - Remove mock_mode, enable_mock, is_mock_mode flags from services - Remove mock CLI options and config - Remove mock mode UI components and state from frontend - Update tests to remove mock references Now system supports only live and backtest modes. Co-Authored-By: Claude Opus 4.6 --- backend/api/runtime.py | 2 - backend/cli.py | 82 +++--- backend/core/pipeline_runner.py | 6 +- backend/core/state_sync.py | 1 - backend/data/__init__.py | 3 +- backend/data/mock_price_manager.py | 244 ---------------- backend/gateway_server.py | 6 +- backend/main.py | 11 +- backend/services/gateway.py | 9 +- backend/services/market.py | 32 +-- backend/tests/test_cli.py | 1 - backend/tests/test_market_service.py | 264 +----------------- backend/tests/test_runtime_service_app.py | 1 - backend/utils/terminal_dashboard.py | 7 - frontend/src/App.jsx | 3 - frontend/src/components/AppShell.jsx | 42 --- frontend/src/components/NetValueChart.jsx | 4 +- .../src/components/RuntimeSettingsPanel.jsx | 17 -- frontend/src/hooks/useRuntimeControls.js | 8 - frontend/src/hooks/useWebSocketConnection.js | 20 +- frontend/src/store/runtimeStore.js | 2 - 21 files changed, 56 insertions(+), 709 deletions(-) delete mode 100644 backend/data/mock_price_manager.py diff --git a/backend/api/runtime.py b/backend/api/runtime.py index a307fd7..24d3a9f 100644 --- a/backend/api/runtime.py +++ b/backend/api/runtime.py @@ -181,7 +181,6 @@ class LaunchConfig(BaseModel): start_date: Optional[str] = Field(default=None, description="回测开始日期 YYYY-MM-DD") end_date: Optional[str] = Field(default=None, description="回测结束日期 YYYY-MM-DD") poll_interval: int = Field(default=10, ge=1, le=300, description="市场数据轮询间隔(秒)") - enable_mock: bool = Field(default=False, description="是否启用模拟模式(使用模拟价格数据)") class LaunchResponse(BaseModel): @@ -756,7 +755,6 @@ async def start_runtime( "start_date": config.start_date, "end_date": config.end_date, "poll_interval": config.poll_interval, - "enable_mock": config.enable_mock, } retention_keep = max(1, int(os.getenv("RUNS_RETENTION_COUNT", "20") or "20")) diff --git a/backend/cli.py b/backend/cli.py index 3297448..6266bd5 100644 --- a/backend/cli.py +++ b/backend/cli.py @@ -1019,11 +1019,6 @@ def backtest( @app.command() def live( - mock: bool = typer.Option( - False, - "--mock", - help="Use mock mode with simulated prices (for testing)", - ), config_name: str = typer.Option( "live", "--config-name", @@ -1078,7 +1073,6 @@ def live( Example: evotraders live # Run immediately (default) - evotraders live --mock # Mock mode evotraders live -t 22:30 # Run at 22:30 local time daily evotraders live --schedule-mode intraday --interval-minutes 60 evotraders live --trigger-time now # Run immediately @@ -1086,33 +1080,31 @@ def live( """ schedule_mode = str(_normalize_typer_value(schedule_mode, "daily")) interval_minutes = int(_normalize_typer_value(interval_minutes, 60)) - mode_name = "MOCK" if mock else "LIVE" console.print( Panel.fit( - f"[bold cyan]EvoTraders {mode_name} Mode[/bold cyan]", + "[bold cyan]EvoTraders LIVE Mode[/bold cyan]", border_style="cyan", ), ) # Check for required API key in live mode - if not mock: - env_file = get_project_root() / ".env" - if not env_file.exists(): - console.print("\n[yellow]Warning: .env file not found[/yellow]") - console.print("Creating from template...\n") - template = get_project_root() / "env.template" - if template.exists(): - shutil.copy(template, env_file) - console.print("[green].env file created[/green]") - console.print( - "\n[red]Error: Please edit .env and set FINNHUB_API_KEY[/red]", - ) - console.print( - "Get your free API key at: https://finnhub.io/register\n", - ) - else: - console.print("[red]Error: env.template not found[/red]") - raise typer.Exit(1) + env_file = get_project_root() / ".env" + if not env_file.exists(): + console.print("\n[yellow]Warning: .env file not found[/yellow]") + console.print("Creating from template...\n") + template = get_project_root() / "env.template" + if template.exists(): + shutil.copy(template, env_file) + console.print("[green].env file created[/green]") + console.print( + "\n[red]Error: Please edit .env and set FINNHUB_API_KEY[/red]", + ) + console.print( + "Get your free API key at: https://finnhub.io/register\n", + ) + else: + console.print("[red]Error: env.template not found[/red]") + raise typer.Exit(1) # Handle historical data cleanup handle_history_cleanup(config_name, auto_clean=clean) @@ -1168,12 +1160,9 @@ def live( # Display configuration console.print("\n[bold]Configuration:[/bold]") - if mock: - console.print(" Mode: [yellow]MOCK[/yellow] (Simulated prices)") - else: - console.print( - " Mode: [green]LIVE[/green] (Real-time prices via Finnhub)", - ) + console.print( + " Mode: [green]LIVE[/green] (Real-time prices via Finnhub)", + ) console.print(f" Config: {config_name}") console.print(f" Server: {host}:{port}") console.print(f" Poll Interval: {poll_interval}s") @@ -1188,22 +1177,17 @@ def live( project_root = get_project_root() os.chdir(project_root) - # Data update (if not mock mode) - if not mock: - run_data_updater(project_root) - auto_update_market_store( - config_name, - end_date=nyse_now.date().isoformat(), - ) - auto_enrich_market_store( - config_name, - end_date=nyse_now.date().isoformat(), - force=False, - ) - else: - console.print( - "\n[dim]Mock mode enabled - skipping data update[/dim]\n", - ) + # Data update + run_data_updater(project_root) + auto_update_market_store( + config_name, + end_date=nyse_now.date().isoformat(), + ) + auto_enrich_market_store( + config_name, + end_date=nyse_now.date().isoformat(), + force=False, + ) # Build command using backend.main cmd = [ @@ -1229,8 +1213,6 @@ def live( str(interval_minutes), ] - if mock: - cmd.append("--mock") if enable_memory: cmd.append("--enable-memory") diff --git a/backend/core/pipeline_runner.py b/backend/core/pipeline_runner.py index c875fa6..5b68db7 100644 --- a/backend/core/pipeline_runner.py +++ b/backend/core/pipeline_runner.py @@ -244,10 +244,8 @@ async def run_pipeline( start_date = bootstrap.get("start_date") end_date = bootstrap.get("end_date") enable_memory = bootstrap.get("enable_memory", False) - enable_mock = bootstrap.get("enable_mock", False) is_backtest = mode == "backtest" - is_mock = enable_mock or mode == "mock" or (not is_backtest and os.getenv("MOCK_MODE", "false").lower() == "true") # ====================================================================== # PHASE 0: Initialize runtime manager @@ -288,9 +286,8 @@ async def run_pipeline( market_service = MarketService( tickers=tickers, poll_interval=10, - mock_mode=is_mock and not is_backtest, backtest_mode=is_backtest, - api_key=os.getenv("FINNHUB_API_KEY") if not is_mock and not is_backtest else None, + api_key=os.getenv("FINNHUB_API_KEY") if not is_backtest else None, backtest_start_date=start_date if is_backtest else None, backtest_end_date=end_date if is_backtest else None, ) @@ -387,7 +384,6 @@ async def run_pipeline( scheduler_callback=scheduler_callback, config={ "mode": mode, - "mock_mode": is_mock, "backtest_mode": is_backtest, "tickers": tickers, "config_name": run_id, diff --git a/backend/core/state_sync.py b/backend/core/state_sync.py index 304e835..4404d52 100644 --- a/backend/core/state_sync.py +++ b/backend/core/state_sync.py @@ -465,7 +465,6 @@ class StateSync: payload = { "server_mode": self._state.get("server_mode", "live"), - "is_mock_mode": self._state.get("is_mock_mode", False), "is_backtest": self._state.get("is_backtest", False), "tickers": self._state.get("tickers"), "runtime_config": self._state.get("runtime_config"), diff --git a/backend/data/__init__.py b/backend/data/__init__.py index eb90e8e..5eb3cff 100644 --- a/backend/data/__init__.py +++ b/backend/data/__init__.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- from backend.data.historical_price_manager import HistoricalPriceManager -from backend.data.mock_price_manager import MockPriceManager from backend.data.polling_price_manager import PollingPriceManager -__all__ = ["MockPriceManager", "PollingPriceManager", "HistoricalPriceManager"] +__all__ = ["PollingPriceManager", "HistoricalPriceManager"] diff --git a/backend/data/mock_price_manager.py b/backend/data/mock_price_manager.py deleted file mode 100644 index cf92935..0000000 --- a/backend/data/mock_price_manager.py +++ /dev/null @@ -1,244 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Mock Price Manager - For testing during non-trading hours -Generates virtual real-time price data -""" -import logging -import os -import random -import threading -import time -from typing import Callable, Dict, List, Optional -from backend.data.provider_utils import normalize_symbol - -logger = logging.getLogger(__name__) - - -class MockPriceManager: - """Mock Price Manager - Generates virtual prices for testing""" - - def __init__(self, poll_interval: int = 10, volatility: float = 0.5): - """ - Args: - poll_interval: Price update interval in seconds - volatility: Price volatility percentage - """ - if poll_interval is None: - poll_interval = int(os.getenv("MOCK_POLL_INTERVAL", "5")) - if volatility is None: - volatility = float(os.getenv("MOCK_VOLATILITY", "0.5")) - - self.poll_interval = poll_interval - self.volatility = volatility - - self.subscribed_symbols: List[str] = [] - self.base_prices: Dict[str, float] = {} - self.open_prices: Dict[str, float] = {} - self.latest_prices: Dict[str, float] = {} - self.price_callbacks: List[Callable] = [] - - self.running = False - self._thread: Optional[threading.Thread] = None - - self.default_base_prices = { - "AAPL": 237.50, - "MSFT": 425.30, - "GOOGL": 161.50, - "AMZN": 218.45, - "NVDA": 950.00, - "META": 573.22, - "TSLA": 342.15, - "AMD": 168.90, - "NFLX": 688.25, - "INTC": 42.18, - "COIN": 285.50, - "PLTR": 45.80, - "BABA": 88.30, - "DIS": 112.50, - "BKNG": 4850.00, - } - - logger.info( - f"MockPriceManager initialized (interval: {self.poll_interval}s, " - f"volatility: {self.volatility}%)", - ) - - def subscribe( - self, - symbols: List[str], - base_prices: Dict[str, float] = None, - ): - """Subscribe to stock symbols""" - for symbol in symbols: - symbol = normalize_symbol(symbol) - if symbol not in self.subscribed_symbols: - self.subscribed_symbols.append(symbol) - - if base_prices and symbol in base_prices: - base_price = base_prices[symbol] - elif symbol in self.default_base_prices: - base_price = self.default_base_prices[symbol] - else: - base_price = random.uniform(50, 500) - - self.base_prices[symbol] = base_price - self.open_prices[symbol] = base_price - self.latest_prices[symbol] = base_price - - logger.info( - f"Subscribed to mock price: {symbol} (base: ${base_price:.2f})", # noqa: E501 - ) - - def unsubscribe(self, symbols: List[str]): - """Unsubscribe from symbols""" - for symbol in symbols: - symbol = normalize_symbol(symbol) - if symbol in self.subscribed_symbols: - self.subscribed_symbols.remove(symbol) - self.base_prices.pop(symbol, None) - self.open_prices.pop(symbol, None) - self.latest_prices.pop(symbol, None) - logger.info(f"Unsubscribed: {symbol}") - - def add_price_callback(self, callback: Callable): - """Add price update callback""" - self.price_callbacks.append(callback) - - def _generate_price_update(self, symbol: str) -> float: - """Generate price update based on random walk""" - current_price = self.latest_prices.get( - symbol, - self.base_prices[symbol], - ) - - change_percent = random.uniform(-self.volatility, self.volatility) - new_price = current_price * (1 + change_percent / 100) - - # 10% chance of larger movement - if random.random() < 0.1: - trend_factor = random.uniform(-2, 2) - new_price = new_price * (1 + trend_factor / 100) - - # Limit intraday movement to +/-10% - open_price = self.open_prices[symbol] - max_price = open_price * 1.10 - min_price = open_price * 0.90 - new_price = max(min_price, min(max_price, new_price)) - - return new_price - - def _update_prices(self): - """Update prices for all subscribed stocks""" - timestamp = int(time.time() * 1000) - - for symbol in self.subscribed_symbols: - try: - new_price = self._generate_price_update(symbol) - self.latest_prices[symbol] = new_price - - open_price = self.open_prices[symbol] - ret = ((new_price - open_price) / open_price) * 100 - - price_data = { - "symbol": symbol, - "price": new_price, - "timestamp": timestamp, - "volume": random.randint(1000000, 10000000), - "open": open_price, - "high": max(new_price, open_price), - "low": min(new_price, open_price), - "previous_close": open_price, - "ret": ret, - } - - for callback in self.price_callbacks: - try: - callback(price_data) - except Exception as e: - logger.error( - f"Mock price callback error ({symbol}): {e}", - ) - - logger.debug( - f"Mock {symbol}: ${new_price:.2f} [ret: {ret:+.2f}%]", - ) - - except Exception as e: - logger.error(f"Failed to generate mock price ({symbol}): {e}") - - def _polling_loop(self): - """Main polling loop""" - logger.info( - f"Mock price generation started (interval: {self.poll_interval}s)", - ) - - while self.running: - try: - start_time = time.time() - self._update_prices() - - elapsed = time.time() - start_time - sleep_time = max(0, self.poll_interval - elapsed) - if sleep_time > 0: - time.sleep(sleep_time) - - except Exception as e: - logger.error(f"Mock polling loop error: {e}") - time.sleep(5) - - def start(self): - """Start mock price generation""" - if self.running: - logger.warning("Mock price manager already running") - return - - if not self.subscribed_symbols: - logger.warning("No stocks subscribed") - return - - self.running = True - self._thread = threading.Thread(target=self._polling_loop, daemon=True) - self._thread.start() - - logger.info( - f"Mock price manager started: {', '.join(self.subscribed_symbols)}", # noqa: E501 - ) - - def stop(self): - """Stop mock price generation""" - self.running = False - if self._thread: - self._thread.join(timeout=5) - logger.info("Mock price manager stopped") - - def get_latest_price(self, symbol: str) -> Optional[float]: - """Get latest price for symbol""" - return self.latest_prices.get(symbol) - - def get_all_latest_prices(self) -> Dict[str, float]: - """Get all latest prices""" - return self.latest_prices.copy() - - def get_open_price(self, symbol: str) -> Optional[float]: - """Get open price for symbol""" - return self.open_prices.get(symbol) - - def reset_open_prices(self): - """Reset open prices for new trading day""" - for symbol in self.subscribed_symbols: - last_close = self.latest_prices[symbol] - gap_percent = random.uniform(-1, 1) - new_open = last_close * (1 + gap_percent / 100) - self.open_prices[symbol] = new_open - self.latest_prices[symbol] = new_open - logger.info("Open prices reset") - - def set_base_price(self, symbol: str, price: float): - """Manually set base price for testing""" - if symbol in self.subscribed_symbols: - self.base_prices[symbol] = price - self.open_prices[symbol] = price - self.latest_prices[symbol] = price - logger.info(f"{symbol} base price set to: ${price:.2f}") - else: - logger.warning(f"{symbol} not subscribed") diff --git a/backend/gateway_server.py b/backend/gateway_server.py index 60cb019..cd9b5eb 100644 --- a/backend/gateway_server.py +++ b/backend/gateway_server.py @@ -130,10 +130,8 @@ async def run_gateway( end_date = bootstrap.get("end_date") enable_memory = bootstrap.get("enable_memory", False) poll_interval = int(bootstrap.get("poll_interval", 10)) - enable_mock = bootstrap.get("enable_mock", False) is_backtest = mode == "backtest" - is_mock = enable_mock or mode == "mock" or (not is_backtest and os.getenv("MOCK_MODE", "false").lower() == "true") logger.info(f"[Gateway Server] Starting run {run_id} on port {port}") @@ -152,9 +150,8 @@ async def run_gateway( market_service = MarketService( tickers=tickers, poll_interval=poll_interval, - mock_mode=is_mock and not is_backtest, backtest_mode=is_backtest, - api_key=os.getenv("FINNHUB_API_KEY") if not is_mock and not is_backtest else None, + api_key=os.getenv("FINNHUB_API_KEY") if not is_backtest else None, backtest_start_date=start_date if is_backtest else None, backtest_end_date=end_date if is_backtest else None, ) @@ -247,7 +244,6 @@ async def run_gateway( scheduler_callback=scheduler_callback, config={ "mode": mode, - "mock_mode": is_mock, "backtest_mode": is_backtest, "tickers": tickers, "config_name": run_id, diff --git a/backend/main.py b/backend/main.py index f5c75e5..b1356a8 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ Main Entry Point -Supports: backtest, live, mock modes +Supports: backtest, live modes """ import argparse import asyncio @@ -231,11 +231,8 @@ async def run_with_gateway(args): market_service = MarketService( tickers=tickers, poll_interval=args.poll_interval, - mock_mode=args.mock and not is_backtest, backtest_mode=is_backtest, - api_key=os.getenv("FINNHUB_API_KEY") - if not args.mock and not is_backtest - else None, + api_key=os.getenv("FINNHUB_API_KEY") if not is_backtest else None, backtest_start_date=args.start_date if is_backtest else None, backtest_end_date=args.end_date if is_backtest else None, ) @@ -320,7 +317,6 @@ async def run_with_gateway(args): scheduler_callback=scheduler_callback, config={ "mode": args.mode, - "mock_mode": args.mock, "backtest_mode": is_backtest, "tickers": tickers, "config_name": config_name, @@ -353,8 +349,7 @@ def main(): """Main entry point""" parser = argparse.ArgumentParser(description="Trading System") parser.add_argument("--mode", choices=["live", "backtest"], default="live") - parser.add_argument("--mock", action="store_true") - parser.add_argument("--config-name", default="mock") + parser.add_argument("--config-name", default="live") parser.add_argument("--host", default="0.0.0.0") parser.add_argument("--port", type=int, default=8765) parser.add_argument( diff --git a/backend/services/gateway.py b/backend/services/gateway.py index 7c7252d..894f1d9 100644 --- a/backend/services/gateway.py +++ b/backend/services/gateway.py @@ -111,7 +111,6 @@ class Gateway: host=host, port=port, poll_interval=self.config.get("poll_interval", 10), - mock=self.config.get("mock_mode", False), tickers=self.config.get("tickers", []), initial_cash=self.storage.initial_cash, start_date=self._backtest_start_date or "", @@ -125,10 +124,6 @@ class Gateway: self.state_sync.update_state("status", "initializing") self.state_sync.update_state("server_mode", self.mode) self.state_sync.update_state("is_backtest", self.is_backtest) - self.state_sync.update_state( - "is_mock_mode", - self.config.get("mock_mode", False), - ) self.state_sync.update_state("tickers", self.config.get("tickers", [])) self.state_sync.update_state( "runtime_config", @@ -545,13 +540,13 @@ class Gateway: websocket: ServerConnection, data: Dict[str, Any], ) -> None: - """Run one live/mock trading cycle on demand.""" + """Run one live trading cycle on demand.""" if self.is_backtest: await websocket.send( json.dumps( { "type": "error", - "message": "Manual trigger is only available in live/mock mode.", + "message": "Manual trigger is only available in live mode.", }, ensure_ascii=False, ), diff --git a/backend/services/market.py b/backend/services/market.py index 2749f76..557fce9 100644 --- a/backend/services/market.py +++ b/backend/services/market.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ Market Data Service -Supports live, mock, and backtest modes +Supports live and backtest modes """ import asyncio import logging @@ -36,7 +36,6 @@ class MarketService: self, tickers: List[str], poll_interval: int = 10, - mock_mode: bool = False, backtest_mode: bool = False, api_key: Optional[str] = None, backtest_start_date: Optional[str] = None, @@ -44,7 +43,6 @@ class MarketService: ): self.tickers = [normalize_symbol(ticker) for ticker in tickers] self.poll_interval = poll_interval - self.mock_mode = mock_mode self.backtest_mode = backtest_mode self.api_key = api_key self.backtest_start_date = backtest_start_date @@ -69,8 +67,6 @@ class MarketService: """Return the active live quote provider for UI/debugging.""" if self.backtest_mode: return "backtest" - if self.mock_mode: - return "mock" if self._price_manager and hasattr(self._price_manager, "provider"): provider = getattr(self._price_manager, "provider", None) if isinstance(provider, str) and provider.strip(): @@ -81,8 +77,6 @@ class MarketService: def mode_name(self) -> str: if self.backtest_mode: return "BACKTEST" - elif self.mock_mode: - return "MOCK" return "LIVE" async def start(self, broadcast_func: Callable): @@ -96,8 +90,6 @@ class MarketService: if self.backtest_mode: self._start_backtest_mode() - elif self.mock_mode: - self._start_mock_mode() else: self._start_real_mode() @@ -125,20 +117,6 @@ class MarketService: return callback - def _start_mock_mode(self): - from backend.data.mock_price_manager import MockPriceManager - - self._price_manager = MockPriceManager( - poll_interval=self.poll_interval, - volatility=0.5, - ) - self._price_manager.add_price_callback(self._make_price_callback()) - self._price_manager.subscribe( - self.tickers, - base_prices={t: 100.0 for t in self.tickers}, - ) - self._price_manager.start() - def _start_real_mode(self): from backend.data.polling_price_manager import PollingPriceManager @@ -262,13 +240,7 @@ class MarketService: if removed: self._price_manager.unsubscribe(removed) if added: - if self.mock_mode: - self._price_manager.subscribe( - added, - base_prices={ticker: 100.0 for ticker in added}, - ) - else: - self._price_manager.subscribe(added) + self._price_manager.subscribe(added) if self.backtest_mode and self._current_date: self._price_manager.set_date(self._current_date) diff --git a/backend/tests/test_cli.py b/backend/tests/test_cli.py index 986be98..92f68e2 100644 --- a/backend/tests/test_cli.py +++ b/backend/tests/test_cli.py @@ -34,7 +34,6 @@ def test_live_runs_incremental_market_store_update_before_start(monkeypatch, tmp monkeypatch.setattr(cli.subprocess, "run", fake_run) cli.live( - mock=False, config_name="smoke_fullstack", host="0.0.0.0", port=8765, diff --git a/backend/tests/test_market_service.py b/backend/tests/test_market_service.py index e376f54..35289de 100644 --- a/backend/tests/test_market_service.py +++ b/backend/tests/test_market_service.py @@ -6,153 +6,10 @@ import logging from unittest.mock import MagicMock, AsyncMock, patch import pytest from backend.services.market import MarketService -from backend.data.mock_price_manager import MockPriceManager from backend.data.polling_price_manager import PollingPriceManager from backend.llm.models import RetryChatModel -class TestMockPriceManager: - def test_init_default(self): - manager = MockPriceManager() - - assert manager.poll_interval == 10 - assert manager.volatility == 0.5 - assert manager.running is False - assert len(manager.subscribed_symbols) == 0 - - def test_init_custom(self): - manager = MockPriceManager(poll_interval=5, volatility=1.0) - - assert manager.poll_interval == 5 - assert manager.volatility == 1.0 - - def test_subscribe(self): - manager = MockPriceManager() - manager.subscribe(["AAPL", "MSFT"]) - - assert "AAPL" in manager.subscribed_symbols - assert "MSFT" in manager.subscribed_symbols - assert manager.base_prices["AAPL"] == 237.50 # default price - assert manager.base_prices["MSFT"] == 425.30 # default price - - def test_subscribe_with_base_prices(self): - manager = MockPriceManager() - manager.subscribe(["AAPL"], base_prices={"AAPL": 100.0}) - - assert manager.base_prices["AAPL"] == 100.0 - assert manager.open_prices["AAPL"] == 100.0 - assert manager.latest_prices["AAPL"] == 100.0 - - def test_subscribe_unknown_symbol(self): - manager = MockPriceManager() - manager.subscribe(["UNKNOWN"]) - - assert "UNKNOWN" in manager.subscribed_symbols - assert manager.base_prices["UNKNOWN"] > 0 # random price generated - - def test_unsubscribe(self): - manager = MockPriceManager() - manager.subscribe(["AAPL", "MSFT"]) - manager.unsubscribe(["AAPL"]) - - assert "AAPL" not in manager.subscribed_symbols - assert "MSFT" in manager.subscribed_symbols - - def test_add_price_callback(self): - manager = MockPriceManager() - callback = MagicMock() - manager.add_price_callback(callback) - - assert callback in manager.price_callbacks - - def test_generate_price_update_within_bounds(self): - manager = MockPriceManager(volatility=0.5) - manager.subscribe(["AAPL"], base_prices={"AAPL": 100.0}) - - for _ in range(100): - new_price = manager._generate_price_update("AAPL") - # Should be within +/-10% of open - assert 90.0 <= new_price <= 110.0 - - def test_update_prices_triggers_callback(self): - manager = MockPriceManager() - manager.subscribe(["AAPL"], base_prices={"AAPL": 100.0}) - - callback = MagicMock() - manager.add_price_callback(callback) - - manager._update_prices() - - callback.assert_called_once() - call_args = callback.call_args[0][0] - assert call_args["symbol"] == "AAPL" - assert "price" in call_args - assert "timestamp" in call_args - - def test_start_stop(self): - manager = MockPriceManager(poll_interval=1) - manager.subscribe(["AAPL"], base_prices={"AAPL": 100.0}) - - manager.start() - assert manager.running is True - - time.sleep(0.1) # let thread start - - manager.stop() - assert manager.running is False - - def test_start_without_subscription(self): - manager = MockPriceManager() - manager.start() - - assert ( - manager.running is False - ) # should not start without subscriptions - - def test_get_latest_price(self): - manager = MockPriceManager() - manager.subscribe(["AAPL"], base_prices={"AAPL": 100.0}) - - price = manager.get_latest_price("AAPL") - assert price == 100.0 - - def test_get_latest_price_unknown(self): - manager = MockPriceManager() - price = manager.get_latest_price("UNKNOWN") - assert price is None - - def test_get_all_latest_prices(self): - manager = MockPriceManager() - manager.subscribe( - ["AAPL", "MSFT"], - base_prices={"AAPL": 100.0, "MSFT": 200.0}, - ) - - prices = manager.get_all_latest_prices() - assert prices["AAPL"] == 100.0 - assert prices["MSFT"] == 200.0 - - def test_reset_open_prices(self): - manager = MockPriceManager() - manager.subscribe(["AAPL"], base_prices={"AAPL": 100.0}) - manager.latest_prices["AAPL"] = 105.0 - - manager.reset_open_prices() - - # Open price should change (based on latest with small gap) - assert manager.open_prices["AAPL"] != 100.0 - - def test_set_base_price(self): - manager = MockPriceManager() - manager.subscribe(["AAPL"], base_prices={"AAPL": 100.0}) - - manager.set_base_price("AAPL", 150.0) - - assert manager.base_prices["AAPL"] == 150.0 - assert manager.open_prices["AAPL"] == 150.0 - assert manager.latest_prices["AAPL"] == 150.0 - - class TestPollingPriceManager: def test_init(self): manager = PollingPriceManager(api_key="test_key", poll_interval=30) @@ -288,35 +145,12 @@ class TestRetryChatModel: class TestMarketService: - def test_init_mock_mode(self): - service = MarketService( - tickers=["AAPL", "MSFT"], - poll_interval=10, - mock_mode=True, - ) - - assert service.tickers == ["AAPL", "MSFT"] - assert service.poll_interval == 10 - assert service.mock_mode is True - assert service.running is False - - def test_init_real_mode(self): - service = MarketService( - tickers=["AAPL"], - mock_mode=False, - api_key="test_key", - ) - - assert service.mock_mode is False - assert service.api_key == "test_key" - @patch("backend.services.market.get_data_sources", return_value=["yfinance", "local_csv"]) @patch.object(PollingPriceManager, "start") def test_start_real_mode_with_yfinance(self, _mock_start, _mock_sources): service = MarketService( tickers=["AAPL"], poll_interval=10, - mock_mode=False, ) service._start_real_mode() @@ -330,7 +164,6 @@ class TestMarketService: service = MarketService( tickers=["AAPL"], poll_interval=10, - mock_mode=False, ) service._start_real_mode() @@ -338,30 +171,11 @@ class TestMarketService: assert isinstance(service._price_manager, PollingPriceManager) assert service._price_manager.provider == "yfinance" - @pytest.mark.asyncio - async def test_start_mock_mode(self): - service = MarketService( - tickers=["AAPL"], - poll_interval=10, - mock_mode=True, - ) - - broadcast_func = AsyncMock() - - await service.start(broadcast_func) - - assert service.running is True - assert service._price_manager is not None - assert isinstance(service._price_manager, MockPriceManager) - - service.stop() - @patch("backend.services.market.get_data_sources", return_value=["finnhub", "yfinance"]) @pytest.mark.asyncio async def test_start_real_mode_without_api_key(self, _mock_sources): service = MarketService( tickers=["AAPL"], - mock_mode=False, api_key=None, ) @@ -376,11 +190,12 @@ class TestMarketService: async def test_start_already_running(self): service = MarketService( tickers=["AAPL"], - mock_mode=True, + backtest_mode=True, ) broadcast_func = AsyncMock() + # First start with backtest mode await service.start(broadcast_func) assert service.running is True @@ -392,7 +207,7 @@ class TestMarketService: def test_stop(self): service = MarketService( tickers=["AAPL"], - mock_mode=True, + backtest_mode=True, ) service.running = True service._price_manager = MagicMock() @@ -405,7 +220,7 @@ class TestMarketService: def test_stop_when_not_running(self): service = MarketService( tickers=["AAPL"], - mock_mode=True, + backtest_mode=True, ) # Should not raise @@ -413,20 +228,20 @@ class TestMarketService: assert service.running is False def test_get_price_sync(self): - service = MarketService(tickers=["AAPL"], mock_mode=True) + service = MarketService(tickers=["AAPL"], backtest_mode=True) service.cache["AAPL"] = {"price": 150.0, "open": 148.0} price = service.get_price_sync("AAPL") assert price == 150.0 def test_get_price_sync_not_found(self): - service = MarketService(tickers=["AAPL"], mock_mode=True) + service = MarketService(tickers=["AAPL"], backtest_mode=True) price = service.get_price_sync("MSFT") assert price is None def test_get_all_prices(self): - service = MarketService(tickers=["AAPL", "MSFT"], mock_mode=True) + service = MarketService(tickers=["AAPL", "MSFT"], backtest_mode=True) service.cache["AAPL"] = {"price": 150.0} service.cache["MSFT"] = {"price": 400.0} @@ -437,7 +252,7 @@ class TestMarketService: @pytest.mark.asyncio async def test_broadcast_price_update(self): - service = MarketService(tickers=["AAPL"], mock_mode=True) + service = MarketService(tickers=["AAPL"], backtest_mode=True) service._broadcast_func = AsyncMock() price_data = { @@ -457,7 +272,7 @@ class TestMarketService: @pytest.mark.asyncio async def test_broadcast_price_update_no_func(self): - service = MarketService(tickers=["AAPL"], mock_mode=True) + service = MarketService(tickers=["AAPL"], backtest_mode=True) service._broadcast_func = None price_data = {"symbol": "AAPL", "price": 150.0, "open": 148.0} @@ -465,67 +280,6 @@ class TestMarketService: # Should not raise await service._broadcast_price_update(price_data) - @pytest.mark.asyncio - async def test_price_callback_thread_safety(self): - service = MarketService( - tickers=["AAPL"], - poll_interval=1, - mock_mode=True, - ) - - received_prices = [] - - async def capture_broadcast(msg): - received_prices.append(msg) - - await service.start(capture_broadcast) - - # Wait for at least one price update - await asyncio.sleep(1.5) - - service.stop() - - # Should have received at least one price update - assert len(received_prices) >= 1 - assert received_prices[0]["type"] == "price_update" - - -class TestMarketServiceIntegration: - @pytest.mark.asyncio - async def test_full_mock_cycle(self): - service = MarketService( - tickers=["AAPL", "MSFT"], - poll_interval=1, - mock_mode=True, - ) - - messages = [] - - async def collect_messages(msg): - messages.append(msg) - - await service.start(collect_messages) - - # Wait for price updates - await asyncio.sleep(2.5) - - service.stop() - - # Should have received multiple price updates - assert len(messages) >= 2 - - # Check message structure - symbols_seen = set() - for msg in messages: - assert msg["type"] == "price_update" - assert "symbol" in msg - assert "price" in msg - assert "ret" in msg - symbols_seen.add(msg["symbol"]) - - # Should have prices for both tickers - assert "AAPL" in symbols_seen or "MSFT" in symbols_seen - if __name__ == "__main__": pytest.main([__file__, "-v"]) diff --git a/backend/tests/test_runtime_service_app.py b/backend/tests/test_runtime_service_app.py index 8801e1e..e464261 100644 --- a/backend/tests/test_runtime_service_app.py +++ b/backend/tests/test_runtime_service_app.py @@ -310,7 +310,6 @@ def test_start_runtime_restore_reuses_historical_run_id(monkeypatch, tmp_path): "enable_memory": False, "mode": "live", "poll_interval": 10, - "enable_mock": False, }, } } diff --git a/backend/utils/terminal_dashboard.py b/backend/utils/terminal_dashboard.py index 1a79e1a..9656954 100644 --- a/backend/utils/terminal_dashboard.py +++ b/backend/utils/terminal_dashboard.py @@ -30,7 +30,6 @@ class TerminalDashboard: self.port = 8765 self.poll_interval = 10 self.trigger_time = "now" - self.mock = False self.enable_memory = False self.local_time = "" self.nyse_time = "" @@ -65,7 +64,6 @@ class TerminalDashboard: port: int, poll_interval: int, trigger_time: str = "now", - mock: bool = False, enable_memory: bool = False, local_time: str = "", nyse_time: str = "", @@ -82,7 +80,6 @@ class TerminalDashboard: self.port = port self.poll_interval = poll_interval self.trigger_time = trigger_time - self.mock = mock self.enable_memory = enable_memory self.local_time = local_time self.nyse_time = nyse_time @@ -109,8 +106,6 @@ class TerminalDashboard: # Mode line if self.mode == "backtest": mode_str = "[cyan]Backtest[/cyan]" - elif self.mock: - mode_str = "[yellow]MOCK[/yellow]" else: mode_str = "[green]LIVE[/green]" @@ -216,8 +211,6 @@ class TerminalDashboard: title = "[bold cyan]EvoTraders[/bold cyan]" if self.mode == "backtest": title += " [dim]Backtest[/dim]" - elif self.mock: - title += " [dim]Mock[/dim]" else: title += " [dim]Live[/dim]" diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index a4ce83f..cfa7717 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -291,7 +291,6 @@ export default function LiveTradingApp() { financial_datasets: 'FINANCIAL DATASETS', local_csv: 'CSV', polygon: 'POLYGON', - mock: 'MOCK', backtest: 'BACKTEST', }), []); @@ -415,7 +414,6 @@ export default function LiveTradingApp() { pollIntervalDraft={runtimeControls.pollIntervalDraft} startDateDraft={runtimeControls.startDateDraft} endDateDraft={runtimeControls.endDateDraft} - enableMockDraft={runtimeControls.enableMockDraft} watchlistDraftSymbols={runtimeControls.watchlistDraftSymbols} watchlistInputValue={runtimeControls.watchlistInputValue} watchlistSuggestions={runtimeControls.watchlistSuggestions} @@ -432,7 +430,6 @@ export default function LiveTradingApp() { onPollIntervalChange={runtimeControls.setPollIntervalDraft} onStartDateChange={runtimeControls.setStartDateDraft} onEndDateChange={runtimeControls.setEndDateDraft} - onEnableMockChange={runtimeControls.setEnableMockDraft} onWatchlistInputChange={runtimeControls.handleWatchlistInputChange} onWatchlistInputKeyDown={runtimeControls.handleWatchlistInputKeyDown} onWatchlistAdd={runtimeControls.handleWatchlistAdd} diff --git a/frontend/src/components/AppShell.jsx b/frontend/src/components/AppShell.jsx index 86eca78..cd7b399 100644 --- a/frontend/src/components/AppShell.jsx +++ b/frontend/src/components/AppShell.jsx @@ -72,7 +72,6 @@ export default function AppShell({ pollIntervalDraft, startDateDraft, endDateDraft, - enableMockDraft, watchlistDraftSymbols, watchlistInputValue, watchlistSuggestions, @@ -89,7 +88,6 @@ export default function AppShell({ onPollIntervalChange, onStartDateChange, onEndDateChange, - onEnableMockChange, onWatchlistInputChange, onWatchlistInputKeyDown, onWatchlistAdd, @@ -186,44 +184,6 @@ export default function AppShell({
- {/* Mock Mode Indicator */} - {virtualTime && ( -
- - - 模拟模式 - -
- )} - - {/* Clock Display (only in Mock mode) */} - {virtualTime && ( -
-
- 虚拟时间 - - {now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false })} - - - {now.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })} - -
- - {/* Fast Forward Button (only in Mock mode) */} - -
- )} - {/* Unified Status Indicator */}
@@ -320,7 +280,6 @@ export default function AppShell({ pollInterval={pollIntervalDraft} startDate={startDateDraft} endDate={endDateDraft} - enableMock={enableMockDraft} watchlistSymbols={watchlistDraftSymbols} watchlistInputValue={watchlistInputValue} watchlistSuggestions={watchlistSuggestions} @@ -339,7 +298,6 @@ export default function AppShell({ onPollIntervalChange={onPollIntervalChange} onStartDateChange={onStartDateChange} onEndDateChange={onEndDateChange} - onEnableMockChange={onEnableMockChange} onWatchlistInputChange={onWatchlistInputChange} onWatchlistInputKeyDown={onWatchlistInputKeyDown} onWatchlistAdd={onWatchlistAdd} diff --git a/frontend/src/components/NetValueChart.jsx b/frontend/src/components/NetValueChart.jsx index 8df4f2a..cc50d39 100644 --- a/frontend/src/components/NetValueChart.jsx +++ b/frontend/src/components/NetValueChart.jsx @@ -5,10 +5,10 @@ import { formatNumber, formatFullNumber } from '../utils/formatters'; /** * Helper function to get the start time of the most recent trading session * Trading session: 22:30 - next day 05:00 - * @param {Date|null} virtualTime - Virtual time from server (for mock mode), or null to use real time + * @param {Date|null} virtualTime - Virtual time from server, or null to use real time */ function getRecentTradingSessionStart(virtualTime = null) { - // Use virtual time if provided (for mock mode), otherwise use real time + // Use virtual time if provided, otherwise use real time let now; if (virtualTime) { // Ensure virtualTime is a valid Date object diff --git a/frontend/src/components/RuntimeSettingsPanel.jsx b/frontend/src/components/RuntimeSettingsPanel.jsx index e9f3ae3..fcd6e0d 100644 --- a/frontend/src/components/RuntimeSettingsPanel.jsx +++ b/frontend/src/components/RuntimeSettingsPanel.jsx @@ -30,7 +30,6 @@ export default function RuntimeSettingsPanel({ pollInterval, startDate, endDate, - enableMock, watchlistSymbols, watchlistInputValue, watchlistSuggestions, @@ -49,7 +48,6 @@ export default function RuntimeSettingsPanel({ onPollIntervalChange, onStartDateChange, onEndDateChange, - onEnableMockChange, onWatchlistInputChange, onWatchlistInputKeyDown, onWatchlistAdd, @@ -580,21 +578,6 @@ export default function RuntimeSettingsPanel({ }} /> - -
)} diff --git a/frontend/src/hooks/useRuntimeControls.js b/frontend/src/hooks/useRuntimeControls.js index b8ad272..c5cf702 100644 --- a/frontend/src/hooks/useRuntimeControls.js +++ b/frontend/src/hooks/useRuntimeControls.js @@ -63,8 +63,6 @@ export function useRuntimeControls({ clientRef, currentTickers, addSystemMessage setStartDateDraft, endDateDraft, setEndDateDraft, - enableMockDraft, - setEnableMockDraft, runtimeConfigFeedback, setRuntimeConfigFeedback, isRuntimeConfigSaving, @@ -400,7 +398,6 @@ export function useRuntimeControls({ clientRef, currentTickers, addSystemMessage poll_interval: Number(pollIntervalDraft) || Number(DEFAULT_POLL_INTERVAL), start_date: startDateDraft || null, end_date: endDateDraft || null, - enable_mock: Boolean(enableMockDraft) }); setIsRuntimeConfigSaving(false); @@ -424,7 +421,6 @@ export function useRuntimeControls({ clientRef, currentTickers, addSystemMessage addSystemMessage, clientRef, enableMemoryDraft, - enableMockDraft, endDateDraft, initialCashDraft, intervalMinutesDraft, @@ -463,11 +459,9 @@ export function useRuntimeControls({ clientRef, currentTickers, addSystemMessage setPollIntervalDraft(DEFAULT_POLL_INTERVAL); setStartDateDraft(""); setEndDateDraft(""); - setEnableMockDraft(false); setRuntimeConfigFeedback(null); }, [ setEnableMemoryDraft, - setEnableMockDraft, setEndDateDraft, setInitialCashDraft, setIntervalMinutesDraft, @@ -542,7 +536,6 @@ export function useRuntimeControls({ clientRef, currentTickers, addSystemMessage pollIntervalDraft, startDateDraft, endDateDraft, - enableMockDraft, runtimeConfigFeedback, isRuntimeConfigSaving, isWatchlistSavingRef, @@ -582,7 +575,6 @@ export function useRuntimeControls({ clientRef, currentTickers, addSystemMessage setPollIntervalDraft, setStartDateDraft, setEndDateDraft, - setEnableMockDraft, setIsWatchlistSaving, setIsRuntimeConfigSaving }; diff --git a/frontend/src/hooks/useWebSocketConnection.js b/frontend/src/hooks/useWebSocketConnection.js index d20f1b5..660e391 100644 --- a/frontend/src/hooks/useWebSocketConnection.js +++ b/frontend/src/hooks/useWebSocketConnection.js @@ -225,18 +225,9 @@ export function useWebSocketConnection({ setTickers((prevTickers) => buildTickersFromSymbols(state.tickers, prevTickers)); } - const isMockMode = state.is_mock_mode === true; if (state.market_status) { setMarketStatus(state.market_status); - if (isMockMode && state.market_status.current_time) { - try { - setVirtualTime(new Date(state.market_status.current_time)); - } catch (error) { - console.error('Error parsing virtual time from market_status:', error); - } - } else { - setVirtualTime(null); - } + setVirtualTime(null); } if (state.trading_days_total) { @@ -783,18 +774,13 @@ export function useWebSocketConnection({ if (e.beijing_time_str) { const statusEmoji = { market_open: '📊', off_market: '⏸️', non_trading_day: '📅', trade_execution: '💼' }; const emoji = statusEmoji[e.status] || '⏰'; - const isMockMode = e.is_mock_mode === true; - let logMessage = `${emoji} ${isMockMode ? '虚拟时间' : '时间'}: ${e.beijing_time_str} | 状态: ${e.status}`; + let logMessage = `${emoji} 时间: ${e.beijing_time_str} | 状态: ${e.status}`; if (e.hours_to_open !== undefined) logMessage += ` | 距离开盘: ${e.hours_to_open}小时`; if (e.hours_to_trade !== undefined) logMessage += ` | 距离交易: ${e.hours_to_trade}小时`; if (e.trading_date) logMessage += ` | 交易日: ${e.trading_date}`; console.log(logMessage); - if (isMockMode && e.beijing_time) { - try { setVirtualTime(new Date(e.beijing_time)); } catch (error) { console.error('Error parsing virtual time:', error); } - } else { - setVirtualTime(null); - } + setVirtualTime(null); } if (e.market_status) setMarketStatus(e.market_status); }, diff --git a/frontend/src/store/runtimeStore.js b/frontend/src/store/runtimeStore.js index 9a61aac..4a236d5 100644 --- a/frontend/src/store/runtimeStore.js +++ b/frontend/src/store/runtimeStore.js @@ -75,7 +75,6 @@ export const useRuntimeStore = create((set) => ({ pollIntervalDraft: '10', startDateDraft: '', endDateDraft: '', - enableMockDraft: false, setLaunchModeDraft: (launchModeDraft) => set((state) => ({ launchModeDraft: resolveValue(launchModeDraft, state.launchModeDraft) })), setRestoreRunIdDraft: (restoreRunIdDraft) => set((state) => ({ restoreRunIdDraft: resolveValue(restoreRunIdDraft, state.restoreRunIdDraft) })), setRuntimeHistoryRuns: (runtimeHistoryRuns) => set((state) => ({ runtimeHistoryRuns: resolveValue(runtimeHistoryRuns, state.runtimeHistoryRuns) })), @@ -90,7 +89,6 @@ export const useRuntimeStore = create((set) => ({ setPollIntervalDraft: (pollIntervalDraft) => set((state) => ({ pollIntervalDraft: resolveValue(pollIntervalDraft, state.pollIntervalDraft) })), setStartDateDraft: (startDateDraft) => set((state) => ({ startDateDraft: resolveValue(startDateDraft, state.startDateDraft) })), setEndDateDraft: (endDateDraft) => set((state) => ({ endDateDraft: resolveValue(endDateDraft, state.endDateDraft) })), - setEnableMockDraft: (enableMockDraft) => set((state) => ({ enableMockDraft: resolveValue(enableMockDraft, state.enableMockDraft) })), // Runtime config feedback runtimeConfigFeedback: null,