File size: 11,894 Bytes
d57ff61
 
 
 
 
 
 
 
 
6f5186e
d57ff61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4ebffd0
d57ff61
 
 
 
4ebffd0
 
 
 
d57ff61
4ebffd0
 
 
d57ff61
 
 
 
4ebffd0
 
d57ff61
 
 
4ebffd0
 
d57ff61
4ebffd0
d57ff61
4ebffd0
 
 
d57ff61
 
 
 
4ebffd0
 
d57ff61
 
 
4ebffd0
 
d57ff61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6f5186e
d57ff61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# app.py - The Analyst Hub Dashboard for the V16 Trading Agent
import gradio as gr
import pandas as pd
import plotly.express as px
import os
import json
import firebase_admin
from firebase_admin import credentials, db
from datetime import datetime
import numpy as np

# --- Configuration ---
# The Firebase DB reference name must match the one used in your agent (App3.txt)
DB_REF_NAME = 'signals_v2' 

# --- Firebase Connection ---
def initialize_firebase():
    """Initializes the Firebase app, checking for existing initializations."""
    try:
        # Get secrets from environment variables
        sa_key_json_str = os.environ.get('FIRESTORE_SA_KEY')
        db_url = os.environ.get('FIREBASE_DB_URL')

        if not all([sa_key_json_str, db_url]):
            print("ANALYST HUB: Firebase secrets not set. App will run in offline mode.")
            return None, "Missing Secrets"

        sa_key_dict = json.loads(sa_key_json_str)
        cred = credentials.Certificate(sa_key_dict)

        # Check if the app is already initialized to prevent errors on hot-reloading
        if not firebase_admin._apps:
            firebase_admin.initialize_app(cred, {'databaseURL': db_url})
            print("ANALYST HUB: Firebase connection established.")
        
        return db.reference(DB_REF_NAME), "Connected"
    except Exception as e:
        print(f"ANALYST HUB: CRITICAL ERROR - Failed to initialize Firebase: {e}")
        return None, f"Connection Error: {e}"

# --- Data Fetching and Processing ---
def fetch_and_process_data(db_ref):
    """Fetches data from Firebase and processes it into Pandas DataFrames."""
    if db_ref is None:
        # Return empty structures if DB is not connected
        return pd.DataFrame(), pd.DataFrame(), {}

    try:
        data = db_ref.get()
        if not data:
            print("ANALYST HUB: No data found in the database reference.")
            return pd.DataFrame(), pd.DataFrame(), {}

        # Convert the dictionary of signals into a list
        all_signals = [signal for signal in data.values()]
        df = pd.DataFrame(all_signals)

        # --- Data Cleaning and Type Conversion ---
        # Convert timestamps to datetime objects for sorting and plotting
        df['timestamp_entry'] = pd.to_datetime(df['timestamp_entry'])
        df['timestamp_exit'] = pd.to_datetime(df['timestamp_exit'], errors='coerce')
        
        # Convert numeric columns, coercing errors to NaN
        numeric_cols = ['entry_price', 'stop_loss', 'take_profit', 'exit_price', 'pnl', 'reward']
        for col in numeric_cols:
            df[col] = pd.to_numeric(df[col], errors='coerce')
            
        # Format PnL for display
        if 'pnl' in df.columns:
            df['pnl_display'] = df['pnl'].map(lambda x: f"{x:.5f}" if pd.notna(x) else "N/A")

        # Separate into closed and open trades
        closed_trades = df[df['pnl'].notna()].sort_values(by='timestamp_entry').copy()
        open_trades = df[df['pnl'].isna()].sort_values(by='timestamp_entry').copy()

        # --- Calculate Key Performance Indicators (KPIs) ---
        kpis = {}
        if not closed_trades.empty:
            total_pnl = closed_trades['pnl'].sum()
            total_trades = len(closed_trades)
            winning_trades = (closed_trades['pnl'] > 0).sum()
            win_rate = (winning_trades / total_trades) * 100 if total_trades > 0 else 0
            
            kpis = {
                "total_pnl": f"{total_pnl:+.5f}",
                "win_rate": f"{win_rate:.2f}%",
                "total_closed_trades": str(total_trades),
                "average_pnl": f"{closed_trades['pnl'].mean():+.5f}"
            }
            # Calculate cumulative PnL for the equity curve
            closed_trades['equity_curve'] = closed_trades['pnl'].cumsum()
        else:
            kpis = {
                "total_pnl": "0.00000",
                "win_rate": "0.00%",
                "total_closed_trades": "0",
                "average_pnl": "0.00000"
            }
            closed_trades['equity_curve'] = 0


        return closed_trades, open_trades, kpis
    except Exception as e:
        print(f"ANALYST HUB: Error processing data: {e}")
        return pd.DataFrame(), pd.DataFrame(), {}

