diff --git a/sandbox/dexorder/tools/evaluate_indicator.py b/sandbox/dexorder/tools/evaluate_indicator.py index c81b330a..1bcc5ba2 100644 --- a/sandbox/dexorder/tools/evaluate_indicator.py +++ b/sandbox/dexorder/tools/evaluate_indicator.py @@ -179,7 +179,10 @@ async def evaluate_indicator( # Determine required columns input_cols = _INPUTS.get(name_lower, ("close",)) - needs_volume = "volume" in input_cols + + # Derive extra_columns from whatever the indicator actually needs + _STANDARD_OHLC = {"open", "high", "low", "close"} + extra_columns = [col for col in input_cols if col not in _STANDARD_OHLC] # Fetch OHLC try: @@ -190,7 +193,7 @@ async def evaluate_indicator( period_seconds=period_seconds, start_time=from_time, end_time=to_time, - extra_columns=["volume"] if needs_volume else [], + extra_columns=extra_columns, ) except Exception as exc: log.exception("evaluate_indicator: OHLC fetch failed") diff --git a/sandbox/dexorder/tools/indicator_harness.py b/sandbox/dexorder/tools/indicator_harness.py index e0447f19..993e0e09 100644 --- a/sandbox/dexorder/tools/indicator_harness.py +++ b/sandbox/dexorder/tools/indicator_harness.py @@ -36,9 +36,9 @@ def make_synthetic_ohlcv(n: int = 200): rng = np.random.default_rng(42) - # Realistic BTC-style price random walk - returns = rng.normal(0, 0.015, n) - closes = 40_000.0 * np.cumprod(1.0 + returns) + # Realistic BTC-style price random walk (log-normal GBM — always positive) + log_returns = rng.normal(0, 0.015, n) + closes = 40_000.0 * np.exp(np.cumsum(log_returns)) opens = np.empty(n) opens[0] = closes[0] @@ -49,12 +49,43 @@ def make_synthetic_ohlcv(n: int = 200): lows = np.minimum(opens, closes) * (1.0 - noise) volumes = rng.uniform(1e6, 1e8, n) + # Taker flow: randomly split volume into buy/sell side + buy_frac = rng.uniform(0.2, 0.8, n) + buy_vols = volumes * buy_frac + sell_vols = volumes - buy_vols + + # Synthetic bar timestamps (1-minute bars starting 2024-01-01 UTC, in nanoseconds) + bar_ns = 60 * 1_000_000_000 + epoch_start = 1_704_067_200_000_000_000 # 2024-01-01 00:00:00 UTC in ns + bar_starts = epoch_start + np.arange(n) * bar_ns + # Each extreme occurs at a random point within its bar + open_times = bar_starts + rng.integers(0, bar_ns, n) + high_times = bar_starts + rng.integers(0, bar_ns, n) + low_times = bar_starts + rng.integers(0, bar_ns, n) + close_times = bar_starts + rng.integers(0, bar_ns, n) + + # Futures open interest: separate slow random walk (log-normal — always positive) + oi_log_returns = rng.normal(0, 0.003, n) + open_interest = 5e8 * np.exp(np.cumsum(oi_log_returns)) + + num_trades = rng.integers(500, 5000, n).astype(float) + quote_volume = closes * volumes + return pd.DataFrame({ - "open": opens, - "high": highs, - "low": lows, - "close": closes, - "volume": volumes, + "open": opens, + "high": highs, + "low": lows, + "close": closes, + "volume": volumes, + "buy_vol": buy_vols, + "sell_vol": sell_vols, + "open_time": open_times.astype(float), + "high_time": high_times.astype(float), + "low_time": low_times.astype(float), + "close_time": close_times.astype(float), + "open_interest": open_interest, + "num_trades": num_trades, + "quote_volume": quote_volume, }) @@ -175,7 +206,13 @@ def run(impl_path: Path, metadata_path: Path) -> dict: args = [] for col in input_series: if col not in df.columns: - return {"success": False, "error": f"input_series '{col}' not in synthetic df columns {list(df.columns)}"} + return { + "success": False, + "error": ( + f"input_series '{col}' is not a valid OHLC column. " + f"Available columns: {list(df.columns)}" + ), + } args.append(df[col]) # --- Execute --- diff --git a/web/src/components/ChatPanel.vue b/web/src/components/ChatPanel.vue index d3db57a2..d70bcf48 100644 --- a/web/src/components/ChatPanel.vue +++ b/web/src/components/ChatPanel.vue @@ -601,7 +601,7 @@ const chatStyles = { }, message: { background: '#141414', - backgroundMe: '#089981', + backgroundMe: '#1c1c1c', color: '#dbdbdb', colorStarted: '#8a8a8a', backgroundDeleted: '#0f0f0f',