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 <noreply@anthropic.com>
This commit is contained in:
@@ -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"))
|
||||
|
||||
@@ -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,16 +1080,14 @@ 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]")
|
||||
@@ -1168,9 +1160,6 @@ 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)",
|
||||
)
|
||||
@@ -1188,8 +1177,7 @@ def live(
|
||||
project_root = get_project_root()
|
||||
os.chdir(project_root)
|
||||
|
||||
# Data update (if not mock mode)
|
||||
if not mock:
|
||||
# Data update
|
||||
run_data_updater(project_root)
|
||||
auto_update_market_store(
|
||||
config_name,
|
||||
@@ -1200,10 +1188,6 @@ def live(
|
||||
end_date=nyse_now.date().isoformat(),
|
||||
force=False,
|
||||
)
|
||||
else:
|
||||
console.print(
|
||||
"\n[dim]Mock mode enabled - skipping data update[/dim]\n",
|
||||
)
|
||||
|
||||
# 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")
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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")
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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,12 +240,6 @@ 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)
|
||||
|
||||
if self.backtest_mode and self._current_date:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]"
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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({
|
||||
<Header />
|
||||
|
||||
<div className="header-right" style={{ display: 'flex', alignItems: 'center', gap: 24, marginLeft: 'auto', flexWrap: 'wrap', minWidth: 0 }}>
|
||||
{/* Mock Mode Indicator */}
|
||||
{virtualTime && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '4px 10px', borderRadius: 4, background: '#FF9800', border: '1px solid #FFB74D' }}>
|
||||
<span style={{ fontSize: '14px' }}></span>
|
||||
<span style={{ fontSize: '11px', fontWeight: 600, color: '#FFFFFF', fontFamily: '"Courier New", monospace', letterSpacing: '0.5px' }}>
|
||||
模拟模式
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Clock Display (only in Mock mode) */}
|
||||
{virtualTime && (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 2, padding: '4px 12px', borderRadius: 4, background: '#1A237E', border: '1px solid #3F51B5' }}>
|
||||
<span style={{ fontSize: '11px', color: '#999', fontFamily: '"Courier New", monospace', textTransform: 'uppercase', letterSpacing: '0.5px' }}>虚拟时间</span>
|
||||
<span style={{ fontSize: '14px', fontWeight: 700, color: '#FFFFFF', fontFamily: '"Courier New", monospace', letterSpacing: '1px' }}>
|
||||
{now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false })}
|
||||
</span>
|
||||
<span style={{ fontSize: '10px', color: '#999', fontFamily: '"Courier New", monospace' }}>
|
||||
{now.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Fast Forward Button (only in Mock mode) */}
|
||||
<button
|
||||
onClick={() => {
|
||||
if (agentRequests?.clientRef?.current) {
|
||||
agentRequests.clientRef.current.send({ type: 'fast_forward_time', minutes: 30 });
|
||||
}
|
||||
}}
|
||||
style={{ padding: '6px 12px', borderRadius: 4, background: '#3F51B5', border: '1px solid #5C6BC0', color: '#FFFFFF', fontSize: '12px', fontFamily: '"Courier New", monospace', fontWeight: 600, cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 4, textTransform: 'uppercase', letterSpacing: '0.5px' }}
|
||||
title="快进30分钟 (Mock模式)"
|
||||
>
|
||||
⏩ +30min
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Unified Status Indicator */}
|
||||
<div className="header-status-inline">
|
||||
<span className={`status-dot ${isConnected ? (isUpdating ? 'updating' : 'live') : 'offline'}`} />
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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({
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 2 }}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={enableMock}
|
||||
onChange={(e) => onEnableMockChange(e.target.checked)}
|
||||
style={{
|
||||
width: 16,
|
||||
height: 16,
|
||||
accentColor: '#0D47A1',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
/>
|
||||
<span style={{ fontSize: '11px', color: '#111111', fontWeight: 700 }}>启用模拟数据 (Mock)</span>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -225,19 +225,10 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
if (state.trading_days_total) {
|
||||
setProgress({
|
||||
@@ -783,19 +774,14 @@ 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);
|
||||
}
|
||||
}
|
||||
if (e.market_status) setMarketStatus(e.market_status);
|
||||
},
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user