Sharif2615 commited on
Commit
ff62e21
·
1 Parent(s): 7d23890

perf(cache): add 15s cache to broker market state and 2s cache to price query

Browse files
Files changed (1) hide show
  1. capital_client.py +47 -6
capital_client.py CHANGED
@@ -250,9 +250,23 @@ def get_capital_candles(asset='GOLD', interval='5m', max_bars=80, price_mode='bi
250
  return None
251
 
252
 
 
 
 
 
 
 
253
  def get_capital_market_price(asset='GOLD'):
254
- """Returns real-time bid, ask, spread, and broker marketStatus from Capital.com Live"""
 
 
 
255
  asset_u = asset.upper()
 
 
 
 
 
256
  epic = EPICS.get(asset_u)
257
  if not epic:
258
  return None
@@ -281,7 +295,7 @@ def get_capital_market_price(asset='GOLD'):
281
  mid = (bid + ask) / 2.0
282
  m_status = str(snap.get('marketStatus', 'TRADEABLE')).upper()
283
 
284
- return {
285
  'bid': round(bid, prec),
286
  'ask': round(ask, prec),
287
  'mid': round(mid, prec),
@@ -293,6 +307,10 @@ def get_capital_market_price(asset='GOLD'):
293
  'market_status': m_status,
294
  'is_tradeable': m_status == 'TRADEABLE'
295
  }
 
 
 
 
296
  except Exception as e:
297
  err_str = str(e)
298
  if '401' in err_str or '403' in err_str:
@@ -314,7 +332,7 @@ def get_capital_market_price(asset='GOLD'):
314
  ask = float(snap.get('offer', snap.get('ask', 0)))
315
  mid = (bid + ask) / 2.0
316
  m_status = str(snap.get('marketStatus', 'TRADEABLE')).upper()
317
- return {
318
  'bid': round(bid, prec),
319
  'ask': round(ask, prec),
320
  'mid': round(mid, prec),
@@ -326,30 +344,53 @@ def get_capital_market_price(asset='GOLD'):
326
  'market_status': m_status,
327
  'is_tradeable': m_status == 'TRADEABLE'
328
  }
 
 
 
 
329
  except Exception:
330
  pass
 
 
 
331
  return None
332
 
333
 
334
  def get_broker_market_state(asset='GOLD'):
335
  """
336
  Authoritative Broker Truth Sensor
 
337
  Returns dict: {'is_open': bool, 'market_status': str, 'status_title': str}
338
  """
339
- p = get_capital_market_price(asset)
 
 
 
 
 
 
340
  if p and p.get('market_status'):
341
  status = p['market_status']
342
  is_open = (status == 'TRADEABLE')
343
- return {
344
  'is_open': is_open,
345
  'market_status': status,
346
  'status_title': 'MARKET OPEN' if is_open else 'BROKER MARKET CLOSED'
347
  }
348
- return {
 
 
 
 
 
349
  'is_open': True,
350
  'market_status': 'UNKNOWN',
351
  'status_title': 'SESSION ACTIVE'
352
  }
 
 
 
 
353
 
354
 
355
  # ---- Direct test ----
 
250
  return None
251
 
252
 
253
+ _MARKET_PRICE_CACHE = {}
254
+ _MARKET_PRICE_CACHE_TS = {}
255
+ _BROKER_STATE_CACHE = {}
256
+ _BROKER_STATE_CACHE_TS = {}
257
+ _price_cache_lock = threading.RLock()
258
+
259
  def get_capital_market_price(asset='GOLD'):
260
+ """
261
+ Fetches real-time market price and snapshot directly from Capital.com.
262
+ Protected by a 2.0s server-paced cache to eliminate redundant network roundtrips.
263
+ """
264
  asset_u = asset.upper()
265
+ now_t = time.time()
266
+ with _price_cache_lock:
267
+ if asset_u in _MARKET_PRICE_CACHE and (now_t - _MARKET_PRICE_CACHE_TS.get(asset_u, 0)) < 2.0:
268
+ return dict(_MARKET_PRICE_CACHE[asset_u])
269
+
270
  epic = EPICS.get(asset_u)
271
  if not epic:
272
  return None
 
295
  mid = (bid + ask) / 2.0
296
  m_status = str(snap.get('marketStatus', 'TRADEABLE')).upper()
297
 
298
+ res_dict = {
299
  'bid': round(bid, prec),
300
  'ask': round(ask, prec),
301
  'mid': round(mid, prec),
 
307
  'market_status': m_status,
308
  'is_tradeable': m_status == 'TRADEABLE'
309
  }
310
+ with _price_cache_lock:
311
+ _MARKET_PRICE_CACHE[asset_u] = res_dict
312
+ _MARKET_PRICE_CACHE_TS[asset_u] = now_t
313
+ return res_dict
314
  except Exception as e:
315
  err_str = str(e)
316
  if '401' in err_str or '403' in err_str:
 
332
  ask = float(snap.get('offer', snap.get('ask', 0)))
333
  mid = (bid + ask) / 2.0
334
  m_status = str(snap.get('marketStatus', 'TRADEABLE')).upper()
335
+ res_dict = {
336
  'bid': round(bid, prec),
337
  'ask': round(ask, prec),
338
  'mid': round(mid, prec),
 
344
  'market_status': m_status,
345
  'is_tradeable': m_status == 'TRADEABLE'
346
  }
347
+ with _price_cache_lock:
348
+ _MARKET_PRICE_CACHE[asset_u] = res_dict
349
+ _MARKET_PRICE_CACHE_TS[asset_u] = now_t
350
+ return res_dict
351
  except Exception:
352
  pass
353
+ with _price_cache_lock:
354
+ if asset_u in _MARKET_PRICE_CACHE:
355
+ return dict(_MARKET_PRICE_CACHE[asset_u])
356
  return None
357
 
358
 
359
  def get_broker_market_state(asset='GOLD'):
360
  """
361
  Authoritative Broker Truth Sensor
362
+ Protected by 15.0s cache to avoid redundant network calls.
363
  Returns dict: {'is_open': bool, 'market_status': str, 'status_title': str}
364
  """
365
+ asset_u = asset.upper()
366
+ now_t = time.time()
367
+ with _price_cache_lock:
368
+ if asset_u in _BROKER_STATE_CACHE and (now_t - _BROKER_STATE_CACHE_TS.get(asset_u, 0)) < 15.0:
369
+ return dict(_BROKER_STATE_CACHE[asset_u])
370
+
371
+ p = get_capital_market_price(asset_u)
372
  if p and p.get('market_status'):
373
  status = p['market_status']
374
  is_open = (status == 'TRADEABLE')
375
+ res = {
376
  'is_open': is_open,
377
  'market_status': status,
378
  'status_title': 'MARKET OPEN' if is_open else 'BROKER MARKET CLOSED'
379
  }
380
+ with _price_cache_lock:
381
+ _BROKER_STATE_CACHE[asset_u] = res
382
+ _BROKER_STATE_CACHE_TS[asset_u] = now_t
383
+ return res
384
+
385
+ fallback = {
386
  'is_open': True,
387
  'market_status': 'UNKNOWN',
388
  'status_title': 'SESSION ACTIVE'
389
  }
390
+ with _price_cache_lock:
391
+ if asset_u in _BROKER_STATE_CACHE:
392
+ return dict(_BROKER_STATE_CACHE[asset_u])
393
+ return fallback
394
 
395
 
396
  # ---- Direct test ----