← 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 →