feat: sentence pattern matching per IMPLEMENT_GUIDE

- 68 regex patterns across 6 parameters (fear_state: 21, greed_state: 19, hype_velocity: 5, pub_velocity: 4, pump_score: 8, dump_score: 11)
- Each pattern has weight (0.8-1.4) for importance scoring
- Patterns cover direct terms, market vernacular, event triggers, technical indicators, on-chain signals, macro, social signals
- Pre-compiled for efficiency with score_text_with_patterns() function
- score_text_with_patterns(text, param) returns weighted match score
- All 46 core NLP tests pass
This commit is contained in:
Codex
2026-09-18 02:02:24 +02:00
parent e6ecbfa2a6
commit ec217b968e

View File

@@ -0,0 +1,224 @@
"""
Sentence Pattern Matching per IMPLEMENT_GUIDE (spec Section 4.3)
Provides regex templates for each output parameter with weights.
These patterns capture linguistic constructions that strongly indicate
each sentiment parameter.
"""
import re
from dataclasses import dataclass
from typing import List, Dict, Tuple
from enum import Enum
@dataclass
class Pattern:
"""Regex pattern with weight and metadata"""
pattern: str
weight: float
param: str # Which parameter this pattern applies to
description: str
flags: int = re.IGNORECASE
# ============================================================
# FEAR_STATE PATTERNS
# ============================================================
FEAR_PATTERNS = [
# Direct fear declarations (weight 1.0)
Pattern(r"\b(?:fear|fearful|scared|terrified|panic|panicked|anxious|anxiety|dread|apprehension|trepidation)\b", 1.0, "fear_state", "Direct fear terms"),
Pattern(r"\b(?:afraid\s+(?:of|that)|scared\s+(?:of|that)|worried\s+about)\b", 1.0, "fear_state", "Fear constructions"),
# Market fear vernacular (weight 1.0)
Pattern(r"\b(?:crash|crashing|crashed|crash[-\s]?risk|flash\s+crash|market\s+crash)\b", 1.2, "fear_state", "Crash language"),
Pattern(r"\b(?:plunge|plunging|plummet|plummeting|nosedive|freefall|free\s+fall|implode|imploding|implosion)\b", 1.1, "fear_state", "Plunge language"),
Pattern(r"\b(?:dump|dumping|dumped|violent\s+dump|massive\s+dump|capitulation|capitulating)\b", 1.1, "fear_state", "Dump/capitulation language"),
Pattern(r"\b(?:selloff|sell[-\s]off|selling\s+pressure|heavy\s+selling|panic\s+sell)\b", 1.1, "fear_state", "Selloff language"),
Pattern(r"\b(?:bear(?:ish)?|bear\s+market|bearish\s+trend|downtrend|downtrending)\b", 1.0, "fear_state", "Bearish language"),
Pattern(r"\b(?:lower\s+(?:high|low)|downside|downside[-\s]?risk)\b", 1.0, "fear_state", "Technical downside"),
Pattern(r"\b(?:bleeding|bleeds|hemorrhage|hemorrhaging|carnage|bloodbath|wipeout|wiped\s+out)\b", 1.2, "fear_state", "Violent downside"),
Pattern(r"\b(?:liquidation|liquidating|liquidated|margin\s+call|forced\s+liquidation|cascade\s+liquidation)\b", 1.1, "fear_state", "Liquidation language"),
Pattern(r"\b(?:rekt|getting\s+rekt|blown\s+up|account\s+blown|margin\s+call)\b", 1.1, "fear_state", "Crypto fear slang"),
# Event-based fear triggers (weight 1.0)
Pattern(r"\b(?:hack|hacked|exploit|exploited|vulnerability|vulnerability|exploited|breach)\b", 1.3, "fear_state", "Security events"),
Pattern(r"\b(?:rug\s+pull|rugpull|exit\s+scam|abandoned|developer\s+fled)\b", 1.3, "fear_state", "Rug pull events"),
Pattern(r"\b(?:bank\s+run|run\s+on\s+(?:exchange|bank)|withdrawal\s+spike|mass\s+withdrawal|insolvent|insolvency)\b", 1.2, "fear_state", "Solvency events"),
Pattern(r"\b(?:regulatory|crackdown|ban|prohibited|sec\s+enforcement|cfdc\s+enforcement|lawsuit|lawsuit|litigation)\b", 1.1, "fear_state", "Regulatory events"),
Pattern(r"\b(?:delist|delisting|delisted|removed\s+from\s+exchange)\b", 1.1, "fear_state", "Delisting events"),
# Technical fear patterns (weight 0.8)
Pattern(r"\b(?:death\s+cross|bearish\s+divergence|hidden\s+bearish\s+divergence|resistance\s+(?:held|holds)|rejection\s+at\s+resistance)\b", 0.9, "fear_state", "Technical bearish"),
Pattern(r"\b(?:rsi\s+overbought|overbought\s+rsi|macd\s+bearish|macd\s+crossunder)\b", 0.8, "fear_state", "Indicator bearish"),
Pattern(r"\b(?:head\s+and\s+shoulders|double\s+top|triple\s+top|rising\s+wedge|bearish\s+pattern)\b", 0.8, "fear_state", "Chart patterns"),
# Sentiment/emotion indicators
Pattern(r"\b(?:fud|fear\s+uncertainty\s+doubt|despair|depression|anger|rage|panic\s+selling|blind\s+panic|herd\s+mentality|lemmings)\b", 1.0, "fear_state", "Fear emotions"),
Pattern(r"\b(?:social\s+dominance\s+(?:drop|falling)|mentions\s+falling|interest\s+fading|google\s+trends\s+down|search\s+interest\s+declining)\b", 0.8, "fear_state", "Social fear signals"),
]
# ============================================================
# GREED_STATE PATTERNS
# ============================================================
GREED_PATTERNS = [
# Direct greed declarations
Pattern(r"\b(?:greed|greedy|greedily|fomo|fear\s+of\s+missing\s+out|fear\s+of\s+missing\s+out)\b", 1.0, "greed_state", "Direct greed terms"),
Pattern(r"\b(?:euphoria|optimistic|optimism|bullish\s+sentiment|extreme\s+greed|greed\s+index)\b", 1.0, "greed_state", "Euphoria terms"),
# Market greed vernacular
Pattern(r"\b(?:surge|surging|surged|pump|pumping|pumped|moon|mooning|mooned|to\s+the\s+moon|rally|rallying|rallied)\b", 1.1, "greed_state", "Pump/moon language"),
Pattern(r"\b(?:breakout|breakouts|breaking\s+out|bull\s+run|bullish|bullish\s+trend|uptrend|uptrending)\b", 1.1, "greed_state", "Breakout language"),
Pattern(r"\b(?:ath|all[-\s]?time\s+high|record\s+high|new\s+high|higher\s+high|higher\s+low)\b", 1.0, "greed_state", "ATH language"),
Pattern(r"\b(?:green\s+candle|green\s+candles|ripping|rips|ripped|parabolic|explosive|violent\s+move\s+up)\b", 1.1, "greed_state", "Explosive upside"),
Pattern(r"\b(?:buy\s+the\s+dip|buying\s+the\s+dip|dip\s+bought|dca|dollar\s+cost\s+average)\b", 0.9, "greed_state", "Accumulation language"),
# Inflows/accumulation (in crypto: outflows = bullish)
Pattern(r"\b(?:outflow|outflows|net\s+outflow|exchange\s+outflow|capital\s+outflow|institutional\s+outflow)\b", 1.1, "greed_state", "Outflow language"),
Pattern(r"\b(?:whale\s+withdrawal|whale\s+withdrawals|large\s+withdrawal|exchange\s+withdrawal\s+to\s+cold)\b", 1.2, "greed_state", "Whale accumulation"),
Pattern(r"\b(?:balance\s+decreasing|off\s+exchange|cold\s+storage|whale\s+accumulation|smart\s+money\s+accumulation)\b", 1.1, "greed_state", "Accumulation signals"),
# Institutional/ETF
Pattern(r"\b(?:etf|spot\s+etf|bitcoin\s+etf|ethereum\s+etf|etf\s+approval|etf\s+approved|etf\s+inflow|etf\s+inflows)\b", 1.2, "greed_state", "ETF language"),
Pattern(r"\b(?:blackrock|fidelity|vanguard|grayscale|ark\s+invest|institutional|institutional\s+adoption|corporate\s+treasury|microstrategy|tesla)\b", 1.1, "greed_state", "Institutional signals"),
# Exchange/listing
Pattern(r"\b(?:listing|listed|new\s+listing|tier\s*1\s+listing|binance\s+listing|coinbase\s+listing)\b", 1.1, "greed_state", "Listing events"),
# Technical bullish
Pattern(r"\b(?:golden\s+cross|macd\s+crossover|rsi\s+oversold|support\s+(?:held|holds)|bounce|bouncing|reversal|reverses|bottomed|local\s+bottom|macro\s+bottom|capitulation\s+over)\b", 1.0, "greed_state", "Technical bullish"),
Pattern(r"\b(?:oversold|oversold\s+rsi|bullish\s+divergence|hidden\s+bullish\s+divergence)\b", 0.9, "greed_state", "Oversold signals"),
# On-chain bullish
Pattern(r"\b(?:staking|staked|staking\s+rewards|validator|delegation|hashrate\s+(?:up|rising)|difficulty\s+(?:up|rising|increasing)|network\s+growth|addresses\s+growing|new\s+addresses|transaction\s+count\s+rising)\b", 0.9, "greed_state", "On-chain bullish"),
# DeFi/yield
Pattern(r"\b(?:yield|apy|apr|high\s+yield|tvl\s+(?:growth|rising)|total\s+value\s+locked|protocol\s+revenue|fees\s+growing|revenue\s+growth|buyback|buybacks|token\s+burn|burns|burning|deflationary|supply\s+decreasing)\b", 0.8, "greed_state", "DeFi/yield signals"),
# Macro/narrative
Pattern(r"\b(?:halving|bitcoin\s+halving|supply\s+shock|supply\s+squeeze|scarcity|inflation\s+hedge|store\s+of\s+value|digital\s+gold|rate\s+cut|rate\s+cuts|fed\s+pivot|liquidity|m2\s+growth|global\s+liquidity|dollar\s+weakness|dxy\s+down|risk\s+on|risk\s+on\s+environment)\b", 0.8, "greed_state", "Macro bullish"),
# Sentiment/social
Pattern(r"\b(?:social\s+dominance|social\s+dominance\s+rising|dominance\s+rising|mentions\s+rising|trending\s+on\s+(?:twitter|reddit)|google\s+trends|search\s+interest|retail\s+fomo|new\s+entrants|onboarding\s+surge)\b", 0.8, "greed_state", "Social bullish signals"),
]
# ============================================================
# HYPE_VELOCITY PATTERNS
# ============================================================
HYPE_PATTERNS = [
# Acceleration declarations
Pattern(r"\b(?:accelerat(?:ing|ion)|surge|surging|surged|spike|spiking|spiked|explod(?:ing|ed)|rocket(?:ing)?)\b", 1.0, "hype_velocity", "Acceleration terms"),
Pattern(r"\b(?:going\s+viral|trending|dominating\s+(?:twitter|reddit|social)|flooding\s+(?:feeds|timelines))\b", 1.2, "hype_velocity", "Viral/hype detection"),
Pattern(r"\b(?:massive\s+(?:surge|spike|increase)|unprecedented\s+(?:volume|surge|coverage)|record\s+(?:volume|mentions|coverage))\b", 1.1, "hype_velocity", "Volume surge"),
Pattern(r"\b(?:velocity|momentum|acceleration)\s+(?:is\s+)?(?:building|increasing|accelerating|surging)\b", 1.0, "hype_velocity", "Velocity declarations"),
Pattern(r"\b(?:time\s+compression|overnight|in\s+hours|in\s+minutes|fast\s+money|quick\s+gains|rapid\s+accumulation)\b", 0.9, "hype_velocity", "Time compression"),
]
# ============================================================
# PUB_VELOCITY PATTERNS
# ============================================================
PUB_PATTERNS = [
Pattern(r"\b(?:breaking\s+news|live\s+updates?|developing\s+story|rolling\s+coverage|continuous\s+coverage|real[-\s]?time\s+updates?)\b", 1.1, "pub_velocity", "Breaking news"),
Pattern(r"\b(?:number\s+of\s+(?:articles|mentions|posts)\s+(?:has\s+)?(?:surged|spiked|doubled|tripled|increased\s+\d+%))\b", 1.2, "pub_velocity", "Quantified volume surge"),
Pattern(r"\b(?:dominating\s+(?:headlines|coverage|feeds)|avalanche\s+of\s+coverage|tidal\s+wave\s+of\s+articles|media\s+flood|press\s+storm)\b", 1.2, "pub_velocity", "Media flood"),
Pattern(r"\b(?:trending\s+topic|viral\s+thread|comment\s+storm|social\s+media\s+explosion|firehose|continuous\s+stream)\b", 1.1, "pub_velocity", "Social surge"),
]
# ============================================================
# PUMP_SCORE PATTERNS
# ============================================================
PUMP_PATTERNS = [
# Coordination detection
Pattern(r"\b(?:we\s+(?:are\s+)?pump|let'?s\s+pump|push\s+it|send\s+it|pump\s+it)\b", 1.4, "pump_score", "Direct coordination"),
Pattern(r"\b(?:pump\s+group|pump\s+channel|signal\s+group|signal\s+channel|alpha\s+group|alpha\s+call|call\s+group|discord\s+pump|telegram\s+pump)\b", 1.3, "pump_score", "Coordination channels"),
Pattern(r"\b(?:copy\s+pasta|copy[-\s]?paste|template\s+messages?|identical\s+posts?|mass\s+messaging|shill\s+army|paid\s+promotion|sponsored|incentivized|shill(?:ing)?)\b", 1.4, "pump_score", "Template/coordinated messaging"),
Pattern(r"\b(?:whale\s+(?:buying|accumulation|accumulating|loading|absorbing|absorption|bid\s+(?:absorption|wall|walls)))\b", 1.2, "pump_score", "Whale accumulation"),
# Hype-driven surge
Pattern(r"\b(?:100x|1000x|10x|moon|to\s+the\s+moon|moon\s+mission|lambo|diamond\s+hands|diamond\s+hodl|rocket|🚀)\b", 1.3, "pump_score", "Extreme multiplier/hype"),
Pattern(r"\b(?:influencer|promot(?:ion|ing)|promote|endorse(?:d|ment)?|paid\s+to\s+promote|sponsored|affiliate|incentivized|shill(?:ing)?)\b", 1.3, "pump_score", "Influencer promotion"),
# Artificial price action
Pattern(r"\b(?:parabolic|parabolic\s+move|rocket\s+fuel|mooning|mooning\s+hard|explod(?:ing|ed)\s+upward|surging\s+up|soaring|raging\s+bull|unstoppable\s+momentum|no\s+top\s+in\s+sight|all\s+new\s+highs|vertical|going\s+vertical|straight\s+up)\b", 1.2, "pump_score", "Artificial price action"),
Pattern(r"\b(?:wash\s+trading|wash\s+trade|spoofing|spoof|layering|quote\s+stuffing|artificial\s+(?:volume|rally|inflation|pump))\b", 1.3, "pump_score", "Manipulation tactics"),
]
# ============================================================
# DUMP_SCORE PATTERNS
# ============================================================
DUMP_PATTERNS = [
# Direct dump declarations
Pattern(r"\b(?:dump|dumping|dumped|violent\s+dump|massive\s+dump|capitulation|capitulating|panic\s+sell|panic\s+selling|panic\s+sold)\b", 1.2, "dump_score", "Dump language"),
Pattern(r"\b(?:crash|crashing|crashed|flash\s+crash|market\s+crash|free\s+fall|freefall|nose\s+dive|nosedive|plunge|plunging|plummet|implosion|implode|wipeout|wiped\s+out)\b", 1.2, "dump_score", "Crash language"),
# Bearish technical
Pattern(r"\b(?:death\s+cross|macd\s+bearish|macd\s+crossunder|rsi\s+overbought|overbought\s+rsi|rsi\s+rejection|resistance\s+(?:held|holds)|rejection|rejected|rejecting)\b", 1.0, "dump_score", "Technical bearish"),
Pattern(r"\b(?:head\s+and\s+shoulders|double\s+top|triple\s+top|rising\s+wedge|bearish\s+pattern|distribution\s+phase|wyckoff\s+distribution)\b", 0.9, "dump_score", "Chart patterns"),
# On-chain bearish
Pattern(r"\b(?:whale\s+selling|whale\s+sells|whale\s+dump|whale\s+dumps|large\s+holder\s+selling|exchange\s+inflow|exchange\s+inflows|balance\s+increasing|on\s+exchange|hot\s+wallet|exchange\s+deposit|deposit\s+spike|whale\s+deposit|large\s+deposit)\b", 1.1, "dump_score", "On-chain bearish"),
Pattern(r"\b(?:unstaking|unstaked|unstake|unstakes|withdrawal|withdrawals|mass\s+withdrawal|bank\s+run|run\s+on\s+exchange|solvency\s+concerns|proof\s+of\s+reserves)\b", 1.1, "dump_score", "Unstaking/withdrawal"),
# DeFi/Protocol issues
Pattern(r"\b(?:tvl\s+(?:drop|dropping|crash)|protocol\s+(?:exploit|hack)|smart\s+contract\s+exploit|oracle\s+failure|bad\s+debt|insolvent\s+protocol|undercollateralized\s+vault)\b", 1.2, "dump_score", "DeFi issues"),
Pattern(r"\b(?:unlock|unlocks|token\s+unlock|vesting\s+unlock|team\s+unlock|investor\s+unlock|cliff\s+unlock|linear\s+unlock|supply\s+shock|dilution|dilutive)\b", 1.0, "dump_score", "Token unlocks"),
# Macro risk-off
Pattern(r"\b(?:rate\s+hike|rate\s+hikes|fed\s+hawkish|hawkish\s+fed|tightening|qt|quantitative\s+tightening|liquidity\s+(?:drain|crunch)|credit\s+crunch|recession|recession\s+fears|economic\s+slowdown|gdp\s+slowing|inflation\s+high|cpi\s+high|sticky\s+inflation|core\s+inflation)\b", 0.8, "dump_score", "Macro risk-off"),
Pattern(r"\b(?:dollar\s+strength|dxy\s+up|dxy\s+rising|risk\s+off|risk\s+off\s+environment|flight\s+to\s+safety|safe\s+haven|treasury\s+yields|yields\s+rising|bond\s+yields|correlation|correlated\s+selloff|broad\s+selloff|market[-\s]wide\s+selloff|contagion|contagion\s+risk|systemic\s+risk|domino\s+effect|spillover)\b", 0.9, "dump_score", "Risk-off signals"),
# Sentiment
Pattern(r"\b(?:fud|fear\s+uncertainty\s+doubt|fear|extreme\s+fear|fear\s+and\s+greed|fear\s+index|capitulation|despair|depression|anger|rage|selling\s+panic|blind\s+panic|herd\s+mentality|herd\s+selling|lemmings|social\s+dominance\s+drop|mentions\s+falling|interest\s+fading|google\s+trends\s+down|search\s+interest\s+declining|retail\s+exodus|users\s+leaving|offboarding|narrative\s+broken|narrative\s+shift|thesis\s+broken|thesis\s+invalidated|disappointment|missed\s+expectations|guidance\s+lowered|warn|warning)\b", 1.0, "dump_score", "Fear/sentiment"),
]
# ============================================================
# ALL PATTERNS BY PARAMETER
# ============================================================
ALL_PATTERNS = {
"fear_state": FEAR_PATTERNS,
"greed_state": GREED_PATTERNS,
"hype_velocity": HYPE_PATTERNS,
"pub_velocity": PUB_PATTERNS,
"pump_score": PUMP_PATTERNS,
"dump_score": DUMP_PATTERNS,
}
def get_patterns_for_param(param: str) -> List[Pattern]:
"""Get all patterns for a given parameter"""
return ALL_PATTERNS.get(param, [])
def compile_all_patterns() -> Dict[str, List[Tuple[re.Pattern, float]]]:
"""Compile all patterns for efficient matching"""
compiled = {}
for param, patterns in ALL_PATTERNS.items():
compiled[param] = [(re.compile(p.pattern, p.flags), p.weight) for p in patterns]
return compiled
# Pre-compiled patterns for efficiency
COMPILED_PATTERNS = compile_all_patterns()
def score_text_with_patterns(text: str, param: str) -> float:
"""Score text against patterns for a given parameter"""
if param not in COMPILED_PATTERNS:
return 0.0
score = 0.0
for pattern, weight in COMPILED_PATTERNS[param]:
matches = pattern.findall(text)
if matches:
# Count unique matches, cap at reasonable number
unique_matches = len(set(matches))
score += min(unique_matches, 5) * weight
return score
# Pattern statistics
TOTAL_PATTERNS = sum(len(p) for p in ALL_PATTERNS.values())
print(f"Loaded {TOTAL_PATTERNS} sentence patterns across 6 parameters:")
for param, patterns in ALL_PATTERNS.items():
print(f" {param}: {len(patterns)} patterns")