Files
The-Ouroboros/scripts/backtest_gate.sh
agentson 8396dc1606
Some checks are pending
Gitea CI / test (push) Waiting to run
Gitea CI / test (pull_request) Waiting to run
process: automate backtest gate for PR/push/schedule (#314)
2026-02-28 03:25:00 +09:00

107 lines
2.9 KiB
Bash
Executable File

#!/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 "$@"