fixtureqa 0.4.31__tar.gz → 0.4.33__tar.gz
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.
- {fixtureqa-0.4.31/fixtureqa.egg-info → fixtureqa-0.4.33}/PKG-INFO +1 -1
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/perf_engine.py +46 -23
- fixtureqa-0.4.31/fixture/static/assets/index-BljNXGeO.js → fixtureqa-0.4.33/fixture/static/assets/index-C7XDCotT.js +9 -9
- fixtureqa-0.4.31/fixture/static/assets/index-BVMltRqp.css → fixtureqa-0.4.33/fixture/static/assets/index-Ycej0J6b.css +1 -1
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/static/index.html +2 -2
- {fixtureqa-0.4.31 → fixtureqa-0.4.33/fixtureqa.egg-info}/PKG-INFO +1 -1
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixtureqa.egg-info/SOURCES.txt +2 -2
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/pyproject.toml +1 -1
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_perf_engine.py +47 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/LICENSE +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/README.md +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/__init__.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/__main__.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/api/__init__.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/api/app.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/api/connection_manager.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/api/deps.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/api/routers/__init__.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/api/routers/admin.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/api/routers/auth.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/api/routers/branding.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/api/routers/custom_tags.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/api/routers/fix_spec.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/api/routers/messages.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/api/routers/perf.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/api/routers/scenarios.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/api/routers/sessions.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/api/routers/setup.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/api/routers/spec_overlay.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/api/routers/templates.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/api/routers/ws.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/api/schemas.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/config/__init__.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/__init__.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/atomic_io.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/auth.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/config_store.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/custom_tag_store.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/db_migrations.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/events.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/fix_application.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/fix_builder.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/fix_parser.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/fix_spec_parser.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/fix_tags.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/fix_time.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/housekeeping.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/inbound.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/json_store.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/latency_csv_writer.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/message_log.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/message_store.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/models.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/perf_charts.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/perf_models.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/perf_payload.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/perf_stats.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/perf_store.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/perf_writer.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/scenario_runner.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/scenario_store.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/session.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/session_manager.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/spec_overlay_store.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/template_store.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/user_store.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/core/venue_responses.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/fix_specs/FIX42.xml +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/fix_specs/FIX44.xml +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/server.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/static/assets/ag-grid-_QKprVdm.js +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/static/assets/index-CyNOPa0n.js +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/static/assets/react-vendor-2eF0YfZT.js +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/static/favicon.svg +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixture/ui/__init__.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixtureqa.egg-info/dependency_links.txt +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixtureqa.egg-info/entry_points.txt +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixtureqa.egg-info/requires.txt +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/fixtureqa.egg-info/top_level.txt +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/setup.cfg +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_atomic_io.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_auth.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_config_store.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_connection_manager.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_db_migrations.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_fix_builder.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_health.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_inbound.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_inbound_validation.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_message_store.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_perf_api.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_perf_models.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_perf_payload.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_perf_rehydrate.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_scenarios.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_session_auto_response.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_session_lifecycle.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_session_manager_concurrency.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_sessions.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_templates.py +0 -0
- {fixtureqa-0.4.31 → fixtureqa-0.4.33}/tests/test_ws.py +0 -0
|
@@ -311,10 +311,11 @@ class PerfRun:
|
|
|
311
311
|
# User pressed Stop — wind down promptly; don't wait on the backlog.
|
|
312
312
|
await asyncio.sleep(_GRACE_DRAIN_S)
|
|
313
313
|
elif self._has_client and self._has_venue:
|
|
314
|
-
# loopback/manager natural end (count OR duration): the
|
|
315
|
-
# still owes fills
|
|
316
|
-
# rather than cutting off and marking in-flight
|
|
317
|
-
|
|
314
|
+
# loopback/manager/middle_manager natural end (count OR duration): the
|
|
315
|
+
# in-process venue still owes fills AND cancel/amend replies — wait until
|
|
316
|
+
# every chain is terminal, rather than cutting off and marking in-flight
|
|
317
|
+
# orders/cancels/amends lost.
|
|
318
|
+
await self._drain_until_quiescent()
|
|
318
319
|
elif self._reached_max_orders:
|
|
319
320
|
# client mode, count-bounded: external venue may never fill — bounded settle.
|
|
320
321
|
deadline = time.perf_counter() + max(_GRACE_DRAIN_S,
|
|
@@ -363,16 +364,40 @@ class PerfRun:
|
|
|
363
364
|
self._publish()
|
|
364
365
|
self._writer.write_run(self._run_record(final=True))
|
|
365
366
|
|
|
366
|
-
|
|
367
|
-
"""
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
367
|
+
def _activity_count(self) -> int:
|
|
368
|
+
"""Monotonic tally of every terminal-chain event — fills/acks AND cancel/
|
|
369
|
+
amend replies — so the drain's quiet window measures from the last reply,
|
|
370
|
+
not just the last fill."""
|
|
371
|
+
s = self.stats
|
|
372
|
+
return (s.fills_sent + s.acks_sent + s.cancel_rejects + s.cancels_canceled
|
|
373
|
+
+ s.replaces_received + s.amend_rejects)
|
|
374
|
+
|
|
375
|
+
def _drain_quiescent(self, now: float, last_activity: float, quiet: float) -> bool:
|
|
376
|
+
"""True when the in-process venue has nothing left in flight: inbound queue
|
|
377
|
+
and order book empty, the client's queue drained, NO cancel/amend reply still
|
|
378
|
+
outstanding on any pending order, and no chain event for a quiet window. The
|
|
379
|
+
in-flight-reply check is what makes a cancel/amend run reconcile to 100% —
|
|
380
|
+
without it the run would end on fill-quiescence while a 35=9/150=4/replace
|
|
381
|
+
ack was still on the wire (counted lost)."""
|
|
382
|
+
replies_inflight = any(e.get("cancel_inflight") or e.get("amend_inflight")
|
|
383
|
+
for e in self._pending.values())
|
|
384
|
+
return (self._venue_sub.qsize() == 0 and self._client_sub.qsize() == 0
|
|
385
|
+
and not self._venue_orders and not replies_inflight
|
|
386
|
+
and (now - last_activity) >= quiet)
|
|
387
|
+
|
|
388
|
+
async def _drain_until_quiescent(self) -> None:
|
|
389
|
+
"""loopback/manager/middle_manager drain (count- OR duration-bounded): the
|
|
390
|
+
in-process venue still owes fills for the backlog AND cancel/amend replies for
|
|
391
|
+
any in-flight 35=F/35=G, so wait until every chain is terminal — rather than
|
|
392
|
+
cutting off at the stop condition and marking in-flight orders/cancels/amends
|
|
393
|
+
lost. (Cancel-on-ack cancels fired for orders acked during the drain are
|
|
394
|
+
awaited too — they land in _pending with cancel_inflight.)
|
|
371
395
|
|
|
372
396
|
The timeout sweeper is paused (``_draining_full``) so backlogged orders
|
|
373
397
|
aren't expired mid-drain; orders the venue won't fill (fill_ratio<1 / partial)
|
|
374
398
|
settle out via quiescence and are finalized after. Bounded by a generous cap
|
|
375
|
-
derived from the backlog
|
|
399
|
+
derived from the backlog, fill rate, amend grace and reply round-trip as a
|
|
400
|
+
safety net against a stuck run (e.g. a reply that never comes)."""
|
|
376
401
|
self._draining_full = True
|
|
377
402
|
r = self.config.rate
|
|
378
403
|
fill_rate = r.fills_per_window / (r.fill_window_ms / 1000.0) # fills/sec
|
|
@@ -382,10 +407,15 @@ class PerfRun:
|
|
|
382
407
|
# latest (no new holds once injection stops), so add one grace.
|
|
383
408
|
backlog = (self._venue_sub.qsize() * max(1, self.config.payload.fill.fills_per_order)
|
|
384
409
|
+ sum(len(e["schedule"]) - e["idx"] for e in self._venue_orders.values()))
|
|
410
|
+
# Reply budget: cancel/amend replies are immediate but still round-trip — give
|
|
411
|
+
# the cap headroom (observed response p99, capped) so it can't fire before the
|
|
412
|
+
# last in-flight reply is due back.
|
|
413
|
+
reply_budget = min(30.0, self.stats.response_latency.stats()["p99_us"] / 1e6 * 2.0)
|
|
385
414
|
cap = time.perf_counter() + max(30.0, (backlog / max(1.0, fill_rate)) * 2.0
|
|
386
|
-
+ self.config.amend.amend_wait_ms / 1000.0
|
|
415
|
+
+ self.config.amend.amend_wait_ms / 1000.0
|
|
416
|
+
+ reply_budget + 10.0)
|
|
387
417
|
|
|
388
|
-
|
|
418
|
+
last_count = self._activity_count()
|
|
389
419
|
last_activity = time.perf_counter()
|
|
390
420
|
while True:
|
|
391
421
|
now = time.perf_counter()
|
|
@@ -393,18 +423,11 @@ class PerfRun:
|
|
|
393
423
|
# finalized as lost instead of ground through at the fill rate.
|
|
394
424
|
if self._stopped_by_user or not self._pending or now >= cap:
|
|
395
425
|
break
|
|
396
|
-
|
|
397
|
-
if
|
|
398
|
-
|
|
426
|
+
cur = self._activity_count()
|
|
427
|
+
if cur != last_count:
|
|
428
|
+
last_count = cur
|
|
399
429
|
last_activity = now
|
|
400
|
-
|
|
401
|
-
# order book), the client has received the fills, and no new
|
|
402
|
-
# fill/ack has gone out for a quiet window (covers the
|
|
403
|
-
# rate-limited last order still being filled).
|
|
404
|
-
quiet_ok = (self._venue_sub.qsize() == 0 and self._client_sub.qsize() == 0
|
|
405
|
-
and not self._venue_orders
|
|
406
|
-
and (now - last_activity) >= quiet)
|
|
407
|
-
if quiet_ok:
|
|
430
|
+
if self._drain_quiescent(now, last_activity, quiet):
|
|
408
431
|
break
|
|
409
432
|
await asyncio.sleep(0.02)
|
|
410
433
|
|