Files
Hermes-Skills/okx-auto-position/scripts/tg_signal_monitor.py
T
mike 657dc41c46 Initial commit: Trading skills collection
- OKX交易自动化 (okx-auto-position, okx-crypto, okx-exchange)
- 交易信号处理 (signal-confirmation-templates, trading-signal-aggregator)
- 量化因子挖掘 (quant-factor-mining)
- 长桥集成 (longbridge-cli, longbridge-python-sdk)
- 六合彩分析 (lottery-hk)
- 股息投资 (dividend-investing, dividend-scanner)
- 日内交易 (intraday-trading)
- 同花顺 (tonghuashun)
2026-07-05 02:39:41 -04:00

209 lines
7.0 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
"""
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()