Skip to content

Feature eodtime#108

Open
alowrydi wants to merge 7 commits into
mainfrom
feature-eodtime
Open

Feature eodtime#108
alowrydi wants to merge 7 commits into
mainfrom
feature-eodtime

Conversation

@alowrydi

Copy link
Copy Markdown
Contributor

EOD time management module, di.eodtime

Extracts code/common/eodtime.q from TorQ and packages it as a standalone kdb-x module. Manages end-of-day timing for tickerplant processes, resolves the current trading date in a configurable timezone, calculates when the next EOD roll is due in UTC, and provides a UTC offset used to timestamp incoming market data.

Trello ticket - https://trello.com/c/rMbaJnZU/87-kdb-x-eod-module


Files created

File Description
di/eodtime/init.q Loads di.tz dependency, loads eodtime.q, defines export of 9 functions
di/eodtime/eodtime.q Full implementation - module state, internal helpers, public API, init
di/eodtime/eodtime.md Full module documentation - see below
di/eodtime/test.csv 49 k4unit tests across 15 test groups

How to test

q
k4unit:use`di.k4unit
k4unit.moduletest`di.eodtime
q)k4unit.moduletest`di.eodtime
2026.06.25T12:42:53.458 start
2026.06.25T12:42:53.458 :/home/alowry/bin/local-kdbx-modules/di/eodtime/test.csv 49 test(s)
2026.06.25D11:42:53.460976931 info PID[1899172] HOST[homer.aquaq.co.uk] eodtime: initialised with rolltimezone=GMT datatimezone=GMT rolltimeoffset=0D00:00:00.000000000
2026.06.25D11:42:53.461021538 info PID[1899172] HOST[homer.aquaq.co.uk] eodtime: normalisation test
2026.06.25T12:42:53.461 end
All tests passed
q)exec sum ok from k4unit.getresults[] // 49/49 pass
49i

Coverage includes: all 9 exported functions, GMT/UTC shortcut behaviour, timezones in both winter and summer (DST transitions), offset behaviour, dep validation (all four failure modes), info-only log dict, kx.log instance acceptance via normlog, and capturing logger assertion.


Design decisions

1. di.tz as hard dependency - Rather than duplicating timezone conversion logic, di.tz is loaded via tz:use\di.tzininit.q`.

2. Setter and getter functions - In TorQ, processes update .eodtime.* variables directly (e.g. stplog.q does .eodtime.d+:1 after each EOD roll). Since direct assignment inside a module is not possible, setter functions (setd, setnextroll, setdailyadj) were introduced so values can be updated at runtime.

3. UTC-equivalent timezone shortcuts - "GMT", "UTC" and "Etc/GMT" are not in the timezone database used by di.tz and cause a notValidTimezone error if passed through. Since "GMT" is TorQ's default timezone, these are short-circuited to return 0D directly, ensuring the module works out of the box with existing TorQ deployments. Note: "Etc/UTC" is valid in di.tz and passes through normally.

4. Graceful DST handling - Non-GMT timezones are handled at runtime rather than caching a static offset, so DST transitions are picked up automatically. Verified in the test suite with Europe/London in both winter (UTC+0) and summer (UTC+1).

5. Required log dependency with normlog - The log dep is required; init errors immediately if absent or malformed. Only info is required since the module never calls warn or error. The normlog internal function detects a kx.log instance by the presence of getlvl, sinks, fmts keys and wraps its monadic functions into the binary {[c;m]} contract automatically, so callers can pass a kx.log instance directly without manual wrapping.

6. Single deps dict - Config values (rolltimezone, datatimezone, rolltimeoffset) and the log dependency are passed together in a single deps dict to init, consistent with the project dependency injection guidelines. All config keys are optional with sensible defaults; only log is required.


Checklist

  • 49/49 k4unit tests passing
  • Follows consistency.md and style.md
  • Follows dependency injection guidelines
  • eodtime.md documents all exported functions, config, usage examples and notes
  • di.tz rename PR Renaming of timezone to tz #91 merged

Documentation

See eodtime.md for full reference including dependency table, configuration options, exported function documentation with examples, usage example, and notes.

@alowrydi

Copy link
Copy Markdown
Contributor Author

The review calls have proved very productive in providing consistency and holding each other accountable, which has now led to my confident submission for this module. A few design decisions were made within the team, most of which are laid out in the design decisions section of the PR, but I also wanted to point out a few that we came up with and some reasoning behind them, as arguments can be made for multiple approaches to each of these situations, but importantly we have consolidated our approaches together in the interest of consistency. We welcome any feedback on these decisions if better alternatives exist, as they will apply to all modules going forward.

Single deps dict for config and dependencies
Rather than splitting init into init[config;deps], config values (rolltimezone, datatimezone, rolltimeoffset) and the log dependency are passed together in a single deps dictionary. Callers omit any config keys they don't need and the module falls back to sensible defaults. This keeps the init signature clean and consistent with the dependency injection guidelines. In the case where a particular value must be defined, the init function will throw (e.g. for the info log argument in this case), but otherwise defaults are set up for other configurable parameters. This design decision was made to align with the consistency.md.
di.tz as a hard dependency
Timezone conversion is loaded via tz:use`di.tz in init.q rather than being injected. The distinction here is that di.tz is a pure utility with no meaningful alternative implementation, so injection would add friction without adding flexibility. This is the intended distinction between hard deps found in init.q and injected deps passed to the init[deps] function.
info-only log validation
For this module, the log dependency is required, but only info is mandatory. Initially, warn and error were also considered mandatory, causing the module to throw if they are not present, and that will be the case going forward for modules which require them. However, in the interest of cutting unnecessary code, there is no need to impose that restriction here. Other logging arguments are still accepted if present (e.g. from a full kx.log instance) but the module never calls them, so requiring them would place unnecessary burden on callers. This is a conscious and considered deviation from the all-three-required pattern, which we plan to impose going forward.
normlog pattern
The internal normlog function detects a kx.log instance by the presence of getlvl, sinks, fmts keys and wraps its monadic functions into the binary {[c;m]} contract automatically. This means callers can pass a kx.log instance directly without manual wrapping. Worth flagging as a reusable pattern for other modules, as it sets a standard for the kx.log format, but does not necessarily restrict the user to have to use it.
.md file template
The sections in the .md file for the module were agreed as the standard to be included going forward. We decided to remove the TorQ migration pattern section, as Jonny specified he wants these modules to be able to act as standalone products too, so this table may only serve to confuse the client.

Ruairi-wq2 added a commit that referenced this pull request Jun 30, 2026
- Log functions updated to dyadic {[ctx;msg]} contract; kx.log instances
  normalised via normlog to wrap monadic functions into the required form
- init validates log at minimum an `info key; warn/error not required
- modulesetup.q renamed to integrationtest.q with dynamic port via -p flag
- test.html subscription fixed to call sub via evaluate (wssub removed);
  snapshot now rendered into table panel on subscribe
- Docs updated to reflect log contract, normlog behaviour, and sub dispatch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@alowrydi

alowrydi commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

On the note of the di.tz dependency, there is potential here to combine the di.tz and di.eodtime into one module to remove hard dependencies in these (standalone) modules and for the sake of "neatness".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant