feat: instrument analysis

This commit is contained in:
OpenSquared
2026-07-24 17:17:20 +02:00
parent adfe520863
commit de0675705b
3 changed files with 37 additions and 8 deletions

View File

@@ -20,8 +20,11 @@ chat context always has fresh slope/energy/ridge state, not just firing
events (see ai_chat_context.py:_block_wavelet_signals).
"""
import json
import logging
from typing import Dict, List, Optional
logger = logging.getLogger(__name__)
def _compute_slope(series: List[float]) -> List[float]:
n = len(series)
@@ -249,11 +252,18 @@ def _fetch_close_series(ticker: str, saxo_symbol: Optional[str]):
bars = get_price_history(saxo_symbol, asset_type, days=400)
return [b["close"] for b in bars], [b["date"] for b in bars]
except Exception as e:
import logging
logging.getLogger(__name__).warning(f"[wavelet_signals] Saxo fetch failed for '{saxo_symbol}', falling back to yfinance: {e}")
logger.warning(f"[wavelet_signals] Saxo fetch failed for '{saxo_symbol}', falling back to yfinance: {e}")
from services.data_fetcher import get_historical
hist = get_historical(ticker, period="1y", interval="1d")
yf_ticker = ticker.upper()
# Bare 6-letter FX pairs (EURUSD, GBPUSD...) are a common Watchlist ticker convention
# here but not a real yfinance symbol (needs the "=X" suffix) — without this, any
# Saxo-linked FX pair whose Saxo fetch fails falls through to a yfinance call that's
# guaranteed to return nothing, permanently keeping it out of the wavelet cache no
# matter how many refreshes run.
if len(yf_ticker) == 6 and yf_ticker.isalpha():
yf_ticker += "=X"
hist = get_historical(yf_ticker, period="1y", interval="1d")
return [h["close"] for h in hist], [h["date"] for h in hist]
@@ -299,6 +309,10 @@ def scan_watchlist_wavelet_signals(run_id: Optional[str] = None) -> List[Dict]:
try:
values, dates = _fetch_close_series(ticker, item.get("saxo_quote_symbol"))
if len(values) < lookback + 32:
logger.warning(
f"[wavelet_signals] Skipping '{ticker}': only {len(values)} price points "
f"fetched, need >= {lookback + 32} (lookback={lookback}) — no cache written this pass."
)
continue
start_idx = max(lookback, len(values) - 60)
decomposed = decomposer(
@@ -307,6 +321,7 @@ def scan_watchlist_wavelet_signals(run_id: Optional[str] = None) -> List[Dict]:
num_levels=num_levels, wavelet=wavelet, step=1,
)
if not decomposed["dates"]:
logger.warning(f"[wavelet_signals] Skipping '{ticker}': decomposition produced no output dates — no cache written this pass.")
continue
save_wavelet_decomposition_cache(ticker, run_id, method, wavelet, num_levels, lookback, decomposed)
price_at_signal = decomposed["original"][-1]
@@ -399,8 +414,9 @@ def scan_watchlist_wavelet_signals(run_id: Optional[str] = None) -> List[Dict]:
"ridge_period_days": ridge_series[-1],
"params_json": json.dumps(ridge_params) if ridge_params else None,
})
except Exception:
continue # one bad ticker must not abort the whole scan
except Exception as e:
logger.warning(f"[wavelet_signals] Skipping '{ticker}': {e}") # one bad ticker must not abort the whole scan
continue
return results