# --- Plotting Functions ---
def create_equity_curve(df):
    """Creates an equity curve chart using Plotly."""
    if df.empty or 'equity_curve' not in df.columns:
        return px.line(title="Equity Curve (No Data)").update_layout(template="plotly_dark")
        
    fig = px.line(
        df, 
        x='timestamp_entry', 
        y='equity_curve', 
        title="Agent Performance: Equity Curve",
        labels={'timestamp_entry': 'Date', 'equity_curve': 'Cumulative PnL (in pips/points)'}
    )
    fig.update_layout(template="plotly_dark", title_x=0.5, font=dict(color="white"))
    fig.update_traces(line_color='#00BFFF', fill='tozeroy') # Deep sky blue with fill
    return fig

def create_pnl_analytics_charts(df):
    """Creates bar charts for PnL breakdown by strategy and regime with distinct profit/loss colors."""
    if df.empty:
        no_data_fig = px.bar(title="PnL by Strategy (No Data)").update_layout(template="plotly_dark")
        return no_data_fig, no_data_fig

    # Define the discrete colors for profit (green) and loss (red)
    color_map = {'Profit': 'darkgreen', 'Loss': 'darkred'}

    # --- PnL by Strategy Chart ---
    pnl_by_strategy = df.groupby('strategy_chosen')['pnl'].sum().reset_index()
    # Create a new column to categorize as 'Profit' or 'Loss'
    pnl_by_strategy['Outcome'] = np.where(pnl_by_strategy['pnl'] >= 0, 'Profit', 'Loss')
    
    fig_strategy = px.bar(
        pnl_by_strategy,
        x='strategy_chosen',
        y='pnl',
        color='Outcome',            # Color bars based on the 'Outcome' column
        color_discrete_map=color_map, # Apply our specific green/red colors
        title="PnL by Strategy",
        labels={'strategy_chosen': 'Strategy', 'pnl': 'Total PnL'}
    )
    # Clean up layout: dark theme, centered title, no redundant legend
    fig_strategy.update_layout(template="plotly_dark", title_x=0.5, font=dict(color="white"), showlegend=False)

    # --- PnL by Market Regime Chart ---
    pnl_by_regime = df.groupby('market_regime')['pnl'].sum().reset_index()
    # Create the same 'Outcome' column for this chart
    pnl_by_regime['Outcome'] = np.where(pnl_by_regime['pnl'] >= 0, 'Profit', 'Loss')

    fig_regime = px.bar(
        pnl_by_regime,
        x='market_regime',
        y='pnl',
        color='Outcome',            # Color bars based on the 'Outcome' column
        color_discrete_map=color_map, # Apply our specific green/red colors
        title="PnL by Market Regime",
        labels={'market_regime': 'Market Regime', 'pnl': 'Total PnL'}
    )
    # Clean up layout
    fig_regime.update_layout(template="plotly_dark", title_x=0.5, font=dict(color="white"), showlegend=False)

    return fig_strategy, fig_regime

