Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions .github/workflows/longbridge-api-probe.yml
Original file line number Diff line number Diff line change
@@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Prevent running editable install after exporting LongPort secrets

The workflow allows a user-provided qpk_ref to control which QuantPlatformKit code is checked out, then exports LONGPORT_* credentials to GITHUB_ENV before running python -m pip install -e quant-platform-kit pytest longport. Editable installs can execute package build/install hooks from that checked-out ref, so a malicious or unreviewed ref can read and exfiltrate the broker credentials during install. This is a real secret-exposure path whenever the dispatch input is set to an untrusted ref; move secret export after all untrusted code execution or restrict qpk_ref to trusted immutable refs.

Useful? React with 👍 / 👎.


- 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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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**。
Expand Down
32 changes: 32 additions & 0 deletions tests/test_longbridge_api_probe_workflow.py
Original file line number Diff line number Diff line change
@@ -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()
23 changes: 23 additions & 0 deletions tests/test_longbridge_api_probe_workflow.sh
Original file line number Diff line number Diff line change
@@ -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"