Skip to content

Instantly share code, notes, and snippets.

@bholzer
Last active January 26, 2025 04:10
Show Gist options
  • Save bholzer/8758b1343731125b618457b9e466eed2 to your computer and use it in GitHub Desktop.
Save bholzer/8758b1343731125b618457b9e466eed2 to your computer and use it in GitHub Desktop.
QuantData time lapse

QuantData Time Lapse

This script uses a browser (via puppeteer) to log into your QuantData account, visit a specified page that has time sliders, and then step through intervals and screenshot the page at each interval.

This is useful to, for example, view exposure graphs as they evolve throughout the day.

Usage

This requires a page in QuantData, with tools that contain a time slider control. The script does not do any setup of your tools (dates, filters, etc), you should manually set up the page for the conditions that you want to see.

Node.js is required.

Download index.js and package.json to the same directory. Downloading the zip from this gist and extracting it somewhere should work well. Run npm install from that directory to install the dependencies.

node index.js will run the script.

Options:

Usage: index [options]

Options:
  -u, --user <username>      QuantData username
  -p, --password <password>  QuantData password
  -q, --page <page>          QuantData page to visit
  -s, --start <start>        Start minute (default: 510)
  -e, --end <end>            End minute (default: 960)
  -i, --interval <interval>  Interval (minutes) (default: 5)
  -d, --delay <delay>        Delay before screenshot (ms) (default: 1500)
  -o, --output <output>      Output directory (default: "2025-01-26T03-55-11")
  -x, --display              Display browser window (default: false)
  -h, --help                 display help for command

The defaults are reasonable and will capture a time lapse of a full trading day in 5 minute intervals. It assumes you have eastern time zone selected in QD. If you are using a different time zone, you will have to adjust your --start and --end options accordingly.

--start and --end are minutes from 0 to 1440 (for a 24-hour day), and define a time frame for which screenshots are generated.

--page is the full URL to a built-in or custom QuantData page.

--username, --password, and --page are required.

Screenshots are saved to a directory with a default name that is the time when the script was started. It is recommended to specify your own directory name, that identifies what it is you are investigating. For example, spx-2025-01-24

Examples

I have set up a page in QD with exposure graphs, and now I want to see how they evolved between noon and 2PM (ET) in 10 minute intervals

node index.js -u myuser -p mypassword --page https://v3.quantdata.us/page/built-in/exposure -s 720 -e 840 -i 10

const puppeteer = require('puppeteer');
const { program } = require('commander');
const fs = require('fs/promises');
// Define default output directory
const scriptStartTime = new Date();
const defaultOutputDir = scriptStartTime.toISOString().replace(/:/g, '-').replace(/\..+/, '');
program
.option('-u, --user <username>', 'QuantData username')
.option('-p, --password <password>', 'QuantData password')
.option('-q, --page <page>', 'QuantData page to visit')
.option('-s, --start <start>', 'Start minute', 510)
.option('-e, --end <end>', 'End minute', 960)
.option('-i, --interval <interval>', 'Interval (minutes)', 5)
.option('-d, --delay <delay>', 'Delay before screenshot (ms)', 1500)
.option('-o, --output <output>', 'Output directory', defaultOutputDir)
.option('-x, --display', 'Display browser window', false)
.parse();
const {
user,
password,
page: pageUrl,
start,
end,
interval,
delay,
output,
display
} = program.opts();
async function ensureDirectoryExists(directoryPath) {
try {
await fs.mkdir(directoryPath, { recursive: true });
console.log(`Directory ensured: ${directoryPath}`);
} catch (err) {
console.error(`Failed to ensure directory: ${err.message}`);
}
}
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Puppeteer main function
(async () => {
await ensureDirectoryExists(output);
const browser = await puppeteer.launch({ headless: !display });
const page = await browser.newPage();
await page.setViewport({ width: 1920, height: 1080 });
try {
// 1. Login
await page.goto('https://v3.quantdata.us/login', { waitUntil: 'networkidle2' });
await page.locator('input[autocomplete="username"]').fill(user);
await page.locator('input[autocomplete="password"]').fill(password);
await page.locator('button[type="submit"]').click();
await page.waitForNetworkIdle();
// 2. Navigate to target page
await page.goto(pageUrl, { waitUntil: 'networkidle2' });
// 3. Move slider from `start` to `end`
let currentMinute = parseInt(start);
while (currentMinute <= parseInt(end)) {
console.log(`Moving slider to minute: ${currentMinute}`);
await page.evaluate((minute) => {
function dragSliderWithMouse(trackElement, fraction) {
const rect = trackElement.getBoundingClientRect();
const x = rect.left + rect.width * fraction;
const y = rect.top + rect.height / 2;
const down = new MouseEvent('mousedown', {
clientX: x,
clientY: y,
bubbles: true,
cancelable: true
});
const move = new MouseEvent('mousemove', {
clientX: x,
clientY: y,
bubbles: true,
cancelable: true
});
const up = new MouseEvent('mouseup', {
clientX: x,
clientY: y,
bubbles: true,
cancelable: true
});
trackElement.dispatchEvent(down);
trackElement.dispatchEvent(move);
trackElement.dispatchEvent(up);
}
function moveAllMantineSliders(desiredValue) {
const trackEls = document.querySelectorAll('.mantine-Slider-track');
trackEls.forEach((trackEl) => {
const thumbEl = trackEl.querySelector('.mantine-Slider-thumb');
if (!thumbEl) return;
const minVal = parseFloat(thumbEl.getAttribute('aria-valuemin'));
const maxVal = parseFloat(thumbEl.getAttribute('aria-valuemax'));
const fraction = (desiredValue - minVal) / (maxVal - minVal);
dragSliderWithMouse(trackEl, fraction);
});
}
moveAllMantineSliders(minute);
}, currentMinute);
// 4. Screenshot
await wait(parseInt(delay));
await page.screenshot({ path: `${output}/${currentMinute}.png` });
// 5. Increment to next interval
currentMinute += parseInt(interval);
await page.waitForNetworkIdle();
}
} catch (error) {
console.error('An error occurred:', error);
} finally {
// Always close the browser
await browser.close();
}
})();
{
"name": "qd-puppet",
"version": "0.0.1",
"dependencies": {
"commander": "^13.1.0",
"puppeteer": "^24.1.1"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment