Files
Hermes-Skills/trading/okx-auto-position/scripts/signal_db.py
T
Hermes Skills Manager 6770bc9b9d 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
2026-07-05 02:31:15 -04:00

469 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()