Daily Trading (auto) #51
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 (auto) | |
| # Autonomous daily Kalshi paper-trading routine for the Aratea predictor. | |
| # | |
| # Two halves run every day: | |
| # A. Learning capture (daily_run.py): fetch_markets --all-weather → | |
| # forward_predict → score_forward. This is what WRITES the learned | |
| # model's training fuel (predictor/data/predictions/forward_*.json) | |
| # and closes the resolution loop (predictor/data/scores/). Without it | |
| # the learning dataset stops growing distinct target_dates — the bug | |
| # fixed on 2026-06-05 (daily_auto.py had silently replaced this half). | |
| # B. Paper-trading (daily_auto.py): | |
| # 1. Auto-finalizes any open runs that Kalshi has settled | |
| # 2. Auto-captures new paper runs across the configured event series | |
| # (per-event |edge| / spread thresholds) | |
| # 3. Rebuilds the dashboard manifest | |
| # Then commits + pushes the result so Vercel auto-deploys the dashboard. | |
| # | |
| # Idempotent. Failing to capture (event not yet published, no qualifying | |
| # bin) exits 0 — the workflow retries the next day. The learning-capture | |
| # step is continue-on-error so a transient forecast/Kalshi hiccup never | |
| # blocks the paper-trading half or the commit. | |
| # | |
| # Security note: this workflow grants `contents: write` to GITHUB_TOKEN | |
| # only because it needs to push the captured report + updated ledger + | |
| # rebuilt manifest. Other workflows in the repo remain read-only by | |
| # default (Settings → Actions → Workflow permissions = "Read repository | |
| # contents and packages permissions"). PR forks cannot trigger this | |
| # workflow (schedule + workflow_dispatch only) so the surface area is | |
| # limited to the maintainer. | |
| on: | |
| schedule: | |
| # 18:00 UTC every day. That's 19:00 Paris (CET winter), 20:00 Paris (CEST summer). | |
| # Chosen so that: | |
| # - NWS Central Park has already published the daily climate report | |
| # for "yesterday" (typically 11-13h UTC), letting auto-finalize close | |
| # out the previous open run without a one-day lag. | |
| # - Kalshi bins for the J+1 event have had a full US trading day to | |
| # attract market-maker quotes, so the |edge|/spread thresholds in | |
| # auto-capture are reachable instead of systematically skipping the | |
| # fresh-publish illiquid window. | |
| # Earlier (14h UTC) saw both finalize and capture systematically skip. | |
| - cron: '0 18 * * *' | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: 'Dry run (do not write report.json, ledger, manifest, or push)' | |
| required: false | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: write | |
| concurrency: | |
| # Prevent overlap if a manual workflow_dispatch lands while a cron run is in flight. | |
| group: daily-trading | |
| cancel-in-progress: false | |
| jobs: | |
| daily-auto: | |
| runs-on: ubuntu-latest | |
| # 50 min : la learning capture prend 20-25 min quand les APIs météo/Kalshi | |
| # rament (runs des 9-10 juin tués à 25 min → site figé au 8 juin). Le | |
| # garde-fou fin est le timeout par step dans daily_run.py (13 min/step), | |
| # qui laisse daily_auto + le push du manifest tourner même si un step cale. | |
| timeout-minutes: 50 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| with: | |
| # Need full history for git operations later in the job. | |
| fetch-depth: 0 | |
| # Ne PAS persister de credentials dans .git/config (revue 2026-06-10 | |
| # D2). Avant, `token: BOT_PAT` laissait le PAT en clair dans | |
| # .git/config pendant TOUT le job. Le clone se fait avec le | |
| # GITHUB_TOKEN par défaut (lecture suffit) ; le PAT fine-grained | |
| # (BOT_PAT, admin du repo, bypass des règles de branche) n'est | |
| # injecté qu'à l'étape push, et seulement là. | |
| persist-credentials: false | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: '3.13' | |
| cache: 'pip' | |
| - name: Install dependencies | |
| working-directory: predictor | |
| # requirements.lock pinne toutes les transitives avec --generate-hashes. | |
| # --require-hashes refuse silencieusement toute dep sans hash : une | |
| # supply-chain compromise sur PyPI (typosquat, mirror malveillant) ne | |
| # peut pas faire glisser un package non listé dans le run quotidien. | |
| # Regen procedure : pip-compile --generate-hashes --output-file= | |
| # predictor/requirements.lock predictor/requirements.txt | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.lock --require-hashes | |
| - name: Run learning capture (forward_predict + score_forward) | |
| # daily_run.py = fetch_markets --all-weather → forward_predict → | |
| # score_forward. Writes data/predictions/forward_*.json (training | |
| # fuel) and data/scores/. continue-on-error so a transient API | |
| # failure here never blocks the paper-trading half or the commit. | |
| # daily_run.py exits 1 if any of its 3 steps fail (but still runs | |
| # the others), so we tolerate a non-zero exit at the job level. | |
| id: learn | |
| continue-on-error: true | |
| working-directory: ${{ github.workspace }} | |
| run: | | |
| python predictor/scripts/daily_run.py | |
| - name: Run daily_auto | |
| id: auto | |
| working-directory: ${{ github.workspace }} | |
| run: | | |
| DRY_RUN_FLAG="" | |
| if [ "${{ inputs.dry_run }}" = "true" ]; then | |
| DRY_RUN_FLAG="--dry-run" | |
| fi | |
| python predictor/scripts/daily_auto.py $DRY_RUN_FLAG | |
| - name: Announce to Discord (captures + résolutions) | |
| # continue-on-error : une panne Discord ne doit JAMAIS bloquer le | |
| # commit/push du manifest (leçon du gel du 8-11 juin 2026). Le | |
| # script lit le working tree (git status) AVANT le commit pour | |
| # savoir ce que daily_auto vient d'écrire. Webhook absent → | |
| # ::warning:: visible dans l'UI, pas de skip invisible. | |
| if: ${{ inputs.dry_run != true }} | |
| continue-on-error: true | |
| env: | |
| DISCORD_WEBHOOK_PREDICTIONS: ${{ secrets.DISCORD_WEBHOOK_PREDICTIONS }} | |
| DISCORD_WEBHOOK_PNL_TRACKER: ${{ secrets.DISCORD_WEBHOOK_PNL_TRACKER }} | |
| run: | | |
| python predictor/scripts/announce_daily.py | |
| - name: Configure git identity | |
| if: ${{ inputs.dry_run != true }} | |
| run: | | |
| git config user.name "aratea-bot" | |
| git config user.email "aratea-bot@users.noreply.github.com" | |
| - name: Detect changes | |
| if: ${{ inputs.dry_run != true }} | |
| id: diff | |
| run: | | |
| if git diff --quiet && git diff --staged --quiet; then | |
| echo "has_changes=false" >> $GITHUB_OUTPUT | |
| echo "No changes to commit." | |
| else | |
| echo "has_changes=true" >> $GITHUB_OUTPUT | |
| git status --short | |
| fi | |
| - name: Commit and push | |
| if: ${{ inputs.dry_run != true && steps.diff.outputs.has_changes == 'true' }} | |
| env: | |
| # PAT injecté UNIQUEMENT ici, via l'env de l'étape — jamais persisté | |
| # dans .git/config (revue 2026-06-10 D2). GitHub masque BOT_PAT dans | |
| # les logs. github.repository = owner/repo (pas un secret). | |
| BOT_PAT: ${{ secrets.BOT_PAT }} | |
| run: | | |
| DATE_ISO=$(date -u +%Y-%m-%d) | |
| git add predictor/runs \ | |
| predictor/data/predictions \ | |
| predictor/data/scores \ | |
| predictor/data/ledger/paper_bets.csv \ | |
| dashboard/public/predictor_manifest.json | |
| git status --short | |
| git commit -m "chore(auto): daily run ${DATE_ISO} (learning capture + finalize + capture + manifest)" || { | |
| echo "Nothing staged after add; that's OK." | |
| exit 0 | |
| } | |
| git push "https://x-access-token:${BOT_PAT}@github.com/${{ github.repository }}.git" HEAD:main |