Add crypto signals pipeline + Polymarket arb scanner
- 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
This commit is contained in:
166
projects/crypto-signals/scripts/signal_parser.py
Normal file
166
projects/crypto-signals/scripts/signal_parser.py
Normal file
@ -0,0 +1,166 @@
|
||||
#!/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)}")
|
||||
Reference in New Issue
Block a user