Skip to content

[codex] Finish Kronos cleanup and hardening#335

Open
jakehiggins89 wants to merge 46 commits into
shiyu-coder:masterfrom
jakehiggins89:codex/finish-kronos-cleanup
Open

[codex] Finish Kronos cleanup and hardening#335
jakehiggins89 wants to merge 46 commits into
shiyu-coder:masterfrom
jakehiggins89:codex/finish-kronos-cleanup

Conversation

@jakehiggins89

@jakehiggins89 jakehiggins89 commented Jun 21, 2026

Copy link
Copy Markdown

Summary

  • Finish repo cleanup by preserving only intentional ignored runtime artifacts and committing the remaining hardening/docs/tests.
  • Add doctor tooling, research-ops coverage, provider-aware market data tests, outcome-store tests, and web UI security tests.
  • Harden live/preflight, web UI local defaults, path handling, runtime artifact ignores, edge quality metadata, and research maintenance flow.

Validation

Rebased onto current upstream master at 67b630e and re-ran:

  • venv\Scripts\python.exe -m pytest -q -> 78 passed
  • venv\Scripts\python.exe -m compileall scanner tests webui kronos_app.py model -> passed
  • venv\Scripts\python.exe -m pip check -> no broken requirements
  • venv\Scripts\python.exe -m scanner.main --mode doctor -> status ok
  • git diff --check origin/master...HEAD -> passed
  • branch secret-value scan -> no matches

GitHub State

  • Head branch: jakehiggins89/Kronos:codex/finish-kronos-cleanup
  • Base branch: shiyu-coder/Kronos:master
  • PR is ready for review and GitHub CLI reports MERGEABLE / CLEAN.
  • Direct upstream merge is blocked because jakehiggins89 does not have MergePullRequest permission on shiyu-coder/Kronos.

@jakehiggins89 jakehiggins89 force-pushed the codex/finish-kronos-cleanup branch from 592de63 to b5e93e9 Compare June 21, 2026 16:30

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 592de6315e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scanner/models/kronos_adapter.py Outdated
Comment on lines +70 to +71
x_timestamp = features.index
y_timestamp = compute_future_timestamps(x_timestamp[-1], PRED_DAYS)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Pass Series timestamps into Kronos

When any scan candidate reaches this Kronos gate, features.index is a DatetimeIndex, and compute_future_timestamps returns another DatetimeIndex; KronosPredictor.predict() immediately calls calc_time_stamps(), which uses x_timestamp.dt.* in model/kronos.py, so this path raises 'DatetimeIndex' object has no attribute 'dt' and the adapter returns a failed KronosResult for every otherwise-valid setup. Convert both timestamp indexes to pd.Series here, as the web UI path already does, before calling the predictor.

Useful? React with 👍 / 👎.

Comment thread scanner/learning/outcome_reviewer.py Outdated
Comment on lines +53 to +56
base_idx = synthetic.index.get_indexer([created], method="nearest")
if len(base_idx) == 0 or base_idx[0] < 0:
continue
start_pos = int(base_idx[0])

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid resolving stale decisions to unrelated bars

For pending decisions older than the 60-day intraday lookback, the fetched synthetic series no longer contains the decision timestamp, but get_indexer(..., method="nearest") still chooses the nearest available bar, often the first bar in the current window. That causes old journal entries to be marked win/loss from an unrelated date, corrupting the outcome history that drives diagnostics and autotuning; add a tolerance/window check or leave the record pending when the created timestamp is outside the fetched data range.

Useful? React with 👍 / 👎.

codex and others added 24 commits July 2, 2026 13:22
Baseline checkpoint of in-progress work from the prior Codex session:
- scanner/learning/adaptive_policy.py + tests
- scanner/strategy/potter_doctrine.py + tests
- after-hours outcome-review anchoring fix + regression test
- zero-result diagnostic test, daily notes, doctrine v2 plan doc

Verified before commit: 101 tests passed, doctor status=ok.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GSD codebase map from 4 parallel mapper agents: stack, integrations,
architecture, structure, conventions, testing, concerns.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ures

Analog retrieval quality overhaul, all evidence-backed defects:

