| |
|
|
| import pandas as pd |
| import numpy as np |
| import os |
| import sys |
| from datetime import datetime, timedelta |
| from stable_baselines3 import SAC |
|
|
| |
| try: |
| |
| sys.path.append(os.getcwd()) |
| except: |
| pass |
|
|
| from fetch_market_data import fetch_market_data, ASSETS, FRED_IDS |
| from llm_analysis_rag import analyze_agent_decision |
|
|
| |
| MODEL_PATH = "checkpoints/sac_portfolio_model.zip" |
| WINDOW_SIZE = 30 |
| MACRO_COLS = list(FRED_IDS.values()) |
|
|
| def get_latest_data_window(window_size=30): |
| """ |
| Fetches live data and returns the last 'window_size' rows. |
| """ |
| print("--- π Fetching Real-Time Data for Prediction ---") |
|
|
| |
| lookback_days = window_size + 100 |
| end_date = datetime.now().strftime('%Y-%m-%d') |
| start_date = (datetime.now() - timedelta(days=lookback_days)).strftime('%Y-%m-%d') |
|
|
| |
| df = fetch_market_data(start_date, end_date, filename=None) |
|
|
| if df is None or len(df) < window_size: |
| print(f"β Not enough data fetched. Got {len(df) if df is not None else 0} rows, needed {window_size}.") |
| return None |
|
|
| |
| return df.iloc[-window_size:].copy() |
|
|
| def prepare_observation(data_window): |
| """ |
| Normalizes data: Window / First_Row_of_Window |
| """ |
| |
| price_data = data_window[ASSETS].values |
| macro_data = data_window[MACRO_COLS].values |
|
|
| |
| norm_prices = price_data / (price_data[0] + 1e-8) |
| norm_macro = macro_data / (macro_data[0] + 1e-8) |
|
|
| |
| obs = np.concatenate([norm_prices, norm_macro], axis=1) |
| return obs.flatten().astype(np.float32) |
|
|
| def get_allocations(action): |
| """Applies Softmax to convert raw action to weights""" |
| action = np.asarray(action).flatten() |
| exp_action = np.exp(action) |
| return exp_action / np.sum(exp_action) |
|
|
| def main(): |
| print(f"π Prediction Job: {datetime.now().strftime('%Y-%m-%d')}") |
|
|
| |
| data_window = get_latest_data_window(WINDOW_SIZE) |
| if data_window is None: return |
|
|
| |
| obs = prepare_observation(data_window) |
|
|
| |
| if not os.path.exists(MODEL_PATH): |
| print(f"β Model not found at {MODEL_PATH}") |
| return |
|
|
| print(f"Loading MLP SAC model...") |
| model = SAC.load(MODEL_PATH) |
|
|
| |
| action, _ = model.predict(obs, deterministic=True) |
| weights = get_allocations(action) |
|
|
| |
| allocations = {} |
| for i, asset in enumerate(ASSETS): |
| allocations[asset] = float(weights[i]) |
| allocations['Cash'] = float(weights[-1]) |
|
|
| |
| print("\n" + "="*40) |
| print(f"π€ SAC MLP MODEL RECOMMENDATION") |
| print("="*40) |
| for asset, weight in allocations.items(): |
| print(f"{asset:<10} : {weight:6.2%}") |
| print("="*40) |
|
|
| |
| print("\nπ§ Running AI Risk Analysis...") |
|
|
| |
| analysis = analyze_agent_decision(data_window, allocations) |
|
|
| if isinstance(analysis, dict): |
| print(f"\nStrategy: {analysis.get('strategy_summary')}") |
| print(f"Risk Level: {analysis.get('risk_level')}") |
| print(f"Justification: {analysis.get('justification')}") |
|
|
| if analysis.get('risk_level') == 'High': |
| print("\nβ BLOCKING TRADE: High Risk detected by AI Guardrail.") |
| else: |
| print("\nβ
TRADE APPROVED.") |
| else: |
| print(analysis) |
|
|
| if __name__ == "__main__": |
| main() |