feat: 迁移 trading 相关脚本到 skill 仓库 (第1批: 🔴 高优先级)
【迁移内容】
- crypto-t-monitor/scripts/t_monitor.py (币圈做T, 25.5 KB)
- strategy-management/scripts/us_t_levels.sh + hk_t_levels.sh (做T点位)
- intraday-trading/scripts/us_intraday_{scanner,monitor,close}_cron.sh + .py
- intraday-trading/scripts/hk_intraday_{scanner,monitor,close}_cron.sh + .py
【配套修改】
- 8 个 cron 任务 script 路径更新 (jobs.json):
- db03f9255ad0 (币圈OKX做T) → crypto-t-monitor/scripts/
- cfa0c1d6 (美股日内盘前) → intraday-trading/scripts/
- bcdf7039 (美股日内交易监控) → intraday-trading/scripts/
- d1acad61 (美股日内平仓) → intraday-trading/scripts/
- c3401d72 (港股日内盘前) → intraday-trading/scripts/
- e3667cb0 (港股日内交易监控) → intraday-trading/scripts/
- 303ec320 (港股日内平仓) → intraday-trading/scripts/
- c4dc9ac8 (港股做T点位) → strategy-management/scripts/
- 70d24624 (美股做T点位) → strategy-management/scripts/
- prompt 字段里路径同步更新
- crypto-t-monitor/SKILL.md scripts 段加 t_monitor.py 描述
【删除】本地旧副本 ~/.hermes/scripts/{t_monitor,us_t_levels,hk_t_levels,us_intraday*,hk_intraday*}.{py,sh}
【保留】~/.hermes/scripts/fetch_policy.py 已迁未删 (本次删)
【测试】us_intraday_scanner.py 跑通 (有 shebang, no_agent cron 自动用 python3)
【未改】devops/ + software-development/ 不在 trading git 仓库, 引用已本地更新
This commit is contained in:
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
# 港股日内平仓 - CLI 路径
|
||||
# 此文件路径固定在 ~/.hermes/scripts/stocks/,symlink 到 .scripts/<name>.sh
|
||||
# 直接调 stocks/ 下的真实脚本
|
||||
export LONGBRIDGE_HTTP_URL=https://openapi.longbridge.com
|
||||
export LONGBRIDGE_REGION=ap
|
||||
export LONGBRIDGE_TRADE_ENABLED=true
|
||||
export PROXYCHAINS_CONF=/home/openclaw/.proxychains/proxychains.conf
|
||||
|
||||
proxychains4 -f ~/.proxychains/proxychains.conf \
|
||||
python3 /home/openclaw/.hermes/scripts/stocks/hk_intraday_cli.py 2>&1 | tail -30
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env python3
|
||||
"""港股日内交易监控+自动下单 - 北京时间9:30-15:45运行"""
|
||||
import os, json, time
|
||||
from datetime import datetime
|
||||
|
||||
# Force SDK to use international endpoint (bypass 602315 mainland CN geo-block)
|
||||
os.environ['LONGBRIDGE_REGION'] = 'ap'
|
||||
|
||||
# Load LongBridge credentials
|
||||
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', '')
|
||||
|
||||
from longport import openapi
|
||||
|
||||
cfg = openapi.Config.from_env()
|
||||
ctx = openapi.QuoteContext(config=cfg)
|
||||
trade_ctx = openapi.TradeContext(config=cfg)
|
||||
|
||||
# 读取盘前筛选结果
|
||||
screen_file = os.path.expanduser('~/.hermes/skills/trading/quant-factor-mining/artifacts/hk_intraday_latest.json')
|
||||
if not os.path.exists(screen_file):
|
||||
print("❌ 未找到盘前筛选结果")
|
||||
exit(1)
|
||||
|
||||
with open(screen_file) as f:
|
||||
screen_data = json.load(f)
|
||||
|
||||
candidates = screen_data.get('results', [])[:3] # 取TOP3
|
||||
|
||||
# 账户信息
|
||||
balance = trade_ctx.account_balance()
|
||||
buying_power = 0
|
||||
for acc in balance:
|
||||
if acc.currency == 'HKD':
|
||||
buying_power = float(acc.buy_power)
|
||||
|
||||
position_size = buying_power * 0.25 # 25%仓位
|
||||
|
||||
print(f"📊 日内交易监控启动")
|
||||
print(f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
|
||||
print(f"购买力: {buying_power:,.0f} HKD")
|
||||
print(f"单笔仓位: {position_size:,.0f} HKD")
|
||||
print()
|
||||
print("🎯 监控标的:")
|
||||
for c in candidates:
|
||||
print(f" {c['ticker']}: 现价 {c['price']} | ADR {c['avg_adr']}% | 评分 {c['score']}")
|
||||
print()
|
||||
|
||||
# 读取已入场记录
|
||||
entry_file = os.path.expanduser('~/.hermes/trading/hk_intraday_entries.json')
|
||||
entries = {}
|
||||
if os.path.exists(entry_file):
|
||||
with open(entry_file) as f:
|
||||
entries = json.load(f)
|
||||
|
||||
# 获取实时行情
|
||||
tickers = [c['ticker'] for c in candidates]
|
||||
quotes = ctx.quote(tickers)
|
||||
|
||||
for q in quotes:
|
||||
ticker = q.symbol
|
||||
current = float(q.last_done)
|
||||
prev_close = float(q.prev_close)
|
||||
change_pct = (current - prev_close) / prev_close * 100
|
||||
|
||||
# 找到对应候选
|
||||
candidate = next((c for c in candidates if c['ticker'] == ticker), None)
|
||||
if not candidate:
|
||||
continue
|
||||
|
||||
# 获取5分钟K线计算入场信号
|
||||
try:
|
||||
candles = ctx.candlesticks(ticker, openapi.Period.Min_5, 20, openapi.AdjustType.ForwardAdjust)
|
||||
if not candles:
|
||||
continue
|
||||
|
||||
closes = [float(c.close) for c in candles]
|
||||
highs = [float(c.high) for c in candles]
|
||||
lows = [float(c.low) for c in candles]
|
||||
|
||||
# 计算SMA
|
||||
sma5 = sum(closes[-5:]) / 5
|
||||
sma10 = sum(closes[-10:]) / 10
|
||||
sma20 = sum(closes) / len(closes)
|
||||
|
||||
# 计算ATR
|
||||
atr = sum(max(highs[i]-lows[i], abs(highs[i]-closes[i-1]), abs(lows[i]-closes[i-1])) for i in range(1, len(candles))) / (len(candles)-1)
|
||||
|
||||
# 入场条件
|
||||
entry_price = None
|
||||
side = None
|
||||
|
||||
# 做多条件: 价格突破SMA5且SMA5>SMA10
|
||||
if current > sma5 and sma5 > sma10 and current > closes[-2]:
|
||||
entry_price = current
|
||||
side = 'buy'
|
||||
stop_loss = max(min(lows[-5:]), current - atr * 2)
|
||||
take_profit = current + atr * 3
|
||||
|
||||
# 做空条件: 价格跌破SMA5且SMA5<SMA10
|
||||
elif current < sma5 and sma5 < sma10 and current < closes[-2]:
|
||||
entry_price = current
|
||||
side = 'sell'
|
||||
stop_loss = min(max(highs[-5:]), current + atr * 2)
|
||||
take_profit = current - atr * 3
|
||||
|
||||
if entry_price and side and ticker not in entries:
|
||||
# 计算股数
|
||||
shares = int(position_size / current / 100) * 100
|
||||
if shares < 100:
|
||||
shares = 100
|
||||
|
||||
print(f"🔔 {ticker} 入场信号!")
|
||||
print(f" 方向: {'做多' if side == 'buy' else '做空'}")
|
||||
print(f" 入场: {current:.2f}")
|
||||
print(f" 止损: {stop_loss:.2f}")
|
||||
print(f" 止盈: {take_profit:.2f}")
|
||||
print(f" 股数: {shares}")
|
||||
|
||||
# 下单
|
||||
try:
|
||||
if side == 'buy':
|
||||
resp = trade_ctx.submit_order(
|
||||
symbol=ticker,
|
||||
order_type=openapi.OrderType.LO,
|
||||
side=openapi.OrderSide.Buy,
|
||||
submitted_quantity=shares,
|
||||
time_in_force=openapi.TimeInForceType.Day,
|
||||
submitted_price=round(current, 2),
|
||||
)
|
||||
else:
|
||||
resp = trade_ctx.submit_order(
|
||||
symbol=ticker,
|
||||
order_type=openapi.OrderType.LO,
|
||||
side=openapi.OrderSide.Sell,
|
||||
submitted_quantity=shares,
|
||||
time_in_force=openapi.TimeInForceType.Day,
|
||||
submitted_price=round(current, 2),
|
||||
)
|
||||
|
||||
print(f" ✅ 下单成功: {resp.order_id}")
|
||||
|
||||
# 记录入场
|
||||
entries[ticker] = {
|
||||
'side': side,
|
||||
'entry_price': current,
|
||||
'stop_loss': stop_loss,
|
||||
'take_profit': take_profit,
|
||||
'shares': shares,
|
||||
'order_id': resp.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]
|
||||
entry_shares = entry.get('shares', 0)
|
||||
order_id = entry.get('order_id', '')
|
||||
|
||||
# 验证订单是否已成交(确保是我们开的仓)
|
||||
if not order_id:
|
||||
print(f"⚠️ {ticker}: 无订单ID,跳过平仓")
|
||||
continue
|
||||
|
||||
if entry['side'] == 'buy':
|
||||
if current <= entry['stop_loss']:
|
||||
print(f"🛑 {ticker} 触发止损! {current:.2f} <= {entry['stop_loss']:.2f}")
|
||||
# 只平我们开的仓位数量
|
||||
try:
|
||||
trade_ctx.submit_order(
|
||||
symbol=ticker,
|
||||
order_type=openapi.OrderType.MO,
|
||||
side=openapi.OrderSide.Sell,
|
||||
submitted_quantity=entry_shares,
|
||||
time_in_force=openapi.TimeInForceType.Day,
|
||||
)
|
||||
print(f" ✅ 平仓成功: 卖出 {entry_shares}股")
|
||||
del entries[ticker]
|
||||
except Exception as e:
|
||||
print(f" ❌ 平仓失败: {e}")
|
||||
|
||||
elif current >= entry['take_profit']:
|
||||
print(f"🎯 {ticker} 触发止盈! {current:.2f} >= {entry['take_profit']:.2f}")
|
||||
# 只平我们开的仓位数量
|
||||
try:
|
||||
trade_ctx.submit_order(
|
||||
symbol=ticker,
|
||||
order_type=openapi.OrderType.MO,
|
||||
side=openapi.OrderSide.Sell,
|
||||
submitted_quantity=entry_shares,
|
||||
time_in_force=openapi.TimeInForceType.Day,
|
||||
)
|
||||
print(f" ✅ 平仓成功: 卖出 {entry_shares}股")
|
||||
del entries[ticker]
|
||||
except Exception as e:
|
||||
print(f" ❌ 平仓失败: {e}")
|
||||
|
||||
elif entry['side'] == 'sell':
|
||||
if current >= entry['stop_loss']:
|
||||
print(f"🛑 {ticker} 触发止损! {current:.2f} >= {entry['stop_loss']:.2f}")
|
||||
# 只平我们开的仓位数量
|
||||
try:
|
||||
trade_ctx.submit_order(
|
||||
symbol=ticker,
|
||||
order_type=openapi.OrderType.MO,
|
||||
side=openapi.OrderSide.Buy,
|
||||
submitted_quantity=entry_shares,
|
||||
time_in_force=openapi.TimeInForceType.Day,
|
||||
)
|
||||
print(f" ✅ 平仓成功: 买入 {entry_shares}股")
|
||||
del entries[ticker]
|
||||
except Exception as e:
|
||||
print(f" ❌ 平仓失败: {e}")
|
||||
|
||||
elif current <= entry['take_profit']:
|
||||
print(f"🎯 {ticker} 触发止盈! {current:.2f} <= {entry['take_profit']:.2f}")
|
||||
# 只平我们开的仓位数量
|
||||
try:
|
||||
trade_ctx.submit_order(
|
||||
symbol=ticker,
|
||||
order_type=openapi.OrderType.MO,
|
||||
side=openapi.OrderSide.Buy,
|
||||
submitted_quantity=entry_shares,
|
||||
time_in_force=openapi.TimeInForceType.Day,
|
||||
)
|
||||
print(f" ✅ 平仓成功: 买入 {entry_shares}股")
|
||||
del entries[ticker]
|
||||
except Exception as e:
|
||||
print(f" ❌ 平仓失败: {e}")
|
||||
|
||||
else:
|
||||
print(f"⏳ {ticker}: 等待信号 | 现价 {current:.2f} | SMA5 {sma5:.2f} | SMA10 {sma10:.2f}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ {ticker}: {e}")
|
||||
|
||||
# 保存更新后的记录
|
||||
with open(entry_file, 'w') as f:
|
||||
json.dump(entries, f, indent=2)
|
||||
|
||||
print()
|
||||
if entries:
|
||||
print("📊 当前持仓:")
|
||||
for ticker, entry in entries.items():
|
||||
print(f" {ticker}: {entry['side']} @ {entry['entry_price']:.2f} | 止损 {entry['stop_loss']:.2f} | 止盈 {entry['take_profit']:.2f}")
|
||||
else:
|
||||
print("📊 当前无持仓")
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash
|
||||
# 港股日内监控 + 自动下单 (CLI 路径, 整体走 proxychains)
|
||||
# 简洁推送: 只推 [下单成功] / [下单失败: 原因] / [开/平仓事件]
|
||||
export LONGBRIDGE_HTTP_URL=https://openapi.longbridge.com
|
||||
export LONGBRIDGE_REGION=ap
|
||||
export LONGBRIDGE_TRADE_ENABLED=true
|
||||
export PROXYCHAINS_CONF=/home/openclaw/.proxychains/proxychains.conf
|
||||
|
||||
LOG=/tmp/hk_intraday_cli.log
|
||||
proxychains4 -f ~/.proxychains/proxychains.conf \
|
||||
python3 /home/openclaw/.hermes/scripts/stocks/hk_intraday_cli.py > $LOG 2>&1
|
||||
|
||||
MSG=""
|
||||
|
||||
# 下单成功
|
||||
if SUCCESS=$(grep '下单成功' $LOG); then
|
||||
MSG+="✅ $SUCCESS\n"
|
||||
# 加上 ticker/方向
|
||||
TICKER=$(grep '入场信号' $LOG | grep -oE '[0-9]+\.[A-Z]+' | head -1)
|
||||
PRICE=$(grep '入场信号' -A2 $LOG | grep -oE '现价 [0-9.]+' | head -1)
|
||||
[ -n "$TICKER" ] && MSG="📊 HK $TICKER $PRICE\n$MSG"
|
||||
fi
|
||||
|
||||
# 下单失败
|
||||
if FAIL=$(grep '下单失败' $LOG); then
|
||||
MSG+="❌ $FAIL\n"
|
||||
fi
|
||||
|
||||
# 开/平仓事件
|
||||
if TRADE=$(grep -E '止损平仓|止盈平仓' $LOG); then
|
||||
MSG+="🎯 $TRADE\n"
|
||||
fi
|
||||
|
||||
# 推送 (无事件则不推, 避免噪音)
|
||||
if [ -n "$MSG" ]; then
|
||||
bash ~/.hermes/scripts/push_to_qq.sh "$(echo -e "$MSG")"
|
||||
fi
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
"""港股日内交易盘前筛选 - 8:30自动运行"""
|
||||
import os, json
|
||||
from datetime import datetime
|
||||
|
||||
# Load LongBridge credentials
|
||||
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', '')
|
||||
|
||||
from longport import openapi
|
||||
|
||||
cfg = openapi.Config.from_env()
|
||||
ctx = openapi.QuoteContext(config=cfg)
|
||||
|
||||
# 候选标的池
|
||||
tickers = [
|
||||
'700.HK', '9988.HK', '1810.HK', '3690.HK', '9888.HK',
|
||||
'9618.HK', '1024.HK', '2015.HK', '9866.HK', '9868.HK',
|
||||
'5.HK', '388.HK', '1299.HK', '2318.HK', '1398.HK',
|
||||
]
|
||||
|
||||
quotes = ctx.quote(tickers)
|
||||
indexes = ctx.calc_indexes(tickers, [
|
||||
openapi.CalcIndex.VolumeRatio, openapi.CalcIndex.TurnoverRate,
|
||||
])
|
||||
|
||||
results = []
|
||||
for ticker in tickers:
|
||||
try:
|
||||
candles = ctx.candlesticks(ticker, openapi.Period.Day, 20, openapi.AdjustType.ForwardAdjust)
|
||||
if not candles:
|
||||
continue
|
||||
highs = [float(c.high) for c in candles]
|
||||
lows = [float(c.low) for c in candles]
|
||||
closes = [float(c.close) for c in candles]
|
||||
adrs = [(h - l) / c * 100 for h, l, c in zip(highs, lows, closes)]
|
||||
avg_adr = sum(adrs[-5:]) / 5 # 近5日ADR
|
||||
q = next((q for q in quotes if q.symbol == ticker), None)
|
||||
idx = next((i for i in indexes if i.symbol == ticker), None)
|
||||
if q and idx:
|
||||
vr = float(getattr(idx, 'volume_ratio', 0) or 0)
|
||||
tr = float(getattr(idx, 'turnover_rate', 0) or 0)
|
||||
# 评分:ADR 40% + 量比 30% + 换手率 30%
|
||||
score = min(avg_adr / 4, 1) * 40 + min(vr / 2, 1) * 30 + min(tr / 2, 1) * 30
|
||||
results.append({
|
||||
'ticker': ticker, 'price': float(q.last_done),
|
||||
'volume_ratio': vr, 'turnover_rate': tr,
|
||||
'avg_adr': round(avg_adr, 2), 'score': round(score, 1),
|
||||
})
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
results.sort(key=lambda x: x['score'], reverse=True)
|
||||
|
||||
# 保存结果
|
||||
out_path = os.path.expanduser('~/.hermes/skills/trading/quant-factor-mining/artifacts/hk_intraday_latest.json')
|
||||
os.makedirs(os.path.dirname(out_path), exist_ok=True)
|
||||
with open(out_path, 'w') as f:
|
||||
json.dump({'date': datetime.now().isoformat(), 'results': results[:8]}, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# 输出报告
|
||||
date_str = datetime.now().strftime('%Y-%m-%d')
|
||||
print(f'🔥 港股日内交易盘前筛选 {date_str}')
|
||||
print('=' * 55)
|
||||
print(f'{"股票":<10}{"现价":>8}{"ADR%":>7}{"量比":>6}{"换手":>6}{"评分":>6}')
|
||||
print('-' * 55)
|
||||
|
||||
for r in results[:8]:
|
||||
emoji = '🟢' if r['score'] > 60 else ('🟡' if r['score'] > 40 else '🔴')
|
||||
print(f'{emoji}{r["ticker"]:<9}{r["price"]:>8.2f}{r["avg_adr"]:>7.2f}{r["volume_ratio"]:>6.2f}{r["turnover_rate"]:>6.2f}{r["score"]:>6.1f}')
|
||||
|
||||
print()
|
||||
print('📋 TOP 3 策略建议:')
|
||||
for r in results[:3]:
|
||||
if r['avg_adr'] > 4:
|
||||
strategy = '动量突破'
|
||||
elif r['avg_adr'] > 3:
|
||||
strategy = '趋势跟踪'
|
||||
else:
|
||||
strategy = 'VWAP回归'
|
||||
print(f' {r["ticker"]}: {strategy} | 止损-1.5% | 量比{r["volume_ratio"]:.1f}')
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
# 美股日内平仓 - CLI 路径
|
||||
export LONGBRIDGE_HTTP_URL=https://openapi.longbridge.com
|
||||
export LONGBRIDGE_REGION=ap
|
||||
export LONGBRIDGE_TRADE_ENABLED=true
|
||||
export PROXYCHAINS_CONF=/home/openclaw/.proxychains/proxychains.conf
|
||||
|
||||
proxychains4 -f ~/.proxychains/proxychains.conf \
|
||||
python3 /home/openclaw/.hermes/scripts/stocks/us_intraday_cli.py 2>&1 | tail -30
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env python3
|
||||
"""美股日内交易监控+自动下单 - 北京时间21:30-4:00运行"""
|
||||
import os, json, time
|
||||
from datetime import datetime
|
||||
|
||||
# Force SDK to use international endpoint (bypass 602315 mainland CN geo-block)
|
||||
os.environ['LONGBRIDGE_REGION'] = 'ap'
|
||||
|
||||
# Load LongBridge credentials
|
||||
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', '')
|
||||
|
||||
from longport import openapi
|
||||
|
||||
cfg = openapi.Config.from_env()
|
||||
ctx = openapi.QuoteContext(config=cfg)
|
||||
trade_ctx = openapi.TradeContext(config=cfg)
|
||||
|
||||
# 读取盘前筛选结果
|
||||
screen_file = os.path.expanduser('~/.hermes/skills/trading/quant-factor-mining/artifacts/us_intraday_latest.json')
|
||||
if not os.path.exists(screen_file):
|
||||
print("❌ 未找到盘前筛选结果")
|
||||
exit(1)
|
||||
|
||||
with open(screen_file) as f:
|
||||
screen_data = json.load(f)
|
||||
|
||||
candidates = screen_data.get('results', [])[:3] # 取TOP3
|
||||
|
||||
# 账户信息
|
||||
balance = trade_ctx.account_balance()
|
||||
buying_power = 0
|
||||
for acc in balance:
|
||||
if acc.currency == 'USD':
|
||||
buying_power = float(acc.buy_power)
|
||||
|
||||
position_size = buying_power * 0.25 # 25%仓位
|
||||
|
||||
print(f"📊 美股日内交易监控启动")
|
||||
print(f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
|
||||
print(f"购买力: ${buying_power:,.0f}")
|
||||
print(f"单笔仓位: ${position_size:,.0f}")
|
||||
print()
|
||||
print("🎯 监控标的:")
|
||||
for c in candidates:
|
||||
print(f" {c['ticker']}: 现价 ${c['price']} | ADR {c['avg_adr']}% | 评分 {c['score']}")
|
||||
print()
|
||||
|
||||
# 读取已入场记录
|
||||
entry_file = os.path.expanduser('~/.hermes/trading/us_intraday_entries.json')
|
||||
entries = {}
|
||||
if os.path.exists(entry_file):
|
||||
with open(entry_file) as f:
|
||||
entries = json.load(f)
|
||||
|
||||
# 获取实时行情
|
||||
tickers = [c['ticker'] for c in candidates]
|
||||
quotes = ctx.quote(tickers)
|
||||
|
||||
for q in quotes:
|
||||
ticker = q.symbol
|
||||
current = float(q.last_done)
|
||||
prev_close = float(q.prev_close)
|
||||
change_pct = (current - prev_close) / prev_close * 100
|
||||
|
||||
# 找到对应候选
|
||||
candidate = next((c for c in candidates if c['ticker'] == ticker), None)
|
||||
if not candidate:
|
||||
continue
|
||||
|
||||
# 获取5分钟K线计算入场信号
|
||||
try:
|
||||
candles = ctx.candlesticks(ticker, openapi.Period.Min_5, 20, openapi.AdjustType.ForwardAdjust)
|
||||
if not candles:
|
||||
continue
|
||||
|
||||
closes = [float(c.close) for c in candles]
|
||||
highs = [float(c.high) for c in candles]
|
||||
lows = [float(c.low) for c in candles]
|
||||
|
||||
# 计算SMA
|
||||
sma5 = sum(closes[-5:]) / 5
|
||||
sma10 = sum(closes[-10:]) / 10
|
||||
sma20 = sum(closes) / len(closes)
|
||||
|
||||
# 计算ATR
|
||||
atr = sum(max(highs[i]-lows[i], abs(highs[i]-closes[i-1]), abs(lows[i]-closes[i-1])) for i in range(1, len(candles))) / (len(candles)-1)
|
||||
|
||||
# 入场条件
|
||||
entry_price = None
|
||||
side = None
|
||||
|
||||
# 做多条件: 价格突破SMA5且SMA5>SMA10
|
||||
if current > sma5 and sma5 > sma10 and current > closes[-2]:
|
||||
entry_price = current
|
||||
side = 'buy'
|
||||
stop_loss = max(min(lows[-5:]), current - atr * 2)
|
||||
take_profit = current + atr * 3
|
||||
|
||||
# 做空条件: 价格跌破SMA5且SMA5<SMA10
|
||||
elif current < sma5 and sma5 < sma10 and current < closes[-2]:
|
||||
entry_price = current
|
||||
side = 'sell'
|
||||
stop_loss = min(max(highs[-5:]), current + atr * 2)
|
||||
take_profit = current - atr * 3
|
||||
|
||||
if entry_price and side and ticker not in entries:
|
||||
# 计算股数
|
||||
shares = int(position_size / current)
|
||||
if shares < 1:
|
||||
shares = 1
|
||||
|
||||
print(f"🔔 {ticker} 入场信号!")
|
||||
print(f" 方向: {'做多' if side == 'buy' else '做空'}")
|
||||
print(f" 入场: ${current:.2f}")
|
||||
print(f" 止损: ${stop_loss:.2f}")
|
||||
print(f" 止盈: ${take_profit:.2f}")
|
||||
print(f" 股数: {shares}")
|
||||
|
||||
# 下单
|
||||
try:
|
||||
if side == 'buy':
|
||||
resp = trade_ctx.submit_order(
|
||||
symbol=ticker,
|
||||
order_type=openapi.OrderType.LO,
|
||||
side=openapi.OrderSide.Buy,
|
||||
submitted_quantity=shares,
|
||||
time_in_force=openapi.TimeInForceType.Day,
|
||||
submitted_price=round(current, 2),
|
||||
)
|
||||
else:
|
||||
resp = trade_ctx.submit_order(
|
||||
symbol=ticker,
|
||||
order_type=openapi.OrderType.LO,
|
||||
side=openapi.OrderSide.Sell,
|
||||
submitted_quantity=shares,
|
||||
time_in_force=openapi.TimeInForceType.Day,
|
||||
submitted_price=round(current, 2),
|
||||
)
|
||||
|
||||
print(f" ✅ 下单成功: {resp.order_id}")
|
||||
|
||||
# 记录入场
|
||||
entries[ticker] = {
|
||||
'side': side,
|
||||
'entry_price': current,
|
||||
'stop_loss': stop_loss,
|
||||
'take_profit': take_profit,
|
||||
'shares': shares,
|
||||
'order_id': resp.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]
|
||||
entry_shares = entry.get('shares', 0)
|
||||
order_id = entry.get('order_id', '')
|
||||
|
||||
# 验证订单是否已成交(确保是我们开的仓)
|
||||
if not order_id:
|
||||
print(f"⚠️ {ticker}: 无订单ID,跳过平仓")
|
||||
continue
|
||||
|
||||
if entry['side'] == 'buy':
|
||||
if current <= entry['stop_loss']:
|
||||
print(f"🛑 {ticker} 触发止损! ${current:.2f} <= ${entry['stop_loss']:.2f}")
|
||||
# 只平我们开的仓位数量
|
||||
try:
|
||||
trade_ctx.submit_order(
|
||||
symbol=ticker,
|
||||
order_type=openapi.OrderType.MO,
|
||||
side=openapi.OrderSide.Sell,
|
||||
submitted_quantity=entry_shares,
|
||||
time_in_force=openapi.TimeInForceType.Day,
|
||||
)
|
||||
print(f" ✅ 平仓成功: 卖出 {entry_shares}股")
|
||||
del entries[ticker]
|
||||
except Exception as e:
|
||||
print(f" ❌ 平仓失败: {e}")
|
||||
|
||||
elif current >= entry['take_profit']:
|
||||
print(f"🎯 {ticker} 触发止盈! ${current:.2f} >= ${entry['take_profit']:.2f}")
|
||||
# 只平我们开的仓位数量
|
||||
try:
|
||||
trade_ctx.submit_order(
|
||||
symbol=ticker,
|
||||
order_type=openapi.OrderType.MO,
|
||||
side=openapi.OrderSide.Sell,
|
||||
submitted_quantity=entry_shares,
|
||||
time_in_force=openapi.TimeInForceType.Day,
|
||||
)
|
||||
print(f" ✅ 平仓成功: 卖出 {entry_shares}股")
|
||||
del entries[ticker]
|
||||
except Exception as e:
|
||||
print(f" ❌ 平仓失败: {e}")
|
||||
|
||||
elif entry['side'] == 'sell':
|
||||
if current >= entry['stop_loss']:
|
||||
print(f"🛑 {ticker} 触发止损! ${current:.2f} >= ${entry['stop_loss']:.2f}")
|
||||
# 只平我们开的仓位数量
|
||||
try:
|
||||
trade_ctx.submit_order(
|
||||
symbol=ticker,
|
||||
order_type=openapi.OrderType.MO,
|
||||
side=openapi.OrderSide.Buy,
|
||||
submitted_quantity=entry_shares,
|
||||
time_in_force=openapi.TimeInForceType.Day,
|
||||
)
|
||||
print(f" ✅ 平仓成功: 买入 {entry_shares}股")
|
||||
del entries[ticker]
|
||||
except Exception as e:
|
||||
print(f" ❌ 平仓失败: {e}")
|
||||
|
||||
elif current <= entry['take_profit']:
|
||||
print(f"🎯 {ticker} 触发止盈! ${current:.2f} <= ${entry['take_profit']:.2f}")
|
||||
# 只平我们开的仓位数量
|
||||
try:
|
||||
trade_ctx.submit_order(
|
||||
symbol=ticker,
|
||||
order_type=openapi.OrderType.MO,
|
||||
side=openapi.OrderSide.Buy,
|
||||
submitted_quantity=entry_shares,
|
||||
time_in_force=openapi.TimeInForceType.Day,
|
||||
)
|
||||
print(f" ✅ 平仓成功: 买入 {entry_shares}股")
|
||||
del entries[ticker]
|
||||
except Exception as e:
|
||||
print(f" ❌ 平仓失败: {e}")
|
||||
|
||||
else:
|
||||
print(f"⏳ {ticker}: 等待信号 | 现价 ${current:.2f} | SMA5 ${sma5:.2f} | SMA10 ${sma10:.2f}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ {ticker}: {e}")
|
||||
|
||||
# 保存更新后的记录
|
||||
with open(entry_file, 'w') as f:
|
||||
json.dump(entries, f, indent=2)
|
||||
|
||||
print()
|
||||
if entries:
|
||||
print("📊 当前持仓:")
|
||||
for ticker, entry in entries.items():
|
||||
print(f" {ticker}: {entry['side']} @ ${entry['entry_price']:.2f} | 止损 ${entry['stop_loss']:.2f} | 止盈 ${entry['take_profit']:.2f}")
|
||||
else:
|
||||
print("📊 当前无持仓")
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
# 美股日内监控 + 自动下单 (CLI 路径)
|
||||
export LONGBRIDGE_HTTP_URL=https://openapi.longbridge.com
|
||||
export LONGBRIDGE_REGION=ap
|
||||
export LONGBRIDGE_TRADE_ENABLED=true
|
||||
export PROXYCHAINS_CONF=/home/openclaw/.proxychains/proxychains.conf
|
||||
|
||||
LOG=/tmp/us_intraday_cli.log
|
||||
proxychains4 -f ~/.proxychains/proxychains.conf \
|
||||
python3 /home/openclaw/.hermes/scripts/stocks/us_intraday_cli.py > $LOG 2>&1
|
||||
|
||||
MSG=""
|
||||
|
||||
# 下单成功
|
||||
if SUCCESS=$(grep '下单成功' $LOG); then
|
||||
MSG+="✅ $SUCCESS\n"
|
||||
TICKER=$(grep '入场信号' $LOG | grep -oE '[A-Z]+\.[A-Z]+' | head -1)
|
||||
PRICE=$(grep '入场信号' -A2 $LOG | grep -oE '现价 [0-9.]+' | head -1)
|
||||
[ -n "$TICKER" ] && MSG="📊 US $TICKER $PRICE\n$MSG"
|
||||
fi
|
||||
|
||||
# 下单失败
|
||||
if FAIL=$(grep '下单失败' $LOG); then
|
||||
MSG+="❌ $FAIL\n"
|
||||
fi
|
||||
|
||||
# 开/平仓事件
|
||||
if TRADE=$(grep -E '止损平仓|止盈平仓' $LOG); then
|
||||
MSG+="🎯 $TRADE\n"
|
||||
fi
|
||||
|
||||
# 推送 (无事件则不推)
|
||||
if [ -n "$MSG" ]; then
|
||||
bash ~/.hermes/scripts/push_to_qq.sh "$(echo -e "$MSG")"
|
||||
fi
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python3
|
||||
"""美股日内交易盘前筛选 - 北京时间21:00自动运行"""
|
||||
import os, json
|
||||
from datetime import datetime
|
||||
|
||||
# Load LongBridge credentials
|
||||
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', '')
|
||||
|
||||
from longport import openapi
|
||||
|
||||
cfg = openapi.Config.from_env()
|
||||
ctx = openapi.QuoteContext(config=cfg)
|
||||
|
||||
# 美股候选标的池(高波动+高流动性)
|
||||
tickers = [
|
||||
'AAPL.US', 'MSFT.US', 'NVDA.US', 'AMZN.US', 'META.US',
|
||||
'GOOGL.US', 'TSLA.US', 'AMD.US', 'NFLX.US', 'CRM.US',
|
||||
'INTC.US', 'MU.US', 'QCOM.US', 'AVGO.US', 'PYPL.US',
|
||||
'SQ.US', 'ROKU.US', 'SNAP.US', 'UBER.US', 'LYFT.US',
|
||||
]
|
||||
|
||||
quotes = ctx.quote(tickers)
|
||||
indexes = ctx.calc_indexes(tickers, [
|
||||
openapi.CalcIndex.VolumeRatio, openapi.CalcIndex.TurnoverRate,
|
||||
])
|
||||
|
||||
results = []
|
||||
for ticker in tickers:
|
||||
try:
|
||||
candles = ctx.candlesticks(ticker, openapi.Period.Day, 20, openapi.AdjustType.ForwardAdjust)
|
||||
if not candles:
|
||||
continue
|
||||
highs = [float(c.high) for c in candles]
|
||||
lows = [float(c.low) for c in candles]
|
||||
closes = [float(c.close) for c in candles]
|
||||
adrs = [(h - l) / c * 100 for h, l, c in zip(highs, lows, closes)]
|
||||
avg_adr = sum(adrs[-5:]) / 5 # 近5日ADR
|
||||
q = next((q for q in quotes if q.symbol == ticker), None)
|
||||
idx = next((i for i in indexes if i.symbol == ticker), None)
|
||||
if q and idx:
|
||||
vr = float(getattr(idx, 'volume_ratio', 0) or 0)
|
||||
tr = float(getattr(idx, 'turnover_rate', 0) or 0)
|
||||
# 评分:ADR 40% + 量比 30% + 换手率 30%
|
||||
score = min(avg_adr / 4, 1) * 40 + min(vr / 2, 1) * 30 + min(tr / 2, 1) * 30
|
||||
results.append({
|
||||
'ticker': ticker, 'price': float(q.last_done),
|
||||
'volume_ratio': vr, 'turnover_rate': tr,
|
||||
'avg_adr': round(avg_adr, 2), 'score': round(score, 1),
|
||||
})
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
results.sort(key=lambda x: x['score'], reverse=True)
|
||||
|
||||
# 保存结果
|
||||
out_path = os.path.expanduser('~/.hermes/skills/trading/quant-factor-mining/artifacts/us_intraday_latest.json')
|
||||
os.makedirs(os.path.dirname(out_path), exist_ok=True)
|
||||
with open(out_path, 'w') as f:
|
||||
json.dump({'date': datetime.now().isoformat(), 'results': results[:8]}, f, ensure_ascii=False, indent=2)
|
||||
|
||||
# 输出报告
|
||||
date_str = datetime.now().strftime('%Y-%m-%d')
|
||||
print(f'🔥 美股日内交易盘前筛选 {date_str}')
|
||||
print('=' * 55)
|
||||
print(f'{"股票":<10}{"现价":>8}{"ADR%":>7}{"量比":>6}{"换手":>6}{"评分":>6}')
|
||||
print('-' * 55)
|
||||
|
||||
for r in results[:8]:
|
||||
emoji = '🟢' if r['score'] > 60 else ('🟡' if r['score'] > 40 else '🔴')
|
||||
print(f'{emoji}{r["ticker"]:<9}{r["price"]:>8.2f}{r["avg_adr"]:>7.2f}{r["volume_ratio"]:>6.2f}{r["turnover_rate"]:>6.2f}{r["score"]:>6.1f}')
|
||||
|
||||
print()
|
||||
print('📋 TOP 3 策略建议:')
|
||||
for r in results[:3]:
|
||||
if r['avg_adr'] > 4:
|
||||
strategy = '动量突破'
|
||||
elif r['avg_adr'] > 3:
|
||||
strategy = '趋势跟踪'
|
||||
else:
|
||||
strategy = 'VWAP回归'
|
||||
print(f' {r["ticker"]}: {strategy} | 止损-1.5% | 量比{r["volume_ratio"]:.1f}')
|
||||
Reference in New Issue
Block a user