Daily Trading Pipeline #215
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Daily Trading Pipeline | |
| on: | |
| schedule: | |
| # US market hours — run twice daily Mon-Fri | |
| # 6:45 AM PT = 14:45 UTC (PST, Nov-Mar) / 13:45 UTC (PDT, Mar-Nov) | |
| # 12:45 PM PT = 20:45 UTC (PST) / 19:45 UTC (PDT) | |
| # Using both UTC offsets to cover DST transitions | |
| - cron: '45 13 * * 1-5' # 6:45 AM PDT | |
| - cron: '45 14 * * 1-5' # 6:45 AM PST | |
| - cron: '45 19 * * 1-5' # 12:45 PM PDT | |
| - cron: '45 20 * * 1-5' # 12:45 PM PST | |
| workflow_dispatch: # Allow manual trigger from GitHub UI | |
| concurrency: | |
| group: daily-trading | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write # Needed for commit-back of state files | |
| env: | |
| ALPACA_API_KEY: ${{ secrets.ALPACA_API_KEY }} | |
| ALPACA_SECRET_KEY: ${{ secrets.ALPACA_SECRET_KEY }} | |
| ALPACA_PAPER: ${{ secrets.ALPACA_PAPER || 'true' }} | |
| FINNHUB_API_KEY: ${{ secrets.FINNHUB_API_KEY }} | |
| OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} | |
| TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} | |
| TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} | |
| SEC_EDGAR_USER_AGENT: ${{ secrets.SEC_EDGAR_USER_AGENT }} | |
| jobs: | |
| trade: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Install uv | |
| uses: astral-sh/setup-uv@v7 | |
| - name: Install Python and dependencies | |
| run: uv sync | |
| - name: Ensure data directory exists | |
| run: mkdir -p data | |
| - name: Restore state from trading-data branch | |
| run: | | |
| git fetch origin trading-data 2>/dev/null || true | |
| git checkout origin/trading-data -- data/ 2>/dev/null || echo "No prior trading data" | |
| # Git restores files at 0644; preflight requires 0600 on the state DB. | |
| chmod 600 data/trading_state.db 2>/dev/null || true | |
| - name: 0. Pre-flight Health Check | |
| id: preflight | |
| timeout-minutes: 2 | |
| run: uv run python scripts/preflight.py | |
| - name: 1. Position Manager (manage exits) | |
| id: position_manager | |
| if: steps.preflight.outcome == 'success' | |
| timeout-minutes: 10 | |
| run: uv run python scripts/position_manager.py --execute --check-sentiment | |
| - name: 2. Screener Auto-Trade (biggest dips) | |
| id: screener | |
| if: steps.preflight.outcome == 'success' && steps.position_manager.outcome == 'success' | |
| timeout-minutes: 10 | |
| run: uv run python scripts/screener_trade.py --min-dip -7.0 --max-trades 8 --amount 3000 --execute | |
| continue-on-error: true | |
| - name: 3. Watchlist Scan (3-layer analysis) | |
| id: watchlist | |
| if: steps.preflight.outcome == 'success' && steps.position_manager.outcome == 'success' | |
| timeout-minutes: 10 | |
| run: uv run python scripts/scan_with_sentiment.py --execute | |
| continue-on-error: true | |
| - name: 4. Momentum Auto-Trade (trend following) | |
| id: momentum | |
| if: steps.preflight.outcome == 'success' && steps.position_manager.outcome == 'success' | |
| timeout-minutes: 10 | |
| run: uv run python scripts/momentum_trade.py --min-momentum 10.0 --max-trades 5 --amount 3000 --execute | |
| continue-on-error: true | |
| - name: 5. Portfolio Summary | |
| if: always() | |
| timeout-minutes: 2 | |
| run: uv run python scripts/portfolio.py | |
| continue-on-error: true | |
| - name: 6. Performance Report | |
| if: always() | |
| timeout-minutes: 2 | |
| run: uv run python scripts/performance.py | |
| continue-on-error: true | |
| - name: 7. Strategy Agent (Event Check) | |
| if: always() | |
| timeout-minutes: 30 | |
| run: uv run python scripts/strategy_agent.py --mode event-check | |
| continue-on-error: true | |
| - name: Commit state to trading-data branch | |
| if: always() | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # Save data directory | |
| cp -r data/ /tmp/trading-data-snapshot/ | |
| # Switch to trading-data branch (create if needed) | |
| git fetch origin trading-data 2>/dev/null || true | |
| git checkout trading-data 2>/dev/null || { | |
| git checkout --orphan trading-data | |
| git rm -rf . 2>/dev/null || true | |
| } | |
| # Clean and restore data | |
| rm -rf data/ | |
| cp -r /tmp/trading-data-snapshot/ data/ | |
| git add data/ | |
| git diff --cached --quiet || git commit -m "chore: update trading state [$(date -u +%Y-%m-%dT%H:%M:%SZ)]" | |
| git push origin trading-data | |
| # Return to original branch for any subsequent steps | |
| git checkout ${{ github.ref_name }} 2>/dev/null || true | |
| - name: Alert on pipeline failures | |
| if: always() | |
| env: | |
| TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} | |
| TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} | |
| GH_REPO: ${{ github.repository }} | |
| GH_RUN_ID: ${{ github.run_id }} | |
| run: | | |
| FAILED="" | |
| [ "${{ steps.preflight.outcome }}" = "failure" ] && FAILED="${FAILED}preflight " | |
| [ "${{ steps.position_manager.outcome }}" = "failure" ] && FAILED="${FAILED}position_manager " | |
| [ "${{ steps.screener.outcome }}" = "failure" ] && FAILED="${FAILED}screener " | |
| [ "${{ steps.watchlist.outcome }}" = "failure" ] && FAILED="${FAILED}watchlist " | |
| [ "${{ steps.momentum.outcome }}" = "failure" ] && FAILED="${FAILED}momentum " | |
| if [ -n "$FAILED" ] && [ -n "$TELEGRAM_BOT_TOKEN" ] && [ -n "$TELEGRAM_CHAT_ID" ]; then | |
| curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ | |
| -d chat_id="$TELEGRAM_CHAT_ID" \ | |
| -d text="GHA Pipeline Alert: Failed steps: ${FAILED}. Check: https://github.com/${GH_REPO}/actions/runs/${GH_RUN_ID}" | |
| fi |