- Distance now uses a curated ANALOG_FEATURE_KEYS allowlist (scale-free
  setup shape, volume/volatility, empty space, doctrine v2, research
  score). Price levels (latest_close, box_top/bottom, atr_value) made
  analogs cluster by share price; options/kronos/data-quality fields are
  populated live but zero/absent in history, distorting every
  live-vs-historical match.
- Optional direction matching (EDGE_ANALOG_DIRECTION_MATCH, on): a
  bullish query no longer borrows expectancy from bearish history.
- Optional cross-ticker embargo (EDGE_CROSS_TICKER_EMBARGO_DAYS=1) used
  in validation so same-day market-wide moves cannot inflate skill.
- Kronos features are None (not 0.0) when Kronos was never consulted.
  0.0 read as maximum disagreement and silently docked ~10 points from
  every candidate scored without a Kronos forecast - which was all of
  them, in both the edge lab and live edge scans.
- Validation now samples the most recent EDGE_VALIDATION_MAX_RECORDS
  (raised 600 -> 1500) by timestamp across all tickers. The old tail
  slice of a ticker-grouped list validated only the last ~3 watchlist
  names.
- Validation top-k decoupled from analog K (EDGE_VALIDATION_TOP_K=25).

FEATURE_VERSION bumped to 3; the index is rebuilt on every edge-lab run.

Verified: 108 tests passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…bie pendings

Outcome truth upgrade:

- Historical edge records are now labeled by the trade plan, not the
  5-bar close sign: stop at -risk, target at +target (default 2R), time
  exit at horizon, evaluated bar-by-bar against the High/Low path.
  Same-bar stop+target resolves conservatively to the stop. A trade
  that stopped out and drifted back green is now a loss.
- Risk gets a defined fallback (one ATR, else 2%, clamped 0.25-15) so
  near-zero empty-space risk can no longer manufacture huge R
  multiples. R capped to +/-10.
- EdgeRecord gains exit_reason / risk_pct_used / outcome_method with
  defaults, so older index files still load; validation candidates
  carry the fields through.
- Outcome reviewer records directional MAE/MFE over the resolved path
  (outcome_mae_pct / outcome_mfe_pct) so the journal can later support
  stop-aware analysis of live and counterfactual decisions.
- Pending decisions that fell out of the sliding 60d intraday window
  are expired (not_applicable, expired_before_resolution_window) after
  OUTCOME_EXPIRY_DAYS=45 instead of being re-checked forever.

Verified: 116 tests passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The audit demanded 20+ signals above score 55 while the scorer caps
gate-failed candidates (nearly the whole cohort) at 44 - an
unsatisfiable gate that measured threshold attainment instead of skill.
Statistical grounding: a rank IC of ~0.07 is detectable at n=600
samples, while a 55%-win-rate threshold cohort needs 600+ independent
trades to distinguish from coin flip.

- validation: Spearman rank IC (score vs r_multiple and vs return) over
  ALL samples with one-sided p-values, top-5/10/20% percentile blocks,
  decile spread, Wilson 95% lower-bound precision, per-block t-stat on
  R, stop/target exit rates, and day-concentration stats.
- audit: new ranking_evidence check (rank IC >= 0.07 with p <= 0.05,
  top decile >= 20 signals, avg R > 0, t >= 2, Wilson-LB precision >=
  0.45). Either evidence route (legacy absolute threshold OR ranking)
  now satisfies the evidence gate; both must fail to block.
- audit: direction guardrails - any direction with >= 15 validation
  samples and negative avg R is flagged (bearish currently: 55%
  precision but negative expectancy) and promoted candidates in a
  blocked direction cannot grant paper_trade_only readiness.
- new scanner/edge/stats.py shared by validation and adaptive policy
  (Wilson bound moved out of adaptive_policy; one source of truth).

Verified: 124 tests passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ial registry

The tighten-only ratchet dead-locked the learning loop: a noise-driven
tightening to 72 left ~5 signals per quarter (20% WR, -1.37% avg) while
threshold 65 held 17 signals at 47% WR / +1.06%, and the policy could
neither gather enough samples at 72 to act nor ever step back down.

