Initial commit: Hermes Agent skills collection
- Trading skills (OKX, dividend, lottery, quantitative) - Creative skills (ASCII art, diagrams, video) - Development skills (GitHub, debugging, TDD) - Research skills (arXiv, blog monitoring) - Productivity skills (email, documents, notes) - MCP integration skills - Custom user skills
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
---
|
||||
name: longbridge-cli
|
||||
description: LongPort OpenAPI CLI for market data, account management, orders, and trading/dividend analysis workflows.
|
||||
---
|
||||
|
||||
# LongBridge CLI (longbridge)
|
||||
|
||||
A specialized skill for interacting with the LongPort OpenAPI via the `longbridge` CLI. This skill handles market data (quotes, candlesticks), account info, and order management.
|
||||
|
||||
## Transport Options
|
||||
|
||||
LongPort can be accessed three ways — choose the one that fits:
|
||||
|
||||
| Transport | When to use |
|
||||
|-----------|-------------|
|
||||
| **CLI** (`longbridge`) | Quick terminal queries, simple scripts (this skill) |
|
||||
| **Python SDK** (`longport`) | Complex analysis, automated trading, batch workflows (see `longbridge-python-sdk` skill) |
|
||||
| **MCP** (native Hermes) | AI-agent-first access — tools auto-discover in Hermes (see `references/longport-mcp-integration.md`) |
|
||||
|
||||
For the MCP transport, LongPort uses a two-endpoint architecture: an auth endpoint (`/agent`) to exchange an auth code for a Bearer token, then the main MCP service at `https://mcp.longport.cn`. Full flow documented in the reference below.
|
||||
|
||||
## Usage
|
||||
All commands should be run with the appropriate environment variables (`LONGBRIDGE_APP_KEY`, `LONGBRIDGE_APP_SECRET`, `LONGBRIDGE_ACCESS_TOKEN`) loaded.
|
||||
|
||||
### Common Commands
|
||||
- **Quotes**: `longbridge quote --json <SYMBOLS>` (Get real-time quotes)
|
||||
- **Candlesticks**: `longbridge candlesticks --json <SYMBOLS>` (Get OHLC data)
|
||||
- **Account**: `longbridge balance --json` or `longbridge positions --json`
|
||||
- **Orders**: `longbridge orders --json` (Today's orders) or `longbridge buy/sell --json <SYMBOLS> <QUANTITY>`
|
||||
|
||||
### Order Placement (做T / Active Trading)
|
||||
|
||||
CLI order commands require `--price` for limit orders and `-y` to skip interactive confirmation (essential for automation):
|
||||
|
||||
```bash
|
||||
# Limit buy
|
||||
longbridge buy RGTI.US --qty 30 --price 18.50 -y
|
||||
|
||||
# Limit sell
|
||||
longbridge sell RGTI.US --qty 30 --price 20.50 -y
|
||||
|
||||
# Check pending orders
|
||||
longbridge orders --json
|
||||
|
||||
# Cancel all orders (or specific ones)
|
||||
longbridge cancel <ORDER_ID>
|
||||
```
|
||||
|
||||
**Pitfall**: `longbridge buy/sell` without `-y` hangs in interactive mode. Always use `-y` in scripts/cron.
|
||||
|
||||
#### T-Trading (做T) Analysis Workflow
|
||||
|
||||
做T = buying/selling around an existing position to lower cost basis. Requires high-volatility stocks with 10%+ daily swings.
|
||||
|
||||
1. **Fetch multi-timeframe data** via Python SDK (5min, 30min, daily candlesticks)
|
||||
2. **Calculate technical indicators**: SMA(5/10/20), ATR(14) for volatility, recent support/resistance from highs/lows
|
||||
3. **Identify key levels**: buy zone (support), sell zone (resistance), breakout/breakdown thresholds
|
||||
4. **Deploy monitoring script** as cron job (every 10-15 min during market hours)
|
||||
5. **Auto-place limit orders** when price hits key levels, notify user via chat
|
||||
|
||||
Technical analysis snippet (run via `execute_code`):
|
||||
```python
|
||||
from longport import openapi
|
||||
cfg = openapi.Config.from_env()
|
||||
ctx = openapi.QuoteContext(config=cfg)
|
||||
|
||||
candles = ctx.candlesticks("SYMBOL.US", openapi.Period.Day, 20, openapi.AdjustType.NoAdjust)
|
||||
closes = [float(c.close) for c in candles]
|
||||
highs = [float(c.high) for c in candles]
|
||||
lows = [float(c.low) for c in candles]
|
||||
|
||||
sma5 = sum(closes[-5:]) / 5
|
||||
atr = sum(max(highs[i]-lows[i], abs(highs[i]-closes[i-1]), abs(lows[i]-closes[i-1])) for i in range(-14, 0)) / 14
|
||||
support = min(lows[-5:])
|
||||
resistance = max(highs[-5:])
|
||||
```
|
||||
|
||||
#### Sell Order Workflow (做T卖出)
|
||||
|
||||
When user wants to place a sell order for an existing position:
|
||||
|
||||
1. **Query actual position first** — `trade_ctx.stock_positions()`, get `quantity`, `cost_price`, `available_quantity`. NEVER guess or use memory.
|
||||
2. **Fetch candlesticks** — 30-day daily for resistance levels, 5-min for intraday context.
|
||||
3. **Calculate technical levels** — SMA(5/10/20), support/resistance from high/low clusters, psychological round numbers ($20, $21, etc.).
|
||||
4. **Present options table** — conservative / recommended / aggressive, with projected P&L based on ACTUAL cost basis.
|
||||
5. **Ask urgency** — "这周要成交吗?" determines how aggressive the price should be. Patient = closer to resistance; urgent = closer to current price.
|
||||
6. **Place order** — Use `execute_code` + Python SDK, `submit_order` with `TimeInForceType.GoodTilCanceled` and `OutsideRTH.AnyTime`.
|
||||
7. **Report order ID** — Always return the order_id so user can track/cancel.
|
||||
|
||||
**Price selection heuristic** (not in a hurry):
|
||||
- Conservative: next psychological round number above current price
|
||||
- Recommended: SMA10 or recent consolidation zone midpoint
|
||||
- Aggressive: SMA20 or prior support-turned-resistance
|
||||
|
||||
For intraday margin trading with actionable entry/exit/position sizing, see `references/intraday-margin-trading.md`.\nFor token refresh automation, see `~/.hermes/scripts/update_longbridge_token.sh` — auto-updates all token locations and verifies.\nFor semi-automatic order placement with price monitoring, see `references/semi-auto-trading.md`.
|
||||
For VWAP + multi-indicator T-trading panel (scoring system, cron-based auto-orders), see `references/vwap-t-trading-panel.md`.
|
||||
For DCA position filtering by dividend yield threshold, see `references/dca-yield-filter.md`.
|
||||
|
||||
### Market Analysis Workflows
|
||||
|
||||
#### Watchlist Query (via Python SDK)
|
||||
The CLI does not support watchlist queries directly. Use `longbridge-python-sdk` skill instead, or use the `execute_code` pattern in `references/execute-code-pattern.py` which reliably loads LONGPORT_* env vars:
|
||||
```python
|
||||
from longport import openapi
|
||||
cfg = openapi.Config.from_env()
|
||||
ctx = openapi.QuoteContext(config=cfg)
|
||||
resp = ctx.watchlist() # Returns all groups with securities
|
||||
```
|
||||
|
||||
#### Dividend/Yield Analysis
|
||||
When looking for income-generating assets:
|
||||
1. **Identify Target**: Determine if the user wants monthly, quarterly, or annual payouts.
|
||||
2. **Filter by Stability**: Prioritize assets with high stability scores (e.g., Dividend Aristocrats/Kings).
|
||||
3. **Group and Sort**: Group by frequency (Monthly vs Quarterly) and sort by stability, then yield.
|
||||
4. **Contextualize**: Provide a clear table or list with enough context (Ticker, Name, Yield, Stability).
|
||||
|
||||
Key dividend stocks by frequency:
|
||||
- **Monthly**: O (Realty Income), MAIN (Main Street Capital)
|
||||
- **Quarterly**: KO (Coca-Cola), PG (Procter & Gamble), and most S&P 500 dividend payers
|
||||
|
||||
### Market Trend & Professional Analysis
|
||||
1. **Identify Asset Class**: Stocks or Crypto.
|
||||
2. **Select Toolset**:
|
||||
- Stocks: Use `stock-analysis` or `stock-analysis-agent` (Yahoo Finance data)
|
||||
- Crypto/professional trading: Use `longbridge` CLI (this skill) or `longbridge-python-sdk`
|
||||
3. **Execute Analysis**: Run the appropriate tool for real-time or historical data.
|
||||
4. **Synthesize**: Summarize into actionable insights.
|
||||
|
||||
### Pitfalls (Analysis-Specific)
|
||||
- **Yield vs. Growth**: High yield alone doesn't guarantee returns; always check stability/growth potential.
|
||||
- **Frequency Confusion**: Distinguish between monthly and quarterly payouts to match user cash-flow needs.
|
||||
- **Data Source Routing**: Stocks → `stock-analysis` (Yahoo Finance). Professional trading → `longbridge`.
|
||||
|
||||
## Pitfalls
|
||||
- **NEVER fabricate trading data (critical)**: When asked about positions, costs, prices, or orders, you MUST query the actual data from LongBridge API FIRST before doing any calculations. Do NOT guess, assume, or use stale data from memory/user profile. The user will catch fabricated numbers and lose trust. **Always**: `trade_ctx.stock_positions()` → get real `quantity`, `cost_price`, `available_quantity` → then calculate. This applies to cost basis calculations, P&L projections, and sell order sizing. One extra API call is infinitely better than a wrong number.
|
||||
- **Missing Symbols**: Most quote/candlestick commands require one or more symbols.
|
||||
- **JSON Output**: Always use the `--json` flag for machine-readable data.
|
||||
- **Environment Variables**: Ensure `.env` or shell exports are active before running commands.
|
||||
- **Command Syntax**: Note that `longbridge` uses a sub-command structure (e.g., `longbridge <command> [OPTIONS] <args>`).
|
||||
- **Token Expiration (401004)**: `LONGBRIDGE_ACCESS_TOKEN` is a **dynamic, time-sensitive token** stored in bashrc (or `.env`). It expires and causes `401004: token invalid` errors. Fix (preferred): run `bash ~/.hermes/scripts/update_longbridge_token.sh NEW_TOKEN` — it auto-updates all locations (bashrc, .env, hermes envs) and verifies both CLI and Python SDK. See `references/token-refresh.md` for full workflow. Never rely on a stale cached token.
|
||||
- **Freshly-generated token still gets 401004**: If a new token (just copied from App) gets 401004, first decode the JWT to verify `exp` is in the future and `ak` matches the configured APP_KEY (see `references/token-refresh.md` → "JWT Verification"). If the JWT is valid but API rejects it, either: (a) wait 30s and retry (propagation delay), (b) re-generate from App (first generation sometimes doesn't register), or (c) try from Web console at https://open.longportapp.com/ (different token type). Do NOT assume the token is wrong — the JWT structure is verifiable independently of the API.
|
||||
- **Command Name**: The npm-installed CLI is `longbridge` (not `lonbh`, `longport`, etc.). Verify with `npm list -g | grep longbridge`.
|
||||
- **Env Var Loading**: Variables in bashrc are not visible to child processes via `env | grep`. Always `source ~/.bashrc` in the same shell session before running commands.
|
||||
- **Validate Token Before Batch**: Before running multi-ticker queries (especially dividend/quote batch calls), run a single-ticker sanity check first: `longbridge quote --json AAPL`. A 401004 on a 15-ticker batch wastes time diagnosing which tickers are the problem vs. the token being expired.
|
||||
- **401004 Diagnostic Protocol**: When hitting 401004, first distinguish between these scenarios:
|
||||
- **Terminal output shows `...`** → That's the tool's secret masking. Verify with `python3 -c "open('/home/openclaw/.bashrc').read().split('LONGBRIDGE_ACCESS_TOKEN=')[1].split()[0]" | wc -c`. If length ~1053, the token is intact.
|
||||
- **Token was never saved** → All files have literal `...` placeholders. Ask user to re-generate.
|
||||
- **Token expired** → error 401003. Run `bash ~/.hermes/scripts/update_longbridge_token.sh NEW_TOKEN`.
|
||||
- **Fresh token gets 401004** → See pitfall "Freshly-generated token still gets 401004" above.
|
||||
Do NOT iterate through config files one by one — run the script which handles all locations in one call.
|
||||
- **CLI Installation Path**: The `longbridge` CLI is installed via `uv tool install` at `~/.local/bin/longbridge`. It is NOT in `$PATH` by default in all sessions. Use the full path `~/.local/bin/longbridge` or add `export PATH="$HOME/.local/bin:$PATH"` to bashrc. Verify with `which longbridge || ls ~/.local/bin/longbridge`.
|
||||
- **CLI Token Masking (Critical - Use Python SDK Instead)**: The terminal tool's secret-redaction layer masks/truncates environment variable values containing tokens. This causes the `longbridge` CLI to get corrupted tokens → 401004 (token invalid) or 403201 (signature invalid) errors. **The Python SDK always works** because `execute_code` scripts read bashrc via `open()` and set `os.environ` programmatically, bypassing the terminal layer. **Rule**: For any order/trade/position operation, always use `execute_code` + Python SDK, never `terminal` + CLI. Quote commands may work via CLI but orders will fail.
|
||||
- **"..." in terminal output ≠ placeholder (critical trap)**: The terminal tool **masks** secrets in both display AND environment variables. When you run `grep LONGBRIDGE_ACCESS_TOKEN ~/.bashrc`, the output shows `m_eyJh...jb-k` even when the actual file has a **complete 1053-char JWT**. This is the tool's secret-redaction layer, NOT file corruption. Never conclude a token is truncated from terminal grep output alone. To verify the file truly has a complete token:
|
||||
```bash
|
||||
python3 -c "
|
||||
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)}') # Should be ~1053
|
||||
"
|
||||
```
|
||||
**Trust the user** when they say "变量没有占位符" — they can see the file without masking.
|
||||
- **Signature Invalid (403201)**: Distinct from 401004 (token expired). Error `403201: signature invalid` means the `LONGBRIDGE_APP_SECRET` (or `LONGPORT_APP_SECRET`) value is wrong, corrupted, or truncated — NOT that the token expired. This commonly happens because of the terminal secret masking above. Fix: use Python SDK instead.
|
||||
- **HK stock symbols**: Use `.HK` suffix (e.g., `0823.HK`, `0778.HK`). The CLI accepts both `0823.HK` and `HK.0823` formats.
|
||||
- **Python SDK Env Var Prefix Mismatch**: The CLI uses `LONGBRIDGE_*` env vars, but the Python SDK (`longport`) reads `LONGPORT_*`. When using Python, you must **manually map** the bashrc vars: `os.environ["LONGPORT_APP_KEY"] = config.get("LONGBRIDGE_APP_KEY", "")` etc. See `references/python-sdk.md`.
|
||||
- **`buy/sell` requires `-y` flag**: Without `-y`, the CLI prompts for confirmation interactively and hangs in scripts/cron. Always `longbridge buy SYM --qty N --price P -y`.
|
||||
- **Read-only mode by default (LONGBRIDGE_TRADE_ENABLED)**: The CLI defaults to read-only mode. `buy`, `sell`, and `cancel` commands fail with `当前为只读模式,下单/撤单操作已禁用` unless `LONGBRIDGE_TRADE_ENABLED=true` is set. This env var must be exported in `~/.bashrc` alongside the other `LONGBRIDGE_*` vars. Without it, even valid tokens reject order commands. **Fix**: `echo 'export LONGBRIDGE_TRADE_ENABLED=true' >> ~/.bashrc` then `source ~/.bashrc`.
|
||||
- **Python SDK `submit_order` API quirks**: The enum is `openapi.TimeInForceType` (NOT `TimeInForce`). The function signature is `submit_order(symbol, order_type, side, submitted_quantity, time_in_force, submitted_price=None, ...)` — note `time_in_force` is a **required positional arg** before the optional `submitted_price`. Correct call:
|
||||
```python
|
||||
# Enums reference:
|
||||
# openapi.OutsideRTH: .AnyTime (pre+regular+post), .Overnight, .RTHOnly, .Unknown
|
||||
# openapi.TimeInForceType: .Day, .GoodTilCanceled, .GoodTilDate, .Unknown
|
||||
# openapi.OrderType: .LO (limit), .MO (market), .ELO (enhanced limit), .ALO, .AO, .SLO, etc.
|
||||
# openapi.OrderSide: .Buy, .Sell, .Unknown
|
||||
|
||||
resp = trade_ctx.submit_order(
|
||||
symbol="RGTI.US",
|
||||
order_type=openapi.OrderType.LO,
|
||||
side=openapi.OrderSide.Sell,
|
||||
submitted_quantity=15,
|
||||
time_in_force=openapi.TimeInForceType.GoodTilCanceled, # GTC = persists until filled/canceled
|
||||
submitted_price=21.00,
|
||||
outside_rth=openapi.OutsideRTH.AnyTime, # pre-market + regular + after-hours
|
||||
)
|
||||
```
|
||||
- **`SecurityQuote` attributes vary**: US quotes from `Nasdaq Basic` may lack `turnover_rate`, `amplitude` etc. that HK LV1 provides. Wrap attribute access in try/except or hasattr. **No `change_rate` attribute**: Calculate change manually: `(float(q.last_done) - float(q.prev_close)) / float(q.prev_close) * 100`. Available attributes: `symbol`, `last_done`, `prev_close`, `open`, `high`, `low`, `timestamp`.
|
||||
- **Period enum uses underscores**: `Period.Min_5` not `Period.Min5`. Full list: `Min_1`, `Min_2`, `Min_3`, `Min_5`, `Min_10`, `Min_15`, `Min_20`, `Min_30`, `Min_45`, `Min_60`, `Min_120`, `Min_180`, `Min_240`, `Day`, `Week`, `Month`, `Quarter`, `Year`.
|
||||
- **AccountBalance attributes**: Has `buy_power`, `total_cash`, `net_assets`, `max_finance_amount`, `remaining_finance_amount`, `risk_level`, `margin_call`. NO `available_cash` or `free` — use `buy_power` for available buying power. **Confirmed HK LV1 attributes** (2026-06-25): `high`, `last_done`, `low`, `open`, `overnight_quote`, `post_market_quote`, `pre_market_quote`, `prev_close`, `symbol`, `timestamp`. **NO `change_rate`** — compute manually: `(last_done - prev_close) / prev_close * 100`.
|
||||
- **`Period` enum format**: Use `Period.Min_5` (underscore), NOT `Period.Min5`. Full list: `Min_1`, `Min_2`, `Min_3`, `Min_5`, `Min_10`, `Min_15`, `Min_20`, `Min_30`, `Min_45`, `Min_60`, `Min_120`, `Min_180`, `Min_240`, `Day`, `Week`, `Month`, `Quarter`, `Year`.
|
||||
- **`SecurityQuote` attributes vary**: US quotes from `Nasdaq Basic` may lack `turnover_rate`, `amplitude` etc. that HK LV1 provides. Wrap attribute access in try/except or hasattr.
|
||||
- **Position fields**: `available_quantity` (settled, sellable) vs `quantity` (total incl unsettled). For T-trading sell, check `available_quantity` first.
|
||||
- **Prefer `execute_code` over `terminal` for Python SDK**: The `execute_code` sandbox can access `LONGPORT_*` vars from the host environment, making `Config.from_env()` work reliably. In contrast, `terminal` + `source ~/.bashrc` frequently fails because env vars get masked/truncated by the terminal tool's secret-redaction layer, producing 403201 or 401004 errors. **Workflow**: for single-call quick data, use `execute_code` with inline Python + `Config.from_env()`. For CLI commands, use `terminal` with `source ~/.bashrc && longbridge ...`.
|
||||
- **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 `longbridge-python-sdk` skill's `references/wireguard-proxy-setup.md`.
|
||||
- **Period enum names**: LongPort Python SDK uses `Period.Min_5` (not `Period.Min5`), `Period.Min_10`, `Period.Min_15`, etc. Always use underscore format.
|
||||
- **ONLY CLOSE YOUR OWN POSITIONS (critical)**: Automated trading systems MUST only close positions that were opened by the same system. Track opened positions in a JSON file (e.g., `entries.json`) with `order_id`, `shares`, `entry_price`. On close, verify `order_id` exists before executing. Never close user's manual positions. User explicitly stated: "只有你开仓的的你才能平,不是你开的你不能操作".
|
||||
@@ -0,0 +1,62 @@
|
||||
# DCA Yield Filter Pattern
|
||||
|
||||
When user wants to filter DCA positions by minimum dividend yield, add this block to the monitor script **after** loading positions but **before** fetching quotes.
|
||||
|
||||
## Code Pattern
|
||||
|
||||
```python
|
||||
positions = config['positions']
|
||||
|
||||
# === Yield filter: skip positions below threshold ===
|
||||
MIN_YIELD = 7.0 # user-configurable
|
||||
filtered_out = []
|
||||
for sym in list(positions.keys()):
|
||||
if positions[sym].get('yield', 0) < MIN_YIELD:
|
||||
filtered_out.append(f"{sym}({positions[sym]['name']} {positions[sym]['yield']}%)")
|
||||
del positions[sym]
|
||||
```
|
||||
|
||||
## Budget Reallocation
|
||||
|
||||
When filtering removes positions, redistribute budget evenly among remaining:
|
||||
|
||||
```python
|
||||
n = len(positions)
|
||||
per_stock_hkd = round(7500 / n) # monthly budget / remaining count
|
||||
usd_hkd = config['budget']['usd_hkd']
|
||||
|
||||
for sym, pos in positions.items():
|
||||
pos['monthly_budget_hkd'] = per_stock_hkd
|
||||
if pos['market'] == 'US':
|
||||
pos['monthly_budget_local'] = round(per_stock_hkd / usd_hkd, 2)
|
||||
else:
|
||||
pos['monthly_budget_local'] = per_stock_hkd
|
||||
```
|
||||
|
||||
## Config File Structure (dca_positions.json)
|
||||
|
||||
Each position has a `yield` field used for filtering:
|
||||
|
||||
```json
|
||||
{
|
||||
"positions": {
|
||||
"NLY.US": {
|
||||
"name": "Annaly Capital",
|
||||
"yield": 13.2,
|
||||
"market": "US",
|
||||
"ladder": [...],
|
||||
"monthly_budget_hkd": 2500,
|
||||
"monthly_budget_local": 320.51
|
||||
}
|
||||
},
|
||||
"alert_settings": { "trigger_pct": 2.0 },
|
||||
"budget": { "monthly_mid_hkd": 7500, "usd_hkd": 7.8 }
|
||||
}
|
||||
```
|
||||
|
||||
## Key Points
|
||||
|
||||
- Filter runs in-memory at script start; the JSON file retains all positions (including filtered ones) for future reference
|
||||
- User can change `MIN_YIELD` threshold without editing the JSON
|
||||
- When adding new positions to the JSON, the filter automatically enforces the threshold
|
||||
- `filtered_out` list can be logged for transparency
|
||||
@@ -0,0 +1,44 @@
|
||||
# LongPort Python SDK via execute_code — reliable pattern
|
||||
# Use this instead of terminal + source ~/.bashrc for Python SDK calls.
|
||||
# The execute_code sandbox inherits LONGPORT_* env vars, so Config.from_env() works.
|
||||
#
|
||||
# Pitfalls:
|
||||
# - execute_code sandbox does NOT inherit bashrc; LONGPORT_* must already be in
|
||||
# the host env (they are, from ~/.bashrc on this system).
|
||||
# - If Config.from_env() throws "missing environment variable", the sandbox
|
||||
# couldn't find the var. Fall back to reading from bashrc via subprocess.
|
||||
# - Decimal fields (market_cap, last_done, etc.) need float() conversion.
|
||||
# - candlesticks() returns list sorted oldest-first; [-1] is latest.
|
||||
# - adjust_type is required for candlesticks: use openapi.AdjustType.NoAdjust
|
||||
# for raw data or openapi.AdjustType.ForwardAdjust for adjusted.
|
||||
|
||||
import os
|
||||
|
||||
# Safety net: if LONGPORT_* not in sandbox env, load from bashrc
|
||||
if not os.environ.get("LONGPORT_APP_KEY"):
|
||||
import subprocess
|
||||
result = subprocess.run(
|
||||
["bash", "-c", "source ~/.bashrc && env"],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
for line in result.stdout.split("\n"):
|
||||
if "=" in line and "LONGPORT_" in line:
|
||||
key, val = line.split("=", 1)
|
||||
os.environ[key] = val
|
||||
|
||||
from longport import openapi
|
||||
|
||||
config = openapi.Config.from_env()
|
||||
ctx = openapi.QuoteContext(config=config)
|
||||
|
||||
# --- Quote ---
|
||||
resp = ctx.quote(["AAPL.US"])
|
||||
q = resp[0]
|
||||
print(f"Latest: {float(q.last_done)}, Open: {float(q.open)}, High: {float(q.high)}, Low: {float(q.low)}")
|
||||
|
||||
# --- Candlesticks (daily, 30 bars) ---
|
||||
candles = ctx.candlesticks("AAPL.US", openapi.Period.Day, 30, openapi.AdjustType.NoAdjust)
|
||||
first_close = float(candles[0].close)
|
||||
last_close = float(candles[-1].close)
|
||||
change_pct = (last_close - first_close) / first_close * 100
|
||||
print(f"30d change: {first_close} -> {last_close} ({change_pct:+.2f}%)")
|
||||
@@ -0,0 +1,106 @@
|
||||
# Intraday Margin Trading Automation
|
||||
|
||||
Complete automated system for HK/US intraday margin trading with LongPort SDK.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
8:30 Beijing → hk_intraday_scanner.py → TOP3 candidates → QQ
|
||||
9:30 Beijing → hk_intraday_monitor.py → entry signals → auto order → QQ
|
||||
15:45 Beijing → hk_intraday_close.py → close all system positions → QQ
|
||||
|
||||
21:00 Beijing → us_intraday_scanner.py → TOP3 candidates → QQ
|
||||
21:30 Beijing → us_intraday_monitor.py → entry signals → auto order → QQ
|
||||
3:45 Beijing → us_intraday_close.py → close all system positions → QQ
|
||||
```
|
||||
|
||||
## Scoring Formula
|
||||
|
||||
```
|
||||
score = min(ADR% / 4, 1) × 40 + min(VolumeRatio / 2, 1) × 30 + min(TurnoverRate / 2, 1) × 30
|
||||
```
|
||||
|
||||
- ADR%: Average Daily Range (近5日高低价差百分比)
|
||||
- VolumeRatio: LongPort CalcIndex.VolumeRatio
|
||||
- TurnoverRate: LongPort CalcIndex.TurnoverRate
|
||||
|
||||
Score > 60 = excellent, 40-60 = good, < 40 = not ideal
|
||||
|
||||
## Entry Signals (5-min SMA)
|
||||
|
||||
**做多条件:**
|
||||
- current > SMA5 > SMA10
|
||||
- current > previous close (上涨趋势)
|
||||
|
||||
**做空条件:**
|
||||
- current < SMA5 < SMA10
|
||||
- current < previous close (下跌趋势)
|
||||
|
||||
## Position Sizing
|
||||
|
||||
```python
|
||||
buying_power = account.buy_power # HKD or USD
|
||||
position_size = buying_power * 0.25 # 25% per trade
|
||||
shares = int(position_size / current_price / 100) * 100 # HK: round to 100
|
||||
shares = int(position_size / current_price) # US: round to 1
|
||||
```
|
||||
|
||||
## Stop Loss / Take Profit
|
||||
|
||||
```python
|
||||
atr = sum(max(h-l, abs(h-pc), abs(l-pc)) for ...) / n # 5-min ATR
|
||||
|
||||
# 做多
|
||||
stop_loss = max(min(lows[-5:]), entry - atr * 2)
|
||||
take_profit = entry + atr * 3
|
||||
|
||||
# 做空
|
||||
stop_loss = min(max(highs[-5:]), entry + atr * 2)
|
||||
take_profit = entry - atr * 3
|
||||
```
|
||||
|
||||
盈亏比 = 3:2 = 1.5:1
|
||||
|
||||
## Position Tracking (CRITICAL)
|
||||
|
||||
Entries tracked in `~/.hermes/trading/{hk,us}_intraday_entries.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"3690.HK": {
|
||||
"side": "buy",
|
||||
"entry_price": 66.10,
|
||||
"stop_loss": 65.85,
|
||||
"take_profit": 66.77,
|
||||
"shares": 100,
|
||||
"order_id": "3686893095794171904",
|
||||
"time": "2026-06-25T09:45:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Safety Rules
|
||||
|
||||
1. **ONLY CLOSE SYSTEM-OPENED POSITIONS** — verify `order_id` exists before closing
|
||||
2. **NEVER touch user's manual positions** (UNH, RGTI, 3416.HK, etc.)
|
||||
3. **Day trade only** — close all at 15:45 HK / 3:45 US Beijing
|
||||
4. **Single trade max** — 25% of buying power
|
||||
5. **Stop loss mandatory** — 2× ATR from entry
|
||||
|
||||
## Cron Jobs
|
||||
|
||||
| Job | Schedule (EDT) | Schedule (Beijing) | Script |
|
||||
|-----|----------------|-------------------|--------|
|
||||
| HK Scanner | `30 20 * * 1-5` | 8:30 | hk_intraday_scanner.py |
|
||||
| HK Monitor | `*/15 9-15 * * 1-5` | 21:15-3:45 | hk_intraday_monitor.py |
|
||||
| HK Close | `45 15 * * 1-5` | 3:45 | hk_intraday_close.py |
|
||||
| US Scanner | `0 9 * * 1-5` | 21:00 | us_intraday_scanner.py |
|
||||
| US Monitor | `*/15 21-23,0-3 * * 1-5` | 9:00-15:45 | us_intraday_monitor.py |
|
||||
| US Close | `45 3 * * 2-6` | 3:45 | us_intraday_close.py |
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **Period enum**: Use `Period.Min_5` not `Period.Min5` (underscore required)
|
||||
- **buy_power**: `account.buy_power` not `account.available_cash`
|
||||
- **SecurityQuote**: Use `q.last_done`, `q.prev_close`, `q.high`, `q.low`, `q.open` — no `change_rate` attribute
|
||||
- **Entry file path**: `~/.hermes/trading/` not `~/.hermes/skills/...`
|
||||
@@ -0,0 +1,117 @@
|
||||
# LongPort MCP Integration
|
||||
|
||||
LongPort offers an MCP (Model Context Protocol) server as an alternative to the longbridge CLI and Python SDK. This is useful for AI agents that need native MCP tool discovery rather than custom CLI/SDK integration.
|
||||
|
||||
## Architecture
|
||||
|
||||
LongPort's MCP service uses a **two-endpoint architecture**:
|
||||
|
||||
| Endpoint | URL | Purpose |
|
||||
|----------|-----|---------|
|
||||
| Auth endpoint | `https://mcp.longport.cn/agent` | Single tool: `authenticate` — exchanges an auth code for an access token |
|
||||
| Main service | `https://mcp.longport.cn` | All LongPort data tools (quotes, orders, positions, etc.) — requires Bearer token |
|
||||
|
||||
The auth endpoint exists only for credential exchange and is NOT a permanent MCP service; disconnect it after obtaining the token.
|
||||
|
||||
## Auth Flow (Two Steps)
|
||||
|
||||
### Step 1: Get Access Token
|
||||
|
||||
Connect to `https://mcp.longport.cn/agent` and call the `authenticate` tool:
|
||||
|
||||
```
|
||||
POST https://mcp.longport.cn/agent
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "authenticate",
|
||||
"arguments": {
|
||||
"code": "<one-time auth code from LongPort App>"
|
||||
}
|
||||
},
|
||||
"id": 1
|
||||
}
|
||||
```
|
||||
|
||||
Response includes the access token and the exact config command for the main service (e.g., headers with `Authorization: Bearer <token>`).
|
||||
|
||||
**Auth codes**: Single-use, valid for ~10 minutes. Generate from LongPort App → Open API → MCP.
|
||||
|
||||
### Step 2: Connect Main Service
|
||||
|
||||
Once you have the token, connect to the main MCP service with the Bearer token as a request header:
|
||||
|
||||
```yaml
|
||||
# In Hermes config.yaml under mcp_servers:
|
||||
mcp_servers:
|
||||
longport:
|
||||
url: "https://mcp.longport.cn"
|
||||
headers:
|
||||
Authorization: "Bearer <your_access_token>"
|
||||
timeout: 180
|
||||
connect_timeout: 60
|
||||
```
|
||||
|
||||
After adding the config, restart Hermes Agent. All LongPort MCP tools will auto-discover and become available as `mcp_longport_*` tools.
|
||||
|
||||
The temp auth endpoint (`/agent`) connection can be removed after token exchange — it's not needed for regular use.
|
||||
|
||||
## Tool Availability
|
||||
|
||||
Once connected to the main service, available tools include:
|
||||
- **Quote tools**: real-time quotes, candlesticks, calc_indexes
|
||||
- **Account tools**: positions, balance, orders
|
||||
- **Trade tools**: buy, sell, cancel orders
|
||||
- **Watchlist tools**: list groups, securities
|
||||
- **Static info**: EPS, BPS, shares outstanding
|
||||
|
||||
(The tool names auto-prefix as `mcp_longport_<tool_name>` in Hermes.)
|
||||
|
||||
## Token Management
|
||||
|
||||
- **Expiry**: LongPort access tokens expire after ~180 days (same as API tokens).
|
||||
- **Refresh**: Generate a new auth code from the LongPort App and repeat the two-step flow.
|
||||
- **401003**: Token expired — re-authenticate from scratch.
|
||||
- **401004**: Token invalid/truncated — verify the token JWT structure is intact.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### DNS / Connectivity
|
||||
|
||||
The LongPort MCP servers live at `mcp.longport.cn`. If this domain doesn't resolve:
|
||||
|
||||
```bash
|
||||
dig mcp.longport.cn
|
||||
curl -s -o /dev/null -w "%{http_code}" https://mcp.longport.cn
|
||||
```
|
||||
|
||||
- **China mainland access**: DNS may be blocked or restricted. Try a VPN/WireGuard exit to Hong Kong or overseas. Use the WireGuard on/off scripts (`wg-on`, `wg-off`) if available.
|
||||
- **Timeout**: Increase `connect_timeout` to 60–120s. LongPort MCP can be slow to respond on first connection.
|
||||
- **ERR_NAME_NOT_RESOLVED**: Domain unreachable from current network. Try alternate DNS (8.8.8.8) or VPN.
|
||||
|
||||
### Auth Code Expiry
|
||||
|
||||
Auth codes are single-use and expire ~10 minutes after generation. If you get an error calling `authenticate`:
|
||||
- Generate a fresh code from the LongPort App
|
||||
- Retry step 1 immediately
|
||||
|
||||
### MCP SDK Requirement
|
||||
|
||||
Hermes' native MCP client requires the `mcp` Python package:
|
||||
|
||||
```bash
|
||||
pip install mcp
|
||||
# or
|
||||
uv pip install mcp
|
||||
```
|
||||
|
||||
Without it, MCP support is silently disabled and no MCP servers connect.
|
||||
|
||||
## See Also
|
||||
|
||||
- `native-mcp` skill: general MCP client configuration for Hermes
|
||||
- `longbridge-cli` skill: CLI-based LongPort access (fallback if MCP is unavailable)
|
||||
- `longbridge-python-sdk` skill: Python SDK-based LongPort access
|
||||
@@ -0,0 +1,113 @@
|
||||
# Semi-Automatic T-Trading Setup
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ Cron (every 10 min, market hours only) │
|
||||
│ ┌─────────────────────────────────────┐ │
|
||||
│ │ rgti_auto_monitor.py │ │
|
||||
│ │ 1. Get quote (Python SDK) │ │
|
||||
│ │ 2. Check position availability │ │
|
||||
│ │ 3. Check pending orders │ │
|
||||
│ │ 4. If price in zone + no orders: │ │
|
||||
│ │ → Auto place limit order │ │
|
||||
│ │ 5. If price moved away: │ │
|
||||
│ │ → Auto cancel stale order │ │
|
||||
│ │ 6. Print message → WeChat delivery │ │
|
||||
│ └─────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Required SDK Calls
|
||||
|
||||
```python
|
||||
import os
|
||||
from longport import openapi
|
||||
|
||||
# Load env
|
||||
bashrc = open(os.path.expanduser("~/.bashrc")).read()
|
||||
for line in bashrc.splitlines():
|
||||
if line.startswith("export LONGPORT_") or line.startswith("export LONGBRIDGE_"):
|
||||
parts = line.replace("export ", "").split("=", 1)
|
||||
if len(parts) == 2:
|
||||
os.environ[parts[0]] = parts[1].strip('"').strip("'")
|
||||
|
||||
os.environ["LONGBRIDGE_TRADE_ENABLED"] = "true"
|
||||
|
||||
cfg = openapi.Config.from_env()
|
||||
trade_ctx = openapi.TradeContext(config=cfg)
|
||||
quote_ctx = openapi.QuoteContext(config=cfg)
|
||||
|
||||
# Quote
|
||||
resp = quote_ctx.quote(["SYMBOL.US"])
|
||||
price = float(resp[0].last_done)
|
||||
|
||||
# Position (check available_quantity for sellable qty)
|
||||
pos = trade_ctx.stock_positions()
|
||||
for ch in pos.channels:
|
||||
for p in ch.positions:
|
||||
avail = int(p.available_quantity)
|
||||
total = int(p.quantity)
|
||||
|
||||
# Pending orders
|
||||
orders = trade_ctx.today_orders()
|
||||
for o in orders:
|
||||
status = str(o.status) # "NotReported", "PendingStatus", etc.
|
||||
|
||||
# Place order (GTC + outside RTH = works pre/regular/post market)
|
||||
resp = trade_ctx.submit_order(
|
||||
symbol="RGTI.US",
|
||||
order_type=openapi.OrderType.LO,
|
||||
side=openapi.OrderSide.Sell,
|
||||
submitted_quantity=15,
|
||||
time_in_force=openapi.TimeInForceType.GoodTilCanceled,
|
||||
submitted_price=21.00,
|
||||
outside_rth=openapi.OutsideRTH.AnyTime,
|
||||
)
|
||||
|
||||
# Cancel
|
||||
trade_ctx.cancel_order(order_id)
|
||||
```
|
||||
|
||||
## State File Pattern
|
||||
|
||||
Track active orders and cooldowns to prevent spam:
|
||||
|
||||
```python
|
||||
STATE_FILE = "~/.hermes/scripts/rgti_t_state.json"
|
||||
|
||||
def load_state():
|
||||
try:
|
||||
return json.load(open(STATE_FILE))
|
||||
except:
|
||||
return {"active_orders": [], "last_action_time": None, "trades_today": 0}
|
||||
|
||||
# Cooldown: 5 min between actions
|
||||
last_t = state.get("last_action_time")
|
||||
if last_t:
|
||||
diff = (now - datetime.fromisoformat(last_t)).total_seconds()
|
||||
if diff < 300:
|
||||
sys.exit(0) # silent exit
|
||||
```
|
||||
|
||||
## Cron Job Setup
|
||||
|
||||
```python
|
||||
# Via Hermes cronjob tool:
|
||||
cronjob(action="create",
|
||||
name="RGTI半自动做T挂单",
|
||||
no_agent=True, # Script-only, no LLM
|
||||
schedule="*/10 9-15 * * 1-5", # Every 10 min, 9-15 ET, Mon-Fri
|
||||
deliver="weixin",
|
||||
script="rgti_auto_monitor.py") # Relative to ~/.hermes/scripts/
|
||||
```
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
1. **No agent (no_agent=True)**: Script runs directly, prints output → delivered as message. No LLM tokens wasted.
|
||||
2. **Empty stdout = silent**: If nothing to report, print nothing → no message sent.
|
||||
3. **GTC + AnyTime**: Orders persist across days and work in pre/post market.
|
||||
4. **5-min cooldown**: Prevents rapid-fire order spam on volatile stocks.
|
||||
5. **Auto-cancel stale orders**: If price moves >$1.50 from order price, cancel and re-evaluate.
|
||||
6. **State file for order tracking**: Prevents duplicate orders and tracks today's trade count.
|
||||
@@ -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)
|
||||
```
|
||||
@@ -0,0 +1,128 @@
|
||||
# VWAP + Multi-Indicator T-Trading Panel
|
||||
|
||||
做T (T-trading) = buying/selling around an existing position to lower cost basis via intraday swings.
|
||||
Best for high-volatility stocks with 10%+ daily ranges (e.g. quantum stocks, biotech, meme stocks).
|
||||
|
||||
## Indicator Stack for T-Trading
|
||||
|
||||
| Indicator | What it tells you | T-trading signal |
|
||||
|-----------|-------------------|------------------|
|
||||
| **VWAP** | Intraday volume-weighted avg price (the "fair value" today) | Price > VWAP = sell zone; < VWAP = buy zone |
|
||||
| **RSI(14)** | Overbought/oversold momentum | >70 = overbought (sell); <30 = oversold (buy) |
|
||||
| **Bollinger(20,2)** | Volatility channel | Touch upper band = sell; touch lower band = buy |
|
||||
| **ATR(14)** | Average True Range — how much it swings per period | Higher ATR = better for T-trading |
|
||||
| **Volume ratio** | Current vol vs average | >1.5x = confirming move; <0.5x = weak/noisy |
|
||||
|
||||
## VWAP Calculation (from 30-min candles)
|
||||
|
||||
```python
|
||||
def calc_vwap(candles):
|
||||
"""Volume-Weighted Average Price"""
|
||||
cum_pv, cum_vol = 0, 0
|
||||
for c in candles:
|
||||
typical = (float(c.high) + float(c.low) + float(c.close)) / 3
|
||||
vol = float(c.volume)
|
||||
cum_pv += typical * vol
|
||||
cum_vol += vol
|
||||
return cum_pv / cum_vol if cum_vol else 0
|
||||
```
|
||||
|
||||
⚠️ VWAP resets each trading day. Use intraday candles (5min, 30min), NOT daily candles.
|
||||
|
||||
## RSI Calculation
|
||||
|
||||
```python
|
||||
def calc_rsi(candles, period=14):
|
||||
closes = [float(c.close) for c in candles]
|
||||
if len(closes) < period + 1:
|
||||
return None
|
||||
gains, losses = [], []
|
||||
for i in range(1, len(closes)):
|
||||
diff = closes[i] - closes[i-1]
|
||||
gains.append(max(diff, 0))
|
||||
losses.append(max(-diff, 0))
|
||||
avg_gain = sum(gains[-period:]) / period
|
||||
avg_loss = sum(losses[-period:]) / period
|
||||
if avg_loss == 0:
|
||||
return 100
|
||||
rs = avg_gain / avg_loss
|
||||
return 100 - (100 / (1 + rs))
|
||||
```
|
||||
|
||||
## Bollinger Bands
|
||||
|
||||
```python
|
||||
def calc_bollinger(candles, period=20, std_mult=2):
|
||||
closes = [float(c.close) for c in candles]
|
||||
data = closes[-period:]
|
||||
mid = sum(data) / period
|
||||
std = (sum((x - mid)**2 for x in data) / period) ** 0.5
|
||||
return mid + std_mult * std, mid, mid - std_mult * std # upper, mid, lower
|
||||
```
|
||||
|
||||
## Composite Scoring System
|
||||
|
||||
Combine all indicators into a single score for clear buy/sell signals:
|
||||
|
||||
```python
|
||||
score = 0 # Range: -100 (strong buy) to +100 (strong sell)
|
||||
|
||||
# VWAP
|
||||
if price > vwap: score += 20 # above VWAP = sell bias
|
||||
else: score -= 20 # below VWAP = buy bias
|
||||
|
||||
# RSI (30-min timeframe preferred for T-trading)
|
||||
if rsi_30m > 70: score += 25 # overbought
|
||||
elif rsi_30m < 30: score -= 25 # oversold
|
||||
|
||||
# Bollinger position
|
||||
boll_pct = (price - boll_low) / (boll_up - boll_low)
|
||||
if boll_pct > 0.8: score += 20 # near upper band
|
||||
elif boll_pct < 0.2: score -= 20 # near lower band
|
||||
|
||||
# Volume confirmation
|
||||
if vol_ratio > 1.5: score += 10 # volume confirms move
|
||||
|
||||
# Decision
|
||||
if score > 30: action = "SELL (reverse T)"
|
||||
elif score < -30: action = "BUY (forward T)"
|
||||
else: action = "WAIT"
|
||||
```
|
||||
|
||||
## T-Trading Execution Modes
|
||||
|
||||
### Manual (Alerts Only)
|
||||
- Cron monitors price every 10-15 min during market hours
|
||||
- Notifies user when price hits key levels
|
||||
- User manually places order
|
||||
|
||||
### Semi-Automatic (Recommended for retail)
|
||||
- Cron monitors price + calculates indicator score
|
||||
- Auto-submits limit orders when score hits threshold
|
||||
- Notifies user of every order placed
|
||||
- Auto-cancels stale orders when price moves away
|
||||
|
||||
### Script Architecture
|
||||
```
|
||||
~/.hermes/scripts/
|
||||
├── rgti_t_panel.py # Manual: run on-demand for indicator dashboard
|
||||
├── rgti_alert.py # Alerts only: cron job, silent when no signal
|
||||
└── rgti_auto_monitor.py # Semi-auto: cron + auto-place orders + notify
|
||||
```
|
||||
|
||||
## Cron Setup (US Market Hours)
|
||||
```
|
||||
# Every 10 min during 9:00-15:59 ET (Mon-Fri)
|
||||
*/10 9-15 * * 1-5
|
||||
|
||||
# Every 15 min (less aggressive)
|
||||
*/15 9-15 * * 1-5
|
||||
```
|
||||
|
||||
## Key Pitfalls
|
||||
- **VWAP needs intraday candles**: Daily VWAP is meaningless. Use 5min or 30min candles.
|
||||
- **RSI on 5min is noisy**: Use 30min RSI for T-trading decisions, 5min only for entry timing.
|
||||
- **Don't T-trade low-volume stocks**: Need volume >1M daily for reliable fills.
|
||||
- **GTC + OutsideRTH for auto-orders**: Use `GoodTilCanceled` + `OutsideRTH.AnyTime` so orders work pre-market, regular hours, and after-hours.
|
||||
- **Position availability**: `available_quantity` (settled, sellable) ≠ `quantity` (total incl unsettled). Check before selling.
|
||||
- **5-min cooldown between orders**: Prevent rapid-fire order spam; state file tracks last action time.
|
||||
Reference in New Issue
Block a user