Skip to content

fix: add hourly_to_shifted_session_daily_ohlcv to fix CI fast-unit-tests#85

Open
lee101 wants to merge 774 commits into
mainfrom
ci-fix/stock-prediction-bar-aggregation
Open

fix: add hourly_to_shifted_session_daily_ohlcv to fix CI fast-unit-tests#85
lee101 wants to merge 774 commits into
mainfrom
ci-fix/stock-prediction-bar-aggregation

Conversation

@lee101

@lee101 lee101 commented Apr 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • CI job fast-unit-tests was failing with ImportError: cannot import name 'hourly_to_shifted_session_daily_ohlcv' from 'src.bar_aggregation'
  • scripts/build_augmented_daily_training.py (added in f31c682) imports this function at module level
  • This caused a collection error in tests/test_build_augmented_daily_training.py, which hit the --maxfail=10 limit along with other collection errors

Fix

Added hourly_to_shifted_session_daily_ohlcv to src/bar_aggregation.py:

  • offset_bars=0: standard daily OHLCV aggregation (same semantics as hourly_to_daily_ohlcv)
  • offset_bars=N: virtual day uses last (M-N) bars from calendar day i plus first N bars from day i+1, producing session-shifted augmentation without lookahead bias
  • Added ShiftedSessionStats dataclass with total_virtual_days and dropped_boundary fields
  • require_full_shift=True (default) drops boundary virtual days that don't have enough bars from the following calendar day

Test plan

  • All 4 _concat_binaries tests in tests/test_build_augmented_daily_training.py pass
  • All 4 existing tests/test_bar_aggregation_hourly_to_daily.py tests still pass
  • tests/test_build_augmented_daily_training.py collection no longer raises ImportError
  • from scripts.build_augmented_daily_training import _concat_binaries imports cleanly

🤖 Generated with Claude Code

lee101 and others added 30 commits March 27, 2026 11:22
…eg exhaustive)

CRITICAL: evaluate_holdout --seed 42 gave misleading results for s36 and stock_ent_05:
- s36 showed -17.5%, 48/50 neg with eval_holdout but is actually +27.9%, 1/111 neg
  (exhaustive eval over all 111 possible 90d windows).
- stock_ent_05 showed +40.7%, 0/50 neg with eval_holdout but is actually +6.1%, 52/111 neg.
- Root cause: eval_holdout selects different windows than canonical (only 34/50 overlap).

Reverted trade_daily_stock_prod.py to correct s123+s15+s36 ensemble (exhaustive: 46.3% med,
0/111 neg). Restarted daily-rl-trader.service. Updated alpacaprod.md with exhaustive eval
results and warning against using evaluate_holdout for deployment decisions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Old TradingPolicy.forward used hasattr(self, 'encoder_norm') which is always
True (encoder_norm always initialized in __init__). This applied LayerNorm with
default init weights to old checkpoints that were trained WITHOUT encoder_norm,
corrupting the activations and giving completely wrong results.

Fix matches evaluate_tail.py's pattern: _use_encoder_norm=False by default,
set True only if checkpoint actually contains encoder_norm.weight.

Impact: s36 now correctly evaluates as +27.93% med 1/111 neg (was -14.77%
109/111 neg with broken code). The --exhaustive flag now works correctly.
Production checkpoints s15/s36/s123 had no encoder_norm; new 15M quickscan
checkpoints do have encoder_norm, so quickscan results were already correct.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…uction

encoder_norm (LayerNorm after encoder) was added 2026-03-25 to fix BF16
precision collapse. For FP32 training it changes the optimization landscape.
Production champions s15/s36 were trained without it.

--no-encoder-norm disables encoder_norm for TradingPolicy, which omits it
from the checkpoint too (so evaluation auto-detects correctly via missing_keys).

Testing hypothesis: FP32 training without encoder_norm may find the same
solution space as s15/s36, recovering seeds that now appear dead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- alpacaprod.md: clarify s123+s15+s36 is confirmed production (exhaustive 0/111 neg,
  med=46.3%). Document confusion around s36 "collapse" (was evaluate_holdout bug).
  Add full comparison table showing no improvement found from new candidates.
- scripts/eval_stocks12_seeds.py: add 50-window holdout evaluation script for
  screening new seed candidates against stocks12_daily_val.bin
- Leaderboard CSVs: add tp05 quickscan and extended seed sweep results

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- val_best.pt: periodic OOS eval during training, saves checkpoint at peak
  validation score (val_med - 2*val_neg) to capture OOS-optimal weights
  instead of peak training return
