|
|
|
|
|
|
|
|
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() |