- Loosening path for RESEARCH_CANDIDATE_MIN_SCORE (a data-collection
  throttle for paper counterfactuals, not a live gate): only when a
  lower-threshold cohort DOMINATES the current one - n >= 12, positive
  avg return, Wilson LB >= 0.30, LB >= current+0.05, avg return >=
  current+0.25pp - capped at 10 points, never below bounds.
- Asymmetric application per small-sample best practice: tightening
  applies immediately; loosening requires a 7-day cooldown since the
  last automatic change plus a second confirmation on a later calendar
  day (pending state stored in overrides _meta).
- Trial registry (scanner/reports/trial_registry.jsonl): every
  adaptive/autotune application attempt is logged so the true
  multiple-testing trial count is a recorded fact.
- autotuner.apply_overrides now merges with the existing overrides file
  instead of overwriting it, which silently dropped adaptive-policy
  keys and change history.

Verified: 129 tests passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lift

Kronos was structurally dormant: the strict pipeline only reaches the
Kronos stage after the options gate, and the Potter gate has never
fully passed, so the foundation model has never been consulted on a
single journaled decision - its value was unmeasurable.

- research_scan and dry-run counterfactual paths now run
  kronos.evaluate() on every research-passed candidate (a few per day,
  best-effort, KRONOS_RESEARCH_ENABLED toggle) and journal agreement,
  median forecast return, worst sampled return, and pass state.
- adaptive policy report gains a kronos_lift section: win rate and
  average return split by agreement >= MIN_KRONOS_AGREEMENT among
  resolved research candidates. This is the case-control evidence that
  decides whether the Kronos confirmation stage earns its gate.

Verified: 133 tests passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Evidence velocity:
- EDGE_INDEX_EXTRA_UNIVERSE adds 25 liquid optionable names to the
  retrieval-index build (deduped against the watchlist), nearly
  doubling the honest walk-forward sample per lab run. The live scan
  watchlist is untouched; the extras exist only as history.

Operator UX:
- New --mode brief renders scanner/reports/daily_brief.md and prints
  it: verdict first, progress toward each evidence gate (rank IC,
  top-decile counts, per-direction R with BLOCKED tags), today's scan,
  learning-loop state incl. Kronos lift, every blocker translated to
  plain English with its concrete fix, and the single next action.
- research_ops now ends with the brief, so the daily loop always
  leaves a current human-readable summary. Verified against today's
  real reports.

Verified: 137 tests passed; --mode brief smoke-tested on live report
artifacts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…loosening, data upgrade paths

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…the frontier

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Red-team verdict on the session's changes was BROKEN with three
confirmed findings; all fixed fail-closed:

1. Direction guardrail failed OPEN for under-sampled or absent
   directions: a promoted candidate in a direction with n=14 and
   average R of -3.0 (or no validation history at all) could still
   reach paper_trade_only. Promotion eligibility now requires a
   direction PROVEN positive (>= min samples AND avg R > 0); negative,
   under-sampled, and absent cohorts are all non-promotable. Tests at
   n=14 and missing-direction added.
2. Gap-through-stop losses were floored at -1R, inflating the
   average_r_multiple that hard-gates promotion (a true -2.75R cohort
   scored +0.5R). Stops now fill at the bar's open when it gaps past
   the stop; favorable gaps through the target stay conservatively
   capped at the target.
3. The journal/adaptive-policy path still labeled outcomes by the sign
   of the close at horizon - the exact optimism the lab upgrade
   removed. Extracted the barrier walk into scanner/edge/outcomes.py
   (one source of truth) and the outcome reviewer now applies the same
   triple-barrier definition (ATR-proxy risk, 2R target) with a
   close_horizon fallback when sessions lack OHLC; outcome_method is
   recorded per row.

Also from the review: brief.py now guards present-but-null report
values (_int/_num helpers), and the loosening cooldown normalizes
tz-naive timestamps instead of silently skipping the check.

Verified: 142 tests passed, compileall OK.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t sets the label

