Skip to content

Daily Trading (auto) #51

Daily Trading (auto)

Daily Trading (auto) #51

Workflow file for this run

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