Skip to content

fix: fetch live contract metadata from exchanges #110

fix: fetch live contract metadata from exchanges

fix: fetch live contract metadata from exchanges #110

Workflow file for this run

name: Tests
on:
push:
branches:
- dev
- development
- master
paths-ignore:
- 'docs/**'
- '*.md'
- '.readthedocs*.yaml'
pull_request:
branches:
- dev
- development
- master
schedule:
# Nightly full-suite run (02:30 UTC) — PRs only run the fast gate.
- cron: '30 2 * * *'
workflow_dispatch: # Allow manual trigger
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Fail on tracked build artifacts
shell: bash
run: |
tracked_artifacts="$(git ls-files | grep -E '(^|/)(build|dist)(/|$)|(^|/)[^/]+\.egg-info(/|$)' || true)"
if [ -n "$tracked_artifacts" ]; then
echo "Tracked build artifacts are not allowed:"
echo "$tracked_artifacts"
exit 1
fi
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- name: Install linting tools
run: |
python -m pip install --upgrade pip
pip install ruff 'black==26.1.0' isort 'mypy==1.16.1' bandit pip-audit
- name: Run ruff
run: ruff check backtrader/
- name: Check formatting with black
run: black --check --line-length 100 backtrader/
- name: Check imports with isort
run: isort --check-only backtrader/
- name: Guard against star imports outside __init__.py (F403)
shell: bash
run: |
violations="$(grep -rEn '^from .* import \*' backtrader --include='*.py' | grep -v '__init__.py' || true)"
if [ -n "$violations" ]; then
echo "Star imports are only allowed in __init__.py:"
echo "$violations"
exit 1
fi
- name: Type check with mypy
shell: bash
run: |
# mypy==1.16.1 keeps the Python 3.8 target available; the current
# gate is fully clean and should fail on any new type error.
MYPY_THRESHOLD=0
mypy backtrader --config-file=pyproject.toml | tee mypy-report.txt || true
count="$(grep -cE 'error:' mypy-report.txt || true)"
echo "mypy error count: ${count} (gate threshold ${MYPY_THRESHOLD})"
if [ "${count}" -gt "${MYPY_THRESHOLD}" ]; then
echo "::error::mypy errors (${count}) exceed threshold (${MYPY_THRESHOLD}). New type errors were introduced."
exit 1
fi
- name: Security scan with bandit (Medium/High blocking)
shell: bash
run: |
# R2-S3: Medium/High 阻塞,Low 仅报告。沿用 pyproject.toml [tool.bandit]
# 的 skips/exclude_dirs。已知的 2 处 Medium(influxfeed B608 / py3 B310)
# 已 triage 并加 # nosec 注释,当前 Medium/High = 0。
bandit -r backtrader -ll -q
echo "bandit Medium/High scan passed (Low findings are reported separately)"
- name: Bandit Low findings watchdog (iter12 S-1/S-4, non-blocking)
continue-on-error: true
shell: bash
run: |
# 迭代12 把所有 Low(B110/B112 静默异常 + B311 弱随机)逐条 triage:
# 安全敏感路径补日志,热路径加 `# nosec <code>` 带理由,弱随机加 # nosec。
# 收敛后基线 = 0。任何新增未 triage 的 Low 都会让计数升过基线并告警,
# 防止静默异常/弱随机回流。
count="$(bandit -r backtrader -c pyproject.toml -f custom \
--msg-template '{test_id}' 2>/dev/null | grep -cE '^B[0-9]+' || true)"
echo "bandit Low (untriaged) finding count: ${count} (iter12 baseline 0)"
if [ "${count}" -gt 0 ]; then
echo "::warning::Untriaged bandit Low findings (${count}) increased above baseline (0). Add a reasoned # nosec or fix."
fi
- name: Dependency vulnerability scan (pip-audit, non-blocking)
continue-on-error: true
shell: bash
run: |
# 迭代12 T-1: 持续监控依赖已知漏洞(CVE)。当前实测
# "No known vulnerabilities found"。先以非阻塞看板起步,稳定后可转阻塞。
pip-audit -r requirements.txt || true
- name: Complexity monitor (radon F-grade, non-blocking)
continue-on-error: true
shell: bash
run: |
# R2-S6: 非阻塞看板,监控 CC>40(F 级)函数数量,防止新增。
# 基线 13(R2-S6 把 _evaluate_signals 从 F/CC57 降到 C/CC17,抵消 R2-S4
# fast/slow-path 拆分使 _once_op 升到 F 的影响)。最高风险的撮合状态机/
# 事件主循环/line 基座函数刻意暂缓(见 Round 1 S5)。
pip install radon
count="$(radon cc backtrader -n F -s 2>/dev/null | grep -cE ' - F ' || true)"
echo "radon F-grade (CC>40) function count: ${count} (R2-S6 baseline 13)"
if [ "${count}" -gt 13 ]; then
echo "::warning::F-grade complex functions (${count}) increased above baseline (13)."
fi
test:
name: Test Python ${{ matrix.python-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
needs: lint
strategy:
fail-fast: false
matrix:
include:
# Ubuntu - test all Python versions (3.8 to 3.13)
- os: ubuntu-latest
python-version: '3.8'
- os: ubuntu-latest
python-version: '3.9'
- os: ubuntu-latest
python-version: '3.10'
- os: ubuntu-latest
python-version: '3.11'
- os: ubuntu-latest
python-version: '3.12'
- os: ubuntu-latest
python-version: '3.13'
# macOS - test all Python versions (3.8 to 3.13)
- os: macos-latest
python-version: '3.8'
- os: macos-latest
python-version: '3.9'
- os: macos-latest
python-version: '3.10'
- os: macos-latest
python-version: '3.11'
- os: macos-latest
python-version: '3.12'
- os: macos-latest
python-version: '3.13'
# Windows - test all Python versions (3.8 to 3.13)
- os: windows-latest
python-version: '3.8'
- os: windows-latest
python-version: '3.9'
- os: windows-latest
python-version: '3.10'
- os: windows-latest
python-version: '3.11'
- os: windows-latest
python-version: '3.12'
- os: windows-latest
python-version: '3.13'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- name: Install dependencies
shell: bash
run: |
python -m pip install --upgrade pip setuptools wheel
# Install from setup.py with dev extras
pip install -e ".[dev]"
- name: Debug environment
shell: bash
run: |
echo "Python version:"
python --version
echo "Installed packages:"
pip list | grep -E "pytest|numpy|pandas"
echo "Test files count:"
find tests -name "test_*.py" | wc -l
- name: Run tests
shell: bash
env:
PYTEST_ADDOPTS: ""
# PR 门禁跑快速分层(-m "not slow",~3.5min),push/nightly/手动跑全量。
EVENT_NAME: ${{ github.event_name }}
run: |
# Verify backtrader import
python -c "import backtrader; print(f'Backtrader imported: {backtrader.__version__}')"
# Show pytest and plugin versions
echo "=== Pytest info ==="
pip show pytest pytest-xdist pytest-timeout | grep -E "Name|Version"
# Choose tier by event: pull_request => fast gate; everything else => full suite.
if [ "${EVENT_NAME}" = "pull_request" ]; then
echo "=== PR fast gate: pytest -m 'not slow' ==="
pytest tests/ -m "not slow" -n auto --tb=short --timeout=300 -q
else
echo "=== Full suite: pytest tests/ ==="
pytest tests/ -n auto --tb=short --timeout=300 -q
fi
coverage:
name: Coverage (non-strategy subset, non-blocking floor)
runs-on: ubuntu-latest
needs: lint
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- name: Install dependencies
shell: bash
run: |
python -m pip install --upgrade pip setuptools wheel
pip install -e ".[dev]"
- name: Measure coverage with floor (R2-S5)
continue-on-error: true
shell: bash
run: |
# R2-S5: baseline 60% on the non-strategy subset (see docs/COVERAGE_BASELINE.md).
# Floor set to 55% (5% margin for parallel/sampling jitter). Non-blocking for
# now (continue-on-error); promote to blocking once stable.
pytest tests --ignore=tests/functional/strategies \
--cov=backtrader --cov-report=term-missing:skip-covered \
--cov-fail-under=55 -n auto --timeout=300 -q
test-summary:
name: Test Summary
runs-on: ubuntu-latest
needs: test
if: always()
steps:
- name: Check test results
run: |
if [ "${{ needs.test.result }}" == "success" ]; then
echo "All matrix tests passed."
else
echo "At least one matrix test job failed"
exit 1
fi