Closes the adversary's remaining LOW: a stopped-out-then-recovered trade recorded a loss label but a positive close-at-horizon return. The reviewer now stores outcome_return_pct from the barrier exit (close-horizon fallback keeps parity) and adaptive-policy metrics prefer it, with outcome_ret_5bar_pct preserved as the legacy close metric. 143 tests passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…RA NBBO)

Wires the researched $0 data upgrade: with TRADIER_API_TOKEN set (a
PRODUCTION brokerage token; sandbox is 15-min delayed), option
selection uses Tradier's OPRA-consolidated chains first - real NBBO
bid/ask with sizes, native volume and open interest, ORATS greeks, and
a true quote timestamp.

- Tradier-first with honest semantics: liquidity-gate failures are
  authoritative (no fallback to lower-grade data to force a pass);
  only infrastructure failures (auth/transport) fall back to the
  legacy indicative pipeline, with a warning.
- options_data_quality: 0.9 for fresh OPRA-consolidated quotes
  (execution-grade, clears the audit's 0.75 bar during market hours),
  degrading past 30 minutes so after-hours scans stay research-grade.
- Handles Tradier's single-item payload collapse, ms-epoch quote
  timestamps, and both directions; bid/ask sizes recorded on
  OptionsContractResult.
- Options gates now read config via module attributes so tuning
  overrides apply without a process restart (first slice of the
  hot-reload fix).

Verified: 7 new tests (ATM selection, authoritative gate fail,
infra fallback, no-token path, stale-quote degradation, payload
collapse, puts) + live production smoke: SOFI 2026-08-21 18C bid 1.93
ask 1.95 spread 1.0% OI 9561 sizes 6x9 greeks present; after-hours
quality 0.7 as designed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
reload_overrides() updated module globals, but most consumers imported
the tunable constants by name and froze them at import time - so
overrides applied mid-process (research_ops applies adaptive policy,
then runs research_scan in the SAME process) silently did not affect
the Potter gates, empty-space gates, Kronos agreement gate, or options
gates until the next process start. Worse, autotune proposals stepped
from the import-time defaults instead of the effective values.

- potter_box: ATR/range/no-trend compression gates read
  scanner_config attributes.
- empty_space: MIN_EMPTY_SPACE_SCORE / MIN_RR via attributes.
- kronos_adapter: MIN_KRONOS_AGREEMENT via attributes.
- autotuner: proposal bases read effective values, not defaults.
- main.py: removed dead MIN_RR / MIN_EMPTY_SPACE_SCORE imports.
- options_data was converted in the Tradier commit.

Regression tests: reload updates all 10 tunables; the empty-space gate
flips with live config; autotune steps from the effective MIN_RR.

Also: scanner/README.md documents the wired Tradier provider and the
remaining equity-feed upgrade paths.

Verified: 153 tests passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…piration

Unquotable rows (zero bid, crossed, zero strike) are filtered before the ATM pick so a quote-less nearest strike falls through to the nearest LIVE strike; spread/OI gates still apply to the pick. 8 Tradier tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The adapter passed bars.index (DatetimeIndex) to KronosPredictor, which
uses the .dt accessor and expects Series - so every inference crashed
with "'DatetimeIndex' object has no attribute 'dt'". Latent forever:
the strict pipeline never reached the Kronos stage, and the first real
invocations (research candidates, wired earlier today) surfaced it.
Verified live after the fix: model loads, samples 10 paths, returns
multi_path_agreement with a real agreement figure.

- kronos_adapter: x/y timestamps wrapped in pd.Series; regression test
  drives evaluate() through a fake predictor asserting Series types.
- research journaling honesty: an adapter error (agreement None) is no
  longer journaled as kronos_passed=False - that would have poisoned
  the kronos_lift measurement with infra failures dressed as
  disagreements. Warning logged, fields omitted, test added.
- brief: options guidance updated for the wired-Tradier reality
  (stale-after-hours explanation and intraday-run next action instead
  of "open an account").

Verified: 156 tests passed; live probe returns agreement 0.30 on
random-walk bars (correctly no edge in noise).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…review

Future-dated quote timestamps (clock skew / corrupt feed epoch) now read as unknown age instead of fresh, so they cannot claim execution-grade quality. reload_overrides() resets tunables whose override keys were removed to their module defaults instead of keeping stale values until restart; malformed override values also fall back to defaults instead of raising. Tests for both. 157 tests passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… nits)

