diff --git a/.github/workflows/longbridge-api-probe.yml b/.github/workflows/longbridge-api-probe.yml new file mode 100644 index 0000000..3295703 --- /dev/null +++ b/.github/workflows/longbridge-api-probe.yml @@ -0,0 +1,114 @@ +name: LongBridge API Probe + +on: + workflow_dispatch: + inputs: + qpk_ref: + description: "QuantPlatformKit ref containing the probe test" + required: false + default: "main" + type: string + symbol: + description: "US symbol to use for the probe" + required: false + default: "SOXX.US" + type: string + limit_price: + description: "Low buy limit price used by the cancellable probe order" + required: false + default: "0.01" + type: string + +env: + GCP_PROJECT_ID: longbridgequant + GCP_WORKLOAD_IDENTITY_PROVIDER: projects/252919773759/locations/global/workloadIdentityPools/github-actions/providers/github-main + GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT: longbridge-platform-deploy@longbridgequant.iam.gserviceaccount.com + +jobs: + hk-fractional-order: + name: Probe HK Simulated Fractional Orders + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + environment: longbridge-hk + env: + LONGPORT_APP_KEY_SECRET_NAME: ${{ vars.LONGPORT_APP_KEY_SECRET_NAME }} + LONGPORT_APP_SECRET_SECRET_NAME: ${{ vars.LONGPORT_APP_SECRET_SECRET_NAME }} + LONGPORT_SECRET_NAME: ${{ vars.LONGPORT_SECRET_NAME }} + steps: + - name: Validate inputs + run: | + set -euo pipefail + + missing_vars=() + for var_name in LONGPORT_APP_KEY_SECRET_NAME LONGPORT_APP_SECRET_SECRET_NAME LONGPORT_SECRET_NAME; do + if [ -z "${!var_name:-}" ]; then + missing_vars+=("${var_name}") + fi + done + + if [ "${#missing_vars[@]}" -gt 0 ]; then + printf 'Missing longbridge-hk variables:\n' >&2 + printf ' - %s\n' "${missing_vars[@]}" >&2 + exit 1 + fi + + - name: Checkout QuantPlatformKit + uses: actions/checkout@v6 + with: + repository: QuantStrategyLab/QuantPlatformKit + ref: ${{ inputs.qpk_ref }} + path: quant-platform-kit + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v3 + with: + workload_identity_provider: ${{ env.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ env.GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT }} + + - name: Set up gcloud + uses: google-github-actions/setup-gcloud@v3 + with: + project_id: ${{ env.GCP_PROJECT_ID }} + version: ">= 416.0.0" + + - name: Export LongPort credentials + run: | + set -euo pipefail + + longport_app_key="$(gcloud secrets versions access latest --secret="${LONGPORT_APP_KEY_SECRET_NAME}")" + longport_app_secret="$(gcloud secrets versions access latest --secret="${LONGPORT_APP_SECRET_SECRET_NAME}")" + longport_access_token="$(gcloud secrets versions access latest --secret="${LONGPORT_SECRET_NAME}")" + + echo "::add-mask::${longport_app_key}" + echo "::add-mask::${longport_app_secret}" + echo "::add-mask::${longport_access_token}" + + { + echo "LONGPORT_APP_KEY=${longport_app_key}" + echo "LONGPORT_APP_SECRET=${longport_app_secret}" + echo "LONGPORT_ACCESS_TOKEN=${longport_access_token}" + } >> "$GITHUB_ENV" + + - name: Install probe dependencies + run: | + set -euo pipefail + + python -m pip install --upgrade pip + python -m pip install -e quant-platform-kit pytest longport + + - name: Run HK simulated fractional order probe + env: + LONGBRIDGE_API_PROBE: "1" + LONGBRIDGE_API_PROBE_SYMBOL: ${{ inputs.symbol }} + LONGBRIDGE_API_PROBE_LIMIT_PRICE: ${{ inputs.limit_price }} + run: | + set -euo pipefail + + python -m pytest quant-platform-kit/tests/test_longbridge_fractional_order_api_probe.py -q -s diff --git a/README.md b/README.md index a6e3a96..6d174fa 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,11 @@ Important: - If you later rename or move this repository, rebuild the GitHub source binding in Google Cloud for both triggers instead of assuming the existing source binding will follow the rename. - For the shared deployment model and trigger migration checklist, see [`QuantPlatformKit/docs/deployment_model.md`](../QuantPlatformKit/docs/deployment_model.md). +### Manual API probes + +- `.github/workflows/longbridge-api-probe.yml` can be triggered manually against the `longbridge-hk` GitHub Environment. It checks out a selected `QuantPlatformKit` ref and runs the skipped LongBridge fractional-order API probe with HK simulated-account credentials from Secret Manager. +- The probe is for broker API validation only. It does not run on normal CI or `main` pushes. + ### Quick deploy 1. Enable **Cloud Run** and **Secret Manager API** in GCP. @@ -299,6 +304,11 @@ Secret Manager 中需存在 `LONGPORT_SECRET_NAME` 指定的密钥(默认: `lo - 如果后面改 GitHub 仓库名或再次迁组织,Google Cloud 里的两个 trigger 都要重新选择 GitHub 来源,不要假设旧绑定会自动跟过去。 - 统一部署模型和触发器迁移清单见 [`QuantPlatformKit/docs/deployment_model.md`](../QuantPlatformKit/docs/deployment_model.md)。 +### 手动 API probe + +- `.github/workflows/longbridge-api-probe.yml` 可手动触发,固定使用 `longbridge-hk` GitHub Environment。它会 checkout 指定的 `QuantPlatformKit` ref,并用 Secret Manager 中的 HK 模拟盘 LongPort 凭证运行默认跳过的碎股下单 API probe。 +- 这个 probe 只用于券商 API 验证,不会进入普通 CI 或 `main` push 流程。 + ### 快速部署 1. 在 GCP 中启用 **Cloud Run** 和 **Secret Manager API**。 diff --git a/tests/test_longbridge_api_probe_workflow.py b/tests/test_longbridge_api_probe_workflow.py new file mode 100644 index 0000000..4b2f29f --- /dev/null +++ b/tests/test_longbridge_api_probe_workflow.py @@ -0,0 +1,32 @@ +import unittest +from pathlib import Path + + +class LongBridgeApiProbeWorkflowTests(unittest.TestCase): + def test_workflow_uses_hk_environment_and_gcp_secrets(self) -> None: + workflow = Path(".github/workflows/longbridge-api-probe.yml").read_text(encoding="utf-8") + + self.assertIn("name: LongBridge API Probe", workflow) + self.assertIn("workflow_dispatch:", workflow) + self.assertIn("environment: longbridge-hk", workflow) + self.assertIn("id-token: write", workflow) + self.assertIn("repository: QuantStrategyLab/QuantPlatformKit", workflow) + self.assertIn("ref: ${{ inputs.qpk_ref }}", workflow) + self.assertIn("google-github-actions/auth@v3", workflow) + self.assertIn("google-github-actions/setup-gcloud@v3", workflow) + self.assertIn("LONGPORT_APP_KEY_SECRET_NAME: ${{ vars.LONGPORT_APP_KEY_SECRET_NAME }}", workflow) + self.assertIn("LONGPORT_APP_SECRET_SECRET_NAME: ${{ vars.LONGPORT_APP_SECRET_SECRET_NAME }}", workflow) + self.assertIn("LONGPORT_SECRET_NAME: ${{ vars.LONGPORT_SECRET_NAME }}", workflow) + self.assertIn('gcloud secrets versions access latest --secret="${LONGPORT_APP_KEY_SECRET_NAME}"', workflow) + self.assertIn( + 'gcloud secrets versions access latest --secret="${LONGPORT_APP_SECRET_SECRET_NAME}"', + workflow, + ) + self.assertIn('gcloud secrets versions access latest --secret="${LONGPORT_SECRET_NAME}"', workflow) + self.assertIn("python -m pip install -e quant-platform-kit pytest longport", workflow) + self.assertIn('LONGBRIDGE_API_PROBE: "1"', workflow) + self.assertIn("test_longbridge_fractional_order_api_probe.py", workflow) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_longbridge_api_probe_workflow.sh b/tests/test_longbridge_api_probe_workflow.sh new file mode 100755 index 0000000..bafc9ae --- /dev/null +++ b/tests/test_longbridge_api_probe_workflow.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_dir="$(cd "$(dirname "$0")/.." && pwd)" +workflow_file="$repo_dir/.github/workflows/longbridge-api-probe.yml" + +grep -Fq "name: LongBridge API Probe" "$workflow_file" +grep -Fq "workflow_dispatch:" "$workflow_file" +grep -Fq "environment: longbridge-hk" "$workflow_file" +grep -Fq "id-token: write" "$workflow_file" +grep -Fq "repository: QuantStrategyLab/QuantPlatformKit" "$workflow_file" +grep -Fq "ref: \${{ inputs.qpk_ref }}" "$workflow_file" +grep -Fq "google-github-actions/auth@v3" "$workflow_file" +grep -Fq "google-github-actions/setup-gcloud@v3" "$workflow_file" +grep -Fq "LONGPORT_APP_KEY_SECRET_NAME: \${{ vars.LONGPORT_APP_KEY_SECRET_NAME }}" "$workflow_file" +grep -Fq "LONGPORT_APP_SECRET_SECRET_NAME: \${{ vars.LONGPORT_APP_SECRET_SECRET_NAME }}" "$workflow_file" +grep -Fq "LONGPORT_SECRET_NAME: \${{ vars.LONGPORT_SECRET_NAME }}" "$workflow_file" +grep -Fq 'gcloud secrets versions access latest --secret="${LONGPORT_APP_KEY_SECRET_NAME}"' "$workflow_file" +grep -Fq 'gcloud secrets versions access latest --secret="${LONGPORT_APP_SECRET_SECRET_NAME}"' "$workflow_file" +grep -Fq 'gcloud secrets versions access latest --secret="${LONGPORT_SECRET_NAME}"' "$workflow_file" +grep -Fq "python -m pip install -e quant-platform-kit pytest longport" "$workflow_file" +grep -Fq 'LONGBRIDGE_API_PROBE: "1"' "$workflow_file" +grep -Fq "test_longbridge_fractional_order_api_probe.py" "$workflow_file"