fixtureqa 0.4.16__tar.gz → 0.4.17__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.16/fixtureqa.egg-info → fixtureqa-0.4.17}/PKG-INFO +1 -1
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/README.md +1 -1
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/perf_engine.py +8 -5
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/perf_models.py +3 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/perf_stats.py +34 -8
- fixtureqa-0.4.16/fixture/static/assets/index-BqX5ek3M.js → fixtureqa-0.4.17/fixture/static/assets/index-BM8-U5CW.js +23 -23
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/static/index.html +1 -1
- {fixtureqa-0.4.16 → fixtureqa-0.4.17/fixtureqa.egg-info}/PKG-INFO +1 -1
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixtureqa.egg-info/SOURCES.txt +1 -1
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/pyproject.toml +1 -1
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_perf_engine.py +18 -1
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/LICENSE +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/__init__.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/__main__.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/api/__init__.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/api/app.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/api/connection_manager.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/api/deps.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/api/routers/__init__.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/api/routers/admin.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/api/routers/auth.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/api/routers/branding.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/api/routers/custom_tags.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/api/routers/fix_spec.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/api/routers/messages.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/api/routers/perf.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/api/routers/scenarios.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/api/routers/sessions.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/api/routers/setup.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/api/routers/spec_overlay.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/api/routers/templates.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/api/routers/ws.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/api/schemas.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/config/__init__.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/__init__.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/atomic_io.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/auth.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/config_store.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/custom_tag_store.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/db_migrations.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/events.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/exec_csv_writer.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/fix_application.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/fix_builder.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/fix_parser.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/fix_spec_parser.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/fix_tags.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/fix_time.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/housekeeping.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/inbound.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/json_store.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/message_log.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/message_store.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/models.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/perf_payload.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/perf_store.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/perf_writer.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/scenario_runner.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/scenario_store.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/session.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/session_manager.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/spec_overlay_store.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/template_store.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/user_store.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/core/venue_responses.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/fix_specs/FIX42.xml +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/fix_specs/FIX44.xml +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/server.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/static/assets/ag-grid-_QKprVdm.js +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/static/assets/index-BwQf-cei.css +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/static/assets/index-CyNOPa0n.js +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/static/assets/react-vendor-2eF0YfZT.js +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/static/favicon.svg +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixture/ui/__init__.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixtureqa.egg-info/dependency_links.txt +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixtureqa.egg-info/entry_points.txt +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixtureqa.egg-info/requires.txt +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/fixtureqa.egg-info/top_level.txt +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/setup.cfg +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_atomic_io.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_auth.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_config_store.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_connection_manager.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_db_migrations.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_fix_builder.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_health.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_inbound.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_inbound_validation.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_message_store.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_perf_api.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_perf_models.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_perf_payload.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_perf_rehydrate.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_scenarios.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_session_lifecycle.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_session_manager_concurrency.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_sessions.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_templates.py +0 -0
- {fixtureqa-0.4.16 → fixtureqa-0.4.17}/tests/test_ws.py +0 -0
|
@@ -17,7 +17,7 @@ A Python FIX protocol testing tool. Supports FIX 4.2 and 4.4, dynamic session cr
|
|
|
17
17
|
- **Templates** — create/edit reusable message templates with FIX spec field definitions
|
|
18
18
|
- **Scenarios** — automated send/expect/delay test scripts with live pass/fail results
|
|
19
19
|
- **Venue simulation** — auto-ack and auto-fill responses for inbound NewOrderSingles
|
|
20
|
-
- **Performance testing** — rate-controlled order injection with latency correlation (p50/p95/p99), live dashboard, run history and CSV export; order/exec shapes templatable per counterparty
|
|
20
|
+
- **Performance testing** — rate-controlled order injection with latency correlation (p50/p95/p99), live dashboard, run history and CSV export; order/exec shapes templatable per counterparty; amend (35=G) and cancel (35=F) flavours with their own reply latencies (see [PERF.md](PERF.md))
|
|
21
21
|
- **Log analysis** — Overview stats, Throughput chart, Reject detail, Latency measurement (p50/p95/p99)
|
|
22
22
|
- **Export** — download session messages as CSV or raw FIX
|
|
23
23
|
- **Sequence numbers** — view and reset TX/RX seqnums per session
|
|
@@ -1014,23 +1014,26 @@ class PerfRun:
|
|
|
1014
1014
|
return (time.perf_counter_ns() - self._start_ns) / 1e9 if self._start_ns else 0.0
|
|
1015
1015
|
|
|
1016
1016
|
def _build_snapshot(self) -> None:
|
|
1017
|
-
|
|
1017
|
+
r = self.stats.rates()
|
|
1018
1018
|
client = None
|
|
1019
1019
|
if self._has_client:
|
|
1020
1020
|
client = ClientLeg(
|
|
1021
|
-
orders_sent=self.stats.orders_sent, ops_live=round(ops, 1),
|
|
1021
|
+
orders_sent=self.stats.orders_sent, ops_live=round(r.ops, 1),
|
|
1022
1022
|
pending=len(self._pending), dropped=self.stats.dropped,
|
|
1023
1023
|
amends_sent=self.stats.amends_sent,
|
|
1024
1024
|
replaces_received=self.stats.replaces_received,
|
|
1025
1025
|
amends_lost=self.stats.amends_lost,
|
|
1026
1026
|
amend_rejects=self.stats.amend_rejects,
|
|
1027
|
-
aps_live=round(aps, 1),
|
|
1027
|
+
aps_live=round(r.aps, 1),
|
|
1028
1028
|
cancels_sent=self.stats.cancels_sent,
|
|
1029
1029
|
cancel_rejects=self.stats.cancel_rejects,
|
|
1030
1030
|
cancels_canceled=self.stats.cancels_canceled,
|
|
1031
1031
|
cancels_lost=self.stats.cancels_lost,
|
|
1032
|
+
cps_live=round(r.cps, 1),
|
|
1033
|
+
crps_live=round(r.crps, 1),
|
|
1034
|
+
ccps_live=round(r.ccps, 1),
|
|
1032
1035
|
scenarios_dispatched=self.stats.scenarios_dispatched,
|
|
1033
|
-
scenarios_per_sec=round(sps, 1),
|
|
1036
|
+
scenarios_per_sec=round(r.sps, 1),
|
|
1034
1037
|
scenarios_completed=self.stats.scenarios_completed,
|
|
1035
1038
|
scenarios_lost=self.stats.scenarios_lost)
|
|
1036
1039
|
venue = None
|
|
@@ -1038,7 +1041,7 @@ class PerfRun:
|
|
|
1038
1041
|
venue = VenueLeg(
|
|
1039
1042
|
orders_received=self.stats.orders_received,
|
|
1040
1043
|
acks_sent=self.stats.acks_sent,
|
|
1041
|
-
fills_sent=self.stats.fills_sent, fps_live=round(fps, 1),
|
|
1044
|
+
fills_sent=self.stats.fills_sent, fps_live=round(r.fps, 1),
|
|
1042
1045
|
fill_ratio=round(self.stats.fill_ratio(), 3),
|
|
1043
1046
|
unfilled=max(0, self.stats.orders_sent - self.stats.completions),
|
|
1044
1047
|
amends_received=self.stats.amends_received,
|
|
@@ -245,6 +245,9 @@ class ClientLeg(BaseModel):
|
|
|
245
245
|
cancel_rejects: int = 0
|
|
246
246
|
cancels_canceled: int = 0
|
|
247
247
|
cancels_lost: int = 0
|
|
248
|
+
cps_live: float = 0.0
|
|
249
|
+
crps_live: float = 0.0
|
|
250
|
+
ccps_live: float = 0.0
|
|
248
251
|
scenarios_dispatched: int = 0
|
|
249
252
|
scenarios_per_sec: float = 0.0
|
|
250
253
|
scenarios_completed: int = 0
|
|
@@ -11,6 +11,20 @@ from __future__ import annotations
|
|
|
11
11
|
import asyncio
|
|
12
12
|
import random
|
|
13
13
|
import time
|
|
14
|
+
from typing import NamedTuple
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Rates(NamedTuple):
|
|
18
|
+
"""Per-second deltas since the previous snapshot (one entry per charted
|
|
19
|
+
stream). Extend here + in PerfStats.rates() when a new flavour adds a
|
|
20
|
+
charted message stream."""
|
|
21
|
+
ops: float # orders sent/s
|
|
22
|
+
fps: float # fills sent/s
|
|
23
|
+
sps: float # scenarios dispatched/s
|
|
24
|
+
aps: float # amends sent/s (35=G)
|
|
25
|
+
cps: float # cancels sent/s (35=F)
|
|
26
|
+
crps: float # cancel-rejects received/s (35=9)
|
|
27
|
+
ccps: float # canceled received/s (150=4)
|
|
14
28
|
|
|
15
29
|
|
|
16
30
|
class TokenBucket:
|
|
@@ -152,23 +166,35 @@ class PerfStats:
|
|
|
152
166
|
self._last_fills = 0
|
|
153
167
|
self._last_scen = 0
|
|
154
168
|
self._last_amends = 0
|
|
169
|
+
self._last_cancels = 0
|
|
170
|
+
self._last_cancel_rejects = 0
|
|
171
|
+
self._last_canceled = 0
|
|
155
172
|
|
|
156
|
-
def rates(self) ->
|
|
157
|
-
"""
|
|
173
|
+
def rates(self) -> Rates:
|
|
174
|
+
"""Per-second deltas since the last call. Stateful — call exactly once
|
|
175
|
+
per snapshot (it advances the delta baseline each time)."""
|
|
158
176
|
now = time.monotonic()
|
|
159
177
|
dt = now - self._last_t
|
|
160
178
|
if dt <= 0:
|
|
161
|
-
return 0.0, 0.0, 0.0, 0.0
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
179
|
+
return Rates(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
|
|
180
|
+
r = Rates(
|
|
181
|
+
ops=(self.orders_sent - self._last_orders) / dt,
|
|
182
|
+
fps=(self.fills_sent - self._last_fills) / dt,
|
|
183
|
+
sps=(self.scenarios_dispatched - self._last_scen) / dt,
|
|
184
|
+
aps=(self.amends_sent - self._last_amends) / dt,
|
|
185
|
+
cps=(self.cancels_sent - self._last_cancels) / dt,
|
|
186
|
+
crps=(self.cancel_rejects - self._last_cancel_rejects) / dt,
|
|
187
|
+
ccps=(self.cancels_canceled - self._last_canceled) / dt,
|
|
188
|
+
)
|
|
166
189
|
self._last_t = now
|
|
167
190
|
self._last_orders = self.orders_sent
|
|
168
191
|
self._last_fills = self.fills_sent
|
|
169
192
|
self._last_scen = self.scenarios_dispatched
|
|
170
193
|
self._last_amends = self.amends_sent
|
|
171
|
-
|
|
194
|
+
self._last_cancels = self.cancels_sent
|
|
195
|
+
self._last_cancel_rejects = self.cancel_rejects
|
|
196
|
+
self._last_canceled = self.cancels_canceled
|
|
197
|
+
return r
|
|
172
198
|
|
|
173
199
|
def fill_ratio(self) -> float:
|
|
174
200
|
# Fraction of orders fully filled (cumQty >= orderQty). Uses completions,
|