Add synthetic taker flow, timestamps, open interest, and metadata to indicator harness; improve error messages and theme tweaks

- Generate buy/sell volume split with random fractions
- Add nanosecond timestamps for OHLC extremes within 1-minute bars
- Include open interest as separate random walk
- Add num_trades and quote_volume derived fields
- Derive extra_columns dynamically from indicator input requirements instead of hardcoded volume check
- Improve input_series error message clarity
- Adjust ChatPanel background color for user messages
This commit is contained in:
2026-04-28 18:47:41 -04:00
parent 6482cfa347
commit 2fded95b31
3 changed files with 52 additions and 12 deletions

View File

@@ -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")

View File

@@ -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 ---

View File

@@ -601,7 +601,7 @@ const chatStyles = {
},
message: {
background: '#141414',
backgroundMe: '#089981',
backgroundMe: '#1c1c1c',
color: '#dbdbdb',
colorStarted: '#8a8a8a',
backgroundDeleted: '#0f0f0f',