Initial commit: Trading skills collection
- OKX交易自动化 (okx-auto-position, okx-crypto, okx-exchange) - 交易信号处理 (signal-confirmation-templates, trading-signal-aggregator) - 量化因子挖掘 (quant-factor-mining) - 长桥集成 (longbridge-cli, longbridge-python-sdk) - 六合彩分析 (lottery-hk) - 股息投资 (dividend-investing, dividend-scanner) - 日内交易 (intraday-trading) - 同花顺 (tonghuashun)
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
# LongBridge Token Refresh Workflow
|
||||
|
||||
## Problem
|
||||
`LONGBRIDGE_ACCESS_TOKEN` expired/invalid → error: `401004: token invalid` or `401003: token expired`.
|
||||
All LongBridge/LongPort API calls fail simultaneously.
|
||||
|
||||
## Fix Steps (Automated — Preferred)
|
||||
|
||||
1. Open LongBridge App → 我的 → 设置 → API 密钥管理 → **重新生成** Access Token
|
||||
2. Copy the new token (starts with `m_`)
|
||||
3. Run the update script:
|
||||
```bash
|
||||
bash ~/.hermes/scripts/update_longbridge_token.sh NEW_TOKEN
|
||||
```
|
||||
4. Script auto-updates ALL locations and runs CLI + Python SDK verification
|
||||
|
||||
## Token Storage Locations (script updates ALL)
|
||||
| Location | Variables |
|
||||
|----------|-----------|
|
||||
| `~/.bashrc` | `LONGBRIDGE_ACCESS_TOKEN` + `LONGPORT_ACCESS_TOKEN` |
|
||||
| `~/.env` | `LONGBRIDGE_ACCESS_TOKEN` (also `LONGPORT_ACCESS_TOKEN` if exists) |
|
||||
| `~/.hermes/envs/*.env` | Any file containing these vars |
|
||||
|
||||
## Manual Fix (if script unavailable)
|
||||
|
||||
```bash
|
||||
# 1. Get new token from App
|
||||
# 2. Update bashrc (two lines)
|
||||
sed -i "s|^export LONGBRIDGE_ACCESS_TOKEN=.*|export LONGBRIDGE_ACCESS_TOKEN=NEW_TOKEN|" ~/.bashrc
|
||||
sed -i "s|^export LONGPORT_ACCESS_TOKEN=.*|export LONGPORT_ACCESS_TOKEN=NEW_TOKEN|" ~/.bashrc
|
||||
|
||||
# 3. Update .env
|
||||
sed -i "s|^LONGBRIDGE_ACCESS_TOKEN=.*|LONGBRIDGE_ACCESS_TOKEN=NEW_TOKEN|" ~/.env
|
||||
|
||||
# 4. Update hermes envs
|
||||
for f in ~/.hermes/envs/*.env; do
|
||||
[ -f "$f" ] && sed -i "s|^LONGBRIDGE_ACCESS_TOKEN=.*|LONGBRIDGE_ACCESS_TOKEN=NEW_TOKEN|" "$f"
|
||||
done
|
||||
|
||||
# 5. Verify
|
||||
source ~/.bashrc && source ~/.env && longbridge balance --json
|
||||
```
|
||||
|
||||
## Token Format
|
||||
JWT mobile session token: `m_<base64url_header>.<base64url_payload>.<signature>`
|
||||
|
||||
Prefix `m_` indicates mobile session token (generated from App, not Web console).
|
||||
|
||||
## ⚠️ Terminal Masking Trap
|
||||
|
||||
The terminal tool **masks** secrets in both **output display and environment variables**.
|
||||
|
||||
```bash
|
||||
# What you SEE in terminal output:
|
||||
$ grep "ACCESS_TOKEN" ~/.bashrc
|
||||
export LONGBRIDGE_ACCESS_TOKEN=m_eyJh...jb-k # <-- those "..." are MASKING, not real!
|
||||
|
||||
# What's actually in the file:
|
||||
m_eyJhbGciOiJSUzI1NiIsImtpZCI6ImQ5YWRiMGIxYTdlNzYxNzEi... # (full 1053-char JWT)
|
||||
```
|
||||
|
||||
**Consequences:**
|
||||
- `grep` output showing `...` DOES NOT mean the token is truncated — it means the tool masked it
|
||||
- `source ~/.bashrc && echo $LONGBRIDGE_ACCESS_TOKEN` also shows `...` but the actual env var in the child process may be correct
|
||||
- **NEVER assume `...` in terminal output means the file has placeholders** — always verify via Python `open()` + SHA256 or byte-length check
|
||||
- This masking affects both the `terminal` tool AND the `execute_code` sandbox
|
||||
|
||||
**How to verify the token is truly intact:**
|
||||
```bash
|
||||
python3 -c "
|
||||
import hashlib
|
||||
with open('/home/openclaw/.bashrc') as f:
|
||||
for line in f:
|
||||
if 'LONGBRIDGE_ACCESS_TOKEN' in line and 'export' in line:
|
||||
tk = line.strip().split('=', 1)[1]
|
||||
print(f'Token length: {len(tk)}')
|
||||
print(f'SHA256: {hashlib.sha256(tk.encode()).hexdigest()[:16]}')
|
||||
# Length should be ~1053 for a valid JWT
|
||||
"
|
||||
```
|
||||
|
||||
**Key rule:** When the user says "变量没有占位符", they're right — trust them over the masked terminal output.
|
||||
|
||||
## JWT Verification (Decode Token)
|
||||
|
||||
When getting 401004 with what looks like a valid token, decode it to check:
|
||||
|
||||
```python
|
||||
import json, base64, time
|
||||
|
||||
# Strip m_ prefix, decode JWT payload
|
||||
jwt = token[2:] # Remove "m_"
|
||||
payload_b64 = jwt.split('.')[1]
|
||||
# Add padding
|
||||
padding = 4 - len(payload_b64) % 4
|
||||
if padding != 4:
|
||||
payload_b64 += '=' * padding
|
||||
payload = json.loads(base64.urlsafe_b64decode(payload_b64))
|
||||
|
||||
exp = payload['exp']
|
||||
now = int(time.time())
|
||||
print(f"Expired: {now > exp}") # Should be False
|
||||
print(f"App Key (ak): {payload['ak']}") # Should match LONGBRIDGE_APP_KEY
|
||||
print(f"EXP: {time.strftime('%Y-%m-%d', time.gmtime(exp))} UTC")
|
||||
```
|
||||
|
||||
**What to check:**
|
||||
| Check | Expected | If wrong |
|
||||
|-------|----------|----------|
|
||||
| `exp > now` | True (not expired) | Token genuinely expired → regenerate |
|
||||
| `ak` matches bashrc | Exact match | Wrong app key → check credentials |
|
||||
| Token length | ~1053 chars | Truncated → re-copy from App |
|
||||
|
||||
## 401004 with Fresh Token (Diagnosis)
|
||||
|
||||
If a **newly-generated** token still gets 401004:
|
||||
|
||||
1. **Wait & retry**: Some tokens take 1-2 minutes to propagate. Run `sleep 30 && source ~/.bashrc && longbridge quote --json AAPL.US`
|
||||
2. **Decode JWT** (see above) to confirm `exp` is in the future and `ak` matches the configured APP_KEY
|
||||
3. **Re-generate from App**: Occasionally the first generation doesn't register properly. Generate again.
|
||||
4. **Fallback: Web console**: Go to https://open.longportapp.com/ → Personal Access Token (different from App token, may work when App token doesn't)
|
||||
5. **Check credentials are intact**: Verify both APP_KEY and APP_SECRET values via Python `open()` + length check (APP_KEY=32 chars, APP_SECRET=64 chars)
|
||||
|
||||
## Verification
|
||||
After updating, test with:
|
||||
```bash
|
||||
source ~/.bashrc && longbridge balance --json
|
||||
```
|
||||
Or use the SDK:
|
||||
```python
|
||||
from longport import openapi
|
||||
cfg = openapi.Config.from_env()
|
||||
ctx = openapi.QuoteContext(config=cfg)
|
||||
print(ctx.quote(['AAPL.US'])[0].last_done)
|
||||
```
|
||||
Reference in New Issue
Block a user