lhllamlam commited on
Commit
6b66ac0
·
verified ·
1 Parent(s): 99c4ea5

feat: wire 4 institutional flow factors (L2, options, ticks, intraday); stub data committed

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .pytest_cache/.gitignore +2 -0
  2. .pytest_cache/CACHEDIR.TAG +4 -0
  3. .pytest_cache/README.md +8 -0
  4. .pytest_cache/v/cache/lastfailed +3 -0
  5. .pytest_cache/v/cache/nodeids +86 -0
  6. README.md +51 -15
  7. app.py +41 -2
  8. data/stubs/_build_stubs.py +175 -0
  9. data/stubs/intraday/AAPL.parquet +3 -0
  10. data/stubs/intraday/ADBE.parquet +3 -0
  11. data/stubs/intraday/AMC.parquet +3 -0
  12. data/stubs/intraday/AMD.parquet +3 -0
  13. data/stubs/intraday/AMZN.parquet +3 -0
  14. data/stubs/intraday/AVGO.parquet +3 -0
  15. data/stubs/intraday/BAC.parquet +3 -0
  16. data/stubs/intraday/CRM.parquet +3 -0
  17. data/stubs/intraday/CVX.parquet +3 -0
  18. data/stubs/intraday/GME.parquet +3 -0
  19. data/stubs/intraday/GOOGL.parquet +3 -0
  20. data/stubs/intraday/GS.parquet +3 -0
  21. data/stubs/intraday/INTC.parquet +3 -0
  22. data/stubs/intraday/JNJ.parquet +3 -0
  23. data/stubs/intraday/JPM.parquet +3 -0
  24. data/stubs/intraday/MA.parquet +3 -0
  25. data/stubs/intraday/META.parquet +3 -0
  26. data/stubs/intraday/MS.parquet +3 -0
  27. data/stubs/intraday/MSFT.parquet +3 -0
  28. data/stubs/intraday/NFLX.parquet +3 -0
  29. data/stubs/intraday/NVDA.parquet +3 -0
  30. data/stubs/intraday/ORCL.parquet +3 -0
  31. data/stubs/intraday/PFE.parquet +3 -0
  32. data/stubs/intraday/PLTR.parquet +3 -0
  33. data/stubs/intraday/QCOM.parquet +3 -0
  34. data/stubs/intraday/RIOT.parquet +3 -0
  35. data/stubs/intraday/TSLA.parquet +3 -0
  36. data/stubs/intraday/UNH.parquet +3 -0
  37. data/stubs/intraday/V.parquet +3 -0
  38. data/stubs/intraday/XOM.parquet +3 -0
  39. data/stubs/l2/AAPL.json +128 -0
  40. data/stubs/l2/ADBE.json +128 -0
  41. data/stubs/l2/AMC.json +128 -0
  42. data/stubs/l2/AMD.json +128 -0
  43. data/stubs/l2/AMZN.json +128 -0
  44. data/stubs/l2/AVGO.json +128 -0
  45. data/stubs/l2/BAC.json +128 -0
  46. data/stubs/l2/CRM.json +128 -0
  47. data/stubs/l2/CVX.json +128 -0
  48. data/stubs/l2/GME.json +128 -0
  49. data/stubs/l2/GOOGL.json +128 -0
  50. data/stubs/l2/GS.json +128 -0
.pytest_cache/.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ # Created by pytest automatically.
2
+ *
.pytest_cache/CACHEDIR.TAG ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ Signature: 8a477f597d28d172789f06886806bc55
2
+ # This file is a cache directory tag created by pytest.
3
+ # For information about cache directory tags, see:
4
+ # https://bford.info/cachedir/spec.html
.pytest_cache/README.md ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # pytest cache directory #
2
+
3
+ This directory contains data from the pytest's cache plugin,
4
+ which provides the `--lf` and `--ff` options, as well as the `cache` fixture.
5
+
6
+ **Do not** commit this to version control.
7
+
8
+ See [the docs](https://docs.pytest.org/en/stable/how-to/cache.html) for more information.
.pytest_cache/v/cache/lastfailed ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "tests/test_flow_algo.py::test_uptrend_has_positive_cmf_and_obv": true
3
+ }
.pytest_cache/v/cache/nodeids ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ "tests/test_flow_algo.py::test_downtrend_has_negative_cmf_and_obv",
3
+ "tests/test_flow_algo.py::test_empty_frame_returns_none",
4
+ "tests/test_flow_algo.py::test_factor_dict_round_trip",
5
+ "tests/test_flow_algo.py::test_factors_finite_and_bounded",
6
+ "tests/test_flow_algo.py::test_too_short_returns_none",
7
+ "tests/test_flow_algo.py::test_uptrend_has_positive_cmf_and_obv",
8
+ "tests/test_flow_algo.py::test_vwap_dev_positive_when_close_above_average",
9
+ "tests/test_history.py::test_latest_snapshot_returns_newest",
10
+ "tests/test_history.py::test_save_and_list_snapshots",
11
+ "tests/test_history.py::test_save_empty_df_returns_none",
12
+ "tests/test_history.py::test_snapshot_summary_lists_saved_runs",
13
+ "tests/test_history.py::test_with_delta_computes_difference",
14
+ "tests/test_history.py::test_with_delta_no_previous",
15
+ "tests/test_intraday_factor.py::test_aggression_persistence_negative_when_recent_selling",
16
+ "tests/test_intraday_factor.py::test_aggression_persistence_positive_when_recent_buying",
17
+ "tests/test_intraday_factor.py::test_aggression_persistence_short_history_uses_all",
18
+ "tests/test_intraday_factor.py::test_batch",
19
+ "tests/test_intraday_factor.py::test_clipped_to_range",
20
+ "tests/test_intraday_factor.py::test_empty_returns_zeros",
21
+ "tests/test_intraday_factor.py::test_stub_synthesises",
22
+ "tests/test_intraday_factor.py::test_vwap_dev_positive_when_above_vwap",
23
+ "tests/test_intraday_factor.py::test_vwap_dev_zero_when_price_equals_vwap",
24
+ "tests/test_l2_factor.py::test_ask_heavy_book_is_negative",
25
+ "tests/test_l2_factor.py::test_balanced_book_is_near_zero",
26
+ "tests/test_l2_factor.py::test_batch_returns_all_tickers",
27
+ "tests/test_l2_factor.py::test_bid_heavy_book_is_positive",
28
+ "tests/test_l2_factor.py::test_book_lying_detection",
29
+ "tests/test_l2_factor.py::test_book_lying_discounts_factor",
30
+ "tests/test_l2_factor.py::test_clipped_to_range",
31
+ "tests/test_l2_factor.py::test_empty_book_returns_zero",
32
+ "tests/test_l2_factor.py::test_spoofed_orders_ignored",
33
+ "tests/test_l2_factor.py::test_stub_data_source_synthesises",
34
+ "tests/test_options_factor.py::test_batch",
35
+ "tests/test_options_factor.py::test_call_otm_spike_is_positive",
36
+ "tests/test_options_factor.py::test_call_otm_weighted_heaviest",
37
+ "tests/test_options_factor.py::test_empty_returns_zero",
38
+ "tests/test_options_factor.py::test_put_otm_spike_is_negative",
39
+ "tests/test_options_factor.py::test_steady_chain_is_near_zero",
40
+ "tests/test_options_factor.py::test_stub_data_source_synthesises",
41
+ "tests/test_options_factor.py::test_zscore_constant_returns_zero",
42
+ "tests/test_options_factor.py::test_zscore_uses_today_vs_history",
43
+ "tests/test_performance.py::test_auto_improve_end_to_end_with_signal",
44
+ "tests/test_performance.py::test_auto_improve_handles_no_data",
45
+ "tests/test_performance.py::test_collect_eval_tables_with_explicit_cache",
46
+ "tests/test_performance.py::test_evaluate_returns_metrics_dict",
47
+ "tests/test_performance.py::test_ic_signal_factor_is_predictive",
48
+ "tests/test_performance.py::test_load_learned_weights_missing",
49
+ "tests/test_performance.py::test_load_learned_weights_round_trip",
50
+ "tests/test_performance.py::test_optimize_finds_signal_factor",
51
+ "tests/test_performance.py::test_optimize_without_enough_history_returns_baseline",
52
+ "tests/test_performance.py::test_performance_log_grows",
53
+ "tests/test_scorer.py::test_empty_factors_returns_empty_df",
54
+ "tests/test_scorer.py::test_ratings_assignment",
55
+ "tests/test_scorer.py::test_robust_zscore_degenerate_series",
56
+ "tests/test_scorer.py::test_robust_zscore_on_outliers",
57
+ "tests/test_scorer.py::test_score_dataframe_shape",
58
+ "tests/test_scorer.py::test_score_range_and_ordering",
59
+ "tests/test_scorer.py::test_top_n_buy_and_sell",
60
+ "tests/test_scorer.py::test_uptrend_scores_higher_than_downtrend",
61
+ "tests/test_scorer.py::test_weights_change_ordering",
62
+ "tests/test_tick_factor.py::test_batch",
63
+ "tests/test_tick_factor.py::test_block_aggression_negative_when_block_sells",
64
+ "tests/test_tick_factor.py::test_block_aggression_positive_when_block_buys",
65
+ "tests/test_tick_factor.py::test_block_share_calculation",
66
+ "tests/test_tick_factor.py::test_bucket_boundaries",
67
+ "tests/test_tick_factor.py::test_buy_ratio_in_unit_interval",
68
+ "tests/test_tick_factor.py::test_empty_ticks",
69
+ "tests/test_tick_factor.py::test_no_bid_ask_falls_back_to_rolling_mid",
70
+ "tests/test_tick_factor.py::test_sign_at_ask_is_buy",
71
+ "tests/test_tick_factor.py::test_sign_at_bid_is_sell",
72
+ "tests/test_tick_factor.py::test_sign_at_mid_carries_forward",
73
+ "tests/test_tick_factor.py::test_stub_synthesises",
74
+ "tests/test_universe.py::test_cached_sectors_for_returns_only_known",
75
+ "tests/test_universe.py::test_fresh_filter_excludes_stale",
76
+ "tests/test_universe.py::test_save_and_load_cache",
77
+ "tests/test_watchlist.py::test_load_malformed_returns_empty",
78
+ "tests/test_watchlist.py::test_load_missing_returns_empty",
79
+ "tests/test_watchlist.py::test_parse_caps_at_max",
80
+ "tests/test_watchlist.py::test_parse_empty_or_none",
81
+ "tests/test_watchlist.py::test_parse_lowercase_and_dedup",
82
+ "tests/test_watchlist.py::test_parse_mixed_delimiters",
83
+ "tests/test_watchlist.py::test_parse_rejects_invalid_symbols",
84
+ "tests/test_watchlist.py::test_parse_simple",
85
+ "tests/test_watchlist.py::test_save_and_load_round_trip"
86
+ ]
README.md CHANGED
@@ -21,20 +21,30 @@ institutional-style buying or selling pressure. Designed to run on
21
 
22
  ## What it does
23
 
24
- For each of ~5000+ US-listed common stocks, the app computes 5 factors and
25
- combines them into a single **-100 to +100 institutional-flow score**:
26
-
27
- | Factor | What it measures |
28
- |-----------------|---------------------------------------------------------------|
29
- | **CMF(20)** | Chaikin Money Flow over 20 days |
30
- | **OBV slope** | 20-day linear regression of On-Balance Volume, normalized |
31
- | **Big-bar ratio** | Unusual-volume + wide-range bars today vs 20-day baseline |
32
- | **VWAP dev** | Close vs 20-day volume-weighted average price |
33
- | **RVOL signed** | Relative volume, sign-flipped by CMF direction |
 
 
 
 
 
34
 
35
  Factors are z-scored cross-sectionally (median + MAD robust normalization)
36
  so the most-extreme name in the universe gets ±100.
37
 
 
 
 
 
 
38
  **Rating thresholds** (tweakable in the UI):
39
  - `≥ +50` → **Strong Buy**
40
  - `+20 … +49` → **Buy**
@@ -42,9 +52,11 @@ so the most-extreme name in the universe gets ±100.
42
  - `-49 … -20` → **Sell**
43
  - `≤ -50` → **Strong Sell**
44
 
45
- The default weights are `0.30 / 0.25 / 0.20 / 0.15 / 0.10` for
46
- `CMF / OBV / big-bar / VWAP / RVOL`. All sliders are exposed in the
47
- sidebar accordion.
 
 
48
 
49
  ## Project layout
50
 
@@ -55,7 +67,12 @@ us-flow-scanner/
55
  │ ├── __init__.py
56
  │ ├── universe.py # Load & filter ~5000 US tickers
57
  │ ├── data_fetcher.py # yfinance + Polygon fallback + cache
58
- │ ├── flow_algo.py # 5-factor computation
 
 
 
 
 
59
  │ ├── scorer.py # Cross-sectional z-score + composite
60
  │ ├── persistence.py # Optional HF Dataset round-trip
61
  │ ├── history.py # Per-scan snapshots + delta vs prior
@@ -63,7 +80,8 @@ us-flow-scanner/
63
  │ ├── performance.py # IC evaluation + self-tuning weights
64
  │ └── paths.py # Centralised, env-var-overridable paths
65
  ├── data/
66
- ── us_tickers.csv # ~5260 US common stocks (committed)
 
67
  ├── tests/ # pytest suite (isolated disk paths)
68
  ├── requirements.txt
69
  ├── requirements-dev.txt
@@ -80,6 +98,24 @@ python app.py
80
 
81
  Then open http://localhost:7860.
82
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  ## Tests
84
 
85
  ```bash
 
21
 
22
  ## What it does
23
 
24
+ For each of ~5000+ US-listed common stocks, the app computes **9 factors**
25
+ (5 daily flow proxies + 4 "real" institutional-flow signals) and combines
26
+ them into a single **-100 to +100 institutional-flow score**:
27
+
28
+ | Factor | What it measures |
29
+ |----------------------|---------------------------------------------------------------|
30
+ | **CMF(20)** | Chaikin Money Flow over 20 days |
31
+ | **OBV slope** | 20-day linear regression of On-Balance Volume, normalized |
32
+ | **Big-bar ratio** | Unusual-volume + wide-range bars today vs 20-day baseline |
33
+ | **VWAP dev** | Close vs 20-day volume-weighted average price |
34
+ | **RVOL signed** | Relative volume, sign-flipped by CMF direction |
35
+ | **L2 imbalance** | Top-of-book depth + large resting orders (with spoofing discount) |
36
+ | **Unusual options** | Vol/OI z-score across 20 days, weighted by moneyness |
37
+ | **Block aggression** | Net buy/sell bias in ≥10k-share trades (Lee-Ready tick rule) |
38
+ | **Buy persistence** | Buy-ratio average over last 1 hour of 5-min bars |
39
 
40
  Factors are z-scored cross-sectionally (median + MAD robust normalization)
41
  so the most-extreme name in the universe gets ±100.
42
 
43
+ The last 4 factors come from a pluggable `FactorDataSource` (see
44
+ [Data sources](#data-sources) below). By default they are computed from
45
+ synthetic stub data committed to the repo, so the app works on
46
+ Hugging Face Spaces with **no live market feed**.
47
+
48
  **Rating thresholds** (tweakable in the UI):
49
  - `≥ +50` → **Strong Buy**
50
  - `+20 … +49` → **Buy**
 
52
  - `-49 … -20` → **Sell**
53
  - `≤ -50` → **Strong Sell**
54
 
55
+ The default weights are
56
+ `0.20 / 0.15 / 0.10 / 0.10 / 0.05` for the daily flow factors and
57
+ `0.15 / 0.10 / 0.10 / 0.05` for the institutional factors.
58
+ The 5 daily sliders are exposed in the sidebar accordion; the 4
59
+ institutional factors always run at their default weights.
60
 
61
  ## Project layout
62
 
 
67
  │ ├── __init__.py
68
  │ ├── universe.py # Load & filter ~5000 US tickers
69
  │ ├── data_fetcher.py # yfinance + Polygon fallback + cache
70
+ │ ├── flow_algo.py # 5 daily-flow factors
71
+ │ ├── l2_factor.py # Level-2 large-resting-order factor
72
+ │ ├── options_factor.py # Unusual options activity
73
+ │ ├── tick_factor.py # Lee-Ready tick + size buckets
74
+ │ ├── intraday_factor.py # Intraday VWAP + buy persistence
75
+ │ ├── factor_sources.py # StubDataSource (default) + FutuDataSource (live)
76
  │ ├── scorer.py # Cross-sectional z-score + composite
77
  │ ├── persistence.py # Optional HF Dataset round-trip
78
  │ ├── history.py # Per-scan snapshots + delta vs prior
 
80
  │ ├── performance.py # IC evaluation + self-tuning weights
81
  │ └── paths.py # Centralised, env-var-overridable paths
82
  ├── data/
83
+ ── us_tickers.csv # ~5260 US common stocks (committed)
84
+ │ └── stubs/ # 30-ticker synthetic L2/options/ticks/intraday
85
  ├── tests/ # pytest suite (isolated disk paths)
86
  ├── requirements.txt
87
  ├── requirements-dev.txt
 
98
 
99
  Then open http://localhost:7860.
100
 
101
+ ## Data sources
102
+
103
+ The four institutional-flow factors (`l2_imbalance`, `unusual_options`,
104
+ `block_aggression`, `buy_persistence`) read from a pluggable
105
+ `FactorDataSource` (`scanner/factor_sources.py`).
106
+
107
+ | Source | When to use | Config env var |
108
+ |---------------------|----------------------------------------------------------|----------------------------|
109
+ | `StubDataSource` | **Default.** Reads `data/stubs/` synthetic data | `FSCANNER_DATA_SOURCE` unset or `=stub` |
110
+ | `FutuDataSource` | Live Level-2 / options / tick data via local Futu OpenD | `FSCANNER_DATA_SOURCE=futu` + `FUTU_OPEND_HOST` + `FUTU_OPEND_PORT` |
111
+
112
+ The Space uses `StubDataSource` so it never depends on a live feed.
113
+ To regenerate the stub data:
114
+
115
+ ```bash
116
+ python data/stubs/_build_stubs.py
117
+ ```
118
+
119
  ## Tests
120
 
121
  ```bash
