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:
@@ -179,7 +179,10 @@ async def evaluate_indicator(
|
|||||||
|
|
||||||
# Determine required columns
|
# Determine required columns
|
||||||
input_cols = _INPUTS.get(name_lower, ("close",))
|
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
|
# Fetch OHLC
|
||||||
try:
|
try:
|
||||||
@@ -190,7 +193,7 @@ async def evaluate_indicator(
|
|||||||
period_seconds=period_seconds,
|
period_seconds=period_seconds,
|
||||||
start_time=from_time,
|
start_time=from_time,
|
||||||
end_time=to_time,
|
end_time=to_time,
|
||||||
extra_columns=["volume"] if needs_volume else [],
|
extra_columns=extra_columns,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.exception("evaluate_indicator: OHLC fetch failed")
|
log.exception("evaluate_indicator: OHLC fetch failed")
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ def make_synthetic_ohlcv(n: int = 200):
|
|||||||
|
|
||||||
rng = np.random.default_rng(42)
|
rng = np.random.default_rng(42)
|
||||||
|
|
||||||
# Realistic BTC-style price random walk
|
# Realistic BTC-style price random walk (log-normal GBM — always positive)
|
||||||
returns = rng.normal(0, 0.015, n)
|
log_returns = rng.normal(0, 0.015, n)
|
||||||
closes = 40_000.0 * np.cumprod(1.0 + returns)
|
closes = 40_000.0 * np.exp(np.cumsum(log_returns))
|
||||||
|
|
||||||
opens = np.empty(n)
|
opens = np.empty(n)
|
||||||
opens[0] = closes[0]
|
opens[0] = closes[0]
|
||||||
@@ -49,12 +49,43 @@ def make_synthetic_ohlcv(n: int = 200):
|
|||||||
lows = np.minimum(opens, closes) * (1.0 - noise)
|
lows = np.minimum(opens, closes) * (1.0 - noise)
|
||||||
volumes = rng.uniform(1e6, 1e8, n)
|
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({
|
return pd.DataFrame({
|
||||||
"open": opens,
|
"open": opens,
|
||||||
"high": highs,
|
"high": highs,
|
||||||
"low": lows,
|
"low": lows,
|
||||||
"close": closes,
|
"close": closes,
|
||||||
"volume": volumes,
|
"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 = []
|
args = []
|
||||||
for col in input_series:
|
for col in input_series:
|
||||||
if col not in df.columns:
|
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])
|
args.append(df[col])
|
||||||
|
|
||||||
# --- Execute ---
|
# --- Execute ---
|
||||||
|
|||||||
@@ -601,7 +601,7 @@ const chatStyles = {
|
|||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
background: '#141414',
|
background: '#141414',
|
||||||
backgroundMe: '#089981',
|
backgroundMe: '#1c1c1c',
|
||||||
color: '#dbdbdb',
|
color: '#dbdbdb',
|
||||||
colorStarted: '#8a8a8a',
|
colorStarted: '#8a8a8a',
|
||||||
backgroundDeleted: '#0f0f0f',
|
backgroundDeleted: '#0f0f0f',
|
||||||
|
|||||||
Reference in New Issue
Block a user