Skip to content

Commit 64d461f

Browse files
costajohntclaude
andauthored
Fix CI/CD: GHA /tmp bug, dep scanning, DST scheduling, pre-commit hooks (#97)
* fix: resolve 4 CI/CD issues (#58, #60, #72, #90) - #58: Replace /tmp snapshot with git stash for trading-data branch commits; add artifact backup for crash resilience - #60: Add pip-audit dependency scanning to CI pipeline and weekly security-audit.yml workflow with bandit - #72: Consolidate 4 DST-fragile cron entries into 2 UTC-based entries with a Python market-hours guard that skips off-hours runs - #90: Add pre-commit hooks (trailing-whitespace, end-of-file-fixer, check-yaml, check-json, check-merge-conflict, detect-private-key, bandit) and bandit config in pyproject.toml Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: resolve 4 review issues in CI/CD batch PR 1. Replace git stash with tar for trading-data branch switch — git stash pop fails with merge conflicts when applied across branches with divergent histories (confirmed via reproduction) 2. Fix market hours guard: hour >= 16 rejects 20:45 UTC during EDT (4:45 PM EDT), blocking the midday run every summer. Extend to >= 17. 3. Update bandit pre-commit hook rev 1.8.3 -> 1.9.4 to match uv.lock 4. Upload bandit JSON report as artifact in security-audit workflow Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent eb6e102 commit 64d461f

6 files changed

Lines changed: 389 additions & 22 deletions

File tree

.github/workflows/ci.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,21 @@ jobs:
5858
name: coverage-report
5959
path: htmlcov/
6060

61+
dependency-audit:
62+
runs-on: ubuntu-latest
63+
needs: secrets-scan
64+
steps:
65+
- uses: actions/checkout@v6
66+
67+
- name: Install uv
68+
uses: astral-sh/setup-uv@v7
69+
70+
- name: Install Python and dependencies
71+
run: uv sync
72+
73+
- name: Audit dependencies with pip-audit
74+
run: uv run pip-audit --strict --desc
75+
6176
integration-test:
6277
runs-on: ubuntu-latest
6378
needs: lint

.github/workflows/daily-trading.yml

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@ name: Daily Trading Pipeline
22

33
on:
44
schedule:
5-
# US market hours — run twice daily Mon-Fri
6-
# 6:45 AM PT = 14:45 UTC (PST, Nov-Mar) / 13:45 UTC (PDT, Mar-Nov)
7-
# 12:45 PM PT = 20:45 UTC (PST) / 19:45 UTC (PDT)
8-
# Using both UTC offsets to cover DST transitions
9-
- cron: '45 13 * * 1-5' # 6:45 AM PDT
10-
- cron: '45 14 * * 1-5' # 6:45 AM PST
11-
- cron: '45 19 * * 1-5' # 12:45 PM PDT
12-
- cron: '45 20 * * 1-5' # 12:45 PM PST
5+
# US market hours — run twice daily Mon-Fri (UTC times)
6+
# 14:45 UTC = 6:45 AM PST / 7:45 AM PDT (9:45-10:45 AM ET)
7+
# 20:45 UTC = 12:45 PM PST / 1:45 PM PDT (3:45 PM EST / 4:45 PM EDT)
8+
# Python market hours guard (9 AM - 5 PM ET) skips off-hours execution
9+
- cron: '45 14 * * 1-5' # Morning run
10+
- cron: '45 20 * * 1-5' # Midday run
1311
workflow_dispatch: # Allow manual trigger from GitHub UI
1412

1513
concurrency:
@@ -40,80 +38,116 @@ jobs:
4038
- name: Install Python and dependencies
4139
run: uv sync
4240

41+
- name: Check US market hours (skip if outside trading window)
42+
id: market_hours
43+
if: github.event_name == 'schedule'
44+
continue-on-error: true
45+
run: |
46+
uv run python -c "
47+
from datetime import datetime
48+
import pytz, sys
49+
now = datetime.now(pytz.timezone('US/Eastern'))
50+
hour = now.hour
51+
# Market open 9:30 AM - 4:00 PM ET
52+
# Allow buffer: 9 AM (pre-open) through 4:59 PM (post-close processing)
53+
# 20:45 UTC = 4:45 PM EDT, so hour=16 must be allowed
54+
if now.weekday() >= 5:
55+
print('Weekend -- skipping pipeline')
56+
sys.exit(1)
57+
if hour < 9 or hour >= 17:
58+
print(f'Outside market hours (ET hour={hour}) -- skipping pipeline')
59+
sys.exit(1)
60+
print(f'Market hours OK (ET hour={hour})')
61+
"
62+
4363
- name: Ensure data directory exists
64+
if: github.event_name != 'schedule' || steps.market_hours.outcome == 'success'
4465
run: mkdir -p data
4566

4667
- name: Restore state from trading-data branch
68+
if: github.event_name != 'schedule' || steps.market_hours.outcome == 'success'
4769
run: |
4870
git fetch origin trading-data 2>/dev/null || true
4971
git checkout origin/trading-data -- data/ 2>/dev/null || echo "No prior trading data"
5072
5173
- name: 1. Position Manager (manage exits)
5274
id: position_manager
75+
if: github.event_name != 'schedule' || steps.market_hours.outcome == 'success'
5376
run: uv run python scripts/position_manager.py --execute --check-sentiment
5477

5578
- name: 2. Screener Auto-Trade (biggest dips)
5679
id: screener
57-
if: steps.position_manager.outcome == 'success'
80+
if: (github.event_name != 'schedule' || steps.market_hours.outcome == 'success') && steps.position_manager.outcome == 'success'
5881
run: uv run python scripts/screener_trade.py --min-dip -7.0 --max-trades 8 --amount 3000 --execute
5982
continue-on-error: true
6083

6184
- name: 3. Watchlist Scan (3-layer analysis)
6285
id: watchlist
63-
if: steps.position_manager.outcome == 'success'
86+
if: (github.event_name != 'schedule' || steps.market_hours.outcome == 'success') && steps.position_manager.outcome == 'success'
6487
run: uv run python scripts/scan_with_sentiment.py --execute
6588
continue-on-error: true
6689

6790
- name: 4. Momentum Auto-Trade (trend following)
6891
id: momentum
69-
if: steps.position_manager.outcome == 'success'
92+
if: (github.event_name != 'schedule' || steps.market_hours.outcome == 'success') && steps.position_manager.outcome == 'success'
7093
run: uv run python scripts/momentum_trade.py --min-momentum 10.0 --max-trades 5 --amount 3000 --execute
7194
continue-on-error: true
7295

7396
- name: 5. Portfolio Summary
74-
if: always()
97+
if: always() && (github.event_name != 'schedule' || steps.market_hours.outcome == 'success')
7598
run: uv run python scripts/portfolio.py
7699
continue-on-error: true
77100

78101
- name: 6. Performance Report
79-
if: always()
102+
if: always() && (github.event_name != 'schedule' || steps.market_hours.outcome == 'success')
80103
run: uv run python scripts/performance.py
81104
continue-on-error: true
82105

83106
- name: 7. Strategy Agent (Event Check)
84-
if: always()
107+
if: always() && (github.event_name != 'schedule' || steps.market_hours.outcome == 'success')
85108
run: uv run python scripts/strategy_agent.py --mode event-check
86109
continue-on-error: true
87110

111+
- name: Backup trading data
112+
if: always() && (github.event_name != 'schedule' || steps.market_hours.outcome == 'success')
113+
uses: actions/upload-artifact@v6
114+
with:
115+
name: trading-data-backup
116+
path: data/
117+
retention-days: 7
118+
88119
- name: Commit state to trading-data branch
89-
if: always()
120+
if: always() && (github.event_name != 'schedule' || steps.market_hours.outcome == 'success')
121+
env:
122+
SOURCE_BRANCH: ${{ github.ref_name }}
90123
run: |
91124
git config user.name "github-actions[bot]"
92125
git config user.email "github-actions[bot]@users.noreply.github.com"
93126
94-
# Save data directory
95-
cp -r data/ /tmp/trading-data-snapshot/
127+
# Snapshot data directory (tar avoids git stash merge conflicts across branches)
128+
tar cf data-snapshot.tar data/
96129
97130
# Switch to trading-data branch (create if needed)
98131
git fetch origin trading-data 2>/dev/null || true
99-
git checkout trading-data 2>/dev/null || {
132+
git checkout -f trading-data 2>/dev/null || {
100133
git checkout --orphan trading-data
101134
git rm -rf . 2>/dev/null || true
102135
}
103136
104-
# Clean and restore data
137+
# Restore data from snapshot
105138
rm -rf data/
106-
cp -r /tmp/trading-data-snapshot/ data/
139+
tar xf data-snapshot.tar
140+
rm -f data-snapshot.tar
107141
108142
git add data/
109143
git diff --cached --quiet || git commit -m "chore: update trading state [$(date -u +%Y-%m-%dT%H:%M:%SZ)]"
110144
git push origin trading-data
111145
112146
# Return to original branch for any subsequent steps
113-
git checkout ${{ github.ref_name }} 2>/dev/null || true
147+
git checkout -f "$SOURCE_BRANCH" 2>/dev/null || true
114148
115149
- name: Alert on pipeline failures
116-
if: always()
150+
if: always() && (github.event_name != 'schedule' || steps.market_hours.outcome == 'success')
117151
env:
118152
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
119153
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Security Audit
2+
3+
on:
4+
schedule:
5+
# Weekly on Monday at 09:00 UTC
6+
- cron: '0 9 * * 1'
7+
workflow_dispatch:
8+
9+
jobs:
10+
dependency-audit:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v6
14+
15+
- name: Install uv
16+
uses: astral-sh/setup-uv@v7
17+
18+
- name: Install Python and dependencies
19+
run: uv sync
20+
21+
- name: Audit dependencies with pip-audit
22+
run: uv run pip-audit --strict --desc
23+
24+
bandit:
25+
runs-on: ubuntu-latest
26+
steps:
27+
- uses: actions/checkout@v6
28+
29+
- name: Install uv
30+
uses: astral-sh/setup-uv@v7
31+
32+
- name: Install Python and dependencies
33+
run: uv sync
34+
35+
- name: Run bandit security linter
36+
run: uv run bandit -c pyproject.toml -r strategies/ scripts/ -f json -o bandit-report.json || true
37+
38+
- name: Display bandit results
39+
if: always()
40+
run: uv run bandit -c pyproject.toml -r strategies/ scripts/ -ll
41+
42+
- name: Upload bandit report
43+
if: always()
44+
uses: actions/upload-artifact@v6
45+
with:
46+
name: bandit-report
47+
path: bandit-report.json
48+
retention-days: 30

.pre-commit-config.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v5.0.0
4+
hooks:
5+
- id: trailing-whitespace
6+
- id: end-of-file-fixer
7+
- id: check-yaml
8+
- id: check-json
9+
- id: check-merge-conflict
10+
- id: detect-private-key
11+
212
- repo: https://github.com/gitleaks/gitleaks
313
rev: v8.21.2
414
hooks:
@@ -11,3 +21,10 @@ repos:
1121
args: [--fix]
1222
- id: ruff-format
1323
args: [--check]
24+
25+
- repo: https://github.com/PyCQA/bandit
26+
rev: 1.9.4
27+
hooks:
28+
- id: bandit
29+
args: ["-c", "pyproject.toml"]
30+
additional_dependencies: ["bandit[toml]"]

pyproject.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ dependencies = [
1919

2020
[dependency-groups]
2121
dev = [
22+
"bandit[toml]>=1.8.0",
2223
"hypothesis>=6.151.9",
24+
"pip-audit>=2.7.0",
2325
"pre-commit>=4.0",
2426
"pytest>=9.0.2",
2527
"pytest-cov>=6.0",
@@ -56,3 +58,8 @@ exclude_lines = [
5658
"if __name__",
5759
"pragma: no cover",
5860
]
61+
62+
[tool.bandit]
63+
targets = ["strategies", "scripts"]
64+
exclude_dirs = ["tests"]
65+
skips = ["B101"] # Skip assert warnings (used extensively in tests)

0 commit comments

Comments
 (0)