- New args: --val-data-path, --val-eval-interval, --val-eval-windows
- Fix _load_resume_checkpoint: use strict=False to allow resuming old
  checkpoints without encoder_norm; gracefully handles optimizer state
  size mismatch (fresh optimizer if param groups don't match)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _checkpoint_payload now saves use_encoder_norm flag (hasattr encoder_norm)
- load_policy in evaluate_multiperiod prefers stored flag, falls back to
  missing-key inference for old checkpoints (backward-compatible)
- eval_when_done.py now applies same logic for consistent exhaustive eval
- Previously: train used hasattr() → always applied; eval used _use_encoder_norm
  flag (default False) → never applied for fresh-trained models, causing
  0/111 neg to appear correct when actual performance (with encoder_norm) was 43/111

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- TradingPolicy now stores _use_encoder_norm=use_encoder_norm in constructor,
  so _load_resume_checkpoint can correctly disable it for old checkpoints
- _encode() checks _use_encoder_norm flag instead of hasattr, preventing
  random-initialized encoder_norm from being applied when resuming old ckpts
- _checkpoint_payload uses the flag (not hasattr) for stored use_encoder_norm
- Together with evaluate_multiperiod fix, all code paths now consistently
  apply encoder_norm only when it was applied during training

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
….6%, 0/111 neg)

Replaces s123+s15+s36 (med=+46.3%, p10=+28.6%) with tp10+s15+s36 which beats
all metrics: +4.6% median, +8.0% p10, +6.3% worst, identical 0/111 neg.
Stock_trade_pen_10 (tp=0.10, ~6.9M steps) acts as conservative anchor member.

Also fix inference.py and trade_daily_stock_prod.py to handle old checkpoints
lacking encoder_norm by detecting keys and setting use_encoder_norm=False,
using strict=False for load_state_dict.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
tp10+s15+s36+gamma_995+muon_wd_005+h1024_a40 ensemble tested exhaustively on all 111
possible 90-day windows in val set:
- @5bps: med=58.0%, p10=45.4%, worst=36.6%, 0/111 neg
- Beats 4-model (55.9%/42.9%/29.7%) and 3-model (50.9%/36.6%/27.3%) on all metrics
- Robust at 0/5/10/20bps — remains 0/111 neg at all tested slippage levels

Full v2_sweep screening: 47 candidates tested as 5th member. muon_wd_005 best
standalone 5th (+58.4% median); adding h1024_a40 as 6th improves p10 (+2pp) and
worst (+1.4pp) at cost of -0.4pp median.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Leverage sweep results (all 100% positive, 15 symbols, binary fills):
- 1x: +34.06% med ret, Sort=5.45, DD=-0.74%
- 2x: +47.74% med ret, Sort=4.01, DD=-1.34%
- 3x: +57.17% med ret, Sort=3.07, DD=-1.85%
- +SHORT: +83.86% med ret, Sort=1.90, DD=-3.26%

All beat worksteal (Sort=0.85) and pufferlib (Sort=4.52) on risk-adjusted basis.
Added leverage/fee/short override flags to portfolio_eval.py.
Documented in binanceprogress6.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Production ensemble updated: tp10+s15+s36+gamma_995+muon_wd005+h1024_a40
- Exhaustive 111-window eval: med=58.0%, p10=45.4%, worst=36.6%, 0/111 neg
- Ensemble method: softmax_avg (NOT logit_avg - these give very different results)
- BAD standalone models (gamma: 59/111 neg, muon: 72/111 neg) help via softmax dilution

Key additions:
- fast_batch_eval.py: GPU-accelerated batch eval for multiple checkpoints
- stocks12_fast_sweep.py: parameterizable seed sweep with trade_penalty/ent_coef/GRPO support
- train.py: best_neg.pt tracking (checkpoint with fewest neg windows, requires trades>=1)
- eval_when_done.py: now monitors best.pt + val_best.pt + best_neg.pt
- prod.md: updated to reflect 6-model ensemble, sweep findings, exhaustive eval results

7th member search: tested ALL v2_sweep models - none improve both med AND p10.
Seeds 1-14 sweep launched (same config as s15/s36 which showed phase transitions).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Upgrade daily-rl-trader.service from PAPER to LIVE mode
- Add resmlp_a40 as 7th ensemble member to trade_daily_stock_prod.py
  → 7-model exhaustive: 0/111 neg, med=60.3%, p10=45.8% @5bps
  → 6-model was: 0/111 neg, med=58.0%, p10=45.4%