# --- Main Gradio Function ---
def update_dashboard():
    """Main function to refresh all UI components."""
    db_ref, status = initialize_firebase()
    if status != "Connected":
        # Handle connection failure gracefully
        no_data_df = pd.DataFrame()
        kpis = { "total_pnl": "N/A", "win_rate": "N/A", "total_closed_trades": "N/A", "average_pnl": "N/A" }
        no_data_plot = px.line(title=f"Database Connection Failed: {status}").update_layout(template="plotly_dark")
        no_data_bar = px.bar(title=f"Database Connection Failed").update_layout(template="plotly_dark")
        return (kpis["total_pnl"], kpis["win_rate"], kpis["total_closed_trades"], kpis["average_pnl"],
                no_data_plot, no_data_df, no_data_df, no_data_bar, no_data_bar,
                f"DB Status: {status} at {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")

    closed_df, open_df, kpis = fetch_and_process_data(db_ref)
    
    # Create plots
    equity_plot = create_equity_curve(closed_df)
    strategy_plot, regime_plot = create_pnl_analytics_charts(closed_df)
    
    # Prepare dataframes for display
    open_trades_display = open_df[[
        'timestamp_entry', 'action', 'strategy_chosen', 'confidence', 
        'entry_price', 'stop_loss', 'take_profit', 'reasoning_initial'
    ]].copy()
    
    closed_trades_display = closed_df[[
        'timestamp_entry', 'action', 'strategy_chosen', 'outcome_reason', 
        'pnl_display', 'entry_price', 'exit_price', 'market_regime'
    ]].copy()
    
    # Format datetime for better readability
    open_trades_display['timestamp_entry'] = open_trades_display['timestamp_entry'].dt.strftime('%Y-%m-%d %H:%M')
    closed_trades_display['timestamp_entry'] = closed_trades_display['timestamp_entry'].dt.strftime('%Y-%m-%d %H:%M')


    return (kpis["total_pnl"], kpis["win_rate"], kpis["total_closed_trades"], kpis["average_pnl"],
            equity_plot,
            open_trades_display.iloc[::-1], # Show newest first
            closed_trades_display.iloc[::-1], # Show newest first
            strategy_plot,
            regime_plot,
            f"DB Status: {status} | Last Updated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")

# --- Gradio UI Layout ---
with gr.Blocks(theme=gr.themes.Glass(), title="Agent Analyst Hub") as demo:
    gr.Markdown("# πŸ“ˆ V16 Agent Analyst Hub")
    gr.Markdown("Live performance monitoring and analysis of the GuardianAI trading agent. Data is fetched directly from the Firebase Realtime Database.")
    
    with gr.Row():
        db_status_output = gr.Textbox(label="Database Status", interactive=False)
        refresh_btn = gr.Button("πŸ”„ Refresh Data", variant="primary")

    gr.Markdown("##  Key Performance Indicators (KPIs)")
    with gr.Row():
        pnl_output = gr.Textbox(label="Total Net PnL", interactive=False)
        win_rate_output = gr.Textbox(label="Win Rate", interactive=False)
        total_trades_output = gr.Textbox(label="Total Closed Trades", interactive=False)
        avg_pnl_output = gr.Textbox(label="Average PnL / Trade", interactive=False)

    with gr.Tabs():
        with gr.TabItem("πŸ“Š Dashboard"):
            equity_curve_plot = gr.Plot()
        
        with gr.TabItem("πŸ” Performance Analytics"):
            gr.Markdown("### PnL Breakdown Analysis")
            with gr.Row():
                pnl_by_strategy_plot = gr.Plot()
                pnl_by_regime_plot = gr.Plot()

        with gr.TabItem("πŸ“– Open Trades"):
            open_trades_df = gr.DataFrame(headers=[
                'Entry Time', 'Action', 'Strategy', 'Confidence', 
                'Entry Price', 'Stop Loss', 'Take Profit', 'Initial Reasoning'
            ], interactive=False)

        with gr.TabItem("πŸ“š Trade History"):
            closed_trades_df = gr.DataFrame(headers=[
                'Entry Time', 'Action', 'Strategy', 'Outcome', 
                'PnL', 'Entry Price', 'Exit Price', 'Market Regime'
            ], interactive=False)
            
    # --- Component Connections ---
    outputs_list = [
        pnl_output, win_rate_output, total_trades_output, avg_pnl_output,
        equity_curve_plot, open_trades_df, closed_trades_df,
        pnl_by_strategy_plot, pnl_by_regime_plot, db_status_output
    ]
    
    # Refresh data on button click
    refresh_btn.click(fn=update_dashboard, inputs=[], outputs=outputs_list)
    
    # Load data when the app starts
    demo.load(fn=update_dashboard, inputs=[], outputs=outputs_list)

if __name__ == "__main__":
    # Check if secrets are present before launching
    if not all([os.environ.get(k) for k in ['FIRESTORE_SA_KEY', 'FIREBASE_DB_URL']]):
        print("\nWARNING: Firebase secrets are not set as environment variables.")
        print("The dashboard will load but will not be able to connect to the database.")
        print("Please set FIRESTORE_SA_KEY and FIREBASE_DB_URL in your environment.\n")

    demo.launch()