PyAlgoEngine 0.12.3__cp315-cp315-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- algo_engine/__infra__.pxd +3 -0
- algo_engine/__init__.pxd +3 -0
- algo_engine/__init__.py +107 -0
- algo_engine/apps/__init__.py +17 -0
- algo_engine/apps/backtest/__init__.py +20 -0
- algo_engine/apps/backtest/doc_server.py +328 -0
- algo_engine/apps/backtest/static/styles/dash.css +48 -0
- algo_engine/apps/backtest/templates/dash.html +20 -0
- algo_engine/apps/backtest/templates/index.html +40 -0
- algo_engine/apps/backtest/tester.py +252 -0
- algo_engine/apps/backtest/web_app.py +125 -0
- algo_engine/apps/bokeh_server.py +245 -0
- algo_engine/apps/demo/__init__.py +0 -0
- algo_engine/apps/demo/test.py +40 -0
- algo_engine/apps/sim_input/__init__.py +23 -0
- algo_engine/apps/sim_input/client.py +412 -0
- algo_engine/apps/sim_input/sim_keyboard.py +88 -0
- algo_engine/apps/sim_input/sim_mouse.py +137 -0
- algo_engine/apps/sim_input/window.py +162 -0
- algo_engine/backtest/__init__.py +19 -0
- algo_engine/backtest/__main__.py +51 -0
- algo_engine/backtest/metrics.py +179 -0
- algo_engine/backtest/replay.py +635 -0
- algo_engine/backtest/sim_match.py +506 -0
- algo_engine/base/__infra__.pxd +3 -0
- algo_engine/base/__init__.pxd +3 -0
- algo_engine/base/__init__.py +60 -0
- algo_engine/base/c_allocator_protocol.c +11608 -0
- algo_engine/base/c_allocator_protocol.cp315-win_amd64.pyd +0 -0
- algo_engine/base/c_allocator_protocol.pxd +24 -0
- algo_engine/base/c_allocator_protocol.pyi +68 -0
- algo_engine/base/c_allocator_protocol.pyx +111 -0
- algo_engine/base/c_intern_string.c +5908 -0
- algo_engine/base/c_intern_string.cp315-win_amd64.pyd +0 -0
- algo_engine/base/c_intern_string.pxd +14 -0
- algo_engine/base/c_intern_string.pyi +22 -0
- algo_engine/base/c_intern_string.pyx +17 -0
- algo_engine/base/c_market_data/__infra__.pxd +196 -0
- algo_engine/base/c_market_data/__init__.pxd +196 -0
- algo_engine/base/c_market_data/__init__.py +24 -0
- algo_engine/base/c_market_data/c_candlestick.c +18996 -0
- algo_engine/base/c_market_data/c_candlestick.cp315-win_amd64.pyd +0 -0
- algo_engine/base/c_market_data/c_candlestick.pxd +27 -0
- algo_engine/base/c_market_data/c_candlestick.pyi +217 -0
- algo_engine/base/c_market_data/c_candlestick.pyx +255 -0
- algo_engine/base/c_market_data/c_internal.c +14059 -0
- algo_engine/base/c_market_data/c_internal.cp315-win_amd64.pyd +0 -0
- algo_engine/base/c_market_data/c_internal.pxd +14 -0
- algo_engine/base/c_market_data/c_internal.pyi +47 -0
- algo_engine/base/c_market_data/c_internal.pyx +41 -0
- algo_engine/base/c_market_data/c_market_data.c +30420 -0
- algo_engine/base/c_market_data/c_market_data.cp315-win_amd64.pyd +0 -0
- algo_engine/base/c_market_data/c_market_data.h +1464 -0
- algo_engine/base/c_market_data/c_market_data.pxd +414 -0
- algo_engine/base/c_market_data/c_market_data.pyi +550 -0
- algo_engine/base/c_market_data/c_market_data.pyx +701 -0
- algo_engine/base/c_market_data/c_market_data_buffer.c +29407 -0
- algo_engine/base/c_market_data/c_market_data_buffer.cp315-win_amd64.pyd +0 -0
- algo_engine/base/c_market_data/c_market_data_buffer.h +941 -0
- algo_engine/base/c_market_data/c_market_data_buffer.pxd +143 -0
- algo_engine/base/c_market_data/c_market_data_buffer.pyi +355 -0
- algo_engine/base/c_market_data/c_market_data_buffer.pyx +571 -0
- algo_engine/base/c_market_data/c_market_data_config.h +29 -0
- algo_engine/base/c_market_data/c_tick.c +44303 -0
- algo_engine/base/c_market_data/c_tick.cp315-win_amd64.pyd +0 -0
- algo_engine/base/c_market_data/c_tick.pxd +53 -0
- algo_engine/base/c_market_data/c_tick.pyi +466 -0
- algo_engine/base/c_market_data/c_tick.pyx +673 -0
- algo_engine/base/c_market_data/c_trade_utils.c +28702 -0
- algo_engine/base/c_market_data/c_trade_utils.cp315-win_amd64.pyd +0 -0
- algo_engine/base/c_market_data/c_trade_utils.pxd +53 -0
- algo_engine/base/c_market_data/c_trade_utils.pyi +602 -0
- algo_engine/base/c_market_data/c_trade_utils.pyx +609 -0
- algo_engine/base/c_market_data/c_transaction.c +23558 -0
- algo_engine/base/c_market_data/c_transaction.cp315-win_amd64.pyd +0 -0
- algo_engine/base/c_market_data/c_transaction.pxd +27 -0
- algo_engine/base/c_market_data/c_transaction.pyi +433 -0
- algo_engine/base/c_market_data/c_transaction.pyx +460 -0
- algo_engine/base/console_utils.py +1070 -0
- algo_engine/base/finance_decimal.py +258 -0
- algo_engine/base/telemetrics.py +18 -0
- algo_engine/engine/__infra__.pxd +10 -0
- algo_engine/engine/__init__.pxd +10 -0
- algo_engine/engine/__init__.py +40 -0
- algo_engine/engine/algo_engine.py +904 -0
- algo_engine/engine/c_event_engine.c +16325 -0
- algo_engine/engine/c_event_engine.cp315-win_amd64.pyd +0 -0
- algo_engine/engine/c_event_engine.pxd +25 -0
- algo_engine/engine/c_event_engine.pyi +68 -0
- algo_engine/engine/c_market_engine.c +24258 -0
- algo_engine/engine/c_market_engine.cp315-win_amd64.pyd +0 -0
- algo_engine/engine/c_market_engine.pxd +87 -0
- algo_engine/engine/c_market_engine.pyi +357 -0
- algo_engine/engine/event_engine.py +53 -0
- algo_engine/engine/trade_engine.py +2037 -0
- algo_engine/exchange_profile/__infra__.pxd +100 -0
- algo_engine/exchange_profile/__init__.pxd +100 -0
- algo_engine/exchange_profile/__init__.py +53 -0
- algo_engine/exchange_profile/c_ex_profile_base.c +87 -0
- algo_engine/exchange_profile/c_ex_profile_base.h +1204 -0
- algo_engine/exchange_profile/c_ex_profile_cn.c +968 -0
- algo_engine/exchange_profile/c_ex_profile_cn.h +39 -0
- algo_engine/exchange_profile/c_exchange_profile.c +52375 -0
- algo_engine/exchange_profile/c_exchange_profile.cp315-win_amd64.pyd +0 -0
- algo_engine/exchange_profile/c_exchange_profile.pxd +336 -0
- algo_engine/exchange_profile/c_exchange_profile.pyi +883 -0
- algo_engine/exchange_profile/c_exchange_profile.pyx +1495 -0
- algo_engine/exchange_profile/c_profile_cn.c +7798 -0
- algo_engine/exchange_profile/c_profile_cn.cp315-win_amd64.pyd +0 -0
- algo_engine/exchange_profile/c_profile_cn.pxd +1 -0
- algo_engine/exchange_profile/c_profile_cn.pyi +3 -0
- algo_engine/exchange_profile/c_profile_cn.pyx +1 -0
- algo_engine/exchange_profile/c_profile_default.c +7798 -0
- algo_engine/exchange_profile/c_profile_default.cp315-win_amd64.pyd +0 -0
- algo_engine/exchange_profile/c_profile_default.pxd +1 -0
- algo_engine/exchange_profile/c_profile_default.pyi +3 -0
- algo_engine/exchange_profile/c_profile_default.pyx +1 -0
- algo_engine/exchange_profile/c_profile_dispatcher.c +7798 -0
- algo_engine/exchange_profile/c_profile_dispatcher.cp315-win_amd64.pyd +0 -0
- algo_engine/exchange_profile/c_profile_dispatcher.pxd +1 -0
- algo_engine/exchange_profile/c_profile_dispatcher.pyi +3 -0
- algo_engine/exchange_profile/c_profile_dispatcher.pyx +1 -0
- algo_engine/includes/algo_engine/base/c_allocator_protocol.c +11608 -0
- algo_engine/includes/algo_engine/base/c_intern_string.c +5908 -0
- algo_engine/includes/algo_engine/base/c_market_data/c_candlestick.c +18996 -0
- algo_engine/includes/algo_engine/base/c_market_data/c_internal.c +14059 -0
- algo_engine/includes/algo_engine/base/c_market_data/c_market_data.c +30420 -0
- algo_engine/includes/algo_engine/base/c_market_data/c_market_data.h +1464 -0
- algo_engine/includes/algo_engine/base/c_market_data/c_market_data_buffer.c +29407 -0
- algo_engine/includes/algo_engine/base/c_market_data/c_market_data_buffer.h +941 -0
- algo_engine/includes/algo_engine/base/c_market_data/c_market_data_config.h +29 -0
- algo_engine/includes/algo_engine/base/c_market_data/c_tick.c +44303 -0
- algo_engine/includes/algo_engine/base/c_market_data/c_trade_utils.c +28702 -0
- algo_engine/includes/algo_engine/base/c_market_data/c_transaction.c +23558 -0
- algo_engine/includes/algo_engine/engine/c_event_engine.c +16325 -0
- algo_engine/includes/algo_engine/engine/c_market_engine.c +24258 -0
- algo_engine/includes/algo_engine/exchange_profile/c_ex_profile_base.c +87 -0
- algo_engine/includes/algo_engine/exchange_profile/c_ex_profile_base.h +1204 -0
- algo_engine/includes/algo_engine/exchange_profile/c_ex_profile_cn.c +968 -0
- algo_engine/includes/algo_engine/exchange_profile/c_ex_profile_cn.h +39 -0
- algo_engine/includes/algo_engine/exchange_profile/c_exchange_profile.c +52375 -0
- algo_engine/includes/algo_engine/exchange_profile/c_profile_cn.c +7798 -0
- algo_engine/includes/algo_engine/exchange_profile/c_profile_default.c +7798 -0
- algo_engine/includes/algo_engine/exchange_profile/c_profile_dispatcher.c +7798 -0
- algo_engine/monitor/__init__.py +15 -0
- algo_engine/monitor/advanced_data_interface.py +334 -0
- algo_engine/strategy/__init__.py +44 -0
- algo_engine/strategy/strategy_engine.py +441 -0
- algo_engine/utils/__init__.py +3 -0
- algo_engine/utils/commit_regularizer.py +49 -0
- algo_engine/utils/data_utils.py +296 -0
- pyalgoengine-0.12.3.dist-info/METADATA +142 -0
- pyalgoengine-0.12.3.dist-info/RECORD +156 -0
- pyalgoengine-0.12.3.dist-info/WHEEL +5 -0
- pyalgoengine-0.12.3.dist-info/licenses/LICENSE +21 -0
- pyalgoengine-0.12.3.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,1204 @@
|
|
|
1
|
+
#ifndef C_EX_PROFILE_BASE_H
|
|
2
|
+
#define C_EX_PROFILE_BASE_H
|
|
3
|
+
|
|
4
|
+
#include <math.h>
|
|
5
|
+
#include <stdbool.h>
|
|
6
|
+
#include <stdint.h>
|
|
7
|
+
#include <stdlib.h>
|
|
8
|
+
#if defined(_WIN32) || defined(_WIN64)
|
|
9
|
+
#include <windows.h>
|
|
10
|
+
typedef SSIZE_T ssize_t;
|
|
11
|
+
#else
|
|
12
|
+
#include <sys/types.h>
|
|
13
|
+
#endif
|
|
14
|
+
#include <time.h>
|
|
15
|
+
|
|
16
|
+
#if defined(_WIN32) || defined(_WIN64)
|
|
17
|
+
/**
|
|
18
|
+
* @brief POSIX-compatible localtime_r/gmtime_r wrappers over the MSVC *_s variants.
|
|
19
|
+
* @return Pointer to result on success, NULL on failure (POSIX semantics).
|
|
20
|
+
*/
|
|
21
|
+
static inline struct tm* localtime_r(const time_t* timer, struct tm* result) {
|
|
22
|
+
return localtime_s(result, timer) == 0 ? result : NULL;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static inline struct tm* gmtime_r(const time_t* timer, struct tm* result) {
|
|
26
|
+
return gmtime_s(result, timer) == 0 ? result : NULL;
|
|
27
|
+
}
|
|
28
|
+
#endif
|
|
29
|
+
|
|
30
|
+
// ========== Constants ==========
|
|
31
|
+
|
|
32
|
+
#ifndef SECONDS_PER_DAY
|
|
33
|
+
#define SECONDS_PER_DAY 86400
|
|
34
|
+
#endif
|
|
35
|
+
|
|
36
|
+
#ifndef SECONDS_PER_HOUR
|
|
37
|
+
#define SECONDS_PER_HOUR 3600
|
|
38
|
+
#endif
|
|
39
|
+
|
|
40
|
+
#ifndef SECONDS_PER_MINUTE
|
|
41
|
+
#define SECONDS_PER_MINUTE 60
|
|
42
|
+
#endif
|
|
43
|
+
|
|
44
|
+
#ifndef NANOS_PER_SECOND
|
|
45
|
+
#define NANOS_PER_SECOND 1000000000
|
|
46
|
+
#endif
|
|
47
|
+
|
|
48
|
+
#ifndef MICROS_PER_SECOND
|
|
49
|
+
#define MICROS_PER_SECOND 1000000
|
|
50
|
+
#endif
|
|
51
|
+
|
|
52
|
+
#ifndef UNIX_EPOCH_ORDINAL
|
|
53
|
+
#define UNIX_EPOCH_ORDINAL ((uint32_t) 719163)
|
|
54
|
+
#endif
|
|
55
|
+
|
|
56
|
+
#ifndef EX_PROFILE_ID_SIZE
|
|
57
|
+
#define EX_PROFILE_ID_SIZE 64
|
|
58
|
+
#endif
|
|
59
|
+
|
|
60
|
+
#ifndef EX_PROFILE_MIN_YEAR
|
|
61
|
+
#define EX_PROFILE_MIN_YEAR ((uint16_t) 1)
|
|
62
|
+
#endif
|
|
63
|
+
|
|
64
|
+
#ifndef EX_PROFILE_MAX_YEAR
|
|
65
|
+
#define EX_PROFILE_MAX_YEAR ((uint16_t) 9999)
|
|
66
|
+
#endif
|
|
67
|
+
|
|
68
|
+
#ifndef EX_PROFILE_MAX_ORDINAL
|
|
69
|
+
#define EX_PROFILE_MAX_ORDINAL ((uint32_t) 3652059)
|
|
70
|
+
#endif
|
|
71
|
+
|
|
72
|
+
static const uint8_t DAYS_IN_MONTH_TABLE[12] = {
|
|
73
|
+
31u, 28u, 31u, 30u, 31u, 30u, 31u, 31u, 30u, 31u, 30u, 31u
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
static const uint16_t DAYS_BEFORE_MONTH_TABLE[12] = {
|
|
77
|
+
0u, 31u, 59u, 90u, 120u, 151u, 181u, 212u, 243u, 273u, 304u, 334u
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
#define HMS_TO_TS(H, M, S) ((double) (H) * SECONDS_PER_HOUR + (double) (M) * SECONDS_PER_MINUTE + (double) (S))
|
|
81
|
+
|
|
82
|
+
// ========== Layout Control ==========
|
|
83
|
+
|
|
84
|
+
#ifndef EX_PROFILE_SESSION_DATE_NO_PADDING
|
|
85
|
+
#define EX_PROFILE_SESSION_DATE_NO_PADDING 1
|
|
86
|
+
#endif
|
|
87
|
+
|
|
88
|
+
// ========== Structs ==========
|
|
89
|
+
|
|
90
|
+
typedef enum session_type {
|
|
91
|
+
SESSION_TYPE_NON_TRADING = 0,
|
|
92
|
+
SESSION_TYPE_NORMINAL = 1 << 0,
|
|
93
|
+
SESSION_TYPE_SUSPENDED = 1 << 1,
|
|
94
|
+
SESSION_TYPE_HALF_DAY = 1 << 2,
|
|
95
|
+
SESSION_TYPE_CIRCUIT_BREAK = 1 << 4,
|
|
96
|
+
} session_type;
|
|
97
|
+
|
|
98
|
+
typedef enum session_phase {
|
|
99
|
+
// 0b1000 << 4 : trading bitmask
|
|
100
|
+
// 0b0100 << 4 : auction bitmask
|
|
101
|
+
// 0b00xx << 4 : variant code
|
|
102
|
+
SESSION_PHASE_UNKNOWN = 0x00, // 0b0000 << 4
|
|
103
|
+
SESSION_PHASE_BREAK = 0x10, // 0b0001 << 4
|
|
104
|
+
SESSION_PHASE_SUSPENDED = 0x20, // 0b0010 << 4
|
|
105
|
+
SESSION_PHASE_CLOSED = 0x30, // 0b0011 << 4
|
|
106
|
+
SESSION_PHASE_PREOPEN = 0x40, // 0b0100 << 4
|
|
107
|
+
SESSION_PHASE_OPEN_AUCTION = 0x80, // 0b1100 << 4
|
|
108
|
+
SESSION_PHASE_CLOSE_AUCTION = 0xC0, // 0b1101 << 4
|
|
109
|
+
SESSION_PHASE_CONTINUOUS = 0xD0, // 0b1000 << 4
|
|
110
|
+
|
|
111
|
+
SESSION_PHASE_MASK = 0xF0
|
|
112
|
+
} session_phase;
|
|
113
|
+
|
|
114
|
+
typedef enum auction_phase {
|
|
115
|
+
AUCTION_PHASE_NONE = 0x00,
|
|
116
|
+
AUCTION_PHASE_ACTIVE = 0x01,
|
|
117
|
+
AUCTION_PHASE_NO_CANCEL = 0x02,
|
|
118
|
+
AUCTION_PHASE_FROZEN = 0x03,
|
|
119
|
+
AUCTION_PHASE_UNCROSSING = 0x04,
|
|
120
|
+
AUCTION_PHASE_DONE = 0x05,
|
|
121
|
+
|
|
122
|
+
AUCTION_PHASE_MASK = 0x0F
|
|
123
|
+
} auction_phase;
|
|
124
|
+
|
|
125
|
+
// typedef struct session_time_t {
|
|
126
|
+
// double elapsed_seconds;
|
|
127
|
+
// uint8_t hour;
|
|
128
|
+
// uint8_t minute;
|
|
129
|
+
// uint8_t second;
|
|
130
|
+
// uint32_t nanosecond;
|
|
131
|
+
// session_phase phase;
|
|
132
|
+
// } session_time_t;
|
|
133
|
+
|
|
134
|
+
typedef struct session_time_t {
|
|
135
|
+
double elapsed_seconds;
|
|
136
|
+
uint32_t nanosecond;
|
|
137
|
+
uint8_t hour;
|
|
138
|
+
uint8_t minute;
|
|
139
|
+
uint8_t second;
|
|
140
|
+
uint8_t phase; // for cache line friendliness, session_phase is stored as uint8_t
|
|
141
|
+
// In the future, the auction_phase will also be stored in this struct, as the lower 4-bits of the phase byte.
|
|
142
|
+
// For now, it is not stored but, using == to compare phase with session_phase enum is not recommended, use bitmasking with SESSION_PHASE_MASK instead.
|
|
143
|
+
} session_time_t;
|
|
144
|
+
|
|
145
|
+
typedef struct session_date_t {
|
|
146
|
+
uint16_t year; // 0-9999
|
|
147
|
+
uint8_t month; // 1-12
|
|
148
|
+
uint8_t day; // 1-31
|
|
149
|
+
#if EX_PROFILE_SESSION_DATE_NO_PADDING
|
|
150
|
+
uint16_t stype; // session_type, widened to uint16_t so the struct has no suffix padding byte.
|
|
151
|
+
#else
|
|
152
|
+
uint8_t stype; // session_type, stored as uint8_t for cache line friendliness.
|
|
153
|
+
#endif
|
|
154
|
+
} session_date_t;
|
|
155
|
+
|
|
156
|
+
#if EX_PROFILE_SESSION_DATE_NO_PADDING
|
|
157
|
+
/* The struct is memcpy'd raw into serialized buffers (e.g. c_md_serialize), so
|
|
158
|
+
* the in-memory image must be fully determined by the fields — no padding — and
|
|
159
|
+
* the widened uint16_t stype pins the byte order. All supported targets
|
|
160
|
+
* (x86-64 Linux, x86-64/ARM64 Windows) are little-endian; a big-endian host
|
|
161
|
+
* only affects loading pre-existing little-endian blobs, so this is an
|
|
162
|
+
* informational diagnostic rather than a hard assertion. */
|
|
163
|
+
#if defined(_WIN32) || defined(_WIN64)
|
|
164
|
+
/* Windows (x86/x64/ARM64) is always little-endian — no diagnostic. */
|
|
165
|
+
#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
|
|
166
|
+
#pragma message("[COMPILE] [DBG] Known big-endian platform: session_date_t raw blobs are byte-order specific; pre-existing little-endian blobs may fail to load. Set EX_PROFILE_SESSION_DATE_NO_PADDING=0 to opt out.")
|
|
167
|
+
#endif
|
|
168
|
+
|
|
169
|
+
_Static_assert(
|
|
170
|
+
sizeof(session_date_t) == 6,
|
|
171
|
+
"session_date_t must be exactly 6 bytes with EX_PROFILE_SESSION_DATE_NO_PADDING"
|
|
172
|
+
);
|
|
173
|
+
#endif
|
|
174
|
+
|
|
175
|
+
typedef struct session_datetime_t {
|
|
176
|
+
session_time_t time;
|
|
177
|
+
session_date_t date;
|
|
178
|
+
double unix_ts;
|
|
179
|
+
double ts;
|
|
180
|
+
uint32_t ordinal;
|
|
181
|
+
} session_datetime_t;
|
|
182
|
+
|
|
183
|
+
typedef struct session_time_range_t {
|
|
184
|
+
session_time_t start;
|
|
185
|
+
session_time_t end;
|
|
186
|
+
double elapsed_seconds;
|
|
187
|
+
} session_time_range_t;
|
|
188
|
+
|
|
189
|
+
typedef struct session_date_range_t {
|
|
190
|
+
session_date_t start;
|
|
191
|
+
session_date_t end;
|
|
192
|
+
size_t n_days;
|
|
193
|
+
session_date_t dates[];
|
|
194
|
+
} session_date_range_t;
|
|
195
|
+
|
|
196
|
+
typedef struct call_auction {
|
|
197
|
+
session_time_t auction_start;
|
|
198
|
+
const session_time_range_t* active;
|
|
199
|
+
const session_time_range_t* no_cancel;
|
|
200
|
+
const session_time_range_t* frozen;
|
|
201
|
+
session_time_t uncross;
|
|
202
|
+
session_time_t auction_end;
|
|
203
|
+
} call_auction;
|
|
204
|
+
|
|
205
|
+
typedef struct session_break {
|
|
206
|
+
session_time_t break_start;
|
|
207
|
+
session_time_t break_end;
|
|
208
|
+
double break_start_ts; // From midnight, not from session start, cached for break adjustment calculation
|
|
209
|
+
double break_end_ts; // From midnight, not from session start, cached for break adjustment calculation
|
|
210
|
+
double break_length_seconds; // Cached for break adjustment calculation
|
|
211
|
+
struct session_break* next;
|
|
212
|
+
} session_break;
|
|
213
|
+
|
|
214
|
+
typedef struct exchange_profile exchange_profile;
|
|
215
|
+
typedef void (*ex_profile_on_activate_func)(const exchange_profile* profile);
|
|
216
|
+
typedef void (*ex_profile_on_deactivate_func)(const exchange_profile* profile);
|
|
217
|
+
typedef session_date_range_t* (*ex_profile_trade_calendar_func)(const session_date_t* start_date, const session_date_t* end_date);
|
|
218
|
+
typedef auction_phase (*ex_profile_resolve_auction_phase_func)(double ts);
|
|
219
|
+
typedef session_phase (*ex_profile_resolve_session_phase_func)(double ts);
|
|
220
|
+
typedef session_type (*ex_profile_resolve_session_type_func)(uint16_t year, uint8_t month, uint8_t day);
|
|
221
|
+
|
|
222
|
+
typedef struct exchange_profile {
|
|
223
|
+
char profile_id[EX_PROFILE_ID_SIZE]; // Unique identifier for the exchange profile (e.g., "NYSE", "NASDAQ")
|
|
224
|
+
session_time_t session_start; // Continuous session start time
|
|
225
|
+
session_time_t session_end; // Continuous session end time
|
|
226
|
+
double session_start_ts; // Continuous session start time in seconds from midnight, cached for session phase resolution and break adjustment calculation
|
|
227
|
+
double session_end_ts; // Continuous session end time in seconds from midnight. For continuous session filtering.
|
|
228
|
+
double session_length_seconds; // Continuous session length in seconds, cached for break adjustment calculation
|
|
229
|
+
const call_auction* open_call_auction; // Pointer to open call auction details (if applicable)
|
|
230
|
+
const call_auction* close_call_auction; // Pointer to close call auction details (if applicable)
|
|
231
|
+
const session_break* session_breaks; // Linked list of session breaks (if applicable)
|
|
232
|
+
const char* time_zone; // IANA time zone string (e.g., "UTC", "America/New_York")
|
|
233
|
+
double tz_offset_seconds; // Time zone offset in seconds from UTC (e.g., -18000 for EST). Subject to machine local time zone to UTC conversion
|
|
234
|
+
|
|
235
|
+
// Function pointers for exchange-specific logic
|
|
236
|
+
ex_profile_on_activate_func on_activate;
|
|
237
|
+
ex_profile_on_deactivate_func on_deactivate;
|
|
238
|
+
ex_profile_trade_calendar_func trade_calendar;
|
|
239
|
+
ex_profile_resolve_auction_phase_func resolve_auction_phase;
|
|
240
|
+
ex_profile_resolve_session_phase_func resolve_session_phase;
|
|
241
|
+
ex_profile_resolve_session_type_func resolve_session_type;
|
|
242
|
+
} exchange_profile;
|
|
243
|
+
|
|
244
|
+
typedef void (*ex_profile_activation_callback)(const exchange_profile* previous_profile, const exchange_profile* new_profile, void* user_data);
|
|
245
|
+
|
|
246
|
+
typedef struct ex_profile_activation_listener {
|
|
247
|
+
uintptr_t listener_id;
|
|
248
|
+
ex_profile_activation_callback callback;
|
|
249
|
+
void* user_data;
|
|
250
|
+
struct ex_profile_activation_listener* next;
|
|
251
|
+
} ex_profile_activation_listener;
|
|
252
|
+
|
|
253
|
+
// ========== Forward Declaration ==========
|
|
254
|
+
|
|
255
|
+
#if defined(_WIN32) || defined(_WIN64)
|
|
256
|
+
// Exported so external extensions can resolve the live profile global at
|
|
257
|
+
// runtime via GetProcAddress (PE has no RTLD_GLOBAL-equivalent scope).
|
|
258
|
+
__declspec(dllexport) extern const exchange_profile* EX_PROFILE;
|
|
259
|
+
__declspec(dllexport) extern const session_date_range_t* EX_TRADE_CALENDAR_CACHE;
|
|
260
|
+
__declspec(dllexport) extern ex_profile_activation_listener* EX_PROFILE_ACTIVATION_LISTENERS;
|
|
261
|
+
__declspec(dllexport) extern const exchange_profile EX_PROFILE_DEFAULT;
|
|
262
|
+
#else
|
|
263
|
+
extern const exchange_profile* EX_PROFILE;
|
|
264
|
+
extern const session_date_range_t* EX_TRADE_CALENDAR_CACHE;
|
|
265
|
+
extern ex_profile_activation_listener* EX_PROFILE_ACTIVATION_LISTENERS;
|
|
266
|
+
extern const exchange_profile EX_PROFILE_DEFAULT;
|
|
267
|
+
#endif
|
|
268
|
+
|
|
269
|
+
static inline double c_utc_offset_seconds(void);
|
|
270
|
+
static inline int c_ex_profile_time_compare(const void* t1, const void* t2);
|
|
271
|
+
static inline double c_ex_profile_time_to_ts(uint8_t hour, uint8_t minute, uint8_t second, uint32_t nanosecond);
|
|
272
|
+
static inline double c_ex_profile_unix_to_ts(double unix_ts);
|
|
273
|
+
static inline double c_ex_profile_ts_to_elapsed(double elapsed);
|
|
274
|
+
|
|
275
|
+
static inline int c_ex_profile_date_compare(const void* d1, const void* d2);
|
|
276
|
+
static inline uint32_t c_ex_profile_days_before_year(uint16_t year);
|
|
277
|
+
static inline bool c_ex_profile_is_leap_year(uint16_t year);
|
|
278
|
+
static inline uint8_t c_ex_profile_days_in_month(uint16_t year, uint8_t month);
|
|
279
|
+
static inline bool c_ex_profile_date_is_valid(const session_date_t* date);
|
|
280
|
+
static inline uint32_t c_ex_profile_date_to_ordinal(const session_date_t* date);
|
|
281
|
+
static inline uint32_t c_ex_profile_unix_to_ordinal(double unix_ts, double tz_offset_seconds);
|
|
282
|
+
static inline int c_ex_profile_date_from_ordinal(uint32_t ordinal, session_date_t* out);
|
|
283
|
+
static inline int c_ex_profile_date_from_year_day(uint16_t year, uint32_t day_of_year, session_date_t* out);
|
|
284
|
+
static inline int c_ex_profile_next_day(const session_date_t* date, session_date_t* out);
|
|
285
|
+
static inline int c_ex_profile_previous_day(const session_date_t* date, session_date_t* out);
|
|
286
|
+
static inline int c_ex_profile_days_after(const session_date_t* date, size_t days_after, session_date_t* out);
|
|
287
|
+
static inline int c_ex_profile_days_before(const session_date_t* date, size_t days_before, session_date_t* out);
|
|
288
|
+
static inline bool c_ex_profile_is_weekend(const session_date_t* date);
|
|
289
|
+
static inline session_date_range_t* c_ex_profile_date_range(const session_date_t* start_date, const session_date_t* end_date);
|
|
290
|
+
|
|
291
|
+
static inline void c_ex_profile_activate(const exchange_profile* profile);
|
|
292
|
+
static inline uintptr_t c_ex_profile_register_activation_listener(ex_profile_activation_callback callback, void* user_data);
|
|
293
|
+
static inline int c_ex_profile_deregister_activation_listener(uintptr_t listener_id);
|
|
294
|
+
|
|
295
|
+
static inline session_time_t* c_ex_profile_session_time_new(uint8_t hour, uint8_t minute, uint8_t second, uint32_t nanosecond);
|
|
296
|
+
static inline int c_ex_profile_session_time_from_ts(double ts, session_time_t* out);
|
|
297
|
+
static inline int c_ex_profile_session_time_from_unix(double unix_ts, session_time_t* out);
|
|
298
|
+
static inline session_time_range_t* c_ex_profile_session_trange_between_time(const session_time_t* start_time, const session_time_t* end_time);
|
|
299
|
+
static inline session_time_range_t* c_ex_profile_session_trange_between_unix(double start_unix_ts, double end_unix_ts);
|
|
300
|
+
|
|
301
|
+
static inline session_date_t* c_ex_profile_session_date_new(uint16_t year, uint8_t month, uint8_t day);
|
|
302
|
+
static inline int c_ex_profile_session_date_init(session_date_t* date, uint16_t year, uint8_t month, uint8_t day);
|
|
303
|
+
static inline int c_ex_profile_session_date_from_unix(double unix_ts, session_date_t* out);
|
|
304
|
+
static inline double c_ex_profile_session_date_to_unix(const session_date_t* date);
|
|
305
|
+
static inline size_t c_ex_profile_session_date_index(const session_date_t* date, const session_date_range_t* drange);
|
|
306
|
+
static inline size_t c_ex_profile_session_ymd_index(uint16_t year, uint8_t month, uint8_t day, const session_date_t* date_array, size_t n_days);
|
|
307
|
+
static inline session_date_range_t* c_ex_profile_session_drange_between(const session_date_t* start_date, const session_date_t* end_date);
|
|
308
|
+
static inline int c_ex_profile_session_trading_days_before(const session_date_t* market_date, size_t days, session_date_t* out);
|
|
309
|
+
static inline int c_ex_profile_session_trading_days_after(const session_date_t* market_date, size_t days, session_date_t* out);
|
|
310
|
+
static inline int c_ex_profile_nearest_trading_date(const session_date_t* market_date, bool previous, session_date_t* out);
|
|
311
|
+
static inline bool c_ex_profile_is_trading_day(const session_date_t* market_date);
|
|
312
|
+
static inline int c_ex_profile_trading_days_between(const session_date_t* start_date, const session_date_t* end_date, ssize_t* out);
|
|
313
|
+
|
|
314
|
+
static inline int c_ex_profile_session_datetime_from_unix(double unix_ts, session_datetime_t* out);
|
|
315
|
+
static inline int c_ex_profile_session_datetime_update(session_datetime_t* dt, double unix_ts);
|
|
316
|
+
|
|
317
|
+
// ========== Utilities Functions (session_time_t) ==========
|
|
318
|
+
|
|
319
|
+
static inline double c_utc_offset_seconds(void) {
|
|
320
|
+
time_t now = time(NULL);
|
|
321
|
+
struct tm local_tm;
|
|
322
|
+
struct tm gmt_tm;
|
|
323
|
+
time_t local_seconds = 0;
|
|
324
|
+
time_t gmt_seconds = 0;
|
|
325
|
+
double diff_seconds = 0.0;
|
|
326
|
+
|
|
327
|
+
localtime_r(&now, &local_tm);
|
|
328
|
+
gmtime_r(&now, &gmt_tm);
|
|
329
|
+
|
|
330
|
+
local_seconds = mktime(&local_tm);
|
|
331
|
+
gmt_seconds = mktime(&gmt_tm);
|
|
332
|
+
diff_seconds = difftime(local_seconds, gmt_seconds);
|
|
333
|
+
|
|
334
|
+
return diff_seconds;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
static inline int c_ex_profile_time_compare(const void* t1, const void* t2) {
|
|
338
|
+
// qsort function, returns 1, -1, or 0
|
|
339
|
+
if (t1 == t2) return 0;
|
|
340
|
+
if (!t1) return -1;
|
|
341
|
+
if (!t2) return 1;
|
|
342
|
+
|
|
343
|
+
const session_time_t* time1 = (const session_time_t*) t1;
|
|
344
|
+
const session_time_t* time2 = (const session_time_t*) t2;
|
|
345
|
+
if (time1->hour < time2->hour) return -1;
|
|
346
|
+
if (time1->hour > time2->hour) return 1;
|
|
347
|
+
if (time1->minute < time2->minute) return -1;
|
|
348
|
+
if (time1->minute > time2->minute) return 1;
|
|
349
|
+
if (time1->second < time2->second) return -1;
|
|
350
|
+
if (time1->second > time2->second) return 1;
|
|
351
|
+
if (time1->nanosecond < time2->nanosecond) return -1;
|
|
352
|
+
if (time1->nanosecond > time2->nanosecond) return 1;
|
|
353
|
+
return 0;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
static inline double c_ex_profile_time_to_ts(uint8_t hour, uint8_t minute, uint8_t second, uint32_t nanosecond) {
|
|
357
|
+
double total_seconds = hour * SECONDS_PER_HOUR + minute * SECONDS_PER_MINUTE + second + (double) nanosecond / NANOS_PER_SECOND;
|
|
358
|
+
return total_seconds;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
static inline double c_ex_profile_unix_to_ts(double unix_ts) {
|
|
362
|
+
// When no active profile is set, fall back to the machine-local Unix day fraction.
|
|
363
|
+
const double tz_offset = EX_PROFILE ? EX_PROFILE->tz_offset_seconds : 0.0;
|
|
364
|
+
double total_seconds = fmod(unix_ts + tz_offset, (double) SECONDS_PER_DAY);
|
|
365
|
+
return total_seconds;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
static inline double c_ex_profile_ts_to_elapsed(double ts) {
|
|
369
|
+
if (!EX_PROFILE) return 0.0;
|
|
370
|
+
|
|
371
|
+
if (ts <= EX_PROFILE->session_start_ts) return ts - EX_PROFILE->session_start_ts;
|
|
372
|
+
if (ts >= EX_PROFILE->session_end_ts) return EX_PROFILE->session_length_seconds + ts - EX_PROFILE->session_end_ts;
|
|
373
|
+
|
|
374
|
+
double break_elapsed = 0;
|
|
375
|
+
const session_break* current_break = EX_PROFILE->session_breaks;
|
|
376
|
+
while (current_break) {
|
|
377
|
+
if (ts > current_break->break_end_ts) break_elapsed += current_break->break_length_seconds;
|
|
378
|
+
else if (ts > current_break->break_start_ts) break_elapsed += ts - current_break->break_start_ts;
|
|
379
|
+
else break;
|
|
380
|
+
current_break = current_break->next;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
return ts - break_elapsed - EX_PROFILE->session_start_ts;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// ========== Utilities Functions (session_date_t) ==========
|
|
387
|
+
|
|
388
|
+
static inline int c_ex_profile_date_compare(const void* d1, const void* d2) {
|
|
389
|
+
// qsort function, returns 1, -1, or 0
|
|
390
|
+
if (d1 == d2) return 0;
|
|
391
|
+
if (!d1) return -1;
|
|
392
|
+
if (!d2) return 1;
|
|
393
|
+
|
|
394
|
+
const session_date_t* date1 = (const session_date_t*) d1;
|
|
395
|
+
const session_date_t* date2 = (const session_date_t*) d2;
|
|
396
|
+
if (date1->year < date2->year) return -1;
|
|
397
|
+
if (date1->year > date2->year) return 1;
|
|
398
|
+
if (date1->month < date2->month) return -1;
|
|
399
|
+
if (date1->month > date2->month) return 1;
|
|
400
|
+
if (date1->day < date2->day) return -1;
|
|
401
|
+
if (date1->day > date2->day) return 1;
|
|
402
|
+
return 0;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
static inline uint32_t c_ex_profile_days_before_year(uint16_t year) {
|
|
406
|
+
uint32_t y = (uint32_t) year - 1u;
|
|
407
|
+
return 365u * y + (y / 4u) - (y / 100u) + (y / 400u);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
static inline bool c_ex_profile_is_leap_year(uint16_t year) {
|
|
411
|
+
if (year < EX_PROFILE_MIN_YEAR || year > EX_PROFILE_MAX_YEAR) return false;
|
|
412
|
+
return ((year % 4u) == 0u) && (((year % 100u) != 0u) || ((year % 400u) == 0u));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
static inline uint8_t c_ex_profile_days_in_month(uint16_t year, uint8_t month) {
|
|
416
|
+
if (year < EX_PROFILE_MIN_YEAR || year > EX_PROFILE_MAX_YEAR || month < 1u || month > 12u) return 0u;
|
|
417
|
+
uint8_t days = DAYS_IN_MONTH_TABLE[(uint8_t) (month - 1u)];
|
|
418
|
+
if (month == 2u && c_ex_profile_is_leap_year(year)) days = 29u;
|
|
419
|
+
return days;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
static inline bool c_ex_profile_date_is_valid(const session_date_t* date) {
|
|
423
|
+
uint8_t dim = 0u;
|
|
424
|
+
if (!date) return false;
|
|
425
|
+
dim = c_ex_profile_days_in_month(date->year, date->month);
|
|
426
|
+
if (dim == 0u) return false;
|
|
427
|
+
return date->day >= 1u && date->day <= dim;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
static inline uint32_t c_ex_profile_date_to_ordinal(const session_date_t* date) {
|
|
431
|
+
uint32_t ordinal = 0u;
|
|
432
|
+
|
|
433
|
+
if (!date) return 0u;
|
|
434
|
+
if (!c_ex_profile_date_is_valid(date)) return 0u;
|
|
435
|
+
|
|
436
|
+
ordinal = c_ex_profile_days_before_year(date->year);
|
|
437
|
+
ordinal += (uint32_t) DAYS_BEFORE_MONTH_TABLE[(uint8_t) (date->month - 1u)];
|
|
438
|
+
if (date->month > 2u && c_ex_profile_is_leap_year(date->year)) {
|
|
439
|
+
ordinal += 1u;
|
|
440
|
+
}
|
|
441
|
+
ordinal += (uint32_t) date->day;
|
|
442
|
+
|
|
443
|
+
return ordinal;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
static inline uint32_t c_ex_profile_unix_to_ordinal(double unix_ts, double tz_offset_seconds) {
|
|
447
|
+
double shifted = unix_ts + tz_offset_seconds;
|
|
448
|
+
double days = floor(shifted / (double) SECONDS_PER_DAY);
|
|
449
|
+
double ordinal = UNIX_EPOCH_ORDINAL + days;
|
|
450
|
+
if (ordinal < 1.0 || ordinal > (double) EX_PROFILE_MAX_ORDINAL) return (uint32_t) -1;
|
|
451
|
+
return (uint32_t) ordinal;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
static inline int c_ex_profile_date_from_ordinal(uint32_t ordinal, session_date_t* out) {
|
|
455
|
+
// Ordinal is 1-based: 1 == 0001-01-01
|
|
456
|
+
// Use 400/100/4/1-year cycle decomposition to avoid binary search.
|
|
457
|
+
// This matches the proleptic Gregorian calendar used by c_ex_profile_days_before_year().
|
|
458
|
+
uint32_t max_ordinal = 0u;
|
|
459
|
+
uint32_t n = 0u; // 0-based day index
|
|
460
|
+
uint32_t n400 = 0u;
|
|
461
|
+
uint32_t n100 = 0u;
|
|
462
|
+
uint32_t n4 = 0u;
|
|
463
|
+
uint32_t n1 = 0u;
|
|
464
|
+
uint32_t day_of_year = 0u; // 0-based day-of-year
|
|
465
|
+
uint16_t year = 0u;
|
|
466
|
+
uint8_t month = 1u;
|
|
467
|
+
uint8_t is_leap = 0u;
|
|
468
|
+
|
|
469
|
+
if (!out) return -1;
|
|
470
|
+
|
|
471
|
+
// Keep bounds consistent even if EX_PROFILE_MAX_YEAR is overridden.
|
|
472
|
+
max_ordinal = c_ex_profile_days_before_year((uint16_t) (EX_PROFILE_MAX_YEAR + 1u));
|
|
473
|
+
if (ordinal < 1u || ordinal > max_ordinal) return -1;
|
|
474
|
+
|
|
475
|
+
n = ordinal - 1u;
|
|
476
|
+
|
|
477
|
+
// Decompose days since 0001-01-01 into Gregorian year and day-of-year.
|
|
478
|
+
// 400-year cycle: 146097 days
|
|
479
|
+
n400 = n / 146097u;
|
|
480
|
+
n %= 146097u;
|
|
481
|
+
|
|
482
|
+
// 100-year cycle: 36524 days, but clamp to 0..3 within a 400-year cycle
|
|
483
|
+
n100 = n / 36524u;
|
|
484
|
+
if (n100 == 4u) n100 = 3u;
|
|
485
|
+
n -= n100 * 36524u;
|
|
486
|
+
|
|
487
|
+
// 4-year cycle: 1461 days
|
|
488
|
+
n4 = n / 1461u;
|
|
489
|
+
n %= 1461u;
|
|
490
|
+
|
|
491
|
+
// 1-year cycle: 365 days, clamp to 0..3 within a 4-year cycle
|
|
492
|
+
n1 = n / 365u;
|
|
493
|
+
if (n1 == 4u) n1 = 3u;
|
|
494
|
+
n -= n1 * 365u;
|
|
495
|
+
|
|
496
|
+
year = (uint16_t) (400u * n400 + 100u * n100 + 4u * n4 + n1 + 1u);
|
|
497
|
+
day_of_year = n;
|
|
498
|
+
|
|
499
|
+
is_leap = (uint8_t) (((year % 4u) == 0u) && (((year % 100u) != 0u) || ((year % 400u) == 0u)));
|
|
500
|
+
|
|
501
|
+
// Convert day-of-year to month/day.
|
|
502
|
+
// DAYS_BEFORE_MONTH_TABLE is for non-leap years; adjust for leap years after Feb.
|
|
503
|
+
for (month = 1u; month <= 12u; ++month) {
|
|
504
|
+
uint16_t next = 0u;
|
|
505
|
+
if (month == 12u) {
|
|
506
|
+
next = (uint16_t) (365u + is_leap);
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
next = DAYS_BEFORE_MONTH_TABLE[month];
|
|
510
|
+
if (is_leap && month >= 2u) next = (uint16_t) (next + 1u);
|
|
511
|
+
}
|
|
512
|
+
if (day_of_year < (uint32_t) next) break;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
{
|
|
516
|
+
uint16_t start = DAYS_BEFORE_MONTH_TABLE[(uint8_t) (month - 1u)];
|
|
517
|
+
if (is_leap && month > 2u) start = (uint16_t) (start + 1u);
|
|
518
|
+
out->year = year;
|
|
519
|
+
out->month = month;
|
|
520
|
+
out->day = (uint8_t) ((uint16_t) (day_of_year - (uint32_t) start + 1u));
|
|
521
|
+
}
|
|
522
|
+
return 0;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
static inline int c_ex_profile_date_from_year_day(uint16_t year, uint32_t day_of_year, session_date_t* out) {
|
|
526
|
+
// day_of_year is 0-based: 0 == Jan 1
|
|
527
|
+
uint8_t month = 1u;
|
|
528
|
+
uint8_t is_leap = 0u;
|
|
529
|
+
|
|
530
|
+
if (!out) return -1;
|
|
531
|
+
if (year < EX_PROFILE_MIN_YEAR || year > EX_PROFILE_MAX_YEAR) return -1;
|
|
532
|
+
|
|
533
|
+
is_leap = (uint8_t) (((year % 4u) == 0u) && (((year % 100u) != 0u) || ((year % 400u) == 0u)));
|
|
534
|
+
if (day_of_year >= (uint32_t) (365u + is_leap)) return -1;
|
|
535
|
+
|
|
536
|
+
// Convert day-of-year to month/day.
|
|
537
|
+
// DAYS_BEFORE_MONTH_TABLE is for non-leap years; adjust for leap years after Feb.
|
|
538
|
+
for (month = 1u; month <= 12u; ++month) {
|
|
539
|
+
uint16_t next = 0u;
|
|
540
|
+
if (month == 12u) {
|
|
541
|
+
next = (uint16_t) (365u + is_leap);
|
|
542
|
+
}
|
|
543
|
+
else {
|
|
544
|
+
next = DAYS_BEFORE_MONTH_TABLE[month];
|
|
545
|
+
if (is_leap && month >= 2u) next = (uint16_t) (next + 1u);
|
|
546
|
+
}
|
|
547
|
+
if (day_of_year < (uint32_t) next) break;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
{
|
|
551
|
+
uint16_t start = DAYS_BEFORE_MONTH_TABLE[(uint8_t) (month - 1u)];
|
|
552
|
+
if (is_leap && month > 2u) start = (uint16_t) (start + 1u);
|
|
553
|
+
out->year = year;
|
|
554
|
+
out->month = month;
|
|
555
|
+
out->day = (uint8_t) ((uint16_t) (day_of_year - (uint32_t) start + 1u));
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return 0;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
static inline int c_ex_profile_next_day(const session_date_t* date, session_date_t* out) {
|
|
562
|
+
// Ultra-fast path for consecutive date iteration.
|
|
563
|
+
if (!date || !out) return -1;
|
|
564
|
+
if (!c_ex_profile_date_is_valid(date)) return -1;
|
|
565
|
+
|
|
566
|
+
uint16_t year = date->year;
|
|
567
|
+
uint8_t month = date->month;
|
|
568
|
+
uint8_t day = date->day;
|
|
569
|
+
uint8_t dim = c_ex_profile_days_in_month(year, month);
|
|
570
|
+
if (dim == 0u) return -1;
|
|
571
|
+
|
|
572
|
+
if (day < dim) {
|
|
573
|
+
*out = *date;
|
|
574
|
+
out->day = (uint8_t) (day + 1u);
|
|
575
|
+
return 0;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// End of month
|
|
579
|
+
if (month < 12u) {
|
|
580
|
+
*out = *date;
|
|
581
|
+
out->month = (uint8_t) (month + 1u);
|
|
582
|
+
out->day = 1u;
|
|
583
|
+
return 0;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// End of year
|
|
587
|
+
if (year >= EX_PROFILE_MAX_YEAR) return -1;
|
|
588
|
+
*out = *date;
|
|
589
|
+
out->year = (uint16_t) (year + 1u);
|
|
590
|
+
out->month = 1u;
|
|
591
|
+
out->day = 1u;
|
|
592
|
+
return 0;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
static inline int c_ex_profile_previous_day(const session_date_t* date, session_date_t* out) {
|
|
596
|
+
// Ultra-fast path for consecutive date iteration.
|
|
597
|
+
// Returns 0 on success; -1 on invalid input or if decrementing would exceed supported range.
|
|
598
|
+
// Supports in-place operation: out may alias date.
|
|
599
|
+
if (!date || !out) return -1;
|
|
600
|
+
if (!c_ex_profile_date_is_valid(date)) return -1;
|
|
601
|
+
|
|
602
|
+
const uint16_t year = date->year;
|
|
603
|
+
const uint8_t month = date->month;
|
|
604
|
+
const uint8_t day = date->day;
|
|
605
|
+
|
|
606
|
+
if (day > 1u) {
|
|
607
|
+
*out = *date;
|
|
608
|
+
out->day = (uint8_t) (day - 1u);
|
|
609
|
+
return 0;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Beginning of month
|
|
613
|
+
if (month > 1u) {
|
|
614
|
+
const uint8_t prev_month = (uint8_t) (month - 1u);
|
|
615
|
+
const uint8_t dim = c_ex_profile_days_in_month(year, prev_month);
|
|
616
|
+
if (dim == 0u) return -1;
|
|
617
|
+
*out = *date;
|
|
618
|
+
out->month = prev_month;
|
|
619
|
+
out->day = dim;
|
|
620
|
+
return 0;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Beginning of year
|
|
624
|
+
if (year <= EX_PROFILE_MIN_YEAR) return -1;
|
|
625
|
+
{
|
|
626
|
+
const uint16_t prev_year = (uint16_t) (year - 1u);
|
|
627
|
+
const uint8_t dim = c_ex_profile_days_in_month(prev_year, 12u);
|
|
628
|
+
if (dim == 0u) return -1;
|
|
629
|
+
*out = *date;
|
|
630
|
+
out->year = prev_year;
|
|
631
|
+
out->month = 12u;
|
|
632
|
+
out->day = dim;
|
|
633
|
+
return 0;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
static inline int c_ex_profile_days_after(const session_date_t* date, size_t days_after, session_date_t* out) {
|
|
638
|
+
if (!date || !out) return -1;
|
|
639
|
+
if (!c_ex_profile_date_is_valid(date)) return -1;
|
|
640
|
+
|
|
641
|
+
if (days_after == 0u) {
|
|
642
|
+
*out = *date;
|
|
643
|
+
return 0;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Ultra-fast small horizon: repeated next_day.
|
|
647
|
+
if (days_after == 1u) return c_ex_profile_next_day(date, out);
|
|
648
|
+
|
|
649
|
+
uint32_t ordinal = c_ex_profile_date_to_ordinal(date);
|
|
650
|
+
if (ordinal == 0u) return -1;
|
|
651
|
+
|
|
652
|
+
// Fast path: small horizon within the same year.
|
|
653
|
+
{
|
|
654
|
+
const uint16_t year = date->year;
|
|
655
|
+
const uint32_t start_of_year = c_ex_profile_days_before_year(year);
|
|
656
|
+
const uint32_t day_of_year = ordinal - start_of_year - 1u;
|
|
657
|
+
const uint8_t is_leap = (uint8_t) (((year % 4u) == 0u) && (((year % 100u) != 0u) || ((year % 400u) == 0u)));
|
|
658
|
+
const uint32_t year_len = (uint32_t) (365u + is_leap);
|
|
659
|
+
|
|
660
|
+
if (day_of_year < year_len) {
|
|
661
|
+
const uint32_t remaining = (uint32_t) (year_len - 1u - day_of_year);
|
|
662
|
+
if (days_after <= (size_t) remaining) {
|
|
663
|
+
return c_ex_profile_date_from_year_day(year, (uint32_t) (day_of_year + (uint32_t) days_after), out);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Long horizon: ordinal math + from_ordinal.
|
|
669
|
+
// Guard against uint32_t wrap.
|
|
670
|
+
if (days_after > (size_t) (UINT32_MAX - ordinal)) return -1;
|
|
671
|
+
|
|
672
|
+
ordinal += (uint32_t) days_after;
|
|
673
|
+
return c_ex_profile_date_from_ordinal(ordinal, out);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
static inline int c_ex_profile_days_before(const session_date_t* date, size_t days_before, session_date_t* out) {
|
|
677
|
+
if (!out || !date) return -1;
|
|
678
|
+
if (!c_ex_profile_date_is_valid(date)) return -1;
|
|
679
|
+
|
|
680
|
+
if (days_before == 0u) {
|
|
681
|
+
*out = *date;
|
|
682
|
+
return 0;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Ultra-fast small horizon: repeated previous_day.
|
|
686
|
+
if (days_before == 1u) return c_ex_profile_previous_day(date, out);
|
|
687
|
+
|
|
688
|
+
uint32_t ordinal = c_ex_profile_date_to_ordinal(date);
|
|
689
|
+
if (ordinal == 0u) return -1;
|
|
690
|
+
|
|
691
|
+
// Fast path: small horizon within the same year.
|
|
692
|
+
{
|
|
693
|
+
const uint16_t year = date->year;
|
|
694
|
+
const uint32_t start_of_year = c_ex_profile_days_before_year(year);
|
|
695
|
+
const uint32_t day_of_year = ordinal - start_of_year - 1u;
|
|
696
|
+
const uint8_t is_leap = (uint8_t) (((year % 4u) == 0u) && (((year % 100u) != 0u) || ((year % 400u) == 0u)));
|
|
697
|
+
const uint32_t year_len = (uint32_t) (365u + is_leap);
|
|
698
|
+
|
|
699
|
+
if (day_of_year < year_len) {
|
|
700
|
+
if (days_before <= (size_t) day_of_year) {
|
|
701
|
+
return c_ex_profile_date_from_year_day(year, (uint32_t) (day_of_year - (uint32_t) days_before), out);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Long horizon: ordinal math + from_ordinal.
|
|
707
|
+
// Guard against size_t -> uint32_t truncation.
|
|
708
|
+
if (days_before > (size_t) (ordinal - 1u)) return -1;
|
|
709
|
+
|
|
710
|
+
ordinal -= (uint32_t) days_before;
|
|
711
|
+
return c_ex_profile_date_from_ordinal(ordinal, out);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
static inline bool c_ex_profile_is_weekend(const session_date_t* date) {
|
|
715
|
+
uint32_t ordinal = c_ex_profile_date_to_ordinal(date);
|
|
716
|
+
if (ordinal == 0u) return false;
|
|
717
|
+
uint8_t wd = (ordinal - 1u) % 7u;
|
|
718
|
+
return wd == 5u || wd == 6u;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
static inline session_date_range_t* c_ex_profile_date_range(const session_date_t* start_date, const session_date_t* end_date) {
|
|
722
|
+
if (!start_date || !end_date) return NULL;
|
|
723
|
+
|
|
724
|
+
uint32_t start_ordinal = c_ex_profile_date_to_ordinal(start_date);
|
|
725
|
+
uint32_t end_ordinal = c_ex_profile_date_to_ordinal(end_date);
|
|
726
|
+
if (start_ordinal == 0u || end_ordinal == 0u || end_ordinal < start_ordinal) return NULL;
|
|
727
|
+
size_t n_days = (size_t) (end_ordinal - start_ordinal + 1u);
|
|
728
|
+
session_date_range_t* drange = (session_date_range_t*) calloc(1, sizeof(session_date_range_t) + n_days * sizeof(session_date_t));
|
|
729
|
+
if (!drange) return NULL;
|
|
730
|
+
drange->start = *start_date;
|
|
731
|
+
drange->end = *end_date;
|
|
732
|
+
drange->n_days = n_days;
|
|
733
|
+
|
|
734
|
+
// Fast fill: write dates[0], then repeated next_day into the array.
|
|
735
|
+
drange->dates[0] = *start_date;
|
|
736
|
+
for (size_t i = 1; i < n_days; ++i) {
|
|
737
|
+
if (c_ex_profile_next_day(&drange->dates[i - 1u], &drange->dates[i]) != 0) {
|
|
738
|
+
free(drange);
|
|
739
|
+
return NULL;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return drange;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// ========== Public APIs (exchange_profile) ==========
|
|
746
|
+
|
|
747
|
+
static inline void c_ex_profile_activate(const exchange_profile* profile) {
|
|
748
|
+
if (EX_PROFILE == profile) return;
|
|
749
|
+
|
|
750
|
+
if (EX_PROFILE && EX_PROFILE->on_deactivate) EX_PROFILE->on_deactivate(EX_PROFILE);
|
|
751
|
+
|
|
752
|
+
if (EX_TRADE_CALENDAR_CACHE) {
|
|
753
|
+
free((void*) EX_TRADE_CALENDAR_CACHE);
|
|
754
|
+
EX_TRADE_CALENDAR_CACHE = NULL;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
for (ex_profile_activation_listener* listener = EX_PROFILE_ACTIVATION_LISTENERS; listener; listener = listener->next) {
|
|
758
|
+
if (listener->callback) {
|
|
759
|
+
listener->callback(EX_PROFILE, profile, listener->user_data);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
if (profile->on_activate) profile->on_activate(profile);
|
|
764
|
+
|
|
765
|
+
EX_PROFILE = profile;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
static inline uintptr_t c_ex_profile_register_activation_listener(ex_profile_activation_callback callback, void* user_data) {
|
|
769
|
+
if (!callback) return -1;
|
|
770
|
+
|
|
771
|
+
ex_profile_activation_listener* listener = (ex_profile_activation_listener*) calloc(1, sizeof(ex_profile_activation_listener));
|
|
772
|
+
if (!listener) return -1;
|
|
773
|
+
|
|
774
|
+
listener->listener_id = (uintptr_t) listener; // Use the pointer value as a unique ID for deregistration.
|
|
775
|
+
listener->callback = callback;
|
|
776
|
+
listener->user_data = user_data;
|
|
777
|
+
|
|
778
|
+
if (!EX_PROFILE_ACTIVATION_LISTENERS) {
|
|
779
|
+
EX_PROFILE_ACTIVATION_LISTENERS = listener;
|
|
780
|
+
}
|
|
781
|
+
else {
|
|
782
|
+
ex_profile_activation_listener* current = EX_PROFILE_ACTIVATION_LISTENERS;
|
|
783
|
+
while (current->next) {
|
|
784
|
+
current = current->next;
|
|
785
|
+
}
|
|
786
|
+
current->next = listener;
|
|
787
|
+
}
|
|
788
|
+
EX_PROFILE_ACTIVATION_LISTENERS = listener;
|
|
789
|
+
|
|
790
|
+
return listener->listener_id;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
static inline int c_ex_profile_deregister_activation_listener(uintptr_t listener_id) {
|
|
794
|
+
if (!EX_PROFILE_ACTIVATION_LISTENERS) return -1;
|
|
795
|
+
|
|
796
|
+
ex_profile_activation_listener* current = EX_PROFILE_ACTIVATION_LISTENERS;
|
|
797
|
+
ex_profile_activation_listener* prev = NULL;
|
|
798
|
+
|
|
799
|
+
while (current) {
|
|
800
|
+
if (current->listener_id == listener_id) {
|
|
801
|
+
if (prev) {
|
|
802
|
+
prev->next = current->next;
|
|
803
|
+
}
|
|
804
|
+
else {
|
|
805
|
+
EX_PROFILE_ACTIVATION_LISTENERS = current->next;
|
|
806
|
+
}
|
|
807
|
+
free(current);
|
|
808
|
+
return 0;
|
|
809
|
+
}
|
|
810
|
+
prev = current;
|
|
811
|
+
current = current->next;
|
|
812
|
+
}
|
|
813
|
+
return -1; // Listener ID not found
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
// ========== Public APIs (session_time_t) ==========
|
|
817
|
+
|
|
818
|
+
static inline session_time_t* c_ex_profile_session_time_new(uint8_t hour, uint8_t minute, uint8_t second, uint32_t nanosecond) {
|
|
819
|
+
session_time_t* out = (session_time_t*) calloc(1, sizeof(session_time_t));
|
|
820
|
+
if (!out) return NULL;
|
|
821
|
+
|
|
822
|
+
out->hour = hour;
|
|
823
|
+
out->minute = minute;
|
|
824
|
+
out->second = second;
|
|
825
|
+
out->nanosecond = nanosecond;
|
|
826
|
+
|
|
827
|
+
double ts = c_ex_profile_time_to_ts(hour, minute, second, nanosecond);
|
|
828
|
+
|
|
829
|
+
// No active exchange profile, return a default session time representation
|
|
830
|
+
if (!EX_PROFILE) {
|
|
831
|
+
out->elapsed_seconds = ts;
|
|
832
|
+
out->phase = SESSION_PHASE_UNKNOWN;
|
|
833
|
+
return out;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Calculate elapsed seconds since continuous session start
|
|
837
|
+
out->elapsed_seconds = c_ex_profile_ts_to_elapsed(ts);
|
|
838
|
+
out->phase = (uint8_t) (EX_PROFILE->resolve_session_phase(ts));
|
|
839
|
+
return out;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
static inline int c_ex_profile_session_time_from_ts(double ts, session_time_t* out) {
|
|
843
|
+
if (!out) return -1;
|
|
844
|
+
|
|
845
|
+
uint8_t hour = (uint8_t) (ts / SECONDS_PER_HOUR);
|
|
846
|
+
uint8_t minute = (uint8_t) ((ts - hour * SECONDS_PER_HOUR) / SECONDS_PER_MINUTE);
|
|
847
|
+
uint8_t second = (uint8_t) (ts - hour * SECONDS_PER_HOUR - minute * SECONDS_PER_MINUTE);
|
|
848
|
+
uint32_t nanosecond = (uint32_t) ((ts - (uint64_t) ts) * NANOS_PER_SECOND);
|
|
849
|
+
|
|
850
|
+
out->hour = hour;
|
|
851
|
+
out->minute = minute;
|
|
852
|
+
out->second = second;
|
|
853
|
+
out->nanosecond = nanosecond;
|
|
854
|
+
out->elapsed_seconds = c_ex_profile_ts_to_elapsed(ts);
|
|
855
|
+
out->phase = (uint8_t) (EX_PROFILE ? EX_PROFILE->resolve_session_phase(ts) : SESSION_PHASE_UNKNOWN);
|
|
856
|
+
return 0;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
static inline int c_ex_profile_session_time_from_unix(double unix_ts, session_time_t* out) {
|
|
860
|
+
if (!out) return -1;
|
|
861
|
+
double ts = c_ex_profile_unix_to_ts(unix_ts);
|
|
862
|
+
return c_ex_profile_session_time_from_ts(ts, out);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
static inline session_time_range_t* c_ex_profile_session_trange_between_time(const session_time_t* start_time, const session_time_t* end_time) {
|
|
866
|
+
session_time_range_t* trange = (session_time_range_t*) calloc(1, sizeof(session_time_range_t));
|
|
867
|
+
if (!trange) return NULL;
|
|
868
|
+
trange->start = *start_time;
|
|
869
|
+
trange->end = *end_time;
|
|
870
|
+
trange->elapsed_seconds = end_time->elapsed_seconds - start_time->elapsed_seconds;
|
|
871
|
+
return trange;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
static inline session_time_range_t* c_ex_profile_session_trange_between_unix(double start_unix_ts, double end_unix_ts) {
|
|
875
|
+
session_time_range_t* trange = (session_time_range_t*) calloc(1, sizeof(session_time_range_t));
|
|
876
|
+
if (!trange) return NULL;
|
|
877
|
+
|
|
878
|
+
c_ex_profile_session_time_from_unix(start_unix_ts, &trange->start);
|
|
879
|
+
c_ex_profile_session_time_from_unix(end_unix_ts, &trange->end);
|
|
880
|
+
trange->elapsed_seconds = trange->end.elapsed_seconds - trange->start.elapsed_seconds;
|
|
881
|
+
return trange;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// ========== Public APIs (session_date_t) ==========
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* @brief Populate an existing session_date_t buffer with the given year/month/day.
|
|
888
|
+
*
|
|
889
|
+
* Mimics c_ex_profile_session_date_new() field-setting behavior, including session type
|
|
890
|
+
* resolution, without allocating a new buffer. The caller owns the memory (stack or embedded).
|
|
891
|
+
*
|
|
892
|
+
* @param date Existing buffer to populate.
|
|
893
|
+
* @param year Calendar year (0-9999).
|
|
894
|
+
* @param month Calendar month (1-12).
|
|
895
|
+
* @param day Calendar day (1-31).
|
|
896
|
+
* @return 0 on success, -1 if date is NULL.
|
|
897
|
+
*/
|
|
898
|
+
static inline int c_ex_profile_session_date_init(session_date_t* date, uint16_t year, uint8_t month, uint8_t day) {
|
|
899
|
+
if (!date) return -1;
|
|
900
|
+
date->year = year;
|
|
901
|
+
date->month = month;
|
|
902
|
+
date->day = day;
|
|
903
|
+
date->stype = EX_PROFILE ? EX_PROFILE->resolve_session_type(year, month, day) : SESSION_TYPE_NORMINAL;
|
|
904
|
+
return 0;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
static inline session_date_t* c_ex_profile_session_date_new(uint16_t year, uint8_t month, uint8_t day) {
|
|
908
|
+
session_date_t* d = (session_date_t*) calloc(1, sizeof(session_date_t));
|
|
909
|
+
if (!d) return NULL;
|
|
910
|
+
c_ex_profile_session_date_init(d, year, month, day);
|
|
911
|
+
return d;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
static inline int c_ex_profile_session_date_from_unix_sys(double unix_ts, session_date_t* out) {
|
|
915
|
+
if (!out) return -1;
|
|
916
|
+
if (!isfinite(unix_ts)) return -1;
|
|
917
|
+
|
|
918
|
+
// If an exchange profile is active, interpret the date in that profile's local time
|
|
919
|
+
// using its precomputed UTC offset. Otherwise, fall back to machine-local time.
|
|
920
|
+
struct tm tm_local;
|
|
921
|
+
if (EX_PROFILE) {
|
|
922
|
+
const double adjusted = unix_ts + EX_PROFILE->tz_offset_seconds;
|
|
923
|
+
const time_t sec = (time_t) adjusted;
|
|
924
|
+
if (!gmtime_r(&sec, &tm_local)) return -1;
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
const time_t sec = (time_t) unix_ts;
|
|
928
|
+
if (!localtime_r(&sec, &tm_local)) return -1;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
out->year = (uint16_t) (tm_local.tm_year + 1900);
|
|
932
|
+
out->month = (uint8_t) (tm_local.tm_mon + 1);
|
|
933
|
+
out->day = (uint8_t) tm_local.tm_mday;
|
|
934
|
+
out->stype = EX_PROFILE ? EX_PROFILE->resolve_session_type(out->year, out->month, out->day) : SESSION_TYPE_NORMINAL;
|
|
935
|
+
return 0;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
static inline int c_ex_profile_session_date_from_unix(double unix_ts, session_date_t* out) {
|
|
939
|
+
if (!out) return -1;
|
|
940
|
+
if (!isfinite(unix_ts)) return -1;
|
|
941
|
+
|
|
942
|
+
uint32_t ordinal = c_ex_profile_unix_to_ordinal(unix_ts, EX_PROFILE ? EX_PROFILE->tz_offset_seconds : 0.0);
|
|
943
|
+
if (ordinal < 1 || ordinal > EX_PROFILE_MAX_ORDINAL) return -1;
|
|
944
|
+
if (c_ex_profile_date_from_ordinal((uint32_t) ordinal, out) != 0) return -1;
|
|
945
|
+
out->stype = EX_PROFILE ? EX_PROFILE->resolve_session_type(out->year, out->month, out->day) : SESSION_TYPE_NORMINAL;
|
|
946
|
+
return 0;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
static inline double c_ex_profile_session_date_to_unix(const session_date_t* date) {
|
|
950
|
+
if (!date) return NAN;
|
|
951
|
+
if (!c_ex_profile_date_is_valid(date)) return NAN;
|
|
952
|
+
|
|
953
|
+
uint32_t ordinal = c_ex_profile_date_to_ordinal(date);
|
|
954
|
+
if (ordinal == 0u) return NAN;
|
|
955
|
+
|
|
956
|
+
// Convert ordinal to Unix timestamp at UTC midnight, then apply profile's UTC offset.
|
|
957
|
+
double unix_ts = ((double) (ordinal - UNIX_EPOCH_ORDINAL)) * (double) SECONDS_PER_DAY;
|
|
958
|
+
if (EX_PROFILE) unix_ts -= EX_PROFILE->tz_offset_seconds;
|
|
959
|
+
return unix_ts;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
static inline size_t c_ex_profile_session_date_index(const session_date_t* date, const session_date_range_t* drange) {
|
|
963
|
+
if (!date) return (size_t) -1;
|
|
964
|
+
if (!drange) return (size_t) -1;
|
|
965
|
+
if (drange->n_days == 0u) return (size_t) -1;
|
|
966
|
+
if (c_ex_profile_date_compare(date, &drange->start) == -1) return 0; // Start date
|
|
967
|
+
if (c_ex_profile_date_compare(date, &drange->end) == 1) return drange->n_days - 1; // End date
|
|
968
|
+
|
|
969
|
+
// Return the largest index i such that drange->dates[i] <= date.
|
|
970
|
+
// Use a half-open binary search to avoid size_t underflow.
|
|
971
|
+
size_t lo = 0;
|
|
972
|
+
size_t hi = drange->n_days; // exclusive
|
|
973
|
+
while (lo < hi) {
|
|
974
|
+
size_t mid = lo + ((hi - lo) >> 1);
|
|
975
|
+
int cmp = c_ex_profile_date_compare(&drange->dates[mid], date);
|
|
976
|
+
if (cmp <= 0) lo = mid + 1u;
|
|
977
|
+
else hi = mid;
|
|
978
|
+
}
|
|
979
|
+
// lo is the first index where drange->dates[lo] > date, so answer is lo-1.
|
|
980
|
+
return lo - 1u;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
static inline size_t c_ex_profile_session_ymd_index(uint16_t year, uint8_t month, uint8_t day, const session_date_t* date_array, size_t n_days) {
|
|
984
|
+
if (!date_array || n_days == 0u) return (size_t) -1;
|
|
985
|
+
|
|
986
|
+
session_date_t target = {.year = year, .month = month, .day = day, .stype = SESSION_TYPE_NORMINAL};
|
|
987
|
+
if (!c_ex_profile_date_is_valid(&target)) return (size_t) -1;
|
|
988
|
+
|
|
989
|
+
// Clamp to endpoints, matching c_ex_profile_session_date_index() behavior.
|
|
990
|
+
if (c_ex_profile_date_compare(&target, date_array) == -1) return 0u;
|
|
991
|
+
if (c_ex_profile_date_compare(&target, date_array + (n_days - 1u)) == 1) return n_days - 1u;
|
|
992
|
+
|
|
993
|
+
// Return the largest index i such that date_array[i] <= target.
|
|
994
|
+
// Use a half-open binary search to avoid size_t underflow.
|
|
995
|
+
size_t lo = 0u;
|
|
996
|
+
size_t hi = n_days; // exclusive
|
|
997
|
+
while (lo < hi) {
|
|
998
|
+
size_t mid = lo + ((hi - lo) >> 1);
|
|
999
|
+
int cmp = c_ex_profile_date_compare(date_array + mid, &target);
|
|
1000
|
+
if (cmp <= 0) lo = mid + 1u;
|
|
1001
|
+
else hi = mid;
|
|
1002
|
+
}
|
|
1003
|
+
return lo - 1u;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
static inline session_date_range_t* c_ex_profile_session_drange_between(const session_date_t* start_date, const session_date_t* end_date) {
|
|
1007
|
+
if (!start_date || !end_date) return NULL;
|
|
1008
|
+
// Cache contains trading days only. If no cache, fall back to natural calendar day range.
|
|
1009
|
+
if (!EX_TRADE_CALENDAR_CACHE) return c_ex_profile_date_range(start_date, end_date);
|
|
1010
|
+
if (EX_TRADE_CALENDAR_CACHE->n_days == 0u) return NULL;
|
|
1011
|
+
|
|
1012
|
+
// Normalize order.
|
|
1013
|
+
if (c_ex_profile_date_compare(start_date, end_date) == 1) {
|
|
1014
|
+
const session_date_t* tmp = start_date;
|
|
1015
|
+
start_date = end_date;
|
|
1016
|
+
end_date = tmp;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
size_t start_idx = c_ex_profile_session_date_index(start_date, EX_TRADE_CALENDAR_CACHE);
|
|
1020
|
+
size_t end_idx = c_ex_profile_session_date_index(end_date, EX_TRADE_CALENDAR_CACHE);
|
|
1021
|
+
if (start_idx == (size_t) -1 || end_idx == (size_t) -1) return NULL;
|
|
1022
|
+
|
|
1023
|
+
// Convert floor-index into a [first >= start_date] index.
|
|
1024
|
+
if (c_ex_profile_date_compare(EX_TRADE_CALENDAR_CACHE->dates + start_idx, start_date) == -1) {
|
|
1025
|
+
if (start_idx + 1u >= EX_TRADE_CALENDAR_CACHE->n_days) return NULL;
|
|
1026
|
+
start_idx += 1u;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// c_ex_profile_session_date_index() is a floor/left indexer, but it clamps "date < start" to 0.
|
|
1030
|
+
// Reject the clamp-at-0 case where even dates[0] is after end_date (no cached date <= end_date).
|
|
1031
|
+
if (end_idx == 0u && c_ex_profile_date_compare(EX_TRADE_CALENDAR_CACHE->dates, end_date) == 1) return NULL;
|
|
1032
|
+
|
|
1033
|
+
if (start_idx > end_idx) return NULL;
|
|
1034
|
+
|
|
1035
|
+
size_t n_days = (size_t) (end_idx - start_idx + 1u);
|
|
1036
|
+
session_date_range_t* drange = (session_date_range_t*) calloc(1, sizeof(session_date_range_t) + n_days * sizeof(session_date_t));
|
|
1037
|
+
if (!drange) return NULL;
|
|
1038
|
+
|
|
1039
|
+
drange->start = EX_TRADE_CALENDAR_CACHE->dates[start_idx];
|
|
1040
|
+
drange->end = EX_TRADE_CALENDAR_CACHE->dates[end_idx];
|
|
1041
|
+
drange->n_days = n_days;
|
|
1042
|
+
|
|
1043
|
+
for (size_t i = 0u; i < n_days; ++i) {
|
|
1044
|
+
drange->dates[i] = EX_TRADE_CALENDAR_CACHE->dates[start_idx + i];
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
return drange;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
static inline int c_ex_profile_session_trading_days_before(const session_date_t* market_date, size_t days, session_date_t* out) {
|
|
1051
|
+
if (!market_date || !out || days == 0) return -1;
|
|
1052
|
+
|
|
1053
|
+
if (!EX_TRADE_CALENDAR_CACHE) return c_ex_profile_days_before(market_date, days, out);
|
|
1054
|
+
if (EX_TRADE_CALENDAR_CACHE->n_days == 0u) return -1;
|
|
1055
|
+
|
|
1056
|
+
size_t idx = c_ex_profile_session_date_index(market_date, EX_TRADE_CALENDAR_CACHE);
|
|
1057
|
+
if (idx == (size_t) -1) return -1;
|
|
1058
|
+
|
|
1059
|
+
// If market_date is not a trading day, idx is the floor trading date (dates[idx] < market_date).
|
|
1060
|
+
// In that case, treat dates[idx] as "1 trading day before market_date".
|
|
1061
|
+
int cmp = c_ex_profile_date_compare(EX_TRADE_CALENDAR_CACHE->dates + idx, market_date);
|
|
1062
|
+
if (cmp == -1) days--;
|
|
1063
|
+
if (cmp == 1) return -1; // market_date is before the first trading day in the cache
|
|
1064
|
+
|
|
1065
|
+
size_t target = (idx > days) ? (idx - days) : 0u;
|
|
1066
|
+
*out = EX_TRADE_CALENDAR_CACHE->dates[target];
|
|
1067
|
+
return 0;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
static inline int c_ex_profile_session_trading_days_after(const session_date_t* market_date, size_t days, session_date_t* out) {
|
|
1071
|
+
if (!market_date || !out || days == 0) return -1;
|
|
1072
|
+
|
|
1073
|
+
if (!EX_TRADE_CALENDAR_CACHE) return c_ex_profile_days_after(market_date, days, out);
|
|
1074
|
+
if (EX_TRADE_CALENDAR_CACHE->n_days == 0u) return -1;
|
|
1075
|
+
|
|
1076
|
+
size_t idx = c_ex_profile_session_date_index(market_date, EX_TRADE_CALENDAR_CACHE);
|
|
1077
|
+
if (idx == (size_t) -1) return -1;
|
|
1078
|
+
int cmp = c_ex_profile_date_compare(EX_TRADE_CALENDAR_CACHE->dates + idx, market_date);
|
|
1079
|
+
if (cmp == 1) {
|
|
1080
|
+
size_t max_idx = EX_TRADE_CALENDAR_CACHE->n_days - 1u;
|
|
1081
|
+
size_t offset = days - 1u;
|
|
1082
|
+
size_t target = (offset > max_idx) ? max_idx : offset;
|
|
1083
|
+
*out = EX_TRADE_CALENDAR_CACHE->dates[target];
|
|
1084
|
+
return 0;
|
|
1085
|
+
}
|
|
1086
|
+
else {
|
|
1087
|
+
size_t max_idx = EX_TRADE_CALENDAR_CACHE->n_days - 1u;
|
|
1088
|
+
size_t target = (days > (max_idx - idx)) ? max_idx : (idx + days);
|
|
1089
|
+
*out = EX_TRADE_CALENDAR_CACHE->dates[target];
|
|
1090
|
+
return 0;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
static inline int c_ex_profile_nearest_trading_date(const session_date_t* market_date, bool previous, session_date_t* out) {
|
|
1095
|
+
if (!market_date || !out) return -1;
|
|
1096
|
+
|
|
1097
|
+
if (!EX_TRADE_CALENDAR_CACHE) {
|
|
1098
|
+
*out = *market_date;
|
|
1099
|
+
out->stype = EX_PROFILE ? EX_PROFILE->resolve_session_type(out->year, out->month, out->day) : SESSION_TYPE_NORMINAL;
|
|
1100
|
+
return c_ex_profile_date_is_valid(out) ? 0 : -1;
|
|
1101
|
+
}
|
|
1102
|
+
if (EX_TRADE_CALENDAR_CACHE->n_days == 0u) return -1;
|
|
1103
|
+
|
|
1104
|
+
size_t idx = c_ex_profile_session_date_index(market_date, EX_TRADE_CALENDAR_CACHE);
|
|
1105
|
+
if (idx == (size_t) -1) return -1;
|
|
1106
|
+
int cmp = c_ex_profile_date_compare(EX_TRADE_CALENDAR_CACHE->dates + idx, market_date);
|
|
1107
|
+
|
|
1108
|
+
if (cmp == 0) {
|
|
1109
|
+
*out = EX_TRADE_CALENDAR_CACHE->dates[idx];
|
|
1110
|
+
return 0;
|
|
1111
|
+
}
|
|
1112
|
+
else if (cmp == -1) {
|
|
1113
|
+
// market_date is after dates[idx], so nearest is either idx or idx + 1
|
|
1114
|
+
if (previous) {
|
|
1115
|
+
*out = EX_TRADE_CALENDAR_CACHE->dates[idx];
|
|
1116
|
+
return 0;
|
|
1117
|
+
}
|
|
1118
|
+
else if (idx + 1u >= EX_TRADE_CALENDAR_CACHE->n_days) return -1;
|
|
1119
|
+
else {
|
|
1120
|
+
*out = EX_TRADE_CALENDAR_CACHE->dates[idx + 1u];
|
|
1121
|
+
return 0;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
else {
|
|
1125
|
+
// market_date is before dates[idx], the only possible case is that idx == 0
|
|
1126
|
+
if (previous) return -1;
|
|
1127
|
+
else {
|
|
1128
|
+
*out = *EX_TRADE_CALENDAR_CACHE->dates;
|
|
1129
|
+
return 0;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
static inline bool c_ex_profile_is_trading_day(const session_date_t* market_date) {
|
|
1135
|
+
if (!market_date) return false;
|
|
1136
|
+
|
|
1137
|
+
if (!EX_TRADE_CALENDAR_CACHE) {
|
|
1138
|
+
return c_ex_profile_date_is_valid(market_date);
|
|
1139
|
+
}
|
|
1140
|
+
if (EX_TRADE_CALENDAR_CACHE->n_days == 0u) return false;
|
|
1141
|
+
|
|
1142
|
+
size_t idx = c_ex_profile_session_date_index(market_date, EX_TRADE_CALENDAR_CACHE);
|
|
1143
|
+
if (idx == (size_t) -1) return false;
|
|
1144
|
+
return c_ex_profile_date_compare(EX_TRADE_CALENDAR_CACHE->dates + idx, market_date) == 0;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
static inline int c_ex_profile_trading_days_between(const session_date_t* start_date, const session_date_t* end_date, ssize_t* out) {
|
|
1148
|
+
if (!start_date || !end_date || !out) return -1;
|
|
1149
|
+
|
|
1150
|
+
if (!EX_TRADE_CALENDAR_CACHE) {
|
|
1151
|
+
ssize_t start_ordinal = c_ex_profile_date_to_ordinal(start_date);
|
|
1152
|
+
ssize_t end_ordinal = c_ex_profile_date_to_ordinal(end_date);
|
|
1153
|
+
if (start_ordinal == -1 || end_ordinal == -1) return -1;
|
|
1154
|
+
*out = end_ordinal - start_ordinal;
|
|
1155
|
+
return 0;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
ssize_t start_idx = c_ex_profile_session_date_index(start_date, EX_TRADE_CALENDAR_CACHE);
|
|
1159
|
+
ssize_t end_idx = c_ex_profile_session_date_index(end_date, EX_TRADE_CALENDAR_CACHE);
|
|
1160
|
+
if (start_idx == -1 || end_idx == -1) return -1;
|
|
1161
|
+
*out = end_idx - start_idx;
|
|
1162
|
+
return 0;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
// ========== Public APIs (session_datetime_t) ==========
|
|
1166
|
+
|
|
1167
|
+
static inline int c_ex_profile_session_datetime_from_unix(double unix_ts, session_datetime_t* out) {
|
|
1168
|
+
if (!out) return -1;
|
|
1169
|
+
if (!isfinite(unix_ts)) return -1;
|
|
1170
|
+
|
|
1171
|
+
double tz_offset = EX_PROFILE ? EX_PROFILE->tz_offset_seconds : 0.0;
|
|
1172
|
+
uint32_t ordinal = c_ex_profile_unix_to_ordinal(unix_ts, tz_offset);
|
|
1173
|
+
double ts = fmod(unix_ts + tz_offset, (double) SECONDS_PER_DAY);
|
|
1174
|
+
|
|
1175
|
+
if (ordinal < 1 || ordinal > EX_PROFILE_MAX_ORDINAL) return -1;
|
|
1176
|
+
if (c_ex_profile_date_from_ordinal(ordinal, &out->date) != 0) return -1;
|
|
1177
|
+
out->date.stype = EX_PROFILE ? EX_PROFILE->resolve_session_type(out->date.year, out->date.month, out->date.day) : SESSION_TYPE_NORMINAL;
|
|
1178
|
+
if (c_ex_profile_session_time_from_ts(ts, &out->time) != 0) return -1;
|
|
1179
|
+
|
|
1180
|
+
out->unix_ts = unix_ts;
|
|
1181
|
+
out->ts = ts;
|
|
1182
|
+
out->ordinal = ordinal;
|
|
1183
|
+
return 0;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
static inline int c_ex_profile_session_datetime_update(session_datetime_t* dt, double unix_ts) {
|
|
1187
|
+
double tz_offset = EX_PROFILE ? EX_PROFILE->tz_offset_seconds : 0.0;
|
|
1188
|
+
uint32_t ordinal = c_ex_profile_unix_to_ordinal(unix_ts, tz_offset);
|
|
1189
|
+
double ts = fmod(unix_ts + tz_offset, (double) SECONDS_PER_DAY);
|
|
1190
|
+
|
|
1191
|
+
if (ordinal != dt->ordinal) {
|
|
1192
|
+
if (ordinal < 1 || ordinal > EX_PROFILE_MAX_ORDINAL) return -1;
|
|
1193
|
+
dt->date.stype = EX_PROFILE ? EX_PROFILE->resolve_session_type(dt->date.year, dt->date.month, dt->date.day) : SESSION_TYPE_NORMINAL;
|
|
1194
|
+
if (c_ex_profile_date_from_ordinal(ordinal, &dt->date) != 0) return -1;
|
|
1195
|
+
dt->ordinal = ordinal;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
if (c_ex_profile_session_time_from_ts(ts, &dt->time) != 0) return -1;
|
|
1199
|
+
dt->unix_ts = unix_ts;
|
|
1200
|
+
dt->ts = ts;
|
|
1201
|
+
return 0;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
#endif /* C_EX_PROFILE_BASE_H */
|