fixtureqa 0.4.21__tar.gz → 0.4.22__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.
Files changed (100) hide show
  1. {fixtureqa-0.4.21/fixtureqa.egg-info → fixtureqa-0.4.22}/PKG-INFO +1 -1
  2. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/perf_engine.py +29 -10
  3. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/perf_models.py +4 -0
  4. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/perf_stats.py +24 -1
  5. fixtureqa-0.4.21/fixture/static/assets/index-BzzfsRz2.js → fixtureqa-0.4.22/fixture/static/assets/index-BQKTSxpO.js +23 -23
  6. fixtureqa-0.4.21/fixture/static/assets/index-o_l1KBYd.css → fixtureqa-0.4.22/fixture/static/assets/index-V2H2CzlQ.css +1 -1
  7. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/static/index.html +2 -2
  8. {fixtureqa-0.4.21 → fixtureqa-0.4.22/fixtureqa.egg-info}/PKG-INFO +1 -1
  9. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixtureqa.egg-info/SOURCES.txt +2 -2
  10. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/pyproject.toml +1 -1
  11. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_perf_engine.py +17 -0
  12. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/LICENSE +0 -0
  13. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/README.md +0 -0
  14. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/__init__.py +0 -0
  15. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/__main__.py +0 -0
  16. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/api/__init__.py +0 -0
  17. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/api/app.py +0 -0
  18. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/api/connection_manager.py +0 -0
  19. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/api/deps.py +0 -0
  20. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/api/routers/__init__.py +0 -0
  21. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/api/routers/admin.py +0 -0
  22. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/api/routers/auth.py +0 -0
  23. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/api/routers/branding.py +0 -0
  24. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/api/routers/custom_tags.py +0 -0
  25. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/api/routers/fix_spec.py +0 -0
  26. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/api/routers/messages.py +0 -0
  27. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/api/routers/perf.py +0 -0
  28. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/api/routers/scenarios.py +0 -0
  29. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/api/routers/sessions.py +0 -0
  30. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/api/routers/setup.py +0 -0
  31. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/api/routers/spec_overlay.py +0 -0
  32. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/api/routers/templates.py +0 -0
  33. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/api/routers/ws.py +0 -0
  34. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/api/schemas.py +0 -0
  35. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/config/__init__.py +0 -0
  36. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/__init__.py +0 -0
  37. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/atomic_io.py +0 -0
  38. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/auth.py +0 -0
  39. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/config_store.py +0 -0
  40. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/custom_tag_store.py +0 -0
  41. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/db_migrations.py +0 -0
  42. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/events.py +0 -0
  43. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/fix_application.py +0 -0
  44. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/fix_builder.py +0 -0
  45. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/fix_parser.py +0 -0
  46. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/fix_spec_parser.py +0 -0
  47. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/fix_tags.py +0 -0
  48. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/fix_time.py +0 -0
  49. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/housekeeping.py +0 -0
  50. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/inbound.py +0 -0
  51. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/json_store.py +0 -0
  52. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/latency_csv_writer.py +0 -0
  53. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/message_log.py +0 -0
  54. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/message_store.py +0 -0
  55. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/models.py +0 -0
  56. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/perf_charts.py +0 -0
  57. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/perf_payload.py +0 -0
  58. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/perf_store.py +0 -0
  59. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/perf_writer.py +0 -0
  60. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/scenario_runner.py +0 -0
  61. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/scenario_store.py +0 -0
  62. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/session.py +0 -0
  63. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/session_manager.py +0 -0
  64. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/spec_overlay_store.py +0 -0
  65. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/template_store.py +0 -0
  66. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/user_store.py +0 -0
  67. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/core/venue_responses.py +0 -0
  68. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/fix_specs/FIX42.xml +0 -0
  69. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/fix_specs/FIX44.xml +0 -0
  70. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/server.py +0 -0
  71. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/static/assets/ag-grid-_QKprVdm.js +0 -0
  72. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/static/assets/index-CyNOPa0n.js +0 -0
  73. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/static/assets/react-vendor-2eF0YfZT.js +0 -0
  74. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/static/favicon.svg +0 -0
  75. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixture/ui/__init__.py +0 -0
  76. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixtureqa.egg-info/dependency_links.txt +0 -0
  77. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixtureqa.egg-info/entry_points.txt +0 -0
  78. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixtureqa.egg-info/requires.txt +0 -0
  79. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/fixtureqa.egg-info/top_level.txt +0 -0
  80. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/setup.cfg +0 -0
  81. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_atomic_io.py +0 -0
  82. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_auth.py +0 -0
  83. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_config_store.py +0 -0
  84. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_connection_manager.py +0 -0
  85. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_db_migrations.py +0 -0
  86. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_fix_builder.py +0 -0
  87. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_health.py +0 -0
  88. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_inbound.py +0 -0
  89. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_inbound_validation.py +0 -0
  90. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_message_store.py +0 -0
  91. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_perf_api.py +0 -0
  92. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_perf_models.py +0 -0
  93. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_perf_payload.py +0 -0
  94. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_perf_rehydrate.py +0 -0
  95. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_scenarios.py +0 -0
  96. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_session_lifecycle.py +0 -0
  97. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_session_manager_concurrency.py +0 -0
  98. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_sessions.py +0 -0
  99. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_templates.py +0 -0
  100. {fixtureqa-0.4.21 → fixtureqa-0.4.22}/tests/test_ws.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixtureqa
3
- Version: 0.4.21
3
+ Version: 0.4.22
4
4
  Summary: FIXture — FIX Protocol Testing Tool
5
5
  Requires-Python: >=3.10
6
6
  License-File: LICENSE
@@ -397,8 +397,19 @@ class PerfRun:
397
397
  self._finalize(entry, "dropped")
398
398
  return True
399
399
 
400
+ async def _send(self, session_id: str, msg: Message) -> bool:
401
+ """Single send path — tallies every outbound app message per leg
402
+ (type-agnostic) for the messages-per-second chart, then delegates."""
403
+ ok = await self._sm.send_message(session_id, msg)
404
+ if ok:
405
+ if session_id == self.config.client_session_id:
406
+ self.stats.msgs_sent_client += 1
407
+ else:
408
+ self.stats.msgs_sent_venue += 1
409
+ return ok
410
+
400
411
  async def _send_order(self, corr: str, msg: Message) -> bool:
401
- ok = await self._sm.send_message(self.config.client_session_id, msg)
412
+ ok = await self._send(self.config.client_session_id, msg)
402
413
  if ok:
403
414
  self.stats.orders_sent += 1
404
415
  else:
@@ -493,6 +504,8 @@ class PerfRun:
493
504
  sub = self._venue_sub
494
505
  while True:
495
506
  inb = await sub.get()
507
+ if not inb.admin:
508
+ self.stats.msgs_recv_venue += 1
496
509
  recv_ns = inb.recv_perf_ns or time.perf_counter_ns()
497
510
  if inb.msg_type == "G":
498
511
  await self._handle_amend(inb.fields, recv_ns)
@@ -517,7 +530,7 @@ class PerfRun:
517
530
  last_qty=0, cum_qty=0, leaves_qty=order_qty, px="0")
518
531
  if ack is not None:
519
532
  send_ns = time.perf_counter_ns()
520
- if await self._sm.send_message(cfg.venue_session_id, ack):
533
+ if await self._send(cfg.venue_session_id, ack):
521
534
  self.stats.acks_sent += 1
522
535
  self._register_venue_send(ack, "ack", corr, of.get(T_CLORDID), send_ns)
523
536
 
@@ -573,7 +586,7 @@ class PerfRun:
573
586
  cum_qty=cum_qty, leaves_qty=leaves_qty, px=entry["px"])
574
587
  if er is not None:
575
588
  send_ns = time.perf_counter_ns()
576
- if await self._sm.send_message(cfg.venue_session_id, er):
589
+ if await self._send(cfg.venue_session_id, er):
577
590
  self.stats.fills_sent += 1
578
591
  self._register_venue_send(er, "fill", corr,
579
592
  entry["of"].get(T_CLORDID), send_ns)
@@ -647,14 +660,14 @@ class PerfRun:
647
660
  orig_clordid=orig)
648
661
  if er is not None:
649
662
  send_ns = time.perf_counter_ns()
650
- if await self._sm.send_message(cfg.venue_session_id, er):
663
+ if await self._send(cfg.venue_session_id, er):
651
664
  self._register_venue_send(er, "pending-replace", corr, clordid, send_ns)
652
665
  er = self._build_exec(of, exec_type="5", ord_status="5", last_qty=0,
653
666
  cum_qty=cum, leaves_qty=leaves, px=px,
654
667
  orig_clordid=orig)
655
668
  if er is not None:
656
669
  send_ns = time.perf_counter_ns()
