- Signal parser for Telegram JSON exports - Price fetcher using Binance US API - Backtester with fee-aware simulation - Polymarket 15-min arb scanner with orderbook checking - Systemd timer every 2 min for arb alerts - Paper trade tracking - Investigation: polymarket-15min-arb.md
167 lines
5.5 KiB
Python
167 lines
5.5 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Telegram Crypto Signal Parser
|
|
Parses exported Telegram JSON chat history and extracts structured trading signals.
|
|
"""
|
|
|
|
import json
|
|
import re
|
|
import sys
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
# Signal patterns - adapt as we see more formats
|
|
PATTERNS = {
|
|
# #TICKER direction entry SL target leverage balance%
|
|
'standard': re.compile(
|
|
r'#(\w+)\s+' # ticker
|
|
r'(Long|Short)\s+' # direction
|
|
r'(?:market\s+entry!?|entry[:\s]+([0-9.]+))\s*' # entry type/price
|
|
r'SL[;:\s]+([0-9.]+)\s*' # stop loss
|
|
r'(?:Targets?|TP)[;:\s]+([0-9.,\s]+)\s*' # targets (can be multiple)
|
|
r'(?:Lev(?:erage)?[:\s]*x?([0-9.]+))?\s*' # leverage (optional)
|
|
r'(?:([0-9.]+)%?\s*balance)?', # balance % (optional)
|
|
re.IGNORECASE
|
|
),
|
|
# Simpler: #TICKER Short/Long entry SL targets
|
|
'simple': re.compile(
|
|
r'#(\w+)\s+(Long|Short)',
|
|
re.IGNORECASE
|
|
),
|
|
}
|
|
|
|
|
|
def parse_signal_text(text):
|
|
"""Parse a single message text into structured signal(s)."""
|
|
signals = []
|
|
|
|
# Try to find all ticker mentions
|
|
ticker_blocks = re.split(r'(?=#\w+USDT)', text)
|
|
|
|
for block in ticker_blocks:
|
|
if not block.strip():
|
|
continue
|
|
|
|
signal = {}
|
|
|
|
# Extract ticker
|
|
ticker_match = re.search(r'#(\w+)', block)
|
|
if not ticker_match:
|
|
continue
|
|
signal['ticker'] = ticker_match.group(1).upper()
|
|
|
|
# Extract direction
|
|
dir_match = re.search(r'\b(Long|Short)\b', block, re.IGNORECASE)
|
|
if not dir_match:
|
|
continue
|
|
signal['direction'] = dir_match.group(1).lower()
|
|
|
|
# Extract entry price (or "market")
|
|
entry_match = re.search(r'(?:entry|enter)[:\s]*([0-9.]+)', block, re.IGNORECASE)
|
|
if entry_match:
|
|
signal['entry'] = float(entry_match.group(1))
|
|
else:
|
|
signal['entry'] = 'market'
|
|
|
|
# Extract stop loss
|
|
sl_match = re.search(r'SL[;:\s]+([0-9.]+)', block, re.IGNORECASE)
|
|
if sl_match:
|
|
signal['stop_loss'] = float(sl_match.group(1))
|
|
|
|
# Extract targets (can be multiple, comma or space separated)
|
|
tp_match = re.search(r'(?:Targets?|TP)[;:\s]+([0-9.,\s]+)', block, re.IGNORECASE)
|
|
if tp_match:
|
|
targets_str = tp_match.group(1)
|
|
targets = [float(t.strip()) for t in re.findall(r'[0-9.]+', targets_str)]
|
|
signal['targets'] = targets
|
|
|
|
# Extract leverage
|
|
lev_match = re.search(r'Lev(?:erage)?[:\s]*x?([0-9.]+)', block, re.IGNORECASE)
|
|
if lev_match:
|
|
signal['leverage'] = float(lev_match.group(1))
|
|
|
|
# Extract balance percentage
|
|
bal_match = re.search(r'([0-9.]+)%?\s*balance', block, re.IGNORECASE)
|
|
if bal_match:
|
|
signal['balance_pct'] = float(bal_match.group(1))
|
|
|
|
if signal.get('ticker') and signal.get('direction'):
|
|
signals.append(signal)
|
|
|
|
return signals
|
|
|
|
|
|
def parse_telegram_export(json_path):
|
|
"""Parse a Telegram JSON export file."""
|
|
with open(json_path, 'r') as f:
|
|
data = json.load(f)
|
|
|
|
messages = data.get('messages', [])
|
|
all_signals = []
|
|
|
|
for msg in messages:
|
|
if msg.get('type') != 'message':
|
|
continue
|
|
|
|
# Get text content (can be string or list of text entities)
|
|
text_parts = msg.get('text', '')
|
|
if isinstance(text_parts, list):
|
|
text = ''.join(
|
|
p if isinstance(p, str) else p.get('text', '')
|
|
for p in text_parts
|
|
)
|
|
else:
|
|
text = text_parts
|
|
|
|
if not text or '#' not in text:
|
|
continue
|
|
|
|
# Check if it looks like a signal
|
|
if not re.search(r'(Long|Short)', text, re.IGNORECASE):
|
|
continue
|
|
|
|
signals = parse_signal_text(text)
|
|
|
|
for signal in signals:
|
|
signal['timestamp'] = msg.get('date', '')
|
|
signal['message_id'] = msg.get('id', '')
|
|
signal['raw_text'] = text[:500]
|
|
all_signals.append(signal)
|
|
|
|
return all_signals
|
|
|
|
|
|
def parse_forwarded_messages(messages_text):
|
|
"""Parse signals from forwarded message text (copy-pasted or forwarded to bot)."""
|
|
signals = parse_signal_text(messages_text)
|
|
return signals
|
|
|
|
|
|
if __name__ == '__main__':
|
|
if len(sys.argv) < 2:
|
|
# Demo with the test signals
|
|
test_text = """#ASTERUSDT Short market entry! SL: 0.6385 Targets: 0.51 Lev x15 1.3% balance
|
|
#HYPEUSDT Short market entry! SL; 33.5 Target 25 Lev x12 1.4% balance"""
|
|
|
|
signals = parse_signal_text(test_text)
|
|
print(f"Parsed {len(signals)} signals:\n")
|
|
for s in signals:
|
|
print(json.dumps(s, indent=2))
|
|
else:
|
|
json_path = sys.argv[1]
|
|
signals = parse_telegram_export(json_path)
|
|
print(f"Parsed {len(signals)} signals from export\n")
|
|
|
|
# Save to output
|
|
out_path = json_path.replace('.json', '_signals.json')
|
|
with open(out_path, 'w') as f:
|
|
json.dump(signals, f, indent=2)
|
|
print(f"Saved to {out_path}")
|
|
|
|
# Quick summary
|
|
longs = sum(1 for s in signals if s['direction'] == 'long')
|
|
shorts = sum(1 for s in signals if s['direction'] == 'short')
|
|
print(f"Longs: {longs}, Shorts: {shorts}")
|
|
tickers = set(s['ticker'] for s in signals)
|
|
print(f"Unique tickers: {len(tickers)}")
|