- Fix unified_orchestrator encoder_norm checkpoint loading bug
- Reconcile live stock/crypto symbol ownership in service_config.json
- Update alpacaprod.md with full audit log and 7-model ensemble docs
- Fix eval/test infrastructure for daily stock trading

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- daily-rl-trader.service: updated to --live (was --paper), sleeping until Mon 2026-03-30
- Phase-transition seed section: 350+ seeds tested, only s15/s36 confirmed, ~1% hit rate
- v3 sweep status: 15 fully-trained, best s301=14/111 neg (not phase-transition)
- s1-14 sweep: s1-4 confirmed bad, s6/s8 in full training

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Key finding: tp05_s28 short-trained scan checkpoint (3M steps) is optimal.
Full 35M training degrades from 0/111 neg to 61/111 neg — shorter is better.

8-model exhaustive @5bps: 0/111 neg, med=60.3%, p10=47.4%, worst=34.9%
vs 7-model: p10 +1.6% improvement, all other metrics unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Verification test with fee=10bps, fill=5bps, 111 windows (exhaustive):
  6-model: neg=0/111, med=58.0%, p10=45.4%, worst=36.6% ← OPTIMAL
  7-model +resmlp_a40: med=57.2%, p10=42.1% (-3.3% p10!)
  8-model +s28_scan:   med=55.9%, p10=41.3% (-4.1% p10!)

Previous session's 60.3%/47.4% claims were incorrect (different eval params).
Restarted daily-rl-trader.service with corrected 6-model config.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix alpacaprod.md: revert 7-model/8-model claims to verified 6-model
  (previous eval numbers were incorrect; re-verification shows both HURT)
- Add exhaustive eval CSVs for s1-14, v2, fullrun, tp03, slip5, v3 sweeps
- Add e2etraining/ proof-of-concept for E2E Chronos2+portfolio training
- Add scripts/eval_stocks12_seeds.py and stocks12_autosweep.sh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…CSVs for seed sweeps

- alpacaprod.md: add s310 ensemble test result to Do NOT add list, establish 7-model bar (p10≥45.4%)
- Add exhaustive CSVs for grpo, new_seeds, s16-50, s21-50, s60-77, s78-150, s80-150, s416-600 sweeps
- eval_when_done.py: monitor all checkpoint types (best.pt, val_best.pt, best_neg.pt)
- stocks12_fast_sweep.py: minor cleanup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…structure

- unified_hourly_experiment/trade_unified_hourly.py: improvements from session
- tests/test_trade_unified_hourly.py: new comprehensive test suite
- autoresearch_rl.py: updated config and cleanup
- test_pufferlib_market_autoresearch_rl.py: updated tests
- stocks12_s151_423_exhaustive.csv: new eval monitor output
- systemd/alpaca-cancel-multi-orders.service: cancel multi-orders guard
- setup scripts for alpaca-cancel-multi-orders.service

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The val eval block was nested inside `if log_info and "total_return" in log_info:`,
which skips it whenever no episode completed in the current rollout. This caused
val evals to silently miss at intervals where no episode data was logged.

Fix: move val eval (and val_neg early stopping check) to the outer loop body,
so it runs unconditionally every val_eval_interval updates regardless of episode data.

Also add val_neg early stopping to stocks12_fast_sweep.py:
- Screen phase: stop if >90% neg for 3 consecutive evals (saves ~2-3 min per bad screen)
- Full training: stop if >30/50 neg for 5 consecutive evals (saves ~15-20 min per bad seed)
- New --val-neg-threshold / --val-neg-patience CLI flags

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Scans all SAPP checkpoint dirs, picks best epoch per run by val_sortino,
then selects overall best per symbol. R5 upgrades on 5 symbols:
- DOTUSD: 240.8 -> 263.5 (fullstack_spec_multi)
- APTUSD: 210.8 -> 217.5 (lr5e4_sam_cosine_wd01)
- ARBUSD: 204.7 -> 220.1 (periodic_wd01_rho02)
- INJUSD: 174.1 -> 198.7 (long40ep_lr1e4_sam)
- ALGOUSD: 166.9 -> 192.4 (lr5e4_sam_cosine_wd01)

Portfolio Sort improved 5.45 -> 5.71, still 100% positive windows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nager

- val_eval_quick now tracks avg trades per 90d window
- New --min-screen-med (default 1.0%): filters do-nothing/cash-holding models
- New --min-screen-trades (default 3.0): filters entropy-collapsed models
- Better rejection reason codes (LOW_MED, LOW_TRADES)
- Trades shown in sweep log for qualified seeds

