onesecondtrader 0.55.0__py3-none-any.whl → 0.56.0__py3-none-any.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.
@@ -0,0 +1,759 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import pathlib
5
+ import sqlite3
6
+ import time
7
+
8
+ from onesecondtrader import events, messaging
9
+
10
+
11
+ BATCH_SIZE = 1000
12
+
13
+
14
+ class RunRecorder(messaging.Subscriber):
15
+ """
16
+ Subscriber that records all trading system events to a SQLite runs database.
17
+
18
+ The recorder subscribes to market data events, order requests, broker responses,
19
+ fills, and expirations, inserting them into the appropriate tables as defined
20
+ in the runs schema.
21
+
22
+ Events are buffered and inserted in batches for performance. The buffer is
23
+ flushed on shutdown via `_cleanup`.
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ event_bus: messaging.EventBus,
29
+ db_path: pathlib.Path,
30
+ run_id: str,
31
+ name: str,
32
+ config: dict | None = None,
33
+ metadata: dict | None = None,
34
+ ) -> None:
35
+ """
36
+ Initialize the recorder and register a new run in the database.
37
+
38
+ The database is created if it does not exist. A new row is inserted into the `runs`
39
+ table with status `running` and the current timestamp as `ts_start`.
40
+
41
+ Parameters:
42
+ event_bus:
43
+ Event bus used for subscribing to system events.
44
+ db_path:
45
+ Filesystem path to the SQLite runs database.
46
+ run_id:
47
+ Unique identifier for this run.
48
+ name:
49
+ Human-readable name for this run.
50
+ config:
51
+ Optional configuration dictionary to store as JSON.
52
+ metadata:
53
+ Optional metadata dictionary to store as JSON.
54
+ """
55
+ self._db_path = db_path
56
+ self._run_id = run_id
57
+ self._name = name
58
+ self._config = config
59
+ self._metadata = metadata
60
+
61
+ self._conn = self._init_db()
62
+ self._buffers: dict[str, list[tuple]] = {
63
+ "bars": [],
64
+ "bars_processed": [],
65
+ "order_submissions": [],
66
+ "order_cancellations": [],
67
+ "order_modifications": [],
68
+ "orders_accepted": [],
69
+ "orders_rejected": [],
70
+ "cancellations_accepted": [],
71
+ "cancellations_rejected": [],
72
+ "modifications_accepted": [],
73
+ "modifications_rejected": [],
74
+ "fills": [],
75
+ "expirations": [],
76
+ }
77
+
78
+ self._register_run()
79
+
80
+ super().__init__(event_bus)
81
+ self._subscribe(
82
+ events.market.BarReceived,
83
+ events.market.BarProcessed,
84
+ events.requests.OrderSubmissionRequest,
85
+ events.requests.OrderCancellationRequest,
86
+ events.requests.OrderModificationRequest,
87
+ events.responses.OrderAccepted,
88
+ events.responses.OrderRejected,
89
+ events.responses.CancellationAccepted,
90
+ events.responses.CancellationRejected,
91
+ events.responses.ModificationAccepted,
92
+ events.responses.ModificationRejected,
93
+ events.orders.FillEvent,
94
+ events.orders.OrderExpired,
95
+ )
96
+
97
+ def _init_db(self) -> sqlite3.Connection:
98
+ """
99
+ Initialize the SQLite database connection.
100
+
101
+ Creates the database file and parent directories if they do not exist.
102
+ Applies the runs schema if the database is newly created.
103
+
104
+ Returns:
105
+ Open database connection configured with WAL journal mode.
106
+ """
107
+ schema_path = pathlib.Path(__file__).parent / "runs_schema.sql"
108
+ db_exists = self._db_path.is_file()
109
+
110
+ self._db_path.parent.mkdir(parents=True, exist_ok=True)
111
+ conn = sqlite3.connect(str(self._db_path), check_same_thread=False)
112
+ conn.execute("PRAGMA foreign_keys = ON")
113
+ conn.execute("PRAGMA journal_mode = WAL")
114
+ conn.execute("PRAGMA synchronous = NORMAL")
115
+
116
+ if not db_exists:
117
+ conn.executescript(schema_path.read_text())
118
+
119
+ return conn
120
+
121
+ def _register_run(self) -> None:
122
+ """
123
+ Insert a new run record into the database.
124
+
125
+ The run is created with status 'running' and the current timestamp as start time.
126
+ """
127
+ self._conn.execute(
128
+ """
129
+ INSERT INTO runs (run_id, name, ts_start, status, config, metadata)
130
+ VALUES (?, ?, ?, ?, ?, ?)
131
+ """,
132
+ (
133
+ self._run_id,
134
+ self._name,
135
+ time.time_ns(),
136
+ "running",
137
+ json.dumps(self._config) if self._config else None,
138
+ json.dumps(self._metadata) if self._metadata else None,
139
+ ),
140
+ )
141
+ self._conn.commit()
142
+
143
+ def update_run_status(
144
+ self,
145
+ status: str,
146
+ ts_end: int | None = None,
147
+ ) -> None:
148
+ """
149
+ Update the status and end timestamp of the current run.
150
+
151
+ Parameters:
152
+ status:
153
+ New status value (e.g., 'completed', 'failed', 'cancelled').
154
+ ts_end:
155
+ End timestamp in nanoseconds since Unix epoch. Defaults to current time.
156
+ """
157
+ if ts_end is None:
158
+ ts_end = time.time_ns()
159
+ self._conn.execute(
160
+ "UPDATE runs SET status = ?, ts_end = ? WHERE run_id = ?",
161
+ (status, ts_end, self._run_id),
162
+ )
163
+ self._conn.commit()
164
+
165
+ def _on_event(self, event: events.EventBase) -> None:
166
+ """
167
+ Dispatch an incoming event to the appropriate buffer method.
168
+
169
+ Parameters:
170
+ event:
171
+ Event received from the event bus.
172
+ """
173
+ match event:
174
+ case events.market.BarProcessed() as matched:
175
+ self._buffer_bar_processed(matched)
176
+ case events.market.BarReceived() as matched:
177
+ self._buffer_bar_received(matched)
178
+ case events.requests.OrderSubmissionRequest() as matched:
179
+ self._buffer_order_submission(matched)
180
+ case events.requests.OrderCancellationRequest() as matched:
181
+ self._buffer_order_cancellation(matched)
182
+ case events.requests.OrderModificationRequest() as matched:
183
+ self._buffer_order_modification(matched)
184
+ case events.responses.OrderAccepted() as matched:
185
+ self._buffer_order_accepted(matched)
186
+ case events.responses.OrderRejected() as matched:
187
+ self._buffer_order_rejected(matched)
188
+ case events.responses.CancellationAccepted() as matched:
189
+ self._buffer_cancellation_accepted(matched)
190
+ case events.responses.CancellationRejected() as matched:
191
+ self._buffer_cancellation_rejected(matched)
192
+ case events.responses.ModificationAccepted() as matched:
193
+ self._buffer_modification_accepted(matched)
194
+ case events.responses.ModificationRejected() as matched:
195
+ self._buffer_modification_rejected(matched)
196
+ case events.orders.FillEvent() as matched:
197
+ self._buffer_fill(matched)
198
+ case events.orders.OrderExpired() as matched:
199
+ self._buffer_expiration(matched)
200
+
201
+ def _on_exception(self, exc: Exception) -> None:
202
+ """
203
+ Handle an exception raised during event processing.
204
+
205
+ Parameters:
206
+ exc:
207
+ Exception that was raised.
208
+ """
209
+ pass
210
+
211
+ def _cleanup(self) -> None:
212
+ """
213
+ Flush all buffered records and close the database connection.
214
+
215
+ Called automatically during subscriber shutdown.
216
+ """
217
+ self._flush_all()
218
+ self._conn.close()
219
+
220
+ def _flush_all(self) -> None:
221
+ """
222
+ Flush all event buffers to the database.
223
+ """
224
+ self._flush_bars()
225
+ self._flush_bars_processed()
226
+ self._flush_order_submissions()
227
+ self._flush_order_cancellations()
228
+ self._flush_order_modifications()
229
+ self._flush_orders_accepted()
230
+ self._flush_orders_rejected()
231
+ self._flush_cancellations_accepted()
232
+ self._flush_cancellations_rejected()
233
+ self._flush_modifications_accepted()
234
+ self._flush_modifications_rejected()
235
+ self._flush_fills()
236
+ self._flush_expirations()
237
+
238
+ def _buffer_bar_received(self, event: events.market.BarReceived) -> None:
239
+ """
240
+ Buffer a bar received event for batch insertion.
241
+
242
+ Parameters:
243
+ event:
244
+ Bar received event to buffer.
245
+ """
246
+ self._buffers["bars"].append(
247
+ (
248
+ self._run_id,
249
+ event.ts_event_ns,
250
+ event.ts_created_ns,
251
+ event.symbol,
252
+ event.bar_period.name,
253
+ event.open,
254
+ event.high,
255
+ event.low,
256
+ event.close,
257
+ event.volume,
258
+ )
259
+ )
260
+ if len(self._buffers["bars"]) >= BATCH_SIZE:
261
+ self._flush_bars()
262
+
263
+ def _buffer_bar_processed(self, event: events.market.BarProcessed) -> None:
264
+ """
265
+ Buffer a bar processed event for batch insertion.
266
+
267
+ Parameters:
268
+ event:
269
+ Bar processed event to buffer.
270
+ """
271
+ self._buffers["bars_processed"].append(
272
+ (
273
+ self._run_id,
274
+ event.ts_event_ns,
275
+ event.ts_created_ns,
276
+ event.symbol,
277
+ event.bar_period.name,
278
+ event.open,
279
+ event.high,
280
+ event.low,
281
+ event.close,
282
+ event.volume,
283
+ json.dumps(event.indicators),
284
+ )
285
+ )
286
+ if len(self._buffers["bars_processed"]) >= BATCH_SIZE:
287
+ self._flush_bars_processed()
288
+
289
+ def _buffer_order_submission(
290
+ self, event: events.requests.OrderSubmissionRequest
291
+ ) -> None:
292
+ """
293
+ Buffer an order submission request for batch insertion.
294
+
295
+ Parameters:
296
+ event:
297
+ Order submission request to buffer.
298
+ """
299
+ self._buffers["order_submissions"].append(
300
+ (
301
+ self._run_id,
302
+ event.ts_event_ns,
303
+ event.ts_created_ns,
304
+ str(event.system_order_id),
305
+ event.symbol,
306
+ event.order_type.name,
307
+ event.side.name,
308
+ event.quantity,
309
+ event.limit_price,
310
+ event.stop_price,
311
+ event.action.name if event.action else None,
312
+ event.signal,
313
+ )
314
+ )
315
+ if len(self._buffers["order_submissions"]) >= BATCH_SIZE:
316
+ self._flush_order_submissions()
317
+
318
+ def _buffer_order_cancellation(
319
+ self, event: events.requests.OrderCancellationRequest
320
+ ) -> None:
321
+ """
322
+ Buffer an order cancellation request for batch insertion.
323
+
324
+ Parameters:
325
+ event:
326
+ Order cancellation request to buffer.
327
+ """
328
+ self._buffers["order_cancellations"].append(
329
+ (
330
+ self._run_id,
331
+ event.ts_event_ns,
332
+ event.ts_created_ns,
333
+ str(event.system_order_id),
334
+ event.symbol,
335
+ )
336
+ )
337
+ if len(self._buffers["order_cancellations"]) >= BATCH_SIZE:
338
+ self._flush_order_cancellations()
339
+
340
+ def _buffer_order_modification(
341
+ self, event: events.requests.OrderModificationRequest
342
+ ) -> None:
343
+ """
344
+ Buffer an order modification request for batch insertion.
345
+
346
+ Parameters:
347
+ event:
348
+ Order modification request to buffer.
349
+ """
350
+ self._buffers["order_modifications"].append(
351
+ (
352
+ self._run_id,
353
+ event.ts_event_ns,
354
+ event.ts_created_ns,
355
+ str(event.system_order_id),
356
+ event.symbol,
357
+ event.quantity,
358
+ event.limit_price,
359
+ event.stop_price,
360
+ )
361
+ )
362
+ if len(self._buffers["order_modifications"]) >= BATCH_SIZE:
363
+ self._flush_order_modifications()
364
+
365
+ def _buffer_order_accepted(self, event: events.responses.OrderAccepted) -> None:
366
+ """
367
+ Buffer an order accepted response for batch insertion.
368
+
369
+ Parameters:
370
+ event:
371
+ Order accepted response to buffer.
372
+ """
373
+ self._buffers["orders_accepted"].append(
374
+ (
375
+ self._run_id,
376
+ event.ts_event_ns,
377
+ event.ts_created_ns,
378
+ event.ts_broker_ns,
379
+ str(event.associated_order_id),
380
+ event.broker_order_id,
381
+ )
382
+ )
383
+ if len(self._buffers["orders_accepted"]) >= BATCH_SIZE:
384
+ self._flush_orders_accepted()
385
+
386
+ def _buffer_order_rejected(self, event: events.responses.OrderRejected) -> None:
387
+ """
388
+ Buffer an order rejected response for batch insertion.
389
+
390
+ Parameters:
391
+ event:
392
+ Order rejected response to buffer.
393
+ """
394
+ self._buffers["orders_rejected"].append(
395
+ (
396
+ self._run_id,
397
+ event.ts_event_ns,
398
+ event.ts_created_ns,
399
+ event.ts_broker_ns,
400
+ str(event.associated_order_id),
401
+ event.rejection_reason.name,
402
+ event.rejection_message,
403
+ )
404
+ )
405
+ if len(self._buffers["orders_rejected"]) >= BATCH_SIZE:
406
+ self._flush_orders_rejected()
407
+
408
+ def _buffer_cancellation_accepted(
409
+ self, event: events.responses.CancellationAccepted
410
+ ) -> None:
411
+ """
412
+ Buffer a cancellation accepted response for batch insertion.
413
+
414
+ Parameters:
415
+ event:
416
+ Cancellation accepted response to buffer.
417
+ """
418
+ self._buffers["cancellations_accepted"].append(
419
+ (
420
+ self._run_id,
421
+ event.ts_event_ns,
422
+ event.ts_created_ns,
423
+ event.ts_broker_ns,
424
+ str(event.associated_order_id),
425
+ event.broker_order_id,
426
+ )
427
+ )
428
+ if len(self._buffers["cancellations_accepted"]) >= BATCH_SIZE:
429
+ self._flush_cancellations_accepted()
430
+
431
+ def _buffer_cancellation_rejected(
432
+ self, event: events.responses.CancellationRejected
433
+ ) -> None:
434
+ """
435
+ Buffer a cancellation rejected response for batch insertion.
436
+
437
+ Parameters:
438
+ event:
439
+ Cancellation rejected response to buffer.
440
+ """
441
+ self._buffers["cancellations_rejected"].append(
442
+ (
443
+ self._run_id,
444
+ event.ts_event_ns,
445
+ event.ts_created_ns,
446
+ event.ts_broker_ns,
447
+ str(event.associated_order_id),
448
+ event.rejection_reason.name,
449
+ event.rejection_message,
450
+ )
451
+ )
452
+ if len(self._buffers["cancellations_rejected"]) >= BATCH_SIZE:
453
+ self._flush_cancellations_rejected()
454
+
455
+ def _buffer_modification_accepted(
456
+ self, event: events.responses.ModificationAccepted
457
+ ) -> None:
458
+ """
459
+ Buffer a modification accepted response for batch insertion.
460
+
461
+ Parameters:
462
+ event:
463
+ Modification accepted response to buffer.
464
+ """
465
+ self._buffers["modifications_accepted"].append(
466
+ (
467
+ self._run_id,
468
+ event.ts_event_ns,
469
+ event.ts_created_ns,
470
+ event.ts_broker_ns,
471
+ str(event.associated_order_id),
472
+ event.broker_order_id,
473
+ )
474
+ )
475
+ if len(self._buffers["modifications_accepted"]) >= BATCH_SIZE:
476
+ self._flush_modifications_accepted()
477
+
478
+ def _buffer_modification_rejected(
479
+ self, event: events.responses.ModificationRejected
480
+ ) -> None:
481
+ """
482
+ Buffer a modification rejected response for batch insertion.
483
+
484
+ Parameters:
485
+ event:
486
+ Modification rejected response to buffer.
487
+ """
488
+ self._buffers["modifications_rejected"].append(
489
+ (
490
+ self._run_id,
491
+ event.ts_event_ns,
492
+ event.ts_created_ns,
493
+ event.ts_broker_ns,
494
+ str(event.associated_order_id),
495
+ event.rejection_reason.name,
496
+ event.rejection_message,
497
+ )
498
+ )
499
+ if len(self._buffers["modifications_rejected"]) >= BATCH_SIZE:
500
+ self._flush_modifications_rejected()
501
+
502
+ def _buffer_fill(self, event: events.orders.FillEvent) -> None:
503
+ """
504
+ Buffer a fill event for batch insertion.
505
+
506
+ Parameters:
507
+ event:
508
+ Fill event to buffer.
509
+ """
510
+ self._buffers["fills"].append(
511
+ (
512
+ self._run_id,
513
+ event.ts_event_ns,
514
+ event.ts_created_ns,
515
+ event.ts_broker_ns,
516
+ str(event.associated_order_id),
517
+ event.broker_order_id,
518
+ event.symbol,
519
+ str(event.fill_id),
520
+ event.broker_fill_id,
521
+ event.side.name,
522
+ event.quantity_filled,
523
+ event.fill_price,
524
+ event.commission,
525
+ event.exchange,
526
+ )
527
+ )
528
+ if len(self._buffers["fills"]) >= BATCH_SIZE:
529
+ self._flush_fills()
530
+
531
+ def _buffer_expiration(self, event: events.orders.OrderExpired) -> None:
532
+ """
533
+ Buffer an order expiration event for batch insertion.
534
+
535
+ Parameters:
536
+ event:
537
+ Order expiration event to buffer.
538
+ """
539
+ self._buffers["expirations"].append(
540
+ (
541
+ self._run_id,
542
+ event.ts_event_ns,
543
+ event.ts_created_ns,
544
+ event.ts_broker_ns,
545
+ str(event.associated_order_id),
546
+ event.broker_order_id,
547
+ event.symbol,
548
+ )
549
+ )
550
+ if len(self._buffers["expirations"]) >= BATCH_SIZE:
551
+ self._flush_expirations()
552
+
553
+ def _flush_bars(self) -> None:
554
+ """
555
+ Insert buffered bar received records into the database.
556
+ """
557
+ if not self._buffers["bars"]:
558
+ return
559
+ self._conn.executemany(
560
+ """
561
+ INSERT INTO bars (run_id, ts_event_ns, ts_created_ns, symbol, bar_period, open, high, low, close, volume)
562
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
563
+ """,
564
+ self._buffers["bars"],
565
+ )
566
+ self._conn.commit()
567
+ self._buffers["bars"].clear()
568
+
569
+ def _flush_bars_processed(self) -> None:
570
+ """
571
+ Insert buffered bar processed records into the database.
572
+ """
573
+ if not self._buffers["bars_processed"]:
574
+ return
575
+ self._conn.executemany(
576
+ """
577
+ INSERT INTO bars_processed (run_id, ts_event_ns, ts_created_ns, symbol, bar_period, open, high, low, close, volume, indicators)
578
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
579
+ """,
580
+ self._buffers["bars_processed"],
581
+ )
582
+ self._conn.commit()
583
+ self._buffers["bars_processed"].clear()
584
+
585
+ def _flush_order_submissions(self) -> None:
586
+ """
587
+ Insert buffered order submission records into the database.
588
+ """
589
+ if not self._buffers["order_submissions"]:
590
+ return
591
+ self._conn.executemany(
592
+ """
593
+ INSERT INTO order_submissions (run_id, ts_event_ns, ts_created_ns, system_order_id, symbol, order_type, side, quantity, limit_price, stop_price, action, signal)
594
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
595
+ """,
596
+ self._buffers["order_submissions"],
597
+ )
598
+ self._conn.commit()
599
+ self._buffers["order_submissions"].clear()
600
+
601
+ def _flush_order_cancellations(self) -> None:
602
+ """
603
+ Insert buffered order cancellation records into the database.
604
+ """
605
+ if not self._buffers["order_cancellations"]:
606
+ return
607
+ self._conn.executemany(
608
+ """
609
+ INSERT INTO order_cancellations (run_id, ts_event_ns, ts_created_ns, system_order_id, symbol)
610
+ VALUES (?, ?, ?, ?, ?)
611
+ """,
612
+ self._buffers["order_cancellations"],
613
+ )
614
+ self._conn.commit()
615
+ self._buffers["order_cancellations"].clear()
616
+
617
+ def _flush_order_modifications(self) -> None:
618
+ """
619
+ Insert buffered order modification records into the database.
620
+ """
621
+ if not self._buffers["order_modifications"]:
622
+ return
623
+ self._conn.executemany(
624
+ """
625
+ INSERT INTO order_modifications (run_id, ts_event_ns, ts_created_ns, system_order_id, symbol, quantity, limit_price, stop_price)
626
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
627
+ """,
628
+ self._buffers["order_modifications"],
629
+ )
630
+ self._conn.commit()
631
+ self._buffers["order_modifications"].clear()
632
+
633
+ def _flush_orders_accepted(self) -> None:
634
+ """
635
+ Insert buffered order accepted records into the database.
636
+ """
637
+ if not self._buffers["orders_accepted"]:
638
+ return
639
+ self._conn.executemany(
640
+ """
641
+ INSERT INTO orders_accepted (run_id, ts_event_ns, ts_created_ns, ts_broker_ns, associated_order_id, broker_order_id)
642
+ VALUES (?, ?, ?, ?, ?, ?)
643
+ """,
644
+ self._buffers["orders_accepted"],
645
+ )
646
+ self._conn.commit()
647
+ self._buffers["orders_accepted"].clear()
648
+
649
+ def _flush_orders_rejected(self) -> None:
650
+ """
651
+ Insert buffered order rejected records into the database.
652
+ """
653
+ if not self._buffers["orders_rejected"]:
654
+ return
655
+ self._conn.executemany(
656
+ """
657
+ INSERT INTO orders_rejected (run_id, ts_event_ns, ts_created_ns, ts_broker_ns, associated_order_id, rejection_reason, rejection_message)
658
+ VALUES (?, ?, ?, ?, ?, ?, ?)
659
+ """,
660
+ self._buffers["orders_rejected"],
661
+ )
662
+ self._conn.commit()
663
+ self._buffers["orders_rejected"].clear()
664
+
665
+ def _flush_cancellations_accepted(self) -> None:
666
+ """
667
+ Insert buffered cancellation accepted records into the database.
668
+ """
669
+ if not self._buffers["cancellations_accepted"]:
670
+ return
671
+ self._conn.executemany(
672
+ """
673
+ INSERT INTO cancellations_accepted (run_id, ts_event_ns, ts_created_ns, ts_broker_ns, associated_order_id, broker_order_id)
674
+ VALUES (?, ?, ?, ?, ?, ?)
675
+ """,
676
+ self._buffers["cancellations_accepted"],
677
+ )
678
+ self._conn.commit()
679
+ self._buffers["cancellations_accepted"].clear()
680
+
681
+ def _flush_cancellations_rejected(self) -> None:
682
+ """
683
+ Insert buffered cancellation rejected records into the database.
684
+ """
685
+ if not self._buffers["cancellations_rejected"]:
686
+ return
687
+ self._conn.executemany(
688
+ """
689
+ INSERT INTO cancellations_rejected (run_id, ts_event_ns, ts_created_ns, ts_broker_ns, associated_order_id, rejection_reason, rejection_message)
690
+ VALUES (?, ?, ?, ?, ?, ?, ?)
691
+ """,
692
+ self._buffers["cancellations_rejected"],
693
+ )
694
+ self._conn.commit()
695
+ self._buffers["cancellations_rejected"].clear()
696
+
697
+ def _flush_modifications_accepted(self) -> None:
698
+ """
699
+ Insert buffered modification accepted records into the database.
700
+ """
701
+ if not self._buffers["modifications_accepted"]:
702
+ return
703
+ self._conn.executemany(
704
+ """
705
+ INSERT INTO modifications_accepted (run_id, ts_event_ns, ts_created_ns, ts_broker_ns, associated_order_id, broker_order_id)
706
+ VALUES (?, ?, ?, ?, ?, ?)
707
+ """,
708
+ self._buffers["modifications_accepted"],
709
+ )
710
+ self._conn.commit()
711
+ self._buffers["modifications_accepted"].clear()
712
+
713
+ def _flush_modifications_rejected(self) -> None:
714
+ """
715
+ Insert buffered modification rejected records into the database.
716
+ """
717
+ if not self._buffers["modifications_rejected"]:
718
+ return
719
+ self._conn.executemany(
720
+ """
721
+ INSERT INTO modifications_rejected (run_id, ts_event_ns, ts_created_ns, ts_broker_ns, associated_order_id, rejection_reason, rejection_message)
722
+ VALUES (?, ?, ?, ?, ?, ?, ?)
723
+ """,
724
+ self._buffers["modifications_rejected"],
725
+ )
726
+ self._conn.commit()
727
+ self._buffers["modifications_rejected"].clear()
728
+
729
+ def _flush_fills(self) -> None:
730
+ """
731
+ Insert buffered fill records into the database.
732
+ """
733
+ if not self._buffers["fills"]:
734
+ return
735
+ self._conn.executemany(
736
+ """
737
+ INSERT INTO fills (run_id, ts_event_ns, ts_created_ns, ts_broker_ns, associated_order_id, broker_order_id, symbol, fill_id, broker_fill_id, side, quantity_filled, fill_price, commission, exchange)
738
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
739
+ """,
740
+ self._buffers["fills"],
741
+ )
742
+ self._conn.commit()
743
+ self._buffers["fills"].clear()
744
+
745
+ def _flush_expirations(self) -> None:
746
+ """
747
+ Insert buffered expiration records into the database.
748
+ """
749
+ if not self._buffers["expirations"]:
750
+ return
751
+ self._conn.executemany(
752
+ """
753
+ INSERT INTO expirations (run_id, ts_event_ns, ts_created_ns, ts_broker_ns, associated_order_id, broker_order_id, symbol)
754
+ VALUES (?, ?, ?, ?, ?, ?, ?)
755
+ """,
756
+ self._buffers["expirations"],
757
+ )
758
+ self._conn.commit()
759
+ self._buffers["expirations"].clear()