No description
  • TypeScript 66.9%
  • PLpgSQL 14.8%
  • CSS 10.4%
  • HTML 4.6%
  • Shell 3.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
sam 41e0331ade Keep futures contracts out of the menu
The page offers every pair that has candles behind it, which was the
whole list until a product's contracts became pairs of their own: two
hundred of them would be the menu, and nobody opens December 2019 gold
by name. They carry their product in parent, so one condition leaves
them out.

The filter goes in AND over an OR, so the existing condition is now
parenthesised: without that it would have offered the contracts that
have ticks and hidden everything else.

What belongs here is one series per product - the continuous one while
there is nothing else, and the series stitched from the roll calendar
once that exists. `ohlcdb pair list --contracts` is where a contract is
visible, as it is for a pair that holds nothing.
2026-10-01 06:50:21 +02:00
build/alpine Widen a window that comes back empty instead of stopping there 2026-09-15 09:04:45 +02:00
public Put the chart's mark in the tab 2026-09-12 18:33:35 +02:00
src Name the instrument in the pair menu, not the symbol 2026-09-15 16:34:38 +02:00
.gitignore Add the chart page 2026-09-11 23:47:07 +02:00
api.sql Keep futures contracts out of the menu 2026-10-01 06:50:21 +02:00
config.json Name and colour the Wall Street session 2026-09-15 15:30:41 +02:00
index.html Go to a date, with a calendar of the days that traded 2026-09-13 23:06:40 +02:00
LICENSE Make the package: PostgREST, the service, the README 2026-09-12 15:51:27 +02:00
package-lock.json Add the chart page 2026-09-11 23:47:07 +02:00
package.json Add the chart page 2026-09-11 23:47:07 +02:00
postgrest.conf Expand the settings before PostgREST reads them 2026-09-12 17:36:06 +02:00
README.md Name the instrument in the pair menu, not the symbol 2026-09-15 16:34:38 +02:00
THIRD-PARTY Put the chart's mark in the tab 2026-09-12 18:33:35 +02:00
tsconfig.json Add the chart page 2026-09-11 23:47:07 +02:00
vite.config.ts Add the chart page 2026-09-11 23:47:07 +02:00

ohlcdb-web

A chart page for the candles ohlcdb collects: candlesticks, the trading sessions as boxes behind them, the previous day, week and month as lines, and a table of the newest sessions.

The page is static files. Its data comes from PostgREST over the api schema that api.sql makes, and both the page and PostgREST are in this package.

Requirements

  • A database filled by ohlcdb, reachable from this host.
  • A web server to publish /usr/share/ohlcdb-web and to pass /api through to PostgREST under the same address as the page. Which web server is the host's business, not this package's.

Install

apk add ohlcdb-web

It brings its own PostgREST, creates the ohlcdb-web user, installs the page in /usr/share/ohlcdb-web and two files in /etc/ohlcdb-web.

Setup

  1. Make the API in the database, as a PostgreSQL superuser, after ohlcdb's schema.sql and levels.sql, then give the web role a password:

    psql -d ohlcdb -f /usr/share/ohlcdb-web/api.sql
    psql -d ohlcdb -c '\password ohlcdb_web'
    
  2. Put that password in the PASSWORD= line of /etc/ohlcdb-web/postgrest.conf, and the database's address in its db-uri.

    A NAME=value line there is not one of PostgREST's settings: the service expands $NAME in the rest of the file and starts PostgREST on the result, written to /run/ohlcdb-web/postgrest.conf. So the password is in the one file that already holds secrets, and not on a command line or in the environment, where ps and a stray env would show it.

    The value is whatever follows the = to the end of the line, quotes and spaces and $ included, so a password needs no escaping of its own. The db-uri names keywords and values rather than a URI for the same reason: no percent-encoding. What libpq does ask is that a ' or a \ inside the password be written \' and \\.

    The service will not start while a variable is empty, or while the file uses one that nothing sets: it says which, by name. Comments are left alone, so a $NAME written in one is only text.

  3. Start it:

    rc-service ohlcdb-web start
    

    curl -s localhost:3000/pairs then answers with the pairs.

  4. Publish the page, and let /api reach 127.0.0.1:3000 under the same address. Until that is arranged, the page can talk to PostgREST directly: set server-host in postgrest.conf to an address the browser can reach, and api in config.json to http://that-address:3000. PostgREST answers a request from another origin on its own.

Configuration

/etc/ohlcdb-web/config.json holds what the page starts with. Every key is optional: the build carries a copy of these defaults, so a file left behind by an upgrade, or a broken one, costs at most the settings it names. A value that fails its check is reported in the browser's console and ignored.

