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,446 @@
|
||||
---
|
||||
name: longbridge-python-sdk
|
||||
description: LongPort Python SDK — 行情、持仓、自选、估值指标(PE/PB/股息率/EPS/BPS)、资金流向。支持港股/美股/A股。bashrc已有LONGPORT_*变量,可直接Config.from_env()。
|
||||
---
|
||||
|
||||
# LongPort Python SDK Usage
|
||||
|
||||
Use this skill to interact with LongPort via Python instead of the CLI. The SDK requires `LONGPORT_` environment variables, while the user's bashrc uses `LONGBRIDGE_`.
|
||||
|
||||
## When to use
|
||||
- User asks for holdings, quotes, or account info via Python.
|
||||
- CLI `longbridge` command fails (e.g., token issues, missing args).
|
||||
|
||||
## Setup
|
||||
1. Install SDK: `pip3 install longbridge` (package name on PyPI is `longbridge`, but import is `from longport import openapi`). Do NOT `pip install longport` — that's a different/empty package.
|
||||
2. `~/.bashrc` now has BOTH sets of variables (added 2026-06-01):
|
||||
```bash
|
||||
# CLI uses these
|
||||
export LONGBRIDGE_APP_KEY=<key>
|
||||
export LONGBRIDGE_APP_SECRET=<secret>
|
||||
export LONGBRIDGE_ACCESS_TOKEN=<token>
|
||||
|
||||
# Python SDK uses these (same values, references LONGBRIDGE_ vars)
|
||||
export LONGPORT_APP_KEY=${LONGBRIDGE_APP_KEY}
|
||||
export LONGPORT_APP_SECRET=${LONGBRIDGE_APP_SECRET}
|
||||
export LONGPORT_ACCESS_TOKEN=${LONGBRIDGE_ACCESS_TOKEN}
|
||||
```
|
||||
3. With both sets in bashrc, `Config.from_env()` works directly without manual mapping.
|
||||
|
||||
## Usage Steps
|
||||
1. **Connect** (LONGPORT_* now in bashrc):
|
||||
```python
|
||||
from longport import openapi
|
||||
cfg = openapi.Config.from_env() # Reads LONGPORT_* vars directly
|
||||
ctx = openapi.QuoteContext(config=cfg)
|
||||
trade_ctx = openapi.TradeContext(config=cfg)
|
||||
```
|
||||
## Usage
|
||||
- Holdings: `resp = ctx.stock_positions()` → iterate `resp.channels[0].positions`
|
||||
- Balance: `ctx.account_balance()`
|
||||
- Orders: `ctx.today_orders()`
|
||||
|
||||
## Extended API (discovered via testing)
|
||||
|
||||
### Watchlist
|
||||
```python
|
||||
resp = ctx.watchlist() # Returns list[WatchlistGroup]
|
||||
for group in resp:
|
||||
print(f'Group: {group.name}') # e.g. "收息", "月派", "all"
|
||||
for sec in group.securities:
|
||||
print(f' {sec.symbol}: {sec.name} @ {sec.watched_price}')
|
||||
```
|
||||
**WatchlistGroup fields:** `name`, `securities` (list)
|
||||
**WatchlistSecurity fields:** `symbol`, `market`, `name`, `watched_price` (Optional), `watched_at` (ISO string)
|
||||
Special groups: `all` (auto-generated, all securities), `us`/`hk` (market-based auto-groups)
|
||||
|
||||
### Static Info (EPS, BPS, shares)
|
||||
```python
|
||||
resp = ctx.static_info(['O.US', '823.HK'])
|
||||
for info in resp:
|
||||
# Key fields: symbol, name_en, name_cn, currency, exchange, board
|
||||
# Valuation: eps, eps_ttm, bps, dividend_yield
|
||||
# Shares: total_shares, circulating_shares, hk_shares
|
||||
# Other: lot_size, stock_derivatives
|
||||
```
|
||||
|
||||
### Calc Indexes (PE, PB, Market Cap, etc.)
|
||||
```python
|
||||
from longport.openapi import CalcIndex
|
||||
|
||||
indexes = [
|
||||
CalcIndex.PeTtmRatio, # PE TTM
|
||||
CalcIndex.PbRatio, # PB
|
||||
CalcIndex.DividendRatioTtm, # Dividend yield TTM
|
||||
CalcIndex.TotalMarketValue, # Total market cap
|
||||
CalcIndex.TurnoverRate, # Turnover rate
|
||||
CalcIndex.VolumeRatio, # Volume ratio
|
||||
CalcIndex.ChangeRate, # Change %
|
||||
]
|
||||
resp = ctx.calc_indexes(['O.US'], indexes)
|
||||
for item in resp:
|
||||
print(f'{item.symbol}: PE={item.pe_ttm_ratio}, PB={item.pb_ratio}')
|
||||
```
|
||||
**Available CalcIndex values:** Amplitude, BalancePoint, CallPrice, CapitalFlow, ChangeRate, ChangeValue, ConversionRatio, Delta, DividendRatioTtm, EffectiveLeverage, ExpiryDate, FiveDayChangeRate, FiveMinutesChangeRate, Gamma, HalfYearChangeRate, ImpliedVolatility, ItmOtm, LastDone, LeverageRatio, LowerStrikePrice, OpenInterest, OutstandingQty, OutstandingRatio, PbRatio, PeTtmRatio, Premium, Rho, StrikePrice, TenDayChangeRate, Theta, ToCallPrice, TotalMarketValue, Turnover, TurnoverRate, UpperStrikePrice, Vega, Volume, VolumeRatio, WarrantDelta, YtdChangeRate
|
||||
|
||||
### Candlesticks (with AdjustType)
|
||||
```python
|
||||
from longport.openapi import Period, AdjustType
|
||||
|
||||
candles = ctx.candlesticks('O.US', Period.Day, 365, AdjustType.ForwardAdjust)
|
||||
# Returns: timestamp, open, high, low, close, volume, turnover
|
||||
```
|
||||
⚠️ **PITFALL:** `candlesticks()` requires `adjust_type` parameter — will fail with "missing 1 required positional argument: 'adjust_type'" without it. Always pass `AdjustType.ForwardAdjust` (前复权) or `AdjustType.NoAdjust`.
|
||||
|
||||
## Fundamental Data (calc_indexes + static_info)
|
||||
|
||||
### calc_indexes — PE, PB, 股息率, 市值等
|
||||
```python
|
||||
from longport.openapi import CalcIndex
|
||||
|
||||
indexes = [
|
||||
CalcIndex.PeTtmRatio, # PE TTM
|
||||
CalcIndex.PbRatio, # PB
|
||||
CalcIndex.DividendRatioTtm, # 股息率 TTM (%)
|
||||
CalcIndex.TotalMarketValue, # 总市值 (货币单位)
|
||||
CalcIndex.TurnoverRate, # 换手率 (%)
|
||||
CalcIndex.VolumeRatio, # 量比
|
||||
CalcIndex.ChangeRate, # 涨跌幅 (%)
|
||||
CalcIndex.FiveDayChangeRate, # 5日涨跌幅
|
||||
CalcIndex.TenDayChangeRate, # 10日涨跌幅
|
||||
CalcIndex.HalfYearChangeRate, # 半年涨跌幅
|
||||
CalcIndex.YtdChangeRate, # 年初至今涨跌幅
|
||||
]
|
||||
|
||||
resp = ctx.calc_indexes(['O.US', '823.HK'], indexes)
|
||||
for item in resp:
|
||||
print(f'{item.symbol}: PE={item.pe_ttm_ratio}, PB={item.pb_ratio}, 股息率={item.dividend_ratio_ttm}%')
|
||||
```
|
||||
|
||||
### static_info — EPS, 每股净资产, 股本
|
||||
```python
|
||||
resp = ctx.static_info(['O.US', '823.HK'])
|
||||
for info in resp:
|
||||
print(f'{info.symbol}: EPS_TTM={info.eps_ttm}, BPS={info.bps}, 总股本={info.total_shares}')
|
||||
```
|
||||
|
||||
**static_info 字段**: `symbol`, `name_cn`, `name_en`, `name_hk`, `currency`, `lot_size`, `eps`, `eps_ttm`, `bps`, `dividend_yield`, `total_shares`, `circulating_shares`, `exchange`, `board`
|
||||
|
||||
### watchlist — 自选列表
|
||||
```python
|
||||
resp = ctx.watchlist()
|
||||
for group in resp:
|
||||
print(f'分组: {group.name} ({len(group.securities)}只)')
|
||||
for sec in group.securities:
|
||||
print(f' {sec.symbol}: {sec.name} @ {sec.watched_price}')
|
||||
```
|
||||
|
||||
**特殊分组**: `all` (全量), `us`/`hk` (按市场自动分组), 用户自建分组 (如"收息", "月派")
|
||||
|
||||
## Order Placement (Trading)
|
||||
|
||||
Trading requires `LONGBRIDGE_TRADE_ENABLED=true` in bashrc. Use `execute_code` for all order operations (not `terminal`).
|
||||
|
||||
### Submit Limit Order
|
||||
```python
|
||||
os.environ["LONGBRIDGE_TRADE_ENABLED"] = "true"
|
||||
|
||||
resp = ctx.submit_order(
|
||||
symbol="RGTI.US",
|
||||
order_type=openapi.OrderType.LO, # Limit Order
|
||||
side=openapi.OrderSide.Sell, # or .Buy
|
||||
submitted_quantity=15,
|
||||
time_in_force=openapi.TimeInForceType.Day, # or .GoodTilCanceled
|
||||
submitted_price=21.00,
|
||||
outside_rth=openapi.OutsideRTH.AnyTime, # optional: pre/post market
|
||||
)
|
||||
print(f"Order ID: {resp.order_id}")
|
||||
```
|
||||
|
||||
### Key Enums
|
||||
- **OrderType**: `LO` (Limit), `MO` (Market), `ALO` (At Limit Open), `ELO` (Extended Limit)
|
||||
- **OrderSide**: `Buy`, `Sell`
|
||||
- **TimeInForceType**: `Day`, `GoodTilCanceled`, `GoodTilDate`, `Unknown`
|
||||
- **OutsideRTH**: `AnyTime` (pre+regular+post), `Overnight`, `RTHOnly`, `Unknown`
|
||||
|
||||
### Cancel / Query Orders
|
||||
```python
|
||||
# Today's orders
|
||||
orders = trade_ctx.today_orders()
|
||||
for o in orders:
|
||||
print(f"{o.symbol} {o.side} {o.quantity}@{o.price} [{o.status}]")
|
||||
|
||||
# Cancel
|
||||
trade_ctx.cancel_order(order_id)
|
||||
```
|
||||
|
||||
### submit_order Signature
|
||||
```python
|
||||
submit_order(symbol, order_type, side, submitted_quantity, time_in_force,
|
||||
submitted_price=None, trigger_price=None, limit_offset=None,
|
||||
trailing_amount=None, trailing_percent=None, expire_date=None,
|
||||
outside_rth=None, remark=None)
|
||||
```
|
||||
⚠️ `time_in_force` is **required positional** before optional `submitted_price`.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Get Watchlist (with groups)
|
||||
```python
|
||||
ctx = openapi.QuoteContext(config=cfg)
|
||||
resp = ctx.watchlist()
|
||||
for group in resp:
|
||||
print(f'{group.name}: {len(group.securities)} stocks')
|
||||
for sec in group.securities:
|
||||
print(f' {sec.symbol}: {sec.name}')
|
||||
```
|
||||
|
||||
### Get Quotes
|
||||
```python
|
||||
resp = ctx.quote(['O.US', '823.HK', 'JEPI.US'])
|
||||
for q in resp:
|
||||
print(f'{q.symbol}: ${q.last_done}')
|
||||
```
|
||||
|
||||
### Get Holdings
|
||||
```python
|
||||
trade_ctx = openapi.TradeContext(config=cfg)
|
||||
positions = trade_ctx.stock_positions()
|
||||
for ch in positions.channels:
|
||||
for pos in ch.positions:
|
||||
print(f'{pos.symbol}: {pos.quantity} @ {pos.cost_price}')
|
||||
```
|
||||
|
||||
For full API surface, see `references/api-reference.md`.
|
||||
|
||||
## Common Pitfalls
|
||||
## Valuation Metrics (calc_indexes)
|
||||
|
||||
Get PE, PB, dividend yield, market cap via `CalcIndex` enum:
|
||||
|
||||
```python
|
||||
from longport.openapi import CalcIndex
|
||||
|
||||
indexes = [
|
||||
CalcIndex.PeTtmRatio, # PE TTM
|
||||
CalcIndex.PbRatio, # PB
|
||||
CalcIndex.DividendRatioTtm, # 股息率 TTM (%)
|
||||
CalcIndex.TotalMarketValue, # 总市值
|
||||
CalcIndex.TurnoverRate, # 换手率
|
||||
CalcIndex.VolumeRatio, # 量比
|
||||
CalcIndex.ChangeRate, # 涨跌幅 (%)
|
||||
]
|
||||
|
||||
resp = ctx.calc_indexes(['O.US', '823.HK'], indexes)
|
||||
for item in resp:
|
||||
print(f'{item.symbol}: PE={item.pe_ttm_ratio}, PB={item.pb_ratio}, Yield={item.dividend_ratio_ttm}%')
|
||||
```
|
||||
|
||||
**Response fields** (direct attributes, NOT a list):
|
||||
- `pe_ttm_ratio`, `pb_ratio`, `dividend_ratio_ttm`
|
||||
- `total_market_value`, `turnover_rate`, `volume_ratio`, `change_rate`
|
||||
|
||||
## Static Info (EPS, BPS, Shares)
|
||||
|
||||
```python
|
||||
resp = ctx.static_info(['O.US'])
|
||||
info = resp[0]
|
||||
print(f'EPS TTM: {info.eps_ttm}')
|
||||
print(f'BPS: {info.bps}')
|
||||
print(f'Dividend Yield: {info.dividend_yield}%')
|
||||
print(f'Total Shares: {info.total_shares}')
|
||||
print(f'Currency: {info.currency}')
|
||||
```
|
||||
|
||||
**Fields**: `eps`, `eps_ttm`, `bps`, `dividend_yield`, `currency`, `total_shares`, `circulating_shares`, `name_en`, `name_cn`, `lot_size`
|
||||
|
||||
## Historical K-lines (Longer History)
|
||||
|
||||
`candlesticks()` is limited to ~1000 bars. For longer history use:
|
||||
|
||||
```python
|
||||
from longport.openapi import Period, AdjustType
|
||||
|
||||
# Parameters: symbol, period, adjust_type, backward, count
|
||||
candles = ctx.history_candlesticks_by_offset(
|
||||
'AAPL.US',
|
||||
Period.Day,
|
||||
AdjustType.ForwardAdjust, # 前复权
|
||||
False, # backward=True means older data
|
||||
1000, # max ~1000 per request
|
||||
)
|
||||
```
|
||||
|
||||
⚠️ **Parameter order is different from `candlesticks()`!**
|
||||
- `candlesticks(symbol, period, count, adjust_type)`
|
||||
- `history_candlesticks_by_offset(symbol, period, adjust_type, backward, count)`
|
||||
|
||||
## Other Broker SDKs
|
||||
> 📖 For comparison with 雪盈证券 (`snbpy`) and other Chinese/Asian broker SDKs, see `references/broker-sdk-comparison.md`.
|
||||
|
||||
## Common Pitfalls
|
||||
- **Env Var Prefix**: CLI uses `LONGBRIDGE_`, SDK uses `LONGPORT_`. Both are now in bashrc (LONGPORT_* references LONGBRIDGE_*), so `Config.from_env()` works directly. If it fails, the fallback is to map manually from bashrc LONGBRIDGE_* values.
|
||||
- **Method Name**: Use `ctx.stock_positions()`, NOT `ctx.positions()`.
|
||||
- **Response Structure**: `stock_positions()` returns a response object with `channels` list, then `positions` inside each channel.
|
||||
- **Decimal Type**: `total_market_value` and some fields return `decimal.Decimal`, not `float`. Always wrap with `float()` for arithmetic.
|
||||
- **adjust_type Required**: `candlesticks()` requires `adjust_type` parameter. Use `AdjustType.ForwardAdjust` for forward-adjusted prices.
|
||||
- **K-line Limit**: Error code 301607 = "request too many klines". Max ~1000 per request. Use `history_candlesticks_by_offset` for pagination.
|
||||
- **calc_indexes Response**: Returns `SecurityCalcIndex` objects with direct attributes (e.g., `item.pe_ttm_ratio`), NOT an `indexes` list.
|
||||
- **Token Expiration — two different codes**:
|
||||
- **401003 "token expired"**: Token was valid but has reached its ~180-day expiry. **All scripts using LongPort fail simultaneously.** Fix: run `bash ~/.hermes/scripts/update_longbridge_token.sh <new_token>` to auto-update all locations and verify both CLI + SDK.
|
||||
- **401004 "token invalid"**: Token was truncated or never valid. Bashrc has a placeholder like `m_eyJh...jb-k` (with literal `...`). Run the same script: `bash ~/.hermes/scripts/update_longbridge_token.sh <new_token>`. The script reads/bashrc-parsing approach shown below is a fallback for when the script is unavailable.
|
||||
```python
|
||||
import os, re
|
||||
env_vars = {}
|
||||
# Try .env first (authoritative), then bashrc
|
||||
for fpath in [os.path.expanduser('~/.env'), os.path.expanduser('~/.bashrc')]:
|
||||
if not os.path.exists(fpath):
|
||||
continue
|
||||
with open(fpath) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith('export LONGBRIDGE_') or line.startswith('export LONGPORT_'):
|
||||
parts = line.replace('export ', '').split('=', 1)
|
||||
if len(parts) == 2 and '...' not in parts[1]: # skip truncated placeholders
|
||||
key, val = parts
|
||||
env_vars[key] = val
|
||||
# Set non-referencing vars first
|
||||
for key, val in env_vars.items():
|
||||
if '${' not in val:
|
||||
os.environ[key] = val
|
||||
# Then resolve ${VAR} references
|
||||
for key, val in env_vars.items():
|
||||
if '${' in val:
|
||||
resolved = re.sub(r'\$\{(\w+)\}', lambda m: os.environ.get(m.group(1), ''), val)
|
||||
os.environ[key] = resolved
|
||||
```
|
||||
⚠️ **Key gotcha**: If bashrc contains `LONGBRIDGE_ACCESS_TOKEN=m_eyJh...jb-k` (with literal `...`), it's a truncated placeholder, NOT a real token. Skip entries containing `...` and prefer `.env` values.
|
||||
- **candlesticks() vs history_candlesticks_by_offset() Parameter Order**: These have DIFFERENT signatures!
|
||||
- `candlesticks(symbol, period, count, adjust_type)` — count is 3rd
|
||||
- `history_candlesticks_by_offset(symbol, period, adjust_type, backward, count)` — adjust_type is 3rd, count is 5th
|
||||
- Always check signatures when switching between these methods.
|
||||
- **history_candlesticks_by_offset backward param**: `False` = get older/historical data, `True` = get newer data from offset.
|
||||
- **calc_indexes Batch Size**: LongPort accepts arbitrary symbol lists but errors/silently drops on very large batches. Safe batch size is **~10-20 symbols per call**. For screener scripts (100+ symbols), iterate in batches of 10.
|
||||
- **python3 -c with HK Stock Codes**: HK codes like `1088.HK`, `3988.HK` start with digits. Python parses them as `1088.HK` → decimal literal error. **Never use `python3 -c` for scripts containing HK stock codes.** Always write to a temp file (`/tmp/script.py`) and run `python3 /tmp/script.py` instead. Same applies to any identifier starting with a digit.
|
||||
- **`quote()` fields**: `SecurityQuote` has `last_done`, `prev_close`, `volume`, `turnover`, `symbol`. It does **NOT** have `change_rate` — use `calc_indexes` with `CalcIndex.ChangeRate` for price change %. Gotcha: accessing `q.change_rate` raises `AttributeError: 'SecurityQuote' object has no attribute 'change_rate'`.
|
||||
- **CLI Token Masking (Critical)**: The `terminal` tool masks/redacts secrets from environment variables, causing the `longbridge` CLI to get truncated tokens → 401004/403201 errors. **The Python SDK always works** because scripts read bashrc directly via `open()` and set `os.environ` programmatically. When CLI fails but SDK works, this is why. Always prefer `execute_code` + SDK over `terminal` + CLI for any order/trade operation.
|
||||
- **China Mainland Geo-Block (Error 602315)**: LongPort API blocks trading from mainland China IPs. Error: `"Due to Mainland China regulatory requirements, you are currently located in Mainland China and cannot perform this action."` (code 602315). Read-only operations (quotes, positions) may still work. **Fix**: Use WireGuard VPN via overseas VPS. On-demand scripts (`wg-trade`, `wg-on/off/status`) route only trading traffic through VPN. Full setup in `references/wireguard-proxy-setup.md`.
|
||||
- **API Rate Limiting (429002)**: LongPort enforces per-app request frequency limits. Error: `api request is limited, please slow down request frequency` (code 429002). **Root cause**: multiple scripts hitting the API simultaneously (e.g. DCA monitor + price alert both running at :00). **Fix**: (1) Stagger cron schedules by ≥15 minutes between LongPort-calling jobs; (2) Reduce polling frequency — 30min is enough for price monitoring, don't use 10/15min intervals; (3) Use market filters (`--market=us/hk/cn`) to reduce per-run API calls; (4) Add exponential backoff retry in scripts for transient 429 errors.
|
||||
- **`source ~/.bashrc` doesn't work in terminal tool**: The terminal tool runs each command in a fresh shell that doesn't persist env vars from `source ~/.bashrc`. If `Config.from_env()` fails with "missing environment variable: LONGPORT_APP_KEY", use a Python script to parse bashrc directly:
|
||||
```python
|
||||
import os, re
|
||||
env_vars = {}
|
||||
with open(os.path.expanduser('~/.bashrc')) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith('export LONGBRIDGE_') or line.startswith('export LONGPORT_'):
|
||||
parts = line.replace('export ', '').split('=', 1)
|
||||
if len(parts) == 2:
|
||||
key, val = parts
|
||||
env_vars[key] = val
|
||||
# Set non-referencing vars first
|
||||
for key, val in env_vars.items():
|
||||
if '${' not in val:
|
||||
os.environ[key] = val
|
||||
# Then resolve ${VAR} references
|
||||
for key, val in env_vars.items():
|
||||
if '${' in val:
|
||||
resolved = re.sub(r'\$\{(\w+)\}', lambda m: os.environ.get(m.group(1), ''), val)
|
||||
os.environ[key] = resolved
|
||||
```
|
||||
Write this to `/tmp/load_env.py` and import at the top of any LongPort script run via `python3 /tmp/script.py`.
|
||||
|
||||
## Dividend/Valuation Screener Pattern
|
||||
|
||||
> 📖 For HK-specific dividend investing (monthly dividend workarounds, entry price methodology, data sources), see `references/hk-dividend-investing.md`.
|
||||
> 📖 For DCA scanner/monitor architecture (multi-market scanning, ladder alerts, cron scheduling), see `references/dca-monitoring-architecture.md`.
|
||||
|
||||
When user asks "which stocks have X% yield" or "find high-dividend stocks", use this pattern:
|
||||
1. Pull watchlist symbols via `ctx.watchlist()` → all user's tracked stocks
|
||||
2. Add a curated candidate list (BDCs, mREITs, MLPs, high-div ETFs, blue-chip dividend stocks)
|
||||
3. Batch `calc_indexes()` with `CalcIndex.DividendRatioTtm` + `CalcIndex.TotalMarketValue` in batches of 10
|
||||
4. Sort by yield descending, present in tiers (🔥 >20%, ⭐ 10-20%, ✅ 5-10%)
|
||||
|
||||
```python
|
||||
from longport.openapi import CalcIndex
|
||||
import os, sys
|
||||
|
||||
cfg = openapi.Config.from_env()
|
||||
ctx = openapi.QuoteContext(config=cfg)
|
||||
|
||||
# Step 1: Get all watchlist symbols
|
||||
wl = ctx.watchlist()
|
||||
watchlist_symbols = list({sec.symbol for group in wl for sec in group.securities})
|
||||
|
||||
# Step 2: Add high-yield candidate universe
|
||||
candidates = [
|
||||
'HRZN.US','PSEC.US','SVOL.US','FSK.US','ORC.US','IVR.US', # BDC/mREIT >20%
|
||||
'ARR.US','DX.US','AGNC.US','NLY.US','NYMT.US','CIM.US', # mREIT
|
||||
'ARCC.US','HTGC.US','TSLX.US','MAIN.US','GAIN.US','GLAD.US', # BDC
|
||||
'JEPI.US','JEPQ.US','QYLD.US','SPYI.US','QQQI.US','DIVO.US', # 高息ETF
|
||||
'MO.US','VZ.US','XOM.US','BTI.US','O.US', # 蓝筹高息
|
||||
'ET.US','EPD.US','MPLX.US','USAC.US', # MLP
|
||||
'3416.HK','3417.HK','3419.HK', # 港股高息ETF
|
||||
'1088.HK','0883.HK','3968.HK','1919.HK','2318.HK', # 港股高息蓝筹
|
||||
]
|
||||
all_symbols = list(set(watchlist_symbols + candidates))
|
||||
|
||||
# Step 3: Batch calc (10 per batch)
|
||||
results = []
|
||||
for i in range(0, len(all_symbols), 10):
|
||||
batch = all_symbols[i:i+10]
|
||||
try:
|
||||
resp = ctx.calc_indexes(batch, [CalcIndex.DividendRatioTtm, CalcIndex.TotalMarketValue])
|
||||
for item in resp:
|
||||
dy = item.dividend_ratio_ttm
|
||||
if dy is not None:
|
||||
try:
|
||||
dy_val = float(dy)
|
||||
if dy_val > 5: # Filter noise
|
||||
cap = float(item.total_market_value) if item.total_market_value else 0
|
||||
results.append({'symbol': item.symbol, 'yield': dy_val, 'cap': cap})
|
||||
except: pass
|
||||
except Exception as e:
|
||||
print(f"Batch error: {e}", file=sys.stderr)
|
||||
|
||||
# Step 4: Sort and present
|
||||
results.sort(key=lambda x: x['yield'], reverse=True)
|
||||
```
|
||||
|
||||
**Note**: `DividendRatioTtm` returns the **trailing 12-month dividend yield as a percentage** (e.g. 14.16 means 14.16%). This is dividend-per-share / price, annualized from actual payments — not a forward estimate.
|
||||
|
||||
## Example Script
|
||||
```python
|
||||
import os
|
||||
from longport import openapi
|
||||
from longport.openapi import CalcIndex
|
||||
|
||||
# Connect (LONGPORT_* now in bashrc)
|
||||
cfg = openapi.Config.from_env()
|
||||
ctx = openapi.QuoteContext(config=cfg)
|
||||
trade_ctx = openapi.TradeContext(config=cfg)
|
||||
|
||||
# 1. Realtime quote
|
||||
resp = ctx.quote(['O.US', '823.HK'])
|
||||
for q in resp:
|
||||
print(f'{q.symbol}: ${q.last_done:.2f}')
|
||||
|
||||
# 2. Fundamental data (PE, PB, dividend yield)
|
||||
resp = ctx.calc_indexes(['O.US'], [CalcIndex.PeTtmRatio, CalcIndex.PbRatio, CalcIndex.DividendRatioTtm])
|
||||
print(f'O.US: PE={resp[0].pe_ttm_ratio}, PB={resp[0].pb_ratio}, 股息率={resp[0].dividend_ratio_ttm}%')
|
||||
|
||||
# 3. Static info (EPS, BPS)
|
||||
info = ctx.static_info(['O.US'])[0]
|
||||
print(f'EPS_TTM: {info.eps_ttm}, BPS: {info.bps}')
|
||||
|
||||
# 4. Holdings
|
||||
positions = trade_ctx.stock_positions()
|
||||
for ch in positions.channels:
|
||||
for pos in ch.positions:
|
||||
print(f'{pos.symbol}: {pos.quantity} @ {pos.cost_price}')
|
||||
|
||||
# 5. Watchlist
|
||||
wl = ctx.watchlist()
|
||||
for group in wl:
|
||||
print(f'分组: {group.name} ({len(group.securities)}只)')
|
||||
```
|
||||
@@ -0,0 +1,105 @@
|
||||
# LongPort Python SDK API Reference
|
||||
|
||||
## QuoteContext Methods (Watchlist & Quotes)
|
||||
|
||||
```python
|
||||
from longport import openapi
|
||||
cfg = openapi.Config.from_env()
|
||||
ctx = openapi.QuoteContext(config=cfg)
|
||||
```
|
||||
|
||||
### Watchlist Management
|
||||
|
||||
| Method | Description | Returns |
|
||||
|--------|-------------|---------|
|
||||
| `ctx.watchlist()` | Get all watchlist groups with securities | `list[WatchlistGroup]` |
|
||||
| `ctx.create_watchlist_group(name, securities)` | Create new watchlist group | - |
|
||||
| `ctx.update_watchlist_group(name, securities)` | Update existing group | - |
|
||||
| `ctx.delete_watchlist_group(name)` | Delete a watchlist group | - |
|
||||
|
||||
### Watchlist Response Structure
|
||||
|
||||
```python
|
||||
resp = ctx.watchlist()
|
||||
for group in resp:
|
||||
print(f'Group: {group.name}')
|
||||
print(f' Securities: {len(group.securities)}')
|
||||
for sec in group.securities:
|
||||
# sec has: symbol, market, name, watched_price, watched_at
|
||||
print(f' - {sec.symbol}: {sec.name} @ {sec.watched_price}')
|
||||
```
|
||||
|
||||
**WatchlistSecurity fields:**
|
||||
- `symbol` — e.g. "O.US", "823.HK"
|
||||
- `market` — "US", "HK", "CN"
|
||||
- `name` — display name
|
||||
- `watched_price` — `Some(float)` or `None`
|
||||
- `watched_at` — ISO timestamp string
|
||||
|
||||
**Special groups:**
|
||||
- `all` — contains all securities across groups (auto-generated)
|
||||
- `us` / `hk` — market-based auto-groups
|
||||
- User-created groups (e.g. "收息", "月派", "月拼", "季派")
|
||||
|
||||
### Quote Methods
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `ctx.quote(symbols)` | Get real-time quotes for symbols |
|
||||
| `ctx.realtime_quote(symbols)` | Real-time quote subscription |
|
||||
| `ctx.candlesticks(symbol, period, count)` | Get K-line data |
|
||||
| `ctx.history_candlesticks_by_offset(...)` | Historical K-lines |
|
||||
| `ctx.depth(symbol)` | Order book depth |
|
||||
| `ctx.trades(symbol)` | Recent trades |
|
||||
| `ctx.static_info(symbols)` | Static security info |
|
||||
| `ctx.capital_flow(symbol)` | Capital flow data |
|
||||
| `ctx.capital_distribution(symbol)` | Capital distribution |
|
||||
|
||||
### Quote Response
|
||||
|
||||
```python
|
||||
resp = ctx.quote(['O.US', 'STAG.US', 'AGNC.US'])
|
||||
for q in resp:
|
||||
print(f'{q.symbol}: ${q.last_done:.2f}, vol={q.volume}')
|
||||
```
|
||||
|
||||
**Quote fields:** `symbol`, `last_done`, `prev_close`, `volume`, `turnover`, `high`, `low`, `open`
|
||||
|
||||
## TradeContext Methods
|
||||
|
||||
```python
|
||||
trade_ctx = openapi.TradeContext(config=cfg)
|
||||
```
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `trade_ctx.stock_positions()` | Get holdings |
|
||||
| `trade_ctx.account_balance()` | Get account balance |
|
||||
| `trade_ctx.today_orders()` | Today's orders |
|
||||
| `trade_ctx.history_orders(...)` | Historical orders |
|
||||
| `trade_ctx.place_order(...)` | Place new order |
|
||||
| `trade_ctx.cancel_order(order_id)` | Cancel order |
|
||||
|
||||
### Positions Response
|
||||
|
||||
```python
|
||||
positions = trade_ctx.stock_positions()
|
||||
for ch in positions.channels:
|
||||
for pos in ch.positions:
|
||||
print(f'{pos.symbol}: {pos.quantity} @ {pos.cost_price}')
|
||||
```
|
||||
|
||||
## Symbol Format
|
||||
|
||||
| Market | Format | Example |
|
||||
|--------|--------|---------|
|
||||
| US | `{TICKER}.US` | `O.US`, `AAPL.US` |
|
||||
| HK | `{CODE}.HK` | `823.HK`, `9988.HK` |
|
||||
| CN | `{CODE}.SZ` or `{CODE}.SH` | `000001.SZ` |
|
||||
|
||||
## Market Access Notes
|
||||
|
||||
- LV1 Real-time Quotes: CN, HK, US
|
||||
- Nasdaq Basic: US stocks
|
||||
- USOption: requires separate purchase
|
||||
- Some markets may show access warnings on connect (normal)
|
||||
@@ -0,0 +1,83 @@
|
||||
# Chinese/Asian Broker SDK Comparison
|
||||
|
||||
## LongPort (长桥) vs Snowball Securities (雪盈)
|
||||
|
||||
| Feature | LongPort (`longbridge`) | Snowball (`snbpy`) |
|
||||
|---|---|---|
|
||||
| **CLI Tool** | ✅ `longport-cli` | ❌ None |
|
||||
| **Python SDK** | ✅ `longbridge` (PyPI) | ✅ `snbpy` (PyPI) |
|
||||
| **Java SDK** | ✅ | ✅ |
|
||||
| **Market Data API** | ✅ Realtime, K-lines, depth, options chain | ❌ No market data |
|
||||
| **Trading API** | ✅ Full (limit/market/stop/trailing) | ✅ 10 APIs |
|
||||
| **Watchlist API** | ✅ | ❌ |
|
||||
| **Fundamentals** | ✅ PE/PB/EPS/BPS/dividend yield | ❌ |
|
||||
| **Capital Flow** | ✅ | ❌ |
|
||||
| **Active Maintenance** | ✅ Regular updates | ⚠️ Last updated ~2021 |
|
||||
| **Market Coverage** | HK, US, CN, SG | HK, US (+ forex, futures, options, bonds) |
|
||||
| **GitHub Stars** | ~hundreds | 35 |
|
||||
|
||||
## Snowball Securities (`snbpy`) Details
|
||||
|
||||
### Install
|
||||
```bash
|
||||
pip install snbpy
|
||||
```
|
||||
|
||||
### 10 Core APIs
|
||||
| Method | Description |
|
||||
|---|---|
|
||||
| `login` | Get auth token |
|
||||
| `get_token_status` | Check token expiry |
|
||||
| `place_order` | Submit order |
|
||||
| `cancel_order` | Cancel order |
|
||||
| `get_order_by_id` | Query single order |
|
||||
| `get_order_list` | Query all orders |
|
||||
| `get_position_list` | Query holdings |
|
||||
| `get_balance` | Query account balance |
|
||||
| `get_security_detail` | Security info |
|
||||
| `get_transaction_list` | Query trade history |
|
||||
|
||||
### Supported Order Types
|
||||
Limit, Market, Stop, Stop Limit, Trailing, Market-on-Open, Limit-on-Open, Market-on-Close, Limit-on-Close
|
||||
|
||||
### Supported Asset Types
|
||||
Stocks (STK), Futures (FUT), Options (OPT), Warrants (WAR), CFDs, Forex (CASH), Bonds, Funds, CBBCs (IOPT)
|
||||
|
||||
### Configuration
|
||||
```python
|
||||
from snbpy.common.domain.snb_config import SnbConfig
|
||||
from snbpy.snb_api_client import SnbHttpClient
|
||||
|
||||
config = SnbConfig()
|
||||
config.account = "DU876752" # Your account ID
|
||||
config.key = 'your_secret_key'
|
||||
config.snb_server = 'openapi.snbsecurities.com' # prod
|
||||
config.snb_port = '443'
|
||||
config.schema = 'https'
|
||||
config.timeout = 1000
|
||||
|
||||
client = SnbHttpClient(config)
|
||||
client.login()
|
||||
```
|
||||
|
||||
### Environments
|
||||
| Env | URL | Account |
|
||||
|---|---|---|
|
||||
| SIT (test) | sandbox.snbsecurities.com | Contact support |
|
||||
| PROD (real) | openapi.snbsecurities.com | Self-register on website |
|
||||
|
||||
### Key Limitations
|
||||
- **No market data** — cannot get quotes, K-lines, or depth
|
||||
- **No CLI** — Python/Java SDK only
|
||||
- **Stale** — last PyPI release ~2021, no recent commits
|
||||
- **Token limit** — server keeps max 10 tokens per user
|
||||
|
||||
### GitHub
|
||||
https://github.com/snowballsecurities/snbpy (35 stars, MIT license)
|
||||
|
||||
### Docs
|
||||
https://snowballsecurities.github.io/
|
||||
|
||||
## When to Use Which
|
||||
- **LongPort**: Primary choice for everything — data, trading, analysis
|
||||
- **Snowball**: Only if you have a Snowball account and want to automate trades there. Use LongPort for all market data regardless.
|
||||
@@ -0,0 +1,91 @@
|
||||
# LongPort CalcIndex Enum Reference
|
||||
|
||||
## 估值相关 (Valuation)
|
||||
| Enum | 说明 | 单位 | 示例 |
|
||||
|------|------|------|------|
|
||||
| `PeTtmRatio` | PE TTM | 倍 | 49.91 |
|
||||
| `PbRatio` | PB | 倍 | 1.43 |
|
||||
| `DividendRatioTtm` | 股息率 TTM | % | 5.40 |
|
||||
| `TotalMarketValue` | 总市值 | 货币单位 | 55921577024.10 |
|
||||
|
||||
## 行情相关 (Market)
|
||||
| Enum | 说明 | 单位 |
|
||||
|------|------|------|
|
||||
| `LastDone` | 最新价 | 货币单位 |
|
||||
| `ChangeRate` | 涨跌幅 | % |
|
||||
| `ChangeValue` | 涨跌额 | 货币单位 |
|
||||
| `Volume` | 成交量 | 股 |
|
||||
| `Turnover` | 成交额 | 货币单位 |
|
||||
| `TurnoverRate` | 换手率 | % |
|
||||
| `VolumeRatio` | 量比 | 倍 |
|
||||
| `Amplitude` | 振幅 | % |
|
||||
|
||||
## 周期涨跌幅 (Period Returns)
|
||||
| Enum | 说明 |
|
||||
|------|------|
|
||||
| `FiveMinutesChangeRate` | 5分钟涨跌幅 |
|
||||
| `FiveDayChangeRate` | 5日涨跌幅 |
|
||||
| `TenDayChangeRate` | 10日涨跌幅 |
|
||||
| `HalfYearChangeRate` | 半年涨跌幅 |
|
||||
| `YtdChangeRate` | 年初至今涨跌幅 |
|
||||
|
||||
## 期权相关 (Options)
|
||||
| Enum | 说明 |
|
||||
|------|------|
|
||||
| `ImpliedVolatility` | 隐含波动率 |
|
||||
| `Delta` | Delta |
|
||||
| `Gamma` | Gamma |
|
||||
| `Theta` | Theta |
|
||||
| `Vega` | Vega |
|
||||
| `Rho` | Rho |
|
||||
| `StrikePrice` | 行权价 |
|
||||
| `ExpiryDate` | 到期日 |
|
||||
| `Premium` | 溢价 |
|
||||
| `ItmOtm` | 价内/价外 |
|
||||
| `EffectiveLeverage` | 有效杠杆 |
|
||||
| `LeverageRatio` | 杠杆比率 |
|
||||
| `CallPrice` | 召回价 |
|
||||
| `ToCallPrice` | 距召回价 |
|
||||
| `ConversionRatio` | 换股比率 |
|
||||
| `BalancePoint` | 打和点 |
|
||||
| `OpenInterest` | 未平仓数 |
|
||||
| `OutstandingQty` | 街货量 |
|
||||
| `OutstandingRatio` | 街货占比 |
|
||||
| `UpperStrikePrice` | 上限价 |
|
||||
| `LowerStrikePrice` | 下限价 |
|
||||
| `WarrantDelta` | 窝轮Delta |
|
||||
|
||||
## static_info 字段
|
||||
| 字段 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `symbol` | 代码 | O.US |
|
||||
| `name_cn` | 中文名 | Realty Income MD |
|
||||
| `name_en` | 英文名 | Realty Income MD |
|
||||
| `name_hk` | 港股名 | Realty Income MD |
|
||||
| `currency` | 货币 | USD |
|
||||
| `lot_size` | 每手股数 | 1 |
|
||||
| `eps` | EPS | 1.135 |
|
||||
| `eps_ttm` | EPS TTM | 1.202 |
|
||||
| `bps` | 每股净资产 | 41.98 |
|
||||
| `dividend_yield` | 股息率 | 3.237 |
|
||||
| `total_shares` | 总股本 | 932492530 |
|
||||
| `circulating_shares` | 流通股 | 930306268 |
|
||||
| `exchange` | 交易所 | NYSE |
|
||||
| `board` | 板块 | SecurityBoard.USMain |
|
||||
|
||||
## 用法示例
|
||||
```python
|
||||
from longport import openapi
|
||||
from longport.openapi import CalcIndex
|
||||
|
||||
cfg = openapi.Config.from_env()
|
||||
ctx = openapi.QuoteContext(config=cfg)
|
||||
|
||||
# 估值指标
|
||||
resp = ctx.calc_indexes(['O.US'], [CalcIndex.PeTtmRatio, CalcIndex.PbRatio, CalcIndex.DividendRatioTtm])
|
||||
print(f'PE: {resp[0].pe_ttm_ratio}, PB: {resp[0].pb_ratio}, 股息率: {resp[0].dividend_ratio_ttm}%')
|
||||
|
||||
# 基本面
|
||||
info = ctx.static_info(['O.US'])[0]
|
||||
print(f'EPS_TTM: {info.eps_ttm}, BPS: {info.bps}')
|
||||
```
|
||||
@@ -0,0 +1,41 @@
|
||||
# DCA Scanner & Monitoring Architecture
|
||||
|
||||
## Overview
|
||||
Pattern for automated high-dividend stock scanning across multiple markets (HK/US/CN), with DCA ladder buy-signal monitoring.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Two script types:
|
||||
1. **Scanner** (`dca_scanner.py`) — Scans a candidate pool for high-yield stocks, pushes TOP5 with ladder prices
|
||||
2. **Monitor** (`dca_monitor.py`) — Watches existing positions for buy-signal triggers against ladder levels
|
||||
|
||||
### Market separation:
|
||||
- Each script accepts `--market=hk|us|cn` to filter positions/candidates
|
||||
- Cron jobs are split per market to avoid API collisions and match trading hours
|
||||
- Scanner candidate pools are hardcoded per market (24 HK / 15 US / 14 CN)
|
||||
|
||||
### Cron schedule pattern (EDT, staggered ≥15min):
|
||||
```
|
||||
A股扫描: 19:30 (= 北京 7:30AM, A股开盘前)
|
||||
港股扫描: 20:00 (= 北京 8:00AM, 港股开盘前)
|
||||
美股扫描: 21:30 (= 北京 9:30AM, 美股开盘前)
|
||||
美股监控1: 22:30 (美股盘中)
|
||||
美股监控2: 02:00 (美股盘中)
|
||||
```
|
||||
|
||||
### Rate limiting prevention:
|
||||
- No two LongPort jobs share the same minute
|
||||
- Scanner and Monitor never run simultaneously
|
||||
- RGTI price monitoring (if needed) at 30min intervals, NOT 10/15min
|
||||
|
||||
## Key design decisions:
|
||||
1. **User wants push-based scanning** — "你要扫描高股息的发通知给我,不是我选" — system scans and pushes candidates, user doesn't manually pick from lists
|
||||
2. **30min polling is enough** for price monitoring — user explicitly said "半小时吧,不需要太频繁"
|
||||
3. **Merge overlapping tasks** — price alert + auto order were merged into one (RGTI)
|
||||
4. **Pause mislabeled tasks** — "港股监控" that only had US stocks was paused
|
||||
5. **Add dividend frequency** to all output — `[季度]` / `[月度]` / `[半年]` suffix
|
||||
|
||||
## Data files:
|
||||
- `~/.hermes/scripts/dca_positions.json` — Current positions with ladder prices, yields, div_freq
|
||||
- `~/.hermes/scripts/dca_scanner.py` — Market scanner with candidate pools
|
||||
- `~/.hermes/scripts/dca_monitor.py` — Ladder monitor with market filter support
|
||||
@@ -0,0 +1,21 @@
|
||||
# LongPort API Error Codes
|
||||
|
||||
## Authentication Errors
|
||||
| Code | Meaning | Fix |
|
||||
|------|---------|-----|
|
||||
| 401004 | Token invalid | Token truncated/expired. Check bashrc has full token (1053 chars). Use Python to parse bashrc directly, don't rely on `source`. |
|
||||
| 403201 | Auth failed | Similar to 401004 — token masking by terminal tool. Use `execute_code` + SDK instead of CLI. |
|
||||
|
||||
## Geo-Restriction Errors
|
||||
| Code | Meaning | Fix |
|
||||
|------|---------|-----|
|
||||
| 602315 | Mainland China regulatory block | API detects mainland China IP. Must use VPN (HK/US/etc). Read-only may still work; trading is blocked. |
|
||||
|
||||
## Trading Errors
|
||||
| Code | Meaning | Fix |
|
||||
|------|---------|-----|
|
||||
| 301607 | Too many k-lines requested | Max ~1000 per `candlesticks()` call. Use `history_candlesticks_by_offset()` for pagination. |
|
||||
|
||||
## Common Error Patterns
|
||||
- **401004 + 602315**: Both appeared in same session. 401004 was from truncated token in .env, 602315 after token was fixed (real token loaded from bashrc).
|
||||
- **Token "..." truncation**: bashrc shows `m_eyJh...jb-k` in grep output even when full 1053-char token exists. The `...` is just display truncation by the shell/terminal, not in the actual file. Use `python3 -c "import os; ..."` to verify real length.
|
||||
@@ -0,0 +1,79 @@
|
||||
# High Dividend Yield Candidate Universe
|
||||
|
||||
Pre-curated symbol lists for dividend screener scripts. Last verified: 2026-06-06 via LongPort.
|
||||
|
||||
## US — BDC (Business Development Companies)
|
||||
| Symbol | Yield (TTM) | Mkt Cap | Notes |
|
||||
|--------|-------------|---------|-------|
|
||||
| HRZN.US | ~25% | 0.3B | Horizon Technology Finance |
|
||||
| PSEC.US | ~24% | 1.1B | Prospect Capital |
|
||||
| FSK.US | ~22% | 3.0B | FS KKR Capital |
|
||||
| TSLX.US | ~11% | 1.7B | Sixth Street Specialty |
|
||||
| HTGC.US | ~10% | 2.8B | Hercules Capital |
|
||||
| ARCC.US | ~10% | 13.5B | Ares Capital (largest BDC) |
|
||||
| MAIN.US | ~6% | 4.8B | Main Street Capital |
|
||||
| GAIN.US | ~6% | 0.6B | Gladstone Investment |
|
||||
| GLAD.US | ~9% | 0.4B | Gladstone Capital |
|
||||
| PFLT.US | ~15% | 0.8B | PennantPark Floating |
|
||||
|
||||
## US — mREIT (Mortgage REITs)
|
||||
| Symbol | Yield (TTM) | Mkt Cap | Notes |
|
||||
|--------|-------------|---------|-------|
|
||||
| ORC.US | ~21% | 1.3B | Orchid Island Capital |
|
||||
| IVR.US | ~21% | 0.7B | Invesco Mortgage Capital |
|
||||
| ARR.US | ~17% | 2.1B | ARMOUR Residential |
|
||||
| DX.US | ~16% | 2.6B | Dynex Capital |
|
||||
| AGNC.US | ~14% | 11.7B | AGNC Investment (largest mREIT) |
|
||||
| NLY.US | ~13% | 15.5B | Annaly Capital |
|
||||
| CIM.US | ~12% | 1.1B | Chimera Investment |
|
||||
| NYMT.US | ~11% | 0.6B | New York Mortgage Trust |
|
||||
|
||||
## US — High Dividend ETFs
|
||||
| Symbol | Yield (TTM) | Mkt Cap | Notes |
|
||||
|--------|-------------|---------|-------|
|
||||
| SVOL.US | ~22% | 0.6B | Simplify Volatility Premium |
|
||||
| QQQI.US | ~14% | 11.0B | Defiance Nasdaq-100 Enhanced |
|
||||
| SPYI.US | ~12% | 9.1B | Neos S&P 500 High Income |
|
||||
| QYLD.US | ~12% | 8.3B | Global X NASDAQ-100 Covered Call |
|
||||
| PTY.US | ~12% | 2.5B | Pimco Corporate & Income |
|
||||
| JEPI.US | ~8% | 43.5B | JPMorgan Equity Premium Income |
|
||||
| JEPQ.US | ~10% | 36.7B | JPMorgan Nasdaq Equity Premium |
|
||||
| DIVO.US | ~6% | 6.9B | Amplify CWP Enhanced Dividend |
|
||||
|
||||
## US — MLP (Master Limited Partnerships)
|
||||
| Symbol | Yield (TTM) | Mkt Cap | Notes |
|
||||
|--------|-------------|---------|-------|
|
||||
| USAC.US | ~8% | 4.0B | USA Compression Partners |
|
||||
| MPLX.US | ~7% | 57.3B | MPLX LP |
|
||||
| ET.US | ~7% | 66.7B | Energy Transfer |
|
||||
| EPD.US | ~6% | 81.8B | Enterprise Products Partners |
|
||||
|
||||
## US — Blue Chip Dividend
|
||||
| Symbol | Yield (TTM) | Mkt Cap | Notes |
|
||||
|--------|-------------|---------|-------|
|
||||
| KHC.US | ~7% | 26.7B | Kraft Heinz |
|
||||
| VZ.US | ~6% | 189.4B | Verizon |
|
||||
| MO.US | ~6% | 120.5B | Altria |
|
||||
| O.US | ~5% | 56.7B | Realty Income (monthly dividend) |
|
||||
|
||||
## HK — High Dividend ETFs
|
||||
| Symbol | Yield (TTM) | Mkt Cap | Notes |
|
||||
|--------|-------------|---------|-------|
|
||||
| 3417.HK | ~19% | 2.9B | 华夏沪深三百高股息ETF |
|
||||
| 3416.HK | ~19% | 24.1B | 华夏恒生高股息ETF |
|
||||
| 3419.HK | ~15% | 1.7B | 华夏沪深三百精选高股息 |
|
||||
|
||||
## HK — Blue Chip High Dividend
|
||||
| Symbol | Yield (TTM) | Mkt Cap | Notes |
|
||||
|--------|-------------|---------|-------|
|
||||
| 1088.HK | ~7% | 1000B | 中国神华 |
|
||||
| 0883.HK | ~5% | — | 中海油 |
|
||||
| 3968.HK | ~7% | 1215B | 招商银行 |
|
||||
| 1919.HK | ~7% | 230B | 中远海控 |
|
||||
| 2318.HK | ~5% | 1030B | 中国平安 |
|
||||
|
||||
## Key Insight
|
||||
**No stock reliably sustains 30%+ dividend yield.** The practical ceiling for "investable" high yield is:
|
||||
- US: ~10-15% (BDC/mREIT, with leverage risk)
|
||||
- HK: ~15-19% (high-div ETFs)
|
||||
- Anything >25% is almost certainly a special dividend, yield trap, or price collapse artifact.
|
||||
@@ -0,0 +1,58 @@
|
||||
# Hong Kong Dividend Investing Reference
|
||||
|
||||
## Key Facts
|
||||
- **No true monthly dividend stocks in HK** — unlike US (Realty Income O, AGNC), no individual HK stock pays monthly
|
||||
- Most HK stocks pay **semi-annually** (年报 + 中报), some pay **quarterly**
|
||||
- To get monthly cash flow, combine stocks with different payment months OR use monthly-dividend ETFs
|
||||
|
||||
## Monthly Dividend ETF (Hong Kong)
|
||||
| ETF | Code | Yield | Freq | Entry Cost |
|
||||
|---|---|---|---|---|
|
||||
| 恒生高息股30 ETF | 3466.HK | ~6.8% | Monthly | ~8,200 HKD |
|
||||
| 富邦沪深港高股息 | 3190.HK | ~6% | Quarterly | ~3,460 HKD |
|
||||
| GX亚太高股息 | 3116.HK | ~6% | Quarterly | ~3,000 HKD |
|
||||
|
||||
**3466** is the only true monthly-dividend HK ETF. Top holdings include 中国宏桥(1378), 裕元(551), 恒隆(101), 伟易达(303), 中远海控(1919).
|
||||
|
||||
## Quarterly Dividend HK Stocks (combine for monthly income)
|
||||
| Stock | Code | Payment Months | Yield |
|
||||
|---|---|---|---|
|
||||
| 中电控股 | 0002.HK | 3/6/9/12 | ~4.5% |
|
||||
| 汇丰控股 | 0005.HK | 4/6/9/12 | ~5% |
|
||||
| 宏利金融 | 0945.HK | 3/6/9/12 | ~4% |
|
||||
| 中银香港 | 2388.HK | 5/9/11/末期 | ~5.5% |
|
||||
| 港通控股 | 0032.HK | 6/7/9/12 | ~3% |
|
||||
|
||||
## Entry Price Analysis Methodology
|
||||
When user asks for entry price for dividend stocks:
|
||||
|
||||
1. **Gather data** (use LongPort SDK):
|
||||
- Current price, PE, PB, 52-week range
|
||||
- TTM dividend yield via `CalcIndex.DividendRatioTtm`
|
||||
2. **Get dividend history** from 理杏仁 (lixinger.com) or web search:
|
||||
- Recent years' per-share dividends
|
||||
- Payment schedule (年报/中报 split)
|
||||
- Payout ratio trend
|
||||
3. **Calculate yield at different price levels**:
|
||||
- 理想 (ideal): near 52-week low, yield >9%, PB <0.7
|
||||
- 合理 (fair): recent support, yield ~8%, PB <0.8
|
||||
- 可接受 (acceptable): current price, yield ~7-8%
|
||||
4. **Cross-reference**: analyst targets (中金/中信/华泰), consensus upside
|
||||
5. **Risk factors**: cycle risk, payout sustainability, short interest
|
||||
|
||||
## DCA Threshold for Dividend Stocks
|
||||
User's rule: only keep stocks with **≥7% dividend yield**. Below 7% → auto-filter out.
|
||||
Current DCA portfolio: NLY, HTGC, ARCC (all BDC/REIT, monthly payers).
|
||||
|
||||
## Data Sources (ranked by reliability for HK dividends)
|
||||
1. **LongPort SDK** — real-time yield, PE, PB (use `calc_indexes`)
|
||||
2. **理杏仁 (lixinger.com)** — best for historical dividend tables, payout rates
|
||||
3. **英为财情 (investing.com)** — dividend calendar, yield comparison
|
||||
4. **华盛通 (hstong.com)** — real-time quotes, news flow
|
||||
5. **券商研报** — target prices, payout forecasts
|
||||
|
||||
## Presentation Style (user preference)
|
||||
- 一句话总结 + 关键数据 + emoji标记
|
||||
- Card/table format, not wall of text
|
||||
- 3-tier entry price table (理想/合理/可接受) with yield at each level
|
||||
- Include risk section but keep it brief (2-3 bullet points)
|
||||
@@ -0,0 +1,155 @@
|
||||
# WireGuard Proxy for LongPort API (China Mainland Bypass)
|
||||
|
||||
## Problem
|
||||
LongPort API blocks trading from mainland China IPs with error 602315:
|
||||
> "Due to Mainland China regulatory requirements, you are currently located in Mainland China and cannot perform this action."
|
||||
|
||||
Read-only operations (quotes, positions) may still work, but order placement fails.
|
||||
|
||||
## Solution: WireGuard VPN with On-Demand Proxy
|
||||
|
||||
### Architecture
|
||||
```
|
||||
Server (China mainland) ──WireGuard──> VPS (HK/US/JP) ──> LongPort API
|
||||
```
|
||||
|
||||
WireGuard is faster and more stable than application-layer proxies (Clash, V2Ray) because it operates at the kernel level.
|
||||
|
||||
### Setup Steps
|
||||
|
||||
#### 1. Install WireGuard
|
||||
```bash
|
||||
sudo apt update && sudo apt install -y wireguard resolvconf
|
||||
```
|
||||
|
||||
#### 2. Get client config from WireGuard server
|
||||
The user provides a config like:
|
||||
```ini
|
||||
[Interface]
|
||||
PrivateKey = <key>
|
||||
Address = 10.8.0.7/32
|
||||
MTU = 1420
|
||||
DNS = 1.1.1.1
|
||||
|
||||
[Peer]
|
||||
PublicKey = <key>
|
||||
PresharedKey = <key>
|
||||
AllowedIPs = 0.0.0.0/0, ::/0
|
||||
PersistentKeepalive = 25
|
||||
Endpoint = wg.example.com:51820
|
||||
```
|
||||
|
||||
#### 3. Install config
|
||||
```bash
|
||||
sudo cp /tmp/wg0.conf /etc/wireguard/wg0.conf
|
||||
sudo chmod 600 /etc/wireguard/wg0.conf
|
||||
```
|
||||
|
||||
#### 4. Start WireGuard
|
||||
```bash
|
||||
sudo wg-quick up wg0
|
||||
```
|
||||
|
||||
#### 5. Enable on boot
|
||||
```bash
|
||||
sudo systemctl enable wg-quick@wg0
|
||||
```
|
||||
|
||||
#### 6. Verify
|
||||
```bash
|
||||
sudo wg show # Check handshake
|
||||
curl -s ifconfig.me # Should show VPS IP, not mainland IP
|
||||
```
|
||||
|
||||
### On-Demand Proxy Scripts
|
||||
|
||||
Instead of routing ALL traffic through VPN (slow), use on-demand scripts:
|
||||
|
||||
**`~/.local/bin/wg-trade`** — Run single command through VPN:
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# Usage: wg-trade <command>
|
||||
CMD="$1"
|
||||
shift
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: wg-trade <command>"
|
||||
exit 1
|
||||
fi
|
||||
if ! sudo wg show wg0 2>/dev/null | grep -q "latest handshake"; then
|
||||
echo "🔄 Starting WireGuard..."
|
||||
sudo wg-quick up wg0 2>/dev/null
|
||||
fi
|
||||
echo "🔒 Running via VPN: $@"
|
||||
"$@"
|
||||
```
|
||||
|
||||
**`~/.local/bin/wg-on`** / **`wg-off`** / **`wg-status`** — Toggle VPN:
|
||||
```bash
|
||||
#!/bin/bash
|
||||
# wg-on: enable full VPN
|
||||
sudo wg-quick up wg0 2>/dev/null
|
||||
echo "✅ WireGuard ON — IP: $(curl -s --max-time 5 ifconfig.me)"
|
||||
|
||||
#!/bin/bash
|
||||
# wg-off: disable VPN
|
||||
sudo wg-quick down wg0 2>/dev/null
|
||||
echo "❌ WireGuard OFF"
|
||||
|
||||
#!/bin/bash
|
||||
# wg-status: check VPN status
|
||||
if sudo wg show wg0 2>/dev/null | grep -q "latest handshake"; then
|
||||
echo "✅ WireGuard: Connected — VPN IP: $(curl -s --max-time 5 ifconfig.me)"
|
||||
else
|
||||
echo "❌ WireGuard: Disconnected"
|
||||
fi
|
||||
```
|
||||
|
||||
Make executable: `chmod +x ~/.local/bin/wg-trade ~/.local/bin/wg-on ~/.local/bin/wg-off ~/.local/bin/wg-status`
|
||||
|
||||
### Usage with LongPort Trading
|
||||
|
||||
```bash
|
||||
# Trade through VPN
|
||||
wg-trade python3 ~/.hermes/scripts/rgti_auto_t.py status
|
||||
|
||||
# Or use Python SDK directly (WireGuard is already routing all traffic when up)
|
||||
python3 -c "
|
||||
import os
|
||||
with open(os.path.expanduser('~/.bashrc')) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line.startswith('export LONGBRIDGE_') or line.startswith('export LONGPORT_'):
|
||||
parts = line.replace('export ', '').split('=', 1)
|
||||
if len(parts) == 2:
|
||||
os.environ[parts[0]] = parts[1]
|
||||
|
||||
from longport import openapi
|
||||
cfg = openapi.Config.from_env()
|
||||
ctx = openapi.TradeContext(config=cfg)
|
||||
resp = ctx.submit_order(
|
||||
symbol='SPCX.US',
|
||||
order_type=openapi.OrderType.LO,
|
||||
side=openapi.OrderSide.Buy,
|
||||
submitted_quantity=2,
|
||||
time_in_force=openapi.TimeInForceType.GoodTilCanceled,
|
||||
submitted_price=150.00,
|
||||
outside_rth=openapi.OutsideRTH.AnyTime,
|
||||
)
|
||||
print(f'Order ID: {resp.order_id}')
|
||||
"
|
||||
```
|
||||
|
||||
### Common WireGuard Commands
|
||||
```bash
|
||||
sudo wg-quick up wg0 # Start
|
||||
sudo wg-quick down wg0 # Stop
|
||||
sudo wg show # Status (handshake, transfer)
|
||||
sudo systemctl status wg-quick@wg0 # Service status
|
||||
```
|
||||
|
||||
### Pitfalls
|
||||
- **resolvconf not installed**: `wg-quick` fails with `resolvconf: command not found`. Fix: `sudo apt install -y resolvconf`
|
||||
- **wg0 already exists**: If WireGuard was up and you try `wg-quick up wg0` again, it fails. Use `sudo wg show wg0` to check status, or `sudo wg-quick down wg0 && sudo wg-quick up wg0` to restart.
|
||||
- **DNS leak**: With `AllowedIPs = 0.0.0.0/0`, DNS also goes through VPN. This is usually desired for geo-unblocking.
|
||||
- **PersistentKeepalive**: Set to 25 for NAT traversal. Without it, idle tunnels may drop.
|
||||
- **Speed**: WireGuard is kernel-level and fast, but still limited by VPS bandwidth. For non-trading traffic, consider split tunneling (only route LongPort IPs through VPN).
|
||||
Reference in New Issue
Block a user