From 8396dc1606572cdf633667e78d28f6d37b49735b Mon Sep 17 00:00:00 2001 From: agentson Date: Sat, 28 Feb 2026 03:25:00 +0900 Subject: [PATCH] process: automate backtest gate for PR/push/schedule (#314) --- .github/workflows/backtest-gate.yml | 66 +++++++++++++++++ docs/testing.md | 23 ++++++ docs/workflow.md | 15 ++++ scripts/backtest_gate.sh | 106 ++++++++++++++++++++++++++++ workflow/session-handover.md | 16 +++++ 5 files changed, 226 insertions(+) create mode 100644 .github/workflows/backtest-gate.yml create mode 100755 scripts/backtest_gate.sh diff --git a/.github/workflows/backtest-gate.yml b/.github/workflows/backtest-gate.yml new file mode 100644 index 0000000..21bd8bf --- /dev/null +++ b/.github/workflows/backtest-gate.yml @@ -0,0 +1,66 @@ +name: Backtest Gate + +on: + pull_request: + branches: ["**"] + push: + branches: + - "feature/**" + schedule: + # Daily scheduled gate (KST 01:20) + - cron: "20 16 * * *" + workflow_dispatch: + inputs: + mode: + description: "backtest mode (auto|smoke|full)" + required: false + default: "auto" + base_ref: + description: "git base ref for changed-file diff" + required: false + default: "origin/main" + +jobs: + backtest-gate: + runs-on: ubuntu-latest + concurrency: + group: backtest-gate-${{ github.ref }} + cancel-in-progress: true + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python 3.11 + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: pip install ".[dev]" + + - name: Resolve base ref + id: base + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + echo "ref=origin/${{ github.base_ref }}" >> "$GITHUB_OUTPUT" + elif [ "${{ github.event_name }}" = "workflow_dispatch" ] && [ -n "${{ github.event.inputs.base_ref }}" ]; then + echo "ref=${{ github.event.inputs.base_ref }}" >> "$GITHUB_OUTPUT" + else + echo "ref=origin/main" >> "$GITHUB_OUTPUT" + fi + + - name: Run backtest gate + env: + BASE_REF: ${{ steps.base.outputs.ref }} + BACKTEST_MODE: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.mode || 'auto' }} + FORCE_FULL_BACKTEST: ${{ github.event_name == 'schedule' && 'true' || 'false' }} + run: bash scripts/backtest_gate.sh + + - name: Upload backtest logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: backtest-gate-logs + path: data/backtest-gate/*.log diff --git a/docs/testing.md b/docs/testing.md index 3ab75a5..83aa6af 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -181,6 +181,29 @@ pytest -v --cov=src --cov-report=term-missing **Note:** `main.py` has lower coverage as it contains the main loop which is tested via integration/manual testing. +## Backtest Automation Gate + +백테스트 관련 검증은 `scripts/backtest_gate.sh`와 `.github/workflows/backtest-gate.yml`로 자동 실행된다. + +- PR: 변경 파일 기준 `auto` 모드 +- `feature/**` push: 변경 파일 기준 `auto` 모드 +- Daily schedule: `full` 강제 실행 +- Manual dispatch: `mode`(`auto|smoke|full`) 지정 가능 + +실행 기준: +- `src/analysis/`, `src/strategy/`, `src/strategies/`, `src/main.py`, `src/markets/`, `src/broker/` +- 백테스트 핵심 테스트 파일 변경 +- `docs/ouroboros/` 변경 + +`auto` 모드에서 백테스트 민감 영역 변경이 없으면 게이트는 `skip` 처리되며 실패로 간주하지 않는다. + +로컬 수동 실행: +```bash +bash scripts/backtest_gate.sh +BACKTEST_MODE=full bash scripts/backtest_gate.sh +BASE_REF=origin/feature/v3-session-policy-stream BACKTEST_MODE=auto bash scripts/backtest_gate.sh +``` + ## Test Configuration ### `pyproject.toml` diff --git a/docs/workflow.md b/docs/workflow.md index 1b07639..9fb3c24 100644 --- a/docs/workflow.md +++ b/docs/workflow.md @@ -51,6 +51,21 @@ Gitea 이슈/PR/코멘트 작업 전에 모든 에이전트는 아래를 먼저 - Until final user sign-off, `main` merge is prohibited. - 각 에이전트는 주요 의사결정(리뷰 지적, 수정 방향, 검증 승인)마다 PR 코멘트를 적극 작성해 의사결정 과정을 남긴다. +## Backtest Gate Policy (Mandatory) + +사람 의존도를 줄이기 위해 백테스트 검증은 자동 게이트를 기본으로 한다. + +- 워크플로우: `.github/workflows/backtest-gate.yml` +- 실행 스크립트: `scripts/backtest_gate.sh` +- 기본 모드: `auto` (변경 파일 기반 실행/skip 판정) +- 정기 스케줄: daily `full` 강제 실행 +- 수동 재실행: workflow dispatch + `mode` 지정 + +강제 규칙: +- 백테스트 민감 변경(PR/feature push)에서 게이트 실패 시 머지 금지 +- 스케줄 게이트 실패 시 이슈 등록 후 원인/복구 계획 기록 +- `python` 대신 `python3` 기준으로 실행한다 + ## Gitea CLI Formatting Troubleshooting Issue/PR 본문 작성 시 줄바꿈(`\n`)이 문자열 그대로 저장되는 문제가 반복될 수 있다. 원인은 `-d "...\n..."` 형태에서 쉘/CLI가 이스케이프를 실제 개행으로 해석하지 않기 때문이다. diff --git a/scripts/backtest_gate.sh b/scripts/backtest_gate.sh new file mode 100755 index 0000000..ef413b6 --- /dev/null +++ b/scripts/backtest_gate.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +# Backtest gate for PR/push/scheduled verification. + +set -euo pipefail + +MODE="${BACKTEST_MODE:-auto}" # auto | smoke | full +BASE_REF="${BASE_REF:-origin/main}" # used when MODE=auto +FORCE_FULL="${FORCE_FULL_BACKTEST:-false}" +LOG_DIR="${LOG_DIR:-data/backtest-gate}" + +mkdir -p "$LOG_DIR" +STAMP="$(date -u +%Y%m%d_%H%M%S)" +LOG_FILE="$LOG_DIR/backtest_gate_${STAMP}.log" + +log() { + printf '%s %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$1" | tee -a "$LOG_FILE" +} + +run_cmd() { + log "[RUN] $*" + "$@" 2>&1 | tee -a "$LOG_FILE" +} + +resolve_mode_from_changes() { + if [ "$FORCE_FULL" = "true" ]; then + echo "full" + return + fi + + if ! git rev-parse --verify "$BASE_REF" >/dev/null 2>&1; then + log "[WARN] BASE_REF not found: $BASE_REF; fallback to full" + echo "full" + return + fi + + changed_files="$(git diff --name-only "$BASE_REF"...HEAD || true)" + if [ -z "$changed_files" ]; then + log "[INFO] no changed files between $BASE_REF...HEAD; skip backtest gate" + echo "skip" + return + fi + + log "[INFO] changed files from $BASE_REF...HEAD:" + while IFS= read -r line; do + [ -n "$line" ] && log " - $line" + done <<< "$changed_files" + + # Backtest-sensitive areas: analysis/strategy/runtime execution semantics. + if printf '%s\n' "$changed_files" | rg -q \ + '^(src/analysis/|src/strategy/|src/strategies/|src/main.py|src/markets/|src/broker/|tests/test_backtest_|tests/test_triple_barrier.py|tests/test_walk_forward_split.py|tests/test_main.py|docs/ouroboros/)' + then + echo "full" + else + echo "skip" + fi +} + +SMOKE_TESTS=( + tests/test_backtest_pipeline_integration.py + tests/test_triple_barrier.py + tests/test_walk_forward_split.py + tests/test_backtest_cost_guard.py + tests/test_backtest_execution_model.py +) + +FULL_TESTS=( + tests/test_backtest_pipeline_integration.py + tests/test_triple_barrier.py + tests/test_walk_forward_split.py + tests/test_backtest_cost_guard.py + tests/test_backtest_execution_model.py + tests/test_main.py +) + +main() { + log "[INFO] backtest gate started mode=$MODE base_ref=$BASE_REF force_full=$FORCE_FULL" + + selected_mode="$MODE" + if [ "$MODE" = "auto" ]; then + selected_mode="$(resolve_mode_from_changes)" + fi + + case "$selected_mode" in + skip) + log "[PASS] backtest gate skipped (no backtest-sensitive changes)" + exit 0 + ;; + smoke) + run_cmd python3 -m pytest -q "${SMOKE_TESTS[@]}" + log "[PASS] smoke backtest gate passed" + ;; + full) + run_cmd python3 -m pytest -q "${SMOKE_TESTS[@]}" + # Runtime semantics tied to v2 staged-exit must remain covered in full gate. + run_cmd python3 -m pytest -q tests/test_main.py -k \ + "staged_exit_override or runtime_exit_cache_cleared or run_daily_session_applies_staged_exit_override_on_hold" + log "[PASS] full backtest gate passed" + ;; + *) + log "[FAIL] invalid BACKTEST_MODE=$selected_mode (expected auto|smoke|full)" + exit 2 + ;; + esac +} + +main "$@" diff --git a/workflow/session-handover.md b/workflow/session-handover.md index 8f2b222..89d09c8 100644 --- a/workflow/session-handover.md +++ b/workflow/session-handover.md @@ -65,3 +65,19 @@ - next_ticket: #305 - process_gate_checked: process_ticket=#306,#308 merged_to_feature_branch=yes - risks_or_notes: 티켓 브랜치 분기 후 strict gate 재통과를 위한 엔트리 추가 + +### 2026-02-27 | session=codex-backtest-gate-automation +- branch: feature/v3-session-policy-stream +- docs_checked: docs/workflow.md, docs/commands.md, docs/agent-constraints.md +- open_issues_reviewed: #304, #305 +- next_ticket: (create) backtest automation gate +- process_gate_checked: process_ticket=#306,#308 merged_to_feature_branch=yes +- risks_or_notes: 백테스트 자동화 누락 재발 방지 위해 이슈/티켓 브랜치/PR 절차로 즉시 정규화 + +### 2026-02-27 | session=codex-issue314-ticket-branch +- branch: feature/issue-314-backtest-gate-automation +- docs_checked: docs/workflow.md, docs/commands.md, docs/agent-constraints.md +- open_issues_reviewed: #314 +- next_ticket: #314 +- process_gate_checked: process_ticket=#306,#308 merged_to_feature_branch=yes +- risks_or_notes: 백테스트 자동 게이트 도입 티켓 브랜치 strict gate 통과용 엔트리 -- 2.49.1