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:
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
TG信号监听器(no_agent模式):
|
||||
从Telegram forwarder数据库读取新信号→调advisor脚本→格式化含📐→推QQ
|
||||
|
||||
用法: python3 tg_signal_monitor.py
|
||||
配合cron: */1 * * * * python3 ~/.hermes/skills/trading/okx-auto-position/scripts/tg_signal_monitor.py
|
||||
"""
|
||||
|
||||
import sqlite3
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
# Paths
|
||||
FORWARDER_DB = "/tmp/forward.db" # TG forwarder DB (docker cp出来)
|
||||
STATE_FILE = Path.home() / ".hermes/trading/.signal_monitor_state"
|
||||
SKILL_DIR = Path.home() / ".hermes/skills/trading/okx-auto-position"
|
||||
ADVISOR_SCRIPT = SKILL_DIR / "scripts" / "okx_position_advisor.py"
|
||||
FORMAT_SCRIPT = SKILL_DIR / "scripts" / "format_signal.py"
|
||||
QQ_PUSH = Path.home() / ".hermes/scripts/push_to_qq.sh"
|
||||
SIGNAL_HISTORY_DB = Path.home() / ".hermes/trading/signal_history.db"
|
||||
|
||||
def get_last_msg_id():
|
||||
"""Read last processed message ID"""
|
||||
if STATE_FILE.exists():
|
||||
return int(STATE_FILE.read_text().strip())
|
||||
return 0
|
||||
|
||||
def save_last_msg_id(msg_id):
|
||||
"""Save last processed message ID"""
|
||||
STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
STATE_FILE.write_text(str(msg_id))
|
||||
|
||||
def parse_signal(text):
|
||||
"""Parse TG signal text, extract key fields"""
|
||||
# Extract trader name
|
||||
trader_match = re.search(r'【([^】]{1,20})】', text)
|
||||
trader = trader_match.group(1) if trader_match else "未知"
|
||||
|
||||
# Extract fields
|
||||
fields = {}
|
||||
patterns = {
|
||||
'symbol': r'【币种】\s*[::]?\s*(\S+)',
|
||||
'side': r'【方向】\s*[::]?\s*(做多|做空)',
|
||||
'leverage': r'【杠杆】\s*[::]?\s*(\d+)',
|
||||
'size': r'【仓位大小】\s*[::]?\s*([\d,.]+)',
|
||||
'value': r'【仓位价值】\s*[::]?\s*\$?\s*([\d,.]+)',
|
||||
'entry': r'【开仓价】\s*[::]?\s*([\d,.]+)',
|
||||
'current': r'【当前价】\s*[::]?\s*([\d,.]+)',
|
||||
'pnl': r'【未实现盈亏】\s*[::]?\s*([-\d,.]+)',
|
||||
}
|
||||
|
||||
for key, pattern in patterns.items():
|
||||
match = re.search(pattern, text)
|
||||
if match:
|
||||
fields[key] = match.group(1).replace(',', '')
|
||||
|
||||
return trader, fields
|
||||
|
||||
def classify_signal(fields, current_positions):
|
||||
"""Classify as A(加仓) or C(新开仓)"""
|
||||
symbol = fields.get('symbol', '').replace('USDT', '').replace('/USDT', '').strip()
|
||||
for pos in current_positions:
|
||||
if symbol.upper() in pos['symbol'].upper():
|
||||
return 'A' # 加仓
|
||||
return 'C' # 新开仓
|
||||
|
||||
def run_format_script(fields, trader, signal_type):
|
||||
"""Run format_signal.py and return the formatted message"""
|
||||
symbol = fields.get('symbol', '').replace('USDT', '').replace('/USDT', '').strip()
|
||||
side = 'long' if fields.get('side', '').startswith('做多') else 'short'
|
||||
leverage = fields.get('leverage', '10')
|
||||
size = fields.get('size', '0')
|
||||
value = fields.get('value', '$0')
|
||||
entry = fields.get('entry', '0')
|
||||
pnl = fields.get('pnl', '0')
|
||||
|
||||
if not value.startswith('$'):
|
||||
value = f'${value}'
|
||||
|
||||
cmd = [
|
||||
'python3', str(FORMAT_SCRIPT),
|
||||
'--symbol', symbol,
|
||||
'--side', side,
|
||||
'--leverage', leverage,
|
||||
'--trader', trader,
|
||||
'--trader-pos', f'{size} {symbol}',
|
||||
'--trader-value', value,
|
||||
'--trader-entry', entry,
|
||||
'--trader-pnl', pnl,
|
||||
'--signal-type', signal_type,
|
||||
]
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
else:
|
||||
return f"⚠️ format_signal.py 错误: {result.stderr.strip()}"
|
||||
except subprocess.TimeoutExpired:
|
||||
return "⚠️ format_signal.py 超时"
|
||||
except Exception as e:
|
||||
return f"⚠️ 执行错误: {e}"
|
||||
|
||||
def push_to_qq(message):
|
||||
"""Push message to QQ via push_to_qq.sh"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['bash', str(QQ_PUSH), message],
|
||||
capture_output=True, text=True, timeout=15
|
||||
)
|
||||
return result.returncode == 0
|
||||
except:
|
||||
return False
|
||||
|
||||
def log_to_db(trader, symbol, side, leverage, raw_text, outcome='pushed'):
|
||||
"""Log signal to history database"""
|
||||
try:
|
||||
conn = sqlite3.connect(str(SIGNAL_HISTORY_DB))
|
||||
conn.execute("""
|
||||
INSERT INTO signals (timestamp, trader, symbol, side, leverage, raw_text, outcome)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""", (datetime.now().isoformat(), trader, symbol, side, leverage, raw_text, outcome))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
def main():
|
||||
# Check if forwarder DB exists
|
||||
if not Path(FORWARDER_DB).exists():
|
||||
# Try to copy from docker
|
||||
try:
|
||||
subprocess.run(
|
||||
['docker', 'cp', 'telegram-forwarder:/app/db/forward.db', FORWARDER_DB],
|
||||
capture_output=True, timeout=10
|
||||
)
|
||||
except:
|
||||
print("❌ 无法获取forwarder DB")
|
||||
return
|
||||
|
||||
last_id = get_last_msg_id()
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(FORWARDER_DB)
|
||||
conn.row_factory = sqlite3.Row
|
||||
|
||||
# Get new messages from the forwarder
|
||||
cursor = conn.execute("""
|
||||
SELECT id, message_text, created_at
|
||||
FROM forwarded_messages
|
||||
WHERE id > ? AND chat_id = '-1003966251111'
|
||||
ORDER BY id ASC
|
||||
LIMIT 10
|
||||
""", (last_id,))
|
||||
|
||||
messages = cursor.fetchall()
|
||||
conn.close()
|
||||
|
||||
if not messages:
|
||||
return # No new messages, silent exit
|
||||
|
||||
for msg in messages:
|
||||
text = msg['message_text'] or ''
|
||||
msg_id = msg['id']
|
||||
|
||||
# Skip non-signal messages
|
||||
if '【币种】' not in text and '【方向】' not in text:
|
||||
save_last_msg_id(msg_id)
|
||||
continue
|
||||
|
||||
# Parse signal
|
||||
trader, fields = parse_signal(text)
|
||||
|
||||
if not fields.get('symbol') or not fields.get('side'):
|
||||
save_last_msg_id(msg_id)
|
||||
continue
|
||||
|
||||
# Classify (simplified - always treat as new for now)
|
||||
signal_type = 'C'
|
||||
|
||||
# Run format_signal.py
|
||||
message = run_format_script(fields, trader, signal_type)
|
||||
|
||||
if message and '⚠️' not in message:
|
||||
# Push to QQ
|
||||
success = push_to_qq(message)
|
||||
|
||||
# Log to DB
|
||||
symbol = fields.get('symbol', '').replace('USDT', '').strip()
|
||||
side = 'long' if fields.get('side', '').startswith('做多') else 'short'
|
||||
log_to_db(trader, symbol, side, fields.get('leverage', '10'), text,
|
||||
'pushed' if success else 'push_failed')
|
||||
|
||||
save_last_msg_id(msg_id)
|
||||
|
||||
except sqlite3.OperationalError as e:
|
||||
print(f"❌ DB错误: {e}")
|
||||
except Exception as e:
|
||||
print(f"❌ 错误: {e}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user