Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 35 additions & 10 deletions api/feed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { VercelRequest, VercelResponse } from '@vercel/node';
import type { AnalyzedTweet, FeedResponse, AccountCategory } from '../src/types/feed';
import { batchGetFromKV, setFeedCache, getFeedCache, getFeedCacheTimestamp } from './lib/cache-helper';
import { kv } from './lib/vercel-kv';
import { getMarkets } from './lib/market-cache'

// ─── KV Storage Keys ───────────────────────────────────────────────────────

Expand Down Expand Up @@ -129,27 +130,51 @@ export default async function handler(

if (feedIndex.length === 0) {
// Empty feed (not an error)
// Fall back to live market movers so the feed always returns something
const markets = await getMarkets();

const movingMarkets = markets
.filter(m => m.platform === 'polymarket')
.filter(m => Math.abs(m.oneDayPriceChange ?? 0) >= 0.04)
.sort((a, b) => Math.abs(b.oneDayPriceChange ?? 0) - Math.abs(a.oneDayPriceChange ?? 0))
.slice(0, limit);

const syntheticTweets = movingMarkets.map(market => ({
tweet: {
id: `synthetic-${market.id}`,
text: market.title,
author: 'synthetic one',
created_at: new Date().toISOString(),
metrics: { likes: 0, retweets: 0, replies: 0, quotes: 0 },
url: market.url,
},
matches: [],
sentiment: {
sentiment: (market.oneDayPriceChange ?? 0) > 0 ? 'bullish' : 'bearish',
confidence: Math.min(Math.abs(market.oneDayPriceChange ?? 0) * 4, 0.95),
},
category: market.category ?? 'finance',
urgency: Math.abs(market.oneDayPriceChange ?? 0) >= 0.10 ? 'high' : 'medium',
confidence: Math.min(0.5 + Math.abs(market.oneDayPriceChange ?? 0) * 2, 0.95),
analyzed_at: new Date().toISOString(),
collected_at: new Date().toISOString(),
}));

const response: FeedResponse = {
success: true,
data: {
tweets: [],
count: 0,
tweets: syntheticTweets as any,
count: syntheticTweets.length,
timestamp: new Date().toISOString(),
filters: {
limit,
category,
minUrgency,
since,
},
filters: { limit, category, minUrgency, since },
metadata: {
processing_time_ms: Date.now() - startTime,
total_in_kv: 0,
},
},
};

// Cache for 30 seconds
res.setHeader('Cache-Control', 's-maxage=30, stale-while-revalidate');
res.setHeader('Cache-Control', 's-maxage=20, stale-while-revalidate');
res.status(200).json(response);
return;
}
Expand Down
10 changes: 9 additions & 1 deletion api/markets/arbitrage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,22 @@ export default async function handler(
.filter(arb => !category || arb.polymarket.category === category || arb.kalshi.category === category)
.slice(0, limitNum);

// add volume alias to market objects because bot uses volume instead of volume24h
const opportunitiesAlias = opportunities.map (arb => ({
...arb,
polymarket: { ...arb.polymarket, volume: arb.polymarket.volume24h },
kalshi: { ...arb.kalshi, volume: arb.kalshi.volume24h },
}));

// Stage 0: Get freshness metadata
const freshnessMetadata = getMarketMetadata();

// Build response
const response = {
success: true,
data: {
opportunities,
opportunities: opportunitiesAlias,
arbitrage_opportunities : opportunitiesAlias, //a key mismatch from bot
count: opportunities.length,
timestamp: new Date().toISOString(),
filters: {
Expand Down
1 change: 1 addition & 0 deletions src/types/market.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface Market {
noBid?: number; // best executable NO bid when available
noAsk?: number; // best executable NO ask when available
volume24h: number; // 24h trading volume in dollars
volume?: number; // alias for volume24h, added so bot can use
url: string;
category: string;
lastUpdated: string; // ISO timestamp
Expand Down