File size: 7,166 Bytes
0125724
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
"""
TradeSense AI — Master Quantitative Research Engine Orchestrator
Executes the end-to-end reproducible research workflow for any ticker and cutoff date.
"""

from datetime import date
from typing import Optional, Dict, Any, Tuple
import pandas as pd
import numpy as np

from backend.app.schemas.contracts import (
    StructuredEvidence,
    PredictionJournalEntry,
    DatasetMetadata,
    FeatureMetadata,
    SignalType
)
from quant.data.fetcher import CanonicalDataEngine
from quant.features.feature_store import FeatureStoreEngine
from quant.backtesting.engine import WalkForwardEngine
from quant.risk.risk_engine import RiskEngine
from quant.evaluation.evidence_builder import EvidenceBuilder
from quant.journal.journal_engine import PredictionJournalEngine
from quant.copilot.explainer import GroundedCopilotEngine


class TradeSenseMasterEngine:
    """
    Main entry point for executing reproducible quantitative research runs.
    """

    def __init__(
        self, 
        polygon_api_key: Optional[str] = None,
        alpha_vantage_api_key: Optional[str] = None,
        fmp_api_key: Optional[str] = None,
        twelve_data_api_key: Optional[str] = None
    ):
        self.data_engine = CanonicalDataEngine(
            polygon_api_key=polygon_api_key,
            alpha_vantage_api_key=alpha_vantage_api_key,
            fmp_api_key=fmp_api_key,
            twelve_data_api_key=twelve_data_api_key
        )
        self.journal_engine = PredictionJournalEngine()
        self.last_df_clean: Dict[str, pd.DataFrame] = {}

    def get_recent_history(self, ticker: str, n_bars: int = 45) -> Dict[str, Any]:
        """Returns recent clean historical OHLCV series for continuous frontend charts."""
        ticker = ticker.upper().strip()
        df = self.last_df_clean.get(ticker)
        if df is None or df.empty:
            return {"sessions": [], "closes": [], "opens": [], "highs": [], "lows": [], "volumes": []}
        tail_df = df.tail(n_bars)
        return {
            "sessions": [d.strftime('%Y-%m-%d') for d in tail_df["session"]],
            "closes": [round(float(c), 2) for c in tail_df["Close"]],
            "opens": [round(float(o), 2) for o in tail_df["Open"]],
            "highs": [round(float(h), 2) for h in tail_df["High"]],
            "lows": [round(float(l), 2) for l in tail_df["Low"]],
            "volumes": [int(v) for v in tail_df["Volume"]]
        }

    def run_research_pipeline(
        self,
        ticker: str,
        cutoff_session: Optional[date] = None,
        horizon_sessions: int = 21
    ) -> Tuple[StructuredEvidence, PredictionJournalEntry, str, DatasetMetadata]:
        """
        Executes the complete end-to-end quantitative pipeline strictly at cutoff session T:
        1. Fetch & clean canonical dataset
        2. Build supervised feature windows (zero lookahead)
        3. Run walk-forward simulation vs. Naive baseline with slippage
        4. Generate probabilistic quantile forecast distribution
        5. Compute ATR risk and trade target geometry
        6. Emit schema-compliant StructuredEvidence JSON object
        7. Synthesize grounded AI research note
        8. Log immutable PredictionJournal entry
        """
        ticker = ticker.upper().strip()
        
        # Step 1: Ingest and clean canonical historical data strictly <= cutoff T
        df_clean, dataset_meta = self.data_engine.build_canonical_dataset(
            ticker=ticker, years=10, cutoff_session=cutoff_session
        )
        self.last_df_clean[ticker] = df_clean
        
        # Step 2: Build supervised feature windows
        X, Y, sessions, feature_cols, target_scaler = FeatureStoreEngine.build_supervised_windows(
            df_clean=df_clean, horizon_sessions=horizon_sessions, lookback_sessions=120
        )
        
        # Step 3: Run Walk-Forward evaluation vs Naive baseline with frictions
        feat_df = FeatureStoreEngine.calculate_technical_features(df_clean).dropna().reset_index(drop=True)
        close_prices = feat_df["Close"].values[120 - 1 : len(feat_df) - horizon_sessions]
        wf_engine = WalkForwardEngine(n_splits=4, test_window_size=63, slippage_bps=5.0)
        candidate_metrics, naive_metrics, statistical_diagnostics = wf_engine.evaluate_model(
            X=X, Y=Y, close_prices=close_prices, sessions=sessions, horizon_sessions=horizon_sessions
        )
        
        # Step 4: Extract live inference features at cutoff T
        X_live, live_cutoff, current_price, _, feature_meta = FeatureStoreEngine.get_latest_inference_features(
            df_clean=df_clean, lookback_sessions=120
        )
        
        # Train candidate model with split conformal calibration on historical data <= T
        from quant.models.conformal_predictor import SplitConformalReturnPredictor
        cal_size = max(10, int(len(X) * 0.20))
        X_tr, X_cal = X[:-cal_size], X[-cal_size:]
        p_tr, p_cal = close_prices[:-cal_size], close_prices[-cal_size:]
        y_tr_rets = (Y[:-cal_size] - p_tr) / np.where(p_tr > 0, p_tr, 1.0)
        y_cal_rets = (Y[-cal_size:] - p_cal) / np.where(p_cal > 0, p_cal, 1.0)

        active_model = SplitConformalReturnPredictor(n_estimators=120, max_depth=4, random_state=42)
        active_model.fit_and_calibrate(X_tr, y_tr_rets, X_cal, y_cal_rets)

        conf_res = active_model.predict_distribution(X_live, current_prices=np.array([current_price]))
        expected_price = float(conf_res.point_forecast_price[0])
        interval_80 = (float(conf_res.interval_80_price[0][0]), float(conf_res.interval_80_price[1][0]))
        interval_95 = (float(conf_res.interval_95_price[0][0]), float(conf_res.interval_95_price[1][0]))
        
        # Step 5: Compute Risk Profile & Volatility Sizing
        risk_profile = RiskEngine.compute_risk_profile(
            df_clean=df_clean, expected_price=expected_price
        )
        
        # Step 6: Determine Active Regime & Build Structured Evidence
        market_regime = EvidenceBuilder.determine_regime(df_clean)
        evidence = EvidenceBuilder.evaluate_signal_and_evidence(
            ticker=ticker,
            cutoff_session=live_cutoff,
            current_price=current_price,
            expected_price=expected_price,
            interval_80=interval_80,
            interval_95=interval_95,
            backtest=candidate_metrics,
            risk=risk_profile,
            market_regime=market_regime,
            dataset_version=dataset_meta.dataset_version,
            feature_version=feature_meta.feature_version,
            dataset_lineage_hash=dataset_meta.sha256_hash,
            horizon_sessions=horizon_sessions,
            data_quality_report=dataset_meta.quality_report,
            statistical_diagnostics=statistical_diagnostics,
        )
        
        # Step 7: Synthesize Grounded AI Research Note
        research_note = GroundedCopilotEngine.generate_research_note(evidence)
        
        # Step 8: Log to Prediction Journal
        journal_entry = self.journal_engine.log_issued_forecast(evidence, current_price=current_price)
        
        return evidence, journal_entry, research_note, dataset_meta