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.
Files changed (156) hide show
  1. algo_engine/__infra__.pxd +3 -0
  2. algo_engine/__init__.pxd +3 -0
  3. algo_engine/__init__.py +107 -0
  4. algo_engine/apps/__init__.py +17 -0
  5. algo_engine/apps/backtest/__init__.py +20 -0
  6. algo_engine/apps/backtest/doc_server.py +328 -0
  7. algo_engine/apps/backtest/static/styles/dash.css +48 -0
  8. algo_engine/apps/backtest/templates/dash.html +20 -0
  9. algo_engine/apps/backtest/templates/index.html +40 -0
  10. algo_engine/apps/backtest/tester.py +252 -0
  11. algo_engine/apps/backtest/web_app.py +125 -0
  12. algo_engine/apps/bokeh_server.py +245 -0
  13. algo_engine/apps/demo/__init__.py +0 -0
  14. algo_engine/apps/demo/test.py +40 -0
  15. algo_engine/apps/sim_input/__init__.py +23 -0
  16. algo_engine/apps/sim_input/client.py +412 -0
  17. algo_engine/apps/sim_input/sim_keyboard.py +88 -0
  18. algo_engine/apps/sim_input/sim_mouse.py +137 -0
  19. algo_engine/apps/sim_input/window.py +162 -0
  20. algo_engine/backtest/__init__.py +19 -0
  21. algo_engine/backtest/__main__.py +51 -0
  22. algo_engine/backtest/metrics.py +179 -0
  23. algo_engine/backtest/replay.py +635 -0
  24. algo_engine/backtest/sim_match.py +506 -0
  25. algo_engine/base/__infra__.pxd +3 -0
  26. algo_engine/base/__init__.pxd +3 -0
  27. algo_engine/base/__init__.py +60 -0
  28. algo_engine/base/c_allocator_protocol.c +11608 -0
  29. algo_engine/base/c_allocator_protocol.cp315-win_amd64.pyd +0 -0
  30. algo_engine/base/c_allocator_protocol.pxd +24 -0
  31. algo_engine/base/c_allocator_protocol.pyi +68 -0
  32. algo_engine/base/c_allocator_protocol.pyx +111 -0
  33. algo_engine/base/c_intern_string.c +5908 -0
  34. algo_engine/base/c_intern_string.cp315-win_amd64.pyd +0 -0
  35. algo_engine/base/c_intern_string.pxd +14 -0
  36. algo_engine/base/c_intern_string.pyi +22 -0
  37. algo_engine/base/c_intern_string.pyx +17 -0
  38. algo_engine/base/c_market_data/__infra__.pxd +196 -0
  39. algo_engine/base/c_market_data/__init__.pxd +196 -0
  40. algo_engine/base/c_market_data/__init__.py +24 -0
  41. algo_engine/base/c_market_data/c_candlestick.c +18996 -0
  42. algo_engine/base/c_market_data/c_candlestick.cp315-win_amd64.pyd +0 -0
  43. algo_engine/base/c_market_data/c_candlestick.pxd +27 -0
  44. algo_engine/base/c_market_data/c_candlestick.pyi +217 -0
  45. algo_engine/base/c_market_data/c_candlestick.pyx +255 -0
  46. algo_engine/base/c_market_data/c_internal.c +14059 -0
  47. algo_engine/base/c_market_data/c_internal.cp315-win_amd64.pyd +0 -0
  48. algo_engine/base/c_market_data/c_internal.pxd +14 -0
  49. algo_engine/base/c_market_data/c_internal.pyi +47 -0
  50. algo_engine/base/c_market_data/c_internal.pyx +41 -0
  51. algo_engine/base/c_market_data/c_market_data.c +30420 -0
  52. algo_engine/base/c_market_data/c_market_data.cp315-win_amd64.pyd +0 -0
  53. algo_engine/base/c_market_data/c_market_data.h +1464 -0
  54. algo_engine/base/c_market_data/c_market_data.pxd +414 -0
  55. algo_engine/base/c_market_data/c_market_data.pyi +550 -0
  56. algo_engine/base/c_market_data/c_market_data.pyx +701 -0
  57. algo_engine/base/c_market_data/c_market_data_buffer.c +29407 -0
  58. algo_engine/base/c_market_data/c_market_data_buffer.cp315-win_amd64.pyd +0 -0
  59. algo_engine/base/c_market_data/c_market_data_buffer.h +941 -0
  60. algo_engine/base/c_market_data/c_market_data_buffer.pxd +143 -0
  61. algo_engine/base/c_market_data/c_market_data_buffer.pyi +355 -0
  62. algo_engine/base/c_market_data/c_market_data_buffer.pyx +571 -0
  63. algo_engine/base/c_market_data/c_market_data_config.h +29 -0
  64. algo_engine/base/c_market_data/c_tick.c +44303 -0
  65. algo_engine/base/c_market_data/c_tick.cp315-win_amd64.pyd +0 -0
  66. algo_engine/base/c_market_data/c_tick.pxd +53 -0
  67. algo_engine/base/c_market_data/c_tick.pyi +466 -0
  68. algo_engine/base/c_market_data/c_tick.pyx +673 -0
  69. algo_engine/base/c_market_data/c_trade_utils.c +28702 -0
  70. algo_engine/base/c_market_data/c_trade_utils.cp315-win_amd64.pyd +0 -0
  71. algo_engine/base/c_market_data/c_trade_utils.pxd +53 -0
  72. algo_engine/base/c_market_data/c_trade_utils.pyi +602 -0
  73. algo_engine/base/c_market_data/c_trade_utils.pyx +609 -0
  74. algo_engine/base/c_market_data/c_transaction.c +23558 -0
  75. algo_engine/base/c_market_data/c_transaction.cp315-win_amd64.pyd +0 -0
  76. algo_engine/base/c_market_data/c_transaction.pxd +27 -0
  77. algo_engine/base/c_market_data/c_transaction.pyi +433 -0
  78. algo_engine/base/c_market_data/c_transaction.pyx +460 -0
  79. algo_engine/base/console_utils.py +1070 -0
  80. algo_engine/base/finance_decimal.py +258 -0
  81. algo_engine/base/telemetrics.py +18 -0
  82. algo_engine/engine/__infra__.pxd +10 -0
  83. algo_engine/engine/__init__.pxd +10 -0
  84. algo_engine/engine/__init__.py +40 -0
  85. algo_engine/engine/algo_engine.py +904 -0
  86. algo_engine/engine/c_event_engine.c +16325 -0
  87. algo_engine/engine/c_event_engine.cp315-win_amd64.pyd +0 -0
  88. algo_engine/engine/c_event_engine.pxd +25 -0
  89. algo_engine/engine/c_event_engine.pyi +68 -0
  90. algo_engine/engine/c_market_engine.c +24258 -0
  91. algo_engine/engine/c_market_engine.cp315-win_amd64.pyd +0 -0
  92. algo_engine/engine/c_market_engine.pxd +87 -0
  93. algo_engine/engine/c_market_engine.pyi +357 -0
  94. algo_engine/engine/event_engine.py +53 -0
  95. algo_engine/engine/trade_engine.py +2037 -0
  96. algo_engine/exchange_profile/__infra__.pxd +100 -0
  97. algo_engine/exchange_profile/__init__.pxd +100 -0
  98. algo_engine/exchange_profile/__init__.py +53 -0
  99. algo_engine/exchange_profile/c_ex_profile_base.c +87 -0
  100. algo_engine/exchange_profile/c_ex_profile_base.h +1204 -0
  101. algo_engine/exchange_profile/c_ex_profile_cn.c +968 -0
  102. algo_engine/exchange_profile/c_ex_profile_cn.h +39 -0
  103. algo_engine/exchange_profile/c_exchange_profile.c +52375 -0
  104. algo_engine/exchange_profile/c_exchange_profile.cp315-win_amd64.pyd +0 -0
  105. algo_engine/exchange_profile/c_exchange_profile.pxd +336 -0
  106. algo_engine/exchange_profile/c_exchange_profile.pyi +883 -0
  107. algo_engine/exchange_profile/c_exchange_profile.pyx +1495 -0
  108. algo_engine/exchange_profile/c_profile_cn.c +7798 -0
  109. algo_engine/exchange_profile/c_profile_cn.cp315-win_amd64.pyd +0 -0
  110. algo_engine/exchange_profile/c_profile_cn.pxd +1 -0
  111. algo_engine/exchange_profile/c_profile_cn.pyi +3 -0
  112. algo_engine/exchange_profile/c_profile_cn.pyx +1 -0
  113. algo_engine/exchange_profile/c_profile_default.c +7798 -0
  114. algo_engine/exchange_profile/c_profile_default.cp315-win_amd64.pyd +0 -0
  115. algo_engine/exchange_profile/c_profile_default.pxd +1 -0
  116. algo_engine/exchange_profile/c_profile_default.pyi +3 -0
  117. algo_engine/exchange_profile/c_profile_default.pyx +1 -0
  118. algo_engine/exchange_profile/c_profile_dispatcher.c +7798 -0
  119. algo_engine/exchange_profile/c_profile_dispatcher.cp315-win_amd64.pyd +0 -0
  120. algo_engine/exchange_profile/c_profile_dispatcher.pxd +1 -0
  121. algo_engine/exchange_profile/c_profile_dispatcher.pyi +3 -0
  122. algo_engine/exchange_profile/c_profile_dispatcher.pyx +1 -0
  123. algo_engine/includes/algo_engine/base/c_allocator_protocol.c +11608 -0
  124. algo_engine/includes/algo_engine/base/c_intern_string.c +5908 -0
  125. algo_engine/includes/algo_engine/base/c_market_data/c_candlestick.c +18996 -0
  126. algo_engine/includes/algo_engine/base/c_market_data/c_internal.c +14059 -0
  127. algo_engine/includes/algo_engine/base/c_market_data/c_market_data.c +30420 -0
  128. algo_engine/includes/algo_engine/base/c_market_data/c_market_data.h +1464 -0
  129. algo_engine/includes/algo_engine/base/c_market_data/c_market_data_buffer.c +29407 -0
  130. algo_engine/includes/algo_engine/base/c_market_data/c_market_data_buffer.h +941 -0
  131. algo_engine/includes/algo_engine/base/c_market_data/c_market_data_config.h +29 -0
  132. algo_engine/includes/algo_engine/base/c_market_data/c_tick.c +44303 -0
  133. algo_engine/includes/algo_engine/base/c_market_data/c_trade_utils.c +28702 -0
  134. algo_engine/includes/algo_engine/base/c_market_data/c_transaction.c +23558 -0
  135. algo_engine/includes/algo_engine/engine/c_event_engine.c +16325 -0
  136. algo_engine/includes/algo_engine/engine/c_market_engine.c +24258 -0
  137. algo_engine/includes/algo_engine/exchange_profile/c_ex_profile_base.c +87 -0
  138. algo_engine/includes/algo_engine/exchange_profile/c_ex_profile_base.h +1204 -0
  139. algo_engine/includes/algo_engine/exchange_profile/c_ex_profile_cn.c +968 -0
  140. algo_engine/includes/algo_engine/exchange_profile/c_ex_profile_cn.h +39 -0
  141. algo_engine/includes/algo_engine/exchange_profile/c_exchange_profile.c +52375 -0
  142. algo_engine/includes/algo_engine/exchange_profile/c_profile_cn.c +7798 -0
  143. algo_engine/includes/algo_engine/exchange_profile/c_profile_default.c +7798 -0
  144. algo_engine/includes/algo_engine/exchange_profile/c_profile_dispatcher.c +7798 -0
  145. algo_engine/monitor/__init__.py +15 -0
  146. algo_engine/monitor/advanced_data_interface.py +334 -0
  147. algo_engine/strategy/__init__.py +44 -0
  148. algo_engine/strategy/strategy_engine.py +441 -0
  149. algo_engine/utils/__init__.py +3 -0
  150. algo_engine/utils/commit_regularizer.py +49 -0
  151. algo_engine/utils/data_utils.py +296 -0
  152. pyalgoengine-0.12.3.dist-info/METADATA +142 -0
  153. pyalgoengine-0.12.3.dist-info/RECORD +156 -0
  154. pyalgoengine-0.12.3.dist-info/WHEEL +5 -0
  155. pyalgoengine-0.12.3.dist-info/licenses/LICENSE +21 -0
  156. pyalgoengine-0.12.3.dist-info/top_level.txt +2 -0