These filters catch the false positives where screen_best.pt happens to
show low val_neg but the model barely trades or has near-zero median return.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Four tests were causing collection failures in the Fast CI job:

1. tests/test_jax_losses.py - imports flax (not in CI deps)
2. tests/test_jax_policy.py - imports jax (not in CI deps)
3. tests/test_jax_trainer_wandboard.py - imports flax (not in CI deps)
4. tests/test_train_crypto_lora_sweep.py - imports resolve_data_path
   which didn't exist in scripts/train_crypto_lora_sweep.py

Fixes:
- Add the three JAX/Flax test files to _FAST_CI_SKIP in conftest.py
  so they are excluded from collection when FAST_CI=1 (same pattern
  used for other tests with optional heavy deps)
- Add resolve_data_path() to scripts/train_crypto_lora_sweep.py,
  which searches stocks/ subdirectory first then falls back to
  data_root directly, matching the test expectation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Key insight from s172/s186/s194 post-mortems: screen_best.pt (highest
in-sample return checkpoint) is often an early, unstable checkpoint that
happens to show low OOS-neg by chance. best_neg.pt (the checkpoint with
fewest val_neg windows DURING screen training) is a much more reliable
OOS signal.

Retrospective re-evaluation shows:
- s172: screen_best=1.0 trades → screen_best_neg=8/20 neg (correctly rejected)
- s186: screen_best=0.1 trades → screen_best_neg=18/20 neg (correctly rejected)
- s194: screen_best=25.8 trades, 5/20 neg → screen_best_neg=8/20 (correctly rejected)

All three false positives would have been caught without wasting 35M-step
full training runs.

Changes:
- After screen completes, copy best_neg.pt → screen_best_neg.pt before
  full training overwrites best_neg.pt
- Use screen_best_neg.pt for qualification check (falls back to
  screen_best.pt if screen_best_neg.pt not available, for backwards compat)
- Same logic in the resume path (for seeds screened in previous sessions)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…neg.pt

screen_best_neg.pt stores best_return=val_med in percentage units (e.g. 6.2
for 6.2%), not in-sample fraction. The max_best_return=0.3 filter was
incorrectly rejecting promising seeds (s226 neg=4/20, s227 neg=2/20,
s633 neg=4/20, s820 neg=4/20) because their val_med > 0.3%.

When using_best_neg=True, skip the filter entirely since the OOS-opt
checkpoint by definition is not an overfitter in the in-sample sense.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Full training processes were launched for all qualified seeds simultaneously,
ignoring gpu_slots. With 8 qualified seeds + ~10 screening processes,
21 concurrent GPU processes caused CUDA OOM (32GB, production using 8.7GB).

Now full training also counts against gpu_slots via total_active check,
same as screening. This ensures total parallel processes never exceeds
the configured slot limit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previous code filled all GPU slots with screening before launching full
training, causing qualified seeds to wait indefinitely. New structure:

1. Pre-process queue CPU-side (skip done, eval already-screened)
2. Check completed screenings
3. Check completed full trainings
4. Launch full training (priority, capped at gpu_slots-1 to leave 1 for screening)
5. Launch screening (fills remaining slots)

This ensures full training always runs alongside screening and qualified
seeds don't starve even when the seed queue is non-empty.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When qualified seeds are waiting for full training but screening fills all
GPU slots, full training could never launch. Now step 5 (screening launch)
caps itself to leave room for pending full-training jobs.

screen_cap = gpu_slots - min(max_full_concurrent, waiting) + running_full

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Track exhaustive 111-window eval results for:
- s151-423: first ~9 seeds evaluated by eval_when_done
- new_seeds batch: up to s301
- s416-600: ongoing evals
- s800-950: s800-s884 evals

Key findings (2026-03-28):
- s241/final.pt: 0/111 neg, p10=+4.1% (buy-and-hold regime, doesn't improve 7-model ensemble)
- s541/val_best.pt: 0/111 neg, p10=+2.6% (same buy-and-hold regime)
- Both REJECTED as 7th ensemble member (7-model p10 drops to 38.5% < 45.4% bar)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
lee101 and others added 27 commits April 8, 2026 11:15
… checkpoint saved)

Root causes:
1. trainer.train_ppo's full_graph_capture branch called build_synthetic_full_step
   instead of build_real_full_step, so the captured graph trained on a fake
   in-memory reward (not gpu_trading_env). It then hard-coded final_sortino=0.0,
   n_episodes=0, last_entropy=0.0 in the returned metrics regardless of what
   the graph produced.
