What it does: it beeps 1 minute before each bar closes, to remind me to get back to the chart and watch how the bar closes.
How I use it: click “Start alarms” and go do something else. When I know the next few bars won’t give me a trade, I hit “Skip next bar” or “Mute 15 min”.
My biggest problem as a trader is drifting off while waiting for a setup: when the wait gets long, I start looking at my phone or something else, and by the time I snap back, the signal bar has already closed.
So I needed an alarm that goes off right before each 5-minute bar closes. For example, bar 1 is 09:30–09:35, so it rings at 09:34; bar 2 at 09:39, bar 3 at 09:44, and so on.
US Eastern time
--:--:--
Paused: nothing rings until you click Start (browsers need a click before they can play sound)
Today's barsClick a bar to turn it on or off; the current session opens automatically
Times are US Eastern, with daylight saving handled automatically. Alarms only ring during RTH on weekdays; exchange holidays are not skipped automatically. Keep this page open (a background tab is fine) and don't let the computer sleep. Settings and skipped bars are stored only in this browser. Shortcuts: N skips the next bar, R restores all.
How to use it
- Click “Start alarms”: browsers only allow sound after you’ve clicked on the page. Nothing will ring before you click.
- Keep the page open: you can switch to another tab or app, but don’t close this page, and don’t let the computer go to sleep.
- When you know the next bars won’t give you a trade:
- “Skip next bar” (or press N on the keyboard) skips only the next alarm;
- “Skip next 2” and “Skip next 3” work the same way;
- “Mute 15 min / 30 min” is handy for the slow midday stretch;
- the list is grouped into morning, midday, afternoon and the last bars, and the current session opens automatically; click any bar to turn it off on its own, for example if you only want an alarm before bar 18 closes.
- If you change something by mistake: hit “Restore all” (or press R).
Settings
Click the gear in the top-right corner of the tool to open the settings:
- Bar size: 1, 2, 3, 5, 10 or 15 minutes; 5 minutes by default.
- How early it rings: 15 seconds to 2 minutes before the close; 1 minute by default.
- RTH hours: 09:30–16:15 US Eastern by default; you can change it to cover only the morning, for example 09:30–11:30.
- Volume and system notifications: with notifications on, a reminder also pops up in the corner of the screen.
A few details
- Times are US Eastern, with daylight saving time handled automatically. I’m in China and my computer runs on Beijing time; that doesn’t matter.
- It only rings Monday through Friday. It doesn’t skip exchange holidays (like Thanksgiving) automatically, so just hit “Pause” on those days.
- It rings on time even in a background tab: browsers slow down timers in background tabs, so the sounds are scheduled ahead of time on the browser’s audio clock instead of relying on timers.
- Settings and skipped bars are stored only in this computer’s browser and aren’t uploaded anywhere.
Definition and how to reproduce it
Bar close alarm
- Time: US Eastern (America/New_York), converted with the browser’s built-in Intl, so daylight saving time is handled automatically.
- Bars: counted from the RTH open. Bar 1 is 09:30–09:35, bar 2 is 09:35–09:40, and so on up to the RTH close (16:15 by default): 81 bars a day on the 5-minute chart.
- Alarm time: bar close − lead time. The default is 1 minute before the close, so bar 1 rings at 09:34.
- Trading days: Monday to Friday; exchange holidays aren’t skipped.
- Sound: two short beeps (880 Hz and 1175 Hz), scheduled ahead of time on the browser’s audio clock so they ring on time even in a background tab.
- Skipping: only the bars you pick are skipped; muting for 15 / 30 minutes silences every alarm in that window.
- Prompt and code: a simplified version that keeps only the timed alarm and “Skip next bar”. Save it as an .html file and open it in a browser. The grouped list, muting and system notifications are left out.
Please write a single HTML file (no libraries) that works when opened in a browser: it beeps shortly before each bar closes, to remind me to get back to the chart. The rules: 1. Times are US Eastern (America/New_York), with daylight saving time handled automatically; my computer runs on Beijing time, and it still has to be right. Use the browser's built-in Intl.DateTimeFormat for the conversion; don't hard-code the offset. 2. Only Monday to Friday, RTH 09:30–16:15. Bars are 5 minutes: bar 1 is 09:30–09:35, bar 2 is 09:35–09:40, and so on. No need to skip exchange holidays. 3. Beep 60 seconds before each bar closes: bar 1 at 09:34, bar 2 at 09:39. Put the bar size, the lead time and the RTH start and end times in constants at the top of the file, so I can change them. 4. Generate the sound with Web Audio: two short beeps (880 Hz and 1175 Hz, about 0.2 seconds each). Browsers only allow sound after a click on the page, so add a "Start alarms" button and create the AudioContext only when it's clicked. 5. Browsers slow down timers in background tabs, so don't wait for a timer to fire before making the sound: work out how many seconds until the next alarm and schedule the sound ahead of time on the AudioContext's audio clock. When the next bar to ring changes (the last alarm has rung, or I clicked "skip"), cancel the alarm that hasn't rung yet and schedule again; don't cut off one that's already playing. 6. Add a "Skip next bar" button: it skips the next alarm; clicking it twice skips two bars. 7. Show the current US Eastern time (hh:mm:ss) and the time of the next alarm with its bar number; refresh every 0.25 seconds. Please comment every step in English, explaining why it's done that way.
<!doctype html>
<!-- Bar close alarm: two beeps LEAD seconds before each bar closes (US Eastern, RTH Monday to Friday) -->
<!-- How to use: save as an .html file, open it in a browser, click "Start alarms" and keep the page open -->
<meta charset="utf-8">
<title>Bar close alarm</title>
<button id="run">Start alarms</button>
<button id="skip">Skip next bar</button>
<p id="now"></p>
<p id="next"></p>
<script>
const TF = 5; // bar size (minutes)
const LEAD = 60; // seconds before the close to ring
const FROM = 9 * 60 + 30; // RTH start 09:30 (US Eastern, in minutes)
const TO = 16 * 60 + 15; // RTH end 16:15
// US Eastern time: converted with Intl, so DST is handled and the computer's own time zone doesn't matter
const fmt = new Intl.DateTimeFormat("en-US", {
timeZone: "America/New_York", hourCycle: "h23",
year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit",
});
function et(ms) {
const p = Object.fromEntries(fmt.formatToParts(new Date(ms)).map(x => [x.type, +x.value]));
return { y: p.year, mo: p.month, d: p.day, h: p.hour, mi: p.minute, s: p.second };
}
// the real moment of minute `minutes` on a US Eastern day: guess it as UTC, then subtract the gap between the guess and what Eastern time shows
function etToMs(y, mo, d, minutes) {
const guess = Date.UTC(y, mo - 1, d, 0, minutes);
const p = et(guess);
return guess - (Date.UTC(p.y, p.mo - 1, p.d, p.h, p.mi) - guess);
}
const pad = n => String(n).padStart(2, "0");
const clock = ms => { const p = et(ms); return `${pad(p.h)}:${pad(p.mi)}:${pad(p.s)}`; };
// the next bar whose alarm is still ahead: search from today on, skipping Saturdays and Sundays (not exchange holidays)
function nextBar(after) {
const t = et(after);
for (let step = 0; step < 7; step++) {
const day = new Date(Date.UTC(t.y, t.mo - 1, t.d + step));
if (day.getUTCDay() === 0 || day.getUTCDay() === 6) continue;
for (let i = 1; FROM + i * TF <= TO; i++) {
const close = etToMs(day.getUTCFullYear(), day.getUTCMonth() + 1, day.getUTCDate(), FROM + i * TF);
if (close - LEAD * 1000 > after) return { n: i, close, alarm: close - LEAD * 1000 };
}
}
}
let ctx = null; // browsers only allow sound after a click, so it's created on "Start"
let planned = null; // the alarm already scheduled { at, nodes }
let skipUntil = 0; // alarms up to this moment are skipped
// two short beeps, scheduled on the audio clock: timers slow down in background tabs, the audio clock doesn't
function beepAt(when) {
return [[0, 880], [0.22, 1175]].map(([offset, freq]) => {
const osc = ctx.createOscillator(), g = ctx.createGain();
osc.frequency.value = freq;
g.gain.setValueAtTime(0, when + offset);
g.gain.linearRampToValueAtTime(0.6, when + offset + 0.01);
g.gain.exponentialRampToValueAtTime(0.0001, when + offset + 0.18);
osc.connect(g).connect(ctx.destination);
osc.start(when + offset);
osc.stop(when + offset + 0.2);
return osc;
});
}
function tick() {
const now = Date.now();
const bar = nextBar(Math.max(now, skipUntil));
document.getElementById("now").textContent = "US Eastern " + clock(now);
document.getElementById("next").textContent = ctx
? `Next alarm ${clock(bar.alarm)} (before bar ${bar.n} closes)`
: "Not started";
// the bar to ring has changed (its time came, or "skip" was clicked): cancel the old one and schedule again
if (ctx && planned?.at !== bar.alarm) {
if (planned?.at > now) planned.nodes.forEach(n => n.stop()); // don't cut off one that's already playing
planned = { at: bar.alarm, nodes: beepAt(ctx.currentTime + (bar.alarm - now) / 1000) };
}
}
document.getElementById("run").onclick = () => { ctx ??= new AudioContext(); ctx.resume(); tick(); };
document.getElementById("skip").onclick = () => { skipUntil = nextBar(Math.max(Date.now(), skipUntil)).alarm; tick(); };
setInterval(tick, 250);
tick();
</script>