RayMelius Claude Sonnet 4.6 commited on
Commit
6ddeb15
·
1 Parent(s): 988cdc7

Add NBG security and restrict symbol entry to known securities

Browse files

- securities.txt: add NBG with PEIR initial values (18.50 / 18.05)
- docker-compose: mount shared_data into frontend container
- frontend.py: add /securities endpoint reading securities file
- fix-ui-client.py: read securities file, pass list to template
- frontend UI: symbol dropdown (loaded via /securities), reconnect button
- fix-ui-client UI: symbol dropdown from Jinja2 securities list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

docker-compose.yml CHANGED
@@ -57,6 +57,7 @@ services:
57
  - "5000:5000"
58
  volumes:
59
  - ./shared:/app/shared
 
60
  environment:
61
  - MATCHER_URL=http://matcher:6000
62
 
 
57
  - "5000:5000"
58
  volumes:
59
  - ./shared:/app/shared
60
+ - ./shared_data:/app/data
61
  environment:
62
  - MATCHER_URL=http://matcher:6000
63
 
fix-ui-client/fix-ui-client.py CHANGED
@@ -144,13 +144,26 @@ def stop_fix():
144
  log("🪫 FIX initiator stopped")
145
  fix_initiator = None
146
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  # --- Flask routes ---
148
  @app.route("/")
149
  def index():
150
  with _msgs_lock:
151
  msgs = list(reversed(_messages)) # newest first
152
  connected = bool(fix_app and fix_app.connected)
153
- return render_template("index.html", messages=msgs, connected=connected)
 
154
 
155
  @app.route("/status")
156
  def status():
 
144
  log("🪫 FIX initiator stopped")
145
  fix_initiator = None
146
 
147
+ def load_securities():
148
+ symbols = []
149
+ try:
150
+ with open(Config.SECURITIES_FILE) as f:
151
+ for line in f:
152
+ line = line.strip()
153
+ if line and not line.startswith('#'):
154
+ symbols.append(line.split('\t')[0])
155
+ except Exception:
156
+ pass
157
+ return symbols or ["ALPHA", "PEIR", "EXAE", "QUEST", "NBG"]
158
+
159
  # --- Flask routes ---
160
  @app.route("/")
161
  def index():
162
  with _msgs_lock:
163
  msgs = list(reversed(_messages)) # newest first
164
  connected = bool(fix_app and fix_app.connected)
165
+ return render_template("index.html", messages=msgs, connected=connected,
166
+ securities=load_securities())
167
 
168
  @app.route("/status")
169
  def status():
fix-ui-client/templates/index.html CHANGED
@@ -135,7 +135,11 @@
135
  </div>
136
  <div class="form-group">
137
  <label>Symbol</label>
138
- <input type="text" name="symbol" value="AAPL">
 
 
 
 
139
  </div>
140
  <div class="form-group">
141
  <label>Quantity</label>
 
135
  </div>
136
  <div class="form-group">
137
  <label>Symbol</label>
138
+ <select name="symbol">
139
+ {% for s in securities %}
140
+ <option value="{{ s }}">{{ s }}</option>
141
+ {% endfor %}
142
+ </select>
143
  </div>
144
  <div class="form-group">
145
  <label>Quantity</label>
frontend/frontend.py CHANGED
@@ -61,6 +61,19 @@ def send_order():
61
  producer.flush()
62
  return jsonify({'status':'sent','data':data})
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  @app.route('/book')
65
  def book():
66
  import requests
 
61
  producer.flush()
62
  return jsonify({'status':'sent','data':data})
63
 
64
+ @app.route('/securities')
65
+ def securities():
66
+ symbols = []
67
+ try:
68
+ with open(Config.SECURITIES_FILE) as f:
69
+ for line in f:
70
+ line = line.strip()
71
+ if line and not line.startswith('#'):
72
+ symbols.append(line.split('\t')[0])
73
+ except Exception:
74
+ pass
75
+ return jsonify(symbols)
76
+
77
  @app.route('/book')