Key What it sets
api where the API is; /api for a web server in front, a full address otherwise
feed the source to open: broker, environment and account type joined by `
pair the pair to open, by the broker's name for it
period the candle length to open
barType time, tick, renko, range or heikin: what ends a bar
tickCounts the ticks-per-bar the row offers
tickCount the count to open with
renkoSizes the brick sizes the row offers, in pips; each says what it is worth on the pair in view in its tooltip
rangeSizes the range sizes it offers, in pips
renkoSize the brick to open with
rangeSize the range to open with
barWindowDays how far back a renko or range load computes, from the bar asked for and not from today; a window that comes back nearly empty - a large size crosses its grid rarely - is asked again twice as wide, up to eight times that
timeZone the zone the times are shown in
timeZones the zones the selector offers
candles green-red, hollow or blue-red
theme auto, light or dark
sessions which sessions start shown, by name: {"asia": true, ...}
levels which of day, week and month start shown
sessionColors a light and a dark colour per session name
lots how many lots the ruler reckons a move in money for
scrollMargin how far the chart can be dragged past its data, as a fraction of the width; 0.5 stops with the outermost bar in the middle
refreshSeconds how often the page asks for what is new
candlesPerLoad how many candles a load brings
chartHeight how tall the chart starts, in pixels; 0 leaves it to the stylesheet

What is chosen on the page outlives a reload in that browser and wins over the file; the "Reset" button clears those choices and goes back to it.

The default session colours pass the colour-blindness and contrast checks in both themes. Colours put in the file do not: they are used as written.

The page

  • Controls, in three rows, with no captions: every control shows the value it holds, and what it is for is in its tooltip. Anything written there can be clicked.
    • the pair, the time zone and the candle style; in the middle of the page the source behind the prices - the broker, whether the account is live or demo, its type; and at the far end the two arrows, the theme and reset, as symbols;
    • what ends a bar - time, tick, renko, range or Heikin Ashi - and beside it what that kind is measured in: the periods for time and Heikin Ashi, the ticks per bar, or the size in pips. One block, the current one filled; and at the far end of that row the drawing tools;
    • a chip per session, in the colour of its boxes, and one per level, showing the line it is drawn with. A chip is the key and the switch at once, so each session can be turned off on its own; and at the end of that row, above the price scale, the padlock and full screen.
  • The menu names the instrument, not the symbol it was sold under: XAUUSD for a broker's XAUUSD.raw and GC for Databento's GC.v.0, with the whole name in the tooltip. What follows the first dot belongs to the seller - an account type, or how a continuous contract rolls - and the source is named beside the menu anyway. Two pairs of one feed that would read alike keep their own names.
  • The pairs offered are those with something stored behind them: a pair whose collection never ran has no kind of bar to draw and is left out of the menu rather than shown and then drawn empty, which is the rule the kinds of bar follow too. ohlcdb pair list on the collector is where such a pair is still visible.
  • The source: with more than one, the broker plate opens a layer over the page listing them - broker, live or demo, account type - in alphabetical order, so the two accounts of one broker stand together. Choosing one narrows the pair menu to that source's pairs and keeps the instrument by name where it exists in both. With a single source there is nothing to choose and the plate is a plate.
  • Candles, a thousand at a time, more as the chart scrolls back.
  • Renko and range bars, at the size chosen in pips, built by the server from the M1 candles rather than stored: no size could be, since the reader picks it. The hourly candles are asked first which hours could hold a bar at that size, and only those have their minutes read, so a coarse size over years costs what a fine one over days does. A brick is a move of one size across a grid of prices; a range bar closes when it has travelled one size between its own high and low. Both are drawn on the same time axis as the candles, which is why they sit unevenly across it: a minute can hold twenty of them, and a quiet hour none.
  • Tick bars, of as many ticks as chosen, counted the way cTrader counts them: the bid and the ask are one stream of events and the price at each is the bid, which is the rule that rebuilds its candles exactly. So a bar of 500 here is a bar of 500 there. They need the ticks ohlcdb downloads, and only reach as far back as those do; a stretch where no bid tick was downloaded draws nothing.
  • Heikin Ashi, which is not a bar of its own but a reading of the candles - the close is the bar's average price and the open is where the last reading stood - so it keeps the periods, and the sessions and levels with them. Its highs and lows are not the real ones, so the previous day's and week's lines will not touch the wicks.
  • Sessions as boxes from a session's low to its high, across its own hours on any period, dashed while the session is still under way. The sessions are whatever the database holds: one added there gets its chip without the page being rebuilt.
  • Levels: the previous day, week and month, each with its own line style and its label on the price scale.
  • A readout of open, high, low and close under the pointer, and a table of the newest sessions, which reads without hovering.
  • Drawing, by hand: a horizontal line at a price, a vertical one at a moment, or a line between two points. One button carries all three: pressing it again moves to the next and, after the third, puts it down. A tool is armed until it has been used, until Esc, or until something outside the chart is clicked.
    • Click a drawing to take it: it thickens and shows its handles. Drag a handle to move that end, or the line itself to move the whole of it. Delete removes it, as does the bin; with nothing taken the bin offers to remove every drawing of the pair, and asks first.
    • Ctrl+Z steps back through the last twenty changes, which is what a line moved by accident needs.
    • A point takes the time of the candle under it, and its price when the pointer is within a few pixels of that candle's open, high, low or close, so a line off a high starts on the high. Alt while placing or dragging leaves the point where the pointer is.
    • The swatch beside the tools opens the eight colours; picking one sets what the next line is drawn in, and recolours the line in hand if there is one. A horizontal line writes its price on the price scale and a vertical one its time on the time scale, in its own colour, the way the day's and week's levels do.
    • Drawings belong to the pair, not to the period: they are kept as a time and a price, so they hold their place across periods, across renko, range and tick bars, and across a reload. They live in the browser, and the two arrows beside the theme write them to a JSON file and read one back - the pairs a file names replace what is held for them, the rest are left alone.
  • The ruler measures a move: press it and drag, or hold Shift and drag without pressing anything. It reads the move in pips, in the quote currency, as a percentage and - when the broker has said how many units a lot holds - in money for the lots named by lots; below that, how many bars were crossed and how long it took. The reading stays until the next thing is done, and is never stored: it is a question, not a drawing.
  • The arrows jump to the newest candle, or to the oldest one held - which is a thousand back, not the start of the history; arriving there asks for more, as scrolling there does.
  • The padlock is the state of the price scale rather than a mode of its own: shut, the prices follow the candles and the chart cannot be dragged up or down; dragging the price scale unfits them and the padlock opens by itself; shutting it again, with the button or L, fits the candles back.
  • Full screen on the chart with the button or F; Esc leaves it. There the controls are out of sight, so the chart shows the session colours as a key in its corner, which it has no reason to elsewhere.
  • A limit to the dragging: the chart stops with the newest or oldest bar in the middle of the screen rather than scrolling off into the empty space, scrollMargin being how much of that space it allows.
  • A height of its own: the strip under the time axis drags the chart taller or shorter, and the height stays for that browser. With the strip focused the arrows move it by 16 pixels and PageUp/PageDown by 64; Home, or a double click, goes back to the height chartHeight asks for.

Times are shown in the chosen zone but stay UTC inside the chart: shifting them would repeat an hour when daylight saving ends, which the chart refuses.

What to know about the derived bars, since nothing on screen says it:

  • The size of a brick or a range is in pips, and what a pip is worth comes from the broker: ohlcdb stores the pip position the symbol declares. Until it has, the page takes a pip to be ten of the last digit, which is the usual convention and right for gold and for the rates.
  • They come from the M1 candles, and within a minute the order of the high and the low is unknown. It is taken from the close, the usual reading: a minute that closed up is assumed to have made its low first. The ticks would settle it for the days that have them.
  • A brick's closes sit on a grid of multiples of its size, so asking for more history does not redraw what is already on screen. The cost is that a reversal takes one brick and not the two of the classic rule, so price hovering over a grid line alternates. A range bar chain has no grid to hold on to, so scrolling far back can shift where its bars begin.
  • A brick, a range bar and a tick bar exist once they are complete, so none of them has a bar under way: the chart's newest bar stands still between updates, where a candle grows. Heikin Ashi has one, being candles.
  • A page of tick bars is bounded by the ticks it reads and not by time, so it costs the same at any size, and each page counts from its own end: the bars near a seam can hold different events than they would have in one long run.
  • Volume is left out of them on purpose. What ohlcdb stores from cTrader is a count of price updates, and splitting a minute's count between the bars it spans would be inventing a number.

Development

The build needs Node; running the page needs nothing. In a container or chroot with nodejs and npm:

npm ci
OHLCDB_API=http://localhost:3000 npm run dev

Vite serves the page and forwards /api to the PostgREST named by OHLCDB_API, so a PostgREST started by hand against the database is enough to work on the page. Started by hand it wants the expanded settings, which the renderer writes anywhere:

/usr/lib/ohlcdb-web/render /etc/ohlcdb-web/postgrest.conf /tmp/postgrest.conf

npm run build writes dist, which is what the package installs, and checks the types first.

License

GPL-3.0; see LICENSE.

The page is drawn with TradingView Lightweight Charts, under Apache-2.0, whose code the build folds into the files in /usr/share/ohlcdb-web. Its attribution notice is in THIRD-PARTY, installed beside them, since minifying drops the one the library's own source carries and the page turns the library's attribution logo off.