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:
Executable
+279
@@ -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=== 完成 ===")
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# 港股日内交易 CLI runner
|
||||
# 用法: bash hk_intraday_cli_runner.sh
|
||||
# 走 ~/.local/bin/longbridge CLI (不用 Python SDK), 走 openapi.longbridge.com (AWS 海外)
|
||||
set -e
|
||||
|
||||
LOG=/tmp/hk_intraday_cli.log
|
||||
SIGNAL_FILE=/tmp/hk_intraday_signals.json
|
||||
|
||||
echo "=== HK 日内 CLI runner @ $(date) ===" > $LOG
|
||||
|
||||
# 0. 确保 hosts 干净 (cn 域名指向 AWS 海外 IP 是有毒的, 真实 DNS 解析即可)
|
||||
# 真实 DNS: openapi.longbridge.com → 18.163.160.163 (AWS 香港)
|
||||
|
||||
# 1. 用 proxychains + CLI 查持仓 + 余额 + 信号生成
|
||||
LONGBRIDGE_REGION=ap LONGBRIDGE_TRADE_ENABLED=true \
|
||||
proxychains4 -f ~/.proxychains/proxychains.conf \
|
||||
~/.local/bin/longbridge --profile lb_real balance 2>&1 | tee -a $LOG
|
||||
|
||||
# 2. 列出当前订单
|
||||
LONGBRIDGE_REGION=ap \
|
||||
proxychains4 -f ~/.proxychains/proxychains.conf \
|
||||
~/.local/bin/longbridge --profile lb_real orders 2>&1 | tee -a $LOG
|
||||
|
||||
# 3. 给个示例: 如果有持仓, 显示; 没持仓, 给信号
|
||||
# (实际信号生成+下单逻辑,需要跟 intraday-trading skill 的 strategy 对接)
|
||||
# 先跑通 CLI 路径, 信号生成后期补
|
||||
|
||||
echo "" >> $LOG
|
||||
echo "=== CLI runner 完成 @ $(date) ===" >> $LOG
|
||||
cat $LOG
|
||||
Executable
+279
@@ -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 / USD cash (不能用 buy_power,要用 cash)
|
||||
hkd_cash = 0
|
||||
usd_cash = 0
|
||||
if bals:
|
||||
for b in bals:
|
||||
cur = str(b.currency).upper()
|
||||
# 优先用 cash_available, fallback 用 buy_power
|
||||
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" [{cur}] cash: {cash:.0f}")
|
||||
print(f"\n💰 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/us_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/us_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=1, 港股=100/200/500/1000/2000
|
||||
lot_size = 1 if ticker.endswith('.US') else 100
|
||||
# 选对应货币的 cash
|
||||
if ticker.endswith('.US'):
|
||||
cash = usd_cash
|
||||
else:
|
||||
cash = hkd_cash
|
||||
target_value = cash * 0.20 # 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)
|
||||
|
||||
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=price,
|
||||
)
|
||||
order_id = resp.order_id
|
||||
print(f" ⏳ 已提交: {order_id}")
|
||||
|
||||
# 反查 status (700 RMB 教训: order_id ≠ 成交)
|
||||
import time as _t
|
||||
status = 'Unknown'
|
||||
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}")
|
||||
exec_price = float(detail.executed_price or price)
|
||||
exec_qty = int(detail.executed_quantity or shares)
|
||||
elif status == 'Rejected':
|
||||
print(f" ❌ 被拒: {order_id} | status={status} | 跳过")
|
||||
continue
|
||||
elif status == 'Canceled':
|
||||
print(f" 🚫 已撤: {order_id}")
|
||||
continue
|
||||
else: # New / NotReported (港股日单未成交)
|
||||
print(f" ⚠️ 已挂单未成交: {order_id} (status={status})")
|
||||
exec_price = price
|
||||
exec_qty = shares
|
||||
|
||||
# 记录
|
||||
entries[ticker] = {
|
||||
'side': 'buy',
|
||||
'entry_price': 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=== 完成 ===")
|
||||
Reference in New Issue
Block a user