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,468 @@
#!/usr/bin/env python3
"""
信号历史数据库 - 记录所有交易信号
用法:
python3 signal_db.py log '<原始信号文本>'
python3 signal_db.py history [--trader NAME] [--symbol BTC] [--days 7] [--limit 20]
python3 signal_db.py stats
python3 signal_db.py traders
"""
import sqlite3
import os
import re
import json
import sys
import time
from datetime import datetime, timedelta
DB_PATH = os.path.expanduser("~/.hermes/trading/signal_history.db")
def get_conn():
os.makedirs(os.path.dirname(DB_PATH), exist_ok=True)
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
def init_db():
conn = get_conn()
conn.execute("""
CREATE TABLE IF NOT EXISTS signals (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp REAL NOT NULL,
time_str TEXT NOT NULL,
trader TEXT,
symbol TEXT,
side TEXT,
leverage INTEGER,
raw_size REAL,
raw_unit TEXT,
entry_price REAL,
current_price REAL,
margin REAL,
margin_unit TEXT,
margin_mode TEXT,
pnl REAL,
pnl_pct REAL,
leverage_change TEXT,
raw_text TEXT NOT NULL,
outcome TEXT DEFAULT 'pending',
outcome_time REAL,
outcome_detail TEXT
)
""")
conn.execute("""
CREATE INDEX IF NOT EXISTS idx_signals_time ON signals(timestamp DESC)
""")
conn.execute("""
CREATE INDEX IF NOT EXISTS idx_signals_trader ON signals(trader)
""")
conn.execute("""
CREATE INDEX IF NOT EXISTS idx_signals_symbol ON signals(symbol)
""")
conn.commit()
conn.close()
def extract_trader(text):
"""Extract trader name from signal text.
Common patterns:
- 【熬鹰资本】 (standalone 【name】 on its own line, no colon)
- 【交易员】xxx
- 【老师】xxx
- 交易员: xxx
- 来自xxx:
- [xxx] at the beginning
- @username
- Name followed by colon (e.g. "张三: BTC做多")
- Name followed by signal keywords
"""
# Priority 1: Standalone 【name】 on its own line (no colon after)
# This matches 【熬鹰资本】 but NOT 【币种】: xxx
standalone = re.search(r'^【([^】]{1,20})】\s*$', text, re.MULTILINE)
if standalone:
return standalone.group(1).strip()
patterns = [
r'【交易员】\s*(.+?)(?:\n|$|【)',
r'【老师】\s*(.+?)(?:\n|$|【)',
r'【来源】\s*(.+?)(?:\n|$|【)',
r'【策略】\s*(.+?)(?:\n|$|【)',
r'交易员[:]\s*(.+?)(?:\n|$)',
r'老师[:]\s*(.+?)(?:\n|$)',
r'来源[:]\s*(.+?)(?:\n|$)',
r'策略师[:]\s*(.+?)(?:\n|$)',
r'^\[([^\]]+)\]', # [TraderName] at start
r'^(@\w+)', # @username at start
r'^(\S+?)\s*[:]\s*(?:【|BTC|ETH|做多|做空|开多|开空)', # Name: signal
r'^(\S{2,10})\s+(?:【|BTC|ETH|做多|做空|开多|开空)', # Name signal (no colon)
]
for p in patterns:
m = re.search(p, text, re.MULTILINE)
if m:
name = m.group(1).strip()
# Filter out non-name matches
if len(name) > 1 and len(name) < 30 and not re.match(r'^[\d.]+$', name):
return name
return None
def extract_signal_fields(text):
"""Parse signal text for key fields."""
result = {'trader': extract_trader(text)}
# Symbol - multiple patterns
m = re.search(r'(?:【币种】|币种[:]\s*)(\w+)', text)
if not m:
m = re.search(r'([A-Z]{2,10})USDT', text)
if not m:
# Bare symbol before direction keywords (e.g. "ETH做空", "BTC 开多")
m = re.search(r'\b([A-Z]{2,10})\s*(?:做多|做空|开多|开空|做多|做空|long|short)', text, re.IGNORECASE)
if m:
raw = m.group(1).upper().replace("USDT", "").replace("/USDT", "").replace(":USDT", "")
if len(raw) >= 2:
result['symbol'] = raw
# Side
if re.search(r'(做空|卖出|short|sell|空单|开空)', text, re.IGNORECASE):
result['side'] = 'short'
elif re.search(r'(做多|买入|long|buy|多单|开多)', text, re.IGNORECASE):
result['side'] = 'long'
# Leverage from field
m = re.search(r'(?:【币种】|币种[:]\s*)[^\n]*?(\d+)\s*[xX倍]', text)
if not m:
m = re.search(r'(\d+)\s*[xX倍]', text)
result['leverage'] = int(m.group(1)) if m else None
# Size
m = re.search(r'(?:【仓位】|仓位[:]\s*)([\d,.]+)\s*(\w+)', text)
if m:
result['raw_size'] = float(m.group(1).replace(",", ""))
result['raw_unit'] = m.group(2)
# Entry price
m = re.search(r'【开仓价】\s*[:]?\s*([\d,.]+)', text)
if m:
result['entry_price'] = float(m.group(1).replace(",", ""))
# Current price
m = re.search(r'【当前价】\s*[:]?\s*([\d,.]+)', text)
if m:
result['current_price'] = float(m.group(1).replace(",", ""))
# Margin
m = re.search(r'【保证金】\s*[:]?\s*([\d,.]+)\s*(\w+)', text)
if m:
result['margin'] = float(m.group(1).replace(",", ""))
result['margin_unit'] = m.group(2)
# Margin mode (全仓/逐仓)
m = re.search(r'(全仓|逐仓)', text)
if m:
result['margin_mode'] = m.group(1)
# PnL
m = re.search(r'【收益额】\s*[:]?\s*([-\d,.]+)\s*(\w+)', text)
if m:
result['pnl'] = float(m.group(1).replace(",", ""))
m = re.search(r'【收益额】\s*[:]?\s*[-\d,.]+\s*\w+\(([-\d.]+)%\)', text)
if m:
result['pnl_pct'] = float(m.group(1))
# Leverage change (e.g. "5→10")
m = re.search(r'修改了杠杆\s*(\d+)\s*[→>→]\s*(\d+)', text)
if m:
result['leverage_change'] = f"{m.group(1)}{m.group(2)}"
# Is close signal
result['is_close'] = bool(re.search(r'(平仓|止盈|止损|close|全平)', text, re.IGNORECASE))
return result
def log_signal(raw_text):
"""Log a signal to the database."""
init_db()
fields = extract_signal_fields(raw_text)
conn = get_conn()
now = time.time()
time_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
conn.execute("""
INSERT INTO signals (timestamp, time_str, trader, symbol, side, leverage,
raw_size, raw_unit, entry_price, current_price,
margin, margin_unit, margin_mode, pnl, pnl_pct,
leverage_change, raw_text)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
now, time_str,
fields.get('trader'),
fields.get('symbol'),
fields.get('side'),
fields.get('leverage'),
fields.get('raw_size'),
fields.get('raw_unit'),
fields.get('entry_price'),
fields.get('current_price'),
fields.get('margin'),
fields.get('margin_unit'),
fields.get('margin_mode'),
fields.get('pnl'),
fields.get('pnl_pct'),
fields.get('leverage_change'),
raw_text,
))
signal_id = conn.execute("SELECT last_insert_rowid()").fetchone()[0]
conn.commit()
conn.close()
return {
'id': signal_id,
'time': time_str,
'trader': fields.get('trader'),
'symbol': fields.get('symbol'),
'side': fields.get('side'),
'leverage': fields.get('leverage'),
}
def update_outcome(signal_id, outcome, detail=""):
"""Update signal outcome (confirmed/cancelled/expired)."""
conn = get_conn()
conn.execute("""
UPDATE signals SET outcome=?, outcome_time=?, outcome_detail=?
WHERE id=?
""", (outcome, time.time(), detail, signal_id))
conn.commit()
conn.close()
def find_latest_signal_id(symbol):
"""Find the most recent pending signal ID for a symbol."""
conn = get_conn()
row = conn.execute("""
SELECT id FROM signals WHERE symbol=? AND outcome='pending'
ORDER BY timestamp DESC LIMIT 1
""", (symbol,)).fetchone()
conn.close()
return row['id'] if row else None
def query_history(trader=None, symbol=None, days=7, limit=20):
"""Query signal history with filters."""
init_db()
conn = get_conn()
conditions = ["timestamp > ?"]
params = [time.time() - days * 86400]
if trader:
conditions.append("trader LIKE ?")
params.append(f"%{trader}%")
if symbol:
conditions.append("symbol LIKE ?")
params.append(f"%{symbol}%")
where = " AND ".join(conditions)
rows = conn.execute(f"""
SELECT * FROM signals WHERE {where}
ORDER BY timestamp DESC LIMIT ?
""", params + [limit]).fetchall()
conn.close()
return [dict(r) for r in rows]
def get_trader_stats():
"""Get stats per trader."""
init_db()
conn = get_conn()
rows = conn.execute("""
SELECT
trader,
COUNT(*) as total,
SUM(CASE WHEN outcome='confirmed' THEN 1 ELSE 0 END) as confirmed,
SUM(CASE WHEN outcome='cancelled' THEN 1 ELSE 0 END) as cancelled,
SUM(CASE WHEN outcome='pending' THEN 1 ELSE 0 END) as pending,
SUM(CASE WHEN outcome='expired' THEN 1 ELSE 0 END) as expired,
GROUP_CONCAT(DISTINCT symbol) as symbols
FROM signals
GROUP BY trader
ORDER BY total DESC
""").fetchall()
conn.close()
return [dict(r) for r in rows]
def get_summary_stats():
"""Get overall summary stats."""
init_db()
conn = get_conn()
total = conn.execute("SELECT COUNT(*) as c FROM signals").fetchone()['c']
today = conn.execute(
"SELECT COUNT(*) as c FROM signals WHERE timestamp > ?",
(time.time() - 86400,)
).fetchone()['c']
by_outcome = conn.execute("""
SELECT outcome, COUNT(*) as c FROM signals GROUP BY outcome
""").fetchall()
by_side = conn.execute("""
SELECT side, COUNT(*) as c FROM signals WHERE side IS NOT NULL GROUP BY side
""").fetchall()
top_symbols = conn.execute("""
SELECT symbol, COUNT(*) as c FROM signals
WHERE symbol IS NOT NULL
GROUP BY symbol ORDER BY c DESC LIMIT 5
""").fetchall()
conn.close()
return {
'total': total,
'today': today,
'by_outcome': {r['outcome']: r['c'] for r in by_outcome},
'by_side': {r['side']: r['c'] for r in by_side},
'top_symbols': [(r['symbol'], r['c']) for r in top_symbols],
}
def format_history(signals):
"""Format history for display."""
if not signals:
return "📭 暂无信号记录"
lines = ["📋 **信号历史记录**\n"]
for s in signals:
side_cn = "做多" if s['side'] == 'long' else ("做空" if s['side'] == 'short' else "?")
outcome_emoji = {
'confirmed': '', 'cancelled': '', 'pending': '', 'expired': ''
}.get(s['outcome'], '')
trader = s['trader'] or '未知'
lev = f"{s['leverage']}x" if s['leverage'] else '?x'
# Extra info
extra = []
if s.get('entry_price'):
extra.append(f"入场{s['entry_price']}")
if s.get('pnl'):
pnl_str = f"{s['pnl']:+,.0f}"
if s.get('pnl_pct'):
pnl_str += f"({s['pnl_pct']:+.1f}%)"
extra.append(f"盈亏{pnl_str}")
if s.get('margin'):
extra.append(f"保证金{s['margin']:,.0f}")
if s.get('margin_mode'):
extra.append(s['margin_mode'])
if s.get('leverage_change'):
extra.append(f"杠杆{s['leverage_change']}")
extra_str = " | " + " ".join(extra) if extra else ""
lines.append(
f"{outcome_emoji} #{s['id']} | {s['time_str']} | "
f"👤{trader} | {s['symbol'] or '?'} {side_cn} | "
f"{lev}{extra_str}"
)
return "\n".join(lines)
def format_stats(stats):
"""Format stats for display."""
lines = ["📊 **信号统计**\n"]
lines.append(f"总计: {stats['total']}")
lines.append(f"今日: {stats['today']}\n")
if stats['by_outcome']:
lines.append("**按结果:**")
for k, v in stats['by_outcome'].items():
emoji = {'confirmed': '', 'cancelled': '', 'pending': '', 'expired': ''}.get(k, '')
lines.append(f" {emoji} {k}: {v}")
if stats['by_side']:
lines.append("\n**按方向:**")
for k, v in stats['by_side'].items():
cn = "做多" if k == 'long' else "做空"
lines.append(f" {cn}: {v}")
if stats['top_symbols']:
lines.append("\n**热门币种:**")
for sym, cnt in stats['top_symbols']:
lines.append(f" {sym}: {cnt}")
return "\n".join(lines)
def format_traders(traders):
"""Format trader stats for display."""
if not traders:
return "📭 暂无交易员数据"
lines = ["👤 **交易员统计**\n"]
for t in traders:
name = t['trader'] or '未知'
lines.append(
f"**{name}**: {t['total']}条信号 | "
f"{t['confirmed']}{t['cancelled']}{t['pending']} | "
f"币种: {t['symbols'] or '-'}"
)
return "\n".join(lines)
def main():
if len(sys.argv) < 2:
print("用法: signal_db.py <log|history|stats|traders|update> [args]")
sys.exit(1)
action = sys.argv[1]
if action == "log":
if len(sys.argv) < 3:
print("用法: signal_db.py log '<raw_text>'")
sys.exit(1)
raw_text = sys.argv[2]
result = log_signal(raw_text)
print(json.dumps(result, ensure_ascii=False))
elif action == "history":
import argparse
# Simple arg parsing
trader = symbol = None
days = 7
limit = 20
for i in range(2, len(sys.argv)):
if sys.argv[i] == "--trader" and i + 1 < len(sys.argv):
trader = sys.argv[i + 1]
elif sys.argv[i] == "--symbol" and i + 1 < len(sys.argv):
symbol = sys.argv[i + 1]
elif sys.argv[i] == "--days" and i + 1 < len(sys.argv):
days = int(sys.argv[i + 1])
elif sys.argv[i] == "--limit" and i + 1 < len(sys.argv):
limit = int(sys.argv[i + 1])
signals = query_history(trader, symbol, days, limit)
print(format_history(signals))
elif action == "stats":
stats = get_summary_stats()
print(format_stats(stats))
elif action == "traders":
traders = get_trader_stats()
print(format_traders(traders))
elif action == "update":
if len(sys.argv) < 4:
print("用法: signal_db.py update <signal_id> <outcome> [detail]")
sys.exit(1)
signal_id = int(sys.argv[2])
outcome = sys.argv[3]
detail = sys.argv[4] if len(sys.argv) > 4 else ""
update_outcome(signal_id, outcome, detail)
print(f"✅ Updated signal #{signal_id}{outcome}")
else:
print(f"Unknown action: {action}")
sys.exit(1)
if __name__ == "__main__":
main()