Spaces:
Running
Running
| # simulation_engine/mock_kucoin.py | |
| # (V1.2 - Realistic Data Feeder with Debug) | |
| import pandas as pd | |
| import numpy as np | |
| import os | |
| class MockKuCoin: | |
| def __init__(self, raw_data_dir): | |
| self.data_store = {} | |
| self.current_time_ms = 0 | |
| self.raw_dir = raw_data_dir | |
| self._warned_empty = set() | |
| print(f"🎭 [MockKuCoin] Initialized. Data source: {self.raw_dir}", flush=True) | |
| async def load_data(self, symbols, timeframes): | |
| print(f"⏳ [MockKuCoin] Loading historical data for {len(symbols)} symbols...", flush=True) | |
| count = 0 | |
| for sym in symbols: | |
| safe_sym = sym.replace('/', '') | |
| self.data_store[sym] = {} | |
| for tf in timeframes: | |
| paths_to_try = [ | |
| os.path.join(self.raw_dir, f"{safe_sym}_{tf}.parquet"), | |
| os.path.join(self.raw_dir, f"{sym.replace('/', '')}_{tf}.parquet") | |
| ] | |
| loaded = False | |
| for path in paths_to_try: | |
| if os.path.exists(path): | |
| try: | |
| df = pd.read_parquet(path) | |
| df = df.sort_values('timestamp').drop_duplicates('timestamp').reset_index(drop=True) | |
| self.data_store[sym][tf] = df | |
| loaded = True | |
| count += 1 | |
| print(f" • Loaded {sym} {tf}: {len(df)} rows", flush=True) | |
| break | |
| except Exception as e: | |
| print(f"⚠️ [MockKuCoin] Error loading {path}: {e}", flush=True) | |
| if not loaded: | |
| print(f"⚠️ [MockKuCoin] Missing file for {sym} {tf}", flush=True) | |
| print(f"✅ [MockKuCoin] Loaded {count} data files into memory.", flush=True) | |
| def set_time(self, timestamp_ms): | |
| self.current_time_ms = timestamp_ms | |
| async def fetch_ohlcv(self, symbol, timeframe, limit=500, since=None): | |
| key = (symbol, timeframe) | |
| if symbol not in self.data_store or timeframe not in self.data_store[symbol]: | |
| if key not in self._warned_empty: | |
| print(f"⚠️ [MockKuCoin] No data store for {symbol} {timeframe}", flush=True) | |
| self._warned_empty.add(key) | |
| return [] | |
| df = self.data_store[symbol][timeframe] | |
| if df.empty: | |
| if key not in self._warned_empty: | |
| print(f"⚠️ [MockKuCoin] Empty DF for {symbol} {timeframe}", flush=True) | |
| self._warned_empty.add(key) | |
| return [] | |
| end_idx = np.searchsorted(df['timestamp'].values, self.current_time_ms, side='right') | |
| if end_idx == 0: | |
| if key not in self._warned_empty: | |
| print(f"⚠️ [MockKuCoin] end_idx=0 for {symbol} {timeframe} @ {self.current_time_ms}", flush=True) | |
| self._warned_empty.add(key) | |
| return [] | |
| start_idx = 0 | |
| if since is not None: | |
| start_idx = np.searchsorted(df['timestamp'].values, since, side='left') | |
| limit_start_idx = max(0, end_idx - limit) | |
| final_start_idx = max(start_idx, limit_start_idx) | |
| if final_start_idx >= end_idx: | |
| if key not in self._warned_empty: | |
| print(f"⚠️ [MockKuCoin] final_start_idx>=end_idx for {symbol} {timeframe}", flush=True) | |
| self._warned_empty.add(key) | |
| return [] | |
| subset = df.iloc[final_start_idx:end_idx] | |
| return subset[['timestamp', 'open', 'high', 'low', 'close', 'volume']].values.tolist() | |
| async def fetch_ticker(self, symbol): | |
| candles = await self.fetch_ohlcv(symbol, '5m', limit=1) | |
| if candles: | |
| last_close = candles[-1][4] | |
| return {'symbol': symbol, 'last': last_close, 'timestamp': candles[-1][0]} | |
| return {'symbol': symbol, 'last': 0.0, 'timestamp': self.current_time_ms} | |
| async def fetch_tickers(self, symbols=None): | |
| tickers = {} | |
| target_syms = symbols if symbols else self.data_store.keys() | |
| for sym in target_syms: | |
| candles = await self.fetch_ohlcv(sym, '1d', limit=1) | |
| current_candle = await self.fetch_ohlcv(sym, '5m', limit=1) | |
| if candles and current_candle: | |
| vol_quote = candles[-1][5] * candles[-1][4] | |
| tickers[sym] = { | |
| 'symbol': sym, | |
| 'last': current_candle[-1][4], | |
| 'quoteVolume': vol_quote, | |
| 'timestamp': self.current_time_ms | |
| } | |
| return tickers | |
| async def load_markets(self): return True | |
| async def close(self): pass |