Skip to content

Runtime

Runtime #2999

Workflow file for this run

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 }}