feat: 备份 crypto/ + stocks/ 子目录到 skill 仓库

【备份】cron 已迁到 ~/.hermes/scripts/symlink, 旧副本 ~/.hermes/scripts/crypto/ 和 stocks/ 即将删, 先备份
- crypto-t-monitor/scripts/backtest.py + okx_t_monitor.py
- intraday-trading/scripts/{hk,us}_intraday_cli.py + hk_intraday_cli_runner.sh
- strategy-management/scripts/backtest.py (与 crypto-t-monitor 重复, 备份占位)

【未删本地】等用户确认
This commit is contained in:
2026-07-24 12:06:22 +08:00
parent 80063a1cac
commit d331b4e682
6 changed files with 1877 additions and 0 deletions
+279
View File
@@ -0,0 +1,279 @@
#!/usr/bin/env python3
"""港股日内交易监控+自动下单 - CLI 路径"""
import os, sys, json, time
from datetime import datetime
# 强制 CLI 路径走 .com 海外域 (避免 602315)
os.environ['LONGBRIDGE_HTTP_URL'] = 'https://openapi.longbridge.com'
os.environ['LONGBRIDGE_REGION'] = 'ap'
os.environ['LONGBRIDGE_TRADE_ENABLED'] = 'true'
# 替换 longport 模块为 CLI helper (Python SDK 走 cn 域会 602315)
sys.path.insert(0, '/home/openclaw/.hermes/scripts')
import longbridge_cli_helper as _helper
_fake_longport = type(sys)('longport')
_fake_longport.openapi = _helper
sys.modules['longport'] = _fake_longport
sys.modules['longport.openapi'] = _helper
from longport import openapi # 现在 openapi 实际是 helper
# 剩余代码跟原版一致
config = {}
with open(os.path.expanduser('~/.bashrc'), 'r') as f:
for line in f:
if line.startswith('export LONGPORT_'):
key, value = line.strip().split('=', 1)
config[key.replace('export ', '')] = value
os.environ['LONGPORT_APP_KEY'] = config.get('LONGPORT_APP_KEY', '')
os.environ['LONGPORT_APP_SECRET'] = config.get('LONGPORT_APP_SECRET', '')
os.environ['LONGPORT_ACCESS_TOKEN'] = config.get('LONGPORT_ACCESS_TOKEN', '')
ctx = openapi.QuoteContext(config=None)
# === 余额 + 持仓 ===
bals = openapi.account_balance()
hkd_cash = 0
usd_cash = 0
if bals:
for b in bals:
cur = str(b.currency).upper()
cash = float(getattr(b, 'cash_available', 0) or 0)
if cash <= 0:
cash = float(getattr(b, 'buy_power', 0) or 0)
if 'USD' in cur:
usd_cash += cash
elif 'HKD' in cur:
hkd_cash += cash
print(f"💰 HKD cash: {hkd_cash:.0f} | USD cash: {usd_cash:.2f}")
print(f"💰 单笔仓位 (HKD): {hkd_cash*0.25:.0f} | (USD): {usd_cash*0.25:.2f}")
# 持仓
held_symbols = set()
positions = openapi.stock_positions()
for ch in positions.channels:
for p in ch.positions:
held_symbols.add(p.symbol)
print(f" 持仓: {p.symbol} {p.quantity}股 @ {p.cost_price}")
# === 读取盘前候选 ===
screen_file = os.path.expanduser('~/.hermes/skills/trading/quant-factor-mining/artifacts/hk_intraday_latest.json')
if not os.path.exists(screen_file):
print("❌ 未找到盘前筛选结果")
sys.exit(1)
with open(screen_file) as f:
screen = json.load(f)
# 取 TOP 3
candidates = [r for r in screen.get('results', [])[:3]]
print(f"\n🎯 监控标的:")
for c in candidates:
print(f" {c['ticker']}: 评分 {c['score']:.1f} | ADR {c['avg_adr']:.2f}%")
# === 读取入场记录 ===
entry_file = os.path.expanduser('~/.hermes/trading/hk_intraday_entries.json')
entries = {}
if os.path.exists(entry_file):
try:
entries = json.load(open(entry_file))
except:
entries = {}
# === 遍历每个候选, 检查入场/出场信号 ===
for c in candidates:
ticker = c['ticker']
try:
q = ctx.quote([ticker])[0]
current = float(q.last_done)
except Exception as e:
print(f"{ticker}: 行情获取失败: {e}")
continue
# 简化版信号: 价格突破 SMA5 且 SMA5 > SMA10 → 入场
try:
cs = ctx.candlesticks(ticker, openapi.Period.Day, 30, openapi.AdjustType.ForwardAdjust)
closes = [float(c2.close) for c2 in cs]
sma5 = sum(closes[-5:]) / 5
sma10 = sum(closes[-10:]) / 10
except Exception as e:
print(f"{ticker}: K线失败: {e}")
continue
if ticker in held_symbols:
print(f"{ticker}: 已有持仓,跳过入场检查 | 现价 {current:.2f}")
continue
# 如果已有日内入场记录, 也跳过(防止重复下单)
if ticker in entries:
# 检查出场信号
entry = entries[ticker]
e_shares = entry.get('shares', 0)
e_order_id = entry.get('order_id', '')
if not e_order_id:
print(f"⚠️ {ticker}: 有入场记录但无订单ID, 跳过")
continue
if current <= entry['stop_loss']:
print(f"\n🛑 {ticker} 触发止损! {current:.2f} <= {entry['stop_loss']}")
try:
openapi.submit_order(
symbol=ticker, order_type=openapi.OrderType.MO,
side=openapi.OrderSide.Sell,
submitted_quantity=e_shares,
time_in_force=openapi.TimeInForceType.Day,
)
print(f" ✅ 止损平仓: 卖 {e_shares}股 @ 市价")
del entries[ticker]
with open(entry_file, 'w') as f:
json.dump(entries, f, indent=2)
except Exception as e:
print(f" ❌ 平仓失败: {e}")
elif current >= entry['take_profit']:
print(f"\n🎯 {ticker} 触发止盈! {current:.2f} >= {entry['take_profit']}")
try:
openapi.submit_order(
symbol=ticker, order_type=openapi.OrderType.MO,
side=openapi.OrderSide.Sell,
submitted_quantity=e_shares,
time_in_force=openapi.TimeInForceType.Day,
)
print(f" ✅ 止盈平仓: 卖 {e_shares}股 @ 市价")
del entries[ticker]
with open(entry_file, 'w') as f:
json.dump(entries, f, indent=2)
except Exception as e:
print(f" ❌ 平仓失败: {e}")
else:
print(f"{ticker}: 已入场,持仓中 | 现价 {current:.2f} | 止损 {entry['stop_loss']} | 止盈 {entry['take_profit']}")
continue
# === 入场信号 ===
if current > sma5 > sma10 and current > closes[-2]:
# 计算仓位: 20% cash (按标的货币), 按 lot_size 取整
price = round(current, 2)
# 港股 lot_size 可能 100/200/500/1000/2000 (ticker 依赖), 美股=1
lot_size = openapi.get_lot_size(ticker) if hasattr(openapi, 'get_lot_size') else 100
# 选对应货币的 cash
cash = hkd_cash # 港股账户默认 HKD
target_value = cash * 0.20
shares = int(target_value / price / lot_size) * lot_size
if shares < lot_size:
print(f"{ticker}: 信号但余额不足 (需要{lot_size}股 @ {price})")
continue
stop_loss = round(price * 0.985, 2)
take_profit = round(price * 1.025, 2)
# 调整下单价格到合法范围 (港股 9 档保护规则)
adjusted_price = openapi.adjust_price_for_order(ticker, price, 'buy') if hasattr(openapi, 'adjust_price_for_order') else price
if abs(adjusted_price - price) > 0.05:
print(f" ⚠️ 价格调整: {price}{adjusted_price} (盘口约束)")
# 基于 adjusted_price 重新算止损止盈
stop_loss = round(adjusted_price * 0.985, 2)
take_profit = round(adjusted_price * 1.025, 2)
print(f"\n🔔 {ticker} 入场信号!")
print(f" 方向: 做多 | 现价 {current:.2f} | SMA5 {sma5:.2f}")
print(f" 止损: {stop_loss} | 止盈: {take_profit} | 股数: {shares}")
# 自动下单
try:
resp = openapi.submit_order(
symbol=ticker, order_type=openapi.OrderType.LO,
side=openapi.OrderSide.Buy,
submitted_quantity=shares,
time_in_force=openapi.TimeInForceType.Day,
submitted_price=adjusted_price,
)
order_id = resp.order_id
print(f" ⏳ 已提交: {order_id}")
# 反查 status (700 RMB 教训)
import time as _t
status = 'Unknown'
detail = None
for retry in range(3):
_t.sleep(0.5)
try:
detail = openapi.order_detail(order_id)
status = str(detail.status).split('.')[-1] if detail else 'Unknown'
if status not in ('New', 'NotReported'):
break
except Exception:
continue
if status == 'Filled':
print(f" ✅ 成交: {order_id}")
elif status == 'Rejected':
print(f" ❌ 被拒: {order_id} | 跳过")
continue
elif status == 'Canceled':
print(f" 🚫 已撤: {order_id}")
continue
else:
print(f" ⚠️ 已挂单未成交: {order_id} (status={status})")
# 记录 (用 adjusted_price 作为 entry_price)
entries[ticker] = {
'side': 'buy',
'entry_price': adjusted_price,
'stop_loss': stop_loss,
'take_profit': take_profit,
'shares': shares,
'order_id': order_id,
'time': datetime.now().isoformat(),
}
os.makedirs(os.path.dirname(entry_file), exist_ok=True)
with open(entry_file, 'w') as f:
json.dump(entries, f, indent=2)
except Exception as e:
print(f" ❌ 下单失败: {e}")
elif ticker in entries:
# === 出场信号 ===
entry = entries[ticker]
e_shares = entry.get('shares', 0)
e_order_id = entry.get('order_id', '')
if not e_order_id:
print(f"⚠️ {ticker}: 无订单ID, 跳过")
continue
if current <= entry['stop_loss']:
print(f"🛑 {ticker} 止损! {current:.2f} <= {entry['stop_loss']}")
try:
openapi.submit_order(
symbol=ticker, order_type=openapi.OrderType.MO,
side=openapi.OrderSide.Sell,
submitted_quantity=e_shares,
time_in_force=openapi.TimeInForceType.Day,
)
print(f" ✅ 止损平仓: 卖 {e_shares}")
del entries[ticker]
with open(entry_file, 'w') as f:
json.dump(entries, f, indent=2)
except Exception as e:
print(f" ❌ 平仓失败: {e}")
elif current >= entry['take_profit']:
print(f"🎯 {ticker} 止盈! {current:.2f} >= {entry['take_profit']}")
try:
openapi.submit_order(
symbol=ticker, order_type=openapi.OrderType.MO,
side=openapi.OrderSide.Sell,
submitted_quantity=e_shares,
time_in_force=openapi.TimeInForceType.Day,
)
print(f" ✅ 止盈平仓: 卖 {e_shares}")
del entries[ticker]
with open(entry_file, 'w') as f:
json.dump(entries, f, indent=2)
except Exception as e:
print(f" ❌ 平仓失败: {e}")
else:
print(f"{ticker}: 等待信号 | 现价 {current:.2f} | SMA5 {sma5:.2f} | SMA10 {sma10:.2f}")
print("\n=== 完成 ===")