feat(longbridge): 602315 mainland CN geo-block bypass + stock_t通用脚本
- SKILL.md: 加 602315 bypass 章节(三件套 LONGBRIDGE_REGION + proxychains + Clash HK)
- longbridge-python-sdk/SKILL.md: Python SDK 路径同样需要 bypass
- references/longbridge-602315-bypass.md: 完整方案+验证步骤
- references/longbridge-cn-vs-com-endpoint.md: cn vs com 域名区别
- references/clash-node-switching.md: Clash 切香港节点操作
- references/stock-t-trading-workflow.md: 通用持仓脚本用法
- intraday-trading/SKILL.md: 同步 602315 限制说明
- scripts/{daily_t_analysis,t_monitor}.py: 之前漏提交,补上
验证: 2026-07-09 下单 RGTI 15股@15.50 订单ID 1259547163696824320 成功
背景: longport SDK 通过 is_cn() 自动探测 geotest.lbkrs.com 选 cn/com endpoint
net_mode下 cn 域(阿里云深圳)被拒,com 域(AWS香港)需绕
唯一可行: LONGBRIDGE_REGION=ap 强制走 com + proxychains + Clash 香港出口
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
每日持仓做T分析 - 交易日早盘前推送
|
||||
分析持仓股票的技术面,给出做T建议+性价比(含真实手续费)
|
||||
用法: python3 daily_t_analysis.py
|
||||
输出: 持仓分析报告(含支撑/阻力/ATR/做T方案/性价比评级)
|
||||
"""
|
||||
import os, sys, json, math
|
||||
from datetime import datetime
|
||||
|
||||
# Load LongPort creds from bashrc
|
||||
with open(os.path.expanduser('~/.bashrc')) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
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]
|
||||
|
||||
from longport import openapi
|
||||
|
||||
def F(val, dec=2):
|
||||
return f'{val:.{dec}f}'
|
||||
|
||||
def calc_hk_fee(amount):
|
||||
"""港股手续费:佣金0.03%(min3) + 印花税0.1%(整数) + 征费0.00278% + 交收费0.002%(min2,max100)"""
|
||||
commission = max(3, amount * 0.0003)
|
||||
stamp = math.ceil(amount * 0.001)
|
||||
levy = amount * 0.0000278
|
||||
trading_fee = amount * 0.0000565
|
||||
settle = max(2, min(100, amount * 0.00002))
|
||||
return commission + stamp + levy + trading_fee + settle
|
||||
|
||||
def calc_us_fee(amount, qty):
|
||||
"""美股手续费:佣金$0 + SEC费0.00278%(卖) + FINRA $0.000166/股(卖)"""
|
||||
sec_fee = amount * 0.0000278
|
||||
finra = max(0.01, qty * 0.000166)
|
||||
return sec_fee + finra
|
||||
|
||||
def analyze():
|
||||
cfg = openapi.Config.from_env()
|
||||
trade_ctx = openapi.TradeContext(config=cfg)
|
||||
quote_ctx = openapi.QuoteContext(config=cfg)
|
||||
|
||||
positions = []
|
||||
symbols_list = []
|
||||
resp = trade_ctx.stock_positions()
|
||||
for ch in resp.channels:
|
||||
for pos in ch.positions:
|
||||
if int(pos.quantity) > 0:
|
||||
positions.append({
|
||||
'symbol': pos.symbol,
|
||||
'qty': int(pos.quantity),
|
||||
'avail': int(pos.available_quantity),
|
||||
'cost': float(pos.cost_price),
|
||||
})
|
||||
symbols_list.append(pos.symbol)
|
||||
|
||||
if not positions:
|
||||
return "📊 无持仓,无需做T分析"
|
||||
|
||||
# Get lot sizes
|
||||
lot_sizes = {}
|
||||
try:
|
||||
infos = quote_ctx.static_info(symbols_list)
|
||||
for info in infos:
|
||||
lot_sizes[info.symbol] = info.lot_size
|
||||
except:
|
||||
for s in symbols_list:
|
||||
lot_sizes[s] = 1
|
||||
|
||||
lines = [f"📊 每日做T分析 | {datetime.now().strftime('%Y-%m-%d')}\n"]
|
||||
|
||||
for p in positions:
|
||||
sym = p['symbol']
|
||||
lot_size = lot_sizes.get(sym, 1)
|
||||
try:
|
||||
candles = quote_ctx.candlesticks(sym, 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
|
||||
sma10 = sum(closes[-10:]) / 10
|
||||
sma20 = sum(closes) / len(closes)
|
||||
current = closes[-1]
|
||||
|
||||
atr_sum = 0
|
||||
for i in range(1, min(15, len(candles))):
|
||||
tr = max(highs[-i]-lows[-i], abs(highs[-i]-closes[-i-1]), abs(lows[-i]-closes[-i-1]))
|
||||
atr_sum += tr
|
||||
atr = atr_sum / min(14, len(candles)-1)
|
||||
|
||||
support = min(lows[-5:])
|
||||
resistance = max(highs[-5:])
|
||||
|
||||
cost = p['cost']
|
||||
qty = p['qty']
|
||||
avail = p['avail']
|
||||
pnl_pct = (current - cost) / cost * 100
|
||||
pnl_emoji = '🟢' if pnl_pct >= 0 else '🔴'
|
||||
|
||||
if current > sma5 > sma10 > sma20:
|
||||
trend = "📈多头"
|
||||
elif current < sma5 < sma10 < sma20:
|
||||
trend = "📉空头"
|
||||
elif current > sma10:
|
||||
trend = "↗️偏多"
|
||||
else:
|
||||
trend = "↘️偏弱"
|
||||
|
||||
atr_pct = atr / current * 100
|
||||
is_worth = atr_pct > 1.5
|
||||
|
||||
is_hk = '.HK' in sym
|
||||
ccy = 'HKD' if is_hk else 'USD'
|
||||
d = 3 if is_hk else 2
|
||||
|
||||
buy_zone = min(support, sma20) + atr * 0.2
|
||||
sell_zone = max(resistance, sma10) - atr * 0.2
|
||||
t_profit_per_share = sell_zone - buy_zone
|
||||
|
||||
raw_t_qty = max(1, int(avail * 0.2))
|
||||
t_qty = max(lot_size, (raw_t_qty // lot_size) * lot_size)
|
||||
if t_qty > avail:
|
||||
t_qty = (avail // lot_size) * lot_size
|
||||
|
||||
capital_used = buy_zone * t_qty
|
||||
expected_profit = t_profit_per_share * t_qty
|
||||
return_rate = (expected_profit / capital_used * 100) if capital_used > 0 else 0
|
||||
|
||||
stop_loss = current - atr * 1.5
|
||||
risk_per_share = buy_zone - stop_loss
|
||||
risk_total = risk_per_share * t_qty
|
||||
rr = (expected_profit / risk_total) if risk_total > 0 else 0
|
||||
|
||||
if is_hk:
|
||||
buy_fee = calc_hk_fee(buy_zone * t_qty)
|
||||
sell_fee = calc_hk_fee(sell_zone * t_qty)
|
||||
else:
|
||||
buy_fee = calc_us_fee(buy_zone * t_qty, t_qty)
|
||||
sell_fee = calc_us_fee(sell_zone * t_qty, t_qty)
|
||||
fee = buy_fee + sell_fee
|
||||
net_profit = expected_profit - fee
|
||||
|
||||
if rr >= 3 and return_rate >= 1.5:
|
||||
rating = "⭐⭐⭐ 高"
|
||||
elif rr >= 2 and return_rate >= 1:
|
||||
rating = "⭐⭐ 中"
|
||||
elif rr >= 1.5 and return_rate >= 0.5:
|
||||
rating = "⭐ 低"
|
||||
else:
|
||||
rating = "❌ 不建议"
|
||||
|
||||
lines.append(f"{'━' * 30}")
|
||||
lines.append(f"📌 {sym} | {qty}股({qty//lot_size}手) | 成本{F(cost, d)}{ccy}")
|
||||
lines.append(f"现价{F(current, d)} | {pnl_emoji}{pnl_pct:+.1f}% | {trend} | ATR{F(atr, d)}({atr_pct:.1f}%)")
|
||||
lines.append(f"支撑{F(support, d)} | 阻力{F(resistance, d)}")
|
||||
|
||||
if is_worth and t_qty >= lot_size:
|
||||
lines.append(f"🎯 低吸{F(buy_zone, d)} → 高抛{F(sell_zone, d)} | {t_qty}股({t_qty//lot_size}手)")
|
||||
lines.append(f"📐 性价比: {rating}")
|
||||
lines.append(f"• 预期利润: {F(net_profit, 1)}{ccy} | 收益率: {return_rate:.1f}%")
|
||||
lines.append(f"• 盈亏比: {rr:.1f}:1 | 手续费: {F(fee, 1)}{ccy}(买{F(buy_fee,1)}+卖{F(sell_fee,1)})")
|
||||
lines.append(f"• 止损: {F(stop_loss, d)} | 最大亏损: {F(risk_total, 1)}{ccy}")
|
||||
elif not is_worth:
|
||||
lines.append(f"💡 波动太小,暂不建议做T | 性价比: {rating}")
|
||||
else:
|
||||
lines.append(f"⚠️ 不足1手({lot_size}股),无法做T")
|
||||
|
||||
except Exception as e:
|
||||
lines.append(f"❌ {sym}: {e}")
|
||||
|
||||
lines.append(f"\n⏰ 港股9:30-16:00 | 美股21:30-04:00 (北京时间)")
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
result = analyze()
|
||||
print(result)
|
||||
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
持仓做T价格监控 - 支撑位低吸、阻力位高抛
|
||||
监控所有持仓(OKX+长桥),价格接近关键位时提醒
|
||||
无提醒时静默输出(cron no_agent模式不推送)
|
||||
"""
|
||||
import os, sys, json, math, subprocess, re
|
||||
from datetime import datetime
|
||||
|
||||
# Load creds
|
||||
okx_creds = {}
|
||||
with open(os.path.expanduser('~/.bashrc')) as f:
|
||||
for line in f:
|
||||
m = re.match(r'export\s+(OKX_\w+)=(.*)', line.strip())
|
||||
if m:
|
||||
okx_creds[m.group(1)] = m.group(2).strip().strip('"').strip("'")
|
||||
line = line.strip()
|
||||
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]
|
||||
|
||||
def okx_get(endpoint, params=""):
|
||||
import hmac, base64, hashlib
|
||||
ts = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.') + f"{datetime.utcnow().microsecond // 1000:03d}Z"
|
||||
path = endpoint + ('?' + params if params else '')
|
||||
msg = ts + 'GET' + path
|
||||
sig = base64.b64encode(hmac.new(okx_creds['OKX_SECRET'].encode(), msg.encode(), hashlib.sha256).digest()).decode()
|
||||
cmd = ['curl', '-s', '--proxy', 'http://127.0.0.1:7890',
|
||||
'-H', f'OK-ACCESS-KEY: {okx_creds["OKX_API_KEY"]}', '-H', f'OK-ACCESS-SIGN: {sig}',
|
||||
'-H', f'OK-ACCESS-TIMESTAMP: {ts}', '-H', f'OK-ACCESS-PASSPHRASE: {okx_creds["OKX_PASSPHRASE"]}',
|
||||
'-H', 'Content-Type: application/json', f'https://www.okx.com{path}']
|
||||
r = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
|
||||
return json.loads(r.stdout)
|
||||
|
||||
def monitor():
|
||||
alerts = []
|
||||
|
||||
# OKX positions
|
||||
try:
|
||||
pos = okx_get('/api/v5/account/positions', 'instType=SWAP')
|
||||
for p in pos.get('data', []):
|
||||
if float(p.get('pos', 0)) == 0:
|
||||
continue
|
||||
sym = p['instId'].replace('-USDT-SWAP', '')
|
||||
try:
|
||||
ticker = okx_get('/api/v5/market/ticker', f'instId={sym}-USDT-SWAP')
|
||||
price = float(ticker['data'][0]['last'])
|
||||
candles = okx_get('/api/v5/market/candles', f'instId={sym}-USDT-SWAP&bar=4H&limit=20')
|
||||
data = candles.get('data', [])
|
||||
if len(data) >= 10:
|
||||
closes = [float(d[4]) for d in data]
|
||||
highs = [float(d[2]) for d in data]
|
||||
lows = [float(d[3]) for d in data]
|
||||
atr_sum = sum(max(highs[-i]-lows[-i], abs(highs[-i]-closes[-i-1]), abs(lows[-i]-closes[-i-1])) for i in range(1, min(15, len(data))))
|
||||
atr = atr_sum / min(14, len(data)-1)
|
||||
support = min(lows[-5:])
|
||||
resistance = max(highs[-5:])
|
||||
sma20 = sum(closes) / len(closes)
|
||||
buy_zone = min(support, sma20) + atr * 0.2
|
||||
sell_zone = max(resistance, sma20) - atr * 0.2
|
||||
|
||||
dist_buy = abs(price - buy_zone) / price * 100
|
||||
dist_sell = abs(price - sell_zone) / price * 100
|
||||
|
||||
if dist_buy < 1.5:
|
||||
alerts.append(f"🟢 {sym} 接近低吸位! 现价{price:.2f} → 低吸{buy_zone:.2f} (差{dist_buy:.1f}%)")
|
||||
elif dist_sell < 1.5:
|
||||
alerts.append(f"🔴 {sym} 接近高抛位! 现价{price:.2f} → 高抛{sell_zone:.2f} (差{dist_sell:.1f}%)")
|
||||
elif price < support:
|
||||
alerts.append(f"⚠️ {sym} 跌破支撑! 现价{price:.2f} < 支撑{support:.2f}")
|
||||
elif price > resistance:
|
||||
alerts.append(f"🚀 {sym} 突破阻力! 现价{price:.2f} > 阻力{resistance:.2f}")
|
||||
except:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
|
||||
# LongBridge positions
|
||||
try:
|
||||
from longport import openapi
|
||||
cfg = openapi.Config.from_env()
|
||||
trade_ctx = openapi.TradeContext(config=cfg)
|
||||
quote_ctx = openapi.QuoteContext(config=cfg)
|
||||
resp = trade_ctx.stock_positions()
|
||||
lb_syms = []
|
||||
lb_pos = {}
|
||||
for ch in resp.channels:
|
||||
for p in ch.positions:
|
||||
if int(p.quantity) > 0:
|
||||
lb_syms.append(p.symbol)
|
||||
lb_pos[p.symbol] = {'cost': float(p.cost_price), 'qty': int(p.quantity)}
|
||||
if lb_syms:
|
||||
quotes = quote_ctx.quote(lb_syms)
|
||||
for q in quotes:
|
||||
price = float(q.last_done)
|
||||
cost = lb_pos[q.symbol]['cost']
|
||||
buy_zone = cost * 0.95
|
||||
sell_zone = cost * 1.05
|
||||
dist_buy = abs(price - buy_zone) / price * 100
|
||||
dist_sell = abs(price - sell_zone) / price * 100
|
||||
if dist_buy < 2:
|
||||
alerts.append(f"🟢 {q.symbol} 接近低吸位! 现价{price:.2f} → 低吸{buy_zone:.2f}")
|
||||
elif dist_sell < 2:
|
||||
alerts.append(f"🔴 {q.symbol} 接近高抛位! 现价{price:.2f} → 高抛{sell_zone:.2f}")
|
||||
except:
|
||||
pass
|
||||
|
||||
if alerts:
|
||||
print("📊 做T监控提醒\n")
|
||||
print("\n".join(alerts))
|
||||
print(f"\n⏰ {datetime.now().strftime('%H:%M')}")
|
||||
# 无输出=静默
|
||||
|
||||
if __name__ == '__main__':
|
||||
monitor()
|
||||
Reference in New Issue
Block a user