Field manual

StockHours

Whether the stock market is trading, answered on Robinhood Chain by the feeds themselves

What it is

Robinhood Chain carries eighteen Chainlink price feeds for US stocks. Nothing on the chain says when the market behind them is trading, so every contract that cares has to guess with a timestamp, keep a calendar, or trust somebody to publish one

StockHours reads the feeds instead. Robinhood's stock tokens trade 24 hours a day on weekdays, and the feeds follow: they write rounds all week and stop dead from Saturday to Monday, and on the holidays the market takes. Thirty days of rounds were measured before the rule was chosen, and over that month the weekends and Labor Day carried zero rounds across all eighteen feeds. The reading is a view call, costs nothing, and cannot be changed after deployment

What it answers is the trading week, not the 13:30 to 20:00 regular session. The session leaves a density peak in the data, not an edge, so no rule over feed activity separates it: the measurements show the best attempt reaching 89% and calling the market open before the bell on one day in five. For that, use a calendar

ChainRobinhood Chain · 4663
Panel18 Chainlink stock feeds
Ownernone, no pause, no upgrade
Readingfree view call, no storage touched

The rule

Count the feeds whose last update falls inside the window. If that count reaches the quorum, the market is on

active = feeds where now − updatedAt ≤ window open = active ≥ quorum

A feed with a price of zero or a timestamp in the future is skipped, so a broken feed cannot vote. Both numbers are immutable, and both are visible on the panel at all times

Window—
Quorum—
Agreement with the trading week99.6% over 30 days, 99.8% over 7
Lag at the weekly open60 seconds, over five opens
Overhang after the close5 minutes median, 19 worst, over four closes

Both numbers came out of the measurement rather than a guess: a four hour window is the first one wide enough to ride through an overnight lull without blinking shut, and five of eighteen is the count that refuses to call the market on when only the two busiest feeds are awake

The log

The reading answers about this second. The log answers about last Tuesday. Anyone may call poke(); when the reading disagrees with the log, the contract opens or closes an entry, which in practice is one trading week

The stamps are not the time of the call. An open carries the tick that first carried the quorum, and a close carries the last tick seen while the session was running, so a session that is poked a few times through the day lines up with the real bell even when nobody rings at the close. Each entry also keeps the two poke times, so you can see how well it was attended

Session ├── openedAt the tick that carried the quorum ├── closedAt the last tick seen before the feeds went quiet ├── openPoke when the open was recorded ├── closePoke when the close was recorded └── peak most feeds seen active at once

Reading it

One interface, all views except poke

function isOpen() view returns (bool);
function requireOpen() view;                       // reverts with MarketClosed()
function activity() view returns (uint8 active, uint64 newest);
function state() view returns (bool open, uint8 active, uint8 total, uint64 newest, uint64 sinceTick, uint64 pokeAge, uint256 sessions);
function feeds() view returns (string[] symbols, address[] addrs, int256[] answers, uint64[] updatedAts, bool[] active);
function sessions(uint256 from, uint256 count) view returns (Session[]);
function lastSession() view returns (Session);
function usualDay() view returns (uint32 open, uint32 close, uint256 counted);
function poke() returns (bool open);

A poke over the eighteen feeds cost 94,670 gas in the fork tests. usualDay averages the closed sessions in the log: it describes what has happened, not what will

How wrong it can be

The rule lags. The weekly open is caught within a minute, because the feeds come back in a burst. The close is slower: the week is held open for up to the window past the last tick, which measured five minutes on median and nineteen at worst

It can be fooled by a quiet market. Over the thirty days measured the deployed rule blinked shut thirteen times inside a trading week, for a median of two minutes and 79 at the longest, adding up to 143 minutes in a month. A gate that must never flicker should read activity() and apply its own hysteresis

It reads no calendar, so a holiday appears as a week that never opened and needs no maintenance. A half day has not been seen yet: thirty days contained none, so that behaviour is unverified

What was measured

Eight thousand seven hundred and eighty nine rounds, walked back one by one through each feed's round counter over thirty days. No read failed and no history was truncated, so the numbers are the feeds' own record rather than a sample of it

Every candidate rule was then replayed minute by minute against that record and scored. The calibration page has the table, the month in bars, and the five weekly opens to the second

Rounds read8,789 over 30 days · 2,037 over 7
Weekend rounds0, every weekend, all 18 feeds
Holiday2026-09-07, Labor Day: 0 rounds, week reopened Tuesday 00:00:23 UTC
Weekly open00:00:19 to 00:00:25 UTC, five out of five

The panel

The eighteen feeds the contract is deployed with, in order

reading…

$STOCKHOURS

A token on Pons. It is not a fee, not a key and not a share: the contract charges nothing and works the same whether or not you hold any

Ticker$STOCKHOURS
LaunchpadPons
Contractsoon

Contract

StockHoursnot deployed yet
Sourcecontracts/src/MarketHours.sol, solc 0.8.28, optimizer 200
Tests99 checks on a fork of Robinhood Chain mainnet