78
  def book():
79
  import requests
frontend/templates/index.html CHANGED
@@ -42,6 +42,22 @@
42
  .status.disconnected { background: #f8d7da; color: #721c24; }
43
  .status.disconnected .dot { background: #dc3545; }
44
  @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
  /* Order form grid */
47
  .order-grid {
@@ -93,7 +109,6 @@
93
  .bid { color: #2e7d32; font-weight: bold; }
94
  .ask { color: #c62828; font-weight: bold; }
95
 
96
- /* Highlight animation for new rows */
97
  @keyframes highlight { from { background: #c8e6c9; } to { background: transparent; } }
98
  .new-row { animation: highlight 2s ease-out; }
99
 
@@ -104,13 +119,14 @@
104
 
105
  <h1>
106
  Order Entry
107
- <span id="status-badge" class="status connected">
108
  <span class="dot"></span>
109
- <span id="status-text">Live</span>
110
  </span>
 
111
  </h1>
112
 
113
- <!-- Order Entry (full width) -->
114
  <div class="panel">
115
  <h2>Place Order</h2>
116
  <form id="orderForm" onsubmit="sendOrder(event)">
@@ -121,7 +137,9 @@
121
  </div>
122
  <div class="form-group">
123
  <label>Symbol</label>
124
- <input name="symbol" value="FOO">
 
 
125
  </div>
126
  <div class="form-group">
127
  <label>Side</label>
@@ -193,6 +211,33 @@
193
  </div>
194
 
195
  <script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
  async function sendOrder(evt) {
197
  evt.preventDefault();
198
  const form = document.getElementById('orderForm');
@@ -278,25 +323,20 @@
278
 
279
  async function loadBook() {
280
  const now = new Date().toLocaleTimeString();
281
- const badge = document.getElementById('status-badge');
282
-
283
  try {
284
  const r = await fetch('/book');
285
  const b = await r.json();
286
  renderBook(b);
287
  document.getElementById('book-updated').textContent = 'Updated: ' + now;
288
- badge.className = 'status connected';
289
- document.getElementById('status-text').textContent = 'Live';
290
  } catch(e) {
291
  document.getElementById('book-body').innerHTML =
292
  '<tr><td colspan="4" style="color:red; text-align:center;">Error loading book</td></tr>';
293
- badge.className = 'status disconnected';
294
- document.getElementById('status-text').textContent = 'Disconnected';
295
  }
296
-
297
  try {
298
  const r2 = await fetch('/trades');
299
- const t = await r2.json();
300
  renderTrades(Array.isArray(t) ? t : (t.trades || []));
301
  document.getElementById('trades-updated').textContent = 'Updated: ' + now;
302
  } catch(e) {
@@ -305,8 +345,18 @@
305
  }
306
  }
307
 
308
- setInterval(loadBook, 2000);
309
- window.onload = loadBook;
 
 
 
 
 
 
 
 
 
 
310
  </script>
311
  </body>
312
  </html>
 
42
  .status.disconnected { background: #f8d7da; color: #721c24; }
43
  .status.disconnected .dot { background: #dc3545; }
44
  @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
45
+ .status.connecting { background: #fff3cd; color: #856404; }
46
+ .status.connecting .dot { background: #ffc107; animation: pulse 1s infinite; }
47
+
48
+ /* Reconnect button */
49
+ .btn-reconnect {
50
+ padding: 4px 12px;
51
+ background: #ff9800;
52
+ color: #fff;
53
+ border: none;
54
+ border-radius: 20px;
55
+ cursor: pointer;
56
+ font-size: 12px;
57
+ font-weight: bold;
58
+ display: none;
59
+ }
60
+ .btn-reconnect:hover { background: #e68900; }
61
 
62
  /* Order form grid */
63
  .order-grid {
 
109
  .bid { color: #2e7d32; font-weight: bold; }
110
  .ask { color: #c62828; font-weight: bold; }
111
 
 
112
  @keyframes highlight { from { background: #c8e6c9; } to { background: transparent; } }
113
  .new-row { animation: highlight 2s ease-out; }
114
 
 
119
 
120
  <h1>
121
  Order Entry
122
+ <span id="status-badge" class="status connecting">
123
  <span class="dot"></span>
124
+ <span id="status-text">Connecting...</span>
125
  </span>
126
+ <button id="reconnect-btn" class="btn-reconnect" onclick="reconnect()">Reconnect</button>
127
  </h1>
128
 
129
+ <!-- Order Entry -->
130
  <div class="panel">
131
  <h2>Place Order</h2>
132
  <form id="orderForm" onsubmit="sendOrder(event)">
 
137
  </div>
138
  <div class="form-group">
139
  <label>Symbol</label>
140
+ <select id="symbol-select" name="symbol">
141
+ <option value="">Loading...</option>
142
+ </select>
143
  </div>
144
  <div class="form-group">
145
  <label>Side</label>
 
211
  </div>
212
 
213
  <script>
214
+ let pollInterval = null;
215
+
216
+ function setStatus(cls, text) {
217
+ const badge = document.getElementById('status-badge');
218
+ const btn = document.getElementById('reconnect-btn');
219
+ badge.className = 'status ' + cls;
220
+ document.getElementById('status-text').textContent = text;
221
+ btn.style.display = (cls === 'disconnected') ? 'inline-block' : 'none';
222
+ }
223
+
224
+ async function loadSecurities() {
225
+ try {
226
+ const r = await fetch('/securities');
227
+ const symbols = await r.json();
228
+ const sel = document.getElementById('symbol-select');
229
+ sel.innerHTML = '';
230
+ symbols.forEach(s => {
231
+ const opt = document.createElement('option');
232
+ opt.value = s;
233
+ opt.textContent = s;
234
+ sel.appendChild(opt);
235
+ });
236
+ } catch(e) {
237
+ console.warn('Could not load securities:', e);
238
+ }
239
+ }
240
+
241
  async function sendOrder(evt) {
242
  evt.preventDefault();
243
  const form = document.getElementById('orderForm');
 
323
 
324
  async function loadBook() {
325
  const now = new Date().toLocaleTimeString();
 
 
326
  try {
327
  const r = await fetch('/book');
328
  const b = await r.json();
329
  renderBook(b);
330
  document.getElementById('book-updated').textContent = 'Updated: ' + now;
331
+ setStatus('connected', 'Live');
 
332
  } catch(e) {
333
  document.getElementById('book-body').innerHTML =
334
  '<tr><td colspan="4" style="color:red; text-align:center;">Error loading book</td></tr>';
335
+ setStatus('disconnected', 'Disconnected');
 
336
  }
 
337
  try {
338
  const r2 = await fetch('/trades');
339
+ const t = await r2.json();
340
  renderTrades(Array.isArray(t) ? t : (t.trades || []));
341
  document.getElementById('trades-updated').textContent = 'Updated: ' + now;
342
  } catch(e) {
 
345
  }
346
  }
347
 
348
+ function reconnect() {
349
+ setStatus('connecting', 'Connecting...');
350
+ loadBook();
351
+ }
352
+
353
+ async function init() {
354
+ await loadSecurities();
355
+ await loadBook();
356
+ pollInterval = setInterval(loadBook, 2000);
357
+ }
358
+
359
+ window.onload = init;
360
  </script>
361
  </body>
362
  </html>
shared_data/securities.txt CHANGED
@@ -3,3 +3,4 @@ ALPHA 24.90 24.95
3
  PEIR 18.50 18.05
4
  EXAE 42.00 42.05
5
  QUEST 12.70 12.60
 
 
3
  PEIR 18.50 18.05
4
  EXAE 42.00 42.05
5
  QUEST 12.70 12.60
6
+ NBG 18.50 18.05