2. The same branch wrote only metrics.json and no .pt file, so scripts/eval_100d
   (which gates on best.pt) was structurally unsatisfiable for this path.

Fix:
- Route full_graph_capture through build_real_full_step(_raw_env, ...).
- Extend build_real_full_step's persistent state with running_returns +
  finished_buf + entropy/n_done/sum_rew scalars so the trainer can harvest
  real episode returns and entropy after each captured replay (one host sync
  per iter, zero per step).
- Compute final_sortino/final_p10/mean_return from harvested episode returns.
- Save best.pt + final.pt via a shared _save_checkpoint helper in both the
  full-graph branch and the eager PPO path; promote final.pt to best.pt when
  no episodes close so eval_100d always has a file to load.
- Add --checkpoint-dir CLI flag to fp4/bench/bench_trading.py so benches can
  pin checkpoint output.
- New test fp4/tests/test_full_graph_learning.py asserts entropy > 0.01,
  n_episodes > 0, finite non-zero sortino, and a loadable best.pt.

1M smoke on gpu_trading_env: n_episodes=512, entropy=3.749, sortino=-0.267,
103k sps, best.pt + final.pt saved. Existing cuda_graph_full tests still pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…covery

The 120d calibrated replay harness silently ignored --min-open-confidence
and --min-open-value-estimate because run_backtest never called the
live open-gate. Every "validated" ensemble replay number in alpacaprod.md
was therefore the ungated ceiling, and the gate could not be tuned offline
at all.

Fix: plumb both gate fields into run_backtest, apply _signal_passes_open_gate
in both the single-position and multi-position open paths, and surface
gate_blocked_opens in the result dict.

Honest 120d replay with the gate wired turns up two major findings:

1. Most of the 32 "prod ensemble" members individually LOSE money on the
   last 120 live trading days. The ensemble's ~0% return is averaging
   ~6 winners against ~36 losers.

2. s15 solo at 2x leverage (alloc 190, BP multiplier 2.0) with the gate
   set to c=0.12 delivers +31.68% / 120d (+5.62% / month) at Sortino 1.22,
   MaxDD 13.9% -- the first deployable positive-edge config on the last
   120 live trading days. Still ~1/5 of the 27%/month HARD RULE but an
   order-of-magnitude improvement over the currently running service.

Regression test added:
tests/test_trade_daily_stock_prod.py::
test_run_backtest_applies_open_gate_to_low_confidence_signals

36/36 backtest tests pass.

See currentstate.md for the updated plan and the full replay table for
s15 / winEns5 / legacy 32-model across gate, allocation, and leverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Refactor all /tmp usage to repo-local tmp/ so CUDA builds, Triton cache,
and temp data don't fill sandboxed /tmp. Add fp4.paths utility (REPO_ROOT,
LOCAL_TMP, ensure_tmp) and fp4.log_utils (RotatingFileLogger). Reduce
production log rotation from 500MB to 50MB cap.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…l_100d)

Add MultiSymbolEnvHandle that loads pufferlib_market .bin files and presents
the production obs_dim=209 (12*16+5+12) and act_dim=25 (1+2*12) so policies
trained on gpu_trading_env have the same shape as the C marketsim expects.
This unblocks eval_100d from falling back to the stub evaluator.

- gpu_trading_env: _load_bin_features, MultiSymbolEnvHandle, make_multi_symbol
- env_adapter: detect train_data .bin and route to multi-symbol path
- Tests: 6 shape/forward-pass tests in test_v5_rsi_shapes.py

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…D RULE

FAIL: all seeds fail the 27%/month target decisively.

seed=0 100M: 172k sps, 578s wall, sortino=1.29 (train), 9M episodes
  best.pt eval_100d: median monthly +0.68% @ 0bps (12/30 neg windows)
  final.pt eval_100d: median monthly -1.07% @ 0bps (25/30 neg)

10M seed dispersion (best.pt eval_100d @ 0bps):
  seed1: median monthly -0.13% (19/30 neg)
  seed2: median monthly -0.61% (30/30 neg)
  seed3: median monthly -2.00% (23/30 neg)

The fp4 gpu_trading_env trainer produces models that are essentially
random on the C marketsim eval. The training reward signal (sortino=1.29)
does not transfer to out-of-sample validation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…hestrator lock

