Runtime #2999
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: Runtime | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| validate_only: | |
| description: "Skip live trading and only validate GitHub OIDC + Firestore access" | |
| required: false | |
| default: false | |
| type: boolean | |
| env: | |
| GCP_PROJECT_ID: binancequant | |
| GCP_WORKLOAD_IDENTITY_PROVIDER: projects/677468735457/locations/global/workloadIdentityPools/github-actions/providers/github-main | |
| GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT: binance-platform-runtime@binancequant.iam.gserviceaccount.com | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref_name }} | |
| cancel-in-progress: false | |
| jobs: | |
| deploy: | |
| runs-on: self-hosted | |
| timeout-minutes: 60 | |
| permissions: | |
| contents: write | |
| id-token: write | |
| steps: | |
| - name: 1. Checkout latest code | |
| uses: actions/checkout@v6 | |
| - name: 2. Authenticate to Google Cloud | |
| uses: google-github-actions/auth@v3 | |
| with: | |
| project_id: ${{ env.GCP_PROJECT_ID }} | |
| workload_identity_provider: ${{ env.GCP_WORKLOAD_IDENTITY_PROVIDER }} | |
| service_account: ${{ env.GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT }} | |
| create_credentials_file: true | |
| export_environment_variables: true | |
| cleanup_credentials: true | |
| - name: 3. Prepare or update dependency environment | |
| run: | | |
| set -euo pipefail | |
| REQ_FILE="requirements-lock.txt" | |
| if [ ! -f "$REQ_FILE" ]; then REQ_FILE="requirements.txt"; fi | |
| PYTHON_BIN="python3" | |
| if command -v python3.11 >/dev/null 2>&1; then PYTHON_BIN="python3.11"; fi | |
| echo "Using interpreter: $PYTHON_BIN ($($PYTHON_BIN --version 2>&1))" | |
| CACHE_ROOT="${RUNNER_WORKSPACE}/.runtime-cache/binance-platform" | |
| VENV_PATH="${CACHE_ROOT}/venv" | |
| HASH_FILE="${CACHE_ROOT}/requirements.sha256" | |
| PYTHON_VERSION_FILE="${CACHE_ROOT}/python.version" | |
| CURR_HASH=$(sha256sum "$REQ_FILE" | cut -d' ' -f1) | |
| CURR_PYTHON_VERSION="$("$PYTHON_BIN" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}")')" | |
| mkdir -p "$CACHE_ROOT" | |
| echo "VENV_PATH=$VENV_PATH" >> "$GITHUB_ENV" | |
| VENV_RECREATED=false | |
| install_with_retry() { | |
| local description="$1" | |
| shift | |
| local attempt | |
| for attempt in 1 2 3; do | |
| echo "${description} (attempt ${attempt}/3)" | |
| if "$@"; then | |
| return 0 | |
| fi | |
| if [ "$attempt" -lt 3 ]; then | |
| sleep $((attempt * 15)) | |
| fi | |
| done | |
| return 1 | |
| } | |
| if [ ! -x "$VENV_PATH/bin/python" ]; then | |
| echo "Creating dependency venv at $VENV_PATH." | |
| rm -rf "$VENV_PATH" | |
| "$PYTHON_BIN" -m venv "$VENV_PATH" | |
| VENV_RECREATED=true | |
| elif [ ! -f "$PYTHON_VERSION_FILE" ] || [ "$CURR_PYTHON_VERSION" != "$(cat "$PYTHON_VERSION_FILE")" ]; then | |
| echo "Python version changed; recreating dependency venv." | |
| rm -rf "$VENV_PATH" | |
| "$PYTHON_BIN" -m venv "$VENV_PATH" | |
| VENV_RECREATED=true | |
| fi | |
| if [ "$VENV_RECREATED" = "true" ]; then | |
| rm -f "$HASH_FILE" "$PYTHON_VERSION_FILE" | |
| fi | |
| if [ "$VENV_RECREATED" = "false" ] && [ -f "$HASH_FILE" ] && [ "$CURR_HASH" = "$(cat "$HASH_FILE")" ]; then | |
| echo "$REQ_FILE unchanged; reusing cached venv." | |
| else | |
| echo "$REQ_FILE changed or cache is cold; updating dependency venv." | |
| install_with_retry "Upgrade pip" "$VENV_PATH/bin/pip" install -U pip | |
| install_with_retry "Install runtime dependencies" "$VENV_PATH/bin/pip" install -r "$REQ_FILE" | |
| echo "$CURR_HASH" > "$HASH_FILE" | |
| echo "$CURR_PYTHON_VERSION" > "$PYTHON_VERSION_FILE" | |
| fi | |
| - name: 4. Run trading strategy | |
| run: | | |
| set -euo pipefail | |
| if [ "${VALIDATE_ONLY:-false}" = "true" ]; then | |
| "$VENV_PATH/bin/python" -c "from google.cloud import firestore; client = firestore.Client(); collections = [collection.id for _, collection in zip(range(3), client.collections())]; print(f'Validated Google Cloud auth and Firestore access. project={client.project} sample_collections={collections}')" | |
| exit 0 | |
| fi | |
| "$VENV_PATH/bin/python" main.py | |
| env: | |
| BINANCE_API_KEY: ${{ secrets.BINANCE_API_KEY }} | |
| BINANCE_API_SECRET: ${{ secrets.BINANCE_API_SECRET }} | |
| STRATEGY_PROFILE: ${{ vars.STRATEGY_PROFILE || 'crypto_live_pool_rotation' }} | |
| TG_TOKEN: ${{ secrets.TG_TOKEN }} | |
| GLOBAL_TELEGRAM_CHAT_ID: ${{ vars.GLOBAL_TELEGRAM_CHAT_ID }} | |
| NOTIFY_LANG: ${{ vars.NOTIFY_LANG }} | |
| EXECUTION_REPORT_GCS_URI: ${{ vars.EXECUTION_REPORT_GCS_URI }} | |
| VALIDATE_ONLY: ${{ github.event.inputs.validate_only || 'false' }} | |
| - name: 5. Capture execution timestamp | |
| id: ts | |
| run: echo "ts=$(date -u +%Y-%m-%dT%H%M)" >> "$GITHUB_OUTPUT" | |
| - name: 6. Push execution log to logs branch | |
| continue-on-error: true | |
| if: ${{ github.event.inputs.validate_only != 'true' }} | |
| run: | | |
| LOGS_BRANCH="logs" | |
| TS="${{ steps.ts.outputs.ts }}" | |
| MONTH="${TS:0:7}" | |
| REPORT_FILE="${GITHUB_WORKSPACE}/reports/execution_report.json" | |
| if [ ! -f "$REPORT_FILE" ]; then | |
| echo "No execution report found, skipping log push." | |
| exit 0 | |
| fi | |
| TMP_DIR=$(mktemp -d) | |
| git clone --no-checkout "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "${TMP_DIR}/logs-repo" | |
| cd "${TMP_DIR}/logs-repo" | |
| if git ls-remote --exit-code --heads origin "${LOGS_BRANCH}" >/dev/null 2>&1; then | |
| git fetch origin "${LOGS_BRANCH}:${LOGS_BRANCH}" | |
| git checkout "${LOGS_BRANCH}" | |
| else | |
| git checkout --orphan "${LOGS_BRANCH}" | |
| git rm -rf . >/dev/null 2>&1 || true | |
| find . -mindepth 1 -maxdepth 1 ! -name '.git' -exec rm -rf {} + | |
| fi | |
| mkdir -p "hourly/${MONTH}" | |
| cp "$REPORT_FILE" "hourly/${MONTH}/${TS}.json" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git add "hourly/${MONTH}/${TS}.json" | |
| git commit -m "execution: ${TS}" | |
| git push origin "HEAD:${LOGS_BRANCH}" | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: 7. Notify Telegram on runtime workflow failure | |
| if: ${{ failure() && github.event.inputs.validate_only != 'true' }} | |
| continue-on-error: true | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${TG_TOKEN:-}" ] || [ -z "${GLOBAL_TELEGRAM_CHAT_ID:-}" ]; then | |
| echo "Telegram workflow failure notification skipped: target is not configured." | |
| exit 0 | |
| fi | |
| REPORT_FILE="${GITHUB_WORKSPACE}/reports/execution_report.json" | |
| if [ -f "$REPORT_FILE" ]; then | |
| echo "Execution report exists; strategy-level runtime handling should already have recorded this failure." | |
| exit 0 | |
| fi | |
| MESSAGE=$(printf '%s\n' \ | |
| "Binance Runtime workflow failed before execution report was written" \ | |
| "repo: ${GITHUB_REPOSITORY}" \ | |
| "workflow: ${GITHUB_WORKFLOW}" \ | |
| "run: #${GITHUB_RUN_NUMBER} attempt ${GITHUB_RUN_ATTEMPT}" \ | |
| "ref: ${GITHUB_REF_NAME}" \ | |
| "sha: ${GITHUB_SHA}" \ | |
| "actor: ${GITHUB_ACTOR}" \ | |
| "url: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}") | |
| curl -fsS -X POST "https://api.telegram.org/bot${TG_TOKEN}/sendMessage" \ | |
| --data-urlencode "chat_id=${GLOBAL_TELEGRAM_CHAT_ID}" \ | |
| --data-urlencode "text=${MESSAGE}" >/dev/null | |
| env: | |
| TG_TOKEN: ${{ secrets.TG_TOKEN }} | |
| GLOBAL_TELEGRAM_CHAT_ID: ${{ vars.GLOBAL_TELEGRAM_CHAT_ID }} |