657
- if await self._sm.send_message(cfg.venue_session_id, er):
670
+ if await self._send(cfg.venue_session_id, er):
658
671
  self._register_venue_send(er, "replace", corr, clordid, send_ns)
659
672
 
660
673
  async def _handle_cancel(self, of: dict, recv_ns: int = 0) -> None:
@@ -691,12 +704,12 @@ class PerfRun:
691
704
  orig_clordid=of.get(T_ORIGCLORDID))
692
705
  if er is not None:
693
706
  send_ns = time.perf_counter_ns()
694
- if await self._sm.send_message(cfg.venue_session_id, er):
707
+ if await self._send(cfg.venue_session_id, er):
695
708
  self._register_venue_send(er, "canceled", corr, clordid, send_ns)
696
709
  return
697
710
  msg = self._build_cancel_reject(of, entry)
698
711
  send_ns = time.perf_counter_ns()
699
- if await self._sm.send_message(cfg.venue_session_id, msg):
712
+ if await self._send(cfg.venue_session_id, msg):
700
713
  self._register_venue_send(msg, "cancel-reject", corr, clordid, send_ns)
701
714
 
702
715
  def _build_cancel_reject(self, of: dict, entry: Optional[dict]) -> Message:
@@ -811,6 +824,8 @@ class PerfRun:
811
824
  sub = self._client_sub
812
825
  while True:
813
826
  inb = await sub.get()
827
+ if not inb.admin:
828
+ self.stats.msgs_recv_client += 1
814
829
  if inb.msg_type == "9":
815
830
  self._on_cancel_reject(inb.fields, inb.recv_perf_ns or time.perf_counter_ns())
816
831
  continue
@@ -998,7 +1013,7 @@ class PerfRun:
998
1013
  entry["cancel_clordid"] = new_clordid
999
1014
  entry["cancel_sent_ns"] = time.perf_counter_ns()
1000
1015
  entry["cancel_sent_at"] = time.time()
1001
- if await self._sm.send_message(self.config.client_session_id, msg):
1016
+ if await self._send(self.config.client_session_id, msg):
1002
1017
  self.stats.cancels_sent += 1
1003
1018
  else:
1004
1019
  entry["cancel_inflight"] = False
@@ -1129,7 +1144,7 @@ class PerfRun:
1129
1144
  entry["amends_sent"] = n + 1
1130
1145
  entry["amend_sent_ns"] = time.perf_counter_ns()
1131
1146
  entry["amend_sent_at"] = time.time()
1132
- if await self._sm.send_message(self.config.client_session_id, msg):
1147
+ if await self._send(self.config.client_session_id, msg):
1133
1148
  self.stats.amends_sent += 1
1134
1149
  else:
1135
1150
  entry["amend_inflight"] = False
@@ -1219,6 +1234,8 @@ class PerfRun:
1219
1234
  cps_live=round(r.cps, 1),
1220
1235
  crps_live=round(r.crps, 1),
1221
1236
  ccps_live=round(r.ccps, 1),
1237
+ msgs_out_live=round(r.mco, 1),
1238
+ msgs_in_live=round(r.mci, 1),
1222
1239
  scenarios_dispatched=self.stats.scenarios_dispatched,
1223
1240
  scenarios_per_sec=round(r.sps, 1),
1224
1241
  scenarios_completed=self.stats.scenarios_completed,
@@ -1235,7 +1252,9 @@ class PerfRun:
1235
1252
  amends_uncorrelated=self.stats.amends_uncorrelated,
1236
1253
  amend_waits_expired=self.stats.amend_waits_expired,
1237
1254
  cancels_received=self.stats.cancels_received,
