[object Object]

← back to Bertha

chore(alshi-dash): update 1 file (.js) [+82/-47]

e39a225580de33d20d85c20b5f32cefe314bc3b6 · 2026-02-16 14:17:31 +0000 · DW Commit Agent

Files touched

Diff

commit e39a225580de33d20d85c20b5f32cefe314bc3b6
Author: DW Commit Agent <commit-agent@dw-agents.com>
Date:   Mon Feb 16 14:17:31 2026 +0000

    chore(alshi-dash): update 1 file (.js) [+82/-47]
---
 kalshi-dash/server.js | 129 ++++++++++++++++++++++++++++++++------------------
 1 file changed, 82 insertions(+), 47 deletions(-)

diff --git a/kalshi-dash/server.js b/kalshi-dash/server.js
index bb8baa3..aabf4f6 100644
--- a/kalshi-dash/server.js
+++ b/kalshi-dash/server.js
@@ -3682,27 +3682,35 @@ async function redditIntelligenceLoop() {
     // Get tracking config from DB
     const { rows: trackers } = await kenQ('SELECT * FROM ken_reddit_tracking WHERE enabled = true');
 
-    for (const tracker of trackers) {
-      const posts = await scrapeReddit(tracker.subreddit, tracker.search_term, 20);
-      for (const post of posts) {
-        const sentiment = scoreSentiment(post.title + ' ' + post.selftext);
-        // Only store if has some engagement
-        if (post.score > 2 || post.num_comments > 1) {
-          await kenQ(`
-            INSERT INTO ken_sentiment (source, subreddit, keyword, post_title, post_url, score, num_comments, sentiment_score, relevance_ticker, raw_text)
-            VALUES ('reddit', $1, $2, $3, $4, $5, $6, $7, $8, $9)
-            ON CONFLICT DO NOTHING
-          `, [
-            tracker.subreddit, tracker.search_term, post.title, post.url,
-            post.score, post.num_comments, sentiment,
-            (tracker.related_tickers || [])[0] || null,
-            (post.selftext || '').substring(0, 1000),
-          ]);
-          totalPosts++;
+    // Process tracked subreddits in parallel batches of 5 (58 serial × 2s = too slow)
+    const TRACKER_BATCH = 5;
+    for (let i = 0; i < trackers.length; i += TRACKER_BATCH) {
+      const batch = trackers.slice(i, i + TRACKER_BATCH);
+      const results = await Promise.allSettled(
+        batch.map(tracker => scrapeReddit(tracker.subreddit, tracker.search_term, 20)
+          .then(posts => ({ tracker, posts })))
+      );
+      for (const result of results) {
+        if (result.status !== 'fulfilled') continue;
+        const { tracker, posts } = result.value;
+        for (const post of posts) {
+          const sentiment = scoreSentiment(post.title + ' ' + post.selftext);
+          if (post.score > 2 || post.num_comments > 1) {
+            await kenQ(`
+              INSERT INTO ken_sentiment (source, subreddit, keyword, post_title, post_url, score, num_comments, sentiment_score, relevance_ticker, raw_text)
+              VALUES ('reddit', $1, $2, $3, $4, $5, $6, $7, $8, $9)
+              ON CONFLICT DO NOTHING
+            `, [
+              tracker.subreddit, tracker.search_term, post.title, post.url,
+              post.score, post.num_comments, sentiment,
+              (tracker.related_tickers || [])[0] || null,
+              (post.selftext || '').substring(0, 1000),
+            ]);
+            totalPosts++;
+          }
         }
       }
-      // Rate limit: wait 2s between subreddit searches
-      await new Promise(r => setTimeout(r, 2000));
+      await new Promise(r => setTimeout(r, 1500)); // 1.5s between batches
     }
 
     // Also scrape hot posts from key subreddits for general intelligence
@@ -3784,16 +3792,25 @@ async function redditIntelligenceLoop() {
       // Space
       'SpaceX launch', 'NASA artemis',
     ];
-    for (const nq of newsQueries) {
-      const articles = await scrapeGoogleNews(nq, 5);
-      for (const article of articles) {
-        const sentiment = scoreSentiment(article.title);
-        await kenQ(`
-          INSERT INTO ken_sentiment (source, subreddit, keyword, post_title, post_url, score, sentiment_score)
-          VALUES ('google_news', $1, $2, $3, $4, 0, $5)
-          ON CONFLICT DO NOTHING
-        `, [article.source || 'news', nq, article.title, article.url, sentiment]);
-        totalPosts++;
+    // Process Google News in batches of 5 (21 serial × 1s = too slow)
+    const NEWS_QUERY_BATCH = 5;
+    for (let i = 0; i < newsQueries.length; i += NEWS_QUERY_BATCH) {
+      const batch = newsQueries.slice(i, i + NEWS_QUERY_BATCH);
+      const results = await Promise.allSettled(
+        batch.map(nq => scrapeGoogleNews(nq, 5).then(articles => ({ nq, articles })))
+      );
+      for (const result of results) {
+        if (result.status !== 'fulfilled') continue;
+        const { nq, articles } = result.value;
+        for (const article of articles) {
+          const sentiment = scoreSentiment(article.title);
+          await kenQ(`
+            INSERT INTO ken_sentiment (source, subreddit, keyword, post_title, post_url, score, sentiment_score)
+            VALUES ('google_news', $1, $2, $3, $4, 0, $5)
+            ON CONFLICT DO NOTHING
+          `, [article.source || 'news', nq, article.title, article.url, sentiment]);
+          totalPosts++;
+        }
       }
       await new Promise(r => setTimeout(r, 1000));
     }
@@ -4308,6 +4325,12 @@ const TICKER_ENTITY_MAP = {
   'LCDR': ['cruz', 'linda cruz', 'sba administrator'],
   'MKRA': ['kratsios', 'michael kratsios'],
   'SWIL': ['wilson', 'sean wilson'],
+  // KXPRESPERSON - 2028 Presidential election
+  'DTRU':   ['trump', 'donald trump', 'president trump', 'former president trump'],
+  'DTRUJR': ['trump jr', 'donald trump jr', 'don jr', 'trump junior'],
+  'RDES':   ['desantis', 'ron desantis', 'florida governor'],
+  'JVAN':   ['vance', 'jd vance', 'vice president vance', 'j.d. vance'],
+  'GYOU':   ['youngkin', 'glenn youngkin', 'virginia governor'],
   // KXFEDCHAIRNOM - Fed Chair nominees
   'JS':   ['dimon', 'jamie dimon'],
   'KW':   ['warsh', 'kevin warsh'],
@@ -4631,17 +4654,6 @@ async function signalPipeline(activeMarkets) {
       if (Math.abs(edge) >= RISK_LIMITS.minEdgeThreshold) {
         const direction = edge > 0 ? 'BUY_YES' : 'BUY_NO';
 
-        // Dedup: skip if we already generated a signal for this market+direction in last 30 min
-        const recentDup = await kenQ(`
-          SELECT id, expected_edge, confidence FROM ken_strategy_signals
-          WHERE market_id = $1 AND direction = $2 AND created_at > NOW() - INTERVAL '30 minutes'
-          ORDER BY created_at DESC LIMIT 1
-        `, [ticker, direction]);
-        if (recentDup.rows.length > 0) {
-          const prevEdge = parseFloat(recentDup.rows[0].expected_edge);
-          if (Math.abs(Math.abs(edge) - Math.abs(prevEdge)) < 0.02) continue;
-        }
-
         // Step 6b: Monte Carlo simulation for probability validation (CPU-intensive)
         const momentum = cachedMomentumMap?.[ticker] || { delta_2h: 0, delta_6h: 0 };
         const mcResult = monteCarloSimulation({
@@ -4658,6 +4670,18 @@ async function signalPipeline(activeMarkets) {
         // Blend Monte Carlo with statistical model
         const mcEdge = mcResult.edgeConsistent ? (edge * 0.65 + mcResult.medianEdge * 0.35) : edge * 0.8;
 
+        // Dedup: skip if we already generated a signal for this market+direction in last 30 min
+        // Compare MC-blended edge (mcEdge) against stored MC-blended edge to avoid false positives
+        const recentDup = await kenQ(`
+          SELECT id, expected_edge, confidence FROM ken_strategy_signals
+          WHERE market_id = $1 AND direction = $2 AND created_at > NOW() - INTERVAL '30 minutes'
+          ORDER BY created_at DESC LIMIT 1
+        `, [ticker, direction]);
+        if (recentDup.rows.length > 0) {
+          const prevEdge = parseFloat(recentDup.rows[0].expected_edge);
+          if (Math.abs(Math.abs(mcEdge) - Math.abs(prevEdge)) < 0.02) continue;
+        }
+
         // Improved confidence using MC validation
         const edgeScore = Math.min(1, Math.pow(Math.abs(mcEdge) / 0.15, 1.5));
         const volumeScore = Math.min(1, numArticles / 8);
@@ -6181,8 +6205,9 @@ server.listen(PORT, '0.0.0.0', () => {
   console.log(`[Ken] Heating intel: ${HEATING_SUBS.length} subreddits | ${HEATING_KEYWORDS.length} keywords | NOAA 5 metro forecasts | EIA electricity/gas prices`);
   console.log(`[Ken] ═══════════════════════════════════════`);
 
-  // Mutex: prevent concurrent sweeps from overlapping and doubling memory
+  // Separate mutexes: sweep (heavy Reddit/news) and scan (light Kalshi API) can run concurrently
   let sweepRunning = false;
+  let scanRunning = false;
 
   async function runSweepSafe(name, fn) {
     if (sweepRunning) { console.log(`[Ken] Skipping ${name} — another sweep in progress`); return; }
@@ -6192,22 +6217,32 @@ server.listen(PORT, '0.0.0.0', () => {
     if (global.gc) global.gc();
   }
 
+  async function runScanSafe(fn) {
+    if (scanRunning) { console.log('[Ken] Skipping Scan — another scan in progress'); return; }
+    scanRunning = true;
+    try { await fn(); } catch (e) { console.error(`[Ken] Scan error:`, e.message); }
+    scanRunning = false;
+    if (global.gc) global.gc();
+  }
+
   // Load stored Kalshi env preference and trade config from DB
   refreshKalshiEnv().then(() => {
     console.log(`[Ken] Kalshi environment: ${KALSHI_ENV.toUpperCase()} (${KALSHI_BASE_URL})`);
   });
   loadTradeConfig();
 
-  // Initial: run reddit sweep first, THEN market scan (never concurrent)
+  // Initial: run reddit sweep and market scan (scan uses separate mutex, can overlap)
   setTimeout(async () => {
-    await runSweepSafe('Reddit init', redditIntelligenceLoop);
-    await runSweepSafe('Initial scan', autonomousScan);
+    // Start sweep in background, don't await — let scan run concurrently
+    runSweepSafe('Reddit init', redditIntelligenceLoop);
+    // Small delay to let sweep start first, then run scan independently
+    setTimeout(() => runScanSafe(autonomousScan), 10000);
   }, 5000);
 
-  // Market scan every 15 minutes (skips if reddit sweep running)
-  setInterval(() => runSweepSafe('Scan', autonomousScan), SCAN_INTERVAL_MS);
+  // Market scan every 8 minutes — independent of sweep (uses own mutex)
+  setInterval(() => runScanSafe(autonomousScan), SCAN_INTERVAL_MS);
 
-  // Reddit/news every 30 minutes (skips if market scan running)
+  // Reddit/news sweep every 15 minutes (only blocks other sweeps, not scans)
   setInterval(() => runSweepSafe('Reddit', redditIntelligenceLoop), REDDIT_INTERVAL_MS);
 
   // Fast Signal Pipeline every 5 minutes — news → sentiment → probability → signals

← df73f40 Ken: market data refinements and portfolio query fixes  ·  back to Bertha  ·  Ken: add probability shift dampening for realistic signal ed 4d47305 →