Files
DOLPHIN/external_factors/indicator_reader.py
hjnormey 01c19662cb initial: import DOLPHIN baseline 2026-04-21 from dolphinng5_predict working tree
Includes core prod + GREEN/BLUE subsystems:
- prod/ (BLUE harness, configs, scripts, docs)
- nautilus_dolphin/ (GREEN Nautilus-native impl + dvae/ preserved)
- adaptive_exit/ (AEM engine + models/bucket_assignments.pkl)
- Observability/ (EsoF advisor, TUI, dashboards)
- external_factors/ (EsoF producer)
- mc_forewarning_qlabs_fork/ (MC regime/envelope)

Excludes runtime caches, logs, backups, and reproducible artifacts per .gitignore.
2026-04-21 16:58:38 +02:00

267 lines
9.2 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
INDICATOR READER v1.0
=====================
Utility to read and analyze processed indicator .npz files.
Usage:
from indicator_reader import IndicatorReader
# Load single file
reader = IndicatorReader("scan_000027_193311__Indicators.npz")
print(reader.summary())
# Get DataFrames
scan_df = reader.scan_derived_df()
external_df = reader.external_df()
asset_df = reader.asset_df()
# Load directory
all_data = IndicatorReader.load_directory("./scans/")
"""
import numpy as np
from pathlib import Path
from typing import Dict, List, Optional, Any, Tuple
from datetime import datetime
class IndicatorReader:
"""Reader for processed indicator .npz files"""
def __init__(self, path: str):
self.path = Path(path)
self._data = dict(np.load(path, allow_pickle=True))
@property
def scan_number(self) -> int:
return int(self._data['scan_number'][0])
@property
def timestamp(self) -> str:
return str(self._data['timestamp'][0])
@property
def processing_time(self) -> float:
return float(self._data['processing_time'][0])
@property
def n_assets(self) -> int:
return len(self._data['asset_symbols'])
@property
def asset_symbols(self) -> List[str]:
return list(self._data['asset_symbols'])
# =========================================================================
# SCAN-DERIVED (eigenvalue indicators from tracking_data/regime_signals)
# =========================================================================
@property
def scan_derived(self) -> np.ndarray:
"""Get scan-derived indicator array"""
return self._data['scan_derived']
@property
def scan_derived_names(self) -> List[str]:
return list(self._data['scan_derived_names'])
def scan_derived_df(self):
"""Get scan-derived as pandas DataFrame"""
import pandas as pd
return pd.DataFrame({
'name': self.scan_derived_names,
'value': self.scan_derived
})
def get_scan_indicator(self, name: str) -> float:
"""Get specific scan-derived indicator by name"""
names = self.scan_derived_names
if name in names:
return float(self.scan_derived[names.index(name)])
raise KeyError(f"Unknown scan indicator: {name}")
# =========================================================================
# EXTERNAL (API-fetched indicators)
# =========================================================================
@property
def external(self) -> np.ndarray:
"""Get external indicator array (85 values, NaN for skipped)"""
return self._data['external']
@property
def external_success(self) -> np.ndarray:
"""Get success flags for external indicators"""
return self._data['external_success']
def external_df(self):
"""Get external indicators as pandas DataFrame"""
import pandas as pd
# Indicator names (would need to import from external_factors_matrix)
names = [f"ext_{i+1}" for i in range(85)]
return pd.DataFrame({
'id': range(1, 86),
'value': self.external,
'success': self.external_success
})
@property
def external_success_rate(self) -> float:
"""Percentage of external indicators successfully fetched"""
valid = ~np.isnan(self.external)
if valid.sum() == 0:
return 0.0
return float(self.external_success[valid].mean())
# =========================================================================
# PER-ASSET
# =========================================================================
@property
def asset_matrix(self) -> np.ndarray:
"""Get per-asset indicator matrix (n_assets x n_indicators)"""
return self._data['asset_matrix']
@property
def asset_indicator_names(self) -> List[str]:
return list(self._data['asset_indicator_names'])
def asset_df(self):
"""Get per-asset indicators as pandas DataFrame"""
import pandas as pd
return pd.DataFrame(
self.asset_matrix,
index=self.asset_symbols,
columns=self.asset_indicator_names
)
def get_asset(self, symbol: str) -> Dict[str, float]:
"""Get all indicators for a specific asset"""
symbols = self.asset_symbols
if symbol not in symbols:
raise KeyError(f"Unknown symbol: {symbol}")
idx = symbols.index(symbol)
return dict(zip(self.asset_indicator_names, self.asset_matrix[idx]))
def get_asset_indicator(self, symbol: str, indicator: str) -> float:
"""Get specific indicator for specific asset"""
asset = self.get_asset(symbol)
if indicator not in asset:
raise KeyError(f"Unknown indicator: {indicator}")
return asset[indicator]
# =========================================================================
# UTILITIES
# =========================================================================
def summary(self) -> str:
"""Get summary string"""
ext_valid = (~np.isnan(self.external)).sum()
ext_success = self.external_success.sum()
return f"""Indicator File: {self.path.name}
Scan: #{self.scan_number} @ {self.timestamp}
Processing: {self.processing_time:.2f}s
Scan-derived: {len(self.scan_derived)} indicators
lambda_max: {self.get_scan_indicator('lambda_max'):.4f}
coherence: {self.get_scan_indicator('market_coherence'):.4f}
instability: {self.get_scan_indicator('instability_score'):.4f}
External: {ext_success}/{ext_valid} successful ({self.external_success_rate*100:.1f}%)
Per-asset: {self.n_assets} assets × {len(self.asset_indicator_names)} indicators
"""
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary"""
return {
'scan_number': self.scan_number,
'timestamp': self.timestamp,
'processing_time': self.processing_time,
'scan_derived': dict(zip(self.scan_derived_names, self.scan_derived.tolist())),
'external': self.external.tolist(),
'external_success': self.external_success.tolist(),
'asset_symbols': self.asset_symbols,
'asset_matrix': self.asset_matrix.tolist(),
}
# =========================================================================
# CLASS METHODS
# =========================================================================
@classmethod
def load_directory(cls, directory: str, pattern: str = "*__Indicators.npz") -> List['IndicatorReader']:
"""Load all indicator files from directory"""
root = Path(directory)
files = sorted(root.rglob(pattern))
return [cls(str(f)) for f in files]
@classmethod
def to_timeseries(cls, readers: List['IndicatorReader']) -> Dict[str, np.ndarray]:
"""Convert list of readers to time series arrays"""
n = len(readers)
if n == 0:
return {}
# Get dimensions from first file
n_scan = len(readers[0].scan_derived)
n_ext = 85
n_assets = readers[0].n_assets
n_asset_ind = len(readers[0].asset_indicator_names)
# Allocate arrays
timestamps = []
scan_series = np.zeros((n, n_scan))
ext_series = np.zeros((n, n_ext))
for i, r in enumerate(readers):
timestamps.append(r.timestamp)
scan_series[i] = r.scan_derived
ext_series[i] = r.external
return {
'timestamps': np.array(timestamps, dtype='U32'),
'scan_derived': scan_series,
'external': ext_series,
'scan_names': readers[0].scan_derived_names,
}
# =============================================================================
# CLI
# =============================================================================
def main():
import argparse
parser = argparse.ArgumentParser(description="Indicator Reader")
parser.add_argument("path", help="Path to .npz file or directory")
parser.add_argument("-a", "--asset", help="Show specific asset")
parser.add_argument("-j", "--json", action="store_true", help="Output as JSON")
args = parser.parse_args()
path = Path(args.path)
if path.is_file():
reader = IndicatorReader(str(path))
if args.json:
import json
print(json.dumps(reader.to_dict(), indent=2))
elif args.asset:
asset = reader.get_asset(args.asset)
for k, v in asset.items():
print(f" {k}: {v:.6f}")
else:
print(reader.summary())
elif path.is_dir():
readers = IndicatorReader.load_directory(str(path))
print(f"Found {len(readers)} indicator files")
if readers:
ts = IndicatorReader.to_timeseries(readers)
print(f"Time range: {ts['timestamps'][0]} to {ts['timestamps'][-1]}")
print(f"Scan-derived shape: {ts['scan_derived'].shape}")
print(f"External shape: {ts['external'].shape}")
if __name__ == "__main__":
main()