Download scripts/render_lob_wave_terminal_preview.py from Ypeng12/quant-ai: direct link, hf CLI and curl.
- Browser
- Download file 11.9 kB
-
https://huggingface.co/spaces/Ypeng12/quant-ai/resolve/main/scripts/render_lob_wave_terminal_preview.py
- Command line
-
hf download hf://spaces/Ypeng12/quant-ai/scripts/render_lob_wave_terminal_preview.py
-
curl -L -o render_lob_wave_terminal_preview.py https://huggingface.co/spaces/Ypeng12/quant-ai/resolve/main/scripts/render_lob_wave_terminal_preview.py
11.9 kB
| # render_lob_wave_terminal_preview.py | |
| """ | |
| Generates an institutional-grade, high-resolution dark-mode preview image of the | |
| LOB Microstructure Wave Alpha Terminal for direct display on GitHub README and documentation. | |
| """ | |
| import os | |
| import sys | |
| import numpy as np | |
| import pandas as pd | |
| import matplotlib.pyplot as plt | |
| import matplotlib.patches as patches | |
| script_dir = os.path.dirname(os.path.abspath(__file__)) | |
| project_root = os.path.dirname(script_dir) | |
| backend_dir = os.path.join(project_root, "backend") | |
| if backend_dir not in sys.path: | |
| sys.path.insert(0, backend_dir) | |
| from app.ml.lob_microstructure_ml import MicrostructureWaveAlphaEngine | |
| from app.alpha_engine import InstitutionalAlphaEngine | |
| def render_terminal_preview(): | |
| # Load model and TSLA 5m data | |
| wave_engine = MicrostructureWaveAlphaEngine.load() | |
| alpha_engine = InstitutionalAlphaEngine() | |
| fpath = os.path.join(backend_dir, "data", "datasets", "advanced_dataset_TSLA.parquet") | |
| df = pd.read_parquet(fpath) | |
| for col in ['open', 'high', 'low', 'close', 'volume']: | |
| if col in df.columns and col.capitalize() not in df.columns: | |
| df[col.capitalize()] = df[col] | |
| df_5m = df.resample('5min').agg({ | |
| 'Open': 'first', 'High': 'max', 'Low': 'min', 'Close': 'last', 'Volume': 'sum' | |
| }).dropna() | |
| df_5m['hour'] = df_5m.index.hour | |
| df_5m['min'] = df_5m.index.minute | |
| df_5m = df_5m[(df_5m['hour'] > 9) | ((df_5m['hour'] == 9) & (df_5m['min'] >= 30))] | |
| df_5m = df_5m[(df_5m['hour'] < 16) | ((df_5m['hour'] == 16) & (df_5m['min'] == 0))] | |
| df_5m['day'] = df_5m.index.strftime('%Y-%m-%d') | |
| latest_day = df_5m['day'].iloc[-1] | |
| day_df = df_5m[df_5m['day'] == latest_day].copy() | |
| # Extract features & predict | |
| df_feat = wave_engine.build_microstructure_features(day_df) | |
| X = df_feat[wave_engine.FEATURE_COLS].fillna(0.0) | |
| p_long_all = wave_engine.model.predict_proba(X)[:, 1] | |
| times = day_df.index.strftime('%H:%M').tolist() | |
| closes = day_df['Close'].values | |
| highs = day_df['High'].values | |
| lows = day_df['Low'].values | |
| opens = day_df['Open'].values | |
| ema9 = day_df['Close'].ewm(span=9, adjust=False).mean().values | |
| ema21 = day_df['Close'].ewm(span=21, adjust=False).mean().values | |
| pv = (day_df['Close'] * day_df['Volume']).cumsum() | |
| vwap = (pv / day_df['Volume'].cumsum().replace(0, 1.0)).values | |
| ofi = df_feat['feature_ofi'].values | |
| micro_drift = df_feat['feature_micro_drift_bps'].values if 'feature_micro_drift_bps' in df_feat.columns else df_feat['feature_micro_drift'].values * 100.0 | |
| queue_imb = df_feat['feature_queue_imbalance'].values | |
| sweep_vel = df_feat['feature_sweep_vel'].values | |
| comp_scores = [] | |
| signals = [] | |
| last_sig_idx = -999 | |
| last_sig_dir = None | |
| for i in range(len(day_df)): | |
| row = day_df.iloc[i].to_dict() | |
| prev_row = day_df.iloc[i-1].to_dict() if i > 0 else None | |
| p_l = float(p_long_all[i]) | |
| a_eval = alpha_engine.evaluate_composite_alpha(row, prev_row, ml_p_win_long=p_l) | |
| score = float(a_eval.get('composite_alpha_score', 0.0)) | |
| comp_scores.append(score) | |
| p_s = 1.0 - p_l | |
| cur_dir = None | |
| if score > 15 and p_l >= 0.54: | |
| cur_dir = 'LONG' | |
| elif score < -15 and p_s >= 0.54: | |
| cur_dir = 'SHORT' | |
| if cur_dir is not None: | |
| if cur_dir != last_sig_dir or (i - last_sig_idx >= 5): | |
| signals.append({ | |
| 'index': i, | |
| 'direction': cur_dir, | |
| 'price': closes[i], | |
| 'high': highs[i], | |
| 'low': lows[i], | |
| 'p_win': round(p_l * 100 if cur_dir == 'LONG' else p_s * 100, 1), | |
| 'alpha': round(score, 1) | |
| }) | |
| last_sig_idx = i | |
| last_sig_dir = cur_dir | |
| # Plotting setup with high-end dark theme & macOS CJK font support | |
| plt.style.use('dark_background') | |
| plt.rcParams['font.sans-serif'] = ['PingFang SC', 'Heiti SC', 'STHeiti', 'Arial Unicode MS', 'DejaVu Sans'] | |
| plt.rcParams['axes.unicode_minus'] = False | |
| fig = plt.figure(figsize=(16, 12), dpi=200, facecolor='#080a11') | |
| # Custom layout | |
| gs = fig.add_gridspec(5, 1, height_ratios=[0.5, 2.5, 1.0, 1.0, 1.0], hspace=0.35) | |
| # 0. Title & KPI Header Ax | |
| ax_header = fig.add_subplot(gs[0]) | |
| ax_header.set_facecolor('#0f1422') | |
| ax_header.axis('off') | |
| # Rounded border rectangle for header | |
| rect = patches.FancyBboxPatch((0.01, 0.05), 0.98, 0.90, boxstyle="round,pad=0.02,rounding_size=0.03", | |
| facecolor='#0f1422', edgecolor='#1e293b', transform=ax_header.transAxes) | |
| ax_header.add_patch(rect) | |
| title_text = "LOB 订单流微观结构波浪研判终端 (Microstructure Wave Alpha Terminal)" | |
| sub_text = f"Ticker: TSLA | Date: {latest_day} | Out-of-Sample Purged CV: 72.07% | Teza Capital ($1.6B AUM, Sharpe > 5.0) 15~30m Wave" | |
| ax_header.text(0.03, 0.65, title_text, fontsize=14, fontweight='bold', color='#38bdf8', transform=ax_header.transAxes) | |
| ax_header.text(0.03, 0.25, sub_text, fontsize=9.5, color='#94a3b8', transform=ax_header.transAxes) | |
| open_p, close_p = opens[0], closes[-1] | |
| pnl_pct = (close_p - open_p) / open_p * 100 | |
| pnl_color = '#10b981' if pnl_pct >= 0 else '#f43f5e' | |
| kpi_str = f"Open: {open_p:.2f} USD • Close: {close_p:.2f} USD • Day PnL: {pnl_pct:+.2f}% • Wave Signals: {len(signals)} • Continuous Alpha" | |
| ax_header.text(0.97, 0.45, kpi_str, fontsize=10, fontweight='bold', color=pnl_color, ha='right', transform=ax_header.transAxes) | |
| # 1. K-line and Wave Signals | |
| ax_kline = fig.add_subplot(gs[1]) | |
| ax_kline.set_facecolor('#0f1422') | |
| x_indices = np.arange(len(day_df)) | |
| # Draw Candlesticks | |
| width = 0.55 | |
| for i in range(len(day_df)): | |
| o, c, h, l = opens[i], closes[i], highs[i], lows[i] | |
| col = '#10b981' if c >= o else '#f43f5e' | |
| # Wick | |
| ax_kline.plot([i, i], [l, h], color=col, linewidth=1.2, zorder=2) | |
| # Body | |
| body_bottom = min(o, c) | |
| body_height = max(abs(c - o), 0.08) | |
| rect = patches.Rectangle((i - width/2, body_bottom), width, body_height, facecolor=col, edgecolor=col, zorder=3) | |
| ax_kline.add_patch(rect) | |
| # Technical lines | |
| ax_kline.plot(x_indices, ema9, label='EMA 9', color='#f59e0b', linewidth=1.3, alpha=0.9, zorder=4) | |
| ax_kline.plot(x_indices, ema21, label='EMA 21', color='#38bdf8', linewidth=1.3, alpha=0.9, zorder=4) | |
| ax_kline.plot(x_indices, vwap, label='VWAP', color='#c084fc', linewidth=1.6, linestyle='--', alpha=0.85, zorder=4) | |
| # Draw Debounced Wave Markers | |
| for sig in signals: | |
| idx = sig['index'] | |
| is_long = sig['direction'] == 'LONG' | |
| marker = '^' if is_long else 'v' | |
| sig_color = '#10b981' if is_long else '#f43f5e' | |
| y_pos = lows[idx] - 0.75 if is_long else highs[idx] + 0.75 | |
| va = 'top' if is_long else 'bottom' | |
| ax_kline.scatter(idx, y_pos, marker=marker, s=110, color=sig_color, edgecolor='#ffffff', linewidth=1.2, zorder=5) | |
| lbl = f"{'Long' if is_long else 'Short'} {sig['p_win']:.0f}%" | |
| ax_kline.text(idx, y_pos + (-0.55 if is_long else 0.55), lbl, color='#ffffff', fontsize=8, fontweight='bold', | |
| ha='center', va=va, bbox=dict(boxstyle="round,pad=0.2", facecolor=sig_color, alpha=0.85, edgecolor='none'), zorder=6) | |
| ax_kline.set_ylabel('Price (USD)', fontsize=10, color='#94a3b8') | |
| ax_kline.set_title('TSLA 5-Minute Bars & 15-30m Wave Turning Signals (Wave Long / Short - Debounced)', fontsize=11, fontweight='bold', color='#f8fafc', loc='left') | |
| ax_kline.legend(loc='upper right', framealpha=0.4, fontsize=9) | |
| ax_kline.grid(True, linestyle=':', alpha=0.25, color='#334155') | |
| ax_kline.set_xlim(-1, len(day_df)) | |
| ax_kline.set_ylim(lows.min() - 1.2, highs.max() + 2.2) | |
| ax_kline.set_xticks([]) | |
| # 2. OFI & Microprice Drift | |
| ax_ofi = fig.add_subplot(gs[2]) | |
| ax_ofi.set_facecolor('#0f1422') | |
| bar_colors = ['#10b981' if v >= 0 else '#f43f5e' for v in ofi] | |
| ax_ofi.bar(x_indices, ofi, color=bar_colors, width=0.6, alpha=0.8, label='Order Flow Imbalance (OFI)') | |
| ax_drift = ax_ofi.twinx() | |
| ax_drift.plot(x_indices, micro_drift, color='#a855f7', linewidth=1.6, label='Microprice Drift (bps)') | |
| ax_drift.set_ylabel('Drift (bps)', fontsize=9, color='#a855f7') | |
| ax_drift.grid(False) | |
| ax_ofi.set_ylabel('OFI', fontsize=9, color='#94a3b8') | |
| ax_ofi.set_title('[1] Order Flow Imbalance (OFI) & Microprice Drift (bps)', fontsize=10.5, fontweight='bold', color='#f8fafc', loc='left') | |
| ax_ofi.legend(loc='upper left', framealpha=0.4, fontsize=8.5) | |
| ax_drift.legend(loc='upper right', framealpha=0.4, fontsize=8.5) | |
| ax_ofi.grid(True, linestyle=':', alpha=0.25, color='#334155') | |
| ax_ofi.set_xlim(-1, len(day_df)) | |
| ax_ofi.set_xticks([]) | |
| # 3. Queue Imbalance & Sweep Velocity | |
| ax_q = fig.add_subplot(gs[3]) | |
| ax_q.set_facecolor('#0f1422') | |
| ax_q.plot(x_indices, queue_imb, color='#f59e0b', linewidth=1.5, label='Queue Depth Imbalance') | |
| ax_q.plot(x_indices, sweep_vel, color='#38bdf8', linewidth=1.4, alpha=0.85, label='Sweep Velocity') | |
| ax_q.axhline(0, color='#334155', linestyle='--', linewidth=0.8) | |
| ax_q.set_ylabel('Queue / Sweep', fontsize=9, color='#94a3b8') | |
| ax_q.set_title('[2] LOB Queue Depth Imbalance & Institutional Sweep Velocity', fontsize=10.5, fontweight='bold', color='#f8fafc', loc='left') | |
| ax_q.legend(loc='upper right', framealpha=0.4, fontsize=8.5) | |
| ax_q.grid(True, linestyle=':', alpha=0.25, color='#334155') | |
| ax_q.set_xlim(-1, len(day_df)) | |
| ax_q.set_xticks([]) | |
| # 4. Composite Alpha Score & Wave Win Rate | |
| ax_alpha = fig.add_subplot(gs[4]) | |
| ax_alpha.set_facecolor('#0f1422') | |
| ax_alpha.plot(x_indices, comp_scores, color='#38bdf8', linewidth=1.8, label='Composite Alpha Score') | |
| ax_alpha.fill_between(x_indices, 0, comp_scores, color='#38bdf8', alpha=0.2) | |
| ax_alpha.axhline(0, color='#334155', linestyle='--', linewidth=0.8) | |
| ax_p = ax_alpha.twinx() | |
| ax_p.plot(x_indices, p_long_all * 100.0, color='#10b981', linewidth=1.8, label='Wave Long Win Rate (%)') | |
| ax_p.set_ylabel('Win Rate (%)', fontsize=9, color='#10b981') | |
| ax_p.set_ylim(20, 80) | |
| ax_p.grid(False) | |
| ax_alpha.set_ylabel('Alpha Score', fontsize=9, color='#38bdf8') | |
| ax_alpha.set_ylim(-60, 60) | |
| ax_alpha.set_title('[3] Composite Multi-Factor Alpha Score & 15~30m Wave Win Rate (%)', fontsize=10.5, fontweight='bold', color='#f8fafc', loc='left') | |
| ax_alpha.legend(loc='upper left', framealpha=0.4, fontsize=8.5) | |
| ax_p.legend(loc='upper right', framealpha=0.4, fontsize=8.5) | |
| ax_alpha.grid(True, linestyle=':', alpha=0.25, color='#334155') | |
| ax_alpha.set_xlim(-1, len(day_df)) | |
| # Format X-axis with sparse time labels | |
| step = max(1, len(times) // 10) | |
| xticks_pos = list(range(0, len(times), step)) | |
| xticks_labels = [times[i] for i in xticks_pos] | |
| ax_alpha.set_xticks(xticks_pos) | |
| ax_alpha.set_xticklabels(xticks_labels, rotation=0, fontsize=9, color='#94a3b8') | |
| ax_alpha.set_xlabel('Market Time (EST 09:30 - 16:00)', fontsize=10, color='#94a3b8') | |
| # Output file | |
| out_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets") | |
| os.makedirs(out_dir, exist_ok=True) | |
| out_png = os.path.join(out_dir, "lob_microstructure_wave_terminal.png") | |
| plt.subplots_adjust(top=0.96, bottom=0.06, left=0.06, right=0.94, hspace=0.32) | |
| fig.savefig(out_png, dpi=200, facecolor='#080a11', edgecolor='none') | |
| plt.close(fig) | |
| print(f"✅ High-resolution preview image successfully rendered at: {out_png}") | |
| if __name__ == "__main__": | |
| render_terminal_preview() | |