Spaces:
Running
Running
Sync from GitHub (tests passed)
Browse files- app/heatmap.py +4 -1
- app/instruments.py +10 -0
- app/main.py +50 -53
- app/settings.py +12 -1
- deep_learning/inference/predictor.py +7 -13
app/heatmap.py
CHANGED
|
@@ -203,6 +203,9 @@ def refresh_market_heatmap() -> None:
|
|
| 203 |
the heatmap cache. Uses project taxonomy (not Yahoo sector/industry).
|
| 204 |
"""
|
| 205 |
from app.db import SessionLocal
|
|
|
|
|
|
|
|
|
|
| 206 |
|
| 207 |
with SessionLocal() as session:
|
| 208 |
cache: Optional[HeatmapCache] = session.query(HeatmapCache).first()
|
|
@@ -292,7 +295,7 @@ def refresh_market_heatmap() -> None:
|
|
| 292 |
now = _utcnow()
|
| 293 |
cache.payload_json = root
|
| 294 |
cache.cached_at = now
|
| 295 |
-
cache.expires_at = now + timedelta(
|
| 296 |
cache.refresh_started_at = None
|
| 297 |
cache.refresh_error = None
|
| 298 |
session.commit()
|
|
|
|
| 203 |
the heatmap cache. Uses project taxonomy (not Yahoo sector/industry).
|
| 204 |
"""
|
| 205 |
from app.db import SessionLocal
|
| 206 |
+
from app.settings import get_settings
|
| 207 |
+
|
| 208 |
+
settings = get_settings()
|
| 209 |
|
| 210 |
with SessionLocal() as session:
|
| 211 |
cache: Optional[HeatmapCache] = session.query(HeatmapCache).first()
|
|
|
|
| 295 |
now = _utcnow()
|
| 296 |
cache.payload_json = root
|
| 297 |
cache.cached_at = now
|
| 298 |
+
cache.expires_at = now + timedelta(seconds=settings.heatmap_cache_ttl_seconds)
|
| 299 |
cache.refresh_started_at = None
|
| 300 |
cache.refresh_error = None
|
| 301 |
session.commit()
|
app/instruments.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Canonical instrument identifiers used across API, training and UI metadata."""
|
| 2 |
+
|
| 3 |
+
TARGET_SYMBOL = "HG=F"
|
| 4 |
+
TARGET_DISPLAY_NAME = "COMEX Copper Futures"
|
| 5 |
+
TARGET_PROVIDER = "yfinance"
|
| 6 |
+
TARGET_TRADINGVIEW_SYMBOL = "COMEX:HG1!"
|
| 7 |
+
|
| 8 |
+
SPOT_COPPER_SYMBOLS = {"XCU/USD", "XCUUSD", "XCU=X"}
|
| 9 |
+
SPOT_COPPER_DISPLAY_NAME = "Copper spot (XCU/USD)"
|
| 10 |
+
|
app/main.py
CHANGED
|
@@ -25,6 +25,7 @@ from app.db import init_db, SessionLocal, get_db_type
|
|
| 25 |
from app.models import NewsArticle, PriceBar, DailySentiment, DailySentimentV2, AnalysisSnapshot, NewsSentimentV2, NewsProcessed, NewsRaw
|
| 26 |
from app.settings import get_settings
|
| 27 |
from app.lock import is_pipeline_locked
|
|
|
|
| 28 |
# NOTE: Faz 1 - API is snapshot-only, no report generation
|
| 29 |
# generate_analysis_report and save_analysis_snapshot are now worker-only
|
| 30 |
from app.schemas import (
|
|
@@ -119,7 +120,7 @@ app.add_middleware(
|
|
| 119 |
description="Returns the latest cached analysis snapshot. No live computation - all heavy work is done by the worker."
|
| 120 |
)
|
| 121 |
async def get_analysis(
|
| 122 |
-
symbol: str = Query(default=
|
| 123 |
):
|
| 124 |
"""
|
| 125 |
Get current analysis report.
|
|
@@ -251,7 +252,7 @@ async def get_analysis(
|
|
| 251 |
description="Returns historical data for charting, including prices and sentiment."
|
| 252 |
)
|
| 253 |
async def get_history(
|
| 254 |
-
symbol: str = Query(default=
|
| 255 |
days: int = Query(default=180, ge=7, le=730, description="Number of days of history")
|
| 256 |
):
|
| 257 |
"""
|
|
@@ -445,7 +446,7 @@ async def health_check():
|
|
| 445 |
# --- Latest persisted TFT snapshot ------------------------------
|
| 446 |
latest_tft = (
|
| 447 |
session.query(TFTPredictionSnapshot)
|
| 448 |
-
.filter(TFTPredictionSnapshot.symbol ==
|
| 449 |
.order_by(TFTPredictionSnapshot.generated_at.desc())
|
| 450 |
.first()
|
| 451 |
)
|
|
@@ -456,7 +457,7 @@ async def health_check():
|
|
| 456 |
# --- Latest TFT training timestamp ------------------------------
|
| 457 |
latest_tft_model = (
|
| 458 |
session.query(TFTModelMetadata)
|
| 459 |
-
.filter(TFTModelMetadata.symbol ==
|
| 460 |
.order_by(TFTModelMetadata.trained_at.desc())
|
| 461 |
.first()
|
| 462 |
)
|
|
@@ -464,7 +465,7 @@ async def health_check():
|
|
| 464 |
tft_model_trained_at = _iso(latest_tft_model.trained_at)
|
| 465 |
|
| 466 |
# --- PriceBar freshness -----------------------------------------
|
| 467 |
-
target =
|
| 468 |
latest_bar = (
|
| 469 |
session.query(PriceBar.date)
|
| 470 |
.filter(PriceBar.symbol == target)
|
|
@@ -590,17 +591,22 @@ async def get_market_prices():
|
|
| 590 |
|
| 591 |
@app.get(
|
| 592 |
"/api/market-heatmap",
|
| 593 |
-
summary="Get CopperMind universe heatmap
|
| 594 |
description=(
|
| 595 |
"Returns a group->subgroup->symbol treemap payload sourced exclusively from the "
|
| 596 |
"CopperMind project universe (broad_universe.csv). Uses stale-while-revalidate "
|
| 597 |
-
"caching
|
|
|
|
| 598 |
)
|
| 599 |
)
|
| 600 |
async def get_market_heatmap(background_tasks: BackgroundTasks):
|
| 601 |
from app.models import HeatmapCache
|
| 602 |
from app.heatmap import refresh_market_heatmap
|
| 603 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 604 |
# Stuck refresh safety: if a refresh has been "in progress" for longer than
|
| 605 |
# this, assume the worker crashed and allow a fresh background refresh to
|
| 606 |
# be kicked off. yfinance batch fetch for the full universe finishes in
|
|
@@ -640,7 +646,11 @@ async def get_market_heatmap(background_tasks: BackgroundTasks):
|
|
| 640 |
"refresh_in_progress": True,
|
| 641 |
"last_updated_at": None,
|
| 642 |
"next_refresh_at": None,
|
| 643 |
-
"source_delay_minutes":
|
|
|
|
|
|
|
|
|
|
|
|
|
| 644 |
"payload_count": 0,
|
| 645 |
"refresh_error": cache.refresh_error if cache else None,
|
| 646 |
"cache_state": "empty",
|
|
@@ -688,7 +698,11 @@ async def get_market_heatmap(background_tasks: BackgroundTasks):
|
|
| 688 |
"refresh_in_progress": refresh_in_progress,
|
| 689 |
"last_updated_at": cache.cached_at.isoformat() if cache.cached_at else None,
|
| 690 |
"next_refresh_at": cache.expires_at.isoformat() if cache.expires_at else None,
|
| 691 |
-
"source_delay_minutes":
|
|
|
|
|
|
|
|
|
|
|
|
|
| 692 |
"payload_count": payload_count,
|
| 693 |
"refresh_error": cache.refresh_error,
|
| 694 |
"cache_state": cache_state,
|
|
@@ -704,50 +718,33 @@ async def get_market_heatmap(background_tasks: BackgroundTasks):
|
|
| 704 |
|
| 705 |
@app.get(
|
| 706 |
"/api/live-price",
|
| 707 |
-
summary="Get
|
| 708 |
-
description=
|
|
|
|
|
|
|
|
|
|
| 709 |
)
|
| 710 |
-
async def get_live_price(
|
|
|
|
|
|
|
| 711 |
"""
|
| 712 |
-
Get
|
| 713 |
-
|
| 714 |
-
|
|
|
|
| 715 |
"""
|
| 716 |
-
import httpx
|
| 717 |
-
|
| 718 |
-
settings = get_settings()
|
| 719 |
-
|
| 720 |
-
if not settings.twelvedata_api_key:
|
| 721 |
-
logger.warning("Twelve Data API key not configured")
|
| 722 |
-
return {"price": None, "error": "API key not configured"}
|
| 723 |
-
|
| 724 |
try:
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
if response.status_code == 200:
|
| 735 |
-
data = response.json()
|
| 736 |
-
price = data.get("price")
|
| 737 |
-
if price:
|
| 738 |
-
return {
|
| 739 |
-
"symbol": "XCU/USD",
|
| 740 |
-
"price": round(float(price), 4),
|
| 741 |
-
"error": None,
|
| 742 |
-
}
|
| 743 |
-
else:
|
| 744 |
-
return {"price": None, "error": data.get("message", "No price data")}
|
| 745 |
-
else:
|
| 746 |
-
return {"price": None, "error": f"API error: {response.status_code}"}
|
| 747 |
-
|
| 748 |
except Exception as e:
|
| 749 |
-
|
| 750 |
-
logger.error(f"Twelve Data API error: {mask_api_key(str(e))}")
|
| 751 |
return {"price": None, "error": "API error"}
|
| 752 |
|
| 753 |
|
|
@@ -840,7 +837,7 @@ async def websocket_live_price(websocket: WebSocket):
|
|
| 840 |
description="Returns the AI-generated analysis stored after pipeline completion."
|
| 841 |
)
|
| 842 |
async def get_commentary(
|
| 843 |
-
symbol: str = Query(default=
|
| 844 |
):
|
| 845 |
"""
|
| 846 |
Get AI commentary for the specified symbol.
|
|
@@ -895,7 +892,7 @@ _TFT_CACHE_TTL_S = 300 # 5 minutes
|
|
| 895 |
},
|
| 896 |
)
|
| 897 |
async def get_tft_analysis(
|
| 898 |
-
symbol: str =
|
| 899 |
source: str = "snapshot",
|
| 900 |
):
|
| 901 |
"""
|
|
@@ -1124,7 +1121,7 @@ async def trigger_pipeline(
|
|
| 1124 |
description="Combines XGBoost and TFT-ASRO signals into a directional consensus."
|
| 1125 |
)
|
| 1126 |
async def get_consensus(
|
| 1127 |
-
symbol: str = Query(default=
|
| 1128 |
):
|
| 1129 |
from deep_learning.inference.predictor import ensemble_directional_vote, generate_tft_analysis
|
| 1130 |
|
|
@@ -1166,7 +1163,7 @@ async def get_consensus(
|
|
| 1166 |
description="Returns training metrics, quality gate results, and feature importance."
|
| 1167 |
)
|
| 1168 |
async def get_tft_summary(
|
| 1169 |
-
symbol: str = Query(default=
|
| 1170 |
):
|
| 1171 |
from app.models import TFTModelMetadata
|
| 1172 |
from app.quality_gate import evaluate_quality_gate
|
|
@@ -1259,7 +1256,7 @@ async def get_tft_summary(
|
|
| 1259 |
"a 404 error."
|
| 1260 |
),
|
| 1261 |
)
|
| 1262 |
-
async def get_latest_backtest(symbol: str = Query(default=
|
| 1263 |
import pathlib
|
| 1264 |
import json as _json
|
| 1265 |
|
|
|
|
| 25 |
from app.models import NewsArticle, PriceBar, DailySentiment, DailySentimentV2, AnalysisSnapshot, NewsSentimentV2, NewsProcessed, NewsRaw
|
| 26 |
from app.settings import get_settings
|
| 27 |
from app.lock import is_pipeline_locked
|
| 28 |
+
from app.instruments import TARGET_SYMBOL
|
| 29 |
# NOTE: Faz 1 - API is snapshot-only, no report generation
|
| 30 |
# generate_analysis_report and save_analysis_snapshot are now worker-only
|
| 31 |
from app.schemas import (
|
|
|
|
| 120 |
description="Returns the latest cached analysis snapshot. No live computation - all heavy work is done by the worker."
|
| 121 |
)
|
| 122 |
async def get_analysis(
|
| 123 |
+
symbol: str = Query(default=TARGET_SYMBOL, description="Trading symbol")
|
| 124 |
):
|
| 125 |
"""
|
| 126 |
Get current analysis report.
|
|
|
|
| 252 |
description="Returns historical data for charting, including prices and sentiment."
|
| 253 |
)
|
| 254 |
async def get_history(
|
| 255 |
+
symbol: str = Query(default=TARGET_SYMBOL, description="Trading symbol"),
|
| 256 |
days: int = Query(default=180, ge=7, le=730, description="Number of days of history")
|
| 257 |
):
|
| 258 |
"""
|
|
|
|
| 446 |
# --- Latest persisted TFT snapshot ------------------------------
|
| 447 |
latest_tft = (
|
| 448 |
session.query(TFTPredictionSnapshot)
|
| 449 |
+
.filter(TFTPredictionSnapshot.symbol == TARGET_SYMBOL)
|
| 450 |
.order_by(TFTPredictionSnapshot.generated_at.desc())
|
| 451 |
.first()
|
| 452 |
)
|
|
|
|
| 457 |
# --- Latest TFT training timestamp ------------------------------
|
| 458 |
latest_tft_model = (
|
| 459 |
session.query(TFTModelMetadata)
|
| 460 |
+
.filter(TFTModelMetadata.symbol == TARGET_SYMBOL)
|
| 461 |
.order_by(TFTModelMetadata.trained_at.desc())
|
| 462 |
.first()
|
| 463 |
)
|
|
|
|
| 465 |
tft_model_trained_at = _iso(latest_tft_model.trained_at)
|
| 466 |
|
| 467 |
# --- PriceBar freshness -----------------------------------------
|
| 468 |
+
target = TARGET_SYMBOL
|
| 469 |
latest_bar = (
|
| 470 |
session.query(PriceBar.date)
|
| 471 |
.filter(PriceBar.symbol == target)
|
|
|
|
| 591 |
|
| 592 |
@app.get(
|
| 593 |
"/api/market-heatmap",
|
| 594 |
+
summary="Get CopperMind universe heatmap",
|
| 595 |
description=(
|
| 596 |
"Returns a group->subgroup->symbol treemap payload sourced exclusively from the "
|
| 597 |
"CopperMind project universe (broad_universe.csv). Uses stale-while-revalidate "
|
| 598 |
+
"caching so Yahoo/yfinance is not polled on every frontend refresh. No general "
|
| 599 |
+
"market indices are included."
|
| 600 |
)
|
| 601 |
)
|
| 602 |
async def get_market_heatmap(background_tasks: BackgroundTasks):
|
| 603 |
from app.models import HeatmapCache
|
| 604 |
from app.heatmap import refresh_market_heatmap
|
| 605 |
|
| 606 |
+
settings = get_settings()
|
| 607 |
+
source_delay_seconds = int(settings.heatmap_cache_ttl_seconds)
|
| 608 |
+
source_delay_minutes = max(1, round(source_delay_seconds / 60))
|
| 609 |
+
|
| 610 |
# Stuck refresh safety: if a refresh has been "in progress" for longer than
|
| 611 |
# this, assume the worker crashed and allow a fresh background refresh to
|
| 612 |
# be kicked off. yfinance batch fetch for the full universe finishes in
|
|
|
|
| 646 |
"refresh_in_progress": True,
|
| 647 |
"last_updated_at": None,
|
| 648 |
"next_refresh_at": None,
|
| 649 |
+
"source_delay_minutes": source_delay_minutes,
|
| 650 |
+
"source_delay_seconds": source_delay_seconds,
|
| 651 |
+
"frontend_poll_seconds": settings.heatmap_frontend_poll_seconds,
|
| 652 |
+
"provider": settings.heatmap_provider,
|
| 653 |
+
"live_update_mode": "snapshot_swr",
|
| 654 |
"payload_count": 0,
|
| 655 |
"refresh_error": cache.refresh_error if cache else None,
|
| 656 |
"cache_state": "empty",
|
|
|
|
| 698 |
"refresh_in_progress": refresh_in_progress,
|
| 699 |
"last_updated_at": cache.cached_at.isoformat() if cache.cached_at else None,
|
| 700 |
"next_refresh_at": cache.expires_at.isoformat() if cache.expires_at else None,
|
| 701 |
+
"source_delay_minutes": source_delay_minutes,
|
| 702 |
+
"source_delay_seconds": source_delay_seconds,
|
| 703 |
+
"frontend_poll_seconds": settings.heatmap_frontend_poll_seconds,
|
| 704 |
+
"provider": settings.heatmap_provider,
|
| 705 |
+
"live_update_mode": "snapshot_swr",
|
| 706 |
"payload_count": payload_count,
|
| 707 |
"refresh_error": cache.refresh_error,
|
| 708 |
"cache_state": cache_state,
|
|
|
|
| 718 |
|
| 719 |
@app.get(
|
| 720 |
"/api/live-price",
|
| 721 |
+
summary="Get canonical copper futures price",
|
| 722 |
+
description=(
|
| 723 |
+
"Returns the canonical CopperMind target price. The project standard is "
|
| 724 |
+
"COMEX copper futures via HG=F; no spot XCU/USD substitution is applied."
|
| 725 |
+
)
|
| 726 |
)
|
| 727 |
+
async def get_live_price(
|
| 728 |
+
symbol: str = Query(default=TARGET_SYMBOL, description="Canonical CopperMind symbol")
|
| 729 |
+
):
|
| 730 |
"""
|
| 731 |
+
Get the current canonical target price.
|
| 732 |
+
|
| 733 |
+
The helper enforces exact-instrument lookup: HG=F uses yfinance first and
|
| 734 |
+
falls back to the latest DB close. Spot XCU/USD is no longer used by the UI.
|
| 735 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 736 |
try:
|
| 737 |
+
from app.inference import get_current_price
|
| 738 |
+
|
| 739 |
+
with SessionLocal() as session:
|
| 740 |
+
price = get_current_price(session, symbol)
|
| 741 |
+
return {
|
| 742 |
+
"symbol": symbol,
|
| 743 |
+
"price": round(float(price), 4) if price is not None else None,
|
| 744 |
+
"error": None if price is not None else "No price data",
|
| 745 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 746 |
except Exception as e:
|
| 747 |
+
logger.error("Canonical live price error: %s", e)
|
|
|
|
| 748 |
return {"price": None, "error": "API error"}
|
| 749 |
|
| 750 |
|
|
|
|
| 837 |
description="Returns the AI-generated analysis stored after pipeline completion."
|
| 838 |
)
|
| 839 |
async def get_commentary(
|
| 840 |
+
symbol: str = Query(default=TARGET_SYMBOL, description="Symbol to get commentary for")
|
| 841 |
):
|
| 842 |
"""
|
| 843 |
Get AI commentary for the specified symbol.
|
|
|
|
| 892 |
},
|
| 893 |
)
|
| 894 |
async def get_tft_analysis(
|
| 895 |
+
symbol: str = TARGET_SYMBOL,
|
| 896 |
source: str = "snapshot",
|
| 897 |
):
|
| 898 |
"""
|
|
|
|
| 1121 |
description="Combines XGBoost and TFT-ASRO signals into a directional consensus."
|
| 1122 |
)
|
| 1123 |
async def get_consensus(
|
| 1124 |
+
symbol: str = Query(default=TARGET_SYMBOL, description="Trading symbol")
|
| 1125 |
):
|
| 1126 |
from deep_learning.inference.predictor import ensemble_directional_vote, generate_tft_analysis
|
| 1127 |
|
|
|
|
| 1163 |
description="Returns training metrics, quality gate results, and feature importance."
|
| 1164 |
)
|
| 1165 |
async def get_tft_summary(
|
| 1166 |
+
symbol: str = Query(default=TARGET_SYMBOL, description="Target symbol")
|
| 1167 |
):
|
| 1168 |
from app.models import TFTModelMetadata
|
| 1169 |
from app.quality_gate import evaluate_quality_gate
|
|
|
|
| 1256 |
"a 404 error."
|
| 1257 |
),
|
| 1258 |
)
|
| 1259 |
+
async def get_latest_backtest(symbol: str = Query(default=TARGET_SYMBOL, description="Target symbol")):
|
| 1260 |
import pathlib
|
| 1261 |
import json as _json
|
| 1262 |
|
app/settings.py
CHANGED
|
@@ -11,6 +11,8 @@ from pathlib import Path
|
|
| 11 |
from typing import Optional
|
| 12 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 13 |
|
|
|
|
|
|
|
| 14 |
logger = logging.getLogger(__name__)
|
| 15 |
|
| 16 |
|
|
@@ -107,6 +109,15 @@ class Settings(BaseSettings):
|
|
| 107 |
# Twelve Data (Live Price)
|
| 108 |
twelvedata_api_key: Optional[str] = None
|
| 109 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
# Inference sentiment adjustment (aggressive but capped)
|
| 111 |
inference_sentiment_multiplier_max: float = 2.0
|
| 112 |
inference_sentiment_multiplier_min: float = 0.5
|
|
@@ -207,7 +218,7 @@ class Settings(BaseSettings):
|
|
| 207 |
def target_symbol(self) -> str:
|
| 208 |
"""Primary symbol for predictions (first in list)."""
|
| 209 |
symbols = self.symbols_list
|
| 210 |
-
return symbols[0] if symbols else
|
| 211 |
|
| 212 |
@staticmethod
|
| 213 |
def _first_non_empty(*values: Optional[str]) -> Optional[str]:
|
|
|
|
| 11 |
from typing import Optional
|
| 12 |
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 13 |
|
| 14 |
+
from app.instruments import TARGET_SYMBOL
|
| 15 |
+
|
| 16 |
logger = logging.getLogger(__name__)
|
| 17 |
|
| 18 |
|
|
|
|
| 109 |
# Twelve Data (Live Price)
|
| 110 |
twelvedata_api_key: Optional[str] = None
|
| 111 |
|
| 112 |
+
# Heatmap quote strategy
|
| 113 |
+
# Yahoo/yfinance is kept as a snapshot source. Polling the full project
|
| 114 |
+
# universe every 2-3 seconds would trigger rate limits and waste CPU, so
|
| 115 |
+
# the frontend can poll the API quickly while this server-side cache
|
| 116 |
+
# controls actual provider refreshes.
|
| 117 |
+
heatmap_cache_ttl_seconds: int = 900
|
| 118 |
+
heatmap_frontend_poll_seconds: int = 3
|
| 119 |
+
heatmap_provider: str = "yfinance_snapshot"
|
| 120 |
+
|
| 121 |
# Inference sentiment adjustment (aggressive but capped)
|
| 122 |
inference_sentiment_multiplier_max: float = 2.0
|
| 123 |
inference_sentiment_multiplier_min: float = 0.5
|
|
|
|
| 218 |
def target_symbol(self) -> str:
|
| 219 |
"""Primary symbol for predictions (first in list)."""
|
| 220 |
symbols = self.symbols_list
|
| 221 |
+
return symbols[0] if symbols else TARGET_SYMBOL
|
| 222 |
|
| 223 |
@staticmethod
|
| 224 |
def _first_non_empty(*values: Optional[str]) -> Optional[str]:
|
deep_learning/inference/predictor.py
CHANGED
|
@@ -44,6 +44,7 @@ warnings.filterwarnings(
|
|
| 44 |
)
|
| 45 |
|
| 46 |
from deep_learning.config import TFTASROConfig, get_tft_config
|
|
|
|
| 47 |
|
| 48 |
logger = logging.getLogger(__name__)
|
| 49 |
|
|
@@ -114,7 +115,7 @@ class TFTPredictor:
|
|
| 114 |
self._pca = load_pca(pca_path)
|
| 115 |
return self._pca
|
| 116 |
|
| 117 |
-
def predict(self, session, symbol: str =
|
| 118 |
"""
|
| 119 |
Generate a TFT-ASRO prediction for the given symbol.
|
| 120 |
|
|
@@ -258,22 +259,15 @@ class TFTPredictor:
|
|
| 258 |
def _describe_instrument(symbol: str) -> Dict[str, str]:
|
| 259 |
"""Return a structured label for the traded instrument."""
|
| 260 |
mapping = {
|
| 261 |
-
|
| 262 |
-
"symbol":
|
| 263 |
"kind": "futures",
|
| 264 |
-
"name":
|
| 265 |
"note": (
|
| 266 |
"Continuous front-month contract (CME Group). Prices "
|
| 267 |
-
"
|
| 268 |
-
"contract roll, typically by 1–3 USD/ton."
|
| 269 |
),
|
| 270 |
},
|
| 271 |
-
"XCU=X": {
|
| 272 |
-
"symbol": "XCU=X",
|
| 273 |
-
"kind": "spot",
|
| 274 |
-
"name": "Copper spot (XCU/USD)",
|
| 275 |
-
"note": "LBMA-style reference spot price; differs from futures by basis.",
|
| 276 |
-
},
|
| 277 |
}
|
| 278 |
return mapping.get(
|
| 279 |
symbol,
|
|
@@ -468,7 +462,7 @@ def get_tft_predictor(cfg: Optional[TFTASROConfig] = None) -> TFTPredictor:
|
|
| 468 |
return _predictor
|
| 469 |
|
| 470 |
|
| 471 |
-
def generate_tft_analysis(session, symbol: str =
|
| 472 |
"""
|
| 473 |
High-level API for generating a TFT-ASRO analysis report.
|
| 474 |
|
|
|
|
| 44 |
)
|
| 45 |
|
| 46 |
from deep_learning.config import TFTASROConfig, get_tft_config
|
| 47 |
+
from app.instruments import TARGET_DISPLAY_NAME, TARGET_SYMBOL
|
| 48 |
|
| 49 |
logger = logging.getLogger(__name__)
|
| 50 |
|
|
|
|
| 115 |
self._pca = load_pca(pca_path)
|
| 116 |
return self._pca
|
| 117 |
|
| 118 |
+
def predict(self, session, symbol: str = TARGET_SYMBOL) -> Dict[str, Any]:
|
| 119 |
"""
|
| 120 |
Generate a TFT-ASRO prediction for the given symbol.
|
| 121 |
|
|
|
|
| 259 |
def _describe_instrument(symbol: str) -> Dict[str, str]:
|
| 260 |
"""Return a structured label for the traded instrument."""
|
| 261 |
mapping = {
|
| 262 |
+
TARGET_SYMBOL: {
|
| 263 |
+
"symbol": TARGET_SYMBOL,
|
| 264 |
"kind": "futures",
|
| 265 |
+
"name": TARGET_DISPLAY_NAME,
|
| 266 |
"note": (
|
| 267 |
"Continuous front-month contract (CME Group). Prices "
|
| 268 |
+
"are used consistently across training, inference and UI."
|
|
|
|
| 269 |
),
|
| 270 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
}
|
| 272 |
return mapping.get(
|
| 273 |
symbol,
|
|
|
|
| 462 |
return _predictor
|
| 463 |
|
| 464 |
|
| 465 |
+
def generate_tft_analysis(session, symbol: str = TARGET_SYMBOL) -> Dict[str, Any]:
|
| 466 |
"""
|
| 467 |
High-level API for generating a TFT-ASRO analysis report.
|
| 468 |
|