From 8e5f500557ed2159780b338526507bca9078e161 Mon Sep 17 00:00:00 2001 From: Annie Weng <126124914+Anniew31@users.noreply.github.com> Date: Tue, 26 May 2026 17:27:20 -0400 Subject: [PATCH] fix: improve signal accuracy and optimize tweet ingestion --- api/cron/collect-tweets.ts | 13 ++++++++++--- src/analysis/keyword-matcher.ts | 4 ++-- src/analysis/sentiment-analyzer.ts | 14 ++++++-------- src/analysis/signal-generator.ts | 2 +- src/api/kalshi-client.ts | 2 +- vercel.json | 4 ---- 6 files changed, 20 insertions(+), 19 deletions(-) diff --git a/api/cron/collect-tweets.ts b/api/cron/collect-tweets.ts index 20b5018..a2d0590 100644 --- a/api/cron/collect-tweets.ts +++ b/api/cron/collect-tweets.ts @@ -134,6 +134,8 @@ export default async function handler( totalCollected += result.tweets.length; + const tweetsToStore: AnalyzedTweet[] = []; + for (const rawTweet of result.tweets) { totalAnalyzed++; @@ -168,12 +170,17 @@ export default async function handler( collected_at: new Date().toISOString(), }; - // Store tweet in KV - await storeTweet(analyzedTweet); - totalStored++; + tweetsToStore.push(analyzedTweet); } + await Promise.allSettled( + tweetsToStore.map(tweet => storeTweet(tweet)) + ); + + totalStored += tweetsToStore.length; } + + // Step 6: Update feed indices await updateFeedIndices(); diff --git a/src/analysis/keyword-matcher.ts b/src/analysis/keyword-matcher.ts index d2da844..80820bd 100644 --- a/src/analysis/keyword-matcher.ts +++ b/src/analysis/keyword-matcher.ts @@ -853,8 +853,8 @@ function isPromotionalContent(text: string): boolean { } // Excessive emoji usage (often in spam) - const emojiCount = (text.match(/[\uD800-\uDFFF]/g) || []).length; - if (emojiCount > 15 && text.length < 200) { + const emojiCount = [...text].filter(c => (c.codePointAt(0) ?? 0) > 0xFFFF).length; + if (emojiCount > 8 && text.length < 200) { return true; // More than 15 emoji chars in a short tweet is suspicious } diff --git a/src/analysis/sentiment-analyzer.ts b/src/analysis/sentiment-analyzer.ts index 21d73b0..d7e8623 100644 --- a/src/analysis/sentiment-analyzer.ts +++ b/src/analysis/sentiment-analyzer.ts @@ -15,7 +15,7 @@ const BULLISH_KEYWORDS = [ 'bullish', 'moon', 'rally', 'pump', 'surge', 'soar', 'skyrocket', 'buy', 'long', 'calls', 'green', 'win', 'winning', 'yes', 'definitely', 'confirmed', 'happening', 'inevitable', 'obvious', 'clearly', 'certain', - 'guarantee', 'lock', 'easy', 'confident', 'predict', 'will happen', + 'guarantee', 'lock', 'easy', 'confident', 'predict', 'happen', 'going to', 'up', 'rise', 'increase', 'gain', 'profit', 'success', 'boom', 'growth', 'explosive', 'parabolic', 'breakout' ]; @@ -53,13 +53,11 @@ export function analyzeSentiment(tweetText: string): SentimentResult { for (let i = 0; i < words.length; i++) { const word = words[i].replace(/[^a-z]/g, ''); - const prevWord = i > 0 ? words[i - 1].replace(/[^a-z]/g, '') : ''; - - // Check for negation - const isNegated = NEGATIONS.includes(prevWord); - - // Check for strong modifier - const isStrong = STRONG_MODIFIERS.includes(prevWord); + // Look back 2 words so negation passes through a modifier + const prev1 = i > 0 ? words[i-1].replace(/[^a-z]/g,'') : ''; + const prev2 = i > 1 ? words[i-2].replace(/[^a-z]/g,'') : ''; + const isNegated = NEGATIONS.includes(prev1) || (STRONG_MODIFIERS.includes(prev1) && NEGATIONS.includes(prev2)); + const isStrong = STRONG_MODIFIERS.includes(prev1) && !NEGATIONS.includes(prev2); const weight = isStrong ? 2 : 1; // Check bullish diff --git a/src/analysis/signal-generator.ts b/src/analysis/signal-generator.ts index 45c1eaf..a981a4c 100644 --- a/src/analysis/signal-generator.ts +++ b/src/analysis/signal-generator.ts @@ -173,7 +173,7 @@ function generateSuggestedAction( urgency: UrgencyLevel ): SuggestedAction { // Don't suggest action if edge is too low - if (edge < 0.10) { + if (edge < 0.05) { return { direction: 'HOLD', confidence: 0, diff --git a/src/api/kalshi-client.ts b/src/api/kalshi-client.ts index f1653a6..906bb88 100644 --- a/src/api/kalshi-client.ts +++ b/src/api/kalshi-client.ts @@ -163,7 +163,7 @@ function toMarket(km: KalshiMarket): Market { keywords: generateKeywords(km.title), yesPrice: +safeYes.toFixed(2), noPrice: safeNo, - volume24h: km.volume_24h ?? km.volume ?? 0, + volume24h: km.volume_24h ?? 0, url: marketUrl, category: inferCategory(km.series_ticker || km.event_ticker || km.ticker), lastUpdated: new Date().toISOString(), diff --git a/vercel.json b/vercel.json index 5d0587d..4c4c49d 100644 --- a/vercel.json +++ b/vercel.json @@ -44,10 +44,6 @@ { "source": "/api/(.*)", "headers": [ - { - "key": "Access-Control-Allow-Credentials", - "value": "true" - }, { "key": "Access-Control-Allow-Origin", "value": "*"