app.py CHANGED
@@ -49,10 +49,15 @@ except Exception as _patch_err: # never let this crash startup
49
  print(f"[gradio-client patch skipped: {_patch_err}]", file=sys.stderr)
50
 
51
  from scanner.data_fetcher import fetch_ohlcv
 
52
  from scanner.flow_algo import compute_factors
53
  from scanner.history import (
54
  latest_snapshot, save_snapshot, snapshot_summary, with_delta,
55
  )
 
 
 
 
56
  from scanner import paths
57
  from scanner.performance import (
58
  HORIZON_DAYS, auto_improve, load_learned_meta,
@@ -110,7 +115,8 @@ def _format_status() -> str:
110
  def _result_columns() -> list[str]:
111
  return ["ticker", "name", "rating", "score", "score_delta", "last_close",
112
  "adv_dollar", "cmf", "obv_slope", "big_bar_ratio", "vwap_dev",
113
- "rvol_signed"]
 
114
 
115
 
116
  def _df_for_display(df: Optional[pd.DataFrame]) -> Optional[pd.DataFrame]:
@@ -468,8 +474,41 @@ def _do_scan(
468
  if not factors:
469
  return None, valid_frames, scanned_universe, "No tickers had enough data."
470
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
471
  progress(0.94, desc="Scoring..." )
472
- df = score_factors(factors, weights=weights)
473
 
474
  # Attach names
475
  name_map = dict(zip(universe["ticker"], universe["name"]))
 
49
  print(f"[gradio-client patch skipped: {_patch_err}]", file=sys.stderr)
50
 
51
  from scanner.data_fetcher import fetch_ohlcv
52
+ from scanner.factor_sources import get_data_source
53
  from scanner.flow_algo import compute_factors
54
  from scanner.history import (
55
  latest_snapshot, save_snapshot, snapshot_summary, with_delta,
56
  )
57
+ from scanner.intraday_factor import compute_intraday_factors_batch
58
+ from scanner.l2_factor import compute_l2_factors
59
+ from scanner.options_factor import compute_options_factors
60
+ from scanner.tick_factor import compute_tick_factors_batch
61
  from scanner import paths
62
  from scanner.performance import (
63
  HORIZON_DAYS, auto_improve, load_learned_meta,
 
115
  def _result_columns() -> list[str]:
116
  return ["ticker", "name", "rating", "score", "score_delta", "last_close",
117
  "adv_dollar", "cmf", "obv_slope", "big_bar_ratio", "vwap_dev",
118
+ "rvol_signed", "l2_imbalance", "unusual_options",
119
+ "block_aggression", "buy_persistence"]
120
 
121
 
122
  def _df_for_display(df: Optional[pd.DataFrame]) -> Optional[pd.DataFrame]:
 
474
  if not factors:
475
  return None, valid_frames, scanned_universe, "No tickers had enough data."
476
 
477
+ # ----------------------------------------------------------------
478
+ # Institutional-flow factors (L2, options, ticks, intraday).
479
+ # Computed from the configured FactorDataSource (StubDataSource by
480
+ # default; set FSCANNER_DATA_SOURCE=futu for live Futu OpenD). Any
481
+ # data-source exception is swallowed - a missing real feed just
482
+ # leaves the new factors at neutral (0.0) and the scan still
483
+ # produces a valid ranking from the original 5 flow factors.
484
+ # ----------------------------------------------------------------
485
+ progress(0.92, desc="Computing institutional factors (L2/options/ticks/intraday)...")
486
+ extra_factors = {"l2_imbalance": {}, "unusual_options": {},
487
+ "block_aggression": {}, "buy_persistence": {}}
488
+ inst_tickers = [fs.ticker for fs in factors]
489
+ try:
490
+ source = get_data_source()
491
+ l2 = compute_l2_factors(inst_tickers, source=source)
492
+ extra_factors["l2_imbalance"] = l2
493
+ opt = compute_options_factors(inst_tickers, source=source)
494
+ extra_factors["unusual_options"] = opt
495
+ ticks_df = compute_tick_factors_batch(inst_tickers, source=source)
496
+ for t in inst_tickers:
497
+ if t in ticks_df.index:
498
+ extra_factors["block_aggression"][t] = float(
499
+ ticks_df.loc[t, "block_aggression"]
500
+ )
501
+ intraday_df = compute_intraday_factors_batch(inst_tickers, source=source)
502
+ for t in inst_tickers:
503
+ if t in intraday_df.index:
504
+ extra_factors["buy_persistence"][t] = float(
505
+ intraday_df.loc[t, "aggression_persistence"]
506
+ )
507
+ except Exception as e:
508
+ print(f"[institutional factors skipped: {e}]", file=sys.stderr)
509
+
510
  progress(0.94, desc="Scoring..." )
511
+ df = score_factors(factors, weights=weights, extra_factors=extra_factors)
512
 
513
  # Attach names
514
  name_map = dict(zip(universe["ticker"], universe["name"]))
data/stubs/_build_stubs.py ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Build the bundled synthetic stub data for the four institutional factors.
2
+
3
+ Run locally to regenerate::
4
+
5
+ python data/stubs/_build_stubs.py
6
+
7
+ This produces realistic-but-synthetic Level-2, options, tick, and
8
+ intraday data for ~30 representative tickers. The Space uses this
9
+ data so the math can be demonstrated without any live market feed.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import math
16
+ import os
17
+ import random
18
+ import sys
19
+ from datetime import datetime, timedelta
20
+
21
+ import numpy as np
22
+ import pandas as pd
23
+
24
+ HERE = os.path.dirname(os.path.abspath(__file__))
25
+ PROJ = os.path.dirname(os.path.dirname(HERE))
26
+ sys.path.insert(0, PROJ)
27
+
28
+
29
+ SAMPLE_TICKERS = [
30
+ # Mega caps (high L2 liquidity, deep options)
31
+ "AAPL", "MSFT", "NVDA", "AMZN", "GOOGL", "META", "TSLA", "AVGO",
32
+ # Mid caps (still liquid, smaller options chains)
33
+ "AMD", "NFLX", "CRM", "ORCL", "ADBE", "INTC", "QCOM",
34
+ # Sector leaders
35
+ "JPM", "BAC", "GS", "MS", "V", "MA", "JNJ", "PFE", "UNH", "XOM", "CVX",
36
+ # Recent IPOs / volatile names (unusual options activity test)
37
+ "PLTR", "GME", "AMC", "RIOT",
38
+ ]
39
+
40
+
41
+ def _rng(ticker: str, salt: str = "") -> random.Random:
42
+ return random.Random(f"{ticker}-{salt}")
43
+
44
+
45
+ def build_l2(out_dir: str) -> None:
46
+ os.makedirs(out_dir, exist_ok=True)
47
+ for t in SAMPLE_TICKERS:
48
+ rng = _rng(t, "l2")
49
+ # Each ticker gets a "lean" direction: positive = bullish, negative = bearish
50
+ # Distribute so ~40% bullish, ~40% bearish, ~20% neutral.
51
+ lean = rng.choices([-1, -0.5, 0, 0.5, 1], weights=[0.2, 0.2, 0.2, 0.2, 0.2])[0]
52
+ mid = rng.uniform(20, 500)
53
+ spread = mid * 0.0005
54
+ bids, asks = [], []
55
+ for i in range(10):
56
+ bp = mid - spread / 2 - i * spread * 0.5
57
+ ap = mid + spread / 2 + i * spread * 0.5
58
+ # Lean shifts size distribution: bullish = bigger bids; bearish = bigger asks
59
+ bs = int(rng.lognormvariate(6 + lean * 0.5, 1.0))
60
+ as_ = int(rng.lognormvariate(6 - lean * 0.5, 1.0))
61
+ age = round(rng.uniform(1.5, 30.0), 1) # all stable
62
+ bids.append([round(bp, 2), bs, "NSDQ", age])
63
+ asks.append([round(ap, 2), as_, "NSDQ", round(rng.uniform(1.5, 30.0), 1)])
64
+ out = {
65
+ "ticker": t,
66
+ "ts": datetime.utcnow().isoformat() + "Z",
67
+ "bids": bids,
68
+ "asks": asks,
69
+ }
70
+ with open(os.path.join(out_dir, f"{t}.json"), "w", encoding="utf-8") as fh:
71
+ json.dump(out, fh, indent=2)
72
+
73
+
74
+ def build_options(out_dir: str) -> None:
75
+ os.makedirs(out_dir, exist_ok=True)
76
+ today = pd.Timestamp.utcnow().tz_localize(None).normalize()
77
+ for t in SAMPLE_TICKERS:
78
+ rng = _rng(t, "opt")
79
+ rows = []
80
+ # Some tickers have unusual activity on a "spike day" in the last week
81
+ spike_day_offset = rng.randint(1, 5)
82
+ spike_side = rng.choice(["call", "put"])
83
+ for d in range(20):
84
+ date = today - pd.Timedelta(days=d)
85
+ for kind in ("call", "put"):
86
+ for bucket in ("itm", "atm", "otm"):
87
+ base = rng.lognormvariate(7, 0.6)
88
+ # Spike day: 3-5x normal volume for the chosen side
89
+ spike = 1.0
90
+ if d == spike_day_offset and kind == spike_side:
91
+ spike = rng.uniform(3.0, 6.0)
92
+ rows.append({
93
+ "date": date,
94
+ "kind": kind,
95
+ "moneyness": bucket,
96
+ "volume": int(base * spike * rng.uniform(0.7, 1.3)),
97
+ "oi": int(base * rng.uniform(3, 10)),
98
+ "avg_iv": rng.uniform(0.18, 0.65),
99
+ })
100
+ df = pd.DataFrame(rows)
101
+ df.to_parquet(os.path.join(out_dir, f"{t}.parquet"), index=False)
102
+
103
+
104
+ def build_ticks(out_dir: str) -> None:
105
+ os.makedirs(out_dir, exist_ok=True)
106
+ for t in SAMPLE_TICKERS:
107
+ rng = _rng(t, "ticks")
108
+ n = rng.randint(800, 1500)
109
+ base = rng.uniform(20, 500)
110
+ ts0 = pd.Timestamp("2026-06-02") + pd.Timedelta(hours=9, minutes=30)
111
+ rows = []
112
+ price = base
113
+ for i in range(n):
114
+ dt = pd.Timedelta(seconds=i * 1.5 + rng.uniform(0, 1.5))
115
+ price *= 1 + rng.gauss(0, 0.0005)
116
+ spread = price * 0.0003
117
+ sz = int(rng.choices(
118
+ [50, 100, 200, 500, 1000, 5000, 10000, 20000],
119
+ weights=[0.25, 0.25, 0.15, 0.15, 0.10, 0.05, 0.03, 0.02],
120
+ )[0])
121
+ rows.append({
122
+ "ts": ts0 + dt,
123
+ "price": round(price, 4),
124
+ "size": sz,
125
+ "bid": round(price - spread / 2, 4),
126
+ "ask": round(price + spread / 2, 4),
127
+ })
128
+ pd.DataFrame(rows).to_parquet(os.path.join(out_dir, f"{t}.parquet"), index=False)
129
+
130
+
131
+ def build_intraday(out_dir: str) -> None:
132
+ os.makedirs(out_dir, exist_ok=True)
133
+ for t in SAMPLE_TICKERS:
134
+ rng = _rng(t, "intra")
135
+ bars = []
136
+ day = pd.Timestamp("2026-06-02") + pd.Timedelta(hours=9, minutes=30)
137
+ price = rng.uniform(20, 500)
138
+ # Each ticker has an aggression lean
139
+ buy_bias = rng.gauss(0, 0.08)
140
+ for i in range(78):
141
+ ts = day + pd.Timedelta(minutes=i * 5)
142
+ o = price
143
+ ret = rng.gauss(0, 0.003)
144
+ c = o * (1 + ret)
145
+ h = max(o, c) * (1 + abs(rng.gauss(0, 0.0015)))
146
+ l = min(o, c) * (1 - abs(rng.gauss(0, 0.0015)))
147
+ v = int(rng.lognormvariate(13, 0.6))
148
+ ratio = 0.5 + buy_bias + rng.gauss(0, 0.06)
149
+ ratio = max(0.30, min(0.70, ratio))
150
+ bv = int(v * ratio)
151
+ bars.append({
152
+ "bar_start": ts, "open": o, "high": h, "low": l, "close": c,
153
+ "volume": v, "buy_vol": bv, "sell_vol": v - bv,
154
+ })
155
+ price = c
156
+ pd.DataFrame(bars).to_parquet(os.path.join(out_dir, f"{t}.parquet"), index=False)
157
+
158
+
159
+ def main() -> None:
160
+ print(f"Building stub data in {HERE}/")
161
+ for fn, name in [
162
+ (build_l2, "l2"),
163
+ (build_options, "options"),
164
+ (build_ticks, "ticks"),
165
+ (build_intraday, "intraday"),
166
+ ]:
167
+ path = os.path.join(HERE, name)
168
+ fn(path)
169
+ files = len(os.listdir(path))
170
+ print(f" {name}: {files} files")
171
+ print("Done.")
172
+
173
+
174
+ if __name__ == "__main__":
175
+ main()
data/stubs/intraday/AAPL.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:298f7305ae43fff7183b736da8d8eae6821325b062c5cfc7148e3d1e90008777
3
+ size 9865
data/stubs/intraday/ADBE.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:82018897a34eb7c46ff6c157c966e2dbc0b8a5e43fcb80f0e442d23ee7d99a56
3
+ size 9869
data/stubs/intraday/AMC.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c33b3c38bb4153a3c353231f2e52455a522876eae5739259b63591a80956ce1c
3
+ size 9858
data/stubs/intraday/AMD.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7140c7baa833545cbb6699e7c3386bfa65220f60697a46ec087777dc21308516
3
+ size 9869
data/stubs/intraday/AMZN.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cfa898fee9d470f01410c04268d5cf3a6f141d7718c883a748d68b6df77af274
3
+ size 9868
data/stubs/intraday/AVGO.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4ce4dcac779dbc8fdd5a4fe8237f0a027f916d01bb9385ba59d7456ee98fabb2
3
+ size 9871
data/stubs/intraday/BAC.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f8b29da1e9e88a213482bffe62c5e8750273fb496522d3af2a379630ed021266
3
+ size 9862
data/stubs/intraday/CRM.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:35ad2b081f5ddf9cf65ee0d7592fdcbb4efc205035354e856cc89abe1853c473
3
+ size 9860
data/stubs/intraday/CVX.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:365691d5be155d46996003180abdd31fb1f913a8d554df8149e699d8c1a61bf7
3
+ size 9879
data/stubs/intraday/GME.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e24286c1b28cb9abbd9896b936478e33ece309863bbb657a5899f7bc61a4a3dc
3
+ size 9862
data/stubs/intraday/GOOGL.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:73f89d334d7cdc6f9a9b823cd2994f272e9ec85c79b0fd6537dd9d815d7e8254
3
+ size 9863
data/stubs/intraday/GS.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:66918c653bd47f4d320a8a135edb33a2824bedd9e707640efacc30fe52b66a09
3
+ size 9858
data/stubs/intraday/INTC.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f2feafd870509f52503a9a44dd77e5f214d2641f323af2190a51fdc396b29d8f
3
+ size 9875
data/stubs/intraday/JNJ.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c838327c83ba33e12743068bc4c25109085a1e08fd4dc1077d62f68dca58e0c8
3
+ size 9863
data/stubs/intraday/JPM.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:52b8c0f9d7631b5e362569096a3408dfee1073f88f69cef1997e968bf2da49bc
3
+ size 9864
data/stubs/intraday/MA.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:58538c46ab8daa87a5ce903b9a1d896062e860bdf736a439cdfe384a9159fcd6
3
+ size 9860
data/stubs/intraday/META.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ae343e29cc0d9eff33b1df9eba200c0499b43c030c5a036147c610f3da0ba63d
3
+ size 9865
data/stubs/intraday/MS.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:75a2fe6ded4fdac33d2acfdc4434821feb43281a127688ae4b9890d27aec4240
3
+ size 9871
data/stubs/intraday/MSFT.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:de1121e0a053b1a6452f224660718f3dfd57b853680c99668dddb882f6bbcf2b
3
+ size 9860
data/stubs/intraday/NFLX.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:686d12c7d913e1d4b3a329838d1ad33b186aa8e454d0b9d93ff81005060f4763
3
+ size 9865
data/stubs/intraday/NVDA.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1e82b91bc034212739526cb525abc80c0954e59b3b6040f52c806d8a2e18abab
3
+ size 9876
data/stubs/intraday/ORCL.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:77e23fdf16981568005a4aefbe304fb7cfff359e71c37b25937d035125e31abf
3
+ size 9871
data/stubs/intraday/PFE.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:88fbdb3c8ac5b240a4a0f009dbd34967452382d7d1234928767441b9f303493b
3
+ size 9867
data/stubs/intraday/PLTR.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a2a27ccd71b6acd5acf428f1c72502b57e76e4a15e664e0c1195782771b156ce
3
+ size 9864
data/stubs/intraday/QCOM.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2d8a666be419f2ff5488b29e1d764ffb8009838d21f492e431990432b06a56c9
3
+ size 9866
data/stubs/intraday/RIOT.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6edfa9aeba6d2ad4067d6293652533446b95bccd04390c7965ae47f917ee60c0
3
+ size 9860
data/stubs/intraday/TSLA.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d34a1b99abd622cd4064d685ea2086f7fdea1b4a53ab1938a4b18a429ed9899b
3
+ size 9879
data/stubs/intraday/UNH.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9c20746a690566500734e4759a62845b2a46fe9c05032b3a0a303b3b5c0b6beb
3
+ size 9862
data/stubs/intraday/V.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cb4d635e599b64512e458ffdbbc745d5685628ac863ddd4f1774e70f228f21e6
3
+ size 9859
data/stubs/intraday/XOM.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f73ba23bb0ad783d5beabb164ddb69dee240f21a3838ebfd442e53596ac307ba
3
+ size 9866
data/stubs/l2/AAPL.json ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ticker": "AAPL",
3
+ "ts": "2026-06-03T02:13:03.084745Z",
4
+ "bids": [
5
+ [
6
+ 33.32,
7
+ 799,
8
+ "NSDQ",
9
+ 16.2
10
+ ],
11
+ [
12
+ 33.31,
13
+ 268,
14
+ "NSDQ",
15
+ 10.1
16
+ ],
17
+ [
18
+ 33.3,
19
+ 486,
20
+ "NSDQ",
21
+ 9.4
22
+ ],
23
+ [
24
+ 33.29,
25
+ 63,
26
+ "NSDQ",
27
+ 25.0
28
+ ],
29
+ [
30
+ 33.28,
31
+ 1987,
32
+ "NSDQ",
33
+ 17.9
34
+ ],
35
+ [
36
+ 33.28,
37
+ 1599,
38
+ "NSDQ",
39
+ 24.4
40
+ ],
41
+ [
42
+ 33.27,
43
+ 221,
44
+ "NSDQ",
45
+ 7.3
46
+ ],
47
+ [
48
+ 33.26,
49
+ 189,
50
+ "NSDQ",
51
+ 20.1
52
+ ],
53
+ [
54
+ 33.25,
55
+ 142,
56
+ "NSDQ",
57
+ 15.8
58
+ ],
59
+ [
60
+ 33.24,
61
+ 849,
62
+ "NSDQ",
63
+ 21.1
64
+ ]
65
+ ],
66
+ "asks": [
67
+ [
68
+ 33.33,
69
+ 286,
70
+ "NSDQ",
71
+ 10.1
72
+ ],
73
+ [
74
+ 33.34,
75
+ 2237,
76
+ "NSDQ",
77
+ 27.3
78
+ ],
79
+ [
80
+ 33.35,
81
+ 949,
82
+ "NSDQ",
83
+ 14.1
84
+ ],
85
+ [
86
+ 33.36,
87
+ 232,
88
+ "NSDQ",
89
+ 27.2
90
+ ],
91
+ [
92
+ 33.37,
93
+ 290,
94
+ "NSDQ",
95
+ 24.0
96
+ ],
97
+ [
98
+ 33.38,
99
+ 93,
100
+ "NSDQ",
101
+ 22.3
102
+ ],
103
+ [
104
+ 33.38,
105
+ 350,
106
+ "NSDQ",
107
+ 16.3
108
+ ],
109
+ [
110
+ 33.39,
111
+ 343,
112
+ "NSDQ",
113
+ 15.7
114
+ ],
115
+ [
116
+ 33.4,
117
+ 465,
118
+ "NSDQ",
119
+ 9.3
120
+ ],
121
+ [
122
+ 33.41,
123
+ 275,
124
+ "NSDQ",
125
+ 13.3
126
+ ]
127
+ ]
128
+ }
data/stubs/l2/ADBE.json ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ticker": "ADBE",
3
+ "ts": "2026-06-03T02:13:03.098593Z",
4
+ "bids": [
5
+ [
6
+ 496.26,
7
+ 1021,
8
+ "NSDQ",
9
+ 16.1
10
+ ],
11
+ [
12
+ 496.14,
13
+ 890,
14
+ "NSDQ",
15
+ 3.6
16
+ ],
17
+ [
18
+ 496.01,
19
+ 216,
20
+ "NSDQ",
21
+ 20.9
22
+ ],
23
+ [
24
+ 495.89,
25
+ 440,
26
+ "NSDQ",
27
+ 4.2
28
+ ],
29
+ [
30
+ 495.77,
31
+ 606,
32
+ "NSDQ",
33
+ 5.4
34
+ ],
35
+ [
36
+ 495.64,
37
+ 632,
38
+ "NSDQ",
39
+ 1.8
40
+ ],
41
+ [
42
+ 495.52,
43
+ 964,
44
+ "NSDQ",
45
+ 10.5
46
+ ],
47
+ [
48
+ 495.39,
49
+ 162,
50
+ "NSDQ",
51
+ 13.5
52
+ ],
53
+ [
54
+ 495.27,
55
+ 558,
56
+ "NSDQ",
57
+ 18.2
58
+ ],
59
+ [
60
+ 495.14,
61
+ 4527,
62
+ "NSDQ",
63
+ 23.3
64
+ ]
65
+ ],
66
+ "asks": [
67
+ [
68
+ 496.51,
69
+ 297,
70
+ "NSDQ",
71
+ 17.0
72
+ ],
73
+ [
74
+ 496.63,
75
+ 202,
76
+ "NSDQ",
77
+ 11.1
78
+ ],
79
+ [
80
+ 496.76,
81
+ 55,
82
+ "NSDQ",
83
+ 25.9
84
+ ],
85
+ [
86
+ 496.88,
87
+ 757,
88
+ "NSDQ",
89
+ 22.0
90
+ ],
91
+ [
92
+ 497.01,
93
+ 158,
94
+ "NSDQ",
95
+ 13.0
96
+ ],
97
+ [
98
+ 497.13,
99
+ 147,
100
+ "NSDQ",
101
+ 28.0
102
+ ],
103
+ [
104
+ 497.25,
105
+ 501,
106
+ "NSDQ",
107
+ 24.0
108
+ ],
109
+ [
110
+ 497.38,
111
+ 266,
112
+ "NSDQ",
113
+ 7.9
114
+ ],
115
+ [
116
+ 497.5,
117
+ 124,
118
+ "NSDQ",
119
+ 10.2
120
+ ],
121
+ [
122
+ 497.63,
123
+ 1034,
124
+ "NSDQ",
125
+ 14.9
126
+ ]
127
+ ]
128
+ }
data/stubs/l2/AMC.json ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ticker": "AMC",
3
+ "ts": "2026-06-03T02:13:03.098593Z",
4
+ "bids": [
5
+ [
6
+ 381.12,
7
+ 286,
8
+ "NSDQ",
9
+ 7.3
10
+ ],
11
+ [
12
+ 381.02,
13
+ 86,
14
+ "NSDQ",
15
+ 17.7
16
+ ],
17
+ [
18
+ 380.93,
19
+ 1491,
20
+ "NSDQ",
21
+ 2.4
22
+ ],
23
+ [
24
+ 380.83,
25
+ 653,
26
+ "NSDQ",
27
+ 4.1
28
+ ],
29
+ [
30
+ 380.73,
31
+ 105,
32
+ "NSDQ",
33
+ 17.3
34
+ ],
35
+ [
36
+ 380.64,
37
+ 476,
38
+ "NSDQ",
39
+ 29.8
40
+ ],
41
+ [
42
+ 380.54,
43
+ 77,
44
+ "NSDQ",
45
+ 21.7
46
+ ],
47
+ [
48
+ 380.45,
49
+ 292,
50
+ "NSDQ",
51
+ 10.8
52
+ ],
53
+ [
54
+ 380.35,
55
+ 146,
56
+ "NSDQ",
57
+ 23.5
58
+ ],
59
+ [
60
+ 380.26,
61
+ 429,
62
+ "NSDQ",
63
+ 2.0
64
+ ]
65
+ ],
66
+ "asks": [
67
+ [
68
+ 381.31,
69
+ 981,
70
+ "NSDQ",
71
+ 11.2
72
+ ],
73
+ [
74
+ 381.4,
75
+ 941,
76
+ "NSDQ",
77
+ 12.2
78
+ ],
79
+ [
80
+ 381.5,
81
+ 301,
82
+ "NSDQ",
83
+ 14.8
84
+ ],
85
+ [
86
+ 381.59,
87
+ 2090,
88
+ "NSDQ",
89
+ 17.9
90
+ ],
91
+ [
92
+ 381.69,
93
+ 568,
94
+ "NSDQ",
95
+ 1.7
96
+ ],
97
+ [
98
+ 381.78,
99
+ 180,
100
+ "NSDQ",
101
+ 17.0
102
+ ],
103
+ [
104
+ 381.88,
105
+ 96,
106
+ "NSDQ",
107
+ 3.3
108
+ ],
109
+ [
110
+ 381.97,
111
+ 330,
112
+ "NSDQ",
113
+ 17.5
114
+ ],
115
+ [
116
+ 382.07,
117
+ 270,
118
+ "NSDQ",
119
+ 15.9
120
+ ],
121
+ [
122
+ 382.16,
123
+ 431,
124
+ "NSDQ",
125
+ 8.5
126
+ ]
127
+ ]
128
+ }
data/stubs/l2/AMD.json ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ticker": "AMD",
3
+ "ts": "2026-06-03T02:13:03.094736Z",
4
+ "bids": [
5
+ [
6
+ 267.94,
7
+ 71,
8
+ "NSDQ",
9
+ 16.5
10
+ ],
11
+ [
12
+ 267.87,
13
+ 667,
14
+ "NSDQ",
15
+ 20.9
16
+ ],
17
+ [
18
+ 267.8,
19
+ 120,
20
+ "NSDQ",
21
+ 9.0
22
+ ],
23
+ [
24
+ 267.74,
25
+ 498,
26
+ "NSDQ",
27
+ 18.1
28
+ ],
29
+ [
30
+ 267.67,
31
+ 432,
32
+ "NSDQ",
33
+ 15.3
34
+ ],
35
+ [
36
+ 267.6,
37
+ 569,
38
+ "NSDQ",
39
+ 26.0
40
+ ],
41
+ [
42
+ 267.53,
43
+ 611,
44
+ "NSDQ",
45
+ 8.6
46
+ ],
47
+ [
48
+ 267.47,
49
+ 204,
50
+ "NSDQ",
51
+ 10.1
52
+ ],
53
+ [
54
+ 267.4,
55
+ 235,
56
+ "NSDQ",
57
+ 20.2
58
+ ],
59
+ [
60
+ 267.33,
61
+ 376,
62
+ "NSDQ",
63
+ 26.5
64
+ ]
65
+ ],
66
+ "asks": [
67
+ [
68
+ 268.07,
69
+ 523,
70
+ "NSDQ",
71
+ 2.5
72
+ ],
73
+ [
74
+ 268.14,
75
+ 4249,
76
+ "NSDQ",
77
+ 12.9
78
+ ],
79
+ [
80
+ 268.2,
81
+ 622,
82
+ "NSDQ",
83
+ 4.7
84
+ ],
85
+ [
86
+ 268.27,
87
+ 250,
88
+ "NSDQ",
89
+ 1.6
90
+ ],
91
+ [
92
+ 268.34,
93
+ 904,
94
+ "NSDQ",
95
+ 17.1
96
+ ],
97
+ [
98
+ 268.41,
99
+ 80,
100
+ "NSDQ",
101
+ 3.8
102
+ ],
103
+ [
104
+ 268.47,
105
+ 482,
106
+ "NSDQ",
107
+ 28.4
108
+ ],
109
+ [
110
+ 268.54,
111
+ 3989,
112
+ "NSDQ",
113
+ 28.2
114
+ ],
115
+ [
116
+ 268.61,
117
+ 879,
118
+ "NSDQ",
119
+ 1.8
120
+ ],
121
+ [
122
+ 268.67,
123
+ 1201,
124
+ "NSDQ",
125
+ 15.4
126
+ ]
127
+ ]
128
+ }
data/stubs/l2/AMZN.json ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ticker": "AMZN",
3
+ "ts": "2026-06-03T02:13:03.094736Z",
4
+ "bids": [
5
+ [
6
+ 459.3,
7
+ 256,
8
+ "NSDQ",
9
+ 28.0
10
+ ],
11
+ [
12
+ 459.18,
13
+ 640,
14
+ "NSDQ",
15
+ 29.5
16
+ ],
17
+ [
18
+ 459.07,
19
+ 157,
20
+ "NSDQ",
21
+ 30.0
22
+ ],
23
+ [
24
+ 458.95,
25
+ 2818,
26
+ "NSDQ",
27
+ 24.0
28
+ ],
29
+ [
30
+ 458.84,
31
+ 308,
32
+ "NSDQ",
33
+ 9.5
34
+ ],
35
+ [
36
+ 458.72,
37
+ 2333,
38
+ "NSDQ",
39
+ 27.2
40
+ ],
41
+ [
42
+ 458.61,
43
+ 3014,
44
+ "NSDQ",
45
+ 22.4
46
+ ],
47
+ [
48
+ 458.49,
49
+ 50,
50
+ "NSDQ",
51
+ 9.2
52
+ ],
53
+ [
54
+ 458.38,
55
+ 271,
56
+ "NSDQ",
57
+ 25.7
58
+ ],
59
+ [
60
+ 458.26,
61
+ 1153,
62
+ "NSDQ",
63
+ 10.2
64
+ ]
65
+ ],
66
+ "asks": [
67
+ [
68
+ 459.53,
69
+ 103,
70
+ "NSDQ",
71
+ 22.5
72
+ ],
73
+ [
74
+ 459.64,
75
+ 858,
76
+ "NSDQ",
77
+ 4.5
78
+ ],
79
+ [
80
+ 459.76,
81
+ 84,
82
+ "NSDQ",
83
+ 18.9
84
+ ],
85
+ [
86
+ 459.87,
87
+ 25,
88
+ "NSDQ",
89
+ 7.7
90
+ ],
91
+ [
92
+ 459.99,
93
+ 269,
94
+ "NSDQ",
95
+ 10.9
96
+ ],
97
+ [
98
+ 460.1,
99
+ 777,
100
+ "NSDQ",
101
+ 18.6
102
+ ],
103
+ [
104
+ 460.22,
105
+ 92,
106
+ "NSDQ",
107
+ 20.1
108
+ ],
109
+ [
110
+ 460.33,
111
+ 830,
112
+ "NSDQ",
113
+ 22.4
114
+ ],
115
+ [
116
+ 460.45,
117
+ 196,
118
+ "NSDQ",
119
+ 20.4
120
+ ],
121
+ [
122
+ 460.56,
123
+ 994,
124
+ "NSDQ",
125
+ 9.7
126
+ ]
127
+ ]
128
+ }
data/stubs/l2/AVGO.json ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ticker": "AVGO",
3
+ "ts": "2026-06-03T02:13:03.094736Z",
4
+ "bids": [
5
+ [
6
+ 100.8,
7
+ 110,
8
+ "NSDQ",
9
+ 26.0
10
+ ],
11
+ [
12
+ 100.78,
13
+ 168,
14
+ "NSDQ",
15
+ 26.4
16
+ ],
17
+ [
18
+ 100.75,
19
+ 245,
20
+ "NSDQ",
21
+ 27.3
22
+ ],
23
+ [
24
+ 100.73,
25
+ 772,
26
+ "NSDQ",
27
+ 27.7
28
+ ],
29
+ [
30
+ 100.7,
31
+ 1962,
32
+ "NSDQ",
33
+ 10.4
34
+ ],
35
+ [
36
+ 100.67,
37
+ 271,
38
+ "NSDQ",
39
+ 12.4
40
+ ],
41
+ [
42
+ 100.65,
43
+ 826,
44
+ "NSDQ",
45
+ 23.4
46
+ ],
47
+ [
48
+ 100.62,
49
+ 385,
50
+ "NSDQ",
51
+ 26.8
52
+ ],
53
+ [
54
+ 100.6,
55
+ 346,
56
+ "NSDQ",
57
+ 4.5
58
+ ],
59
+ [
60
+ 100.57,
61
+ 432,
62
+ "NSDQ",
63
+ 7.9
64
+ ]
65
+ ],
66
+ "asks": [
67
+ [
68
+ 100.85,
69
+ 648,
70
+ "NSDQ",
71
+ 17.6
72
+ ],
73
+ [
74
+ 100.88,
75
+ 161,
76
+ "NSDQ",
77
+ 7.7
78
+ ],
79
+ [
80
+ 100.9,
81
+ 412,
82
+ "NSDQ",
83
+ 19.0
84
+ ],
85
+ [
86
+ 100.93,
87
+ 694,
88
+ "NSDQ",
89
+ 2.3
90
+ ],
91
+ [
92
+ 100.95,
93
+ 1602,
94
+ "NSDQ",
95
+ 4.5
96
+ ],
97
+ [
98
+ 100.98,
99
+ 745,
100
+ "NSDQ",
101
+ 18.3
102
+ ],
103
+ [
104
+ 101.0,
105
+ 966,
106
+ "NSDQ",
107
+ 6.4
108
+ ],
109
+ [
110
+ 101.03,
111
+ 1183,
112
+ "NSDQ",
113
+ 2.8
114
+ ],
115
+ [
116
+ 101.05,
117
+ 273,
118
+ "NSDQ",
119
+ 22.8
120
+ ],
121
+ [
122
+ 101.08,
123
+ 173,
124
+ "NSDQ",
125
+ 5.4
126
+ ]
127
+ ]
128
+ }
data/stubs/l2/BAC.json ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ticker": "BAC",
3
+ "ts": "2026-06-03T02:13:03.098593Z",
4
+ "bids": [
5
+ [
6
+ 372.76,
7
+ 344,
8
+ "NSDQ",
9
+ 16.9
10
+ ],
11
+ [
12
+ 372.66,
13
+ 261,
14
+ "NSDQ",
15
+ 22.7
16
+ ],
17
+ [
18
+ 372.57,
19
+ 272,
20
+ "NSDQ",
21
+ 12.9
22
+ ],
23
+ [
24
+ 372.48,
25
+ 315,
26
+ "NSDQ",
27
+ 29.1
28
+ ],
29
+ [
30
+ 372.38,
31
+ 1017,
32
+ "NSDQ",
33
+ 20.7
34
+ ],
35
+ [
36
+ 372.29,
37
+ 2350,
38
+ "NSDQ",
39
+ 26.9
40
+ ],
41
+ [
42
+ 372.2,
43
+ 948,
44
+ "NSDQ",
45
+ 14.8
46
+ ],
47
+ [
48
+ 372.1,
49
+ 438,
50
+ "NSDQ",
51
+ 17.6
52
+ ],
53
+ [
54
+ 372.01,
55
+ 372,
56
+ "NSDQ",
57
+ 4.1
58
+ ],
59
+ [
60
+ 371.92,
61
+ 350,
62
+ "NSDQ",
63
+ 25.6
64
+ ]
65
+ ],
66
+ "asks": [
67
+ [
68
+ 372.94,
69
+ 202,
70
+ "NSDQ",
71
+ 16.6
72
+ ],
73
+ [
74
+ 373.04,
75
+ 55,
76
+ "NSDQ",
77
+ 13.0
78
+ ],
79
+ [
80
+ 373.13,
81
+ 61,
82
+ "NSDQ",
83
+ 11.6
84
+ ],
85
+ [
86
+ 373.22,
87
+ 170,
88
+ "NSDQ",
89
+ 18.6
90
+ ],
91
+ [
92
+ 373.32,
93
+ 318,
94
+ "NSDQ",
95
+ 1.5
96
+ ],
97
+ [
98
+ 373.41,
99
+ 168,
100
+ "NSDQ",
101
+ 19.1
102
+ ],
103
+ [
104
+ 373.5,
105
+ 66,
106
+ "NSDQ",
107
+ 24.7
108
+ ],
109
+ [
110
+ 373.6,
111
+ 112,
112
+ "NSDQ",
113
+ 29.5
114
+ ],
115
+ [
116
+ 373.69,
117
+ 129,
118
+ "NSDQ",
119
+ 16.4
120
+ ],
121
+ [
122
+ 373.78,
123
+ 215,
124
+ "NSDQ",
125
+ 18.6
126
+ ]
127
+ ]
128
+ }
data/stubs/l2/CRM.json ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ticker": "CRM",
3
+ "ts": "2026-06-03T02:13:03.098593Z",
4
+ "bids": [
5
+ [
6
+ 294.82,
7
+ 314,
8
+ "NSDQ",
9
+ 15.7
10
+ ],
11
+ [
12
+ 294.74,
13
+ 1191,
14
+ "NSDQ",
15
+ 20.5
16
+ ],
17
+ [
18
+ 294.67,
19
+ 2014,
20
+ "NSDQ",
21
+ 24.5
22
+ ],
23
+ [
24
+ 294.59,
25
+ 228,
26
+ "NSDQ",
27
+ 14.7
28
+ ],
29
+ [
30
+ 294.52,
31
+ 1408,
32
+ "NSDQ",
33
+ 28.3
34
+ ],
35
+ [
36
+ 294.45,
37
+ 341,
38
+ "NSDQ",
39
+ 21.6
40
+ ],
41
+ [
42
+ 294.37,
43
+ 396,
44
+ "NSDQ",
45
+ 12.7
46
+ ],
47
+ [
48
+ 294.3,
49
+ 567,
50
+ "NSDQ",
51
+ 15.2
52
+ ],
53
+ [
54
+ 294.23,
55
+ 694,
56
+ "NSDQ",
57
+ 20.6
58
+ ],
59
+ [
60
+ 294.15,
61
+ 457,
62
+ "NSDQ",
63
+ 19.1
64
+ ]
65
+ ],
66
+ "asks": [
67
+ [
68
+ 294.96,
69
+ 1034,
70
+ "NSDQ",
71
+ 9.3
72
+ ],
73
+ [
74
+ 295.04,
75
+ 758,
76
+ "NSDQ",
77
+ 16.5
78
+ ],
79
+ [
80
+ 295.11,
81
+ 2466,
82
+ "NSDQ",
83
+ 29.3
84
+ ],
85
+ [
86
+ 295.18,
87
+ 536,
88
+ "NSDQ",
89
+ 18.4
90
+ ],
91
+ [
92
+ 295.26,
93
+ 490,
94
+ "NSDQ",
95
+ 17.2
96
+ ],
97
+ [
98
+ 295.33,
99
+ 1480,
100
+ "NSDQ",
101
+ 21.2
102
+ ],
103
+ [
104
+ 295.41,
105
+ 1280,
106
+ "NSDQ",
107
+ 17.7
108
+ ],
109
+ [
110
+ 295.48,
111
+ 587,
112
+ "NSDQ",
113
+ 7.3
114
+ ],
115
+ [
116
+ 295.55,
117
+ 278,
118
+ "NSDQ",
119
+ 21.0
120
+ ],
121
+ [
122
+ 295.63,
123
+ 1060,
124
+ "NSDQ",
125
+ 11.3
126
+ ]
127
+ ]
128
+ }
data/stubs/l2/CVX.json ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ticker": "CVX",
3
+ "ts": "2026-06-03T02:13:03.098593Z",
4
+ "bids": [
5
+ [
6
+ 346.98,
7
+ 224,
8
+ "NSDQ",
9
+ 20.5
10
+ ],
11
+ [
12
+ 346.89,
13
+ 2087,
14
+ "NSDQ",
15
+ 21.1
16
+ ],
17
+ [
18
+ 346.8,
19
+ 1395,
20
+ "NSDQ",
21
+ 10.7
22
+ ],
23
+ [
24
+ 346.72,
25
+ 287,
26
+ "NSDQ",
27
+ 25.8
28
+ ],
29
+ [
30
+ 346.63,
31
+ 1373,
32
+ "NSDQ",
33
+ 17.5
34
+ ],
35
+ [
36
+ 346.54,
37
+ 1756,
38
+ "NSDQ",
39
+ 25.6
40
+ ],
41
+ [
42
+ 346.46,
43
+ 917,
44
+ "NSDQ",
45
+ 12.6
46
+ ],
47
+ [
48
+ 346.37,
49
+ 424,
50
+ "NSDQ",
51
+ 11.3
52
+ ],
53
+ [
54
+ 346.28,
55
+ 841,
56
+ "NSDQ",
57
+ 7.3
58
+ ],
59
+ [
60
+ 346.2,
61
+ 439,
62
+ "NSDQ",
63
+ 13.8
64
+ ]
65
+ ],
66
+ "asks": [
67
+ [
68
+ 347.15,
69
+ 126,
70
+ "NSDQ",
71
+ 11.4
72
+ ],
73
+ [
74
+ 347.24,
75
+ 136,
76
+ "NSDQ",
77
+ 2.2
78
+ ],
79
+ [
80
+ 347.32,
81
+ 548,
82
+ "NSDQ",
83
+ 14.6
84
+ ],
85
+ [
86
+ 347.41,
87
+ 135,
88
+ "NSDQ",
89
+ 19.0
90
+ ],
91
+ [
92
+ 347.5,
93
+ 1123,
94
+ "NSDQ",
95
+ 7.7
96
+ ],
97
+ [
98
+ 347.58,
99
+ 178,
100
+ "NSDQ",
101
+ 8.6
102
+ ],
103
+ [
104
+ 347.67,
105
+ 305,
106
+ "NSDQ",
107
+ 13.2
108
+ ],
109
+ [
110
+ 347.76,
111
+ 412,
112
+ "NSDQ",
113
+ 10.2
114
+ ],
115
+ [
116
+ 347.84,
117
+ 139,
118
+ "NSDQ",
119
+ 23.4
120
+ ],
121
+ [
122
+ 347.93,
123
+ 573,
124
+ "NSDQ",
125
+ 17.5
126
+ ]
127
+ ]
128
+ }
data/stubs/l2/GME.json ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ticker": "GME",
3
+ "ts": "2026-06-03T02:13:03.098593Z",
4
+ "bids": [
5
+ [
6
+ 146.47,
7
+ 288,
8
+ "NSDQ",
9
+ 9.6
10
+ ],
11
+ [
12
+ 146.43,
13
+ 298,
14
+ "NSDQ",
15
+ 21.3
16
+ ],
17
+ [
18
+ 146.39,
19
+ 140,
20
+ "NSDQ",
21
+ 10.5
22
+ ],
23
+ [
24
+ 146.36,
25
+ 374,
26
+ "NSDQ",
27
+ 18.6
28
+ ],
29
+ [
30
+ 146.32,
31
+ 526,
32
+ "NSDQ",
33
+ 4.4
34
+ ],
35
+ [
36
+ 146.28,
37
+ 1620,
38
+ "NSDQ",
39
+ 10.3
40
+ ],
41
+ [
42
+ 146.25,
43
+ 407,
44
+ "NSDQ",
45
+ 12.0
46
+ ],
47
+ [
48
+ 146.21,
49
+ 209,
50
+ "NSDQ",
51
+ 18.8
52
+ ],
53
+ [
54
+ 146.17,
55
+ 262,
56
+ "NSDQ",
57
+ 26.3
58
+ ],
59
+ [
60
+ 146.14,
61
+ 168,
62
+ "NSDQ",
63
+ 19.5
64
+ ]
65
+ ],
66
+ "asks": [
67
+ [
68
+ 146.54,
69
+ 579,
70
+ "NSDQ",
71
+ 22.8
72
+ ],
73
+ [
74
+ 146.58,
75
+ 508,
76
+ "NSDQ",
77
+ 26.3
78
+ ],
79
+ [
80
+ 146.61,
81
+ 1172,
82
+ "NSDQ",
83
+ 25.7
84
+ ],
85
+ [
86
+ 146.65,
87
+ 1320,
88
+ "NSDQ",
89
+ 20.0
90
+ ],
91
+ [
92
+ 146.69,
93
+ 422,
94
+ "NSDQ",
95
+ 15.7
96
+ ],
97
+ [
98
+ 146.72,
99
+ 88,
100
+ "NSDQ",
101
+ 12.4
102
+ ],
103
+ [
104
+ 146.76,
105
+ 109,
106
+ "NSDQ",
107
+ 16.5
108
+ ],
109
+ [
110
+ 146.79,
111
+ 333,
112
+ "NSDQ",
113
+ 12.2
114
+ ],
115
+ [
116
+ 146.83,
117
+ 406,
118
+ "NSDQ",
119
+ 6.2
120
+ ],
121
+ [
122
+ 146.87,
123
+ 177,
124
+ "NSDQ",
125
+ 29.5
126
+ ]
127
+ ]
128
+ }
data/stubs/l2/GOOGL.json ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ticker": "GOOGL",
3
+ "ts": "2026-06-03T02:13:03.094736Z",
4
+ "bids": [
5
+ [
6
+ 481.63,
7
+ 238,
8
+ "NSDQ",
9
+ 16.6
10
+ ],
11
+ [
12
+ 481.51,
13
+ 269,
14
+ "NSDQ",
15
+ 21.0
16
+ ],
17
+ [
18
+ 481.39,
19
+ 1355,
20
+ "NSDQ",
21
+ 10.5
22
+ ],
23
+ [
24
+ 481.27,
25
+ 91,
26
+ "NSDQ",
27
+ 14.5
28
+ ],
29
+ [
30
+ 481.15,
31
+ 1091,
32
+ "NSDQ",
33
+ 12.3
34
+ ],
35
+ [
36
+ 481.03,
37
+ 115,
38
+ "NSDQ",
39
+ 17.2
40
+ ],
41
+ [
42
+ 480.91,
43
+ 131,
44
+ "NSDQ",
45
+ 17.7
46
+ ],
47
+ [
48
+ 480.78,
49
+ 679,
50
+ "NSDQ",
51
+ 6.3
52
+ ],
53
+ [
54
+ 480.66,
55
+ 156,
56
+ "NSDQ",
57
+ 29.2
58
+ ],
59
+ [
60
+ 480.54,
61
+ 538,
62
+ "NSDQ",
63
+ 20.9
64
+ ]
65
+ ],
66
+ "asks": [
67
+ [
68
+ 481.87,
69
+ 1097,
70
+ "NSDQ",
71
+ 26.7
72
+ ],
73
+ [
74
+ 481.99,
75
+ 310,
76
+ "NSDQ",
77
+ 24.5
78
+ ],
79
+ [
80
+ 482.11,
81
+ 1357,
82
+ "NSDQ",
83
+ 10.3
84
+ ],
85
+ [
86
+ 482.23,
87
+ 1664,
88
+ "NSDQ",
89
+ 5.6
90
+ ],
91
+ [
92
+ 482.35,
93
+ 1273,
94
+ "NSDQ",
95
+ 12.1
96
+ ],
97
+ [
98
+ 482.47,
99
+ 1003,
100
+ "NSDQ",
101
+ 28.8
102
+ ],
103
+ [
104
+ 482.59,
105
+ 1621,
106
+ "NSDQ",
107
+ 10.9
108
+ ],
109
+ [
110
+ 482.71,
111
+ 1486,
112
+ "NSDQ",
113
+ 24.4
114
+ ],
115
+ [
116
+ 482.83,
117
+ 579,
118
+ "NSDQ",
119
+ 2.5
120
+ ],
121
+ [
122
+ 482.95,
123
+ 4857,
124
+ "NSDQ",
125
+ 20.9
126
+ ]
127
+ ]
128
+ }
data/stubs/l2/GS.json ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ticker": "GS",
3
+ "ts": "2026-06-03T02:13:03.098593Z",
4
+ "bids": [
5
+ [
6
+ 203.18,
7
+ 123,
8
+ "NSDQ",
9
+ 4.4
10
+ ],
11
+ [
12
+ 203.13,
13
+ 163,
14
+ "NSDQ",
15
+ 18.2
16
+ ],
17
+ [
18
+ 203.08,
19
+ 1150,
20
+ "NSDQ",
21
+ 2.9
22
+ ],
23
+ [
24
+ 203.03,
25
+ 23,
26
+ "NSDQ",
27
+ 2.9
28
+ ],
29
+ [
30
+ 202.98,
31
+ 171,
32
+ "NSDQ",
33
+ 5.6
34
+ ],
35
+ [
36
+ 202.93,
37
+ 2206,
38
+ "NSDQ",
39
+ 8.2
40
+ ],
41
+ [
42
+ 202.88,
43
+ 784,
44
+ "NSDQ",
45
+ 15.6
46
+ ],
47
+ [
48
+ 202.83,
49
+ 515,
50
+ "NSDQ",
51
+ 9.6
52
+ ],
53
+ [
54
+ 202.78,
55
+ 883,
56
+ "NSDQ",
57
+ 24.2
58
+ ],
59
+ [
60
+ 202.73,
61
+ 100,
62
+ "NSDQ",
63
+ 6.2
64
+ ]
65
+ ],
66
+ "asks": [
67
+ [
68
+ 203.29,
69
+ 3501,
70
+ "NSDQ",
71
+ 28.9
72
+ ],
73
+ [
74
+ 203.34,
75
+ 173,
76
+ "NSDQ",
77
+ 27.3
78
+ ],
79
+ [
80
+ 203.39,
81
+ 507,
82
+ "NSDQ",
83
+ 5.8
84
+ ],
85
+ [
86
+ 203.44,
87
+ 1431,
88
+ "NSDQ",
89
+ 16.3
90
+ ],
91
+ [
92
+ 203.49,
93
+ 1297,
94
+ "NSDQ",
95
+ 16.1
96
+ ],
97
+ [
98
+ 203.54,
99
+ 217,
100
+ "NSDQ",
101
+ 7.9
102
+ ],
103
+ [
104
+ 203.59,
105
+ 128,
106
+ "NSDQ",
107
+ 28.6
108
+ ],
109
+ [
110
+ 203.64,
111
+ 84,
112
+ "NSDQ",
113
+ 16.2
114
+ ],
115
+ [
116
+ 203.69,
117
+ 429,
118
+ "NSDQ",
119
+ 11.0
120
+ ],
121
+ [
122
+ 203.74,
123
+ 94,
124
+ "NSDQ",
125
+ 20.8
126
+ ]
127
+ ]
128
+ }