@@ -2,14 +2,12 @@ name: Daily Trading Pipeline
22
33on :
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
1513concurrency :
@@ -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 }}
0 commit comments