From ec217b968ea2513e081933242ee591ab0bff8077 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 18 Sep 2026 02:02:24 +0200 Subject: [PATCH] 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 --- .../src/sentiment_engine/signal/patterns.py | 224 ++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 sentiment_engine/src/sentiment_engine/signal/patterns.py diff --git a/sentiment_engine/src/sentiment_engine/signal/patterns.py b/sentiment_engine/src/sentiment_engine/signal/patterns.py new file mode 100644 index 0000000..9909c4a --- /dev/null +++ b/sentiment_engine/src/sentiment_engine/signal/patterns.py @@ -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")