The 32-model ensemble consistently outputs confidence ~0.11-0.12 for its top
action (13 long-only actions, uniform=0.077). DEFAULT_MIN_OPEN_CONFIDENCE=0.20
was silently blocking every trade since the service restarted on 2026-04-08.

- src/daily_stock_defaults.py: lower MIN_OPEN_CONFIDENCE 0.20 → 0.05
  (above random ~0.04 but below observed 0.11-0.12 ensemble output)
- unified_orchestrator/orchestrator.py: respect --lock-name in live mode
  so llm-stock-trader (llm_stock_writer) can coexist with daily-rl-trader
  (alpaca_live_writer) on disjoint symbol sets
- unified_orchestrator/service_config.json: add OPTX to llm-stock-trader
  (Syntec Optics Holdings, Technology; non-overlapping with RL trader)
- alpacaprod.md: document 2026-04-11 fix, OPTX addition, restarts

Services: daily-rl-trader restarted PID 1652847 (next tick Mon 2026-04-14);
          llm-stock-trader restarted PID 1686618 with YELP/NET/DBX/OPTX.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resolves ruff lint failures in Fast CI lint job.
Fix _concat_binaries to properly parse the full MKTD binary layout:
- header (64B) + symbol table (S×16B) + features + prices + masks
- Previous impl wrote symbol tables from every offset file, corrupting
  the concatenated binary with extra 272B between each segment

Also:
- Lower min_days for augmented offset exports (50 instead of 200)
  so hourly-shifted windows (~107 days) are included in training
- Add scripts/train_stocks17_augmented.sh (AdamW seeds 1-5)
- Add scripts/train_stocks17_augmented_muon.sh (Muon seeds 10-14)
- Update service_config.json with full llm-stock-trader symbol list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5 tests verifying: shape, header timestep count, file size formula,
single-file passthrough, and NaN-free real augmented bins.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The CI fast-unit-tests job was failing with:
  ImportError: cannot import name 'hourly_to_shifted_session_daily_ohlcv'
  from 'src.bar_aggregation'

scripts/build_augmented_daily_training.py (added in f31c682) imports
hourly_to_shifted_session_daily_ohlcv at module level, causing collection
errors for tests/test_build_augmented_daily_training.py.  This broke the
fast-unit-tests job because --maxfail=10 was reached during collection.

Adds hourly_to_shifted_session_daily_ohlcv with full implementation:
- offset_bars=0: standard daily OHLCV (equivalent to hourly_to_daily_ohlcv)
- offset_bars=N: virtual day uses last (M-N) bars of day i plus first N bars
  of day i+1, enabling session-shifted augmentation without lookahead bias
- ShiftedSessionStats dataclass for diagnostics (total_virtual_days, dropped_boundary)
- All 4 _concat_binaries tests pass; existing bar_aggregation tests unchanged

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@lee101

lee101 commented Apr 11, 2026

Copy link
Copy Markdown
Owner Author

Codex Infinity
Hi! I'm Codex Infinity, your coding agent for this repo.

Start a task on this PR's branch by commenting:

Tasks and logs: https://codex-infinity.com

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

ℹ️ 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 src/bar_aggregation.py
df["_day"] = df[timestamp_col].dt.floor("D")
days = sorted(df["_day"].unique())

if offset_bars == 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.

P1 Badge Delegate offset_bars=0 path to canonical daily aggregator

The offset_bars == 0 branch reimplements daily aggregation and returns immediately without applying the incomplete-last-day handling used by hourly_to_daily_ohlcv (drop_incomplete_last_day=True by default). On datasets that include an in-progress final day, this produces a partial trailing candle that the canonical path would drop, so offset_bars=0 can silently generate inconsistent training/eval data despite the function/doc claiming equivalent semantics.

Useful? React with 👍 / 👎.

Comment thread src/bar_aggregation.py

df = hourly.copy()
df[timestamp_col] = pd.to_datetime(df[timestamp_col], utc=True, errors="coerce")
df = df.dropna(subset=[timestamp_col]).sort_values(timestamp_col).reset_index(drop=True)

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 Remove duplicate timestamps before shifted-day computation

After timestamp parsing, the new function sorts rows but never deduplicates repeated timestamps. If an hourly feed contains duplicate bars for the same hour, both shifted and unshifted outputs can double-count volume and pick incorrect open/close boundaries, which corrupts derived OHLCV features. hourly_to_daily_ohlcv already defends against this by keeping the last duplicate, so this path should do the same.

Useful? React with 👍 / 👎.

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.

1 participant