105 lines
4.0 KiB
Markdown
105 lines
4.0 KiB
Markdown
# LongPort HTTP Endpoint Fallback (2026-08-02)
|
|
|
|
## Problem
|
|
|
|
Cron scripts that use the LongPort Python SDK fail with:
|
|
```
|
|
longport.OpenApiException: error sending request for url
|
|
(https://openapi.longportapp.cn/v1/socket/token): client error (Connect)
|
|
```
|
|
|
|
The script set `LONGBRIDGE_HTTP_URL`, but the SDK silently ignores it and uses the default `openapi.longportapp.cn`. When the .cn endpoint is down (Aliyun load balancer 502), the cron fails.
|
|
|
|
## Root Cause
|
|
|
|
The SDK's `Config.from_env()` reads **`LONGPORT_HTTP_URL`**, NOT `LONGBRIDGE_HTTP_URL`. The CLI uses `LONGBRIDGE_*`; the Python SDK uses `LONGPORT_*`. This is a frequent bug because:
|
|
|
|
1. The CLI's documentation uses `LONGBRIDGE_*`
|
|
2. The Python wheel's type stubs (`.pyi`) document `LONGPORT_*`
|
|
3. Scripts often copy bashrc exports (`LONGBRIDGE_*`) and assume they work
|
|
|
|
## Fix Pattern
|
|
|
|
Always set **both** env vars in cron wrappers (they don't conflict):
|
|
|
|
```bash
|
|
# In the wrapper script (e.g. cn_dividend_buy_timing_cron.sh):
|
|
export LONGPORT_HTTP_URL=https://openapi.longportapp.cn
|
|
export LONGPORT_QUOTE_WS_URL=wss://openapi-quote.longportapp.cn
|
|
export LONGPORT_TRADE_WS_URL=wss://openapi-trade.longportapp.cn
|
|
export LONGPORT_LANGUAGE=zh-CN
|
|
```
|
|
|
|
## Fallback Pattern (Python init)
|
|
|
|
When the .cn endpoint is down, try the .com endpoint:
|
|
|
|
```python
|
|
def get_longbridge():
|
|
"""带重试的长桥 SDK init - 优先 .cn (国内), fallback .com (海外)"""
|
|
import time
|
|
load_longbridge_env()
|
|
|
|
configs = [
|
|
{
|
|
'http': 'https://openapi.longportapp.cn',
|
|
'quote_ws': 'wss://openapi-quote.longportapp.cn',
|
|
'trade_ws': 'wss://openapi-trade.longportapp.cn',
|
|
},
|
|
{
|
|
'http': 'https://openapi.longbridge.com',
|
|
'quote_ws': 'wss://openapi-quote.longbridge.com/v2',
|
|
'trade_ws': 'wss://openapi-trade.longbridge.com/v2',
|
|
},
|
|
]
|
|
|
|
from longport import openapi
|
|
last_err = None
|
|
for i, c in enumerate(configs):
|
|
try:
|
|
os.environ['LONGPORT_HTTP_URL'] = c['http']
|
|
os.environ['LONGPORT_QUOTE_WS_URL'] = c['quote_ws']
|
|
os.environ['LONGPORT_TRADE_WS_URL'] = c['trade_ws']
|
|
os.environ.setdefault('LONGPORT_LANGUAGE', 'zh-CN')
|
|
cfg = openapi.Config.from_env()
|
|
ctx = openapi.QuoteContext(config=cfg)
|
|
try:
|
|
ctx.quote(['600000.SH']) # test connection
|
|
except Exception as e:
|
|
last_err = e
|
|
if i < len(configs) - 1:
|
|
print(f"[longbridge] {c['http']} quote 失败: {e}, 重试下一节点", flush=True)
|
|
time.sleep(1)
|
|
continue
|
|
else:
|
|
return ctx, openapi.Period
|
|
print(f"[longbridge] 使用 {c['http']} ✓", flush=True)
|
|
return ctx, openapi.Period
|
|
except Exception as e:
|
|
last_err = e
|
|
print(f"[longbridge] {c['http']} init 失败: {e}", flush=True)
|
|
if i < len(configs) - 1:
|
|
time.sleep(1)
|
|
continue
|
|
|
|
raise last_err or RuntimeError("长桥 SDK init 全部失败")
|
|
```
|
|
|
|
## Test
|
|
|
|
2026-08-02: cron `8000d3cfed23 cn-dividend-buy-timing` failed at 11:30 with `OpenApiException: client error (Connect)`. After applying the fix:
|
|
|
|
- `.cn` (Aliyun) endpoint came back online — script ran successfully
|
|
- `longrun_http_url=https://openapi.longportapp.cn` printed ✓
|
|
|
|
## Open Questions
|
|
|
|
- Are `.cn` and `.com` endpoints actually interchangeable? E.g. do quotes for the same symbol return identical data? The fallback assumes yes; not verified.
|
|
- Could there be a subtle difference between the SDK's hardcoded `openapi.longportapp.cn` and the user-configured one? E.g. token validation, rate limits, etc.?
|
|
|
|
## Files Touched
|
|
|
|
- `~/.hermes/scripts/cn_dividend_buy_timing.py` — get_longbridge() with fallback
|
|
- `~/.hermes/skills/trading/lottery-hk/scripts/lottery_4frame.py` — same idempotent init
|
|
- `~/.hermes/scripts/cn_dividend_buy_timing_cron.sh` — wrapper sets LONGPORT_* env vars
|