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:
Hermes Skills Manager
2026-07-05 02:31:15 -04:00
commit 6770bc9b9d
908 changed files with 239614 additions and 0 deletions
@@ -0,0 +1,411 @@
#!/usr/bin/env python3
"""
交易信号处理器 - 一体化脚本
用法:
python3 trade_signal_handler.py signal "【币种】BTCUSDT|永续|10x\n【方向】做多\n【仓位】0.5 BTC"
python3 trade_signal_handler.py confirm BTC
python3 trade_signal_handler.py cancel BTC
python3 trade_signal_handler.py status
"""
import re
import os
import sys
import json
import time
import glob
import subprocess
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
ADVISOR_SCRIPT = os.path.join(SCRIPT_DIR, "okx_position_advisor.py")
SIGNAL_DB_SCRIPT = os.path.join(SCRIPT_DIR, "signal_db.py")
PENDING_DIR = os.path.expanduser("~/.hermes/trading/pending")
os.makedirs(PENDING_DIR, exist_ok=True)
def log_signal_to_db(signal_text):
"""Log signal to history database, return signal_id or None"""
try:
result = subprocess.run(
[sys.executable, SIGNAL_DB_SCRIPT, "log", signal_text],
capture_output=True, text=True, timeout=10,
)
if result.returncode == 0:
return json.loads(result.stdout).get("id")
except Exception:
pass
return None
def update_signal_outcome(signal_id, outcome, detail=""):
"""Update signal outcome in database"""
if not signal_id:
return
try:
subprocess.run(
[sys.executable, SIGNAL_DB_SCRIPT, "update", str(signal_id), outcome, detail],
capture_output=True, text=True, timeout=10,
)
except Exception:
pass
def parse_signal(text):
"""Parse trading signal text, extract symbol/direction/leverage/size"""
result = {}
# 币种: BTCUSDT|永续|10x or 【币种】BTCUSDT
symbol_match = re.search(r'(?:【币种】|币种[:]\s*)(\w+)', text)
if not symbol_match:
symbol_match = re.search(r'([A-Z]{2,10})USDT', text)
if symbol_match:
raw = symbol_match.group(1).upper()
raw = raw.replace("USDT", "").replace("/USDT", "").replace(":USDT", "")
result["symbol"] = raw
else:
return None
# 方向
if re.search(r'(做空|卖出|short|sell|空单|开空)', text, re.IGNORECASE):
result["side"] = "short"
elif re.search(r'(做多|买入|long|buy|多单|开多)', text, re.IGNORECASE):
result["side"] = "long"
else:
return None
# 杠杆
lev_match = re.search(r'(\d+)\s*[xX倍]', text)
result["leverage"] = int(lev_match.group(1)) if lev_match else 10
# 仓位数量
size_match = re.search(r'(?:【仓位】|仓位[:]\s*)([\d,.]+)\s*(\w+)', text)
if size_match:
result["raw_size"] = float(size_match.group(1).replace(",", ""))
result["raw_unit"] = size_match.group(2)
# 是否加仓/平仓
result["is_add"] = bool(re.search(r'(加仓|追仓)', text))
result["is_close"] = bool(re.search(r'(平仓|止盈|止损|close|全平)', text, re.IGNORECASE))
return result
def save_pending(symbol, rec_json, signal_text, signal_id=None):
"""Save pending recommendation to file"""
path = os.path.join(PENDING_DIR, f"{symbol.upper()}.json")
data = {
"symbol": symbol.upper(),
"rec": rec_json,
"signal": signal_text,
"signal_id": signal_id,
"timestamp": time.time(),
"time_str": time.strftime("%Y-%m-%d %H:%M:%S"),
}
with open(path, "w") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
return path
def load_pending(symbol):
"""Load pending recommendation"""
path = os.path.join(PENDING_DIR, f"{symbol.upper()}.json")
if not os.path.exists(path):
return None
with open(path) as f:
return json.load(f)
def remove_pending(symbol):
"""Remove pending recommendation"""
path = os.path.join(PENDING_DIR, f"{symbol.upper()}.json")
if os.path.exists(path):
os.remove(path)
def run_advisor(symbol, side, leverage):
"""Run the advisor script and return JSON result"""
cmd = [
sys.executable, ADVISOR_SCRIPT,
"--symbol", symbol,
"--side", side,
"--leverage", str(leverage),
"--json",
]
env = os.environ.copy()
# Source bashrc to get OKX credentials
result = subprocess.run(
["bash", "-c", f"source ~/.bashrc && {' '.join(cmd)}"],
capture_output=True, text=True, timeout=30,
)
if result.returncode != 0:
return {"error": result.stderr.strip() or "Advisor script failed"}
try:
return json.loads(result.stdout)
except json.JSONDecodeError:
return {"error": f"Invalid JSON output: {result.stdout[:200]}"}
def execute_trade(rec_json):
"""Execute the trade using the advisor script"""
import shlex
rec_str = json.dumps(rec_json, ensure_ascii=False)
symbol = rec_json.get("symbol", "").split("/")[0]
side = rec_json.get("side", "")
cmd = f"source ~/.bashrc && python3 {ADVISOR_SCRIPT} --symbol {symbol} --side {side} --execute --json --rec-json {shlex.quote(rec_str)}"
result = subprocess.run(
["bash", "-c", cmd],
capture_output=True, text=True, timeout=60,
)
if result.returncode != 0:
return {"error": result.stderr.strip() or "Execution failed"}
try:
return json.loads(result.stdout)
except json.JSONDecodeError:
return {"raw": result.stdout.strip()}
def format_recommendation(rec, signal_text=""):
"""Format recommendation for user display"""
symbol = rec.get("symbol", "?")
side = rec.get("side", "?")
side_cn = "做多" if side in ("long", "buy") else "做空"
leverage = rec.get("leverage", 10)
contracts = rec.get("contracts", 0)
entry = rec.get("entry_price", 0)
tp = rec.get("tp_price", 0)
sl = rec.get("sl_price", 0)
margin = rec.get("margin_used", 0)
balance = rec.get("balance", 0)
margin_pct = rec.get("margin_pct", 0)
tp_pct = rec.get("tp_pct", 0) # 标的价格变动%
sl_pct = rec.get("sl_pct", 0)
tp_pnl = rec.get("tp_pnl", 0)
sl_pnl = rec.get("sl_pnl", 0)
# 保证金收益率
tp_margin_pct = (tp_pnl / margin * 100) if margin > 0 else 0
sl_margin_pct = (sl_pnl / margin * 100) if margin > 0 else 0
liq_price = rec.get("liq_price", 0)
liq_pct = rec.get("liq_pct", 0)
rr = rec.get("rr_ratio", 0)
lines = [
f"📊 **{symbol}USDT {side_cn}** - 仓位推荐",
"",
f"💰 可用余额: {balance:.2f} USDT",
f"📈 当前价: **{entry}**",
"",
"**开仓方案:**",
f"• 方向: {side_cn}",
f"• 杠杆: **{leverage}x**",
f"• 张数: **{contracts}张**",
f"• 保证金: {margin:.2f} USDT ({margin_pct:.0f}%)",
"",
"**止盈止损:**",
f"• 🎯 止盈: **{tp}** (保证金+{tp_margin_pct:.0f}%) → +{tp_pnl:.2f} USDT",
f"• 🛑 止损: **{sl}** (保证金-{sl_margin_pct:.0f}%) → -{sl_pnl:.2f} USDT",
f"• 📐 盈亏比: **{rr:.1f}:1**",
]
if liq_price:
lines.append(f"• ⚠️ 清算价: {liq_price} (距离 {liq_pct:.1f}%)")
lines.extend([
"",
"回复 **Y** 确认下单",
"回复 **N** 取消",
])
return "\n".join(lines)
def format_execution_result(result, symbol, side):
"""Format execution result for user display"""
if "error" in result:
return f"❌ **{symbol}USDT 下单失败**\n\n{result['error']}"
side_cn = "做多" if side in ("long", "buy") else "做空"
lines = [f"✅ **{symbol}USDT {side_cn} 开仓成功**"]
# Parse steps
for step in result.get('steps', []):
if step['step'] == 'leverage':
if step['status'] == 'ok':
lines.append("✅ 杠杆设置成功")
else:
lines.append(f"⚠️ 杠杆: {step.get('msg', '')}")
elif step['step'] == 'order':
if step['status'] == 'ok':
lines.append(f"✅ 下单成功 (ID: {step['order_id']})")
else:
lines.append(f"❌ 下单失败: {step.get('msg', '')}")
return '\n'.join(lines)
elif step['step'] == 'tp_sl':
if step['status'] == 'ok':
lines.append(f"✅ 止盈止损设置成功 (ID: {step['algo_id']})")
else:
lines.append(f"⚠️ 止盈止损: {step.get('msg', '')}")
# Position info
pos = result.get('position')
if pos:
pnl_emoji = "🟢" if pos.get('pnl', 0) >= 0 else "🔴"
lines.extend([
"",
"📊 **持仓确认:**",
f"• 方向: {side_cn}",
f"• 数量: {pos.get('contracts', '?')}",
f"• 入场价: **{pos.get('entry', '?')}**",
f"{pnl_emoji} 浮盈: {pos.get('pnl', 0):.2f} USDT",
])
# TP/SL info
algo = result.get('algo')
if algo:
lines.extend([
"",
"🎯 **止盈止损:**",
f"• 止盈: **{algo.get('tp', '?')}**",
f"• 止损: **{algo.get('sl', '?')}**",
])
return "\n".join(lines)
def main():
if len(sys.argv) < 2:
print("用法: trade_signal_handler.py <signal|confirm|cancel|status> [args]")
sys.exit(1)
action = sys.argv[1]
if action == "signal":
if len(sys.argv) < 3:
print("用法: trade_signal_handler.py signal '<signal_text>'")
sys.exit(1)
signal_text = sys.argv[2]
parsed = parse_signal(signal_text)
if not parsed:
print(json.dumps({"error": "无法解析信号", "raw": signal_text}))
sys.exit(1)
if parsed.get("is_close"):
# 平仓信号
print(json.dumps({"action": "close", "symbol": parsed["symbol"]}))
sys.exit(0)
# 记录信号到数据库
signal_id = log_signal_to_db(signal_text)
# 计算仓位
rec = run_advisor(parsed["symbol"], parsed["side"], parsed["leverage"])
if "error" in rec:
if signal_id:
update_signal_outcome(signal_id, "error", rec["error"])
print(json.dumps(rec))
sys.exit(1)
# 保存待确认
save_pending(parsed["symbol"], rec, signal_text, signal_id)
# 输出推荐
output = {
"action": "recommend",
"symbol": parsed["symbol"],
"side": parsed["side"],
"recommendation": rec,
"display": format_recommendation(rec, signal_text),
}
print(json.dumps(output, ensure_ascii=False))
elif action == "confirm":
if len(sys.argv) < 3:
print("用法: trade_signal_handler.py confirm <SYMBOL>")
sys.exit(1)
symbol = sys.argv[2].upper().replace("USDT", "")
pending = load_pending(symbol)
if not pending:
print(json.dumps({"error": f"没有待确认的 {symbol} 交易"}))
sys.exit(1)
rec = pending["rec"]
signal_id = pending.get("signal_id")
result = execute_trade(rec)
# Only remove pending if execution succeeded
if not result.get("error"):
remove_pending(symbol)
if signal_id:
update_signal_outcome(signal_id, "confirmed", json.dumps(result, ensure_ascii=False)[:500])
else:
if signal_id:
update_signal_outcome(signal_id, "error", result.get("error", "")[:200])
output = {
"action": "executed",
"symbol": symbol,
"side": rec.get("side"),
"result": result,
"display": format_execution_result(result, symbol, rec.get("side")),
}
print(json.dumps(output, ensure_ascii=False))
elif action == "cancel":
if len(sys.argv) < 3:
print("用法: trade_signal_handler.py cancel <SYMBOL>")
sys.exit(1)
symbol = sys.argv[2].upper().replace("USDT", "")
pending = load_pending(symbol)
signal_id = pending.get("signal_id") if pending else None
remove_pending(symbol)
if signal_id:
update_signal_outcome(signal_id, "cancelled")
print(json.dumps({"action": "cancelled", "symbol": symbol}))
elif action == "status":
pending_files = glob.glob(os.path.join(PENDING_DIR, "*.json"))
if not pending_files:
print(json.dumps({"pending": []}))
else:
pending = []
for f in pending_files:
with open(f) as fh:
d = json.load(fh)
pending.append({
"symbol": d["symbol"],
"side": d["rec"].get("side"),
"time": d.get("time_str", d.get("timestamp", "unknown")),
})
print(json.dumps({"pending": pending}, ensure_ascii=False))
elif action == "history":
# Forward to signal_db.py
result = subprocess.run(
[sys.executable, SIGNAL_DB_SCRIPT, "history"] + sys.argv[2:],
capture_output=True, text=True, timeout=10,
)
print(result.stdout)
if result.returncode != 0 and result.stderr:
print(result.stderr, file=sys.stderr)
elif action == "stats":
result = subprocess.run(
[sys.executable, SIGNAL_DB_SCRIPT, "stats"],
capture_output=True, text=True, timeout=10,
)
print(result.stdout)
elif action == "traders":
result = subprocess.run(
[sys.executable, SIGNAL_DB_SCRIPT, "traders"],
capture_output=True, text=True, timeout=10,
)
print(result.stdout)
else:
print(f"Unknown action: {action}")
sys.exit(1)
if __name__ == "__main__":
main()