Spaces:
Running on Zero
Running on Zero
Commit ·
ff62e21
1
Parent(s): 7d23890
perf(cache): add 15s cache to broker market state and 2s cache to price query
Browse files- 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 |
-
"""
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
if p and p.get('market_status'):
|
| 341 |
status = p['market_status']
|
| 342 |
is_open = (status == 'TRADEABLE')
|
| 343 |
-
|
| 344 |
'is_open': is_open,
|
| 345 |
'market_status': status,
|
| 346 |
'status_title': 'MARKET OPEN' if is_open else 'BROKER MARKET CLOSED'
|
| 347 |
}
|
| 348 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 ----
|