PineForge unreleased
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
session_time.hpp
Go to the documentation of this file.
1#pragma once
2#include <cstdint>
3#include <ctime>
4#include <string>
5
6namespace pineforge {
7
8// TradingView accepts fixed-offset names like "GMT+1" / "UTC-5" with the
9// human-readable sign convention. POSIX TZ strings use the opposite sign, so
10// engine code that calls setenv("TZ", ...) must normalize through this helper.
11std::string normalize_timezone_for_posix(const std::string& tz);
12
13// ---------------------------------------------------------------------------
14// Timeframe bucket open / close for a bar timestamp (the `time(timeframe,
15// session?, timezone?)` and `time_close(...)` family a source frontend lowers
16// to).
17// Returns Unix milliseconds, or na<int64_t>() when the bar is outside the
18// requested session (TradingView semantics for filtered sessions).
19//
20// `syminfo_tz` is the symbol's exchange timezone (Pine `syminfo.timezone`;
21// codegen passes `syminfo_.timezone`). It is the DEFAULT zone the `session`
22// window is interpreted in when `tz` is empty — Pine reads a tz-less session
23// in the exchange timezone, not UTC. An explicit `tz` always wins. Under a
24// D/W/M `tf` a valid `session` is its own bar, in that zone (see the
25// symbol-clock forms below); without one the D/W/M open/close is the
26// explicit `tz`'s calendar, else UTC's, exactly as before. An empty
27// `syminfo_tz` reproduces the historical UTC session default byte-for-byte.
28//
29// Feature macro: generated code tests this to decide whether the trailing
30// `syminfo_tz` argument exists, so one transpiler output compiles against
31// both this header and older ones (`PF_PINE_TIME_SYMINFO_TZ_ARG(x)` in the
32// emitted prelude expands to `, (x)` only when it is defined).
33// ---------------------------------------------------------------------------
34
35#define PF_PINE_TIME_HAS_SYMINFO_TZ 1
36
37int64_t timeframe_time(int64_t bar_ms,
38 const std::string& tf,
39 const std::string& session,
40 const std::string& tz,
41 const std::string& chart_tf,
42 const std::string& syminfo_tz = std::string());
43
44int64_t timeframe_time_close(int64_t bar_ms,
45 const std::string& tf,
46 const std::string& session,
47 const std::string& tz,
48 const std::string& chart_tf,
49 const std::string& syminfo_tz = std::string());
50
51// Symbol-clock forms. `sym_tz` / `sym_session` are Pine's syminfo.timezone
52// and syminfo.session (the engine's runtime overrides). For a D/W/M `tf`
53// called WITHOUT a valid session argument the returned open / close is the
54// SYMBOL's daily/weekly/monthly bar (17:00 ET on OANDA forex, 09:30 ET RTH
55// on NASDAQ equities, 00:00 UTC on a 24x7 UTC symbol — see
56// session_period_open_ms in timeframe.hpp). A valid `session` argument is
57// the bar TradingView builds on that session, in the explicit `tz`, else
58// `sym_tz`, else UTC, on every chart (lab tv tapes w12-tfd*,
59// tests/fixtures/session_period): each session day is one D bar from its
60// first window's open to its last window's close (`time("D", "1700-2400",
61// "America/New_York")` opens at 17:00 New York, `time("D", "0000-2359",
62// "America/New_York")` at New York's midnight), na where no day bar holds the
63// bar; a W / M bar opens at its week's or month's first session day and is
64// never na; time_close of a W / M is the next period's open on an intraday
65// chart and its last session day's close on a daily-or-higher one.
66// An intraday `tf` without a session argument is the symbol's
67// day-stamp-anchored HTF grid bucket (session_intraday_bucket_open_ms in
68// timeframe.hpp — the grid request.security aggregates on): time("60") on
69// NYSE:F is 09:30 / 10:30 / .. / 15:30 ET, time("240") on NSE:NIFTY 09:15 /
70// 13:15 IST and on OANDA:XAUUSD the 17:00-ET-anchored 4h grid (pin-time-hours
71// tapes, 2025-04-01..07-01); the five-argument forms stay on the epoch grid.
72// With a session argument it is the session's own grid: tf bars from each
73// window's open, the last cut at its close (time("240", "1800-1700") on
74// OANDA:XAUUSD opens at 18:00 ET, time(timeframe.period, "0930-1600") on
75// BINANCE:BTCUSDT 60 at 09:30), na outside every window.
76// With sym_tz="UTC" and an empty / "24x7" sym_session these are
77// bit-identical to the five-argument forms above.
78//
79// Feature macro: generated code tests this to decide whether the trailing
80// (sym_tz, sym_session) pair exists (`PF_PINE_TIME_SESSION_DAY_ARGS(tz, s)`
81// in the emitted prelude expands to `, tz, s` only when it is defined, so
82// the same generated.cpp collapses to the 5-arg call on older engines).
83#define PF_PINE_TIME_HAS_SESSION_DAY 1
84
85int64_t timeframe_time(int64_t bar_ms,
86 const std::string& tf,
87 const std::string& session,
88 const std::string& tz,
89 const std::string& chart_tf,
90 const std::string& sym_tz,
91 const std::string& sym_session);
92
93int64_t timeframe_time_close(int64_t bar_ms,
94 const std::string& tf,
95 const std::string& session,
96 const std::string& tz,
97 const std::string& chart_tf,
98 const std::string& sym_tz,
99 const std::string& sym_session);
100
101// ---------------------------------------------------------------------------
102// Low-level session helpers (exposed for engine_run.cpp, unit tests,
103// session_trading_day_open_ms, and session predicates).
104// ---------------------------------------------------------------------------
105// Convert "HHMM" string to minutes-since-midnight. Returns -1 on parse error.
106int hhmm_to_minutes(const std::string& hhmm);
107
108// Pine time/date extraction from a Unix-ms bar timestamp in timezone `tz`.
109// Value-identical to Pine hour()/minute()/dayofweek()/... (dayofweek 1=Sun..7=Sat,
110// month 1..12, year full). Codegen routes hour()/minute()/... through these
111// instead of an inline setenv+tzset lambda, which stops the per-call macOS
112// tzset()->notifyd IPC storm: UTC uses tzset-free gmtime_r, other zones
113// use the cached ScopedTimezone. DST-correct (localtime_r via the tz database).
114int local_hour(int64_t bar_ms, const std::string& tz);
115int local_minute(int64_t bar_ms, const std::string& tz);
116int local_second(int64_t bar_ms, const std::string& tz);
117int local_dayofmonth(int64_t bar_ms, const std::string& tz);
118int local_dayofweek(int64_t bar_ms, const std::string& tz);
119int local_month(int64_t bar_ms, const std::string& tz);
120int local_year(int64_t bar_ms, const std::string& tz);
121int local_weekofyear(int64_t bar_ms, const std::string& tz);
122
123// True when local_tm falls within any of the comma-separated HHMM-HHMM
124// windows in windows_body. "24x7" or empty always returns true.
125bool local_time_in_session_windows(const std::string& windows_body,
126 const struct tm& local_tm);
127
128// True when the symbol's session trades at bar_ms: a window of it on one of
129// its session days, where a session that names no days and has trading hours
130// trades Monday to Friday (the weekday rule session_period_last_traded_close_ms
131// reads), and 24x7 or an empty session every day. Exchange holidays are not
132// modelled. The calendar a Pine time() reading a bar past its data steps.
133bool session_trades_at(const std::string& session, const std::string& tz, int64_t bar_ms);
134
135// True when bar_ms (Unix ms) falls inside the session string for the given tz.
136bool passes_session_filter(const std::string& session,
137 const std::string& tz,
138 int64_t bar_ms);
139
140// Floor bar_ms to local-calendar midnight in the given IANA timezone.
141// DST edge-case: if mktime() returns -1 (non-existent midnight — e.g.,
142// America/Havana, Pacific/Lord_Howe spring-forward), the implementation
143// retries with midnight + 1 h and midnight + 2 h to find a valid epoch
144// (i.e. uses the first available second after the gap). A warning is
145// logged once per occurrence via fprintf(stderr). The fallback is
146// conservative: the returned timestamp is never more than 2 hours later
147// than the true calendar-day start; downstream consumers
148// (session_trading_day_open_ms) inherit the same semantics.
149int64_t calendar_day_open_local_ms(int64_t bar_ms, const std::string& tz);
150
151// -------------------------------------------------------------------------
152// time_tradingday built-in
153// -------------------------------------------------------------------------
154// Returns the Unix-ms timestamp of the session-open of the trading day
155// that contains bar_ms. See implementation for full algorithm + DST notes.
156int64_t session_trading_day_open_ms(int64_t bar_ms,
157 const std::string& session,
158 const std::string& tz);
159
160// ---------------------------------------------------------------------------
161// Session predicates backing session.is* Pine v6 variables.
162//
163// syminfo.session is the regular session: session.isfirstbar_regular /
164// islastbar_regular mark its day, and session.isfirstbar / islastbar the
165// chart's, which on an extended-hours chart takes in the pre- and post-market
166// bars the chart holds (PineStrategyHost's session flags, tests/
167// test_session_extended_day_tape.cpp). A chart without extended hours holds
168// none, so there each pair is one bar.
169//
170// Pre/post-market predicates assume standard US ETH windows around the day:
171// premarket : 04:00 – its first open, postmarket: its last close – 20:00,
172// over every window in any order; a break between windows is neither, and an
173// overnight or 24-hour session has neither (TradingView's TSE, HKEX, CBOT, CME
174// and OANDA tapes, tests/test_session_prepost_windows_tape.cpp).
175// ---------------------------------------------------------------------------
176
177bool session_in_market(const std::string& session,
178 const std::string& tz,
179 int64_t bar_ms);
180
181bool session_in_premarket(const std::string& session,
182 const std::string& tz,
183 int64_t bar_ms);
184
185bool session_in_postmarket(const std::string& session,
186 const std::string& tz,
187 int64_t bar_ms);
188
189// Chart-timeframe forms: the rule the session.is* variables of a CHART bar
190// follow. TradingView evaluates them on the chart bar, and a daily-or-higher
191// bar covers whole session days rather than a time of day, so on "1D" and
192// above it documents session.ismarket as always true and session.ispremarket
193// / session.ispostmarket as always false (Pine docs, Sessions). OANDA:XAUUSD
194// @1D made the gap physical: an 1800-1700 America/New_York session and a tape
195// stamped at the 17:00 ET break put every daily open OUTSIDE the time-of-day
196// windows above, session.ismarket never held, and a script that ANDs every
197// signal with it took 0 trades against TradingView's 57. With an intraday or
198// empty chart_tf these forms defer to the three-argument time-of-day forms
199// above, byte for byte.
200bool session_in_market(const std::string& session,
201 const std::string& tz,
202 int64_t bar_ms,
203 const std::string& chart_tf);
204
205bool session_in_premarket(const std::string& session,
206 const std::string& tz,
207 int64_t bar_ms,
208 const std::string& chart_tf);
209
210bool session_in_postmarket(const std::string& session,
211 const std::string& tz,
212 int64_t bar_ms,
213 const std::string& chart_tf);
214
215// ---------------------------------------------------------------------------
216// Deprecated `pine_*` spellings. They are exact inline forwards, kept so
217// generated code and the source adapter compile unchanged; new code calls the
218// neutral names above.
219// ---------------------------------------------------------------------------
220
221inline int64_t pine_time(int64_t bar_ms, const std::string& tf,
222 const std::string& session, const std::string& tz,
223 const std::string& chart_tf,
224 const std::string& syminfo_tz = std::string()) {
225 return timeframe_time(bar_ms, tf, session, tz, chart_tf, syminfo_tz);
226}
227
228inline int64_t pine_time(int64_t bar_ms, const std::string& tf,
229 const std::string& session, const std::string& tz,
230 const std::string& chart_tf, const std::string& sym_tz,
231 const std::string& sym_session) {
232 return timeframe_time(bar_ms, tf, session, tz, chart_tf, sym_tz, sym_session);
233}
234
235inline int64_t pine_time_close(int64_t bar_ms, const std::string& tf,
236 const std::string& session, const std::string& tz,
237 const std::string& chart_tf,
238 const std::string& syminfo_tz = std::string()) {
239 return timeframe_time_close(bar_ms, tf, session, tz, chart_tf, syminfo_tz);
240}
241
242inline int64_t pine_time_close(int64_t bar_ms, const std::string& tf,
243 const std::string& session, const std::string& tz,
244 const std::string& chart_tf, const std::string& sym_tz,
245 const std::string& sym_session) {
246 return timeframe_time_close(bar_ms, tf, session, tz, chart_tf, sym_tz, sym_session);
247}
248
249inline int pine_hour(int64_t bar_ms, const std::string& tz) { return local_hour(bar_ms, tz); }
250inline int pine_minute(int64_t bar_ms, const std::string& tz) { return local_minute(bar_ms, tz); }
251inline int pine_second(int64_t bar_ms, const std::string& tz) { return local_second(bar_ms, tz); }
252inline int pine_dayofmonth(int64_t bar_ms, const std::string& tz) { return local_dayofmonth(bar_ms, tz); }
253inline int pine_dayofweek(int64_t bar_ms, const std::string& tz) { return local_dayofweek(bar_ms, tz); }
254inline int pine_month(int64_t bar_ms, const std::string& tz) { return local_month(bar_ms, tz); }
255inline int pine_year(int64_t bar_ms, const std::string& tz) { return local_year(bar_ms, tz); }
256inline int pine_weekofyear(int64_t bar_ms, const std::string& tz) { return local_weekofyear(bar_ms, tz); }
257
258inline int64_t pine_time_tradingday(int64_t bar_ms, const std::string& session,
259 const std::string& tz) {
260 return session_trading_day_open_ms(bar_ms, session, tz);
261}
262
263inline bool pine_session_ismarket(const std::string& session, const std::string& tz,
264 int64_t bar_ms) {
265 return session_in_market(session, tz, bar_ms);
266}
267
268inline bool pine_session_ispremarket(const std::string& session, const std::string& tz,
269 int64_t bar_ms) {
270 return session_in_premarket(session, tz, bar_ms);
271}
272
273inline bool pine_session_ispostmarket(const std::string& session, const std::string& tz,
274 int64_t bar_ms) {
275 return session_in_postmarket(session, tz, bar_ms);
276}
277
278inline bool pine_session_ismarket(const std::string& session, const std::string& tz,
279 int64_t bar_ms, const std::string& chart_tf) {
280 return session_in_market(session, tz, bar_ms, chart_tf);
281}
282
283inline bool pine_session_ispremarket(const std::string& session, const std::string& tz,
284 int64_t bar_ms, const std::string& chart_tf) {
285 return session_in_premarket(session, tz, bar_ms, chart_tf);
286}
287
288inline bool pine_session_ispostmarket(const std::string& session, const std::string& tz,
289 int64_t bar_ms, const std::string& chart_tf) {
290 return session_in_postmarket(session, tz, bar_ms, chart_tf);
291}
292
293// ---------------------------------------------------------------------------
294// TradingView's session clock across a daylight-saving switch (TradingView
295// tapes of synthetic BINANCE:ETHUSDT.P 15 scripts,
296// tests/fixtures/session_transition_clock). With an intraday `tf`,
297// timeframe_time / timeframe_time_close read a session argument's occurrence
298// that starts or ends at the switch's pre-transition wall time on the
299// switch's date (New York 02:00 both ways, London 02:00 in autumn and 01:00
300// in spring) on the post-transition offset throughout, and every other
301// occurrence's repeated and skipped hour as TradingView does
302// (session_argument_intraday_bar in src/session_time.cpp). The tapes pin a
303// script's own session argument in New York and London; other zones follow
304// the same form untaped.
305// `transition_wall_clock` = false restores the plain wall-clock windows the
306// engine read before. Process-wide and on by default; no script, input or
307// public API reaches it: only tests change it.
308// ---------------------------------------------------------------------------
309namespace detail {
314
315// The close of the intraday `tf` bar holding `bar_ms` on the SYMBOL's own
316// session `session` in `tz`: the chart's time_close as the Pine host reads it
317// on an intraday chart (timeframe_time_close's six-argument reading, no
318// syminfo zone). It keeps the plain wall-clock windows: the session clock
319// above is pinned on script arguments only, and no tape has a symbol session
320// with an endpoint at its zone's switch hour yet.
321int64_t symbol_session_time_close(int64_t bar_ms, const std::string& tf,
322 const std::string& session, const std::string& tz,
323 const std::string& chart_tf);
324} // namespace detail
325
326} // namespace pineforge
int64_t symbol_session_time_close(int64_t bar_ms, const std::string &tf, const std::string &session, const std::string &tz, const std::string &chart_tf)
SessionClockSwitches & session_clock_switches() noexcept
bool pine_session_ispostmarket(const std::string &session, const std::string &tz, int64_t bar_ms)
int local_year(int64_t bar_ms, const std::string &tz)
int64_t pine_time(int64_t bar_ms, const std::string &tf, const std::string &session, const std::string &tz, const std::string &chart_tf, const std::string &syminfo_tz=std::string())
int local_dayofweek(int64_t bar_ms, const std::string &tz)
int pine_year(int64_t bar_ms, const std::string &tz)
int local_dayofmonth(int64_t bar_ms, const std::string &tz)
std::string normalize_timezone_for_posix(const std::string &tz)
int local_minute(int64_t bar_ms, const std::string &tz)
int64_t session_trading_day_open_ms(int64_t bar_ms, const std::string &session, const std::string &tz)
int pine_weekofyear(int64_t bar_ms, const std::string &tz)
bool pine_session_ispremarket(const std::string &session, const std::string &tz, int64_t bar_ms)
int local_weekofyear(int64_t bar_ms, const std::string &tz)
int64_t timeframe_time(int64_t bar_ms, const std::string &tf, const std::string &session, const std::string &tz, const std::string &chart_tf, const std::string &syminfo_tz=std::string())
int local_month(int64_t bar_ms, const std::string &tz)
bool passes_session_filter(const std::string &session, const std::string &tz, int64_t bar_ms)
bool local_time_in_session_windows(const std::string &windows_body, const struct tm &local_tm)
bool session_in_market(const std::string &session, const std::string &tz, int64_t bar_ms)
int pine_dayofmonth(int64_t bar_ms, const std::string &tz)
int pine_month(int64_t bar_ms, const std::string &tz)
bool session_trades_at(const std::string &session, const std::string &tz, int64_t bar_ms)
int64_t calendar_day_open_local_ms(int64_t bar_ms, const std::string &tz)
bool pine_session_ismarket(const std::string &session, const std::string &tz, int64_t bar_ms)
int pine_minute(int64_t bar_ms, const std::string &tz)
bool session_in_premarket(const std::string &session, const std::string &tz, int64_t bar_ms)
int64_t timeframe_time_close(int64_t bar_ms, const std::string &tf, const std::string &session, const std::string &tz, const std::string &chart_tf, const std::string &syminfo_tz=std::string())
int pine_dayofweek(int64_t bar_ms, const std::string &tz)
int pine_second(int64_t bar_ms, const std::string &tz)
int hhmm_to_minutes(const std::string &hhmm)
int local_second(int64_t bar_ms, const std::string &tz)
int pine_hour(int64_t bar_ms, const std::string &tz)
int local_hour(int64_t bar_ms, const std::string &tz)
bool session_in_postmarket(const std::string &session, const std::string &tz, int64_t bar_ms)
int64_t pine_time_tradingday(int64_t bar_ms, const std::string &session, const std::string &tz)
int64_t pine_time_close(int64_t bar_ms, const std::string &tf, const std::string &session, const std::string &tz, const std::string &chart_tf, const std::string &syminfo_tz=std::string())