Kronos eval failures journal a kronos_eval_error field so a persistently broken model surfaces in the brief and adaptive report (rows_with_eval_errors) instead of looking like normal early accumulation. Int tunables accept float-looking override strings. 157 tests passed. Adversary closing verdict: HOLDS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- add REPO_MAP.md naming the three products (upstream model, desktop app,
  scanner evidence lab); link it from README.md and README_JAKE.md;
  scanner/README.md stays the live docs
- fix stale CUDA/RTX claims (README_JAKE.md, launch_kronos.bat banner,
  kronos_app.py sub-header) — venv torch is 2.7.0+cpu, CPU-only
- reconcile pins with the installed venv: flask-cors 6.0.2, plotly 6.7.0,
  numpy 2.4.6, yfinance 1.2.1, pytest 9.0.3 across requirements.txt,
  pyproject.toml, scanner/requirements-scanner.txt, webui/requirements.txt;
  pip --dry-run confirms pins == installed
- install_deps.bat: drop dead ccxt and the unpinned CUDA torch install;
  now bootstraps venv/ if missing and delegates to setup_dependencies.bat
- delete stale LLM_PROJECT_MEMORY.md (superseded by REPO_MAP.md) and the
  empty .venv/ stub — venv/ is the only environment

verified: pytest 162 passed, doctor status ok

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Zero-memory operation:
- Windows scheduled task "Kronos Daily Research Ops" runs
  scanner\run_research_ops_scheduled.bat every weekday at 13:30 Central
  (14:30 ET, mid-session so Tradier quotes are execution-grade), wakes
  the PC, catches up after missed starts, and propagates the python
  exit code. Registered and verified (next run confirmed).
- Every ops run (and --mode brief) delivers a condensed phone-sized
  brief to Telegram when TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID are set:
  verdict, gate progress, scan, journal/policy state, Kronos health,
  next action. Best-effort and fail-safe; status report only - live
  alerting stays evidence-gated. (Creds are currently EMPTY in .env;
  path verified to no_credentials gracefully.)

Kronos research fix shiyu-coder#2 (stacked behind the Series fix):
- The research path builds ~42-46 synthetic sessions from the 60-day
  intraday window, but the adapter demanded 60 bars - Kronos could
  never run on research candidates. New KRONOS_MIN_BARS=30 floor; the
  adapter uses available history up to the 60-bar lookback.
- End-to-end verified on real data: F, 46 sessions, model sampled 10
  paths, 50% directional agreement journal-ready.

Also: scanner/reports/daily_brief.md untracked + gitignored (runtime
artifact), exit-geometry config knobs land with this commit (their
consumer wiring follows from the exit-geometry work stream).

Verified: 162 tests passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First lab run (20260702T190537Z-c0d68cd8) showed the edge score ranks
raw 5-bar returns (IC .075, p .002) while the encoded plan bleeds
(bearish avg R -.152, t -7.5; ~66% target-hit rate yet negative avg R):
the nearest empty-space target sits too close to the stop.

- resolve_plan_target_pct in edge/outcomes.py: nearest_empty_space /
  next_empty_space / atr_multiple target modes plus an R-floor,
  measured against the resolved stop; every branch keeps the 2x-risk
  degenerate fallback so the baseline mode reproduces the original
  geometry exactly.
- empty_space diagnostics now report the next-further level.
- Index records carry target_pct_used/target_mode provenance; the
  validation report stamps exit_geometry_config.
- Consumes the KRONOS_EXIT_TARGET_* env knobs from 37917f4 so lab
  sweeps flip variants per process without code edits.
- Live gates, live plan encoding, and the journal reviewer untouched.

Verified: 174 tests passed; single-ticker smoke shows the 1.5R floor
engaging on 141/179 SOFI candidates (confirms targets sit inside 1.5R).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
webui/prediction_results/ is gitignored (.gitignore:90), but these 29
prediction_20250826_*.json outputs were committed before the ignore rule
existed and stayed tracked — contradicting the doctor hygiene check's
intent that web prediction outputs stay out of git (CONCERNS.md "Dead
code and upstream dead weight"). git rm --cached only; files remain on
disk and are now properly ignored.

verified: check-ignore passes (example probe + real file),
doctor status ok, pytest 174 passed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e edge

Six-variant walk-forward sweep on identical data (11,033 index records,
1,500 validation samples, all logged as exit_geometry_trial in the
trial registry): bullish avg R by target geometry -
  nearest empty-space level (old): -0.014   (t -0.82)
  next-further level:              -0.016   (t -0.97)
  1.5R floor:                      +0.135   (t +4.46)
  2.0R floor:                      +0.153   (t +4.77)
  2x ATR:                          +0.158   (t +4.92)
  no target (stop/horizon only):   +0.195   (t +5.16)  <- shipped

Monotone dose-response: the further the target, the better, saturating
at none. The bullish edge is a right-tail drift edge; the old
nearest-level exits cashed 68% of trades at +0.11R while stops cost
-1.04R. Decomposition: no-target horizon exits average +0.53R.

Bearish stayed negative under all six geometries (-0.14 to -0.25,
|t| >= 6.4) - structurally bad post-2015, remains direction-blocked.
With honest geometry the audit now shows promotable=[bullish],
blocked=[bearish]; overall readiness stays "blocked" (within-direction
ranking skill still unproven - score calibration is the next stream;
no gate was touched).

"none" is a first-class mode: resolve_plan_target_pct returns a None
target and walk_triple_barrier skips the target barrier (the 2x-risk
fallback stays for degenerate numeric targets). Confirmation run
20260702T235540Z-d09ea09b reproduces the swept v5 bit-for-bit.

Verified: 177 tests passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
codex and others added 18 commits July 2, 2026 19:02
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…contract

Ground-truth accuracy fixes for every R-multiple the evidence engine
computes:

- Edge-index daily bars now fetch adjustment='split' (config
  EDGE_BARS_ADJUSTMENT, env KRONOS_BARS_ADJUSTMENT for sweeps, not an
  adaptive tunable). Raw bars let splits/dividends read as real price
  moves inside the 5-bar outcome window; the top ticker by record count
  (AGNC) distributes ~1%/month.
- yfinance daily fallback stamps its true basis (Yahoo is always
  split-adjusted) and logs provider fallbacks instead of failing silent.
- _to_ny_index no longer shifts naive midnight DATE indexes to the
  prior NY evening via the UTC assumption.
- New fail-closed OHLCV bar contract (scanner/data/bar_contract.py):
  hard invariants fail the ticker at index build; split-sized one-bar
  moves surface as warnings in the index report.
- _alpaca_get retries transport exceptions with the same budget as
  retryable HTTP statuses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 'purged_walk_forward' label was only partially true: allow_future
blocked future ENTRIES, but analogs entered 1-8 days before the query
had 5-trading-bar outcome windows extending past the query bar - their
realized R (not knowable at query time) fed analog_expectancy, leaking
concurrent-week market moves into apparent ranking skill.

- EDGE_EMBARGO_DAYS 5 -> 9 and EDGE_CROSS_TICKER_EMBARGO_DAYS 1 -> 9
  calendar days: covers the 5-bar horizon worst case incl. a holiday.
- Validation report stamps purge_config provenance.
- Regression tests pin horizon coverage and the unresolved-outcome
  exclusion.

Honest numbers may drop; that is the point.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The declared frontier is 'does anything rank R inside bullish' - but no
per-direction rank IC existed anywhere, and every inferential stat
treated 910 overlapping trades as independent.

- by_direction validation blocks now carry: within-direction rank IC
  (score vs R and vs return) with a day-clustered p-value, day-clustered
  t on R, tercile lift with a day-block bootstrap CI (deciles at n~900
  are noise: ~0.12-0.15R SE per bucket), and a tail-retention
  diagnostic (share of >=2R trades captured by the top tercile - the
  veto that stops mean-R calibration silently repeating what profit
  targets did).
- Mass score ties (51% of bullish walk-forward rows score exactly 0)
  previously handed decile membership to input recency via stable sort;
  tercile machinery breaks ties by deterministic hash instead.
- Audit ranking gate now ALSO requires within-direction IC >= 0.07 with
  day-clustered p <= 0.05 in at least one direction: pooled IC could
  pass on bullish-vs-bearish separation while ranking nothing inside
  either direction. Fail-closed when per-direction blocks are absent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
outcome_reviewer hardcoded a 2R profit target while the lab ships
no-target (stop + 5-bar horizon) - so every journal label the adaptive
policy and autotuner learn from described trades under a geometry that
is no longer traded. The reviewer now routes through
resolve_plan_target_pct (single source of truth with the lab) and
stamps outcome_target_mode for provenance. Level-based sweep modes
resolve to their documented 2R fallback at review time, identical to
the old behaviour, so only mode 'none' changes labels.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The frontier work. The composite score is a fail-closed evidence
accumulator whose gate caps collapse walk-forward scores into ties
(51% of bullish rows score exactly 0) - it cannot rank R within a
direction, and monotone calibration of it never will. The literature's
answer is meta-labeling (Lopez de Prado; Joubert et al. JFDS 2022-23):
a secondary model predicting P(win) for setups the primary already
surfaced, used only to rank/size within a direction, strictly
downstream of every gate.

scanner/edge/calibration.py (numpy-only; venv has no sklearn/scipy):
- L2 logistic (IRLS) on 10 PRE-REGISTERED scale-free features led by
  relative volume/participation (the strongest published conditioner
  of short-horizon breakout outcomes); winsorize 1/99 + standardize,
  parameters frozen into a JSON-safe model dict.
- Expanding-window walk-forward OOF evaluation: refit every 21 days,
  9-day purge (outcome must be resolved before prediction time).
- PRE-REGISTERED acceptance: OOF within-bullish rank IC >= 0.07 at
  day-clustered p <= 0.05, n >= 300, tercile spread CI low > 0, tail
  retention >= pro-rata (the right-tail veto - mean-R filters can
  silently repeat what profit targets did), and OOF Brier beating the
  base-rate Brier. Anything less ships the constant predictor.

Wiring: run_validate_edge evaluates over the FULL index history, joins
OOF p_win_meta onto validation candidates, reports report['meta_model'],
persists the final model to reports/meta_model.json, and registers
every evaluation as kind=calibration_trial. run_edge_scan attaches
advisory p_win_meta/expected_r_meta to live bullish candidates without
touching ranking or recommendations. conftest now isolates the trial
registry and meta-model paths from tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reliability layer for the daily hands-off loop:

- New scanner/utils/atomic_io.py (temp + fsync + os.replace, temp file in
  the target's own directory). Wired into: the 31MB edge index, the
  scan-decisions journal, tuning/overrides.json (both writers - a torn
  write there silently reverted every tuned gate AND lost the cooldown
  state), every edge report (the audit authorizes live mode), and the
  meta-model artifact.
- load_decisions now recovers exactly one torn FINAL line by
  quarantining it (scan_decisions.quarantine.jsonl) and fails CLOSED on
  mid-file corruption - it previously deleted every unparseable row
  silently, shrinking the evidence base without a trace.
- load_edge_index gives a clear rebuild instruction on corrupt JSON and
  ignores unknown record fields (a newer schema no longer TypeErrors
  the whole lab).
- Live preflight rejects an edge audit older than 24h: a stale
  paper_trade_only verdict must not authorize live mode days later.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The first full-history evaluation FAILED acceptance and proved P(win)
anti-informative for this right-tail edge (top tercile +0.06R vs bottom
+0.20R, tail retention 0.25 < 0.33): high-p_win annotations on live
candidates would invite exactly the trade selection the evidence
rejects. run_validate_edge now persists meta_model.json only when the
pre-registered acceptance passes and removes any stale artifact
otherwise, so run_edge_scan's advisory join goes silent instead of
misleading.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adversarial review verdict on the changeset: HOLDS (brute-forced 5y of
NYSE holiday calendars: 0 strict outcome-window leaks at purge=9; no
self-training; 0 join-key collisions on the real index; 0/20 pure-noise
datasets accepted). One confirmed-latent defect fixed here:

- An all-NaN feature column (feature outage / un-backfilled new key)
  poisoned winsorize bounds -> NaN standardization -> NaN IRLS weights,
  and predict returned NaN (which 'is None' checks miss), silently
  annotating live candidates. Fit now neutralizes dead columns, refuses
  to return non-finite parameters, predict fails closed on non-finite
  z, and the live join double-checks isfinite.
- tercile/tail bucket size guard (ordered[-0:] returns the whole list).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Jake's directive: the system must improve itself - no human remembering
to 'rerun with a different objective'. The calibration frontier now
runs as a pre-registered THREE-OBJECTIVE suite on every lab run:

  p_win       L2 logistic on R>0   (control arm - proven anti-informative)
  expected_r  L2 ridge on R        (hunts magnitude)
  tail_prob   L2 logistic on R>=2  (hunts the right tail directly)

Autonomy contract, enforced in code:
- every objective's out-of-fold evaluation registers in the trial
  registry every run (the multiple-testing ledger keeps counting);
- a model ships as the live advisory ONLY on the two-touch rule: pass
  the pre-registered acceptance on THIS run AND the previous registered
  run (read back from the registry, not a mutable state file);
- if several qualify, highest current OOF rank IC ships -
  deterministic, pre-registered, no human pick;
- otherwise nothing ships and take-all-bullish stays the policy.

Advisory annotations are objective-aware (meta_objective, meta_score,
expected_r_meta where defensible). trial_registry gains load_trials
(torn-tail tolerant). 237 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hion

Options DTE used the machine (Central) date instead of the exchange date;
Yahoo ex-dividend epochs were rendered in local time, shifting the date back
a day. Yahoo forward earnings dates are estimates with documented off-by-one
failure modes, so the block window gains a +1 day cushion and a stale past
date now blocks as unknown-next-earnings instead of passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ompleteness

Deterministic stale-feed rules (5+ identical closes / 4+ identical OHLCV
bars fail daily evidence; warn-only intraday since quiet 30m bars can
legitimately repeat), zero-volume-with-range and forward-filled flat bars as
hard violations, and a split-ratio snap test on overnight gaps that catches
the 3:2 splits the 45% move rule misses. New session-completeness check
diffs daily bars against the NYSE calendar (pandas_market_calendars, new
pinned dep) so halted/partially-delivered histories fail out of evidence.
Also bumps yfinance 1.2.1 -> 1.5.1 (1.5.0 is retracted upstream).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…finance repair

Alpaca adjustment=split adjusts price AND volume on every timeframe, so all
intraday fetches now request it - a split inside the 60d window no longer
reads as a giant gap in the synthetic sessions. drop_in_progress_daily_bar
removes the current session still-forming daily bar (the 13:30 CT scheduled
run was ingesting ~60%-of-a-day bars as the newest evidence every day). The
yfinance daily fallback runs repair=True (fixes Yahoo unapplied splits and
100x glitches) and stamps repaired-bar provenance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The evidence index build now runs session-completeness and, when a Tradier
production token is configured, cross-checks each ticker daily RETURNS
against Tradier /markets/history - returns, not levels, so the vendors
different adjustment bases cancel while a missed split shows as one 33%+
single-day divergence. One >20pp session or a >5% flagged-day rate fails the
ticker for the run (Alpaca has documented missing-split-factor lapses); no
token or transport failure is skipped, never a block. The index report
records per-ticker cross-check results and dropped partial bars. conftest
scrubs provider credentials from the test environment so pytest can never
reach live APIs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…antine

Outcome resolution - the ground truth behind autotune and the adaptive
policy - was walking triple-barrier paths on live IEX bars: ~3% of the tape
with structurally clipped High/Low (stop AND target touches under-detected)
and raw adjustment. Resolution happens days after the decision, so the free
16-minute-delayed SIP feed applies; bars are now consolidated and
split-adjusted. Corrupt bars (contract violations) block resolution for
retry instead of becoming labels, and a split between decision and review is
quarantined via an entry-vs-decision-session-close scale check ([0.75,1.33])
rather than resolving to a fabricated stop-out.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants