XAU/XAG: 3-TF analysis (M15+H1+H4 resampled) via per-symbol override
- New _PER_SYMBOL_TFS map in signal_aggregator routes gold/silver to M15+H1+H4 confirmation; forex pairs untouched. - H4 for XAU/XAG is resampled from yfinance H1 bars (yfinance has no native 4h candle). New _resample_h1_to_h4 helper. - Batch prefetch unions per-symbol TFs so future per-pair overrides work without extra plumbing. XAU/XAG still excluded from Twelve Data batch — H4 resample triggers a single yfinance H1 fetch. - Telegram SCAN_TIMEFRAMES restored to [M15, H1] so /scan emits both M15-primary and H1-primary cards. Shared cache means no extra credits. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+25
-3
@@ -169,13 +169,24 @@ class ForexDataProvider:
|
||||
return cached
|
||||
|
||||
# Symbols not on Twelve Data (gold, silver) always use yfinance.
|
||||
# D1 is intentionally skipped for these — yfinance daily for futures
|
||||
# is unreliable and the engine drops missing confirmation TFs cleanly.
|
||||
# D1 is skipped (yfinance daily for futures is unreliable).
|
||||
# H4 is resampled from H1 since yfinance has no native 4h candle.
|
||||
if symbol in _YF_FALLBACK_MAP:
|
||||
if timeframe == Timeframe.D1:
|
||||
raise DataUnavailableError(
|
||||
f"Daily chart skipped for {symbol} — gold/silver use M15/H1 only."
|
||||
f"Daily chart skipped for {symbol} — gold/silver use M15/H1/H4 only."
|
||||
)
|
||||
if timeframe == Timeframe.H4:
|
||||
h1_df = self._fetch_yfinance_fallback(symbol, Timeframe.H1)
|
||||
if h1_df is None:
|
||||
raise DataUnavailableError(
|
||||
f"Yahoo Finance unavailable for {symbol} (H4 resample source H1)."
|
||||
)
|
||||
df = self._resample_h1_to_h4(h1_df)
|
||||
df = self._validate_and_clean(df, symbol, timeframe)
|
||||
self._cache[cache_key] = df
|
||||
self._cache_timestamps[cache_key] = now
|
||||
return df
|
||||
yf_df = self._fetch_yfinance_fallback(symbol, timeframe)
|
||||
if yf_df is None:
|
||||
raise DataUnavailableError(
|
||||
@@ -277,6 +288,17 @@ class ForexDataProvider:
|
||||
}
|
||||
return fallback_spreads.get(symbol, 0.00020)
|
||||
|
||||
@staticmethod
|
||||
def _resample_h1_to_h4(h1_df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""Resample 1-hour OHLCV to 4-hour bars (yfinance has no native 4h)."""
|
||||
return h1_df.resample("4h", label="right", closed="right").agg({
|
||||
"Open": "first",
|
||||
"High": "max",
|
||||
"Low": "min",
|
||||
"Close": "last",
|
||||
"Volume": "sum",
|
||||
}).dropna()
|
||||
|
||||
def get_pair_info(self, symbol: str) -> Dict:
|
||||
"""Return display name and pip value for a pair."""
|
||||
display = SYMBOL_DISPLAY.get(symbol, symbol)
|
||||
|
||||
Reference in New Issue
Block a user