@@ -0,0 +1,941 @@
1
+ #ifndef C_MARKET_DATA_BUFFER_H
2
+ #define C_MARKET_DATA_BUFFER_H
3
+
4
+ #include <stdatomic.h>
5
+ #include <stddef.h>
6
+ #include <stdint.h>
7
+ #include <stdlib.h>
8
+ #include <string.h>
9
+ #include <time.h>
10
+
11
+ #include <algo_engine/base/c_market_data/c_market_data.h>
12
+ #include <cbase/allocator_protocol/c_allocator_protocol.h>
13
+ #include <cbase/intern_string/c_intern_string.h>
14
+
15
+ #include <math.h>
16
+ #ifdef _WIN32
17
+ #include <cbase/nt/time_sched_nt_compat.h>
18
+ #else
19
+ #include <sched.h>
20
+ #endif
21
+
22
+ #ifndef MD_BUF_PTR_DEFAULT_CAP
23
+ #define MD_BUF_PTR_DEFAULT_CAP 16
24
+ #endif
25
+
26
+ #ifndef MD_BUF_DATA_DEFAULT_CAP
27
+ #define MD_BUF_DATA_DEFAULT_CAP 1024
28
+ #endif
29
+
30
+ // ========== MarketData Buffer Structs ==========
31
+
32
+ typedef struct md_ptr_array {
33
+ size_t capacity;
34
+ size_t idx_head;
35
+ size_t idx_tail;
36
+ size_t* offsets; // Array of offsets to the actual data in the buffer.
37
+ } md_ptr_array;
38
+
39
+ typedef struct md_data_array {
40
+ size_t capacity;
41
+ size_t occupied;
42
+ char* buf; // Contiguous buffer storing serialized market data.
43
+ } md_data_array;
44
+
45
+ typedef struct md_block_buffer {
46
+ md_ptr_array ptr_array;
47
+ md_data_array data_array;
48
+ double current_timestamp;
49
+ bool sorted;
50
+ } md_block_buffer;
51
+
52
+ typedef struct md_ring_buffer {
53
+ md_ptr_array ptr_array;
54
+ md_data_array data_array;
55
+ } md_ring_buffer;
56
+
57
+ typedef struct md_concurrent_buffer_worker_ctx {
58
+ _Atomic size_t ptr_head;
59
+ _Atomic bool enabled;
60
+ } md_concurrent_buffer_worker_ctx;
61
+
62
+ typedef struct md_concurrent_buffer {
63
+ md_concurrent_buffer_worker_ctx* workers;
64
+ size_t n_workers;
65
+ const md_variant** buffer;
66
+ size_t capacity;
67
+ _Atomic size_t tail;
68
+ } md_concurrent_buffer;
69
+
70
+ // ========== Forward Declarations (Public API) ==========
71
+
72
+ static inline int c_md_compare_serialized(const void* a, const void* b);
73
+ static inline size_t c_md_total_buffer_size(const md_variant** md_array, size_t n_md);
74
+ static inline const md_variant* c_md_send_to_shm(md_variant* market_data, allocator_protocol* shm_allocator, istr_map* shm_pool);
75
+
76
+ static inline md_block_buffer* c_md_block_buffer_new(size_t ptr_capacity, size_t data_capacity, allocator_protocol* allocator);
77
+ static inline void c_md_block_buffer_free(md_block_buffer* buffer);
78
+ static inline int c_md_block_buffer_extend(md_block_buffer* buffer, size_t new_ptr_capacity, size_t new_data_capacity);
79
+ static inline int c_md_block_buffer_put(md_block_buffer* buffer, const md_variant* market_data);
80
+ static inline int c_md_block_buffer_get(md_block_buffer* buffer, size_t idx, const char** out);
81
+ static inline int c_md_block_buffer_sort(md_block_buffer* buffer);
82
+ static inline int c_md_block_buffer_clear(md_block_buffer* buffer);
83
+ static inline size_t c_md_block_buffer_serialized_size(md_block_buffer* buffer);
84
+ static inline int c_md_block_buffer_serialize(md_block_buffer* buffer, char* out);
85
+ static inline md_block_buffer* c_md_block_buffer_deserialize(const char* blob, allocator_protocol* allocator);
86
+
87
+ static inline md_ring_buffer* c_md_ring_buffer_new(size_t ptr_capacity, size_t data_capacity, allocator_protocol* allocator);
88
+ static inline void c_md_ring_buffer_free(md_ring_buffer* buffer);
89
+ static inline int c_md_ring_buffer_is_full(md_ring_buffer* buffer, const md_variant* market_data);
90
+ static inline int c_md_ring_buffer_is_empty(md_ring_buffer* buffer);
91
+ static inline size_t c_md_ring_buffer_size(md_ring_buffer* buffer);
92
+ static inline int c_md_ring_buffer_put(md_ring_buffer* buffer, const md_variant* market_data, bool block, double timeout);
93
+ static inline int c_md_ring_buffer_get(md_ring_buffer* buffer, size_t index, const char** out);
94
+ static inline int c_md_ring_buffer_listen(md_ring_buffer* buffer, bool block, double timeout, const char** out);
95
+
96
+ static inline md_concurrent_buffer* c_md_concurrent_buffer_new(size_t n_workers, size_t capacity, allocator_protocol* shm_allocator);
97
+ static inline void c_md_concurrent_buffer_free(md_concurrent_buffer* buffer);
98
+ static inline int c_md_concurrent_buffer_enable_worker(md_concurrent_buffer* buffer, size_t worker_id);
99
+ static inline int c_md_concurrent_buffer_disable_worker(md_concurrent_buffer* buffer, size_t worker_id);
100
+ static inline int c_md_concurrent_buffer_is_full(md_concurrent_buffer* buffer);
101
+ static inline int c_md_concurrent_buffer_is_empty(md_concurrent_buffer* buffer, size_t worker_id);
102
+ static inline int c_md_concurrent_buffer_put(md_concurrent_buffer* buffer, const md_variant* market_data, bool block, double timeout);
103
+ static inline int c_md_concurrent_buffer_listen(md_concurrent_buffer* buffer, size_t worker_id, bool block, double timeout, const md_variant** out);
104
+
105
+ // ========== Utility Functions ==========
106
+
107
+ static inline int c_md_compare_serialized(const void* a, const void* b) {
108
+ const char* md_serialized_a = *(const char* const*) a;
109
+ const char* md_serialized_b = *(const char* const*) b;
110
+ double timestamp_a = *(const double*) (md_serialized_a + sizeof(uint8_t));
111
+ double timestamp_b = *(const double*) (md_serialized_b + sizeof(uint8_t));
112
+
113
+ if (timestamp_a < timestamp_b) return -1;
114
+ if (timestamp_a > timestamp_b) return 1;
115
+ return 0;
116
+ }
117
+
118
+ static inline size_t c_md_total_buffer_size(const md_variant** md_array, size_t n_md) {
119
+ const md_variant* md;
120
+ size_t total_size = 0;
121
+
122
+ for (size_t i = 0; i < n_md; i++) {
123
+ md = md_array[i];
124
+ total_size += c_md_serialized_size(md);
125
+ }
126
+ return total_size;
127
+ }
128
+
129
+ static inline const md_variant* c_md_send_to_shm(md_variant* market_data, allocator_protocol* shm_allocator, istr_map* shm_pool) {
130
+ if (!market_data || !shm_allocator || !shm_pool) return NULL;
131
+
132
+ // Step 1: Intern ticker string
133
+ const char* interned_ticker;
134
+ if (market_data->meta_info.ticker) {
135
+ if (shm_allocator->with_lock) {
136
+ interned_ticker = c_istr_synced(shm_pool, market_data->meta_info.ticker, 0, NULL);
137
+ }
138
+ else {
139
+ interned_ticker = c_istr(shm_pool, market_data->meta_info.ticker, 0, NULL);
140
+ }
141
+ if (!interned_ticker) return NULL;
142
+ }
143
+ else {
144
+ interned_ticker = NULL;
145
+ }
146
+
147
+ // Step 2: Check if already in SHM
148
+ allocator_protocol* original_allocator = c_ap_protocol_from_ptr(market_data);
149
+ if (original_allocator->shm_allocator == shm_allocator->shm_allocator) {
150
+ market_data->meta_info.ticker = interned_ticker;
151
+ return market_data;
152
+ }
153
+
154
+ // Step 3: Initialize new market_data in SHM
155
+ md_data_type dtype = market_data->meta_info.dtype;
156
+ md_variant* payload = c_md_new(dtype, shm_allocator);
157
+ size_t size = c_md_get_size(dtype);
158
+ if (!payload) return NULL;
159
+
160
+ memcpy((void*) payload, (void*) market_data, size);
161
+ payload->meta_info.ticker = interned_ticker;
162
+
163
+ // Step 4: Handle order book pointers if md_tick_data
164
+ if (dtype == DTYPE_TICK) {
165
+ md_tick_data* src_tick = &market_data->tick_data_full;
166
+ md_tick_data* dst_tick = &payload->tick_data_full;
167
+
168
+ // Bid order book
169
+ if (src_tick->bid) {
170
+ size_t ob_capacity = src_tick->bid->capacity;
171
+ size_t ob_size = sizeof(md_orderbook) + (ob_capacity * sizeof(md_orderbook_entry));
172
+ md_orderbook* ob_shm = c_md_orderbook_new(ob_capacity, shm_allocator);
173
+ if (!ob_shm) {
174
+ c_ap_free(payload);
175
+ return NULL;
176
+ }
177
+ memcpy((void*) ob_shm, (void*) src_tick->bid, ob_size);
178
+ dst_tick->bid = ob_shm;
179
+ }
180
+ else {
181
+ dst_tick->bid = NULL;
182
+ }
183
+
184
+ // Ask order book
185
+ if (src_tick->ask) {
186
+ size_t ob_capacity = src_tick->ask->capacity;
187
+ size_t ob_size = sizeof(md_orderbook) + (ob_capacity * sizeof(md_orderbook_entry));
188
+ md_orderbook* ob_shm = c_md_orderbook_new(ob_capacity, shm_allocator);
189
+ if (!ob_shm) {
190
+ if (dst_tick->bid) {
191
+ c_ap_free(dst_tick->bid);
192
+ }
193
+ c_ap_free(payload);
194
+ return NULL;
195
+ }
196
+ memcpy((void*) ob_shm, (void*) src_tick->ask, ob_size);
197
+ dst_tick->ask = ob_shm;
198
+ }
199
+ else {
200
+ dst_tick->ask = NULL;
201
+ }
202
+ }
203
+
204
+ return payload;
205
+ }
206
+
207
+ // ========== BlockBuffer API Functions ==========
208
+
209
+ static inline md_block_buffer* c_md_block_buffer_new(size_t ptr_capacity, size_t data_capacity, allocator_protocol* allocator) {
210
+ md_block_buffer* buffer = (md_block_buffer*) c_ap_alloc(sizeof(md_block_buffer), allocator);
211
+ if (!buffer) return NULL;
212
+
213
+ if (ptr_capacity) {
214
+ buffer->ptr_array.capacity = ptr_capacity;
215
+ buffer->ptr_array.offsets = (size_t*) c_ap_alloc(ptr_capacity * sizeof(size_t), allocator);
216
+ if (!buffer->ptr_array.offsets) goto oom;
217
+ }
218
+
219
+ if (data_capacity) {
220
+ buffer->data_array.capacity = data_capacity;
221
+ buffer->data_array.buf = (char*) c_ap_alloc(data_capacity, allocator);
222
+ if (!buffer->data_array.buf) goto oom;
223
+ }
224
+
225
+ buffer->sorted = 1;
226
+ buffer->current_timestamp = NAN;
227
+ return buffer;
228
+
229
+ oom:
230
+ c_md_block_buffer_free(buffer);
231
+ return NULL;
232
+ }
233
+
234
+ static inline void c_md_block_buffer_free(md_block_buffer* buffer) {
235
+ if (!buffer) return;
236
+ if (buffer->ptr_array.offsets) c_ap_free(buffer->ptr_array.offsets);
237
+ if (buffer->data_array.buf) c_ap_free(buffer->data_array.buf);
238
+ c_ap_free(buffer);
239
+ }
240
+
241
+ static inline int c_md_block_buffer_extend(md_block_buffer* buffer, size_t new_ptr_capacity, size_t new_data_capacity) {
242
+ if (!buffer) return MD_ERR_INVALID_INPUT;
243
+
244
+ allocator_protocol* allocator = c_ap_protocol_from_ptr(buffer);
245
+
246
+ if (new_ptr_capacity > buffer->ptr_array.capacity) {
247
+ size_t* new_offsets = (size_t*) c_ap_alloc(new_ptr_capacity * sizeof(size_t), allocator);
248
+ if (!new_offsets) return MD_ERR_OOM;
249
+ if (buffer->ptr_array.offsets) {
250
+ memcpy(new_offsets, buffer->ptr_array.offsets, buffer->ptr_array.capacity * sizeof(size_t));
251
+ c_ap_free(buffer->ptr_array.offsets);
252
+ }
253
+ buffer->ptr_array.offsets = new_offsets;
254
+ buffer->ptr_array.capacity = new_ptr_capacity;
255
+ }
256
+
257
+ if (new_data_capacity > buffer->data_array.capacity) {
258
+ char* new_data_buf = (char*) c_ap_alloc(new_data_capacity, allocator);
259
+ if (!new_data_buf) return MD_ERR_OOM;
260
+ if (buffer->data_array.buf) {
261
+ memcpy(new_data_buf, buffer->data_array.buf, buffer->data_array.capacity);
262
+ c_ap_free(buffer->data_array.buf);
263
+ }
264
+ buffer->data_array.buf = new_data_buf;
265
+ buffer->data_array.capacity = new_data_capacity;
266
+ }
267
+
268
+ return MD_OK;
269
+ }
270
+
271
+ static inline int c_md_block_buffer_put(md_block_buffer* buffer, const md_variant* market_data) {
272
+ if (!buffer || !market_data) return MD_ERR_INVALID_INPUT;
273
+
274
+ size_t idx_tail = buffer->ptr_array.idx_tail;
275
+ size_t ptr_capacity = buffer->ptr_array.capacity;
276
+ if (idx_tail >= ptr_capacity) {
277
+ int ret_code = c_md_block_buffer_extend(
278
+ buffer,
279
+ ptr_capacity ? ptr_capacity * 2 : MD_BUF_PTR_DEFAULT_CAP,
280
+ 0
281
+ );
282
+ if (ret_code != MD_OK) return ret_code;
283
+ }
284
+
285
+ size_t serialized_size = c_md_serialized_size(market_data);
286
+ size_t data_tail = buffer->data_array.occupied;
287
+ size_t data_capacity = buffer->data_array.capacity;
288
+ if (data_tail + serialized_size > data_capacity) {
289
+ int ret_code = c_md_block_buffer_extend(
290
+ buffer,
291
+ 0,
292
+ data_capacity ? data_capacity * 2 : MD_BUF_DATA_DEFAULT_CAP
293
+ );
294
+ if (ret_code != MD_OK) return ret_code;
295
+ }
296
+
297
+ char* data_ptr = buffer->data_array.buf + data_tail;
298
+ c_md_serialize(market_data, data_ptr);
299
+
300
+ buffer->ptr_array.offsets[idx_tail] = data_tail;
301
+ buffer->ptr_array.idx_tail++;
302
+ buffer->data_array.occupied += serialized_size;
303
+
304
+ double ts = market_data->meta_info.timestamp;
305
+ if (idx_tail > 0) {
306
+ if (ts < buffer->current_timestamp) buffer->sorted = 0;
307
+ else buffer->current_timestamp = ts;
308
+ }
309
+ else {
310
+ buffer->sorted = 1;
311
+ buffer->current_timestamp = ts;
312
+ }
313
+
314
+ return MD_OK;
315
+ }
316
+
317
+ static inline int c_md_block_buffer_get(md_block_buffer* buffer, size_t idx, const char** out) {
318
+ if (!buffer) return MD_ERR_INVALID_INPUT;
319
+ size_t ptr_tail = buffer->ptr_array.idx_tail;
320
+ if (idx >= ptr_tail) return MD_ERR_OOR;
321
+
322
+ size_t data_offset = buffer->ptr_array.offsets[idx];
323
+ if (out) *out = (const char*) buffer->data_array.buf + data_offset;
324
+ return MD_OK;
325
+ }
326
+
327
+ static inline int c_md_block_buffer_sort(md_block_buffer* buffer) {
328
+ if (!buffer) return MD_ERR_INVALID_INPUT;
329
+ if (buffer->sorted) return MD_OK;
330
+
331
+ size_t ptr_tail = buffer->ptr_array.idx_tail;
332
+ if (ptr_tail <= 1) {
333
+ buffer->sorted = 1;
334
+ return MD_OK;
335
+ }
336
+
337
+ size_t* offset_array = buffer->ptr_array.offsets;
338
+ char* data_array = buffer->data_array.buf;
339
+ const md_variant** ptr_array = (const md_variant**) malloc(ptr_tail * sizeof(md_variant*));
340
+ if (!ptr_array) return MD_ERR_OOM;
341
+
342
+ for (size_t i = 0; i < ptr_tail; i++) {
343
+ ptr_array[i] = (const md_variant*) (data_array + offset_array[i]);
344
+ }
345
+
346
+ qsort(ptr_array, ptr_tail, sizeof(md_variant*), c_md_compare_serialized);
347
+
348
+ for (size_t i = 0; i < ptr_tail; i++) {
349
+ offset_array[i] = (size_t) ((char*) ptr_array[i] - data_array);
350
+ }
351
+
352
+ buffer->sorted = 1;
353
+ free(ptr_array);
354
+ return MD_OK;
355
+ }
356
+
357
+ static inline int c_md_block_buffer_clear(md_block_buffer* buffer) {
358
+ if (!buffer) return MD_ERR_INVALID_INPUT;
359
+
360
+ buffer->ptr_array.idx_tail = 0;
361
+ if (buffer->ptr_array.offsets) memset(buffer->ptr_array.offsets, 0, buffer->ptr_array.capacity * sizeof(size_t));
362
+ buffer->data_array.occupied = 0;
363
+ if (buffer->data_array.buf) memset(buffer->data_array.buf, 0, buffer->data_array.capacity);
364
+ buffer->current_timestamp = NAN;
365
+ buffer->sorted = 1;
366
+ return MD_OK;
367
+ }
368
+
369
+ static inline size_t c_md_block_buffer_serialized_size(md_block_buffer* buffer) {
370
+ if (!buffer) return 0;
371
+ size_t ttl_size = sizeof(md_block_buffer);
372
+ ttl_size += buffer->ptr_array.idx_tail * sizeof(size_t);
373
+ ttl_size += buffer->data_array.occupied;
374
+ return ttl_size;
375
+ }
376
+
377
+ static inline int c_md_block_buffer_serialize(md_block_buffer* buffer, char* out) {
378
+ if (!buffer || !out) return 0;
379
+
380
+ char* ptr = out;
381
+ memcpy(ptr, buffer, sizeof(md_block_buffer));
382
+ ptr += sizeof(md_block_buffer);
383
+
384
+ memcpy(ptr, buffer->ptr_array.offsets, buffer->ptr_array.idx_tail * sizeof(size_t));
385
+ ptr += buffer->ptr_array.idx_tail * sizeof(size_t);
386
+
387
+ memcpy(ptr, buffer->data_array.buf, buffer->data_array.occupied);
388
+ ptr += buffer->data_array.occupied;
389
+
390
+ md_block_buffer* out_buf = (md_block_buffer*) out;
391
+ out_buf->ptr_array.offsets = NULL;
392
+ out_buf->ptr_array.capacity = buffer->ptr_array.idx_tail;
393
+ out_buf->data_array.buf = NULL;
394
+ out_buf->data_array.capacity = buffer->data_array.occupied;
395
+
396
+ return MD_OK;
397
+ }
398
+
399
+ static inline md_block_buffer* c_md_block_buffer_deserialize(const char* blob, allocator_protocol* allocator) {
400
+ if (!blob) return NULL;
401
+
402
+ md_block_buffer* in_buf = (md_block_buffer*) blob;
403
+ md_block_buffer* buffer = c_md_block_buffer_new(
404
+ in_buf->ptr_array.idx_tail,
405
+ in_buf->data_array.occupied,
406
+ allocator
407
+ );
408
+ if (!buffer) return NULL;
409
+
410
+ buffer->ptr_array.idx_tail = in_buf->ptr_array.idx_tail;
411
+ buffer->data_array.occupied = in_buf->data_array.occupied;
412
+ buffer->current_timestamp = in_buf->current_timestamp;
413
+ buffer->sorted = in_buf->sorted;
414
+ const char* ptr = blob + sizeof(md_block_buffer);
415
+
416
+ memcpy(buffer->ptr_array.offsets, ptr, in_buf->ptr_array.idx_tail * sizeof(size_t));
417
+ ptr += in_buf->ptr_array.idx_tail * sizeof(size_t);
418
+
419
+ memcpy(buffer->data_array.buf, ptr, in_buf->data_array.occupied);
420
+ ptr += in_buf->data_array.occupied;
421
+
422
+ return buffer;
423
+ }
424
+
425
+ // ========== RingBuffer API Functions ==========
426
+
427
+ static inline md_ring_buffer* c_md_ring_buffer_new(size_t ptr_capacity, size_t data_capacity, allocator_protocol* allocator) {
428
+ md_ring_buffer* buffer = (md_ring_buffer*) c_ap_alloc(sizeof(md_ring_buffer), allocator);
429
+ if (!buffer) return NULL;
430
+
431
+ buffer->ptr_array.capacity = ptr_capacity;
432
+ if (ptr_capacity) {
433
+ buffer->ptr_array.offsets = (size_t*) c_ap_alloc(ptr_capacity * sizeof(size_t), allocator);
434
+ if (!buffer->ptr_array.offsets) goto oom;
435
+ }
436
+
437
+ buffer->data_array.capacity = data_capacity;
438
+ if (data_capacity) {
439
+ buffer->data_array.buf = (char*) c_ap_alloc(data_capacity, allocator);
440
+ if (!buffer->data_array.buf) goto oom;
441
+ }
442
+
443
+ return buffer;
444
+
445
+ oom:
446
+ c_md_ring_buffer_free(buffer);
447
+ return NULL;
448
+ }
449
+
450
+ static inline void c_md_ring_buffer_free(md_ring_buffer* buffer) {
451
+ if (!buffer) return;
452
+ if (buffer->ptr_array.offsets) c_ap_free(buffer->ptr_array.offsets);
453
+ if (buffer->data_array.buf) c_ap_free(buffer->data_array.buf);
454
+ c_ap_free(buffer);
455
+ }
456
+
457
+ static inline int c_md_ring_buffer_is_full(md_ring_buffer* buffer, const md_variant* market_data) {
458
+ if (!buffer) return MD_ERR_INVALID_INPUT;
459
+
460
+ size_t ptr_head = buffer->ptr_array.idx_head;
461
+ size_t ptr_tail = buffer->ptr_array.idx_tail;
462
+ size_t ptr_capacity = buffer->ptr_array.capacity;
463
+ size_t ptr_next = (ptr_tail + 1) % ptr_capacity;
464
+
465
+ if (ptr_head == ptr_next) return 1;
466
+
467
+ if (ptr_head == ptr_tail) return 0;
468
+
469
+ size_t payload_size = c_md_serialized_size(market_data);
470
+ size_t data_head = buffer->ptr_array.offsets[ptr_head];
471
+ size_t data_tail = buffer->data_array.occupied;
472
+
473
+ if (data_tail >= data_head) {
474
+ size_t space_end = buffer->data_array.capacity - data_tail;
475
+ size_t space_start = data_head;
476
+ if (payload_size <= space_end) {
477
+ return 0;
478
+ }
479
+ // Will not wrapped write, if the end space is insufficient, we just check the start space
480
+ else if (payload_size <= space_start) {
481
+ return 0;
482
+ }
483
+ else {
484
+ return 1;
485
+ }
486
+ }
487
+ else {
488
+ size_t space_middle = data_head - data_tail;
489
+ if (payload_size <= space_middle) {
490
+ return 0;
491
+ }
492
+ else {
493
+ return 1;
494
+ }
495
+ }
496
+ }
497
+
498
+ static inline int c_md_ring_buffer_is_empty(md_ring_buffer* buffer) {
499
+ if (!buffer) return MD_ERR_INVALID_INPUT;
500
+
501
+ size_t ptr_head = buffer->ptr_array.idx_head;
502
+ size_t ptr_tail = buffer->ptr_array.idx_tail;
503
+
504
+ if (ptr_head == ptr_tail) return 1;
505
+ else return 0;
506
+ }
507
+
508
+ static inline size_t c_md_ring_buffer_size(md_ring_buffer* buffer) {
509
+ if (!buffer) return 0;
510
+
511
+ size_t ptr_head = buffer->ptr_array.idx_head;
512
+ size_t ptr_tail = buffer->ptr_array.idx_tail;
513
+ size_t capacity = buffer->ptr_array.capacity;
514
+
515
+ if (ptr_tail >= ptr_head) {
516
+ return ptr_tail - ptr_head;
517
+ }
518
+ else {
519
+ return (capacity - ptr_head) + ptr_tail;
520
+ }
521
+ }
522
+
523
+ static inline int c_md_ring_buffer_put(md_ring_buffer* buffer, const md_variant* market_data, bool block, double timeout) {
524
+ if (!buffer || !market_data) return MD_ERR_INVALID_INPUT;
525
+
526
+ const uint32_t spin_per_check = 1000;
527
+ time_t start_time = 0;
528
+ time_t current_time;
529
+ double elapsed = 0.0;
530
+ uint32_t spin_count = 0;
531
+ uint32_t sleep_us = 0;
532
+ const int use_timeout = timeout > 0.0;
533
+
534
+ size_t serialized_size = c_md_serialized_size(market_data);
535
+
536
+ time(&start_time);
537
+
538
+ for (;;) {
539
+ size_t* offset_array = buffer->ptr_array.offsets;
540
+ size_t ptr_head = buffer->ptr_array.idx_head;
541
+ size_t ptr_tail = buffer->ptr_array.idx_tail;
542
+ size_t ptr_next = (ptr_tail + 1) % buffer->ptr_array.capacity;
543
+
544
+ /* Check pointer slot availability */
545
+ if (ptr_head == ptr_next) {
546
+ if (!block) return MD_ERR_BUF_FULL; /* full, non-blocking */
547
+ }
548
+ else {
549
+ /* Check data space availability and compute write_offset */
550
+ size_t data_head = offset_array[ptr_head];
551
+ size_t data_tail = buffer->data_array.occupied;
552
+ size_t write_offset = 0;
553
+
554
+ if (data_tail >= data_head) {
555
+ size_t space_end = buffer->data_array.capacity - data_tail;
556
+ if (serialized_size <= space_end) {
557
+ write_offset = data_tail;
558
+ buffer->data_array.occupied += serialized_size;
559
+ }
560
+ else {
561
+ /* Wrap around */
562
+ if (serialized_size <= data_head) {
563
+ write_offset = 0;
564
+ buffer->data_array.occupied = serialized_size;
565
+ }
566
+ else {
567
+ /* insufficient space; treat as full */
568
+ write_offset = (size_t) -1;
569
+ }
570
+ }
571
+ }
572
+ else {
573
+ size_t space_middle = data_head - data_tail;
574
+ if (serialized_size <= space_middle) {
575
+ write_offset = data_tail;
576
+ buffer->data_array.occupied += serialized_size;
577
+ }
578
+ else {
579
+ write_offset = (size_t) -1;
580
+ }
581
+ }
582
+
583
+ if (write_offset != (size_t) -1) {
584
+ /* Commit write */
585
+ char* data_ptr = buffer->data_array.buf + write_offset;
586
+ c_md_serialize(market_data, data_ptr);
587
+ offset_array[ptr_tail] = write_offset;
588
+ buffer->ptr_array.idx_tail = ptr_next;
589
+ return MD_OK;
590
+ }
591
+
592
+ /* No data space available */
593
+ if (!block) return MD_ERR_BUF_FULL;
594
+ }
595
+
596
+ /* Blocking wait with timeout/backoff */
597
+ if ((spin_count % spin_per_check) == 0) {
598
+ time(&current_time);
599
+ elapsed = difftime(current_time, start_time);
600
+
601
+ if (use_timeout && elapsed >= timeout) {
602
+ return MD_ERR_TIMEOUT; /* timeout */
603
+ }
604
+
605
+ if (elapsed < 0.1) {
606
+ sleep_us = 0; /* <100 ms: pure spin */
607
+ }
608
+ else if (elapsed < 1.0) {
609
+ sleep_us = 1; /* 100-1000 ms */
610
+ }
611
+ else if (elapsed < 3.0) {
612
+ sleep_us = 10; /* 1-3 s */
613
+ }
614
+ else if (elapsed < 15.0) {
615
+ sleep_us = 100; /* 3-15 s */
616
+ }
617
+ else {
618
+ sleep_us = 1000; /* >15 s */
619
+ }
620
+ }
621
+
622
+ if (sleep_us > 0) {
623
+ c_usleep(sleep_us);
624
+ }
625
+ else {
626
+ sched_yield();
627
+ }
628
+ spin_count += 1;
629
+ }
630
+ }
631
+
632
+ static inline int c_md_ring_buffer_get(md_ring_buffer* buffer, size_t index, const char** out) {
633
+ if (!buffer) return MD_ERR_INVALID_INPUT;
634
+ if (index >= buffer->ptr_array.capacity) return MD_ERR_OOR;
635
+ size_t size = c_md_ring_buffer_size(buffer);
636
+ if (index >= size) return MD_ERR_OOR;
637
+
638
+ size_t ptr_head = buffer->ptr_array.idx_head;
639
+ size_t ptr_capacity = buffer->ptr_array.capacity;
640
+ size_t ptr_idx = (ptr_head + index) % ptr_capacity;
641
+
642
+ size_t* offset_array = buffer->ptr_array.offsets;
643
+ size_t data_offset = offset_array[ptr_idx];
644
+
645
+ if (data_offset >= buffer->data_array.capacity) return MD_ERR_BUF_CORRUPTED;
646
+ if (out) *out = buffer->data_array.buf + data_offset;
647
+ return MD_OK;
648
+ }
649
+
650
+ static inline int c_md_ring_buffer_listen(md_ring_buffer* buffer, bool block, double timeout, const char** out) {
651
+ if (!buffer || !out) return MD_ERR_INVALID_INPUT;
652
+
653
+ const uint32_t spin_per_check = 1000;
654
+ time_t start_time = 0;
655
+ time_t current_time;
656
+ double elapsed = 0.0;
657
+ uint32_t spin_count = 0;
658
+ uint32_t sleep_us = 0;
659
+ const int use_timeout = timeout > 0.0;
660
+ size_t idx = buffer->ptr_array.idx_head;
661
+
662
+ if (!block && idx == buffer->ptr_array.idx_tail) {
663
+ return MD_ERR_BUF_EMPTY; /* empty and non-blocking */
664
+ }
665
+
666
+ time(&start_time);
667
+
668
+ for (;;) {
669
+ if (idx != buffer->ptr_array.idx_tail) {
670
+ size_t* offset_array = buffer->ptr_array.offsets;
671
+ size_t data_offset = offset_array[idx];
672
+ if (data_offset >= buffer->data_array.capacity) return MD_ERR_BUF_CORRUPTED; /* corrupt offset */
673
+ const char* data_ptr = buffer->data_array.buf + data_offset;
674
+ buffer->ptr_array.idx_head = (idx + 1) % buffer->ptr_array.capacity;
675
+ if (out) *out = data_ptr;
676
+ return MD_OK;
677
+ }
678
+
679
+ if ((spin_count % spin_per_check) == 0) {
680
+ time(&current_time);
681
+ elapsed = difftime(current_time, start_time);
682
+
683
+ if (use_timeout && elapsed >= timeout) {
684
+ return MD_ERR_TIMEOUT; /* timeout */
685
+ }
686
+
687
+ if (elapsed < 0.1) {
688
+ sleep_us = 0; /* <100 ms: pure spin */
689
+ }
690
+ else if (elapsed < 1.0) {
691
+ sleep_us = 1; /* 100-1000 ms */
692
+ }
693
+ else if (elapsed < 3.0) {
694
+ sleep_us = 10; /* 1-3 s */
695
+ }
696
+ else if (elapsed < 15.0) {
697
+ sleep_us = 100; /* 3-15 s */
698
+ }
699
+ else {
700
+ sleep_us = 1000; /* >15 s */
701
+ }
702
+ }
703
+
704
+ if (sleep_us > 0) {
705
+ c_usleep(sleep_us);
706
+ }
707
+ else {
708
+ sched_yield();
709
+ }
710
+ spin_count += 1;
711
+ }
712
+ }
713
+
714
+ // ========== ConcurrentBuffer API Functions ==========
715
+
716
+ /* Concurrent buffer uses the same unified return codes */
717
+
718
+ static inline md_concurrent_buffer* c_md_concurrent_buffer_new(size_t n_workers, size_t capacity, allocator_protocol* shm_allocator) {
719
+ size_t size = sizeof(md_concurrent_buffer) + n_workers * sizeof(md_concurrent_buffer_worker_ctx) + capacity * sizeof(md_variant*);
720
+
721
+ if (size == 0) return NULL;
722
+
723
+ if (!shm_allocator || !shm_allocator->with_shm) return NULL; /* only support shm allocator!*/
724
+
725
+ md_concurrent_buffer* buffer = (md_concurrent_buffer*) c_ap_alloc(size, shm_allocator);
726
+ if (!buffer) return NULL;
727
+ buffer->n_workers = n_workers;
728
+ buffer->workers = (md_concurrent_buffer_worker_ctx*) (buffer + 1);
729
+
730
+ for (size_t i = 0; i < n_workers; i++) {
731
+ atomic_init(&buffer->workers[i].enabled, true);
732
+ atomic_init(&buffer->workers[i].ptr_head, 0);
733
+ }
734
+
735
+ buffer->buffer = (const md_variant**) (buffer->workers + n_workers);
736
+ buffer->capacity = capacity;
737
+ atomic_init(&buffer->tail, 0);
738
+ return buffer;
739
+ }
740
+
741
+ static inline void c_md_concurrent_buffer_free(md_concurrent_buffer* buffer) {
742
+ if (!buffer) return;
743
+ c_ap_free((void*) buffer);
744
+ }
745
+
746
+ static inline int c_md_concurrent_buffer_enable_worker(md_concurrent_buffer* buffer, size_t worker_id) {
747
+ if (!buffer) return MD_ERR_INVALID_INPUT;
748
+ if (worker_id >= buffer->n_workers) return MD_ERR_OOR;
749
+
750
+ atomic_store_explicit(&buffer->workers[worker_id].ptr_head, 0, memory_order_relaxed);
751
+ atomic_store_explicit(&buffer->workers[worker_id].enabled, true, memory_order_release);
752
+ return 0;
753
+ }
754
+
755
+ static inline int c_md_concurrent_buffer_disable_worker(md_concurrent_buffer* buffer, size_t worker_id) {
756
+ if (!buffer) return MD_ERR_INVALID_INPUT;
757
+ if (worker_id >= buffer->n_workers) return MD_ERR_OOR;
758
+
759
+ atomic_store_explicit(&buffer->workers[worker_id].enabled, false, memory_order_release);
760
+ size_t tail_val = atomic_load_explicit(&buffer->tail, memory_order_acquire);
761
+ atomic_store_explicit(&buffer->workers[worker_id].ptr_head, (tail_val + buffer->capacity - 1) % buffer->capacity, memory_order_relaxed);
762
+ return 0;
763
+ }
764
+
765
+ static inline int c_md_concurrent_buffer_is_full(md_concurrent_buffer* buffer) {
766
+ if (!buffer) return MD_ERR_INVALID_INPUT;
767
+
768
+ size_t next_tail = (atomic_load_explicit(&buffer->tail, memory_order_relaxed) + 1) % buffer->capacity;
769
+
770
+ for (size_t i = 0; i < buffer->n_workers; i++) {
771
+ if (!atomic_load_explicit(&buffer->workers[i].enabled, memory_order_acquire)) continue;
772
+
773
+ if (atomic_load_explicit(&buffer->workers[i].ptr_head, memory_order_acquire) == next_tail) {
774
+ return 1;
775
+ }
776
+ }
777
+ return 0;
778
+ }
779
+
780
+ static inline int c_md_concurrent_buffer_is_empty(md_concurrent_buffer* buffer, size_t worker_id) {
781
+ if (!buffer) return MD_ERR_INVALID_INPUT;
782
+ if (worker_id >= buffer->n_workers) return MD_ERR_OOR;
783
+
784
+ md_concurrent_buffer_worker_ctx* worker = buffer->workers + worker_id;
785
+ if (!atomic_load_explicit(&worker->enabled, memory_order_acquire)) return MD_ERR_DISABLED;
786
+ if (atomic_load_explicit(&worker->ptr_head, memory_order_acquire) == atomic_load_explicit(&buffer->tail, memory_order_acquire)) return 1;
787
+ else return 0;
788
+ }
789
+
790
+ static inline int c_md_concurrent_buffer_put(md_concurrent_buffer* buffer, const md_variant* market_data, bool block, double timeout) {
791
+ /* Returns: MD_OK, MD_ERR_INVALID_INPUT, MD_ERR_INVALID_ALLOCATOR, MD_ERR_BUF_FULL, MD_ERR_TIMEOUT */
792
+ if (!buffer || !market_data) return MD_ERR_INVALID_INPUT;
793
+
794
+ allocator_protocol* market_data_allocator = c_ap_protocol_from_ptr(market_data);
795
+ if (!market_data_allocator->with_shm) return MD_ERR_INVALID_ALLOCATOR;
796
+
797
+ const uint32_t spin_per_check = 1000;
798
+ time_t start_time = 0;
799
+ time_t current_time;
800
+ double elapsed = 0.0;
801
+ uint32_t spin_count = 0;
802
+ uint32_t sleep_us = 0;
803
+ const int use_timeout = timeout > 0.0;
804
+
805
+ size_t tail_val = atomic_load_explicit(&buffer->tail, memory_order_relaxed);
806
+ size_t next_tail = (tail_val + 1) % buffer->capacity;
807
+
808
+ // Fast check: if full and non-blocking, exit
809
+ for (size_t i = 0; i < buffer->n_workers; i++) {
810
+ if (!atomic_load_explicit(&buffer->workers[i].enabled, memory_order_acquire)) continue;
811
+ if (atomic_load_explicit(&buffer->workers[i].ptr_head, memory_order_acquire) == next_tail) {
812
+ if (!block) return MD_ERR_BUF_FULL; /* full, non-blocking */
813
+ time(&start_time);
814
+ break;
815
+ }
816
+ }
817
+
818
+ // Blocking wait until a slot is available or timeout
819
+ // Single-producer assumption: caller provides external synchronization
820
+ while (block) {
821
+ bool is_full = 0;
822
+ for (size_t i = 0; i < buffer->n_workers; i++) {
823
+ if (!atomic_load_explicit(&buffer->workers[i].enabled, memory_order_acquire)) continue;
824
+ if (atomic_load_explicit(&buffer->workers[i].ptr_head, memory_order_acquire) == next_tail) {
825
+ is_full = 1;
826
+ break;
827
+ }
828
+ }
829
+
830
+ if (!is_full) break; /* space available */
831
+
832
+ if ((spin_count % spin_per_check) == 0) {
833
+ time(&current_time);
834
+ elapsed = difftime(current_time, start_time);
835
+
836
+ if (use_timeout && elapsed >= timeout) {
837
+ return MD_ERR_TIMEOUT; /* timeout */
838
+ }
839
+
840
+ if (elapsed < 0.1) {
841
+ sleep_us = 0; /* <100 ms: pure spin */
842
+ }
843
+ else if (elapsed < 1.0) {
844
+ sleep_us = 1; /* 100-1000 ms */
845
+ }
846
+ else if (elapsed < 3.0) {
847
+ sleep_us = 10; /* 1-3 s */
848
+ }
849
+ else if (elapsed < 15.0) {
850
+ sleep_us = 100; /* 3-15 s */
851
+ }
852
+ else {
853
+ sleep_us = 1000; /* >15 s */
854
+ }
855
+ }
856
+
857
+ if (sleep_us > 0) {
858
+ c_usleep(sleep_us);
859
+ }
860
+ else {
861
+ sched_yield();
862
+ }
863
+ spin_count += 1;
864
+ }
865
+
866
+ // Commit: write data, then release-publish tail (consumer's acquire on tail sees data)
867
+ buffer->buffer[tail_val] = market_data;
868
+ atomic_store_explicit(&buffer->tail, next_tail, memory_order_release);
869
+
870
+ return MD_OK;
871
+ }
872
+
873
+ static inline int c_md_concurrent_buffer_listen(md_concurrent_buffer* buffer, size_t worker_id, bool block, double timeout, const md_variant** out) {
874
+ /* Returns: MD_OK, MD_ERR_INVALID_INPUT, -2 (worker OOR), -3 (worker disabled), MD_ERR_BUF_EMPTY, MD_ERR_TIMEOUT */
875
+ if (!buffer || !out) return MD_ERR_INVALID_INPUT;
876
+ if (worker_id >= buffer->n_workers) return MD_ERR_OOR; /* worker out of range */
877
+
878
+ md_concurrent_buffer_worker_ctx* worker = buffer->workers + worker_id;
879
+ if (!atomic_load_explicit(&worker->enabled, memory_order_acquire)) return MD_ERR_DISABLED; /* worker disabled */
880
+
881
+ const uint32_t spin_per_check = 1000;
882
+ time_t start_time = 0;
883
+ time_t current_time;
884
+ double elapsed = 0.0;
885
+ uint32_t spin_count = 0;
886
+ uint32_t sleep_us = 0;
887
+ const int use_timeout = timeout > 0.0;
888
+ size_t idx = atomic_load_explicit(&worker->ptr_head, memory_order_relaxed);
889
+
890
+ if (!block && idx == atomic_load_explicit(&buffer->tail, memory_order_acquire)) {
891
+ return MD_ERR_BUF_EMPTY; /* empty and non-blocking */
892
+ }
893
+
894
+ time(&start_time);
895
+
896
+ for (;;) {
897
+ size_t current_tail = atomic_load_explicit(&buffer->tail, memory_order_acquire);
898
+ if (idx != current_tail) {
899
+ // acquire on tail guarantees data written before producer's release-store is visible
900
+ const md_variant* md = buffer->buffer[idx];
901
+ atomic_store_explicit(&worker->ptr_head, (idx + 1) % buffer->capacity, memory_order_release);
902
+ *out = md;
903
+ return MD_OK;
904
+ }
905
+
906
+ if ((spin_count % spin_per_check) == 0) {
907
+ time(&current_time);
908
+ elapsed = difftime(current_time, start_time);
909
+
910
+ if (use_timeout && elapsed >= timeout) {
911
+ return MD_ERR_TIMEOUT; /* timeout */
912
+ }
913
+
914
+ if (elapsed < 0.1) {
915
+ sleep_us = 0; /* <100 ms: pure spin */
916
+ }
917
+ else if (elapsed < 1.0) {
918
+ sleep_us = 1; /* 100-1000 ms */
919
+ }
920
+ else if (elapsed < 3.0) {
921
+ sleep_us = 10; /* 1-3 s */
922
+ }
923
+ else if (elapsed < 15.0) {
924
+ sleep_us = 100; /* 3-15 s */
925
+ }
926
+ else {
927
+ sleep_us = 1000; /* >15 s */
928
+ }
929
+ }
930
+
931
+ if (sleep_us > 0) {
932
+ c_usleep(sleep_us);
933
+ }
934
+ else {
935
+ sched_yield();
936
+ }
937
+ spin_count += 1;
938
+ }
939
+ }
940
+
941
+ #endif /* C_MARKET_DATA_BUFFER_H */