Pagination
Working with large result sets using pagination.
Overview
Pagination allows you to retrieve large datasets in manageable chunks using the setRange() method.
typescript
screener.setRange(from, to);Basic Pagination
Setting Range
typescript
import { StockScreener, StockField } from 'tradingview-screener';
const screener = new StockScreener();
// Get first 50 results (0-49)
screener.setRange(0, 50);
// Get next 50 results (50-99)
screener.setRange(50, 100);
// Get results 100-149
screener.setRange(100, 150);Default Range
If you don't call setRange(), the default range is used:
typescript
// Default: first 150 results
const results = await screener.get();
// Equivalent to: screener.setRange(0, 150)Page-Based Pagination
Calculate Page Ranges
typescript
const pageSize = 50;
const page = 0; // 0-indexed
screener.setRange(page * pageSize, (page + 1) * pageSize);
// Page 0: 0-50
// Page 1: 50-100
// Page 2: 100-150Page Function
typescript
function getPage(screener: StockScreener, page: number, pageSize: number = 50) {
const start = page * pageSize;
const end = start + pageSize;
screener.setRange(start, end);
return screener.get();
}
// Usage
const page1 = await getPage(screener, 0); // First page
const page2 = await getPage(screener, 1); // Second page
const page3 = await getPage(screener, 2); // Third pageFetching All Results
Sequential Fetching
typescript
async function getAllResults(screener: StockScreener) {
const pageSize = 150; // Max recommended page size
const allResults: any[] = [];
let page = 0;
while (true) {
screener.setRange(page * pageSize, (page + 1) * pageSize);
const results = await screener.get();
allResults.push(...results.data);
// Stop if we got less than a full page
if (results.data.length < pageSize) {
break;
}
page++;
}
return {
data: allResults,
totalCount: allResults.length
};
}
// Usage
const screener = new StockScreener();
screener.where(StockField.MARKET_CAPITALIZATION.gt(1e9));
const allData = await getAllResults(screener);
console.log(`Retrieved ${allData.totalCount} total results`);With Progress Reporting
typescript
async function getAllResultsWithProgress(screener: StockScreener) {
const pageSize = 150;
const allResults: any[] = [];
let page = 0;
while (true) {
screener.setRange(page * pageSize, (page + 1) * pageSize);
const results = await screener.get();
allResults.push(...results.data);
console.log(`Fetched page ${page + 1}: ${results.data.length} results`);
console.log(`Total so far: ${allResults.length} of ${results.totalCount}`);
if (results.data.length < pageSize) {
console.log('Reached last page');
break;
}
page++;
}
return {
data: allResults,
totalCount: allResults.length
};
}Total Count
Use totalCount to determine how many pages exist:
typescript
const screener = new StockScreener();
screener.where(StockField.MARKET_CAPITALIZATION.gt(1e9));
// Get first page
screener.setRange(0, 50);
const firstPage = await screener.get();
console.log(`Total matches: ${firstPage.totalCount}`);
console.log(`Fetched: ${firstPage.data.length}`);
// Calculate total pages
const pageSize = 50;
const totalPages = Math.ceil(firstPage.totalCount / pageSize);
console.log(`Total pages: ${totalPages}`);Practical Examples
Paginated UI
typescript
class StockPaginator {
private screener: StockScreener;
private pageSize: number;
private currentPage: number = 0;
constructor(screener: StockScreener, pageSize: number = 50) {
this.screener = screener;
this.pageSize = pageSize;
}
async getCurrentPage() {
this.screener.setRange(
this.currentPage * this.pageSize,
(this.currentPage + 1) * this.pageSize
);
return await this.screener.get();
}
async nextPage() {
this.currentPage++;
return await this.getCurrentPage();
}
async previousPage() {
if (this.currentPage > 0) {
this.currentPage--;
}
return await this.getCurrentPage();
}
async goToPage(page: number) {
this.currentPage = Math.max(0, page);
return await this.getCurrentPage();
}
async getTotalPages() {
const results = await this.getCurrentPage();
return Math.ceil(results.totalCount / this.pageSize);
}
}
// Usage
const paginator = new StockPaginator(screener, 50);
// First page
const page1 = await paginator.getCurrentPage();
console.log(`Page 1: ${page1.data.length} results`);
// Next page
const page2 = await paginator.nextPage();
console.log(`Page 2: ${page2.data.length} results`);
// Jump to page 5
const page5 = await paginator.goToPage(5);Export to CSV
typescript
async function exportToCSV(screener: StockScreener, filename: string) {
const pageSize = 150;
let page = 0;
const rows: string[] = [];
// Add header
rows.push('Symbol,Name,Price,Volume');
while (true) {
screener.setRange(page * pageSize, (page + 1) * pageSize);
const results = await screener.get();
// Add data rows
results.data.forEach(row => {
rows.push(`${row.symbol},${row.name},${row.close},${row.volume}`);
});
console.log(`Exported page ${page + 1}...`);
if (results.data.length < pageSize) {
break;
}
page++;
}
// Write to file
const fs = require('fs');
fs.writeFileSync(filename, rows.join('\n'));
console.log(`Exported ${rows.length - 1} results to ${filename}`);
}
// Usage
const screener = new StockScreener();
screener
.where(StockField.MARKET_CAPITALIZATION.gt(1e9))
.select(StockField.NAME, StockField.PRICE, StockField.VOLUME);
await exportToCSV(screener, 'stocks.csv');Parallel Fetching
typescript
async function fetchPagesInParallel(
screener: StockScreener,
startPage: number,
endPage: number,
pageSize: number = 50
) {
const promises = [];
for (let page = startPage; page <= endPage; page++) {
const pageScreener = new StockScreener();
// Copy filters from original screener
pageScreener.setRange(page * pageSize, (page + 1) * pageSize);
promises.push(pageScreener.get());
}
const results = await Promise.all(promises);
// Combine all pages
const allData = results.flatMap(r => r.data);
return {
data: allData,
totalCount: results[0]?.totalCount || 0
};
}
// Usage: Fetch pages 0-4 in parallel
const results = await fetchPagesInParallel(screener, 0, 4, 50);
console.log(`Fetched ${results.data.length} total results`);Performance Considerations
Optimal Page Size
typescript
// Too small: Many requests
screener.setRange(0, 10); // Only 10 results
// Good: Balanced
screener.setRange(0, 50); // 50 results
screener.setRange(0, 100); // 100 results
// Large: Fewer requests
screener.setRange(0, 150); // 150 results
// Recommended maximum
screener.setRange(0, 150); // Don't exceed thisRate Limiting
Add delays between pages to avoid rate limiting:
typescript
async function getAllResultsWithDelay(screener: StockScreener) {
const pageSize = 150;
const allResults: any[] = [];
let page = 0;
while (true) {
screener.setRange(page * pageSize, (page + 1) * pageSize);
const results = await screener.get();
allResults.push(...results.data);
if (results.data.length < pageSize) {
break;
}
page++;
// Delay between pages
if (results.data.length === pageSize) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
return { data: allResults, totalCount: allResults.length };
}Handling Errors
Retry Failed Pages
typescript
async function fetchPageWithRetry(
screener: StockScreener,
page: number,
pageSize: number,
maxRetries: number = 3
) {
let retries = 0;
while (retries < maxRetries) {
try {
screener.setRange(page * pageSize, (page + 1) * pageSize);
return await screener.get();
} catch (error) {
retries++;
console.error(`Page ${page} failed (attempt ${retries}):`, error);
if (retries === maxRetries) {
throw error;
}
// Wait before retry
await new Promise(resolve => setTimeout(resolve, 1000 * retries));
}
}
}Partial Results on Error
typescript
async function getAllResultsWithErrorHandling(screener: StockScreener) {
const pageSize = 150;
const allResults: any[] = [];
let page = 0;
let consecutiveErrors = 0;
while (consecutiveErrors < 3) {
try {
screener.setRange(page * pageSize, (page + 1) * pageSize);
const results = await screener.get();
allResults.push(...results.data);
consecutiveErrors = 0; // Reset on success
if (results.data.length < pageSize) {
break;
}
page++;
} catch (error) {
console.error(`Failed to fetch page ${page}:`, error);
consecutiveErrors++;
page++; // Skip failed page and continue
}
}
return {
data: allResults,
totalCount: allResults.length,
hasErrors: consecutiveErrors > 0
};
}Memory Management
Processing Pages Individually
typescript
// Good: Process each page separately
async function processInPages(screener: StockScreener) {
const pageSize = 150;
let page = 0;
while (true) {
screener.setRange(page * pageSize, (page + 1) * pageSize);
const results = await screener.get();
// Process page
processPage(results.data);
// Data is garbage collected after this
if (results.data.length < pageSize) {
break;
}
page++;
}
}
// Avoid: Accumulating all data
async function accumulateAll(screener: StockScreener) {
const allResults = []; // Grows indefinitely
let page = 0;
while (true) {
screener.setRange(page * pageSize, (page + 1) * pageSize);
const results = await screener.get();
allResults.push(...results.data); // Memory grows
page++;
}
}Cursor-Based Pagination
For very large datasets, implement cursor-based pagination:
typescript
async function* paginateWithCursor(screener: StockScreener, pageSize: number = 50) {
let page = 0;
while (true) {
screener.setRange(page * pageSize, (page + 1) * pageSize);
const results = await screener.get();
if (results.data.length === 0) {
break;
}
yield {
data: results.data,
page,
hasMore: results.data.length === pageSize,
totalCount: results.totalCount
};
if (results.data.length < pageSize) {
break;
}
page++;
}
}
// Usage with async generator
for await (const page of paginateWithCursor(screener, 50)) {
console.log(`Page ${page.page}: ${page.data.length} results`);
console.log(`Has more: ${page.hasMore}`);
// Process page
processData(page.data);
}Best Practices
1. Use Reasonable Page Sizes
typescript
// Good: Standard page sizes
const pageSize = 50; // Good for UI
const pageSize = 100; // Good balance
const pageSize = 150; // Max recommended
// Avoid: Extreme sizes
const pageSize = 10; // Too small
const pageSize = 1000; // Too large2. Check Total Count First
typescript
// Get first page to check total
const firstPage = await screener.setRange(0, 50).get();
if (firstPage.totalCount > 10000) {
console.log('Large dataset, consider adding filters');
}3. Add Progress Indication
typescript
async function fetchWithProgress(screener: StockScreener) {
const pageSize = 150;
const firstPage = await screener.setRange(0, pageSize).get();
const totalPages = Math.ceil(firstPage.totalCount / pageSize);
const allResults = [...firstPage.data];
for (let page = 1; page < totalPages; page++) {
screener.setRange(page * pageSize, (page + 1) * pageSize);
const results = await screener.get();
allResults.push(...results.data);
const progress = ((page + 1) / totalPages * 100).toFixed(1);
console.log(`Progress: ${progress}% (${page + 1}/${totalPages} pages)`);
}
return allResults;
}4. Handle Empty Results
typescript
screener.setRange(0, 50);
const results = await screener.get();
if (results.data.length === 0) {
console.log('No results found');
return;
}
console.log(`Found ${results.totalCount} total results`);Next Steps
- Performance Tips - Optimizing queries
- Streaming Data - Real-time updates
- Error Handling - Handling errors