diff --git a/.claude/projects/-Users-yirugi-mycloud-personal-workspace-fithia2/memory/feedback_stock_oracle_data.md b/.claude/projects/-Users-yirugi-mycloud-personal-workspace-fithia2/memory/feedback_stock_oracle_data.md new file mode 100644 index 0000000..53d22d5 --- /dev/null +++ b/.claude/projects/-Users-yirugi-mycloud-personal-workspace-fithia2/memory/feedback_stock_oracle_data.md @@ -0,0 +1,11 @@ +--- +name: feedback_stock_oracle_data +description: All data must come through Stock Oracle API. If Oracle doesn't provide something, report what's missing instead of using alternatives. +type: feedback +--- + +All data must be fetched through the Stock Oracle API. Do not use alternative data sources (direct SEC EDGAR, Yahoo Finance, web scraping, etc.). + +**Why:** Stock Oracle is the single source of truth for this project. Using other sources creates inconsistencies and bypasses the project's data infrastructure. + +**How to apply:** When a data need arises, check Stock Oracle API first. If Oracle doesn't provide it, tell the user what's missing rather than silently using an alternative source. diff --git a/.claude/projects/-Users-yirugi-mycloud-personal-workspace-fithia2/memory/project_pead_strategy.md b/.claude/projects/-Users-yirugi-mycloud-personal-workspace-fithia2/memory/project_pead_strategy.md new file mode 100644 index 0000000..0646fa3 --- /dev/null +++ b/.claude/projects/-Users-yirugi-mycloud-personal-workspace-fithia2/memory/project_pead_strategy.md @@ -0,0 +1,39 @@ +--- +name: PEAD Strategy Results +description: Pure PEAD 7% strategy is the first configuration to show consistent OOS profitability across all temporal splits +type: project +--- + +## PEAD 7% Base — Production Candidate (2026-03-14) + +First strategy to show positive expectancy out-of-sample on all splits. + +### Signal +- Entry: earnings reaction_day_return >= 7%, volume_ratio_20d >= 1.5x +- Scoring model: `pead` in `libs/backtest/scoring.py` (`compute_pead_score()`) +- Earnings-only, all other event types disabled +- Filters disabled: veto_oneoff_penalty=1.0, veto_unknown_direction=false, veto_bearish_direction=false + +### Combined Results (155 trades across train/test/valid) +- Win Rate: 67.1% (stable 64.7-67.5% across all splits) +- Avg Win: +5.45%, Avg Loss: -3.48% +- Mean PnL per trade: +2.51% +- Median PnL: +3.73% +- W/L Ratio: 1.57 +- PF: 1.31 (test), 1.95 (valid), 1.68 (train) + +### Key Files +- Manifests: `configs/experiments/pead_pure_7pct.json`, `pead_7pct_drift_a.json` +- Scoring: `libs/backtest/scoring.py` — `compute_pead_score()` +- Domain: `libs/backtest/domain.py` — `SignalConfig.scoring_model`, `pead_reaction_threshold`, `pead_volume_threshold` +- Store wiring: `apps/backtester/run.py` — `_build_store()` dispatches to PEAD scoring + +### Drift A Variant (wider target 3.0 ATR, hold 25d) +- Higher per-trade returns (+7.13% avg win) but lower win rate (56.1%) +- Mean PnL nearly identical (+2.59%) but median much lower (+0.40%) +- OOS less stable (valid WR dropped to 40%) +- Not recommended for production + +**Why:** Previous strategies (baseline 3-factor scoring, small/mid-cap variants, execution optimization) all failed OOS. PEAD 7% succeeds because it relies on a well-documented academic anomaly with a simple, hard-to-overfit signal. + +**How to apply:** Use PEAD 7% Base as the foundation. Any future strategy modifications should be compared against this baseline. The signal is the edge — execution/risk parameters have limited room for improvement (5 execution variants tested, none beat baseline OOS). diff --git a/README.md b/README.md index 48551a2..0c5b6c2 100644 --- a/README.md +++ b/README.md @@ -1,322 +1,770 @@ -# ACE-F v1 — US Stock Event Swing Trading System +# ACE-F v1 — AI Catalyst Event Engine (Free Data) -AI-powered event-driven swing trading system that uses SEC filings and free market data -to identify 1-5 day continuation trades in US equities. +미국 주식 이벤트 기반 중단기 자동매매 시스템. +SEC 공시 + 무료 시장 데이터를 활용하여 1~5일 continuation 종목을 자동 선별하고, +다음 세션에 기계적으로 진입·청산하는 AI 이벤트 트레이딩 엔진. -**Core principle:** AI/LLM is a document interpreter, not a price predictor. -Entry signals come from official filings + price confirmation, never from social data alone. +--- + +## 목차 + +1. [프로젝트 목적](#1-프로젝트-목적) +2. [핵심 철학](#2-핵심-철학) +3. [시스템 아키텍처](#3-시스템-아키텍처) +4. [디렉토리 구조](#4-디렉토리-구조) +5. [데이터 파이프라인](#5-데이터-파이프라인) +6. [전략 엔진](#6-전략-엔진) +7. [백테스트 시스템](#7-백테스트-시스템) +8. [전략 개선 시스템 (SQS / Journal / Leaderboard)](#8-전략-개선-시스템) +9. [현재 개발 현황](#9-현재-개발-현황) +10. [현재 최고 전략 성과](#10-현재-최고-전략-성과) +11. [설치 및 실행](#11-설치-및-실행) +12. [사용법](#12-사용법) +13. [기술 스택](#13-기술-스택) +14. [데이터 소스](#14-데이터-소스) + +--- + +## 1. 프로젝트 목적 + +**"공식 문서와 무료 attention 데이터를 이용해, 1~5일짜리 중단기 continuation 종목을 자동으로 선별하고, 다음 세션에 기계적으로 진입·청산하는 AI 이벤트 트레이딩 시스템"** + +핵심 목표: + +- **무료 데이터만** 사용하여 미국 주식의 1~5거래일 이벤트 드리프트를 자동 탐지 +- LLM/AI는 가격 예측기가 아니라 **공시·문서 해석기**로 사용 +- 전략 중심: **공식 이벤트 + 가격 반응 확인 + 리스크 통제** +- 초기 버전은 수익률 최대화보다 **재현성, 운영 안정성, 확장 가능성** 우선 + +--- + +## 2. 핵심 철학 ``` -SEC Filings → Document Parser → Feature Builder → Signal Ranker - ↓ ↓ -Price/Volume Confirmation ←──── Backtest Engine ←── Risk Engine - ↓ ↓ -Attention Overlay (optional) ──→ Execution Engine → Post-trade Review +AI/LLM = 문서 해석기, ≠ 가격 예측기 ``` -## Architecture +1. **공식 이벤트가 중심** — SEC 8-K, 10-Q, 6-K 등 기업이 직접 배포한 공시가 신호의 원천 +2. **가격이 반드시 1차 검증** — 이벤트 발생 후 첫 정규장 반응(reaction day)이 강하게 확인된 종목만 후보 +3. **재현성 최우선** — 무료 데이터 환경에서 가장 재현성 높은 데이터(SEC)를 중심에 배치 + +--- + +## 3. 시스템 아키텍처 + +### 전체 파이프라인 ``` -apps/ # Application entry points -├── backtester/ # Event-driven backtest simulation & CLI -├── pipeline/ # Multi-stage data processing -│ ├── filing_poller/ # Poll SEC EDGAR for new filings -│ ├── filing_fetcher/ # Download filing documents -│ ├── event_parser/ # Parse events from filings (rule + LLM) -│ ├── feature_builder/ # Generate scoring features -│ ├── label_generator/ # Create forward-return labels -│ └── dataset_export/ # Export Parquet snapshots for backtesting -├── sync/ # Data synchronization -│ ├── issuer_sync/ # Company metadata from Stock Oracle -│ ├── macro_sync/ # FRED macro indicators -│ └── short_volume_sync/ # FINRA short sale volume -├── tracker/ # Strategy improvement tracking CLI -├── tools/ # Analysis & utility scripts -├── review/ # Manual review queue -└── qa/ # Data quality checks - -libs/ # Core libraries -├── backtest/ # Backtesting engine -│ ├── domain.py # Pydantic domain models (30+) -│ ├── tracker.py # SQS scoring, journal I/O, leaderboard -│ ├── execution.py # Entry/exit simulation -│ ├── allocator.py # Position sizing & entry gates -│ ├── scoring.py # Candidate scoring (PEAD, composite) -│ ├── selector.py # Candidate filtering & ranking -│ ├── metrics.py # 21-metric performance bundle + bootstrap CIs -│ ├── artifacts.py # Run output writer (Parquet, JSON, CSV) -│ ├── manifests.py # Experiment config resolution -│ ├── snapshot_store.py # Parquet data loader -│ ├── splits.py # Walk-forward window generation -│ └── calendar.py # Trading day utilities -├── common/ # Logging, config, time utils -├── db/ # PostgreSQL models (async SQLAlchemy) -├── oracle_client/ # Stock Oracle API client -├── parser/ # Filing document parser -├── features/ # Feature engineering -├── labeler/ # Label generation -├── schemas/ # Shared data schemas -├── export/ # Snapshot export -├── review/ # Review logic -└── llm/ # LLM integration layer - -configs/ -├── backtest/ # Base backtest configs (defaults.json) -├── experiments/ # 66 experiment manifests -├── app.yaml # Application settings -└── symbols_*.yaml # Asset universe definitions - -data/ # Data storage (parquet snapshots, cache) -runs/ # Backtest execution outputs -journal/ # Strategy improvement journal & leaderboard -tests/ -├── unit/ # Unit tests (270+) -├── integration/ # Integration tests (requires PostgreSQL) -└── replay/ # Determinism replay tests +[SEC EDGAR / Stock Oracle / FRED / FINRA] + ↓ + Source Adapters + ↓ + Raw Storage (원문 보관) + ↓ + Normalizer / Document Parser + (규칙 기반 + LLM 보강) + ↓ + Feature Store (Parquet 스냅샷) + ↓ + Signal Ranker + (PEAD / Composite 스코어링) + ↓ + Portfolio & Risk Engine + (포지션 사이징, 섹터 제한, 진입 게이트) + ↓ + Backtest Engine / Execution + (이벤트 드리븐 시뮬레이션, 슬리피지) + ↓ + Strategy Improvement Tracker + (SQS, Journal, Leaderboard) ``` -## Setup +### 3대 핵심 엔진 -**Requirements:** Python 3.11+, PostgreSQL 16 (via Docker) +| 엔진 | 역할 | 입력 | 출력 | +|------|------|------|------| +| **Event Engine** | 공식 문서에서 이벤트 후보 생성 | SEC 8-K, 10-Q, 6-K | event_type, direction, confidence | +| **Document Understanding** | 규칙+LLM으로 문서 질 해석 | exhibit text, XBRL | guidance_direction, demand_strength, margin_quality, oneoff_flags | +| **Market Confirmation** | 시장의 실제 반응 확인 | OHLCV bars | reaction_return, volume_ratio, close_location, gap_size | -```bash -# Install dependencies -pip install -e ".[dev]" +--- -# Start PostgreSQL -docker compose up -d +## 4. 디렉토리 구조 -# Run database migrations -alembic upgrade head - -# Copy and configure environment -cp .env.example .env ``` +fithia2/ +├── apps/ # 애플리케이션 엔트리포인트 +│ ├── backtester/ # 이벤트 드리븐 백테스트 시뮬레이션 & CLI +│ │ ├── run.py # 메인 백테스트 실행기 (BacktestRunner) +│ │ └── replay.py # 결정론 검증 (replay test) +│ ├── pipeline/ # 멀티스테이지 데이터 처리 파이프라인 +│ │ ├── filing_poller/ # SEC EDGAR 신규 공시 폴링 +│ │ ├── filing_fetcher/ # 공시 문서 다운로드 +│ │ ├── event_parser/ # 이벤트 파싱 (규칙 + LLM) +│ │ ├── feature_builder/ # 스코어링 피처 생성 +│ │ ├── label_generator/ # forward-return 라벨 생성 +│ │ └── dataset_export/ # Parquet 스냅샷 내보내기 +│ ├── sync/ # 데이터 동기화 +│ │ ├── issuer_sync/ # 기업 메타데이터 (Stock Oracle) +│ │ ├── macro_sync/ # FRED 거시 지표 +│ │ └── short_volume_sync/ # FINRA 공매도 잔량 +│ ├── tracker/ # 전략 개선 추적 CLI (SQS, Journal, Leaderboard) +│ ├── tools/ # 분석·유틸리티 스크립트 (10개) +│ ├── review/ # 수동 리뷰 큐 +│ └── qa/ # 데이터 품질 체크 +│ +├── libs/ # 핵심 라이브러리 +│ ├── backtest/ # 백테스트 엔진 (3,600+ lines) +│ │ ├── domain.py # Pydantic 도메인 모델 30개+ +│ │ ├── scoring.py # 후보 스코어링 (PEAD, composite) +│ │ ├── execution.py # 진입/청산 시뮬레이션 +│ │ ├── allocator.py # 포지션 사이징 & 7개 진입 게이트 +│ │ ├── selector.py # 후보 필터링 & 랭킹 +│ │ ├── metrics.py # 21개 성과 지표 + 부트스트랩 CI +│ │ ├── tracker.py # SQS 계산, Journal I/O, Leaderboard +│ │ ├── snapshot_store.py # Parquet 데이터 로더 +│ │ ├── splits.py # Walk-forward 윈도우 생성 +│ │ ├── manifests.py # 실험 config 해석 +│ │ ├── artifacts.py # 실행 결과 출력 (Parquet, JSON, CSV) +│ │ └── calendar.py # 거래일 유틸리티 +│ ├── oracle_client/ # Stock Oracle API 클라이언트 +│ │ ├── client.py # 비동기 httpx 클라이언트 (retry 로직) +│ │ ├── price.py # OHLCV bars, ATR-14 +│ │ ├── financial.py # 재무제표 (BS, IS) +│ │ ├── filings.py # SEC 공시 검색/다운로드 +│ │ ├── company.py # 기업 메타데이터 +│ │ ├── screener.py # 유니버스 스크리닝 +│ │ ├── fred.py # 거시 데이터 +│ │ └── finra.py # 공매도 잔량 +│ ├── parser/ # 공시 문서 파서 +│ │ ├── rule_parser.py # 규칙 기반 8-K/6-K 파서 +│ │ ├── merger.py # 규칙+LLM 결과 병합 +│ │ └── text_normalizer.py # 텍스트 정규화 +│ ├── features/ # 피처 엔지니어링 +│ │ ├── builder.py # 오케스트레이터 (market+event+financial) +│ │ ├── market_features.py # 가격/거래량 피처 +│ │ ├── event_features.py # 이벤트 품질 피처 +│ │ ├── financial_features.py # 재무 피처 (EPS, 마진, 레버리지) +│ │ └── text_features.py # LLM 기반 텍스트 피처 +│ ├── db/ # PostgreSQL 모델 (async SQLAlchemy) +│ │ ├── models.py # 7개 테이블 (Issuer, Symbol, Event, Document, ...) +│ │ └── migrations/ # Alembic 마이그레이션 +│ ├── labeler/ # 라벨 생성 (1/3/5/7/15일 forward return, MFE/MAE) +│ ├── common/ # 로깅, config, 시간 유틸리티 +│ ├── schemas/ # 공유 Pydantic 스키마 +│ ├── export/ # 스냅샷 내보내기 (DB → Parquet) +│ ├── review/ # 리뷰 로직 +│ └── llm/ # LLM 통합 (Claude/OpenAI, 캐시, 프롬프트) +│ +├── configs/ +│ ├── backtest/ +│ │ └── defaults.json # 기본 전략 파라미터 (21개 설정) +│ ├── experiments/ # 실험 매니페스트 66개 (pead_midcap_step*, ...) +│ ├── app.yaml # 애플리케이션 설정 +│ ├── symbols.yaml # 활성 유니버스 +│ ├── symbols_midcap.yaml # 미드캡 유니버스 ($2B-10B) +│ ├── symbols_largecap.yaml # 라지캡 유니버스 ($10B+) +│ ├── symbols_smallmid.yaml # 스몰미드 유니버스 +│ └── fred_series.yaml # FRED 거시 지표 정의 +│ +├── data/ +│ ├── datasets/snapshots/ # Parquet 스냅샷 (후보, bars, macro) +│ ├── cache/ # LLM/API 응답 캐시 +│ ├── analysis/ # 분석 결과 (플롯, CSV) +│ └── parquet/ # 원시 Parquet 내보내기 +│ +├── runs/ # 백테스트 실행 결과 +│ └── midcap_steps/ # 65+ 실행 기록 (metrics.json, trade_blotter.csv, equity_curve.parquet) +│ +├── journal/ # 전략 개선 저널 +│ ├── improvement_journal.jsonl # append-only 개선 사이클 로그 (21개+ 엔트리) +│ ├── experiment_registry.json # 리더보드 데이터 (자동 생성) +│ └── LEADERBOARD.md # 사람이 읽을 수 있는 리더보드 (자동 생성) +│ +├── tests/ +│ ├── unit/ # 단위 테스트 270개+ (외부 의존성 없음) +│ ├── integration/ # 통합 테스트 (PostgreSQL 필요) +│ └── replay/ # 결정론/재현성 테스트 +│ +├── dev/ # 개발 문서 +│ ├── overview.md # 전체 아키텍처 비전 +│ ├── finished/ # 완료된 Phase 문서 (0, 1, 2, 3, 4) +│ └── phase5~8_deliverables/ # 진행 중인 Phase 문서 +│ +├── docker/ # Docker 관련 파일 +├── docker-compose.yml # PostgreSQL 16 + Adminer +├── pyproject.toml # 의존성 & 빌드 설정 +├── Makefile # 빌드 & 테스트 자동화 +└── alembic.ini # DB 마이그레이션 설정 +``` + +--- -**Key environment variables:** +## 5. 데이터 파이프라인 -| Variable | Default | Description | -|----------|---------|-------------| -| `STOCK_ORACLE_URL` | `http://localhost:18001` | Stock Oracle API endpoint | -| `POSTGRES_DSN` | `postgresql+asyncpg://acef:acef@localhost:5432/acef` | Database connection | -| `DATA_ROOT` | `./data` | Data storage root | -| `LOG_LEVEL` | `INFO` | Logging level | -| `LLM_ENABLED` | `false` | Enable LLM document parsing | +### 5.1 데이터 흐름 -## Usage +``` +단계 1: 원시 데이터 수집 (Phase 2) +┌──────────────────────────────────────────────────────┐ +│ SEC EDGAR → 공시 JSON → 문서 (HTML/TXT) → raw zone │ +│ Stock Oracle → 일봉 OHLCV → market_bars │ +│ FRED → 거시 지표 (금리, 스프레드) → macro_features │ +│ FINRA → 일별 공매도 잔량 → short_volume │ +└──────────────────────────────────────────────────────┘ + ↓ +단계 2: 파싱 & 피처 엔지니어링 (Phase 3) +┌──────────────────────────────────────────────────────┐ +│ Rule Parser → event_type, guidance, confidence │ +│ LLM Parser → 문서 이해 보강 (비활성화) │ +│ Feature Builder → XBRL, reaction, text 스코어 │ +│ Label Generator → 1/3/5/7/15일 forward return, │ +│ MFE/MAE, time-to-target │ +└──────────────────────────────────────────────────────┘ + ↓ +단계 3: 스냅샷 내보내기 +┌──────────────────────────────────────────────────────┐ +│ PostgreSQL → Parquet 스냅샷 (point-in-time) │ +│ 후보 (symbol, score, features) │ +│ Bars (symbol, date, OHLCV) │ +│ Macro (date, 거시 지표) │ +└──────────────────────────────────────────────────────┘ + ↓ +단계 4: 백테스트 & 평가 (Phase 4) +┌──────────────────────────────────────────────────────┐ +│ SnapshotStore.load() → 메모리 로드 │ +│ Signal Ranker → Candidate Selection → Position Sizing │ +│ Entry/Exit Simulation → 21개 성과 지표 계산 │ +│ SQS Score → Journal 기록 → Leaderboard 갱신 │ +└──────────────────────────────────────────────────────┘ +``` -### Data Pipeline +### 5.2 실행 명령어 ```bash -# Sync company metadata +# 기업 메타데이터 동기화 python -m apps.sync.issuer_sync.main -# Fetch and parse filings +# 공시 폴링 → 다운로드 → 파싱 python -m apps.pipeline.filing_poller.main python -m apps.pipeline.filing_fetcher.main python -m apps.pipeline.event_parser.main -# Build features and labels +# 피처 빌드 → 라벨 생성 python -m apps.pipeline.feature_builder.main python -m apps.pipeline.label_generator.main -# Export Parquet snapshot for backtesting +# Parquet 스냅샷 내보내기 python -m apps.pipeline.dataset_export.main ``` -### Backtesting +--- + +## 6. 전략 엔진 + +### 6.1 현재 구현된 전략: PEAD (Post-Earnings Announcement Drift) + +**가설:** 시장은 어닝 발표에 과소반응하며, 발표 후 3~5일간 드리프트(continuation)가 발생한다. + +| 구성 요소 | 설정 | +|----------|------| +| **신호** | 어닝 발표 + reaction day return ≥ 10% + PEAD score ≥ 0.65 | +| **진입** | reaction day 다음 세션 시초가 (next-open) | +| **보유** | 최대 7 거래일 | +| **청산** | Target (ATR 기반) 또는 Stop loss (ATR 기반) 또는 시간 만기 | +| **방향** | 롱 + 숏 (양방향) | +| **유니버스** | 미드캡 ($2B~$10B), NYSE/Nasdaq 보통주 | + +### 6.2 PEAD 스코어링 + +3개 컴포넌트의 가중 합산: + +| 컴포넌트 | 비중 | 피처 | +|----------|------|------| +| **Event Quality** | 65% | parser confidence + signal strength + guidance | +| **Reaction Direction** | 20% | positive/flat/negative 방향성 | +| **Volume Conviction** | 15% | 평균 대비 거래량 확인 | + +### 6.3 진입 게이트 (7개 Veto) + +포지션이 열리기 전 모든 게이트를 통과해야 함: + +1. **유니버스 필터** — 최소 가격 $5, 최소 ADV $1M, ETF 제외 +2. **최대 포지션 수** — 포트폴리오 전체 동시 보유 제한 (기본 8) +3. **섹터 집중 제한** — 동일 섹터 최대 포지션 수 +4. **포지션 크기 제한** — 포트폴리오 대비 최대 비중 +5. **ADV 비율 제한** — 일평균 거래대금의 1% 이내 +6. **연패 쿨다운** — 연속 손실 후 대기 (설정 가능) +7. **파싱 신뢰도 게이트** — 최소 confidence 40% + +### 6.4 Exit 로직 + +``` +매 거래일 포지션 상태 체크: + 1) Target 도달? → target_1_fraction만큼 청산 (기본 50%) + 2) Stop loss 도달? → 전량 청산 + 3) 보유 기한 초과? → 전량 시가 청산 + 4) Trailing stop? → 최고점 대비 하락 시 청산 +``` + +| 파라미터 | 기본값 | 설명 | +|----------|--------|------| +| `stop_atr_multiplier` | 3.0 | ATR-14 × 3.0 stop | +| `target_atr_multiplier` | 1.5 | ATR-14 × 1.5 target | +| `target_1_fraction` | 0.5 | target 도달 시 50% 청산 | +| `trailing_stop_pct` | 0.05 | 5% trailing stop | +| `max_holding_days` | 15 | 최대 보유일 | +| `slippage_bps` | 10 | 10bps 슬리피지 | + +--- + +## 7. 백테스트 시스템 + +### 7.1 이벤트 드리븐 시뮬레이션 + +- **이벤트 귀속**: reaction day (공시 후 첫 거래일) 기준 +- **신호 계산**: reaction day 종가 기준 +- **진입**: 다음 세션 시초가 (look-ahead bias 방지) +- **일별 포트폴리오 상태**: 매일 equity, exposure, drawdown 추적 +- **Kill switch**: 최대 낙폭 25% 초과 시 전략 중단 + +### 7.2 Walk-Forward 검증 + +3-split 체계로 과적합 방지: + +| Split | 역할 | 용도 | +|-------|------|------| +| **Train** | 파라미터 탐색 | 최적화용 | +| **Valid** | 검증 | 과적합 체크 | +| **Test** | 최종 평가 | OOS 성과 (SQS 계산 대상) | + +### 7.3 21개 성과 지표 + +**거래 지표 (7개)** +- win_rate, avg_win%, avg_loss%, profit_factor, expectancy_r, avg_r, trade_count + +**포트폴리오 지표 (8개)** +- total_return, monthly_returns, sharpe_ratio, max_drawdown, equity_curve_r², VaR, CVaR, consecutive_losses + +**부트스트랩 95% 신뢰구간** +- 리샘플링을 통한 통계적 유의성 확인 + +### 7.4 실행 예시 ```bash -# Single split backtest +# 단일 split 백테스트 python -m apps.backtester.run \ - --manifest configs/experiments/pead_midcap_step1_fixedr.json \ - --split test --output-root runs/midcap_steps + --manifest configs/experiments/pead_midcap_step14_score65.json \ + --split test \ + --snapshot-dir data/datasets/snapshots \ + --output-root runs/midcap_steps -# 3-split backtest (train/valid/test) +# 3-split 전체 백테스트 for split in train valid test; do python -m apps.backtester.run \ - --manifest configs/experiments/pead_midcap_step1_fixedr.json \ - --split $split --output-root runs/midcap_steps + --manifest configs/experiments/pead_midcap_step14_score65.json \ + --split $split \ + --snapshot-dir data/datasets/snapshots \ + --output-root runs/midcap_steps done -# Walk-forward cross-validation +# Walk-forward CV python -m apps.backtester.run \ - --manifest configs/experiments/pead_midcap_step1_fixedr.json \ + --manifest configs/experiments/pead_midcap_step14_score65.json \ --walk-forward --wf-train-days 252 --wf-test-days 63 - -# Output includes SQS score after each run: -# Run complete: bt_baseline_swing_v1_... -# Trades: 95 -# Total return: -0.46% -# SQS: 39.7 (profitability=23.9, risk=52.4, consistency=24.8, robustness=80.7) ``` -### Experiment Configuration - -Experiments are defined as JSON manifests in `configs/experiments/`: - -```json -{ - "experiment_name": "pead_midcap_step1_fixedr", - "dataset_snapshot_id": "midcap-filtered", - "base_config": "configs/backtest/defaults.json", - "overrides": { - "signal": { "scoring_model": "pead", "pead_reaction_threshold": 0.07 }, - "execution": { "target_model": "fixed_r", "target_1_r": 2.0 }, - "risk": { "max_positions": 8 } - }, - "tags": ["pead", "midcap"] -} +**출력 예시:** +``` +Run complete: bt_baseline_swing_v1_midcap-filte_20260316... +Trades: 72 +Total return: +0.73% +SQS: 64.2 (profitability=68.4, risk=61.2, consistency=58.7, robustness=65.3) ``` --- -## Strategy Improvement Tracking System +## 8. 전략 개선 시스템 + +전략 개선을 체계적으로 관리하기 위한 3계층 시스템. +중복 실험 방지, 데이터 기반 의사결정, 리더보드를 통한 최고 전략 추적. + +### 8.1 Strategy Quality Score (SQS) -A structured system to prevent duplicate experiments, enable data-driven decisions, -and track the best strategy via a leaderboard. +**test split 지표만으로** 계산하는 종합 점수 (0~100). 높을수록 좋음. -### Strategy Quality Score (SQS) +| 카테고리 | 비중 | 하위 지표 | 비중 | 0점 기준 | 100점 기준 | +|----------|------|-----------|------|----------|-----------| +| **Profitability** | 40% | profit_factor | 60% | ≤ 0.8 | ≥ 2.0 | +| | | total_return_pct | 40% | ≤ -5% | ≥ +5% | +| **Risk** | 25% | max_drawdown_pct (역) | 50% | ≥ 10% | ≤ 1% | +| | | sharpe_ratio | 50% | ≤ -1.0 | ≥ 2.0 | +| **Consistency** | 20% | win_rate | 50% | ≤ 0.35 | ≥ 0.65 | +| | | monthly_win_rate | 50% | ≤ 0.30 | ≥ 0.70 | +| **Robustness** | 15% | equity_curve_r² | 50% | ≤ 0.0 | ≥ 0.80 | +| | | trade_count | 50% | ≤ 10 | ≥ 100 | -Composite score (0-100) computed from **test split metrics only**. Higher is better. +**Low-trade penalty:** test 거래 수 < 20이면 SQS를 절반으로 감산. -| Category | Weight | Sub-metric | Weight | 0 pts | 100 pts | -|----------|--------|------------|--------|-------|---------| -| **Profitability** | 40% | profit_factor | 60% | ≤0.8 | ≥2.0 | -| | | total_return_pct | 40% | ≤-5% | ≥+5% | -| **Risk** | 25% | max_drawdown_pct (inv) | 50% | ≥10% | ≤1% | -| | | sharpe_ratio | 50% | ≤-1.0 | ≥2.0 | -| **Consistency** | 20% | win_rate | 50% | ≤0.35 | ≥0.65 | -| | | monthly_win_rate | 50% | ≤0.30 | ≥0.70 | -| **Robustness** | 15% | equity_curve_r_squared | 50% | ≤0.0 | ≥0.80 | -| | | trade_count | 50% | ≤10 | ≥100 | +**SQS 해석 기준:** -**Low-trade penalty:** If test trades < 20, SQS is halved. +| SQS 범위 | 해석 | +|-----------|------| +| 0–20 | 손실 전략 | +| 20–40 | 손익분기 근처 | +| 40–55 | 유망, 개선 필요 | +| **55–70** | **좋음, OOS 엣지 있음** | +| 70–85 | 강함, 실전 후보 | +| 85–100 | 예외적 (데이터 오류 확인 필요) | -| SQS Range | Interpretation | -|-----------|----------------| -| 0-20 | Losing strategy | -| 20-40 | Near breakeven | -| 40-55 | Promising, needs work | -| 55-70 | Good, has OOS edge | -| 70-85 | Strong, live candidate | -| 85-100 | Exceptional (check for data issues) | +### 8.2 Journal (개선 저널) -### Journal & Leaderboard +모든 실험 결과를 기록하는 append-only 로그. ``` journal/ -├── improvement_journal.jsonl ← Append-only improvement cycle log -├── experiment_registry.json ← Leaderboard data (regenerated) -└── LEADERBOARD.md ← Human-readable leaderboard (regenerated) +├── improvement_journal.jsonl ← append-only 개선 사이클 로그 +├── experiment_registry.json ← 리더보드 데이터 (자동 생성) +└── LEADERBOARD.md ← 사람이 읽는 리더보드 (자동 생성) ``` -Each journal entry records one improvement cycle: +**Journal 엔트리 구조:** ```json { - "entry_id": "IMP-0001", - "timestamp": "2026-03-16T19:30:00", - "experiment_name": "pead_midcap_step3_10pct", - "hypothesis": "Raise reaction threshold to 10% for stronger signals", + "entry_id": "IMP-0015", + "timestamp": "2026-03-17T01:45:00+00:00", + "experiment_name": "pead_midcap_step14_score65", + "hypothesis": "Score threshold 0.60→0.65로 높여 약한 신호 필터링", + "config_delta": { + "base_experiment": "pead_midcap_step13_best", + "changes": { "signal.score_threshold": "0.60 → 0.65" } + }, "results": { - "train": { "run_id": "bt_...", "trade_count": 420, "profit_factor": 0.95, ... }, - "valid": { "run_id": "bt_...", ... }, - "test": { "run_id": "bt_...", ... } + "train": { "run_id": "bt_...", "trade_count": 312, "profit_factor": 1.15, "..." : "..." }, + "valid": { "run_id": "bt_...", "trade_count": 145, "profit_factor": 1.28, "..." : "..." }, + "test": { "run_id": "bt_...", "trade_count": 72, "profit_factor": 1.22, "..." : "..." } + }, + "sqs_score": 64.2, + "sqs_breakdown": { + "profitability": 68.4, + "risk": 61.2, + "consistency": 58.7, + "robustness": 65.3 }, - "sqs_score": 38.5, - "sqs_breakdown": { "profitability": 35.2, "risk": 45.0, "consistency": 30.0, "robustness": 42.0 }, "verdict": "better", - "verdict_reasoning": "Test PF 0.89 -> 1.00, breakeven achieved", - "next_direction": "Combine 10% threshold + maxcand3" + "verdict_reasoning": "Test SQS 64.2 > 57.7. Return +0.73% > +0.43%. Trade count 72 sufficient.", + "next_direction": "Test exit tuning (target, fraction, hold) on top of score 0.65" } ``` -### Tracker CLI +### 8.3 실험 매니페스트 + +실험은 JSON 매니페스트로 정의. `base_config`에 `overrides`를 적용하는 구조: + +```json +{ + "experiment_name": "pead_midcap_step14_score65", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 14: Score threshold 0.60→0.65", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { "max_holding_days": 7 }, + "risk": { "max_positions": 8 } + }, + "tags": ["pead", "midcap", "step14", "score65"] +} +``` + +### 8.4 개선 워크플로우 + +``` +1. 실험 config 생성 configs/experiments/my_experiment.json +2. 3-split 백테스트 실행 for split in train valid test; do ... done +3. Journal에 기록 python -m apps.tracker.cli record ... +4. Leaderboard 확인 python -m apps.tracker.cli leaderboard ... +5. 다음 실험 계획 verdict + SQS breakdown 기반 +6. 중복 실험 확인 python -m apps.tracker.cli check-duplicate ... +7. 1번부터 반복 +``` + +### 8.5 Tracker CLI ```bash -# Record experiment results to journal +# 실험 결과 기록 python -m apps.tracker.cli record \ --journal-dir journal/ \ --runs-dir runs/midcap_steps/ \ - --experiment pead_midcap_step3_10pct \ - --hypothesis "Raise reaction threshold to 10%" \ - --baseline pead_7pct_midcap \ + --experiment pead_midcap_step14_score65 \ + --hypothesis "Score threshold 0.60→0.65" \ + --baseline pead_midcap_step13_best \ --verdict better \ - --reasoning "Test PF improved from 0.89 to 1.00" \ - --next "Combine 10% threshold + maxcand3" + --reasoning "Test SQS 64.2 > 57.7, Return +0.73% > +0.43%" \ + --next "Test exit tuning on top of score 0.65" -# View leaderboard +# 리더보드 출력 python -m apps.tracker.cli leaderboard --journal-dir journal/ -# # Experiment SQS PF Ret% Trades -# ------------------------------------------------------------------ -# 1 pead_7pct_longshort_v2 60.7 1.31 +2.0 55 -# 2 pead_midcap_step3_10pct 38.5 1.00 +0.0 85 +# 엔트리 상세 조회 +python -m apps.tracker.cli show --journal-dir journal/ IMP-0015 -# Show entry details -python -m apps.tracker.cli show --journal-dir journal/ IMP-0001 - -# Check for duplicate experiments +# 중복 실험 확인 python -m apps.tracker.cli check-duplicate \ - --journal-dir journal/ --experiment pead_midcap_step3_10pct + --journal-dir journal/ --experiment my_experiment_name + +# 레지스트리 재생성 +python -m apps.tracker.cli rebuild-registry --journal-dir journal/ ``` -### Improvement Workflow +### 8.6 핵심 발견사항 + +21개 실험을 통해 얻은 인사이트: + +| 발견 | 설명 | +|------|------| +| **Score threshold가 가장 강력한 레버** | 0.60→0.65로 올리면 SQS 50.2→64.2 (+28%) | +| **넓은 funnel은 OOS에서 실패** | reaction 0.10→0.07로 낮추면 거래 수는 늘지만 품질 하락 | +| **Exit 튜닝은 한계적** | target/fraction/hold 변경은 noise 범위 내 | +| **조합이 개별보다 나쁨** | 한계적 개선들을 합치면 과적합 리스크로 오히려 하락 | +| **단순함이 최고** | Step14(score 0.65만 변경)가 모든 복합 변형보다 우수 | +| **라지캡 확장 실패** | $10B+ 대형주에서는 OOS 성과 없음, 미드캡이 최적 | +| **보유기간 5~7일 최적** | PEAD 드리프트의 평균 보유는 3.28일 | + +--- + +## 9. 현재 개발 현황 + +### Phase별 진행 상태 + +| Phase | 이름 | 상태 | 설명 | +|-------|------|------|------| +| **Phase 0** | 전략/운용 명세 동결 | **완료** | strategy_spec.md, risk_policy.md, data_source_policy.md, event_taxonomy.md | +| **Phase 1** | 개발 기반 & DB 스키마 | **완료** | Docker, PostgreSQL, Alembic, adapter contracts, 공통 인프라 | +| **Phase 2** | 핵심 데이터 수집 | **완료** | SEC, Alpaca(→Stock Oracle), FRED, FINRA 어댑터 구축 | +| **Phase 3** | 문서 파서 & 피처 빌더 | **완료** | 규칙 기반 파서 + LLM 보강, 피처 엔지니어링, 라벨 생성 | +| **Phase 4** | 백테스트 엔진 | **완료** | 이벤트 드리븐 시뮬레이터, walk-forward CV, SQS 스코어링, 21개 지표 | + +### 현재 작동 중인 것 + +- 백테스트 엔진 (이벤트 드리븐, 재현 가능, 21개 지표) +- Walk-forward cross-validation (train/valid/test 분할) +- 실험 추적 (Journal, Leaderboard, SQS 스코어링) +- PEAD 전략 (주력 신호, SQS 64.2, +0.73% OOS) +- 단위/통합 테스트 (270개+ 자동화) +- 포트폴리오 리스크 엔진 (포지션 사이징, 섹터 제한, stop/target) +- 데이터 파이프라인 (filing_poller → event_parser → feature_builder → dataset_export) + +--- + +## 10. 현재 최고 전략 성과 + +### Leaderboard (Top 10, 2026-03-17 기준) + +| # | Experiment | SQS | PF | Ret% | WR | Sharpe | DD% | Trades | +|---|-----------|-----|-----|------|-----|--------|-----|--------| +| 1 | **pead_midcap_step14_score65** | **64.2** | 1.22 | +0.7 | 57% | 1.3 | 0.9 | 72 | +| 2 | pead_midcap_step18_nofrac | 63.2 | 1.23 | +0.8 | 52% | 1.4 | 0.9 | 64 | +| 3 | pead_midcap_step19_hold5 | 62.9 | 1.20 | +0.7 | 57% | 1.2 | 0.9 | 72 | +| 4 | pead_midcap_step20_best3 | 62.0 | 1.22 | +0.7 | 52% | 1.3 | 0.9 | 64 | +| 5 | pead_midcap_step17_target2 | 59.4 | 1.18 | +0.6 | 53% | 1.1 | 0.8 | 66 | +| 6 | pead_midcap_step13_best | 57.7 | 1.12 | +0.4 | 55% | 0.8 | 0.9 | 75 | +| 7 | pead_midcap_step16_react7_score65 | 53.2 | 0.97 | -0.1 | 56% | -0.2 | 1.6 | 89 | +| 8 | pead_midcap_step5_maxcand3 | 52.5 | 0.95 | -0.2 | 54% | -0.4 | 1.4 | 96 | +| 9 | pead_midcap_step15_react7 | 51.7 | 0.94 | -0.3 | 55% | -0.5 | 1.6 | 91 | +| 10 | pead_midcap_step11_score60 | 50.2 | 1.02 | +0.1 | 52% | 0.2 | 0.9 | 77 | + +### 최고 전략: `pead_midcap_step14_score65` ``` -1. Create experiment config configs/experiments/my_experiment.json -2. Run 3-split backtest for split in train valid test; do ... done -3. Record to journal python -m apps.tracker.cli record ... -4. Check leaderboard python -m apps.tracker.cli leaderboard ... -5. Plan next experiment based on verdict + SQS breakdown -6. Check for duplicates python -m apps.tracker.cli check-duplicate ... -7. Repeat from step 1 +SQS: 64.2 (Good — OOS edge 확인) +수익률: +0.73% (OOS test) +PF: 1.22 +승률: 56.9% +Sharpe: 1.3 +최대낙폭: 0.9% +거래 수: 72 +보유기간: 평균 3.28일 ``` +**핵심 설정:** +- reaction threshold: 10% (어닝 발표 후 첫날 10% 이상 반응한 종목만) +- score threshold: 0.65 (PEAD 스코어 0.65 이상만 진입) +- volume threshold: 2.0x (평균 대비 2배 이상 거래량) +- max candidates/day: 3 (하루 최대 3개 후보) +- max holding: 7일 +- earnings_release만 활성화 (다른 이벤트 타입 비활성화) + --- -## Testing +## 11. 설치 및 실행 + +### 요구사항 + +- Python 3.11+ +- PostgreSQL 16 (Docker 제공) +- Stock Oracle API (Docker, localhost:18001) + +### 설치 ```bash -# Unit tests (fast, no external deps) +# 의존성 설치 +pip install -e ".[dev]" + +# PostgreSQL 시작 +docker compose up -d + +# DB 마이그레이션 +alembic upgrade head + +# 환경 변수 설정 +cp .env.example .env +``` + +### 환경 변수 + +| 변수 | 기본값 | 설명 | +|------|--------|------| +| `STOCK_ORACLE_URL` | `http://localhost:18001` | Stock Oracle API 엔드포인트 | +| `POSTGRES_DSN` | `postgresql+asyncpg://acef:acef@localhost:5432/acef` | DB 연결 | +| `DATA_ROOT` | `./data` | 데이터 저장 루트 | +| `LOG_LEVEL` | `INFO` | 로그 레벨 | +| `LLM_ENABLED` | `false` | LLM 문서 파싱 활성화 | + +### Makefile 타겟 + +```bash +make bootstrap # 의존성 설치 +make db-upgrade # 마이그레이션 실행 +make db-reset # DB 전체 리셋 +make test-unit # 단위 테스트만 +make test-integration # 통합 테스트 (PostgreSQL 필요) +make test-replay # 결정론 테스트 +make lint # Ruff 린트 +make typecheck # MyPy 엄격 모드 +make ci # 전체 CI (lint + typecheck + test) +``` + +--- + +## 12. 사용법 + +### 데이터 파이프라인 실행 + +```bash +# 1) 기업 메타데이터 동기화 +python -m apps.sync.issuer_sync.main + +# 2) 공시 수집 → 파싱 → 피처 빌드 → 라벨 생성 +python -m apps.pipeline.filing_poller.main +python -m apps.pipeline.filing_fetcher.main +python -m apps.pipeline.event_parser.main +python -m apps.pipeline.feature_builder.main +python -m apps.pipeline.label_generator.main + +# 3) 백테스트용 Parquet 스냅샷 내보내기 +python -m apps.pipeline.dataset_export.main +``` + +### 백테스트 실행 + +```bash +# 3-split 백테스트 (train/valid/test) +for split in train valid test; do + python -m apps.backtester.run \ + --manifest configs/experiments/pead_midcap_step14_score65.json \ + --split $split \ + --snapshot-dir data/datasets/snapshots \ + --output-root runs/midcap_steps +done +``` + +> **주의:** Stock Oracle API는 단일 스레드이므로 백테스트를 **순차적으로** 실행해야 합니다. +> 병렬 실행 시 API가 과부하되어 연결이 끊깁니다. + +### 실험 결과 기록 + +```bash +# Journal에 기록 +python -m apps.tracker.cli record \ + --journal-dir journal/ \ + --runs-dir runs/midcap_steps/ \ + --experiment pead_midcap_step14_score65 \ + --hypothesis "Score threshold 0.60→0.65" \ + --baseline pead_midcap_step13_best \ + --verdict better \ + --reasoning "Test SQS 64.2 > 57.7" \ + --next "Exit tuning on top of score 0.65" + +# 리더보드 확인 +python -m apps.tracker.cli leaderboard --journal-dir journal/ +``` + +### 테스트 + +```bash +# 단위 테스트 (빠름, 외부 의존성 없음) pytest tests/unit/ -v -# Backtest module tests only +# 백테스트 모듈 테스트 pytest tests/unit/backtest/ -v -# Integration tests (requires PostgreSQL) +# 통합 테스트 (PostgreSQL 필요) pytest tests/integration/ -v -# Full CI check (lint + typecheck + unit tests) +# 전체 CI make ci ``` -## Data Sources - -| Source | Role | Cost | -|--------|------|------| -| **SEC EDGAR** | Primary event source (8-K, 10-Q, 6-K filings) | Free | -| **Stock Oracle API** | Market data (OHLCV bars, company info) | Internal | -| **FRED** | Macro regime indicators (rates, spreads) | Free | -| **FINRA** | Short sale volume (crowding signal) | Free | -| Wikimedia | Retail attention via pageviews | Free | -| YouTube | Channel-based attention tracking | Free (quota limited) | -| Yahoo RSS | Headline burst detection | Free | - -## Tech Stack - -| Component | Technology | -|-----------|-----------| -| Language | Python 3.11+ | -| Models | Pydantic v2 | -| Database | PostgreSQL 16 + async SQLAlchemy | -| Research data | DuckDB + Parquet | -| Containers | Docker Compose | -| Linting | Ruff | -| Type checking | MyPy (strict) | -| Testing | Pytest + asyncio | -| Logging | structlog (JSON) | +--- + +## 13. 기술 스택 + +| 구성 요소 | 기술 | +|-----------|------| +| 언어 | Python 3.11+ | +| 도메인 모델 | Pydantic v2 | +| 운영 DB | PostgreSQL 16 + async SQLAlchemy + asyncpg | +| 연구 데이터 | DuckDB + Apache Parquet | +| 마이그레이션 | Alembic | +| HTTP 클라이언트 | httpx (비동기) | +| 컨테이너 | Docker Compose | +| 린트 | Ruff (line 100, Python 3.11+) | +| 타입 체크 | MyPy (strict mode) | +| 테스트 | Pytest + pytest-asyncio (270+ 테스트) | +| 로깅 | structlog (JSON) | +| LLM | Ollama (인프라 구현, 파서 비활성화) | +| 문서 파싱 | BeautifulSoup4 + 규칙 기반 + LLM | +| 거래일 캘린더 | exchange-calendars | + +--- + +## 14. 데이터 소스 + +| 소스 | 역할 | 계층 | 비용 | +|------|------|------|------| +| **SEC EDGAR** | 이벤트 원천 (8-K, 10-Q, 6-K 공시) | Core | 무료 | +| **Stock Oracle API** | 시장 데이터 (OHLCV, 기업정보, 재무) | Core | 내부 | +| **FRED** | 거시 레짐 (금리, 스프레드, 경기 지표) | Core | 무료 | +| **FINRA** | 공매도 잔량 (crowding 신호) | 보조 | 무료 | + +### 데이터 정책 + +- **모든 시장 데이터는 Stock Oracle API를 통해서만 접근** — 직접 외부 API 호출 금지 +- API 부재 기능은 구현 대신 보고 +- 유료 데이터 소스 사용 금지 +- LLM은 문서 해석기로만 사용, 가격 예측 금지 + +--- ## License diff --git a/apps/pipeline/dataset_export/main.py b/apps/pipeline/dataset_export/main.py index a93e97d..85b6d3a 100644 --- a/apps/pipeline/dataset_export/main.py +++ b/apps/pipeline/dataset_export/main.py @@ -5,6 +5,8 @@ import argparse import asyncio import json +import yaml + from libs.common.config import get_settings from libs.common.logging import configure_logging, get_logger from libs.db.session import get_session @@ -18,6 +20,8 @@ async def run_dataset_export( split_policy: str, output_dir: str, feature_versions: list[str] | None = None, + label_version: str = "label-2.0.0", + symbols: list[str] | None = None, ) -> dict: async with get_session() as session: manifest = await export_dataset_snapshot( @@ -26,6 +30,8 @@ async def run_dataset_export( split_policy=split_policy, output_dir=output_dir, feature_versions=feature_versions, + label_version=label_version, + symbols=symbols, ) return manifest @@ -49,18 +55,37 @@ def main() -> None: default=None, help="Feature versions to merge (e.g. market_v1 event_v1 financial_v1)", ) + parser.add_argument( + "--label-version", + default="label-2.0.0", + help="Label version to export (default: label-2.0.0)", + ) + parser.add_argument( + "--symbols-file", + default=None, + help="YAML file with 'symbols' list to filter export (e.g. configs/symbols_midcap.yaml)", + ) parser.add_argument("--json", action="store_true", help="Print manifest JSON to stdout") args = parser.parse_args() settings = get_settings() configure_logging(settings.log_level) + symbols = None + if args.symbols_file: + with open(args.symbols_file) as f: + cfg = yaml.safe_load(f) or {} + symbols = [s for s in cfg.get("symbols", []) if isinstance(s, str)] + print(f"Filtering to {len(symbols)} symbols from {args.symbols_file}") + manifest = asyncio.run( run_dataset_export( snapshot_id=args.snapshot_id, split_policy=args.split_policy, output_dir=args.output_dir, feature_versions=args.feature_versions, + label_version=args.label_version, + symbols=symbols, ) ) diff --git a/apps/pipeline/event_parser/main.py b/apps/pipeline/event_parser/main.py index 8b13d5a..2773290 100644 --- a/apps/pipeline/event_parser/main.py +++ b/apps/pipeline/event_parser/main.py @@ -1,4 +1,10 @@ -"""Event Parser: parse exhibit text → events + event_parses.""" +"""Event Parser: parse exhibit text → events + event_parses. + +Supports two modes: + - Normal: parse documents with parsed_status="ready_for_parse" + - Reparse (--reparse): re-classify existing events using item_numbers + from the Document table, updating event_type in Event rows. +""" from __future__ import annotations import argparse @@ -6,7 +12,7 @@ import asyncio import datetime as dt import uuid -from sqlalchemy import select +from sqlalchemy import select, update from libs.common.config import get_settings from libs.common.file_store import read_exhibit @@ -15,7 +21,8 @@ from libs.common.ids import new_job_run_id from libs.common.logging import bind_job_run_id, configure_logging, get_logger from libs.db.models import Document, Event, EventParse, JobRun from libs.db.session import get_session -from libs.parser.rule_parser import PARSER_VERSION, SCHEMA_VERSION, RuleBasedParser +from libs.oracle_client import FilingsService, make_oracle_client +from libs.parser.rule_parser import PARSER_VERSION, SCHEMA_VERSION, RuleBasedParser, _classify_event_type from libs.parser.schema_validator import validate_parser_output from libs.parser.text_normalizer import normalize_text @@ -76,103 +83,109 @@ async def run_event_parser(run_id: str) -> dict[str, int]: doc.accepted_at_utc.isoformat() if doc.accepted_at_utc else None ), "form_type": doc.form_type, + "item_numbers": doc.item_numbers or [], } try: - output = _parser.parse( - document_id=doc.document_id, - form_type=doc.form_type, - text=normalized, - metadata=metadata, - ) - output_dict = output.model_dump() - errors = validate_parser_output(output_dict) - - if errors: - logger.warning( - "parse_validation_failed", + # Use savepoint so one failure doesn't break the session + async with session.begin_nested(): + output = _parser.parse( document_id=doc.document_id, - errors=errors[:3], - ) - # Store invalid parse, no event row - event_id_str = make_event_id(doc.document_id, output.event_type) - # Create a minimal event row first (needed for FK) - event = Event( - event_id=event_id_str, - primary_document_id=doc.document_id, - issuer_id=doc.issuer_id, - symbol_id=doc.symbol_id, - event_type=output.event_type, - event_direction=output.event_direction, - event_date=doc.filing_date, - filed_at_utc=doc.accepted_at_utc, - parser_version=PARSER_VERSION, - parse_confidence=output.confidence.overall, - status="rejected", - ) - session.add(event) - await session.flush() - - parse_row = EventParse( - event_id=event_id_str, - parser_kind="rule", - parser_version=PARSER_VERSION, - schema_version=SCHEMA_VERSION, - output_json=output_dict, - validation_status="invalid", - validation_errors={"errors": errors}, - ) - session.add(parse_row) - doc.parsed_status = "failed" - stats["invalid"] += 1 - else: - event_id_str = make_event_id(doc.document_id, output.event_type) - - # Check duplicate event - existing_evt = await session.execute( - select(Event).where(Event.event_id == event_id_str) + form_type=doc.form_type, + text=normalized, + metadata=metadata, ) - if existing_evt.scalar_one_or_none() is not None: - logger.info("event_already_exists", event_id=event_id_str) + output_dict = output.model_dump() + # Sanitize null bytes that PostgreSQL JSONB rejects + import json as _json + _raw = _json.dumps(output_dict) + if "\x00" in _raw or "\\u0000" in _raw: + _raw = _raw.replace("\x00", "").replace("\\u0000", "") + output_dict = _json.loads(_raw) + errors = validate_parser_output(output_dict) + + if errors: + logger.warning( + "parse_validation_failed", + document_id=doc.document_id, + errors=errors[:3], + ) + event_id_str = make_event_id(doc.document_id, output.event_type) + event = Event( + event_id=event_id_str, + primary_document_id=doc.document_id, + issuer_id=doc.issuer_id, + symbol_id=doc.symbol_id, + event_type=output.event_type, + event_direction=output.event_direction, + event_date=doc.filing_date, + filed_at_utc=doc.accepted_at_utc, + parser_version=PARSER_VERSION, + parse_confidence=output.confidence.overall, + status="rejected", + ) + session.add(event) + await session.flush() + + parse_row = EventParse( + event_id=event_id_str, + parser_kind="rule", + parser_version=PARSER_VERSION, + schema_version=SCHEMA_VERSION, + output_json=output_dict, + validation_status="invalid", + validation_errors={"errors": errors}, + ) + session.add(parse_row) + doc.parsed_status = "failed" + stats["invalid"] += 1 + else: + event_id_str = make_event_id(doc.document_id, output.event_type) + + existing_evt = await session.execute( + select(Event).where(Event.event_id == event_id_str) + ) + if existing_evt.scalar_one_or_none() is not None: + logger.info("event_already_exists", event_id=event_id_str) + doc.parsed_status = "succeeded" + stats["valid"] += 1 + continue + + event = Event( + event_id=event_id_str, + primary_document_id=doc.document_id, + issuer_id=doc.issuer_id, + symbol_id=doc.symbol_id, + event_type=output.event_type, + event_direction=output.event_direction, + event_date=doc.filing_date, + filed_at_utc=doc.accepted_at_utc, + parser_version=PARSER_VERSION, + parse_confidence=output.confidence.overall, + status="pending", + ) + session.add(event) + await session.flush() + + parse_row = EventParse( + event_id=event_id_str, + parser_kind="rule", + parser_version=PARSER_VERSION, + schema_version=SCHEMA_VERSION, + output_json=output_dict, + validation_status="valid", + validation_errors=None, + ) + session.add(parse_row) doc.parsed_status = "succeeded" stats["valid"] += 1 - continue - event = Event( - event_id=event_id_str, - primary_document_id=doc.document_id, - issuer_id=doc.issuer_id, - symbol_id=doc.symbol_id, - event_type=output.event_type, - event_direction=output.event_direction, - event_date=doc.filing_date, - filed_at_utc=doc.accepted_at_utc, - parser_version=PARSER_VERSION, - parse_confidence=output.confidence.overall, - status="pending", - ) - session.add(event) - await session.flush() - - parse_row = EventParse( - event_id=event_id_str, - parser_kind="rule", - parser_version=PARSER_VERSION, - schema_version=SCHEMA_VERSION, - output_json=output_dict, - validation_status="valid", - validation_errors=None, - ) - session.add(parse_row) - doc.parsed_status = "succeeded" - stats["valid"] += 1 - - logger.info( - "event_created", - event_id=event_id_str, - event_type=output.event_type, - confidence=output.confidence.overall, - ) + logger.info( + "event_created", + event_id=event_id_str, + event_type=output.event_type, + confidence=output.confidence.overall, + ) except Exception as exc: logger.error( @@ -195,16 +208,170 @@ async def run_event_parser(run_id: str) -> dict[str, int]: return stats +async def reparse_events(run_id: str) -> dict[str, int]: + """Re-classify existing events by fetching item_numbers from Oracle API. + + For each Document with a linked Event where event_type='unknown': + 1. Look up filing in Oracle API to get item_numbers + 2. Store item_numbers in Document row + 3. Re-classify event_type based on items + 4. Update Event row with new event_type and re-parse with exhibit text + """ + settings = get_settings() + app_config = settings.get_app_config() + exhibit_types = app_config.get("pipeline", {}).get("exhibit_types", ["EX-99.1"]) + + stats = {"seen": 0, "updated": 0, "skipped": 0, "errors": 0} + + async with make_oracle_client() as client: + svc = FilingsService(client) + + async with get_session() as session: + job = JobRun( + job_run_id=uuid.UUID(run_id), + job_name="event_parser_reparse", + source_name="oracle", + run_date=dt.date.today(), + status="running", + ) + session.add(job) + await session.flush() + + # Find all events with event_type='unknown' + result = await session.execute( + select(Event, Document) + .join(Document, Event.primary_document_id == Document.document_id) + .where(Event.event_type == "unknown") + ) + pairs = result.all() + stats["seen"] = len(pairs) + + if not pairs: + logger.info("reparse_nothing_to_do") + job.status = "succeeded" + job.finished_at_utc = dt.datetime.now(tz=dt.UTC) + job.records_seen = 0 + return stats + + # Step 1: Fetch item_numbers from Oracle for all documents missing them + docs_needing_items = { + doc.document_id: doc + for _, doc in pairs + if not doc.item_numbers + } + + if docs_needing_items: + logger.info("reparse_fetching_items", count=len(docs_needing_items)) + + for doc in docs_needing_items.values(): + if not doc.accession_no: + continue + try: + items = await svc.get_filing_items(doc.accession_no) + if items: + doc.item_numbers = items + logger.info( + "reparse_items_found", + accession_no=doc.accession_no, + items=items, + ) + except Exception as exc: + logger.warning( + "reparse_oracle_error", + accession_no=doc.accession_no, + error=str(exc), + ) + + await session.flush() + + # Step 2: Re-parse each event with updated item_numbers + for event, doc in pairs: + try: + items = doc.item_numbers or [] + new_event_type = _classify_event_type(items) + + if new_event_type == event.event_type: + stats["skipped"] += 1 + continue + + # Also re-parse the full exhibit text with items + text: str | None = None + for exhibit_type in exhibit_types: + try: + text = read_exhibit(doc.accession_no, exhibit_type) + break + except FileNotFoundError: + continue + + if text: + normalized = normalize_text(text) + metadata = { + "filing_date": doc.filing_date.isoformat(), + "accepted_at_utc": ( + doc.accepted_at_utc.isoformat() + if doc.accepted_at_utc + else None + ), + "form_type": doc.form_type, + "item_numbers": items, + } + output = _parser.parse( + document_id=doc.document_id, + form_type=doc.form_type, + text=normalized, + metadata=metadata, + ) + event.event_type = output.event_type + event.event_direction = output.event_direction + event.parse_confidence = output.confidence.overall + else: + # No exhibit text available, just update event_type from items + event.event_type = new_event_type + + stats["updated"] += 1 + logger.info( + "reparse_event_updated", + event_id=event.event_id, + old_type="unknown", + new_type=event.event_type, + ) + + except Exception as exc: + logger.error( + "reparse_error", + event_id=event.event_id, + error=str(exc), + ) + stats["errors"] += 1 + + job.status = "succeeded" if stats["errors"] == 0 else "partial" + job.finished_at_utc = dt.datetime.now(tz=dt.UTC) + job.records_seen = stats["seen"] + job.records_written = stats["updated"] + job.error_count = stats["errors"] + + logger.info("reparse_done", **stats) + return stats + + def main() -> None: parser = argparse.ArgumentParser(description="Event Parser") parser.add_argument("--run-id", default=new_job_run_id()) + parser.add_argument( + "--reparse", + action="store_true", + help="Re-classify existing events with event_type='unknown' using Oracle API item numbers", + ) args = parser.parse_args() settings = get_settings() configure_logging(settings.log_level) bind_job_run_id(args.run_id) - asyncio.run(run_event_parser(args.run_id)) + if args.reparse: + asyncio.run(reparse_events(args.run_id)) + else: + asyncio.run(run_event_parser(args.run_id)) if __name__ == "__main__": diff --git a/apps/pipeline/feature_builder/main.py b/apps/pipeline/feature_builder/main.py index 30fbbb5..8d08f9e 100644 --- a/apps/pipeline/feature_builder/main.py +++ b/apps/pipeline/feature_builder/main.py @@ -14,9 +14,7 @@ from libs.common.logging import bind_job_run_id, configure_logging, get_logger from libs.db.models import Event, JobRun from libs.db.session import get_session from libs.features.builder import build_features_for_event -from libs.oracle_client.client import make_oracle_client -from libs.oracle_client.financial import FinancialService -from libs.oracle_client.price import PriceService +from libs.oracle_client import FinancialService, PriceService, make_oracle_client logger = get_logger(__name__) diff --git a/apps/pipeline/filing_fetcher/main.py b/apps/pipeline/filing_fetcher/main.py index 9d87f44..7bfcbab 100644 --- a/apps/pipeline/filing_fetcher/main.py +++ b/apps/pipeline/filing_fetcher/main.py @@ -14,9 +14,8 @@ from libs.common.ids import new_job_run_id from libs.common.logging import bind_job_run_id, configure_logging, get_logger from libs.db.models import Document, ExhibitCache, JobRun from libs.db.session import get_session -from libs.oracle_client.client import make_oracle_client +from libs.oracle_client import FilingsService, make_oracle_client from libs.oracle_client.exceptions import OracleNotFoundError -from libs.oracle_client.filings import FilingsService logger = get_logger(__name__) @@ -28,6 +27,8 @@ async def fetch_exhibits(run_id: str) -> dict[str, int]: stats = {"seen": 0, "written": 0, "skipped": 0, "errors": 0} + batch_size = 100 + async with make_oracle_client() as client: svc = FilingsService(client) @@ -41,6 +42,7 @@ async def fetch_exhibits(run_id: str) -> dict[str, int]: ) session.add(job) await session.flush() + await session.commit() result = await session.execute( select(Document).where(Document.parsed_status == "pending") @@ -48,7 +50,7 @@ async def fetch_exhibits(run_id: str) -> dict[str, int]: docs = result.scalars().all() stats["seen"] = len(docs) - for doc in docs: + for idx, doc in enumerate(docs, 1): if not doc.accession_no: stats["skipped"] += 1 continue @@ -65,7 +67,10 @@ async def fetch_exhibits(run_id: str) -> dict[str, int]: continue try: - response = await svc.get_exhibit(doc.accession_no, exhibit_type) + response = await asyncio.wait_for( + svc.get_exhibit(doc.accession_no, exhibit_type), + timeout=30.0, + ) checksum = write_exhibit( doc.accession_no, exhibit_type, response.content ) @@ -112,9 +117,41 @@ async def fetch_exhibits(run_id: str) -> dict[str, int]: ) stats["errors"] += 1 - if fetched_any: - doc.parsed_status = "ready_for_parse" - doc.updated_at_utc = dt.datetime.now(tz=dt.UTC) + # Extract item_numbers from SGML header if not already set + if not doc.item_numbers: + try: + items = await asyncio.wait_for( + svc.get_filing_items(doc.accession_no), + timeout=15.0, + ) + if items: + doc.item_numbers = items + logger.info( + "item_numbers_extracted", + accession_no=doc.accession_no, + items=items, + ) + except Exception as exc: + logger.debug( + "item_numbers_extraction_failed", + accession_no=doc.accession_no, + error=str(exc), + ) + + # Always advance to ready_for_parse (exhibit may not exist) + doc.parsed_status = "ready_for_parse" + doc.updated_at_utc = dt.datetime.now(tz=dt.UTC) + + # Commit in batches to preserve progress + if idx % batch_size == 0: + await session.commit() + logger.info( + "batch_committed", + processed=idx, + total=stats["seen"], + written=stats["written"], + errors=stats["errors"], + ) job.status = "succeeded" if stats["errors"] == 0 else "partial" job.finished_at_utc = dt.datetime.now(tz=dt.UTC) diff --git a/apps/pipeline/filing_poller/main.py b/apps/pipeline/filing_poller/main.py index d0f941d..e2c1916 100644 --- a/apps/pipeline/filing_poller/main.py +++ b/apps/pipeline/filing_poller/main.py @@ -14,8 +14,7 @@ from libs.common.ids import new_job_run_id from libs.common.logging import bind_job_run_id, configure_logging, get_logger from libs.db.models import Document, IssuerMaster, JobRun, SymbolMaster from libs.db.session import get_session -from libs.oracle_client.client import make_oracle_client -from libs.oracle_client.filings import FilingsService +from libs.oracle_client import FilingsService, make_oracle_client logger = get_logger(__name__) @@ -103,6 +102,7 @@ async def poll_filings( else None ), primary_document_name=filing.primary_document, + item_numbers=filing.items if filing.items else None, parsed_status="pending", ) session.add(doc) diff --git a/apps/pipeline/label_generator/main.py b/apps/pipeline/label_generator/main.py index aed1294..f003caa 100644 --- a/apps/pipeline/label_generator/main.py +++ b/apps/pipeline/label_generator/main.py @@ -14,8 +14,7 @@ from libs.common.logging import bind_job_run_id, configure_logging, get_logger from libs.db.models import Event, EventLabel, JobRun, SymbolMaster from libs.db.session import get_session from libs.labeler.label_generator import LABEL_VERSION, generate_labels -from libs.oracle_client.client import make_oracle_client -from libs.oracle_client.price import PriceService +from libs.oracle_client import PriceService, make_oracle_client logger = get_logger(__name__) diff --git a/apps/sync/issuer_sync/main.py b/apps/sync/issuer_sync/main.py index 8b002da..4430fa3 100644 --- a/apps/sync/issuer_sync/main.py +++ b/apps/sync/issuer_sync/main.py @@ -13,8 +13,7 @@ from libs.common.ids import issuer_id_from_cik, new_job_run_id, symbol_id_from_t from libs.common.logging import bind_job_run_id, configure_logging, get_logger from libs.db.models import IssuerMaster, JobRun, SymbolMaster from libs.db.session import get_session -from libs.oracle_client.client import make_oracle_client -from libs.oracle_client.financial import FinancialService +from libs.oracle_client import FinancialService, make_oracle_client logger = get_logger(__name__) diff --git a/apps/sync/macro_sync/main.py b/apps/sync/macro_sync/main.py index 7e0e121..63aa396 100644 --- a/apps/sync/macro_sync/main.py +++ b/apps/sync/macro_sync/main.py @@ -14,8 +14,7 @@ from libs.common.ids import new_job_run_id from libs.common.logging import bind_job_run_id, configure_logging, get_logger from libs.db.models import JobRun, MacroObservation, MacroSeries, SyncCheckpoint from libs.db.session import get_session -from libs.oracle_client.client import make_oracle_client -from libs.oracle_client.fred import FredService +from libs.oracle_client import FredService, make_oracle_client logger = get_logger(__name__) diff --git a/apps/sync/short_volume_sync/main.py b/apps/sync/short_volume_sync/main.py index 8fce00f..a6e5143 100644 --- a/apps/sync/short_volume_sync/main.py +++ b/apps/sync/short_volume_sync/main.py @@ -13,8 +13,7 @@ from libs.common.ids import new_job_run_id from libs.common.logging import bind_job_run_id, configure_logging, get_logger from libs.db.models import JobRun, ShortSaleDaily, SyncCheckpoint from libs.db.session import get_session -from libs.oracle_client.client import make_oracle_client -from libs.oracle_client.finra import FinraService +from libs.oracle_client import FinraService, make_oracle_client logger = get_logger(__name__) diff --git a/apps/tools/__init__.py b/apps/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/dev/analysis/concurrent_position_analysis.py b/apps/tools/concurrent_position_analysis.py similarity index 100% rename from dev/analysis/concurrent_position_analysis.py rename to apps/tools/concurrent_position_analysis.py diff --git a/apps/tools/coverage_check.py b/apps/tools/coverage_check.py new file mode 100644 index 0000000..212d7c2 --- /dev/null +++ b/apps/tools/coverage_check.py @@ -0,0 +1,314 @@ +"""Coverage Check: Validate Stock Oracle data availability for symbol universe. + +Checks each ticker for: + 1. Price data coverage (daily bars since start_date, require 90%+ trading days) + 2. Filing data (8-K filings, require 3+ filings) + 3. Company info (name, CIK, sector must exist) + 4. Market cap range ($500M - $10B for small/mid-cap) + +Outputs CSV report + pass/fail summary. Optionally writes a filtered YAML +with only passing tickers. + +Usage: + python -m dev.analysis.coverage_check \ + --symbols-file configs/symbols_smallmid.yaml \ + --start-date 2022-07-01 \ + [--output-dir ./data/analysis] \ + [--write-filtered] +""" +from __future__ import annotations + +import argparse +import asyncio +import csv +from datetime import datetime +from pathlib import Path +from typing import Any + +import yaml + +from libs.oracle_client import FilingsService, FinancialService, PriceService, make_oracle_client + + +# --- Constants --- +MIN_MARKET_CAP = 5e8 # $500M +MAX_MARKET_CAP = 1e10 # $10B +MIN_FILINGS = 3 +MIN_PRICE_COVERAGE = 0.90 # 90% of expected trading days +APPROX_TRADING_DAYS_PER_YEAR = 252 + + +def _load_symbols(path: str) -> list[str]: + with open(path) as f: + cfg = yaml.safe_load(f) or {} + symbols = cfg.get("symbols", []) + # Deduplicate while preserving order + seen: set[str] = set() + unique: list[str] = [] + for s in symbols: + if s not in seen: + seen.add(s) + unique.append(s) + return unique + + +async def _check_ticker( + ticker: str, + price_svc: PriceService, + filings_svc: FilingsService, + financial_svc: FinancialService, + start_date: str, + expected_trading_days: int, +) -> dict[str, Any]: + """Run all coverage checks for a single ticker.""" + result: dict[str, Any] = { + "ticker": ticker, + "price_bars": 0, + "price_coverage": 0.0, + "price_pass": False, + "filing_count": 0, + "filing_pass": False, + "has_name": False, + "has_cik": False, + "has_sector": False, + "sector": None, + "info_pass": False, + "market_cap": None, + "market_cap_pass": False, + "overall_pass": False, + "error": None, + } + + # 1. Price data + try: + end_date = datetime.now().strftime("%Y-%m-%d") + price_resp = await price_svc.get_daily_bars(ticker, start=start_date, end=end_date) + n_bars = len(price_resp.bars) + result["price_bars"] = n_bars + coverage = n_bars / expected_trading_days if expected_trading_days > 0 else 0 + result["price_coverage"] = round(coverage, 3) + result["price_pass"] = coverage >= MIN_PRICE_COVERAGE + except Exception as e: + result["error"] = f"price: {e}" + + # 2. Filings (8-K) + try: + filings_resp = await filings_svc.search_filings( + ticker, form_type="8-K", start_date=start_date + ) + n_filings = len(filings_resp.filings) + result["filing_count"] = n_filings + result["filing_pass"] = n_filings >= MIN_FILINGS + except Exception as e: + err = f"filings: {e}" + result["error"] = err if not result["error"] else f"{result['error']}; {err}" + + # 3. Company info + market cap + try: + info = await financial_svc.get_company_info(ticker) + result["has_name"] = bool(info.name) + result["has_cik"] = bool(info.cik) + result["has_sector"] = bool(info.sector) + result["sector"] = info.sector + result["info_pass"] = all([info.name, info.cik, info.sector]) + if info.market_cap is not None: + result["market_cap"] = info.market_cap + result["market_cap_pass"] = MIN_MARKET_CAP <= info.market_cap <= MAX_MARKET_CAP + else: + # If market_cap not available, pass this check (can't verify) + result["market_cap_pass"] = True + except Exception as e: + err = f"info: {e}" + result["error"] = err if not result["error"] else f"{result['error']}; {err}" + # If company info fails entirely, market_cap check is inconclusive + result["market_cap_pass"] = True + + # Overall + result["overall_pass"] = all([ + result["price_pass"], + result["filing_pass"], + result["info_pass"], + result["market_cap_pass"], + ]) + + return result + + +async def run_coverage_check( + symbols: list[str], + start_date: str, + output_dir: Path, + write_filtered: bool = False, + symbols_file: str | None = None, + concurrency: int = 5, +) -> None: + """Run coverage check for all symbols.""" + output_dir.mkdir(parents=True, exist_ok=True) + + # Calculate expected trading days + start_dt = datetime.strptime(start_date, "%Y-%m-%d") + years_elapsed = (datetime.now() - start_dt).days / 365.25 + expected_trading_days = int(years_elapsed * APPROX_TRADING_DAYS_PER_YEAR) + + print(f"\n{'='*80}") + print(f"Coverage Check — {len(symbols)} tickers, start_date={start_date}") + print(f"Expected trading days: ~{expected_trading_days}, concurrency={concurrency}") + print(f"{'='*80}\n") + + results: list[dict[str, Any]] = [] + sem = asyncio.Semaphore(concurrency) + counter = {"done": 0} + + async def _check_with_sem(ticker: str) -> dict[str, Any]: + async with sem: + result = await _check_ticker( + ticker, price_svc, filings_svc, financial_svc, + start_date, expected_trading_days, + ) + counter["done"] += 1 + status = "PASS" if result["overall_pass"] else "FAIL" + mcap_str = f"${result['market_cap']/1e6:.0f}M" if result["market_cap"] else "N/A" + print( + f" [{counter['done']:3d}/{len(symbols)}] {ticker:<6s} " + f"price={result['price_bars']:>4d} bars ({result['price_coverage']:.0%}) " + f"filings={result['filing_count']:>3d} " + f"mcap={mcap_str:>8s} " + f"-> {status}" + ) + if result["error"]: + print(f" ! {result['error']}") + return result + + async with make_oracle_client() as client: + price_svc = PriceService(client) + filings_svc = FilingsService(client) + financial_svc = FinancialService(client) + + tasks = [_check_with_sem(ticker) for ticker in symbols] + results_unordered = await asyncio.gather(*tasks) + + # Preserve original symbol order + ticker_to_result = {r["ticker"]: r for r in results_unordered} + results = [ticker_to_result[s] for s in symbols] + + # Write CSV + csv_path = output_dir / "coverage_check.csv" + if results: + fieldnames = list(results[0].keys()) + with open(csv_path, "w", newline="") as f: + writer = csv.DictWriter(f, fieldnames=fieldnames) + writer.writeheader() + writer.writerows(results) + + # Summary + passed = [r for r in results if r["overall_pass"]] + failed = [r for r in results if not r["overall_pass"]] + + print(f"\n{'='*80}") + print(f"Summary") + print(f"{'='*80}") + print(f" Total: {len(results)}") + print(f" Passed: {len(passed)}") + print(f" Failed: {len(failed)}") + + if failed: + # Breakdown of failure reasons + price_fail = sum(1 for r in failed if not r["price_pass"]) + filing_fail = sum(1 for r in failed if not r["filing_pass"]) + info_fail = sum(1 for r in failed if not r["info_pass"]) + mcap_fail = sum(1 for r in failed if not r["market_cap_pass"]) + print(f"\n Failure breakdown:") + print(f" Price coverage < {MIN_PRICE_COVERAGE:.0%}: {price_fail}") + print(f" Filings < {MIN_FILINGS}: {filing_fail}") + print(f" Missing company info: {info_fail}") + print(f" Market cap out of range: {mcap_fail}") + print(f"\n Failed tickers: {', '.join(r['ticker'] for r in failed)}") + + # Sector distribution of passed tickers + sector_counts: dict[str, int] = {} + for r in passed: + sector = r.get("sector") or "Unknown" + sector_counts[sector] = sector_counts.get(sector, 0) + 1 + if sector_counts: + print(f"\n Sector distribution (passed):") + for sector, count in sorted(sector_counts.items(), key=lambda x: -x[1]): + print(f" {sector:<30s} {count:>3d}") + + print(f"\n CSV report: {csv_path}") + + # Write filtered YAML + if write_filtered and passed: + filtered_path = output_dir / "symbols_smallmid_filtered.yaml" + filtered_symbols = [r["ticker"] for r in passed] + with open(filtered_path, "w") as f: + yaml.dump( + {"symbols": filtered_symbols}, + f, + default_flow_style=False, + sort_keys=False, + ) + print(f" Filtered YAML ({len(filtered_symbols)} tickers): {filtered_path}") + if symbols_file: + print(f"\n To use filtered list:") + print(f" cp {filtered_path} {symbols_file}") + + # Go/No-Go + print(f"\n{'='*80}") + if len(passed) >= 150: + print(f"GO: {len(passed)} tickers passed (>= 150 threshold)") + elif len(passed) >= 100: + print(f"MARGINAL: {len(passed)} tickers passed (100-150 range, consider adding more)") + else: + print(f"NO-GO: Only {len(passed)} tickers passed (< 100, need more tickers)") + print(f"{'='*80}\n") + + +def main() -> None: + parser = argparse.ArgumentParser(description="Coverage Check for Symbol Universe") + parser.add_argument( + "--symbols-file", + required=True, + help="Path to symbols YAML file", + ) + parser.add_argument( + "--start-date", + default="2022-07-01", + help="Start date for data coverage check (YYYY-MM-DD)", + ) + parser.add_argument( + "--output-dir", + default="./data/analysis", + help="Output directory for CSV report", + ) + parser.add_argument( + "--write-filtered", + action="store_true", + help="Write filtered YAML with only passing tickers", + ) + parser.add_argument( + "--concurrency", + type=int, + default=5, + help="Max concurrent API calls (default: 5)", + ) + args = parser.parse_args() + + symbols = _load_symbols(args.symbols_file) + if not symbols: + print(f"No symbols found in {args.symbols_file}") + return + + asyncio.run( + run_coverage_check( + symbols=symbols, + start_date=args.start_date, + output_dir=Path(args.output_dir), + write_filtered=args.write_filtered, + symbols_file=args.symbols_file, + concurrency=args.concurrency, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/apps/tools/entry_timing_experiment.py b/apps/tools/entry_timing_experiment.py new file mode 100644 index 0000000..78bf196 --- /dev/null +++ b/apps/tools/entry_timing_experiment.py @@ -0,0 +1,391 @@ +"""Diagnostic Experiments: Fixed-holding & entry-timing analysis. + +Experiment 1 — Stop-off + fixed holding period exit (3/5/10/20d) + Purpose: Does the raw signal carry time-based alpha? + Method: Use pre-computed fwd_return_Xd (no stop, no target, pure hold). + Report: Mean return, median, win rate, by score bucket and event type. + +Experiment 2 — Reaction-day close entry vs next-open entry + Purpose: Is the overnight gap eating the edge? + Method: Compare fwd_return_Xd (next-open base) with + close_entry_return = (entry_price / event_close) * (1 + fwd_return_Xd) - 1 + Constraint: Only pre-market/intraday filings (reaction_date == event_date) + are eligible for close entry; after-close filings shown separately. + +Usage: + python -m dev.analysis.entry_timing_experiment \ + --snapshot-dir data/datasets/snapshots/a6687401-afdd-4bb7-9bb1-4cd32c5190bb \ + --split train +""" +from __future__ import annotations + +import argparse +import statistics +from pathlib import Path +from typing import Any + +import pyarrow.parquet as pq + +from libs.backtest.scoring import compute_entry_score + + +FWD_HORIZONS = ["fwd_return_3d", "fwd_return_5d", "fwd_return_10d", "fwd_return_20d"] +HORIZON_LABELS = ["3d", "5d", "10d", "20d"] + + +def _load_rows(snapshot_dir: Path, split: str) -> list[dict[str, Any]]: + table = pq.read_table(snapshot_dir / f"{split}.parquet") + return table.to_pylist() + + +def _stats(values: list[float]) -> dict[str, float | None]: + if not values: + return {"mean": None, "median": None, "win_rate": None, "n": 0} + return { + "mean": statistics.mean(values), + "median": statistics.median(values), + "win_rate": sum(1 for v in values if v > 0) / len(values), + "n": len(values), + } + + +def _fmt(val: float | None, pct: bool = True) -> str: + if val is None: + return " N/A" + if pct: + return f"{val:+.2%}" + return f"{val:.1%}" + + +def _score_bucket(score: float) -> str: + if score < 0.3: + return "low(<0.3)" + if score < 0.5: + return "mid(0.3-0.5)" + if score < 0.7: + return "high(0.5-0.7)" + return "top(>=0.7)" + + +BUCKET_ORDER = ["low(<0.3)", "mid(0.3-0.5)", "high(0.5-0.7)", "top(>=0.7)"] + + +def _is_intraday_filing(row: dict) -> bool: + """Pre-market / regular-hours filing: reaction_date == event_date.""" + return row.get("reaction_date") == row.get("event_date") + + +# ── Experiment 1 ────────────────────────────────────────────────────── + +def experiment_1(rows: list[dict[str, Any]]) -> None: + """Stop-off + fixed holding period: raw forward returns.""" + print(f"\n{'='*100}") + print("EXPERIMENT 1: Stop-off + Fixed Holding Period Exit") + print(f"{'='*100}") + + # Enrich with score + for r in rows: + r["_score"] = compute_entry_score(r) + r["_bucket"] = _score_bucket(r["_score"]) + + # ── 1a. ALL events ── + print(f"\n--- 1a. ALL events (N={len(rows)}) ---") + _print_horizon_table("ALL", rows) + + # ── 1b. By score bucket ── + print(f"\n--- 1b. By score bucket ---") + by_bucket: dict[str, list] = {b: [] for b in BUCKET_ORDER} + for r in rows: + by_bucket[r["_bucket"]].append(r) + + header = f"{'Bucket':<16} {'N':>5}" + for lbl in HORIZON_LABELS: + header += f" {lbl+'_mean':>8} {lbl+'_med':>8} {lbl+'_wr':>7}" + print(header) + print("-" * len(header)) + + for bucket in BUCKET_ORDER: + items = by_bucket[bucket] + line = f"{bucket:<16} {len(items):>5}" + for h in FWD_HORIZONS: + vals = [float(r[h]) for r in items if r.get(h) is not None] + s = _stats(vals) + line += f" {_fmt(s['mean']):>8} {_fmt(s['median']):>8} {_fmt(s['win_rate'], pct=False):>7}" + print(line) + + # ── 1c. By event_type ── + print(f"\n--- 1c. By event_type ---") + by_type: dict[str, list] = {} + for r in rows: + et = r.get("event_type", "unknown") + by_type.setdefault(et, []).append(r) + + header = f"{'EventType':<25} {'N':>5} {'AvgScr':>7}" + for lbl in HORIZON_LABELS: + header += f" {lbl+'_mean':>8} {lbl+'_wr':>7}" + print(header) + print("-" * len(header)) + + for et in sorted(by_type, key=lambda k: -len(by_type[k])): + items = by_type[et] + avg_score = statistics.mean(r["_score"] for r in items) + line = f"{et:<25} {len(items):>5} {avg_score:>7.3f}" + for h in FWD_HORIZONS: + vals = [float(r[h]) for r in items if r.get(h) is not None] + s = _stats(vals) + line += f" {_fmt(s['mean']):>8} {_fmt(s['win_rate'], pct=False):>7}" + print(line) + + # ── 1d. MFE/MAE analysis (reward:risk) ── + print(f"\n--- 1d. MFE/MAE analysis (max favorable / max adverse excursion) ---") + mfe_mae_horizons = [("3d", "mfe_3d", "mae_3d"), + ("5d", "mfe_5d", "mae_5d"), + ("10d", "mfe_10d", "mae_10d"), + ("20d", "mfe_20d", "mae_20d")] + + header = f"{'Horizon':<10} {'MFE_mean':>10} {'MAE_mean':>10} {'R:R':>8} {'MFE_med':>10} {'MAE_med':>10}" + print(header) + print("-" * len(header)) + + for lbl, mfe_col, mae_col in mfe_mae_horizons: + mfe_vals = [float(r[mfe_col]) for r in rows if r.get(mfe_col) is not None] + mae_vals = [float(r[mae_col]) for r in rows if r.get(mae_col) is not None] + if mfe_vals and mae_vals: + mfe_mean = statistics.mean(mfe_vals) + mae_mean = statistics.mean(mae_vals) + rr = mfe_mean / abs(mae_mean) if mae_mean != 0 else 0 + mfe_med = statistics.median(mfe_vals) + mae_med = statistics.median(mae_vals) + print(f"{lbl:<10} {mfe_mean:>+10.2%} {mae_mean:>+10.2%} {rr:>8.2f} {mfe_med:>+10.2%} {mae_med:>+10.2%}") + + # ── 1e. Score monotonicity per horizon ── + print(f"\n--- 1e. Score monotonicity (does higher score → better return?) ---") + for h, lbl in zip(FWD_HORIZONS, HORIZON_LABELS): + bucket_means = [] + for bucket in BUCKET_ORDER: + vals = [float(r[h]) for r in by_bucket[bucket] if r.get(h) is not None] + bucket_means.append(statistics.mean(vals) if vals else None) + + valid_means = [m for m in bucket_means if m is not None] + monotonic = all(a <= b for a, b in zip(valid_means, valid_means[1:])) if len(valid_means) >= 2 else False + direction = "MONOTONIC" if monotonic else "NOT monotonic" + means_str = " → ".join(f"{m:+.2%}" if m is not None else "N/A" for m in bucket_means) + print(f" {lbl}: {means_str} [{direction}]") + + +# ── Experiment 2 ────────────────────────────────────────────────────── + +def experiment_2(rows: list[dict[str, Any]]) -> None: + """Reaction-close entry vs next-open entry.""" + print(f"\n{'='*100}") + print("EXPERIMENT 2: Reaction-day Close Entry vs Next-Open Entry") + print(f"{'='*100}") + + # Split by filing timing + intraday = [r for r in rows if _is_intraday_filing(r)] + after_close = [r for r in rows if not _is_intraday_filing(r)] + + print(f"\nFiling timing split:") + print(f" Pre-market / intraday (reaction_date == event_date): {len(intraday)}") + print(f" After-close (reaction_date > event_date): {len(after_close)}") + + # ── 2a. Overnight gap analysis (intraday filings only) ── + print(f"\n--- 2a. Overnight gap cost (intraday filings, N={len(intraday)}) ---") + gaps = [] + for r in intraday: + ep = r.get("entry_price") + ec = r.get("event_close") + if ep and ec and ec > 0: + gap = ep / ec - 1.0 + gaps.append(gap) + + if gaps: + s = _stats(gaps) + print(f" Overnight gap (next_open / reaction_close - 1):") + print(f" Mean: {_fmt(s['mean'])}") + print(f" Median: {_fmt(s['median'])}") + print(f" Gap up rate (open > close): {_fmt(s['win_rate'], pct=False)}") + print(f" Std: {statistics.stdev(gaps):+.2%}" if len(gaps) > 1 else "") + + # Distribution + gap_buckets = {"< -2%": 0, "-2% to 0%": 0, "0% to +1%": 0, + "+1% to +3%": 0, "+3% to +5%": 0, "> +5%": 0} + for g in gaps: + if g < -0.02: + gap_buckets["< -2%"] += 1 + elif g < 0: + gap_buckets["-2% to 0%"] += 1 + elif g < 0.01: + gap_buckets["0% to +1%"] += 1 + elif g < 0.03: + gap_buckets["+1% to +3%"] += 1 + elif g < 0.05: + gap_buckets["+3% to +5%"] += 1 + else: + gap_buckets["> +5%"] += 1 + + print(f"\n Gap distribution:") + for label, count in gap_buckets.items(): + pct = count / len(gaps) * 100 + bar = "#" * int(pct / 2) + print(f" {label:>12}: {count:>4} ({pct:5.1f}%) {bar}") + + # ── 2b. Close entry vs next-open returns (intraday only) ── + print(f"\n--- 2b. Close entry vs Next-open entry returns (intraday, N={len(intraday)}) ---") + + header = f"{'Horizon':<10} {'NextOpen_mean':>13} {'NextOpen_wr':>11} {'CloseEntry_mean':>15} {'CloseEntry_wr':>13} {'Diff':>8}" + print(header) + print("-" * len(header)) + + for h, lbl in zip(FWD_HORIZONS, HORIZON_LABELS): + next_open_vals = [] + close_entry_vals = [] + + for r in intraday: + fwd = r.get(h) + ep = r.get("entry_price") + ec = r.get("event_close") + if fwd is not None and ep and ec and ec > 0: + fwd_f = float(fwd) + next_open_vals.append(fwd_f) + # close_entry_return = (entry_price / event_close) * (1 + fwd) - 1 + close_ret = (ep / ec) * (1.0 + fwd_f) - 1.0 + close_entry_vals.append(close_ret) + + no_s = _stats(next_open_vals) + ce_s = _stats(close_entry_vals) + diff = (ce_s["mean"] - no_s["mean"]) if ce_s["mean"] is not None and no_s["mean"] is not None else None + diff_str = _fmt(diff) if diff is not None else " N/A" + + print(f"{lbl:<10} {_fmt(no_s['mean']):>13} {_fmt(no_s['win_rate'], pct=False):>11}" + f" {_fmt(ce_s['mean']):>15} {_fmt(ce_s['win_rate'], pct=False):>13} {diff_str:>8}") + + # ── 2c. After-close filings (separate analysis) ── + if after_close: + print(f"\n--- 2c. After-close filings (N={len(after_close)}) ---") + print(f" (These events filed after market close; reaction_date = next trading day)") + + header = f"{'Horizon':<10} {'NextOpen_mean':>13} {'NextOpen_wr':>11}" + print(f" {header}") + print(f" {'-' * len(header)}") + + for h, lbl in zip(FWD_HORIZONS, HORIZON_LABELS): + vals = [float(r[h]) for r in after_close if r.get(h) is not None] + s = _stats(vals) + print(f" {lbl:<10} {_fmt(s['mean']):>13} {_fmt(s['win_rate'], pct=False):>11}") + + # ── 2d. Close entry by event type (intraday only) ── + print(f"\n--- 2d. Close-entry returns by event type (intraday only) ---") + by_type: dict[str, list] = {} + for r in intraday: + et = r.get("event_type", "unknown") + by_type.setdefault(et, []).append(r) + + header = f"{'EventType':<25} {'N':>5} {'Gap_mean':>9}" + for lbl in HORIZON_LABELS: + header += f" {lbl+'_CE':>8} {lbl+'_NO':>8}" + print(header) + print("-" * len(header)) + + for et in sorted(by_type, key=lambda k: -len(by_type[k])): + items = by_type[et] + # Gap + type_gaps = [] + for r in items: + ep = r.get("entry_price") + ec = r.get("event_close") + if ep and ec and ec > 0: + type_gaps.append(ep / ec - 1.0) + + gap_mean = statistics.mean(type_gaps) if type_gaps else None + line = f"{et:<25} {len(items):>5} {_fmt(gap_mean):>9}" + + for h in FWD_HORIZONS: + ce_vals = [] + no_vals = [] + for r in items: + fwd = r.get(h) + ep = r.get("entry_price") + ec = r.get("event_close") + if fwd is not None and ep and ec and ec > 0: + fwd_f = float(fwd) + no_vals.append(fwd_f) + ce_vals.append((ep / ec) * (1.0 + fwd_f) - 1.0) + ce_s = _stats(ce_vals) + no_s = _stats(no_vals) + line += f" {_fmt(ce_s['mean']):>8} {_fmt(no_s['mean']):>8}" + print(line) + + # ── 2e. Score-filtered close entry (score >= 0.5, intraday) ── + filtered = [r for r in intraday if r.get("_score", 0) >= 0.5] + if filtered: + print(f"\n--- 2e. Score-filtered (>= 0.5) close entry (intraday, N={len(filtered)}) ---") + + header = f"{'Horizon':<10} {'CloseEntry_mean':>15} {'CE_wr':>8} {'NextOpen_mean':>14} {'NO_wr':>8}" + print(header) + print("-" * len(header)) + + for h, lbl in zip(FWD_HORIZONS, HORIZON_LABELS): + ce_vals = [] + no_vals = [] + for r in filtered: + fwd = r.get(h) + ep = r.get("entry_price") + ec = r.get("event_close") + if fwd is not None and ep and ec and ec > 0: + fwd_f = float(fwd) + no_vals.append(fwd_f) + ce_vals.append((ep / ec) * (1.0 + fwd_f) - 1.0) + ce_s = _stats(ce_vals) + no_s = _stats(no_vals) + print(f"{lbl:<10} {_fmt(ce_s['mean']):>15} {_fmt(ce_s['win_rate'], pct=False):>8}" + f" {_fmt(no_s['mean']):>14} {_fmt(no_s['win_rate'], pct=False):>8}") + + +def _print_horizon_table(label: str, items: list[dict]) -> None: + header = f"{'Horizon':<10} {'Mean':>10} {'Median':>10} {'WinRate':>8} {'N':>6} {'StdDev':>10}" + print(header) + print("-" * len(header)) + for h, lbl in zip(FWD_HORIZONS, HORIZON_LABELS): + vals = [float(r[h]) for r in items if r.get(h) is not None] + s = _stats(vals) + std = statistics.stdev(vals) if len(vals) > 1 else 0 + print(f"{lbl:<10} {_fmt(s['mean']):>10} {_fmt(s['median']):>10}" + f" {_fmt(s['win_rate'], pct=False):>8} {s['n']:>6} {std:>10.2%}") + + +def main() -> None: + parser = argparse.ArgumentParser(description="Entry Timing Experiments") + parser.add_argument("--snapshot-dir", required=True) + parser.add_argument("--split", default="train") + args = parser.parse_args() + + rows = _load_rows(Path(args.snapshot_dir), args.split) + if not rows: + print("No data found.") + return + + print(f"Loaded {len(rows)} events from {args.split} split") + + experiment_1(rows) + experiment_2(rows) + + # ── Summary ── + print(f"\n{'='*100}") + print("KEY DIAGNOSTIC QUESTIONS") + print(f"{'='*100}") + print(""" + Exp 1 — Time-based alpha: + • Are any fixed-horizon returns consistently positive? + • Does score monotonicity hold at any horizon? + • Which event types carry actual alpha (mean > 0, win rate > 50%)? + + Exp 2 — Entry timing: + • Is the overnight gap systematically positive (eating the edge)? + • Does close-entry improve returns vs next-open? + • Is the gap effect uniform across event types, or concentrated? +""") + + +if __name__ == "__main__": + main() diff --git a/dev/analysis/event_type_decomposition.py b/apps/tools/event_type_decomposition.py similarity index 100% rename from dev/analysis/event_type_decomposition.py rename to apps/tools/event_type_decomposition.py diff --git a/apps/tools/fix_null_symbols.py b/apps/tools/fix_null_symbols.py new file mode 100644 index 0000000..90ed3d7 --- /dev/null +++ b/apps/tools/fix_null_symbols.py @@ -0,0 +1,141 @@ +"""Fix documents and events with NULL symbol_id / issuer_id. + +The filing_poller stores ticker in document_id as TICKER::{ticker}. +When issuer_sync hadn't run before filing_poller, the lookup maps were +empty and symbol_id / issuer_id were inserted as NULL. + +This script: + 1. Extracts ticker from document_id for NULL-symbol docs + 2. Looks up correct symbol_id / issuer_id from symbol_master / issuer_master + 3. Updates documents and events + +Usage: + python -m apps.tools.fix_null_symbols [--dry-run] +""" +from __future__ import annotations + +import argparse +import asyncio + +from sqlalchemy import select, text, update + +from libs.common.config import get_settings +from libs.common.logging import configure_logging, get_logger +from libs.db.models import Document, Event, IssuerMaster, SymbolMaster +from libs.db.session import get_session + +logger = get_logger(__name__) + + +async def fix_null_symbols(dry_run: bool = False) -> dict[str, int]: + stats = {"docs_total_null": 0, "docs_fixed": 0, "docs_no_match": 0, + "events_fixed": 0, "events_no_match": 0} + + async with get_session() as session: + # 1. Build ticker lookup maps from current DB + issuer_rows = await session.execute(select(IssuerMaster)) + ticker_to_issuer: dict[str, str] = {} + for im in issuer_rows.scalars().all(): + if im.ticker: + ticker_to_issuer[im.ticker.upper()] = im.issuer_id + + symbol_rows = await session.execute( + select(SymbolMaster).where(SymbolMaster.is_primary == True) # noqa: E712 + ) + ticker_to_symbol: dict[str, str] = {} + for sm in symbol_rows.scalars().all(): + if sm.ticker: + ticker_to_symbol[sm.ticker.upper()] = sm.symbol_id + + print(f"Lookup maps: {len(ticker_to_issuer)} issuers, {len(ticker_to_symbol)} symbols") + + # 2. Find documents with NULL symbol_id + null_docs = await session.execute( + select(Document).where(Document.symbol_id.is_(None)) + ) + docs = null_docs.scalars().all() + stats["docs_total_null"] = len(docs) + print(f"Documents with NULL symbol_id: {len(docs)}") + + # 3. Extract ticker from document_id and update + no_match_tickers: set[str] = set() + for doc in docs: + # document_id format: DOC::sec::TICKER::{ticker}::{date}::{accession} + parts = doc.document_id.split("::") + if len(parts) >= 4 and parts[2] == "TICKER": + ticker = parts[3].upper() + else: + logger.warning("unexpected_doc_id_format", document_id=doc.document_id) + stats["docs_no_match"] += 1 + continue + + sym_id = ticker_to_symbol.get(ticker) + iss_id = ticker_to_issuer.get(ticker) + + if sym_id: + doc.symbol_id = sym_id + doc.issuer_id = iss_id # may still be None if issuer doesn't exist + stats["docs_fixed"] += 1 + else: + no_match_tickers.add(ticker) + stats["docs_no_match"] += 1 + + if no_match_tickers: + print(f"No symbol_master match for {len(no_match_tickers)} tickers: " + f"{sorted(no_match_tickers)[:20]}{'...' if len(no_match_tickers) > 20 else ''}") + + # 4. Fix events with NULL symbol_id (join via primary_document_id) + null_events = await session.execute( + select(Event).where(Event.symbol_id.is_(None)) + ) + events = null_events.scalars().all() + print(f"Events with NULL symbol_id: {len(events)}") + + for evt in events: + # Look up the parent document + doc_result = await session.execute( + select(Document).where(Document.document_id == evt.primary_document_id) + ) + parent_doc = doc_result.scalar_one_or_none() + if parent_doc and parent_doc.symbol_id: + evt.symbol_id = parent_doc.symbol_id + evt.issuer_id = parent_doc.issuer_id + stats["events_fixed"] += 1 + else: + stats["events_no_match"] += 1 + + # 5. Commit or rollback + if dry_run: + print("\n[DRY RUN] Rolling back — no changes saved.") + await session.rollback() + else: + await session.flush() + print("\nChanges committed.") + + # Report + print(f"\n{'='*50}") + print("Fix NULL Symbols Report") + print(f"{'='*50}") + print(f" Documents with NULL symbol_id: {stats['docs_total_null']}") + print(f" Documents fixed: {stats['docs_fixed']}") + print(f" Documents no match: {stats['docs_no_match']}") + print(f" Events fixed: {stats['events_fixed']}") + print(f" Events no match: {stats['events_no_match']}") + print(f"{'='*50}") + + return stats + + +def main() -> None: + parser = argparse.ArgumentParser(description="Fix NULL symbol_id in documents and events") + parser.add_argument("--dry-run", action="store_true", help="Preview changes without saving") + args = parser.parse_args() + + settings = get_settings() + configure_logging(settings.log_level) + + asyncio.run(fix_null_symbols(dry_run=args.dry_run)) + + +if __name__ == "__main__": + main() diff --git a/dev/analysis/kill_switch_impact_analysis.py b/apps/tools/kill_switch_impact_analysis.py similarity index 100% rename from dev/analysis/kill_switch_impact_analysis.py rename to apps/tools/kill_switch_impact_analysis.py diff --git a/apps/tools/label_horizon_analysis.py b/apps/tools/label_horizon_analysis.py new file mode 100644 index 0000000..ddec32e --- /dev/null +++ b/apps/tools/label_horizon_analysis.py @@ -0,0 +1,245 @@ +"""MFE/MAE Distribution Analysis by Event Type and Horizon. + +Reads EventLabel records from the database and analyzes: +- MFE/MAE distributions per event_type per horizon (3d, 5d, 10d, 20d) +- Optimal holding period per event type (where MFE peaks) +- Forward return distributions + +Outputs: +- Matplotlib plots (saved to output directory) +- Summary CSV with statistics per event_type × horizon + +Usage: + python -m dev.analysis.label_horizon_analysis [--output-dir ./data/analysis] +""" +from __future__ import annotations + +import argparse +import asyncio +import csv +import datetime as dt +from pathlib import Path +from typing import Any + +from libs.common.logging import configure_logging, get_logger + +logger = get_logger(__name__) + +HORIZONS = ["3d", "5d", "10d", "20d"] + + +async def _load_label_data() -> list[dict[str, Any]]: + """Load EventLabel + Event data from DB.""" + from sqlalchemy import select + from libs.db.models import Event, EventLabel + from libs.db.session import get_session + + rows: list[dict[str, Any]] = [] + async with get_session() as session: + result = await session.execute( + select(EventLabel, Event.event_type) + .join(Event, EventLabel.event_id == Event.event_id) + .where(EventLabel.label_status == "ok") + .where(EventLabel.invalid_event_for_labeling.is_(False)) + ) + for lbl, event_type in result.all(): + rows.append({ + "event_id": lbl.event_id, + "event_type": event_type, + "fwd_return_3d": float(lbl.fwd_return_3d) if lbl.fwd_return_3d is not None else None, + "fwd_return_5d": float(lbl.fwd_return_5d) if lbl.fwd_return_5d is not None else None, + "fwd_return_10d": float(lbl.fwd_return_10d) if lbl.fwd_return_10d is not None else None, + "fwd_return_20d": float(lbl.fwd_return_20d) if lbl.fwd_return_20d is not None else None, + "mfe_3d": float(lbl.mfe_3d) if lbl.mfe_3d is not None else None, + "mae_3d": float(lbl.mae_3d) if lbl.mae_3d is not None else None, + "mfe_5d": float(lbl.mfe_5d) if lbl.mfe_5d is not None else None, + "mae_5d": float(lbl.mae_5d) if lbl.mae_5d is not None else None, + "mfe_10d": float(lbl.mfe_10d) if lbl.mfe_10d is not None else None, + "mae_10d": float(lbl.mae_10d) if lbl.mae_10d is not None else None, + "mfe_20d": float(lbl.mfe_20d) if lbl.mfe_20d is not None else None, + "mae_20d": float(lbl.mae_20d) if lbl.mae_20d is not None else None, + }) + return rows + + +def _compute_stats(values: list[float]) -> dict[str, float | None]: + """Compute summary statistics for a list of values.""" + if not values: + return {"count": 0, "mean": None, "median": None, "std": None, "min": None, "max": None} + import statistics + return { + "count": len(values), + "mean": statistics.mean(values), + "median": statistics.median(values), + "std": statistics.stdev(values) if len(values) > 1 else 0.0, + "min": min(values), + "max": max(values), + } + + +def analyze_and_write(rows: list[dict[str, Any]], output_dir: Path) -> None: + """Compute statistics and generate plots.""" + output_dir.mkdir(parents=True, exist_ok=True) + + # Group by event_type + by_type: dict[str, list[dict[str, Any]]] = {} + for r in rows: + et = r["event_type"] + by_type.setdefault(et, []).append(r) + + # Summary CSV + csv_rows: list[dict[str, Any]] = [] + optimal_holding: dict[str, str] = {} + + for event_type, type_rows in sorted(by_type.items()): + mfe_means: dict[str, float] = {} + for horizon in HORIZONS: + mfe_vals = [r[f"mfe_{horizon}"] for r in type_rows if r.get(f"mfe_{horizon}") is not None] + mae_vals = [r[f"mae_{horizon}"] for r in type_rows if r.get(f"mae_{horizon}") is not None] + fwd_vals = [r[f"fwd_return_{horizon}"] for r in type_rows if r.get(f"fwd_return_{horizon}") is not None] + + mfe_stats = _compute_stats(mfe_vals) + mae_stats = _compute_stats(mae_vals) + fwd_stats = _compute_stats(fwd_vals) + + if mfe_stats["mean"] is not None: + mfe_means[horizon] = mfe_stats["mean"] + + csv_rows.append({ + "event_type": event_type, + "horizon": horizon, + "n": mfe_stats["count"], + "mfe_mean": mfe_stats["mean"], + "mfe_median": mfe_stats["median"], + "mae_mean": mae_stats["mean"], + "mae_median": mae_stats["median"], + "fwd_return_mean": fwd_stats["mean"], + "fwd_return_median": fwd_stats["median"], + "fwd_return_std": fwd_stats["std"], + "reward_risk_ratio": ( + mfe_stats["mean"] / abs(mae_stats["mean"]) + if mfe_stats["mean"] is not None and mae_stats["mean"] and abs(mae_stats["mean"]) > 0 + else None + ), + }) + + # Optimal holding period: horizon with highest mean MFE + if mfe_means: + best_h = max(mfe_means, key=lambda h: mfe_means[h]) + optimal_holding[event_type] = best_h + + # Write CSV + csv_path = output_dir / "mfe_mae_summary.csv" + if csv_rows: + fieldnames = list(csv_rows[0].keys()) + with open(csv_path, "w", newline="") as f: + writer = csv.DictWriter(f, fieldnames=fieldnames) + writer.writeheader() + writer.writerows(csv_rows) + logger.info("summary_csv_written", path=str(csv_path), rows=len(csv_rows)) + + # Print summary table + print(f"\n{'='*80}") + print(f"MFE/MAE Distribution Analysis — {len(rows)} events, {len(by_type)} event types") + print(f"{'='*80}") + for event_type in sorted(by_type): + n = len(by_type[event_type]) + best_h = optimal_holding.get(event_type, "N/A") + print(f"\n {event_type} (n={n}, optimal holding={best_h})") + for horizon in HORIZONS: + matching = [r for r in csv_rows if r["event_type"] == event_type and r["horizon"] == horizon] + if matching: + m = matching[0] + mfe_str = f"{m['mfe_mean']:+.2%}" if m["mfe_mean"] is not None else "N/A" + mae_str = f"{m['mae_mean']:+.2%}" if m["mae_mean"] is not None else "N/A" + fwd_str = f"{m['fwd_return_mean']:+.2%}" if m["fwd_return_mean"] is not None else "N/A" + rr_str = f"{m['reward_risk_ratio']:.2f}" if m["reward_risk_ratio"] is not None else "N/A" + print(f" {horizon}: MFE={mfe_str} MAE={mae_str} FwdRet={fwd_str} R:R={rr_str}") + + # Generate plots (if matplotlib available) + try: + _generate_plots(by_type, output_dir) + except ImportError: + logger.info("matplotlib_not_available", msg="Skipping plot generation") + print("\n(matplotlib not available — skipping plot generation)") + + +def _generate_plots( + by_type: dict[str, list[dict[str, Any]]], + output_dir: Path, +) -> None: + """Generate MFE/MAE distribution plots using matplotlib.""" + import matplotlib + matplotlib.use("Agg") + import matplotlib.pyplot as plt + + event_types = sorted(by_type.keys()) + + # Plot 1: MFE by horizon per event type + fig, axes = plt.subplots( + len(event_types), len(HORIZONS), + figsize=(4 * len(HORIZONS), 3 * len(event_types)), + squeeze=False, + ) + fig.suptitle("MFE Distribution by Event Type and Horizon", fontsize=14) + + for i, et in enumerate(event_types): + for j, h in enumerate(HORIZONS): + ax = axes[i][j] + vals = [r[f"mfe_{h}"] for r in by_type[et] if r.get(f"mfe_{h}") is not None] + if vals: + ax.hist(vals, bins=max(5, len(vals) // 3), alpha=0.7, color="steelblue", edgecolor="white") + ax.axvline(x=sum(vals) / len(vals), color="red", linestyle="--", linewidth=1) + ax.set_title(f"{et}\n{h}" if i == 0 else h, fontsize=8) + if j == 0: + ax.set_ylabel(et[:15], fontsize=8) + ax.tick_params(labelsize=6) + + plt.tight_layout() + mfe_path = output_dir / "mfe_distributions.png" + fig.savefig(str(mfe_path), dpi=150) + plt.close(fig) + print(f"\nMFE plot saved: {mfe_path}") + + # Plot 2: Mean MFE across horizons (optimal holding period) + fig2, ax2 = plt.subplots(figsize=(10, 6)) + horizon_days = [3, 5, 10, 20] + for et in event_types: + means = [] + for h in HORIZONS: + vals = [r[f"mfe_{h}"] for r in by_type[et] if r.get(f"mfe_{h}") is not None] + means.append(sum(vals) / len(vals) * 100 if vals else 0) + ax2.plot(horizon_days, means, marker="o", label=et) + ax2.set_xlabel("Horizon (trading days)") + ax2.set_ylabel("Mean MFE (%)") + ax2.set_title("Optimal Holding Period — Mean MFE by Horizon") + ax2.legend(fontsize=8, loc="upper left") + ax2.grid(True, alpha=0.3) + plt.tight_layout() + optimal_path = output_dir / "optimal_holding_period.png" + fig2.savefig(str(optimal_path), dpi=150) + plt.close(fig2) + print(f"Optimal holding plot saved: {optimal_path}") + + +def main() -> None: + parser = argparse.ArgumentParser(description="MFE/MAE Distribution Analysis") + parser.add_argument( + "--output-dir", + default="./data/analysis", + help="Output directory for plots and CSV (default: ./data/analysis)", + ) + args = parser.parse_args() + + configure_logging("info") + rows = asyncio.run(_load_label_data()) + + if not rows: + print("No label data found. Run the labeler pipeline first.") + return + + analyze_and_write(rows, Path(args.output_dir)) + + +if __name__ == "__main__": + main() diff --git a/dev/analysis/signal_quality_analysis.py b/apps/tools/signal_quality_analysis.py similarity index 100% rename from dev/analysis/signal_quality_analysis.py rename to apps/tools/signal_quality_analysis.py diff --git a/apps/tools/universe_builder.py b/apps/tools/universe_builder.py new file mode 100644 index 0000000..51ff6f5 --- /dev/null +++ b/apps/tools/universe_builder.py @@ -0,0 +1,155 @@ +"""Universe Builder: expand symbol universe via Stock Oracle screener API. + +Fetches large-cap liquid equities from screener, merges with existing +symbols.yaml, and writes the union back. + +Usage: + python -m apps.tools.universe_builder [--keep-existing] [--output configs/symbols.yaml] +""" +from __future__ import annotations + +import argparse +import asyncio +from pathlib import Path + +import yaml + +from libs.common.config import get_settings +from libs.common.logging import configure_logging, get_logger +from libs.oracle_client import ScreenerService, make_oracle_client + +logger = get_logger(__name__) + +# ── Screener defaults ────────────────────────────────────────────── +SCREENER_DEFAULTS = { + "market_cap_min": 10_000_000_000, # $10B + "min_avg_volume": 1_000_000, # 1M shares/day + "exchange": "NYSE,NASDAQ", + "exclude_types": "ETF,FUND", + "price_min": 5, +} + + +def load_existing_symbols(path: Path) -> list[str]: + """Load current symbols.yaml and return list of tickers.""" + if not path.exists(): + return [] + with open(path) as f: + cfg = yaml.safe_load(f) or {} + return cfg.get("symbols", []) + + +def build_symbols_yaml( + existing: list[str], + screener_results: list[dict], +) -> str: + """Build YAML content: existing-only tickers first, then screener (sorted).""" + screener_set: set[str] = set() + for item in screener_results: + ticker = item.get("symbol", "") + if ticker: + screener_set.add(ticker.upper()) + + existing_set = {s.upper() for s in existing} + existing_only = sorted(existing_set - screener_set) + screener_sorted = sorted(screener_set) + + lines = ["symbols:"] + if existing_only: + lines.append(" # --- Existing-only (not in screener, kept) ---") + for t in existing_only: + lines.append(f" - {t}") + lines.append(f" # --- Screener ($10B+ mcap, 1M+ vol) — {len(screener_sorted)} symbols ---") + for t in screener_sorted: + lines.append(f" - {t}") + lines.append("") # trailing newline + + return "\n".join(lines) + + +async def run_universe_builder( + keep_existing: bool, + output_path: Path, +) -> dict[str, int]: + existing = load_existing_symbols(output_path) if keep_existing else [] + existing_set = {s.upper() for s in existing} + + async with make_oracle_client() as client: + svc = ScreenerService(client) + screener_stocks = await svc.search_all_stocks(**SCREENER_DEFAULTS) + + screener_results = [{"symbol": s.symbol, "name": s.name} for s in screener_stocks] + screener_tickers = {s.symbol.upper() for s in screener_stocks if s.symbol} + + overlap = existing_set & screener_tickers + new_only = screener_tickers - existing_set + existing_only = existing_set - screener_tickers + union = existing_set | screener_tickers + + stats = { + "screener": len(screener_tickers), + "existing": len(existing_set), + "overlap": len(overlap), + "existing_only": len(existing_only), + "new": len(new_only), + "total": len(union), + } + + yaml_content = build_symbols_yaml(existing, screener_results) + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_text(yaml_content) + + # Report + logger.info("universe_report", **stats) + print(f"\n{'='*50}") + print("Universe Builder Report") + print(f"{'='*50}") + print(f" Screener results: {stats['screener']:>6}") + print(f" Existing symbols: {stats['existing']:>6}") + print(f" Overlap: {stats['overlap']:>6}") + print(f" Existing-only (kept): {stats['existing_only']:>6}") + print(f" New additions: {stats['new']:>6}") + print(f" Total (union): {stats['total']:>6}") + print(f"{'='*50}") + print(f" Written to: {output_path}") + + if existing_only: + print(f"\n Existing-only tickers (kept): {sorted(existing_only)}") + + return stats + + +def main() -> None: + parser = argparse.ArgumentParser( + description="Build symbol universe from Stock Oracle screener", + ) + parser.add_argument( + "--keep-existing", + action="store_true", + default=True, + help="Keep all existing symbols (default: True)", + ) + parser.add_argument( + "--no-keep-existing", + action="store_false", + dest="keep_existing", + help="Replace existing symbols entirely with screener results", + ) + parser.add_argument( + "--output", + default="configs/symbols.yaml", + help="Output YAML path (default: configs/symbols.yaml)", + ) + args = parser.parse_args() + + settings = get_settings() + configure_logging(settings.log_level) + + asyncio.run(run_universe_builder( + keep_existing=args.keep_existing, + output_path=Path(args.output), + )) + + +if __name__ == "__main__": + main() diff --git a/configs/app.yaml b/configs/app.yaml index f9bc737..cb95ab4 100644 --- a/configs/app.yaml +++ b/configs/app.yaml @@ -16,7 +16,6 @@ feature_flags: pipeline: exhibit_types: - "EX-99.1" - - "EX-99.2" form_types: - "8-K" - "6-K" diff --git a/configs/backtest/defaults.json b/configs/backtest/defaults.json index 0c2a1b7..bb90851 100644 --- a/configs/backtest/defaults.json +++ b/configs/backtest/defaults.json @@ -7,7 +7,7 @@ "exclude_asset_types": ["ETF", "FUND"] }, "signal": { - "score_threshold": 0.5, + "score_threshold": 0.55, "max_candidates_per_day": 3, "execution_timing": "next_open", "decision_timing": "reaction_close", @@ -27,10 +27,10 @@ "stop_atr_multiplier": 3.0, "backtest_mode": "research", "kill_switch_cooldown_days": 20, - "kill_switch_log_only": false, + "kill_switch_log_only": true, "veto_oneoff_penalty": 0.7, "veto_parse_confidence_min": 0.4, - "veto_unknown_direction": true, + "veto_unknown_direction": false, "veto_bearish_direction": true }, "execution": { @@ -43,10 +43,10 @@ "target_1_r": 2.0, "target_atr_multiplier": 1.5, "target_1_fraction": 0.5, - "trailing_model": "pct_3", + "trailing_model": "pct_5", "trailing_warmup_days": 2, - "max_holding_days": 10, - "no_follow_through_exit": true + "max_holding_days": 15, + "no_follow_through_exit": false }, "reporting": { "write_trade_blotter": true, @@ -59,20 +59,25 @@ "earnings_release": { "enabled": true, "max_holding_days_override": 15, - "direction_filter": "bullish_only" + "direction_filter": "any" }, "guidance_update": { "enabled": true, - "max_holding_days_override": 10, - "direction_filter": "bullish_only" + "max_holding_days_override": 15, + "direction_filter": "any" }, "management_change": { - "enabled": false + "enabled": true, + "direction_filter": "any" }, "material_contract": { "enabled": true, - "max_holding_days_override": 5, - "direction_filter": "bullish_only" + "max_holding_days_override": 10, + "direction_filter": "any" + }, + "unknown": { + "enabled": true, + "direction_filter": "any" }, "other_material_event": { "enabled": false diff --git a/configs/backtest/defaults_smallmid.json b/configs/backtest/defaults_smallmid.json new file mode 100644 index 0000000..5982bdc --- /dev/null +++ b/configs/backtest/defaults_smallmid.json @@ -0,0 +1,86 @@ +{ + "strategy_name": "smallmid_swing_v1", + "dataset_snapshot_id": "pending_phase7", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 300000.0, + "exclude_asset_types": ["ETF", "FUND"] + }, + "signal": { + "score_threshold": 0.55, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": ["score", "avg_dollar_volume"] + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.015, + "max_positions": 4, + "max_positions_per_sector": 2, + "max_position_value_pct": 0.10, + "max_adv_fraction": 0.02, + "cooldown_after_loss_streak": 3, + "cooldown_days": 2, + "macro_regime_enabled": false, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 0.7, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": true + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 25.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": ["event_type", "sector", "score_bucket"] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "max_holding_days_override": 15, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": true, + "max_holding_days_override": 15, + "direction_filter": "any" + }, + "management_change": { + "enabled": true, + "direction_filter": "any" + }, + "material_contract": { + "enabled": true, + "max_holding_days_override": 10, + "direction_filter": "any" + }, + "unknown": { + "enabled": true, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false + } + } +} diff --git a/configs/experiments/earnings_smallmid_v1.json b/configs/experiments/earnings_smallmid_v1.json new file mode 100644 index 0000000..eda7152 --- /dev/null +++ b/configs/experiments/earnings_smallmid_v1.json @@ -0,0 +1,21 @@ +{ + "experiment_name": "earnings_smallmid_v1", + "dataset_snapshot_id": "smallmid-only-8ef47ee5", + "description": "PEAD alpha test on small/mid-cap earnings. Tests hypothesis that PEAD is stronger in less efficient market segments.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "bullish_only" + }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { "score_threshold": 0.45 } + }, + "tags": ["earnings-only", "smallmid", "pead"] +} diff --git a/configs/experiments/earnings_smallmid_v2.json b/configs/experiments/earnings_smallmid_v2.json new file mode 100644 index 0000000..fa24ec0 --- /dev/null +++ b/configs/experiments/earnings_smallmid_v2.json @@ -0,0 +1,29 @@ +{ + "experiment_name": "earnings_smallmid_v2", + "dataset_snapshot_id": "smallmid-only-8ef47ee5", + "description": "PEAD small/mid-cap v2: relaxed oneoff filter (0.9), more positions (6), more candidates/day (5). Tests if conservative filters were destroying alpha.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "bullish_only" + }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "score_threshold": 0.45, + "max_candidates_per_day": 5 + }, + "risk": { + "max_positions": 6, + "max_positions_per_sector": 3, + "veto_oneoff_penalty": 0.9 + } + }, + "tags": ["earnings-only", "smallmid", "pead", "relaxed-filters"] +} diff --git a/configs/experiments/earnings_smallmid_v3.json b/configs/experiments/earnings_smallmid_v3.json new file mode 100644 index 0000000..f06184a --- /dev/null +++ b/configs/experiments/earnings_smallmid_v3.json @@ -0,0 +1,31 @@ +{ + "experiment_name": "earnings_smallmid_v3", + "dataset_snapshot_id": "smallmid-only-8ef47ee5", + "description": "PEAD small/mid-cap v3: oneoff filter effectively disabled, 8 max positions, 8 candidates/day. Maximum exposure to test raw PEAD signal.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "bullish_only" + }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "score_threshold": 0.45, + "max_candidates_per_day": 8 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 4, + "veto_oneoff_penalty": 1.0, + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.025 + } + }, + "tags": ["earnings-only", "smallmid", "pead", "max-exposure"] +} diff --git a/configs/experiments/exec_opt_a_wider.json b/configs/experiments/exec_opt_a_wider.json new file mode 100644 index 0000000..495645f --- /dev/null +++ b/configs/experiments/exec_opt_a_wider.json @@ -0,0 +1,16 @@ +{ + "experiment_name": "exec_opt_a_wider", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "Execution opt A: wider stops (3.5 ATR), higher targets (2.0 ATR), wider trailing (7%), longer hold (20d). Let winners run.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "signal": { "score_threshold": 0.5 }, + "risk": { "stop_atr_multiplier": 3.5 }, + "execution": { + "target_atr_multiplier": 2.0, + "trailing_model": "pct_7", + "max_holding_days": 20 + } + }, + "tags": ["exec-opt", "wider"] +} diff --git a/configs/experiments/exec_opt_b_tight.json b/configs/experiments/exec_opt_b_tight.json new file mode 100644 index 0000000..fcf1952 --- /dev/null +++ b/configs/experiments/exec_opt_b_tight.json @@ -0,0 +1,16 @@ +{ + "experiment_name": "exec_opt_b_tight", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "Execution opt B: tighter stops (2.5 ATR), quick targets (1.5 ATR), tight trailing (3%), short hold (10d). Cut losses fast.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "signal": { "score_threshold": 0.5 }, + "risk": { "stop_atr_multiplier": 2.5 }, + "execution": { + "target_atr_multiplier": 1.5, + "trailing_model": "pct_3", + "max_holding_days": 10 + } + }, + "tags": ["exec-opt", "tight"] +} diff --git a/configs/experiments/exec_opt_c_no_partial.json b/configs/experiments/exec_opt_c_no_partial.json new file mode 100644 index 0000000..013ee87 --- /dev/null +++ b/configs/experiments/exec_opt_c_no_partial.json @@ -0,0 +1,15 @@ +{ + "experiment_name": "exec_opt_c_no_partial", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "Execution opt C: no partial exit (full position rides to trailing/stop/max_hold). Tests if partial exit is killing winners.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "signal": { "score_threshold": 0.5 }, + "execution": { + "target_1_fraction": 0.0, + "trailing_model": "pct_5", + "max_holding_days": 15 + } + }, + "tags": ["exec-opt", "no-partial"] +} diff --git a/configs/experiments/exec_opt_d_wide_trail.json b/configs/experiments/exec_opt_d_wide_trail.json new file mode 100644 index 0000000..3fb9066 --- /dev/null +++ b/configs/experiments/exec_opt_d_wide_trail.json @@ -0,0 +1,14 @@ +{ + "experiment_name": "exec_opt_d_wide_trail", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "Execution opt D: default stops/targets but much wider trailing (10%). Tests if trailing stop is choking winners prematurely.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "signal": { "score_threshold": 0.5 }, + "execution": { + "trailing_model": "pct_10", + "max_holding_days": 15 + } + }, + "tags": ["exec-opt", "wide-trail"] +} diff --git a/configs/experiments/exec_opt_e_big_winners.json b/configs/experiments/exec_opt_e_big_winners.json new file mode 100644 index 0000000..5a0107e --- /dev/null +++ b/configs/experiments/exec_opt_e_big_winners.json @@ -0,0 +1,17 @@ +{ + "experiment_name": "exec_opt_e_big_winners", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "Execution opt E: big winner focus. Wider stops (3.5 ATR), high targets (2.5 ATR), wide trailing (10%), long hold (20d), small partial (33%).", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "signal": { "score_threshold": 0.5 }, + "risk": { "stop_atr_multiplier": 3.5 }, + "execution": { + "target_atr_multiplier": 2.5, + "target_1_fraction": 0.33, + "trailing_model": "pct_10", + "max_holding_days": 20 + } + }, + "tags": ["exec-opt", "big-winners"] +} diff --git a/configs/experiments/pead_10pct.json b/configs/experiments/pead_10pct.json new file mode 100644 index 0000000..5ab634f --- /dev/null +++ b/configs/experiments/pead_10pct.json @@ -0,0 +1,29 @@ +{ + "experiment_name": "pead_10pct", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "PEAD 10% threshold: ultra-selective, only biggest earnings reactions. Maximum signal strength.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "bullish_only" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "risk": { + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "10pct"] +} diff --git a/configs/experiments/pead_10pct_expanded.json b/configs/experiments/pead_10pct_expanded.json new file mode 100644 index 0000000..d4e4595 --- /dev/null +++ b/configs/experiments/pead_10pct_expanded.json @@ -0,0 +1,34 @@ +{ + "experiment_name": "pead_10pct_expanded", + "dataset_snapshot_id": "5f3c99d9-693c-48d6-88bf-644666559ca9", + "description": "Expanded universe with 10% reaction threshold. Large-cap 7% reactions include noise (sector rotation, short covering). 10%+ reactions are more likely genuine PEAD drift. Quality > quantity approach.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 4, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "10pct", "longshort", "expanded", "universe-741"] +} diff --git a/configs/experiments/pead_10pct_smallmid.json b/configs/experiments/pead_10pct_smallmid.json new file mode 100644 index 0000000..1b29ac5 --- /dev/null +++ b/configs/experiments/pead_10pct_smallmid.json @@ -0,0 +1,34 @@ +{ + "experiment_name": "pead_10pct_smallmid", + "dataset_snapshot_id": "smallmid-only-8ef47ee5", + "description": "PEAD 10% L+S on small/mid-cap universe. Higher bar for reaction strength on smaller stocks where 7% reactions are more common.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "10pct", "longshort", "smallmid", "nosectorlimit"] +} diff --git a/configs/experiments/pead_5pct_longshort_v2.json b/configs/experiments/pead_5pct_longshort_v2.json new file mode 100644 index 0000000..8c2486a --- /dev/null +++ b/configs/experiments/pead_5pct_longshort_v2.json @@ -0,0 +1,34 @@ +{ + "experiment_name": "pead_5pct_longshort_v2", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "Long+Short PEAD with 5% threshold: 8 max positions, 4 per sector, higher daily risk budget. Tests lower threshold for more trades.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.05, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 4, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "5pct", "longshort", "expanded"] +} diff --git a/configs/experiments/pead_7pct_best_combo.json b/configs/experiments/pead_7pct_best_combo.json new file mode 100644 index 0000000..1e3dfaa --- /dev/null +++ b/configs/experiments/pead_7pct_best_combo.json @@ -0,0 +1,35 @@ +{ + "experiment_name": "pead_7pct_best_combo", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "PEAD 7% best combo: tighter stop (2.0 ATR) + more positions (6) + no cooldown. Higher capital deployment with tighter risk control.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "bullish_only" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "risk": { + "stop_atr_multiplier": 2.0, + "max_positions": 6, + "max_positions_per_sector": 3, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "max_daily_new_risk_pct": 0.03, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "combo", "stop2", "uncapped"] +} diff --git a/configs/experiments/pead_7pct_drift_a.json b/configs/experiments/pead_7pct_drift_a.json new file mode 100644 index 0000000..a30d7a8 --- /dev/null +++ b/configs/experiments/pead_7pct_drift_a.json @@ -0,0 +1,37 @@ +{ + "experiment_name": "pead_7pct_drift_a", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "PEAD 7% with extended hold (25d) and wider target (3.0 ATR). Same stop. Tests if longer hold captures more drift.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "bullish_only", + "max_holding_days_override": 25 + }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "execution": { + "max_holding_days": 25, + "target_atr_multiplier": 3.0 + }, + "risk": { + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "drift", "hold25", "wide_target"] +} diff --git a/configs/experiments/pead_7pct_drift_a_phase6.json b/configs/experiments/pead_7pct_drift_a_phase6.json new file mode 100644 index 0000000..355a6b0 --- /dev/null +++ b/configs/experiments/pead_7pct_drift_a_phase6.json @@ -0,0 +1,37 @@ +{ + "experiment_name": "pead_7pct_drift_a_phase6", + "dataset_snapshot_id": "46950fc5-4155-4eb0-ab1c-018774e444cc", + "description": "PEAD 7% Drift A (hold25, wide target 3.0 ATR) on Phase 6 dataset.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "bullish_only", + "max_holding_days_override": 25 + }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "execution": { + "max_holding_days": 25, + "target_atr_multiplier": 3.0 + }, + "risk": { + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "drift", "hold25", "phase6"] +} diff --git a/configs/experiments/pead_7pct_drift_b.json b/configs/experiments/pead_7pct_drift_b.json new file mode 100644 index 0000000..58ee9a9 --- /dev/null +++ b/configs/experiments/pead_7pct_drift_b.json @@ -0,0 +1,38 @@ +{ + "experiment_name": "pead_7pct_drift_b", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "PEAD 7% with extended hold (25d), no partial exit, wider target (3.0 ATR). Full position rides the drift.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "bullish_only", + "max_holding_days_override": 25 + }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "execution": { + "max_holding_days": 25, + "target_atr_multiplier": 3.0, + "target_1_fraction": null + }, + "risk": { + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "drift", "hold25", "no_partial"] +} diff --git a/configs/experiments/pead_7pct_drift_c.json b/configs/experiments/pead_7pct_drift_c.json new file mode 100644 index 0000000..e77770c --- /dev/null +++ b/configs/experiments/pead_7pct_drift_c.json @@ -0,0 +1,42 @@ +{ + "experiment_name": "pead_7pct_drift_c", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "PEAD 7% aggressive: 2x risk (1%), 6 max positions, hold 25d, no partial exit, wide target. Maximizes capital deployment on high-conviction PEAD trades.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "bullish_only", + "max_holding_days_override": 25 + }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "execution": { + "max_holding_days": 25, + "target_atr_multiplier": 3.0, + "target_1_fraction": null + }, + "risk": { + "per_trade_risk_pct": 0.01, + "max_daily_new_risk_pct": 0.03, + "max_positions": 6, + "max_positions_per_sector": 3, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "drift", "hold25", "aggressive"] +} diff --git a/configs/experiments/pead_7pct_expanded_combo_a.json b/configs/experiments/pead_7pct_expanded_combo_a.json new file mode 100644 index 0000000..dd39ab4 --- /dev/null +++ b/configs/experiments/pead_7pct_expanded_combo_a.json @@ -0,0 +1,34 @@ +{ + "experiment_name": "pead_7pct_expanded_combo_a", + "dataset_snapshot_id": "5f3c99d9-693c-48d6-88bf-644666559ca9", + "description": "Combo A: nosectorlimit + 10pct threshold. Removes sector gate bug AND raises reaction bar to filter large-cap noise. Expects fewer but higher-quality trades.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "10pct", "longshort", "expanded", "universe-741", "nosectorlimit", "combo"] +} diff --git a/configs/experiments/pead_7pct_expanded_combo_b.json b/configs/experiments/pead_7pct_expanded_combo_b.json new file mode 100644 index 0000000..b63ce58 --- /dev/null +++ b/configs/experiments/pead_7pct_expanded_combo_b.json @@ -0,0 +1,35 @@ +{ + "experiment_name": "pead_7pct_expanded_combo_b", + "dataset_snapshot_id": "5f3c99d9-693c-48d6-88bf-644666559ca9", + "description": "Combo B: nosectorlimit + 10pct threshold + stop ATR 2.0. Triple fix: remove sector bug, raise quality bar, widen stop for post-earnings volatility.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "stop_atr_multiplier": 2.0, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "10pct", "longshort", "expanded", "universe-741", "nosectorlimit", "stop2", "combo"] +} diff --git a/configs/experiments/pead_7pct_expanded_combo_c.json b/configs/experiments/pead_7pct_expanded_combo_c.json new file mode 100644 index 0000000..be1c2e5 --- /dev/null +++ b/configs/experiments/pead_7pct_expanded_combo_c.json @@ -0,0 +1,34 @@ +{ + "experiment_name": "pead_7pct_expanded_combo_c", + "dataset_snapshot_id": "5f3c99d9-693c-48d6-88bf-644666559ca9", + "description": "Combo C: ALL fixes combined. nosectorlimit + 10pct + maxcand4 + continuous scoring. Maximum selectivity on expanded universe — only top 4 candidates per day with 10%+ reaction.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 4 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "10pct", "longshort", "expanded", "universe-741", "nosectorlimit", "maxcand4", "combo"] +} diff --git a/configs/experiments/pead_7pct_expanded_continuous.json b/configs/experiments/pead_7pct_expanded_continuous.json new file mode 100644 index 0000000..121bea6 --- /dev/null +++ b/configs/experiments/pead_7pct_expanded_continuous.json @@ -0,0 +1,34 @@ +{ + "experiment_name": "pead_7pct_expanded_continuous", + "dataset_snapshot_id": "5f3c99d9-693c-48d6-88bf-644666559ca9", + "description": "Expanded universe with continuous PEAD scoring. Multi-factor score (reaction 60%, volume 25%, gap 15%) replaces binary pass/fail. Combined with max_candidates_per_day=4 for synergy — top 4 by continuous score instead of volume ranking.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 4 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 4, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "longshort", "expanded", "universe-741", "continuous-score"] +} diff --git a/configs/experiments/pead_7pct_expanded_maxcand4.json b/configs/experiments/pead_7pct_expanded_maxcand4.json new file mode 100644 index 0000000..543dec7 --- /dev/null +++ b/configs/experiments/pead_7pct_expanded_maxcand4.json @@ -0,0 +1,34 @@ +{ + "experiment_name": "pead_7pct_expanded_maxcand4", + "dataset_snapshot_id": "5f3c99d9-693c-48d6-88bf-644666559ca9", + "description": "Expanded universe with max 4 candidates per day (down from 8). With 741 symbols, earnings season produces 10+ qualifying events per day — limiting to top 4 reduces concentration risk and improves signal quality.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 4 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 4, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "longshort", "expanded", "universe-741", "maxcand4"] +} diff --git a/configs/experiments/pead_7pct_expanded_nosectorlimit.json b/configs/experiments/pead_7pct_expanded_nosectorlimit.json new file mode 100644 index 0000000..0985b38 --- /dev/null +++ b/configs/experiments/pead_7pct_expanded_nosectorlimit.json @@ -0,0 +1,34 @@ +{ + "experiment_name": "pead_7pct_expanded_nosectorlimit", + "dataset_snapshot_id": "5f3c99d9-693c-48d6-88bf-644666559ca9", + "description": "Expanded universe with sector gate disabled. All symbols have sector=UNKNOWN, so max_positions_per_sector=4 was randomly blocking entries after 4 positions. Setting equal to max_positions effectively disables the gate.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "longshort", "expanded", "universe-741", "nosectorlimit"] +} diff --git a/configs/experiments/pead_7pct_expanded_stop2.json b/configs/experiments/pead_7pct_expanded_stop2.json new file mode 100644 index 0000000..f64cc58 --- /dev/null +++ b/configs/experiments/pead_7pct_expanded_stop2.json @@ -0,0 +1,35 @@ +{ + "experiment_name": "pead_7pct_expanded_stop2", + "dataset_snapshot_id": "5f3c99d9-693c-48d6-88bf-644666559ca9", + "description": "Expanded universe with wider 2.0 ATR stop. Test split showed 75% stop exit rate — post-earnings volatility triggers 1.5 ATR stops prematurely. Wider stop allows trades to reach profit target.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 4, + "max_daily_new_risk_pct": 0.04, + "stop_atr_multiplier": 2.0, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "longshort", "expanded", "universe-741", "stop2"] +} diff --git a/configs/experiments/pead_7pct_longshort.json b/configs/experiments/pead_7pct_longshort.json new file mode 100644 index 0000000..10a92d6 --- /dev/null +++ b/configs/experiments/pead_7pct_longshort.json @@ -0,0 +1,32 @@ +{ + "experiment_name": "pead_7pct_longshort", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "Long+Short PEAD: buy after +7% earnings reaction, short after -7% reaction. Both directions with volume >= 1.5x.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "risk": { + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "longshort"] +} diff --git a/configs/experiments/pead_7pct_longshort_v2.json b/configs/experiments/pead_7pct_longshort_v2.json new file mode 100644 index 0000000..616018b --- /dev/null +++ b/configs/experiments/pead_7pct_longshort_v2.json @@ -0,0 +1,34 @@ +{ + "experiment_name": "pead_7pct_longshort_v2", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "Long+Short PEAD with expanded capacity: 8 max positions, 4 per sector, higher daily risk budget. Allows both sides to coexist.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 4, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "longshort", "expanded"] +} diff --git a/configs/experiments/pead_7pct_longshort_v2_expanded.json b/configs/experiments/pead_7pct_longshort_v2_expanded.json new file mode 100644 index 0000000..7887e9a --- /dev/null +++ b/configs/experiments/pead_7pct_longshort_v2_expanded.json @@ -0,0 +1,34 @@ +{ + "experiment_name": "pead_7pct_longshort_v2_expanded", + "dataset_snapshot_id": "5f3c99d9-693c-48d6-88bf-644666559ca9", + "description": "PEAD 7% L+S v2 on expanded universe (~741 symbols via screener $10B+ mcap, 1M+ vol). Same signal/risk params as v2, testing trade frequency scaling.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 4, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "longshort", "expanded", "universe-741"] +} diff --git a/configs/experiments/pead_7pct_lowliq.json b/configs/experiments/pead_7pct_lowliq.json new file mode 100644 index 0000000..1f361bc --- /dev/null +++ b/configs/experiments/pead_7pct_lowliq.json @@ -0,0 +1,33 @@ +{ + "experiment_name": "pead_7pct_lowliq", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "PEAD 7% with relaxed universe filter: min_avg_dollar_volume 200K (from 1M) and min_price 3 (from 5). Captures more events in same dataset.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "bullish_only" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "universe": { + "min_price": 3.0, + "min_avg_dollar_volume": 200000.0 + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "risk": { + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "lowliq", "universe_expansion"] +} diff --git a/configs/experiments/pead_7pct_macro.json b/configs/experiments/pead_7pct_macro.json new file mode 100644 index 0000000..efc405a --- /dev/null +++ b/configs/experiments/pead_7pct_macro.json @@ -0,0 +1,31 @@ +{ + "experiment_name": "pead_7pct_macro", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "PEAD 7% + macro regime filter: only enter when SPY > SMA(20). Avoid PEAD trades in bear market.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "bullish_only" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "risk": { + "macro_regime_enabled": true, + "macro_sma_period": 20, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "macro"] +} diff --git a/configs/experiments/pead_7pct_midcap.json b/configs/experiments/pead_7pct_midcap.json new file mode 100644 index 0000000..85ba9a2 --- /dev/null +++ b/configs/experiments/pead_7pct_midcap.json @@ -0,0 +1,34 @@ +{ + "experiment_name": "pead_7pct_midcap", + "dataset_snapshot_id": "midcap-filtered", + "description": "PEAD 7% L+S on mid-cap universe ($2B-$10B, ~104 symbols). Academic PEAD strongest in less-followed stocks.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "longshort", "midcap", "2b-10b"] +} diff --git a/configs/experiments/pead_7pct_nft.json b/configs/experiments/pead_7pct_nft.json new file mode 100644 index 0000000..986e3c2 --- /dev/null +++ b/configs/experiments/pead_7pct_nft.json @@ -0,0 +1,32 @@ +{ + "experiment_name": "pead_7pct_nft", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "PEAD 7% + No-Follow-Through exit: if D+1 close < entry price, exit immediately. Quick cut of non-drifters.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "bullish_only" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "execution": { + "no_follow_through_exit": true + }, + "risk": { + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "nft"] +} diff --git a/configs/experiments/pead_7pct_nft_macro.json b/configs/experiments/pead_7pct_nft_macro.json new file mode 100644 index 0000000..1dfe725 --- /dev/null +++ b/configs/experiments/pead_7pct_nft_macro.json @@ -0,0 +1,34 @@ +{ + "experiment_name": "pead_7pct_nft_macro", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "PEAD 7% combined: no-follow-through exit + macro regime filter. Double filter for quality.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "bullish_only" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "execution": { + "no_follow_through_exit": true + }, + "risk": { + "macro_regime_enabled": true, + "macro_sma_period": 20, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "nft", "macro", "combined"] +} diff --git a/configs/experiments/pead_7pct_notrail.json b/configs/experiments/pead_7pct_notrail.json new file mode 100644 index 0000000..68ebe59 --- /dev/null +++ b/configs/experiments/pead_7pct_notrail.json @@ -0,0 +1,32 @@ +{ + "experiment_name": "pead_7pct_notrail", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "PEAD 7% with no trailing stop. Only fixed stop + target + time exit. Test if trailing is hurting by cutting winners early.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "bullish_only" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "execution": { + "trailing_model": null + }, + "risk": { + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "notrail"] +} diff --git a/configs/experiments/pead_7pct_phase6.json b/configs/experiments/pead_7pct_phase6.json new file mode 100644 index 0000000..107aaa8 --- /dev/null +++ b/configs/experiments/pead_7pct_phase6.json @@ -0,0 +1,32 @@ +{ + "experiment_name": "pead_7pct_phase6", + "dataset_snapshot_id": "46950fc5-4155-4eb0-ab1c-018774e444cc", + "description": "PEAD 7% base strategy on Phase 6 dataset for cross-validation.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "bullish_only" + }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "risk": { + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "phase6", "cross_validation"] +} diff --git a/configs/experiments/pead_7pct_short.json b/configs/experiments/pead_7pct_short.json new file mode 100644 index 0000000..3bbbd40 --- /dev/null +++ b/configs/experiments/pead_7pct_short.json @@ -0,0 +1,32 @@ +{ + "experiment_name": "pead_7pct_short", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "Short-side PEAD strategy: short after earnings with reaction <= -7% and volume >= 1.5x. Negative drift after bad earnings reactions.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "bearish_only" + }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "risk": { + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "short", "7pct"] +} diff --git a/configs/experiments/pead_7pct_smallmid.json b/configs/experiments/pead_7pct_smallmid.json new file mode 100644 index 0000000..064e94a --- /dev/null +++ b/configs/experiments/pead_7pct_smallmid.json @@ -0,0 +1,30 @@ +{ + "experiment_name": "pead_7pct_smallmid", + "dataset_snapshot_id": "smallmid-only-8ef47ee5", + "description": "PEAD 7% on small/mid-cap dataset. Tests if PEAD signal works on smaller stocks (previously failed with default scoring).", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "bullish_only" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "risk": { + "stop_atr_multiplier": 3.0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "smallmid"] +} diff --git a/configs/experiments/pead_7pct_smallmid_v2.json b/configs/experiments/pead_7pct_smallmid_v2.json new file mode 100644 index 0000000..d78b3fd --- /dev/null +++ b/configs/experiments/pead_7pct_smallmid_v2.json @@ -0,0 +1,34 @@ +{ + "experiment_name": "pead_7pct_smallmid_v2", + "dataset_snapshot_id": "smallmid-only-8ef47ee5", + "description": "PEAD 7% L+S on small/mid-cap universe (~185 symbols). v2 capacity settings (8 max pos, no sector limit). Academic PEAD strongest in less-followed stocks.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "longshort", "smallmid", "nosectorlimit"] +} diff --git a/configs/experiments/pead_7pct_stop2.json b/configs/experiments/pead_7pct_stop2.json new file mode 100644 index 0000000..a19cabb --- /dev/null +++ b/configs/experiments/pead_7pct_stop2.json @@ -0,0 +1,30 @@ +{ + "experiment_name": "pead_7pct_stop2", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "PEAD 7% with tighter stop (2.0 ATR instead of 3.0). Reduce avg loss per trade.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "bullish_only" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "risk": { + "stop_atr_multiplier": 2.0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "stop2"] +} diff --git a/configs/experiments/pead_7pct_stop4.json b/configs/experiments/pead_7pct_stop4.json new file mode 100644 index 0000000..d116d50 --- /dev/null +++ b/configs/experiments/pead_7pct_stop4.json @@ -0,0 +1,30 @@ +{ + "experiment_name": "pead_7pct_stop4", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "PEAD 7% with wider stop (4.0 ATR instead of 3.0). Give trades more room to breathe.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "bullish_only" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "risk": { + "stop_atr_multiplier": 4.0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "stop4"] +} diff --git a/configs/experiments/pead_7pct_uncapped.json b/configs/experiments/pead_7pct_uncapped.json new file mode 100644 index 0000000..37a7cf2 --- /dev/null +++ b/configs/experiments/pead_7pct_uncapped.json @@ -0,0 +1,34 @@ +{ + "experiment_name": "pead_7pct_uncapped", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "PEAD 7% with more capacity: 6 max positions, no cooldown, 3% daily risk budget. More capital deployed.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "bullish_only" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "risk": { + "max_positions": 6, + "max_positions_per_sector": 3, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "max_daily_new_risk_pct": 0.03, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "uncapped"] +} diff --git a/configs/experiments/pead_7pct_vol1x.json b/configs/experiments/pead_7pct_vol1x.json new file mode 100644 index 0000000..ee5639d --- /dev/null +++ b/configs/experiments/pead_7pct_vol1x.json @@ -0,0 +1,29 @@ +{ + "experiment_name": "pead_7pct_vol1x", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "PEAD 7% with lower volume threshold (1.0x instead of 1.5x). More trades by relaxing volume filter.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "bullish_only" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.0, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "risk": { + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "vol1x"] +} diff --git a/configs/experiments/pead_midcap_combo_10pct_maxcand3.json b/configs/experiments/pead_midcap_combo_10pct_maxcand3.json new file mode 100644 index 0000000..710f100 --- /dev/null +++ b/configs/experiments/pead_midcap_combo_10pct_maxcand3.json @@ -0,0 +1,34 @@ +{ + "experiment_name": "pead_midcap_combo_10pct_maxcand3", + "dataset_snapshot_id": "midcap-filtered", + "description": "Combo: Step3 (10% threshold) + Step5 (max 3 candidates/day). Both are signal quality filters — strongest midcap PEAD signals only.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "10pct", "longshort", "midcap", "combo", "maxcand3"] +} diff --git a/configs/experiments/pead_midcap_step10_short.json b/configs/experiments/pead_midcap_step10_short.json new file mode 100644 index 0000000..39179f4 --- /dev/null +++ b/configs/experiments/pead_midcap_step10_short.json @@ -0,0 +1,39 @@ +{ + "experiment_name": "pead_midcap_step10_short", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 10: Combo base (10% + maxcand3) + shorter hold (7 days). Avg holding is 3.4 days, so 15-day max is unnecessary exposure. 7 days captures PEAD drift without mean reversion.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": {"enabled": true, "direction_filter": "any", "max_holding_days_override": 7}, + "guidance_update": {"enabled": false}, + "management_change": {"enabled": false}, + "material_contract": {"enabled": false}, + "unknown": {"enabled": false}, + "other_material_event": {"enabled": false} + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": ["pead", "midcap", "step10", "short", "combo"], + "notes": null +} diff --git a/configs/experiments/pead_midcap_step11_score60.json b/configs/experiments/pead_midcap_step11_score60.json new file mode 100644 index 0000000..c454035 --- /dev/null +++ b/configs/experiments/pead_midcap_step11_score60.json @@ -0,0 +1,36 @@ +{ + "experiment_name": "pead_midcap_step11_score60", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 11: Combo base + higher score threshold (0.60). All prior experiments used 0.50 (below default 0.55). Raising to 0.60 should filter weak signals and improve trade quality.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": {"enabled": true, "direction_filter": "any"}, + "guidance_update": {"enabled": false}, + "management_change": {"enabled": false}, + "material_contract": {"enabled": false}, + "unknown": {"enabled": false}, + "other_material_event": {"enabled": false} + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 1.5, + "score_threshold": 0.60, + "max_candidates_per_day": 3 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": ["pead", "midcap", "step11", "score60", "combo"], + "notes": null +} diff --git a/configs/experiments/pead_midcap_step12_vol2x.json b/configs/experiments/pead_midcap_step12_vol2x.json new file mode 100644 index 0000000..0d3b6fb --- /dev/null +++ b/configs/experiments/pead_midcap_step12_vol2x.json @@ -0,0 +1,36 @@ +{ + "experiment_name": "pead_midcap_step12_vol2x", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 12: Combo base + volume threshold 2.0x (from 1.5x). Require stronger volume conviction on reaction day. Higher volume = more institutional participation = stronger drift.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": {"enabled": true, "direction_filter": "any"}, + "guidance_update": {"enabled": false}, + "management_change": {"enabled": false}, + "material_contract": {"enabled": false}, + "unknown": {"enabled": false}, + "other_material_event": {"enabled": false} + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 2.0, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": ["pead", "midcap", "step12", "vol2x", "combo"], + "notes": null +} diff --git a/configs/experiments/pead_midcap_step13_best.json b/configs/experiments/pead_midcap_step13_best.json new file mode 100644 index 0000000..537040c --- /dev/null +++ b/configs/experiments/pead_midcap_step13_best.json @@ -0,0 +1,39 @@ +{ + "experiment_name": "pead_midcap_step13_best", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 13: Best combo. 10% reaction + maxcand3 + score 0.60 + short hold 7d + vol 2.0x. Combines all positive findings from steps 10-12.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": {"enabled": true, "direction_filter": "any", "max_holding_days_override": 7}, + "guidance_update": {"enabled": false}, + "management_change": {"enabled": false}, + "material_contract": {"enabled": false}, + "unknown": {"enabled": false}, + "other_material_event": {"enabled": false} + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 2.0, + "score_threshold": 0.60, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": ["pead", "midcap", "step13", "best", "combo"], + "notes": null +} diff --git a/configs/experiments/pead_midcap_step14_score65.json b/configs/experiments/pead_midcap_step14_score65.json new file mode 100644 index 0000000..0001558 --- /dev/null +++ b/configs/experiments/pead_midcap_step14_score65.json @@ -0,0 +1,39 @@ +{ + "experiment_name": "pead_midcap_step14_score65", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 14: Higher score threshold 0.65 (from 0.60). Stronger signal filter to improve WR/PF with fewer trades.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": {"enabled": true, "direction_filter": "any", "max_holding_days_override": 7}, + "guidance_update": {"enabled": false}, + "management_change": {"enabled": false}, + "material_contract": {"enabled": false}, + "unknown": {"enabled": false}, + "other_material_event": {"enabled": false} + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": ["pead", "midcap", "step14", "score65"], + "notes": null +} diff --git a/configs/experiments/pead_midcap_step15_react7.json b/configs/experiments/pead_midcap_step15_react7.json new file mode 100644 index 0000000..76418fe --- /dev/null +++ b/configs/experiments/pead_midcap_step15_react7.json @@ -0,0 +1,39 @@ +{ + "experiment_name": "pead_midcap_step15_react7", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 15: Lower reaction threshold 0.07 (from 0.10). Wider funnel to increase trade count for statistical significance.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": {"enabled": true, "direction_filter": "any", "max_holding_days_override": 7}, + "guidance_update": {"enabled": false}, + "management_change": {"enabled": false}, + "material_contract": {"enabled": false}, + "unknown": {"enabled": false}, + "other_material_event": {"enabled": false} + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 2.0, + "score_threshold": 0.60, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": ["pead", "midcap", "step15", "react7"], + "notes": null +} diff --git a/configs/experiments/pead_midcap_step16_react7_score65.json b/configs/experiments/pead_midcap_step16_react7_score65.json new file mode 100644 index 0000000..a135bdd --- /dev/null +++ b/configs/experiments/pead_midcap_step16_react7_score65.json @@ -0,0 +1,39 @@ +{ + "experiment_name": "pead_midcap_step16_react7_score65", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 16: Combined lower reaction 0.07 + higher score 0.65. Wider funnel filtered by stricter quality gate.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": {"enabled": true, "direction_filter": "any", "max_holding_days_override": 7}, + "guidance_update": {"enabled": false}, + "management_change": {"enabled": false}, + "material_contract": {"enabled": false}, + "unknown": {"enabled": false}, + "other_material_event": {"enabled": false} + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": ["pead", "midcap", "step16", "react7", "score65"], + "notes": null +} diff --git a/configs/experiments/pead_midcap_step17_target2.json b/configs/experiments/pead_midcap_step17_target2.json new file mode 100644 index 0000000..a691f3e --- /dev/null +++ b/configs/experiments/pead_midcap_step17_target2.json @@ -0,0 +1,40 @@ +{ + "experiment_name": "pead_midcap_step17_target2", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 17: Target ATR 2.0 (from 1.5). Based on step14 (score 0.65). Improves R:R from 3:1.5 to 3:2.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": {"enabled": true, "direction_filter": "any", "max_holding_days_override": 7}, + "guidance_update": {"enabled": false}, + "management_change": {"enabled": false}, + "material_contract": {"enabled": false}, + "unknown": {"enabled": false}, + "other_material_event": {"enabled": false} + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7, + "target_atr_multiplier": 2.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": ["pead", "midcap", "step17", "target2"], + "notes": null +} diff --git a/configs/experiments/pead_midcap_step18_nofrac.json b/configs/experiments/pead_midcap_step18_nofrac.json new file mode 100644 index 0000000..8b9b1e7 --- /dev/null +++ b/configs/experiments/pead_midcap_step18_nofrac.json @@ -0,0 +1,40 @@ +{ + "experiment_name": "pead_midcap_step18_nofrac", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 18: Full exit at target (fraction 1.0 from 0.5). Based on step14 (score 0.65). Prevents trailing stop eating profits.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": {"enabled": true, "direction_filter": "any", "max_holding_days_override": 7}, + "guidance_update": {"enabled": false}, + "management_change": {"enabled": false}, + "material_contract": {"enabled": false}, + "unknown": {"enabled": false}, + "other_material_event": {"enabled": false} + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7, + "target_1_fraction": 1.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": ["pead", "midcap", "step18", "nofrac"], + "notes": null +} diff --git a/configs/experiments/pead_midcap_step19_hold5.json b/configs/experiments/pead_midcap_step19_hold5.json new file mode 100644 index 0000000..c641790 --- /dev/null +++ b/configs/experiments/pead_midcap_step19_hold5.json @@ -0,0 +1,39 @@ +{ + "experiment_name": "pead_midcap_step19_hold5", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 19: Shorter hold 5d (from 7d). Based on step14 (score 0.65). Avg hold is 3.28d so 5d should be sufficient.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": {"enabled": true, "direction_filter": "any", "max_holding_days_override": 5}, + "guidance_update": {"enabled": false}, + "management_change": {"enabled": false}, + "material_contract": {"enabled": false}, + "unknown": {"enabled": false}, + "other_material_event": {"enabled": false} + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 5 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": ["pead", "midcap", "step19", "hold5"], + "notes": null +} diff --git a/configs/experiments/pead_midcap_step1_fixedr.json b/configs/experiments/pead_midcap_step1_fixedr.json new file mode 100644 index 0000000..1744738 --- /dev/null +++ b/configs/experiments/pead_midcap_step1_fixedr.json @@ -0,0 +1,38 @@ +{ + "experiment_name": "pead_midcap_step1_fixedr", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 1: R:R fix. Switch to fixed_r target_1_r=2.0 (target=2x stop). Breakeven WR drops from 67% to 33%. Baseline uses atr_multiple 1.5 ATR target vs 3.0 ATR stop.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "execution": { + "target_model": "fixed_r", + "target_1_r": 2.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "longshort", "midcap", "step1", "fixedr"] +} diff --git a/configs/experiments/pead_midcap_step20_best3.json b/configs/experiments/pead_midcap_step20_best3.json new file mode 100644 index 0000000..29eb943 --- /dev/null +++ b/configs/experiments/pead_midcap_step20_best3.json @@ -0,0 +1,40 @@ +{ + "experiment_name": "pead_midcap_step20_best3", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 20: Best combo. score 0.65 (step14) + nofrac 1.0 (step18) + hold 5d (step19). Combines all marginally positive Phase A+B findings.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": {"enabled": true, "direction_filter": "any", "max_holding_days_override": 5}, + "guidance_update": {"enabled": false}, + "management_change": {"enabled": false}, + "material_contract": {"enabled": false}, + "unknown": {"enabled": false}, + "other_material_event": {"enabled": false} + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 5, + "target_1_fraction": 1.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": ["pead", "midcap", "step20", "best3", "combo"], + "notes": null +} diff --git a/configs/experiments/pead_midcap_step2_notrail.json b/configs/experiments/pead_midcap_step2_notrail.json new file mode 100644 index 0000000..2ce010e --- /dev/null +++ b/configs/experiments/pead_midcap_step2_notrail.json @@ -0,0 +1,37 @@ +{ + "experiment_name": "pead_midcap_step2_notrail", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 2: Disable trailing stop. Baseline pct_5 trail may be too tight for midcap volatility, cutting PEAD drift early.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "execution": { + "trailing_model": null + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "longshort", "midcap", "step2", "notrail"] +} diff --git a/configs/experiments/pead_midcap_step3_10pct.json b/configs/experiments/pead_midcap_step3_10pct.json new file mode 100644 index 0000000..7c1d4a8 --- /dev/null +++ b/configs/experiments/pead_midcap_step3_10pct.json @@ -0,0 +1,34 @@ +{ + "experiment_name": "pead_midcap_step3_10pct", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 3: Raise reaction threshold to 10%. Stronger signal filter, fewer trades, higher quality expected.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "10pct", "longshort", "midcap", "step3", "threshold"] +} diff --git a/configs/experiments/pead_midcap_step4_longonly.json b/configs/experiments/pead_midcap_step4_longonly.json new file mode 100644 index 0000000..a999d0b --- /dev/null +++ b/configs/experiments/pead_midcap_step4_longonly.json @@ -0,0 +1,34 @@ +{ + "experiment_name": "pead_midcap_step4_longonly", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 4: Long-only. Disable short side to test if midcap short PEAD mean-reverts instead of drifting.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "bullish_only" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "longonly", "midcap", "step4", "bullish"] +} diff --git a/configs/experiments/pead_midcap_step5_maxcand3.json b/configs/experiments/pead_midcap_step5_maxcand3.json new file mode 100644 index 0000000..27f8fea --- /dev/null +++ b/configs/experiments/pead_midcap_step5_maxcand3.json @@ -0,0 +1,34 @@ +{ + "experiment_name": "pead_midcap_step5_maxcand3", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 5: Limit to 3 candidates per day. Remove lower-ranked signals, keep only top entries.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "longshort", "midcap", "step5", "maxcand3"] +} diff --git a/configs/experiments/pead_midcap_step6_drift.json b/configs/experiments/pead_midcap_step6_drift.json new file mode 100644 index 0000000..fc141ce --- /dev/null +++ b/configs/experiments/pead_midcap_step6_drift.json @@ -0,0 +1,38 @@ +{ + "experiment_name": "pead_midcap_step6_drift", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 6: Extended hold 25 days + disable partial exit. Test if PEAD drift needs more time than 15 days in midcap.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { "enabled": true, "direction_filter": "any" }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "execution": { + "max_holding_days": 25, + "target_1_fraction": null + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "7pct", "longshort", "midcap", "step6", "drift25"] +} diff --git a/configs/experiments/pead_midcap_step7_fixedr.json b/configs/experiments/pead_midcap_step7_fixedr.json new file mode 100644 index 0000000..a8d2bc0 --- /dev/null +++ b/configs/experiments/pead_midcap_step7_fixedr.json @@ -0,0 +1,40 @@ +{ + "experiment_name": "pead_midcap_step7_fixedr", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 7: Combo base (10% threshold + max 3 cand) + fixed_r target 2.0. Fixes inverted R:R from atr_multiple (1.5 ATR target vs 3.0 ATR stop). With proper R:R, need only 33% WR to breakeven.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": {"enabled": true, "direction_filter": "any"}, + "guidance_update": {"enabled": false}, + "management_change": {"enabled": false}, + "material_contract": {"enabled": false}, + "unknown": {"enabled": false}, + "other_material_event": {"enabled": false} + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "execution": { + "target_model": "fixed_r", + "target_1_r": 2.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": ["pead", "midcap", "step7", "fixedr", "combo"], + "notes": null +} diff --git a/configs/experiments/pead_midcap_step8_nft.json b/configs/experiments/pead_midcap_step8_nft.json new file mode 100644 index 0000000..cace14e --- /dev/null +++ b/configs/experiments/pead_midcap_step8_nft.json @@ -0,0 +1,41 @@ +{ + "experiment_name": "pead_midcap_step8_nft", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 8: Step 7 base + no_follow_through_exit. Exit at D+1 close if price < entry. Cuts dead trades early before stop is hit, reducing 85% stop exit rate.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": {"enabled": true, "direction_filter": "any"}, + "guidance_update": {"enabled": false}, + "management_change": {"enabled": false}, + "material_contract": {"enabled": false}, + "unknown": {"enabled": false}, + "other_material_event": {"enabled": false} + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "execution": { + "target_model": "fixed_r", + "target_1_r": 2.0, + "no_follow_through_exit": true + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": ["pead", "midcap", "step8", "nft", "fixedr", "combo"], + "notes": null +} diff --git a/configs/experiments/pead_midcap_step9_stop2.json b/configs/experiments/pead_midcap_step9_stop2.json new file mode 100644 index 0000000..474fbb3 --- /dev/null +++ b/configs/experiments/pead_midcap_step9_stop2.json @@ -0,0 +1,41 @@ +{ + "experiment_name": "pead_midcap_step9_stop2", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 9: Step 7 base + tighter stop (2.0 ATR instead of 3.0). With fixed_r target 2.0, target = 4 ATR. Tighter stop reduces avg loss, but may increase stop exit rate.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": {"enabled": true, "direction_filter": "any"}, + "guidance_update": {"enabled": false}, + "management_change": {"enabled": false}, + "material_contract": {"enabled": false}, + "unknown": {"enabled": false}, + "other_material_event": {"enabled": false} + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.10, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "execution": { + "target_model": "fixed_r", + "target_1_r": 2.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "stop_atr_multiplier": 2.0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": ["pead", "midcap", "step9", "stop2", "fixedr", "combo"], + "notes": null +} diff --git a/configs/experiments/pead_pure_5pct.json b/configs/experiments/pead_pure_5pct.json new file mode 100644 index 0000000..e8f1599 --- /dev/null +++ b/configs/experiments/pead_pure_5pct.json @@ -0,0 +1,32 @@ +{ + "experiment_name": "pead_pure_5pct", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "Pure PEAD strategy: buy after earnings with reaction >= 5% and volume >= 1.5x. No complex scoring, just price/volume confirmation.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "bullish_only" + }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.05, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "risk": { + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "pure", "5pct"] +} diff --git a/configs/experiments/pead_pure_7pct.json b/configs/experiments/pead_pure_7pct.json new file mode 100644 index 0000000..f6cac66 --- /dev/null +++ b/configs/experiments/pead_pure_7pct.json @@ -0,0 +1,32 @@ +{ + "experiment_name": "pead_pure_7pct", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "Pure PEAD strategy: buy after earnings with reaction >= 7% and volume >= 1.5x. Higher threshold = stronger signal but fewer trades.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "bullish_only" + }, + "guidance_update": { "enabled": false }, + "management_change": { "enabled": false }, + "material_contract": { "enabled": false }, + "unknown": { "enabled": false }, + "other_material_event": { "enabled": false } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 5 + }, + "risk": { + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "tags": ["pead", "pure", "7pct"] +} diff --git a/configs/experiments/phase5_no_trail_v1.json b/configs/experiments/phase5_no_trail_v1.json new file mode 100644 index 0000000..13ae3a6 --- /dev/null +++ b/configs/experiments/phase5_no_trail_v1.json @@ -0,0 +1,25 @@ +{ + "experiment_name": "phase5_no_trail_v1", + "dataset_snapshot_id": "e684ab2c-cfd9-4d72-94de-f7cc497211b8", + "description": "Phase 5 without trailing stop to isolate ATR target + wider stop effect.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 10 + }, + "risk": { + "per_trade_risk_pct": 0.01, + "max_daily_new_risk_pct": 0.05, + "max_positions": 10, + "max_positions_per_sector": 5 + }, + "execution": { + "trailing_model": null, + "target_1_fraction": 1.0, + "max_holding_days": 15 + } + }, + "splits": [], + "tags": ["phase5", "no-trailing", "atr-targets"] +} diff --git a/configs/experiments/phase5_v1.json b/configs/experiments/phase5_v1.json new file mode 100644 index 0000000..5376345 --- /dev/null +++ b/configs/experiments/phase5_v1.json @@ -0,0 +1,21 @@ +{ + "experiment_name": "phase5_v1", + "dataset_snapshot_id": "cd664594-6a6c-4e16-afef-9104ce900f33", + "description": "Phase 5 strategy improvements: ATR targets, partial exits, wider stops, event-type filtering, SUE gate, direction filter.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 10 + }, + "risk": { + "per_trade_risk_pct": 0.01, + "max_daily_new_risk_pct": 0.05, + "max_positions": 10, + "max_positions_per_sector": 5 + } + }, + "splits": [], + "tags": ["phase5", "atr-targets", "partial-exits", "event-type-filter", "sue-gate"], + "notes": "Phase 5 improvements over expanded_scored_v1. Uses new defaults: stop_atr_multiplier=3.0, target_model=atr_multiple, target_1_fraction=0.5, trailing pct_3 with warmup=2, event_type_profiles active." +} diff --git a/configs/experiments/phase6_v1.json b/configs/experiments/phase6_v1.json new file mode 100644 index 0000000..6600754 --- /dev/null +++ b/configs/experiments/phase6_v1.json @@ -0,0 +1,20 @@ +{ + "experiment_name": "phase6_v1", + "dataset_snapshot_id": "46950fc5-4155-4eb0-ab1c-018774e444cc", + "description": "Phase 6: Open filtering gates, disable no-follow-through, kill_switch log_only, wider trailing, fix attribution.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "signal": { + "score_threshold": 0.55, + "max_candidates_per_day": 10 + }, + "risk": { + "per_trade_risk_pct": 0.01, + "max_daily_new_risk_pct": 0.05, + "max_positions": 10, + "max_positions_per_sector": 5 + } + }, + "splits": [], + "tags": ["phase6", "gate-fix", "no-nft", "wider-trail", "attribution-fix"] +} diff --git a/configs/experiments/phase7_smallmid_v1.json b/configs/experiments/phase7_smallmid_v1.json new file mode 100644 index 0000000..434867b --- /dev/null +++ b/configs/experiments/phase7_smallmid_v1.json @@ -0,0 +1,20 @@ +{ + "experiment_name": "phase7_smallmid_v1", + "dataset_snapshot_id": "9bb74bb1-fefc-412b-b234-f25087634ad1", + "description": "Phase 7: Small/mid-cap universe. Hypothesis: less coverage = more alpha from 8-K filings.", + "base_config": "configs/backtest/defaults_smallmid.json", + "overrides": { + "signal": { + "score_threshold": 0.55, + "max_candidates_per_day": 10 + }, + "risk": { + "per_trade_risk_pct": 0.01, + "max_daily_new_risk_pct": 0.05, + "max_positions": 10, + "max_positions_per_sector": 5 + } + }, + "splits": [], + "tags": ["phase7", "smallmid", "universe-swap", "slippage-25bps"] +} diff --git a/configs/experiments/phase7_smallmid_v2.json b/configs/experiments/phase7_smallmid_v2.json new file mode 100644 index 0000000..06df5e4 --- /dev/null +++ b/configs/experiments/phase7_smallmid_v2.json @@ -0,0 +1,20 @@ +{ + "experiment_name": "phase7_smallmid_v2", + "dataset_snapshot_id": "smallmid-only-8ef47ee5", + "description": "Phase 7 v2: Small/mid-cap only dataset (largecap filtered out). Clean isolation test.", + "base_config": "configs/backtest/defaults_smallmid.json", + "overrides": { + "signal": { + "score_threshold": 0.55, + "max_candidates_per_day": 10 + }, + "risk": { + "per_trade_risk_pct": 0.01, + "max_daily_new_risk_pct": 0.05, + "max_positions": 10, + "max_positions_per_sector": 5 + } + }, + "splits": [], + "tags": ["phase7", "smallmid", "isolated", "slippage-25bps"] +} diff --git a/configs/experiments/phase7_smallmid_v3.json b/configs/experiments/phase7_smallmid_v3.json new file mode 100644 index 0000000..957b185 --- /dev/null +++ b/configs/experiments/phase7_smallmid_v3.json @@ -0,0 +1,43 @@ +{ + "experiment_name": "phase7_smallmid_v3", + "dataset_snapshot_id": "smallmid-only-8ef47ee5", + "description": "Phase 7 v3: Earnings-focused. Disable unknown/material_contract/guidance_update event types. Conservative risk.", + "base_config": "configs/backtest/defaults_smallmid.json", + "overrides": { + "signal": { + "score_threshold": 0.55, + "max_candidates_per_day": 5 + }, + "risk": { + "per_trade_risk_pct": 0.01, + "max_daily_new_risk_pct": 0.03, + "max_positions": 6, + "max_positions_per_sector": 3 + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "max_holding_days_override": 15, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": true, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + } + }, + "splits": [], + "tags": ["phase7", "smallmid", "earnings-focused", "slippage-25bps"] +} diff --git a/configs/experiments/phase7_smallmid_v3_slip10.json b/configs/experiments/phase7_smallmid_v3_slip10.json new file mode 100644 index 0000000..fc12f7f --- /dev/null +++ b/configs/experiments/phase7_smallmid_v3_slip10.json @@ -0,0 +1,51 @@ +{ + "experiment_name": "phase7_smallmid_v3_slip10", + "dataset_snapshot_id": "smallmid-only-8ef47ee5", + "description": "Phase 7 v3: Earnings-focused, slippage=10bps", + "base_config": "configs/backtest/defaults_smallmid.json", + "overrides": { + "signal": { + "score_threshold": 0.55, + "max_candidates_per_day": 5 + }, + "risk": { + "per_trade_risk_pct": 0.01, + "max_daily_new_risk_pct": 0.03, + "max_positions": 6, + "max_positions_per_sector": 3 + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "max_holding_days_override": 15, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": true, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "execution": { + "slippage_bps_base": 10.0 + } + }, + "splits": [], + "tags": [ + "phase7", + "smallmid", + "earnings-focused", + "slippage-10bps" + ] +} \ No newline at end of file diff --git a/configs/experiments/phase7_smallmid_v3_slip15.json b/configs/experiments/phase7_smallmid_v3_slip15.json new file mode 100644 index 0000000..a50eca6 --- /dev/null +++ b/configs/experiments/phase7_smallmid_v3_slip15.json @@ -0,0 +1,51 @@ +{ + "experiment_name": "phase7_smallmid_v3_slip15", + "dataset_snapshot_id": "smallmid-only-8ef47ee5", + "description": "Phase 7 v3: Earnings-focused, slippage=15bps", + "base_config": "configs/backtest/defaults_smallmid.json", + "overrides": { + "signal": { + "score_threshold": 0.55, + "max_candidates_per_day": 5 + }, + "risk": { + "per_trade_risk_pct": 0.01, + "max_daily_new_risk_pct": 0.03, + "max_positions": 6, + "max_positions_per_sector": 3 + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "max_holding_days_override": 15, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": true, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "execution": { + "slippage_bps_base": 15.0 + } + }, + "splits": [], + "tags": [ + "phase7", + "smallmid", + "earnings-focused", + "slippage-15bps" + ] +} \ No newline at end of file diff --git a/configs/experiments/phase7_smallmid_v3_slip20.json b/configs/experiments/phase7_smallmid_v3_slip20.json new file mode 100644 index 0000000..40d4218 --- /dev/null +++ b/configs/experiments/phase7_smallmid_v3_slip20.json @@ -0,0 +1,51 @@ +{ + "experiment_name": "phase7_smallmid_v3_slip20", + "dataset_snapshot_id": "smallmid-only-8ef47ee5", + "description": "Phase 7 v3: Earnings-focused, slippage=20bps", + "base_config": "configs/backtest/defaults_smallmid.json", + "overrides": { + "signal": { + "score_threshold": 0.55, + "max_candidates_per_day": 5 + }, + "risk": { + "per_trade_risk_pct": 0.01, + "max_daily_new_risk_pct": 0.03, + "max_positions": 6, + "max_positions_per_sector": 3 + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "max_holding_days_override": 15, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": true, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "execution": { + "slippage_bps_base": 20.0 + } + }, + "splits": [], + "tags": [ + "phase7", + "smallmid", + "earnings-focused", + "slippage-20bps" + ] +} \ No newline at end of file diff --git a/configs/symbols.yaml b/configs/symbols.yaml index c7d6a28..381679b 100644 --- a/configs/symbols.yaml +++ b/configs/symbols.yaml @@ -1,102 +1,744 @@ symbols: - # --- Mega-cap tech (original 15) --- - - AAPL - - MSFT - - GOOGL - - AMZN - - META - - NVDA - - TSLA - - AMD - - NFLX - - CRM - - SNOW - - NET - - DDOG - - ZS - - CRWD - # --- Large-cap tech additions --- - - ADBE - - ORCL - - INTC - - QCOM - - AVGO - - MU - - PANW - - FTNT - - NOW - - WDAY - - SHOP - - SQ - - MELI - - UBER - - DASH - # --- Mid-cap tech / growth ($5B-50B — stronger PEAD) --- + # --- Existing-only (not in screener, kept) --- + - AXON - BILL - - HUBS - - PCOR - - CFLT - - MNDY + - BLK + - BRZE + - DOCN + - DPZ - GTLB + - HIMS + - MELI + - MNDY + - PCOR - S - - IOT - - DOCN - - BRZE - # --- Healthcare --- - - UNH - - JNJ - - LLY + - SQ + - URI + # --- Screener ($10B+ mcap, 1M+ vol) — 727 symbols --- + - A + - AA + - AAPL - ABBV - - PFE - - MRK - - TMO + - ABNB - ABT - - ISRG - - DXCM - - VEEV - - HIMS - # --- Industrials --- + - ACGL + - ACM + - ACN + - ADBE + - ADI + - ADM + - ADP + - ADSK + - AEE + - AEG + - AEM + - AEP + - AER + - AES + - AFL + - AFRM + - AG + - AGI + - AGNC + - AIG + - AJG + - AKAM + - ALAB + - ALB + - ALC + - ALGN + - ALL + - ALLY + - ALNY + - AM + - AMAT + - AMCR + - AMD + - AME + - AMGN + - AMH + - AMKR + - AMRZ + - AMT + - AMX + - AMZN + - ANET + - AON + - APA + - APD + - APG + - APH + - APO + - APP + - APTV + - AR + - ARCC + - ARES + - ARM + - ARMK + - AS + - ASML + - ASTS + - ASX + - ATI + - ATO + - AU + - AVAV + - AVB + - AVGO + - AWK + - AXIA + - AXP + - AZN + - B + - BA + - BABA + - BAC + - BALL + - BAM + - BBIO + - BBVA + - BBY + - BCE + - BCS + - BDX + - BE + - BEKE + - BEN + - BF-B + - BG + - BHP + - BIDU + - BIIB + - BILI + - BJ + - BK + - BKR + - BMRN + - BMY + - BN + - BNS + - BNTX + - BP + - BR + - BRK-B + - BRO + - BSX + - BSY + - BTI + - BUD + - BWA + - BX + - C + - CAH + - CARR + - CART - CAT + - CB + - CBRE + - CCEP + - CCI + - CCJ + - CCK + - CCL + - CDE + - CDNS + - CDW + - CEG + - CELH + - CF + - CFG + - CFLT + - CG + - CHD + - CHKP + - CHRW + - CHTR + - CHWY + - CI + - CIEN + - CL + - CLS + - CLX + - CM + - CMCSA + - CME + - CMG + - CMS + - CNC + - CNH + - CNI + - CNP + - CNQ + - COF + - COHR + - COIN + - COO + - COP + - COR + - COST + - CP + - CPNG + - CPRT + - CPT + - CRBG + - CRCL + - CRDO + - CRH + - CRM + - CRWD + - CRWV + - CSCO + - CSGP + - CSX + - CTAS + - CTRA + - CTSH + - CTVA + - CUK + - CVE + - CVNA + - CVS + - CVX + - CX + - D + - DAL + - DASH + - DB + - DD + - DDOG - DE - - GE - - HON - - RTX - - LMT + - DECK + - DELL + - DEO + - DG + - DHI + - DHR + - DINO + - DIS + - DKNG + - DKS + - DLR + - DLTR + - DOC + - DOV + - DOW + - DRI + - DRS + - DT + - DTE + - DUK + - DVA + - DVN + - DXCM + - EA + - EBAY + - EC + - ECL + - ED + - EFX + - EHC + - EIX + - EL + - ELAN + - ELS + - ELV + - EMBJ - EMR + - ENB + - ENTG + - EOG + - EPD + - EQH + - EQNR + - EQR + - EQT + - ERIC + - ES + - ET - ETN - - URI - - AXON - # --- Consumer --- - - COST - - WMT - - MCD - - SBUX - - NKE - - TGT + - ETR + - EVRG + - EW + - EWBC + - EXAS + - EXC + - EXE + - EXEL + - EXPD + - EXPE + - EXR + - F + - FANG + - FAST + - FCX + - FDX + - FE + - FER + - FERG + - FHN + - FIG + - FIS + - FISV + - FITB + - FIVE + - FLEX + - FLUT + - FNF + - FOX + - FOXA + - FSLR + - FTAI + - FTI + - FTNT + - FTV + - FUTU + - FWONK + - GD + - GDDY + - GE + - GEHC + - GEN + - GEV + - GFI + - GFL + - GFS + - GH + - GIL + - GILD + - GIS + - GLPI + - GLW + - GM + - GMAB + - GME + - GMED + - GNRC + - GOOG + - GOOGL + - GPC + - GPN + - GRMN + - GS + - GSK + - GWRE + - HAL + - HAS + - HBAN + - HCA - HD + - HDB + - HIG + - HL + - HLN + - HLT + - HMC + - HOLX + - HON + - HOOD + - HPE + - HPQ + - HRL + - HSBC + - HST + - HSY + - HTHT + - HUBS + - HUM + - HWM + - IAG + - IBKR + - IBM + - IBN + - ICE + - IFF + - ILMN + - INCY + - INFY + - ING + - INSM + - INTC + - INTU + - INVH + - IONQ + - IONS + - IOT + - IP + - IQV + - IR + - IREN + - IRM + - ISRG + - IT + - ITUB + - ITW + - IVZ + - JAZZ + - JBHT + - JBL + - JBS + - JCI + - JD + - JHX + - JNJ + - JPM + - KDP + - KEY + - KEYS + - KGC + - KHC + - KIM + - KKR + - KLAC + - KMB + - KMI + - KO + - KR + - KRMN + - KT + - KTOS + - KVUE + - LDOS + - LEN + - LHX + - LI + - LIN + - LITE + - LLY + - LMT + - LNG + - LNT + - LOGI - LOW + - LRCX + - LSCC + - LTM - LULU - - DPZ - # --- Financials --- - - JPM - - GS - - V + - LUV + - LVS + - LYB + - LYG + - LYV - MA - - AXP - - BLK - - COIN - - HOOD - - SOFI - - NU - # --- Energy --- - - XOM - - CVX - - COP - - EOG - - FANG - # --- Communication / Media --- - - DIS - - CMCSA + - MAA + - MAR + - MAS + - MCD + - MCHP + - MCO + - MDB + - MDLN + - MDLZ + - MDT + - MET + - META + - MFC + - MFG + - MGA + - MGM + - MKC + - MKSI + - MMM + - MNST + - MO + - MP + - MPC + - MPLX + - MRK + - MRNA + - MRSH + - MRVL + - MS + - MSFT + - MSI + - MSTR + - MT + - MTB + - MTSI + - MU + - MUFG + - NBIS + - NBIX + - NDAQ + - NEE + - NEM + - NET - NFLX - - SPOT + - NI + - NIO + - NKE + - NLY + - NMR + - NOK + - NOW + - NRG + - NSC + - NTAP + - NTNX + - NTR + - NTRA + - NTRS + - NU + - NUE + - NVDA + - NVO + - NVS + - NVT + - NWG + - NWS + - NWSA + - NXPI + - NXT + - NYT + - O + - ODFL + - OHI + - OKE + - OKTA + - OMC + - ON + - ONON + - ORCL + - ORLY + - OTIS + - OVV + - OWL + - OXY + - PAA + - PAAS + - PANW + - PAYX + - PBA + - PBR + - PBR-A + - PCAR + - PCG + - PDD + - PEG + - PEN + - PEP + - PFE + - PFG + - PFGC + - PG + - PGR + - PHM + - PINS + - PLD + - PLTR + - PM + - PNC + - PNFP + - PNR + - PNW + - PPG + - PPL + - PR + - PRU + - PSA + - PSKY + - PSTG + - PSX + - PTC + - PWR + - PYPL + - Q + - QCOM + - QSR + - QXO + - RBA - RBLX + - RBRK + - RCI + - RCL + - RDDT + - RDY + - REG + - RELX + - RF + - RIO + - RIVN + - RJF + - RKLB + - RKT + - RMBS + - RMD + - ROIV + - ROKU + - ROL + - ROP + - ROST + - RPM + - RPRX + - RRC + - RRX + - RSG + - RTO + - RTX + - RVMD + - RY + - RYAAY + - SAN + - SAP + - SATS + - SBS + - SBUX + - SCCO + - SCHW + - SCI + - SE + - SF + - SGI + - SHEL + - SHOP + - SHW + - SJM + - SKM + - SLB + - SMCI + - SMFG + - SMMT + - SN + - SNDK + - SNOW + - SNPS + - SNY + - SO + - SOFI + - SOLS + - SOLV + - SONY + - SPG + - SPGI + - SPOT + - SQM + - SRE + - SSNC + - STLA + - STLD + - STM + - STT + - STX + - STZ + - SU + - SUNB + - SUZ + - SW + - SWK + - SYF + - SYK + - SYM + - SYY + - T + - TAK + - TCOM + - TD + - TEAM + - TECK + - TEL + - TER + - TEVA + - TFC + - TGT + - TIGO + - TJX + - TKO + - TME + - TMO + - TMUS + - TOL + - TOST + - TPG + - TPR + - TRGP + - TRI + - TRMB + - TROW + - TRP + - TRU + - TRV + - TS + - TSCO + - TSEM + - TSLA + - TSM + - TSN + - TT + - TTD + - TTE + - TTWO + - TU + - TW + - TWLO + - TXN + - TXT + - UAL + - UBER + - UBS + - UDR + - UL + - ULS + - UMC + - UNH + - UNM + - UNP + - UPS + - USB + - USFD + - V + - VALE + - VEEV + - VG + - VICI + - VIK + - VLO + - VLTO + - VMC + - VNOM + - VOD + - VRSK + - VRT + - VRTX + - VST + - VTR + - VTRS + - VZ + - WAT + - WBD + - WBS + - WCN + - WDAY + - WDC + - WDS + - WEC + - WELL + - WES + - WFC + - WLK + - WM + - WMB + - WMG + - WMT + - WPC + - WPM + - WRB + - WSM + - WTRG + - WY + - WYNN + - XEL + - XOM + - XPEV + - XPO + - XYL + - XYZ + - YPF + - YUM + - YUMC + - Z + - ZBH + - ZG + - ZM + - ZS + - ZTO + - ZTS diff --git a/configs/symbols_backup_largecap.yaml b/configs/symbols_backup_largecap.yaml new file mode 100644 index 0000000..381679b --- /dev/null +++ b/configs/symbols_backup_largecap.yaml @@ -0,0 +1,744 @@ +symbols: + # --- Existing-only (not in screener, kept) --- + - AXON + - BILL + - BLK + - BRZE + - DOCN + - DPZ + - GTLB + - HIMS + - MELI + - MNDY + - PCOR + - S + - SQ + - URI + # --- Screener ($10B+ mcap, 1M+ vol) — 727 symbols --- + - A + - AA + - AAPL + - ABBV + - ABNB + - ABT + - ACGL + - ACM + - ACN + - ADBE + - ADI + - ADM + - ADP + - ADSK + - AEE + - AEG + - AEM + - AEP + - AER + - AES + - AFL + - AFRM + - AG + - AGI + - AGNC + - AIG + - AJG + - AKAM + - ALAB + - ALB + - ALC + - ALGN + - ALL + - ALLY + - ALNY + - AM + - AMAT + - AMCR + - AMD + - AME + - AMGN + - AMH + - AMKR + - AMRZ + - AMT + - AMX + - AMZN + - ANET + - AON + - APA + - APD + - APG + - APH + - APO + - APP + - APTV + - AR + - ARCC + - ARES + - ARM + - ARMK + - AS + - ASML + - ASTS + - ASX + - ATI + - ATO + - AU + - AVAV + - AVB + - AVGO + - AWK + - AXIA + - AXP + - AZN + - B + - BA + - BABA + - BAC + - BALL + - BAM + - BBIO + - BBVA + - BBY + - BCE + - BCS + - BDX + - BE + - BEKE + - BEN + - BF-B + - BG + - BHP + - BIDU + - BIIB + - BILI + - BJ + - BK + - BKR + - BMRN + - BMY + - BN + - BNS + - BNTX + - BP + - BR + - BRK-B + - BRO + - BSX + - BSY + - BTI + - BUD + - BWA + - BX + - C + - CAH + - CARR + - CART + - CAT + - CB + - CBRE + - CCEP + - CCI + - CCJ + - CCK + - CCL + - CDE + - CDNS + - CDW + - CEG + - CELH + - CF + - CFG + - CFLT + - CG + - CHD + - CHKP + - CHRW + - CHTR + - CHWY + - CI + - CIEN + - CL + - CLS + - CLX + - CM + - CMCSA + - CME + - CMG + - CMS + - CNC + - CNH + - CNI + - CNP + - CNQ + - COF + - COHR + - COIN + - COO + - COP + - COR + - COST + - CP + - CPNG + - CPRT + - CPT + - CRBG + - CRCL + - CRDO + - CRH + - CRM + - CRWD + - CRWV + - CSCO + - CSGP + - CSX + - CTAS + - CTRA + - CTSH + - CTVA + - CUK + - CVE + - CVNA + - CVS + - CVX + - CX + - D + - DAL + - DASH + - DB + - DD + - DDOG + - DE + - DECK + - DELL + - DEO + - DG + - DHI + - DHR + - DINO + - DIS + - DKNG + - DKS + - DLR + - DLTR + - DOC + - DOV + - DOW + - DRI + - DRS + - DT + - DTE + - DUK + - DVA + - DVN + - DXCM + - EA + - EBAY + - EC + - ECL + - ED + - EFX + - EHC + - EIX + - EL + - ELAN + - ELS + - ELV + - EMBJ + - EMR + - ENB + - ENTG + - EOG + - EPD + - EQH + - EQNR + - EQR + - EQT + - ERIC + - ES + - ET + - ETN + - ETR + - EVRG + - EW + - EWBC + - EXAS + - EXC + - EXE + - EXEL + - EXPD + - EXPE + - EXR + - F + - FANG + - FAST + - FCX + - FDX + - FE + - FER + - FERG + - FHN + - FIG + - FIS + - FISV + - FITB + - FIVE + - FLEX + - FLUT + - FNF + - FOX + - FOXA + - FSLR + - FTAI + - FTI + - FTNT + - FTV + - FUTU + - FWONK + - GD + - GDDY + - GE + - GEHC + - GEN + - GEV + - GFI + - GFL + - GFS + - GH + - GIL + - GILD + - GIS + - GLPI + - GLW + - GM + - GMAB + - GME + - GMED + - GNRC + - GOOG + - GOOGL + - GPC + - GPN + - GRMN + - GS + - GSK + - GWRE + - HAL + - HAS + - HBAN + - HCA + - HD + - HDB + - HIG + - HL + - HLN + - HLT + - HMC + - HOLX + - HON + - HOOD + - HPE + - HPQ + - HRL + - HSBC + - HST + - HSY + - HTHT + - HUBS + - HUM + - HWM + - IAG + - IBKR + - IBM + - IBN + - ICE + - IFF + - ILMN + - INCY + - INFY + - ING + - INSM + - INTC + - INTU + - INVH + - IONQ + - IONS + - IOT + - IP + - IQV + - IR + - IREN + - IRM + - ISRG + - IT + - ITUB + - ITW + - IVZ + - JAZZ + - JBHT + - JBL + - JBS + - JCI + - JD + - JHX + - JNJ + - JPM + - KDP + - KEY + - KEYS + - KGC + - KHC + - KIM + - KKR + - KLAC + - KMB + - KMI + - KO + - KR + - KRMN + - KT + - KTOS + - KVUE + - LDOS + - LEN + - LHX + - LI + - LIN + - LITE + - LLY + - LMT + - LNG + - LNT + - LOGI + - LOW + - LRCX + - LSCC + - LTM + - LULU + - LUV + - LVS + - LYB + - LYG + - LYV + - MA + - MAA + - MAR + - MAS + - MCD + - MCHP + - MCO + - MDB + - MDLN + - MDLZ + - MDT + - MET + - META + - MFC + - MFG + - MGA + - MGM + - MKC + - MKSI + - MMM + - MNST + - MO + - MP + - MPC + - MPLX + - MRK + - MRNA + - MRSH + - MRVL + - MS + - MSFT + - MSI + - MSTR + - MT + - MTB + - MTSI + - MU + - MUFG + - NBIS + - NBIX + - NDAQ + - NEE + - NEM + - NET + - NFLX + - NI + - NIO + - NKE + - NLY + - NMR + - NOK + - NOW + - NRG + - NSC + - NTAP + - NTNX + - NTR + - NTRA + - NTRS + - NU + - NUE + - NVDA + - NVO + - NVS + - NVT + - NWG + - NWS + - NWSA + - NXPI + - NXT + - NYT + - O + - ODFL + - OHI + - OKE + - OKTA + - OMC + - ON + - ONON + - ORCL + - ORLY + - OTIS + - OVV + - OWL + - OXY + - PAA + - PAAS + - PANW + - PAYX + - PBA + - PBR + - PBR-A + - PCAR + - PCG + - PDD + - PEG + - PEN + - PEP + - PFE + - PFG + - PFGC + - PG + - PGR + - PHM + - PINS + - PLD + - PLTR + - PM + - PNC + - PNFP + - PNR + - PNW + - PPG + - PPL + - PR + - PRU + - PSA + - PSKY + - PSTG + - PSX + - PTC + - PWR + - PYPL + - Q + - QCOM + - QSR + - QXO + - RBA + - RBLX + - RBRK + - RCI + - RCL + - RDDT + - RDY + - REG + - RELX + - RF + - RIO + - RIVN + - RJF + - RKLB + - RKT + - RMBS + - RMD + - ROIV + - ROKU + - ROL + - ROP + - ROST + - RPM + - RPRX + - RRC + - RRX + - RSG + - RTO + - RTX + - RVMD + - RY + - RYAAY + - SAN + - SAP + - SATS + - SBS + - SBUX + - SCCO + - SCHW + - SCI + - SE + - SF + - SGI + - SHEL + - SHOP + - SHW + - SJM + - SKM + - SLB + - SMCI + - SMFG + - SMMT + - SN + - SNDK + - SNOW + - SNPS + - SNY + - SO + - SOFI + - SOLS + - SOLV + - SONY + - SPG + - SPGI + - SPOT + - SQM + - SRE + - SSNC + - STLA + - STLD + - STM + - STT + - STX + - STZ + - SU + - SUNB + - SUZ + - SW + - SWK + - SYF + - SYK + - SYM + - SYY + - T + - TAK + - TCOM + - TD + - TEAM + - TECK + - TEL + - TER + - TEVA + - TFC + - TGT + - TIGO + - TJX + - TKO + - TME + - TMO + - TMUS + - TOL + - TOST + - TPG + - TPR + - TRGP + - TRI + - TRMB + - TROW + - TRP + - TRU + - TRV + - TS + - TSCO + - TSEM + - TSLA + - TSM + - TSN + - TT + - TTD + - TTE + - TTWO + - TU + - TW + - TWLO + - TXN + - TXT + - UAL + - UBER + - UBS + - UDR + - UL + - ULS + - UMC + - UNH + - UNM + - UNP + - UPS + - USB + - USFD + - V + - VALE + - VEEV + - VG + - VICI + - VIK + - VLO + - VLTO + - VMC + - VNOM + - VOD + - VRSK + - VRT + - VRTX + - VST + - VTR + - VTRS + - VZ + - WAT + - WBD + - WBS + - WCN + - WDAY + - WDC + - WDS + - WEC + - WELL + - WES + - WFC + - WLK + - WM + - WMB + - WMG + - WMT + - WPC + - WPM + - WRB + - WSM + - WTRG + - WY + - WYNN + - XEL + - XOM + - XPEV + - XPO + - XYL + - XYZ + - YPF + - YUM + - YUMC + - Z + - ZBH + - ZG + - ZM + - ZS + - ZTO + - ZTS diff --git a/configs/symbols_largecap.yaml b/configs/symbols_largecap.yaml new file mode 100644 index 0000000..c7d6a28 --- /dev/null +++ b/configs/symbols_largecap.yaml @@ -0,0 +1,102 @@ +symbols: + # --- Mega-cap tech (original 15) --- + - AAPL + - MSFT + - GOOGL + - AMZN + - META + - NVDA + - TSLA + - AMD + - NFLX + - CRM + - SNOW + - NET + - DDOG + - ZS + - CRWD + # --- Large-cap tech additions --- + - ADBE + - ORCL + - INTC + - QCOM + - AVGO + - MU + - PANW + - FTNT + - NOW + - WDAY + - SHOP + - SQ + - MELI + - UBER + - DASH + # --- Mid-cap tech / growth ($5B-50B — stronger PEAD) --- + - BILL + - HUBS + - PCOR + - CFLT + - MNDY + - GTLB + - S + - IOT + - DOCN + - BRZE + # --- Healthcare --- + - UNH + - JNJ + - LLY + - ABBV + - PFE + - MRK + - TMO + - ABT + - ISRG + - DXCM + - VEEV + - HIMS + # --- Industrials --- + - CAT + - DE + - GE + - HON + - RTX + - LMT + - EMR + - ETN + - URI + - AXON + # --- Consumer --- + - COST + - WMT + - MCD + - SBUX + - NKE + - TGT + - HD + - LOW + - LULU + - DPZ + # --- Financials --- + - JPM + - GS + - V + - MA + - AXP + - BLK + - COIN + - HOOD + - SOFI + - NU + # --- Energy --- + - XOM + - CVX + - COP + - EOG + - FANG + # --- Communication / Media --- + - DIS + - CMCSA + - NFLX + - SPOT + - RBLX diff --git a/configs/symbols_midcap.yaml b/configs/symbols_midcap.yaml new file mode 100644 index 0000000..e889971 --- /dev/null +++ b/configs/symbols_midcap.yaml @@ -0,0 +1,804 @@ +symbols: + # --- Screener ($2B-$10B mcap, 500K+ vol) — 802 symbols --- + - AAL + - AAOI + - AAON + - AAP + - AAUC + - ABCB + - ABM + - ABVX + - ACAD + - ACHC + - ACHR + - ACI + - ACIW + - ACLS + - ACLX + - ACMR + - ADC + - ADEA + - ADMA + - ADPT + - ADT + - AEO + - AGCO + - AHR + - AKR + - AL + - ALGM + - ALH + - ALHC + - ALK + - ALKS + - ALM + - ALMS + - ALSN + - ALV + - AMBA + - AMPX + - AMRX + - AMTM + - ANF + - AOS + - APAM + - APGE + - APLD + - APLE + - APLS + - AQN + - ARE + - ARIS + - AROC + - ARQT + - ARR + - ARW + - ARWR + - ARX + - ASB + - ASH + - ASO + - ATAT + - ATHM + - ATMU + - ATR + - ATRO + - AUB + - AUGO + - AVA + - AVNT + - AVPT + - AVT + - AVTR + - AWI + - AXS + - AXSM + - AXTA + - AXTI + - BAH + - BANC + - BAX + - BBAR + - BBT + - BBWI + - BC + - BCRX + - BEAM + - BEPC + - BETA + - BFAM + - BFH + - BGC + - BHF + - BILL + - BIPC + - BIRK + - BKD + - BKH + - BKU + - BKV + - BL + - BLDR + - BLSH + - BNL + - BOBS + - BOOT + - BOX + - BPOP + - BRKR + - BROS + - BRSL + - BRX + - BRZE + - BTSG + - BTU + - BULL + - BUSE + - BVN + - BWIN + - BXMT + - BXP + - BXSL + - BYD + - BZ + - CAE + - CAG + - CAI + - CAKE + - CALM + - CALX + - CALY + - CAMT + - CAR + - CARG + - CAVA + - CBSH + - CC + - CCC + - CDP + - CE + - CELC + - CENX + - CFR + - CGAU + - CGNX + - CGON + - CHA + - CHDN + - CHH + - CHRD + - CHYM + - CIFR + - CLBT + - CLF + - CLMT + - CLSK + - CMBT + - CMC + - CNK + - CNM + - CNO + - CNR + - CNTA + - CNX + - COCO + - COGT + - COLB + - COLD + - COLM + - COMP + - CON + - CORT + - CORZ + - CPB + - CPRI + - CPRX + - CRC + - CRGY + - CRK + - CRL + - CRNX + - CROX + - CRSP + - CRUS + - CSTM + - CTRE + - CTRI + - CUBE + - CURB + - CUZ + - CVBF + - CVI + - CVLT + - CWAN + - CWEN + - CWK + - CWST + - CXT + - CYTK + - CZR + - DAN + - DAR + - DAVE + - DAWN + - DBRG + - DBX + - DCI + - DHT + - DJT + - DK + - DLB + - DLO + - DNLI + - DNOW + - DNP + - DNTH + - DOCN + - DOCS + - DOCU + - DOX + - DSGX + - DUOL + - DX + - DXC + - DYN + - EAT + - EBC + - ECG + - EDU + - EEFT + - EFXT + - EGO + - ELF + - EMN + - ENPH + - EPAM + - EPR + - EPRT + - EQPT + - ERAS + - ERO + - ESI + - ESNT + - ESTC + - ETOR + - ETSY + - EWTX + - EXG + - EXK + - EXLS + - EXP + - EYE + - FAF + - FBIN + - FBP + - FCPT + - FDS + - FFBC + - FFIN + - FG + - FHB + - FHI + - FIBK + - FIGR + - FIGS + - FLG + - FLNC + - FLR + - FLS + - FLY + - FNB + - FND + - FOLD + - FORM + - FOUR + - FPS + - FR + - FRMI + - FRO + - FROG + - FRPT + - FRSH + - FRT + - FSK + - FSLY + - FSM + - FTDR + - FULT + - G + - GAP + - GBCI + - GBDC + - GBTG + - GDS + - GEO + - GGAL + - GKOS + - GLBE + - GLNG + - GLXY + - GNL + - GNTX + - GNW + - GOF + - GPCR + - GPGI + - GPK + - GRFS + - GRND + - GSAT + - GTES + - GTLB + - GTLS + - GTX + - GVA + - GXO + - HAE + - HAFN + - HALO + - HASI + - HAYW + - HBM + - HCC + - HE + - HESM + - HG + - HGV + - HIMS + - HIW + - HLI + - HLNE + - HMY + - HNGE + - HNI + - HOG + - HOMB + - HP + - HPP + - HQY + - HR + - HRB + - HRI + - HSAI + - HSIC + - HTGC + - HUBG + - HUN + - HUT + - HWC + - HXL + - HYMC + - IAC + - IBRX + - ICL + - ICLR + - IDYA + - IEP + - IHS + - IMAX + - IMNM + - IMVT + - INDV + - INFQ + - INGM + - INGR + - INSW + - INTA + - INTR + - IPGP + - IRDM + - IRON + - IRT + - IRTC + - ITGR + - ITRI + - JBTM + - JEF + - JHG + - JOBY + - JPC + - JXN + - KBH + - KBR + - KC + - KD + - KEX + - KFY + - KGS + - KLAR + - KLIC + - KMT + - KMX + - KN + - KNF + - KNSA + - KNTK + - KNX + - KRC + - KRG + - KTB + - KVYO + - KYIV + - KYMR + - LASR + - LAUR + - LAZ + - LB + - LBRDK + - LBRT + - LBTYA + - LBTYK + - LCID + - LEA + - LEGN + - LEU + - LEVI + - LFST + - LGN + - LIF + - LINE + - LION + - LIVN + - LKQ + - LMND + - LNC + - LNTH + - LOAR + - LPX + - LQDA + - LRN + - LSTR + - LTH + - LUMN + - LUNR + - LW + - LXP + - LYFT + - M + - MAC + - MAIN + - MANH + - MARA + - MASI + - MAT + - MBLY + - MC + - MCHB + - MCW + - MDU + - MEOH + - MGY + - MH + - MHK + - MIAX + - MICC + - MIDD + - MIR + - MIRM + - MKTX + - MLCO + - MLYS + - MMED + - MMS + - MMSI + - MMYT + - MNDY + - MNSO + - MOD + - MOH + - MORN + - MOS + - MRCY + - MRP + - MRX + - MSM + - MTCH + - MTDR + - MTG + - MTH + - MTN + - MUR + - MWA + - MWH + - NAD + - NAMS + - NATL + - NAVN + - NCLH + - NE + - NEA + - NFG + - NICE + - NIQ + - NJR + - NKTR + - NMRK + - NN + - NNN + - NOG + - NOV + - NSA + - NTCT + - NTSK + - NTST + - NUVL + - NVG + - NVST + - NVTS + - NWE + - NXE + - NZF + - OBDC + - OC + - OGE + - OII + - OKLO + - OLED + - OLLI + - OLN + - OMF + - ONB + - ONDS + - ONTO + - OPCH + - OPLN + - OR + - ORA + - ORI + - OS + - OSCR + - OSK + - OSW + - OTEX + - OTF + - OUT + - OZK + - PACS + - PAGP + - PAGS + - PARR + - PATH + - PAY + - PAYC + - PB + - PBF + - PCOR + - PCTY + - PCVX + - PDI + - PECO + - PEGA + - PFS + - PFSI + - PI + - PII + - PK + - PL + - PLNT + - PONY + - POOL + - POR + - POST + - POWI + - PPC + - PPTA + - PRAX + - PRDO + - PRIM + - PRM + - PRMB + - PRVA + - PSN + - PSO + - PTCT + - PTEN + - PTGX + - PTRN + - PTY + - PVH + - QBTS + - QGEN + - QLYS + - QRVO + - QS + - QTWO + - RAL + - RARE + - RCUS + - RDN + - RDNT + - RELY + - REXR + - REYN + - REZI + - RGEN + - RGTI + - RH + - RHI + - RIG + - RIOT + - RITM + - RLI + - RNG + - RNST + - ROAD + - RRR + - RSI + - RUN + - RUSHA + - RVTY + - RYAN + - RYN + - RYTM + - S + - SA + - SAIA + - SAIC + - SAIL + - SANM + - SARO + - SBCF + - SBLK + - SBRA + - SBSW + - SDRL + - SEDG + - SEE + - SEI + - SEIC + - SEM + - SEZL + - SFD + - SFM + - SFNC + - SGHC + - SHAK + - SHC + - SHOO + - SIG + - SIGI + - SIMO + - SIRI + - SITE + - SKE + - SKT + - SKY + - SLAB + - SLDE + - SLG + - SLGN + - SLM + - SM + - SMA + - SMG + - SMR + - SMTC + - SNDR + - SNDX + - SOBO + - SOC + - SON + - SOUN + - SPHR + - SPNT + - SPSC + - SPXC + - SRAD + - SRRK + - SSB + - SSL + - SSRM + - ST + - STAG + - STEP + - STNE + - STNG + - STUB + - STVN + - STWD + - SUPN + - SWKS + - SWX + - SYNA + - SYRE + - TAC + - TAL + - TALO + - TAP + - TARS + - TBBB + - TBBK + - TDC + - TDS + - TDW + - TECH + - TEM + - TENB + - TERN + - TEX + - TFPM + - TFSL + - TFX + - TGNA + - TGTX + - THO + - TKC + - TKR + - TMC + - TMDX + - TMHC + - TNGX + - TNL + - TPC + - TPH + - TREX + - TRMD + - TRN + - TRNO + - TTAN + - TTC + - TTEK + - TTMI + - TVTX + - TWST + - TXG + - TXNM + - U + - UA + - UAA + - UBSI + - UCB + - UCTT + - UE + - UGI + - UMBF + - UNFI + - UPST + - URBN + - USAR + - VAC + - VAL + - VC + - VCTR + - VCYT + - VERA + - VERX + - VFC + - VIAV + - VICR + - VIPS + - VIRT + - VISN + - VIST + - VKTX + - VLY + - VNET + - VNO + - VNT + - VOYA + - VRDN + - VRNS + - VRRM + - VSAT + - VSCO + - VSEC + - VSH + - VSNT + - VVV + - W + - WAFD + - WAL + - WAY + - WB + - WFRD + - WGS + - WH + - WHD + - WHR + - WING + - WIX + - WK + - WPP + - WRBY + - WRD + - WSBC + - WSC + - WT + - WTFC + - WU + - WULF + - WVE + - XENE + - XP + - XRAY + - XXI + - YETI + - YMM + - YOU + - YSS + - ZETA + - ZGN + - ZIM + - ZION + - ZLAB + - ZWS diff --git a/configs/symbols_smallmid.yaml b/configs/symbols_smallmid.yaml new file mode 100644 index 0000000..38c7cd0 --- /dev/null +++ b/configs/symbols_smallmid.yaml @@ -0,0 +1,186 @@ +symbols: +- DIOD +- AEIS +- POWI +- AMBA +- SITM +- ACLS +- CEVA +- SMTC +- CIEN +- CALX +- VIAV +- TENB +- VRNS +- QLYS +- ALRM +- NCNO +- QTWO +- FIVN +- CWAN +- PRGS +- CVLT +- WK +- APPF +- CSGS +- PAYO +- NOVT +- ITRI +- MTSI +- LITE +- MKSI +- CARG +- FOUR +- VERX +- PCTY +- EXAS +- NTRA +- HALO +- PCVX +- CRNX +- KRYS +- INSM +- IONS +- RARE +- CORT +- GKOS +- FOLD +- XNCR +- TGTX +- NVCR +- LIVN +- NVST +- NEOG +- OFIX +- GMED +- IRTC +- INSP +- TNDM +- SUPN +- PCRX +- IART +- LNTH +- MEDP +- DOCS +- PGNY +- SMMT +- RXRX +- RDNT +- RVMD +- ATKR +- ESAB +- MATX +- SPSC +- ENSG +- ZWS +- AAON +- AIT +- GGG +- SITE +- TREX +- AWI +- EPAC +- GATX +- UFPI +- WDFC +- HAYW +- ROCK +- CMCO +- KTOS +- CACI +- MRCY +- RKLB +- ASGN +- HRI +- KAI +- MHO +- BOOT +- SHAK +- PLAY +- WING +- EAT +- CAKE +- TXRH +- SFM +- BROS +- PLNT +- XPOF +- FOXF +- ASO +- DECK +- CROX +- YETI +- IPAR +- ELF +- CELH +- FRPT +- FIZZ +- OLPX +- MNST +- STEP +- HASI +- HLNE +- OWL +- PIPR +- VCTR +- ESNT +- NMIH +- RDN +- VIRT +- PNFP +- IBOC +- SFBS +- GBCI +- CATY +- SBCF +- TFIN +- TREE +- TOST +- PRAA +- HLI +- RYAN +- KNSL +- OSCR +- PTEN +- LBRT +- PUMP +- HP +- RIG +- MTDR +- TRGP +- CTRA +- SM +- MGY +- OVV +- CHRD +- NOV +- XPRO +- WHD +- AROC +- GPOR +- RRC +- AR +- CNX +- IOSP +- HUN +- TROX +- AVNT +- MP +- CBT +- NGVT +- IIPR +- REXR +- STAG +- NNN +- COLD +- APLE +- SUI +- RUN +- STEM +- MGNI +- PUBM +- COUR +- UDMY +- TTGT +- YELP +- ZD diff --git a/dev/finished/new_plan1.md b/dev/finished/new_plan1.md new file mode 100644 index 0000000..128e94e --- /dev/null +++ b/dev/finished/new_plan1.md @@ -0,0 +1,448 @@ +# 1. 전략 정의 + +## 전략 이름 + +**PER-v1 (Post-Event Reversal v1)** + +## 전략 목적 + +SEC 공시 직후 강하게 반응한 종목 중, +**초기 반응이 과도했고 다음 거래일에 follow-through가 약한 경우**를 골라 +**1~3거래일 mean reversion**을 노립니다. + +## 기본 철학 + +이 전략은 “이벤트가 좋으냐”보다 아래를 더 중요하게 봅니다. + +* 반응이 **너무 강했는가** +* 다음 날 **추가 매수세가 약한가** +* 이미 **좋은 뉴스가 가격에 과반영되었는가** +* 그 종목이 **1~3일 안에 되돌릴 가능성**이 높은가 + +즉, 기존 전략의 + +* high score +* strong reaction +* next-open 추격 + +이 오히려 손실로 이어졌던 구조를 반대로 쓰는 것입니다. + +--- + +# 2. 어떤 종목만 거래할지 + +처음에는 아주 좁게 가는 게 맞습니다. + +## 유니버스 + +* 미국 보통주만 +* NYSE / Nasdaq +* 가격 $10 이상 +* 최근 20일 평균 거래대금 충분히 큰 종목만 +* 저유동성 바이오 / low-float / memestock 제외 +* ADR / ETF / SPAC 제외 + +## 이벤트 타입 + +v1에서는 아래만 허용합니다. + +* `earnings_release` +* `guidance_update` + +초기에는 **material_contract도 제외**하는 게 좋습니다. +표본이 너무 적고, 반응 구조가 실적 이벤트와 다를 가능성이 큽니다. + +## 차단 이벤트 + +아래는 제외합니다. + +* `unknown event_type` +* `management_change` +* `other_material_event` +* `oneoff risk high` +* parse confidence 낮음 +* direction unknown + +즉, **이벤트 자체가 애매한 종목은 거래하지 않습니다.** + +--- + +# 3. 전략 가설 + +이 전략은 아래 상황을 노립니다. + +1. 공시가 나왔다 +2. 시장이 반응일에 너무 강하게 샀다 +3. 그런데 다음 거래일 초반/당일 흐름이 약하다 +4. 그러면 좋은 뉴스가 이미 충분히 반영되었을 가능성이 높다 +5. 1~3일 안에 평균 회귀가 나온다 + +즉, 이 전략은 +**“좋은 뉴스 = 추가 상승”** 이 아니라 +**“좋은 뉴스 + 과열 반응 + follow-through 부재 = 단기 되돌림”** +이라는 가설입니다. + +--- + +# 4. 신호 구조 + +이 전략은 기존 continuation 점수를 그대로 쓰면 안 됩니다. +이번에는 아예 별도 점수로 분리해야 합니다. + +## 핵심 신호 4개 + +### A. Overreaction Score + +반응일에 얼마나 과열되었는지를 봅니다. + +예시 구성: + +* reaction_day_return 높음 +* close_location 높음 +* volume_ratio_20d 높음 +* gap_up 큼 +* high-to-close extension 큼 + +즉, “이날 너무 강하게 갔는가”를 측정합니다. + +--- + +### B. Exhaustion Score + +다음 날 follow-through가 약한지를 봅니다. + +예시: + +* D+1 open이 reaction_day_close 대비 높게 시작했지만 +* D+1 intraday에서 밀림 +* D+1 close가 open 또는 VWAP 아래 +* D+1 close가 reaction_day high 아래에서 약하게 마감 + +무료 데이터 제약 때문에 intraday를 못 보면 일봉 근사도 가능합니다. + +예: + +* D+1 close < D+1 open +* D+1 high만 찍고 음봉 마감 +* D+1 close < reaction_day_close + +--- + +### C. Event Overpricing Score + +문서 품질은 좋더라도, 시장 반응이 그보다 더 강했는지 봅니다. + +예: + +* event_quality는 높지만 +* price reaction이 과도하게 큼 +* guidance는 좋아도 숫자 자체는 폭발적이지 않음 +* oneoff는 아니지만 valuation stretch가 큼 + +즉, 문서와 가격 반응의 **불균형**을 보는 것입니다. + +--- + +### D. Reversal Confirmation Score + +진짜로 뒤집히는지 확인합니다. + +예: + +* D+1 close가 reaction_day midpoint 아래 +* D+1 close가 reaction_day high 근처 안착 실패 +* D+2 early weakness + +v1에서는 너무 복잡하게 말고 +**D+1 약세 확인** 정도만 써도 충분합니다. + +--- + +# 5. 진입 규칙 + +## v1 기본 진입 + +**D+1 close 기준 확인 → D+2 open 숏 진입** + +이유는 단순합니다. +현재 데이터상으로 “next-open 추격”은 continuation에서 불리했으므로, +reversal에서는 반대로 **D+1 약세가 실제로 확인된 뒤** 들어가는 것이 맞습니다. + +## 구체 룰 + +아래를 모두 만족할 때만 진입: + +1. event_type = earnings_release 또는 guidance_update +2. event_direction = bullish +3. oneoff_penalty 낮음 +4. parse_confidence 충분 +5. reaction_day_return이 상위 구간 +6. D+1 close < D+1 open +7. D+1 close < reaction_day_close +8. D+1 close가 reaction_day high breakout 유지 실패 + +이 조건이면 +“좋은 뉴스가 나왔는데도 다음 날 더 못 간다”는 뜻이므로, +되돌림 가설에 맞습니다. + +--- + +# 6. 더 단순한 대안 진입 + +숏 체결/borrow 문제가 부담되면, 같은 논리를 **롱 회피 전략**으로 먼저 쓸 수도 있습니다. + +즉, + +* continuation long 전략에서 +* 과열 + D+1 약세 확인 종목은 그냥 **long 금지** + +이건 바로 production 필터로 넣을 수 있습니다. + +그리고 그 다음 단계에서만 reversal short로 확장해도 됩니다. + +즉, 개발 순서는 이렇게 가는 게 좋습니다. + +1. **Long Avoidance Filter** +2. **Paper Short Reversal** +3. **Small Capital Short Live** + +--- + +# 7. 청산 규칙 + +이 전략은 long continuation보다 더 짧게 가져가야 합니다. + +## 기본 보유기간 + +* 최대 3거래일 +* 권장 기본은 2거래일 + +## 기본 청산 + +### 1) 시간 청산 + +* D+2 진입이면 D+3 또는 D+4 종가 청산 +* 무조건 짧게 + +### 2) 타겟 청산 + +* 반응일 과열분의 일부만 먹으면 됨 +* 1R 또는 1.2R 정도면 충분 + +### 3) 실패 청산 + +* D+2에 바로 반등해버리면 빠르게 손절 +* reversal이 안 나오는 종목은 오래 끌 이유가 없음 + +## 중요한 점 + +이 전략은 continuation보다 더 “타이밍” 중심이므로 +**시간 손절이 하드 손절만큼 중요**합니다. + +--- + +# 8. 손절 규칙 + +v1에서는 너무 넓은 ATR stop보다, 구조적 손절이 더 낫습니다. + +## 권장 손절 + +* D+1 high 돌파 시 손절 +* 또는 진입가 대비 1 ATR 안쪽의 보수적 손절 + +왜냐하면 이 전략의 핵심은 +“D+1에 이미 힘이 약해야 한다”는 전제이기 때문입니다. +그런데 D+2에서 바로 D+1 high를 뚫고 올라가면, 가설이 틀린 것입니다. + +즉, 이 전략의 stop은 +**“변동성 stop”보다 “가설 실패 stop”** 에 가깝게 설계하는 것이 좋습니다. + +--- + +# 9. 포지션 사이징 + +숏 전략은 더 보수적으로 가야 합니다. + +## 권장값 + +* trade당 리스크: 0.25% 이하 +* 동시 포지션: 최대 2개 +* 같은 섹터: 최대 1개 +* gross short exposure 작게 + +그리고 처음에는 아예 + +* paper only +* 또는 micro capital + +로만 보시는 게 맞습니다. + +--- + +# 10. score 설계 예시 + +이 전략은 continuation score를 뒤집는 게 아니라, 새로 만들어야 합니다. + +예시: + +* Overreaction Score: 35% +* Exhaustion Score: 30% +* Event Overpricing Score: 20% +* Reversal Confirmation Score: 15% + +그리고 threshold를 넘는 것만 거래합니다. + +중요한 점은, 여기서는 +**문서가 좋을수록 무조건 좋은 게 아니라, 문서 대비 가격 반응이 과하면 오히려 reversal 후보**가 된다는 것입니다. + +--- + +# 11. 꼭 필요한 필터 + +## 하드 필터 + +* unknown event_type 금지 +* parse confidence 낮음 금지 +* oneoff high 금지 +* D+1 약세 미확인 금지 +* borrow unavailable 금지 +* HTB 비용 과도 금지 + +## 운영 필터 + +* 시장이 강한 risk-on 폭등일이면 숏 금지 +* index strong trend up일 때는 size 축소 +* earnings mega-cap bull squeeze 날은 숏 제한 + +즉, macro regime는 이 전략에서는 **더 중요**합니다. + +--- + +# 12. 백테스트 설계 + +이건 꼭 별도 전략으로 돌리셔야 합니다. + +## 비교 대상 + +반드시 4개를 같이 비교하십시오. + +1. continuation long baseline +2. long avoidance filter만 적용 +3. reversal short candidate only +4. reversal short + regime filter + +## 봐야 할 지표 + +* 1d / 2d / 3d return +* win rate +* PF +* expectancy +* MFE / MAE +* reaction size bucket별 성과 +* D+1 weakness 강도별 성과 +* sector별 성과 + +## 가장 중요한 검증 + +* reaction 강할수록 reversal이 강한가? +* D+1 음봉 강할수록 next 1~3일 수익이 좋아지는가? +* event type별로 차이가 있는가? + +--- + +# 13. 실전 투입 전 조건 + +이 전략은 바로 live로 가면 안 됩니다. + +## 승격 순서 + +1. backtest +2. shadow mode +3. paper short +4. micro capital +5. limited capital + +## live 전 필수 확인 + +* borrow availability hit rate +* locate latency +* fill slippage +* upward gap risk +* short squeeze 리스크 + +--- + +# 14. 지금 인프라에서 바로 재사용 가능한 것 + +이미 만들어둔 걸 대부분 그대로 쓸 수 있습니다. + +## 그대로 쓰는 것 + +* SEC ingestion +* parser +* event taxonomy +* feature store +* backtester +* portfolio engine +* attribution +* runbook / incident 체계 + +## 조금 바꾸는 것 + +* score model +* entry/exit policy +* short risk policy +* borrow/locate checks +* regime gate + +즉, 이 전략은 **새 시스템이 아니라 새 전략 모듈**입니다. + +--- + +# 15. 개발 우선순위 + +제가 바로 개발한다면 순서는 이렇게 갑니다. + +## Step 1 + +**long avoidance filter**부터 구현 +현재 continuation 전략에서 + +* 과열 반응 +* D+1 약세 확인 + 인 종목을 long에서 제외 + +이건 가장 빠르게 효과를 확인할 수 있습니다. + +## Step 2 + +reversal short를 backtest로 구현 + +* D+2 open 진입 +* 1~3일 보유 +* D+1 high stop +* fixed target/time exit + +## Step 3 + +paper short 운영 +borrow/locate 문제와 execution 품질 확인 + +--- + +# 16. 제 최종 판단 + +이 전략은 꽤 합리적입니다. +특히 지금까지의 결과를 보면, continuation보다 **오히려 더 데이터 친화적인 가설**일 가능성이 있습니다. + +왜냐하면 현재 결과가 말하는 건 사실상 이거였기 때문입니다. + +* 강한 이벤트를 따라 사면 잘 안 된다 +* 높은 점수일수록 더 별로다 +* D+1 follow-through가 자주 깨진다 + +그렇다면 자연스러운 다음 가설은 +**“좋은 뉴스 + 과열 반응 + D+1 실패 = 단기 되돌림”** 입니다. + +즉, 이건 억지 피벗이 아니라 +**지금 데이터가 직접 제안하는 다음 전략**이라고 보겠습니다. \ No newline at end of file diff --git a/dev/phase0/README.md b/dev/finished/phase0/README.md similarity index 100% rename from dev/phase0/README.md rename to dev/finished/phase0/README.md diff --git a/dev/phase0/data_source_policy.md b/dev/finished/phase0/data_source_policy.md similarity index 100% rename from dev/phase0/data_source_policy.md rename to dev/finished/phase0/data_source_policy.md diff --git a/dev/phase0/event_taxonomy.md b/dev/finished/phase0/event_taxonomy.md similarity index 100% rename from dev/phase0/event_taxonomy.md rename to dev/finished/phase0/event_taxonomy.md diff --git a/dev/phase0/risk_policy.md b/dev/finished/phase0/risk_policy.md similarity index 100% rename from dev/phase0/risk_policy.md rename to dev/finished/phase0/risk_policy.md diff --git a/dev/phase0/strategy_spec.md b/dev/finished/phase0/strategy_spec.md similarity index 100% rename from dev/phase0/strategy_spec.md rename to dev/finished/phase0/strategy_spec.md diff --git a/dev/phase1_deliverables/README.md b/dev/finished/phase1_deliverables/README.md similarity index 100% rename from dev/phase1_deliverables/README.md rename to dev/finished/phase1_deliverables/README.md diff --git a/dev/phase1_deliverables/architecture_and_repo_plan.md b/dev/finished/phase1_deliverables/architecture_and_repo_plan.md similarity index 100% rename from dev/phase1_deliverables/architecture_and_repo_plan.md rename to dev/finished/phase1_deliverables/architecture_and_repo_plan.md diff --git a/dev/phase1_deliverables/coding_rules.md b/dev/finished/phase1_deliverables/coding_rules.md similarity index 100% rename from dev/phase1_deliverables/coding_rules.md rename to dev/finished/phase1_deliverables/coding_rules.md diff --git a/dev/phase1_deliverables/db_schema.md b/dev/finished/phase1_deliverables/db_schema.md similarity index 100% rename from dev/phase1_deliverables/db_schema.md rename to dev/finished/phase1_deliverables/db_schema.md diff --git a/dev/phase1_deliverables/implementation_plan.md b/dev/finished/phase1_deliverables/implementation_plan.md similarity index 100% rename from dev/phase1_deliverables/implementation_plan.md rename to dev/finished/phase1_deliverables/implementation_plan.md diff --git a/dev/phase1_deliverables/parser_event.schema.json b/dev/finished/phase1_deliverables/parser_event.schema.json similarity index 100% rename from dev/phase1_deliverables/parser_event.schema.json rename to dev/finished/phase1_deliverables/parser_event.schema.json diff --git a/dev/phase1_deliverables/parser_json_schema.md b/dev/finished/phase1_deliverables/parser_json_schema.md similarity index 100% rename from dev/phase1_deliverables/parser_json_schema.md rename to dev/finished/phase1_deliverables/parser_json_schema.md diff --git a/dev/phase1_deliverables/service_contracts.md b/dev/finished/phase1_deliverables/service_contracts.md similarity index 100% rename from dev/phase1_deliverables/service_contracts.md rename to dev/finished/phase1_deliverables/service_contracts.md diff --git a/dev/phase1_deliverables/testing_checklist.md b/dev/finished/phase1_deliverables/testing_checklist.md similarity index 100% rename from dev/phase1_deliverables/testing_checklist.md rename to dev/finished/phase1_deliverables/testing_checklist.md diff --git a/dev/phase2_deliverables/README.md b/dev/finished/phase2_deliverables/README.md similarity index 100% rename from dev/phase2_deliverables/README.md rename to dev/finished/phase2_deliverables/README.md diff --git a/dev/phase2_deliverables/implementation_plan.md b/dev/finished/phase2_deliverables/implementation_plan.md similarity index 100% rename from dev/phase2_deliverables/implementation_plan.md rename to dev/finished/phase2_deliverables/implementation_plan.md diff --git a/dev/phase2_deliverables/ingestion_architecture.md b/dev/finished/phase2_deliverables/ingestion_architecture.md similarity index 100% rename from dev/phase2_deliverables/ingestion_architecture.md rename to dev/finished/phase2_deliverables/ingestion_architecture.md diff --git a/dev/phase2_deliverables/job_catalog.md b/dev/finished/phase2_deliverables/job_catalog.md similarity index 100% rename from dev/phase2_deliverables/job_catalog.md rename to dev/finished/phase2_deliverables/job_catalog.md diff --git a/dev/phase2_deliverables/operator_runbook.md b/dev/finished/phase2_deliverables/operator_runbook.md similarity index 100% rename from dev/phase2_deliverables/operator_runbook.md rename to dev/finished/phase2_deliverables/operator_runbook.md diff --git a/dev/phase2_deliverables/orchestration_and_scheduling.md b/dev/finished/phase2_deliverables/orchestration_and_scheduling.md similarity index 100% rename from dev/phase2_deliverables/orchestration_and_scheduling.md rename to dev/finished/phase2_deliverables/orchestration_and_scheduling.md diff --git a/dev/phase2_deliverables/source_adapter_specs.md b/dev/finished/phase2_deliverables/source_adapter_specs.md similarity index 100% rename from dev/phase2_deliverables/source_adapter_specs.md rename to dev/finished/phase2_deliverables/source_adapter_specs.md diff --git a/dev/phase2_deliverables/storage_layout_and_data_contracts.md b/dev/finished/phase2_deliverables/storage_layout_and_data_contracts.md similarity index 100% rename from dev/phase2_deliverables/storage_layout_and_data_contracts.md rename to dev/finished/phase2_deliverables/storage_layout_and_data_contracts.md diff --git a/dev/phase2_deliverables/testing_checklist.md b/dev/finished/phase2_deliverables/testing_checklist.md similarity index 100% rename from dev/phase2_deliverables/testing_checklist.md rename to dev/finished/phase2_deliverables/testing_checklist.md diff --git a/dev/phase3_deliverables/README.md b/dev/finished/phase3_deliverables/README.md similarity index 100% rename from dev/phase3_deliverables/README.md rename to dev/finished/phase3_deliverables/README.md diff --git a/dev/phase3_deliverables/document_parser_design.md b/dev/finished/phase3_deliverables/document_parser_design.md similarity index 100% rename from dev/phase3_deliverables/document_parser_design.md rename to dev/finished/phase3_deliverables/document_parser_design.md diff --git a/dev/phase3_deliverables/feature_catalog.md b/dev/finished/phase3_deliverables/feature_catalog.md similarity index 100% rename from dev/phase3_deliverables/feature_catalog.md rename to dev/finished/phase3_deliverables/feature_catalog.md diff --git a/dev/phase3_deliverables/feature_record.schema.json b/dev/finished/phase3_deliverables/feature_record.schema.json similarity index 100% rename from dev/phase3_deliverables/feature_record.schema.json rename to dev/finished/phase3_deliverables/feature_record.schema.json diff --git a/dev/phase3_deliverables/implementation_plan.md b/dev/finished/phase3_deliverables/implementation_plan.md similarity index 100% rename from dev/phase3_deliverables/implementation_plan.md rename to dev/finished/phase3_deliverables/implementation_plan.md diff --git a/dev/phase3_deliverables/labeling_and_dataset_spec.md b/dev/finished/phase3_deliverables/labeling_and_dataset_spec.md similarity index 100% rename from dev/phase3_deliverables/labeling_and_dataset_spec.md rename to dev/finished/phase3_deliverables/labeling_and_dataset_spec.md diff --git a/dev/phase3_deliverables/parser_and_feature_architecture.md b/dev/finished/phase3_deliverables/parser_and_feature_architecture.md similarity index 100% rename from dev/phase3_deliverables/parser_and_feature_architecture.md rename to dev/finished/phase3_deliverables/parser_and_feature_architecture.md diff --git a/dev/phase3_deliverables/prompt_and_llm_policy.md b/dev/finished/phase3_deliverables/prompt_and_llm_policy.md similarity index 100% rename from dev/phase3_deliverables/prompt_and_llm_policy.md rename to dev/finished/phase3_deliverables/prompt_and_llm_policy.md diff --git a/dev/phase3_deliverables/quality_assurance_and_review.md b/dev/finished/phase3_deliverables/quality_assurance_and_review.md similarity index 100% rename from dev/phase3_deliverables/quality_assurance_and_review.md rename to dev/finished/phase3_deliverables/quality_assurance_and_review.md diff --git a/dev/phase3_deliverables/review_queue_contract.md b/dev/finished/phase3_deliverables/review_queue_contract.md similarity index 100% rename from dev/phase3_deliverables/review_queue_contract.md rename to dev/finished/phase3_deliverables/review_queue_contract.md diff --git a/dev/phase3_deliverables/review_record.schema.json b/dev/finished/phase3_deliverables/review_record.schema.json similarity index 100% rename from dev/phase3_deliverables/review_record.schema.json rename to dev/finished/phase3_deliverables/review_record.schema.json diff --git a/dev/phase3_deliverables/testing_checklist.md b/dev/finished/phase3_deliverables/testing_checklist.md similarity index 100% rename from dev/phase3_deliverables/testing_checklist.md rename to dev/finished/phase3_deliverables/testing_checklist.md diff --git a/dev/phase4_deliverables/README.md b/dev/finished/phase4_deliverables/README.md similarity index 100% rename from dev/phase4_deliverables/README.md rename to dev/finished/phase4_deliverables/README.md diff --git a/dev/phase4_deliverables/backtest_architecture.md b/dev/finished/phase4_deliverables/backtest_architecture.md similarity index 100% rename from dev/phase4_deliverables/backtest_architecture.md rename to dev/finished/phase4_deliverables/backtest_architecture.md diff --git a/dev/phase4_deliverables/backtest_config.schema.json b/dev/finished/phase4_deliverables/backtest_config.schema.json similarity index 100% rename from dev/phase4_deliverables/backtest_config.schema.json rename to dev/finished/phase4_deliverables/backtest_config.schema.json diff --git a/dev/phase4_deliverables/configuration_and_schemas.md b/dev/finished/phase4_deliverables/configuration_and_schemas.md similarity index 100% rename from dev/phase4_deliverables/configuration_and_schemas.md rename to dev/finished/phase4_deliverables/configuration_and_schemas.md diff --git a/dev/phase4_deliverables/experiment_and_evaluation_plan.md b/dev/finished/phase4_deliverables/experiment_and_evaluation_plan.md similarity index 100% rename from dev/phase4_deliverables/experiment_and_evaluation_plan.md rename to dev/finished/phase4_deliverables/experiment_and_evaluation_plan.md diff --git a/dev/phase4_deliverables/experiment_manifest.schema.json b/dev/finished/phase4_deliverables/experiment_manifest.schema.json similarity index 100% rename from dev/phase4_deliverables/experiment_manifest.schema.json rename to dev/finished/phase4_deliverables/experiment_manifest.schema.json diff --git a/dev/phase4_deliverables/implementation_plan.md b/dev/finished/phase4_deliverables/implementation_plan.md similarity index 100% rename from dev/phase4_deliverables/implementation_plan.md rename to dev/finished/phase4_deliverables/implementation_plan.md diff --git a/dev/phase4_deliverables/operator_research_runbook.md b/dev/finished/phase4_deliverables/operator_research_runbook.md similarity index 100% rename from dev/phase4_deliverables/operator_research_runbook.md rename to dev/finished/phase4_deliverables/operator_research_runbook.md diff --git a/dev/phase4_deliverables/portfolio_and_risk_model.md b/dev/finished/phase4_deliverables/portfolio_and_risk_model.md similarity index 100% rename from dev/phase4_deliverables/portfolio_and_risk_model.md rename to dev/finished/phase4_deliverables/portfolio_and_risk_model.md diff --git a/dev/phase4_deliverables/simulation_engine_design.md b/dev/finished/phase4_deliverables/simulation_engine_design.md similarity index 100% rename from dev/phase4_deliverables/simulation_engine_design.md rename to dev/finished/phase4_deliverables/simulation_engine_design.md diff --git a/dev/phase4_deliverables/testing_checklist.md b/dev/finished/phase4_deliverables/testing_checklist.md similarity index 100% rename from dev/phase4_deliverables/testing_checklist.md rename to dev/finished/phase4_deliverables/testing_checklist.md diff --git a/dev/phase7_deliverables/README.md b/dev/phase7_deliverables/README.md new file mode 100644 index 0000000..d314865 --- /dev/null +++ b/dev/phase7_deliverables/README.md @@ -0,0 +1,58 @@ +# Phase 7 Deliverables — Controlled Live Rollout and Operational Governance + +## Purpose +Phase 7 defines how the system moves from paper trading / small pilot execution into controlled live operation. The focus is not on new alpha generation. The focus is safe capital deployment, operational discipline, change control, incident handling, model drift detection, and post-trade learning. + +This phase assumes the following are already in place: +- Phase 0: strategy, risk, data, and event policy are approved. +- Phase 1: repository, schemas, service boundaries, and coding rules are approved. +- Phase 2: ingestion jobs are stable and replayable. +- Phase 3: parsers and feature builders are producing reviewable records. +- Phase 4: backtests and experiment process are reproducible. +- Phase 5: attention overlay is optional and non-critical. +- Phase 6: paper trading and live order lifecycle are implemented with risk guards. + +## Primary Goals +1. Start live trading with small capital and constrained risk. +2. Introduce staged capital ramp rules based on stability and realized performance. +3. Add governance for releases, approvals, incidents, and rollback. +4. Formalize daily/weekly/monthly operational review. +5. Detect signal drift, parser drift, and execution drift before they become expensive. +6. Produce auditable records for every trade, model version, config version, and incident. + +## Deliverables in This Phase +- `live_rollout_and_capital_ramp.md` +- `production_hardening_and_governance.md` +- `change_management_and_release_policy.md` +- `drift_monitoring_and_post_trade_review.md` +- `performance_attribution_and_reporting.md` +- `implementation_plan.md` +- `testing_checklist.md` +- `operator_runbook.md` +- `rollout_config.schema.json` +- `incident_record.schema.json` + +## Reading Order for AI Coding Agent +1. Phase 0 documents +2. Phase 1 documents +3. Phase 2 documents +4. Phase 3 documents +5. Phase 4 documents +6. Phase 5 documents +7. Phase 6 documents +8. Phase 7 `README.md` +9. `live_rollout_and_capital_ramp.md` +10. `production_hardening_and_governance.md` +11. `change_management_and_release_policy.md` +12. `drift_monitoring_and_post_trade_review.md` +13. `performance_attribution_and_reporting.md` +14. `implementation_plan.md` +15. `testing_checklist.md` + +## Phase 7 Exit Criteria +Phase 7 is complete only when all of the following are true: +- The system can operate in live mode with explicit capital caps and rollback rules. +- Release approval, emergency stop, and incident logging are operational. +- Daily reconciliation, attribution, and drift checks run automatically. +- Configuration and model versions used for each trade are recorded and reproducible. +- A week of live or pilot trading can be reviewed end-to-end without manual log archaeology. diff --git a/dev/phase7_deliverables/change_management_and_release_policy.md b/dev/phase7_deliverables/change_management_and_release_policy.md new file mode 100644 index 0000000..61b1a0a --- /dev/null +++ b/dev/phase7_deliverables/change_management_and_release_policy.md @@ -0,0 +1,104 @@ +# Change Management and Release Policy + +## Objective +The trading system must distinguish research changes from production changes. A profitable backtest is not sufficient to justify live rollout. This policy defines how code, configuration, prompts, schemas, and model versions move from idea to production. + +## Change Categories + +### Category 1 — Non-functional +Examples: +- logging changes +- dashboard text +- documentation updates +- internal refactoring with no output impact + +Approval: +- engineering review +- no strategy re-approval required + +### Category 2 — Operational Functional +Examples: +- adapter retry policy +- alert routing +- improved reconciliation diagnostics +- performance optimization with no intended decision change + +Approval: +- engineering review +- operations review +- replay validation required + +### Category 3 — Decision-Adjacent +Examples: +- parser prompt changes +- feature computation changes +- entity mapping changes +- attention overlay changes +- fill model parameter changes in backtester + +Approval: +- engineering review +- strategy review +- replay and shadow-run evidence required +- release note must include expected behavioral impact + +### Category 4 — Strategy Material +Examples: +- entry/exit logic change +- risk cap change +- new data source enabling live gating +- model reranking rule change +- holding period change + +Approval: +- engineering review +- strategy owner approval +- risk approval +- fresh backtest and walk-forward evidence +- paper or shadow validation window unless emergency exception + +## Required Evidence by Change Type +Every change request must include: +- summary of intent +- affected components +- risk classification +- expected behavior changes +- test evidence +- replay or shadow-run evidence where applicable +- rollback plan +- approver list + +## Prompt and Schema Changes +Prompt version, parser schema version, and output contract changes must be treated as decision-adjacent or strategy-material depending on impact. They may not be edited directly in production without review. + +## Emergency Release Rules +Emergency releases are allowed only to reduce risk or restore safe operation. +Requirements: +- declare incident id +- minimize scope +- execute rollback-ready patch +- perform post-incident review within one business day +- do not include opportunistic unrelated improvements + +## Release Artifacts +Every release must generate: +- release id +- code commit/build id +- config hash +- migration record if any +- validation report link +- operator acknowledgement + +## Canary / Shadow Rules +Before broad activation, decision-adjacent and strategy-material changes should run in one or more of: +- replay validation +- paper trading shadow mode +- candidate diff mode against current production + +Diff metrics to compare: +- candidate count change +- rank order change +- rejection rate change +- order count change +- expected exposure change +- attribution by feature family diff --git a/dev/phase7_deliverables/drift_monitoring_and_post_trade_review.md b/dev/phase7_deliverables/drift_monitoring_and_post_trade_review.md new file mode 100644 index 0000000..897e67e --- /dev/null +++ b/dev/phase7_deliverables/drift_monitoring_and_post_trade_review.md @@ -0,0 +1,98 @@ +# Drift Monitoring and Post-Trade Review + +## Objective +Live systems fail slowly before they fail obviously. Drift monitoring detects when the strategy, parser, data, or execution quality no longer matches the assumptions validated in research and paper trading. + +## Drift Domains +1. Data drift +2. Parser drift +3. Feature drift +4. Candidate drift +5. Execution drift +6. Performance drift + +## Data Drift +Monitor for: +- missing mandatory fields by source +- source latency changes +- unexpected filing/exhibit parse failures +- symbol mapping failures +- bar completeness issues + +Suggested metrics: +- null rate by field +- source freshness SLA miss rate +- adapter error rate +- duplicate raw payload rate +- daily event count vs rolling baseline + +## Parser Drift +Monitor for: +- confidence score distribution shift +- event_type mix change +- oneoff_flag rate change +- guidance_direction distribution shift +- increase in manual review rate + +Use a gold set and periodic re-score process. Any meaningful degradation must freeze capital ramp. + +## Feature Drift +Monitor for: +- z-score distribution change by feature +- feature null rate +- clipping rate +- value range violations +- population stability index or equivalent simple distribution checks + +## Candidate Drift +Monitor for: +- candidate count per day +- pass rate by rule gate +- score distribution +- rank order concentration +- sector concentration + +## Execution Drift +Monitor for: +- live slippage vs modeled slippage +- open-to-fill latency +- partial fill rate +- cancel/replace rate +- rejected order rate +- discrepancy between planned and realized size + +## Performance Drift +Monitor for: +- rolling expectancy +- rolling hit rate +- rolling average R multiple +- MFE/MAE relationship +- realized vs backtest tolerance band + +## Alerting Rules +- Warning alert: threshold crossed once or mildly. +- Action alert: threshold crossed repeatedly or materially. +- Freeze alert: capital ramp freeze condition met. +- Kill alert: operational integrity risk. + +## Post-Trade Review Process +Every closed trade must receive a structured review. +Fields should include: +- thesis type +- event category +- approval path +- whether data was clean +- whether parser output looked reasonable +- whether execution matched plan +- exit reason +- what worked +- what failed +- preventable vs non-preventable +- follow-up action + +## Weekly Review Questions +- Are losses coming from market regime or operational issues? +- Are parser changes affecting candidate quality? +- Is attention overlay helping or just adding noise? +- Are live fills materially worse than expected? +- Are more trades being blocked by guards than research expected? diff --git a/dev/phase7_deliverables/implementation_plan.md b/dev/phase7_deliverables/implementation_plan.md new file mode 100644 index 0000000..caef6f3 --- /dev/null +++ b/dev/phase7_deliverables/implementation_plan.md @@ -0,0 +1,81 @@ +# Phase 7 Implementation Plan + +## Objective +Implement the control plane and operating discipline required for live rollout, capital ramp, governance, and drift-aware operation. + +## Workstreams + +### Workstream 1 — Rollout Control Plane +Tasks: +- implement capital stage registry +- implement stage transition workflow +- store stage evidence and approvals +- enforce live eligibility rules by stage + +Outputs: +- stage table / registry +- promotion and rollback service +- audit records for stage changes + +### Workstream 2 — Governance and Release Records +Tasks: +- implement release record model +- implement change request model +- attach config/code/model versions to runtime +- expose release status in dashboard/logs + +Outputs: +- release and change tables +- governance API or command hooks +- immutable release bundle references + +### Workstream 3 — Drift Monitoring +Tasks: +- compute daily drift metrics +- define thresholds and alert routes +- store snapshots for weekly/monthly comparison +- produce freeze/rollback recommendations + +Outputs: +- drift metric jobs +- alert rules +- drift history tables + +### Workstream 4 — Post-Trade Review and Attribution +Tasks: +- generate review tickets on trade close +- support manual completion and status tracking +- build daily/weekly/monthly reports +- join trade, feature, config, and incident data + +Outputs: +- review workflow +- attribution queries +- report generation jobs + +### Workstream 5 — Operator Tools +Tasks: +- add commands for stage freeze, rollback, emergency no-trade +- add dashboards or CLI views for current stage, incidents, drift, approvals +- add runbook links and checklists + +Outputs: +- ops CLI/dashboard endpoints +- emergency controls +- operator summary views + +## Suggested Build Order +1. rollout stage registry and config schema +2. release/change records +3. drift snapshot jobs +4. post-trade review workflow +5. daily and weekly reporting +6. stage transition approval flow +7. operator tools and polish + +## Done Criteria +- Live stage can be changed only through approved workflow. +- Every trade can be mapped to stage, release, config, and review. +- Drift alerts are generated automatically and stored. +- Daily and weekly reports are reproducible from stored records. +- Rollback/freeze/no-trade actions are tested end-to-end. diff --git a/dev/phase7_deliverables/incident_record.schema.json b/dev/phase7_deliverables/incident_record.schema.json new file mode 100644 index 0000000..c5a08a9 --- /dev/null +++ b/dev/phase7_deliverables/incident_record.schema.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.local/schemas/incident_record.schema.json", + "title": "IncidentRecord", + "type": "object", + "additionalProperties": false, + "required": [ + "incident_id", + "severity", + "status", + "detected_at", + "summary", + "systems_impacted", + "mitigation_taken" + ], + "properties": { + "incident_id": {"type": "string"}, + "severity": { + "type": "string", + "enum": ["sev1", "sev2", "sev3", "sev4"] + }, + "status": { + "type": "string", + "enum": ["open", "mitigated", "closed"] + }, + "detected_at": { + "type": "string", + "format": "date-time" + }, + "summary": {"type": "string"}, + "systems_impacted": { + "type": "array", + "items": {"type": "string"} + }, + "trade_ids": { + "type": "array", + "items": {"type": "string"} + }, + "release_id": {"type": "string"}, + "config_hash": {"type": "string"}, + "mitigation_taken": {"type": "string"}, + "root_cause": {"type": "string"}, + "preventive_action": {"type": "string"} + } +} diff --git a/dev/phase7_deliverables/live_rollout_and_capital_ramp.md b/dev/phase7_deliverables/live_rollout_and_capital_ramp.md new file mode 100644 index 0000000..3c55c11 --- /dev/null +++ b/dev/phase7_deliverables/live_rollout_and_capital_ramp.md @@ -0,0 +1,107 @@ +# Live Rollout and Capital Ramp + +## Objective +This document defines how capital is introduced gradually into live trading. The strategy is assumed to be profitable enough in research and stable enough in paper trading to justify real capital exposure. The purpose of the ramp is to limit operational losses while verifying that live performance matches expectations. + +## Rollout Principles +- Start smaller than feels necessary. +- Increase capital only after stability thresholds are met. +- Treat unexpected operational behavior as more serious than short-term PnL noise. +- Favor “no trade” over ambiguous or degraded execution. +- Never ramp capital because of a short streak of wins. + +## Capital Stages + +### Stage A — Pilot Live +Purpose: confirm end-to-end live behavior at minimal financial risk. +- Max portfolio capital allocated: 5% to 10% of planned production capital. +- Max per-trade risk: 0.10% to 0.15% of total account equity. +- Max simultaneous positions: 1 to 2. +- Overlay features optional; may be disabled by default. +- Human approval required before order submission. +- No strategy branching. Use only the primary long continuation engine. + +Promotion conditions: +- Minimum 20 live trades OR 4 live trading weeks, whichever is later. +- No unresolved severe incident in the observation window. +- Data freshness violations below threshold. +- Order/position reconciliation success on every trading day. +- Live slippage within acceptable band vs simulated expectation. +- Parser and feature quality review passing rate above threshold. + +### Stage B — Controlled Live +Purpose: allow limited autonomy while validating consistency. +- Max portfolio capital allocated: 20% to 35% of planned production capital. +- Max per-trade risk: 0.20% to 0.25% of total account equity. +- Max simultaneous positions: 2 to 3. +- Human approval optional for low-risk standard setups; required for anomalies. +- Attention overlay may be enabled only if Phase 5 research justified it. + +Promotion conditions: +- Minimum additional 30 live trades. +- No medium-or-higher unresolved incidents for the last 20 trading days. +- Outlier slippage days investigated and closed. +- Drift checks stable for the observation window. +- Daily review completed on time for at least 90% of trading days. + +### Stage C — Production Live +Purpose: run the approved strategy under standard production governance. +- Max portfolio capital allocated: 100% of approved production capital. +- Max per-trade risk: determined by current risk policy, but may not exceed Phase 0 limits. +- Max simultaneous positions: as allowed by approved risk policy. +- Human approval required only for exceptions, config changes, or degraded mode. + +## Ramp Freeze Conditions +Capital increase is automatically frozen if any of the following occur: +- New unresolved high-severity incident. +- Reconciliation mismatch not resolved by the next trading day. +- Live execution drift outside threshold for 3 days in a rolling 10-day window. +- Parser confidence or gold-set accuracy drops below threshold. +- Data source instability causes candidate corruption or missing mandatory features. +- Strategy underperforms simulation beyond allowed tolerance after sufficient sample. + +## Ramp Rollback Conditions +Capital must be reduced to the prior stage if any of the following occur: +- Emergency kill switch triggered. +- Two severe incidents within 30 calendar days. +- Unauthorized config or code drift detected. +- Daily max-loss breach caused by operational fault. +- Strategy produces materially different positions than approved research spec. + +## Live Eligibility Filter +A trade candidate is eligible for live execution only if: +- It is produced by an approved strategy id and approved config version. +- Mandatory market, event, and risk features are present. +- Candidate timestamp is valid for session timing rules. +- Risk guard passes. +- Required approval state is satisfied. +- Data freshness SLA is satisfied for all mandatory sources. + +## Approval Matrix +- Standard candidate in Stage A: human approval required. +- Standard candidate in Stage B: optional if no anomalies. +- Candidate with degraded overlay, stale optional features, or manual override: human approval required. +- Candidate generated under degraded mandatory source mode: reject or no-trade. + +## Metrics for Ramp Decisions +The capital committee or operator should review: +- Realized expectancy. +- Win rate and payoff ratio. +- Slippage vs expectation. +- Fill quality. +- Average holding period. +- Risk utilization. +- Cancel/reject ratio. +- Incident count by severity. +- Review SLA adherence. +- Drift metrics. + +## Required Records +Every ramp decision must create an auditable record containing: +- date and effective stage +- prior stage and new stage +- approver +- summary of evidence +- performance window used +- incidents considered +- config and model versions in effect diff --git a/dev/phase7_deliverables/operator_runbook.md b/dev/phase7_deliverables/operator_runbook.md new file mode 100644 index 0000000..a6a98d8 --- /dev/null +++ b/dev/phase7_deliverables/operator_runbook.md @@ -0,0 +1,53 @@ +# Phase 7 Operator Runbook + +## Purpose +This runbook describes the human operating procedure for controlled live rollout. + +## Daily Pre-Market Checklist +- Confirm current live stage and approved capital limits. +- Review unresolved incidents. +- Review overnight drift alerts. +- Confirm active release/config ids match approved versions. +- Confirm all mandatory sources met freshness SLA. +- Confirm no-trade or degraded mode status. + +## During Market Hours +- Monitor new alerts. +- Review exceptional approval tickets. +- Do not change stage or release unless emergency procedure is invoked. +- If severe incident occurs, prioritize containment over diagnosis. + +## End-of-Day Checklist +- Confirm reconciliation completed. +- Review fill quality and slippage outliers. +- Check post-trade reviews created for all closed trades. +- Review freeze or rollback recommendations. +- Verify daily report generated and archived. + +## Emergency No-Trade Procedure +1. Activate no-trade mode. +2. Verify new orders are blocked. +3. Decide whether open non-protective orders should be canceled. +4. Record incident id and operator note. +5. Notify approver/governance group. + +## Stage Freeze Procedure +1. Confirm freeze condition evidence. +2. Record stage freeze event. +3. Prevent further promotions. +4. Continue or reduce trading according to risk policy. +5. Schedule review before lifting freeze. + +## Stage Rollback Procedure +1. Confirm rollback trigger. +2. Move to prior approved stage. +3. Update active capital and risk caps. +4. Record change and approver. +5. Review all open positions for compliance. + +## Post-Incident Procedure +- Open incident record if not already created. +- Collect affected trade ids. +- Link config/release versions. +- Assign root-cause owner. +- Schedule corrective and preventive actions. diff --git a/dev/phase7_deliverables/performance_attribution_and_reporting.md b/dev/phase7_deliverables/performance_attribution_and_reporting.md new file mode 100644 index 0000000..902d0a6 --- /dev/null +++ b/dev/phase7_deliverables/performance_attribution_and_reporting.md @@ -0,0 +1,77 @@ +# Performance Attribution and Reporting + +## Objective +Performance reporting in Phase 7 must separate alpha quality from operational quality. A winning week is not evidence of healthy production if reconciliation is failing. A losing week is not evidence of broken alpha if execution matched plan and market conditions were adverse. + +## Reporting Layers +1. Strategy-level performance +2. Trade-level attribution +3. Feature-family attribution +4. Operational attribution +5. Stage/ramp governance reporting + +## Daily Report +Required sections: +- positions opened and closed +- realized and unrealized PnL +- gross and net exposure +- slippage summary +- incidents and alerts +- reconciliation outcome +- candidates rejected by guard reason +- top winners and losers + +## Weekly Report +Required sections: +- expectancy, hit rate, payoff ratio +- attribution by event type +- attribution by hold horizon bucket +- attribution by score bucket +- attribution by approval path +- execution quality summary +- parser/manual review summary +- drift summary +- release/change summary + +## Monthly Report +Required sections: +- cumulative return and drawdown +- risk-adjusted performance +- capital stage history +- configuration changes and their impact +- recurring incident themes +- operational debt backlog +- recommendation: hold, ramp, freeze, or rollback + +## Attribution Dimensions +Every trade should be attributable by: +- event_type +- direction +- score bucket +- overlay on/off +- sector +- approval path +- config version +- parser version +- feature version +- live stage + +## Minimum Metrics +- gross pnl +- net pnl +- expectancy +- win rate +- average win +- average loss +- profit factor +- average holding period +- turnover +- realized slippage +- percent of days with incidents +- percent of trades requiring manual intervention + +## Interpretation Rules +- Do not attribute alpha improvement to a release without sufficient sample. +- Do not attribute operational issues to market conditions without evidence. +- Compare live vs simulation using matched assumptions where possible. +- Maintain a stable definition of score buckets across review periods. diff --git a/dev/phase7_deliverables/production_hardening_and_governance.md b/dev/phase7_deliverables/production_hardening_and_governance.md new file mode 100644 index 0000000..5971e68 --- /dev/null +++ b/dev/phase7_deliverables/production_hardening_and_governance.md @@ -0,0 +1,94 @@ +# Production Hardening and Governance + +## Objective +This document defines operational governance for live trading. It assumes trading can lose money quickly when operational discipline is weak. Governance must therefore be explicit, logged, and testable. + +## Governance Domains +1. Runtime safety +2. Access control +3. Config integrity +4. Release approval +5. Incident response +6. Auditability +7. Reporting and review + +## Runtime Safety Controls +Mandatory controls: +- kill switch with immediate new-order disable +- no-trade mode +- degraded mode classification by source and subsystem +- hard daily loss limit +- hard gross exposure limit +- hard net exposure limit +- per-symbol and per-sector caps +- stale-data rejection for mandatory inputs +- duplicate order prevention +- restart-safe state recovery + +## Access Control +Minimum roles: +- Operator: may start/stop jobs and review health, but may not change strategy logic. +- Approver: may approve live rollout stage changes and exceptional trades. +- Engineer: may deploy code through approved process, but not bypass approvals. +- Auditor: read-only access to logs, configs, incidents, and trade history. + +## Configuration Governance +- All live configs must be versioned and immutable after activation. +- Any manual override must create an audit log. +- Runtime should record exact config hash used for each trade. +- Config changes may not be applied mid-session unless emergency procedure is used. + +## Release Gates +A release may move to live only if: +- code passed all required tests +- migration steps were rehearsed +- rollback steps are documented +- a release note exists with risk classification +- dry-run or replay evidence is attached +- approval is recorded + +## Emergency Procedures +Emergency actions include: +- disable new orders +- cancel open non-protective orders +- force no-trade mode +- disable specific adapters or overlays +- revert to previous known-good config +- pause stage promotion + +Each emergency action must be logged with who, when, why, and expected blast radius. + +## Incident Governance +Incidents are classified by severity: +- Sev-1: material financial or safety risk; immediate intervention required. +- Sev-2: significant malfunction but contained. +- Sev-3: degraded behavior with workaround. +- Sev-4: cosmetic or low-risk issue. + +For every incident, capture: +- detection time +- impacted systems +- first symptom +- trades affected +- capital at risk +- mitigation taken +- root cause +- corrective action +- prevention action + +## Audit Requirements +The system must retain for each trade: +- strategy id +- config version/hash +- code version/build id +- parser version +- feature version +- approval state +- order lifecycle events +- reconciliation outcome +- post-trade review id + +## Governance Meetings +- Daily ops review: incidents, fills, reconciliation, drift alerts. +- Weekly strategy ops review: attribution, execution drift, parser quality, config exceptions. +- Monthly governance review: stage eligibility, risk policy adherence, release quality, recurring issues. diff --git a/dev/phase7_deliverables/rollout_config.schema.json b/dev/phase7_deliverables/rollout_config.schema.json new file mode 100644 index 0000000..cbc6c23 --- /dev/null +++ b/dev/phase7_deliverables/rollout_config.schema.json @@ -0,0 +1,58 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://example.local/schemas/rollout_config.schema.json", + "title": "RolloutConfig", + "type": "object", + "additionalProperties": false, + "required": [ + "stage", + "effective_from", + "max_portfolio_allocation_pct", + "max_per_trade_risk_pct", + "max_positions", + "human_approval_mode", + "freeze_conditions", + "rollback_conditions" + ], + "properties": { + "stage": { + "type": "string", + "enum": ["pilot_live", "controlled_live", "production_live"] + }, + "effective_from": { + "type": "string", + "format": "date-time" + }, + "max_portfolio_allocation_pct": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "max_per_trade_risk_pct": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "max_positions": { + "type": "integer", + "minimum": 1 + }, + "human_approval_mode": { + "type": "string", + "enum": ["always", "anomalies_only", "exceptions_only"] + }, + "freeze_conditions": { + "type": "array", + "minItems": 1, + "items": {"type": "string"} + }, + "rollback_conditions": { + "type": "array", + "minItems": 1, + "items": {"type": "string"} + }, + "notes": { + "type": "string" + } + } +} diff --git a/dev/phase7_deliverables/testing_checklist.md b/dev/phase7_deliverables/testing_checklist.md new file mode 100644 index 0000000..68b5558 --- /dev/null +++ b/dev/phase7_deliverables/testing_checklist.md @@ -0,0 +1,39 @@ +# Phase 7 Testing Checklist + +## 1. Unit Tests +- [ ] Stage promotion rule evaluator returns correct decision for boundary cases. +- [ ] Stage rollback triggers on each configured rollback condition. +- [ ] Release classifier maps change categories correctly. +- [ ] Drift calculators handle missing data and sparse windows. +- [ ] Attribution joins are stable when optional overlay data is absent. +- [ ] Incident severity routing produces correct escalation path. +- [ ] Config hash and release id are attached to runtime records. + +## 2. Integration Tests +- [ ] Create a mock stage transition request and verify approval, persistence, and activation. +- [ ] Trigger freeze condition from drift metrics and verify stage freeze is enforced. +- [ ] Trigger rollback after simulated Sev-1 incident and verify exposure constraints update. +- [ ] Submit a mock release record and verify runtime references pick up the correct active version. +- [ ] Close a trade and verify post-trade review ticket is created automatically. +- [ ] Reconcile a day of trades and verify attribution report includes stage and config references. + +## 3. Replay / Scenario Tests +- [ ] Replay a benign week and confirm no false freeze events. +- [ ] Replay a week with stale data and verify no-trade or freeze behavior occurs. +- [ ] Replay a sequence with parser drift and verify review queue and alerts increase. +- [ ] Replay degraded fill quality and verify execution drift alerts trigger. +- [ ] Replay performance under expected drawdown and confirm no improper rollback. + +## 4. Ops / Failure Tests +- [ ] Emergency no-trade command disables new orders without corrupting state. +- [ ] Emergency rollback to prior stage is logged and survives restart. +- [ ] Release activation failure falls back to previous known-good configuration. +- [ ] Incident creation and closure workflow can be completed without manual database edits. +- [ ] Missing report input data produces a degraded report, not a crash. + +## 5. Acceptance Checklist +- [ ] All stage changes are auditable. +- [ ] Daily drift snapshot exists for every trading day in the test window. +- [ ] At least one daily, weekly, and monthly report can be reproduced exactly. +- [ ] Operator runbook steps were executed in a dry run. +- [ ] Freeze and rollback logic was validated by scenario test. diff --git a/dev/phase8_deliverables/README.md b/dev/phase8_deliverables/README.md new file mode 100644 index 0000000..bf16711 --- /dev/null +++ b/dev/phase8_deliverables/README.md @@ -0,0 +1,46 @@ +# Phase 8 Deliverables + +이 문서 묶음은 **확장 단계(Phase 8)** 를 위한 개발/운영 명세입니다. + +Phase 8의 목적은 기본 이벤트 continuation 엔진(Phase 0~7)이 이미 안정적으로 운영되고 있다는 전제 아래, +전략·모델·실행·인프라·연구 자동화를 **안전하게 확장**하는 것입니다. + +## Phase 8의 진입 조건 +- Phase 6 paper trading 안정화 +- Phase 7 live rollout 안정화 +- core strategy 성과 attribution 가능 +- parser / feature / overlay / execution drift 분리 관찰 가능 +- kill switch, rollback, no-trade mode 구현 완료 +- 실험/릴리즈/운영 로그 감사 가능 + +## 문서 구성 +- `multi_strategy_expansion.md` +- `short_engine_design.md` +- `model_upgrade_and_ensemble.md` +- `execution_optimization.md` +- `research_automation.md` +- `platform_scaling_and_ops.md` +- `implementation_plan.md` +- `testing_checklist.md` +- `operator_runbook.md` +- `phase8_config.schema.json` +- `strategy_registry.schema.json` + +## 운영 원칙 +1. Phase 8은 **core strategy 대체**가 아니라 **확장**이다. +2. 새 기능은 반드시 feature flag 아래에서 도입한다. +3. 새 전략/모델은 독립 평가와 shadow mode를 통과해야 한다. +4. attribution이 불가능하면 production 승격 금지. +5. 항상 Phase 7 안정 상태로 롤백 가능해야 한다. + +## AI 코딩 에이전트 전달 순서 +1. Phase 0~7 문서 +2. 이 `README.md` +3. `multi_strategy_expansion.md` +4. `short_engine_design.md` +5. `model_upgrade_and_ensemble.md` +6. `execution_optimization.md` +7. `research_automation.md` +8. `platform_scaling_and_ops.md` +9. `implementation_plan.md` +10. `testing_checklist.md` diff --git a/dev/phase8_deliverables/execution_optimization.md b/dev/phase8_deliverables/execution_optimization.md new file mode 100644 index 0000000..d0f4333 --- /dev/null +++ b/dev/phase8_deliverables/execution_optimization.md @@ -0,0 +1,35 @@ +# Execution Optimization + +## 목적 +같은 신호를 더 안정적이고 더 낮은 비용으로 체결하기 위한 **실행 최적화 레이어**를 설계합니다. + +## 최적화 대상 +### 1. Entry Timing +- next-open market +- next-open limit band +- close auction participation +- split-entry window +- liquidity-aware delay + +### 2. Exit Timing +- close-based exit vs staggered exit +- stop handling 개선 +- partial exit ladder + +### 3. Order Shaping +- child order 분할 +- participation cap +- spread-aware limit offset +- liquidity gate + +## 정책 +- baseline execution을 항상 유지 +- execution 최적화는 feature flag 하에서만 활성화 +- failure 시 baseline execution으로 자동 fallback + +## 평가 지표 +- arrival slippage +- decision-to-fill latency +- implementation shortfall +- fill ratio +- partial fill rate diff --git a/dev/phase8_deliverables/implementation_plan.md b/dev/phase8_deliverables/implementation_plan.md new file mode 100644 index 0000000..63e3f26 --- /dev/null +++ b/dev/phase8_deliverables/implementation_plan.md @@ -0,0 +1,44 @@ +# Phase 8 Implementation Plan + +## 목표 +Phase 8은 확장 기능을 한 번에 다 넣는 단계가 아닙니다. +다음 순서대로, attribution이 가능한 범위 내에서 점진적으로 구현합니다. + +## Workstream A — 전략 확장 +### A1. 전략 레지스트리 도입 +- strategy registry 스키마 구현 +- long core / overlay / experimental strategy 구분 +- status: draft, backtest, shadow, paper, live_limited, live_full + +### A2. short engine scaffold +- event taxonomy 확장 +- borrow/locate contract 추가 +- short-specific risk policy 추가 + +### A3. holding-period specialization +- 1D/3D/5D horizon variant를 별도 strategy_id로 등록 + +## Workstream B — 모델 고도화 +### B1. model registry +### B2. meta model / horizon selector +### B3. disagreement reporting + +## Workstream C — 실행 최적화 +### C1. execution metrics collection +### C2. execution policy flagging +### C3. replay evaluation + +## Workstream D — 연구 자동화 +### D1. post-trade review assistant +### D2. drift diagnosis +### D3. experiment recommender + +## Workstream E — 플랫폼 확장 +### E1. registry layer +### E2. feature flags +### E3. staged rollout/rollback automation + +## 완료 조건 +- core strategy attribution 유지 +- 새 기능 shadow/paper 검증 통과 +- Phase 7 safe mode로 즉시 롤백 가능 diff --git a/dev/phase8_deliverables/model_upgrade_and_ensemble.md b/dev/phase8_deliverables/model_upgrade_and_ensemble.md new file mode 100644 index 0000000..2cacbb8 --- /dev/null +++ b/dev/phase8_deliverables/model_upgrade_and_ensemble.md @@ -0,0 +1,45 @@ +# Model Upgrade and Ensemble + +## 목적 +현재의 규칙 + LLM 파서 + 트리 기반 랭킹을 유지하면서, 이를 **고도화된 모델 스택**으로 확장합니다. + +## 원칙 +- 해석 가능성을 완전히 버리지 않는다. +- feature leakage 위험이 높은 모델은 production 전 별도 검증. +- 더 복잡한 모델은 항상 baseline을 이겨야 한다. +- inference 실패 시 baseline 모델로 fallback. + +## 확장 후보 +### 1. Meta Model +목적: “이번 신호를 얼마나 믿어야 하는가”를 판단 + +입력: +- event score +- parser confidence +- market regime +- recent drift metrics +- symbol/sector stability + +출력: +- trade_quality_probability +- recommended_size_multiplier +- holding_period_bias + +### 2. Horizon Selector +- 1D 기대값 +- 3D 기대값 +- 5D 기대값 +- stop-hit probability + +### 3. Ensemble Ranker +후보: +- gradient boosting +- calibrated logistic model +- event-type specialist model +- sector specialist model + +## 운영 규칙 +- baseline model은 항상 유지 +- 새 모델은 shadow inference 기간을 거친다 +- production prediction과 shadow prediction을 동시 기록 +- disagreement report를 주기적으로 생성 diff --git a/dev/phase8_deliverables/multi_strategy_expansion.md b/dev/phase8_deliverables/multi_strategy_expansion.md new file mode 100644 index 0000000..89f073b --- /dev/null +++ b/dev/phase8_deliverables/multi_strategy_expansion.md @@ -0,0 +1,56 @@ +# Multi-Strategy Expansion + +## 목적 +기존의 단일 전략 중심 시스템을 **전략 포트폴리오 시스템**으로 확장합니다. + +목표: +- alpha source 다변화 +- 특정 레짐/섹터/이벤트 타입 편중 완화 + +## 확장 대상 전략 +### 1. Short Catalyst Engine +- 가이던스 대폭 하향 +- 구조적 수요 둔화 +- 희석성 자금조달 +- 규제/회계/소송/임상 실패 +- 실적 반응 후 dead-cat bounce 재하락 + +### 2. Sector / Theme Continuation Engine +- 개별 종목 이벤트보다 섹터/테마 전체 재평가에 초점 +- 예: AI 인프라, 비만치료제, 원전, 국방, 반도체 장비 + +### 3. Cash Account Satellite Intraday Engine +- 메인 엔진과 분리된 소액 보조 엔진 +- settled cash 규율 준수 +- same-day turnover는 독립 리스크 버킷 관리 + +### 4. Holding-Period Specialization Engine +- 1~2일형 +- 3~5일형 +- 5~10일형 + +## 전략 포트폴리오 원칙 +- 각 전략은 독립된 `strategy_id`를 가진다. +- 독립된 feature set / label set / config / risk bucket을 가진다. +- 독립적인 paper track record를 축적해야 한다. +- 중앙 allocator가 capital allocation을 결정한다. + +## 전략 간 상호작용 규칙 +### 금지 +- 동일 종목 long/short 동시 live 포지션 +- 전략 간 신호 직접 overwrite +- 한 전략의 overlay를 다른 전략의 핵심 신호로 승격 + +### 허용 +- 중앙 allocator의 통합 랭킹 +- 섹터 익스포저 관점의 순위 조정 +- 상관관계 기반 capital budget 조정 + +## 전략 승격 기준 +1. offline backtest +2. walk-forward validation +3. shadow mode +4. paper trading +5. micro-capital live +6. limited capital live +7. full rollout diff --git a/dev/phase8_deliverables/operator_runbook.md b/dev/phase8_deliverables/operator_runbook.md new file mode 100644 index 0000000..2d5125e --- /dev/null +++ b/dev/phase8_deliverables/operator_runbook.md @@ -0,0 +1,43 @@ +# Phase 8 Operator Runbook + +## 목적 +확장 기능이 production에 들어온 뒤, 운영자가 어떤 순서로 켜고 끄고 감시하고 롤백할지 정의합니다. + +## 일일 운영 절차 +### 장 시작 전 +1. registry 상태 확인 +2. strategy별 rollout 상태 확인 +3. feature flag diff 확인 +4. shadow/paper/live 상태 요약 확인 +5. broker / data / parser / model health 확인 + +### 장중 +1. short engine borrow 상태 확인 +2. optimized execution 이상치 확인 +3. disagreement spike 확인 +4. drift alert 확인 +5. incident 필요 시 feature flag off + +### 장 종료 후 +1. strategy attribution 확인 +2. post-trade review assistant 결과 검토 +3. drift diagnosis 검토 +4. incident / anomaly 티켓 발행 +5. 다음 거래일 rollout 유지/축소 결정 + +## 롤백 규칙 +다음 중 하나라도 발생하면 확장 기능을 우선 끕니다. +- 새 전략의 이상 손실 +- borrow/locate 실패율 급증 +- model disagreement 급등 +- execution slippage 급등 +- parser/prompt drift 심화 +- attribution 불가능 + +## 롤백 순서 +1. optimized execution off +2. candidate model off → baseline only +3. experimental strategy off +4. short engine off +5. overlay off +6. Phase 7 safe mode 유지 diff --git a/dev/phase8_deliverables/phase8_config.schema.json b/dev/phase8_deliverables/phase8_config.schema.json new file mode 100644 index 0000000..37c968b --- /dev/null +++ b/dev/phase8_deliverables/phase8_config.schema.json @@ -0,0 +1,114 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Phase8Config", + "type": "object", + "additionalProperties": false, + "required": [ + "phase", + "flags", + "registry", + "rollout", + "fallbacks" + ], + "properties": { + "phase": { + "type": "string", + "const": "phase8" + }, + "flags": { + "type": "object", + "additionalProperties": false, + "properties": { + "enable_short_engine": { + "type": "boolean" + }, + "enable_meta_model": { + "type": "boolean" + }, + "enable_horizon_selector": { + "type": "boolean" + }, + "enable_execution_optimization": { + "type": "boolean" + }, + "enable_research_automation": { + "type": "boolean" + } + }, + "required": [ + "enable_short_engine", + "enable_meta_model", + "enable_horizon_selector", + "enable_execution_optimization", + "enable_research_automation" + ] + }, + "registry": { + "type": "object", + "additionalProperties": false, + "required": [ + "strategy_registry_path", + "model_registry_path", + "prompt_registry_path" + ], + "properties": { + "strategy_registry_path": { + "type": "string" + }, + "model_registry_path": { + "type": "string" + }, + "prompt_registry_path": { + "type": "string" + } + } + }, + "rollout": { + "type": "object", + "additionalProperties": false, + "required": [ + "mode", + "max_capital_fraction", + "require_shadow_pass" + ], + "properties": { + "mode": { + "type": "string", + "enum": [ + "paper", + "live_limited", + "live_full" + ] + }, + "max_capital_fraction": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "require_shadow_pass": { + "type": "boolean" + } + } + }, + "fallbacks": { + "type": "object", + "additionalProperties": false, + "required": [ + "baseline_model_id", + "baseline_execution_policy_id", + "phase7_safe_mode_profile" + ], + "properties": { + "baseline_model_id": { + "type": "string" + }, + "baseline_execution_policy_id": { + "type": "string" + }, + "phase7_safe_mode_profile": { + "type": "string" + } + } + } + } +} \ No newline at end of file diff --git a/dev/phase8_deliverables/platform_scaling_and_ops.md b/dev/phase8_deliverables/platform_scaling_and_ops.md new file mode 100644 index 0000000..6ee2475 --- /dev/null +++ b/dev/phase8_deliverables/platform_scaling_and_ops.md @@ -0,0 +1,46 @@ +# Platform Scaling and Operations + +## 목적 +개인용/단일전략 운영 구조를, 다중 전략과 장기 운영에 적합한 플랫폼 구조로 확장합니다. + +## 확장 방향 +### 1. Environment Separation +- dev +- replay / research +- paper +- staging-live +- production-live + +### 2. Registry +- strategy registry +- model registry +- prompt registry +- data source registry +- execution policy registry + +### 3. Workflow Orchestration +- backfill/replay workflow +- retraining workflow +- shadow inference workflow +- rollout workflow +- rollback workflow + +### 4. Observability +- service health +- data freshness +- parser error rate +- model disagreement +- broker reconciliation lag +- strategy-level PnL / exposure + +## 접근 제어 +- 운영자 권한과 연구자 권한 분리 +- production config 수정은 승인 필요 +- live routing 변경은 감사 로그 필수 +- API key rotation 정책 필수 + +## 백업/복구 +- raw / structured / runtime state 별 백업 정책 분리 +- restart 시 checkpoint recovery 지원 +- registry와 config는 versioned snapshot 유지 +- rollback 경로를 자동화와 문서 양쪽에 준비 diff --git a/dev/phase8_deliverables/research_automation.md b/dev/phase8_deliverables/research_automation.md new file mode 100644 index 0000000..c651ea6 --- /dev/null +++ b/dev/phase8_deliverables/research_automation.md @@ -0,0 +1,45 @@ +# Research Automation + +## 목적 +사람이 수동으로 하던 전략 복기, 실험 제안, 오류 분류를 점진적으로 자동화합니다. + +## 자동화 대상 +### 1. Post-Trade Review Assistant +입력: +- event record +- parser output +- feature snapshot +- order/fill timeline +- realized PnL +- MFE/MAE +- regime context + +출력: +- trade_summary +- primary success/failure reason +- candidate label corrections +- follow-up investigation tags + +### 2. Drift Diagnosis +입력: +- parser confidence 변화 +- feature distribution shift +- score bucket hit-rate 변화 +- sector/event mix 변화 + +출력: +- suspected drift source +- severity +- recommended action + +### 3. Experiment Recommender +출력: +- 우선 실험 후보 +- 예상 영향 범위 +- required data / code dependencies + +## 원칙 +- 자동 요약은 결론을 대신하지 않는다. +- 운영 판단은 사람이 최종 승인한다. +- 자동화 결과는 근거 로그를 포함해야 한다. +- production rule 변경은 review 없이 자동 적용 금지. diff --git a/dev/phase8_deliverables/short_engine_design.md b/dev/phase8_deliverables/short_engine_design.md new file mode 100644 index 0000000..97fb63b --- /dev/null +++ b/dev/phase8_deliverables/short_engine_design.md @@ -0,0 +1,42 @@ +# Short Engine Design + +## 목적 +core long continuation 엔진과 별개로, **하방 이벤트를 거래하는 숏 엔진**을 설계합니다. + +## 왜 Phase 8에서만 하는가 +- borrow / locate 제약 +- HTB(hard-to-borrow) 종목 처리 +- gap-up risk +- 이론상 손실 무한대 +- 브로커/계좌 정책 차이 + +## 거래 대상 이벤트 +### A급 숏 이벤트 +- 가이던스 대폭 하향 +- 다분기 성장률 둔화 명시 +- 대규모 희석성 자금조달 +- 규제/감사/회계 문제 +- 임상 실패 / 승인 거절 +- 계약 해지 / 대형 고객 이탈 + +### B급 숏 이벤트 +- 실적 miss + weak guidance +- margin compression + demand deterioration +- after-hours 급락 후 dead-cat bounce + +## 유니버스 제한 +- long보다 더 높은 유동성 기준 +- low-float/memestock 성격 종목 기본 제외 +- borrow availability 확인 가능한 종목만 허용 + +## 리스크 정책 +- long보다 낮은 포지션 사이징 +- gross / net / sector short exposure 별도 제한 +- HTB 비용 임계값 초과 시 자동 제외 +- 상승 갭 against position emergency exit 필요 + +## 실행 정책 +- v1 short engine은 limit / stop-limit 위주 +- market order 금지 +- locate 실패 시 자동 no-trade +- borrow cost가 높으면 자동 차단 diff --git a/dev/phase8_deliverables/strategy_registry.schema.json b/dev/phase8_deliverables/strategy_registry.schema.json new file mode 100644 index 0000000..7d3bd6a --- /dev/null +++ b/dev/phase8_deliverables/strategy_registry.schema.json @@ -0,0 +1,78 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "StrategyRegistryEntry", + "type": "object", + "additionalProperties": false, + "required": [ + "strategy_id", + "name", + "category", + "status", + "direction", + "feature_set_version", + "label_policy_version", + "risk_profile_id" + ], + "properties": { + "strategy_id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "category": { + "type": "string", + "enum": [ + "core_long", + "short_catalyst", + "sector_theme", + "cash_intraday", + "holding_period_variant", + "experimental" + ] + }, + "status": { + "type": "string", + "enum": [ + "draft", + "backtest", + "shadow", + "paper", + "live_limited", + "live_full", + "disabled" + ] + }, + "direction": { + "type": "string", + "enum": [ + "long", + "short", + "both" + ] + }, + "feature_set_version": { + "type": "string" + }, + "label_policy_version": { + "type": "string" + }, + "risk_profile_id": { + "type": "string" + }, + "owner": { + "type": "string" + }, + "notes": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } +} \ No newline at end of file diff --git a/dev/phase8_deliverables/testing_checklist.md b/dev/phase8_deliverables/testing_checklist.md new file mode 100644 index 0000000..ce75062 --- /dev/null +++ b/dev/phase8_deliverables/testing_checklist.md @@ -0,0 +1,49 @@ +# Phase 8 Testing Checklist + +## 1. 전략 확장 테스트 +- [ ] strategy registry에 새 전략 등록/비활성화/승격 가능 +- [ ] draft 상태 전략이 live로 잘못 라우팅되지 않음 +- [ ] 동일 종목 long/short 동시 포지션 금지 규칙 동작 +- [ ] strategy_id 별 리스크 버킷 분리 +- [ ] shadow / paper / live_limited / live_full 상태 전이 검증 + +## 2. 숏 엔진 테스트 +- [ ] borrow unavailable이면 자동 no-trade +- [ ] locate/borrow 오류가 주문 제출 전에 감지 +- [ ] HTB 비용 임계값 초과 시 진입 차단 +- [ ] short-specific kill switch 동작 +- [ ] 상승 갭 against position 시 emergency handling 동작 + +## 3. 모델 고도화 테스트 +- [ ] baseline model이 항상 fallback으로 남음 +- [ ] 새 모델 shadow inference가 production decision에 영향 없음 +- [ ] model disagreement 로그 저장 +- [ ] calibration 리포트 생성 +- [ ] feature set version 불일치 시 배포 차단 + +## 4. 실행 최적화 테스트 +- [ ] optimized execution off 시 baseline execution 복귀 +- [ ] partial fill / no fill / delayed fill 재현 +- [ ] implementation shortfall 계산 정확 +- [ ] replay 결과와 live metric 포맷 일치 +- [ ] broker adapter failure 시 no-trade 또는 baseline fallback + +## 5. 연구 자동화 테스트 +- [ ] post-trade review assistant가 입력 부족 시 안전 실패 +- [ ] drift diagnosis가 과도한 경보를 남발하지 않음 +- [ ] experiment recommender가 unsupported feature를 요구하지 않음 +- [ ] 자동 요약이 원본 로그 링크 포함 +- [ ] 자동화 결과가 rule 변경을 직접 수행하지 않음 + +## 6. 플랫폼/운영 테스트 +- [ ] registry snapshot 복구 가능 +- [ ] feature flag 롤백 가능 +- [ ] config diff review 없이 production rollout 차단 +- [ ] 권한 없는 사용자가 live config 변경 불가 +- [ ] incident record 감사 가능 + +## 7. 통합 테스트 +- [ ] 새 전략 추가 후 backtest → shadow → paper → live_limited 경로 동작 +- [ ] core strategy attribution과 새 전략 attribution 분리 +- [ ] degraded mode에서 core strategy만 남기고 나머지 off 가능 +- [ ] 운영 재시작 후 registry / flags / runtime state 복구 diff --git a/journal/LEADERBOARD.md b/journal/LEADERBOARD.md new file mode 100644 index 0000000..8b3a711 --- /dev/null +++ b/journal/LEADERBOARD.md @@ -0,0 +1,58 @@ +# Strategy Improvement Leaderboard +_Updated: 2026-03-17T04:06:21.261225+00:00_ + +| # | Experiment | SQS | PF | Ret% | WR | Sharpe | DD% | Trades | Date | +|---|-----------|-----|-----|------|-----|--------|-----|--------|------| +| 1 | pead_midcap_step14_score65 | 64.2 | 1.22 | +0.7 | 57% | 1.3 | 0.9 | 72 | 2026-03-17 | +| 2 | pead_midcap_step18_nofrac | 63.2 | 1.23 | +0.8 | 52% | 1.4 | 0.9 | 64 | 2026-03-17 | +| 3 | pead_midcap_step19_hold5 | 62.9 | 1.20 | +0.7 | 57% | 1.2 | 0.9 | 72 | 2026-03-17 | +| 4 | pead_midcap_step20_best3 | 62.0 | 1.22 | +0.7 | 52% | 1.3 | 0.9 | 64 | 2026-03-17 | +| 5 | pead_midcap_step17_target2 | 59.4 | 1.18 | +0.6 | 53% | 1.1 | 0.8 | 66 | 2026-03-17 | +| 6 | pead_midcap_step13_best | 57.7 | 1.12 | +0.4 | 55% | 0.8 | 0.9 | 75 | 2026-03-16 | +| 7 | pead_midcap_step16_react7_score65 | 53.2 | 0.97 | -0.1 | 56% | -0.2 | 1.6 | 89 | 2026-03-17 | +| 8 | pead_midcap_step5_maxcand3 | 52.5 | 0.95 | -0.2 | 54% | -0.4 | 1.4 | 96 | 2026-03-16 | +| 9 | pead_midcap_step15_react7 | 51.7 | 0.94 | -0.3 | 55% | -0.5 | 1.6 | 91 | 2026-03-17 | +| 10 | pead_midcap_step11_score60 | 50.2 | 1.02 | +0.1 | 52% | 0.2 | 0.9 | 77 | 2026-03-16 | +| 11 | pead_midcap_step12_vol2x | 50.2 | 1.02 | +0.1 | 52% | 0.1 | 0.9 | 77 | 2026-03-16 | +| 12 | pead_midcap_step3_10pct | 49.8 | 1.00 | +0.0 | 50% | 0.0 | 1.4 | 98 | 2026-03-16 | +| 13 | pead_midcap_step10_short | 49.2 | 1.00 | -0.0 | 52% | -0.0 | 0.9 | 79 | 2026-03-16 | +| 14 | pead_midcap_step2_notrail | 41.4 | 0.93 | -0.3 | 67% | -0.4 | 1.7 | 54 | 2026-03-16 | +| 15 | pead_midcap_step1_fixedr | 39.7 | 0.91 | -0.5 | 47% | -0.7 | 1.5 | 95 | 2026-03-16 | +| 16 | pead_midcap_combo_10pct_maxcand3 | 39.2 | 0.97 | -0.1 | 51% | -0.2 | 0.9 | 79 | 2026-03-16 | +| 17 | pead_midcap_step6_drift | 37.0 | 0.86 | -0.9 | 43% | -1.6 | 1.7 | 100 | 2026-03-16 | +| 18 | pead_midcap_step7_fixedr | 35.4 | 0.94 | -0.2 | 45% | -0.4 | 1.0 | 71 | 2026-03-16 | +| 19 | pead_midcap_step4_longonly | 32.6 | 0.84 | -0.8 | 45% | -1.2 | 1.4 | 78 | 2026-03-16 | +| 20 | pead_midcap_step8_nft | 31.3 | 0.65 | -1.8 | 41% | -3.0 | 2.2 | 71 | 2026-03-16 | +| 21 | pead_midcap_step9_stop2 | 31.1 | 0.77 | -1.7 | 45% | -1.8 | 2.5 | 71 | 2026-03-16 | + +## Recent Entries +### IMP-0021 (2026-03-17) — pead_midcap_step20_best3 +Hypothesis: Combine all marginally positive changes: score 0.65 + nofrac 1.0 + hold 5d +Verdict: **NEUTRAL** (SQS 62.0) +Reasoning: Test SQS 62.0 < 64.2. Return +0.72% similar to +0.73%. Combo did not beat step14 alone. Over-optimization reduces robustness. +Next: Step14 (score 0.65) remains the best. Simple is better. + +### IMP-0020 (2026-03-17) — pead_midcap_step19_hold5 +Hypothesis: Shorter 5d hold reduces exposure since avg hold is 3.28d. Less time for reversals. +Verdict: **NEUTRAL** (SQS 62.9) +Reasoning: Test SQS 62.9 vs 64.2. Return +0.67% vs +0.73%. Valid SQS 77.4 was strong. Similar trade count (72). Marginal effect. +Next: Hold5d marginal positive on valid but neutral on test + +### IMP-0019 (2026-03-17) — pead_midcap_step18_nofrac +Hypothesis: Full exit at target (fraction 1.0) prevents trailing stop from eating profits on partial positions +Verdict: **NEUTRAL** (SQS 63.2) +Reasoning: Test SQS 63.2 vs 64.2. Return +0.78% slightly better than +0.73%. Within noise. Fewer trades (64 vs 72). Marginal effect. +Next: Nofrac marginal positive on return but not SQS + +### IMP-0018 (2026-03-17) — pead_midcap_step17_target2 +Hypothesis: Wider target (ATR 2.0) improves R:R ratio from 3:1.5 to 3:2 with step14 stronger signals +Verdict: **WORSE** (SQS 59.4) +Reasoning: Test SQS 59.4 < 64.2. Return +0.59% < +0.73%. Wider target reduced trade count (66 vs 72) without improving win rate. R:R improvement not impactful. +Next: Target tuning not effective for PEAD + +### IMP-0017 (2026-03-17) — pead_midcap_step16_react7_score65 +Hypothesis: Combine wider funnel (react 0.07) with stricter quality gate (score 0.65) for best of both +Verdict: **WORSE** (SQS 53.2) +Reasoning: Test SQS 53.2 < 57.7. Return -0.13% negative. Valid SQS 92.7 was excellent but didn't transfer to OOS. Overfitting risk. +Next: Combination of wider funnel + higher score did not help OOS + diff --git a/journal/experiment_registry.json b/journal/experiment_registry.json new file mode 100644 index 0000000..66f366f --- /dev/null +++ b/journal/experiment_registry.json @@ -0,0 +1,257 @@ +{ + "entries": [ + { + "entry_id": "IMP-0015", + "experiment_name": "pead_midcap_step14_score65", + "sqs_score": 64.2, + "profit_factor": 1.2192504658724292, + "total_return_pct": 0.7284410148207681, + "win_rate": 0.5694444444444444, + "sharpe_ratio": 1.3410575857129192, + "max_drawdown_pct": 0.8797326864948225, + "trade_count": 72, + "timestamp": "2026-03-17T02:16:29.511903+00:00" + }, + { + "entry_id": "IMP-0019", + "experiment_name": "pead_midcap_step18_nofrac", + "sqs_score": 63.2, + "profit_factor": 1.233557927923508, + "total_return_pct": 0.7760032481101371, + "win_rate": 0.515625, + "sharpe_ratio": 1.4353513823455328, + "max_drawdown_pct": 0.8900540815144506, + "trade_count": 64, + "timestamp": "2026-03-17T02:17:01.474375+00:00" + }, + { + "entry_id": "IMP-0020", + "experiment_name": "pead_midcap_step19_hold5", + "sqs_score": 62.9, + "profit_factor": 1.2003700113675047, + "total_return_pct": 0.6659802746770583, + "win_rate": 0.5694444444444444, + "sharpe_ratio": 1.2282104833801621, + "max_drawdown_pct": 0.8797326864948225, + "trade_count": 72, + "timestamp": "2026-03-17T02:17:01.827556+00:00" + }, + { + "entry_id": "IMP-0021", + "experiment_name": "pead_midcap_step20_best3", + "sqs_score": 62.0, + "profit_factor": 1.2154843477872561, + "total_return_pct": 0.7161940318976412, + "win_rate": 0.515625, + "sharpe_ratio": 1.3247288791439433, + "max_drawdown_pct": 0.8900540815144506, + "trade_count": 64, + "timestamp": "2026-03-17T02:17:02.185037+00:00" + }, + { + "entry_id": "IMP-0018", + "experiment_name": "pead_midcap_step17_target2", + "sqs_score": 59.4, + "profit_factor": 1.1771413380479396, + "total_return_pct": 0.5891346601967962, + "win_rate": 0.5303030303030303, + "sharpe_ratio": 1.0752287681773998, + "max_drawdown_pct": 0.8393581435568818, + "trade_count": 66, + "timestamp": "2026-03-17T02:16:52.415056+00:00" + }, + { + "entry_id": "IMP-0014", + "experiment_name": "pead_midcap_step13_best", + "sqs_score": 57.7, + "profit_factor": 1.1182776518630022, + "total_return_pct": 0.4293895929202554, + "win_rate": 0.5466666666666666, + "sharpe_ratio": 0.7812081874914202, + "max_drawdown_pct": 0.8801478276052886, + "trade_count": 75, + "timestamp": "2026-03-16T23:05:05.412943+00:00" + }, + { + "entry_id": "IMP-0017", + "experiment_name": "pead_midcap_step16_react7_score65", + "sqs_score": 53.2, + "profit_factor": 0.9701741019713591, + "total_return_pct": -0.12615119218212204, + "win_rate": 0.5617977528089888, + "sharpe_ratio": -0.2273584908824299, + "max_drawdown_pct": 1.5543376309462142, + "trade_count": 89, + "timestamp": "2026-03-17T02:16:52.056303+00:00" + }, + { + "entry_id": "IMP-0005", + "experiment_name": "pead_midcap_step5_maxcand3", + "sqs_score": 52.5, + "profit_factor": 0.9504596545547419, + "total_return_pct": -0.2388243054896011, + "win_rate": 0.5416666666666666, + "sharpe_ratio": -0.4009079471879985, + "max_drawdown_pct": 1.4011627353834164, + "trade_count": 96, + "timestamp": "2026-03-16T22:52:30.561839+00:00" + }, + { + "entry_id": "IMP-0016", + "experiment_name": "pead_midcap_step15_react7", + "sqs_score": 51.7, + "profit_factor": 0.9429428813222424, + "total_return_pct": -0.2583590451609634, + "win_rate": 0.5494505494505495, + "sharpe_ratio": -0.4757579191663637, + "max_drawdown_pct": 1.5948632659956048, + "trade_count": 91, + "timestamp": "2026-03-17T02:16:34.131542+00:00" + }, + { + "entry_id": "IMP-0012", + "experiment_name": "pead_midcap_step11_score60", + "sqs_score": 50.2, + "profit_factor": 1.0190825170597053, + "total_return_pct": 0.07685407145853969, + "win_rate": 0.5194805194805194, + "sharpe_ratio": 0.15103876740694316, + "max_drawdown_pct": 0.8954589900133336, + "trade_count": 77, + "timestamp": "2026-03-16T23:04:14.974407+00:00" + }, + { + "entry_id": "IMP-0013", + "experiment_name": "pead_midcap_step12_vol2x", + "sqs_score": 50.2, + "profit_factor": 1.018057074188992, + "total_return_pct": 0.07292559720991995, + "win_rate": 0.5194805194805194, + "sharpe_ratio": 0.1450757451336352, + "max_drawdown_pct": 0.8904960176547445, + "trade_count": 77, + "timestamp": "2026-03-16T23:04:15.346697+00:00" + }, + { + "entry_id": "IMP-0003", + "experiment_name": "pead_midcap_step3_10pct", + "sqs_score": 49.8, + "profit_factor": 1.0020701077990406, + "total_return_pct": 0.013499456389559782, + "win_rate": 0.5, + "sharpe_ratio": 0.03939447191001189, + "max_drawdown_pct": 1.4225278540108528, + "trade_count": 98, + "timestamp": "2026-03-16T22:52:29.857239+00:00" + }, + { + "entry_id": "IMP-0011", + "experiment_name": "pead_midcap_step10_short", + "sqs_score": 49.2, + "profit_factor": 0.9958404155353467, + "total_return_pct": -0.013683964940166334, + "win_rate": 0.5189873417721519, + "sharpe_ratio": -0.007699537912677983, + "max_drawdown_pct": 0.9293000795486674, + "trade_count": 79, + "timestamp": "2026-03-16T23:04:14.616459+00:00" + }, + { + "entry_id": "IMP-0002", + "experiment_name": "pead_midcap_step2_notrail", + "sqs_score": 41.4, + "profit_factor": 0.9313134618740863, + "total_return_pct": -0.26847486723051406, + "win_rate": 0.6666666666666666, + "sharpe_ratio": -0.366331653948068, + "max_drawdown_pct": 1.7431414336132605, + "trade_count": 54, + "timestamp": "2026-03-16T22:52:29.505541+00:00" + }, + { + "entry_id": "IMP-0001", + "experiment_name": "pead_midcap_step1_fixedr", + "sqs_score": 39.7, + "profit_factor": 0.9135744830352539, + "total_return_pct": -0.4557621829436975, + "win_rate": 0.47368421052631576, + "sharpe_ratio": -0.690798512095932, + "max_drawdown_pct": 1.4922738831283402, + "trade_count": 95, + "timestamp": "2026-03-16T22:52:29.147972+00:00" + }, + { + "entry_id": "IMP-0007", + "experiment_name": "pead_midcap_combo_10pct_maxcand3", + "sqs_score": 39.2, + "profit_factor": 0.9701095917976659, + "total_return_pct": -0.11821922524747788, + "win_rate": 0.5063291139240507, + "sharpe_ratio": -0.18779926674176511, + "max_drawdown_pct": 0.9293000795486674, + "trade_count": 79, + "timestamp": "2026-03-16T22:52:31.270613+00:00" + }, + { + "entry_id": "IMP-0006", + "experiment_name": "pead_midcap_step6_drift", + "sqs_score": 37.0, + "profit_factor": 0.8557264506988863, + "total_return_pct": -0.8788682238009642, + "win_rate": 0.43, + "sharpe_ratio": -1.5533516509762217, + "max_drawdown_pct": 1.7395188967538284, + "trade_count": 100, + "timestamp": "2026-03-16T22:52:30.916045+00:00" + }, + { + "entry_id": "IMP-0008", + "experiment_name": "pead_midcap_step7_fixedr", + "sqs_score": 35.4, + "profit_factor": 0.9376665114180485, + "total_return_pct": -0.24986939032697408, + "win_rate": 0.4507042253521127, + "sharpe_ratio": -0.39554365980509315, + "max_drawdown_pct": 0.9924460035465441, + "trade_count": 71, + "timestamp": "2026-03-16T23:01:54.422047+00:00" + }, + { + "entry_id": "IMP-0004", + "experiment_name": "pead_midcap_step4_longonly", + "sqs_score": 32.6, + "profit_factor": 0.8355717037294544, + "total_return_pct": -0.8088267292581296, + "win_rate": 0.44871794871794873, + "sharpe_ratio": -1.1731301213701413, + "max_drawdown_pct": 1.4401741814514755, + "trade_count": 78, + "timestamp": "2026-03-16T22:52:30.203924+00:00" + }, + { + "entry_id": "IMP-0009", + "experiment_name": "pead_midcap_step8_nft", + "sqs_score": 31.3, + "profit_factor": 0.6522816324098041, + "total_return_pct": -1.8153531028857979, + "win_rate": 0.4084507042253521, + "sharpe_ratio": -2.981974625656072, + "max_drawdown_pct": 2.1930148870201824, + "trade_count": 71, + "timestamp": "2026-03-16T23:01:54.804591+00:00" + }, + { + "entry_id": "IMP-0010", + "experiment_name": "pead_midcap_step9_stop2", + "sqs_score": 31.1, + "profit_factor": 0.7658210615369154, + "total_return_pct": -1.7396298311107383, + "win_rate": 0.4507042253521127, + "sharpe_ratio": -1.8398745071636018, + "max_drawdown_pct": 2.4893517163865826, + "trade_count": 71, + "timestamp": "2026-03-16T23:01:55.163461+00:00" + } + ], + "updated_at": "2026-03-17T04:06:21.261225+00:00" +} \ No newline at end of file diff --git a/journal/improvement_journal.jsonl b/journal/improvement_journal.jsonl new file mode 100644 index 0000000..dedd9be --- /dev/null +++ b/journal/improvement_journal.jsonl @@ -0,0 +1,21 @@ +{"entry_id":"IMP-0001","timestamp":"2026-03-16T22:52:29.147972+00:00","experiment_name":"pead_midcap_step1_fixedr","hypothesis":"Step 1: fixed_r target 2.0 R:R","config_delta":{"base_experiment":"pead_midcap_step1_fixedr","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214","trade_count":528,"profit_factor":0.9200217466205215,"total_return_pct":-3.124588680970817,"win_rate":0.4090909090909091,"max_drawdown_pct":7.849601936494004,"sharpe_ratio":-0.33161166573941914,"monthly_win_rate":0.38461538461538464,"equity_curve_r_squared":0.049564252587599174},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214","trade_count":69,"profit_factor":1.7731598619764342,"total_return_pct":2.779776127733232,"win_rate":0.4927536231884058,"max_drawdown_pct":1.5546437384924012,"sharpe_ratio":3.3855945499328657,"monthly_win_rate":0.5,"equity_curve_r_squared":0.46467275010982595},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214","trade_count":95,"profit_factor":0.9135744830352539,"total_return_pct":-0.4557621829436975,"win_rate":0.47368421052631576,"max_drawdown_pct":1.4922738831283402,"sharpe_ratio":-0.690798512095932,"monthly_win_rate":0.3333333333333333,"equity_curve_r_squared":0.53626294582183}},"sqs_score":39.7,"sqs_breakdown":{"profitability":23.9,"risk":52.4,"consistency":24.8,"robustness":80.7},"verdict":"neutral","verdict_reasoning":"Baseline for midcap step series","next_direction":"Step 2: disable trailing stop","tags":["pead","midcap","step1","fixedr"]} +{"entry_id":"IMP-0002","timestamp":"2026-03-16T22:52:29.505541+00:00","experiment_name":"pead_midcap_step2_notrail","hypothesis":"Step 2: Disable trailing stop for midcap volatility","config_delta":{"base_experiment":"pead_midcap_step1_fixedr","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963","trade_count":456,"profit_factor":0.8306562780589708,"total_return_pct":-7.062952270161506,"win_rate":0.6578947368421053,"max_drawdown_pct":11.950419851957642,"sharpe_ratio":-0.696356079596736,"monthly_win_rate":0.525,"equity_curve_r_squared":0.07229984947776134},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963","trade_count":62,"profit_factor":1.073493344735863,"total_return_pct":0.3745287237202429,"win_rate":0.7258064516129032,"max_drawdown_pct":1.7163906170323242,"sharpe_ratio":0.39419762465723635,"monthly_win_rate":0.25,"equity_curve_r_squared":0.32372614376351805},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963","trade_count":54,"profit_factor":0.9313134618740863,"total_return_pct":-0.26847486723051406,"win_rate":0.6666666666666666,"max_drawdown_pct":1.7431414336132605,"sharpe_ratio":-0.366331653948068,"monthly_win_rate":0.3333333333333333,"equity_curve_r_squared":0.27335022008413906}},"sqs_score":41.4,"sqs_breakdown":{"profitability":25.5,"risk":56.4,"consistency":54.2,"robustness":41.5},"verdict":"worse","verdict_reasoning":"Train PF dropped to 0.73, worst overall","next_direction":"Step 3: raise reaction threshold","tags":["pead","midcap","step2","notrail"]} +{"entry_id":"IMP-0003","timestamp":"2026-03-16T22:52:29.857239+00:00","experiment_name":"pead_midcap_step3_10pct","hypothesis":"Step 3: Raise reaction threshold 7%->10% for stronger signals","config_delta":{"base_experiment":"pead_midcap_step1_fixedr","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3","trade_count":554,"profit_factor":0.8983598002644894,"total_return_pct":-3.280603570163337,"win_rate":0.5,"max_drawdown_pct":6.615859124596835,"sharpe_ratio":-0.410178158923494,"monthly_win_rate":0.42857142857142855,"equity_curve_r_squared":0.3246242852362157},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3","trade_count":78,"profit_factor":1.664496799271172,"total_return_pct":1.9591117241190805,"win_rate":0.6025641025641025,"max_drawdown_pct":0.6538050285655718,"sharpe_ratio":2.907578829549799,"monthly_win_rate":0.75,"equity_curve_r_squared":0.6968678861478663},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3","trade_count":98,"profit_factor":1.0020701077990406,"total_return_pct":0.013499456389559782,"win_rate":0.5,"max_drawdown_pct":1.4225278540108528,"sharpe_ratio":0.03939447191001189,"monthly_win_rate":0.6666666666666666,"equity_curve_r_squared":0.0029903324045531966}},"sqs_score":49.8,"sqs_breakdown":{"profitability":30.2,"risk":65.0,"consistency":70.8,"robustness":49.1},"verdict":"better","verdict_reasoning":"Only experiment reaching test breakeven (PF=1.00, Ret=+0.01%)","next_direction":"Step 4: try long-only","tags":["pead","midcap","step3","10pct"]} +{"entry_id":"IMP-0004","timestamp":"2026-03-16T22:52:30.203924+00:00","experiment_name":"pead_midcap_step4_longonly","hypothesis":"Step 4: Long-only, disable short side","config_delta":{"base_experiment":"pead_midcap_step1_fixedr","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0","trade_count":486,"profit_factor":0.924546532180125,"total_return_pct":-2.2530301807158395,"win_rate":0.5432098765432098,"max_drawdown_pct":6.787125867039724,"sharpe_ratio":-0.20236278759112156,"monthly_win_rate":0.46153846153846156,"equity_curve_r_squared":0.1343287887075526},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0","trade_count":76,"profit_factor":1.430929775113959,"total_return_pct":1.4935870708133152,"win_rate":0.6052631578947368,"max_drawdown_pct":1.597646524797458,"sharpe_ratio":1.6551004874178592,"monthly_win_rate":0.5,"equity_curve_r_squared":0.6129327020059054},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0","trade_count":78,"profit_factor":0.8355717037294544,"total_return_pct":-0.8088267292581296,"win_rate":0.44871794871794873,"max_drawdown_pct":1.4401741814514755,"sharpe_ratio":-1.1731301213701413,"monthly_win_rate":0.3333333333333333,"equity_curve_r_squared":0.3740006709035379}},"sqs_score":32.6,"sqs_breakdown":{"profitability":18.5,"risk":47.6,"consistency":20.6,"robustness":61.2},"verdict":"worse","verdict_reasoning":"Test PF 0.94, return -0.81%. Long-only worse than long+short","next_direction":"Step 5: limit candidates per day","tags":["pead","midcap","step4","longonly"]} +{"entry_id":"IMP-0005","timestamp":"2026-03-16T22:52:30.561839+00:00","experiment_name":"pead_midcap_step5_maxcand3","hypothesis":"Step 5: Max 3 candidates per day, top signals only","config_delta":{"base_experiment":"pead_midcap_step1_fixedr","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf","trade_count":667,"profit_factor":0.8936008117413707,"total_return_pct":-4.084212058311663,"win_rate":0.512743628185907,"max_drawdown_pct":6.529337814573298,"sharpe_ratio":-0.46728383699893133,"monthly_win_rate":0.358974358974359,"equity_curve_r_squared":0.5365488680633558},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf","trade_count":89,"profit_factor":2.081614051785466,"total_return_pct":3.3263332066270377,"win_rate":0.651685393258427,"max_drawdown_pct":1.1046989753464673,"sharpe_ratio":3.9774346553681674,"monthly_win_rate":0.75,"equity_curve_r_squared":0.5734715756129087},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf","trade_count":96,"profit_factor":0.9504596545547419,"total_return_pct":-0.2388243054896011,"win_rate":0.5416666666666666,"max_drawdown_pct":1.4011627353834164,"sharpe_ratio":-0.4009079471879985,"monthly_win_rate":0.6666666666666666,"equity_curve_r_squared":0.5009577858012723}},"sqs_score":52.5,"sqs_breakdown":{"profitability":26.6,"risk":57.8,"consistency":77.8,"robustness":79.1},"verdict":"neutral","verdict_reasoning":"Best valid (+3.33%) but test still negative (-0.24%)","next_direction":"Step 6: extended hold","tags":["pead","midcap","step5","maxcand3"]} +{"entry_id":"IMP-0006","timestamp":"2026-03-16T22:52:30.916045+00:00","experiment_name":"pead_midcap_step6_drift","hypothesis":"Step 6: Extended hold 25 days for PEAD drift","config_delta":{"base_experiment":"pead_midcap_step1_fixedr","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568","trade_count":586,"profit_factor":0.9126734877022061,"total_return_pct":-3.711358688232649,"win_rate":0.4129692832764505,"max_drawdown_pct":6.665875438412343,"sharpe_ratio":-0.39694526230308885,"monthly_win_rate":0.38461538461538464,"equity_curve_r_squared":0.33883652745194087},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568","trade_count":75,"profit_factor":1.704720436988778,"total_return_pct":2.6652714937057898,"win_rate":0.5066666666666667,"max_drawdown_pct":1.0499246227859185,"sharpe_ratio":3.7096311156929622,"monthly_win_rate":0.5,"equity_curve_r_squared":0.5754494959825112},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568","trade_count":100,"profit_factor":0.8557264506988863,"total_return_pct":-0.8788682238009642,"win_rate":0.43,"max_drawdown_pct":1.7395188967538284,"sharpe_ratio":-1.5533516509762217,"monthly_win_rate":0.3333333333333333,"equity_curve_r_squared":0.7311683777861855}},"sqs_score":37.0,"sqs_breakdown":{"profitability":19.3,"risk":45.9,"consistency":17.5,"robustness":95.7},"verdict":"worse","verdict_reasoning":"Worst test return (-0.88%), longer hold hurts","next_direction":"Combo: step3 + step5","tags":["pead","midcap","step6","drift"]} +{"entry_id":"IMP-0007","timestamp":"2026-03-16T22:52:31.270613+00:00","experiment_name":"pead_midcap_combo_10pct_maxcand3","hypothesis":"Combo: 10% threshold + max 3 candidates","config_delta":{"base_experiment":"pead_midcap_step1_fixedr","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090","trade_count":533,"profit_factor":0.9034427861136265,"total_return_pct":-2.91390932899194,"win_rate":0.49906191369606,"max_drawdown_pct":5.821672597926446,"sharpe_ratio":-0.3760744161321293,"monthly_win_rate":0.45714285714285713,"equity_curve_r_squared":0.3049801722988121},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090","trade_count":72,"profit_factor":1.9089335238197231,"total_return_pct":2.2525004674136144,"win_rate":0.625,"max_drawdown_pct":0.6530874475348815,"sharpe_ratio":2.9527896546854935,"monthly_win_rate":0.75,"equity_curve_r_squared":0.6965943198525836},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090","trade_count":79,"profit_factor":0.9701095917976659,"total_return_pct":-0.11821922524747788,"win_rate":0.5063291139240507,"max_drawdown_pct":0.9293000795486674,"sharpe_ratio":-0.18779926674176511,"monthly_win_rate":0.3333333333333333,"equity_curve_r_squared":0.03631581199315345}},"sqs_score":39.2,"sqs_breakdown":{"profitability":28.0,"risk":63.5,"consistency":30.2,"robustness":40.6},"verdict":"better","verdict_reasoning":"Smallest test loss (-0.12%), lowest DD (0.93%). Best combined filter","next_direction":"Next: explore stop/target tuning or new scoring features","tags":["pead","midcap","combo","10pct","maxcand3"]} +{"entry_id":"IMP-0008","timestamp":"2026-03-16T23:01:54.422047+00:00","experiment_name":"pead_midcap_step7_fixedr","hypothesis":"Combo base (10% + maxcand3) + fixed_r target 2.0. Fix inverted R:R","config_delta":{"base_experiment":"pead_midcap_combo_10pct_maxcand3","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693","trade_count":425,"profit_factor":0.9087588895218256,"total_return_pct":-2.684538647143214,"win_rate":0.4023529411764706,"max_drawdown_pct":5.9541536964340045,"sharpe_ratio":-0.31665196683581115,"monthly_win_rate":0.45714285714285713,"equity_curve_r_squared":0.0655821811757635},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693","trade_count":53,"profit_factor":2.0006534393926754,"total_return_pct":2.3868649231372836,"win_rate":0.5283018867924528,"max_drawdown_pct":0.6860864501039302,"sharpe_ratio":3.087810484110416,"monthly_win_rate":0.75,"equity_curve_r_squared":0.6619146653298694},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693","trade_count":71,"profit_factor":0.9376665114180485,"total_return_pct":-0.24986939032697408,"win_rate":0.4507042253521127,"max_drawdown_pct":0.9924460035465441,"sharpe_ratio":-0.39554365980509315,"monthly_win_rate":0.0,"equity_curve_r_squared":0.16973213763570125}},"sqs_score":35.4,"sqs_breakdown":{"profitability":25.9,"risk":60.1,"consistency":16.8,"robustness":44.5},"verdict":"neutral","verdict_reasoning":"Test PF improved but return still negative (-0.25%). Stop exit rate should drop with proper R:R","next_direction":"Step 8: add NFT exit; Step 9: tighter stop","tags":["pead","midcap","step7","fixedr"]} +{"entry_id":"IMP-0009","timestamp":"2026-03-16T23:01:54.804591+00:00","experiment_name":"pead_midcap_step8_nft","hypothesis":"Step 7 + no_follow_through_exit. Cut dead trades at D+1 close","config_delta":{"base_experiment":"pead_midcap_step7_fixedr","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d","trade_count":454,"profit_factor":0.8441650849745413,"total_return_pct":-5.223338416972663,"win_rate":0.3766519823788546,"max_drawdown_pct":7.842211530994003,"sharpe_ratio":-0.6465192822980859,"monthly_win_rate":0.4,"equity_curve_r_squared":0.36446813528716165},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d","trade_count":57,"profit_factor":1.5546541014245128,"total_return_pct":1.8349072453313857,"win_rate":0.45614035087719296,"max_drawdown_pct":0.9575880067210154,"sharpe_ratio":2.3501871656264828,"monthly_win_rate":0.5,"equity_curve_r_squared":0.4737973824333247},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d","trade_count":71,"profit_factor":0.6522816324098041,"total_return_pct":-1.8153531028857979,"win_rate":0.4084507042253521,"max_drawdown_pct":2.1930148870201824,"sharpe_ratio":-2.981974625656072,"monthly_win_rate":0.3333333333333333,"equity_curve_r_squared":0.8832469667893551}},"sqs_score":31.3,"sqs_breakdown":{"profitability":12.7,"risk":43.4,"consistency":13.9,"robustness":83.9},"verdict":"worse","verdict_reasoning":"NFT exit hurts: test return -1.82% vs step7 -0.25%. Cutting too many trades that would have recovered","next_direction":"NFT too aggressive for PEAD drift strategy. Try tighter stop instead","tags":["pead","midcap","step8","nft"]} +{"entry_id":"IMP-0010","timestamp":"2026-03-16T23:01:55.163461+00:00","experiment_name":"pead_midcap_step9_stop2","hypothesis":"Step 7 + tighter stop 2.0 ATR. Reduce avg loss per trade","config_delta":{"base_experiment":"pead_midcap_step7_fixedr","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3","trade_count":432,"profit_factor":0.8175378156440357,"total_return_pct":-8.776000630653071,"win_rate":0.4074074074074074,"max_drawdown_pct":12.886676673013914,"sharpe_ratio":-0.7360418140485263,"monthly_win_rate":0.42857142857142855,"equity_curve_r_squared":0.4421218813805471},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3","trade_count":53,"profit_factor":1.8143851030277298,"total_return_pct":3.223451666916022,"win_rate":0.5283018867924528,"max_drawdown_pct":1.2331625402933826,"sharpe_ratio":2.813496185483277,"monthly_win_rate":0.75,"equity_curve_r_squared":0.5891081686619382},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3","trade_count":71,"profit_factor":0.7658210615369154,"total_return_pct":-1.7396298311107383,"win_rate":0.4507042253521127,"max_drawdown_pct":2.4893517163865826,"sharpe_ratio":-1.8398745071636018,"monthly_win_rate":0.0,"equity_curve_r_squared":0.7504756156161969}},"sqs_score":31.1,"sqs_breakdown":{"profitability":13.0,"risk":41.7,"consistency":16.8,"robustness":80.8},"verdict":"worse","verdict_reasoning":"Tighter stop hurts: test return -1.74% vs step7 -0.25%. More stops hit in volatile midcaps","next_direction":"Stop tuning counterproductive. Focus on signal quality or scoring improvements","tags":["pead","midcap","step9","stop2"]} +{"entry_id":"IMP-0011","timestamp":"2026-03-16T23:04:14.616459+00:00","experiment_name":"pead_midcap_step10_short","hypothesis":"Combo base + shorter hold (7 days). Reduce unnecessary exposure","config_delta":{"base_experiment":"pead_midcap_combo_10pct_maxcand3","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872","trade_count":535,"profit_factor":0.9383880570062944,"total_return_pct":-1.8280832909611637,"win_rate":0.508411214953271,"max_drawdown_pct":5.0343002325613835,"sharpe_ratio":-0.23100167769304789,"monthly_win_rate":0.4857142857142857,"equity_curve_r_squared":0.12398583357859513},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872","trade_count":70,"profit_factor":1.6081573944406065,"total_return_pct":1.5626791854819602,"win_rate":0.5857142857142857,"max_drawdown_pct":0.6539417061012387,"sharpe_ratio":2.2442047142281463,"monthly_win_rate":0.75,"equity_curve_r_squared":0.6019214395233538},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872","trade_count":79,"profit_factor":0.9958404155353467,"total_return_pct":-0.013683964940166334,"win_rate":0.5189873417721519,"max_drawdown_pct":0.9293000795486674,"sharpe_ratio":-0.007699537912677983,"monthly_win_rate":0.6666666666666666,"equity_curve_r_squared":0.01071589487383525}},"sqs_score":49.2,"sqs_breakdown":{"profitability":29.7,"risk":66.5,"consistency":74.0,"robustness":39.0},"verdict":"better","verdict_reasoning":"Test near breakeven (-0.01%), SQS 49.2 (up from 39.2). Consistency 74.0 dramatically improved","next_direction":"Try combo with score threshold and short hold","tags":["pead","midcap","step10","short"]} +{"entry_id":"IMP-0012","timestamp":"2026-03-16T23:04:14.974407+00:00","experiment_name":"pead_midcap_step11_score60","hypothesis":"Combo base + higher score threshold 0.50->0.60. Filter weak signals","config_delta":{"base_experiment":"pead_midcap_combo_10pct_maxcand3","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036","trade_count":529,"profit_factor":0.9051644971123368,"total_return_pct":-2.850449875516715,"win_rate":0.499054820415879,"max_drawdown_pct":5.806253410597396,"sharpe_ratio":-0.3721590200001434,"monthly_win_rate":0.45714285714285713,"equity_curve_r_squared":0.28879703955671965},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036","trade_count":72,"profit_factor":1.9089335238197231,"total_return_pct":2.2525004674136144,"win_rate":0.625,"max_drawdown_pct":0.6530874475348815,"sharpe_ratio":2.9527896546854935,"monthly_win_rate":0.75,"equity_curve_r_squared":0.6965943198525836},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036","trade_count":77,"profit_factor":1.0190825170597053,"total_return_pct":0.07685407145853969,"win_rate":0.5194805194805194,"max_drawdown_pct":0.8954589900133336,"sharpe_ratio":0.15103876740694316,"monthly_win_rate":0.6666666666666666,"equity_curve_r_squared":0.001051223399886146}},"sqs_score":50.2,"sqs_breakdown":{"profitability":31.3,"risk":69.2,"consistency":74.1,"robustness":37.3},"verdict":"better","verdict_reasoning":"Test turns positive! +0.08%, PF>1.0, SQS 50.2. Score filter is effective","next_direction":"Combine score 0.60 + short hold for best result","tags":["pead","midcap","step11","score60"]} +{"entry_id":"IMP-0013","timestamp":"2026-03-16T23:04:15.346697+00:00","experiment_name":"pead_midcap_step12_vol2x","hypothesis":"Combo base + volume threshold 2.0x (from 1.5x). Stronger conviction","config_delta":{"base_experiment":"pead_midcap_combo_10pct_maxcand3","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000","trade_count":525,"profit_factor":0.8908517859321168,"total_return_pct":-3.263530815059654,"win_rate":0.49714285714285716,"max_drawdown_pct":6.158693883828255,"sharpe_ratio":-0.4271081260405149,"monthly_win_rate":0.45714285714285713,"equity_curve_r_squared":0.3844589392112171},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000","trade_count":72,"profit_factor":1.9089335238197231,"total_return_pct":2.2525004674136144,"win_rate":0.625,"max_drawdown_pct":0.6530874475348815,"sharpe_ratio":2.9527896546854935,"monthly_win_rate":0.75,"equity_curve_r_squared":0.6965943198525836},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000","trade_count":77,"profit_factor":1.018057074188992,"total_return_pct":0.07292559720991995,"win_rate":0.5194805194805194,"max_drawdown_pct":0.8904960176547445,"sharpe_ratio":0.1450757451336352,"monthly_win_rate":0.6666666666666666,"equity_curve_r_squared":0.006128571981766798}},"sqs_score":50.2,"sqs_breakdown":{"profitability":31.2,"risk":69.1,"consistency":74.1,"robustness":37.6},"verdict":"better","verdict_reasoning":"Test +0.07%, similar to score60. Volume 2x also effective quality filter. Nearly identical result to step11","next_direction":"Combine score 0.60 + short hold + vol 2x","tags":["pead","midcap","step12","vol2x"]} +{"entry_id":"IMP-0014","timestamp":"2026-03-16T23:05:05.412943+00:00","experiment_name":"pead_midcap_step13_best","hypothesis":"Best combo: 10% reaction + maxcand3 + score 0.60 + 7-day hold + vol 2.0x","config_delta":{"base_experiment":"pead_midcap_combo_10pct_maxcand3","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807","trade_count":523,"profit_factor":0.9277895576563163,"total_return_pct":-2.1147577797892154,"win_rate":0.5066921606118547,"max_drawdown_pct":5.363056148167064,"sharpe_ratio":-0.27517043819279563,"monthly_win_rate":0.4857142857142857,"equity_curve_r_squared":0.19731788832163766},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807","trade_count":70,"profit_factor":1.6081573944406065,"total_return_pct":1.5626791854819602,"win_rate":0.5857142857142857,"max_drawdown_pct":0.6539417061012387,"sharpe_ratio":2.2442047142281463,"monthly_win_rate":0.75,"equity_curve_r_squared":0.6019214395233538},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807","trade_count":75,"profit_factor":1.1182776518630022,"total_return_pct":0.4293895929202554,"win_rate":0.5466666666666666,"max_drawdown_pct":0.8801478276052886,"sharpe_ratio":0.7812081874914202,"monthly_win_rate":1.0,"equity_curve_r_squared":0.08086932876015243}},"sqs_score":57.7,"sqs_breakdown":{"profitability":37.6,"risk":79.7,"consistency":82.8,"robustness":41.2},"verdict":"better","verdict_reasoning":"NEW BEST! Test +0.43%, SQS 57.7. First clearly positive OOS result. Consistency 82.8 (best ever)","next_direction":"Explore fine-tuning: score 0.65, or combine with original step3 7pct threshold for more trades","tags":["pead","midcap","step13","best"]} +{"entry_id":"IMP-0015","timestamp":"2026-03-17T02:16:29.511903+00:00","experiment_name":"pead_midcap_step14_score65","hypothesis":"Higher score threshold 0.65 filters for stronger PEAD signals, improving WR and PF","config_delta":{"base_experiment":"pead_midcap_step13_best","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d","trade_count":513,"profit_factor":0.9330034935848248,"total_return_pct":-1.9162420951407404,"win_rate":0.5087719298245614,"max_drawdown_pct":4.806826210534917,"sharpe_ratio":-0.2519747350079355,"monthly_win_rate":0.45714285714285713,"equity_curve_r_squared":0.1816099118661623},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d","trade_count":66,"profit_factor":1.3965047202887975,"total_return_pct":1.0156494476850202,"win_rate":0.5606060606060606,"max_drawdown_pct":0.6560660596828557,"sharpe_ratio":1.5832382440205535,"monthly_win_rate":0.75,"equity_curve_r_squared":0.5472739451051728},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d","trade_count":72,"profit_factor":1.2192504658724292,"total_return_pct":0.7284410148207681,"win_rate":0.5694444444444444,"max_drawdown_pct":0.8797326864948225,"sharpe_ratio":1.3410575857129192,"monthly_win_rate":1.0,"equity_curve_r_squared":0.20418254692529553}},"sqs_score":64.2,"sqs_breakdown":{"profitability":43.9,"risk":89.0,"consistency":86.6,"robustness":47.2},"verdict":"better","verdict_reasoning":"Test SQS 64.2 > 57.7 (step13). Return +0.73% > +0.43%. Score filter continues to be the most effective lever. Trade count 72 still sufficient.","next_direction":"Use as new base for Phase B exit optimization","tags":["pead","midcap","step14","score65"]} +{"entry_id":"IMP-0016","timestamp":"2026-03-17T02:16:34.131542+00:00","experiment_name":"pead_midcap_step15_react7","hypothesis":"Lower reaction threshold 0.07 widens funnel for more trades and statistical significance","config_delta":{"base_experiment":"pead_midcap_step13_best","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec","trade_count":628,"profit_factor":0.8880804531017018,"total_return_pct":-4.025803281272252,"win_rate":0.5127388535031847,"max_drawdown_pct":5.920220358574264,"sharpe_ratio":-0.4789738641324651,"monthly_win_rate":0.4473684210526316,"equity_curve_r_squared":0.5590772121494917},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec","trade_count":84,"profit_factor":1.8949702222034897,"total_return_pct":2.6109835614154147,"win_rate":0.6190476190476191,"max_drawdown_pct":0.9498747174984115,"sharpe_ratio":3.486940708619626,"monthly_win_rate":0.75,"equity_curve_r_squared":0.5520524518827523},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec","trade_count":91,"profit_factor":0.9429428813222424,"total_return_pct":-0.2583590451609634,"win_rate":0.5494505494505495,"max_drawdown_pct":1.5948632659956048,"sharpe_ratio":-0.4757579191663637,"monthly_win_rate":0.6666666666666666,"equity_curve_r_squared":0.5116157273037042}},"sqs_score":51.7,"sqs_breakdown":{"profitability":26.1,"risk":55.4,"consistency":79.1,"robustness":77.0},"verdict":"worse","verdict_reasoning":"Test SQS 51.7 < 57.7. Return -0.26% negative. More trades (91) but quality dropped. Wider funnel added noise.","next_direction":"Lowering reaction threshold not effective without compensating quality filter","tags":["pead","midcap","step15","react7"]} +{"entry_id":"IMP-0017","timestamp":"2026-03-17T02:16:52.056303+00:00","experiment_name":"pead_midcap_step16_react7_score65","hypothesis":"Combine wider funnel (react 0.07) with stricter quality gate (score 0.65) for best of both","config_delta":{"base_experiment":"pead_midcap_step13_best","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22","trade_count":613,"profit_factor":0.8978576295267423,"total_return_pct":-3.5846850918965063,"win_rate":0.5138662316476346,"max_drawdown_pct":5.32752847925319,"sharpe_ratio":-0.429214740253919,"monthly_win_rate":0.4473684210526316,"equity_curve_r_squared":0.6021795418809431},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22","trade_count":83,"profit_factor":2.0144135190573675,"total_return_pct":2.7839796082119577,"win_rate":0.6265060240963856,"max_drawdown_pct":0.7815696983079155,"sharpe_ratio":3.7031973701157277,"monthly_win_rate":0.75,"equity_curve_r_squared":0.6307575259856522},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22","trade_count":89,"profit_factor":0.9701741019713591,"total_return_pct":-0.12615119218212204,"win_rate":0.5617977528089888,"max_drawdown_pct":1.5543376309462142,"sharpe_ratio":-0.2273584908824299,"monthly_win_rate":0.6666666666666666,"equity_curve_r_squared":0.4506121283479019}},"sqs_score":53.2,"sqs_breakdown":{"profitability":28.0,"risk":59.8,"consistency":81.1,"robustness":72.1},"verdict":"worse","verdict_reasoning":"Test SQS 53.2 < 57.7. Return -0.13% negative. Valid SQS 92.7 was excellent but didn't transfer to OOS. Overfitting risk.","next_direction":"Combination of wider funnel + higher score did not help OOS","tags":["pead","midcap","step16","react7","score65"]} +{"entry_id":"IMP-0018","timestamp":"2026-03-17T02:16:52.415056+00:00","experiment_name":"pead_midcap_step17_target2","hypothesis":"Wider target (ATR 2.0) improves R:R ratio from 3:1.5 to 3:2 with step14 stronger signals","config_delta":{"base_experiment":"pead_midcap_step14_score65","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e","trade_count":470,"profit_factor":0.9520470746330688,"total_return_pct":-1.3594521214714332,"win_rate":0.4702127659574468,"max_drawdown_pct":4.642828380427953,"sharpe_ratio":-0.17199413327760185,"monthly_win_rate":0.4857142857142857,"equity_curve_r_squared":0.0555160147865378},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e","trade_count":59,"profit_factor":1.3782504917267295,"total_return_pct":0.9689947279659247,"win_rate":0.5084745762711864,"max_drawdown_pct":0.7074931393576637,"sharpe_ratio":1.4547676904551556,"monthly_win_rate":0.75,"equity_curve_r_squared":0.5050319501387333},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e","trade_count":66,"profit_factor":1.1771413380479396,"total_return_pct":0.5891346601967962,"win_rate":0.5303030303030303,"max_drawdown_pct":0.8393581435568818,"sharpe_ratio":1.0752287681773998,"monthly_win_rate":1.0,"equity_curve_r_squared":0.11485286243962597}},"sqs_score":59.4,"sqs_breakdown":{"profitability":41.2,"risk":84.6,"consistency":80.1,"robustness":38.3},"verdict":"worse","verdict_reasoning":"Test SQS 59.4 < 64.2. Return +0.59% < +0.73%. Wider target reduced trade count (66 vs 72) without improving win rate. R:R improvement not impactful.","next_direction":"Target tuning not effective for PEAD","tags":["pead","midcap","step17","target2"]} +{"entry_id":"IMP-0019","timestamp":"2026-03-17T02:17:01.474375+00:00","experiment_name":"pead_midcap_step18_nofrac","hypothesis":"Full exit at target (fraction 1.0) prevents trailing stop from eating profits on partial positions","config_delta":{"base_experiment":"pead_midcap_step14_score65","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde","trade_count":437,"profit_factor":0.9549362257822231,"total_return_pct":-1.303548314107451,"win_rate":0.42105263157894735,"max_drawdown_pct":4.52621951607018,"sharpe_ratio":-0.16411744461886982,"monthly_win_rate":0.5142857142857142,"equity_curve_r_squared":0.048794422109098394},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde","trade_count":55,"profit_factor":1.4603949049610698,"total_return_pct":1.180333505141476,"win_rate":0.4727272727272727,"max_drawdown_pct":0.624348249096465,"sharpe_ratio":1.8572252886649268,"monthly_win_rate":0.75,"equity_curve_r_squared":0.602794881326535},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde","trade_count":64,"profit_factor":1.233557927923508,"total_return_pct":0.7760032481101371,"win_rate":0.515625,"max_drawdown_pct":0.8900540815144506,"sharpe_ratio":1.4353513823455328,"monthly_win_rate":1.0,"equity_curve_r_squared":0.2803203771052179}},"sqs_score":63.2,"sqs_breakdown":{"profitability":44.8,"risk":90.6,"consistency":77.6,"robustness":47.5},"verdict":"neutral","verdict_reasoning":"Test SQS 63.2 vs 64.2. Return +0.78% slightly better than +0.73%. Within noise. Fewer trades (64 vs 72). Marginal effect.","next_direction":"Nofrac marginal positive on return but not SQS","tags":["pead","midcap","step18","nofrac"]} +{"entry_id":"IMP-0020","timestamp":"2026-03-17T02:17:01.827556+00:00","experiment_name":"pead_midcap_step19_hold5","hypothesis":"Shorter 5d hold reduces exposure since avg hold is 3.28d. Less time for reversals.","config_delta":{"base_experiment":"pead_midcap_step14_score65","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2","trade_count":513,"profit_factor":0.9321637680892956,"total_return_pct":-1.9498490194724147,"win_rate":0.49902534113060426,"max_drawdown_pct":5.109402635025258,"sharpe_ratio":-0.2558888083394153,"monthly_win_rate":0.4857142857142857,"equity_curve_r_squared":0.2741765876459641},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2","trade_count":68,"profit_factor":1.5486357888297084,"total_return_pct":1.4116877933366923,"win_rate":0.5735294117647058,"max_drawdown_pct":0.6559648601902311,"sharpe_ratio":2.211147627502439,"monthly_win_rate":0.6666666666666666,"equity_curve_r_squared":0.6062916819268495},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2","trade_count":72,"profit_factor":1.2003700113675047,"total_return_pct":0.6659802746770583,"win_rate":0.5694444444444444,"max_drawdown_pct":0.8797326864948225,"sharpe_ratio":1.2282104833801621,"monthly_win_rate":1.0,"equity_curve_r_squared":0.16598640737563777}},"sqs_score":62.9,"sqs_breakdown":{"profitability":42.7,"risk":87.1,"consistency":86.6,"robustness":44.8},"verdict":"neutral","verdict_reasoning":"Test SQS 62.9 vs 64.2. Return +0.67% vs +0.73%. Valid SQS 77.4 was strong. Similar trade count (72). Marginal effect.","next_direction":"Hold5d marginal positive on valid but neutral on test","tags":["pead","midcap","step19","hold5"]} +{"entry_id":"IMP-0021","timestamp":"2026-03-17T02:17:02.185037+00:00","experiment_name":"pead_midcap_step20_best3","hypothesis":"Combine all marginally positive changes: score 0.65 + nofrac 1.0 + hold 5d","config_delta":{"base_experiment":"pead_midcap_step14_score65","changes":{}},"results":{"train":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f","trade_count":441,"profit_factor":0.967683028533835,"total_return_pct":-0.9321417326055089,"win_rate":0.41950113378684806,"max_drawdown_pct":4.355001847653457,"sharpe_ratio":-0.11372635609251765,"monthly_win_rate":0.5142857142857142,"equity_curve_r_squared":0.038072957614376554},"valid":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f","trade_count":56,"profit_factor":1.6107426395301327,"total_return_pct":1.5733831641505238,"win_rate":0.48214285714285715,"max_drawdown_pct":0.628137470729625,"sharpe_ratio":2.475989818011853,"monthly_win_rate":0.6666666666666666,"equity_curve_r_squared":0.6343682913335291},"test":{"run_id":"bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f","trade_count":64,"profit_factor":1.2154843477872561,"total_return_pct":0.7161940318976412,"win_rate":0.515625,"max_drawdown_pct":0.8900540815144506,"sharpe_ratio":1.3247288791439433,"monthly_win_rate":1.0,"equity_curve_r_squared":0.24515410354772538}},"sqs_score":62.0,"sqs_breakdown":{"profitability":43.6,"risk":88.7,"consistency":77.6,"robustness":45.3},"verdict":"neutral","verdict_reasoning":"Test SQS 62.0 < 64.2. Return +0.72% similar to +0.73%. Combo did not beat step14 alone. Over-optimization reduces robustness.","next_direction":"Step14 (score 0.65) remains the best. Simple is better.","tags":["pead","midcap","step20","best3"]} diff --git a/libs/backtest/allocator.py b/libs/backtest/allocator.py index 1076a4c..56c92fb 100644 --- a/libs/backtest/allocator.py +++ b/libs/backtest/allocator.py @@ -25,6 +25,7 @@ def compute_stop_price(candidate: Candidate, config: RiskConfig) -> float: """Compute stop price based on ATR-14 or a percentage fallback. Uses entry_price_est (reaction close) as the price basis. + For long: stop below entry. For short: stop above entry. Actual fill uses the real open + slippage; R-multiple uses actual fill price. """ price = candidate.entry_price_est @@ -33,6 +34,8 @@ def compute_stop_price(candidate: Candidate, config: RiskConfig) -> float: else: # Fallback: 2% of price stop_distance = price * 0.02 + if candidate.trade_direction == "short": + return price + stop_distance return max(0.01, price - stop_distance) @@ -44,21 +47,26 @@ def compute_target_price( target_model: str = "fixed_r", target_atr_multiplier: float = 1.5, atr_14: float | None = None, + trade_direction: str = "long", ) -> float: """Compute target price using fixed R-multiple or ATR-based model. + For long: target above entry. For short: target below entry. + Models: - - "fixed_r": target = entry + risk * target_r (original) - - "atr_multiple": target = entry + atr_14 * target_atr_multiplier + - "fixed_r": target = entry +/- risk * target_r + - "atr_multiple": target = entry +/- atr_14 * target_atr_multiplier """ + sign = -1.0 if trade_direction == "short" else 1.0 + if target_model == "atr_multiple" and atr_14 and atr_14 > 0: - return entry_price_est + atr_14 * target_atr_multiplier + return entry_price_est + sign * atr_14 * target_atr_multiplier # Default: fixed R-multiple - risk = entry_price_est - stop_price + risk = abs(entry_price_est - stop_price) if risk <= 0: - return entry_price_est * 1.10 # 10% default target - return entry_price_est + risk * target_r + return entry_price_est * (0.90 if trade_direction == "short" else 1.10) + return entry_price_est + sign * risk * target_r def compute_shares( @@ -67,8 +75,8 @@ def compute_shares( stop_price: float, config: RiskConfig, ) -> int: - """Compute integer share count. Always math.floor() — never round up.""" - stop_distance = entry_price - stop_price + """Compute integer share count. Always math.floor() -- never round up.""" + stop_distance = abs(entry_price - stop_price) if stop_distance <= 0: return 0 risk_dollars = equity * config.per_trade_risk_pct @@ -119,9 +127,10 @@ def run_entry_gates( return "macro_regime_unfavorable" # else: size scaler applied in build_planned_order - # Gate 1: Kill switch + # Gate 1: Kill switch (skip if log-only mode) if portfolio_state.current_drawdown_pct >= _KILL_SWITCH_DRAWDOWN_PCT: - return "kill_switch_drawdown" + if not config.risk.kill_switch_log_only: + return "kill_switch_drawdown" # Gate 2: Max positions if len(open_positions) >= config.risk.max_positions: @@ -160,10 +169,12 @@ def run_entry_gates( # Gate 9: Event-type direction filter profile = config.get_event_profile(candidate.event_type) - if profile and profile.direction_filter == "bullish_only": + if profile: reaction = candidate.features.get("reaction_day_return") - if reaction is not None and float(reaction) < 0: + if profile.direction_filter == "bullish_only" and reaction is not None and float(reaction) < 0: return "direction_filter_bearish" + if profile.direction_filter == "bearish_only" and reaction is not None and float(reaction) > 0: + return "direction_filter_bullish" # --- Veto gates: document quality hard filters --- @@ -227,6 +238,7 @@ def build_planned_order( target_model=config.execution.target_model, target_atr_multiplier=target_atr_mult, atr_14=candidate.atr_14, + trade_direction=candidate.trade_direction, ) shares = 0 @@ -256,7 +268,7 @@ def build_planned_order( ): shares = max(1, math.floor(shares * config.risk.macro_regime_size_scaler)) - risk_dollars = (candidate.entry_price_est - stop_price) * shares + risk_dollars = abs(candidate.entry_price_est - stop_price) * shares return PlannedOrder( candidate=candidate, diff --git a/libs/backtest/execution.py b/libs/backtest/execution.py index f36bfbd..792a8ce 100644 --- a/libs/backtest/execution.py +++ b/libs/backtest/execution.py @@ -34,6 +34,16 @@ def _long_exit_fill(price: float, slippage_bps: float) -> float: return price * (1.0 - slippage_bps / 10_000) +def _short_entry_fill(open_price: float, slippage_bps: float) -> float: + """Sell short at open - slippage (receives less).""" + return open_price * (1.0 - slippage_bps / 10_000) + + +def _short_exit_fill(price: float, slippage_bps: float) -> float: + """Buy to cover at price + slippage (pays more).""" + return price * (1.0 + slippage_bps / 10_000) + + # --------------------------------------------------------------------------- # Entry simulation # --------------------------------------------------------------------------- @@ -67,8 +77,12 @@ def simulate_entry( logger.warning("entry_skip_zero_shares", event_id=plan.candidate.event_id) return None - fill_price = _long_entry_fill(float(bar_open), config.slippage_bps_base) - slippage_bps_actual = (fill_price / float(bar_open) - 1.0) * 10_000 + is_short = plan.candidate.trade_direction == "short" + if is_short: + fill_price = _short_entry_fill(float(bar_open), config.slippage_bps_base) + else: + fill_price = _long_entry_fill(float(bar_open), config.slippage_bps_base) + slippage_bps_actual = abs(fill_price / float(bar_open) - 1.0) * 10_000 pid = position_id or str(uuid.uuid4()) @@ -130,9 +144,19 @@ def simulate_exit( bar_high = bar.get("high") bar_close = bar.get("close") slippage = config.slippage_bps_base - - stop_hit = bar_low is not None and float(bar_low) <= position.current_stop - target_hit = bar_high is not None and float(bar_high) >= position.target_price + is_short = position.plan.candidate.trade_direction == "short" + + # Direction-aware stop/target detection + if is_short: + # Short: stop is above entry (hit when price goes up), target below entry (hit when price drops) + stop_hit = bar_high is not None and float(bar_high) >= position.current_stop + target_hit = bar_low is not None and float(bar_low) <= position.target_price + exit_fill_fn = _short_exit_fill + else: + # Long: stop below entry (hit when price drops), target above entry (hit when price rises) + stop_hit = bar_low is not None and float(bar_low) <= position.current_stop + target_hit = bar_high is not None and float(bar_high) >= position.target_price + exit_fill_fn = _long_exit_fill exit_reason: ExitReason | None = None exit_fill_price: float | None = None @@ -141,33 +165,36 @@ def simulate_exit( # Same-bar conflict if config.same_bar_priority == "stop_first_conservative": exit_reason = ExitReason.STOP - exit_fill_price = _long_exit_fill(position.current_stop, slippage) + exit_fill_price = exit_fill_fn(position.current_stop, slippage) else: # target_first_aggressive exit_reason = ExitReason.TARGET - exit_fill_price = _long_exit_fill(position.target_price, slippage) + exit_fill_price = exit_fill_fn(position.target_price, slippage) elif stop_hit: exit_reason = ExitReason.STOP - exit_fill_price = _long_exit_fill(position.current_stop, slippage) + exit_fill_price = exit_fill_fn(position.current_stop, slippage) elif target_hit: exit_reason = ExitReason.TARGET - exit_fill_price = _long_exit_fill(position.target_price, slippage) + exit_fill_price = exit_fill_fn(position.target_price, slippage) elif position.days_held >= config.max_holding_days: exit_reason = ExitReason.TIME if bar_close is not None and float(bar_close) > 0: - exit_fill_price = _long_exit_fill(float(bar_close), slippage) + exit_fill_price = exit_fill_fn(float(bar_close), slippage) else: exit_fill_price = position.entry_price # fallback (shouldn't happen) - # No-follow-through early exit (D+1 close < entry) + # No-follow-through early exit + # Long: D+1 close < entry. Short: D+1 close > entry. if ( exit_reason is None and config.no_follow_through_exit and position.days_held == 1 and bar_close is not None - and float(bar_close) < position.entry_price ): - exit_reason = ExitReason.NO_FOLLOW_THROUGH - exit_fill_price = _long_exit_fill(float(bar_close), slippage) + close_val = float(bar_close) + nft_triggered = (close_val > position.entry_price) if is_short else (close_val < position.entry_price) + if nft_triggered: + exit_reason = ExitReason.NO_FOLLOW_THROUGH + exit_fill_price = exit_fill_fn(close_val, slippage) if exit_reason is None or exit_fill_price is None: return None @@ -209,8 +236,10 @@ def simulate_kill_switch_exit( ) -> FilledTrade: """Force-close a position due to kill switch (portfolio drawdown).""" slippage = config.slippage_bps_base + is_short = position.plan.candidate.trade_direction == "short" + exit_fill_fn = _short_exit_fill if is_short else _long_exit_fill if bar is not None and bar.get("close") is not None: - exit_price = _long_exit_fill(float(bar["close"]), slippage) + exit_price = exit_fill_fn(float(bar["close"]), slippage) else: exit_price = position.entry_price # last known price fallback @@ -241,37 +270,62 @@ def update_trailing_stop( trailing_model: str = "bar_low", warmup_days: int = 0, ) -> None: - """Ratchet stop up based on trailing model (never down). Mutates position in place. + """Ratchet stop towards price for trailing model. Mutates position in place. + + For long: ratchet stop UP (never down). Tracks peak_price as highest high. + For short: ratchet stop DOWN (never up). Tracks peak_price as lowest low. Models: - - "bar_low": trail to each day's low (tightest, aggressive) - - "pct_3": trail at peak_price * (1 - 3%) — moderate - - "pct_5": trail at peak_price * (1 - 5%) — wider + - "bar_low"/"bar_high": trail to bar extreme (tightest, aggressive) + - "pct_3": trail at peak_price * (1 +/- 3%) -- moderate + - "pct_5": trail at peak_price * (1 +/- 5%) -- wider Args: warmup_days: Skip trailing until position has been held this many days. """ - # Always track peak price (even during warmup) - bar_high = bar.get("high") - if bar_high is not None: - position.peak_price = max(position.peak_price, float(bar_high)) + is_short = position.plan.candidate.trade_direction == "short" + + # Track best price (peak for long = highest high, for short = lowest low) + if is_short: + bar_low = bar.get("low") + if bar_low is not None: + position.peak_price = min(position.peak_price, float(bar_low)) + else: + bar_high = bar.get("high") + if bar_high is not None: + position.peak_price = max(position.peak_price, float(bar_high)) # Don't tighten stop during warmup period if position.days_held < warmup_days: return - if trailing_model == "bar_low": - bar_low = bar.get("low") - if bar_low is not None: - new_stop = max(position.current_stop, float(bar_low)) - position.current_stop = new_stop - elif trailing_model.startswith("pct_"): - try: - trail_pct = float(trailing_model.split("_")[1]) / 100.0 - except (IndexError, ValueError): - trail_pct = 0.03 - trail_stop = position.peak_price * (1.0 - trail_pct) - position.current_stop = max(position.current_stop, trail_stop) + if is_short: + # Short: trail stop DOWN towards price (tighter = lower stop) + if trailing_model == "bar_low" or trailing_model == "bar_high": + bar_high = bar.get("high") + if bar_high is not None: + position.current_stop = min(position.current_stop, float(bar_high)) + elif trailing_model.startswith("pct_"): + try: + trail_pct = float(trailing_model.split("_")[1]) / 100.0 + except (IndexError, ValueError): + trail_pct = 0.03 + trail_stop = position.peak_price * (1.0 + trail_pct) + position.current_stop = min(position.current_stop, trail_stop) + else: + # Long: trail stop UP (original behavior) + if trailing_model == "bar_low": + bar_low = bar.get("low") + if bar_low is not None: + new_stop = max(position.current_stop, float(bar_low)) + position.current_stop = new_stop + elif trailing_model.startswith("pct_"): + try: + trail_pct = float(trailing_model.split("_")[1]) / 100.0 + except (IndexError, ValueError): + trail_pct = 0.03 + trail_stop = position.peak_price * (1.0 - trail_pct) + position.current_stop = max(position.current_stop, trail_stop) # --------------------------------------------------------------------------- @@ -288,16 +342,23 @@ def _build_filled_trade_partial( shares: int, ) -> FilledTrade: """Build a FilledTrade for a partial exit (specific share count).""" + is_short = position.plan.candidate.trade_direction == "short" commission = shares * config.commission_per_share # only exit leg for partial - gross_pnl = (exit_price - position.entry_price) * shares + if is_short: + gross_pnl = (position.entry_price - exit_price) * shares + else: + gross_pnl = (exit_price - position.entry_price) * shares net_pnl = gross_pnl - commission entry_price = position.entry_price - pnl_pct = (exit_price - entry_price) / entry_price if entry_price != 0 else 0.0 + if is_short: + pnl_pct = (entry_price - exit_price) / entry_price if entry_price != 0 else 0.0 + else: + pnl_pct = (exit_price - entry_price) / entry_price if entry_price != 0 else 0.0 - stop_distance = entry_price - position.plan.stop_price + stop_distance = abs(entry_price - position.plan.stop_price) if stop_distance > 0: - r_multiple = (exit_price - entry_price) / stop_distance + r_multiple = gross_pnl / shares / stop_distance else: r_multiple = 0.0 @@ -332,18 +393,25 @@ def _build_filled_trade( exit_date: dt.date, config: ExecutionConfig, ) -> FilledTrade: + is_short = position.plan.candidate.trade_direction == "short" shares = position.shares_open commission = shares * config.commission_per_share * 2 # entry + exit legs - gross_pnl = (exit_price - position.entry_price) * shares + if is_short: + gross_pnl = (position.entry_price - exit_price) * shares + else: + gross_pnl = (exit_price - position.entry_price) * shares net_pnl = gross_pnl - commission entry_price = position.entry_price - pnl_pct = (exit_price - entry_price) / entry_price if entry_price != 0 else 0.0 + if is_short: + pnl_pct = (entry_price - exit_price) / entry_price if entry_price != 0 else 0.0 + else: + pnl_pct = (exit_price - entry_price) / entry_price if entry_price != 0 else 0.0 # R-multiple uses actual fill price - stop_distance = entry_price - position.plan.stop_price + stop_distance = abs(entry_price - position.plan.stop_price) if stop_distance > 0: - r_multiple = (exit_price - entry_price) / stop_distance + r_multiple = gross_pnl / shares / stop_distance else: r_multiple = 0.0 diff --git a/libs/backtest/scoring.py b/libs/backtest/scoring.py index 81cccea..20f0a32 100644 --- a/libs/backtest/scoring.py +++ b/libs/backtest/scoring.py @@ -4,17 +4,22 @@ Computes a composite score in [0, 1] from alpha-only features available at entry time (no forward-looking data). Higher score = more favorable entry conditions for a long swing trade. -Alpha features (5 components, 100% weight): - Event Quality (35%): - - Event quality (35%) — parser confidence + signal strength + guidance - - Market Confirmation (65%): - - Reaction quality (25%) — moderate positive return is ideal - - Close strength (18%) — close near high = buyers won the day - - Volume conviction (14%) — above-average but not exhaustion - - Gap quality (8%) — small positive gap = orderly strength - -Non-alpha features (removed from composite, retained for analysis scripts): +Weights are empirically calibrated from signal quality analysis on 1,675+ +events (2022-07 to 2026-03). Components that showed no signal or +anti-signal were removed. + +Active components (3, 100% weight): + Event Quality (65%): + - Event quality — parser confidence + signal strength + guidance + - Only component with monotonic win-rate increase across buckets + + Market Confirmation (35%): + - Reaction direction (20%) — simple directional: positive > flat > negative + - Volume conviction (15%) — above-average volume confirms conviction + +Removed components (empirically no signal or anti-signal): + - Close strength — data showed inverted relationship (close near high = worse) + - Gap quality — no sorting power across 1,675 events - Earnings surprise — eps_growth_qoq is sequential growth, not real surprise - Risk penalty — already hard-gated at Gate 10 - Parse confidence — already hard-gated at Gate 11 @@ -31,77 +36,126 @@ logger = get_logger(__name__) def compute_entry_score(row: dict[str, Any]) -> float: - """Compute composite entry score from alpha-only features. + """Compute composite entry score from empirically validated features. - Components and weights: - Event Quality (35%): - 1. Event quality (35%) — parser confidence + signal strength - Market Confirmation (65%): - 2. Reaction quality (25%) — moderate positive return is ideal - 3. Close strength (18%) — close near high = buyers won the day - 4. Volume conviction (14%) — above-average but not exhaustion - 5. Gap quality (8%) — small positive gap = orderly strength + Components and weights (calibrated on 1,675+ events): + 1. Event quality (65%) — only monotonic signal in bucket analysis + 2. Reaction direction (20%) — simple positive/negative directional + 3. Volume conviction (15%) — above-average confirms conviction Returns float in [0.0, 1.0]. """ - # Alpha features only event = _event_quality_score(row) reaction = _reaction_score(row) - close = _close_strength_score(row) volume = _volume_score(row) - gap = _gap_score(row) raw = ( - # Event Quality (35%) - event * 0.35 - # Market Confirmation (65%) - + reaction * 0.25 - + close * 0.18 - + volume * 0.14 - + gap * 0.08 + event * 0.65 + + reaction * 0.20 + + volume * 0.15 ) return max(0.0, min(1.0, raw)) +def compute_pead_score( + row: dict[str, Any], + reaction_threshold: float = 0.05, + volume_threshold: float = 1.5, +) -> float: + """Continuous PEAD score based on reaction strength, volume, and gap alignment. + + Academically grounded: stocks with strong earnings reactions (PEAD) + continue to drift in the same direction. Supports both long (positive + reaction) and short (negative reaction) sides. + + For short-side, sets row["trade_direction"] = "short" as a side effect + so downstream components know to use short execution logic. + + Score components (weighted): + - Reaction magnitude (60%): continuous 0.0-1.0 based on abs(return) + - Volume conviction (25%): continuous 0.0-1.0 based on volume_ratio + - Gap alignment (15%): bonus when gap direction matches reaction + + Returns continuous score in [0.0, 1.0]. Scores above ~0.5 indicate + qualifying PEAD setups. Returns 0.0 if below threshold or missing data. + """ + ret = row.get("reaction_day_return") + vol = row.get("volume_ratio_20d") + + if ret is None or vol is None: + return 0.0 + + abs_ret = abs(ret) + + # Gate: must pass minimum reaction and volume thresholds + if abs_ret < reaction_threshold or vol < volume_threshold: + return 0.0 + + # Set trade direction + if ret >= reaction_threshold: + row["trade_direction"] = "long" + else: + row["trade_direction"] = "short" + + # --- Component 1: Reaction magnitude (60%) --- + # Linear scaling: threshold -> 0.60, 15%+ -> 0.95, capped at 1.0 + reaction_score = min(1.0, 0.60 + (abs_ret - reaction_threshold) * (0.35 / 0.08)) + + # --- Component 2: Volume conviction (25%) --- + # Linear scaling: volume_ratio / 5.0, capped at 1.0 + volume_score = min(1.0, vol / 5.0) + + # --- Component 3: Gap alignment (15%) --- + gap = row.get("gap_size") + if gap is not None: + gap = float(gap) + if ret > 0: + # Long: positive gap aligns with positive reaction + gap_score = min(1.0, max(0.0, gap / 0.03)) if gap > 0 else 0.2 + else: + # Short: negative gap aligns with negative reaction + gap_score = min(1.0, max(0.0, abs(gap) / 0.03)) if gap < 0 else 0.2 + else: + gap_score = 0.5 # neutral when missing + + # Weighted composite + raw = reaction_score * 0.60 + volume_score * 0.25 + gap_score * 0.15 + return max(0.0, min(1.0, raw)) + + # --------------------------------------------------------------------------- # Market feature scoring # --------------------------------------------------------------------------- def _reaction_score(row: dict[str, Any]) -> float: - """Score based on reaction_day_return. + """Score based on reaction_day_return direction. - Sweet spot: moderate positive return (0.5-3%) suggests post-event - continuation without being "already priced in". + Simple directional mapping — no "ideal zone" assumptions. + Empirical analysis showed the prior PEAD-zone mapping was anti-signal. Mapping: - +0.5% to +3% -> 0.9 (ideal PEAD zone) - +0% to +0.5% -> 0.65 (flat, uncertain direction) - +3% to +8% -> 0.45 (getting priced in) - > +8% -> 0.2 (extreme — mean reversion risk) - -2% to 0% -> 0.4 (mild negative) - -5% to -2% -> 0.25 (moderate negative) - < -5% -> 0.1 (strongly bearish) + > +3% -> 0.7 (strong positive) + > 0% -> 0.6 (positive) + 0% to -2% -> 0.45 (flat to mild negative) + -2% to -5% -> 0.3 (moderate negative) + < -5% -> 0.2 (strongly negative) """ rdr = row.get("reaction_day_return") if rdr is None: return 0.5 r = float(rdr) - if 0.005 <= r <= 0.03: - return 0.9 - elif 0.0 <= r < 0.005: - return 0.65 - elif 0.03 < r <= 0.08: + if r > 0.03: + return 0.7 + elif r > 0.0: + return 0.6 + elif r >= -0.02: return 0.45 - elif r > 0.08: - return 0.2 - elif -0.02 <= r < 0.0: - return 0.4 - elif -0.05 <= r < -0.02: - return 0.25 + elif r >= -0.05: + return 0.3 else: # r < -0.05 - return 0.1 + return 0.2 def _close_strength_score(row: dict[str, Any]) -> float: diff --git a/libs/backtest/selector.py b/libs/backtest/selector.py index acf9093..6bb83f4 100644 --- a/libs/backtest/selector.py +++ b/libs/backtest/selector.py @@ -84,6 +84,8 @@ def build_candidate(row: dict[str, Any]) -> Candidate | None: # Classify score bucket score_bucket = _classify_score_bucket(score) + trade_direction = str(row.get("trade_direction", "long")) + return Candidate( event_id=event_id, symbol=str(row.get("symbol", row.get("ticker", ""))), @@ -99,6 +101,7 @@ def build_candidate(row: dict[str, Any]) -> Candidate | None: avg_dollar_volume=avg_dollar_volume, atr_14=atr_14, score_bucket=score_bucket, + trade_direction=trade_direction, features={k: v for k, v in row.items() if k not in _RESERVED_KEYS}, ) @@ -107,7 +110,7 @@ _RESERVED_KEYS = { "event_id", "symbol", "ticker", "issuer_id", "score", "sector", "event_type", "event_timestamp", "filing_time_bucket", "reaction_date", "entry_date", "execution_date", "entry_price", "entry_price_est", - "avg_dollar_volume", "atr_14", "score_bucket", + "avg_dollar_volume", "atr_14", "score_bucket", "trade_direction", } diff --git a/libs/backtest/snapshot_store.py b/libs/backtest/snapshot_store.py index d82bd79..3147da8 100644 --- a/libs/backtest/snapshot_store.py +++ b/libs/backtest/snapshot_store.py @@ -83,9 +83,11 @@ class SnapshotStore: split_name: str, oracle_url: str, db_dsn: str, + scoring_fn: Any | None = None, ) -> "SnapshotStore": """Synchronous factory. Internally uses asyncio.run() to prefetch data. + scoring_fn: optional callable(row_dict) -> float to override default scoring. Raises RuntimeError if called from within a running event loop. """ try: @@ -99,7 +101,9 @@ class SnapshotStore: "Use SnapshotStore._async_load() directly in async contexts." ) - data = asyncio.run(cls._async_load(Path(snapshot_dir), split_name, oracle_url, db_dsn)) + data = asyncio.run( + cls._async_load(Path(snapshot_dir), split_name, oracle_url, db_dsn, scoring_fn) + ) return cls(**data) @classmethod @@ -109,6 +113,7 @@ class SnapshotStore: split_name: str, oracle_url: str, db_dsn: str, + scoring_fn: Any | None = None, ) -> dict[str, Any]: """Async data loading pipeline. Returns kwargs dict for __init__.""" parquet_path = snapshot_dir / f"{split_name}.parquet" @@ -182,9 +187,12 @@ class SnapshotStore: enriched["entry_price_est"] = enriched["event_close"] # score: use existing column or compute from market features if "score" not in enriched or enriched.get("score") is None: - from libs.backtest.scoring import compute_entry_score + if scoring_fn is not None: + enriched["score"] = scoring_fn(enriched) + else: + from libs.backtest.scoring import compute_entry_score - enriched["score"] = compute_entry_score(enriched) + enriched["score"] = compute_entry_score(enriched) candidates_by_exec_date.setdefault(exec_date, []).append(enriched) @@ -265,8 +273,7 @@ class SnapshotStore: if not symbols or date_range is None: return {}, {} try: - from libs.oracle_client.client import OracleClient - from libs.oracle_client.price import PriceService + from libs.oracle_client import OracleClient, PriceService start_str = date_range[0].isoformat() end_str = date_range[1].isoformat() @@ -321,15 +328,14 @@ class SnapshotStore: return {} result: dict[str, str] = {} try: - from libs.oracle_client.client import OracleClient - from libs.oracle_client.financial import FinancialService + from libs.oracle_client import CompanyService, OracleClient async with OracleClient(base_url=oracle_url) as client: + company_svc = CompanyService(client) for sym in symbols: try: - info = await client.get(f"/api/v1/company/{sym}") - sector = info.get("sector") if isinstance(info, dict) else None - result[sym] = str(sector) if sector else "UNKNOWN" + info = await company_svc.get_company(sym) + result[sym] = info.sector or "UNKNOWN" except Exception: result[sym] = "UNKNOWN" except Exception as exc: @@ -388,8 +394,7 @@ class SnapshotStore: if date_range is None: return {} try: - from libs.oracle_client.client import OracleClient - from libs.oracle_client.price import PriceService + from libs.oracle_client import OracleClient, PriceService # Extend start date back by sma_period trading days for SMA warm-up warmup_days = sma_period * 2 # calendar days (conservative buffer) diff --git a/libs/common/config.py b/libs/common/config.py index bb9856e..e759223 100644 --- a/libs/common/config.py +++ b/libs/common/config.py @@ -38,6 +38,9 @@ class Settings(BaseSettings): # File storage data_root: str = "./data" + # Symbol universe + symbols_file: str = "configs/symbols.yaml" + # Logging log_level: str = "INFO" @@ -72,7 +75,7 @@ class Settings(BaseSettings): return self._app_config def get_symbols(self) -> list[str]: - cfg = _load_yaml("configs/symbols.yaml") + cfg = _load_yaml(self.symbols_file) return cfg.get("symbols", []) def get_fred_series(self) -> list[dict[str, Any]]: diff --git a/libs/db/migrations/versions/0004_document_item_numbers.py b/libs/db/migrations/versions/0004_document_item_numbers.py new file mode 100644 index 0000000..b34c40a --- /dev/null +++ b/libs/db/migrations/versions/0004_document_item_numbers.py @@ -0,0 +1,29 @@ +"""Add item_numbers JSONB column to documents table. + +Revision ID: 0004 +Revises: 0003 +Create Date: 2026-03-13 + +Stores SEC 8-K item numbers (e.g. ["2.02", "9.01"]) from the Oracle API +so the event parser can classify event_type correctly. +""" +from __future__ import annotations + +from collections.abc import Sequence + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects.postgresql import JSONB + +revision: str = "0004" +down_revision: str | None = "0003" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + op.add_column("documents", sa.Column("item_numbers", JSONB, nullable=True)) + + +def downgrade() -> None: + op.drop_column("documents", "item_numbers") diff --git a/libs/db/models.py b/libs/db/models.py index 207653c..b2e16fe 100644 --- a/libs/db/models.py +++ b/libs/db/models.py @@ -124,6 +124,7 @@ class Document(Base): DateTime(timezone=True), nullable=True ) primary_document_name: Mapped[str | None] = mapped_column(Text, nullable=True) + item_numbers: Mapped[list | None] = mapped_column(JSONB, nullable=True) parsed_status: Mapped[str] = mapped_column(Text, default="pending", nullable=False) created_at_utc: Mapped[dt.datetime] = mapped_column( DateTime(timezone=True), default=_utcnow, nullable=False diff --git a/libs/export/snapshot_export.py b/libs/export/snapshot_export.py index 292c731..66b1b09 100644 --- a/libs/export/snapshot_export.py +++ b/libs/export/snapshot_export.py @@ -1,6 +1,7 @@ """Export feature snapshots + labels to Parquet with train/valid/test split.""" from __future__ import annotations +import datetime as dt import json import subprocess import uuid @@ -89,6 +90,7 @@ async def export_dataset_snapshot( feature_versions: list[str] | None = None, label_version: str = "label-1.0.0", parser_version: str = "rule-1.0.0", + symbols: list[str] | None = None, ) -> dict[str, Any]: """Join FeatureSnapshot + EventLabel and export to Parquet. @@ -105,7 +107,7 @@ async def export_dataset_snapshot( Returns: Manifest dict with metadata and row counts. """ - from libs.db.models import EventLabel, FeatureSnapshot + from libs.db.models import Event, EventLabel, FeatureSnapshot, SymbolMaster if snapshot_id is None: snapshot_id = str(uuid.uuid4()) @@ -118,25 +120,37 @@ async def export_dataset_snapshot( # Query: JOIN feature_snapshots + event_labels via event_id # When merging multiple versions, query all and group by event_id stmt = ( - select(FeatureSnapshot, EventLabel) + select(FeatureSnapshot, EventLabel, Event) .join(EventLabel, FeatureSnapshot.event_id == EventLabel.event_id) + .join(Event, FeatureSnapshot.event_id == Event.event_id) .where(FeatureSnapshot.snapshot_name.in_(versions)) .where(EventLabel.label_version == label_version) - .where(EventLabel.label_status == "ok") + .where(EventLabel.label_status.in_(["ok", "truncated"])) .where(EventLabel.invalid_event_for_labeling.is_(False)) ) + if symbols: + stmt = stmt.where( + Event.symbol_id.in_( + select(SymbolMaster.symbol_id).where( + SymbolMaster.ticker.in_([s.upper() for s in symbols]) + ) + ) + ) + result = await session.execute(stmt) pairs = result.all() # Group features by event_id, merge feature_json from all versions event_features: dict[str, dict[str, Any]] = {} event_labels: dict[str, Any] = {} - for fs, lbl in pairs: + event_dates: dict[str, dt.date] = {} + for fs, lbl, evt in pairs: eid = fs.event_id if eid not in event_features: event_features[eid] = {} event_labels[eid] = lbl + event_dates[eid] = evt.event_date event_features[eid].update(fs.feature_json) rows: list[dict[str, Any]] = [] @@ -162,11 +176,18 @@ async def export_dataset_snapshot( "mae_3d": float(lbl.mae_3d) if lbl.mae_3d else None, "mfe_5d": float(lbl.mfe_5d) if lbl.mfe_5d else None, "mae_5d": float(lbl.mae_5d) if lbl.mae_5d else None, + "fwd_return_10d": float(lbl.fwd_return_10d) if lbl.fwd_return_10d else None, + "fwd_return_20d": float(lbl.fwd_return_20d) if lbl.fwd_return_20d else None, + "mfe_10d": float(lbl.mfe_10d) if lbl.mfe_10d else None, + "mae_10d": float(lbl.mae_10d) if lbl.mae_10d else None, + "mfe_20d": float(lbl.mfe_20d) if lbl.mfe_20d else None, + "mae_20d": float(lbl.mae_20d) if lbl.mae_20d else None, "label_status": lbl.label_status, "label_version": lbl.label_version, } - if "event_date" not in row: - row["event_date"] = str(lbl.reaction_date) if lbl.reaction_date else "" + # Use DB Event.event_date as authoritative source + ed = event_dates.get(eid) + row["event_date"] = ed.isoformat() if ed else row.get("event_date", "") rows.append(row) logger.info("snapshot_export_rows", snapshot_id=snapshot_id, total=len(rows)) diff --git a/libs/features/builder.py b/libs/features/builder.py index c86475c..28e90c3 100644 --- a/libs/features/builder.py +++ b/libs/features/builder.py @@ -6,11 +6,15 @@ import datetime as dt from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession +from libs.common.file_store import read_exhibit from libs.common.logging import get_logger -from libs.db.models import Event, EventParse, FeatureSnapshot +from libs.common.time_utils import filing_time_bucket as classify_time_bucket +from libs.labeler.reaction_date import compute_reaction_date +from libs.db.models import Document, Event, EventParse, FeatureSnapshot from libs.features.event_features import compute_event_features from libs.features.financial_features import compute_financial_features from libs.features.market_features import compute_market_features +from libs.features.text_features import compute_text_features from libs.oracle_client.financial import FinancialService from libs.oracle_client.price import PriceService @@ -43,6 +47,14 @@ async def build_features_for_event( event_date_str = event.event_date.isoformat() + # Compute reaction date for market feature alignment + if event.filed_at_utc is not None: + ftb = classify_time_bucket(event.filed_at_utc) + else: + ftb = "unknown" + reaction_date = compute_reaction_date(event.event_date, ftb) + reaction_date_str = reaction_date.isoformat() + # Fetch price bars from Stock Oracle try: # 30 trading days before event @@ -57,7 +69,7 @@ async def build_features_for_event( return None # Compute market features - mf = compute_market_features(bars, event_date_str) + mf = compute_market_features(bars, reaction_date_str) # Get latest valid event parse result = await session.execute( @@ -92,6 +104,29 @@ async def build_features_for_event( session.add(event_snapshot) await session.flush() + # Optional: text sentiment features (non-fatal if unavailable) + try: + doc_result = await session.execute( + select(Document).where(Document.document_id == event.primary_document_id) + ) + doc = doc_result.scalar_one_or_none() + if doc and doc.accession_no: + exhibit_text = read_exhibit(doc.accession_no, "EX-99.1") + tf = compute_text_features(exhibit_text) + text_snapshot = FeatureSnapshot( + event_id=event.event_id, + snapshot_name="text_v1", + snapshot_version=SNAPSHOT_VERSION, + feature_json=tf, + ) + session.add(text_snapshot) + await session.flush() + logger.info("text_features_built", event_id=event.event_id, word_count=tf["lm_word_count"]) + except FileNotFoundError: + logger.debug("text_features_no_exhibit", event_id=event.event_id) + except Exception as exc: + logger.warning("text_features_skipped", event_id=event.event_id, error=str(exc)) + # Optional: financial features (non-fatal if unavailable) if financial_service is not None: try: diff --git a/libs/features/event_features.py b/libs/features/event_features.py index 98061ec..5b62c8a 100644 --- a/libs/features/event_features.py +++ b/libs/features/event_features.py @@ -85,4 +85,6 @@ def compute_event_features(parser_output: dict[str, Any]) -> dict[str, Any]: "event_type": parser_output.get("event_type", "unknown"), "event_direction": parser_output.get("event_direction", "unknown"), "parse_confidence_overall": confidence.overall, + "filing_time_bucket": parser_output.get("filing_time_bucket", "unknown"), + "event_date": parser_output.get("event_date", ""), } diff --git a/libs/features/text_features.py b/libs/features/text_features.py new file mode 100644 index 0000000..8fe81b7 --- /dev/null +++ b/libs/features/text_features.py @@ -0,0 +1,190 @@ +"""Loughran-McDonald text sentiment features for SEC filings. + +Computes word-frequency-based sentiment scores using the Loughran-McDonald +financial sentiment dictionary. Features: + - lm_positive_pct: fraction of words in positive word list + - lm_negative_pct: fraction of words in negative word list + - lm_net_sentiment: (positive - negative) / total_words + - lm_uncertainty_pct: fraction of words in uncertainty word list +""" +from __future__ import annotations + +import csv +import re +from functools import lru_cache +from pathlib import Path +from typing import Any + +from libs.common.logging import get_logger + +logger = get_logger(__name__) + +_DICT_PATH = Path("data/dictionaries/lm_master_dictionary.csv") + +# Fallback minimal word lists if the full dictionary is not available. +# These are the most common Loughran-McDonald words by category. +_FALLBACK_POSITIVE = { + "achieve", "achieved", "achieves", "achieving", "advantage", "advantages", + "benefit", "beneficial", "benefits", "better", "boost", "boosted", + "create", "created", "creative", "effective", "efficiency", "enable", + "enabled", "enhance", "enhanced", "exceed", "exceeded", "exceeding", + "excellent", "favorable", "gain", "gained", "gains", "good", "great", + "greater", "grew", "growth", "highest", "improve", "improved", + "improvement", "improvements", "improving", "increase", "increased", + "increases", "increasing", "innovative", "leading", "opportunity", + "opportunities", "optimal", "outperform", "outperformed", "positive", + "profitability", "profitable", "progress", "prosper", "record", + "reward", "rewarding", "strong", "stronger", "strongest", "succeed", + "succeeded", "success", "successful", "superior", "surpass", "surpassed", + "upturn", "win", "winning", +} + +_FALLBACK_NEGATIVE = { + "abandon", "abandoned", "adversarial", "adverse", "adversely", + "challenge", "challenged", "challenges", "challenging", "close", + "closed", "closing", "concern", "concerned", "concerns", "decline", + "declined", "declines", "declining", "default", "defaults", "deficit", + "deficient", "delay", "delayed", "delays", "deteriorate", "deteriorated", + "deteriorating", "difficult", "difficulties", "difficulty", "diminish", + "disappointed", "disappointing", "discontinue", "disruption", "doubt", + "downturn", "downturns", "drop", "dropped", "drops", "fail", "failed", + "failing", "failure", "failures", "fell", "impair", "impaired", + "impairment", "impairments", "inability", "inadequate", "investigation", + "investigations", "lawsuit", "lawsuits", "liability", "liabilities", + "liquidate", "liquidation", "litigation", "loss", "losses", "lost", + "negative", "negatively", "penalty", "penalties", "problem", "problems", + "recession", "recessions", "restructure", "restructured", "restructuring", + "risk", "risks", "risky", "shortfall", "shutdown", "slump", "slowdown", + "suffer", "suffered", "suffering", "suspend", "suspended", "terminate", + "terminated", "termination", "threat", "threaten", "uncertain", + "unfavorable", "unforeseen", "unprofitable", "volatile", "volatility", + "weakness", "weaknesses", "worsen", "worsened", "worsening", "writedown", + "writeoff", +} + +_FALLBACK_UNCERTAINTY = { + "almost", "ambiguity", "ambiguous", "approximate", "approximately", + "assume", "assumed", "assumes", "assuming", "assumption", "assumptions", + "believe", "believed", "believes", "cautious", "conceivable", + "conditional", "could", "depend", "dependent", "depending", "depends", + "doubt", "doubtful", "estimate", "estimated", "estimates", "estimating", + "estimation", "expect", "expected", "expecting", "expects", "fluctuate", + "fluctuated", "fluctuates", "fluctuating", "fluctuation", "fluctuations", + "indefinite", "indefinitely", "indicate", "indicated", "indicates", + "indication", "likely", "may", "maybe", "might", "nearly", "pending", + "perhaps", "possible", "possibly", "potential", "potentially", + "predict", "predicted", "prediction", "predicting", "preliminary", + "presumably", "probable", "probably", "project", "projected", + "projecting", "projection", "projections", "risk", "risky", + "roughly", "seem", "seemed", "seems", "sometimes", "somewhat", + "suggest", "suggested", "suggesting", "suggests", "suppose", "tend", + "tended", "tends", "uncertain", "uncertainty", "unclear", "undetermined", + "unlikely", "unpredictable", "unsettled", "unsure", "variable", + "variability", +} + +_WORD_RE = re.compile(r"[a-z]+", re.IGNORECASE) + + +@lru_cache(maxsize=1) +def _load_dictionary() -> tuple[set[str], set[str], set[str]]: + """Load Loughran-McDonald dictionary. Returns (positive, negative, uncertainty) word sets.""" + if not _DICT_PATH.exists(): + logger.info("lm_dict_using_fallback", path=str(_DICT_PATH)) + return _FALLBACK_POSITIVE, _FALLBACK_NEGATIVE, _FALLBACK_UNCERTAINTY + + positive: set[str] = set() + negative: set[str] = set() + uncertainty: set[str] = set() + + try: + with open(_DICT_PATH, encoding="utf-8") as f: + reader = csv.DictReader(f) + for row in reader: + word = row.get("Word", row.get("word", "")).lower().strip() + if not word: + continue + # LM dictionary uses non-zero year values to indicate category membership + if _is_positive_value(row): + positive.add(word) + if _is_negative_value(row): + negative.add(word) + if _is_uncertainty_value(row): + uncertainty.add(word) + + logger.info( + "lm_dict_loaded", + positive=len(positive), + negative=len(negative), + uncertainty=len(uncertainty), + ) + except Exception as exc: + logger.warning("lm_dict_load_failed", error=str(exc)) + return _FALLBACK_POSITIVE, _FALLBACK_NEGATIVE, _FALLBACK_UNCERTAINTY + + # Fall back if dictionary seems empty + if not positive and not negative: + return _FALLBACK_POSITIVE, _FALLBACK_NEGATIVE, _FALLBACK_UNCERTAINTY + + return positive, negative, uncertainty + + +def _is_positive_value(row: dict[str, str]) -> bool: + val = row.get("Positive", row.get("positive", "0")) + try: + return int(val) != 0 + except (ValueError, TypeError): + return False + + +def _is_negative_value(row: dict[str, str]) -> bool: + val = row.get("Negative", row.get("negative", "0")) + try: + return int(val) != 0 + except (ValueError, TypeError): + return False + + +def _is_uncertainty_value(row: dict[str, str]) -> bool: + val = row.get("Uncertainty", row.get("uncertainty", "0")) + try: + return int(val) != 0 + except (ValueError, TypeError): + return False + + +def compute_text_features(text: str) -> dict[str, Any]: + """Compute Loughran-McDonald text sentiment features from filing text. + + Args: + text: Raw or normalized filing/exhibit text. + + Returns: + Dict with keys: lm_positive_pct, lm_negative_pct, lm_net_sentiment, + lm_uncertainty_pct, lm_word_count. + """ + positive_words, negative_words, uncertainty_words = _load_dictionary() + + words = _WORD_RE.findall(text.lower()) + total = len(words) + + if total == 0: + return { + "lm_positive_pct": 0.0, + "lm_negative_pct": 0.0, + "lm_net_sentiment": 0.0, + "lm_uncertainty_pct": 0.0, + "lm_word_count": 0, + } + + pos_count = sum(1 for w in words if w in positive_words) + neg_count = sum(1 for w in words if w in negative_words) + unc_count = sum(1 for w in words if w in uncertainty_words) + + return { + "lm_positive_pct": round(pos_count / total, 6), + "lm_negative_pct": round(neg_count / total, 6), + "lm_net_sentiment": round((pos_count - neg_count) / total, 6), + "lm_uncertainty_pct": round(unc_count / total, 6), + "lm_word_count": total, + } diff --git a/libs/labeler/label_generator.py b/libs/labeler/label_generator.py index bd8279b..8565330 100644 --- a/libs/labeler/label_generator.py +++ b/libs/labeler/label_generator.py @@ -8,7 +8,7 @@ from typing import Any from sqlalchemy.ext.asyncio import AsyncSession from libs.common.logging import get_logger -from libs.common.time_utils import next_trading_day, trading_days_between +from libs.common.time_utils import filing_time_bucket as classify_time_bucket, next_trading_day, trading_days_between from libs.labeler.reaction_date import compute_reaction_date logger = get_logger(__name__) @@ -128,8 +128,9 @@ async def generate_labels( from libs.db.models import EventLabel # 1. Compute reaction_date - filing_time_bucket = getattr(event, "filing_time_bucket", "unknown") - if not filing_time_bucket: + if event.filed_at_utc is not None: + filing_time_bucket = classify_time_bucket(event.filed_at_utc) + else: filing_time_bucket = "unknown" event_date: dt.date = event.event_date diff --git a/libs/oracle_client/__init__.py b/libs/oracle_client/__init__.py index e69de29..0e478aa 100644 --- a/libs/oracle_client/__init__.py +++ b/libs/oracle_client/__init__.py @@ -0,0 +1,22 @@ +"""Stock Oracle typed client library.""" + +from libs.oracle_client.client import OracleClient, make_oracle_client +from libs.oracle_client.company import CompanyService +from libs.oracle_client.filings import FilingsService +from libs.oracle_client.financial import FinancialService +from libs.oracle_client.finra import FinraService +from libs.oracle_client.fred import FredService +from libs.oracle_client.price import PriceService +from libs.oracle_client.screener import ScreenerService + +__all__ = [ + "OracleClient", + "make_oracle_client", + "CompanyService", + "FilingsService", + "FinancialService", + "FinraService", + "FredService", + "PriceService", + "ScreenerService", +] diff --git a/libs/oracle_client/company.py b/libs/oracle_client/company.py new file mode 100644 index 0000000..b9894c3 --- /dev/null +++ b/libs/oracle_client/company.py @@ -0,0 +1,32 @@ +"""Company info Oracle service methods.""" + +from __future__ import annotations + +from libs.oracle_client.client import OracleClient +from libs.oracle_client.models import CompanyInfo + + +class CompanyService: + def __init__(self, client: OracleClient) -> None: + self._client = client + + async def get_company(self, symbol: str) -> CompanyInfo: + """Fetch company info via GET /api/v1/financial/data/{symbol}. + + Uses the financial/data endpoint because /api/v1/company/{symbol} + does not exist in the current Stock Oracle API. + """ + data = await self._client.get( + f"/api/v1/financial/data/{symbol}", params={"period": "1y"} + ) + company = data.get("company", {}) + return CompanyInfo( + ticker=company.get("ticker", symbol), + name=company.get("name"), + cik=company.get("cik"), + exchange=company.get("exchange"), + sector=company.get("sector"), + industry=company.get("industry"), + country=company.get("country"), + market_cap=company.get("market_cap"), + ) diff --git a/libs/oracle_client/filings.py b/libs/oracle_client/filings.py index 18f7185..7bc0a7d 100644 --- a/libs/oracle_client/filings.py +++ b/libs/oracle_client/filings.py @@ -2,6 +2,8 @@ from __future__ import annotations +import re + from libs.oracle_client.client import OracleClient from libs.oracle_client.models import ( ExhibitDocument, @@ -11,6 +13,49 @@ from libs.oracle_client.models import ( FilingSearchResponse, ) +# Mapping from SGML ITEM INFORMATION descriptions to SEC 8-K item numbers. +_ITEM_DESC_TO_NUMBER: dict[str, str] = { + "entry into a material definitive agreement": "1.01", + "termination of a material definitive agreement": "1.02", + "bankruptcy or receivership": "1.03", + "mine safety": "1.04", + "completion of acquisition or disposition of assets": "2.01", + "results of operations and financial condition": "2.02", + "creation of a direct financial obligation": "2.03", + "triggering events that accelerate": "2.04", + "costs associated with exit or disposal": "2.05", + "material impairments": "2.06", + "notice of delisting": "3.01", + "unregistered sales of equity securities": "3.02", + "material modification to rights": "3.03", + r"changes in registrant.s certifying accountant": "4.01", + "non-reliance on previously issued": "4.02", + "changes in control of registrant": "5.01", + "departure of directors or certain officers": "5.02", + "amendments to articles of incorporation or bylaws": "5.03", + "temporary suspension of trading": "5.04", + r"amendments to the registrant.s code of ethics": "5.05", + "change in shell company status": "5.06", + "submission of matters to a vote": "5.07", + "shareholder nominations": "5.08", + "regulation fd disclosure": "7.01", + "other events": "8.01", + "financial statements and exhibits": "9.01", +} + + +def extract_items_from_sgml_header(content: str) -> list[str]: + """Extract SEC 8-K item numbers from SGML header ITEM INFORMATION fields.""" + items_found: list[str] = [] + matches = re.findall(r"ITEM INFORMATION:\s*(.+)", content) + for desc in matches: + desc_lower = desc.strip().lower() + for key, item_num in _ITEM_DESC_TO_NUMBER.items(): + if re.search(key, desc_lower): + items_found.append(item_num) + break + return list(dict.fromkeys(items_found)) + class FilingsService: def __init__(self, client: OracleClient) -> None: @@ -37,8 +82,10 @@ class FilingsService: accession_no=f["accession_number"], form_type=f["form_type"], filing_date=f["filing_date"], + accepted_at=f.get("accepted_at"), primary_document=f.get("primary_document"), description=f.get("filing_description"), + items=f.get("items", []), ) for f in data.get("filings", []) ] @@ -67,3 +114,20 @@ class FilingsService: exhibit_type=data.get("exhibit_type", exhibit_type), content=data.get("content", ""), ) + + async def get_filing_items(self, accession_no: str) -> list[str]: + """Extract SEC 8-K item numbers from the complete submission text. + + Fetches the full submission text (exhibit_type="") which contains the + SGML header with ITEM INFORMATION fields, then parses item numbers. + """ + try: + data = await self._client.get( + f"/api/v1/filings/exhibit/{accession_no}", params={"exhibit_type": ""} + ) + content = data.get("content", "") + if not content: + return [] + return extract_items_from_sgml_header(content) + except Exception: + return [] diff --git a/libs/oracle_client/financial.py b/libs/oracle_client/financial.py index 2a6d52d..5fc53e4 100644 --- a/libs/oracle_client/financial.py +++ b/libs/oracle_client/financial.py @@ -41,6 +41,9 @@ class FinancialService: ticker=company.get("ticker", ticker), name=company.get("name"), cik=company.get("cik"), + exchange=company.get("exchange"), sector=company.get("sector"), industry=company.get("industry"), + country=company.get("country"), + market_cap=company.get("market_cap"), ) diff --git a/libs/oracle_client/models.py b/libs/oracle_client/models.py index 9783f75..457514a 100644 --- a/libs/oracle_client/models.py +++ b/libs/oracle_client/models.py @@ -180,3 +180,23 @@ class ShortRatioPoint(BaseModel): class ShortRatioResponse(BaseModel): symbol: str data: list[ShortRatioPoint] = Field(default_factory=list) + + +# --------------------------------------------------------------------------- +# Screener +# --------------------------------------------------------------------------- + + +class ScreenerStock(BaseModel): + symbol: str + name: str | None = None + exchange: str | None = None + market_cap: float | None = None + avg_volume: float | None = None + + +class ScreenerResponse(BaseModel): + stocks: list[ScreenerStock] = Field(default_factory=list) + total: int = 0 + page: int = 1 + page_size: int = 250 diff --git a/libs/oracle_client/screener.py b/libs/oracle_client/screener.py new file mode 100644 index 0000000..ffb2a19 --- /dev/null +++ b/libs/oracle_client/screener.py @@ -0,0 +1,70 @@ +"""Screener Oracle service methods.""" + +from __future__ import annotations + +from libs.oracle_client.client import OracleClient +from libs.oracle_client.models import ScreenerResponse, ScreenerStock + +_SCREENER_PATH = "/api/v1/screener/stocks" +_DEFAULT_PAGE_SIZE = 250 + + +class ScreenerService: + def __init__(self, client: OracleClient) -> None: + self._client = client + + async def search_stocks( + self, + market_cap_min: float | None = None, + market_cap_max: float | None = None, + min_avg_volume: int | None = None, + exchange: str | None = None, + exclude_types: str | None = None, + price_min: float | None = None, + page: int = 1, + page_size: int = _DEFAULT_PAGE_SIZE, + ) -> ScreenerResponse: + """GET /api/v1/screener/stocks — single page.""" + params: dict[str, str | int | float] = { + "page": page, + "page_size": page_size, + } + if market_cap_min is not None: + params["market_cap_min"] = market_cap_min + if market_cap_max is not None: + params["market_cap_max"] = market_cap_max + if min_avg_volume is not None: + params["min_avg_volume"] = min_avg_volume + if exchange is not None: + params["exchange"] = exchange + if exclude_types is not None: + params["exclude_types"] = exclude_types + if price_min is not None: + params["price_min"] = price_min + + data = await self._client.get(_SCREENER_PATH, params=params) + + stocks = [ScreenerStock.model_validate(s) for s in data.get("stocks", [])] + return ScreenerResponse( + stocks=stocks, + total=data.get("total", len(stocks)), + page=page, + page_size=page_size, + ) + + async def search_all_stocks(self, **kwargs) -> list[ScreenerStock]: + """Auto-paginate and return all matching stocks.""" + all_stocks: list[ScreenerStock] = [] + page = 1 + page_size = kwargs.pop("page_size", _DEFAULT_PAGE_SIZE) + + while True: + resp = await self.search_stocks(**kwargs, page=page, page_size=page_size) + if not resp.stocks: + break + all_stocks.extend(resp.stocks) + if len(resp.stocks) < page_size: + break + page += 1 + + return all_stocks diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..b6c9cfd Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/artifacts/position_timeline.parquet new file mode 100644 index 0000000..ea56512 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..198cf8c Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/manifest.json new file mode 100644 index 0000000..b2695a9 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/manifest.json @@ -0,0 +1,60 @@ +{ + "experiment_name": "pead_midcap_step1_fixedr", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 1: R:R fix. Switch to fixed_r target_1_r=2.0 (target=2x stop). Breakeven WR drops from 67% to 33%. Baseline uses atr_multiple 1.5 ATR target vs 3.0 ATR stop.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "execution": { + "target_model": "fixed_r", + "target_1_r": 2.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "7pct", + "longshort", + "midcap", + "step1", + "fixedr" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/metadata.json new file mode 100644 index 0000000..238ba0e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214", + "started_at": "2026-03-16T19:18:35.061144+00:00", + "finished_at": "2026-03-16T19:18:35.259907+00:00", + "elapsed_seconds": 0.198763, + "git_commit_hash": "1916533", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 370 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..d85fc63 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,528,216,0.4090909090909091,-3160.973,-0.0061 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..8bf6d00 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,528,216,0.4090909090909091,-3160.973,-0.0061 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/metrics/metrics_summary.json new file mode 100644 index 0000000..8c7e499 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 528, + "win_rate": 0.4090909090909091, + "avg_win_pct": 0.0563398119346303, + "avg_loss_pct": -0.036273823388917464, + "profit_factor": 0.9200217466205215, + "expectancy_r": -0.006072735627157988, + "avg_r_multiple": -0.006072735627157988, + "total_return_pct": -3.124588680970817, + "annualized_return_pct": -0.862822744868319, + "max_drawdown_pct": 7.849601936494004, + "calmar_ratio": -0.1099192993286607, + "sharpe_ratio": -0.33161166573941914, + "sortino_ratio": -0.35905008062806276, + "avg_daily_pnl": -3.392604431021517, + "avg_positions_held": 2.4002169197396963, + "trade_skewness": 0.9772284162966697, + "trade_kurtosis": 1.6396058582271116, + "monthly_win_rate": 0.38461538461538464, + "equity_curve_r_squared": 0.049564252587599174, + "avg_holding_days": 6.100378787878788, + "stop_exit_rate": 0.9299242424242424, + "target_exit_rate": 0.003787878787878788, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "medium_high": 0.36428571428571427, + "high": 0.41304347826086957, + "medium": 0.65 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.36742424242424243, + 0.44886363636363635 + ], + "avg_win_pct_ci_95": [ + 0.050028455013153644, + 0.06364708090291488 + ], + "avg_loss_pct_ci_95": [ + -0.03865609274295419, + -0.03406549588096849 + ], + "profit_factor_ci_95": [ + 0.7260709627812177, + 1.142384935525512 + ], + "expectancy_r_ci_95": [ + -0.03902883684026925, + 0.025931993581874965 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/metrics/score_bucket_report.csv new file mode 100644 index 0000000..f10e9dd --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,368,0.413 +medium,20,0.65 +medium_high,140,0.3643 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/resolved_config.json new file mode 100644 index 0000000..c8ba855 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191835_e0902214/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 8, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "fixed_r", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..2aece0f Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/artifacts/position_timeline.parquet new file mode 100644 index 0000000..52ad100 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..0a592b9 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/manifest.json new file mode 100644 index 0000000..b2695a9 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/manifest.json @@ -0,0 +1,60 @@ +{ + "experiment_name": "pead_midcap_step1_fixedr", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 1: R:R fix. Switch to fixed_r target_1_r=2.0 (target=2x stop). Breakeven WR drops from 67% to 33%. Baseline uses atr_multiple 1.5 ATR target vs 3.0 ATR stop.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "execution": { + "target_model": "fixed_r", + "target_1_r": 2.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "7pct", + "longshort", + "midcap", + "step1", + "fixedr" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/metadata.json new file mode 100644 index 0000000..0eb9e52 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214", + "started_at": "2026-03-16T19:18:41.589787+00:00", + "finished_at": "2026-03-16T19:18:41.708505+00:00", + "elapsed_seconds": 0.118718, + "git_commit_hash": "1916533", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 48 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..d2e2a6c --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,69,34,0.4927536231884058,2767.9468,0.081 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..09fb446 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,69,34,0.4927536231884058,2767.9468,0.081 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/metrics/metrics_summary.json new file mode 100644 index 0000000..60d60a1 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 69, + "win_rate": 0.4927536231884058, + "avg_win_pct": 0.0705385285020944, + "avg_loss_pct": -0.02959772175819073, + "profit_factor": 1.7731598619764342, + "expectancy_r": 0.08100723503593749, + "avg_r_multiple": 0.08100723503593749, + "total_return_pct": 2.779776127733232, + "annualized_return_pct": 13.160474379963262, + "max_drawdown_pct": 1.5546437384924012, + "calmar_ratio": 8.465267028139506, + "sharpe_ratio": 3.3855945499328657, + "sortino_ratio": 6.6367909328671955, + "avg_daily_pnl": 50.541384140604215, + "avg_positions_held": 4.535714285714286, + "trade_skewness": 0.9979266055566872, + "trade_kurtosis": 1.59397976063382, + "monthly_win_rate": 0.5, + "equity_curve_r_squared": 0.46467275010982595, + "avg_holding_days": 5.594202898550725, + "stop_exit_rate": 0.9420289855072463, + "target_exit_rate": 0.0, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.5106382978723404, + "medium_high": 0.5, + "medium": 0.25 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.37681159420289856, + 0.5942028985507246 + ], + "avg_win_pct_ci_95": [ + 0.05244889673669019, + 0.0899563974141387 + ], + "avg_loss_pct_ci_95": [ + -0.035323890831765646, + -0.024112353254257708 + ], + "profit_factor_ci_95": [ + 0.9585233670149906, + 3.1034580745508533 + ], + "expectancy_r_ci_95": [ + -0.00335982393659076, + 0.16120492330084626 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/metrics/score_bucket_report.csv new file mode 100644 index 0000000..cd0140f --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,47,0.5106 +medium,4,0.25 +medium_high,18,0.5 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/resolved_config.json new file mode 100644 index 0000000..c8ba855 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191841_e0902214/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 8, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "fixed_r", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..b83e2e6 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/artifacts/position_timeline.parquet new file mode 100644 index 0000000..4b201b5 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..602b5d5 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/manifest.json new file mode 100644 index 0000000..b2695a9 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/manifest.json @@ -0,0 +1,60 @@ +{ + "experiment_name": "pead_midcap_step1_fixedr", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 1: R:R fix. Switch to fixed_r target_1_r=2.0 (target=2x stop). Breakeven WR drops from 67% to 33%. Baseline uses atr_multiple 1.5 ATR target vs 3.0 ATR stop.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "execution": { + "target_model": "fixed_r", + "target_1_r": 2.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "7pct", + "longshort", + "midcap", + "step1", + "fixedr" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/metadata.json new file mode 100644 index 0000000..775d9a7 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214", + "started_at": "2026-03-16T19:18:47.418894+00:00", + "finished_at": "2026-03-16T19:18:47.551905+00:00", + "elapsed_seconds": 0.133011, + "git_commit_hash": "1916533", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 59 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..b3a6390 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,95,45,0.47368421052631576,-469.5186,-0.0076 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..b9d118b --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,95,45,0.47368421052631576,-469.5186,-0.0076 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/metrics/metrics_summary.json new file mode 100644 index 0000000..0f009d6 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 95, + "win_rate": 0.47368421052631576, + "avg_win_pct": 0.05027045914891779, + "avg_loss_pct": -0.037906907928045, + "profit_factor": 0.9135744830352539, + "expectancy_r": -0.007565616276669523, + "avg_r_multiple": -0.007565616276669523, + "total_return_pct": -0.4557621829436975, + "annualized_return_pct": -2.4963083484986215, + "max_drawdown_pct": 1.4922738831283402, + "calmar_ratio": -1.672821843712406, + "sharpe_ratio": -0.690798512095932, + "sortino_ratio": -1.1225802085206673, + "avg_daily_pnl": -10.128048509859946, + "avg_positions_held": 5.434782608695652, + "trade_skewness": 0.5211440672868409, + "trade_kurtosis": 0.16986826133042277, + "monthly_win_rate": 0.3333333333333333, + "equity_curve_r_squared": 0.53626294582183, + "avg_holding_days": 3.9368421052631577, + "stop_exit_rate": 0.9473684210526315, + "target_exit_rate": 0.0, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.4852941176470588, + "medium_high": 0.5, + "medium": 0.2 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.3684210526315789, + 0.5789473684210527 + ], + "avg_win_pct_ci_95": [ + 0.03860971281051395, + 0.06391002493483054 + ], + "avg_loss_pct_ci_95": [ + -0.0423109939044087, + -0.03298196495559431 + ], + "profit_factor_ci_95": [ + 0.5472176035918008, + 1.4904088830131075 + ], + "expectancy_r_ci_95": [ + -0.059352872324749215, + 0.048288288854503474 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/metrics/score_bucket_report.csv new file mode 100644 index 0000000..bfc5f34 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,68,0.4853 +medium,5,0.2 +medium_high,22,0.5 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/resolved_config.json new file mode 100644 index 0000000..c8ba855 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191847_e0902214/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 8, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "fixed_r", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..71e8382 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/artifacts/position_timeline.parquet new file mode 100644 index 0000000..d3e9ab5 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..0129378 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/manifest.json new file mode 100644 index 0000000..82bd61d --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step2_notrail", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 2: Disable trailing stop. Baseline pct_5 trail may be too tight for midcap volatility, cutting PEAD drift early.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "execution": { + "trailing_model": null + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "7pct", + "longshort", + "midcap", + "step2", + "notrail" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/metadata.json new file mode 100644 index 0000000..1178da6 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963", + "started_at": "2026-03-16T19:18:57.738624+00:00", + "finished_at": "2026-03-16T19:18:57.932270+00:00", + "elapsed_seconds": 0.193646, + "git_commit_hash": "1916533", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 581 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..f7053af --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,456,300,0.6578947368421053,-7109.6627,0.1073 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..5431b15 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,456,300,0.6578947368421053,-7109.6627,0.1073 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/metrics/metrics_summary.json new file mode 100644 index 0000000..4331f3d --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 456, + "win_rate": 0.6578947368421053, + "avg_win_pct": 0.07221899972272196, + "avg_loss_pct": -0.08687275453313756, + "profit_factor": 0.8306562780589708, + "expectancy_r": 0.10732449817480527, + "avg_r_multiple": 0.10732449817480527, + "total_return_pct": -7.062952270161506, + "annualized_return_pct": -1.9796762120900757, + "max_drawdown_pct": 11.950419851957642, + "calmar_ratio": -0.16565746112809399, + "sharpe_ratio": -0.696356079596736, + "sortino_ratio": -0.8316900926636784, + "avg_daily_pnl": -7.668786395397944, + "avg_positions_held": 3.4468546637744035, + "trade_skewness": -1.3825488928393401, + "trade_kurtosis": 0.4364410368940028, + "monthly_win_rate": 0.525, + "equity_curve_r_squared": 0.07229984947776134, + "avg_holding_days": 12.760964912280702, + "stop_exit_rate": 0.14473684210526316, + "target_exit_rate": 0.5679824561403509, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "medium_high": 0.5966386554621849, + "high": 0.6592356687898089, + "medium": 0.9565217391304348 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.6140350877192983, + 0.6995614035087719 + ], + "avg_win_pct_ci_95": [ + 0.06901912855571508, + 0.07554512147726788 + ], + "avg_loss_pct_ci_95": [ + -0.09845938001722145, + -0.07636280842809136 + ], + "profit_factor_ci_95": [ + 0.668084960340011, + 1.0360032177902063 + ], + "expectancy_r_ci_95": [ + 0.05934930903010344, + 0.15323209789019018 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/metrics/score_bucket_report.csv new file mode 100644 index 0000000..e65e7ed --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,314,0.6592 +medium,23,0.9565 +medium_high,119,0.5966 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/resolved_config.json new file mode 100644 index 0000000..057b913 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191857_52e64963/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 8, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": null, + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..716334d Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/artifacts/position_timeline.parquet new file mode 100644 index 0000000..017dcb5 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..ceb7982 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/manifest.json new file mode 100644 index 0000000..82bd61d --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step2_notrail", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 2: Disable trailing stop. Baseline pct_5 trail may be too tight for midcap volatility, cutting PEAD drift early.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "execution": { + "trailing_model": null + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "7pct", + "longshort", + "midcap", + "step2", + "notrail" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/metadata.json new file mode 100644 index 0000000..204c57b --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963", + "started_at": "2026-03-16T19:19:04.723634+00:00", + "finished_at": "2026-03-16T19:19:04.855803+00:00", + "elapsed_seconds": 0.132169, + "git_commit_hash": "1916533", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 76 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..76a9152 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,62,45,0.7258064516129032,355.0954,0.169 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..b2b9413 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,62,45,0.7258064516129032,355.0954,0.169 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/metrics/metrics_summary.json new file mode 100644 index 0000000..7fc521b --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 62, + "win_rate": 0.7258064516129032, + "avg_win_pct": 0.08272636285539206, + "avg_loss_pct": -0.09470492230056836, + "profit_factor": 1.073493344735863, + "expectancy_r": 0.16896130763575995, + "avg_r_multiple": 0.16896130763575995, + "total_return_pct": 0.3745287237202429, + "annualized_return_pct": 1.6999803458148444, + "max_drawdown_pct": 1.7163906170323242, + "calmar_ratio": 0.9904390812588725, + "sharpe_ratio": 0.39419762465723635, + "sortino_ratio": 0.5521198487943033, + "avg_daily_pnl": 6.80961315854987, + "avg_positions_held": 6.517857142857143, + "trade_skewness": -1.6579431679070804, + "trade_kurtosis": 1.348948084885921, + "monthly_win_rate": 0.25, + "equity_curve_r_squared": 0.32372614376351805, + "avg_holding_days": 11.080645161290322, + "stop_exit_rate": 0.11290322580645161, + "target_exit_rate": 0.6129032258064516, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.75, + "medium_high": 0.6842105263157895, + "medium": 0.6666666666666666 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.6129032258064516, + 0.8387096774193549 + ], + "avg_win_pct_ci_95": [ + 0.07131661243171639, + 0.09386392667983862 + ], + "avg_loss_pct_ci_95": [ + -0.13640431538252842, + -0.062440603571405684 + ], + "profit_factor_ci_95": [ + 0.5782213974135565, + 2.2278462086707758 + ], + "expectancy_r_ci_95": [ + 0.0302568256314396, + 0.28676203943550277 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/metrics/score_bucket_report.csv new file mode 100644 index 0000000..8ae25da --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,40,0.75 +medium,3,0.6667 +medium_high,19,0.6842 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/resolved_config.json new file mode 100644 index 0000000..057b913 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191904_52e64963/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 8, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": null, + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..ec10c2b Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/artifacts/position_timeline.parquet new file mode 100644 index 0000000..89aad33 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..6ac4c98 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/manifest.json new file mode 100644 index 0000000..82bd61d --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step2_notrail", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 2: Disable trailing stop. Baseline pct_5 trail may be too tight for midcap volatility, cutting PEAD drift early.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "execution": { + "trailing_model": null + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "7pct", + "longshort", + "midcap", + "step2", + "notrail" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/metadata.json new file mode 100644 index 0000000..d524aa9 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963", + "started_at": "2026-03-16T19:19:11.024278+00:00", + "finished_at": "2026-03-16T19:19:11.157053+00:00", + "elapsed_seconds": 0.132775, + "git_commit_hash": "1916533", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 117 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..c6faef4 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,54,36,0.6666666666666666,-296.6378,0.1324 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..12f0817 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,54,36,0.6666666666666666,-296.6378,0.1324 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/metrics/metrics_summary.json new file mode 100644 index 0000000..377e568 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 54, + "win_rate": 0.6666666666666666, + "avg_win_pct": 0.08431526871412863, + "avg_loss_pct": -0.09194941142249424, + "profit_factor": 0.9313134618740863, + "expectancy_r": 0.13243954144621284, + "avg_r_multiple": 0.13243954144621284, + "total_return_pct": -0.26847486723051406, + "annualized_return_pct": -1.4767498477639363, + "max_drawdown_pct": 1.7431414336132605, + "calmar_ratio": -0.8471772968547159, + "sharpe_ratio": -0.366331653948068, + "sortino_ratio": -0.6794717087903452, + "avg_daily_pnl": -5.966108160678091, + "avg_positions_held": 6.913043478260869, + "trade_skewness": -1.5934930910395666, + "trade_kurtosis": 1.1808093064068452, + "monthly_win_rate": 0.3333333333333333, + "equity_curve_r_squared": 0.27335022008413906, + "avg_holding_days": 10.925925925925926, + "stop_exit_rate": 0.12962962962962962, + "target_exit_rate": 0.5555555555555556, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.625, + "medium_high": 0.7777777777777778, + "medium": 0.5 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.5370370370370371, + 0.7777777777777778 + ], + "avg_win_pct_ci_95": [ + 0.07177529981219137, + 0.09785904679497576 + ], + "avg_loss_pct_ci_95": [ + -0.13733849671181633, + -0.05111155143311817 + ], + "profit_factor_ci_95": [ + 0.4969521528770112, + 2.0744990537123065 + ], + "expectancy_r_ci_95": [ + -0.0049948755467955465, + 0.25492266208542025 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/metrics/score_bucket_report.csv new file mode 100644 index 0000000..458f695 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,32,0.625 +medium,4,0.5 +medium_high,18,0.7778 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/resolved_config.json new file mode 100644 index 0000000..057b913 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191911_52e64963/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 8, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": null, + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..b1b62b5 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/artifacts/position_timeline.parquet new file mode 100644 index 0000000..31d5954 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..65fb6b1 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/manifest.json new file mode 100644 index 0000000..c8304a9 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/manifest.json @@ -0,0 +1,56 @@ +{ + "experiment_name": "pead_midcap_step3_10pct", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 3: Raise reaction threshold to 10%. Stronger signal filter, fewer trades, higher quality expected.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "10pct", + "longshort", + "midcap", + "step3", + "threshold" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/metadata.json new file mode 100644 index 0000000..df8426b --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3", + "started_at": "2026-03-16T19:19:21.337931+00:00", + "finished_at": "2026-03-16T19:19:21.512138+00:00", + "elapsed_seconds": 0.174207, + "git_commit_hash": "1916533", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 210 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..9c3e1cc --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,554,277,0.5,-3289.2983,0.0686 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..cbec4a0 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,554,277,0.5,-3289.2983,0.0686 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/metrics/metrics_summary.json new file mode 100644 index 0000000..253a958 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 554, + "win_rate": 0.5, + "avg_win_pct": 0.06653511294399073, + "avg_loss_pct": -0.036788277181761024, + "profit_factor": 0.8983598002644894, + "expectancy_r": 0.06855646204545317, + "avg_r_multiple": 0.06855646204545317, + "total_return_pct": -3.280603570163337, + "annualized_return_pct": -0.9064318880483535, + "max_drawdown_pct": 6.615859124596835, + "calmar_ratio": -0.13700894637831196, + "sharpe_ratio": -0.410178158923494, + "sortino_ratio": -0.38272431203418716, + "avg_daily_pnl": -3.562001704846186, + "avg_positions_held": 1.6041214750542299, + "trade_skewness": -0.611676726139368, + "trade_kurtosis": 0.3676409186844394, + "monthly_win_rate": 0.42857142857142855, + "equity_curve_r_squared": 0.3246242852362157, + "avg_holding_days": 4.617328519855596, + "stop_exit_rate": 0.7093862815884476, + "target_exit_rate": 0.2779783393501805, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.5155875299760192, + "medium_high": 0.45112781954887216, + "medium": 0.5 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4602888086642599, + 0.5415162454873647 + ], + "avg_win_pct_ci_95": [ + 0.061521758460766525, + 0.07240362117477504 + ], + "avg_loss_pct_ci_95": [ + -0.0399570895830492, + -0.03396784885309128 + ], + "profit_factor_ci_95": [ + 0.7425336522571615, + 1.0895845581484322 + ], + "expectancy_r_ci_95": [ + 0.040301681151920746, + 0.09735070954219772 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/metrics/score_bucket_report.csv new file mode 100644 index 0000000..04311e0 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,417,0.5156 +medium,4,0.5 +medium_high,133,0.4511 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/resolved_config.json new file mode 100644 index 0000000..798947e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191921_2dd6b0b3/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 8, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..2ae0519 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/artifacts/position_timeline.parquet new file mode 100644 index 0000000..2ce4f59 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..edbe984 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/manifest.json new file mode 100644 index 0000000..c8304a9 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/manifest.json @@ -0,0 +1,56 @@ +{ + "experiment_name": "pead_midcap_step3_10pct", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 3: Raise reaction threshold to 10%. Stronger signal filter, fewer trades, higher quality expected.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "10pct", + "longshort", + "midcap", + "step3", + "threshold" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/metadata.json new file mode 100644 index 0000000..0bf7a80 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3", + "started_at": "2026-03-16T19:19:28.554457+00:00", + "finished_at": "2026-03-16T19:19:28.686290+00:00", + "elapsed_seconds": 0.131833, + "git_commit_hash": "1916533", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 36 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..8fed4c9 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,78,47,0.6025641025641025,1957.3237,0.1518 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..4daac26 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,78,47,0.6025641025641025,1957.3237,0.1518 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/metrics/metrics_summary.json new file mode 100644 index 0000000..a35d720 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 78, + "win_rate": 0.6025641025641025, + "avg_win_pct": 0.07397416975842343, + "avg_loss_pct": -0.03094295442082105, + "profit_factor": 1.664496799271172, + "expectancy_r": 0.15179363046061833, + "avg_r_multiple": 0.15179363046061833, + "total_return_pct": 1.9591117241190805, + "annualized_return_pct": 9.142830700630444, + "max_drawdown_pct": 0.6538050285655718, + "calmar_ratio": 13.984032396767482, + "sharpe_ratio": 2.907578829549799, + "sortino_ratio": 4.9577268450346486, + "avg_daily_pnl": 35.62021316580147, + "avg_positions_held": 3.357142857142857, + "trade_skewness": -0.6044342939440066, + "trade_kurtosis": -0.7144311848319411, + "monthly_win_rate": 0.75, + "equity_curve_r_squared": 0.6968678861478663, + "avg_holding_days": 4.987179487179487, + "stop_exit_rate": 0.7051282051282052, + "target_exit_rate": 0.28205128205128205, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.5454545454545454, + "medium_high": 0.7272727272727273, + "medium": 1.0 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.5, + 0.717948717948718 + ], + "avg_win_pct_ci_95": [ + 0.05877636930843529, + 0.09100981577426039 + ], + "avg_loss_pct_ci_95": [ + -0.03681695613021674, + -0.025087981260142875 + ], + "profit_factor_ci_95": [ + 1.0184271383563828, + 2.9391110981649113 + ], + "expectancy_r_ci_95": [ + 0.08084121300536487, + 0.2236793809275467 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/metrics/score_bucket_report.csv new file mode 100644 index 0000000..f78412c --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,55,0.5455 +medium,1,1.0 +medium_high,22,0.7273 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/resolved_config.json new file mode 100644 index 0000000..798947e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191928_2dd6b0b3/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 8, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..c522933 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/artifacts/position_timeline.parquet new file mode 100644 index 0000000..d3d9a8d Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..2aa3731 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/manifest.json new file mode 100644 index 0000000..c8304a9 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/manifest.json @@ -0,0 +1,56 @@ +{ + "experiment_name": "pead_midcap_step3_10pct", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 3: Raise reaction threshold to 10%. Stronger signal filter, fewer trades, higher quality expected.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "10pct", + "longshort", + "midcap", + "step3", + "threshold" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/metadata.json new file mode 100644 index 0000000..142def7 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3", + "started_at": "2026-03-16T19:19:35.285608+00:00", + "finished_at": "2026-03-16T19:19:35.417268+00:00", + "elapsed_seconds": 0.13166, + "git_commit_hash": "1916533", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 34 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..c010771 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,98,49,0.5,10.4075,0.0556 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..8d903fe --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,98,49,0.5,10.4075,0.0556 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/metrics/metrics_summary.json new file mode 100644 index 0000000..6682f36 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 98, + "win_rate": 0.5, + "avg_win_pct": 0.07311747830324754, + "avg_loss_pct": -0.03870820350316749, + "profit_factor": 1.0020701077990406, + "expectancy_r": 0.05564085840449998, + "avg_r_multiple": 0.05564085840449998, + "total_return_pct": 0.013499456389559782, + "annualized_return_pct": 0.07473008582785656, + "max_drawdown_pct": 1.4225278540108528, + "calmar_ratio": 0.0525333023301816, + "sharpe_ratio": 0.03939447191001189, + "sortino_ratio": 0.06808857288496131, + "avg_daily_pnl": 0.29998791976799516, + "avg_positions_held": 4.369565217391305, + "trade_skewness": 0.12290927173343433, + "trade_kurtosis": -0.5075292246794811, + "monthly_win_rate": 0.6666666666666666, + "equity_curve_r_squared": 0.0029903324045531966, + "avg_holding_days": 3.3877551020408165, + "stop_exit_rate": 0.8469387755102041, + "target_exit_rate": 0.1326530612244898, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "medium_high": 0.46153846153846156, + "high": 0.5362318840579711, + "medium": 0.0 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.3979591836734694, + 0.6122448979591837 + ], + "avg_win_pct_ci_95": [ + 0.05835223980837764, + 0.09076052272181616 + ], + "avg_loss_pct_ci_95": [ + -0.043469053332429126, + -0.03382516563412737 + ], + "profit_factor_ci_95": [ + 0.6242138655664322, + 1.6407858806851663 + ], + "expectancy_r_ci_95": [ + -0.003706576443546997, + 0.1195609609198027 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/metrics/score_bucket_report.csv new file mode 100644 index 0000000..b15d652 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,69,0.5362 +medium,3,0.0 +medium_high,26,0.4615 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/resolved_config.json new file mode 100644 index 0000000..798947e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191935_2dd6b0b3/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 8, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..a498329 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/artifacts/position_timeline.parquet new file mode 100644 index 0000000..8554e20 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..49c38b0 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/manifest.json new file mode 100644 index 0000000..4b9a006 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/manifest.json @@ -0,0 +1,56 @@ +{ + "experiment_name": "pead_midcap_step4_longonly", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 4: Long-only. Disable short side to test if midcap short PEAD mean-reverts instead of drifting.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "bullish_only" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "7pct", + "longonly", + "midcap", + "step4", + "bullish" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/metadata.json new file mode 100644 index 0000000..7397d56 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0", + "started_at": "2026-03-16T19:19:45.581217+00:00", + "finished_at": "2026-03-16T19:19:45.760080+00:00", + "elapsed_seconds": 0.178863, + "git_commit_hash": "1916533", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 520 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..d8e2fae --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,486,264,0.5432098765432098,-2280.7432,0.0942 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..6abf1e0 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,486,264,0.5432098765432098,-2280.7432,0.0942 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/metrics/metrics_summary.json new file mode 100644 index 0000000..f7df7fd --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 486, + "win_rate": 0.5432098765432098, + "avg_win_pct": 0.05838168898066179, + "avg_loss_pct": -0.03689124896673376, + "profit_factor": 0.924546532180125, + "expectancy_r": 0.09423870440102407, + "avg_r_multiple": 0.09423870440102407, + "total_return_pct": -2.2530301807158395, + "annualized_return_pct": -0.6201403100610792, + "max_drawdown_pct": 6.787125867039724, + "calmar_ratio": -0.09137009128895968, + "sharpe_ratio": -0.20236278759112156, + "sortino_ratio": -0.18052072044143347, + "avg_daily_pnl": -2.446286841168121, + "avg_positions_held": 1.4349240780911063, + "trade_skewness": -0.7353804361455075, + "trade_kurtosis": -0.0951503294970304, + "monthly_win_rate": 0.46153846153846156, + "equity_curve_r_squared": 0.1343287887075526, + "avg_holding_days": 5.07201646090535, + "stop_exit_rate": 0.5925925925925926, + "target_exit_rate": 0.38271604938271603, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "medium_high": 0.5111111111111111, + "high": 0.5443425076452599, + "medium": 0.7083333333333334 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.49794238683127573, + 0.5864197530864198 + ], + "avg_win_pct_ci_95": [ + 0.0540886613777028, + 0.06263019352014379 + ], + "avg_loss_pct_ci_95": [ + -0.04066115719730869, + -0.034076174363228226 + ], + "profit_factor_ci_95": [ + 0.7535562725384669, + 1.1253924639572563 + ], + "expectancy_r_ci_95": [ + 0.05811324621087071, + 0.1271749743026268 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/metrics/score_bucket_report.csv new file mode 100644 index 0000000..4c50af3 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,327,0.5443 +medium,24,0.7083 +medium_high,135,0.5111 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/resolved_config.json new file mode 100644 index 0000000..8c63085 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191945_bb0ddfa0/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 8, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "bullish_only" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..614a541 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/artifacts/position_timeline.parquet new file mode 100644 index 0000000..2900f82 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..aff9758 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/manifest.json new file mode 100644 index 0000000..4b9a006 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/manifest.json @@ -0,0 +1,56 @@ +{ + "experiment_name": "pead_midcap_step4_longonly", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 4: Long-only. Disable short side to test if midcap short PEAD mean-reverts instead of drifting.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "bullish_only" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "7pct", + "longonly", + "midcap", + "step4", + "bullish" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/metadata.json new file mode 100644 index 0000000..856aef8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0", + "started_at": "2026-03-16T19:19:52.645286+00:00", + "finished_at": "2026-03-16T19:19:52.777590+00:00", + "elapsed_seconds": 0.132304, + "git_commit_hash": "1916533", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 60 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..33f53d4 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,76,46,0.6052631578947368,1492.0926,0.1575 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..bf2c36e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,76,46,0.6052631578947368,1492.0926,0.1575 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/metrics/metrics_summary.json new file mode 100644 index 0000000..ade9c25 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 76, + "win_rate": 0.6052631578947368, + "avg_win_pct": 0.06839185067864997, + "avg_loss_pct": -0.030790687304538534, + "profit_factor": 1.430929775113959, + "expectancy_r": 0.1574651292704541, + "avg_r_multiple": 0.1574651292704541, + "total_return_pct": 1.4935870708133152, + "annualized_return_pct": 6.913691627005503, + "max_drawdown_pct": 1.597646524797458, + "calmar_ratio": 4.327422567943799, + "sharpe_ratio": 1.6551004874178592, + "sortino_ratio": 2.5518947418510423, + "avg_daily_pnl": 27.156128560242095, + "avg_positions_held": 3.0892857142857144, + "trade_skewness": -0.765151770495949, + "trade_kurtosis": -0.7611970270906343, + "monthly_win_rate": 0.5, + "equity_curve_r_squared": 0.6129327020059054, + "avg_holding_days": 4.4868421052631575, + "stop_exit_rate": 0.5921052631578947, + "target_exit_rate": 0.3815789473684211, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.5918367346938775, + "medium_high": 0.6086956521739131, + "medium": 0.75 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.5, + 0.7105263157894737 + ], + "avg_win_pct_ci_95": [ + 0.05668743027243357, + 0.08239031126365184 + ], + "avg_loss_pct_ci_95": [ + -0.03658125054992014, + -0.024506963248520423 + ], + "profit_factor_ci_95": [ + 0.8896362102228705, + 2.508195049355009 + ], + "expectancy_r_ci_95": [ + 0.08391645687861664, + 0.23555254163055978 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/metrics/score_bucket_report.csv new file mode 100644 index 0000000..f973084 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,49,0.5918 +medium,4,0.75 +medium_high,23,0.6087 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/resolved_config.json new file mode 100644 index 0000000..8c63085 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191952_bb0ddfa0/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 8, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "bullish_only" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..eb36e63 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/artifacts/position_timeline.parquet new file mode 100644 index 0000000..2a57696 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..9977a87 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/manifest.json new file mode 100644 index 0000000..4b9a006 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/manifest.json @@ -0,0 +1,56 @@ +{ + "experiment_name": "pead_midcap_step4_longonly", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 4: Long-only. Disable short side to test if midcap short PEAD mean-reverts instead of drifting.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "bullish_only" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "7pct", + "longonly", + "midcap", + "step4", + "bullish" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/metadata.json new file mode 100644 index 0000000..b1f337e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0", + "started_at": "2026-03-16T19:19:59.746692+00:00", + "finished_at": "2026-03-16T19:19:59.874726+00:00", + "elapsed_seconds": 0.128034, + "git_commit_hash": "1916533", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 89 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..534b95f --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,78,35,0.44871794871794873,-808.8267,0.0619 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..16cb968 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,78,35,0.44871794871794873,-808.8267,0.0619 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/metrics/metrics_summary.json new file mode 100644 index 0000000..8bb71b0 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 78, + "win_rate": 0.44871794871794873, + "avg_win_pct": 0.08345425466654806, + "avg_loss_pct": -0.036209070635147736, + "profit_factor": 0.8355717037294544, + "expectancy_r": 0.06187463213456239, + "avg_r_multiple": 0.06187463213456239, + "total_return_pct": -0.8088267292581296, + "annualized_return_pct": -4.39482244277497, + "max_drawdown_pct": 1.4401741814514755, + "calmar_ratio": -3.0515909112782875, + "sharpe_ratio": -1.1731301213701413, + "sortino_ratio": -1.7920294122626035, + "avg_daily_pnl": -17.973927316847323, + "avg_positions_held": 3.8260869565217392, + "trade_skewness": 0.15483958871188747, + "trade_kurtosis": -0.6226190142865606, + "monthly_win_rate": 0.3333333333333333, + "equity_curve_r_squared": 0.3740006709035379, + "avg_holding_days": 4.089743589743589, + "stop_exit_rate": 0.7435897435897436, + "target_exit_rate": 0.24358974358974358, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.391304347826087, + "medium_high": 0.5862068965517241, + "medium": 0.0 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.34615384615384615, + 0.5641025641025641 + ], + "avg_win_pct_ci_95": [ + 0.06351933066117077, + 0.10625319726973598 + ], + "avg_loss_pct_ci_95": [ + -0.04113616500813396, + -0.031032615182228555 + ], + "profit_factor_ci_95": [ + 0.49832355850400134, + 1.3927412123357568 + ], + "expectancy_r_ci_95": [ + -0.017828101043777963, + 0.13824612776162956 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/metrics/score_bucket_report.csv new file mode 100644 index 0000000..441627e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,46,0.3913 +medium,3,0.0 +medium_high,29,0.5862 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/resolved_config.json new file mode 100644 index 0000000..8c63085 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316191959_bb0ddfa0/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 8, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "bullish_only" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..5d70499 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/artifacts/position_timeline.parquet new file mode 100644 index 0000000..c308642 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..0cc935d Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/manifest.json new file mode 100644 index 0000000..ec88c45 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/manifest.json @@ -0,0 +1,56 @@ +{ + "experiment_name": "pead_midcap_step5_maxcand3", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 5: Limit to 3 candidates per day. Remove lower-ranked signals, keep only top entries.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "7pct", + "longshort", + "midcap", + "step5", + "maxcand3" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/metadata.json new file mode 100644 index 0000000..5e95bc6 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf", + "started_at": "2026-03-16T19:20:09.877940+00:00", + "finished_at": "2026-03-16T19:20:10.080423+00:00", + "elapsed_seconds": 0.202483, + "git_commit_hash": "1916533", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 35 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..4117496 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,667,342,0.512743628185907,-4118.3303,0.0677 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..b98b2f1 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,667,342,0.512743628185907,-4118.3303,0.0677 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/metrics/metrics_summary.json new file mode 100644 index 0000000..b2651f8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 667, + "win_rate": 0.512743628185907, + "avg_win_pct": 0.06019633258304865, + "avg_loss_pct": -0.03495329536332582, + "profit_factor": 0.8936008117413707, + "expectancy_r": 0.06773125123949603, + "avg_r_multiple": 0.06773125123949603, + "total_return_pct": -4.084212058311663, + "annualized_return_pct": -1.1318695733611217, + "max_drawdown_pct": 6.529337814573298, + "calmar_ratio": -0.17335135744314237, + "sharpe_ratio": -0.46728383699893133, + "sortino_ratio": -0.49731629053668736, + "avg_daily_pnl": -4.434540779925801, + "avg_positions_held": 2.0455531453362257, + "trade_skewness": -0.6058028923831786, + "trade_kurtosis": 0.046428327907105604, + "monthly_win_rate": 0.358974358974359, + "equity_curve_r_squared": 0.5365488680633558, + "avg_holding_days": 4.871064467766117, + "stop_exit_rate": 0.6956521739130435, + "target_exit_rate": 0.28335832083958024, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "medium_high": 0.4713375796178344, + "high": 0.5144628099173554, + "medium": 0.7307692307692307 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4782608695652174, + 0.5502248875562219 + ], + "avg_win_pct_ci_95": [ + 0.05590125359606896, + 0.06499611939131857 + ], + "avg_loss_pct_ci_95": [ + -0.03729255728959693, + -0.03276800947618573 + ], + "profit_factor_ci_95": [ + 0.7622221082144762, + 1.061727989278031 + ], + "expectancy_r_ci_95": [ + 0.04373553411139082, + 0.0940832067959831 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/metrics/score_bucket_report.csv new file mode 100644 index 0000000..a8b3544 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,484,0.5145 +medium,26,0.7308 +medium_high,157,0.4713 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/resolved_config.json new file mode 100644 index 0000000..1513bf2 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192009_4f4f05bf/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..e2f6392 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/artifacts/position_timeline.parquet new file mode 100644 index 0000000..e598d9d Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..62787f9 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/manifest.json new file mode 100644 index 0000000..ec88c45 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/manifest.json @@ -0,0 +1,56 @@ +{ + "experiment_name": "pead_midcap_step5_maxcand3", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 5: Limit to 3 candidates per day. Remove lower-ranked signals, keep only top entries.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "7pct", + "longshort", + "midcap", + "step5", + "maxcand3" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/metadata.json new file mode 100644 index 0000000..f5a323b --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf", + "started_at": "2026-03-16T19:20:17.132890+00:00", + "finished_at": "2026-03-16T19:20:17.265337+00:00", + "elapsed_seconds": 0.132447, + "git_commit_hash": "1916533", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 4 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..9475975 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,89,58,0.651685393258427,3317.6421,0.1882 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..e04606e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,89,58,0.651685393258427,3317.6421,0.1882 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/metrics/metrics_summary.json new file mode 100644 index 0000000..8fd5682 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 89, + "win_rate": 0.651685393258427, + "avg_win_pct": 0.07044162829934004, + "avg_loss_pct": -0.0292848211302031, + "profit_factor": 2.081614051785466, + "expectancy_r": 0.18816677840250112, + "avg_r_multiple": 0.18816677840250112, + "total_return_pct": 3.3263332066270377, + "annualized_return_pct": 15.899393379843453, + "max_drawdown_pct": 1.1046989753464673, + "calmar_ratio": 14.392512109334506, + "sharpe_ratio": 3.9774346553681674, + "sortino_ratio": 8.028296592487237, + "avg_daily_pnl": 60.47878557503705, + "avg_positions_held": 3.875, + "trade_skewness": -0.854936059752067, + "trade_kurtosis": -0.2709802016570486, + "monthly_win_rate": 0.75, + "equity_curve_r_squared": 0.5734715756129087, + "avg_holding_days": 4.831460674157303, + "stop_exit_rate": 0.6179775280898876, + "target_exit_rate": 0.34831460674157305, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.6065573770491803, + "medium_high": 0.782608695652174, + "medium": 0.6 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.550561797752809, + 0.7528089887640449 + ], + "avg_win_pct_ci_95": [ + 0.05797522868353516, + 0.08399307061108284 + ], + "avg_loss_pct_ci_95": [ + -0.035471524899722176, + -0.023373246901195844 + ], + "profit_factor_ci_95": [ + 1.2775877429141869, + 3.416893300239283 + ], + "expectancy_r_ci_95": [ + 0.11953212938390559, + 0.2491102909958885 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/metrics/score_bucket_report.csv new file mode 100644 index 0000000..ea8b335 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,61,0.6066 +medium,5,0.6 +medium_high,23,0.7826 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/resolved_config.json new file mode 100644 index 0000000..1513bf2 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192017_4f4f05bf/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..a4966b4 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/artifacts/position_timeline.parquet new file mode 100644 index 0000000..d7c0e3e Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..2559fb8 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/manifest.json new file mode 100644 index 0000000..ec88c45 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/manifest.json @@ -0,0 +1,56 @@ +{ + "experiment_name": "pead_midcap_step5_maxcand3", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 5: Limit to 3 candidates per day. Remove lower-ranked signals, keep only top entries.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "7pct", + "longshort", + "midcap", + "step5", + "maxcand3" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/metadata.json new file mode 100644 index 0000000..2e81a9e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf", + "started_at": "2026-03-16T19:20:23.532573+00:00", + "finished_at": "2026-03-16T19:20:23.661408+00:00", + "elapsed_seconds": 0.128835, + "git_commit_hash": "1916533", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 2 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..2a14239 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,96,52,0.5416666666666666,-249.6882,0.0526 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..bec085e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,96,52,0.5416666666666666,-249.6882,0.0526 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/metrics/metrics_summary.json new file mode 100644 index 0000000..0320535 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 96, + "win_rate": 0.5416666666666666, + "avg_win_pct": 0.0628055895178845, + "avg_loss_pct": -0.038946224618478215, + "profit_factor": 0.9504596545547419, + "expectancy_r": 0.0526469214433297, + "avg_r_multiple": 0.0526469214433297, + "total_return_pct": -0.2388243054896011, + "annualized_return_pct": -1.3145396314983016, + "max_drawdown_pct": 1.4011627353834164, + "calmar_ratio": -0.9381776993509529, + "sharpe_ratio": -0.4009079471879985, + "sortino_ratio": -0.8389633758840738, + "avg_daily_pnl": -5.307206788657802, + "avg_positions_held": 4.543478260869565, + "trade_skewness": -0.3059090090040871, + "trade_kurtosis": -1.065164975992197, + "monthly_win_rate": 0.6666666666666666, + "equity_curve_r_squared": 0.5009577858012723, + "avg_holding_days": 3.5416666666666665, + "stop_exit_rate": 0.8229166666666666, + "target_exit_rate": 0.13541666666666666, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.5441176470588235, + "medium_high": 0.5833333333333334, + "medium": 0.25 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4375, + 0.6354166666666666 + ], + "avg_win_pct_ci_95": [ + 0.04992688152784606, + 0.07664629713448276 + ], + "avg_loss_pct_ci_95": [ + -0.043282602608056026, + -0.0343044378639196 + ], + "profit_factor_ci_95": [ + 0.5992941030848132, + 1.460811659790241 + ], + "expectancy_r_ci_95": [ + -0.006853390341764055, + 0.1100058195046667 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/metrics/score_bucket_report.csv new file mode 100644 index 0000000..e416806 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,68,0.5441 +medium,4,0.25 +medium_high,24,0.5833 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/resolved_config.json new file mode 100644 index 0000000..1513bf2 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192023_4f4f05bf/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..ad329a2 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/artifacts/position_timeline.parquet new file mode 100644 index 0000000..52d0ad5 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..f487a28 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/manifest.json new file mode 100644 index 0000000..410db03 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/manifest.json @@ -0,0 +1,60 @@ +{ + "experiment_name": "pead_midcap_step6_drift", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 6: Extended hold 25 days + disable partial exit. Test if PEAD drift needs more time than 15 days in midcap.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "execution": { + "max_holding_days": 25, + "target_1_fraction": null + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "7pct", + "longshort", + "midcap", + "step6", + "drift25" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/metadata.json new file mode 100644 index 0000000..511e085 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568", + "started_at": "2026-03-16T19:20:33.633396+00:00", + "finished_at": "2026-03-16T19:20:33.821846+00:00", + "elapsed_seconds": 0.18845, + "git_commit_hash": "1916533", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 311 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..83b3ab3 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,586,242,0.4129692832764505,-3749.6937,-0.0101 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..7470ef0 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,586,242,0.4129692832764505,-3749.6937,-0.0101 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/metrics/metrics_summary.json new file mode 100644 index 0000000..bbd2e8a --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 586, + "win_rate": 0.4129692832764505, + "avg_win_pct": 0.05541411043293476, + "avg_loss_pct": -0.03607421603076796, + "profit_factor": 0.9126734877022061, + "expectancy_r": -0.010134131060411028, + "avg_r_multiple": -0.010134131060411028, + "total_return_pct": -3.711358688232649, + "annualized_return_pct": -1.0271022962128784, + "max_drawdown_pct": 6.665875438412343, + "calmar_ratio": -0.15408363173037484, + "sharpe_ratio": -0.39694526230308885, + "sortino_ratio": -0.39599578011460074, + "avg_daily_pnl": -4.029705416104939, + "avg_positions_held": 2.0748373101952278, + "trade_skewness": 0.14997459414263675, + "trade_kurtosis": -0.739782269423614, + "monthly_win_rate": 0.38461538461538464, + "equity_curve_r_squared": 0.33883652745194087, + "avg_holding_days": 4.738907849829351, + "stop_exit_rate": 0.7627986348122867, + "target_exit_rate": 0.21331058020477817, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "medium_high": 0.375, + "high": 0.41545893719806765, + "medium": 0.65 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.37372013651877134, + 0.45051194539249145 + ], + "avg_win_pct_ci_95": [ + 0.05081935639645947, + 0.06020358525449668 + ], + "avg_loss_pct_ci_95": [ + -0.03819111218354676, + -0.03399812811197436 + ], + "profit_factor_ci_95": [ + 0.7560146388931802, + 1.1087158270627258 + ], + "expectancy_r_ci_95": [ + -0.03562678092441662, + 0.01762102326858226 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/metrics/score_bucket_report.csv new file mode 100644 index 0000000..4602d10 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,414,0.4155 +medium,20,0.65 +medium_high,152,0.375 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/resolved_config.json new file mode 100644 index 0000000..5c9a1ba --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192033_be4b4568/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 8, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": null, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 25, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..3049a1b Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/artifacts/position_timeline.parquet new file mode 100644 index 0000000..b8ef424 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..953283a Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/manifest.json new file mode 100644 index 0000000..410db03 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/manifest.json @@ -0,0 +1,60 @@ +{ + "experiment_name": "pead_midcap_step6_drift", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 6: Extended hold 25 days + disable partial exit. Test if PEAD drift needs more time than 15 days in midcap.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "execution": { + "max_holding_days": 25, + "target_1_fraction": null + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "7pct", + "longshort", + "midcap", + "step6", + "drift25" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/metadata.json new file mode 100644 index 0000000..64121f9 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568", + "started_at": "2026-03-16T19:20:40.893273+00:00", + "finished_at": "2026-03-16T19:20:41.017289+00:00", + "elapsed_seconds": 0.124016, + "git_commit_hash": "1916533", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 42 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..f8d3893 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,75,38,0.5066666666666667,2659.9562,0.0718 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..40d2e7a --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,75,38,0.5066666666666667,2659.9562,0.0718 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/metrics/metrics_summary.json new file mode 100644 index 0000000..9c169ae --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 75, + "win_rate": 0.5066666666666667, + "avg_win_pct": 0.06254263285166468, + "avg_loss_pct": -0.03086834542664909, + "profit_factor": 1.704720436988778, + "expectancy_r": 0.0718119957275626, + "avg_r_multiple": 0.0718119957275626, + "total_return_pct": 2.6652714937057898, + "annualized_return_pct": 12.593104382912346, + "max_drawdown_pct": 1.0499246227859185, + "calmar_ratio": 11.994293789869621, + "sharpe_ratio": 3.7096311156929622, + "sortino_ratio": 6.919964657236709, + "avg_daily_pnl": 48.45948170374164, + "avg_positions_held": 3.875, + "trade_skewness": 0.10389738407097612, + "trade_kurtosis": -1.3196506179977516, + "monthly_win_rate": 0.5, + "equity_curve_r_squared": 0.5754494959825112, + "avg_holding_days": 4.426666666666667, + "stop_exit_rate": 0.7066666666666667, + "target_exit_rate": 0.28, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.46938775510204084, + "medium_high": 0.5714285714285714, + "medium": 0.6 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4, + 0.6266666666666667 + ], + "avg_win_pct_ci_95": [ + 0.04995816659926279, + 0.07572915148115296 + ], + "avg_loss_pct_ci_95": [ + -0.03627491810337894, + -0.025345318132306137 + ], + "profit_factor_ci_95": [ + 1.0245708482890747, + 3.048740635666616 + ], + "expectancy_r_ci_95": [ + 0.004590347011903175, + 0.14712030845342788 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/metrics/score_bucket_report.csv new file mode 100644 index 0000000..a7c5b77 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,49,0.4694 +medium,5,0.6 +medium_high,21,0.5714 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/resolved_config.json new file mode 100644 index 0000000..5c9a1ba --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192040_be4b4568/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 8, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": null, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 25, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..5dfb409 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/artifacts/position_timeline.parquet new file mode 100644 index 0000000..7a3277c Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..01152e2 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/manifest.json new file mode 100644 index 0000000..410db03 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/manifest.json @@ -0,0 +1,60 @@ +{ + "experiment_name": "pead_midcap_step6_drift", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 6: Extended hold 25 days + disable partial exit. Test if PEAD drift needs more time than 15 days in midcap.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 8 + }, + "execution": { + "max_holding_days": 25, + "target_1_fraction": null + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "7pct", + "longshort", + "midcap", + "step6", + "drift25" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/metadata.json new file mode 100644 index 0000000..d9b815c --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568", + "started_at": "2026-03-16T19:20:47.520110+00:00", + "finished_at": "2026-03-16T19:20:47.653225+00:00", + "elapsed_seconds": 0.133115, + "git_commit_hash": "1916533", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 54 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..0673bd0 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,100,43,0.43,-886.7783,-0.0155 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..9eae276 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,100,43,0.43,-886.7783,-0.0155 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/metrics/metrics_summary.json new file mode 100644 index 0000000..881f299 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 100, + "win_rate": 0.43, + "avg_win_pct": 0.056566459526807715, + "avg_loss_pct": -0.03765415827339769, + "profit_factor": 0.8557264506988863, + "expectancy_r": -0.01552826264308369, + "avg_r_multiple": -0.01552826264308369, + "total_return_pct": -0.8788682238009642, + "annualized_return_pct": -4.767827631464749, + "max_drawdown_pct": 1.7395188967538284, + "calmar_ratio": -2.740888667758737, + "sharpe_ratio": -1.5533516509762217, + "sortino_ratio": -2.895968661712348, + "avg_daily_pnl": -19.53040497335476, + "avg_positions_held": 5.1521739130434785, + "trade_skewness": 0.5191678841570022, + "trade_kurtosis": -0.34456023277563386, + "monthly_win_rate": 0.3333333333333333, + "equity_curve_r_squared": 0.7311683777861855, + "avg_holding_days": 3.54, + "stop_exit_rate": 0.85, + "target_exit_rate": 0.12, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.463768115942029, + "medium_high": 0.38461538461538464, + "medium": 0.2 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.34, + 0.52 + ], + "avg_win_pct_ci_95": [ + 0.04237759359604527, + 0.07339863131847742 + ], + "avg_loss_pct_ci_95": [ + -0.04215731929777798, + -0.03318783283566877 + ], + "profit_factor_ci_95": [ + 0.4988082134092214, + 1.3406360448670946 + ], + "expectancy_r_ci_95": [ + -0.0694034733676772, + 0.037103909248896234 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/metrics/score_bucket_report.csv new file mode 100644 index 0000000..c98ce1c --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,69,0.4638 +medium,5,0.2 +medium_high,26,0.3846 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/resolved_config.json new file mode 100644 index 0000000..5c9a1ba --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316192047_be4b4568/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 8, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": null, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 25, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..32ce926 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/artifacts/position_timeline.parquet new file mode 100644 index 0000000..bf01b3c Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..8d7bd0c Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/manifest.json new file mode 100644 index 0000000..5acbbe2 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/manifest.json @@ -0,0 +1,56 @@ +{ + "experiment_name": "pead_midcap_combo_10pct_maxcand3", + "dataset_snapshot_id": "midcap-filtered", + "description": "Combo: Step3 (10% threshold) + Step5 (max 3 candidates/day). Both are signal quality filters — strongest midcap PEAD signals only.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "10pct", + "longshort", + "midcap", + "combo", + "maxcand3" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/metadata.json new file mode 100644 index 0000000..8d7b474 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090", + "started_at": "2026-03-16T19:58:57.585843+00:00", + "finished_at": "2026-03-16T19:58:57.756622+00:00", + "elapsed_seconds": 0.170779, + "git_commit_hash": "1916533", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 19 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..83c4b4f --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,533,266,0.49906191369606,-2922.6041,0.0666 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..6d21cf1 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,533,266,0.49906191369606,-2922.6041,0.0666 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/metrics/metrics_summary.json new file mode 100644 index 0000000..f2e30e9 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 533, + "win_rate": 0.49906191369606, + "avg_win_pct": 0.06450254384731703, + "avg_loss_pct": -0.03576439852482536, + "profit_factor": 0.9034427861136265, + "expectancy_r": 0.06664732463405437, + "avg_r_multiple": 0.06664732463405437, + "total_return_pct": -2.91390932899194, + "annualized_return_pct": -0.8040147947431575, + "max_drawdown_pct": 5.821672597926446, + "calmar_ratio": -0.13810718160783042, + "sharpe_ratio": -0.3760744161321293, + "sortino_ratio": -0.37676838108689714, + "avg_daily_pnl": -3.163853777407101, + "avg_positions_held": 1.5206073752711498, + "trade_skewness": -0.5834949539740082, + "trade_kurtosis": 0.31880568262513487, + "monthly_win_rate": 0.45714285714285713, + "equity_curve_r_squared": 0.3049801722988121, + "avg_holding_days": 4.525328330206379, + "stop_exit_rate": 0.7148217636022514, + "target_exit_rate": 0.27204502814258913, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.5118483412322274, + "medium_high": 0.4485981308411215, + "medium": 0.5 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4540337711069418, + 0.5422138836772983 + ], + "avg_win_pct_ci_95": [ + 0.05929429524763285, + 0.06976997655863869 + ], + "avg_loss_pct_ci_95": [ + -0.038343935448577234, + -0.033207799323037135 + ], + "profit_factor_ci_95": [ + 0.7402941349783875, + 1.1001772981821412 + ], + "expectancy_r_ci_95": [ + 0.03711311708028525, + 0.09505018284015125 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/metrics/score_bucket_report.csv new file mode 100644 index 0000000..c4d4500 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,422,0.5118 +medium,4,0.5 +medium_high,107,0.4486 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/resolved_config.json new file mode 100644 index 0000000..1c96f76 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316195857_708d9090/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..7e14841 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/artifacts/position_timeline.parquet new file mode 100644 index 0000000..935975b Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..ca1e449 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/manifest.json new file mode 100644 index 0000000..5acbbe2 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/manifest.json @@ -0,0 +1,56 @@ +{ + "experiment_name": "pead_midcap_combo_10pct_maxcand3", + "dataset_snapshot_id": "midcap-filtered", + "description": "Combo: Step3 (10% threshold) + Step5 (max 3 candidates/day). Both are signal quality filters — strongest midcap PEAD signals only.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "10pct", + "longshort", + "midcap", + "combo", + "maxcand3" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/metadata.json new file mode 100644 index 0000000..4b5f5fa --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090", + "started_at": "2026-03-16T20:00:03.999908+00:00", + "finished_at": "2026-03-16T20:00:04.130479+00:00", + "elapsed_seconds": 0.130571, + "git_commit_hash": "1916533", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 2 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..c79efdf --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,72,45,0.625,2250.7124,0.1665 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..0c4de11 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,72,45,0.625,2250.7124,0.1665 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/metrics/metrics_summary.json new file mode 100644 index 0000000..2140a00 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 72, + "win_rate": 0.625, + "avg_win_pct": 0.07349070749214903, + "avg_loss_pct": -0.029881141465994514, + "profit_factor": 1.9089335238197231, + "expectancy_r": 0.16654809465475515, + "avg_r_multiple": 0.16654809465475515, + "total_return_pct": 2.2525004674136144, + "annualized_return_pct": 10.566176102154735, + "max_drawdown_pct": 0.6530874475348815, + "calmar_ratio": 16.17880751197625, + "sharpe_ratio": 2.9527896546854935, + "sortino_ratio": 5.248937130061984, + "avg_daily_pnl": 40.954553952974805, + "avg_positions_held": 3.0535714285714284, + "trade_skewness": -0.691278694353826, + "trade_kurtosis": -0.463005437676427, + "monthly_win_rate": 0.75, + "equity_curve_r_squared": 0.6965943198525836, + "avg_holding_days": 4.958333333333333, + "stop_exit_rate": 0.6944444444444444, + "target_exit_rate": 0.2916666666666667, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.5614035087719298, + "medium_high": 0.8666666666666667 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.5277777777777778, + 0.7361111111111112 + ], + "avg_win_pct_ci_95": [ + 0.05888098583191333, + 0.08915966900496577 + ], + "avg_loss_pct_ci_95": [ + -0.036024102044495535, + -0.02379513868929774 + ], + "profit_factor_ci_95": [ + 1.1568754589888037, + 3.581321782695694 + ], + "expectancy_r_ci_95": [ + 0.0982337966483657, + 0.23562726708722362 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/metrics/score_bucket_report.csv new file mode 100644 index 0000000..5aff516 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,57,0.5614 +medium_high,15,0.8667 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/resolved_config.json new file mode 100644 index 0000000..1c96f76 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200003_708d9090/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..8ed75ae Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/artifacts/position_timeline.parquet new file mode 100644 index 0000000..d1ee33f Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..05ac413 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/manifest.json new file mode 100644 index 0000000..5acbbe2 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/manifest.json @@ -0,0 +1,56 @@ +{ + "experiment_name": "pead_midcap_combo_10pct_maxcand3", + "dataset_snapshot_id": "midcap-filtered", + "description": "Combo: Step3 (10% threshold) + Step5 (max 3 candidates/day). Both are signal quality filters — strongest midcap PEAD signals only.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "10pct", + "longshort", + "midcap", + "combo", + "maxcand3" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/metadata.json new file mode 100644 index 0000000..5987496 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/metadata.json @@ -0,0 +1,10 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090", + "started_at": "2026-03-16T20:01:15.624391+00:00", + "finished_at": "2026-03-16T20:01:15.749868+00:00", + "elapsed_seconds": 0.125477, + "git_commit_hash": "1916533", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 0 +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..2ed788c --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,79,40,0.5063291139240507,-121.3111,0.0474 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..54a0d9d --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,79,40,0.5063291139240507,-121.3111,0.0474 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/metrics/metrics_summary.json new file mode 100644 index 0000000..44a16da --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 79, + "win_rate": 0.5063291139240507, + "avg_win_pct": 0.073113647827567, + "avg_loss_pct": -0.0398078931948907, + "profit_factor": 0.9701095917976659, + "expectancy_r": 0.047414434880377936, + "avg_r_multiple": 0.047414434880377936, + "total_return_pct": -0.11821922524747788, + "annualized_return_pct": -0.6524849727959192, + "max_drawdown_pct": 0.9293000795486674, + "calmar_ratio": -0.7021251661926158, + "sharpe_ratio": -0.18779926674176511, + "sortino_ratio": -0.2737602859200252, + "avg_daily_pnl": -2.6270938943883975, + "avg_positions_held": 3.608695652173913, + "trade_skewness": 0.26075038717656446, + "trade_kurtosis": -0.3542927977294985, + "monthly_win_rate": 0.3333333333333333, + "equity_curve_r_squared": 0.03631581199315345, + "avg_holding_days": 3.3544303797468356, + "stop_exit_rate": 0.8607594936708861, + "target_exit_rate": 0.11392405063291139, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "medium_high": 0.4, + "high": 0.5483870967741935, + "medium": 0.0 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.3924050632911392, + 0.620253164556962 + ], + "avg_win_pct_ci_95": [ + 0.05408195425479622, + 0.0924197699594558 + ], + "avg_loss_pct_ci_95": [ + -0.04448987773809103, + -0.034140513780057294 + ], + "profit_factor_ci_95": [ + 0.5818092993159681, + 1.6115428336066464 + ], + "expectancy_r_ci_95": [ + -0.012631911513116044, + 0.11480864668276741 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/metrics/score_bucket_report.csv new file mode 100644 index 0000000..f1cf3ef --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,62,0.5484 +medium,2,0.0 +medium_high,15,0.4 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/resolved_config.json new file mode 100644 index 0000000..1c96f76 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316200115_708d9090/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..278deda Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/artifacts/position_timeline.parquet new file mode 100644 index 0000000..4393dcf Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..90d3fbf Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/manifest.json new file mode 100644 index 0000000..fe3e21c --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step7_fixedr", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 7: Combo base (10% threshold + max 3 cand) + fixed_r target 2.0. Fixes inverted R:R from atr_multiple (1.5 ATR target vs 3.0 ATR stop). With proper R:R, need only 33% WR to breakeven.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "execution": { + "target_model": "fixed_r", + "target_1_r": 2.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step7", + "fixedr", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/metadata.json new file mode 100644 index 0000000..b95e408 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693", + "started_at": "2026-03-16T22:59:31.906800+00:00", + "finished_at": "2026-03-16T22:59:32.093737+00:00", + "elapsed_seconds": 0.186937, + "git_commit_hash": "764493d", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 41, + "split_name": "train" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..0446bd3 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,425,171,0.4023529411764706,-2694.4387,-0.0055 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..2fe0fc0 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,425,171,0.4023529411764706,-2694.4387,-0.0055 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/metrics/metrics_summary.json new file mode 100644 index 0000000..2f57ac5 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 425, + "win_rate": 0.4023529411764706, + "avg_win_pct": 0.060797281126670995, + "avg_loss_pct": -0.0359889506291995, + "profit_factor": 0.9087588895218256, + "expectancy_r": -0.00550489713701189, + "avg_r_multiple": -0.00550489713701189, + "total_return_pct": -2.684538647143214, + "annualized_return_pct": -0.7400948384740591, + "max_drawdown_pct": 5.9541536964340045, + "calmar_ratio": -0.12429891403665116, + "sharpe_ratio": -0.31665196683581115, + "sortino_ratio": -0.3379484287304984, + "avg_daily_pnl": -2.91480852024236, + "avg_positions_held": 1.7657266811279826, + "trade_skewness": 0.9294663608735747, + "trade_kurtosis": 1.9947016410701952, + "monthly_win_rate": 0.45714285714285713, + "equity_curve_r_squared": 0.0655821811757635, + "avg_holding_days": 5.614117647058824, + "stop_exit_rate": 0.9529411764705882, + "target_exit_rate": 0.004705882352941176, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.4161676646706587, + "medium_high": 0.3448275862068966, + "medium": 0.5 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.3552941176470588, + 0.45176470588235296 + ], + "avg_win_pct_ci_95": [ + 0.0517787374109485, + 0.07012053466923625 + ], + "avg_loss_pct_ci_95": [ + -0.038689345600851584, + -0.03338270635472573 + ], + "profit_factor_ci_95": [ + 0.6965501840329784, + 1.157754910466904 + ], + "expectancy_r_ci_95": [ + -0.0396859955004218, + 0.028765286142380803 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/metrics/score_bucket_report.csv new file mode 100644 index 0000000..a3df0a5 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,334,0.4162 +medium,4,0.5 +medium_high,87,0.3448 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/resolved_config.json new file mode 100644 index 0000000..ecacd8c --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225931_249ee693/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "fixed_r", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..cd1c311 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/artifacts/position_timeline.parquet new file mode 100644 index 0000000..384fdb3 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..e6ac8ef Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/manifest.json new file mode 100644 index 0000000..7dbdf7e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/manifest.json @@ -0,0 +1,61 @@ +{ + "experiment_name": "pead_midcap_step8_nft", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 8: Step 7 base + no_follow_through_exit. Exit at D+1 close if price < entry. Cuts dead trades early before stop is hit, reducing 85% stop exit rate.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "execution": { + "target_model": "fixed_r", + "target_1_r": 2.0, + "no_follow_through_exit": true + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step8", + "nft", + "fixedr", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/metadata.json new file mode 100644 index 0000000..fe466f2 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d", + "started_at": "2026-03-16T22:59:32.044282+00:00", + "finished_at": "2026-03-16T22:59:32.223160+00:00", + "elapsed_seconds": 0.178878, + "git_commit_hash": "764493d", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 12, + "split_name": "train" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..6f2c7a2 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,454,171,0.3766519823788546,-5232.9889,-0.0165 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..821e284 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,454,171,0.3766519823788546,-5232.9889,-0.0165 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/metrics/metrics_summary.json new file mode 100644 index 0000000..6f79114 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 454, + "win_rate": 0.3766519823788546, + "avg_win_pct": 0.0646552705214415, + "avg_loss_pct": -0.041087652274453004, + "profit_factor": 0.8441650849745413, + "expectancy_r": -0.01651629962074056, + "avg_r_multiple": -0.01651629962074056, + "total_return_pct": -5.223338416972663, + "annualized_return_pct": -1.4537955196428198, + "max_drawdown_pct": 7.842211530994003, + "calmar_ratio": -0.18538080921397318, + "sharpe_ratio": -0.6465192822980859, + "sortino_ratio": -0.6431427525180934, + "avg_daily_pnl": -5.671377217125585, + "avg_positions_held": 1.4338394793926248, + "trade_skewness": 0.6423283089090295, + "trade_kurtosis": 1.4576223421840426, + "monthly_win_rate": 0.4, + "equity_curve_r_squared": 0.36446813528716165, + "avg_holding_days": 4.266519823788546, + "stop_exit_rate": 0.4889867841409692, + "target_exit_rate": 0.004405286343612335, + "no_follow_through_exit_rate": 0.4713656387665198, + "score_bucket_hit_rate": { + "high": 0.39775910364145656, + "medium_high": 0.2903225806451613, + "medium": 0.5 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.33480176211453744, + 0.42070484581497797 + ], + "avg_win_pct_ci_95": [ + 0.05584378503668242, + 0.07376556078166495 + ], + "avg_loss_pct_ci_95": [ + -0.0460300377363, + -0.03618864501062029 + ], + "profit_factor_ci_95": [ + 0.6622423398301451, + 1.0783061001439125 + ], + "expectancy_r_ci_95": [ + -0.05158527571792612, + 0.01851869547803921 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/metrics/score_bucket_report.csv new file mode 100644 index 0000000..1bfa7d7 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,357,0.3978 +medium,4,0.5 +medium_high,93,0.2903 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/resolved_config.json new file mode 100644 index 0000000..568f249 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225932_3e736f0d/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "fixed_r", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": true + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..86f2b54 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/artifacts/position_timeline.parquet new file mode 100644 index 0000000..3f1d649 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..0bc9f23 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/manifest.json new file mode 100644 index 0000000..fb512ec --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/manifest.json @@ -0,0 +1,61 @@ +{ + "experiment_name": "pead_midcap_step9_stop2", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 9: Step 7 base + tighter stop (2.0 ATR instead of 3.0). With fixed_r target 2.0, target = 4 ATR. Tighter stop reduces avg loss, but may increase stop exit rate.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "execution": { + "target_model": "fixed_r", + "target_1_r": 2.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "stop_atr_multiplier": 2.0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step9", + "stop2", + "fixedr", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/metadata.json new file mode 100644 index 0000000..809e012 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3", + "started_at": "2026-03-16T22:59:39.805180+00:00", + "finished_at": "2026-03-16T22:59:39.981185+00:00", + "elapsed_seconds": 0.176005, + "git_commit_hash": "764493d", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 38, + "split_name": "train" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..65557e9 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,432,176,0.4074074074074074,-8790.0484,-0.0172 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..0892c62 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,432,176,0.4074074074074074,-8790.0484,-0.0172 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/metrics/metrics_summary.json new file mode 100644 index 0000000..6d563c5 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 432, + "win_rate": 0.4074074074074074, + "avg_win_pct": 0.06181323796440763, + "avg_loss_pct": -0.040593308529704955, + "profit_factor": 0.8175378156440357, + "expectancy_r": -0.017243174686067265, + "avg_r_multiple": -0.017243174686067265, + "total_return_pct": -8.776000630653071, + "annualized_return_pct": -2.476225387797948, + "max_drawdown_pct": 12.886676673013914, + "calmar_ratio": -0.19215391606615148, + "sharpe_ratio": -0.7360418140485263, + "sortino_ratio": -0.7499643923109495, + "avg_daily_pnl": -9.528773757495191, + "avg_positions_held": 1.7407809110629067, + "trade_skewness": 0.5254707052903451, + "trade_kurtosis": 0.46678761786838185, + "monthly_win_rate": 0.42857142857142855, + "equity_curve_r_squared": 0.4421218813805471, + "avg_holding_days": 5.583333333333333, + "stop_exit_rate": 0.9421296296296297, + "target_exit_rate": 0.018518518518518517, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.42058823529411765, + "medium_high": 0.3522727272727273, + "medium": 0.5 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.36574074074074076, + 0.4537037037037037 + ], + "avg_win_pct_ci_95": [ + 0.0538092596867895, + 0.0711028185320134 + ], + "avg_loss_pct_ci_95": [ + -0.04430340716369355, + -0.037068468066665215 + ], + "profit_factor_ci_95": [ + 0.6401891919401093, + 1.0451715083570519 + ], + "expectancy_r_ci_95": [ + -0.06982107741537055, + 0.03737942253826373 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/metrics/score_bucket_report.csv new file mode 100644 index 0000000..c9a47b6 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,340,0.4206 +medium,4,0.5 +medium_high,88,0.3523 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/resolved_config.json new file mode 100644 index 0000000..a541359 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316225939_36ab78b3/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 2.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "fixed_r", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..49dff9c Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/artifacts/position_timeline.parquet new file mode 100644 index 0000000..db3bd53 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..d787061 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/manifest.json new file mode 100644 index 0000000..fe3e21c --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step7_fixedr", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 7: Combo base (10% threshold + max 3 cand) + fixed_r target 2.0. Fixes inverted R:R from atr_multiple (1.5 ATR target vs 3.0 ATR stop). With proper R:R, need only 33% WR to breakeven.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "execution": { + "target_model": "fixed_r", + "target_1_r": 2.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step7", + "fixedr", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/metadata.json new file mode 100644 index 0000000..4a75d44 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693", + "started_at": "2026-03-16T23:00:20.773080+00:00", + "finished_at": "2026-03-16T23:00:20.895992+00:00", + "elapsed_seconds": 0.122912, + "git_commit_hash": "764493d", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 5, + "split_name": "valid" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..d64b54d --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,53,28,0.5283018867924528,2383.2888,0.0912 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..2104454 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,53,28,0.5283018867924528,2383.2888,0.0912 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/metrics/metrics_summary.json new file mode 100644 index 0000000..7835068 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 53, + "win_rate": 0.5283018867924528, + "avg_win_pct": 0.06784002234930957, + "avg_loss_pct": -0.02976926983455299, + "profit_factor": 2.0006534393926754, + "expectancy_r": 0.09122596720768676, + "avg_r_multiple": 0.09122596720768676, + "total_return_pct": 2.3868649231372836, + "annualized_return_pct": 11.22283410095406, + "max_drawdown_pct": 0.6860864501039302, + "calmar_ratio": 16.357755060246408, + "sharpe_ratio": 3.087810484110416, + "sortino_ratio": 6.265419666469826, + "avg_daily_pnl": 43.39754405704152, + "avg_positions_held": 3.232142857142857, + "trade_skewness": 0.4664094348445004, + "trade_kurtosis": -0.4494051042465439, + "monthly_win_rate": 0.75, + "equity_curve_r_squared": 0.6619146653298694, + "avg_holding_days": 5.2075471698113205, + "stop_exit_rate": 0.9622641509433962, + "target_exit_rate": 0.0, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.46511627906976744, + "medium_high": 0.8 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.39622641509433965, + 0.660377358490566 + ], + "avg_win_pct_ci_95": [ + 0.04734990291222306, + 0.09124522994807853 + ], + "avg_loss_pct_ci_95": [ + -0.0362351224410746, + -0.02280162849679659 + ], + "profit_factor_ci_95": [ + 1.0589306506247045, + 4.035515076427477 + ], + "expectancy_r_ci_95": [ + 0.009297084512708656, + 0.17818646356451648 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/metrics/score_bucket_report.csv new file mode 100644 index 0000000..9f5fd8c --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,43,0.4651 +medium_high,10,0.8 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/resolved_config.json new file mode 100644 index 0000000..ecacd8c --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_249ee693/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "fixed_r", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..6998278 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/artifacts/position_timeline.parquet new file mode 100644 index 0000000..0fc639c Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..ef67e14 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/manifest.json new file mode 100644 index 0000000..fb512ec --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/manifest.json @@ -0,0 +1,61 @@ +{ + "experiment_name": "pead_midcap_step9_stop2", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 9: Step 7 base + tighter stop (2.0 ATR instead of 3.0). With fixed_r target 2.0, target = 4 ATR. Tighter stop reduces avg loss, but may increase stop exit rate.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "execution": { + "target_model": "fixed_r", + "target_1_r": 2.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "stop_atr_multiplier": 2.0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step9", + "stop2", + "fixedr", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/metadata.json new file mode 100644 index 0000000..5470b85 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3", + "started_at": "2026-03-16T23:00:20.822170+00:00", + "finished_at": "2026-03-16T23:00:20.943012+00:00", + "elapsed_seconds": 0.120842, + "git_commit_hash": "764493d", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 5, + "split_name": "valid" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..34e3a66 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,53,28,0.5283018867924528,3218.0875,0.1235 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..fbb0c5f --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,53,28,0.5283018867924528,3218.0875,0.1235 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/metrics/metrics_summary.json new file mode 100644 index 0000000..c56e5ae --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 53, + "win_rate": 0.5283018867924528, + "avg_win_pct": 0.06784002234930957, + "avg_loss_pct": -0.03195682304312155, + "profit_factor": 1.8143851030277298, + "expectancy_r": 0.12345349432848295, + "avg_r_multiple": 0.12345349432848295, + "total_return_pct": 3.223451666916022, + "annualized_return_pct": 15.379931085059816, + "max_drawdown_pct": 1.2331625402933826, + "calmar_ratio": 12.471941518269576, + "sharpe_ratio": 2.813496185483277, + "sortino_ratio": 5.587424321912006, + "avg_daily_pnl": 58.60821212574586, + "avg_positions_held": 3.1964285714285716, + "trade_skewness": 0.1804041306583546, + "trade_kurtosis": -0.13893949302057385, + "monthly_win_rate": 0.75, + "equity_curve_r_squared": 0.5891081686619382, + "avg_holding_days": 5.09433962264151, + "stop_exit_rate": 0.9622641509433962, + "target_exit_rate": 0.0, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.46511627906976744, + "medium_high": 0.8 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.39622641509433965, + 0.660377358490566 + ], + "avg_win_pct_ci_95": [ + 0.04734990291222306, + 0.09124522994807853 + ], + "avg_loss_pct_ci_95": [ + -0.03991972172401575, + -0.02356127366956299 + ], + "profit_factor_ci_95": [ + 0.9554289369908819, + 3.696049706393317 + ], + "expectancy_r_ci_95": [ + -0.0070883999954807105, + 0.2590652459148132 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/metrics/score_bucket_report.csv new file mode 100644 index 0000000..9f5fd8c --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,43,0.4651 +medium_high,10,0.8 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/resolved_config.json new file mode 100644 index 0000000..a541359 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_36ab78b3/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 2.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "fixed_r", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..b388fc6 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/artifacts/position_timeline.parquet new file mode 100644 index 0000000..b1ee855 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..fc37356 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/manifest.json new file mode 100644 index 0000000..7dbdf7e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/manifest.json @@ -0,0 +1,61 @@ +{ + "experiment_name": "pead_midcap_step8_nft", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 8: Step 7 base + no_follow_through_exit. Exit at D+1 close if price < entry. Cuts dead trades early before stop is hit, reducing 85% stop exit rate.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "execution": { + "target_model": "fixed_r", + "target_1_r": 2.0, + "no_follow_through_exit": true + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step8", + "nft", + "fixedr", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/metadata.json new file mode 100644 index 0000000..9e64c9e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d", + "started_at": "2026-03-16T23:00:20.708345+00:00", + "finished_at": "2026-03-16T23:00:20.839266+00:00", + "elapsed_seconds": 0.130921, + "git_commit_hash": "764493d", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 1, + "split_name": "valid" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..6724ca9 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,57,26,0.45614035087719296,1831.3822,0.0661 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..b684083 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,57,26,0.45614035087719296,1831.3822,0.0661 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/metrics/metrics_summary.json new file mode 100644 index 0000000..5bd364d --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 57, + "win_rate": 0.45614035087719296, + "avg_win_pct": 0.07648807575116437, + "avg_loss_pct": -0.0359980798286353, + "profit_factor": 1.5546541014245128, + "expectancy_r": 0.06606326910908045, + "avg_r_multiple": 0.06606326910908045, + "total_return_pct": 1.8349072453313857, + "annualized_return_pct": 8.544579881077041, + "max_drawdown_pct": 0.9575880067210154, + "calmar_ratio": 8.923023075795923, + "sharpe_ratio": 2.3501871656264828, + "sortino_ratio": 4.814004760511402, + "avg_daily_pnl": 33.3619499151161, + "avg_positions_held": 2.9464285714285716, + "trade_skewness": 0.961750433168802, + "trade_kurtosis": 1.8255068527321763, + "monthly_win_rate": 0.5, + "equity_curve_r_squared": 0.4737973824333247, + "avg_holding_days": 4.298245614035087, + "stop_exit_rate": 0.5263157894736842, + "target_exit_rate": 0.0, + "no_follow_through_exit_rate": 0.42105263157894735, + "score_bucket_hit_rate": { + "high": 0.425531914893617, + "medium_high": 0.6 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.3333333333333333, + 0.5964912280701754 + ], + "avg_win_pct_ci_95": [ + 0.054571207508606674, + 0.10131813835699345 + ], + "avg_loss_pct_ci_95": [ + -0.04609462470490575, + -0.027533348801257417 + ], + "profit_factor_ci_95": [ + 0.7751479661172251, + 2.9849286161226933 + ], + "expectancy_r_ci_95": [ + -0.029853730276134725, + 0.17116543920389019 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/metrics/score_bucket_report.csv new file mode 100644 index 0000000..60e394f --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,47,0.4255 +medium_high,10,0.6 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/resolved_config.json new file mode 100644 index 0000000..568f249 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230020_3e736f0d/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "fixed_r", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": true + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..1c3a0ae Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/artifacts/position_timeline.parquet new file mode 100644 index 0000000..8572de1 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..bd6918d Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/manifest.json new file mode 100644 index 0000000..fe3e21c --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step7_fixedr", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 7: Combo base (10% threshold + max 3 cand) + fixed_r target 2.0. Fixes inverted R:R from atr_multiple (1.5 ATR target vs 3.0 ATR stop). With proper R:R, need only 33% WR to breakeven.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "execution": { + "target_model": "fixed_r", + "target_1_r": 2.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step7", + "fixedr", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/metadata.json new file mode 100644 index 0000000..cc894c2 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693", + "started_at": "2026-03-16T23:01:08.298264+00:00", + "finished_at": "2026-03-16T23:01:08.421744+00:00", + "elapsed_seconds": 0.12348, + "git_commit_hash": "764493d", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 0, + "split_name": "test" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..07e07ec --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,71,32,0.4507042253521127,-252.9613,-0.0043 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..d3aaf75 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,71,32,0.4507042253521127,-252.9613,-0.0043 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/metrics/metrics_summary.json new file mode 100644 index 0000000..7878ea5 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 71, + "win_rate": 0.4507042253521127, + "avg_win_pct": 0.06349279185652297, + "avg_loss_pct": -0.0398078931948907, + "profit_factor": 0.9376665114180485, + "expectancy_r": -0.004332362136955376, + "avg_r_multiple": -0.004332362136955376, + "total_return_pct": -0.24986939032697408, + "annualized_return_pct": -1.3749898607159472, + "max_drawdown_pct": 0.9924460035465441, + "calmar_ratio": -1.3854555873088992, + "sharpe_ratio": -0.39554365980509315, + "sortino_ratio": -0.5363496528949203, + "avg_daily_pnl": -5.5526531183772025, + "avg_positions_held": 3.630434782608696, + "trade_skewness": 0.8046089861253759, + "trade_kurtosis": 0.35483246963431725, + "monthly_win_rate": 0.0, + "equity_curve_r_squared": 0.16973213763570125, + "avg_holding_days": 3.464788732394366, + "stop_exit_rate": 0.971830985915493, + "target_exit_rate": 0.0, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "medium_high": 0.35714285714285715, + "high": 0.4909090909090909, + "medium": 0.0 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.3380281690140845, + 0.5774647887323944 + ], + "avg_win_pct_ci_95": [ + 0.04326324076739327, + 0.08729261945911203 + ], + "avg_loss_pct_ci_95": [ + -0.04447751947802544, + -0.033935246436580246 + ], + "profit_factor_ci_95": [ + 0.5125664019678017, + 1.7204909862581708 + ], + "expectancy_r_ci_95": [ + -0.06592271324028512, + 0.06399849723013065 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/metrics/score_bucket_report.csv new file mode 100644 index 0000000..d1e717a --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,55,0.4909 +medium,2,0.0 +medium_high,14,0.3571 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/resolved_config.json new file mode 100644 index 0000000..ecacd8c --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_249ee693/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "fixed_r", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..7b3bb49 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/artifacts/position_timeline.parquet new file mode 100644 index 0000000..8204411 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..b0d2005 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/manifest.json new file mode 100644 index 0000000..fb512ec --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/manifest.json @@ -0,0 +1,61 @@ +{ + "experiment_name": "pead_midcap_step9_stop2", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 9: Step 7 base + tighter stop (2.0 ATR instead of 3.0). With fixed_r target 2.0, target = 4 ATR. Tighter stop reduces avg loss, but may increase stop exit rate.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "execution": { + "target_model": "fixed_r", + "target_1_r": 2.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "stop_atr_multiplier": 2.0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step9", + "stop2", + "fixedr", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/metadata.json new file mode 100644 index 0000000..d323229 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3", + "started_at": "2026-03-16T23:01:08.194440+00:00", + "finished_at": "2026-03-16T23:01:08.325133+00:00", + "elapsed_seconds": 0.130693, + "git_commit_hash": "764493d", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 0, + "split_name": "test" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..e6caf8f --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,71,32,0.4507042253521127,-1744.1884,-0.0453 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..7aa473a --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,71,32,0.4507042253521127,-1744.1884,-0.0453 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/metrics/metrics_summary.json new file mode 100644 index 0000000..7881fc3 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 71, + "win_rate": 0.4507042253521127, + "avg_win_pct": 0.06349279185652297, + "avg_loss_pct": -0.05079143230729524, + "profit_factor": 0.7658210615369154, + "expectancy_r": -0.04534646202974923, + "avg_r_multiple": -0.04534646202974923, + "total_return_pct": -1.7396298311107383, + "annualized_return_pct": -9.255283127151303, + "max_drawdown_pct": 2.4893517163865826, + "calmar_ratio": -3.7179491617142015, + "sharpe_ratio": -1.8398745071636018, + "sortino_ratio": -2.3274759742660747, + "avg_daily_pnl": -38.658440691349746, + "avg_positions_held": 3.5434782608695654, + "trade_skewness": 0.23859416259802124, + "trade_kurtosis": 0.2667244037610783, + "monthly_win_rate": 0.0, + "equity_curve_r_squared": 0.7504756156161969, + "avg_holding_days": 3.408450704225352, + "stop_exit_rate": 0.971830985915493, + "target_exit_rate": 0.0, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "medium_high": 0.35714285714285715, + "high": 0.4909090909090909, + "medium": 0.0 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.3380281690140845, + 0.5774647887323944 + ], + "avg_win_pct_ci_95": [ + 0.04363432208575816, + 0.08596540730946903 + ], + "avg_loss_pct_ci_95": [ + -0.06425212141968317, + -0.03974943875630138 + ], + "profit_factor_ci_95": [ + 0.4127208869222768, + 1.4296945143262958 + ], + "expectancy_r_ci_95": [ + -0.1512556167252362, + 0.0718493010713484 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/metrics/score_bucket_report.csv new file mode 100644 index 0000000..d1e717a --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,55,0.4909 +medium,2,0.0 +medium_high,14,0.3571 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/resolved_config.json new file mode 100644 index 0000000..a541359 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_36ab78b3/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 2.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "fixed_r", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..254148c Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/artifacts/position_timeline.parquet new file mode 100644 index 0000000..09afe6f Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..ab93759 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/manifest.json new file mode 100644 index 0000000..7dbdf7e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/manifest.json @@ -0,0 +1,61 @@ +{ + "experiment_name": "pead_midcap_step8_nft", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 8: Step 7 base + no_follow_through_exit. Exit at D+1 close if price < entry. Cuts dead trades early before stop is hit, reducing 85% stop exit rate.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "execution": { + "target_model": "fixed_r", + "target_1_r": 2.0, + "no_follow_through_exit": true + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step8", + "nft", + "fixedr", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/metadata.json new file mode 100644 index 0000000..5154091 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d", + "started_at": "2026-03-16T23:01:08.293571+00:00", + "finished_at": "2026-03-16T23:01:08.420249+00:00", + "elapsed_seconds": 0.126678, + "git_commit_hash": "764493d", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 0, + "split_name": "test" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..dcfc617 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,71,29,0.4084507042253521,-1815.3531,-0.0496 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..8e58aa4 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,71,29,0.4084507042253521,-1815.3531,-0.0496 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/metrics/metrics_summary.json new file mode 100644 index 0000000..4e6bbfd --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 71, + "win_rate": 0.4084507042253521, + "avg_win_pct": 0.06108718372516865, + "avg_loss_pct": -0.05509295177087258, + "profit_factor": 0.6522816324098041, + "expectancy_r": -0.04958989060601057, + "avg_r_multiple": -0.04958989060601057, + "total_return_pct": -1.8153531028857979, + "annualized_return_pct": -9.641614426808875, + "max_drawdown_pct": 2.1930148870201824, + "calmar_ratio": -4.396511160902184, + "sharpe_ratio": -2.981974625656072, + "sortino_ratio": -3.7877745076765232, + "avg_daily_pnl": -40.34118006412884, + "avg_positions_held": 2.6956521739130435, + "trade_skewness": 0.3297462840097395, + "trade_kurtosis": 0.31275612720200874, + "monthly_win_rate": 0.3333333333333333, + "equity_curve_r_squared": 0.8832469667893551, + "avg_holding_days": 2.591549295774648, + "stop_exit_rate": 0.4647887323943662, + "target_exit_rate": 0.0, + "no_follow_through_exit_rate": 0.5211267605633803, + "score_bucket_hit_rate": { + "medium_high": 0.2857142857142857, + "medium": 0.0, + "high": 0.45454545454545453 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.29577464788732394, + 0.5211267605633803 + ], + "avg_win_pct_ci_95": [ + 0.04037824606344773, + 0.08451239782033358 + ], + "avg_loss_pct_ci_95": [ + -0.07036798246872278, + -0.04218193754586563 + ], + "profit_factor_ci_95": [ + 0.337350956822823, + 1.1833880142860553 + ], + "expectancy_r_ci_95": [ + -0.11968477123437775, + 0.023126836940711146 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/metrics/score_bucket_report.csv new file mode 100644 index 0000000..09091aa --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,55,0.4545 +medium,2,0.0 +medium_high,14,0.2857 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/resolved_config.json new file mode 100644 index 0000000..568f249 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230108_3e736f0d/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "fixed_r", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": true + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..ff41145 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/artifacts/position_timeline.parquet new file mode 100644 index 0000000..4669035 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..5174f2e Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/manifest.json new file mode 100644 index 0000000..f19af33 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step10_short", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 10: Combo base (10% + maxcand3) + shorter hold (7 days). Avg holding is 3.4 days, so 15-day max is unnecessary exposure. 7 days captures PEAD drift without mean reversion.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step10", + "short", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/metadata.json new file mode 100644 index 0000000..b157170 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872", + "started_at": "2026-03-16T23:03:08.990239+00:00", + "finished_at": "2026-03-16T23:03:09.159183+00:00", + "elapsed_seconds": 0.168944, + "git_commit_hash": "764493d", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 14, + "split_name": "train" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..3b72326 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,535,272,0.508411214953271,-1836.8543,0.068 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..9f707ff --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,535,272,0.508411214953271,-1836.8543,0.068 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/metrics/metrics_summary.json new file mode 100644 index 0000000..5816b84 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 535, + "win_rate": 0.508411214953271, + "avg_win_pct": 0.06254774170649, + "avg_loss_pct": -0.03570403824119417, + "profit_factor": 0.9383880570062944, + "expectancy_r": 0.06802022112453358, + "avg_r_multiple": 0.06802022112453358, + "total_return_pct": -1.8280832909611637, + "annualized_return_pct": -0.5023854913470349, + "max_drawdown_pct": 5.0343002325613835, + "calmar_ratio": -0.0997925169614741, + "sharpe_ratio": -0.23100167769304789, + "sortino_ratio": -0.22649718762763335, + "avg_daily_pnl": -1.9848895667330766, + "avg_positions_held": 1.4219088937093276, + "trade_skewness": -0.5968615217283192, + "trade_kurtosis": 0.3833172880490685, + "monthly_win_rate": 0.4857142857142857, + "equity_curve_r_squared": 0.12398583357859513, + "avg_holding_days": 4.140186915887851, + "stop_exit_rate": 0.685981308411215, + "target_exit_rate": 0.25981308411214954, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.5188679245283019, + "medium_high": 0.4672897196261682, + "medium": 0.5 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4672897196261682, + 0.5495327102803739 + ], + "avg_win_pct_ci_95": [ + 0.05721346870120623, + 0.06771263802377263 + ], + "avg_loss_pct_ci_95": [ + -0.03850443034774955, + -0.03306900893194294 + ], + "profit_factor_ci_95": [ + 0.7704805579841751, + 1.1448666740306768 + ], + "expectancy_r_ci_95": [ + 0.03960594624643264, + 0.09770214583865379 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/metrics/score_bucket_report.csv new file mode 100644 index 0000000..ca874f4 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,424,0.5189 +medium,4,0.5 +medium_high,107,0.4673 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/resolved_config.json new file mode 100644 index 0000000..c4589ea --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230308_b26b6872/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..2be7a4a Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/artifacts/position_timeline.parquet new file mode 100644 index 0000000..cb37dc7 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..695ce57 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/manifest.json new file mode 100644 index 0000000..f19af33 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step10_short", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 10: Combo base (10% + maxcand3) + shorter hold (7 days). Avg holding is 3.4 days, so 15-day max is unnecessary exposure. 7 days captures PEAD drift without mean reversion.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step10", + "short", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/metadata.json new file mode 100644 index 0000000..7e8d8cf --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872", + "started_at": "2026-03-16T23:03:14.101134+00:00", + "finished_at": "2026-03-16T23:03:14.226743+00:00", + "elapsed_seconds": 0.125609, + "git_commit_hash": "764493d", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 1, + "split_name": "valid" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..616ddac --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,70,41,0.5857142857142857,1562.6792,0.1335 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..7216355 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,70,41,0.5857142857142857,1562.6792,0.1335 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/metrics/metrics_summary.json new file mode 100644 index 0000000..34ede80 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 70, + "win_rate": 0.5857142857142857, + "avg_win_pct": 0.0742841995575788, + "avg_loss_pct": -0.029687790889600586, + "profit_factor": 1.6081573944406065, + "expectancy_r": 0.13350775410348972, + "avg_r_multiple": 0.13350775410348972, + "total_return_pct": 1.5626791854819602, + "annualized_return_pct": 7.242276186624741, + "max_drawdown_pct": 0.6539417061012387, + "calmar_ratio": 11.074803945144833, + "sharpe_ratio": 2.2442047142281463, + "sortino_ratio": 4.279957910263686, + "avg_daily_pnl": 28.412348826944733, + "avg_positions_held": 2.7142857142857144, + "trade_skewness": -0.5402725001915989, + "trade_kurtosis": -0.5840994784130609, + "monthly_win_rate": 0.75, + "equity_curve_r_squared": 0.6019214395233538, + "avg_holding_days": 3.9, + "stop_exit_rate": 0.7285714285714285, + "target_exit_rate": 0.22857142857142856, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.543859649122807, + "medium_high": 0.7692307692307693 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4857142857142857, + 0.7 + ], + "avg_win_pct_ci_95": [ + 0.05737457568701041, + 0.09064283536032813 + ], + "avg_loss_pct_ci_95": [ + -0.03657107143152661, + -0.02339973217677251 + ], + "profit_factor_ci_95": [ + 0.9849502341256496, + 2.817779654607078 + ], + "expectancy_r_ci_95": [ + 0.06378972291627225, + 0.20236472296796962 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/metrics/score_bucket_report.csv new file mode 100644 index 0000000..8eb6be8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,57,0.5439 +medium_high,13,0.7692 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/resolved_config.json new file mode 100644 index 0000000..c4589ea --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230314_b26b6872/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..9534865 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/artifacts/position_timeline.parquet new file mode 100644 index 0000000..c525320 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..d0235d6 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/manifest.json new file mode 100644 index 0000000..f19af33 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step10_short", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 10: Combo base (10% + maxcand3) + shorter hold (7 days). Avg holding is 3.4 days, so 15-day max is unnecessary exposure. 7 days captures PEAD drift without mean reversion.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step10", + "short", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/metadata.json new file mode 100644 index 0000000..df11c3b --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872", + "started_at": "2026-03-16T23:03:19.039214+00:00", + "finished_at": "2026-03-16T23:03:19.165637+00:00", + "elapsed_seconds": 0.126423, + "git_commit_hash": "764493d", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 0, + "split_name": "test" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..94e57ca --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,79,41,0.5189873417721519,-16.7759,0.05 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..cf1a042 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,79,41,0.5189873417721519,-16.7759,0.05 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/metrics/metrics_summary.json new file mode 100644 index 0000000..9d69531 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 79, + "win_rate": 0.5189873417721519, + "avg_win_pct": 0.07178768057560496, + "avg_loss_pct": -0.04074641081108098, + "profit_factor": 0.9958404155353467, + "expectancy_r": 0.050049992532623044, + "avg_r_multiple": 0.050049992532623044, + "total_return_pct": -0.013683964940166334, + "annualized_return_pct": -0.07570481719820776, + "max_drawdown_pct": 0.9293000795486674, + "calmar_ratio": -0.08146433952203606, + "sharpe_ratio": -0.007699537912677983, + "sortino_ratio": -0.01125136696979486, + "avg_daily_pnl": -0.3040881097814741, + "avg_positions_held": 3.4347826086956523, + "trade_skewness": 0.22836465024738897, + "trade_kurtosis": -0.39248834025121226, + "monthly_win_rate": 0.6666666666666666, + "equity_curve_r_squared": 0.01071589487383525, + "avg_holding_days": 3.2278481012658227, + "stop_exit_rate": 0.8607594936708861, + "target_exit_rate": 0.11392405063291139, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "medium_high": 0.4, + "high": 0.5645161290322581, + "medium": 0.0 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4050632911392405, + 0.6329113924050633 + ], + "avg_win_pct_ci_95": [ + 0.05358946899225602, + 0.09209116308671031 + ], + "avg_loss_pct_ci_95": [ + -0.045146744900320175, + -0.03537711943662088 + ], + "profit_factor_ci_95": [ + 0.5854891489391493, + 1.737841121255371 + ], + "expectancy_r_ci_95": [ + -0.014211798186945611, + 0.1186496574846276 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/metrics/score_bucket_report.csv new file mode 100644 index 0000000..b714960 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,62,0.5645 +medium,2,0.0 +medium_high,15,0.4 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/resolved_config.json new file mode 100644 index 0000000..c4589ea --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230319_b26b6872/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..bcdcaaa Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/artifacts/position_timeline.parquet new file mode 100644 index 0000000..0bb507f Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..1e3d137 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/manifest.json new file mode 100644 index 0000000..f61144f --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/manifest.json @@ -0,0 +1,55 @@ +{ + "experiment_name": "pead_midcap_step11_score60", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 11: Combo base + higher score threshold (0.60). All prior experiments used 0.50 (below default 0.55). Raising to 0.60 should filter weak signals and improve trade quality.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.6, + "max_candidates_per_day": 3 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step11", + "score60", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/metadata.json new file mode 100644 index 0000000..39c501d --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036", + "started_at": "2026-03-16T23:03:27.887387+00:00", + "finished_at": "2026-03-16T23:03:28.057448+00:00", + "elapsed_seconds": 0.170061, + "git_commit_hash": "764493d", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 19, + "split_name": "train" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..e64abf1 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,529,264,0.499054820415879,-2859.1446,0.0673 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..90771ca --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,529,264,0.499054820415879,-2859.1446,0.0673 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/metrics/metrics_summary.json new file mode 100644 index 0000000..1b1f3b8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 529, + "win_rate": 0.499054820415879, + "avg_win_pct": 0.06487296432965489, + "avg_loss_pct": -0.03590199741059391, + "profit_factor": 0.9051644971123368, + "expectancy_r": 0.0673141712179278, + "avg_r_multiple": 0.0673141712179278, + "total_return_pct": -2.850449875516715, + "annualized_return_pct": -0.7863192328984581, + "max_drawdown_pct": 5.806253410597396, + "calmar_ratio": -0.13542626842006109, + "sharpe_ratio": -0.3721590200001434, + "sortino_ratio": -0.37131250855645487, + "avg_daily_pnl": -3.0949510049041424, + "avg_positions_held": 1.509761388286334, + "trade_skewness": -0.5829942197490245, + "trade_kurtosis": 0.29996891081726984, + "monthly_win_rate": 0.45714285714285713, + "equity_curve_r_squared": 0.28879703955671965, + "avg_holding_days": 4.531190926275992, + "stop_exit_rate": 0.7126654064272212, + "target_exit_rate": 0.2741020793950851, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.5118483412322274, + "medium_high": 0.4485981308411215 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.45179584120982985, + 0.5387523629489603 + ], + "avg_win_pct_ci_95": [ + 0.05970863417509167, + 0.07055568736269924 + ], + "avg_loss_pct_ci_95": [ + -0.038634740137761456, + -0.03317006690874992 + ], + "profit_factor_ci_95": [ + 0.7346578584451648, + 1.094532248888858 + ], + "expectancy_r_ci_95": [ + 0.036870746173262615, + 0.09533156473253014 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/metrics/score_bucket_report.csv new file mode 100644 index 0000000..f6e14af --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,422,0.5118 +medium_high,107,0.4486 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/resolved_config.json new file mode 100644 index 0000000..e775eb0 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230327_b5298036/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.6, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..7e14841 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/artifacts/position_timeline.parquet new file mode 100644 index 0000000..a31faac Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..1d5ed4b Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/manifest.json new file mode 100644 index 0000000..f61144f --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/manifest.json @@ -0,0 +1,55 @@ +{ + "experiment_name": "pead_midcap_step11_score60", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 11: Combo base + higher score threshold (0.60). All prior experiments used 0.50 (below default 0.55). Raising to 0.60 should filter weak signals and improve trade quality.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.6, + "max_candidates_per_day": 3 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step11", + "score60", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/metadata.json new file mode 100644 index 0000000..641b9d2 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036", + "started_at": "2026-03-16T23:03:33.457740+00:00", + "finished_at": "2026-03-16T23:03:33.578255+00:00", + "elapsed_seconds": 0.120515, + "git_commit_hash": "764493d", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 2, + "split_name": "valid" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..c79efdf --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,72,45,0.625,2250.7124,0.1665 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..0c4de11 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,72,45,0.625,2250.7124,0.1665 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/metrics/metrics_summary.json new file mode 100644 index 0000000..2140a00 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 72, + "win_rate": 0.625, + "avg_win_pct": 0.07349070749214903, + "avg_loss_pct": -0.029881141465994514, + "profit_factor": 1.9089335238197231, + "expectancy_r": 0.16654809465475515, + "avg_r_multiple": 0.16654809465475515, + "total_return_pct": 2.2525004674136144, + "annualized_return_pct": 10.566176102154735, + "max_drawdown_pct": 0.6530874475348815, + "calmar_ratio": 16.17880751197625, + "sharpe_ratio": 2.9527896546854935, + "sortino_ratio": 5.248937130061984, + "avg_daily_pnl": 40.954553952974805, + "avg_positions_held": 3.0535714285714284, + "trade_skewness": -0.691278694353826, + "trade_kurtosis": -0.463005437676427, + "monthly_win_rate": 0.75, + "equity_curve_r_squared": 0.6965943198525836, + "avg_holding_days": 4.958333333333333, + "stop_exit_rate": 0.6944444444444444, + "target_exit_rate": 0.2916666666666667, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.5614035087719298, + "medium_high": 0.8666666666666667 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.5277777777777778, + 0.7361111111111112 + ], + "avg_win_pct_ci_95": [ + 0.05888098583191333, + 0.08915966900496577 + ], + "avg_loss_pct_ci_95": [ + -0.036024102044495535, + -0.02379513868929774 + ], + "profit_factor_ci_95": [ + 1.1568754589888037, + 3.581321782695694 + ], + "expectancy_r_ci_95": [ + 0.0982337966483657, + 0.23562726708722362 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/metrics/score_bucket_report.csv new file mode 100644 index 0000000..5aff516 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,57,0.5614 +medium_high,15,0.8667 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/resolved_config.json new file mode 100644 index 0000000..e775eb0 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230333_b5298036/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.6, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..80e9e3e Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/artifacts/position_timeline.parquet new file mode 100644 index 0000000..182c4d7 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..2d66c02 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/manifest.json new file mode 100644 index 0000000..f61144f --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/manifest.json @@ -0,0 +1,55 @@ +{ + "experiment_name": "pead_midcap_step11_score60", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 11: Combo base + higher score threshold (0.60). All prior experiments used 0.50 (below default 0.55). Raising to 0.60 should filter weak signals and improve trade quality.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5, + "score_threshold": 0.6, + "max_candidates_per_day": 3 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step11", + "score60", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/metadata.json new file mode 100644 index 0000000..8f32562 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036", + "started_at": "2026-03-16T23:03:38.627466+00:00", + "finished_at": "2026-03-16T23:03:38.750950+00:00", + "elapsed_seconds": 0.123484, + "git_commit_hash": "764493d", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 0, + "split_name": "test" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..2794009 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,77,40,0.5194805194805194,73.7622,0.0537 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..1b9656f --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,77,40,0.5194805194805194,73.7622,0.0537 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/metrics/metrics_summary.json new file mode 100644 index 0000000..ed481ec --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 77, + "win_rate": 0.5194805194805194, + "avg_win_pct": 0.073113647827567, + "avg_loss_pct": -0.03920291444866857, + "profit_factor": 1.0190825170597053, + "expectancy_r": 0.05368030141844395, + "avg_r_multiple": 0.05368030141844395, + "total_return_pct": 0.07685407145853969, + "annualized_return_pct": 0.4260591270758951, + "max_drawdown_pct": 0.8954589900133336, + "calmar_ratio": 0.47579970923018033, + "sharpe_ratio": 0.15103876740694316, + "sortino_ratio": 0.2177158907708136, + "avg_daily_pnl": 1.7078682546342154, + "avg_positions_held": 3.5217391304347827, + "trade_skewness": 0.21458989550467822, + "trade_kurtosis": -0.36151274500043096, + "monthly_win_rate": 0.6666666666666666, + "equity_curve_r_squared": 0.001051223399886146, + "avg_holding_days": 3.3896103896103895, + "stop_exit_rate": 0.8571428571428571, + "target_exit_rate": 0.11688311688311688, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "medium_high": 0.4, + "high": 0.5483870967741935 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.38961038961038963, + 0.6363636363636364 + ], + "avg_win_pct_ci_95": [ + 0.05538266447078478, + 0.09450722950084432 + ], + "avg_loss_pct_ci_95": [ + -0.044080078704733995, + -0.03348397649203741 + ], + "profit_factor_ci_95": [ + 0.6053874953080377, + 1.772683024426203 + ], + "expectancy_r_ci_95": [ + -0.01557076708884812, + 0.12639637472159732 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/metrics/score_bucket_report.csv new file mode 100644 index 0000000..1d03eab --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,62,0.5484 +medium_high,15,0.4 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/resolved_config.json new file mode 100644 index 0000000..e775eb0 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230338_b5298036/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.6, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 1.5 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..7120738 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/artifacts/position_timeline.parquet new file mode 100644 index 0000000..25a65f1 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..4694ad4 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/manifest.json new file mode 100644 index 0000000..456c912 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/manifest.json @@ -0,0 +1,55 @@ +{ + "experiment_name": "pead_midcap_step12_vol2x", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 12: Combo base + volume threshold 2.0x (from 1.5x). Require stronger volume conviction on reaction day. Higher volume = more institutional participation = stronger drift.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step12", + "vol2x", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/metadata.json new file mode 100644 index 0000000..642e523 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000", + "started_at": "2026-03-16T23:03:47.420676+00:00", + "finished_at": "2026-03-16T23:03:47.592016+00:00", + "elapsed_seconds": 0.17134, + "git_commit_hash": "764493d", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 19, + "split_name": "train" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..2a4e453 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,525,261,0.49714285714285716,-3272.2256,0.0644 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..68cd57b --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,525,261,0.49714285714285716,-3272.2256,0.0644 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/metrics/metrics_summary.json new file mode 100644 index 0000000..631c5b2 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 525, + "win_rate": 0.49714285714285716, + "avg_win_pct": 0.06338053305104943, + "avg_loss_pct": -0.03578192991062882, + "profit_factor": 0.8908517859321168, + "expectancy_r": 0.06443516186211533, + "avg_r_multiple": 0.06443516186211533, + "total_return_pct": -3.263530815059654, + "annualized_return_pct": -0.9016572358042874, + "max_drawdown_pct": 6.158693883828255, + "calmar_ratio": -0.1464039701943776, + "sharpe_ratio": -0.4271081260405149, + "sortino_ratio": -0.4215267353233614, + "avg_daily_pnl": -3.543464511465422, + "avg_positions_held": 1.5065075921908895, + "trade_skewness": -0.5793006683302223, + "trade_kurtosis": 0.32996932913623267, + "monthly_win_rate": 0.45714285714285713, + "equity_curve_r_squared": 0.3844589392112171, + "avg_holding_days": 4.561904761904762, + "stop_exit_rate": 0.7161904761904762, + "target_exit_rate": 0.2704761904761905, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.5107398568019093, + "medium_high": 0.4411764705882353, + "medium": 0.5 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4533333333333333, + 0.5371428571428571 + ], + "avg_win_pct_ci_95": [ + 0.057959880909009616, + 0.06852332205235206 + ], + "avg_loss_pct_ci_95": [ + -0.038684590849839905, + -0.033143598491949325 + ], + "profit_factor_ci_95": [ + 0.7258398089539569, + 1.0772036917378662 + ], + "expectancy_r_ci_95": [ + 0.032723674724550116, + 0.0923861814514615 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/metrics/score_bucket_report.csv new file mode 100644 index 0000000..8741ed3 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,419,0.5107 +medium,4,0.5 +medium_high,102,0.4412 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/resolved_config.json new file mode 100644 index 0000000..51399a6 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230347_534e7000/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..7e14841 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/artifacts/position_timeline.parquet new file mode 100644 index 0000000..901f805 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..7fac162 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/manifest.json new file mode 100644 index 0000000..456c912 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/manifest.json @@ -0,0 +1,55 @@ +{ + "experiment_name": "pead_midcap_step12_vol2x", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 12: Combo base + volume threshold 2.0x (from 1.5x). Require stronger volume conviction on reaction day. Higher volume = more institutional participation = stronger drift.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step12", + "vol2x", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/metadata.json new file mode 100644 index 0000000..25e0118 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000", + "started_at": "2026-03-16T23:03:53.094260+00:00", + "finished_at": "2026-03-16T23:03:53.218591+00:00", + "elapsed_seconds": 0.124331, + "git_commit_hash": "764493d", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 2, + "split_name": "valid" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..c79efdf --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,72,45,0.625,2250.7124,0.1665 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..0c4de11 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,72,45,0.625,2250.7124,0.1665 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/metrics/metrics_summary.json new file mode 100644 index 0000000..2140a00 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 72, + "win_rate": 0.625, + "avg_win_pct": 0.07349070749214903, + "avg_loss_pct": -0.029881141465994514, + "profit_factor": 1.9089335238197231, + "expectancy_r": 0.16654809465475515, + "avg_r_multiple": 0.16654809465475515, + "total_return_pct": 2.2525004674136144, + "annualized_return_pct": 10.566176102154735, + "max_drawdown_pct": 0.6530874475348815, + "calmar_ratio": 16.17880751197625, + "sharpe_ratio": 2.9527896546854935, + "sortino_ratio": 5.248937130061984, + "avg_daily_pnl": 40.954553952974805, + "avg_positions_held": 3.0535714285714284, + "trade_skewness": -0.691278694353826, + "trade_kurtosis": -0.463005437676427, + "monthly_win_rate": 0.75, + "equity_curve_r_squared": 0.6965943198525836, + "avg_holding_days": 4.958333333333333, + "stop_exit_rate": 0.6944444444444444, + "target_exit_rate": 0.2916666666666667, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.5614035087719298, + "medium_high": 0.8666666666666667 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.5277777777777778, + 0.7361111111111112 + ], + "avg_win_pct_ci_95": [ + 0.05888098583191333, + 0.08915966900496577 + ], + "avg_loss_pct_ci_95": [ + -0.036024102044495535, + -0.02379513868929774 + ], + "profit_factor_ci_95": [ + 1.1568754589888037, + 3.581321782695694 + ], + "expectancy_r_ci_95": [ + 0.0982337966483657, + 0.23562726708722362 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/metrics/score_bucket_report.csv new file mode 100644 index 0000000..5aff516 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,57,0.5614 +medium_high,15,0.8667 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/resolved_config.json new file mode 100644 index 0000000..51399a6 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230353_534e7000/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..c9f053b Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/artifacts/position_timeline.parquet new file mode 100644 index 0000000..0a0be17 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..2888eca Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/manifest.json new file mode 100644 index 0000000..456c912 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/manifest.json @@ -0,0 +1,55 @@ +{ + "experiment_name": "pead_midcap_step12_vol2x", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 12: Combo base + volume threshold 2.0x (from 1.5x). Require stronger volume conviction on reaction day. Higher volume = more institutional participation = stronger drift.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.5, + "max_candidates_per_day": 3 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step12", + "vol2x", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/metadata.json new file mode 100644 index 0000000..8a45fee --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000", + "started_at": "2026-03-16T23:03:58.098669+00:00", + "finished_at": "2026-03-16T23:03:58.220247+00:00", + "elapsed_seconds": 0.121578, + "git_commit_hash": "764493d", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 0, + "split_name": "test" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..d786c6b --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,77,40,0.5194805194805194,69.8337,0.0536 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..326e1b7 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,77,40,0.5194805194805194,69.8337,0.0536 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/metrics/metrics_summary.json new file mode 100644 index 0000000..17b3d29 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/metrics/metrics_summary.json @@ -0,0 +1,52 @@ +{ + "trade_count": 77, + "win_rate": 0.5194805194805194, + "avg_win_pct": 0.073113647827567, + "avg_loss_pct": -0.039329840555458274, + "profit_factor": 1.018057074188992, + "expectancy_r": 0.05356543271575569, + "avg_r_multiple": 0.05356543271575569, + "total_return_pct": 0.07292559720991995, + "annualized_return_pct": 0.40424467410222764, + "max_drawdown_pct": 0.8904960176547445, + "calmar_ratio": 0.4539544996134479, + "sharpe_ratio": 0.1450757451336352, + "sortino_ratio": 0.20381256277636906, + "avg_daily_pnl": 1.62056882688711, + "avg_positions_held": 3.5, + "trade_skewness": 0.21072672790815783, + "trade_kurtosis": -0.35991089274419474, + "monthly_win_rate": 0.6666666666666666, + "equity_curve_r_squared": 0.006128571981766798, + "avg_holding_days": 3.3766233766233764, + "stop_exit_rate": 0.8571428571428571, + "target_exit_rate": 0.11688311688311688, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "medium_high": 0.46153846153846156, + "high": 0.5483870967741935, + "medium": 0.0 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4025974025974026, + 0.6363636363636364 + ], + "avg_win_pct_ci_95": [ + 0.055481912406328174, + 0.0927889265391391 + ], + "avg_loss_pct_ci_95": [ + -0.04386098705559402, + -0.03335305765816466 + ], + "profit_factor_ci_95": [ + 0.6348442203586265, + 1.7526170222810766 + ], + "expectancy_r_ci_95": [ + -0.007120998157792672, + 0.12468203591240727 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/metrics/score_bucket_report.csv new file mode 100644 index 0000000..e5db34e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/metrics/score_bucket_report.csv @@ -0,0 +1,4 @@ +score_bucket,trade_count,win_rate +high,62,0.5484 +medium,2,0.0 +medium_high,13,0.4615 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/resolved_config.json new file mode 100644 index 0000000..51399a6 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230358_534e7000/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.5, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 15, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..22a75c2 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/artifacts/position_timeline.parquet new file mode 100644 index 0000000..78ac2b9 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..731ece1 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/manifest.json new file mode 100644 index 0000000..fca3533 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step13_best", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 13: Best combo. 10% reaction + maxcand3 + score 0.60 + short hold 7d + vol 2.0x. Combines all positive findings from steps 10-12.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.6, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step13", + "best", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/metadata.json new file mode 100644 index 0000000..d17ad1f --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807", + "started_at": "2026-03-16T23:04:43.257174+00:00", + "finished_at": "2026-03-16T23:04:43.430624+00:00", + "elapsed_seconds": 0.17345, + "git_commit_hash": "764493d", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 14, + "split_name": "train" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..f686e24 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,523,265,0.5066921606118547,-2123.5288,0.0665 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..392cd77 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,523,265,0.5066921606118547,-2123.5288,0.0665 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/metrics/metrics_summary.json new file mode 100644 index 0000000..f62c726 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 523, + "win_rate": 0.5066921606118547, + "avg_win_pct": 0.06176005330847324, + "avg_loss_pct": -0.03586213974957642, + "profit_factor": 0.9277895576563163, + "expectancy_r": 0.06650559616370527, + "avg_r_multiple": 0.06650559616370527, + "total_return_pct": -2.1147577797892154, + "annualized_return_pct": -0.5817835872963584, + "max_drawdown_pct": 5.363056148167064, + "calmar_ratio": -0.10847986133712119, + "sharpe_ratio": -0.27517043819279563, + "sortino_ratio": -0.26534992431483057, + "avg_daily_pnl": -2.296153941139213, + "avg_positions_held": 1.3969631236442517, + "trade_skewness": -0.5915211498253831, + "trade_kurtosis": 0.3765581089156771, + "monthly_win_rate": 0.4857142857142857, + "equity_curve_r_squared": 0.19731788832163766, + "avg_holding_days": 4.173996175908222, + "stop_exit_rate": 0.6845124282982792, + "target_exit_rate": 0.26003824091778205, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.517814726840855, + "medium_high": 0.46078431372549017 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4627151051625239, + 0.5468451242829828 + ], + "avg_win_pct_ci_95": [ + 0.056435297073871855, + 0.0673635375259474 + ], + "avg_loss_pct_ci_95": [ + -0.038719102992205316, + -0.03325074041672668 + ], + "profit_factor_ci_95": [ + 0.7600100199252642, + 1.1223115306786142 + ], + "expectancy_r_ci_95": [ + 0.03745086169768331, + 0.09427813455646056 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/metrics/score_bucket_report.csv new file mode 100644 index 0000000..d28ff44 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,421,0.5178 +medium_high,102,0.4608 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/resolved_config.json new file mode 100644 index 0000000..9fc9520 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230443_15fa3807/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.6, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..2be7a4a Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/artifacts/position_timeline.parquet new file mode 100644 index 0000000..65fafc8 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..27edbb7 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/manifest.json new file mode 100644 index 0000000..fca3533 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step13_best", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 13: Best combo. 10% reaction + maxcand3 + score 0.60 + short hold 7d + vol 2.0x. Combines all positive findings from steps 10-12.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.6, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step13", + "best", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/metadata.json new file mode 100644 index 0000000..bec3670 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807", + "started_at": "2026-03-16T23:04:48.870106+00:00", + "finished_at": "2026-03-16T23:04:48.995414+00:00", + "elapsed_seconds": 0.125308, + "git_commit_hash": "764493d", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 1, + "split_name": "valid" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..616ddac --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,70,41,0.5857142857142857,1562.6792,0.1335 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..7216355 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,70,41,0.5857142857142857,1562.6792,0.1335 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/metrics/metrics_summary.json new file mode 100644 index 0000000..34ede80 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 70, + "win_rate": 0.5857142857142857, + "avg_win_pct": 0.0742841995575788, + "avg_loss_pct": -0.029687790889600586, + "profit_factor": 1.6081573944406065, + "expectancy_r": 0.13350775410348972, + "avg_r_multiple": 0.13350775410348972, + "total_return_pct": 1.5626791854819602, + "annualized_return_pct": 7.242276186624741, + "max_drawdown_pct": 0.6539417061012387, + "calmar_ratio": 11.074803945144833, + "sharpe_ratio": 2.2442047142281463, + "sortino_ratio": 4.279957910263686, + "avg_daily_pnl": 28.412348826944733, + "avg_positions_held": 2.7142857142857144, + "trade_skewness": -0.5402725001915989, + "trade_kurtosis": -0.5840994784130609, + "monthly_win_rate": 0.75, + "equity_curve_r_squared": 0.6019214395233538, + "avg_holding_days": 3.9, + "stop_exit_rate": 0.7285714285714285, + "target_exit_rate": 0.22857142857142856, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.543859649122807, + "medium_high": 0.7692307692307693 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4857142857142857, + 0.7 + ], + "avg_win_pct_ci_95": [ + 0.05737457568701041, + 0.09064283536032813 + ], + "avg_loss_pct_ci_95": [ + -0.03657107143152661, + -0.02339973217677251 + ], + "profit_factor_ci_95": [ + 0.9849502341256496, + 2.817779654607078 + ], + "expectancy_r_ci_95": [ + 0.06378972291627225, + 0.20236472296796962 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/metrics/score_bucket_report.csv new file mode 100644 index 0000000..8eb6be8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,57,0.5439 +medium_high,13,0.7692 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/resolved_config.json new file mode 100644 index 0000000..9fc9520 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230448_15fa3807/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.6, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..5d98be0 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/artifacts/position_timeline.parquet new file mode 100644 index 0000000..6160b1b Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..5cfcf56 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/manifest.json new file mode 100644 index 0000000..fca3533 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step13_best", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 13: Best combo. 10% reaction + maxcand3 + score 0.60 + short hold 7d + vol 2.0x. Combines all positive findings from steps 10-12.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.6, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step13", + "best", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/metadata.json new file mode 100644 index 0000000..ab75874 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807", + "started_at": "2026-03-16T23:04:54.002433+00:00", + "finished_at": "2026-03-16T23:04:54.131269+00:00", + "elapsed_seconds": 0.128836, + "git_commit_hash": "764493d", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 0, + "split_name": "test" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..a00d752 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,75,41,0.5466666666666666,426.2779,0.0642 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..807f133 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,75,41,0.5466666666666666,426.2779,0.0642 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/metrics/metrics_summary.json new file mode 100644 index 0000000..9ecda21 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 75, + "win_rate": 0.5466666666666666, + "avg_win_pct": 0.07178768057560496, + "avg_loss_pct": -0.039675290493302824, + "profit_factor": 1.1182776518630022, + "expectancy_r": 0.06424131352841454, + "avg_r_multiple": 0.06424131352841454, + "total_return_pct": 0.4293895929202554, + "annualized_return_pct": 2.3995301761035703, + "max_drawdown_pct": 0.8801478276052886, + "calmar_ratio": 2.7262808596962924, + "sharpe_ratio": 0.7812081874914202, + "sortino_ratio": 1.1045299884641355, + "avg_daily_pnl": 9.541990953783452, + "avg_positions_held": 3.239130434782609, + "trade_skewness": 0.16304423235338822, + "trade_kurtosis": -0.2668479445046241, + "monthly_win_rate": 1.0, + "equity_curve_r_squared": 0.08086932876015243, + "avg_holding_days": 3.28, + "stop_exit_rate": 0.8533333333333334, + "target_exit_rate": 0.12, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "medium_high": 0.46153846153846156, + "high": 0.5645161290322581 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4266666666666667, + 0.6533333333333333 + ], + "avg_win_pct_ci_95": [ + 0.05455068878977596, + 0.09181130743980183 + ], + "avg_loss_pct_ci_95": [ + -0.044984827453199025, + -0.03357043451354392 + ], + "profit_factor_ci_95": [ + 0.6705791773180493, + 1.937066695694822 + ], + "expectancy_r_ci_95": [ + 0.00040395915851316604, + 0.1290771702572712 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/metrics/score_bucket_report.csv new file mode 100644 index 0000000..a0d62eb --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,62,0.5645 +medium_high,13,0.4615 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/resolved_config.json new file mode 100644 index 0000000..9fc9520 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260316230454_15fa3807/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.6, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..a0f1901 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/artifacts/position_timeline.parquet new file mode 100644 index 0000000..9d69c0a Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..b93e740 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/manifest.json new file mode 100644 index 0000000..71e16dc --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/manifest.json @@ -0,0 +1,58 @@ +{ + "experiment_name": "pead_midcap_step14_score65", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 14: Higher score threshold 0.65 (from 0.60). Stronger signal filter to improve WR/PF with fewer trades.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step14", + "score65" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/metadata.json new file mode 100644 index 0000000..bbd240b --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d", + "started_at": "2026-03-17T02:08:37.623793+00:00", + "finished_at": "2026-03-17T02:08:37.800307+00:00", + "elapsed_seconds": 0.176514, + "git_commit_hash": "764493d", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 12, + "split_name": "train" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..eeed1f9 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,513,261,0.5087719298245614,-1925.0131,0.0667 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..bfadf6a --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,513,261,0.5087719298245614,-1925.0131,0.0667 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/metrics/metrics_summary.json new file mode 100644 index 0000000..3b6475b --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 513, + "win_rate": 0.5087719298245614, + "avg_win_pct": 0.06172726993762428, + "avg_loss_pct": -0.03587714844186037, + "profit_factor": 0.9330034935848248, + "expectancy_r": 0.06672425270139702, + "avg_r_multiple": 0.06672425270139702, + "total_return_pct": -1.9162420951407404, + "annualized_return_pct": -0.5267842099437092, + "max_drawdown_pct": 4.806826210534917, + "calmar_ratio": -0.10959085826510197, + "sharpe_ratio": -0.2519747350079355, + "sortino_ratio": -0.2409755060608505, + "avg_daily_pnl": -2.0806103095990665, + "avg_positions_held": 1.3741865509761388, + "trade_skewness": -0.6022592334756911, + "trade_kurtosis": 0.4107947364575222, + "monthly_win_rate": 0.45714285714285713, + "equity_curve_r_squared": 0.1816099118661623, + "avg_holding_days": 4.183235867446394, + "stop_exit_rate": 0.6861598440545809, + "target_exit_rate": 0.2573099415204678, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.517814726840855, + "medium_high": 0.4673913043478261 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4600389863547758, + 0.5555555555555556 + ], + "avg_win_pct_ci_95": [ + 0.05652278944587471, + 0.067518030240823 + ], + "avg_loss_pct_ci_95": [ + -0.038845247765728425, + -0.03312159731632588 + ], + "profit_factor_ci_95": [ + 0.7552342084840343, + 1.1669323356566097 + ], + "expectancy_r_ci_95": [ + 0.03626462473983762, + 0.09647506698922802 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/metrics/score_bucket_report.csv new file mode 100644 index 0000000..d55783f --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,421,0.5178 +medium_high,92,0.4674 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/resolved_config.json new file mode 100644 index 0000000..5fbf604 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317020837_45dfcf5d/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.65, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..d238b08 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/artifacts/position_timeline.parquet new file mode 100644 index 0000000..14b997e Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..5108b6e Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/manifest.json new file mode 100644 index 0000000..71e16dc --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/manifest.json @@ -0,0 +1,58 @@ +{ + "experiment_name": "pead_midcap_step14_score65", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 14: Higher score threshold 0.65 (from 0.60). Stronger signal filter to improve WR/PF with fewer trades.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step14", + "score65" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/metadata.json new file mode 100644 index 0000000..7b5871d --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d", + "started_at": "2026-03-17T02:10:38.913478+00:00", + "finished_at": "2026-03-17T02:10:39.045360+00:00", + "elapsed_seconds": 0.131882, + "git_commit_hash": "764493d", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 1, + "split_name": "valid" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..17b1253 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,66,37,0.5606060606060606,1015.6494,0.1084 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..573e247 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,66,37,0.5606060606060606,1015.6494,0.1084 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/metrics/metrics_summary.json new file mode 100644 index 0000000..5f0799e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 66, + "win_rate": 0.5606060606060606, + "avg_win_pct": 0.07159771212305432, + "avg_loss_pct": -0.029687790889600586, + "profit_factor": 1.3965047202887975, + "expectancy_r": 0.1084477010131475, + "avg_r_multiple": 0.1084477010131475, + "total_return_pct": 1.0156494476850202, + "annualized_return_pct": 4.662139245961949, + "max_drawdown_pct": 0.6560660596828557, + "calmar_ratio": 7.1062039822874565, + "sharpe_ratio": 1.5832382440205535, + "sortino_ratio": 2.7600094034379246, + "avg_daily_pnl": 18.466353594273095, + "avg_positions_held": 2.642857142857143, + "trade_skewness": -0.48174493035124905, + "trade_kurtosis": -0.6167292485734621, + "monthly_win_rate": 0.75, + "equity_curve_r_squared": 0.5472739451051728, + "avg_holding_days": 3.984848484848485, + "stop_exit_rate": 0.7575757575757576, + "target_exit_rate": 0.19696969696969696, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.543859649122807, + "medium_high": 0.6666666666666666 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4393939393939394, + 0.6818181818181818 + ], + "avg_win_pct_ci_95": [ + 0.05482466918413423, + 0.08936961937702613 + ], + "avg_loss_pct_ci_95": [ + -0.036662603001615564, + -0.023302500568286474 + ], + "profit_factor_ci_95": [ + 0.8182557169698457, + 2.5972802170352183 + ], + "expectancy_r_ci_95": [ + 0.038124296350169354, + 0.17938981190372472 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/metrics/score_bucket_report.csv new file mode 100644 index 0000000..ff679dc --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,57,0.5439 +medium_high,9,0.6667 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/resolved_config.json new file mode 100644 index 0000000..5fbf604 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021038_45dfcf5d/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.65, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..413894f Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/artifacts/position_timeline.parquet new file mode 100644 index 0000000..89da0f5 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..96d8f99 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/manifest.json new file mode 100644 index 0000000..71e16dc --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/manifest.json @@ -0,0 +1,58 @@ +{ + "experiment_name": "pead_midcap_step14_score65", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 14: Higher score threshold 0.65 (from 0.60). Stronger signal filter to improve WR/PF with fewer trades.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step14", + "score65" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/metadata.json new file mode 100644 index 0000000..187e435 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d", + "started_at": "2026-03-17T02:12:40.628636+00:00", + "finished_at": "2026-03-17T02:12:40.761467+00:00", + "elapsed_seconds": 0.132831, + "git_commit_hash": "764493d", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 0, + "split_name": "test" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..df8b02d --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,72,41,0.5694444444444444,725.3293,0.075 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..8bfcde2 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,72,41,0.5694444444444444,725.3293,0.075 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/metrics/metrics_summary.json new file mode 100644 index 0000000..bb1476e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 72, + "win_rate": 0.5694444444444444, + "avg_win_pct": 0.07178768057560496, + "avg_loss_pct": -0.04087226058308316, + "profit_factor": 1.2192504658724292, + "expectancy_r": 0.07496561481498636, + "avg_r_multiple": 0.07496561481498636, + "total_return_pct": 0.7284410148207681, + "annualized_return_pct": 4.098405282629525, + "max_drawdown_pct": 0.8797326864948225, + "calmar_ratio": 4.65869388002289, + "sharpe_ratio": 1.3410575857129192, + "sortino_ratio": 1.7508396255134193, + "avg_daily_pnl": 16.18757810712818, + "avg_positions_held": 3.108695652173913, + "trade_skewness": 0.1552834592355392, + "trade_kurtosis": -0.2861928056499625, + "monthly_win_rate": 1.0, + "equity_curve_r_squared": 0.20418254692529553, + "avg_holding_days": 3.3055555555555554, + "stop_exit_rate": 0.8472222222222222, + "target_exit_rate": 0.125, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "medium_high": 0.6, + "high": 0.5645161290322581 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4722222222222222, + 0.6805555555555556 + ], + "avg_win_pct_ci_95": [ + 0.05335055121924863, + 0.09087937745193758 + ], + "avg_loss_pct_ci_95": [ + -0.04580530727910989, + -0.0346775916302105 + ], + "profit_factor_ci_95": [ + 0.7371689462531391, + 2.0976797562696894 + ], + "expectancy_r_ci_95": [ + 0.012984590748254773, + 0.14099041139119411 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/metrics/score_bucket_report.csv new file mode 100644 index 0000000..4fff109 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,62,0.5645 +medium_high,10,0.6 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/resolved_config.json new file mode 100644 index 0000000..5fbf604 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021240_45dfcf5d/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.65, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..75b501f Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/artifacts/position_timeline.parquet new file mode 100644 index 0000000..8866299 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..9cc39db Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/manifest.json new file mode 100644 index 0000000..2e5fac4 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/manifest.json @@ -0,0 +1,58 @@ +{ + "experiment_name": "pead_midcap_step15_react7", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 15: Lower reaction threshold 0.07 (from 0.10). Wider funnel to increase trade count for statistical significance.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 2.0, + "score_threshold": 0.6, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step15", + "react7" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/metadata.json new file mode 100644 index 0000000..86c85f0 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec", + "started_at": "2026-03-17T02:13:02.365120+00:00", + "finished_at": "2026-03-17T02:13:02.539393+00:00", + "elapsed_seconds": 0.174273, + "git_commit_hash": "764493d", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 26, + "split_name": "train" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..6609e90 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,628,322,0.5127388535031847,-4050.6571,0.0605 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..fc07db9 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,628,322,0.5127388535031847,-4050.6571,0.0605 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/metrics/metrics_summary.json new file mode 100644 index 0000000..2a008f2 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 628, + "win_rate": 0.5127388535031847, + "avg_win_pct": 0.05817827405731796, + "avg_loss_pct": -0.03529691696480051, + "profit_factor": 0.8880804531017018, + "expectancy_r": 0.06045167117760914, + "avg_r_multiple": 0.06045167117760914, + "total_return_pct": -4.025803281272252, + "annualized_return_pct": -1.1154378795377173, + "max_drawdown_pct": 5.920220358574264, + "calmar_ratio": -0.18841154754015652, + "sharpe_ratio": -0.4789738641324651, + "sortino_ratio": -0.49062423727668025, + "avg_daily_pnl": -4.371121912347722, + "avg_positions_held": 1.7874186550976139, + "trade_skewness": -0.5866108231715665, + "trade_kurtosis": 0.10571292129651555, + "monthly_win_rate": 0.4473684210526316, + "equity_curve_r_squared": 0.5590772121494917, + "avg_holding_days": 4.412420382165605, + "stop_exit_rate": 0.6640127388535032, + "target_exit_rate": 0.25477707006369427, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.5246913580246914, + "medium_high": 0.47183098591549294 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4713375796178344, + 0.552547770700637 + ], + "avg_win_pct_ci_95": [ + 0.053753055522174194, + 0.06332892686684623 + ], + "avg_loss_pct_ci_95": [ + -0.03781073660642595, + -0.03286930735205427 + ], + "profit_factor_ci_95": [ + 0.7374163476544604, + 1.0812004194137232 + ], + "expectancy_r_ci_95": [ + 0.0326403041629129, + 0.08846926645006387 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/metrics/score_bucket_report.csv new file mode 100644 index 0000000..ad2b064 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,486,0.5247 +medium_high,142,0.4718 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/resolved_config.json new file mode 100644 index 0000000..8892e78 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021302_84f50bec/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.6, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..ec4035b Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/artifacts/position_timeline.parquet new file mode 100644 index 0000000..bd0fcef Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..4259684 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/manifest.json new file mode 100644 index 0000000..2e5fac4 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/manifest.json @@ -0,0 +1,58 @@ +{ + "experiment_name": "pead_midcap_step15_react7", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 15: Lower reaction threshold 0.07 (from 0.10). Wider funnel to increase trade count for statistical significance.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 2.0, + "score_threshold": 0.6, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step15", + "react7" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/metadata.json new file mode 100644 index 0000000..f8ae133 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec", + "started_at": "2026-03-17T02:13:08.741639+00:00", + "finished_at": "2026-03-17T02:13:08.869686+00:00", + "elapsed_seconds": 0.128047, + "git_commit_hash": "764493d", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 2, + "split_name": "valid" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..e21c945 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,84,52,0.6190476190476191,2609.4891,0.1663 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..a772a4d --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,84,52,0.6190476190476191,2609.4891,0.1663 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/metrics/metrics_summary.json new file mode 100644 index 0000000..7249033 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 84, + "win_rate": 0.6190476190476191, + "avg_win_pct": 0.07355629314614216, + "avg_loss_pct": -0.028169712426051593, + "profit_factor": 1.8949702222034897, + "expectancy_r": 0.16626153592330253, + "avg_r_multiple": 0.16626153592330253, + "total_return_pct": 2.6109835614154147, + "annualized_return_pct": 12.324882761962485, + "max_drawdown_pct": 0.9498747174984115, + "calmar_ratio": 12.97527193314638, + "sharpe_ratio": 3.486940708619626, + "sortino_ratio": 6.435367181544559, + "avg_daily_pnl": 47.472428389371174, + "avg_positions_held": 3.2857142857142856, + "trade_skewness": -0.6820490409965436, + "trade_kurtosis": -0.5472919506905338, + "monthly_win_rate": 0.75, + "equity_curve_r_squared": 0.5520524518827523, + "avg_holding_days": 3.892857142857143, + "stop_exit_rate": 0.6309523809523809, + "target_exit_rate": 0.2976190476190476, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.6031746031746031, + "medium_high": 0.6666666666666666 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.5119047619047619, + 0.7142857142857143 + ], + "avg_win_pct_ci_95": [ + 0.058939634717401546, + 0.0882214201813046 + ], + "avg_loss_pct_ci_95": [ + -0.034049084552223996, + -0.021678535330068346 + ], + "profit_factor_ci_95": [ + 1.1938856101993276, + 3.2609697013934 + ], + "expectancy_r_ci_95": [ + 0.10110190703597007, + 0.23457592125481785 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/metrics/score_bucket_report.csv new file mode 100644 index 0000000..6b51949 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,63,0.6032 +medium_high,21,0.6667 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/resolved_config.json new file mode 100644 index 0000000..8892e78 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021308_84f50bec/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.6, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..87b0a7c Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/artifacts/position_timeline.parquet new file mode 100644 index 0000000..790ff6d Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..08ba74e Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/manifest.json new file mode 100644 index 0000000..2e5fac4 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/manifest.json @@ -0,0 +1,58 @@ +{ + "experiment_name": "pead_midcap_step15_react7", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 15: Lower reaction threshold 0.07 (from 0.10). Wider funnel to increase trade count for statistical significance.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 2.0, + "score_threshold": 0.6, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step15", + "react7" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/metadata.json new file mode 100644 index 0000000..0d6ae82 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec", + "started_at": "2026-03-17T02:13:14.235245+00:00", + "finished_at": "2026-03-17T02:13:14.363709+00:00", + "elapsed_seconds": 0.128464, + "git_commit_hash": "764493d", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 1, + "split_name": "test" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..93266fc --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,91,50,0.5494505494505495,-269.2229,0.0487 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..059c8e7 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,91,50,0.5494505494505495,-269.2229,0.0487 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/metrics/metrics_summary.json new file mode 100644 index 0000000..9879ad0 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 91, + "win_rate": 0.5494505494505495, + "avg_win_pct": 0.05987858309009936, + "avg_loss_pct": -0.04018248443484526, + "profit_factor": 0.9429428813222424, + "expectancy_r": 0.048674103069745865, + "avg_r_multiple": 0.048674103069745865, + "total_return_pct": -0.2583590451609634, + "annualized_return_pct": -1.4214334862792555, + "max_drawdown_pct": 1.5948632659956048, + "calmar_ratio": -0.8912572736396405, + "sharpe_ratio": -0.4757579191663637, + "sortino_ratio": -1.0047653205186677, + "avg_daily_pnl": -5.741312114688077, + "avg_positions_held": 4.1521739130434785, + "trade_skewness": -0.34205487750442987, + "trade_kurtosis": -1.019084111214395, + "monthly_win_rate": 0.6666666666666666, + "equity_curve_r_squared": 0.5116157273037042, + "avg_holding_days": 3.4175824175824174, + "stop_exit_rate": 0.8241758241758241, + "target_exit_rate": 0.13186813186813187, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.5588235294117647, + "medium_high": 0.5217391304347826 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.45054945054945056, + 0.6483516483516484 + ], + "avg_win_pct_ci_95": [ + 0.04659125476609088, + 0.07361023609909033 + ], + "avg_loss_pct_ci_95": [ + -0.04457941678035646, + -0.03505144963684541 + ], + "profit_factor_ci_95": [ + 0.6121838644799084, + 1.4994265498683157 + ], + "expectancy_r_ci_95": [ + -0.0064711874370327665, + 0.1090320010502017 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/metrics/score_bucket_report.csv new file mode 100644 index 0000000..461fa97 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,68,0.5588 +medium_high,23,0.5217 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/resolved_config.json new file mode 100644 index 0000000..8892e78 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021314_84f50bec/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.6, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..9f4e12e Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/artifacts/position_timeline.parquet new file mode 100644 index 0000000..54b66c0 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..9a4cbe6 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/manifest.json new file mode 100644 index 0000000..2622c6e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step16_react7_score65", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 16: Combined lower reaction 0.07 + higher score 0.65. Wider funnel filtered by stricter quality gate.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step16", + "react7", + "score65" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/metadata.json new file mode 100644 index 0000000..5348a1e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22", + "started_at": "2026-03-17T02:13:23.109305+00:00", + "finished_at": "2026-03-17T02:13:23.277371+00:00", + "elapsed_seconds": 0.168066, + "git_commit_hash": "764493d", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 25, + "split_name": "train" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..3fda92f --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,613,315,0.5138662316476346,-3605.0827,0.0627 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..09a0073 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,613,315,0.5138662316476346,-3605.0827,0.0627 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/metrics/metrics_summary.json new file mode 100644 index 0000000..f1330c7 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 613, + "win_rate": 0.5138662316476346, + "avg_win_pct": 0.05891687437020322, + "avg_loss_pct": -0.03562351511382414, + "profit_factor": 0.8978576295267423, + "expectancy_r": 0.06271421258421411, + "avg_r_multiple": 0.06271421258421411, + "total_return_pct": -3.5846850918965063, + "annualized_return_pct": -0.9915756788257846, + "max_drawdown_pct": 5.32752847925319, + "calmar_ratio": -0.18612301795987454, + "sharpe_ratio": -0.429214740253919, + "sortino_ratio": -0.440355528419936, + "avg_daily_pnl": -3.8921662235575534, + "avg_positions_held": 1.729934924078091, + "trade_skewness": -0.5849102620066147, + "trade_kurtosis": 0.10048497442378945, + "monthly_win_rate": 0.4473684210526316, + "equity_curve_r_squared": 0.6021795418809431, + "avg_holding_days": 4.394779771615008, + "stop_exit_rate": 0.6655791190864601, + "target_exit_rate": 0.25938009787928223, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.5236139630390144, + "medium_high": 0.47619047619047616 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4763458401305057, + 0.5546492659053833 + ], + "avg_win_pct_ci_95": [ + 0.054290505932260265, + 0.06369001054624872 + ], + "avg_loss_pct_ci_95": [ + -0.038219486112982676, + -0.033142186600625345 + ], + "profit_factor_ci_95": [ + 0.7556467694140785, + 1.076349733130698 + ], + "expectancy_r_ci_95": [ + 0.03781208889906759, + 0.08836738250383509 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/metrics/score_bucket_report.csv new file mode 100644 index 0000000..c3c0c84 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,487,0.5236 +medium_high,126,0.4762 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/resolved_config.json new file mode 100644 index 0000000..9e85cac --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021323_5ffc5d22/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.65, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..17b4e6d Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/artifacts/position_timeline.parquet new file mode 100644 index 0000000..ec76f4d Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..fc532be Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/manifest.json new file mode 100644 index 0000000..2622c6e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step16_react7_score65", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 16: Combined lower reaction 0.07 + higher score 0.65. Wider funnel filtered by stricter quality gate.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step16", + "react7", + "score65" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/metadata.json new file mode 100644 index 0000000..056ce68 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22", + "started_at": "2026-03-17T02:13:29.609441+00:00", + "finished_at": "2026-03-17T02:13:29.736502+00:00", + "elapsed_seconds": 0.127061, + "git_commit_hash": "764493d", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 2, + "split_name": "valid" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..d5cd6ff --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,83,52,0.6265060240963856,2782.4851,0.1723 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..08cbd18 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,83,52,0.6265060240963856,2782.4851,0.1723 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/metrics/metrics_summary.json new file mode 100644 index 0000000..a09f30f --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 83, + "win_rate": 0.6265060240963856, + "avg_win_pct": 0.07355629314614216, + "avg_loss_pct": -0.027431638633343584, + "profit_factor": 2.0144135190573675, + "expectancy_r": 0.1723044902324546, + "avg_r_multiple": 0.1723044902324546, + "total_return_pct": 2.7839796082119577, + "annualized_return_pct": 13.181344862862087, + "max_drawdown_pct": 0.7815696983079155, + "calmar_ratio": 16.86521994314706, + "sharpe_ratio": 3.7031973701157277, + "sortino_ratio": 6.997964065248963, + "avg_daily_pnl": 50.61781105839923, + "avg_positions_held": 3.232142857142857, + "trade_skewness": -0.7098496733506752, + "trade_kurtosis": -0.45065855176369274, + "monthly_win_rate": 0.75, + "equity_curve_r_squared": 0.6307575259856522, + "avg_holding_days": 3.8795180722891565, + "stop_exit_rate": 0.6265060240963856, + "target_exit_rate": 0.30120481927710846, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.6031746031746031, + "medium_high": 0.7 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.5180722891566265, + 0.7228915662650602 + ], + "avg_win_pct_ci_95": [ + 0.060850759147795495, + 0.08780305876899128 + ], + "avg_loss_pct_ci_95": [ + -0.0339186411453489, + -0.02108470584083595 + ], + "profit_factor_ci_95": [ + 1.2172207824272716, + 3.676256868231999 + ], + "expectancy_r_ci_95": [ + 0.10577455161245414, + 0.24314480660669463 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/metrics/score_bucket_report.csv new file mode 100644 index 0000000..7f2e488 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,63,0.6032 +medium_high,20,0.7 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/resolved_config.json new file mode 100644 index 0000000..9e85cac --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021329_5ffc5d22/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.65, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..52eeb19 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/artifacts/position_timeline.parquet new file mode 100644 index 0000000..9b6327a Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..42b346a Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/manifest.json new file mode 100644 index 0000000..2622c6e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step16_react7_score65", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 16: Combined lower reaction 0.07 + higher score 0.65. Wider funnel filtered by stricter quality gate.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step16", + "react7", + "score65" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/metadata.json new file mode 100644 index 0000000..55a9b18 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22", + "started_at": "2026-03-17T02:13:35.172439+00:00", + "finished_at": "2026-03-17T02:13:35.300082+00:00", + "elapsed_seconds": 0.127643, + "git_commit_hash": "764493d", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 1, + "split_name": "test" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..a703b78 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,89,50,0.5617977528089888,-137.0681,0.0525 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..5c416a6 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,89,50,0.5617977528089888,-137.0681,0.0525 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/metrics/metrics_summary.json new file mode 100644 index 0000000..8d9883c --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 89, + "win_rate": 0.5617977528089888, + "avg_win_pct": 0.05987858309009936, + "avg_loss_pct": -0.04040542374850018, + "profit_factor": 0.9701741019713591, + "expectancy_r": 0.05249864084347278, + "avg_r_multiple": 0.05249864084347278, + "total_return_pct": -0.12615119218212204, + "annualized_return_pct": -0.6961385390092389, + "max_drawdown_pct": 1.5543376309462142, + "calmar_ratio": -0.44786829138625406, + "sharpe_ratio": -0.2273584908824299, + "sortino_ratio": -0.4651465695180815, + "avg_daily_pnl": -2.8033598262693786, + "avg_positions_held": 4.065217391304348, + "trade_skewness": -0.3715446667655314, + "trade_kurtosis": -1.0253534173624939, + "monthly_win_rate": 0.6666666666666666, + "equity_curve_r_squared": 0.4506121283479019, + "avg_holding_days": 3.449438202247191, + "stop_exit_rate": 0.8202247191011236, + "target_exit_rate": 0.1348314606741573, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.5588235294117647, + "medium_high": 0.5714285714285714 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4606741573033708, + 0.6629213483146067 + ], + "avg_win_pct_ci_95": [ + 0.0466433930834166, + 0.07393609251171794 + ], + "avg_loss_pct_ci_95": [ + -0.04481408020871433, + -0.03496643046971651 + ], + "profit_factor_ci_95": [ + 0.6110251453871614, + 1.5962438200956155 + ], + "expectancy_r_ci_95": [ + -0.008645899318934178, + 0.11432209511817257 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/metrics/score_bucket_report.csv new file mode 100644 index 0000000..c73fe2d --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,68,0.5588 +medium_high,21,0.5714 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/resolved_config.json new file mode 100644 index 0000000..9e85cac --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021335_5ffc5d22/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.65, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.07, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..b79768a Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/artifacts/position_timeline.parquet new file mode 100644 index 0000000..264b9a9 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..7b28bee Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/manifest.json new file mode 100644 index 0000000..198fa8e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step17_target2", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 17: Target ATR 2.0 (from 1.5). Based on step14 (score 0.65). Improves R:R from 3:1.5 to 3:2.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7, + "target_atr_multiplier": 2.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step17", + "target2" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/metadata.json new file mode 100644 index 0000000..875dd62 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e", + "started_at": "2026-03-17T02:14:23.549368+00:00", + "finished_at": "2026-03-17T02:14:23.723574+00:00", + "elapsed_seconds": 0.174206, + "git_commit_hash": "764493d", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 16, + "split_name": "train" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..190b0a2 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,470,221,0.4702127659574468,-1369.5288,0.052 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..8bdcee0 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,470,221,0.4702127659574468,-1369.5288,0.052 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/metrics/metrics_summary.json new file mode 100644 index 0000000..a603f2c --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 470, + "win_rate": 0.4702127659574468, + "avg_win_pct": 0.06463218718988971, + "avg_loss_pct": -0.035898924379169764, + "profit_factor": 0.9520470746330688, + "expectancy_r": 0.051977229799842324, + "avg_r_multiple": 0.051977229799842324, + "total_return_pct": -1.3594521214714332, + "annualized_return_pct": -0.37295443690339436, + "max_drawdown_pct": 4.642828380427953, + "calmar_ratio": -0.08032914558625517, + "sharpe_ratio": -0.17199413327760185, + "sortino_ratio": -0.17013635445771932, + "avg_daily_pnl": -1.4760609353652911, + "avg_positions_held": 1.4436008676789587, + "trade_skewness": -0.29258507013256424, + "trade_kurtosis": -0.012374465150950531, + "monthly_win_rate": 0.4857142857142857, + "equity_curve_r_squared": 0.0555160147865378, + "avg_holding_days": 4.534042553191489, + "stop_exit_rate": 0.774468085106383, + "target_exit_rate": 0.15319148936170213, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.4845360824742268, + "medium_high": 0.4024390243902439 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4276595744680851, + 0.5191489361702127 + ], + "avg_win_pct_ci_95": [ + 0.058433260029259186, + 0.07122838110057103 + ], + "avg_loss_pct_ci_95": [ + -0.03890564510749366, + -0.03331049341538755 + ], + "profit_factor_ci_95": [ + 0.7698818009274484, + 1.191298876108728 + ], + "expectancy_r_ci_95": [ + 0.019599495935574977, + 0.08637204701445492 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/metrics/score_bucket_report.csv new file mode 100644 index 0000000..7ce7c9f --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,388,0.4845 +medium_high,82,0.4024 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/resolved_config.json new file mode 100644 index 0000000..b9435f3 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021423_323a5b5e/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.65, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 2.0, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..7b33362 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/artifacts/position_timeline.parquet new file mode 100644 index 0000000..9128251 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..3e1422f Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/manifest.json new file mode 100644 index 0000000..198fa8e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step17_target2", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 17: Target ATR 2.0 (from 1.5). Based on step14 (score 0.65). Improves R:R from 3:1.5 to 3:2.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7, + "target_atr_multiplier": 2.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step17", + "target2" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/metadata.json new file mode 100644 index 0000000..461afd9 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e", + "started_at": "2026-03-17T02:14:29.439166+00:00", + "finished_at": "2026-03-17T02:14:29.565521+00:00", + "elapsed_seconds": 0.126355, + "git_commit_hash": "764493d", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 1, + "split_name": "valid" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..a84f4f7 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,59,30,0.5084745762711864,968.9947,0.0766 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..4be61de --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,59,30,0.5084745762711864,968.9947,0.0766 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/metrics/metrics_summary.json new file mode 100644 index 0000000..2344f3a --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 59, + "win_rate": 0.5084745762711864, + "avg_win_pct": 0.06902186149756807, + "avg_loss_pct": -0.029687790889600586, + "profit_factor": 1.3782504917267295, + "expectancy_r": 0.07656851400818651, + "avg_r_multiple": 0.07656851400818651, + "total_return_pct": 0.9689947279659247, + "annualized_return_pct": 4.444343303689946, + "max_drawdown_pct": 0.7074931393576637, + "calmar_ratio": 6.281818234626256, + "sharpe_ratio": 1.4547676904551556, + "sortino_ratio": 2.4918735031281867, + "avg_daily_pnl": 17.61808596301681, + "avg_positions_held": 2.6607142857142856, + "trade_skewness": 0.10584990812685979, + "trade_kurtosis": -0.32357543989078774, + "monthly_win_rate": 0.75, + "equity_curve_r_squared": 0.5050319501387333, + "avg_holding_days": 4.203389830508475, + "stop_exit_rate": 0.864406779661017, + "target_exit_rate": 0.0847457627118644, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.49019607843137253, + "medium_high": 0.625 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.3728813559322034, + 0.6440677966101694 + ], + "avg_win_pct_ci_95": [ + 0.04784353899961522, + 0.09374120617778123 + ], + "avg_loss_pct_ci_95": [ + -0.03562963639107497, + -0.023481315442560238 + ], + "profit_factor_ci_95": [ + 0.7112924296349984, + 2.615799046076873 + ], + "expectancy_r_ci_95": [ + -0.004848104112490605, + 0.15362659026551384 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/metrics/score_bucket_report.csv new file mode 100644 index 0000000..842eaa0 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,51,0.4902 +medium_high,8,0.625 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/resolved_config.json new file mode 100644 index 0000000..b9435f3 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021429_323a5b5e/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.65, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 2.0, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..12d9d1d Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/artifacts/position_timeline.parquet new file mode 100644 index 0000000..d3b2dfb Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..a859889 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/manifest.json new file mode 100644 index 0000000..198fa8e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step17_target2", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 17: Target ATR 2.0 (from 1.5). Based on step14 (score 0.65). Improves R:R from 3:1.5 to 3:2.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7, + "target_atr_multiplier": 2.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step17", + "target2" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/metadata.json new file mode 100644 index 0000000..e121dc4 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e", + "started_at": "2026-03-17T02:14:34.924363+00:00", + "finished_at": "2026-03-17T02:14:35.052692+00:00", + "elapsed_seconds": 0.128329, + "git_commit_hash": "764493d", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 0, + "split_name": "test" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..3541e24 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,66,35,0.5303030303030303,586.0229,0.0402 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..32e2b47 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,66,35,0.5303030303030303,586.0229,0.0402 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/metrics/metrics_summary.json new file mode 100644 index 0000000..0a0d25b --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 66, + "win_rate": 0.5303030303030303, + "avg_win_pct": 0.06851375056178613, + "avg_loss_pct": -0.04087226058308316, + "profit_factor": 1.1771413380479396, + "expectancy_r": 0.04024323277439004, + "avg_r_multiple": 0.04024323277439004, + "total_return_pct": 0.5891346601967962, + "annualized_return_pct": 3.3041728420364347, + "max_drawdown_pct": 0.8393581435568818, + "calmar_ratio": 3.9365470715928272, + "sharpe_ratio": 1.0752287681773998, + "sortino_ratio": 1.4227915731691994, + "avg_daily_pnl": 13.091881337706582, + "avg_positions_held": 3.130434782608696, + "trade_skewness": 0.3687959938994035, + "trade_kurtosis": -0.49742151152837355, + "monthly_win_rate": 1.0, + "equity_curve_r_squared": 0.11485286243962597, + "avg_holding_days": 3.4242424242424243, + "stop_exit_rate": 0.9393939393939394, + "target_exit_rate": 0.030303030303030304, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "medium_high": 0.5555555555555556, + "high": 0.5263157894736842 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.42424242424242425, + 0.6515151515151515 + ], + "avg_win_pct_ci_95": [ + 0.04874929099387959, + 0.0933964628368811 + ], + "avg_loss_pct_ci_95": [ + -0.045764401908824665, + -0.0347034314904391 + ], + "profit_factor_ci_95": [ + 0.6674077058093247, + 2.1087847945106746 + ], + "expectancy_r_ci_95": [ + -0.028319144729206144, + 0.11585565132283315 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/metrics/score_bucket_report.csv new file mode 100644 index 0000000..ffb1a3b --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,57,0.5263 +medium_high,9,0.5556 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/resolved_config.json new file mode 100644 index 0000000..b9435f3 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021434_323a5b5e/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.65, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 2.0, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..dcc1f78 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/artifacts/position_timeline.parquet new file mode 100644 index 0000000..41065af Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..ff253c6 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/manifest.json new file mode 100644 index 0000000..4bd8c2b --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step18_nofrac", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 18: Full exit at target (fraction 1.0 from 0.5). Based on step14 (score 0.65). Prevents trailing stop eating profits.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7, + "target_1_fraction": 1.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step18", + "nofrac" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/metadata.json new file mode 100644 index 0000000..69ffe68 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde", + "started_at": "2026-03-17T02:14:43.808093+00:00", + "finished_at": "2026-03-17T02:14:43.978017+00:00", + "elapsed_seconds": 0.169924, + "git_commit_hash": "764493d", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 8, + "split_name": "train" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..cbab68d --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,437,184,0.42105263157894735,-1311.0136,-0.0035 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..d3c1100 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,437,184,0.42105263157894735,-1311.0136,-0.0035 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/metrics/metrics_summary.json new file mode 100644 index 0000000..f39976c --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 437, + "win_rate": 0.42105263157894735, + "avg_win_pct": 0.055786833356269425, + "avg_loss_pct": -0.036073083203589304, + "profit_factor": 0.9549362257822231, + "expectancy_r": -0.00350372301018163, + "avg_r_multiple": -0.00350372301018163, + "total_return_pct": -1.303548314107451, + "annualized_return_pct": -0.3575442529836659, + "max_drawdown_pct": 4.52621951607018, + "calmar_ratio": -0.07899401514094, + "sharpe_ratio": -0.16411744461886982, + "sortino_ratio": -0.15599568186642976, + "avg_daily_pnl": -1.4153619045683508, + "avg_positions_held": 1.297180043383948, + "trade_skewness": 0.09855744470233073, + "trade_kurtosis": -0.3338303774907678, + "monthly_win_rate": 0.5142857142857142, + "equity_curve_r_squared": 0.048794422109098394, + "avg_holding_days": 4.05720823798627, + "stop_exit_rate": 0.7574370709382151, + "target_exit_rate": 0.18306636155606407, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.43175487465181056, + "medium_high": 0.3717948717948718 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.37528604118993136, + 0.4645308924485126 + ], + "avg_win_pct_ci_95": [ + 0.050054053910234896, + 0.06104257154875959 + ], + "avg_loss_pct_ci_95": [ + -0.038856339325186805, + -0.03333635785585507 + ], + "profit_factor_ci_95": [ + 0.7478442874594907, + 1.1773488569392971 + ], + "expectancy_r_ci_95": [ + -0.03438320200670505, + 0.023849475714168265 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/metrics/score_bucket_report.csv new file mode 100644 index 0000000..54b4169 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,359,0.4318 +medium_high,78,0.3718 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/resolved_config.json new file mode 100644 index 0000000..4bd687b --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021443_be3d5bde/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.65, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 1.0, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..ff7a435 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/artifacts/position_timeline.parquet new file mode 100644 index 0000000..0d8546a Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..77e8eac Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/manifest.json new file mode 100644 index 0000000..4bd8c2b --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step18_nofrac", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 18: Full exit at target (fraction 1.0 from 0.5). Based on step14 (score 0.65). Prevents trailing stop eating profits.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7, + "target_1_fraction": 1.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step18", + "nofrac" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/metadata.json new file mode 100644 index 0000000..4f5f0dc --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde", + "started_at": "2026-03-17T02:14:50.032669+00:00", + "finished_at": "2026-03-17T02:14:50.158206+00:00", + "elapsed_seconds": 0.125537, + "git_commit_hash": "764493d", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 1, + "split_name": "valid" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..53d99ea --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,55,26,0.4727272727272727,1180.3335,0.0448 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..d78291e --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,55,26,0.4727272727272727,1180.3335,0.0448 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/metrics/metrics_summary.json new file mode 100644 index 0000000..777504f --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 55, + "win_rate": 0.4727272727272727, + "avg_win_pct": 0.061089423907063775, + "avg_loss_pct": -0.029687790889600586, + "profit_factor": 1.4603949049610698, + "expectancy_r": 0.04475624608071933, + "avg_r_multiple": 0.04475624608071933, + "total_return_pct": 1.180333505141476, + "annualized_return_pct": 5.433752973850425, + "max_drawdown_pct": 0.624348249096465, + "calmar_ratio": 8.703080342923942, + "sharpe_ratio": 1.8572252886649268, + "sortino_ratio": 3.4469296543553756, + "avg_daily_pnl": 21.460609184390474, + "avg_positions_held": 2.4464285714285716, + "trade_skewness": 0.30040127974674696, + "trade_kurtosis": -0.8694130121084451, + "monthly_win_rate": 0.75, + "equity_curve_r_squared": 0.602794881326535, + "avg_holding_days": 3.963636363636364, + "stop_exit_rate": 0.7454545454545455, + "target_exit_rate": 0.2, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.44680851063829785, + "medium_high": 0.625 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.34545454545454546, + 0.6 + ], + "avg_win_pct_ci_95": [ + 0.04361640524795444, + 0.08084045495250257 + ], + "avg_loss_pct_ci_95": [ + -0.036609137725405846, + -0.023281797542879543 + ], + "profit_factor_ci_95": [ + 0.767083544117903, + 2.857144163746921 + ], + "expectancy_r_ci_95": [ + -0.02539156978016001, + 0.11831101331321375 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/metrics/score_bucket_report.csv new file mode 100644 index 0000000..2f2af8c --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,47,0.4468 +medium_high,8,0.625 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/resolved_config.json new file mode 100644 index 0000000..4bd687b --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021450_be3d5bde/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.65, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 1.0, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..c80d5e4 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/artifacts/position_timeline.parquet new file mode 100644 index 0000000..16a38e8 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..b7b78fc Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/manifest.json new file mode 100644 index 0000000..4bd8c2b --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/manifest.json @@ -0,0 +1,59 @@ +{ + "experiment_name": "pead_midcap_step18_nofrac", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 18: Full exit at target (fraction 1.0 from 0.5). Based on step14 (score 0.65). Prevents trailing stop eating profits.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 7 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 7, + "target_1_fraction": 1.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step18", + "nofrac" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/metadata.json new file mode 100644 index 0000000..1e8e641 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde", + "started_at": "2026-03-17T02:14:55.456672+00:00", + "finished_at": "2026-03-17T02:14:55.584257+00:00", + "elapsed_seconds": 0.127585, + "git_commit_hash": "764493d", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 0, + "split_name": "test" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..4193bc4 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,64,33,0.515625,772.8915,0.0266 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..03d45de --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,64,33,0.515625,772.8915,0.0266 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/metrics/metrics_summary.json new file mode 100644 index 0000000..e70964a --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 64, + "win_rate": 0.515625, + "avg_win_pct": 0.0634272939941408, + "avg_loss_pct": -0.04087226058308316, + "profit_factor": 1.233557927923508, + "expectancy_r": 0.02656695645642718, + "avg_r_multiple": 0.02656695645642718, + "total_return_pct": 0.7760032481101371, + "annualized_return_pct": 4.370716415195286, + "max_drawdown_pct": 0.8900540815144506, + "calmar_ratio": 4.910618923019145, + "sharpe_ratio": 1.4353513823455328, + "sortino_ratio": 1.877208513828647, + "avg_daily_pnl": 17.244516624669714, + "avg_positions_held": 2.9347826086956523, + "trade_skewness": 0.5190925256855792, + "trade_kurtosis": -0.5752996473384884, + "monthly_win_rate": 1.0, + "equity_curve_r_squared": 0.2803203771052179, + "avg_holding_days": 3.28125, + "stop_exit_rate": 0.84375, + "target_exit_rate": 0.125, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "medium_high": 0.5555555555555556, + "high": 0.509090909090909 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.40625, + 0.640625 + ], + "avg_win_pct_ci_95": [ + 0.04466427402153174, + 0.08620955136689483 + ], + "avg_loss_pct_ci_95": [ + -0.045558009582101815, + -0.0343611733090387 + ], + "profit_factor_ci_95": [ + 0.6849511026364801, + 2.2497150026014094 + ], + "expectancy_r_ci_95": [ + -0.0364135439317928, + 0.09732874555343708 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/metrics/score_bucket_report.csv new file mode 100644 index 0000000..2b0c66d --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,55,0.5091 +medium_high,9,0.5556 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/resolved_config.json new file mode 100644 index 0000000..4bd687b --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021455_be3d5bde/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.65, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 1.0, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 7, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 7, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..5926aeb Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/artifacts/position_timeline.parquet new file mode 100644 index 0000000..5c97d02 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..de72d4c Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/manifest.json new file mode 100644 index 0000000..39f1953 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/manifest.json @@ -0,0 +1,58 @@ +{ + "experiment_name": "pead_midcap_step19_hold5", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 19: Shorter hold 5d (from 7d). Based on step14 (score 0.65). Avg hold is 3.28d so 5d should be sufficient.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 5 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 5 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step19", + "hold5" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/metadata.json new file mode 100644 index 0000000..404e586 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2", + "started_at": "2026-03-17T02:15:04.338675+00:00", + "finished_at": "2026-03-17T02:15:04.515976+00:00", + "elapsed_seconds": 0.177301, + "git_commit_hash": "764493d", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 8, + "split_name": "train" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..d47ae54 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,513,256,0.49902534113060426,-1958.62,0.0635 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..731340d --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,513,256,0.49902534113060426,-1958.62,0.0635 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/metrics/metrics_summary.json new file mode 100644 index 0000000..c911c75 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 513, + "win_rate": 0.49902534113060426, + "avg_win_pct": 0.062202605197572945, + "avg_loss_pct": -0.03551270293463093, + "profit_factor": 0.9321637680892956, + "expectancy_r": 0.06348675520140099, + "avg_r_multiple": 0.06348675520140099, + "total_return_pct": -1.9498490194724147, + "annualized_return_pct": -0.5360894169666564, + "max_drawdown_pct": 5.109402635025258, + "calmar_ratio": -0.10492213185387497, + "sharpe_ratio": -0.2558888083394153, + "sortino_ratio": -0.24400180818562653, + "avg_daily_pnl": -2.117099912565054, + "avg_positions_held": 1.3004338394793926, + "trade_skewness": -0.5836312941799318, + "trade_kurtosis": 0.38668367472510967, + "monthly_win_rate": 0.4857142857142857, + "equity_curve_r_squared": 0.2741765876459641, + "avg_holding_days": 3.8693957115009745, + "stop_exit_rate": 0.6627680311890838, + "target_exit_rate": 0.24951267056530213, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.5083135391923991, + "medium_high": 0.45652173913043476 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4580896686159844, + 0.543859649122807 + ], + "avg_win_pct_ci_95": [ + 0.05682689185776403, + 0.06794610188648573 + ], + "avg_loss_pct_ci_95": [ + -0.03838050969908669, + -0.03291857815626014 + ], + "profit_factor_ci_95": [ + 0.7621203212530651, + 1.1516152896701046 + ], + "expectancy_r_ci_95": [ + 0.03413164205963457, + 0.09509438251494069 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/metrics/score_bucket_report.csv new file mode 100644 index 0000000..39c77cb --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,421,0.5083 +medium_high,92,0.4565 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/resolved_config.json new file mode 100644 index 0000000..e0076c3 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021504_27a63cc2/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.65, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 5, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 5, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..5882a39 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/artifacts/position_timeline.parquet new file mode 100644 index 0000000..afcc025 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..5e006a4 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/manifest.json new file mode 100644 index 0000000..39f1953 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/manifest.json @@ -0,0 +1,58 @@ +{ + "experiment_name": "pead_midcap_step19_hold5", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 19: Shorter hold 5d (from 7d). Based on step14 (score 0.65). Avg hold is 3.28d so 5d should be sufficient.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 5 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 5 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step19", + "hold5" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/metadata.json new file mode 100644 index 0000000..cd1f077 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2", + "started_at": "2026-03-17T02:15:10.630357+00:00", + "finished_at": "2026-03-17T02:15:10.754789+00:00", + "elapsed_seconds": 0.124432, + "git_commit_hash": "764493d", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 0, + "split_name": "valid" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..808cde0 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,68,39,0.5735294117647058,1411.6878,0.1237 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..66a8496 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,68,39,0.5735294117647058,1411.6878,0.1237 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/metrics/metrics_summary.json new file mode 100644 index 0000000..ba196f9 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 68, + "win_rate": 0.5735294117647058, + "avg_win_pct": 0.07160910490251128, + "avg_loss_pct": -0.029726703579869913, + "profit_factor": 1.5486357888297084, + "expectancy_r": 0.12374021043912341, + "avg_r_multiple": 0.12374021043912341, + "total_return_pct": 1.4116877933366923, + "annualized_return_pct": 6.525214842869276, + "max_drawdown_pct": 0.6559648601902311, + "calmar_ratio": 9.947506701772028, + "sharpe_ratio": 2.211147627502439, + "sortino_ratio": 3.7863148922601657, + "avg_daily_pnl": 25.66705078793986, + "avg_positions_held": 2.5535714285714284, + "trade_skewness": -0.5531304039524586, + "trade_kurtosis": -0.6426066699899948, + "monthly_win_rate": 0.6666666666666666, + "equity_curve_r_squared": 0.6062916819268495, + "avg_holding_days": 3.764705882352941, + "stop_exit_rate": 0.7205882352941176, + "target_exit_rate": 0.22058823529411764, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.559322033898305, + "medium_high": 0.6666666666666666 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.45588235294117646, + 0.6764705882352942 + ], + "avg_win_pct_ci_95": [ + 0.05482512162691855, + 0.0898433252693634 + ], + "avg_loss_pct_ci_95": [ + -0.035840031128755646, + -0.02306682033706807 + ], + "profit_factor_ci_95": [ + 0.896462622258289, + 2.816021175315767 + ], + "expectancy_r_ci_95": [ + 0.04737497209222317, + 0.19450879830009574 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/metrics/score_bucket_report.csv new file mode 100644 index 0000000..5d88217 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,59,0.5593 +medium_high,9,0.6667 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/resolved_config.json new file mode 100644 index 0000000..e0076c3 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021510_27a63cc2/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.65, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 5, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 5, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..d800d42 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/artifacts/position_timeline.parquet new file mode 100644 index 0000000..e34420a Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..1d35e47 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/manifest.json new file mode 100644 index 0000000..39f1953 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/manifest.json @@ -0,0 +1,58 @@ +{ + "experiment_name": "pead_midcap_step19_hold5", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 19: Shorter hold 5d (from 7d). Based on step14 (score 0.65). Avg hold is 3.28d so 5d should be sufficient.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 5 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 5 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step19", + "hold5" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/metadata.json new file mode 100644 index 0000000..0a18b6d --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2", + "started_at": "2026-03-17T02:15:16.314274+00:00", + "finished_at": "2026-03-17T02:15:16.441888+00:00", + "elapsed_seconds": 0.127614, + "git_commit_hash": "764493d", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 0, + "split_name": "test" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..0bd35f2 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,72,41,0.5694444444444444,662.8685,0.0733 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..3ef8391 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,72,41,0.5694444444444444,662.8685,0.0733 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/metrics/metrics_summary.json new file mode 100644 index 0000000..179acc4 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 72, + "win_rate": 0.5694444444444444, + "avg_win_pct": 0.07147359767518763, + "avg_loss_pct": -0.04087226058308316, + "profit_factor": 1.2003700113675047, + "expectancy_r": 0.07333898588560193, + "avg_r_multiple": 0.07333898588560193, + "total_return_pct": 0.6659802746770583, + "annualized_return_pct": 3.7416791900955593, + "max_drawdown_pct": 0.8797326864948225, + "calmar_ratio": 4.2532001453802755, + "sharpe_ratio": 1.2282104833801621, + "sortino_ratio": 1.6048455992917061, + "avg_daily_pnl": 14.799561659490186, + "avg_positions_held": 3.0652173913043477, + "trade_skewness": 0.17336106753316186, + "trade_kurtosis": -0.25537148787099184, + "monthly_win_rate": 1.0, + "equity_curve_r_squared": 0.16598640737563777, + "avg_holding_days": 3.25, + "stop_exit_rate": 0.8472222222222222, + "target_exit_rate": 0.125, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "medium_high": 0.6, + "high": 0.5645161290322581 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.4722222222222222, + 0.6805555555555556 + ], + "avg_win_pct_ci_95": [ + 0.053712800384199344, + 0.09042550687383535 + ], + "avg_loss_pct_ci_95": [ + -0.04572385273482796, + -0.03457772597753712 + ], + "profit_factor_ci_95": [ + 0.7352487192982754, + 2.0546750066970616 + ], + "expectancy_r_ci_95": [ + 0.009647478517021923, + 0.13943441763718736 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/metrics/score_bucket_report.csv new file mode 100644 index 0000000..4fff109 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,62,0.5645 +medium_high,10,0.6 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/resolved_config.json new file mode 100644 index 0000000..e0076c3 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021516_27a63cc2/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.65, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 0.5, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 5, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 5, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..0b13ab7 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/artifacts/position_timeline.parquet new file mode 100644 index 0000000..b61db50 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..d0dc564 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/manifest.json new file mode 100644 index 0000000..6d74792 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/manifest.json @@ -0,0 +1,60 @@ +{ + "experiment_name": "pead_midcap_step20_best3", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 20: Best combo. score 0.65 (step14) + nofrac 1.0 (step18) + hold 5d (step19). Combines all marginally positive Phase A+B findings.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 5 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 5, + "target_1_fraction": 1.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step20", + "best3", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/metadata.json new file mode 100644 index 0000000..cd82e5a --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f", + "started_at": "2026-03-17T02:15:59.367265+00:00", + "finished_at": "2026-03-17T02:15:59.532266+00:00", + "elapsed_seconds": 0.165001, + "git_commit_hash": "764493d", + "total_trading_days": 922, + "total_candidates_seen": 6987, + "total_orders_rejected": 4, + "split_name": "train" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..3829f0f --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,441,185,0.41950113378684806,-939.607,-0.0017 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..a5440dc --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,441,185,0.41950113378684806,-939.607,-0.0017 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/metrics/metrics_summary.json new file mode 100644 index 0000000..2304deb --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 441, + "win_rate": 0.41950113378684806, + "avg_win_pct": 0.05623485243875578, + "avg_loss_pct": -0.03564751818046933, + "profit_factor": 0.967683028533835, + "expectancy_r": -0.0017247328154696546, + "avg_r_multiple": -0.0017247328154696546, + "total_return_pct": -0.9321417326055089, + "annualized_return_pct": -0.2553248394503327, + "max_drawdown_pct": 4.355001847653457, + "calmar_ratio": -0.05862795203816175, + "sharpe_ratio": -0.11372635609251765, + "sortino_ratio": -0.10754065084655946, + "avg_daily_pnl": -1.0120974295390976, + "avg_positions_held": 1.2288503253796095, + "trade_skewness": 0.07552951232311288, + "trade_kurtosis": -0.3226127696503984, + "monthly_win_rate": 0.5142857142857142, + "equity_curve_r_squared": 0.038072957614376554, + "avg_holding_days": 3.7777777777777777, + "stop_exit_rate": 0.7256235827664399, + "target_exit_rate": 0.1746031746031746, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.4281767955801105, + "medium_high": 0.379746835443038 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.37188208616780044, + 0.46485260770975056 + ], + "avg_win_pct_ci_95": [ + 0.05103226839206002, + 0.06199208313935218 + ], + "avg_loss_pct_ci_95": [ + -0.038526503845993, + -0.03304933574535225 + ], + "profit_factor_ci_95": [ + 0.7746708789077718, + 1.1934897393746897 + ], + "expectancy_r_ci_95": [ + -0.030204060907575863, + 0.02514258731288471 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/metrics/score_bucket_report.csv new file mode 100644 index 0000000..5edcfcf --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,362,0.4282 +medium_high,79,0.3797 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/resolved_config.json new file mode 100644 index 0000000..c4a7f98 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021559_27d04e9f/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.65, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 1.0, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 5, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 5, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..853d232 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/artifacts/position_timeline.parquet new file mode 100644 index 0000000..535141d Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..1edba7d Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/manifest.json new file mode 100644 index 0000000..6d74792 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/manifest.json @@ -0,0 +1,60 @@ +{ + "experiment_name": "pead_midcap_step20_best3", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 20: Best combo. score 0.65 (step14) + nofrac 1.0 (step18) + hold 5d (step19). Combines all marginally positive Phase A+B findings.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 5 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 5, + "target_1_fraction": 1.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step20", + "best3", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/metadata.json new file mode 100644 index 0000000..c44cd7c --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f", + "started_at": "2026-03-17T02:16:05.201240+00:00", + "finished_at": "2026-03-17T02:16:05.330393+00:00", + "elapsed_seconds": 0.129153, + "git_commit_hash": "764493d", + "total_trading_days": 56, + "total_candidates_seen": 1497, + "total_orders_rejected": 0, + "split_name": "valid" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..765060a --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,56,27,0.48214285714285715,1573.3832,0.0579 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..85768ea --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,56,27,0.48214285714285715,1573.3832,0.0579 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/metrics/metrics_summary.json new file mode 100644 index 0000000..23fa6fd --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 56, + "win_rate": 0.48214285714285715, + "avg_win_pct": 0.06225245549813298, + "avg_loss_pct": -0.029726703579869913, + "profit_factor": 1.6107426395301327, + "expectancy_r": 0.057895169643472515, + "avg_r_multiple": 0.057895169643472515, + "total_return_pct": 1.5733831641505238, + "annualized_return_pct": 7.2932518195405915, + "max_drawdown_pct": 0.628137470729625, + "calmar_ratio": 11.610916653434122, + "sharpe_ratio": 2.475989818011853, + "sortino_ratio": 4.540332949148253, + "avg_daily_pnl": 28.606966620918616, + "avg_positions_held": 2.3392857142857144, + "trade_skewness": 0.21180606730435514, + "trade_kurtosis": -1.0147594785191094, + "monthly_win_rate": 0.6666666666666666, + "equity_curve_r_squared": 0.6343682913335291, + "avg_holding_days": 3.732142857142857, + "stop_exit_rate": 0.7142857142857143, + "target_exit_rate": 0.21428571428571427, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "high": 0.4583333333333333, + "medium_high": 0.625 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.35714285714285715, + 0.625 + ], + "avg_win_pct_ci_95": [ + 0.045831639615291464, + 0.08178241003342081 + ], + "avg_loss_pct_ci_95": [ + -0.03609471396453922, + -0.023249852120434403 + ], + "profit_factor_ci_95": [ + 0.8593701241775414, + 3.087917136764868 + ], + "expectancy_r_ci_95": [ + -0.013909496925683518, + 0.13534715015008442 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/metrics/score_bucket_report.csv new file mode 100644 index 0000000..c97b444 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,48,0.4583 +medium_high,8,0.625 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/resolved_config.json new file mode 100644 index 0000000..c4a7f98 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021605_27d04e9f/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.65, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 1.0, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 5, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 5, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/artifacts/daily_equity_curve.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/artifacts/daily_equity_curve.parquet new file mode 100644 index 0000000..863a042 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/artifacts/daily_equity_curve.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/artifacts/position_timeline.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/artifacts/position_timeline.parquet new file mode 100644 index 0000000..44dfcd4 Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/artifacts/position_timeline.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/artifacts/trade_blotter.parquet b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/artifacts/trade_blotter.parquet new file mode 100644 index 0000000..fda441c Binary files /dev/null and b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/artifacts/trade_blotter.parquet differ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/manifest.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/manifest.json new file mode 100644 index 0000000..6d74792 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/manifest.json @@ -0,0 +1,60 @@ +{ + "experiment_name": "pead_midcap_step20_best3", + "dataset_snapshot_id": "midcap-filtered", + "description": "Step 20: Best combo. score 0.65 (step14) + nofrac 1.0 (step18) + hold 5d (step19). Combines all marginally positive Phase A+B findings.", + "base_config": "configs/backtest/defaults.json", + "overrides": { + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "direction_filter": "any", + "max_holding_days_override": 5 + }, + "guidance_update": { + "enabled": false + }, + "management_change": { + "enabled": false + }, + "material_contract": { + "enabled": false + }, + "unknown": { + "enabled": false + }, + "other_material_event": { + "enabled": false + } + }, + "signal": { + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0, + "score_threshold": 0.65, + "max_candidates_per_day": 3 + }, + "execution": { + "max_holding_days": 5, + "target_1_fraction": 1.0 + }, + "risk": { + "max_positions": 8, + "max_positions_per_sector": 8, + "max_daily_new_risk_pct": 0.04, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "veto_oneoff_penalty": 1.0, + "veto_unknown_direction": false, + "veto_bearish_direction": false + } + }, + "splits": [], + "tags": [ + "pead", + "midcap", + "step20", + "best3", + "combo" + ], + "notes": null +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/metadata.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/metadata.json new file mode 100644 index 0000000..4732f0d --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/metadata.json @@ -0,0 +1,11 @@ +{ + "run_id": "bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f", + "started_at": "2026-03-17T02:16:10.856328+00:00", + "finished_at": "2026-03-17T02:16:10.980300+00:00", + "elapsed_seconds": 0.123972, + "git_commit_hash": "764493d", + "total_trading_days": 46, + "total_candidates_seen": 1498, + "total_orders_rejected": 0, + "split_name": "test" +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/metrics/attribution_by_event_type.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/metrics/attribution_by_event_type.csv new file mode 100644 index 0000000..53e0bcb --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/metrics/attribution_by_event_type.csv @@ -0,0 +1,2 @@ +event_type,count,wins,win_rate,net_pnl,avg_r +earnings_release,64,33,0.515625,713.0823,0.0247 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/metrics/attribution_by_sector.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/metrics/attribution_by_sector.csv new file mode 100644 index 0000000..0c197d5 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/metrics/attribution_by_sector.csv @@ -0,0 +1,2 @@ +sector,count,wins,win_rate,net_pnl,avg_r +Technology,64,33,0.515625,713.0823,0.0247 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/metrics/metrics_summary.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/metrics/metrics_summary.json new file mode 100644 index 0000000..75e2683 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/metrics/metrics_summary.json @@ -0,0 +1,51 @@ +{ + "trade_count": 64, + "win_rate": 0.515625, + "avg_win_pct": 0.06303706978453137, + "avg_loss_pct": -0.04087226058308316, + "profit_factor": 1.2154843477872561, + "expectancy_r": 0.024736998910869702, + "avg_r_multiple": 0.024736998910869702, + "total_return_pct": 0.7161940318976412, + "annualized_return_pct": 4.028381162820938, + "max_drawdown_pct": 0.8900540815144506, + "calmar_ratio": 4.525995943939204, + "sharpe_ratio": 1.3247288791439433, + "sortino_ratio": 1.7300097874668419, + "avg_daily_pnl": 15.915422931058695, + "avg_positions_held": 2.891304347826087, + "trade_skewness": 0.5404738880482032, + "trade_kurtosis": -0.5420366362520443, + "monthly_win_rate": 1.0, + "equity_curve_r_squared": 0.24515410354772538, + "avg_holding_days": 3.21875, + "stop_exit_rate": 0.84375, + "target_exit_rate": 0.125, + "no_follow_through_exit_rate": 0.0, + "score_bucket_hit_rate": { + "medium_high": 0.5555555555555556, + "high": 0.509090909090909 + }, + "bootstrap_cis": { + "win_rate_ci_95": [ + 0.40625, + 0.640625 + ], + "avg_win_pct_ci_95": [ + 0.04441068510387179, + 0.08723280668114561 + ], + "avg_loss_pct_ci_95": [ + -0.0455732637761196, + -0.034580080747582696 + ], + "profit_factor_ci_95": [ + 0.6698813188399805, + 2.213707778764144 + ], + "expectancy_r_ci_95": [ + -0.038310597007484175, + 0.09375528347198758 + ] + } +} \ No newline at end of file diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/metrics/score_bucket_report.csv b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/metrics/score_bucket_report.csv new file mode 100644 index 0000000..2b0c66d --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/metrics/score_bucket_report.csv @@ -0,0 +1,3 @@ +score_bucket,trade_count,win_rate +high,55,0.5091 +medium_high,9,0.5556 diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/notes/run_notes.md b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/notes/run_notes.md new file mode 100644 index 0000000..80ecda8 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/notes/run_notes.md @@ -0,0 +1,3 @@ +# Run Notes + +_No notes provided._ diff --git a/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/resolved_config.json b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/resolved_config.json new file mode 100644 index 0000000..c4a7f98 --- /dev/null +++ b/runs/midcap_steps/bt_baseline_swing_v1_midcap-filte_20260317021610_27d04e9f/resolved_config.json @@ -0,0 +1,124 @@ +{ + "strategy_name": "baseline_swing_v1", + "dataset_snapshot_id": "midcap-filtered", + "universe": { + "min_price": 5.0, + "min_avg_dollar_volume": 1000000.0, + "exclude_asset_types": [ + "ETF", + "FUND" + ], + "allowed_exchanges": null + }, + "signal": { + "score_threshold": 0.65, + "max_candidates_per_day": 3, + "execution_timing": "next_open", + "decision_timing": "reaction_close", + "ranking_fields": [ + "score", + "avg_dollar_volume" + ], + "scoring_model": "pead", + "pead_reaction_threshold": 0.1, + "pead_volume_threshold": 2.0 + }, + "risk": { + "per_trade_risk_pct": 0.005, + "max_daily_new_risk_pct": 0.04, + "max_positions": 8, + "max_positions_per_sector": 8, + "max_position_value_pct": 0.1, + "max_adv_fraction": 0.01, + "cooldown_after_loss_streak": 0, + "cooldown_days": 0, + "macro_regime_enabled": false, + "macro_regime_size_scaler": 1.0, + "macro_sma_period": 20, + "stop_atr_multiplier": 3.0, + "backtest_mode": "research", + "kill_switch_cooldown_days": 20, + "kill_switch_log_only": true, + "veto_oneoff_penalty": 1.0, + "veto_parse_confidence_min": 0.4, + "veto_unknown_direction": false, + "veto_bearish_direction": false + }, + "execution": { + "entry_fill_model": "next_open", + "exit_fill_model": "daily_bar_approximation", + "slippage_bps_base": 10.0, + "commission_per_share": 0.005, + "same_bar_priority": "stop_first_conservative", + "stop_model": null, + "target_model": "atr_multiple", + "target_1_r": 2.0, + "target_atr_multiplier": 1.5, + "target_1_fraction": 1.0, + "trailing_model": "pct_5", + "trailing_warmup_days": 2, + "max_holding_days": 5, + "no_follow_through_exit": false + }, + "reporting": { + "write_trade_blotter": true, + "write_equity_curve": true, + "write_metrics_summary": true, + "generate_plots": false, + "attribution_buckets": [ + "event_type", + "sector", + "score_bucket" + ] + }, + "event_type_profiles": { + "earnings_release": { + "enabled": true, + "score_threshold_override": null, + "max_holding_days_override": 5, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "guidance_update": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 15, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "management_change": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "material_contract": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": 10, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "unknown": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + }, + "other_material_event": { + "enabled": false, + "score_threshold_override": null, + "max_holding_days_override": null, + "stop_atr_multiplier_override": null, + "target_atr_multiplier_override": null, + "direction_filter": "any" + } + } +} \ No newline at end of file diff --git a/tests/fixtures/financial_data.json b/tests/fixtures/financial_data.json index d18aad4..50f39f8 100644 --- a/tests/fixtures/financial_data.json +++ b/tests/fixtures/financial_data.json @@ -3,8 +3,11 @@ "ticker": "AAPL", "name": "Apple Inc.", "cik": "0000320193", + "exchange": "NASDAQ", "sector": "Technology", - "industry": "Consumer Electronics" + "industry": "Consumer Electronics", + "country": "US", + "market_cap": 3500000000000.0 }, "financial_data": [ { diff --git a/tests/integration/test_feature_pipeline.py b/tests/integration/test_feature_pipeline.py index 0262954..d1aa5a5 100644 --- a/tests/integration/test_feature_pipeline.py +++ b/tests/integration/test_feature_pipeline.py @@ -52,6 +52,7 @@ async def test_feature_snapshot_created(db_session, sample_parser_output): event_type="earnings_release", event_direction="bullish", event_date=dt.date(2026, 1, 29), + filed_at_utc=dt.datetime(2026, 1, 29, 22, 0, tzinfo=dt.UTC), parser_version="rule-1.0.0", status="pending", ) @@ -130,6 +131,7 @@ async def test_financial_v1_snapshot_created(db_session, sample_parser_output): event_type="earnings_release", event_direction="bullish", event_date=dt.date(2026, 1, 29), + filed_at_utc=dt.datetime(2026, 1, 29, 22, 0, tzinfo=dt.UTC), parser_version="rule-1.0.0", status="pending", ) diff --git a/tests/integration/test_label_pipeline.py b/tests/integration/test_label_pipeline.py index c17ded2..f695a39 100644 --- a/tests/integration/test_label_pipeline.py +++ b/tests/integration/test_label_pipeline.py @@ -14,7 +14,7 @@ def mock_event() -> MagicMock: event = MagicMock() event.event_id = "EVT::sec::ISSUER::0000320193::2026-01-29::earnings_release::0" event.event_date = dt.date(2026, 1, 29) - event.filing_time_bucket = "post_market" + event.filed_at_utc = dt.datetime(2026, 1, 29, 22, 0, tzinfo=dt.UTC) # post_market event.symbol_id = "SYM::AAPL::NASDAQ" event.status = "valid" return event diff --git a/tests/unit/backtest/test_allocator.py b/tests/unit/backtest/test_allocator.py index af92976..f254b26 100644 --- a/tests/unit/backtest/test_allocator.py +++ b/tests/unit/backtest/test_allocator.py @@ -142,14 +142,16 @@ class TestComputeShares: # raw = 1000 / 3.3 ≈ 303.03 → floor = 303 assert shares == math.floor(1000.0 / 3.3) - def test_zero_when_stop_above_entry(self): + def test_stop_above_entry_valid_for_short(self): + """Stop above entry is valid for short positions -- compute_shares uses abs distance.""" from libs.backtest.allocator import compute_shares shares = compute_shares(100_000.0, 95.0, 100.0, RiskConfig( per_trade_risk_pct=0.01, max_daily_new_risk_pct=0.03, max_positions=10, max_positions_per_sector=3 )) - assert shares == 0 + # stop_distance = abs(95 - 100) = 5.0, risk = 1000, shares = floor(1000/5) = 200 + assert shares == 200 class TestRunEntryGates: diff --git a/tests/unit/backtest/test_scoring.py b/tests/unit/backtest/test_scoring.py index 85569b1..a374031 100644 --- a/tests/unit/backtest/test_scoring.py +++ b/tests/unit/backtest/test_scoring.py @@ -19,36 +19,30 @@ from libs.backtest.scoring import ( class TestReactionScore: - """Reaction day return scoring.""" + """Reaction day return scoring — simple directional mapping.""" - def test_sweet_spot_moderate_positive(self): - """Moderate positive return (0.5-3%) is ideal.""" - assert _reaction_score({"reaction_day_return": 0.01}) == 0.9 - assert _reaction_score({"reaction_day_return": 0.025}) == 0.9 + def test_strong_positive(self): + """Strong positive return (>3%).""" + assert _reaction_score({"reaction_day_return": 0.05}) == 0.7 + assert _reaction_score({"reaction_day_return": 0.15}) == 0.7 - def test_flat(self): - """Flat to slightly positive (0-0.5%).""" - assert _reaction_score({"reaction_day_return": 0.002}) == 0.65 + def test_positive(self): + """Positive return (0-3%).""" + assert _reaction_score({"reaction_day_return": 0.01}) == 0.6 + assert _reaction_score({"reaction_day_return": 0.025}) == 0.6 + assert _reaction_score({"reaction_day_return": 0.002}) == 0.6 - def test_large_positive_penalized(self): - """Large positive (3-8%) — getting priced in.""" - assert _reaction_score({"reaction_day_return": 0.05}) == 0.45 - - def test_extreme_positive_penalized(self): - """Extreme positive (>8%) — mean reversion risk.""" - assert _reaction_score({"reaction_day_return": 0.15}) == 0.2 - - def test_mild_negative(self): - """Small negative (-2% to 0%).""" - assert _reaction_score({"reaction_day_return": -0.01}) == 0.4 + def test_flat_to_mild_negative(self): + """Flat to mild negative (0% to -2%).""" + assert _reaction_score({"reaction_day_return": -0.01}) == 0.45 def test_moderate_negative(self): """Moderate negative (-5% to -2%).""" - assert _reaction_score({"reaction_day_return": -0.03}) == 0.25 + assert _reaction_score({"reaction_day_return": -0.03}) == 0.3 def test_strongly_bearish(self): """Strongly bearish (<-5%).""" - assert _reaction_score({"reaction_day_return": -0.08}) == 0.1 + assert _reaction_score({"reaction_day_return": -0.08}) == 0.2 def test_missing_returns_default(self): assert _reaction_score({}) == 0.5 @@ -134,29 +128,28 @@ class TestComputeEntryScore: assert score == pytest.approx(0.5, abs=0.02) def test_ideal_setup_scores_high(self): - """Moderate positive return + close near high + healthy volume. + """Positive return + healthy volume (no event features → event=0.5). - event=0.5*0.35 + reaction=0.9*0.25 + close=0.82*0.18 + volume=0.8*0.14 + gap=0.8*0.08 ≈ 0.72 + event=0.5*0.65 + reaction=0.6*0.20 + volume=0.8*0.15 ≈ 0.565 """ row = { - "reaction_day_return": 0.01, # sweet spot → 0.9 - "close_location": 0.8, # near high → 0.82 + "reaction_day_return": 0.01, # positive → 0.6 "volume_ratio_20d": 1.5, # conviction → 0.8 - "gap_size": 0.01, # orderly → 0.8 } score = compute_entry_score(row) - assert score > 0.65 + assert score > 0.55 def test_bearish_setup_scores_low(self): - """Negative return + close near low + below avg volume.""" + """Negative return + below avg volume (no event features → event=0.5). + + event=0.5*0.65 + reaction=0.3*0.20 + volume=0.3*0.15 ≈ 0.43 + """ row = { - "reaction_day_return": -0.04, # moderate negative → 0.25 - "close_location": 0.1, # near low → 0.19 + "reaction_day_return": -0.04, # moderate negative → 0.3 "volume_ratio_20d": 0.8, # no conviction → 0.3 - "gap_size": -0.03, # bearish gap → 0.2 } score = compute_entry_score(row) - assert score < 0.38 + assert score < 0.45 def test_extreme_positive_penalized(self): """Very large positive reaction should be penalized.""" @@ -204,23 +197,24 @@ class TestComputeEntryScore: assert aapl > ddog, f"AAPL-like {aapl:.3f} should beat DDOG-like {ddog:.3f}" def test_real_data_scores(self): - """Verify scores for real data samples match expectations.""" - # TSLA 1/2: bearish (return -2.6%, close near low) + """Verify scores for real data samples match expectations. + + With no event features, both get event=0.5. + TSLA: 0.5*0.65 + 0.3*0.20 + 0.6*0.15 = 0.475 + AAPL: 0.5*0.65 + 0.6*0.20 + 0.8*0.15 = 0.565 + """ + # TSLA 1/2: bearish (return -2.6%) tsla = compute_entry_score({ "reaction_day_return": -0.026, - "close_location": 0.12, "volume_ratio_20d": 1.13, - "gap_size": 0.018, }) - # AAPL 1/29: bullish (moderate +, close near high) + # AAPL 1/29: bullish (moderate +) aapl = compute_entry_score({ "reaction_day_return": 0.007, - "close_location": 0.74, "volume_ratio_20d": 1.45, - "gap_size": 0.006, }) - assert aapl > 0.65, f"AAPL should be above 0.65, got {aapl:.3f}" - assert tsla < 0.46, f"TSLA should be below 0.46, got {tsla:.3f}" + assert aapl > 0.55, f"AAPL should be above 0.55, got {aapl:.3f}" + assert tsla < 0.49, f"TSLA should be below 0.49, got {tsla:.3f}" assert aapl > tsla def test_event_features_boost_score(self): diff --git a/tests/unit/backtest/test_selector.py b/tests/unit/backtest/test_selector.py index 667612c..1b1e9fa 100644 --- a/tests/unit/backtest/test_selector.py +++ b/tests/unit/backtest/test_selector.py @@ -244,6 +244,25 @@ class TestFilterByEventType: assert len(filtered) == 1 assert filtered[0].symbol == "A" + def test_unknown_event_type_passes_when_in_profiles(self): + """Event type 'unknown' passes through when explicitly enabled in profiles.""" + from libs.backtest.selector import build_candidate, filter_by_event_type + + rows = [ + _make_raw_row(symbol="A", event_type="earnings_release"), + _make_raw_row(symbol="B", event_type="unknown"), + ] + candidates = [build_candidate(r) for r in rows if build_candidate(r)] + profiles = { + "earnings_release": EventTypeProfile(enabled=True), + "unknown": EventTypeProfile(enabled=True, direction_filter="any"), + } + filtered = filter_by_event_type(candidates, profiles) + assert len(filtered) == 2 + symbols = [c.symbol for c in filtered] + assert "A" in symbols + assert "B" in symbols + def test_no_profiles_passthrough(self): from libs.backtest.selector import build_candidate, filter_by_event_type diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index 40f17bd..e30e9f4 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -61,3 +61,28 @@ def test_get_symbols(tmp_path): os.chdir(old) # May return [] if configs/symbols.yaml not in cwd — just check type assert isinstance(syms, list) + + +@pytest.mark.unit +def test_symbols_file_configurable(tmp_path): + """symbols_file field can point to a custom YAML file.""" + import yaml + + from libs.common.config import Settings + + custom = tmp_path / "custom_symbols.yaml" + custom.write_text(yaml.dump({"symbols": ["CIEN", "CALX", "TENB"]})) + + s = Settings(symbols_file=str(custom)) + assert s.symbols_file == str(custom) + syms = s.get_symbols() + assert syms == ["CIEN", "CALX", "TENB"] + + +@pytest.mark.unit +def test_symbols_file_default(): + """Default symbols_file is configs/symbols.yaml.""" + from libs.common.config import Settings + + s = Settings() + assert s.symbols_file == "configs/symbols.yaml" diff --git a/tests/unit/test_event_features.py b/tests/unit/test_event_features.py index e109f1f..86c45a4 100644 --- a/tests/unit/test_event_features.py +++ b/tests/unit/test_event_features.py @@ -85,3 +85,5 @@ def test_compute_event_features(sample_parser_output): assert "signal_strength_score" in features assert "document_quality_score" in features assert "event_type" in features + assert "filing_time_bucket" in features + assert "event_date" in features diff --git a/tests/unit/test_labeler.py b/tests/unit/test_labeler.py index 47a3012..718e46f 100644 --- a/tests/unit/test_labeler.py +++ b/tests/unit/test_labeler.py @@ -130,7 +130,7 @@ class TestGenerateLabels: mock_event = MagicMock() mock_event.event_id = "EVT::test::001" mock_event.event_date = dt.date(2026, 1, 5) # Monday - mock_event.filing_time_bucket = "post_market" + mock_event.filed_at_utc = dt.datetime(2026, 1, 5, 22, 0, tzinfo=dt.UTC) # 5PM ET = post_market # Mock price service mock_price_svc = AsyncMock() @@ -180,7 +180,7 @@ class TestGenerateLabels: mock_event = MagicMock() mock_event.event_id = "EVT::test::002" mock_event.event_date = dt.date(2026, 1, 5) - mock_event.filing_time_bucket = "post_market" + mock_event.filed_at_utc = dt.datetime(2026, 1, 5, 22, 0, tzinfo=dt.UTC) mock_price_svc = AsyncMock() mock_price_svc.get_daily_bars = AsyncMock(side_effect=Exception("Oracle unavailable")) @@ -195,3 +195,82 @@ class TestGenerateLabels: ) assert label.label_status == "unavailable" + + @pytest.mark.asyncio + async def test_generate_labels_pre_market_same_day_reaction(self) -> None: + """Pre-market filing → reaction_date == event_date.""" + from libs.labeler.label_generator import generate_labels + + mock_event = MagicMock() + mock_event.event_id = "EVT::test::003" + mock_event.event_date = dt.date(2026, 1, 5) # Monday + mock_event.filed_at_utc = dt.datetime(2026, 1, 5, 12, 0, tzinfo=dt.UTC) # 7AM ET = pre_market + + # Create bars starting from entry_date (next day after reaction=event_date) + bars = [] + for i in range(8): + b = MagicMock() + date = dt.date(2026, 1, 6) + dt.timedelta(days=i) + b.model_dump.return_value = { + "date": date.isoformat(), + "open": 100.0 + i, + "high": 105.0 + i, + "low": 98.0, + "close": 103.0 + i, + } + bars.append(b) + + mock_resp = MagicMock() + mock_resp.bars = bars + mock_price_svc = AsyncMock() + mock_price_svc.get_daily_bars = AsyncMock(return_value=mock_resp) + mock_session = AsyncMock() + + label = await generate_labels( + session=mock_session, + event=mock_event, + price_svc=mock_price_svc, + ticker="AAPL", + ) + + # Pre-market on Monday → reaction_date == event_date (Monday) + assert label.reaction_date == dt.date(2026, 1, 5) + + @pytest.mark.asyncio + async def test_generate_labels_filed_at_utc_none_defaults_post_market(self) -> None: + """filed_at_utc=None → 'unknown' → next trading day.""" + from libs.labeler.label_generator import generate_labels + + mock_event = MagicMock() + mock_event.event_id = "EVT::test::004" + mock_event.event_date = dt.date(2026, 1, 5) # Monday + mock_event.filed_at_utc = None + + bars = [] + for i in range(8): + b = MagicMock() + date = dt.date(2026, 1, 7) + dt.timedelta(days=i) + b.model_dump.return_value = { + "date": date.isoformat(), + "open": 100.0 + i, + "high": 105.0 + i, + "low": 98.0, + "close": 103.0 + i, + } + bars.append(b) + + mock_resp = MagicMock() + mock_resp.bars = bars + mock_price_svc = AsyncMock() + mock_price_svc.get_daily_bars = AsyncMock(return_value=mock_resp) + mock_session = AsyncMock() + + label = await generate_labels( + session=mock_session, + event=mock_event, + price_svc=mock_price_svc, + ticker="AAPL", + ) + + # None → unknown → next trading day after Monday = Tuesday + assert label.reaction_date > dt.date(2026, 1, 5) diff --git a/tests/unit/test_oracle_client.py b/tests/unit/test_oracle_client.py index 9dc87b4..52eb4ca 100644 --- a/tests/unit/test_oracle_client.py +++ b/tests/unit/test_oracle_client.py @@ -143,6 +143,27 @@ async def test_get_financial_data(httpx_mock: HTTPXMock): assert len(result.periods) == 2 +@pytest.mark.asyncio +async def test_get_company_info(httpx_mock: HTTPXMock): + from libs.oracle_client.client import OracleClient + from libs.oracle_client.financial import FinancialService + + data = load_fixture("financial_data.json") + httpx_mock.add_response(json=data) + + async with OracleClient("http://oracle:18001") as client: + svc = FinancialService(client) + info = await svc.get_company_info("AAPL") + + assert info.ticker == "AAPL" + assert info.name == "Apple Inc." + assert info.cik == "0000320193" + assert info.sector == "Technology" + assert info.exchange == "NASDAQ" + assert info.country == "US" + assert info.market_cap == 3500000000000.0 + + @pytest.mark.asyncio async def test_connection_error_raises_oracle_connection_error(httpx_mock: HTTPXMock): """ConnectError 시 OracleConnectionError가 발생한다 (3회 재시도 후)."""