fixcore-engine 0.1.2__tar.gz → 0.1.3__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 (54) hide show
  1. {fixcore_engine-0.1.2/fixcore_engine.egg-info → fixcore_engine-0.1.3}/PKG-INFO +1 -1
  2. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/gui_ui/app.js +113 -0
  3. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/gui_ui/index.html +11 -1
  4. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/gui_ui/style.css +74 -0
  5. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3/fixcore_engine.egg-info}/PKG-INFO +1 -1
  6. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/pyproject.toml +1 -1
  7. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/LICENSE +0 -0
  8. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/README.md +0 -0
  9. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/__init__.py +0 -0
  10. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/application.py +0 -0
  11. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/gui.py +0 -0
  12. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/gui_ui/fixcore_logo.svg +0 -0
  13. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/log/__init__.py +0 -0
  14. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/log/base.py +0 -0
  15. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/log/factory.py +0 -0
  16. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/log/file_log.py +0 -0
  17. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/log/screen.py +0 -0
  18. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/message/__init__.py +0 -0
  19. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/message/cracker.py +0 -0
  20. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/message/data_dictionary.py +0 -0
  21. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/message/exceptions.py +0 -0
  22. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/message/field.py +0 -0
  23. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/message/message.py +0 -0
  24. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/session/__init__.py +0 -0
  25. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/session/session.py +0 -0
  26. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/session/session_id.py +0 -0
  27. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/session/session_settings.py +0 -0
  28. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/session/state.py +0 -0
  29. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/store/__init__.py +0 -0
  30. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/store/base.py +0 -0
  31. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/store/factory.py +0 -0
  32. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/store/file_store.py +0 -0
  33. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/store/memory.py +0 -0
  34. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/transport/__init__.py +0 -0
  35. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/transport/acceptor.py +0 -0
  36. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/transport/framer.py +0 -0
  37. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore/transport/initiator.py +0 -0
  38. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore_engine.egg-info/SOURCES.txt +0 -0
  39. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore_engine.egg-info/dependency_links.txt +0 -0
  40. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore_engine.egg-info/entry_points.txt +0 -0
  41. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore_engine.egg-info/requires.txt +0 -0
  42. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/fixcore_engine.egg-info/top_level.txt +0 -0
  43. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/setup.cfg +0 -0
  44. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/tests/test_cracker.py +0 -0
  45. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/tests/test_data_dictionary.py +0 -0
  46. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/tests/test_file_log.py +0 -0
  47. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/tests/test_file_store.py +0 -0
  48. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/tests/test_framer.py +0 -0
  49. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/tests/test_integration.py +0 -0
  50. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/tests/test_message.py +0 -0
  51. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/tests/test_session.py +0 -0
  52. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/tests/test_session_id.py +0 -0
  53. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/tests/test_store.py +0 -0
  54. {fixcore_engine-0.1.2 → fixcore_engine-0.1.3}/tests/test_transport.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixcore-engine
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Pure Python FIX protocol engine
5
5
  Author-email: Aidan Chisholm <aidan.chisholm@gmail.com>
6
6
  License: MIT License
@@ -84,6 +84,14 @@ document.addEventListener('DOMContentLoaded', () => {
84
84
  updateClordidPreview();
85
85
  });
86
86
  $id('inp-clordid-prefix').addEventListener('input', updateClordidPreview);
87
+
88
+ $id('btn-close-detail').addEventListener('click', closeMsgDetail);
89
+ $id('log-entries').addEventListener('click', e => {
90
+ const row = e.target.closest('.log-entry');
91
+ if (!row || !row._logEntry) return;
92
+ if (row === selectedLogRow) { closeMsgDetail(); return; }
93
+ showMsgDetail(row, row._logEntry);
94
+ });
87
95
  });
88
96
 
89
97
  // ── Polling (seq nums + state) ────────────────────────────────────────────
@@ -550,6 +558,96 @@ $id('paste-fix-input').addEventListener('keydown', e => {
550
558
  }
551
559
  });
552
560
 
561
+ // ── FIX tag reference ─────────────────────────────────────────────────────
562
+ const FIX_TAG_NAMES = {
563
+ 1:'Account', 6:'AvgPx', 7:'BeginSeqNo', 8:'BeginString', 9:'BodyLength',
564
+ 10:'CheckSum', 11:'ClOrdID', 14:'CumQty', 15:'Currency', 16:'EndSeqNo',
565
+ 31:'LastPx', 32:'LastQty',
566
+ 17:'ExecID', 18:'ExecInst', 20:'ExecTransType', 21:'HandlInst',
567
+ 34:'MsgSeqNum', 35:'MsgType', 36:'NewSeqNo', 37:'OrderID', 38:'OrderQty',
568
+ 39:'OrdStatus', 40:'OrdType', 41:'OrigClOrdID', 43:'PossDupFlag',
569
+ 44:'Price', 45:'RefSeqNum', 49:'SenderCompID', 50:'SenderSubID',
570
+ 52:'SendingTime', 54:'Side', 55:'Symbol', 56:'TargetCompID',
571
+ 57:'TargetSubID', 58:'Text', 60:'TransactTime', 97:'PossResend',
572
+ 98:'EncryptMethod', 108:'HeartBtInt', 110:'MinQty', 111:'MaxFloor',
573
+ 112:'TestReqID', 114:'LocateReqd', 122:'OrigSendingTime', 123:'GapFillFlag',
574
+ 126:'ExpireTime', 131:'QuoteReqID', 140:'PrevClosePx', 146:'NoRelatedSym',
575
+ 150:'ExecType', 151:'LeavesQty', 167:'SecurityType', 200:'MaturityMonthYear',
576
+ 202:'StrikePrice', 207:'SecurityExchange', 262:'MDReqID',
577
+ 263:'SubscriptionRequestType', 264:'MarketDepth', 267:'NoMDEntryTypes',
578
+ 268:'NoMDEntries', 269:'MDEntryType', 270:'MDEntryPx', 271:'MDEntrySize',
579
+ };
580
+
581
+ const FIX_ENUM_VALUES = {
582
+ 35: {'0':'Heartbeat','1':'TestRequest','2':'ResendRequest','3':'Reject',
583
+ '4':'SequenceReset','5':'Logout','8':'ExecutionReport',
584
+ '9':'OrderCancelReject','A':'Logon','D':'NewOrderSingle',
585
+ 'F':'OrderCancelRequest','G':'OrderCancelReplaceRequest',
586
+ 'H':'OrderStatusRequest','V':'MarketDataRequest',
587
+ 'W':'MarketDataSnapshot','X':'MarketDataIncremental','Y':'MarketDataRequestReject'},
588
+ 39: {'0':'New','1':'PartiallyFilled','2':'Filled','3':'DoneForDay',
589
+ '4':'Canceled','5':'Replaced','6':'PendingCancel','7':'Stopped',
590
+ '8':'Rejected','9':'Suspended','A':'PendingNew','C':'Expired'},
591
+ 40: {'1':'Market','2':'Limit','3':'Stop','4':'StopLimit','5':'MarketOnClose'},
592
+ 54: {'1':'Buy','2':'Sell','3':'BuyMinus','4':'SellPlus','5':'SellShort','6':'SellShortExempt'},
593
+ 98: {'0':'None','1':'Value','2':'DES','3':'PKCS','4':'PGP','5':'PCSDSS','6':'PEM'},
594
+ 150:{'0':'New','1':'PartialFill','2':'Fill','3':'DoneForDay','4':'Canceled',
595
+ '5':'Replace','6':'PendingCancel','7':'Stopped','8':'Rejected',
596
+ 'D':'Restated','E':'Trade','F':'TradeCorrect','G':'TradeCancel','H':'OrderStatus'},
597
+ };
598
+
599
+ function fixTagName(tag) { return FIX_TAG_NAMES[parseInt(tag)] || ''; }
600
+ function fixEnumVal(tag, val) {
601
+ const map = FIX_ENUM_VALUES[parseInt(tag)];
602
+ return map ? (map[val] || val) : val;
603
+ }
604
+
605
+ // ── Message detail panel ──────────────────────────────────────────────────
606
+ let selectedLogRow = null;
607
+
608
+ function showMsgDetail(row, entry) {
609
+ if (selectedLogRow) selectedLogRow.classList.remove('selected');
610
+ selectedLogRow = row;
611
+ row.classList.add('selected');
612
+
613
+ const content = $id('msg-detail-content');
614
+ content.innerHTML = '';
615
+
616
+ // Meta info
617
+ const meta = document.createElement('div');
618
+ meta.className = 'msg-detail-meta';
619
+ meta.innerHTML = `<strong>${dirArrow(entry.dir)} ${entry.dir.toUpperCase()}</strong>
620
+ &nbsp;${escHtml(entry.time)}
621
+ &nbsp;<span title="${escHtml(entry.sid)}">${escHtml(shortSid(entry.sid))}</span>`;
622
+ content.appendChild(meta);
623
+
624
+ // Parse fields
625
+ entry.raw.split('|').filter(Boolean).forEach(part => {
626
+ const eq = part.indexOf('=');
627
+ if (eq < 1) return;
628
+ const tag = part.slice(0, eq).trim();
629
+ const val = part.slice(eq + 1);
630
+ const name = fixTagName(tag);
631
+ const display = fixEnumVal(tag, val);
632
+
633
+ const row = document.createElement('div');
634
+ row.className = 'msg-detail-row';
635
+ row.innerHTML =
636
+ `<span class="detail-tag">${escHtml(tag)}</span>` +
637
+ `<span class="detail-name" title="${escHtml(name)}">${escHtml(name)}</span>` +
638
+ `<span class="detail-val" title="${escHtml(val)}">${escHtml(display)}</span>`;
639
+ content.appendChild(row);
640
+ });
641
+
642
+ $id('msg-detail-panel').classList.remove('hidden');
643
+ }
644
+
645
+ function closeMsgDetail() {
646
+ if (selectedLogRow) { selectedLogRow.classList.remove('selected'); selectedLogRow = null; }
647
+ $id('msg-detail-panel').classList.add('hidden');
648
+ }
649
+
650
+
553
651
  // ── Message log ───────────────────────────────────────────────────────────
