Use keyset pagination for full market scans

This commit is contained in:
Theodore Song
2026-07-26 16:15:48 -04:00
parent 734b989072
commit 4b3e03a353
+34 -14
View File
@@ -739,7 +739,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
</section> </section>
<footer> <footer>
Build all-market-suggestions · Paper trading only · Live prices from Polymarket's public Gamma API · Not financial advice · Build keyset-market-scan · Paper trading only · Live prices from Polymarket's public Gamma API · Not financial advice ·
<a class="market-link" href="https://github.com/theodore-song/polymarket-analyst" target="_blank" rel="noopener">Source on GitHub</a> <a class="market-link" href="https://github.com/theodore-song/polymarket-analyst" target="_blank" rel="noopener">Source on GitHub</a>
</footer> </footer>
</div> </div>
@@ -766,7 +766,7 @@ const EXIT_STALE_DAYS = 10;
const EXIT_RESOLUTION_DAYS = 2; const EXIT_RESOLUTION_DAYS = 2;
const AGENTS_KEY = "pma_agents_v2"; const AGENTS_KEY = "pma_agents_v2";
const SUG_KEY = "pma_suggestions_v5"; const SUG_KEY = "pma_suggestions_v5";
const SUGGESTION_ENGINE_VERSION = 8; const SUGGESTION_ENGINE_VERSION = 9;
const FOCUS_KEY = "pma_focus_v1"; const FOCUS_KEY = "pma_focus_v1";
const VIEW_KEY = "pma_view_v1"; const VIEW_KEY = "pma_view_v1";
const PF_SORT_KEY = "pma_portfolio_sort_v1"; const PF_SORT_KEY = "pma_portfolio_sort_v1";
@@ -950,12 +950,21 @@ async function fetchMarkets(pages=Infinity,perPage=100,onProgress=null){
const all=[]; const all=[];
const maxPages=Number.isFinite(pages)?pages:MAX_ACTIVE_MARKET_PAGES; const maxPages=Number.isFinite(pages)?pages:MAX_ACTIVE_MARKET_PAGES;
const seen=new Set(); const seen=new Set();
const useKeyset=!Number.isFinite(pages)||pages>20;
let cursor=null;
for(let i=0;i<maxPages;i++){ for(let i=0;i<maxPages;i++){
const params=new URLSearchParams({closed:"false",active:"true",archived:"false",include_tag:"true", const params=new URLSearchParams({closed:"false",active:"true",archived:"false",include_tag:"true",
limit:String(perPage),offset:String(i*perPage),order:"volume24hr",ascending:"false"}); limit:String(perPage),order:"volume24hr",ascending:"false"});
const r=await fetch(`${GAMMA}/markets?${params}`); if(useKeyset){
if(cursor)params.set("after_cursor",cursor);
}else{
params.set("offset",String(i*perPage));
}
const r=await fetch(`${GAMMA}/markets${useKeyset?"/keyset":""}?${params}`);
if(!r.ok){if(i===0)throw new Error("Polymarket API "+r.status);break;} if(!r.ok){if(i===0)throw new Error("Polymarket API "+r.status);break;}
const raw=await r.json(); if(!raw.length)break; const page=await r.json();
const raw=useKeyset?(page.markets||[]):page;
if(!raw.length)break;
let added=0; let added=0;
for(const m of raw){ for(const m of raw){
const id=String(m.id||""); const id=String(m.id||"");
@@ -963,8 +972,9 @@ async function fetchMarkets(pages=Infinity,perPage=100,onProgress=null){
if(id)seen.add(id); if(id)seen.add(id);
all.push(m);added++; all.push(m);added++;
} }
if(useKeyset)cursor=page.next_cursor||null;
if(onProgress)onProgress(all.length); if(onProgress)onProgress(all.length);
if(raw.length<perPage||added===0)break; if(raw.length<perPage||added===0||(useKeyset&&!cursor))break;
} }
return all.map(normalizeMarket).filter(m=>m&&m.question); return all.map(normalizeMarket).filter(m=>m&&m.question);
} }
@@ -3106,19 +3116,24 @@ async function loadPaperMarketPage(reset=false,loadAll=false){
if(reset){PAPER_SEARCH_RESULTS=[];PAPER_MARKET_OFFSET=0;} if(reset){PAPER_SEARCH_RESULTS=[];PAPER_MARKET_OFFSET=0;}
let loaded=0,rawCount=0,keepGoing=true; let loaded=0,rawCount=0,keepGoing=true;
const limit=100,maxPages=loadAll?MAX_ACTIVE_MARKET_PAGES:1; const limit=100,maxPages=loadAll?MAX_ACTIVE_MARKET_PAGES:1;
let cursor=null;
for(let page=0;page<maxPages&&keepGoing;page++){ for(let page=0;page<maxPages&&keepGoing;page++){
const params=new URLSearchParams({closed:"false",active:"true",archived:"false",include_tag:"true",limit:String(limit),offset:String(PAPER_MARKET_OFFSET),order:"volume24hr",ascending:"false"}); const params=new URLSearchParams({closed:"false",active:"true",archived:"false",include_tag:"true",limit:String(limit),order:"volume24hr",ascending:"false"});
if(loadAll){if(cursor)params.set("after_cursor",cursor);}
else params.set("offset",String(PAPER_MARKET_OFFSET));
if(q)params.set("search",q); if(q)params.set("search",q);
const r=await fetch(`${GAMMA}/markets?${params}`); const r=await fetch(`${GAMMA}/markets${loadAll?"/keyset":""}?${params}`);
if(!r.ok)throw new Error("market load failed"); if(!r.ok)throw new Error("market load failed");
const raw=await r.json(); const pageData=await r.json();
const raw=loadAll?(pageData.markets||[]):pageData;
rawCount+=raw.length; rawCount+=raw.length;
const next=raw.map(normalizeMarket).filter(Boolean).map(paperTradeFromMarket); const next=raw.map(normalizeMarket).filter(Boolean).map(paperTradeFromMarket);
PAPER_SEARCH_RESULTS=(PAPER_SEARCH_RESULTS||[]).concat(next); PAPER_SEARCH_RESULTS=(PAPER_SEARCH_RESULTS||[]).concat(next);
PAPER_MARKET_OFFSET+=raw.length; PAPER_MARKET_OFFSET+=raw.length;
if(loadAll)cursor=pageData.next_cursor||null;
loaded+=next.length; loaded+=next.length;
if(loadAll)setStatus(`loading active markets (${PAPER_SEARCH_RESULTS.length})…`,true); if(loadAll)setStatus(`loading active markets (${PAPER_SEARCH_RESULTS.length})…`,true);
if(raw.length<limit)keepGoing=false; if(raw.length<limit||(loadAll&&!cursor))keepGoing=false;
} }
renderPaperTab();setStatus("up to date",false); renderPaperTab();setStatus("up to date",false);
toast(loaded?`Loaded ${PAPER_SEARCH_RESULTS.length} active markets.`:rawCount?"No tradable binary markets found in this page.":"No more active markets found."); toast(loaded?`Loaded ${PAPER_SEARCH_RESULTS.length} active markets.`:rawCount?"No tradable binary markets found in this page.":"No more active markets found.");
@@ -3467,19 +3482,24 @@ async function loadLiveMarketPage(reset=false,loadAll=false){
if(reset){LIVE_SEARCH_RESULTS=[];LIVE_MARKET_OFFSET=0;} if(reset){LIVE_SEARCH_RESULTS=[];LIVE_MARKET_OFFSET=0;}
let loaded=0,rawCount=0,keepGoing=true; let loaded=0,rawCount=0,keepGoing=true;
const limit=100,maxPages=loadAll?MAX_ACTIVE_MARKET_PAGES:1; const limit=100,maxPages=loadAll?MAX_ACTIVE_MARKET_PAGES:1;
let cursor=null;
for(let page=0;page<maxPages&&keepGoing;page++){ for(let page=0;page<maxPages&&keepGoing;page++){
const params=new URLSearchParams({closed:"false",active:"true",archived:"false",include_tag:"true",limit:String(limit),offset:String(LIVE_MARKET_OFFSET),order:"volume24hr",ascending:"false"}); const params=new URLSearchParams({closed:"false",active:"true",archived:"false",include_tag:"true",limit:String(limit),order:"volume24hr",ascending:"false"});
if(loadAll){if(cursor)params.set("after_cursor",cursor);}
else params.set("offset",String(LIVE_MARKET_OFFSET));
if(q)params.set("search",q); if(q)params.set("search",q);
const r=await fetch(`${GAMMA}/markets?${params}`); const r=await fetch(`${GAMMA}/markets${loadAll?"/keyset":""}?${params}`);
if(!r.ok)throw new Error("market load failed"); if(!r.ok)throw new Error("market load failed");
const raw=await r.json(); const pageData=await r.json();
const raw=loadAll?(pageData.markets||[]):pageData;
rawCount+=raw.length; rawCount+=raw.length;
const next=raw.map(normalizeMarket).filter(Boolean).map(paperTradeFromMarket); const next=raw.map(normalizeMarket).filter(Boolean).map(paperTradeFromMarket);
LIVE_SEARCH_RESULTS=(LIVE_SEARCH_RESULTS||[]).concat(next); LIVE_SEARCH_RESULTS=(LIVE_SEARCH_RESULTS||[]).concat(next);
LIVE_MARKET_OFFSET+=raw.length; LIVE_MARKET_OFFSET+=raw.length;
if(loadAll)cursor=pageData.next_cursor||null;
loaded+=next.length; loaded+=next.length;
if(loadAll)setStatus(`loading active markets (${LIVE_SEARCH_RESULTS.length})…`,true); if(loadAll)setStatus(`loading active markets (${LIVE_SEARCH_RESULTS.length})…`,true);
if(raw.length<limit)keepGoing=false; if(raw.length<limit||(loadAll&&!cursor))keepGoing=false;
} }
renderLiveMarkets();setStatus("up to date",false); renderLiveMarkets();setStatus("up to date",false);
toast(loaded?`Loaded ${LIVE_SEARCH_RESULTS.length} active markets.`:rawCount?"No tradable binary markets found in this page.":"No more active markets found."); toast(loaded?`Loaded ${LIVE_SEARCH_RESULTS.length} active markets.`:rawCount?"No tradable binary markets found in this page.":"No more active markets found.");