Merge pull request 'process: automate backtest gate for PR/push/schedule (#314)' (#315) from feature/issue-314-backtest-gate-automation into feature/v3-session-policy-stream
Some checks are pending
Gitea CI / test (push) Waiting to run
Some checks are pending
Gitea CI / test (push) Waiting to run
This commit was merged in pull request #315.
This commit is contained in:
66
.github/workflows/backtest-gate.yml
vendored
Normal file
66
.github/workflows/backtest-gate.yml
vendored
Normal file
@@ -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
|
||||
@@ -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`
|
||||
|
||||
@@ -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가 이스케이프를 실제 개행으로 해석하지 않기 때문이다.
|
||||
|
||||
106
scripts/backtest_gate.sh
Executable file
106
scripts/backtest_gate.sh
Executable file
@@ -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 "$@"
|
||||
@@ -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 통과용 엔트리
|
||||
|
||||
Reference in New Issue
Block a user