554
652
  const LOG_STORAGE_KEY = 'fixcore_log';
555
653
 
@@ -580,6 +678,7 @@ function appendLogRow(entry) {
580
678
  const row = document.createElement('div');
581
679
  row.className = `log-entry ${entry.dir}`;
582
680
  row.dataset.dir = entry.dir;
681
+ row._logEntry = entry;
583
682
  row.innerHTML = `
584
683
  <span class="log-time">${escHtml(entry.time)}</span>
585
684
  <span class="log-dir">${dirArrow(entry.dir)}</span>
@@ -605,9 +704,21 @@ function shortSid(sid) {
605
704
  : sid.slice(0, 12);
606
705
  }
607
706
 
707
+ const SESSION_MSG_TYPES = new Set(['0','1','2','3','4','5','A']);
708
+
709
+ function getMsgType(raw) {
710
+ const m = raw.match(/\b35=([^|]+)/);
711
+ return m ? m[1].trim() : null;
712
+ }
713
+
608
714
  function matchesFilter(entry) {
609
715
  if (activeSid && entry.sid !== activeSid) return false;
610
716
  if (logFilter === 'all') return true;
717
+ if (logFilter === 'app') {
718
+ if (entry.dir === 'evt') return false;
719
+ const mt = getMsgType(entry.raw);
720
+ return mt !== null && !SESSION_MSG_TYPES.has(mt);
721
+ }
611
722
  if (logFilter === 'in') return entry.dir === 'in';
612
723
  if (logFilter === 'out') return entry.dir === 'out';
613
724
  if (logFilter === 'event') return entry.dir === 'evt';
@@ -634,6 +745,7 @@ $id('btn-clear-log').addEventListener('click', () => {
634
745
  logEntries = [];
635
746
  localStorage.removeItem(LOG_STORAGE_KEY);
636
747
  $id('log-entries').innerHTML = '';
748
+ closeMsgDetail();
637
749
  });
638
750
 
639
751
  // ── Keyboard shortcuts ────────────────────────────────────────────────────
@@ -642,6 +754,7 @@ document.addEventListener('keydown', e => {
642
754
  $id('modal-overlay').classList.add('hidden');
643
755
  $id('paste-modal-overlay').classList.add('hidden');
644
756
  editMode = false; editOldKey = null;
757
+ closeMsgDetail();
645
758
  }
646
759
  if ((e.ctrlKey || e.metaKey) && e.key === 'n') {
647
760
  e.preventDefault();
@@ -101,13 +101,23 @@
101
101
  <span class="panel-title">Message Log</span>
102
102
  <div class="log-filters">
103
103
  <button class="filter-btn active" data-filter="all">All</button>
104
+ <button class="filter-btn" data-filter="app">App</button>
104
105
  <button class="filter-btn" data-filter="out">▶ Out</button>
105
106
  <button class="filter-btn" data-filter="in">◀ In</button>
106
107
  <button class="filter-btn" data-filter="event">Events</button>
107
108
  </div>
108
109
  <button class="btn btn-secondary btn-sm" id="btn-clear-log">Clear</button>
109
110
  </div>
110
- <div id="log-entries" class="log-entries"></div>
111
+ <div class="log-body">
112
+ <div id="log-entries" class="log-entries"></div>
113
+ <aside id="msg-detail-panel" class="msg-detail-panel hidden">
114
+ <div class="msg-detail-header">
115
+ <span class="msg-detail-title">Message Detail</span>
116
+ <button id="btn-close-detail" class="btn-close-detail">×</button>
117
+ </div>
118
+ <div id="msg-detail-content" class="msg-detail-content"></div>
119
+ </aside>
120
+ </div>
111
121
  </section>
112
122
  </main>
113
123
  </div>
@@ -315,6 +315,12 @@ body {
315
315
  border-color: var(--accent);
316
316
  color: #fff;
317
317
  }
318
+ .log-body {
319
+ display: flex;
320
+ flex: 1;
321
+ gap: 0;
322
+ overflow: hidden;
323
+ }
318
324
  .log-entries {
319
325
  flex: 1;
320
326
  overflow-y: auto;
@@ -324,6 +330,7 @@ body {
324
330
  border: 1px solid var(--border);
325
331
  border-radius: var(--radius);
326
332
  padding: 6px 0;
333
+ min-width: 0;
327
334
  }
328
335
  .log-entry {
329
336
  display: flex;
@@ -331,8 +338,10 @@ body {
331
338
  padding: 2px 10px;
332
339
  border-bottom: 1px solid transparent;
333
340
  line-height: 1.5;
341
+ cursor: pointer;
334
342
  }
335
343
  .log-entry:hover { background: var(--bg2); }
344
+ .log-entry.selected { background: var(--bg3); outline: 1px solid var(--accent); }
336
345
  .log-entry.in .log-dir { color: var(--in-color); }
337
346
  .log-entry.out .log-dir { color: var(--out-color); }
338
347
  .log-entry.evt .log-dir { color: var(--event-color); }
@@ -342,6 +351,71 @@ body {
342
351
  .log-sid { color: var(--text-dim); flex-shrink: 0; max-width: 120px; overflow: hidden; text-overflow: ellipsis; }
343
352
  .log-msg { color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
344
353
 
354
+ /* ── Message detail panel ─────────────────────────────────────────────── */
355
+ .msg-detail-panel {
356
+ width: 320px;
357
+ flex-shrink: 0;
358
+ border-left: 1px solid var(--border);
359
+ display: flex;
360
+ flex-direction: column;
361
+ overflow: hidden;
362
+ background: var(--bg);
363
+ margin-left: 8px;
364
+ border-radius: var(--radius);
365
+ border: 1px solid var(--border);
366
+ }
367
+ .msg-detail-header {
368
+ display: flex;
369
+ align-items: center;
370
+ justify-content: space-between;
371
+ padding: 6px 10px;
372
+ border-bottom: 1px solid var(--border);
373
+ flex-shrink: 0;
374
+ background: var(--bg2);
375
+ }
376
+ .msg-detail-title {
377
+ font-size: 11px;
378
+ font-weight: 600;
379
+ text-transform: uppercase;
380
+ letter-spacing: .08em;
381
+ color: var(--text-dim);
382
+ }
383
+ .btn-close-detail {
384
+ background: none;
385
+ border: none;
386
+ color: var(--text-dim);
387
+ cursor: pointer;
388
+ font-size: 16px;
389
+ padding: 0 2px;
390
+ line-height: 1;
391
+ }
392
+ .btn-close-detail:hover { color: var(--text); }
393
+ .msg-detail-content {
394
+ flex: 1;
395
+ overflow-y: auto;
396
+ padding: 4px 0;
397
+ }
398
+ .msg-detail-meta {
399
+ font-size: 11px;
400
+ color: var(--text-dim);
401
+ padding: 4px 10px 6px;
402
+ border-bottom: 1px solid var(--border);
403
+ margin-bottom: 2px;
404
+ }
405
+ .msg-detail-meta strong { color: var(--text); }
406
+ .msg-detail-row {
407
+ display: grid;
408
+ grid-template-columns: 42px minmax(0,1fr) minmax(0,1.4fr);
409
+ gap: 0 6px;
410
+ padding: 2px 10px;
411
+ font-size: 11px;
412
+ line-height: 1.6;
413
+ }
414
+ .msg-detail-row:hover { background: var(--bg2); }
415
+ .detail-tag { color: var(--text-dim); font-variant-numeric: tabular-nums; }
416
+ .detail-name { color: var(--accent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
417
+ .detail-val { color: var(--text); word-break: break-all; }
418
+
345
419
  /* ── Buttons ─────────────────────────────────────────────────────────── */
346
420
  .btn {
347
421
  font-family: var(--font);
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fixcore-engine
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Pure Python FIX protocol engine
5
5
  Author-email: Aidan Chisholm <aidan.chisholm@gmail.com>
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "fixcore-engine"
7
- version = "0.1.2"
7
+ version = "0.1.3"
8
8
  description = "Pure Python FIX protocol engine"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
File without changes
File without changes
File without changes