1238
- cancels_uncorrelated=self.stats.cancels_uncorrelated)
1255
+ cancels_uncorrelated=self.stats.cancels_uncorrelated,
1256
+ msgs_out_live=round(r.mvo, 1),
1257
+ msgs_in_live=round(r.mvi, 1))
1239
1258
  snap = LiveSnapshot(
1240
1259
  run_id=self.run_id, status=self.status, mode=self.config.mode,
1241
1260
  elapsed_s=round(self._elapsed_s(), 1), duration_s=self.config.test.duration,
@@ -252,6 +252,8 @@ class ClientLeg(BaseModel):
252
252
  cps_live: float = 0.0
253
253
  crps_live: float = 0.0
254
254
  ccps_live: float = 0.0
255
+ msgs_out_live: float = 0.0 # all app messages this leg sent/s (MPS chart)
256
+ msgs_in_live: float = 0.0 # all app messages this leg received/s
255
257
  scenarios_dispatched: int = 0
256
258
  scenarios_per_sec: float = 0.0
257
259
  scenarios_completed: int = 0
@@ -271,6 +273,8 @@ class VenueLeg(BaseModel):
271
273
  amend_waits_expired: int = 0
272
274
  cancels_received: int = 0
273
275
  cancels_uncorrelated: int = 0
276
+ msgs_out_live: float = 0.0 # all app messages this leg sent/s (MPS chart)
277
+ msgs_in_live: float = 0.0 # all app messages this leg received/s
274
278
 
275
279
 
276
280
  class SnapshotErrors(BaseModel):
@@ -25,6 +25,12 @@ class Rates(NamedTuple):
25
25
  cps: float # cancels sent/s (35=F)
26
26
  crps: float # cancel-rejects received/s (35=9)
27
27
  ccps: float # canceled received/s (150=4)
28
+ # Type-agnostic message rates (every app message sent/received per leg) for
29
+ # the messages-per-second chart — counts all kinds, so it can't drift.
30
+ mco: float # client leg messages sent/s (orders + amends + cancels …)
31
+ mci: float # client leg messages received/s (acks + fills + replies …)
32
+ mvo: float # venue leg messages sent/s (acks + fills + replies …)
33
+ mvi: float # venue leg messages received/s (orders + amends + cancels …)
28
34
 
29
35
 
30
36
  class TokenBucket:
@@ -147,6 +153,11 @@ class PerfStats:
147
153
  self.cancels_received = 0 # venue saw an inbound 35=F
148
154
  self.cancels_uncorrelated = 0 # inbound 35=F lacked the correlation tag (no reply)
149
155
  self.venue_sends_unmatched = 0 # one-way send records evicted w/o a matching recv
156
+ # Type-agnostic per-leg message tallies (every app message), for the MPS chart.
157
+ self.msgs_sent_client = 0
158
+ self.msgs_recv_client = 0
159
+ self.msgs_sent_venue = 0
160
+ self.msgs_recv_venue = 0
150
161
  self.lost_timeout = 0
151
162
  self.dropped = 0
152
163
  self.rejected = 0
@@ -170,6 +181,10 @@ class PerfStats:
170
181
  self._last_cancels = 0
171
182
  self._last_cancel_rejects = 0
172
183
  self._last_canceled = 0
184
+ self._last_msc = 0
185
+ self._last_mrc = 0
186
+ self._last_msv = 0
187
+ self._last_mrv = 0
173
188
 
174
189
  def rates(self) -> Rates:
175
190
  """Per-second deltas since the last call. Stateful — call exactly once
@@ -177,7 +192,7 @@ class PerfStats:
177
192
  now = time.monotonic()
178
193
  dt = now - self._last_t
179
194
  if dt <= 0:
180
- return Rates(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
195
+ return Rates(*([0.0] * 11))
181
196
  r = Rates(
182
197
  ops=(self.orders_sent - self._last_orders) / dt,
183
198
  fps=(self.fills_sent - self._last_fills) / dt,
@@ -186,6 +201,10 @@ class PerfStats:
186
201
  cps=(self.cancels_sent - self._last_cancels) / dt,
187
202
  crps=(self.cancel_rejects - self._last_cancel_rejects) / dt,
188
203
  ccps=(self.cancels_canceled - self._last_canceled) / dt,
204
+ mco=(self.msgs_sent_client - self._last_msc) / dt,
205
+ mci=(self.msgs_recv_client - self._last_mrc) / dt,
206
+ mvo=(self.msgs_sent_venue - self._last_msv) / dt,
207
+ mvi=(self.msgs_recv_venue - self._last_mrv) / dt,
189
208
  )
190
209
  self._last_t = now
191
210
  self._last_orders = self.orders_sent
@@ -195,6 +214,10 @@ class PerfStats:
195
214
  self._last_cancels = self.cancels_sent
196
215
  self._last_cancel_rejects = self.cancel_rejects
197
216
  self._last_canceled = self.cancels_canceled
217
+ self._last_msc = self.msgs_sent_client
218
+ self._last_mrc = self.msgs_recv_client
219
+ self._last_msv = self.msgs_sent_venue
220
+ self._last_mrv = self.msgs_recv_venue
198
221
  return r
199
222
 
200
223
  def fill_ratio(self) -> float: