wip COB data source - not ready
This commit is contained in:
@ -1137,8 +1137,19 @@ class DataProvider:
|
||||
# Extract timestamp and price from tick
|
||||
if isinstance(tick, dict):
|
||||
timestamp = tick.get('timestamp')
|
||||
price = tick.get('price', tick.get('mid_price', 0))
|
||||
volume = tick.get('volume', 1.0) # Default volume if not available
|
||||
# Prefer explicit price if available, fallback to stats.mid_price
|
||||
stats = tick.get('stats', {}) if isinstance(tick.get('stats', {}), dict) else {}
|
||||
price = tick.get('price')
|
||||
if not price:
|
||||
price = tick.get('mid_price') or stats.get('mid_price', 0)
|
||||
# Derive a volume proxy if not provided (use bid+ask volume from stats)
|
||||
volume = tick.get('volume')
|
||||
if volume is None:
|
||||
bid_vol = stats.get('bid_volume', 0) or 0
|
||||
ask_vol = stats.get('ask_volume', 0) or 0
|
||||
volume = float(bid_vol) + float(ask_vol)
|
||||
if volume == 0:
|
||||
volume = 1.0 # Minimal placeholder to avoid zero-volume bars
|
||||
else:
|
||||
continue
|
||||
|
||||
@ -2221,14 +2232,40 @@ class DataProvider:
|
||||
|
||||
# Get latest COB data from cache
|
||||
cob_data = self.get_latest_cob_data(symbol)
|
||||
if cob_data and 'current_price' in cob_data:
|
||||
if cob_data:
|
||||
# Determine current price (prefer explicit field, fallback to stats.mid_price)
|
||||
stats = cob_data.get('stats', {}) if isinstance(cob_data.get('stats', {}), dict) else {}
|
||||
current_price = cob_data.get('current_price') or stats.get('mid_price', 0.0)
|
||||
bucket_size = 1.0 if 'ETH' in symbol else 10.0
|
||||
|
||||
# Ensure price buckets exist; compute from bids/asks if missing
|
||||
price_buckets = cob_data.get('price_buckets') or {}
|
||||
if (not price_buckets) and current_price:
|
||||
price_buckets = self._compute_price_buckets_from_snapshot(
|
||||
current_price=current_price,
|
||||
bucket_size=bucket_size,
|
||||
bids=cob_data.get('bids', []),
|
||||
asks=cob_data.get('asks', [])
|
||||
)
|
||||
|
||||
# Build imbalance map (price -> imbalance) if not provided
|
||||
bid_ask_imbalance = cob_data.get('bid_ask_imbalance') or {}
|
||||
if not bid_ask_imbalance and price_buckets:
|
||||
tmp = {}
|
||||
for price, bucket in price_buckets.items():
|
||||
bid_vol = float(bucket.get('bid_volume', 0.0) or 0.0)
|
||||
ask_vol = float(bucket.get('ask_volume', 0.0) or 0.0)
|
||||
denom = bid_vol + ask_vol
|
||||
tmp[price] = (bid_vol - ask_vol) / denom if denom > 0 else 0.0
|
||||
bid_ask_imbalance = tmp
|
||||
|
||||
return COBData(
|
||||
symbol=symbol,
|
||||
timestamp=datetime.now(),
|
||||
current_price=cob_data['current_price'],
|
||||
bucket_size=1.0 if 'ETH' in symbol else 10.0,
|
||||
price_buckets=cob_data.get('price_buckets', {}),
|
||||
bid_ask_imbalance=cob_data.get('bid_ask_imbalance', {}),
|
||||
current_price=float(current_price or 0.0),
|
||||
bucket_size=bucket_size,
|
||||
price_buckets=price_buckets,
|
||||
bid_ask_imbalance=bid_ask_imbalance,
|
||||
volume_weighted_prices=cob_data.get('volume_weighted_prices', {}),
|
||||
order_flow_metrics=cob_data.get('order_flow_metrics', {}),
|
||||
ma_1s_imbalance=cob_data.get('ma_1s_imbalance', {}),
|
||||
@ -2241,6 +2278,67 @@ class DataProvider:
|
||||
logger.error(f"Error getting COB data object for {symbol}: {e}")
|
||||
return None
|
||||
|
||||
def _compute_price_buckets_from_snapshot(
|
||||
self,
|
||||
current_price: float,
|
||||
bucket_size: float,
|
||||
bids: List[List[float]],
|
||||
asks: List[List[float]]
|
||||
) -> Dict[float, Dict[str, float]]:
|
||||
"""Compute ±20 price buckets around current price from raw bids/asks.
|
||||
|
||||
Returns dict: price -> {bid_volume, ask_volume, total_volume, imbalance}
|
||||
"""
|
||||
try:
|
||||
# Initialize bucket map for ±20 buckets
|
||||
bucket_map: Dict[float, Dict[str, float]] = {}
|
||||
if not current_price or bucket_size <= 0:
|
||||
return bucket_map
|
||||
|
||||
# Center-aligned bucket prices
|
||||
bucket_count = 20
|
||||
for i in range(-bucket_count, bucket_count + 1):
|
||||
price = (round(current_price / bucket_size) * bucket_size) + (i * bucket_size)
|
||||
bucket_map[price] = {
|
||||
'bid_volume': 0.0,
|
||||
'ask_volume': 0.0,
|
||||
'total_volume': 0.0,
|
||||
'imbalance': 0.0,
|
||||
}
|
||||
|
||||
# Aggregate bids
|
||||
for level in (bids or [])[:200]:
|
||||
try:
|
||||
price, size = float(level[0]), float(level[1])
|
||||
except Exception:
|
||||
continue
|
||||
bucket_price = round(price / bucket_size) * bucket_size
|
||||
if bucket_price in bucket_map:
|
||||
bucket_map[bucket_price]['bid_volume'] += size
|
||||
|
||||
# Aggregate asks
|
||||
for level in (asks or [])[:200]:
|
||||
try:
|
||||
price, size = float(level[0]), float(level[1])
|
||||
except Exception:
|
||||
continue
|
||||
bucket_price = round(price / bucket_size) * bucket_size
|
||||
if bucket_price in bucket_map:
|
||||
bucket_map[bucket_price]['ask_volume'] += size
|
||||
|
||||
# Compute totals and imbalance
|
||||
for price, bucket in bucket_map.items():
|
||||
bid_vol = float(bucket['bid_volume'])
|
||||
ask_vol = float(bucket['ask_volume'])
|
||||
total = bid_vol + ask_vol
|
||||
bucket['total_volume'] = total
|
||||
bucket['imbalance'] = (bid_vol - ask_vol) / total if total > 0 else 0.0
|
||||
|
||||
return bucket_map
|
||||
except Exception as e:
|
||||
logger.debug(f"Error computing price buckets: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
|
||||
def _add_basic_indicators(self, df: pd.DataFrame) -> pd.DataFrame:
|
||||
@ -4278,13 +4376,46 @@ class DataProvider:
|
||||
if symbol not in self.cob_data_cache:
|
||||
self.cob_data_cache[symbol] = []
|
||||
|
||||
# Convert WebSocket format to standard format
|
||||
# Convert WebSocket format to standard format and enrich stats if missing
|
||||
bids_arr = [[bid['price'], bid['size']] for bid in cob_data.get('bids', [])[:50]]
|
||||
asks_arr = [[ask['price'], ask['size']] for ask in cob_data.get('asks', [])[:50]]
|
||||
stats_in = cob_data.get('stats', {}) if isinstance(cob_data.get('stats', {}), dict) else {}
|
||||
|
||||
# Derive stats when not provided by source
|
||||
best_bid = max([b[0] for b in bids_arr], default=0)
|
||||
best_ask = min([a[0] for a in asks_arr], default=0)
|
||||
mid = stats_in.get('mid_price') or ((best_bid + best_ask) / 2.0 if best_bid > 0 and best_ask > 0 else 0)
|
||||
|
||||
total_bid_liq = sum([b[0] * b[1] for b in bids_arr]) # price*size USD approx
|
||||
total_ask_liq = sum([a[0] * a[1] for a in asks_arr])
|
||||
spread_bps = 0
|
||||
if best_bid > 0 and best_ask > 0 and mid > 0:
|
||||
spread_bps = ((best_ask - best_bid) / mid) * 10000
|
||||
imbalance = 0.0
|
||||
denom = (total_bid_liq + total_ask_liq)
|
||||
if denom > 0:
|
||||
imbalance = (total_bid_liq - total_ask_liq) / denom
|
||||
|
||||
stats_out = {
|
||||
'mid_price': mid,
|
||||
'spread_bps': spread_bps,
|
||||
'imbalance': imbalance,
|
||||
'best_bid': best_bid,
|
||||
'best_ask': best_ask,
|
||||
'bid_volume': total_bid_liq,
|
||||
'ask_volume': total_ask_liq,
|
||||
'bid_levels': len(bids_arr),
|
||||
'ask_levels': len(asks_arr)
|
||||
}
|
||||
# Merge any provided stats atop computed defaults
|
||||
stats_out.update(stats_in or {})
|
||||
|
||||
standard_cob_data = {
|
||||
'symbol': symbol,
|
||||
'timestamp': int(cob_data['timestamp'] * 1000), # Convert to milliseconds
|
||||
'bids': [[bid['price'], bid['size']] for bid in cob_data.get('bids', [])[:50]],
|
||||
'asks': [[ask['price'], ask['size']] for ask in cob_data.get('asks', [])[:50]],
|
||||
'stats': cob_data.get('stats', {})
|
||||
'bids': bids_arr,
|
||||
'asks': asks_arr,
|
||||
'stats': stats_out
|
||||
}
|
||||
|
||||
# Add to cache
|
||||
|
Reference in New Issue
Block a user