feat: instrument analysis
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user